196 lines
4.8 KiB
Go
196 lines
4.8 KiB
Go
package service
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
type ReleaseInfo struct {
|
|
Title string `json:"title"`
|
|
Resolution string `json:"resolution"`
|
|
Source string `json:"source"`
|
|
VideoCodec string `json:"video_codec"`
|
|
AudioFormat string `json:"audio_format"`
|
|
ReleaseGroup string `json:"release_group"`
|
|
ParseWarning bool `json:"parse_warning"`
|
|
}
|
|
|
|
type ReleaseParser struct{}
|
|
|
|
func NewReleaseParser() *ReleaseParser {
|
|
return &ReleaseParser{}
|
|
}
|
|
|
|
var (
|
|
bracketRe = regexp.MustCompile(`\[.*?\]`)
|
|
releaseRe = regexp.MustCompile(`(?i)` +
|
|
`(?:.*?[-. ])?` +
|
|
`(?:(?P<resolution>480[p|i]|576[p|i]|720[p|i]|1080[p|i]|2160[p|i]|4K)[-. ])?` +
|
|
`(?:(?P<source>HDTV|PDTV|SDTV|WEB-DL|WEBDL|WEBRip|WEB\s|BluRay|BDRip|BRRip|REMUX|Remux|DVDRip|DVD|CAM|TS|HDCAM)[-. ])?` +
|
|
`(?:(?P<codec>x264|h264|X264|H264|x265|h265|X265|HEVC|XviD|MPEG2|VC1|AV1)[-. ])?` +
|
|
`(?:(?P<audio>DTS-HD\.MA|DTS-HD|DTS\.HD|DTS-X|DTS\.X|ATMOS|Atmos|TrueHD|DDP5\.1|DD\+?5\.1|DolbyDigitalPlus|AAC[. ]?2\.0|AAC[. ]?5\.1|AAC|AC3|DD|FLAC|MP3)[-. ])?` +
|
|
`.*?(?:-(?P<group>[A-Za-z0-9]+))?$`,
|
|
)
|
|
|
|
resolutionCleanRe = regexp.MustCompile(`(?i)(480)[pi]|(576)[pi]|(720)[pi]|(1080)[pi]|(2160)[pi]|4K`)
|
|
|
|
knownSuffixes = []string{"Esubs", "TGx", "ettv", "eztv", "x0r", "FGT", "ION10", "NTb"}
|
|
)
|
|
|
|
func normalizeResolution(raw string) string {
|
|
if raw == "" {
|
|
return ""
|
|
}
|
|
raw = strings.TrimSpace(raw)
|
|
raw = strings.TrimRight(raw, "pPiI")
|
|
if strings.EqualFold(raw, "4K") {
|
|
return "2160p"
|
|
}
|
|
return raw + "p"
|
|
}
|
|
|
|
func normalizeSource(raw string) string {
|
|
if raw == "" {
|
|
return ""
|
|
}
|
|
raw = strings.TrimSpace(raw)
|
|
raw = strings.TrimRight(raw, " -.")
|
|
upper := strings.ToUpper(raw)
|
|
switch {
|
|
case upper == "HDTV", upper == "PDTV", upper == "SDTV":
|
|
return upper
|
|
case upper == "WEB-DL", upper == "WEBDL", strings.HasPrefix(upper, "WEB"):
|
|
return "WEB-DL"
|
|
case strings.HasPrefix(upper, "BLURAY"), upper == "BDRIP", upper == "BRRIP":
|
|
return "BluRay"
|
|
case upper == "REMUX":
|
|
return "REMUX"
|
|
case strings.HasPrefix(upper, "DVD"):
|
|
return "DVD"
|
|
case upper == "CAM", upper == "TS", upper == "HDCAM":
|
|
return upper
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func normalizeCodec(raw string) string {
|
|
if raw == "" {
|
|
return ""
|
|
}
|
|
raw = strings.TrimSpace(raw)
|
|
switch {
|
|
case strings.EqualFold(raw, "x264"), strings.EqualFold(raw, "h264"):
|
|
return "x264"
|
|
case strings.EqualFold(raw, "x265"), strings.EqualFold(raw, "h265"), strings.EqualFold(raw, "HEVC"):
|
|
return "x265"
|
|
case strings.EqualFold(raw, "XviD"):
|
|
return "XviD"
|
|
case strings.EqualFold(raw, "MPEG2"):
|
|
return "MPEG2"
|
|
case strings.EqualFold(raw, "VC1"):
|
|
return "VC1"
|
|
case strings.EqualFold(raw, "AV1"):
|
|
return "AV1"
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func normalizeAudio(raw string) string {
|
|
if raw == "" {
|
|
return ""
|
|
}
|
|
raw = strings.TrimSpace(raw)
|
|
upper := strings.ToUpper(raw)
|
|
switch {
|
|
case strings.HasPrefix(upper, "DTS-HD"):
|
|
return "DTS-HD"
|
|
case strings.HasPrefix(upper, "DTS-X") || strings.HasPrefix(upper, "DTS.X"):
|
|
return "DTS-X"
|
|
case upper == "ATMOS":
|
|
return "ATMOS"
|
|
case upper == "TRUEHD":
|
|
return "TrueHD"
|
|
case strings.HasPrefix(upper, "DDP") || strings.HasPrefix(upper, "DD+") || strings.HasPrefix(upper, "DOLBYDIGITALPLUS"):
|
|
return "DDP5.1"
|
|
case strings.HasPrefix(upper, "DD") && (strings.Contains(upper, "5.1") || strings.Contains(upper, "51")):
|
|
return "DDP5.1"
|
|
case upper == "AAC":
|
|
return "AAC"
|
|
case strings.HasPrefix(upper, "AAC"):
|
|
return "AAC"
|
|
case upper == "AC3":
|
|
return "AC3"
|
|
case upper == "DD":
|
|
return "DD"
|
|
case upper == "FLAC":
|
|
return "FLAC"
|
|
case upper == "MP3":
|
|
return "MP3"
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func cleanGroup(raw string) string {
|
|
if raw == "" {
|
|
return ""
|
|
}
|
|
for _, suffix := range knownSuffixes {
|
|
if strings.EqualFold(raw, suffix) {
|
|
return ""
|
|
}
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func (p *ReleaseParser) Parse(title string) ReleaseInfo {
|
|
info := ReleaseInfo{Title: title}
|
|
|
|
cleaned := bracketRe.ReplaceAllString(title, " ")
|
|
cleaned = strings.TrimSpace(cleaned)
|
|
|
|
match := releaseRe.FindStringSubmatch(cleaned)
|
|
if match == nil {
|
|
info.ParseWarning = true
|
|
return info
|
|
}
|
|
|
|
for i, name := range releaseRe.SubexpNames() {
|
|
if i == 0 || name == "" {
|
|
continue
|
|
}
|
|
value := match[i]
|
|
switch name {
|
|
case "resolution":
|
|
info.Resolution = normalizeResolution(value)
|
|
case "source":
|
|
info.Source = normalizeSource(value)
|
|
case "codec":
|
|
info.VideoCodec = normalizeCodec(value)
|
|
case "audio":
|
|
info.AudioFormat = normalizeAudio(value)
|
|
case "group":
|
|
info.ReleaseGroup = cleanGroup(value)
|
|
}
|
|
}
|
|
|
|
if info.Resolution == "" {
|
|
info.ParseWarning = true
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
func (p *ReleaseParser) MatchQuality(info ReleaseInfo) *QualityTier {
|
|
for i := len(QualityTiers) - 1; i >= 0; i-- {
|
|
tier := &QualityTiers[i]
|
|
if tier.Resolution != "" && tier.Resolution != info.Resolution {
|
|
continue
|
|
}
|
|
if info.Source == "" || SourceMatch(tier.Source, info.Source) {
|
|
return tier
|
|
}
|
|
}
|
|
|
|
return &QualityTiers[0]
|
|
}
|