From 2d528733ffd89e5adce6feba4085c41a116e0119 Mon Sep 17 00:00:00 2001 From: Ksrx01 <118474867+Ksrx01@users.noreply.github.com> Date: Wed, 22 Feb 2023 07:50:12 +0000 Subject: [PATCH] Improved gallery cover lookup (#3391) * Gallery cover lookup by regex in config.yml * Added regex validation and an in-app manual entry * Improved settings description + some translations * Add changelog entry --- graphql/documents/data/config.graphql | 1 + graphql/schema/types/config.graphql | 4 ++++ internal/api/resolver_model_gallery.go | 5 +++-- internal/api/resolver_mutation_configure.go | 10 ++++++++++ internal/api/resolver_query_configuration.go | 1 + internal/manager/config/config.go | 19 +++++++++++++++++++ pkg/image/query.go | 17 ++++++----------- .../Settings/SettingsLibraryPanel.tsx | 8 ++++++++ ui/v2.5/src/docs/en/Changelog/v0200.md | 1 + ui/v2.5/src/docs/en/Manual/Configuration.md | 1 + ui/v2.5/src/locales/de-DE.json | 2 ++ ui/v2.5/src/locales/en-GB.json | 2 ++ ui/v2.5/src/locales/it-IT.json | 2 ++ 13 files changed, 60 insertions(+), 13 deletions(-) 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_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/api/resolver_mutation_configure.go b/internal/api/resolver_mutation_configure.go index db7bc8995a6..8dc46277d27 100644 --- a/internal/api/resolver_mutation_configure.go +++ b/internal/api/resolver_mutation_configure.go @@ -191,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/internal/manager/config/config.go b/internal/manager/config/config.go index 668b2d138e9..ea0ed4adb5a 100644 --- a/internal/manager/config/config.go +++ b/internal/manager/config/config.go @@ -136,6 +136,10 @@ const ( // rather than use the embedded UI. CustomUILocation = "custom_ui_location" + // Gallery Cover Regex + GalleryCoverRegex = "gallery_cover_regex" + galleryCoverRegexDefault = `(poster|cover|folder|board)\.[^\.]+$` + // Interface options MenuItems = "menu_items" @@ -642,6 +646,18 @@ func (i *Instance) GetVideoFileNamingAlgorithm() models.HashAlgorithm { return models.HashAlgorithm(ret) } +func (i *Instance) GetGalleryCoverRegex() string { + var regexString = i.getString(GalleryCoverRegex) + + _, err := regexp.Compile(regexString) + if err != nil { + logger.Warnf("Gallery cover regex '%v' invalid, reverting to default.", regexString) + return galleryCoverRegexDefault + } + + return regexString +} + func (i *Instance) GetScrapersPath() string { return i.getString(ScrapersPath) } @@ -1468,6 +1484,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) + // Set NoProxy default i.main.SetDefault(NoProxy, noProxyDefault) 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, } } diff --git a/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx b/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx index 550c646c8a6..b91f73f8bc6 100644 --- a/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx @@ -129,6 +129,14 @@ export const SettingsLibraryPanel: React.FC = () => { checked={general.writeImageThumbnails ?? false} onChange={(v) => saveGeneral({ writeImageThumbnails: v })} /> + + saveGeneral({ galleryCoverRegex: v })} + /> 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)) diff --git a/ui/v2.5/src/docs/en/Manual/Configuration.md b/ui/v2.5/src/docs/en/Manual/Configuration.md index f9206ab0b05..8928a07895a 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 | | `proxy` | The url of a HTTP(S) proxy to be used when stash makes calls to online services Example: https://user:password@my.proxy:8080 | | `no_proxy` | A list of domains for which the proxy must not be used. Default is all local LAN: localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12 | diff --git a/ui/v2.5/src/locales/de-DE.json b/ui/v2.5/src/locales/de-DE.json index b49bef34b15..8212db5d886 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 8338a325c92..43f5916bf20 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -266,6 +266,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": "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 7431cae3373..31d2bd632d5 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",