Initial implementation of Music Metadata Enricher
AI-powered per-album pipeline: scan → local hints → MusicBrainz/Discogs/Claude resolve → cover art → interactive or auto review → tag write + rename + report. All external dependencies optional; 17/17 unit tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b273052f68
commit
f7cf520dbe
8 changed files with 1748 additions and 0 deletions
79
models.py
Normal file
79
models.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
|
||||
AUDIO_EXTENSIONS = {
|
||||
".mp3", ".flac", ".m4a", ".aac", ".ogg", ".opus",
|
||||
".wav", ".wma", ".aiff", ".ape",
|
||||
}
|
||||
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".gif"}
|
||||
TRACKLIST_EXTENSIONS = {".txt", ".htm", ".html", ".nfo"}
|
||||
PLAYLIST_EXTENSIONS = {".m3u", ".m3u8", ".pls"}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ScannedFile:
|
||||
path: Path
|
||||
kind: str # "audio" | "image" | "tracklist" | "playlist" | "other"
|
||||
|
||||
|
||||
@dataclass
|
||||
class AlbumScan:
|
||||
album_dir: Path
|
||||
audio_files: List[Path] = field(default_factory=list)
|
||||
image_files: List[Path] = field(default_factory=list)
|
||||
tracklist_files: List[Path] = field(default_factory=list)
|
||||
other_files: List[Path] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrackHints:
|
||||
path: Path
|
||||
track_number: Optional[int] = None
|
||||
disc_number: Optional[int] = None
|
||||
title: Optional[str] = None
|
||||
artist: Optional[str] = None
|
||||
duration: Optional[float] = None
|
||||
existing_tags: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AlbumHints:
|
||||
album_dir: Path
|
||||
dir_artist: Optional[str] = None
|
||||
dir_album: Optional[str] = None
|
||||
dir_year: Optional[str] = None
|
||||
tracklist_text: Optional[str] = None # merged text from all tracklist files
|
||||
cover_images: List[Path] = field(default_factory=list)
|
||||
tracks: List[TrackHints] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrackProposal:
|
||||
path: Path
|
||||
title: str
|
||||
artist: str
|
||||
track_number: Optional[int]
|
||||
disc_number: Optional[int]
|
||||
new_filename: Optional[str] = None # only set when --rename is active
|
||||
mbid: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class AlbumProposal:
|
||||
album_dir: Path
|
||||
album: str
|
||||
albumartist: str
|
||||
date: Optional[str]
|
||||
genre: Optional[str]
|
||||
label: Optional[str]
|
||||
mbid: Optional[str] # MusicBrainz release ID
|
||||
cover_path: Optional[Path] # resolved local or downloaded cover
|
||||
cover_source: Optional[str] # "local" | "musicbrainz" | "discogs"
|
||||
tracks: List[TrackProposal]
|
||||
confidence: float
|
||||
sources: List[str] = field(default_factory=list)
|
||||
notes: List[str] = field(default_factory=list)
|
||||
Loading…
Add table
Add a link
Reference in a new issue