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.
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.
If you are using ENV Config for multi-scrobbler and just want a quick and easy setup, skip to ENV Configuration.
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).
{
// ...
"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.
{
// ...
"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:
[
{
"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.
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_IDor ENVSPOTIFY_CLIENT_IDfrom the Spotify Source - Client Secret => ENV
SPOTIFY_TRANSFORM_CLIENT_SECRETor ENVSPOTIFY_CLIENT_SECRETfrom 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.
searchIsrc- Restricts search methods toisrconly (default isisrcandbasic)missingFields- Only run transform ifalbum,artist,title, ordurationare missing in datamissingIds- Only run transform if spotify ids are missing in datamissingIsrc- Only run transform ifisrcis missing in datamissingArt- Only run transform ifartis missing in datadecomp- Deprioritize compilations when ranking matches
More than one preset can be applied, in which case they combine.
Example
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=myuseretc... - 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
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
Should MS Search?
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, andidsidsis any missing spotify id (track, artists, albumArtists, and album ids)- the default when
searchWhenMissingis not defined is:album,artists,title, andduration
forceSearch(defaultfalse) - 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.
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.
If searchOrder is undefined Multi-scrobbler defaults to using isrc then basic.
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"
}
}
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
localeset, a lookup returnsアトラスサウンドチーム; - with
"locale": "en_US"the same lookup returnsATLUS Sound Team.
market alone does not control this.
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
basicsearch 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
[
{
"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.