package arr import ( "regexp" "strings" ) // MediaFile is a downloaded file as Radarr (movieFile) and Sonarr // (episodeFile) describe it. type MediaFile struct { Size int64 `json:"size"` SceneName string `json:"sceneName,omitempty"` RelativePath string `json:"relativePath,omitempty"` Quality struct { Quality struct { Name string `json:"name"` Resolution int `json:"resolution"` } `json:"quality"` } `json:"quality"` MediaInfo struct { VideoCodec string `json:"videoCodec"` VideoDynamicRange string `json:"videoDynamicRange"` VideoDynamicRangeType string `json:"videoDynamicRangeType"` } `json:"mediaInfo"` Languages []struct { Name string `json:"name"` } `json:"languages"` } // Codec names used for filtering. const ( CodecX265 = "x265" CodecX264 = "x264" CodecAV1 = "AV1" ) // FileAttrs are the properties of a file the library can be filtered by. type FileAttrs struct { Resolution int // 2160, 1080, 720, 480 ... or 0 if unknown Codec string // CodecX265, CodecX264, CodecAV1 or "" HDR bool // HDR10, HDR10+, HLG, PQ (also with Dolby Vision on top) DV bool // Dolby Vision Languages []string // audio languages } var ( reRes = regexp.MustCompile(`(?i)\b(2160|1080|720|576|480)p\b`) re265 = regexp.MustCompile(`(?i)(x\.?265|h\.?265|hevc)`) re264 = regexp.MustCompile(`(?i)(x\.?264|h\.?264|\bavc\b)`) reAV1 = regexp.MustCompile(`(?i)\bav1\b`) reDV = regexp.MustCompile(`(?i)(\bdv\b|dovi|dolby[ .]?vision)`) reHDR = regexp.MustCompile(`(?i)\b(hdr(10(\+|plus)?)?|hlg|pq)\b`) ) // Attrs derives the filterable properties. Radarr/Sonarr's media info is used // when present, otherwise the names (scene name, path, quality) are searched. func (f MediaFile) Attrs() FileAttrs { names := strings.Join([]string{f.SceneName, f.RelativePath, f.Quality.Quality.Name}, " ") a := FileAttrs{Resolution: f.Quality.Quality.Resolution} if a.Resolution == 0 { if m := reRes.FindStringSubmatch(names); m != nil { a.Resolution = atoi(m[1]) } } 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 if strings.TrimSpace(dr) == "" { dr = names } a.DV = reDV.MatchString(dr) a.HDR = reHDR.MatchString(dr) for _, l := range f.Languages { if l.Name != "" { a.Languages = append(a.Languages, l.Name) } } return a } func atoi(s string) (n int) { for _, c := range s { n = n*10 + int(c-'0') } 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 "" }