mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +08:00
18e6cb40fd
Co-authored-by: Cursor <cursoragent@cursor.com>
205 lines
5.7 KiB
Go
205 lines
5.7 KiB
Go
package service
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var namedTheatricalFolderRE = regexp.MustCompile(`(?i)(?:剧场版|劇場版|动画电影|動畫電影|电影版|電影版)`)
|
|
|
|
func mediaFolderTitle(mediaPath, libraryRoot string) string {
|
|
dir := parentSlashPath(mediaPath)
|
|
root := comparableLibraryRoot(libraryRoot)
|
|
for depth := 0; depth < 5 && dir != ""; depth++ {
|
|
if root != "" && sameSlashPath(dir, root) {
|
|
return libraryRootTitle(libraryRoot)
|
|
}
|
|
base := pathBaseSlash(dir)
|
|
if base == "" || base == "." {
|
|
return ""
|
|
}
|
|
if isTechnicalMediaFolder(base) || strictSeasonFolderMatched(base) || isTheatricalFolder(base) {
|
|
dir = parentSlashPath(dir)
|
|
continue
|
|
}
|
|
if isMediaCollectionFolder(base) {
|
|
dir = parentSlashPath(dir)
|
|
continue
|
|
}
|
|
if isGenericMediaCategoryFolder(base) {
|
|
return ""
|
|
}
|
|
title, _ := CleanQuery(base)
|
|
if title == "" {
|
|
title = strings.TrimSpace(base)
|
|
}
|
|
return strings.TrimSpace(title)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func isTechnicalMediaFolder(name string) bool {
|
|
key := strings.ToLower(strings.TrimSpace(name))
|
|
compact := strings.NewReplacer(" ", "", "_", "", ".", "", "-", "").Replace(key)
|
|
switch compact {
|
|
case "bdmv", "stream", "certificate", "videots", "audiots",
|
|
"subs", "subtitles", "subtitle", "sample", "samples",
|
|
"extra", "extras", "featurette", "featurettes":
|
|
return true
|
|
default:
|
|
return numberedTechnicalFolder(compact, "disc") ||
|
|
numberedTechnicalFolder(compact, "disk") ||
|
|
numberedTechnicalFolder(compact, "cd") ||
|
|
numberedTechnicalFolder(compact, "dvd") ||
|
|
numberedTechnicalFolder(compact, "part")
|
|
}
|
|
}
|
|
|
|
func numberedTechnicalFolder(value, prefix string) bool {
|
|
if !strings.HasPrefix(value, prefix) || len(value) == len(prefix) {
|
|
return false
|
|
}
|
|
for _, r := range value[len(prefix):] {
|
|
if r < '0' || r > '9' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func cleanSlashPath(value string) string {
|
|
value = strings.TrimSpace(strings.ReplaceAll(value, "\\", "/"))
|
|
return strings.TrimRight(value, "/")
|
|
}
|
|
|
|
func pathBaseSlash(value string) string {
|
|
value = strings.Trim(strings.ReplaceAll(strings.TrimSpace(value), "\\", "/"), "/")
|
|
if value == "" {
|
|
return ""
|
|
}
|
|
parts := strings.Split(value, "/")
|
|
return parts[len(parts)-1]
|
|
}
|
|
|
|
func comparableLibraryRoot(libraryRoot string) string {
|
|
if info, ok := ParseCloudLibraryMount(libraryRoot); ok {
|
|
if strings.TrimSpace(info.DisplayDir) == "" {
|
|
return "cloud://" + info.Provider
|
|
}
|
|
return "cloud://" + info.Provider + "/" + info.DisplayDir
|
|
}
|
|
return cleanSlashPath(libraryRoot)
|
|
}
|
|
|
|
func sameSlashPath(a, b string) bool {
|
|
return strings.EqualFold(cleanSlashPath(a), cleanSlashPath(b))
|
|
}
|
|
|
|
func parentSlashPath(value string) string {
|
|
value = cleanSlashPath(value)
|
|
if value == "" {
|
|
return ""
|
|
}
|
|
idx := strings.LastIndex(value, "/")
|
|
if idx < 0 {
|
|
return ""
|
|
}
|
|
return strings.TrimRight(value[:idx], "/")
|
|
}
|
|
|
|
func seriesFolderTitle(mediaPath, libraryRoot string) string {
|
|
dir := parentSlashPath(mediaPath)
|
|
if strictSeasonFolderMatched(pathBaseSlash(dir)) || isTheatricalFolder(pathBaseSlash(dir)) {
|
|
dir = parentSlashPath(dir)
|
|
}
|
|
if root := comparableLibraryRoot(libraryRoot); root != "" && sameSlashPath(dir, root) {
|
|
return libraryRootTitle(libraryRoot)
|
|
}
|
|
base := pathBaseSlash(dir)
|
|
if base == "" || base == "." {
|
|
return ""
|
|
}
|
|
if isGenericMediaCategoryFolder(base) || isTechnicalMediaFolder(base) || strictSeasonFolderMatched(base) || isTheatricalFolder(base) {
|
|
return ""
|
|
}
|
|
return base
|
|
}
|
|
|
|
func isTheatricalFolder(name string) bool {
|
|
key := strings.ToLower(strings.TrimSpace(name))
|
|
key = strings.Trim(key, `\/`)
|
|
if namedTheatricalFolderRE.MatchString(key) {
|
|
return true
|
|
}
|
|
switch key {
|
|
case "剧场版", "劇場版", "动画电影", "動畫電影", "特别篇", "特別篇",
|
|
"special", "specials", "sp", "ova", "ovas", "oad", "oads", "ovd", "ovds", "ona", "onas",
|
|
"extra", "extras", "bonus", "bonuses", "omake", "picture drama", "ncop", "nced", "画像特典":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func pathHasTheatricalFolder(path string) bool {
|
|
for _, part := range strings.FieldsFunc(path, func(r rune) bool {
|
|
return r == '/' || r == '\\'
|
|
}) {
|
|
if isTheatricalFolder(part) && namedTheatricalFolderRE.MatchString(part) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func libraryRootTitle(libraryRoot string) string {
|
|
base := ""
|
|
if info, ok := ParseCloudLibraryMount(libraryRoot); ok {
|
|
base = pathBaseSlash(info.DisplayDir)
|
|
} else {
|
|
base = pathBaseSlash(libraryRoot)
|
|
}
|
|
if base == "" || base == "." || isGenericMediaCategoryFolder(base) || isTechnicalMediaFolder(base) || strictSeasonFolderMatched(base) || isMediaCollectionFolder(base) {
|
|
return ""
|
|
}
|
|
return base
|
|
}
|
|
|
|
func isGenericMediaCategoryFolder(name string) bool {
|
|
key := strings.ToLower(strings.TrimSpace(name))
|
|
key = strings.Trim(key, `\/`)
|
|
switch key {
|
|
case "",
|
|
"电影", "movies", "movie",
|
|
"电视剧", "剧集", "tv", "shows", "series",
|
|
"动漫", "动画", "anime", "bangumi",
|
|
"国产剧", "国剧", "大陆剧", "国产电视剧",
|
|
"欧美剧", "欧美电视剧",
|
|
"日韩剧", "日剧", "韩剧",
|
|
"华语电影", "国产电影", "大陆电影",
|
|
"外语电影", "外国电影", "欧美电影", "日韩电影", "演唱会", "音乐会",
|
|
"动画电影", "动漫电影",
|
|
"国漫", "国产动漫", "日番", "日漫", "日本动漫", "日本动画", "韩漫", "韩国动漫", "韩国动画", "美漫", "欧美动漫", "欧美动画", "西方动画", "其他动漫", "其它动漫",
|
|
"综艺", "真人秀",
|
|
"纪录片", "纪录",
|
|
"儿童", "少儿",
|
|
"成人", "番号", "9kg",
|
|
"未分类", "uncategorized":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func strictSeasonFolder(name string) int {
|
|
if season, ok := seasonFromDir(name); ok {
|
|
return season
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func strictSeasonFolderMatched(name string) bool {
|
|
_, ok := seasonFromDir(name)
|
|
return ok
|
|
}
|