Skip to content

bulletinmybeard/spotisync

Repository files navigation

SpotiSync

CI PyPI version Python Versions Poetry Ruff Type checking: mypy License: MIT

Automatically sync tracks between playlists - from your Liked Songs, your own playlists, or any public playlist on Spotify.

Want to share your liked tracks as a public playlist? Create a backup that never loses tracks? Mirror someone else's playlist to your own? SpotiSync handles it all.

What It Does

  • Sync your Liked Songs to any playlist you own
  • Use any public Spotify playlist as a source (even ones you don't own!)
  • Sync between your own playlists
  • Archive mode: keep tracks even after their removal from the source playlist (skip_removals)
  • Filter out local files, podcasts, and specific tracks
  • Create artist-specific playlists with include filters
  • Preview changes before applying (dry-run mode)
  • Run on a schedule with Docker

Quick Start

Create a Spotify Developer App

  1. Go to the Spotify Developer Dashboard
  2. Click Create app
  3. Set Redirect URI to https://example.com/callback

    This is the default. If you change it, update spotify.redirect_uri in your config to match exactly.

  4. Note down the App's Client ID and Client Secret

Required scopes (configured automatically):

  • user-library-read - Read your Liked Songs
  • playlist-read-private - Read private playlists
  • playlist-modify-public / playlist-modify-private - Modify playlists

Install SpotiSync

Install SpotiSync with pipx to run it as a standalone tool without affecting your system Python:

pipx install spotisync

Or with pip in a virtual environment:

pip install spotisync

Note: I recommend pipx for global CLI installation!

Or with Poetry (for development):

git clone https://github.com/bulletinmybeard/spotisync.git
cd spotisync
poetry install

Run the Setup Wizard

spotisync init

This guides you through:

  • Entering your Spotify credentials
  • Choosing a source (Liked Songs or a playlist)
  • Selecting a target playlist
  • Configuring filters (optional)

Authenticate

spotisync auth

A browser window opens to authorize SpotiSync with your Spotify account.

Sync

# Preview what will change
spotisync sync --dry-run

# Run the sync
spotisync sync

CLI Commands

Command Description
spotisync init Interactive setup wizard
spotisync init -o PATH Setup with custom config output path
spotisync auth Authenticate with Spotify
spotisync auth --clear Clear stored authentication token
spotisync sync Run all enabled sync groups
spotisync sync --dry-run Preview changes without applying
spotisync sync -n NAME Sync specific group(s) by name
spotisync sync --json JSON output for automation
spotisync status Show auth and config status
spotisync status --json JSON output
spotisync config Display current configuration (YAML)
spotisync config --json Display as JSON
spotisync add Add a new sync group (wizard)

Global option: --config/-c PATH to override config file location.

Automation

SpotiSync supports JSON output for scripting and CI/CD:

# Check sync results programmatically
spotisync sync --json | jq '.summary'

# Sync specific groups with JSON output
spotisync sync -n "daily-backup" -n "discover-weekly" --json

# Get authentication status
spotisync status --json | jq '.authentication.authenticated'

Exit codes: 0 on success, 1 on error.

Configuration

SpotiSync uses a config.yaml file. The setup wizard creates this for you, or see config.example.yaml for all options.

Multiple sync groups - Sync different sources to different targets:

sync_groups:
  - name: liked-to-public
    source: liked_tracks
    target: 3cEYpjA9oz9GiPac4AsH4n

  - name: discover-backup
    source: 37i9dQZEVXcQ9COmYvdajy  # Any public playlist (e.g., Spotify's Discover Weekly)
    target: 5Rrf7mqN8uus2AaQQQNdc1

Config file locations (checked in order):

  1. Docker: /app/config.yaml
  2. Development (git repo): ./config.yaml
  3. User home: ~/.spotisync/config.yaml

Override with --config/-c PATH on any command.

How to get a playlist ID: Open the playlist in Spotify, click Share → Copy link. The ID is the string after /playlist/: https://open.spotify.com/playlist/37i9dQZEVXcQ9COmYvdajy37i9dQZEVXcQ9COmYvdajy

Filtering

SpotiSync supports multiple different filter options to dictate which tracks should be included or excluded from the sync.

Examples

Include filters - Create artist-specific playlists from Liked Songs:

sync_groups:
  - name: eddie-unchained
    source: liked_tracks
    target: 3cEYpjA9oz9GiPac4AsH4n   # Your target playlist ID
    filters:
      include:
        artists:
          - 6mdiAmATAx73kdxrNrnlao # Iron Maiden

Include filters - Filter a public playlist to specific artists:

sync_groups:
  - name: doom-eternal-uncluttered
    source: 6s4aGjq9b42OP4nMGNCLUu   # DOOM Eternal soundtrack (public playlist)
    target: 9pXbKfV5dQ3sLz2nTj1uRw   # Your custom playlist
    filters:
      include:
        artists:
          - 13ab1LgQZ3tQOhkDRRYB8Y   # Mick Gordon

Exclude filters - Skip unwanted tracks:

sync_groups:
  - name: no-remasters
    source: 6vr1Nnese49l0hxQEljOQn # Your source playlist ID
    target: 3cEYojA9oz9hiPac4AsH4n # Your target playlist ID
    filters:
      exclude:
        tracks:
          - '(?i)\bremaster(?:ed)?(?:\s+\d{4})?\b' # e.g., Metallica / Enter Sandman - Remastered 2021

Combined filters - Use both include and exclude:

filters:
  exclude:
    artists:
      - '(?i)christmas' # Regex patterns
    albums:
      - 0sNOF9WDwhWunNAHPD3Baj # Spotify IDs
    tracks:
      - '(?i)remix'
      - '(?i)live'

Other filters:

  • skip_podcasts: true - Skip podcast episodes (default)

Include filters are applied before exclude filters, so you can create an artist playlist and still exclude particular tracks.

Filter inheritance: Per-group filters merge with root filters:

  • skip_podcasts: Per-group value overrides root
  • include/exclude lists: Combined (both root and group patterns apply)

Archive Mode

Want a backup playlist that never loses tracks? Use skip_removals to keep tracks in the target even after removing them from the source:

sync_groups:
  - name: liked-tracks-archive
    source: liked_tracks
    target: 3cEYpjA9oz9GiPac4AsH4n # Your target playlist ID
    skip_removals: true  # Tracks removed from the source stay in the target playlist

This is useful for:

  • Backup playlists that hold all tracks you've ever liked
  • Preserving a history of tracks from a dynamic source playlist

Using Regex for Track Filtering

Spotify tracks often include version markers in their titles like "(Live)", "- Remastered 2021", or "(Acoustic)". Regular expressions (regex) let you match these patterns flexibly to include or exclude specific track versions.

Regex Basics & Common Patterns

Regex Basics

The patterns below use these common regex features:

Syntax Meaning Example
(?i) Case-insensitive (?i)live matches "Live", "LIVE", "live"
\b Word boundary \blive\b matches "Live" but not "Oliver"
(a|b) Either a or b (remix|live) matches either
? Optional remaster(ed)? matches "remaster" or "remastered"
\d{4} Four digits Matches years like "2021"

YAML tip: Use single quotes around patterns to avoid escaping backslashes.

Common Patterns

Pattern Matches Example Track Title
'(?i)\bremaster(ed)?(\s+\d{4})?\b' Remastered versions "Enter Sandman - Remastered 2021"
'(?i)\blive\b' Live recordings "Nothing Else Matters (Live)"
'(?i)acoustic' Acoustic versions "Layla (Acoustic)"
'(?i)remix' Remixes "Blinding Lights (Remix)"
'(?i)\bdemo\b' Demo recordings "Bohemian Rhapsody (Demo)"
'(?i)instrumental' Instrumentals "Stairway to Heaven (Instrumental)"
'(?i)radio edit' Radio edits "Purple Haze - Radio Edit"
'(?i)(deluxe|bonus)' Deluxe/bonus tracks "Album Name (Deluxe Edition)"
'(?i)(feat\.|ft\.|featuring)' Featuring artists "Song (feat. Artist)"

Docker

This repo includes docker-compose.yml for scheduled syncing.

# Start the container (runs cron daemon)
docker compose up -d

# Run a manual sync
docker exec spotisync spotisync sync

# Check status
docker exec spotisync spotisync status

Volume mounts required:

  • ./config.yaml:/app/config.yaml - Your configuration
  • ./tokens.json:/app/tokens.json - Auth tokens (created after spotisync auth)

Configure the schedule in config.yaml:

cron:
  enabled: true
  schedule: "0 * * * *"  # Every hour

Note: Container name is spotisync - matches the shell wrapper functions below.

Shell Integration (Development Only)

Note: This section is only relevant if you run SpotiSync from the cloned repo via poetry run. If you installed it via pip or pipx, just use spotisync directly.

These optional wrapper functions provide a convenient spotisync shortcut when running from a cloned repo.

Linux/macOS (ZSH/Bash)

Create ~/spotisync_shell.sh:

For Poetry:

spotisync() {
    local project_dir="$HOME/path/to/spotisync"
    if [[ ! -d "$project_dir" ]]; then
        echo "Error: spotisync project not found at $project_dir"
        return 1
    fi
    (cd "$project_dir" && poetry run spotisync "$@")
}

For Docker:

spotisync() {
    if ! docker ps --format '{{.Names}}' | grep -q 'spotisync'; then
        echo "Error: spotisync container is not running"
        return 1
    fi
    docker exec -it spotisync spotisync "$@"
}

Then add to ~/.zshrc or ~/.bashrc:

[ -f "$HOME/spotisync_shell.sh" ] && source "$HOME/spotisync_shell.sh"

Reload: source ~/.zshrc

Windows (PowerShell)

Create ~\spotisync_shell.ps1:

For Poetry:

function spotisync {
    $projectDir = "$env:USERPROFILE\path\to\spotisync"
    if (-not (Test-Path $projectDir)) {
        Write-Error "spotisync project not found at $projectDir"
        return
    }
    Push-Location $projectDir
    try { poetry run spotisync @args }
    finally { Pop-Location }
}

For Docker:

function spotisync {
    $running = docker ps --format '{{.Names}}' | Select-String -Quiet 'spotisync'
    if (-not $running) {
        Write-Error "spotisync container is not running"
        return
    }
    docker exec -it spotisync spotisync @args
}

Then add to your PowerShell profile ($PROFILE):

if (Test-Path "$env:USERPROFILE\spotisync_shell.ps1") { . "$env:USERPROFILE\spotisync_shell.ps1" }

Reload: . $PROFILE

Requirements

  • Spotify account
  • Spotify Developer App credentials
  • Python 3.12+

Links

License

MIT License - see the LICENSE file for details.