Release list: filter by quality, codec, HDR, DV and language

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 20:30:12 +02:00
co-authored by Claude Sonnet 5.5
parent b34f88ef3e
commit 47b9459a5e
9 changed files with 131 additions and 27 deletions
+19 -16
View File
@@ -63,22 +63,12 @@ func (f MediaFile) Attrs() FileAttrs {
}
}
codec := f.MediaInfo.VideoCodec + " " + names
switch {
case re265.MatchString(f.MediaInfo.VideoCodec):
a.Codec = CodecX265
case re264.MatchString(f.MediaInfo.VideoCodec):
a.Codec = CodecX264
case reAV1.MatchString(f.MediaInfo.VideoCodec):
a.Codec = CodecAV1
case f.MediaInfo.VideoCodec != "":
// A codec we do not offer as a filter (VC-1, MPEG-2...): do not guess from the name.
case re265.MatchString(codec):
a.Codec = CodecX265
case re264.MatchString(codec):
a.Codec = CodecX264
case reAV1.MatchString(codec):
a.Codec = CodecAV1
if f.MediaInfo.VideoCodec != "" {
// A codec we do not offer as a filter (VC-1, MPEG-2...) stays empty
// rather than being guessed from the name.
a.Codec = codecOf(f.MediaInfo.VideoCodec)
} else {
a.Codec = codecOf(names)
}
dr := f.MediaInfo.VideoDynamicRangeType + " " + f.MediaInfo.VideoDynamicRange
@@ -101,3 +91,16 @@ func atoi(s string) (n int) {
}
return n
}
// codecOf finds a codec we offer as a filter in free text ("x265", "H.264", "HEVC"...).
func codecOf(s string) string {
switch {
case re265.MatchString(s):
return CodecX265
case re264.MatchString(s):
return CodecX264
case reAV1.MatchString(s):
return CodecAV1
}
return ""
}
+24
View File
@@ -47,3 +47,27 @@ func TestAttrsVariants(t *testing.T) {
}
}
}
func TestReleaseAttrs(t *testing.T) {
var r Release
raw := `{"title":"Dune.2021.2160p.UHD.BluRay.DV.HDR10.x265-GRP","quality":{"quality":{"name":"Bluray-2160p","resolution":2160}},
"languages":[{"name":"English"}],"customFormats":[{"name":"HDR"}]}`
if err := json.Unmarshal([]byte(raw), &r); err != nil {
t.Fatal(err)
}
want := FileAttrs{Resolution: 2160, Codec: CodecX265, HDR: true, DV: true, Languages: []string{"English"}}
if got := r.Attrs(); !reflect.DeepEqual(got, want) {
t.Errorf("got %+v, want %+v", got, want)
}
// Resolution from the title when the quality carries none; codec spelled H.264.
r = Release{Title: "Show.S01E01.720p.WEB.H.264-GRP"}
if got := r.Attrs(); got.Resolution != 720 || got.Codec != CodecX264 || got.HDR || got.DV {
t.Errorf("got %+v", got)
}
// "HDTV" and "HDRip" are not HDR.
r = Release{Title: "Show.S01E01.HDTV.x264-GRP"}
if got := r.Attrs(); got.HDR || got.DV {
t.Errorf("HDTV detected as HDR: %+v", got)
}
}
+31 -1
View File
@@ -27,12 +27,16 @@ type Release struct {
FullSeason bool `json:"fullSeason"`
Quality struct {
Quality struct {
Name string `json:"name"`
Name string `json:"name"`
Resolution int `json:"resolution"`
} `json:"quality"`
} `json:"quality"`
Languages []struct {
Name string `json:"name"`
} `json:"languages"`
CustomFormats []struct {
Name string `json:"name"`
} `json:"customFormats"`
}
// QualityName returns the release's quality, e.g. "Bluray-1080p".
@@ -64,3 +68,29 @@ func (c *Client) Grab(ctx context.Context, r Release) error {
body := map[string]any{"guid": r.GUID, "indexerId": r.IndexerID}
return c.Do(ctx, http.MethodPost, "release", nil, body, nil)
}
// Attrs derives the filterable properties from what the release announces:
// its title, quality, custom formats and languages. Codec and HDR only show up
// when the uploader put them into the title.
func (r Release) Attrs() FileAttrs {
names := []string{r.Title, r.Quality.Quality.Name}
for _, cf := range r.CustomFormats {
names = append(names, cf.Name)
}
text := strings.Join(names, " ")
a := FileAttrs{Resolution: r.Quality.Quality.Resolution, Codec: codecOf(text)}
if a.Resolution == 0 {
if m := reRes.FindStringSubmatch(text); m != nil {
a.Resolution = atoi(m[1])
}
}
a.DV = reDV.MatchString(text)
a.HDR = reHDR.MatchString(text)
for _, l := range r.Languages {
if l.Name != "" {
a.Languages = append(a.Languages, l.Name)
}
}
return a
}