diff --git a/pkg/metadata/tv.go b/pkg/metadata/tv.go index 4a96b9d..d3fc7b5 100644 --- a/pkg/metadata/tv.go +++ b/pkg/metadata/tv.go @@ -7,18 +7,20 @@ import ( "regexp" "strconv" "strings" + "time" ) -type Metadata struct { +type Info struct { NameEn string NameCn string Season int - Episode int + StartEpisode int + EndEpisode int Resolution string IsSeasonPack bool } -func (m *Metadata) IsAcceptable(names... string) bool { +func (m *Info) IsAcceptable(names ...string) bool { for _, name := range names { re := regexp.MustCompile(`[^\p{L}\w\s]`) name = re.ReplaceAllString(strings.ToLower(name), " ") @@ -29,220 +31,62 @@ func (m *Metadata) IsAcceptable(names... string) bool { nameEN = strings.Join(strings.Fields(nameEN), " ") if utils.IsASCII(name) { //ascii name should match words re := regexp.MustCompile(`\b` + name + `\b`) - return re.MatchString(nameCN) || re.MatchString(nameEN) + return re.MatchString(nameCN) || re.MatchString(nameEN) } - if strings.Contains(nameCN, name) || strings.Contains(nameEN, name) { + if strings.Contains(nameCN, name) || strings.Contains(nameEN, name) { return true } - + } return false } - -func ParseTv(name string) *Metadata { +func ParseTv(name string) *Info { name = strings.ToLower(name) name = strings.ReplaceAll(name, "\u200b", "") //remove unicode hidden character - if utils.ContainsChineseChar(name) { - return parseChineseName(name) - } - return parseEnglishName(name) + + return parseName(name) } -func parseEnglishName(name string) *Metadata { - re := regexp.MustCompile(`[^\p{L}\w\s]`) - name = re.ReplaceAllString(strings.ToLower(name), " ") - newSplits := strings.Split(strings.TrimSpace(name), " ") - - seasonRe := regexp.MustCompile(`^s\d{1,2}`) - resRe := regexp.MustCompile(`^\d{3,4}p`) - episodeRe := regexp.MustCompile(`e\d{1,3}`) - - var seasonIndex = -1 - var episodeIndex = -1 - var resIndex = -1 - for i, p := range newSplits { - p = strings.TrimSpace(p) - if p == "" { - continue - } - if seasonRe.MatchString(p) { - //season part - seasonIndex = i - } else if resRe.MatchString(p) { - resIndex = i - } - if i >= seasonIndex && episodeRe.MatchString(p) { - episodeIndex = i +func adjacentNumber(s string, start int) (n1 int, l int) { + runes := []rune(s) + if start > len(runes)-1 { //out of bound + return -1, -1 + } + var n []rune + for i := start; i < len(runes); i++ { + k := runes[i] + if (k < '0' || k > '9') && !chineseNum[k] { //not digit anymore + break } + n = append(n, k) } - - meta := &Metadata{ - Season: -1, - Episode: -1, + if len(n) == 0 { + return -1, -1 } - if seasonIndex != -1 { - //season exists - ss := seasonRe.FindAllString(newSplits[seasonIndex], -1) - if len(ss) != 0 { - //season info - - ssNum := strings.TrimLeft(ss[0], "s") - n, err := strconv.Atoi(ssNum) - if err != nil { - panic(fmt.Sprintf("convert %s error: %v", ssNum, err)) - } - meta.Season = n - } - } else { //maybe like Season 1? - seasonRe := regexp.MustCompile(`season \d{1,2}`) - matches := seasonRe.FindAllString(name, -1) - if len(matches) > 0 { - for i, s := range newSplits { - if s == "season" { - seasonIndex = i - } - } - numRe := regexp.MustCompile(`\d{1,2}`) - seNum := numRe.FindAllString(matches[0], -1)[0] - n, err := strconv.Atoi(seNum) - if err != nil { - panic(fmt.Sprintf("convert %s error: %v", seNum, err)) - } - meta.Season = n - - } + m, err := strconv.Atoi(string(n)) + if err != nil { + return chinese2Num[string(n)], len(n) } - if episodeIndex != -1 { - ep := episodeRe.FindAllString(newSplits[episodeIndex], -1) - if len(ep) > 0 { - //episode info exists - epNum := strings.TrimLeft(ep[0], "e") - n, err := strconv.Atoi(epNum) - if err != nil { - panic(fmt.Sprintf("convert %s error: %v", epNum, err)) - } - meta.Episode = n - } - } else { //no episode, maybe like One Punch Man S2 - 08 [1080p].mkv - - // numRe := regexp.MustCompile(`^\d{1,2}$`) - // for i, p := range newSplits { - // if numRe.MatchString(p) { - // if i > 0 && strings.Contains(newSplits[i-1], "season") { //last word cannot be season - // continue - // } - // if i < seasonIndex { - // //episode number most likely should comes alfter season number - // continue - // } - // //episodeIndex = i - // n, err := strconv.Atoi(p) - // if err != nil { - // panic(fmt.Sprintf("convert %s error: %v", p, err)) - // } - // meta.Episode = n - - // } - // } - - } - if resIndex != -1 { - //resolution exists - meta.Resolution = newSplits[resIndex] - } - if meta.Episode == -1 { - meta.Episode = -1 - meta.IsSeasonPack = true - } - - if seasonIndex > 0 { - //name exists - names := newSplits[0:seasonIndex] - meta.NameEn = strings.TrimSpace(strings.Join(names, " ")) - } else { - meta.NameEn = name - } - - return meta + return m, len(n) } -func parseChineseName(name string) *Metadata { - var meta = parseEnglishName(name) - if meta.Season != -1 && (meta.Episode != -1 || meta.IsSeasonPack) { - return meta - } - meta = &Metadata{Season: 1} - //season pack - packRe := regexp.MustCompile(`(\d{1,2}-\d{1,2})|(全集)`) - if packRe.MatchString(name) { - meta.IsSeasonPack = true - } - //resolution - resRe := regexp.MustCompile(`\d{3,4}p`) - resMatches := resRe.FindAllString(name, -1) - if len(resMatches) != 0 { - meta.Resolution = resMatches[0] - } else { - if strings.Contains(name, "720") { - meta.Resolution = "720p" - } else if strings.Contains(name, "1080") { - meta.Resolution = "1080p" - } - - } - - //episode number - re1 := regexp.MustCompile(`\[\d{1,3}\]`) - episodeMatches1 := re1.FindAllString(name, -1) - if len(episodeMatches1) > 0 { //[11] [1080p] - epNum := strings.TrimRight(strings.TrimLeft(episodeMatches1[0], "["), "]") - n, err := strconv.Atoi(epNum) - if err != nil { - panic(fmt.Sprintf("convert %s error: %v", epNum, err)) - } - meta.Episode = n - } else { //【第09話】 - re2 := regexp.MustCompile(`第\d{1,4}(话|話|集)`) - episodeMatches1 := re2.FindAllString(name, -1) - if len(episodeMatches1) > 0 { - re := regexp.MustCompile(`\d{1,4}`) - epNum := re.FindAllString(episodeMatches1[0], -1)[0] - n, err := strconv.Atoi(epNum) - if err != nil { - panic(fmt.Sprintf("convert %s error: %v", epNum, err)) - } - meta.Episode = n - } else { //SHY 靦腆英雄 / Shy -05 ( CR 1920x1080 AVC AAC MKV) - re3 := regexp.MustCompile(`[^\d\w]\d{1,2}[^\d\w]`) - epNums := re3.FindAllString(name, -1) - if len(epNums) > 0 { - - re3 := regexp.MustCompile(`\d{1,2}`) - epNum := re3.FindAllString(epNums[0], -1)[0] - n, err := strconv.Atoi(epNum) - if err != nil { - panic(fmt.Sprintf("convert %s error: %v", epNum, err)) - } - meta.Episode = n - } - } - } - +func findSeason(s string) (n int, p int) { //season numner seasonRe1 := regexp.MustCompile(`s\d{1,2}`) - seasonMatches := seasonRe1.FindAllString(name, -1) + seasonMatches := seasonRe1.FindAllString(s, -1) if len(seasonMatches) > 0 { seNum := seasonMatches[0][1:] n, err := strconv.Atoi(seNum) if err != nil { panic(fmt.Sprintf("convert %s error: %v", seNum, err)) } - meta.Season = n + + return n, strings.Index(s, seNum) } else { seasonRe1 := regexp.MustCompile(`season \d{1,2}`) - seasonMatches := seasonRe1.FindAllString(name, -1) + seasonMatches := seasonRe1.FindAllString(s, -1) if len(seasonMatches) > 0 { re3 := regexp.MustCompile(`\d{1,2}`) seNum := re3.FindAllString(seasonMatches[0], -1)[0] @@ -250,10 +94,10 @@ func parseChineseName(name string) *Metadata { if err != nil { panic(fmt.Sprintf("convert %s error: %v", seNum, err)) } - meta.Season = n + return n, strings.Index(s, seasonMatches[0]) } else { seasonRe1 := regexp.MustCompile(`第.{1,2}季`) - seasonMatches := seasonRe1.FindAllString(name, -1) + seasonMatches := seasonRe1.FindAllString(s, -1) if len(seasonMatches) > 0 { m1 := []rune(seasonMatches[0]) seNum := m1[1 : len(m1)-1] @@ -262,58 +106,363 @@ func parseChineseName(name string) *Metadata { log.Warnf("parse season number %v error: %v, try to parse using chinese", seNum, err) n = chinese2Num[string(seNum)] } - meta.Season = n + return n, strings.Index(s, seasonMatches[0]) + } + } + } + return -1, -1 +} +func findEpisodes(s string) (start int, end int) { + var episodeCn = map[rune]bool{ + '话': true, + '話': true, + '集': true, + } + + rr := []rune(s) + for i := 0; i < len(rr); i++ { + r := rr[i] + if r == 'e' { + n, l := adjacentNumber(s, i+1) + end := i + l + + if n > 0 { + + if len(rr) > end+1 && rr[end+1] == '-' { //multi episodes + if len(rr) > end+2 { + if rr[end+2] == 'e' { + n1, _ := adjacentNumber(s, end+3) + if n1 > 0 { + return n, n1 + } + } else { + n1, _ := adjacentNumber(s, end+2) + if n1 > 0 { + return n, n1 + } + } + } + } else if len(rr) > end+2 && rr[end+1] == ' ' && rr[end+2] == '-' { + start := 0 + for j := end + 3; j < len(rr); j++ { + if rr[j] == ' ' || rr[j] == 'e' { + continue + } + start = j + break + } + if start != 0 { + n1, _ := adjacentNumber(s, start) + if n1 > 0 { + return n, n1 + } + } + } + return n, n + } + } else if r == '第' { + n, l := adjacentNumber(s, i+1) + if len(rr) > i+l+1 && episodeCn[rr[i+l+1]] { + return n, n + } else if len(rr) > i+l+1 { + if rr[i+l+1] == '-' { + n1, l1 := adjacentNumber(s, i+l+2) + if episodeCn[rr[i+l+2+l1]] { + return n, n1 + } + } + } + + } + } + //episode number + re1 := regexp.MustCompile(`\[\d{1,4}\]`) + episodeMatches1 := re1.FindAllString(s, -1) + if len(episodeMatches1) > 0 { //[11] [1080p], [2022][113][HEVC][GB][4K] + for _, m := range episodeMatches1 { + epNum := strings.TrimRight(strings.TrimLeft(m, "["), "]") + n, err := strconv.Atoi(epNum) + if err != nil { + log.Debugf("convert %s error: %v", epNum, err) + continue + } + nowYear := time.Now().Year() + if n > nowYear-50 { //high possibility is year number + continue + } + return n, n + } + } else { //【第09話】 + re2 := regexp.MustCompile(`第\d{1,4}([话話集])`) + episodeMatches1 := re2.FindAllString(s, -1) + if len(episodeMatches1) > 0 { + re := regexp.MustCompile(`\d{1,4}`) + epNum := re.FindAllString(episodeMatches1[0], -1)[0] + n, err := strconv.Atoi(epNum) + if err != nil { + panic(fmt.Sprintf("convert %s error: %v", epNum, err)) + } + return n, n + } else { //The Road Season 2 Episode 12 XviD-AFG + re3 := regexp.MustCompile(`episode \d{1,4}`) + epNums := re3.FindAllString(s, -1) + if len(epNums) > 0 { + re3 := regexp.MustCompile(`\d{1,4}`) + epNum := re3.FindAllString(epNums[0], -1)[0] + n, err := strconv.Atoi(epNum) + if err != nil { + panic(fmt.Sprintf("convert %s error: %v", epNum, err)) + } + return n, n + + } else { //SHY 靦腆英雄 / Shy -05 ( CR 1920x1080 AVC AAC MKV) + if maybeSeasonPack(s) { //avoid miss match, season pack not use this rule + return -1, -1 + } + re3 := regexp.MustCompile(`[^\d\w]\d{1,2}[^\d\w]`) + epNums := re3.FindAllString(s, -1) + if len(epNums) > 0 { + + re3 := regexp.MustCompile(`\d{1,2}`) + epNum := re3.FindAllString(epNums[0], -1)[0] + n, err := strconv.Atoi(epNum) + if err != nil { + panic(fmt.Sprintf("convert %s error: %v", epNum, err)) + } + return n, n + } } } } - if meta.IsSeasonPack && meta.Episode != 0 { - meta.Season = meta.Episode - meta.Episode = -1 + return -1, -1 +} + +func matchResolution(s string) string { + //resolution + resRe := regexp.MustCompile(`\d{3,4}p`) + resMatches := resRe.FindAllString(s, -1) + if len(resMatches) != 0 { + return resMatches[0] + } else { + if strings.Contains(s, "720") { + return "720p" + } else if strings.Contains(s, "1080") { + return "1080p" + } } + return "" +} + +func maybeSeasonPack(s string) bool { + //season pack + packRe := regexp.MustCompile(`((\d{1,2}-\d{1,2}))|(complete)|(全集)`) + if packRe.MatchString(s) { + return true + } + return false +} + +//func parseEnglishName(name string) *Info { +// meta := &Info{ +// //Season: -1, +// Episode: -1, +// } +// +// start, end := findEpisodes(name) +// if start > 0 && end > 0 { +// meta.Episode = start +// } +// +// re := regexp.MustCompile(`[^\p{L}\w\s]`) +// name = re.ReplaceAllString(strings.ToLower(name), " ") +// newSplits := strings.Split(strings.TrimSpace(name), " ") +// +// seasonRe := regexp.MustCompile(`^s\d{1,2}`) +// resRe := regexp.MustCompile(`^\d{3,4}p`) +// episodeRe := regexp.MustCompile(`e\d{1,3}`) +// +// var seasonIndex = -1 +// var episodeIndex = -1 +// var resIndex = -1 +// for i, p := range newSplits { +// p = strings.TrimSpace(p) +// if p == "" { +// continue +// } +// if seasonRe.MatchString(p) { +// //season part +// seasonIndex = i +// } else if resRe.MatchString(p) { +// resIndex = i +// } +// if i >= seasonIndex && episodeRe.MatchString(p) { +// episodeIndex = i +// } +// } +// +// if seasonIndex != -1 { +// //season exists +// ss := seasonRe.FindAllString(newSplits[seasonIndex], -1) +// if len(ss) != 0 { +// //season info +// +// ssNum := strings.TrimLeft(ss[0], "s") +// n, err := strconv.Atoi(ssNum) +// if err != nil { +// panic(fmt.Sprintf("convert %s error: %v", ssNum, err)) +// } +// meta.Season = n +// } +// } else { //maybe like Season 1? +// seasonRe := regexp.MustCompile(`season \d{1,2}`) +// matches := seasonRe.FindAllString(name, -1) +// if len(matches) > 0 { +// for i, s := range newSplits { +// if s == "season" { +// seasonIndex = i +// } +// } +// numRe := regexp.MustCompile(`\d{1,2}`) +// seNum := numRe.FindAllString(matches[0], -1)[0] +// n, err := strconv.Atoi(seNum) +// if err != nil { +// panic(fmt.Sprintf("convert %s error: %v", seNum, err)) +// } +// meta.Season = n +// +// } +// } +// +// if episodeIndex != -1 { +// // ep := episodeRe.FindAllString(newSplits[episodeIndex], -1) +// //if len(ep) > 0 { +// // //episode info exists +// // epNum := strings.TrimLeft(ep[0], "e") +// // n, err := strconv.Atoi(epNum) +// // if err != nil { +// // panic(fmt.Sprintf("convert %s error: %v", epNum, err)) +// // } +// // meta.Episode = n +// //} +// } else { //no episode, maybe like One Punch Man S2 - 08 [1080p].mkv +// +// // numRe := regexp.MustCompile(`^\d{1,2}$`) +// // for i, p := range newSplits { +// // if numRe.MatchString(p) { +// // if i > 0 && strings.Contains(newSplits[i-1], "season") { //last word cannot be season +// // continue +// // } +// // if i < seasonIndex { +// // //episode number most likely should comes alfter season number +// // continue +// // } +// // //episodeIndex = i +// // n, err := strconv.Atoi(p) +// // if err != nil { +// // panic(fmt.Sprintf("convert %s error: %v", p, err)) +// // } +// // meta.Episode = n +// +// // } +// // } +// +// } +// if resIndex != -1 { +// //resolution exists +// meta.Resolution = newSplits[resIndex] +// } +// if meta.Episode == -1 { +// meta.Episode = -1 +// meta.IsSeasonPack = true +// } +// +// if seasonIndex > 0 { +// //name exists +// names := newSplits[0:seasonIndex] +// meta.NameEn = strings.TrimSpace(strings.Join(names, " ")) +// } else { +// meta.NameEn = name +// } +// +// return meta +//} + +func parseName(name string) *Info { + meta := &Info{Season: 1} + + season, p := findSeason(name) + if season == -1 { + log.Debugf("not find season info: %s", name) + if !utils.IsASCII(name) { + season = 1 + } + _ = p + } + meta.Season = season + + start, end := findEpisodes(name) + if start > 0 && end > 0 { + meta.StartEpisode = start + meta.EndEpisode = end + } else { + meta.IsSeasonPack = true + } + + meta.Resolution = matchResolution(name) + + //if meta.IsSeasonPack && meta.Episode != 0 { + // meta.Season = meta.Episode + // meta.Episode = -1 + //} //tv name - - fields := strings.FieldsFunc(name, func(r rune) bool { - return r == '[' || r == ']' || r == '【' || r == '】' - }) - titleCn := "" - title := "" - for _, p := range fields { //寻找匹配的最长的字符串,最有可能是名字 - if utils.ContainsChineseChar(p) && len([]rune(p)) > len([]rune(titleCn)) { //最长含中文字符串 - titleCn = p - } - if len([]rune(p)) > len([]rune(title)) { //最长字符串 - title = p - } - } - re := regexp.MustCompile(`[^\p{L}\w\s]`) - title = re.ReplaceAllString(strings.TrimSpace(strings.ToLower(title)), "") //去除标点符号 - titleCn = re.ReplaceAllString(strings.TrimSpace(strings.ToLower(titleCn)), "") - - meta.NameCn = titleCn - cnRe := regexp.MustCompile(`\p{Han}.*\p{Han}`) - cnmatches := cnRe.FindAllString(titleCn, -1) - - //titleCn中最长的中文字符 - if len(cnmatches) > 0 { - for _, t := range cnmatches { - if len([]rune(t)) > len([]rune(meta.NameCn)) { - meta.NameCn = strings.ToLower(t) + if utils.IsASCII(name) { + meta.NameEn = name[:p] + meta.NameCn = meta.NameEn + } else { + fields := strings.FieldsFunc(name, func(r rune) bool { + return r == '[' || r == ']' || r == '【' || r == '】' + }) + titleCn := "" + title := "" + for _, p := range fields { //寻找匹配的最长的字符串,最有可能是名字 + if utils.ContainsChineseChar(p) && len([]rune(p)) > len([]rune(titleCn)) { //最长含中文字符串 + titleCn = p + } + if len([]rune(p)) > len([]rune(title)) { //最长字符串 + title = p } } - } + re := regexp.MustCompile(`[^\p{L}\w\s]`) + title = re.ReplaceAllString(strings.TrimSpace(strings.ToLower(title)), "") //去除标点符号 + titleCn = re.ReplaceAllString(strings.TrimSpace(strings.ToLower(titleCn)), "") - //匹配title中最长拉丁字符串 - enRe := regexp.MustCompile(`[[:ascii:]]*`) - enM := enRe.FindAllString(title, -1) - if len(enM) > 0 { - for _, t := range enM { - if len(t) > len(meta.NameEn) { - meta.NameEn = strings.TrimSpace(strings.ToLower(t)) + meta.NameCn = titleCn + cnRe := regexp.MustCompile(`\p{Han}.*\p{Han}`) + cnmatches := cnRe.FindAllString(titleCn, -1) + + //titleCn中最长的中文字符 + if len(cnmatches) > 0 { + for _, t := range cnmatches { + if len([]rune(t)) > len([]rune(meta.NameCn)) { + meta.NameCn = strings.ToLower(t) + } } } + + //匹配title中最长拉丁字符串 + enRe := regexp.MustCompile(`[[:ascii:]]*`) + enM := enRe.FindAllString(title, -1) + if len(enM) > 0 { + for _, t := range enM { + if len(t) > len(meta.NameEn) { + meta.NameEn = strings.TrimSpace(strings.ToLower(t)) + } + } + } + } return meta @@ -330,3 +479,15 @@ var chinese2Num = map[string]int{ "八": 8, "九": 9, } + +var chineseNum = map[rune]bool{ + '一': true, + '二': true, + '三': true, + '四': true, + '五': true, + '六': true, + '七': true, + '八': true, + '九': true, +} diff --git a/pkg/metadata/tv_test.go b/pkg/metadata/tv_test.go index fc7d597..44ab09f 100644 --- a/pkg/metadata/tv_test.go +++ b/pkg/metadata/tv_test.go @@ -21,7 +21,7 @@ func Test_ParseTV2(t *testing.T) { m := ParseTv(s1) log.Infof("results: %+v", m) assert.Equal(t, m.Season, 1) - assert.Equal(t, m.Episode, 4) + assert.Equal(t, m.StartEpisode, 4) assert.Equal(t, m.IsSeasonPack, false) assert.Equal(t, m.Resolution, "1080p") } @@ -31,7 +31,7 @@ func Test_ParseTV3(t *testing.T) { m := ParseTv(s1) log.Infof("results: %+v", m) assert.Equal(t, m.Season, 37) - assert.Equal(t, m.Episode, 219) + assert.Equal(t, m.StartEpisode, 219) assert.Equal(t, m.IsSeasonPack, false) //assert.Equal(t, m.Resolution, "1080p") } @@ -41,8 +41,8 @@ func Test_ParseTV4(t *testing.T) { m := ParseTv(s1) log.Infof("results: %+v", m) assert.Equal(t, m.Season, 2) - //assert.Equal(t, m.Episode, 219) - assert.Equal(t, m.IsSeasonPack, true) + assert.Equal(t, m.StartEpisode, 12) + assert.Equal(t, m.IsSeasonPack, false) //assert.Equal(t, m.Resolution, "1080p") } @@ -61,7 +61,7 @@ func Test_ParseTV6(t *testing.T) { m := ParseTv(s1) log.Infof("results: %+v", m) assert.Equal(t, m.Season, 1) - assert.Equal(t, m.Episode, 3) + assert.Equal(t, m.StartEpisode, 3) assert.Equal(t, m.IsSeasonPack, false) assert.Equal(t, m.Resolution, "1080p") } @@ -71,7 +71,7 @@ func Test_ParseTV7(t *testing.T) { m := ParseTv(s1) log.Infof("results: %+v", m) assert.Equal(t, m.Season, 1) - assert.Equal(t, m.Episode, 1113) + assert.Equal(t, m.StartEpisode, 1113) assert.Equal(t, m.IsSeasonPack, false) assert.Equal(t, m.Resolution, "1080p") } @@ -81,7 +81,7 @@ func Test_ParseTV8(t *testing.T) { m := ParseTv(s1) log.Infof("results: %+v", m) assert.Equal(t, m.Season, 1) - assert.Equal(t, m.Episode, 4) + assert.Equal(t, m.StartEpisode, 4) assert.Equal(t, m.IsSeasonPack, false) assert.Equal(t, m.Resolution, "1080p") } @@ -91,7 +91,7 @@ func Test_ParseTV9(t *testing.T) { m := ParseTv(s1) log.Infof("results: %+v", m) assert.Equal(t, m.Season, 1) - assert.Equal(t, m.Episode, 16) + assert.Equal(t, m.StartEpisode, 16) assert.Equal(t, m.IsSeasonPack, false) assert.Equal(t, m.Resolution, "1080p") } @@ -111,7 +111,7 @@ func Test_ParseTV11(t *testing.T) { m := ParseTv(s1) log.Infof("results: %+v", m) assert.Equal(t, 2, m.Season) - assert.Equal(t, 4, m.Episode) + assert.Equal(t, 4, m.StartEpisode) assert.Equal(t, false, m.IsSeasonPack) assert.Equal(t, "1080p", m.Resolution) } @@ -121,7 +121,7 @@ func Test_ParseTV12(t *testing.T) { m := ParseTv(s1) log.Infof("results: %+v", m) assert.Equal(t, 2, m.Season) - assert.Equal(t, 4, m.Episode) + assert.Equal(t, 4, m.StartEpisode) assert.Equal(t, false, m.IsSeasonPack) assert.Equal(t, "1080p", m.Resolution) } @@ -131,7 +131,7 @@ func Test_ParseTV13(t *testing.T) { m := ParseTv(s1) log.Infof("results: %+v", m) assert.Equal(t, 2, m.Season) - assert.Equal(t, 8, m.Episode) + assert.Equal(t, 8, m.StartEpisode) assert.Equal(t, false, m.IsSeasonPack) assert.Equal(t, "1080p", m.Resolution) } @@ -141,10 +141,11 @@ func Test_ParseTV14(t *testing.T) { m := ParseTv(s1) log.Infof("results: %+v", m) assert.Equal(t, 5, m.Season) - assert.Equal(t, 113, m.Episode) + assert.Equal(t, 113, m.StartEpisode) assert.Equal(t, false, m.IsSeasonPack) //assert.Equal(t, "720p", m.Resolution) } + // func Test_ParseTV15(t *testing.T) { @@ -157,4 +158,41 @@ func Test_ParseTV15(t *testing.T) { //assert.Equal(t, 113, m.Episode) //assert.Equal(t, false, m.IsSeasonPack) //assert.Equal(t, "720p", m.Resolution) -} \ No newline at end of file +} + +func Test_ParseTV16(t *testing.T) { + s1 := "Romance in the Alley 2024 S01 E24-E25 1080p WEB-DL H.264 AAC-PTerWEB" + m := ParseTv(s1) + log.Infof("results: %+v", m) + + assert.Equal(t, 1, m.Season) + assert.Equal(t, 24, m.StartEpisode) + assert.Equal(t, 25, m.EndEpisode) + //assert.Equal(t, false, m.IsSeasonPack) + //assert.Equal(t, "720p", m.Resolution) +} + +func Test_ParseTV17(t *testing.T) { + s1 := "小巷人家/Romance in the Alley 第24-25集 " + m := ParseTv(s1) + log.Infof("results: %+v", m) + + assert.Equal(t, 1, m.Season) + assert.Equal(t, 24, m.StartEpisode) + assert.Equal(t, 25, m.EndEpisode) + //assert.Equal(t, false, m.IsSeasonPack) + //assert.Equal(t, "720p", m.Resolution) +} + +// Romance in the Alley 2024 S01E01-S01E21 2160p WEB-DL HEVC AAC-UBWEB +func Test_ParseTV18(t *testing.T) { + s1 := "Romance in the Alley 2024 S01E01-S01E21 2160p WEB-DL HEVC AAC-UBWEB " + m := ParseTv(s1) + log.Infof("results: %+v", m) + + assert.Equal(t, 1, m.Season) + assert.Equal(t, 1, m.StartEpisode) + assert.Equal(t, 21, m.EndEpisode) + //assert.Equal(t, false, m.IsSeasonPack) + //assert.Equal(t, "720p", m.Resolution) +} diff --git a/pkg/torznab/torznab.go b/pkg/torznab/torznab.go index 6f2da4e..47bdef0 100644 --- a/pkg/torznab/torznab.go +++ b/pkg/torznab/torznab.go @@ -93,6 +93,7 @@ func (r *Response) ToResults(indexer *db.TorznabInfo) []Result { } r := Result{ Name: item.Title, + Description: item.Description, Link: item.Link, Size: mustAtoI(item.Size), Seeders: mustAtoI(item.GetAttr("seeders")), @@ -180,6 +181,7 @@ func doRequest(req *http.Request) (*Response, error) { type Result struct { Name string `json:"name"` + Description string `json:"description"` Link string `json:"link"` Size int `json:"size"` Seeders int `json:"seeders"` diff --git a/server/core/integration.go b/server/core/integration.go index b7b0fae..9ba7594 100644 --- a/server/core/integration.go +++ b/server/core/integration.go @@ -300,9 +300,9 @@ func (c *Client) findEpisodeFilesPreMoving(historyId int) error { } meta := metadata.ParseTv(f.Name()) - if meta.Episode > 0 { + if meta.StartEpisode > 0 { //episode exists - ep, err := c.db.GetEpisode(his.MediaID, seasonNum, meta.Episode) + ep, err := c.db.GetEpisode(his.MediaID, seasonNum, meta.StartEpisode) if err != nil { return err } diff --git a/server/core/torrent.go b/server/core/torrent.go index f1c7c0e..d29f668 100644 --- a/server/core/torrent.go +++ b/server/core/torrent.go @@ -36,11 +36,19 @@ func SearchTvSeries(db1 *db.Client, param *SearchParam) ([]torznab.Result, error res := searchWithTorznab(db1, prowlarr.TV, series.NameEn, series.NameCn, series.OriginalName) var filtered []torznab.Result +lo: for _, r := range res { //log.Infof("torrent resource: %+v", r) meta := metadata.ParseTv(r.Name) - if meta == nil { //cannot parse name - continue + + if meta.IsSeasonPack {//try to parse episode number with description + mm := metadata.ParseTv(r.Description) + if mm.StartEpisode > 0 { //sometimes they put episode info in desc text + meta.IsSeasonPack = false + meta.StartEpisode = mm.StartEpisode + meta.EndEpisode = mm.EndEpisode + } + } if isImdbidNotMatch(series.ImdbID, r.ImdbId) { //has imdb id and not match continue @@ -61,9 +69,12 @@ func SearchTvSeries(db1 *db.Client, param *SearchParam) ([]torznab.Result, error continue } - if len(param.Episodes) > 0 && !slices.Contains(param.Episodes, meta.Episode) { //not season pack, but episode number not equal - continue - + if len(param.Episodes) > 0 { //not season pack, but episode number not equal + for i := meta.StartEpisode; i < meta.EndEpisode; i++ { + if !slices.Contains(param.Episodes, i) { + continue lo + } + } } else if len(param.Episodes) == 0 && !meta.IsSeasonPack { //want season pack, but not season pack continue }