diff --git a/cmd/main.go b/cmd/main.go index 23ff3fd..c9acf49 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,19 +1,41 @@ package main import ( - "polaris/db" "polaris/log" - "polaris/server" + "polaris/pkg/metadata" + "polaris/pkg/utils" + "regexp" + "strings" + "unicode" ) func main() { - dbClient, err := db.Open() - if err != nil { - log.Panicf("init db error: %v", err) - } + b := utils.IsNameAcceptable("legal high 2_勝利即是正", "胜利即是正义") + log.Info(b) + m := metadata.ParseMovie(" Inside Out (2013) 1080p WEBRip x264 -YTS") + log.Infof("%+v", m) + // dbClient, err := db.Open() + // if err != nil { + // log.Panicf("init db error: %v", err) + // } - s := server.NewServer(dbClient) - if err := s.Serve(); err != nil { - log.Errorf("server start error: %v", err) - } + // s := server.NewServer(dbClient) + // if err := s.Serve(); err != nil { + // log.Errorf("server start error: %v", err) + // } +} + +func preProcess(name string) string { + re := regexp.MustCompile(`[^\p{L}\w\s]`) + name1 := re.ReplaceAllString(strings.ToLower(name), "") + return name1 +} + +func asciiString(s string) bool { + for _, r := range s { + if r > unicode.MaxASCII { + return false + } + } + return true } diff --git a/pkg/metadata/movie.go b/pkg/metadata/movie.go new file mode 100644 index 0000000..5a4172c --- /dev/null +++ b/pkg/metadata/movie.go @@ -0,0 +1,43 @@ +package metadata + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +type MovieMetadata struct { + NameEn string + NameCN string + Year int + Resolution string +} + +func ParseMovie(name string) *MovieMetadata { + name = strings.ToLower(strings.TrimSpace(name)) + var meta = &MovieMetadata{} + yearRe := regexp.MustCompile(`\(\d{4}\)`) + yearMatches := yearRe.FindAllString(name, -1) + var yearIndex = -1 + if len(yearMatches) > 0 { + yearIndex = strings.Index(name, yearMatches[0]) + y := yearMatches[0][1 : len(yearMatches[0])-1] + n, err := strconv.Atoi(y) + if err != nil { + panic(fmt.Sprintf("convert %s error: %v", y, err)) + } + meta.Year = n + } + if yearIndex != -1 { + meta.NameEn = name[:yearIndex] + } else { + meta.NameEn = name + } + resRe := regexp.MustCompile(`\d{3,4}p`) + resMatches := resRe.FindAllString(name, -1) + if len(resMatches) > 0 { + meta.Resolution = resMatches[0] + } + return meta +} diff --git a/pkg/metadata/tv.go b/pkg/metadata/tv.go new file mode 100644 index 0000000..6d6ff57 --- /dev/null +++ b/pkg/metadata/tv.go @@ -0,0 +1,264 @@ +package metadata + +import ( + "fmt" + "polaris/log" + "polaris/pkg/utils" + "regexp" + "strconv" + "strings" +) + +type Metadata struct { + NameEn string + NameCn string + Season int + Episode int + Resolution string + IsSeasonPack bool +} + +func ParseTv(name string) *Metadata { + name = strings.ToLower(name) + if utils.IsASCII(name) { //english name + return parseEnglishName(name) + } + if utils.ContainsChineseChar(name) { + return parseChineseName(name) + } + return nil +} + +func parseEnglishName(name string) *Metadata { + re := regexp.MustCompile(`[^\p{L}\w\s]`) + name = re.ReplaceAllString(strings.ToLower(name), "") + + splits := strings.Split(strings.TrimSpace(name), " ") + var newSplits []string + for _, p := range splits { + p = strings.TrimSpace(p) + if p == "" { + continue + } + newSplits = append(newSplits, p) + } + + seasonRe := regexp.MustCompile(`^s\d{1,2}`) + resRe := regexp.MustCompile(`^\d{3,4}p`) + episodeRe := regexp.MustCompile(`e\d{1,2}`) + + 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 episodeRe.MatchString(p) { + episodeIndex = i + } + } + + meta := &Metadata{ + Season: -1, + Episode: -1, + } + if seasonIndex != -1 { + //season exists + if seasonIndex != 0 { + //name exists + names := newSplits[0:seasonIndex] + meta.NameEn = strings.Join(names, " ") + } + 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 + } + } + 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 + } + } + if resIndex != -1 { + //resolution exists + meta.Resolution = newSplits[resIndex] + } + if meta.Episode == -1 { + meta.IsSeasonPack = true + } + return meta +} + +func parseChineseName(name string) *Metadata { + var 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,2}\]`) + 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,2}(话|話|集)`) + episodeMatches1 := re2.FindAllString(name, -1) + if len(episodeMatches1) > 0 { + re := regexp.MustCompile(`\d{1,2}`) + 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 + } + } + } + + //season numner + seasonRe1 := regexp.MustCompile(`s\d{1,2}`) + seasonMatches := seasonRe1.FindAllString(name, -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 + } else { + seasonRe1 := regexp.MustCompile(`season \d{1,2}`) + seasonMatches := seasonRe1.FindAllString(name, -1) + if len(seasonMatches) > 0 { + re3 := regexp.MustCompile(`\d{1,2}`) + seNum := re3.FindAllString(seasonMatches[0], -1)[0] + n, err := strconv.Atoi(seNum) + if err != nil { + panic(fmt.Sprintf("convert %s error: %v", seNum, err)) + } + meta.Season = n + } else { + seasonRe1 := regexp.MustCompile(`第.{1}季`) + seasonMatches := seasonRe1.FindAllString(name, -1) + if len(seasonMatches) > 0 { + se := []rune(seasonMatches[0]) + seNum := se[1] + meta.Season = chinese2Num[string(seNum)] + + } + } + } + + if meta.IsSeasonPack && meta.Episode != 0{ + meta.Season = meta.Episode + meta.Episode = -1 + } + + //tv name + title := name + + fields := strings.FieldsFunc(title, func(r rune) bool { + return r == '[' || r == ']' || r == '【' || r == '】' + }) + title = "" + log.Info(fields) + for _, p := range fields { //寻找匹配的最长的字符串,最有可能是名字 + if len([]rune(p)) > len([]rune(title)) { + title = p + } + } + log.Infof("title: %v", title) + re := regexp.MustCompile(`[^\p{L}\w\s]`) + title = re.ReplaceAllString(strings.TrimSpace(strings.ToLower(title)), "") + + meta.NameCn = title + cnRe := regexp.MustCompile(`\p{Han}.*\p{Han}`) + cnmatches := cnRe.FindAllString(title, -1) + + if len(cnmatches) > 0 { + for _, t := range cnmatches { + if len([]rune(t)) > len([]rune(meta.NameCn)) { + meta.NameCn = strings.ToLower(t) + } + } + } + + 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.ToLower(t) + } + } + } + + return meta +} + +var chinese2Num = map[string]int{ + "一": 1, + "二": 2, + "三": 3, + "四": 4, + "五": 5, + "六": 6, + "七": 7, + "八": 8, + "九": 9, +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 7fcde68..6c856ea 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -15,7 +15,7 @@ import ( "golang.org/x/sys/unix" ) -func isASCII(s string) bool { +func IsASCII(s string) bool { for _, c := range s { if c > unicode.MaxASCII { return false @@ -36,7 +36,7 @@ func VerifyPassword(password, hash string) bool { return err == nil } -func IsChineseChar(str string) bool { +func ContainsChineseChar(str string) bool { for _, r := range str { if unicode.Is(unicode.Han, r) || (regexp.MustCompile("[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]").MatchString(string(r))) { @@ -61,7 +61,7 @@ func IsNameAcceptable(name1, name2 string) bool { re := regexp.MustCompile(`[^\p{L}\w\s]`) name1 = re.ReplaceAllString(strings.ToLower(name1), "") name2 = re.ReplaceAllString(strings.ToLower(name2), "") - return strutil.Similarity(name1, name2, metrics.NewHamming()) > 0.1 + return strutil.Similarity(name1, name2, metrics.NewHamming()) > 0.4 } func FindSeasonEpisodeNum(name string) (se int, ep int, err error) { diff --git a/server/core/torrent.go b/server/core/torrent.go index 3a72a15..4daf4c9 100644 --- a/server/core/torrent.go +++ b/server/core/torrent.go @@ -3,9 +3,8 @@ package core import ( "fmt" "polaris/db" - "polaris/ent" - "polaris/ent/media" "polaris/log" + "polaris/pkg/metadata" "polaris/pkg/torznab" "polaris/pkg/utils" "sort" @@ -16,33 +15,7 @@ import ( ) func SearchSeasonPackage(db1 *db.Client, seriesId, seasonNum int, checkResolution bool) ([]torznab.Result, error) { - series := db1.GetMediaDetails(seriesId) - if series == nil { - return nil, fmt.Errorf("no tv series of id %v", seriesId) - } - q := fmt.Sprintf("%s S%02d", series.NameEn, seasonNum) - - res := searchWithTorznab(db1, q) - if len(res) == 0 { - return nil, fmt.Errorf("no resource found") - } - var filtered []torznab.Result - for _, r := range res { - if !isNameAcceptable(r.Name, series.Media, seasonNum, -1) { - continue - } - if checkResolution && !IsWantedResolution(r.Name, series.Resolution) { - continue - } - - filtered = append(filtered, r) - - } - - if len(filtered) == 0 { - return nil, errors.New("no resource found") - } - return filtered, nil + return SearchEpisode(db1, seriesId, seasonNum, -1, checkResolution) } func SearchEpisode(db1 *db.Client, seriesId, seasonNum, episodeNum int, checkResolution bool) ([]torznab.Result, error) { @@ -51,23 +24,30 @@ func SearchEpisode(db1 *db.Client, seriesId, seasonNum, episodeNum int, checkRes return nil, fmt.Errorf("no tv series of id %v", seriesId) } - q := fmt.Sprintf("%s S%02dE%02d", series.NameEn, seasonNum, episodeNum) - res := searchWithTorznab(db1, q) - if len(res) == 0 { - return nil, fmt.Errorf("no resource found") - } + res := searchWithTorznab(db1, series.NameEn) + resCn := searchWithTorznab(db1, series.NameCn) + res = append(res, resCn...) var filtered []torznab.Result for _, r := range res { - if !isNameAcceptable(r.Name, series.Media, seasonNum, episodeNum) { + meta := metadata.ParseTv(r.Name) + if meta.Season != seasonNum { continue } - if checkResolution && !IsWantedResolution(r.Name, series.Resolution) { + if episodeNum != -1 && meta.Episode != episodeNum { //not season pack + continue + } + if checkResolution && meta.Resolution != series.Resolution.String() { + continue + } + if !utils.IsNameAcceptable(meta.NameEn, series.NameEn) && !utils.IsNameAcceptable(meta.NameCn, series.NameCn) { continue } - filtered = append(filtered, r) } + if len(filtered) == 0 { + return nil, errors.New("no resource found") + } return filtered, nil @@ -89,10 +69,16 @@ func SearchMovie(db1 *db.Client, movieId int, checkResolution bool) ([]torznab.R } var filtered []torznab.Result for _, r := range res { - if !isNameAcceptable(r.Name, movieDetail.Media, -1, -1) { + meta := metadata.ParseMovie(r.Name) + if !utils.IsNameAcceptable(meta.NameEn, movieDetail.NameEn) { continue } - if checkResolution && !IsWantedResolution(r.Name, movieDetail.Resolution) { + if checkResolution && meta.Resolution != movieDetail.Resolution.String() { + continue + } + ss := strings.Split(movieDetail.AirDate, "-")[0] + year, _ := strconv.Atoi(ss) + if meta.Year != year && meta.Year != year-1 && meta.Year != year+1 { //year not match continue } @@ -127,53 +113,3 @@ func searchWithTorznab(db *db.Client, q string) []torznab.Result { return res } - -func isNameAcceptable(torrentName string, m *ent.Media, seasonNum, episodeNum int) bool { - if !utils.IsNameAcceptable(torrentName, m.NameCn) && !utils.IsNameAcceptable(torrentName, m.NameEn) && !utils.IsNameAcceptable(torrentName, m.OriginalName){ - return false //name not match - } - - ss := strings.Split(m.AirDate, "-")[0] - year, _ := strconv.Atoi(ss) - if m.MediaType == media.MediaTypeMovie { - if !strings.Contains(torrentName, strconv.Itoa(year)) && !strings.Contains(torrentName, strconv.Itoa(year+1)) && !strings.Contains(torrentName, strconv.Itoa(year-1)) { - return false //not the same movie, if year is not correct - } - } - - if m.MediaType == media.MediaTypeTv { - if episodeNum != -1 { - se := fmt.Sprintf("S%02dE%02d", seasonNum, episodeNum) - if !utils.ContainsIgnoreCase(torrentName, se) { - return false - } - } else { - //season package - if !utils.IsSeasonPackageName(torrentName) { - return false - } - - seNum, err := utils.FindSeasonPackageInfo(torrentName) - if err != nil { - return false - } - if seNum != seasonNum { - return false - } - - } - } - return true -} - -func IsWantedResolution(name string, res media.Resolution) bool { - switch res { - case media.Resolution720p: - return utils.ContainsIgnoreCase(name, "720p") - case media.Resolution1080p: - return utils.ContainsIgnoreCase(name, "1080p") - case media.Resolution4k: - return utils.ContainsIgnoreCase(name, "4k") || utils.ContainsIgnoreCase(name, "2160p") - } - return false -} \ No newline at end of file diff --git a/server/storage.go b/server/storage.go index 91cd5e8..f342784 100644 --- a/server/storage.go +++ b/server/storage.go @@ -62,7 +62,7 @@ func (s *Server) SuggestedSeriesFolderName(c *gin.Context) (interface{}, error) } name = fmt.Sprintf("%s %s", name, originalName) - if !utils.IsChineseChar(name) { + if !utils.ContainsChineseChar(name) { name = originalName } if year != "" {