- Python 100%
|
|
||
|---|---|---|
| custom_components/music_own | ||
| .gitattributes | ||
| .gitignore | ||
| hacs.json | ||
| README.md | ||
Music Own
A Home Assistant custom integration that migrates your Music Assistant playlists (Spotify, Apple Music — including multiple Apple Music accounts — or anything else MA connects to) into native Plex playlists, and maintains a Qobuz buy-list for the tracks you don't own yet — so you own your music instead of renting it.
The only manual step in the whole system is clicking a buy link and checking out. Nothing here downloads, rips, or circumvents anything: it reads catalogues, writes Plex playlists you own, and shows you what to purchase.
Model
- Owned track (found in your Plex library) → added to a native Plex playlist of the same name, in the same order as the source playlist.
- Unowned track → recorded in a durable SQLite
wantedstore with a Qobuz album URL, surfaced onsensor.music_to_buy(state = count,attributes.links= items). - Each sync promotes any
wantedtrack that has since appeared in Plex and drops it from the buy-list. You never hand-edit the list. - Plex playlist content is authoritative to the source: every sync makes a Plex playlist's track set and order match its Music Assistant source exactly — tracks removed from the source playlist are removed from the Plex playlist too, and tracks are reordered to match. The Plex API calls to reorder are skipped entirely when a playlist is already in the right order, so a normal sync where nothing changed does no extra work.
- The playlist itself is never deleted. If a source playlist disappears from Music Assistant entirely (provider disconnected, playlist deleted upstream), its Plex playlist is simply left untouched — not emptied, not removed. Only playlists that are still present in this run's Music Assistant fetch get their contents reconciled.
Multiple Apple Music accounts
Music Assistant already distinguishes separate logins of the same provider (e.g. two
Apple Music accounts) by provider instance, not just provider type — this integration
reads and writes through that distinction, so nothing extra needs to be configured to
mirror playlists from several Apple Music accounts at once. Each playlist keeps its
originating account attached (visible in provider on the per-playlist sensor
attributes), and playlist names only need to be unique per account, not globally.
Each playlist's tracks are fetched using the specific account's own mapping, not Music Assistant's generic "pick whichever mapping comes first" fallback — that fallback has no account awareness at all and can, in a multi-account setup, try to fetch one account's playlist using a different account's session, which Apple correctly rejects with a 403. Resolving the mapping ourselves avoids that entirely.
Account sessions expire — sometimes much sooner than the documented 180 days
Apple Music's Music-User-Token is meant to last ~180 days per Apple's own docs, but
it's created by the browser/device, not the app, and Apple's own developer relations
guidance is that it's meant to be re-derived each session — not persisted long-term by
a backend. In practice this means a token stored server-side (exactly what Music
Assistant does) can start failing with 403s after just a few days, and this is a
widely-reported discrepancy independent of Music Assistant or this integration.
There's no fix for this beyond re-authenticating that account's provider in Music
Assistant's settings when it happens.
To make that obvious instead of something you have to notice in the logs: when every playlist attempted for one account fails in a sync (≥2 attempted, all failed — a strong signal of a dead session rather than one odd playlist), Music Own raises a persistent Home Assistant notification naming that account. It clears automatically once a following sync succeeds for that account again.
If you hit the "wrong account got re-authenticated" symptom (completing the
re-auth flow for one account ends up granting a token for a different account you
also have signed in), it's very likely because Music Assistant's browser-based sign-in
reuses whatever Apple ID web session is already active in that browser — try a private/
incognito window, or sign out of Apple ID in that browser first. Music Assistant's
"Manual token" option (extracting the media-user-token cookie directly from
music.apple.com's DevTools) sidesteps this ambiguity because you can visually confirm
which account is signed in on that page before copying the token — but per Music
Assistant's own docs it's the same kind of token with the same nominal 180-day
lifetime, so it likely won't change how often you need to re-authenticate.
Missing-track destinations: Qobuz buy-list and "Plex Wish List"
There are two ways to see what you're missing:
-
Qobuz buy-list (
sensor.music_to_buyand friends) — a durable list with buy links, purchase price comparisons, and hi-res availability. Push it to your Qobuz favorites on demand with the Push buy-list to Qobuz wishlist button. -
"Plex Wish List" — an Apple Music playlist, on whichever one Apple Music account you designate, that this integration keeps stocked automatically on every run: every track currently missing from Plex is added to it. This gives you a normal, playable Apple Music playlist of everything you're missing, no clicking required.
Create a playlist named "Plex Wish List" (configurable via
wishlist_playlist_name) on the Apple Music account of your choice inside Music Assistant, and add at least one track to it before your first sync — Music Assistant does not appear to index empty playlists into its library at all, so a playlist with zero tracks is invisible toget_library_playlists()and this integration will never find it (it'll just look like it isn't there; nothing will be logged about it either, since it never shows up in the first place). Once it has a track, it stays indexed even after this integration clears it back to your actual gaps. That playlist is automatically excluded from the normal MA → Plex mirror — it is a write-only destination for gaps and is never itself turned into a Plex playlist. If the name isn't found on any Apple Music account, this is logged as a warning each sync (not silent) and the rest of the sync (Plex mirror, Qobuz buy-list) proceeds unaffected; if it's found on more than one account, the first match is used and a warning asks you to rename the others.One-way sync, by design of Apple Music's API: this integration only ever adds to "Plex Wish List". Music Assistant's Apple Music provider unconditionally rejects removing tracks from a playlist ("Removing tracks from playlists is not supported by the Apple Music API. Make sure to delete them using the Apple Music app." — from MA's own source). So once a track you were missing gets bought and lands in Plex, it is not auto-removed from "Plex Wish List" —
sensor.plex_wish_list'sstale_needs_manual_removalattribute tells you how many tracks are sitting there that you can now delete by hand in the Apple Music app.Known soft spot: a missing track's identity comes from whatever provider originally hosted its source playlist (Spotify, Apple Music, ...), not necessarily the Apple Music account that owns "Plex Wish List". Whether Music Assistant can resolve a Spotify-sourced track into an Apple-Music-native playlist entry hasn't been verified against a live server yet — check
sensor.plex_wish_list'sfailed_last_syncattribute and the logs after your first sync.
Why a snapshot store
Music Assistant playlists from a provider are a live view: disconnect the provider and they vanish. So a sync snapshots the desired tracks into SQLite before you disconnect a provider; the Plex playlists and the buy-list then persist.
Install
- Add this repo to HACS as a custom repository (category: Integration), install, restart HA.
- Settings → Devices & Services → Add Integration → Music Own. Enter the values below. Credentials are stored by HA; they are never placed in code or env.
- Add a dashboard card (see below).
Getting the values
- MA URL: the WebSocket URL of your Music Assistant server, e.g.
http://homeassistant.local:8095. - MA token: Music Assistant requires an access token (introduced in server schema
version 31).
- Open the Music Assistant web UI (same host, port 8095).
- Go to Settings → Users → click your user.
- Scroll to Access Tokens → Create token, give it a name (e.g. "Music Own").
- Copy the token immediately — it is only shown once.
- Plex URL: base URL of your Plex server, e.g.
http://homeassistant.local:32400. - Plex token:
X-Plex-Tokenfrom any authenticated Plex request (sign in toplex.tv, open DevTools → Network, filter forplex.tvrequests and look for theX-Plex-Tokenheader or query param). - Qobuz app_id and Qobuz token: the Qobuz API requires both your app ID
and a user session token. Get them in one step:
- Open the Qobuz web player and log in.
- Open DevTools (F12) → Network tab → filter for
api.json. - Click any request to
api.json/0.2/.... - In Request Headers copy
X-App-Id→ paste as Qobuz app_id. - In Request Headers copy
X-User-Auth-Token→ paste as Qobuz token. Nothing is downloaded; the integration only reads the Qobuz catalogue.
- Wishlist playlist name (optional, default
Plex Wish List): the exact name of the Apple Music playlist that should receive missing tracks. Leave it as the default unless you have a naming conflict.
Runbook (one-time migration)
- Connect Spotify and Apple Music (any number of accounts) in Music Assistant.
- Call service
music_own.migrate. Owned tracks land in Plex playlists in source order; gaps go to the buy-list and to "Plex Wish List" if configured. - Work through
sensor.music_to_buy: click each Qobuz link, buy, let the files land in your Plex music folder. The next scheduled sync (or a manualmigrate/reconcilecall) moves each newly-owned track into its Plex playlist automatically. It stays in "Plex Wish List" though (Apple Music's API doesn't support removing playlist tracks) — clear it out by hand periodically usingsensor.plex_wish_list'sstale_needs_manual_removalcount as your guide. - When a playlist shows 0 gaps it's fully in Plex. Disconnect Spotify (and Apple Music if you like) — Plex playlists and buy-list persist.
Steady state
Create new playlists in MA (Apple Music is fine for instant play). Every sync mirrors them into Plex and buys the gaps, same as above. If you reorder or trim a source playlist, the next sync brings the matching Plex playlist back in line automatically — no manual playlist editing needed on the Plex side.
Cleanup (careful)
music_own.cleanup deletes MA-native playlists that are fully present in Plex.
It refuses to delete provider-backed playlists (Spotify/Apple Music/etc.), because
deleting those through MA can propagate the delete to the streaming service. For
provider playlists, "cleanup" = remove the provider from MA. This service only ever
touches Music Assistant's own library entries — it never deletes a Plex playlist.
Sensors and buttons
All entities are grouped under the Music Own device.
| Sensor | State | Key attributes |
|---|---|---|
sensor.music_to_buy |
items to buy | links, albums_count, tracks_count, tracks_wanted_raw, last_scan |
sensor.albums_to_buy |
albums to buy | downloadable/not-downloadable/unknown counts |
sensor.tracks_to_buy |
individual tracks to buy | downloadable/not-downloadable/unknown counts |
sensor.hi_res_available_to_buy |
buy-list items with a hi-res Qobuz version | downloadable/not-downloadable/unknown counts |
sensor.ownership |
% of tracks owned | tracks_owned, tracks_total, tracks_missing |
sensor.last_sync |
timestamp of last completed sync | — |
sensor.playlists_to_remove |
streaming playlists fully replicated in Plex | playlists (name + provider + track count), last_scan |
sensor.playlists_pending |
playlists with ≥1 track still to buy | playlists (per-playlist breakdown), last_scan |
sensor.tracks_owned |
tracks owned in Plex | tracks_total, percent_owned, playlists (per-playlist breakdown), accounts (per-account health, see below), last_scan |
sensor.plex_wish_list |
tracks currently in the "Plex Wish List" Apple Music playlist | found, account, added_last_sync, failed_last_sync, stale_needs_manual_removal, last_scan |
Buttons: Sync now (manual full sync), Reset buy-list database (wipes the
wanted table and re-syncs from scratch), Push buy-list to Qobuz wishlist (adds
the current buy-list to your Qobuz favorites).
Dashboard card
type: markdown
content: >
## 🛒 {{ states('sensor.music_to_buy') }} items to buy
_{{ state_attr('sensor.music_to_buy', 'albums_count') }} albums ·
{{ state_attr('sensor.music_to_buy', 'tracks_count') }} individual tracks_
{% for it in state_attr('sensor.music_to_buy', 'links') or [] %}
- [{{ it.artist }} – {{ it.title }}]({{ it.qobuz_url }}){% if it.is_album %} _(album · {{ it.track_count }} tracks)_{% elif it.album %} _({{ it.album }})_{% endif %}
{% endfor %}
---
**{{ states('sensor.tracks_owned') }} / {{ state_attr('sensor.tracks_owned', 'tracks_total') }} tracks owned
({{ state_attr('sensor.tracks_owned', 'percent_owned') }}%)**
{% for pl in state_attr('sensor.tracks_owned', 'playlists') or [] %}
- {{ pl.name }}: {{ pl.tracks_owned }}/{{ pl.tracks_total }}{% if pl.complete %} ✓{% endif %}
{% endfor %}
{% if states('sensor.playlists_to_remove') | int > 0 %}
---
**{{ states('sensor.playlists_to_remove') }} playlist(s) ready to remove from streaming:**
{% for pl in state_attr('sensor.playlists_to_remove', 'playlists') or [] %}
- {{ pl.name }} _({{ pl.provider }}, {{ pl.tracks }} tracks)_
{% endfor %}
{% endif %}
---
**Plex Wish List:** {{ states('sensor.plex_wish_list') }} track(s)
{% if not state_attr('sensor.plex_wish_list', 'found') %}_(playlist not found this run)_{% endif %}
Clicking a link opens Qobuz in a new tab in the browser you're already logged into. (HA can't auto-open tabs — a click is required — which is exactly the manual buy step.)
Known soft spots
- The Music Assistant client method names in
engine.py(music.get_library_playlists,music.get_playlist_tracks,music.remove_playlist) and the ISRC field are markedTODO/VERIFY— confirm them against your MA:8095/api-docs. Without ISRC the matcher falls back to artist+title. MATCH_THRESHOLD(default 85) trades false matches against missed ones. If the first sync reports far more gaps than expected, lower it or improve Plex tags.- "Plex Wish List" cross-provider track resolution is unverified — see above.