From 19e69f5310c503c1c28ab06598b55d7c2b0c6a83 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 26 Nov 2021 08:29:25 +1100 Subject: [PATCH] Prefer studio name later in filename (#2057) --- pkg/autotag/tagger.go | 6 +- pkg/match/path.go | 62 ++++++++------- pkg/match/path_test.go | 76 +++++++++++++++++++ pkg/scraper/autotag.go | 8 +- .../components/Changelog/versions/v0120.md | 1 + 5 files changed, 118 insertions(+), 35 deletions(-) create mode 100644 pkg/match/path_test.go diff --git a/pkg/autotag/tagger.go b/pkg/autotag/tagger.go index b64e4e507..74ea86a41 100644 --- a/pkg/autotag/tagger.go +++ b/pkg/autotag/tagger.go @@ -60,14 +60,12 @@ func (t *tagger) tagPerformers(performerReader models.PerformerReader, addFunc a } func (t *tagger) tagStudios(studioReader models.StudioReader, addFunc addLinkFunc) error { - others, err := match.PathToStudios(t.Path, studioReader) + studio, err := match.PathToStudio(t.Path, studioReader) if err != nil { return err } - // only add first studio - if len(others) > 0 { - studio := others[0] + if studio != nil { added, err := addFunc(t.ID, studio.ID) if err != nil { diff --git a/pkg/match/path.go b/pkg/match/path.go index 5596d8e36..2de78e716 100644 --- a/pkg/match/path.go +++ b/pkg/match/path.go @@ -58,7 +58,9 @@ func getPathWords(path string) []string { return ret } -func nameMatchesPath(name, path string) bool { +// nameMatchesPath returns the index in the path for the right-most match. +// Returns -1 if not found. +func nameMatchesPath(name, path string) int { // escape specific regex characters name = regexp.QuoteMeta(name) @@ -72,7 +74,13 @@ func nameMatchesPath(name, path string) bool { reStr = `(?:^|_|[^\w\d])` + reStr + `(?:$|_|[^\w\d])` re := regexp.MustCompile(reStr) - return re.MatchString(path) + found := re.FindAllStringIndex(path, -1) + + if found == nil { + return -1 + } + + return found[len(found)-1][0] } func PathToPerformers(path string, performerReader models.PerformerReader) ([]*models.Performer, error) { @@ -86,7 +94,7 @@ func PathToPerformers(path string, performerReader models.PerformerReader) ([]*m var ret []*models.Performer for _, p := range performers { // TODO - commenting out alias handling until both sides work correctly - if nameMatchesPath(p.Name.String, path) { // || nameMatchesPath(p.Aliases.String, path) { + if nameMatchesPath(p.Name.String, path) != -1 { // || nameMatchesPath(p.Aliases.String, path) { ret = append(ret, p) } } @@ -94,7 +102,10 @@ func PathToPerformers(path string, performerReader models.PerformerReader) ([]*m return ret, nil } -func PathToStudios(path string, reader models.StudioReader) ([]*models.Studio, error) { +// PathToStudio returns the Studio that matches the given path. +// Where multiple matching studios are found, the one that matches the latest +// position in the path is returned. +func PathToStudio(path string, reader models.StudioReader) (*models.Studio, error) { words := getPathWords(path) candidates, err := reader.QueryForAutoTag(words) @@ -102,29 +113,26 @@ func PathToStudios(path string, reader models.StudioReader) ([]*models.Studio, e return nil, err } - var ret []*models.Studio + var ret *models.Studio + index := -1 for _, c := range candidates { - matches := false - if nameMatchesPath(c.Name.String, path) { - matches = true + matchIndex := nameMatchesPath(c.Name.String, path) + if matchIndex != -1 && matchIndex > index { + ret = c + index = matchIndex } - if !matches { - aliases, err := reader.GetAliases(c.ID) - if err != nil { - return nil, err - } - - for _, alias := range aliases { - if nameMatchesPath(alias, path) { - matches = true - break - } - } + aliases, err := reader.GetAliases(c.ID) + if err != nil { + return nil, err } - if matches { - ret = append(ret, c) + for _, alias := range aliases { + matchIndex = nameMatchesPath(alias, path) + if matchIndex != -1 && matchIndex > index { + ret = c + index = matchIndex + } } } @@ -142,7 +150,7 @@ func PathToTags(path string, tagReader models.TagReader) ([]*models.Tag, error) var ret []*models.Tag for _, t := range tags { matches := false - if nameMatchesPath(t.Name, path) { + if nameMatchesPath(t.Name, path) != -1 { matches = true } @@ -152,7 +160,7 @@ func PathToTags(path string, tagReader models.TagReader) ([]*models.Tag, error) return nil, err } for _, alias := range aliases { - if nameMatchesPath(alias, path) { + if nameMatchesPath(alias, path) != -1 { matches = true break } @@ -223,7 +231,7 @@ func PathToScenes(name string, paths []string, sceneReader models.SceneReader) ( var ret []*models.Scene for _, p := range scenes { - if nameMatchesPath(name, p.Path) { + if nameMatchesPath(name, p.Path) != -1 { ret = append(ret, p) } } @@ -287,7 +295,7 @@ func PathToImages(name string, paths []string, imageReader models.ImageReader) ( var ret []*models.Image for _, p := range images { - if nameMatchesPath(name, p.Path) { + if nameMatchesPath(name, p.Path) != -1 { ret = append(ret, p) } } @@ -351,7 +359,7 @@ func PathToGalleries(name string, paths []string, galleryReader models.GalleryRe var ret []*models.Gallery for _, p := range gallerys { - if nameMatchesPath(name, p.Path.String) { + if nameMatchesPath(name, p.Path.String) != -1 { ret = append(ret, p) } } diff --git a/pkg/match/path_test.go b/pkg/match/path_test.go new file mode 100644 index 000000000..f2818a801 --- /dev/null +++ b/pkg/match/path_test.go @@ -0,0 +1,76 @@ +package match + +import "testing" + +func Test_nameMatchesPath(t *testing.T) { + const name = "first last" + + tests := []struct { + name string + path string + want int + }{ + { + "exact", + name, + 0, + }, + { + "partial", + "first", + -1, + }, + { + "separator", + "first.last", + 0, + }, + { + "separator", + "first-last", + 0, + }, + { + "separator", + "first_last", + 0, + }, + { + "separators", + "first.-_ last", + 0, + }, + { + "within string", + "before_first last/after", + 6, + }, + { + "not within string", + "beforefirst last/after", + -1, + }, + { + "not within string", + "before/first lastafter", + -1, + }, + { + "not within string", + "first last1", + -1, + }, + { + "not within string", + "1first last", + -1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := nameMatchesPath(name, tt.path); got != tt.want { + t.Errorf("nameMatchesPath() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/scraper/autotag.go b/pkg/scraper/autotag.go index 92bc0a239..ce128b080 100644 --- a/pkg/scraper/autotag.go +++ b/pkg/scraper/autotag.go @@ -46,15 +46,15 @@ func autotagMatchPerformers(path string, performerReader models.PerformerReader) } func autotagMatchStudio(path string, studioReader models.StudioReader) (*models.ScrapedStudio, error) { - st, err := match.PathToStudios(path, studioReader) + studio, err := match.PathToStudio(path, studioReader) if err != nil { return nil, fmt.Errorf("error matching studios: %w", err) } - if len(st) > 0 { - id := strconv.Itoa(st[0].ID) + if studio != nil { + id := strconv.Itoa(studio.ID) return &models.ScrapedStudio{ - Name: st[0].Name.String, + Name: studio.Name.String, StoredID: &id, }, nil } diff --git a/ui/v2.5/src/components/Changelog/versions/v0120.md b/ui/v2.5/src/components/Changelog/versions/v0120.md index 50c5dd9fe..3a24776ec 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0120.md +++ b/ui/v2.5/src/components/Changelog/versions/v0120.md @@ -4,6 +4,7 @@ * Add forward jump 10 second button to video player. ([#1973](https://github.com/stashapp/stash/pull/1973)) ### 🎨 Improvements +* Prefer right-most Studio match in the file path when autotagging. ([#2057](https://github.com/stashapp/stash/pull/2057)) * Added plugin hook for Tag merge operation. ([#2010](https://github.com/stashapp/stash/pull/2010)) ### 🐛 Bug fixes