mirror of
https://github.com/simon-ding/polaris.git
synced 2026-04-20 10:47:30 +08:00
feat: japan anime support
This commit is contained in:
42
cmd/main.go
42
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
|
||||
}
|
||||
|
||||
43
pkg/metadata/movie.go
Normal file
43
pkg/metadata/movie.go
Normal file
@@ -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
|
||||
}
|
||||
264
pkg/metadata/tv.go
Normal file
264
pkg/metadata/tv.go
Normal file
@@ -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,
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 != "" {
|
||||
|
||||
Reference in New Issue
Block a user