feat(metadata): implement single-track metadata editor API (§A7/§1H)
Adds inline AcoustID match-finding (multiple ranked candidates via
lookup_all) and PUT /tracks/{id}/metadata for manual edits, resolving
artist/album and setting metadata_status=manual. Extends TrackOut with
genre/year/track_number.
This commit is contained in:
@@ -162,6 +162,29 @@ class MetadataEnrichmentService:
|
||||
return "No metadata match: AcoustID lookup is unavailable (no API key)."
|
||||
return "No metadata match found in tags or AcoustID."
|
||||
|
||||
async def find_matches(self, track_id: uuid.UUID) -> list[RecordingMatch]:
|
||||
"""AcoustID candidates for the metadata editor's match picker (§A7).
|
||||
|
||||
Read-only — unlike :meth:`enrich`, never touches the track. Runs
|
||||
inline (single track, user-triggered) rather than via the worker.
|
||||
Degrades to ``[]`` whenever fingerprinting/AcoustID is unavailable or
|
||||
the file can't be read, same as the enrichment pipeline.
|
||||
"""
|
||||
track = await self._tracks.get_by_id(track_id)
|
||||
if track is None:
|
||||
return []
|
||||
if not self._acoustid.is_available() or not self._fingerprinter.is_available():
|
||||
return []
|
||||
try:
|
||||
async with self._storage.as_local_path(track.storage_uri) as path:
|
||||
fingerprint = await self._fingerprinter.calculate(path)
|
||||
if fingerprint is None:
|
||||
return []
|
||||
return await self._acoustid.lookup_all(fingerprint)
|
||||
except Exception:
|
||||
log.warning("find_matches_failed", track_id=str(track_id))
|
||||
return []
|
||||
|
||||
async def _read_local(self, storage_uri: str) -> AudioTags | None:
|
||||
try:
|
||||
async with self._storage.as_local_path(storage_uri) as path:
|
||||
|
||||
Reference in New Issue
Block a user