release / govulncheck (push) Successful in 21s
release / go (push) Failing after 33s
release / web (push) Successful in 56s
release / integration (push) Failing after 4m48s
release / android (push) Successful in 5m6s
release / Build signed APK (releases and dev) (push) Successful in 5m16s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
- internal/release: reads a release name (format, bitrate, bit depth and sample rate, source, year, video, discography) and judges it against the album wanted: the artist and title must be in it; video rips, discographies, other versions (drumless, instrumental, karaoke, remix, live unless the album is live…) and singles or EPs for an album are refused. Quality tiers on Lidarr's ladder; a .torrent's file list is read for format, video files and a track count short of the album. - internal/acquire: fan-out across every search source with a narrowed search kept, a process-wide pacer per source, every candidate judged and kept with its reasons, and the grab: one live grab per want, tie-broken (a person's grab beats the schedule's; a better release replaces a worse one unless it is nearly done). Each download lands in its own folder under the client's downloads folder. - internal/wanted: the search schedule, every ten minutes, never-searched first, then requests, missing files and followed artists, taking the best release when auto-grab is on (the default). - Migration 0082: acquisition_settings (auto-grab, interval, allowed qualities seeded from Lidarr's "Any"), want search columns, want_candidates, grabs, release_blocks. - Web: the Wanted page shows each want's last search, a Search button and every release found with its verdict (Grab, Grab anyway, Never this one, Allow again). Integrations gains "Searching and grabbing". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
77 lines
1.9 KiB
Go
77 lines
1.9 KiB
Go
package release
|
|
|
|
import (
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// File is one file inside a torrent.
|
|
type File struct {
|
|
Index int
|
|
Path string
|
|
Size int64
|
|
}
|
|
|
|
// Contents is what a torrent's file list holds.
|
|
type Contents struct {
|
|
Audio int
|
|
Video int
|
|
// Format is the format most of the audio files are in, "" when none.
|
|
Format string
|
|
}
|
|
|
|
var videoExt = map[string]bool{
|
|
"mkv": true, "mp4": true, "avi": true, "m2ts": true, "vob": true,
|
|
"mov": true, "webm": true, "wmv": true, "m4v": true, "ts": true,
|
|
}
|
|
|
|
// Inspect counts a file list's audio and video files.
|
|
func Inspect(files []File) Contents {
|
|
var c Contents
|
|
formats := map[string]int{}
|
|
for _, f := range files {
|
|
ext := strings.TrimPrefix(strings.ToLower(path.Ext(f.Path)), ".")
|
|
if fm := formatOf(ext); fm != "" {
|
|
c.Audio++
|
|
formats[fm]++
|
|
} else if videoExt[ext] {
|
|
c.Video++
|
|
}
|
|
}
|
|
best := 0
|
|
for fm, n := range formats {
|
|
if n > best || (n == best && formatRank[fm] > formatRank[c.Format]) {
|
|
c.Format, best = fm, n
|
|
}
|
|
}
|
|
return c
|
|
}
|
|
|
|
// JudgeFiles says what a torrent's files say about whether it is the album:
|
|
// a veto (code and sentence), or a doubt for a person, or nothing.
|
|
func JudgeFiles(w Want, files []File) (veto, reason, doubt string) {
|
|
if len(files) == 0 {
|
|
return "", "", ""
|
|
}
|
|
c := Inspect(files)
|
|
switch {
|
|
case c.Audio == 0 && c.Video > 0:
|
|
return VetoVideo, "It holds video files and no audio.", ""
|
|
case c.Audio == 0:
|
|
return VetoNoAudio, "It holds no audio files Minstrel can read.", ""
|
|
case w.MinTracks > 0 && c.Audio < w.MinTracks:
|
|
return VetoIncomplete, "It holds " + tracks(c.Audio) + "; the shortest edition has " + tracks(w.MinTracks) + ".", ""
|
|
case w.MaxTracks > 0 && c.Audio > 2*w.MaxTracks+4:
|
|
return "", "", "It holds " + tracks(c.Audio) + ", more than any edition of the album has: probably more than one album."
|
|
}
|
|
return "", "", ""
|
|
}
|
|
|
|
func tracks(n int) string {
|
|
if n == 1 {
|
|
return "1 track"
|
|
}
|
|
return strconv.Itoa(n) + " tracks"
|
|
}
|