Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/FoxxMD/multi-scrobbler. Scrobble plays from multiple sources to multiple clients docs.multi-scrobbler.app
deezer docker jellyfin koito lastfm listenbrainz maloja mopidy mpris music music-assistant plex scrobble self-hosted spotify subsonic tautulli youtube-music
Something went wrong. Try again.
17 kB · 466 lines
MDX
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467---title: Spotify Stagedescription: Enhancing Scrobbles with Spotifytoc_min_heading_level: 2toc_max_heading_level: 5---
The **Spotify** [Stage](/configuration/transforms#stage) matches your Play data with the [Spotify](https://open.spotify.com/) catalog, using the [Spotify Web API](https://developer.spotify.com/documentation/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](/configuration/transforms/musicbrainz) if you'd prefer matches to come from Spotify's own catalog.
:::important
Like the [Spotify Source](/configuration/sources/spotify), 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](/configuration?config-type=env#configuration-types) for multi-scrobbler and just want a quick and easy setup, skip to [**ENV Configuration**](#env-configuration).
:::
:::tip
Set up [Valkey Caching](/configuration?cachedThings=metadata#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](https://developer.spotify.com/documentation/web-api/concepts/apps) if you don't already have one (the same application used for the [Spotify Source](/configuration/sources/spotify) can be reused here).
```json5 title="config.json"{ // ... "transformers": [ { "type": "spotify", "name": "MySpotify", "data": { "clientId": "787c921a2a2ab42320831aba0c8f2fc2", "clientSecret": "ec42e09d5ae0ee0f0816ca151008412a" }, } ]}```
<details>
<summary>Rate Limiting</summary>
`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`):
```json5{ "type": "spotify", "name": "MySpotify", "data": { "clientId": "787c921a2a2ab42320831aba0c8f2fc2", "clientSecret": "ec42e09d5ae0ee0f0816ca151008412a", // highlight-start "rate": { "requests": 10, "perTime": 1 } // highlight-end },}```
</details>
### Stage Configuration
All of the properties found in [**Matching with Spotify**](#matching-with-spotify) section are configured in [Stage Configuration](/configuration/transforms#configuring-stages) as `defaults`.
These are all also **optional** so you can use the Spotify Transform stage without configuring any of this.
```json5 title="config.json"{ // ... "transformers": [ { "type": "spotify", "name": "MySpotify", "data": { "clientId": "787c921a2a2ab42320831aba0c8f2fc2", "clientSecret": "ec42e09d5ae0ee0f0816ca151008412a" }, // highlight-start "defaults": { "score": 0.6, "deprioritizeCompilations": true } // highlight-end } ]}```
### Rules and Hooks
[Add your Stage](/configuration/transforms/#stage) to a Source or Client by specifying it in a [Hook](/configuration/transforms/#hook):
```json5 title="subsonic.json"[ { "name": "MySubsonic", "data": { /* ... */}, "options": { "playTransform": { "preCompare": [ { "type": "spotify", "name": "MySpotify" } ] } } }]```
Each [**Stage Rule**](/configuration/transforms#stage-rules) (`title`/`artists`/`albumArtists`/`album`/`duration`/`meta`/`art`) works the same way as it does for the [Musicbrainz Stage](/configuration/transforms/musicbrainz#rules-and-hooks): either a boolean specifying whether the transformed data should be used for this field, or a [`when` condition](/configuration/transforms#conditional-modification).
:::tip[Per Component Override]
The `defaults` you set in [Stage Configuration](#stage-configuration) [can be overriden/added to](/configuration/transforms/#overriding-configuration) (per property) in each Hook, exactly as with the [Musicbrainz Stage](/configuration/transforms/musicbrainz#rules-and-hooks).
:::
## ENV Configuration
Your Spotify Stage can be configured using [ENV Config](/configuration?configType=env#configuration-types) 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](/configuration/sources/spotify)* Client Secret => ENV `SPOTIFY_TRANSFORM_CLIENT_SECRET` or ENV `SPOTIFY_CLIENT_SECRET` from the [Spotify Source](/configuration/sources/spotify)
To configure [stage defaults](#stage-configuration) 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](#search-methods) to `isrc` only (default is `isrc` and `basic`)* `missingFields` - Only run transform if `album`, `artist`, `title`, or `duration` are [missing in data](#should-ms-search)* `missingIds` - Only run transform if spotify ids are [missing in data](#should-ms-search)* `missingIsrc` - Only run transform if `isrc` is [missing in data](#should-ms-search)* `missingArt` - Only run transform if `art` is [missing in data](#should-ms-search)* `decomp` - [Deprioritize compilations](#deprioritize-compilations) when ranking matches
More than one preset can be applied, in which case they combine.
<details>
<summary>Example</summary>
```yaml title="docker-compose.yaml"services: multi-scrobbler: # ... environment: # ... - SPOTIFY_CLIENT_ID=787c921a2a2ab42320831aba0c8f2fc2 - SPOTIFY_CLIENT_SECRET=ec42e09d5ae0ee0f0816ca151008412a // highlight-start # 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 // highlight-end # ...```
</details>
Finally, use ENV `*_TRANSFORMS=spotify` on each Source/Client you wish to apply this stage to. This applies the stage in the [`preCompare` Hook](/configuration/transforms#lifecycle-hooks) with all [Rules](#rules-and-hooks) 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](/configuration/sources/subsonic) ENVs look like `SUBSONIC_USER=myuser` etc...* Use `SUBSONIC_TRANSFORMS=spotify`
<details>
<summary>Example Full Docker Deploy with ENV Configuration</summary>
```yamlservices: multi-scrobbler: image: foxxmd/multi-scrobbler container_name: multi-scrobbler environment: // highlight-start # 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 // highlight-end - JELLYFIN_URL=192.168.0.110:8096 - JELLYFIN_APIKEY=c9fae8756fbf481ebd9c5bb56b - JELLYFIN_USER=MyUser // highlight-start # applies spotify Stage to preCompare of Jellyfin source - JELLYFIN_TRANSFORMS=spotify // highlight-end
# 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```
</details>
To use the configured ENV stage in [Rules and Hooks](#rules-and-hooks) with File/AIO config either [omit `name` or specify it as `"name": "MSEnv"`.](/configuration/transforms#stage-names-and-default-stages)
## 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**](#searching) Spotify using parts of your Scrobble as queries* [**Ranking**](#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**.](/configuration/transforms/#flow-control) 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:
```json5// ..."defaults": { "searchOrder": ["isrc", "basic"] }```
<details>
<summary>ISRC (`isrc`)</summary>
If your Scrobble data contains an [ISRC](https://musicbrainz.org/doc/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](#ranking) against your scrobble's existing album/artist data to pick the best candidate.
:::tip[ISRC Matches Skip the Score Threshold]
If an ISRC search finds results then filtering results by [score](#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`.
:::
</details>
<details>
<summary>Album, Artist, and Title Fields (`basic`)</summary>
Searches Spotify using any/all available text fields from your scrobble: Album, primary Artist, and Title. Results are always [ranked using fuzzy matching](#ranking) since Spotify's text search does not otherwise guarantee an accurate/confident result.
</details>
:::tip[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](#ranking)) then the stage is marked as [**failed** (`onFailure`) for **Flow Control**](/configuration/transforms/#flow-control).
A failed Stage never modifies your play data unless you eplicitly configure it to. See [Failed and Skipped Stages](/configuration/transforms/#flow-control) for details.
:::
<DetailsAdmo type="tip" summary="Biasing Search Results with market and locale">
`market` (an [ISO 3166-1 alpha-2 country code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)) can be set to bias/limit search results to what is available in a specific market:
<details>
<summary>Example</summary>
```json5{ "type": "spotify", "name": "MySpotify", "data": {/* */}, "defaults": { // ... "market": "US" }}```
</details>
`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:
<details>
<summary>Example</summary>
```json5{ "type": "spotify", "name": "MySpotify", "data": {/* */}, "defaults": { // ... "locale": "en_US" }}
```</details>
:::tip[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.
:::
</DetailsAdmo >
### 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.
```json5{ // ... "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):
```json5// ..."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`:
```json5// ..."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](/configuration/transforms#caching) to reduce API calls, improve transform performance, and reduce memory usage when using this stage.
### Using Partial Match
Use [Rules](#rules-and-hooks) 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](/configuration/clients) or other Stages to leverage them.
:::note
With these Rules IDs are added to the title, album, and artists your Play already has. See [how `meta` combines with other Rules](/configuration/transforms#stage-rules) to learn how IDs are matched to existing artists and what happens to existing IDs when `meta` is `false`.
:::
<details>
<summary>Example</summary>
```json5 title="subsonic.json"[ { "name": "MySubsonic", "data": { /* ... */}, "options": { "playTransform": { "preCompare": [ { "type": "spotify", "name": "MySpotify", "title": false, "artists": false, "album": false, "albumArtists": false, "meta": true } ] } } }]```
</details>
## Logging
If Spotify is not returning matches, or the resulting enhanced Scrobble is not what you expected, enable [**Debug Mode**](/configuration#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](#rules-and-hooks), as described for the [Musicbrainz Stage](/configuration/transforms/musicbrainz#logging).