og:image

Source code Spotitube

Synchronize your Spotify collections downloading from external providers
#

About

demo

Spotitube is a CLI application to authenticate to Spotify account, fetch music collections — such as account library, playlists, albums or specific tracks —, look them up on a defined set of providers — such as YouTube —, download them and inflate the downloaded assets with metadata collected from Spotify, further enriched with lyrics.

Usage

By default, Spotitube will synchronize user's library:

spotitube sync

In order to synchronize further set of tracks, use the dedicated flags:

spotitube sync --playlist spotitube-sync \
    --album https://open.spotify.com/album/6Jx4cGhWHewTcfKDJKguBQ?si=426ac1fd0fbe4cab \
    --playlist-tracks 5s9gvhZDtfTaM8VMfBtssy \
    --track 6SdAztAqklk1zAmUHhU4N7 \
    --fix /path/to/already/downloaded/track.mp3

As showed in the previous example, there are several ways to indicate the ID of a resource — be it a playlist, album or track: regardless Spotitube is given a full URL to that resource (e.g. https://open.spotify.com/playlist/2wyZKlaKzPEUurb6KshAwQ?si=426ac1fd0fbe4cab), a URI (e.g. spotify:playlist:2wyZKlaKzPEUurb6KshAwQ) or an ID (e.g. 2wyZKlaKzPEUurb6KshAwQ), it should be smart enough to solve the effective ID resolution all by itself.

Furthermore, in case of playlist, automatic aliasing of personal playlist names into their ID is applied: this enables passing playlist by name instead of ID in case user wants to synchronize personal playlists.

By default, Spotitube uses XDG Base/User Directory Specification to resolve user's Music folder (which usually maps to ~/Music), but it can be obviously overridden using a dedicated flag:

spotitube sync -o ~/MyMusic

Further auxiliary subcommands are defined and accessible via:

spotitube --help

Docker

In order to make Spotitube work via Docker, it has to expose its dedicated port (i.e. 65535) and mount both the cache and the music directories as volumes:

docker run -it --rm \
    -p 65535:65535/tcp \
    -v ~/.cache:/cache \
    -v ~/Music:/data \
    ghcr.io/streambinder/spotitube --help

Headless

The only real issue to be addressed when working with Spotitube running in headless mode, is the redirect during Spotify authentication.

By default, once authenticated to Spotify via web, Spotify itself redirects to a predefined callback URL, which corresponds to http://localhost:65535. In order to make that redirect go against a custom server, on Spotitube Spotify app, a further callback URL has been defined, i.e. http://spotitube.local:65535. This is the one that is set as callback at runtime when Spotitube goes through authentication with the --remote flag.

So, assuming the server on which Spotitube is running is reachable at 1.2.3.4, make sure the client can correctly resolve spotitube.local as 1.2.3.4.

Then, let's authenticate on the server, running the following command:

spotitube auth --remote

This should show a URL to be reached using your client's browser and which, on successful authentication, will hand further doing over to Spotitube on the server on which is running.

Once there, Spotitube can be used normally on the server.

Installation

Official releases

Binaries released officially include all the needed tokens and keys to make Spotitube work at its best (e.g. Spotify app ID and key, or Genius token).

To install, head to Spotitube Releases page, or pull via Docker:

docker pull ghcr.io/streambinder/spotitube:latest

Custom build

Spotitube's been written to be as much vanilla Go as possible, so all the traditional Go build/install methods are supported:

go install github.com/streambinder/spotitube@latest

Or:

git clone https://github.com/streambinder/spotitube.git
cd spotitube
go build
go install

By default, Spotitube will use SPOTIFY_ID, SPOTIFY_KEY and GENIUS_TOKEN environment variables to authenticate to the corresponding APIs. If those are not found, though, it will fall back to the fallback fields defined in the corresponding source code modules (which, in turn, are empty, by default). In order to build a binary which contains these fields, the following formula can be used:

go build -ldflags="
    -X github.com/streambinder/spotitube/spotify.fallbackSpotifyID='awesomeSpotifyID'
    -X github.com/streambinder/spotitube/spotify.fallbackSpotifyKey='awesomeSpotifyKey'
    -X github.com/streambinder/spotitube/lyrics.fallbackGeniusToken='awesomeGeniusToken'
"

Design

Spotitube is made of a pool of routines which carry out their job independently and in parallel. Each single one of these routines, will possibly receive a work mandate from a fellow routine, process that work unit and pass the ball.

It's an assembly line, where every single step has a very constrained work to do and a dedicated queue for items to accomplish that work for. Such queues usually carry a specific track (be it part of synchronization of user's library, of an album, a playlist, or a single track), but sometimes they only represent a semaphore or other types such as playlists.

The assembly line is made of the following routines:

design

Indexer

Scans the music folder in order to parse all the assets that have been synchronized using Spotitube. It is achieved by reading a specific custom ID3 metadata field corresponding to the Spotify track ID (which, in turn, is stuck into the MP3 file at processing time).

This is done to ensure that tracks collisions are properly handled and that already downloaded songs are skipped.

Authenticator

Self-explainatory: handles Spotify authentication.

Fetcher

Once Indexer and Authenticator succeed, they signal their status to the Fetcher, using a semaphor-like queue (of length of one and of boolean type).

The fetcher, then, goes through any given arg (be it the library, a playlist, an album, or a single track) and handles the fetching of data for each track composing the given collection, all from Spotify APIs.

That data is then parsed into a custom Track object which is passed to the Decider queue.

Decider

For each Track passed over by the Fetcher, it queries every provider defined (e.g. YouTube), looking for a result that best matches the given track data.

Collector

This component is split in three parts:

  1. Downloader: downloads the result which the Decider picked for the given track.
  2. Composer: queries every lyrics provider defined (e.g. Genius) and — if found — downloads it.
  3. Painter: downloads the artwork from the URL which was given by Spotify APIs.

Processor

The Processor applies further customization to the asset, such as rebalancing the volume of the track file or encoding all the metadata collected as ID3 (MP3) metadata.

Installer

Moves the file into its final location.

Mixer

For each playlist passed for synchronization, bundles it into a PLS (or whatever other encoding is used, e.g. M3U) file which contains every track composing the playlist which has been successfully installed.