diff --git a/internal/naming/group_identity.go b/internal/naming/group_identity.go index f9b990f0..97320024 100644 --- a/internal/naming/group_identity.go +++ b/internal/naming/group_identity.go @@ -60,13 +60,15 @@ func InferGroupIdentity(filePath string, libraryType string, assignment RootAssi populateMovieGroupIdentity(cleanFilePath, assignment, idAnchored, &group) } + // ID persistence must mirror hasStructuredIDAnchor: any evidence strong + // enough to anchor identity must also reach downstream matching. if ids := ParseFolderIDs(filepath.Base(group.ObservedRootPath)); ids != nil { group.TmdbID = ids.TmdbID group.ImdbID = ids.ImdbID group.TvdbID = ids.TvdbID } if group.TmdbID == "" || group.ImdbID == "" || group.TvdbID == "" { - if ids := ParseStructuredFolderIDs(strings.TrimSuffix(filepath.Base(cleanFilePath), filepath.Ext(cleanFilePath))); ids != nil { + if ids := ParseFolderIDs(strings.TrimSuffix(filepath.Base(cleanFilePath), filepath.Ext(cleanFilePath))); ids != nil { if group.TmdbID == "" { group.TmdbID = ids.TmdbID } @@ -249,15 +251,16 @@ func populateSeriesGroupIdentity(filePath string, libraryType string, assignment } // hasStructuredIDAnchor reports whether the observed root folder or the file -// name carries explicit structured provider IDs (e.g. {tmdb-694938}, -// [tvdbid-81189]). Bare trailing numerics are deliberately excluded — only an -// explicit tag is strong enough to anchor identity over a title conflict. +// name carries explicit provider IDs — structured tags (e.g. {tmdb-694938}, +// [tvdbid-81189]) or an unambiguous tt-prefixed IMDb id ("Eggs Run (2021) +// tt8049994"). ParseFolderIDs returns only such explicit evidence, which is +// strong enough to anchor identity over a title conflict. func hasStructuredIDAnchor(filePath, observedRootPath string) bool { - if ParseStructuredFolderIDs(filepath.Base(observedRootPath)) != nil { + if ParseFolderIDs(filepath.Base(observedRootPath)) != nil { return true } baseNoExt := strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath)) - return ParseStructuredFolderIDs(baseNoExt) != nil + return ParseFolderIDs(baseNoExt) != nil } func deriveObservedRootPath(filePath, candidateRoot string) string { diff --git a/internal/naming/group_identity_test.go b/internal/naming/group_identity_test.go index 087231f9..f4c0a741 100644 --- a/internal/naming/group_identity_test.go +++ b/internal/naming/group_identity_test.go @@ -30,6 +30,48 @@ func TestInferGroupIdentity_StructuredIDsResolveMovieTitleConflict(t *testing.T) } } +// An unbracketed trailing IMDb id anchors identity the same way a structured +// tag does — the tt prefix is unambiguous. Mirrors the dev-library case +// "Eggs Run (2021) tt8049994" whose file carries a different release title. +func TestInferGroupIdentity_TrailingImdbIDResolvesMovieTitleConflict(t *testing.T) { + group := InferGroupIdentity( + "/movies-int/es/Eggs Run (2021) tt8049994/Huevitos Congelados (2022) [WEBDL-1080p]-GRP.mkv", + "movies", + RootAssignment{ + RootPath: "/movies-int/es/Eggs Run (2021) tt8049994", + InferredType: "movie", + }, + ) + + if got, want := group.State, "resolved"; got != want { + t.Fatalf("State = %q, want %q", got, want) + } + if got, want := group.ImdbID, "tt8049994"; got != want { + t.Fatalf("ImdbID = %q, want %q", got, want) + } +} + +// A trailing IMDb id on the file stem (folder carries none) must both anchor +// the identity and be persisted into the group, so downstream matching sees +// the provider ID that justified the resolution. +func TestInferGroupIdentity_FileLevelTrailingImdbIDAnchorsAndPersists(t *testing.T) { + group := InferGroupIdentity( + "/movies/On Fire (2024)/Soul on Fire tt28078628.mkv", + "movies", + RootAssignment{ + RootPath: "/movies/On Fire (2024)", + InferredType: "movie", + }, + ) + + if got, want := group.State, "resolved"; got != want { + t.Fatalf("State = %q, want %q", got, want) + } + if got, want := group.ImdbID, "tt28078628"; got != want { + t.Fatalf("ImdbID = %q, want %q", got, want) + } +} + // Without provider IDs the same folder/file title conflict must still be // flagged ambiguous — there is nothing to anchor the identity. func TestInferGroupIdentity_TitleConflictWithoutIDsStaysAmbiguous(t *testing.T) {