Skip to main content

Spotify Stage

The Spotify Stage matches your Play data with the Spotify catalog, using the Spotify Web API. If the match is confident enough then Multi-Scrobbler uses it to correct and fill-in missing information in your Play data.

This Stage is useful for standardizing your Scrobble's Play data, regardless of the Source it is coming from, and is a good complement (or alternative) to the Musicbrainz Stage if you'd prefer matches to come from Spotify's own catalog.

important

Like the Spotify Source, this Stage requires Spotify Premium to access the Spotify API. This is not due to any specific functionality this Stage uses but due to Spotify now requiring Premium for any access to their API.

See the Premium Account Required expandable section in the Spotify Source docs for your options, if you are not a premium user.

tip

If you are using ENV Config for multi-scrobbler and just want a quick and easy setup, skip to ENV Configuration.

tip

Set up Valkey Caching to cache Spotify API calls for faster processing.

Configuration​

API Setup​

This Stage uses Spotify's API to search/lookup the Spotify catalog. This does not require a user to log in -- only an app clientId/clientSecret is needed. Create a Spotify application if you don't already have one (the same application used for the Spotify Source can be reused here).

config.json
{
// ...
"transformers": [
{
"type": "spotify",
"name": "MySpotify",
"data": {
"clientId": "787c921a2a2ab42320831aba0c8f2fc2",
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a"
},
}
]
}
Rate Limiting

rate can be used to configure how many requests are made to the Spotify API, defined by max number of requests within timespan of N seconds (default 10 req/1s):

{
"type": "spotify",
"name": "MySpotify",
"data": {
"clientId": "787c921a2a2ab42320831aba0c8f2fc2",
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a",
"rate": {
"requests": 10,
"perTime": 1
}
},
}

Stage Configuration​

All of the properties found in Matching with Spotify section are configured in Stage Configuration as defaults.

These are all also optional so you can use the Spotify Transform stage without configuring any of this.

config.json
{
// ...
"transformers": [
{
"type": "spotify",
"name": "MySpotify",
"data": {
"clientId": "787c921a2a2ab42320831aba0c8f2fc2",
"clientSecret": "ec42e09d5ae0ee0f0816ca151008412a"
},
"defaults": {
"score": 0.6,
"deprioritizeCompilations": true
}
}
]
}

Rules and Hooks​

Add your Stage to a Source or Client by specifying it in a Hook:

subsonic.json
[
{
"name": "MySubsonic",
"data": { /* ... */},
"options": {
"playTransform": {
"preCompare": [
{
"type": "spotify",
"name": "MySpotify"
}
]
}
}
}
]

Each Stage Rule (title/artists/albumArtists/album/duration/meta) works the same way as it does for the Musicbrainz Stage: either a boolean specifying whether the transformed data should be used for this field, or a when condition.

Per Component Override

The defaults you set in Stage Configuration can be overriden/added to (per property) in each Hook, exactly as with the Musicbrainz Stage.

ENV Configuration​

Your Spotify Stage can be configured using ENV Config by specifying client/secret and, optionally, combining different presets.

Client ID and Secret are the only required configuration:

  • Client Id => ENV SPOTIFY_TRANSFORM_CLIENT_ID or ENV SPOTIFY_CLIENT_ID from the Spotify Source
  • Client Secret => ENV SPOTIFY_TRANSFORM_CLIENT_SECRET or ENV SPOTIFY_CLIENT_SECRET from the Spotify Source

To configure stage defaults use ENV SPOTIFY_TRANSFORM_PRESETS= with a comma-delimited list of presets you wish to apply. If SPOTIFY_TRANSFORM_PRESETS is not set the stage will use defaults.

More than one preset can be applied, in which case they combine.

Example
docker-compose.yaml
services:
multi-scrobbler:
# ...
environment:
# ...
- SPOTIFY_CLIENT_ID=787c921a2a2ab42320831aba0c8f2fc2
- SPOTIFY_CLIENT_SECRET=ec42e09d5ae0ee0f0816ca151008412a
# only runs transform if album, artists, title, duration, or art are missing in data
# and only searches by isrc
- SPOTIFY_TRANSFORMS_PRESETS=missingArt,missingFields,searchIsrc
# ...

Finally, use ENV *_TRANSFORMS=spotify on each Source/Client you wish to apply this stage to. This applies the stage in the preCompare Hook with all Rules enabled.

The * stands for the prefix used for each Source/Client's ENV keys. Refer to the individual Source/Client Configuration sections to find this. Example:

  • All Subsonic Source ENVs look like SUBSONIC_USER=myuser etc...
  • Use SUBSONIC_TRANSFORMS=spotify
Example Full Docker Deploy with ENV Configuration
services:
multi-scrobbler:
image: foxxmd/multi-scrobbler
container_name: multi-scrobbler
environment:
# only runs transform if art or fields are missing in data
# and only searches by isrc
- SPOTIFY_TRANSFORMS_PRESETS=missingArt,missingFields,searchIsrc
- SPOTIFY_CLIENT_ID=787c921a2a2ab42320831aba0c8f2fc2
- SPOTIFY_CLIENT_SECRET=ec42e09d5ae0ee0f0816ca151008412a
- JELLYFIN_URL=192.168.0.110:8096
- JELLYFIN_APIKEY=c9fae8756fbf481ebd9c5bb56b
- JELLYFIN_USER=MyUser
# applies spotify Stage to preCompare of Jellyfin source
- JELLYFIN_TRANSFORMS=spotify

# maloja receives enhanced scrobble from Jellyfin
- MALOJA_URL=http://192.168.0.100:42010
- MALOJA_API_KEY=myApiKey

volumes:
- "./config:/config"
ports:
- "9078:9078"
restart: unless-stopped

To use the configured ENV stage in Rules and Hooks with File/AIO config either omit name or specify it as "name": "MSEnv".

Matching with Spotify​

note

All properties found in this section are optional.

Matching your Scrobble's Play data with a result from Spotify is comprised of two steps:

  • Searching Spotify using parts of your Scrobble as queries
  • Ranking matched results to select the desired match

Searching​

Before MS begins a search it checks if your Scrobble data already contains:

  • A track title, artists, album, and duration
  • All spotify IDs and an ISRC

If it already has all required data types then the entire Spotify Stage is skipped. If any are missing then a search is performed.

Define which data types are required using:

  • searchWhenMissing - A list containing any of: album,title,artists,duration,isrc, and ids
    • ids is any missing spotify id (track, artists, albumArtists, and album ids)
    • the default when searchWhenMissing is not defined is: album,artists,title, and duration
  • forceSearch (default false) - Force searching even if all required data is present

Search Methods​

Multi-scrobbler searches for a Spotify match using, in order, up to two methods. Use searchOrder to control which methods run and in what order:

// ...
"defaults": {
"searchOrder": ["isrc", "basic"]
}
ISRC (isrc)

If your Scrobble data contains an ISRC then Multi-scrobbler searches Spotify's catalog using this ID.

If the ISRC is present on more than one Spotify album/track (which happens often IE singles, re-releases, and compilation appearances of the same recording all share an ISRC) then the results are ranked using fuzzy matching against your scrobble's existing album/artist data to pick the best candidate.

ISRC Matches Skip the Score Threshold

If an ISRC search finds results then filtering results by score is skipped since all results are confirmed matches. Ranking by fuzzy matching, as described above, still occurs.

This matters because Spotify's title/artist text for the same recording can differ substantially from your scrobble source (localized titles, "feat." credits, movie/theatrical edition suffixes, etc) while still being the correct match.

Only results from a basic search are filtered by the minimum score.

Album, Artist, and Title Fields (basic)

Searches Spotify using any/all available text fields from your scrobble: Album, primary Artist, and Title. Results are always ranked using fuzzy matching since Spotify's text search does not otherwise guarantee an accurate/confident result.

Default Search Methods

If searchOrder is undefined Multi-scrobbler defaults to using isrc then basic.

note

If all defined search methods do not return any results (or, for a basic search, no results score high enough, see Ranking) then the stage is marked as failed (onFailure) for Flow Control.

A failed Stage never modifies your play data unless you eplicitly configure it to. See Failed and Skipped Stages for details.

Biasing Search Results with market and locale

market (an ISO 3166-1 alpha-2 country code) can be set to bias/limit search results to what is available in a specific market:

Example
{
"type": "spotify",
"name": "MySpotify",
"data": {/* */},
"defaults": {
// ...
"market": "US"
}
}

locale (in ISO-639-1_ISO-3166-1 format, EX en_US, ja_JP) can additionally be set to try to bias which translation of a localized catalog name (artist/album/track) Spotify returns for a match:

Example
{
"type": "spotify",
"name": "MySpotify",
"data": {/* */},
"defaults": {
// ...
"locale": "en_US"
}
}

Real World Example

Some catalog entries carry a translated name depending on locale, independent of market. EX the Japanese game-music artist "ATLUS Sound Team" is stored in Spotify's catalog under multiple localized names for the exact same artist ID.

  • Without locale set, a lookup returns アトラスサウンドチーム;
  • with "locale": "en_US" the same lookup returns ATLUS Sound Team.

market alone does not control this.

important

locale is an UNDOCUMENTED parameter of the Spotify Web API and may break at any time, without notice or warning.

Its behavior may also be inconsistent. Some catalog entries only exist with one language's name at all, in which case no locale/market combination will produce an alternate translation because Spotify doesn't have one to return.

Ranking​

Spotify's search results do not carry a confidence/similarity score so Multi-scrobbler fuzzy-matches every candidate against your original scrobble's title/artist(s)/album to:

  • disambiguate between multiple candidates (EX an ISRC present on more than one album)
  • for basic search results only, determine whether any candidate is a confident enough match to use at all

Score​

Each candidate is scored between 0 and 1 based on how similar its title/artist(s)/album are to your original scrobble. Set score to change the minimum score a basic-search candidate must have to be used. Default is 0.6. This has no effect on isrc matches, which are always used.

{
// ...
"score": 0.6 // matches must score 0.6 or higher to be considered
}

You can bias which of title/artist/album contributes most to a candidate's score with titleWeight, artistWeight, and albumWeight (defaults are 0.4/0.3/0.3, respectively):

// ...
"defaults": {
"titleWeight": 0.4,
"artistWeight": 0.3,
"albumWeight": 0.3
}

Deprioritize Compilations​

Spotify catalogs many recordings across multiple compilation albums (Greatest Hits, movie soundtracks that reuse a song, etc...) in addition to their "proper" studio album. If you'd prefer matches to avoid compilation albums when a better alternative exists, enable deprioritizeCompilations:

// ...
"defaults": {
"deprioritizeCompilations": true
}

This does not exclude compilations -- it only lowers their score relative to other candidates, so a compilation can still be used if it's the only match found.

Best Practices​

Caching​

You should setup metadata caching to reduce API calls, improve transform performance, and reduce memory usage when using this stage.

Using Partial Match​

Use Rules to apply Spotify match data selectively. This is useful if you don't want your scrobble's Artist/Title/Album modified but still want Spotify IDs or ISRC defined for Clients or other Stages to leverage them.

Example
subsonic.json
[
{
"name": "MySubsonic",
"data": { /* ... */},
"options": {
"playTransform": {
"preCompare": [
{
"type": "spotify",
"name": "MySpotify",
"title": false,
"artists": false,
"album": false,
"albumArtists": false,
"meta": true
}
]
}
}
}
]

Logging​

If Spotify is not returning matches, or the resulting enhanced Scrobble is not what you expected, enable Debug Mode to help diagnose issues with the Spotify API and Scrobble enhancement.

If you have multiple Modification Stages and need to see the diff for your Play between each Stage, enable "log": "all" in the individual Modification Stage, as described for the Musicbrainz Stage.