Skip to content

Cross-Service Playlist Pushing

Overview

Pushtunes supports pushing playlists directly between services without going through a separate CSV export step. This allows you to:

  • Migrate playlists between streaming services (Spotify ↔ YouTube Music)
  • Migrate playlists from streaming services to local servers (Spotify/YTM → Subsonic/Jellyfin)
  • Migrate playlists from local servers to streaming services (Subsonic/Jellyfin → Spotify/YTM)
  • Keep playlists synchronized across multiple services

Note: This feature performs track-by-track matching using similarity algorithms. For best results, use --require-all-tracks to ensure all tracks can be found on both sides before creating the playlist.

Supported Directions

  • Spotify ↔ YouTube Music: Between streaming services
  • Spotify/YTM → Subsonic/Jellyfin: From streaming services to local servers (uses in-memory fuzzy matching)
  • Subsonic/Jellyfin → Spotify/YTM: From local servers to streaming services

Requirements

Authentication

You need to be authenticated with both the source and target services:

Spotify (both as source and target):

export SPOTIFY_CLIENT_ID=your-client-id
export SPOTIFY_CLIENT_SECRET=your-client-secret

YouTube Music (both as source and target):

ytmusicapi browser  # Creates browser.json
# Or specify a different file with --ytm-auth=filename.json

Subsonic (both as source and target):

export SUBSONIC_URL=https://your-server.com
export SUBSONIC_USER=your-username
export SUBSONIC_PASS=your-password

Jellyfin (both as source and target):

export JELLYFIN_URL=https://your-server.com
export JELLYFIN_USER=your-username
export JELLYFIN_PASS=your-password

Basic Usage

Between Streaming Services

# Spotify → YouTube Music
# Note: Spotify requires --source-playlist-id (not --playlist-name)
pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to ytm

# YouTube Music → Spotify
pushtunes push playlist --from ytm --playlist-name "Synthwave Mix" --to spotify

To Local Servers (Subsonic/Jellyfin)

# Spotify → Subsonic
# Note: Spotify requires --source-playlist-id (not --playlist-name)
pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to subsonic

# YouTube Music → Jellyfin
pushtunes push playlist --from ytm --playlist-name "Chill Vibes" --to jellyfin

Note: When pushing to Subsonic/Jellyfin, pushtunes uses in-memory fuzzy matching against your entire music library. Only tracks that exist in your library can be added to playlists.

From Local Servers

# Subsonic → Spotify
pushtunes push playlist --from subsonic --playlist-name "My Mix" --to spotify

# Jellyfin → YouTube Music
# Note: Jellyfin requires --source-playlist-id (not --playlist-name)
pushtunes push playlist --from jellyfin --source-playlist-id "8e5d6f1d17423354e83a3c17cae758dc" --to ytm

Strict Matching with --require-all-tracks

By default, pushtunes will create a playlist even if some tracks can't be matched (partial success). For cross-service pushing, you probably want to use strict mode to ensure all tracks are found:

pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to ytm \
  --require-all-tracks

What happens in strict mode:

  • All tracks must be matched (either automatically or via mappings)
  • If even one track can't be matched, the operation fails
  • No partial playlist is created
  • You can then use mappings to fix the failures and retry

Without strict mode (default):

  • Playlist is created with whatever tracks could be matched
  • Missing tracks are skipped
  • Useful for migrating large playlists where some items might be unavailable

Using Playlist IDs

Spotify (Required)

Spotify requires --source-playlist-id when used as a source because Spotify allows multiple playlists with the same name.

Jellyfin requires --source-playlist-id when used as a source for the same reason.

Finding Spotify playlist ID: 1. Open the playlist in Spotify (desktop or web) 2. Click Share → Copy link to playlist 3. The URL looks like: https://open.spotify.com/playlist/37i9dQZF1DXaXB8fQg7xif 4. The ID is the last part: 37i9dQZF1DXaXB8fQg7xif

Or use the Spotify URI: 1. Right-click playlist → Share → Copy Spotify URI 2. The URI looks like: spotify:playlist:37i9dQZF1DXaXB8fQg7xif 3. The ID is after playlist:: 37i9dQZF1DXaXB8fQg7xif

Finding Jellyfin playlist ID: 1. Open the playlist in Jellyfin web UI 2. The URL looks like: https://jellyfin.example.com/web/index.html#!/details?id=8e5d6f1d17423354e83a3c17cae758dc 3. The ID is the value after id=: 8e5d6f1d17423354e83a3c17cae758dc

Examples:

# From Spotify
pushtunes push playlist \
  --from spotify \
  --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" \
  --to ytm \
  --require-all-tracks

# From Jellyfin
pushtunes push playlist \
  --from jellyfin \
  --source-playlist-id "8e5d6f1d17423354e83a3c17cae758dc" \
  --to spotify \
  --require-all-tracks

YouTube Music (Optional)

YouTube Music can use either --playlist-name or --source-playlist-id:

To find a YouTube Music playlist ID: 1. Open playlist in browser 2. URL looks like: https://music.youtube.com/playlist?list=PLrAXtmErZgOeiu... 3. The ID is after list=: PLrAXtmErZgOeiu...

Example with ID:

pushtunes push playlist \
  --from ytm \
  --source-playlist-id "PLrAXtmErZgOeiu..." \
  --to spotify

Example with name:

pushtunes push playlist \
  --from ytm \
  --playlist-name "Synthwave Mix" \
  --to spotify

Working with Mappings

When tracks can't be auto-matched (different names, remasters, etc.), use mappings to provide manual matches.

See mappings_complete_example.csv for a comprehensive example file with all columns and common use cases.

Step 1: Export Failed Matches

pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to ytm \
  --require-all-tracks \
  --export-csv=mappings-file \
  --export-csv-file=playlist_mappings.csv

This creates a template with all tracks that had status not_found or similarity_too_low.

Step 2: Fill in Mappings

Edit playlist_mappings.csv:

type,artist,title,spotify_id,ytm_id,spotify_artist,spotify_title,ytm_artist,ytm_title,subsonic_artist,subsonic_title,subsonic_album,jellyfin_artist,jellyfin_title,jellyfin_album
track,"Volkor X","This Means War","","","","","Volkor X","This Really Means War",,,,,
track,"Mitch Murder","Current Events","","","","","Mitch Murder","Current Events (Remastered)",,,,,

For Subsonic/Jellyfin targets, use the subsonic_* and jellyfin_* columns:

type,artist,title,spotify_id,ytm_id,spotify_artist,spotify_title,ytm_artist,ytm_title,subsonic_artist,subsonic_title,subsonic_album,jellyfin_artist,jellyfin_title,jellyfin_album
track,"The Beatles","Let It Be",,,,,,,Beatles,Let It Be,Let It Be (Remastered),,,
track,"Foo Fighters","Best Of You",,,,,,,Foo Fighters,Best of You,Greatest Hits,,,

Step 3: Retry with Mappings

pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to ytm \
  --require-all-tracks \
  --mappings-file=playlist_mappings.csv

Now the tracks that couldn't be auto-matched will use your manual mappings. See Mappings for more about this feature.

Conflict Resolution

If a playlist with the same name already exists on the target, use --on-conflict.

For Spotify and Jellyfin targets, you can also use --playlist-id to target a specific existing playlist by ID:

# Show differences and abort (default)
pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to ytm \
  --on-conflict=abort

# Replace entire playlist
pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to ytm \
  --on-conflict=replace

# Add missing tracks only
pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to ytm \
  --on-conflict=append

# Add missing, remove extras (full sync)
pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to ytm \
  --on-conflict=sync

Adjusting Similarity Threshold

If too many tracks aren't matching, try lowering the similarity threshold:

pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to ytm \
  --similarity=0.7 \
  --require-all-tracks

Default: 0.8 (80% similarity required) Range: 0.0 to 1.0

Be careful: Lower thresholds may cause incorrect matches!

Example Workflows

Safe Cross-Service Migration

# Step 1: Try with strict mode
pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to ytm \
  --require-all-tracks \
  --export-csv=mappings-file

# Step 2: Fill in mappings.csv for failed tracks

# Step 3: Retry with mappings
pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to ytm \
  --require-all-tracks \
  --mappings-file=albums_mappings_template.csv

# Step 4: If whatever gods are in charge of music metadata are well-disposed towards you,
# All tracks are matched and playlist is created

Bidirectional Sync

# Mirror Spotify playlist to YTM
pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to ytm \
  --on-conflict=sync

# Mirror YTM playlist to Spotify
pushtunes push playlist --from ytm --playlist-name "Favorites" --to spotify \
  --on-conflict=sync

Verbose Debugging

pushtunes push playlist --from spotify --source-playlist-id "37i9dQZF1DXaXB8fQg7xif" --to ytm \
  --require-all-tracks \
  --verbose \
  --report=not_found,similarity_too_low

This shows:

  • Detailed track matching results (--verbose)
  • Full report of failures (--report)

Limitations

  1. Track availability: Not all tracks available on one service exist on another
  2. Different versions: Services may have different versions (remaster, live, etc.)
  3. Metadata differences: Artist names and track titles may differ between services
  4. Rate limiting: Large playlists may take time due to API rate limits

Tips for Best Results

  1. Always use --require-all-tracks for cross-service pushing to ensure completeness
  2. Use playlist IDs when possible for more reliable lookups
  3. Create mappings for problem tracks instead of lowering similarity threshold
  4. Test with small playlists first before migrating large collections
  5. Use --verbose and --report to understand matching behavior

Troubleshooting

"Playlist not found"

  • Check playlist name spelling (case-insensitive but must match)
  • Try using --source-playlist-id instead
  • Verify you're authenticated (check env vars for Spotify, browser.json for YTM)

"Only X/Y tracks matched"

  • Use --export-csv=mappings-file to see which tracks failed
  • Review the failures and create manual mappings
  • Consider lowering --similarity threshold (but be careful)

"No tracks could be matched"

  • Verify the target service has the music available
  • Check if track/artist names are significantly different
  • Use mappings to provide manual matches

See Also