Backfill podcast covers for episodes already on disk

A podcast's episode cover was only fetched right after the feed sync downloaded that
episode, so anything placed by hand (or downloaded before the og:image fallback
existed) never got one - in Wissen macht Ah only the three newest episodes had their
own art. The sync now fetches the cover of every on-disk episode in the feed window
that lacks a sidecar image. One that can't be found or fetched is recorded in
.missing-covers.json and left alone for the same 7-day backoff as failed downloads,
so a feed without per-episode art doesn't cost a page fetch per episode every poll.

The scanner's per-episode fingerprint now includes the sidecar cover, so one added
after the episode was first scanned invalidates its cached entry instead of being
ignored until the index is rebuilt. Episodes without a sidecar fingerprint exactly
as before, so existing caches stay valid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 21:53:06 +02:00
parent 0f08e1d16c
commit 5af5c54c62
4 changed files with 181 additions and 28 deletions

View File

@@ -284,6 +284,25 @@ async def test_an_episodes_sidecar_cover_wins_over_the_shows_shared_one(config_d
assert red < 60 and blue > red, "falls back to the show's shared cover"
async def test_a_sidecar_cover_added_later_replaces_the_cached_one(config_dir: Path) -> None:
"""`podcast_feeds.py` backfills covers for episodes already on disk - the episode's
audio is untouched, so only the sidecar can tell the cache its entry is stale."""
from PIL import Image
podcast = config_dir / "music" / "Kinderpodcasts" / "Wissen macht Ah"
Image.new("RGB", (32, 32), (10, 20, 30)).save(podcast / "cover.jpg")
first = album_named(await build(config_dir), "Alt")
alt_audio = first.tracks[0].path
Image.new("RGB", (32, 32), (200, 40, 30)).save(alt_audio.with_suffix(".jpg"))
second = album_named(await build(config_dir), "Alt")
assert second.cover is not None
with Image.open(second.cover) as image:
red, green, _blue = image.convert("RGB").getpixel((0, 0)) # type: ignore[misc]
assert red > 150 and green < 90, "the new sidecar should replace the shared cover"
# --------------------------------------------------------------------------- cache