feat: generate 96x96 WebP thumbnails for artwork (#976)
* feat: generate 96x96 WebP thumbnails for track/album/playlist artwork
Full-resolution images (potentially megabytes) were served for 48px
display contexts. This adds thumbnail generation on upload, a storage
protocol for type safety, fixes the image delete key prefix bug, and
includes a backfill script for existing images.
- Add StorageProtocol for type-safe dependency injection
- Generate 96x96 WebP thumbnails via Pillow on image upload
- Add thumbnail_url column to tracks, albums, playlists
- Fix image delete key missing images/ prefix
- Add build_image_url() to consolidate URL construction
- Frontend falls back to image_url when thumbnail_url is null
- Backfill script: scripts/backfill_thumbnails.py
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: improve test_storage_types patterns
- Return mock client from factory instead of module-level global
- Replace inspect.getsource assertion with behavioral delete test
that verifies the actual key passed to head_object
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: move deferred imports to top-level, add generate_and_save tests
- Move BytesIO and generate_and_save imports to module level in
uploads.py, albums.py, lists.py, and metadata_service.py
- Restore accidentally deleted comments in metadata_service.py
- Add test coverage for generate_and_save success/failure paths
- Add thumbnail_url to Playlist frontend type
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
authored by