Skip to main content

Push music (albums, tracks, playlists) from Subsonic, Jellyfin or plain CSV files to Spotify and YouTube Music

Project description

Pushtunes

Pushtunes is a small tool to push your music from local sources (Subsonic-compatible server/Navidrome, a CSV file, etc.) to music streaming services. Currently only Spotify and YouTube Music are supported. See "Music streaming services" below for more.

Installation

From PyPI using uv:

uv venv .venv
source .ven/bin/activate
uv pip install pushtunes

Or if using plain pip:

python3 -m venv .venv
source .venv/bin/activate
pip install pushtunes

Installing from source instead

From the source directory, in a virtualenv, using uv:

uv venv .venv
source .venv/bin/activate
uv pip install .

Using plain old Python/pip:

python3 -m venv .venv
source .venv/bin/activate
pip install .

Usage

Set your music service and source credentials (see below) and, for example:

# Push albums from Subsonic to Spotify
pushtunes push albums --from subsonic --to spotify

# Push individual tracks (starred/favorites) from Subsonic to Spotify
pushtunes push tracks --from subsonic --to spotify

# Push playlists from Subsonic to Spotify or YouTube Music
pushtunes push playlist --from subsonic --playlist-name=myplaylist --to spotify
pushtunes push playlist --from subsonic --playlist-name=myplaylist --to ytm

# Push from CSV file
pushtunes push tracks --from csv --csv-file=tracks.csv --to spotify

See pushtunes --help, pushtunes push albums --help, pushtunes push tracks --help, or pushtunes push playlist --help for more options.

Mapping items that really can't be found

It's very difficult to use unreliable metadata in your random assortment of audio files (well, sorry, just pointing it out) to whatever the streaming services are using right thi smoment. What makes this even more challenging is that some artists changed names over the years or are known under different names depending on the region. Or there can be legal trouble when a band is not allowed to use its original name for a while, but then the rights revert to the band after some lawsuit. Depending on when you bought their music, they might no longer be known under that name to Spotify and YouTube Music.

So Pushtunes is in a bad position and has to do a lot of guessing. The similarity matching with --similarity can go some way to fixing this. But sometimes you just have stuff that doesn't match up. That's what the --mappings-file feature is for. See MAPPINGS.md for how to use it.

Filtering

There is a very primitive filter language you can use to skip/filter out things you don't want pushed. It works like this:

field:'regex'

They can be chained together with comma and will be ORed.

For albums: The fields supported are artist and album. A real example:

pushtunes push albums --filter="artist:'Apocryphos, Kammarheit, Atrium Carceri', artist:'Black.*', album:'Sunset.*'" --from=csv --csv-file=test.csv  --to=spotify

This would skip the following things:

  • All albums by the artist(s) called "Apocryphos, Kammarheit, Atrium Carceri" who did only one or two albums together
  • Anything by "Black Hills", "Black Mountain", "Black Eyed Peas" and other artists starting with "Black" because of the regex Black.*
  • In this particular case, the album "Sunset Mission" by "Bohren & der Club of Gore", but any album that starts with "Sunset" by any artist would be skipped using Sunset.*

For tracks: The fields supported are artist, track, and album (album is optional). Example:

pushtunes push tracks --filter="artist:'Volkor X', track:'.*Live.*'" --from=subsonic --to=spotify

This would skip tracks by "Volkor X" and any live tracks.

There's no way to combine with anything other than OR and there's no way to specify "skip this album but only by that artist". Sorry, I did say it's primitive!

Similarity Matching

Pushtunes uses fuzzy matching to handle differences in how music services structure metadata. This applies to albums, tracks, and playlists.

What gets matched:

  • Case-insensitive: "The Beatles" matches "the beatles"
  • Artist name variations: "Artist A & Artist B" matches "Artist A, Artist B"
  • Featured artists: Handles tracks where one service lists featured artists separately and another includes them in the title
  • Subset matching: A track with artist "Perturbator" will match a track with artists "Perturbator, Greta Link" (similarity 0.95)
  • Soundtrack suffixes: "Album Title (Original Soundtrack)" matches "Album Title"
  • Remaster handling: By default, remasters are kept separate from originals to avoid accidentally replacing your original album with a remastered version

Adjusting the threshold:

The --similarity option controls how strict the matching is (default 0.8):

# Stricter matching (0.9 = 90% similar)
pushtunes push tracks --from subsonic --to spotify --similarity=0.9

# More lenient matching (0.7 = 70% similar)
pushtunes push albums --from csv --csv-file=albums.csv --to ytm --similarity=0.7

A higher value means tracks/albums must be more similar to match. If you're getting too many false matches, increase the threshold. If valid matches are being rejected, decrease it.

Playlists

Pushtunes can push playlists from Subsonic to Spotify or YouTube Music, preserving track order and handling conflicts intelligently.

Basic usage:

# Push a playlist by name
pushtunes push playlist --from subsonic --playlist-name=myplaylist --to spotify

Handling conflicts:

When a playlist with the same name already exists on the target service, you can control the behavior with --on-conflict:

  • abort (default): Show differences and exit without making changes

    pushtunes push playlist --from subsonic --playlist-name=chill --to ytm --on-conflict=abort
    

    This will display:

    • Number of tracks in the existing playlist vs. source
    • Tracks in common
    • Tracks that would be added
    • Tracks that would be removed
  • replace: Replace the entire playlist with tracks from source

    pushtunes push playlist --from subsonic --playlist-name=workout --to spotify --on-conflict=replace
    

    All existing tracks are removed and replaced with the source playlist.

  • append: Add only missing tracks to the existing playlist

    pushtunes push playlist --from subsonic --playlist-name=favorites --to ytm --on-conflict=append
    

    Existing tracks are preserved, new tracks are added at the end.

  • sync: Fully synchronize the playlist (add missing, remove extras)

    pushtunes push playlist --from subsonic --playlist-name=discover --to spotify --on-conflict=sync
    

    The target playlist will be made identical to the source, maintaining the same track order.

Spotify-specific: Using playlist IDs

Spotify allows duplicate playlist names, which can cause conflicts. For Spotify, you can target a specific playlist using its ID instead of relying on name matching:

# First push creates the playlist - note the Playlist ID in output
pushtunes push playlist --from subsonic --playlist-name=argon --to spotify
# Output: Playlist ID: 59EkFxUZG0CnApHQIgYVUK

# Subsequent pushes using the ID
pushtunes push playlist --from subsonic --playlist-name=argon --to spotify \
  --playlist-id=59EkFxUZG0CnApHQIgYVUK --on-conflict=sync

You can find playlist IDs in the Spotify web player URL or from the previous push output.

Verbose output:

Use -v or --verbose to see detailed track matching results and conflict information:

pushtunes push playlist --from subsonic --playlist-name=jazz --to spotify -v

Music sources

Subsonic (including Navidrome, etc.)

Set your Subsonic server's URL and credentials:

export SUBSONIC_URL=https://your-music.example.com
export SUBSONIC_USER=yourusername
export SUBSONIC_PASS="correct horse battery staple"

You are now ready to use the --from=subsonic source.

Note on tracks: When using push tracks --from=subsonic, pushtunes fetches your starred/favorite tracks from the Subsonic server using the getStarred2 API endpoint.

Jellyfin

Set your Jellyfin server's URL and credentials:

export JELLYFIN_URL=https://jellyfin.example.com
export JELLYFIN_USER=yourusername
export JELLYFIN_PASS="correct horse battery staple"

You are now ready to use the --from=jellyfin source.

CSV files

You can import albums or tracks from CSV files.

Album CSV format:

artist,album,year
"Carbon Based Lifeforms","Interloper",2010
"Igorrr","Spirituality and Distortion",2020

For albums with multiple artists, you can use artist2, artist3, etc. columns (up to artist10):

artist,artist2,artist3,album,year
"Apocryphos","Kammarheit","Atrium Carceri","Echo",2016

Track CSV format:

artist,track,album,year
"Dire Straits","Sultans of Swing","Dire Straits",1978
"Thy Catafalque","Molekuláris Gépezetek","Róka Hasa Rádió",2009

For tracks, the album and year columns are optional. The CSV format supports only a single artist per track (the main artist).

Music streaming services

The streaming service market is in a very sad state of affairs regarding APIs. Spotify has a good one, YouTube Music is almost unusable and working with it is only possible thanks to the people who maintain the unofficial ytmusicapi library. Deezer and Qobuz don't allow anyone to use their API anymore, requests for API keys go unanswered, documentation is being deleted and no up to date libraries exist. Tidal might still allow people to use the API but the Python support is incomplete and lacking the functions Pushtunes needs.

That's why realistically, YTM and Spotify are your choices. Here's how to authorize Pushtunes for them.

YouTube Music

In the same virtualenv where you've installed Pushtunes, you'll have the tool ytmusicapi. The only currently working authorization is via intercepted browser headers, what a great deal of fun. Try the following:

ytmusicapi browser

Then, in your browser (Firefox as an example):

  1. Browse to https://music.youtube.com
  2. Log in so you can go to your YTM library
  3. Click in the search field ("Search songs, albums, artists, podcasts")
  4. Open the browser tools so you can see the network requests (Ctrl-Shift-I and click the "Network" tab)
  5. Search for some words, doesn't matter what you search for. Submit the search with enter
  6. You should see the POST request for "search" in the network tab. Right-click it, select "Copy value" -> "Copy request headers"
  7. This is what you paste into the console, finishing with enter and then Ctrl-D

This will generate a browser.json file with your authorization. Note that this will expire after a short time (30 minutes?) so you'll have to do this again before you use Pushtunes again. You should now be ready to push some tunes.

Spotify

On the Spotify developer dashboard, create a new application. If it asks for redirect URIs (they are not important), try something like this:

http://127.0.0.1:8080
http://127.0.0.1:8080/callback
http://127.0.0.1:8080/

You will receive a client ID and client secret. Set them in the same shell where you're running Pushtunes:

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

You should now be ready to push some tunes.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pushtunes-0.9.1.tar.gz (69.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pushtunes-0.9.1-py3-none-any.whl (66.3 kB view details)

Uploaded Python 3

File details

Details for the file pushtunes-0.9.1.tar.gz.

File metadata

  • Download URL: pushtunes-0.9.1.tar.gz
  • Upload date:
  • Size: 69.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.7

File hashes

Hashes for pushtunes-0.9.1.tar.gz
Algorithm Hash digest
SHA256 b080136d890961823ac912ab74fb8be025ba6bd2845366c4e2f673e16b6efbf3
MD5 883413b926b460d22d136151c1d4e9cb
BLAKE2b-256 0be14fe12b24803fea35360d16468f6b3a7d9fad7eb0824ee20ae9dc3bd7ffc8

See more details on using hashes here.

File details

Details for the file pushtunes-0.9.1-py3-none-any.whl.

File metadata

  • Download URL: pushtunes-0.9.1-py3-none-any.whl
  • Upload date:
  • Size: 66.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.7

File hashes

Hashes for pushtunes-0.9.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3f1a4dfa358639f231a496d37bf6dce9291b8e6cbb99ba1f0d0583c308bd3b12
MD5 45eecbe3eeb228b0e44c04e77d211937
BLAKE2b-256 9477f20394178972287e76f448036289f3733d8e0ec8baef657a39dabea2b22d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page