0bb752f582
Resolve, store and serve album cover art.
Sources (tag-first, mirroring enrichment): embedded artwork extracted
offline via mutagen (ID3 APIC / FLAC+OGG Picture / MP4 covr), then Cover
Art Archive by release-group MBID as a network fallback. Resolution runs
inside MetadataEnrichmentService after album resolution, only when the
album has no cover yet (idempotent, never overwrites), and is best-effort
so a cover failure never affects enrichment status.
- CoverArt value object + CoverArtExtractor/CoverArtProvider ports
- MutagenCoverExtractor + CoverArtArchiveClient adapters
- AcoustID parser now captures release_group_mbid
- Covers stored via FileStorage at covers/{album_id}.{ext} (local + S3)
- AlbumRepository.set_cover_path
- Serve real covers: GET /api/v1/albums|tracks/{id}/cover (StreamUser,
?token=), Subsonic getCoverArt (placeholder fallback)
- has_cover flag on AlbumOut/TrackOut
- coverart_enabled / coverart_base_url settings
- tests: cover resolution units + release_group parse + DB-backed
test_cover_api.py (139 green via make test-api)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
29 lines
823 B
Python
29 lines
823 B
Python
"""Domain entities and value objects — pure, framework-free."""
|
|
|
|
from app.domain.entities.album import Album
|
|
from app.domain.entities.cover import CoverArt
|
|
from app.domain.entities.history import PlayHistoryEntry
|
|
from app.domain.entities.like import Like
|
|
from app.domain.entities.metadata import AudioTags, Fingerprint, RecordingMatch
|
|
from app.domain.entities.playlist import Playlist
|
|
from app.domain.entities.storage import ObjectStat
|
|
from app.domain.entities.track import Artist, Track
|
|
from app.domain.entities.user import Credentials, SubsonicCredentials, User
|
|
|
|
__all__ = [
|
|
"Album",
|
|
"Artist",
|
|
"AudioTags",
|
|
"CoverArt",
|
|
"Credentials",
|
|
"Fingerprint",
|
|
"Like",
|
|
"ObjectStat",
|
|
"PlayHistoryEntry",
|
|
"Playlist",
|
|
"RecordingMatch",
|
|
"SubsonicCredentials",
|
|
"Track",
|
|
"User",
|
|
]
|