fix(metadata): tighten matcher and bare IMDb parsing
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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])}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user