fix(metadata): tighten matcher and bare IMDb parsing

This commit is contained in:
Quick
2026-05-28 13:09:56 -04:00
parent 5d6be3ad4b
commit 3f49271f63
4 changed files with 48 additions and 11 deletions
+5 -1
View File
@@ -539,7 +539,11 @@ func selectInitialMatchCandidate(hints *MatchHints, candidates []MatchCandidate,
titleCorroborated := hints.Year != 0 && best.candidate.Year != 0 &&
absYearDelta(best.candidate.Year, hints.Year) <= 2 &&
normalizeTitleForScoring(best.candidate.Title) == normalizeTitleForScoring(hints.Title)
if yearCorroborated || multiSourceCorroborated || titleCorroborated {
// Year or source-count corroboration is only meaningful when the winning
// candidate is at least title-coherent with the scanner hint. Otherwise a
// high source/provider score can auto-accept an unrelated same-year result.
hintTitleCoherent := inferTitleSimilarity(hints.Title, best.candidate.Title, hints.Year) > 0
if (hintTitleCoherent && (yearCorroborated || multiSourceCorroborated)) || titleCorroborated {
return pickByProviderPriority(topGroup, providerPriority), true
}
}
@@ -39,6 +39,26 @@ func TestSelectInitialMatchCandidate_LoneResultYearMismatchStillRejected(t *test
}
}
func TestSelectInitialMatchCandidate_CorroborationRequiresHintTitleCoherence(t *testing.T) {
// Same-year, multi-source candidate scores above the 55 floor via provider
// evidence, but the title is not coherent with the scanner hint. The lone
// result rule must not rescue it just because the year/source evidence is
// strong enough to reach the corroboration branch.
hints := &MatchHints{Title: "Hotel Transylvania Puppy!", Year: 2017, Type: "movie"}
cands := []MatchCandidate{
{
Title: "Puppy!",
Year: 2017,
ContentType: "movie",
Sources: []string{"tmdb", "tvdb", "imdb"},
ProviderIDs: map[string]string{"tmdb": "222"},
},
}
if got, ok := selectInitialMatchCandidate(hints, cands, []string{"tmdb", "tvdb", "imdb"}); ok {
t.Fatalf("unrelated same-year candidate must not be auto-accepted, got %+v", got)
}
}
func TestSelectInitialMatchCandidate_TwoDifferentShowsUnchanged(t *testing.T) {
// Two genuinely different shows that BOTH score >=55 (so the new rule IS evaluated,
// not short-circuited by the <55 floor): candidatesAreSingleDistinctShow must return
+6 -10
View File
@@ -18,15 +18,11 @@ var trailingNumericIDPattern = regexp.MustCompile(`(?:^|\s)(\d+)$`)
// tt-prefixed number is unambiguously IMDb.
var bracketedBareImdbPattern = regexp.MustCompile(`(?i)[{\[](tt\d{7,8})[}\]]`)
// ParseStructuredFolderIDs extracts only explicit structured provider IDs from
// a folder or file name, such as {tmdb-27205} or [imdbid-tt1375666}. It does
// ParseStructuredFolderIDs extracts only explicit provider IDs from a folder or
// file name, such as {tmdb-27205}, [imdbid-tt1375666], or [tt1375666]. It does
// not consider trailing bare IDs or folderType-based heuristics.
func ParseStructuredFolderIDs(name string) *FolderIDHints {
matches := folderIDPattern.FindAllStringSubmatch(name, -1)
if len(matches) == 0 {
return nil
}
hints := &FolderIDHints{}
for _, m := range matches {
provider := strings.ToLower(m[1])
@@ -42,6 +38,10 @@ func ParseStructuredFolderIDs(name string) *FolderIDHints {
}
}
if m := bracketedBareImdbPattern.FindStringSubmatch(name); m != nil && hints.ImdbID == "" {
hints.ImdbID = strings.ToLower(m[1])
}
if hints.TmdbID == "" && hints.ImdbID == "" && hints.TvdbID == "" {
return nil
}
@@ -57,10 +57,6 @@ func ParseFolderIDs(folderName string, folderType string) *FolderIDHints {
return hints
}
if m := bracketedBareImdbPattern.FindStringSubmatch(folderName); m != nil {
return &FolderIDHints{ImdbID: strings.ToLower(m[1])}
}
trimmed := strings.TrimSpace(folderName)
if m := trailingImdbIDPattern.FindStringSubmatch(trimmed); m != nil {
return &FolderIDHints{ImdbID: strings.ToLower(m[1])}
+17
View File
@@ -27,3 +27,20 @@ func TestParseFolderIDs_BracketedBareImdb(t *testing.T) {
t.Errorf("no-id folder must return nil, got %+v", got)
}
}
func TestParseStructuredFolderIDs_BracketedBareImdb(t *testing.T) {
got := ParseStructuredFolderIDs("17 Blocks (2021) [tt10011226]")
if got == nil || got.ImdbID != "tt10011226" {
t.Fatalf("ParseStructuredFolderIDs bare imdb = %+v, want ImdbID tt10011226", got)
}
got = ParseStructuredFolderIDs("Some Movie {tmdb-12345} [tt7654321]")
if got == nil || got.TmdbID != "12345" || got.ImdbID != "tt7654321" {
t.Fatalf("ParseStructuredFolderIDs mixed ids = %+v, want tmdb+imdb", got)
}
got = ParseStructuredFolderIDs("Some Movie [imdb-tt1375666] [tt7654321]")
if got == nil || got.ImdbID != "tt1375666" {
t.Fatalf("structured imdb should win over bare imdb, got %+v", got)
}
}