mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 19:36:36 +08:00
优化
优化
This commit is contained in:
@@ -31,7 +31,10 @@ var (
|
||||
var adultExcludedPrefixes = map[string]struct{}{
|
||||
"AC": {}, "AAC": {}, "AVC": {}, "BD": {}, "CD": {}, "DDP": {}, "DTS": {},
|
||||
"FHD": {}, "HD": {}, "HEVC": {}, "HDR": {}, "MP": {}, "SD": {}, "UHD": {},
|
||||
"WEB": {}, "X264": {}, "X265": {},
|
||||
"WEB": {}, "X264": {}, "X265": {}, "H264": {}, "H265": {}, "AV1": {},
|
||||
"SEASON": {}, "EPISODE": {}, "EP": {}, "SP": {}, "OVA": {}, "OAD": {},
|
||||
"TMDB": {}, "TMDBID": {}, "DOUBAN": {}, "BANGUMI": {}, "BGM": {}, "TVDB": {}, "THETVDB": {},
|
||||
"PART": {}, "VOL": {}, "DISC": {}, "DISK": {}, "TRACK": {}, "CHAPTER": {}, "SAMPLE": {}, "TRAILER": {},
|
||||
}
|
||||
|
||||
var defaultAdultBases = []string{
|
||||
@@ -87,11 +90,12 @@ func (p *AdultProvider) Search(ctx context.Context, code string) (*Match, error)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if match != nil {
|
||||
match.OriginalName = code
|
||||
match.NSFW = true
|
||||
return match, nil
|
||||
}
|
||||
if match != nil {
|
||||
match.OriginalName = code
|
||||
match.Title = FormatAdultTitle(code, match.Title)
|
||||
match.NSFW = true
|
||||
return match, nil
|
||||
}
|
||||
}
|
||||
return nil, lastErr
|
||||
}
|
||||
|
||||
@@ -39,3 +39,57 @@ func normalizeAdultCode(input string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// CleanAdultTitle 去除标题中冗余的前缀番号、方括号、分隔符,提取纯标题内容
|
||||
func CleanAdultTitle(code, rawTitle string) string {
|
||||
rawTitle = strings.TrimSpace(rawTitle)
|
||||
code = normalizeAdultCode(code)
|
||||
if rawTitle == "" {
|
||||
return ""
|
||||
}
|
||||
clean := rawTitle
|
||||
for {
|
||||
trimmed := strings.TrimSpace(clean)
|
||||
if trimmed == "" {
|
||||
return ""
|
||||
}
|
||||
if code != "" {
|
||||
upper := strings.ToUpper(trimmed)
|
||||
upperCode := strings.ToUpper(code)
|
||||
if strings.HasPrefix(upper, upperCode) {
|
||||
clean = strings.TrimSpace(trimmed[len(upperCode):])
|
||||
clean = strings.TrimLeft(clean, "-_ ::]】")
|
||||
continue
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(trimmed, "[") || strings.HasPrefix(trimmed, "【") {
|
||||
idx := strings.IndexAny(trimmed, "]】")
|
||||
if idx > 0 {
|
||||
bracketContent := normalizeAdultCode(trimmed[1:idx])
|
||||
if bracketContent != "" && (code == "" || bracketContent == code) {
|
||||
clean = strings.TrimSpace(trimmed[idx+len("]"):])
|
||||
clean = strings.TrimLeft(clean, "-_ ::")
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return strings.TrimSpace(clean)
|
||||
}
|
||||
|
||||
// FormatAdultTitle 格式化番号标题:把番号放在最前面,如 "IPZZ-090-具体标题" 或 "IPZZ-090"
|
||||
func FormatAdultTitle(code, rawTitle string) string {
|
||||
code = normalizeAdultCode(code)
|
||||
if code == "" {
|
||||
code = normalizeAdultCode(rawTitle)
|
||||
}
|
||||
clean := CleanAdultTitle(code, rawTitle)
|
||||
if code == "" {
|
||||
return clean
|
||||
}
|
||||
if clean == "" || strings.EqualFold(clean, code) {
|
||||
return code
|
||||
}
|
||||
return code + "-" + clean
|
||||
}
|
||||
|
||||
@@ -9,14 +9,17 @@ import (
|
||||
)
|
||||
|
||||
func parseAdultDetailHTML(body, code, source, detailURL string) *Match {
|
||||
code = normalizeAdultCode(code)
|
||||
match := &Match{
|
||||
OriginalName: code,
|
||||
MediaType: "adult",
|
||||
NSFW: true,
|
||||
Genres: []string{"Adult", source},
|
||||
}
|
||||
if title := firstAdultTitle(body, code); title != "" {
|
||||
match.Title = title
|
||||
rawTitle := firstAdultTitle(body, code)
|
||||
match.Title = FormatAdultTitle(code, rawTitle)
|
||||
if match.Title == "" {
|
||||
match.Title = code
|
||||
}
|
||||
if match.Title == "" {
|
||||
return nil
|
||||
@@ -48,7 +51,10 @@ func firstAdultTitle(body, code string) string {
|
||||
if title == "" {
|
||||
continue
|
||||
}
|
||||
title = strings.TrimSpace(strings.TrimPrefix(strings.TrimPrefix(title, code), strings.ToUpper(code)))
|
||||
clean := CleanAdultTitle(code, title)
|
||||
if clean != "" {
|
||||
return clean
|
||||
}
|
||||
if title != "" {
|
||||
return title
|
||||
}
|
||||
|
||||
@@ -36,13 +36,13 @@ func TestParseAdultDetailHTML(t *testing.T) {
|
||||
<div>日期 2024-05-01</div>
|
||||
</html>`
|
||||
|
||||
got := parseAdultDetailHTML(html, "SSIS-001", "javdb", "https://javdb.com/v/abc")
|
||||
if got == nil {
|
||||
t.Fatal("parseAdultDetailHTML returned nil")
|
||||
}
|
||||
if got.Title != "测试标题" || got.OriginalName != "SSIS-001" || !got.NSFW {
|
||||
t.Fatalf("unexpected metadata: %+v", got)
|
||||
}
|
||||
got := parseAdultDetailHTML(html, "SSIS-001", "javdb", "https://javdb.com/v/abc")
|
||||
if got == nil {
|
||||
t.Fatal("parseAdultDetailHTML returned nil")
|
||||
}
|
||||
if got.Title != "SSIS-001-测试标题" || got.OriginalName != "SSIS-001" || !got.NSFW {
|
||||
t.Fatalf("unexpected metadata: %+v", got)
|
||||
}
|
||||
if got.PosterURL != "https://javdb.com/covers/ssis001.jpg" || got.BackdropURL != "https://javdb.com/samples/1.jpg" {
|
||||
t.Fatalf("unexpected artwork: %+v", got)
|
||||
}
|
||||
@@ -98,9 +98,9 @@ func TestAdultProviderUsesConfiguredMultipleSources(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if match == nil || match.Title != "多源命中标题" || match.OriginalName != "SSIS-001" || !match.NSFW {
|
||||
t.Fatalf("multi-source adult match = %+v", match)
|
||||
}
|
||||
if match == nil || match.Title != "SSIS-001-多源命中标题" || match.OriginalName != "SSIS-001" || !match.NSFW {
|
||||
t.Fatalf("multi-source adult match = %+v", match)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdultSourceKindRecognizesJavBusMirrors(t *testing.T) {
|
||||
|
||||
+13
-2
@@ -152,9 +152,20 @@ func WriteMediaNFO(m *model.Media) (string, error) {
|
||||
Language: splitNFOList(m.Languages),
|
||||
}
|
||||
} else {
|
||||
title := m.Title
|
||||
original := m.OriginalName
|
||||
adultCode := normalizeAdultCode(firstText(m.OriginalName, AdultCodeFromMediaPath(m.Path), m.Title))
|
||||
if adultCode != "" || m.NSFW {
|
||||
if adultCode != "" {
|
||||
title = FormatAdultTitle(adultCode, m.Title)
|
||||
if original == "" || strings.EqualFold(original, m.Title) {
|
||||
original = adultCode
|
||||
}
|
||||
}
|
||||
}
|
||||
doc = movieNFO{
|
||||
Title: m.Title,
|
||||
Original: m.OriginalName,
|
||||
Title: title,
|
||||
Original: original,
|
||||
Year: m.Year,
|
||||
Plot: m.Overview,
|
||||
Rating: m.Rating,
|
||||
|
||||
@@ -68,3 +68,35 @@ func TestWriteMediaNFOUsesEpisodeTitleForEpisodeDetails(t *testing.T) {
|
||||
t.Fatalf("episode nfo did not keep episode/show titles separate:\n%s", text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMediaNFOAdultTitleWithCode(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mediaPath := filepath.Join(root, "成人", "IPZZ-090-测试成人影片", "IPZZ-090.mp4")
|
||||
if err := os.MkdirAll(filepath.Dir(mediaPath), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(mediaPath, []byte("media"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := WriteMediaNFO(&model.Media{
|
||||
Title: "测试成人影片",
|
||||
OriginalName: "IPZZ-090",
|
||||
Path: mediaPath,
|
||||
NSFW: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body, err := os.ReadFile(got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
text := string(body)
|
||||
if !strings.Contains(text, "<title>IPZZ-090-测试成人影片</title>") {
|
||||
t.Fatalf("expected title with code at front in adult nfo, got:\n%s", text)
|
||||
}
|
||||
if !strings.Contains(text, "<originaltitle>IPZZ-090</originaltitle>") {
|
||||
t.Fatalf("expected originaltitle with code, got:\n%s", text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,13 +63,13 @@ func TestOrganizeDirectoryUsesAdultMetadataBeforeRename(t *testing.T) {
|
||||
if res.Organized != 1 || len(res.Items) != 1 {
|
||||
t.Fatalf("result = %+v, want one organized preview", res)
|
||||
}
|
||||
if res.Items[0].Title != "整理成人标题" || res.Items[0].MediaType != "adult" {
|
||||
t.Fatalf("adult organize item = %+v", res.Items[0])
|
||||
}
|
||||
wantSuffix := filepath.Join("成人", "整理成人标题 (2024)", "整理成人标题 (2024).mkv")
|
||||
if !strings.Contains(res.Items[0].Target, wantSuffix) {
|
||||
t.Fatalf("adult target = %q, want suffix %q", res.Items[0].Target, wantSuffix)
|
||||
}
|
||||
if res.Items[0].Title != "SSIS-001-整理成人标题" || res.Items[0].MediaType != "adult" {
|
||||
t.Fatalf("adult organize item = %+v", res.Items[0])
|
||||
}
|
||||
wantSuffix := filepath.Join("成人", "SSIS-001-整理成人标题", "SSIS-001-整理成人标题.mkv")
|
||||
if !strings.Contains(res.Items[0].Target, wantSuffix) {
|
||||
t.Fatalf("adult target = %q, want suffix %q", res.Items[0].Target, wantSuffix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrganizeDirectoryUsesBangumiForAnimeRename(t *testing.T) {
|
||||
|
||||
@@ -31,9 +31,22 @@ func (o *OrganizerService) buildOrganizeTargetPath(ctx context.Context, in organ
|
||||
if root == "" || root == "." {
|
||||
return organizeTargetPath{}, fmt.Errorf("organize target root required")
|
||||
}
|
||||
isAdult := normalizeOrganizeMediaType(in.MediaType) == "adult" || normalizeOrganizeCategoryKey(in.Category) == normalizeOrganizeCategoryKey("成人")
|
||||
var adultCode string
|
||||
if isAdult {
|
||||
adultCode = normalizeAdultCode(firstText(AdultCodeFromMediaPath(in.Source), normalizeAdultCode(in.Title)))
|
||||
}
|
||||
|
||||
title := sanitizeFilename(strings.TrimSpace(in.Title))
|
||||
if isAdult && adultCode != "" {
|
||||
title = sanitizeFilename(FormatAdultTitle(adultCode, title))
|
||||
}
|
||||
if title == "" {
|
||||
title = "Unknown"
|
||||
if adultCode != "" {
|
||||
title = adultCode
|
||||
} else {
|
||||
title = "Unknown"
|
||||
}
|
||||
}
|
||||
ext := strings.TrimSpace(in.Ext)
|
||||
if ext != "" && !strings.HasPrefix(ext, ".") {
|
||||
@@ -56,10 +69,11 @@ func (o *OrganizerService) buildOrganizeTargetPath(ctx context.Context, in organ
|
||||
template := strings.TrimSpace(o.organizeNamingFormat(ctx, in.MediaType, in.Series))
|
||||
var rel string
|
||||
if template == "" {
|
||||
rel = defaultOrganizeRelativePath(title, ext, in.Year, in.Season, in.Episode, in.Series)
|
||||
rel = defaultOrganizeRelativePath(title, ext, in.Year, in.Season, in.Episode, in.Series, isAdult)
|
||||
} else {
|
||||
rel = renderOrganizeNamingTemplate(template, organizeNamingData{
|
||||
Title: title,
|
||||
Code: adultCode,
|
||||
Year: in.Year,
|
||||
Season: in.Season,
|
||||
Episode: in.Episode,
|
||||
@@ -73,7 +87,7 @@ func (o *OrganizerService) buildOrganizeTargetPath(ctx context.Context, in organ
|
||||
})
|
||||
rel = cleanOrganizeRelativePath(rel)
|
||||
if rel == "" {
|
||||
rel = defaultOrganizeRelativePath(title, ext, in.Year, in.Season, in.Episode, in.Series)
|
||||
rel = defaultOrganizeRelativePath(title, ext, in.Year, in.Season, in.Episode, in.Series, isAdult)
|
||||
}
|
||||
if ext != "" && !strings.EqualFold(filepath.Ext(rel), ext) {
|
||||
rel += ext
|
||||
@@ -98,6 +112,10 @@ func (o *OrganizerService) organizeNamingFormat(ctx context.Context, mediaType s
|
||||
} else {
|
||||
key = "organize.tv_format"
|
||||
}
|
||||
} else if normalizeOrganizeMediaType(mediaType) == "adult" {
|
||||
if value, err := o.repo.Setting.Get(ctx, "organize.adult_format"); err == nil && strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
value, err := o.repo.Setting.Get(ctx, key)
|
||||
if err != nil {
|
||||
@@ -106,7 +124,7 @@ func (o *OrganizerService) organizeNamingFormat(ctx context.Context, mediaType s
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
func defaultOrganizeRelativePath(title, ext string, year, season, episode int, series bool) string {
|
||||
func defaultOrganizeRelativePath(title, ext string, year, season, episode int, series bool, isAdult ...bool) string {
|
||||
if series {
|
||||
if season < 0 {
|
||||
season = 1
|
||||
@@ -117,6 +135,9 @@ func defaultOrganizeRelativePath(title, ext string, year, season, episode int, s
|
||||
episodeTag := fmt.Sprintf("S%02dE%02d", season, episode)
|
||||
return filepath.Join(title, fmt.Sprintf("Season %02d", season), fmt.Sprintf("%s - %s%s", title, episodeTag, ext))
|
||||
}
|
||||
if len(isAdult) > 0 && isAdult[0] {
|
||||
return filepath.Join(title, title+ext)
|
||||
}
|
||||
folder := title
|
||||
if year > 0 {
|
||||
folder = fmt.Sprintf("%s (%d)", title, year)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
type organizeNamingData struct {
|
||||
Title string
|
||||
Code string
|
||||
Year int
|
||||
Season int
|
||||
Episode int
|
||||
@@ -73,9 +74,11 @@ func renderOrganizeConditionals(template string, data organizeNamingData) string
|
||||
|
||||
func organizeTemplateTruthy(name string, data organizeNamingData) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(name)) {
|
||||
case "title", "name":
|
||||
return data.Title != ""
|
||||
case "year":
|
||||
case "title", "name":
|
||||
return data.Title != ""
|
||||
case "code", "num", "adult_code":
|
||||
return data.Code != ""
|
||||
case "year":
|
||||
return data.Year > 0
|
||||
case "season":
|
||||
return data.Season >= 0 && data.Episode > 0
|
||||
@@ -102,9 +105,11 @@ func organizeTemplateTruthy(name string, data organizeNamingData) bool {
|
||||
|
||||
func organizeTemplateValue(name, format string, data organizeNamingData, fallback string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(name)) {
|
||||
case "title", "name":
|
||||
return data.Title
|
||||
case "year":
|
||||
case "title", "name":
|
||||
return data.Title
|
||||
case "code", "num", "adult_code":
|
||||
return data.Code
|
||||
case "year":
|
||||
if data.Year <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user