v0.5.0: send album art to the Chromecast

The device is on a TV, so it should show the cover. play_media now carries
thumb=, which pychromecast folds into metadata["images"] — the field the
receiver paints full-screen. Verified against the real device: it fetches
both the audio and the artwork URL from us on every track change.

Album art lives in the audio file's tags rather than as a file of its own,
so TrackServer tokens now resolve to an _Asset that is either a path or a
blob held in memory. Audio and art get separate eviction rings so a cover
can't push out the previous track's audio while the device is still
fetching it; Range and HEAD work on both.

The image type is sniffed from the cover's magic bytes rather than trusted
from the tag — ID3 APIC mimes are routinely wrong or blank, and the
receiver silently drops an image whose declared type doesn't match its
content. Anything unrecognized is treated as "no cover".

Best-effort throughout: no art, junk where the art should be, or an
unreadable file all just play without a cover rather than failing the
load. Also sends albumArtist and trackNumber in the metadata.

tests/test_round29.py: 74 tests; 447 pass overall.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 22:06:27 -04:00
co-authored by Claude Opus 5
parent 087c4bf103
commit 6c746c3330
7 changed files with 217 additions and 33 deletions
+19
View File
@@ -1,5 +1,24 @@
## Done
### Round 30 (2026-08-13) — Album art on the cast device (v0.5.0)
The Chromecast is on a TV, so it should show the cover. `play_media` now
carries `thumb=`, which pychromecast folds into `metadata["images"]` — the
field the receiver paints full-screen. Verified against the real device: it
fetches both the audio and the artwork URL from us on every track change.
- [x] **`TrackServer` serves in-memory blobs** — album art lives in the audio
file's tags, not as a file of its own, so a token now resolves to an
`_Asset` that is either a path or bytes. Audio and art get **separate
eviction rings**, so a cover can't push out the previous track's audio while
a device is still fetching it. Range and HEAD work on both.
- [x] **`support.image_type_for`** sniffs the cover's type from its magic bytes
rather than trusting the tag — ID3 APIC mimes are routinely wrong or blank,
and the receiver silently drops an image whose type doesn't match.
- [x] **Best-effort throughout** — no cover, junk where the cover should be, or
an unreadable file all just play without art rather than failing the load.
- [x] Also sends `albumArtist` and `trackNumber` in the metadata.
### Round 29 (2026-08-13) — Cast to Chromecast (v0.4.0)
The Device menu is now **Connections**, with "Connect to Chromecast…" alongside