Add album/track releases with audio analysis, AnalyzeAlbumRelease RPC, Docker path auto-resolution, release parsing decision tree

This commit is contained in:
Alexander
2026-05-09 23:16:59 +02:00
parent 1e8506f146
commit 2740585261
25 changed files with 1841 additions and 125 deletions
@@ -0,0 +1,193 @@
@startuml Release Parsing Decision Tree
skinparam ActivityBackgroundColor #f8f8f8
skinparam ActivityBorderColor #333333
skinparam DiamondBackgroundColor #fffde7
skinparam NoteBackgroundColor #e3f2fd
title Release Parsing Decision Tree
start
partition "1. Resolve Torrent Data" {
if (DownloadLink starts\nwith "magnet:?") then (yes)
:MagnetResolver.Resolve(magnetURI);
note right
DHT lookup via anacrolix/torrent
30s timeout, 15s early exit
if peers but none active
end note
if (Resolve succeeded?) then (yes)
:torrentData = resolved bytes;
else (no)
:fallback to **title-only parse**;
note right
parser.Parse(item.Title)
No torrent data available
No info_hash computed
end note
goto TitleOnlyParse
endif
else (HTTP link)
:downloadTorrentData(url);
note right
HTTP GET with 30s timeout
Expects .torrent file bytes
end note
if (Download succeeded?) then (yes)
:torrentData = downloaded bytes;
else (no)
:fallback to **title-only parse**;
goto TitleOnlyParse
endif
endif
}
partition "2. ParseTorrent (torrentData + metadata album)" {
partition "2a. Fill from Metadata Album" {
:Artist = album.Artists[0].Name;
:Album = album.Title;
:Year = album.ReleaseDate[:4];
:Type = album.AlbumType
(album/ep/single/compilation/...);
:Genres = album.Genres[].Name;
:Label = album.Label.Name;
:TrackCount = album.TotalTracks;
:ReleaseCount = album.TotalDiscs;
}
partition "2b. Fill from Torrent Data" {
:metainfo.Load(torrentData);
note right
Bencode decode via
anacrolix/torrent/metainfo
end note
if (Parse failed?) then (yes)
:Append to ParseErrors;
:Skip torrent analysis;
else (no)
:info = mi.UnmarshalInfo();
if (Unmarshal failed?) then (yes)
:Append to ParseErrors;
:Skip torrent analysis;
else (no)
:RawTitle = info.Name;
:InfoHash = SHA1(info dict);
if (info.Files is empty?\n(single-file torrent)) then (yes)
:ext = filepath.Ext(info.Name);
if (ext is audio?\n(.flac/.mp3/.aac/...)) then (yes)
:Format = audioExtensions[ext];
:AudioFileCount = 1;
:TotalAudioSize = info.Length;
else (no)
:Format = unknown;
endif
else (multi-file torrent)
:Iterate all files in torrent;
repeat
:file = next torrent file;
:ext = filepath.Ext(file.Path);
if (ext is audio?) then (yes)
:formatCounts[ext]++;
:formatSizes[ext] += file.Length;
:TrackNames += cleanTrackName(file);
note right
Strip leading "01. " or "1 - "
from filename
end note
elseif (ext is .jpg/.jpeg/.png?) then (yes)
:HasCoverArt = true;
elseif (ext is .cue?) then (yes)
:HasCueSheet = true;
elseif (ext is .log?) then (yes)
:HasRipLog = true;
endif
repeat while (more files?)
:Format = dominant format\n(most audio files);
:AudioFileCount = count of dominant;
:TotalAudioSize = sum of dominant;
endif
if (HasRipLog?) then (yes)
:Source = CD;
note right
.log file = EAC/XLD rip log
implies CD source
end note
endif
if (TrackCount == 0?) then (yes)
:TrackCount = AudioFileCount;
endif
endif
endif
}
partition "2c. Fill from Title (torrent name)" #f0f0f0 {
label TitleParsing
:title = info.Name (or item.Title for fallback);
if (title matches\n"(\\d{2,3})\\s*kbps"?) then (yes)
:Bitrate = matched value + " kbps";
endif
:Try hi-res patterns (in order):;
note right
1. "24Bit-96kHz" / "24 Bit / 48 kHz"
2. "FLAC 24-96" / "Flac 24-44"
3. "24Bit" (bit depth only)
First match wins.
end note
if (Hi-res pattern matched?) then (yes)
if (BitDepth still 0?) then (yes)
:BitDepth = matched group 1;
endif
if (SampleRate still 0\nand group 2 exists?) then (yes)
:SampleRate = matched × 1000;
endif
endif
if (title matches\n"\\[(CD|WEB|Vinyl|...)\\]"\nand Source still unknown?) then (yes)
:Source = matched value;
note right
CD, WEB, Vinyl/LP,
Cassette/MC, DVD,
Blu-Ray
end note
endif
if (title matches\nrip type pattern?) then (yes)
:RipType = matched value;
note right
vinyl rip, SACD-R,
HDCD, DSD, tape rip
end note
endif
}
:ParsedSuccessfully = (Artist != "" && Album != "");
if (not ParsedSuccessfully?) then (yes)
:ParseErrors += "missing artist or album";
endif
:Return Release;
stop
}
partition "3. Title-Only Parse (fallback)" #fff3e0 {
label TitleOnlyParse
:r = Release{RawTitle: item.Title};
note right
No torrent data available.
No InfoHash. No file analysis.
No TrackNames. No cover/cue/log.
Format stays **unknown**.
end note
goto TitleParsing
}
@enduml