Add album/track releases with audio analysis, AnalyzeAlbumRelease RPC, Docker path auto-resolution, release parsing decision tree
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user