Repeatable -x/--exclude NAME skips any artist- or source-level folder whose name
matches (case-insensitive) when walking the library, so hand-curated folders like
/media/music/Unsorted or .../playlists are left untouched. Threaded through
_iter_source_files -> repair_library / retag_library_from_path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Offline re-tag of artist/title from the artist folder + filename: strips
(Official Video)/(Lyrics)-style decorations and trailing [id], and treats an
'Artist - Title' filename as authoritative (recovering the real artist for
music videos filed under a channel name). Overwrites artist/title only; leaves
album/year. Honors --dry-run.
Refactors the source-folder walk into _iter_source_files, shared by --repair
and --retag-from-path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
--repair was clobbering good tags and erroring on real libraries:
- Validate the parsed id per source (YouTube 11-char, SoundCloud numeric) so
junk ids from bracketed descriptors ([Official Video]) are skipped, not queried.
- Skip files whose source returns no real music metadata (no album/year, e.g.
music videos) instead of overwriting clean tags with channel/decorated titles.
- Year from release info only (sane 1000-2100), never upload_date (which gave
wrong years for old songs and bogus values like 6577).
- album/year are authoritative; artist/title are fill-missing-only (no clobber).
Also: download_single now uses the first artist for the folder (matching the
search/playlist paths) so single-URL downloads stop creating multi-artist dirs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Walks <root>/<artist>/<source>/ (known yt-dlp source folders only; skips Lidarr
album dirs), re-queries each file's source by the [id] in its filename, and fixes
tags (album/year/artist/title) via mutagen. Honors --dry-run for preview. CLI-only
(not the REST API). Fixes downloads that landed with missing album / wrong year.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SoundCloud sets (and similar) return flat-playlist entries without per-track
artist/title. When a track Hit has no artist, download via an output template
(-o <root>/%(artist,uploader,channel)s/<source>/...) so yt-dlp places the file
under the real artist instead of "Unknown Artist". yt_download gains an optional
outtmpl mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generalize URL handling beyond YouTube to any yt-dlp-supported site
(SoundCloud, Bandcamp, etc), single tracks and playlists/sets/albums.
- probe_url(): one yt-dlp --flat-playlist probe classifies playlist vs track
and returns per-entry Hits; YouTube playlists still use ytmusicapi.
- _track_url(): YouTube tracks keep the music.youtube album-art URL; other
platforms download via their native entry URL (no more videoId reconstruction).
- Per-source folders: <root>/<artist>/<extractor>/ (soundcloud/bandcamp/youtube)
instead of hardcoded youtube; download_single derives source from metadata.
- download_hits() downloads pre-probed Hits; API probes once and passes hits
into the job closure. Replaces YouTube-only is_playlist_url/expand_playlist.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add musicbrainz_best_album() that resolves an artist+track pair to its
best studio album via the MusicBrainz search API, with a 1 req/sec
courtesy rate-limiter. Prefers plain studio albums over compilations,
singles, and live releases; falls back to any release group when no
studio album is found. Never raises — returns None on any failure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Live end-to-end test surfaced two bugs in youtube tagging:
- `--replace-in-metadata artist .* NAME` matched twice and doubled the
artist tag (e.g. "SLVMLORDSLVMLORD"). Anchor with ^.*$ to match once.
- Use only the first artist when several are present (SLVMLORD, not
"SLVMLORD, Travis Bradley, ...") for both the embedded tag and the
spoken/echoed API messages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>