diff --git a/gui/frontend/src/hooks/useSettingsState.tsx b/gui/frontend/src/hooks/useSettingsState.tsx index b45cbabd..2efbb42e 100644 --- a/gui/frontend/src/hooks/useSettingsState.tsx +++ b/gui/frontend/src/hooks/useSettingsState.tsx @@ -653,6 +653,13 @@ const trackerSchemas: Record = { trackerFieldMeta.APIKey, trackerFieldMeta.Anon, ], + VMF: [ + trackerFieldMeta.FaviconURL, + trackerFieldMeta.LinkDirName, + trackerFieldMeta.APIKey, + trackerFieldMeta.Anon, + trackerFieldMeta.ModQ, + ], YUS: [ trackerFieldMeta.FaviconURL, trackerFieldMeta.LinkDirName, diff --git a/internal/clients/service.go b/internal/clients/service.go index ea960a35..dfa5fa6d 100644 --- a/internal/clients/service.go +++ b/internal/clients/service.go @@ -345,6 +345,11 @@ func (s *Service) injectQbit(ctx context.Context, name string, client config.Tor if mapped { options.SavePath = savePath s.logger.Debugf("clients: qbit path mapping ready client=%s save_path=%s", name, savePath) + } else { + if sp, err := sourcePathForQbitSavePath(meta); err == nil { + options.SavePath = qbitSavePath(filepath.Dir(sp)) + s.logger.Debugf("clients: qbit local save_path ready client=%s save_path=%s", name, options.SavePath) + } } } if category := strings.TrimSpace(client.QbitCrossCategory); torrent.CrossSeed && category != "" { diff --git a/internal/config/defaults/example.yaml b/internal/config/defaults/example.yaml index 59932788..6e85b58b 100644 --- a/internal/config/defaults/example.yaml +++ b/internal/config/defaults/example.yaml @@ -519,6 +519,12 @@ trackers: api_key: "" url: "https://utp.to" anon: false + VMF: + link_dir_name: "" + api_key: "" + url: "https://tracker.vietmediaf.store" + anon: false + modq: false YUS: link_dir_name: "" api_key: "" diff --git a/internal/metadata/media_details.go b/internal/metadata/media_details.go index 087c623f..6e06e667 100644 --- a/internal/metadata/media_details.go +++ b/internal/metadata/media_details.go @@ -54,8 +54,18 @@ func (s *Service) ApplyMediaDetails(ctx context.Context, meta api.PreparedMetada s.logger.Warnf("metadata: mediainfo validation failed (missing unique id)") } meta.AudioLanguages, meta.SubtitleLanguages = extractMediaInfoLanguages(miDoc) + var audioTitles []string + for _, track := range miDoc.Media.Track { + if strings.EqualFold(strings.TrimSpace(trackString(track, "@type")), "Audio") { + title := trackString(track, "Title", "Title_String", "Title_String2", "Title_String3") + if strings.TrimSpace(title) != "" { + audioTitles = append(audioTitles, title) + } + } + } + meta.AudioTitles = audioTitles if s.logger != nil && (len(meta.AudioLanguages) > 0 || len(meta.SubtitleLanguages) > 0) { - s.logger.Debugf("metadata: media languages audio=%v subs=%v", meta.AudioLanguages, meta.SubtitleLanguages) + s.logger.Debugf("metadata: media languages audio=%v subs=%v titles=%v", meta.AudioLanguages, meta.SubtitleLanguages, meta.AudioTitles) } bdinfo := loadBDInfo(meta, s.cfg.MainSettings.DBPath) diff --git a/internal/trackers/catalog.go b/internal/trackers/catalog.go index da92f8a3..6eab0474 100644 --- a/internal/trackers/catalog.go +++ b/internal/trackers/catalog.go @@ -136,7 +136,7 @@ func IsNonUnit3DTracker(name string) bool { // or any tracker known to accept modified releases. func skipsModifiedReleaseCheck(name string) bool { switch strings.ToUpper(strings.TrimSpace(name)) { - case "MANUAL": + case "MANUAL", "VMF": return true default: return false diff --git a/internal/trackers/impl/unit3d/names.go b/internal/trackers/impl/unit3d/names.go index 0bf1fe23..45cd1e3d 100644 --- a/internal/trackers/impl/unit3d/names.go +++ b/internal/trackers/impl/unit3d/names.go @@ -20,6 +20,8 @@ import ( ) var noGroupTagPattern = regexp.MustCompile(`(?i)-(nogrp|nogroup|unknown|-unk-)`) +var vmfDubPattern = regexp.MustCompile(`(?i)(lồng tiếng|long tieng|\b(?:us|vn)lt\b)`) +var vmfViePattern = regexp.MustCompile(`(?i)(thuyết minh|thuyet minh|\btm\b)`) var ( languageTagLookupOnce sync.Once languageTagLookup map[string]language.Tag @@ -57,6 +59,8 @@ func buildUnit3DName(tracker string, meta api.PreparedMetadata, cfg config.Track return addNoGroupSuffix(name, meta, "NOGRP") case "ULCX": return buildULCXName(name, meta) + case "VMF": + return buildVMFName(name, meta) case "ZNTH": return buildZNTHName(name, meta) default: @@ -87,6 +91,60 @@ func buildULCXName(name string, meta api.PreparedMetadata) string { return strings.TrimSpace(strings.Join(strings.Fields(name), " ")) } +func buildVMFName(name string, meta api.PreparedMetadata) string { + hasDub := false + hasVie := false + + for _, title := range meta.AudioTitles { + if vmfDubPattern.MatchString(title) { + hasDub = true + } + if vmfViePattern.MatchString(title) { + hasVie = true + } + } + + for _, lang := range meta.AudioLanguages { + if strings.ToLower(strings.TrimSpace(lang)) == "vietnamese" { + hasVie = true + } + } + + tag := "" + if hasDub { + tag = "ViE DUB" + } else if hasVie { + tag = "ViE" + } + + if tag == "" || strings.Contains(strings.ToLower(name), strings.ToLower(tag)) { + return strings.TrimSpace(strings.Join(strings.Fields(name), " ")) + } + + words := strings.Fields(name) + insertIdx := -1 + parsedRes := strings.ToLower(meta.Release.Resolution) + + // Attempt to find resolution to insert before + for i, w := range words { + lowerW := strings.ToLower(w) + if parsedRes != "" && lowerW == parsedRes { + insertIdx = i + break + } + } + + if insertIdx != -1 { + newWords := make([]string, 0, len(words)+2) + newWords = append(newWords, words[:insertIdx]...) + newWords = append(newWords, tag) + newWords = append(newWords, words[insertIdx:]...) + return strings.Join(newWords, " ") + } + + return strings.TrimSpace(name + " " + tag) +} + func resolveDPAudioLabel(languages []string) string { normalized := make(map[string]struct{}, len(languages)) for _, language := range languages { diff --git a/internal/trackers/unit3dmeta/metadata.go b/internal/trackers/unit3dmeta/metadata.go index 69eaa8d2..7c76009e 100644 --- a/internal/trackers/unit3dmeta/metadata.go +++ b/internal/trackers/unit3dmeta/metadata.go @@ -36,7 +36,7 @@ func initTrackers() { "FRIKI", "HHD", "IHD", "ITT", "LCD", "LDU", "LST", "LT", "LUME", "MNS", "OE", "OTW", "PT", "PTT", "R4E", "RAS", "RF", "RHD", "SAM", "SHRI", "SP", "STC", "TIK", "TLZ", "TOS", "TTR", - "ULCX", "UTP", "YUS", "ZNTH", + "ULCX", "UTP", "VMF", "YUS", "ZNTH", } for _, name := range unit3DTrackers { diff --git a/pkg/api/services.go b/pkg/api/services.go index 0f93e147..49c407f8 100644 --- a/pkg/api/services.go +++ b/pkg/api/services.go @@ -216,6 +216,7 @@ type PreparedMetadata struct { ExternalMetadata ExternalMetadata AudioLanguages []string SubtitleLanguages []string + AudioTitles []string Container string Audio string Channels string