music-assistant-server
Branch:dev-lidarr-plugin
Commit Details
Apple Music: don't report purchase-only library items as available (#6123)
# What does this implement/fix?
`parse_track()` and `parse_album()` both computed availability as
`is_library_item or has_play_params`. The left side short-circuits, so
**any** item in the user's library was reported `available: True`
regardless of whether Apple would actually serve it. Playback then fails
with `Failed to get song stream metadata: None`.
Two distinct groups are affected, and requiring `playParams` only fixes
the first:
1. Items Apple has **withdrawn** -> no usable `playParams` at all.
2. **Purchase-only** items -> these *do* have `playParams`, but carry a
`purchasedId` with no `catalogId` (iTunes purchases, and the 2014 U2
*Songs of Innocence* giveaway, which Apple also records as a purchase).
Requiring `playParams` changes nothing for these.
This implements the rule agreed with @MarvinSchenkel in the issue.
Usable `playParams`, and not a `purchasedId` without a `catalogId`, as a
shared `_is_available()` helper used by both parsers. **Uploads carry
neither marker, so they stay available**, which is the case #4108 was
protecting. That was the deciding reason for this rule over the simpler
`has_catalogId and has_playParams`, which would have marked uploads
unavailable.
**Related issue:**
- https://github.com/music-assistant/support/issues/6032
## Measured against a real library
| | items | changed | direction |
|---|---|---|---|
| `me/library/songs` | 1423 | **27** | all `True` → `False` |
| `me/library/albums` | 186 | **0** | — |
The 27 split 3 withdrawn / 24 purchase-only, and **nothing changes in
the other direction** means
no item becomes newly available. The baseline for that comparison was
produced by calling the installed `parse_track()` / `parse_album()`
themselves, so it is the real current behaviour rather than a
reimplementation of it.
I don't own any uploads, so I could not exercise that path directly —
@MarvinSchenkel offered to test an uploaded track once this was up,
which is the one case worth confirming before merge.
## Two things to flag
**This does not fix the album-level symptom I reported in the issue.** I
said `parse_album()`
looked to have the same problem, and it does share the buggy expression.
But Apple exposes no
purchase marker on a library album. My purchased album `l.ZGZGdjd`
returns:
```json
{"id": "l.ZGZGdjd", "isLibrary": true, "kind": "album"}
```
No `purchasedId`, no `catalogId`, and no catalog twin. So the rule
cannot fire there, and that album still reports `available: True` even
though every track on it is a purchase. `parse_album` is included here
only so the two functions do not diverge; detecting unplayable albums
needs a different signal and I did not want to guess at one. Happy to
take direction if you have a view.
**`available` had no test coverage at all.** `test_parsers.py` was 1011
lines with zero assertions on it, and all its `library-songs` /
`library-albums` fixtures carry `playParams`, which is why this went
unnoticed. Coverage added below.
## Types of changes
- [x] Bugfix (non-breaking change which fixes an issue) — `bugfix`
- [ ] New feature (non-breaking change which adds functionality) —
`new-feature`
- [ ] Enhancement to an existing feature — `enhancement`
- [ ] New music/player/metadata/plugin provider — `new-provider`
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected) — `breaking-change`
- [ ] Refactor (no behaviour change) — `refactor`
- [ ] Documentation only — `documentation`
- [ ] Maintenance / chore — `maintenance`
- [ ] CI / workflow change — `ci`
- [ ] Dependencies bump — `dependencies`
## Tests
Seven tests added to `tests/providers/apple_music/test_parsers.py`:
*Cover the fix* — reverting `parsers.py` alone fails these three:
- purchase-only track (`purchasedId`, no `catalogId`) → unavailable
- track with unusable `playParams` → unavailable
- album with unusable `playParams` → unavailable
*Guard against over-reach* — these pass before and after, deliberately:
- **upload** (`playParams`, neither marker) → stays available
- purchase that also has a `catalogId` → stays available
- plain catalog song → stays available
- library album with usable `playParams` → stays available, documenting
the gap above
Full Apple Music provider suite: **103 passed**. `ruff check`, `ruff
format --check` and `mypy` are clean on both changed files.
## Checklist
- [x] The code change is tested and works locally.
- [x] `pre-commit run --all-files` passes.
- [x] `pytest` passes, and tests have been added/updated under `tests/`
where applicable.
- [ ] For changes to shared models, the companion PR in
`music-assistant/models` is linked.
- [ ] For changes affecting the UI, the companion PR in
`music-assistant/frontend` is linked.
- [x] I have read and complied with the project's [AI
Policy](https://github.com/music-assistant/.github/blob/main/AI_POLICY.md)
for any AI-assisted contributions.
- [ ] I have [raised a PR against the documentation
repository](https://github.com/music-assistant/music-assistant.io/blob/main/CONTRIBUTING.md)
targeting the main or beta branch as appropriate.
Co-authored-by: anthonws <[email protected]>
Co-authored-by: Claude Opus 5 <[email protected]>
Co-authored-by: OzGav <[email protected]>Author
anthonws<[email protected]>
August 31, 2026 at 01:42 PM UTC
Committer
Marvin Schenkel<[email protected]>
September 4, 2026 at 07:55 AM UTC
132additions
8deletions
2files changed
Parent commits
Changes
2 files
+132-8
Changed Files (2)
music_assistant/providers/apple_music/
tests/providers/apple_music/
music_assistant/providers/apple_music/parsers.py
+2-8
tests/providers/apple_music/test_parsers.py
+130