mirror of
https://github.com/simon-ding/polaris.git
synced 2026-03-03 08:00:47 +08:00
feat: ability to parse multi episode
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user