Include albumartist in filename; remove Claude API from LLM chain

Filename schema now: TT - AlbumArtist - TrackArtist - Title when albumartist
differs from track artist (e.g. pianist vs. composer). Identical artist → old
two-part format unchanged.

metadata_resolver: removed Claude API fallback entirely from _claude_resolve.
Chain is now Ollama (local, free) → OpenRouter (DeepSeek V3, cheap) only.

music_enricher: updated status line and use_claude flag accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-04-28 22:22:10 +02:00
commit 8bd48cf166
3 changed files with 13 additions and 24 deletions

View file

@ -36,12 +36,16 @@ def _safe_name(s: str) -> str:
return _SAFE_RE.sub("_", s).strip(". ")
def _proposed_filename(proposal: TrackProposal, ext: str) -> str:
def _proposed_filename(proposal: TrackProposal, ext: str, albumartist: str = "") -> str:
tn = f"{proposal.track_number:02d}" if proposal.track_number else "00"
prefix = f"{proposal.disc_number}-{tn}" if proposal.disc_number and proposal.disc_number > 1 else tn
artist = _safe_name(proposal.artist or "Unknown")
track_artist = _safe_name(proposal.artist or "Unknown")
aa = _safe_name(albumartist)
title = _safe_name(proposal.title or "Unknown")
return f"{prefix} - {artist} - {title}{ext}"
# Include albumartist when it differs from track artist (e.g. pianist vs. composer)
if aa and aa.casefold() != track_artist.casefold() and aa.casefold() not in ("various artists", "unknown"):
return f"{prefix} - {aa} - {track_artist} - {title}{ext}"
return f"{prefix} - {track_artist} - {title}{ext}"
def backup_file(path: Path, backup_dir: Path) -> bool:
@ -194,7 +198,7 @@ def execute_album(
cover_embedded = True
if do_rename:
new_name = _proposed_filename(tp, tp.path.suffix)
new_name = _proposed_filename(tp, tp.path.suffix, proposal.albumartist or "")
candidate = tp.path.parent / new_name
if candidate != tp.path:
try:

View file

@ -272,8 +272,8 @@ def _resolve_via_openrouter(hints: AlbumHints, partial: Dict) -> Optional[Dict]:
def _claude_resolve(hints: AlbumHints, partial: Dict) -> Optional[Dict]:
"""
Reihenfolge: Ollama (lokal, kostenlos) OpenRouter (günstig) Claude API.
Ollama wird versucht wenn OLLAMA_HOST erreichbar; kein Key nötig.
Reihenfolge: Ollama (lokal, kostenlos) OpenRouter (günstig).
Claude API wird bewusst nicht genutzt (zu teuer).
"""
# 1. Ollama lokal (bevorzugt — kostenlos, RTX 3090)
result = _resolve_via_ollama(hints, partial)
@ -286,21 +286,6 @@ def _claude_resolve(hints: AlbumHints, partial: Dict) -> Optional[Dict]:
if result:
return result
# 3. Claude API als letzter Fallback
if not HAS_ANTHROPIC or not ANTHROPIC_API_KEY:
return None
try:
client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
prompt = _build_resolve_prompt(hints, partial)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=300,
messages=[{"role": "user", "content": prompt}],
)
text = message.content[0].text.strip()
return _parse_json_response(text)
except Exception as e:
print(f" ⚠️ Claude-API-Fehler: {e}", file=sys.stderr)
return None

View file

@ -96,7 +96,7 @@ def process_album(
hints,
use_fingerprint=not args.no_fingerprint,
use_api=not args.no_api,
use_claude=bool(os.getenv("ANTHROPIC_API_KEY")),
use_claude=not args.no_api,
)
# Cover art
@ -235,8 +235,8 @@ def main() -> None:
sys.exit(1)
print(f"🎵 {len(album_dirs)} Album-Verzeichnisse gefunden.")
if os.getenv("ANTHROPIC_API_KEY"):
print("🤖 Claude API aktiv.")
if os.getenv("OLLAMA_HOST") or True: # Ollama always attempted
print("🤖 LLM-Resolve: Ollama → OpenRouter (kein Claude)")
if not args.no_api:
print("🔍 MusicBrainz-Lookup aktiv.")
if args.dry_run: