From 5871987c036edcf0db6863f4d68c1ffbdfa1e4b7 Mon Sep 17 00:00:00 2001 From: Marlon Sardini Date: Fri, 3 Feb 2023 13:20:49 +0100 Subject: [PATCH 1/9] Gallery cover lookup by regex in config.yml --- internal/api/resolver_model_gallery.go | 5 +++-- internal/manager/config/config.go | 11 +++++++++++ pkg/image/query.go | 17 ++++++----------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/internal/api/resolver_model_gallery.go b/internal/api/resolver_model_gallery.go index 43c5b22216d..e7fd70f9d28 100644 --- a/internal/api/resolver_model_gallery.go +++ b/internal/api/resolver_model_gallery.go @@ -6,6 +6,7 @@ import ( "time" "github.com/stashapp/stash/internal/api/loaders" + "github.com/stashapp/stash/internal/manager/config" "github.com/stashapp/stash/pkg/file" "github.com/stashapp/stash/pkg/image" @@ -145,8 +146,8 @@ func (r *galleryResolver) Images(ctx context.Context, obj *models.Gallery) (ret func (r *galleryResolver) Cover(ctx context.Context, obj *models.Gallery) (ret *models.Image, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - // find cover.jpg first - ret, err = image.FindGalleryCover(ctx, r.repository.Image, obj.ID) + // Find cover image first + ret, err = image.FindGalleryCover(ctx, r.repository.Image, obj.ID, config.GetInstance().GetGalleryCoverRegex()) return err }); err != nil { return nil, err diff --git a/internal/manager/config/config.go b/internal/manager/config/config.go index c422b267098..db9158079fd 100644 --- a/internal/manager/config/config.go +++ b/internal/manager/config/config.go @@ -129,6 +129,10 @@ const ( // rather than use the embedded UI. CustomUILocation = "custom_ui_location" + // Gallery Cover Regex + GalleryCoverRegex = "gallery_cover_regex" + galleryCoverRegexDefault = `(poster|cover|folder|board)(\.jpg|\.jpeg|\.png)$` + // Interface options MenuItems = "menu_items" @@ -635,6 +639,10 @@ func (i *Instance) GetVideoFileNamingAlgorithm() models.HashAlgorithm { return models.HashAlgorithm(ret) } +func (i *Instance) GetGalleryCoverRegex() string { + return i.getString(GalleryCoverRegex) +} + func (i *Instance) GetScrapersPath() string { return i.getString(ScrapersPath) } @@ -1440,6 +1448,9 @@ func (i *Instance) setDefaultValues(write bool) error { i.main.SetDefault(ScrapersPath, defaultScrapersPath) i.main.SetDefault(PluginsPath, defaultPluginsPath) + // Set default gallery cover regex + i.main.SetDefault(GalleryCoverRegex, galleryCoverRegexDefault) + if write { return i.main.WriteConfig() } diff --git a/pkg/image/query.go b/pkg/image/query.go index 45f1cb687be..82125d65de8 100644 --- a/pkg/image/query.go +++ b/pkg/image/query.go @@ -7,11 +7,6 @@ import ( "github.com/stashapp/stash/pkg/models" ) -const ( - coverFilename = "cover.jpg" - coverFilenameSearchString = "%" + coverFilename -) - type Queryer interface { Query(ctx context.Context, options models.ImageQueryOptions) (*models.ImageQueryResult, error) } @@ -102,9 +97,9 @@ func FindByGalleryID(ctx context.Context, r Queryer, galleryID int, sortBy strin }, &findFilter) } -func FindGalleryCover(ctx context.Context, r Queryer, galleryID int) (*models.Image, error) { +func FindGalleryCover(ctx context.Context, r Queryer, galleryID int, galleryCoverRegex string) (*models.Image, error) { const useCoverJpg = true - img, err := findGalleryCover(ctx, r, galleryID, useCoverJpg) + img, err := findGalleryCover(ctx, r, galleryID, useCoverJpg, galleryCoverRegex) if err != nil { return nil, err } @@ -114,10 +109,10 @@ func FindGalleryCover(ctx context.Context, r Queryer, galleryID int) (*models.Im } // return the first image in the gallery - return findGalleryCover(ctx, r, galleryID, !useCoverJpg) + return findGalleryCover(ctx, r, galleryID, !useCoverJpg, galleryCoverRegex) } -func findGalleryCover(ctx context.Context, r Queryer, galleryID int, useCoverJpg bool) (*models.Image, error) { +func findGalleryCover(ctx context.Context, r Queryer, galleryID int, useCoverJpg bool, galleryCoverRegex string) (*models.Image, error) { // try to find cover.jpg in the gallery perPage := 1 sortBy := "path" @@ -138,8 +133,8 @@ func findGalleryCover(ctx context.Context, r Queryer, galleryID int, useCoverJpg if useCoverJpg { imageFilter.Path = &models.StringCriterionInput{ - Value: coverFilenameSearchString, - Modifier: models.CriterionModifierEquals, + Value: "(?i)" + galleryCoverRegex, + Modifier: models.CriterionModifierMatchesRegex, } } From 1e89a2e46963a7404724eef52a5f5ce5138b67dd Mon Sep 17 00:00:00 2001 From: Ksrx01 <118474867+Ksrx01@users.noreply.github.com> Date: Sat, 4 Feb 2023 12:32:37 +0000 Subject: [PATCH 2/9] Updated Regex, removing extension restrictions --- internal/manager/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/manager/config/config.go b/internal/manager/config/config.go index db9158079fd..aeb5e886de4 100644 --- a/internal/manager/config/config.go +++ b/internal/manager/config/config.go @@ -131,7 +131,7 @@ const ( // Gallery Cover Regex GalleryCoverRegex = "gallery_cover_regex" - galleryCoverRegexDefault = `(poster|cover|folder|board)(\.jpg|\.jpeg|\.png)$` + galleryCoverRegexDefault = `(poster|cover|folder|board)\.[^\.]+$` // Interface options MenuItems = "menu_items" From b270f50c8603ef0847b4e5392008077e7cead6c8 Mon Sep 17 00:00:00 2001 From: Marlon Sardini Date: Sat, 4 Feb 2023 20:36:46 +0100 Subject: [PATCH 3/9] Added regex validation and an in-app manual entry --- internal/manager/config/config.go | 9 +++++++++ ui/v2.5/src/docs/en/Manual/Configuration.md | 1 + 2 files changed, 10 insertions(+) diff --git a/internal/manager/config/config.go b/internal/manager/config/config.go index aeb5e886de4..df4504e6b26 100644 --- a/internal/manager/config/config.go +++ b/internal/manager/config/config.go @@ -21,6 +21,7 @@ import ( "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models/paths" + "github.com/stashapp/stash/pkg/plugin/common/log" ) var officialBuild string @@ -640,6 +641,14 @@ func (i *Instance) GetVideoFileNamingAlgorithm() models.HashAlgorithm { } func (i *Instance) GetGalleryCoverRegex() string { + var regexString = i.getString(GalleryCoverRegex) + + _, err := regexp.Compile(regexString) + if err != nil { + log.Warn(fmt.Sprintf("Gallery cover regex '%v' invalid, reverting to default.", regexString)) + return galleryCoverRegexDefault + } + return i.getString(GalleryCoverRegex) } diff --git a/ui/v2.5/src/docs/en/Manual/Configuration.md b/ui/v2.5/src/docs/en/Manual/Configuration.md index 68037fa8357..5d685b413c3 100644 --- a/ui/v2.5/src/docs/en/Manual/Configuration.md +++ b/ui/v2.5/src/docs/en/Manual/Configuration.md @@ -129,6 +129,7 @@ These options are typically not exposed in the UI and must be changed manually i | `custom_ui_location` | The file system folder where the UI files will be served from, instead of using the embedded UI. Empty to disable. Stash must be restarted to take effect. | | `max_upload_size` | Maximum file upload size for import files. Defaults to 1GB. | | `theme_color` | Sets the `theme-color` property in the UI. | +| `gallery_cover_regex` | The regex responsible for selecting images as gallery covers | ### Custom served folders From 3947619d2db84c7128d738210322ce5c07423ff3 Mon Sep 17 00:00:00 2001 From: Marlon Sardini Date: Sun, 5 Feb 2023 09:43:34 +0100 Subject: [PATCH 4/9] Code clean-up --- internal/manager/config/config.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/manager/config/config.go b/internal/manager/config/config.go index df4504e6b26..61661f81172 100644 --- a/internal/manager/config/config.go +++ b/internal/manager/config/config.go @@ -21,7 +21,6 @@ import ( "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models/paths" - "github.com/stashapp/stash/pkg/plugin/common/log" ) var officialBuild string @@ -645,11 +644,11 @@ func (i *Instance) GetGalleryCoverRegex() string { _, err := regexp.Compile(regexString) if err != nil { - log.Warn(fmt.Sprintf("Gallery cover regex '%v' invalid, reverting to default.", regexString)) + logger.Warnf(fmt.Sprintf("Gallery cover regex '%v' invalid, reverting to default.", regexString)) return galleryCoverRegexDefault } - return i.getString(GalleryCoverRegex) + return regexString } func (i *Instance) GetScrapersPath() string { From c0bb3cee6e2652abae40d8a3cbc4db9fd8b9068a Mon Sep 17 00:00:00 2001 From: Marlon Sardini Date: Mon, 6 Feb 2023 11:32:58 +0100 Subject: [PATCH 5/9] Removed redundant formatting --- internal/manager/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/manager/config/config.go b/internal/manager/config/config.go index 61661f81172..f22aca17153 100644 --- a/internal/manager/config/config.go +++ b/internal/manager/config/config.go @@ -644,7 +644,7 @@ func (i *Instance) GetGalleryCoverRegex() string { _, err := regexp.Compile(regexString) if err != nil { - logger.Warnf(fmt.Sprintf("Gallery cover regex '%v' invalid, reverting to default.", regexString)) + logger.Warnf("Gallery cover regex '%v' invalid, reverting to default.", regexString) return galleryCoverRegexDefault } From 96a68d82ae6059f02023557f3c9e5c6b8fb2c640 Mon Sep 17 00:00:00 2001 From: Marlon Sardini Date: Fri, 10 Feb 2023 11:10:14 +0100 Subject: [PATCH 6/9] Gallery cover regex now changeable in settings --- graphql/documents/data/config.graphql | 1 + graphql/schema/types/config.graphql | 4 ++++ internal/api/resolver_mutation_configure.go | 11 +++++++++++ internal/api/resolver_query_configuration.go | 1 + .../src/components/Settings/SettingsLibraryPanel.tsx | 10 ++++++++++ ui/v2.5/src/locales/en-GB.json | 2 ++ 6 files changed, 29 insertions(+) diff --git a/graphql/documents/data/config.graphql b/graphql/documents/data/config.graphql index b5e377ec55c..3332ad15b88 100644 --- a/graphql/documents/data/config.graphql +++ b/graphql/documents/data/config.graphql @@ -31,6 +31,7 @@ fragment ConfigGeneralData on ConfigGeneralResult { logLevel logAccess createGalleriesFromFolders + galleryCoverRegex videoExtensions imageExtensions galleryExtensions diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index 0b17c1c011b..237fdacc61f 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -104,6 +104,8 @@ input ConfigGeneralInput { logAccess: Boolean """True if galleries should be created from folders with images""" createGalleriesFromFolders: Boolean + """Regex used to identify images as gallery covers""" + galleryCoverRegex: String """Array of video file extensions""" videoExtensions: [String!] """Array of image file extensions""" @@ -210,6 +212,8 @@ type ConfigGeneralResult { galleryExtensions: [String!]! """True if galleries should be created from folders with images""" createGalleriesFromFolders: Boolean! + """Regex used to identify images as gallery covers""" + galleryCoverRegex: String! """Array of file regexp to exclude from Video Scans""" excludes: [String!]! """Array of file regexp to exclude from Image Scans""" diff --git a/internal/api/resolver_mutation_configure.go b/internal/api/resolver_mutation_configure.go index 29dec27e016..faff1aa0033 100644 --- a/internal/api/resolver_mutation_configure.go +++ b/internal/api/resolver_mutation_configure.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "path/filepath" + "regexp" "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager/config" @@ -190,6 +191,16 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen c.Set(config.WriteImageThumbnails, *input.WriteImageThumbnails) } + if input.GalleryCoverRegex != nil { + + _, err := regexp.Compile(*input.GalleryCoverRegex) + if err != nil { + return makeConfigGeneralResult(), fmt.Errorf("Gallery cover regex '%v' invalid, '%v'.", *input.GalleryCoverRegex, err.Error()) + } + + c.Set(config.GalleryCoverRegex, *input.GalleryCoverRegex) + } + if input.Username != nil { c.Set(config.Username, input.Username) } diff --git a/internal/api/resolver_query_configuration.go b/internal/api/resolver_query_configuration.go index ebc51c64fec..aecc7d67503 100644 --- a/internal/api/resolver_query_configuration.go +++ b/internal/api/resolver_query_configuration.go @@ -103,6 +103,7 @@ func makeConfigGeneralResult() *ConfigGeneralResult { MaxTranscodeSize: &maxTranscodeSize, MaxStreamingTranscodeSize: &maxStreamingTranscodeSize, WriteImageThumbnails: config.IsWriteImageThumbnails(), + GalleryCoverRegex: config.GetGalleryCoverRegex(), APIKey: config.GetAPIKey(), Username: config.GetUsername(), Password: config.GetPasswordHash(), diff --git a/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx b/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx index 7c631d7d1d7..f70a10d88da 100644 --- a/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx @@ -134,6 +134,16 @@ export const SettingsLibraryPanel: React.FC = () => { checked={general.writeImageThumbnails ?? false} onChange={(v) => saveGeneral({ writeImageThumbnails: v })} /> + + + saveGeneral({ galleryCoverRegex: v }) + } + /> diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index dc41b2e1144..766e06e6564 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -265,6 +265,8 @@ "chrome_cdp_path_desc": "File path to the Chrome executable, or a remote address (starting with http:// or https://, for example http://localhost:9222/json/version) to a Chrome instance.", "create_galleries_from_folders_desc": "If true, creates galleries from folders containing images.", "create_galleries_from_folders_label": "Create galleries from folders containing images", + "gallery_cover_regex_desc": "Regex used to identify images as gallery covers", + "gallery_cover_regex_label": "Gallery cover regex", "db_path_head": "Database Path", "directory_locations_to_your_content": "Directory locations to your content", "excluded_image_gallery_patterns_desc": "Regexps of image and gallery files/paths to exclude from Scan and add to Clean", From 2c122aa90e4b8b0f20a4a3eaf360125602530f39 Mon Sep 17 00:00:00 2001 From: Marlon Sardini Date: Fri, 10 Feb 2023 11:45:14 +0100 Subject: [PATCH 7/9] Improved settings description + some translations --- ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx | 4 +--- ui/v2.5/src/locales/de-DE.json | 2 ++ ui/v2.5/src/locales/en-GB.json | 4 ++-- ui/v2.5/src/locales/it-IT.json | 2 ++ 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx b/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx index f70a10d88da..f0ec0d1fbdc 100644 --- a/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx @@ -140,9 +140,7 @@ export const SettingsLibraryPanel: React.FC = () => { headingID="config.general.gallery_cover_regex_label" subHeadingID="config.general.gallery_cover_regex_desc" value={general.galleryCoverRegex ?? ""} - onChange={(v) => - saveGeneral({ galleryCoverRegex: v }) - } + onChange={(v) => saveGeneral({ galleryCoverRegex: v })} /> diff --git a/ui/v2.5/src/locales/de-DE.json b/ui/v2.5/src/locales/de-DE.json index 186c35a8827..a5c0432dcdb 100644 --- a/ui/v2.5/src/locales/de-DE.json +++ b/ui/v2.5/src/locales/de-DE.json @@ -263,6 +263,8 @@ "chrome_cdp_path_desc": "Dateipfad zur Chrome Executable oder einer externen Adresse (beginnend mit http:// oder https://, bspw. http://localhost:9222/json/version) die auf eine Chrome Instanz zeigt.", "create_galleries_from_folders_desc": "Wenn ausgewählt, erzeuge Galerien aus Verzeichnissen, welche Bilder enthalten.", "create_galleries_from_folders_label": "Erzeuge Galerien aus Verzeichnissen mit Bilder darin", + "gallery_cover_regex_desc": "Regulärer Ausdruck, verwendet um ein Bild als Galerietitelbild zu identifiziert", + "gallery_cover_regex_label": "Schema für Galerietitelbilder", "db_path_head": "Datenbank Pfad", "directory_locations_to_your_content": "Verzeichnis zu Ihren Inhalten", "excluded_image_gallery_patterns_desc": "Reguläre Ausdrücke für Dateinamen/Pfade von Bildern/Galerien, welche von Scans ausgeschlossen werden und beim Aufräumen der Datenbank berücksichtigt werden sollen", diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 766e06e6564..b294b495321 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -265,8 +265,8 @@ "chrome_cdp_path_desc": "File path to the Chrome executable, or a remote address (starting with http:// or https://, for example http://localhost:9222/json/version) to a Chrome instance.", "create_galleries_from_folders_desc": "If true, creates galleries from folders containing images.", "create_galleries_from_folders_label": "Create galleries from folders containing images", - "gallery_cover_regex_desc": "Regex used to identify images as gallery covers", - "gallery_cover_regex_label": "Gallery cover regex", + "gallery_cover_regex_desc": "Regexp used to identify an image as gallery cover", + "gallery_cover_regex_label": "Gallery cover pattern", "db_path_head": "Database Path", "directory_locations_to_your_content": "Directory locations to your content", "excluded_image_gallery_patterns_desc": "Regexps of image and gallery files/paths to exclude from Scan and add to Clean", diff --git a/ui/v2.5/src/locales/it-IT.json b/ui/v2.5/src/locales/it-IT.json index 5dfff34d5ad..76580bf0a7a 100644 --- a/ui/v2.5/src/locales/it-IT.json +++ b/ui/v2.5/src/locales/it-IT.json @@ -265,6 +265,8 @@ "chrome_cdp_path_desc": "Percorso all'eseguibile di Chrome, o indirizzo remoto (iniziando con http:// o https://, per esempio http://localhost:9222/json/version) verso un'istanza Chrome.", "create_galleries_from_folders_desc": "Se spuntato, crea gallerie dalle cartelle che contengono immagini.", "create_galleries_from_folders_label": "Crea gallerie dalle cartelle con immagini", + "gallery_cover_regex_desc": "Espressione regolare usata per identificare un immagine come copertina di galleria", + "gallery_cover_regex_label": "Schema copertina di galleria", "db_path_head": "Percorso del Database", "directory_locations_to_your_content": "Percorso della Cartella del tuo contenuto", "excluded_image_gallery_patterns_desc": "Espressioni Regolari di file/percorsi di immagini e gallerie per escluderle dalla Scansione e aggiungerle alla Pulizia", From a562c4413a7f13b44ad9edffdb4a0d15136a82c3 Mon Sep 17 00:00:00 2001 From: Marlon Sardini Date: Fri, 10 Feb 2023 17:14:19 +0100 Subject: [PATCH 8/9] Fixed lint issue --- internal/api/resolver_mutation_configure.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/api/resolver_mutation_configure.go b/internal/api/resolver_mutation_configure.go index faff1aa0033..2c5686e7342 100644 --- a/internal/api/resolver_mutation_configure.go +++ b/internal/api/resolver_mutation_configure.go @@ -195,7 +195,7 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen _, err := regexp.Compile(*input.GalleryCoverRegex) if err != nil { - return makeConfigGeneralResult(), fmt.Errorf("Gallery cover regex '%v' invalid, '%v'.", *input.GalleryCoverRegex, err.Error()) + return makeConfigGeneralResult(), fmt.Errorf("Gallery cover regex '%v' invalid, '%v'", *input.GalleryCoverRegex, err.Error()) } c.Set(config.GalleryCoverRegex, *input.GalleryCoverRegex) From ebab508191f24b05899e62c4aa3d9ebb935ad2db Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Wed, 22 Feb 2023 17:08:51 +1100 Subject: [PATCH 9/9] Add changelog entry --- ui/v2.5/src/docs/en/Changelog/v0200.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/v2.5/src/docs/en/Changelog/v0200.md b/ui/v2.5/src/docs/en/Changelog/v0200.md index 4b5de96968d..874b842d141 100644 --- a/ui/v2.5/src/docs/en/Changelog/v0200.md +++ b/ui/v2.5/src/docs/en/Changelog/v0200.md @@ -1,4 +1,5 @@ ### ✨ New Features +* Support customising the filename regex used for determining the gallery cover image. ([#3391](https://github.com/stashapp/stash/pull/3391)) * Added tenth-place rating precision option. ([#3343](https://github.com/stashapp/stash/pull/3343)) * Added toggleable favorite button to Performer cards. ([#3369](https://github.com/stashapp/stash/pull/3369))