mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 16:34:02 +01:00
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
This commit is contained in:
parent
8b6f7db4ef
commit
2d528733ff
13 changed files with 60 additions and 13 deletions
|
|
@ -31,6 +31,7 @@ fragment ConfigGeneralData on ConfigGeneralResult {
|
||||||
logLevel
|
logLevel
|
||||||
logAccess
|
logAccess
|
||||||
createGalleriesFromFolders
|
createGalleriesFromFolders
|
||||||
|
galleryCoverRegex
|
||||||
videoExtensions
|
videoExtensions
|
||||||
imageExtensions
|
imageExtensions
|
||||||
galleryExtensions
|
galleryExtensions
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,8 @@ input ConfigGeneralInput {
|
||||||
logAccess: Boolean
|
logAccess: Boolean
|
||||||
"""True if galleries should be created from folders with images"""
|
"""True if galleries should be created from folders with images"""
|
||||||
createGalleriesFromFolders: Boolean
|
createGalleriesFromFolders: Boolean
|
||||||
|
"""Regex used to identify images as gallery covers"""
|
||||||
|
galleryCoverRegex: String
|
||||||
"""Array of video file extensions"""
|
"""Array of video file extensions"""
|
||||||
videoExtensions: [String!]
|
videoExtensions: [String!]
|
||||||
"""Array of image file extensions"""
|
"""Array of image file extensions"""
|
||||||
|
|
@ -210,6 +212,8 @@ type ConfigGeneralResult {
|
||||||
galleryExtensions: [String!]!
|
galleryExtensions: [String!]!
|
||||||
"""True if galleries should be created from folders with images"""
|
"""True if galleries should be created from folders with images"""
|
||||||
createGalleriesFromFolders: Boolean!
|
createGalleriesFromFolders: Boolean!
|
||||||
|
"""Regex used to identify images as gallery covers"""
|
||||||
|
galleryCoverRegex: String!
|
||||||
"""Array of file regexp to exclude from Video Scans"""
|
"""Array of file regexp to exclude from Video Scans"""
|
||||||
excludes: [String!]!
|
excludes: [String!]!
|
||||||
"""Array of file regexp to exclude from Image Scans"""
|
"""Array of file regexp to exclude from Image Scans"""
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stashapp/stash/internal/api/loaders"
|
"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/file"
|
||||||
"github.com/stashapp/stash/pkg/image"
|
"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) {
|
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 {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
// find cover.jpg first
|
// Find cover image first
|
||||||
ret, err = image.FindGalleryCover(ctx, r.repository.Image, obj.ID)
|
ret, err = image.FindGalleryCover(ctx, r.repository.Image, obj.ID, config.GetInstance().GetGalleryCoverRegex())
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,16 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen
|
||||||
c.Set(config.WriteImageThumbnails, *input.WriteImageThumbnails)
|
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 {
|
if input.Username != nil {
|
||||||
c.Set(config.Username, input.Username)
|
c.Set(config.Username, input.Username)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ func makeConfigGeneralResult() *ConfigGeneralResult {
|
||||||
MaxTranscodeSize: &maxTranscodeSize,
|
MaxTranscodeSize: &maxTranscodeSize,
|
||||||
MaxStreamingTranscodeSize: &maxStreamingTranscodeSize,
|
MaxStreamingTranscodeSize: &maxStreamingTranscodeSize,
|
||||||
WriteImageThumbnails: config.IsWriteImageThumbnails(),
|
WriteImageThumbnails: config.IsWriteImageThumbnails(),
|
||||||
|
GalleryCoverRegex: config.GetGalleryCoverRegex(),
|
||||||
APIKey: config.GetAPIKey(),
|
APIKey: config.GetAPIKey(),
|
||||||
Username: config.GetUsername(),
|
Username: config.GetUsername(),
|
||||||
Password: config.GetPasswordHash(),
|
Password: config.GetPasswordHash(),
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,10 @@ const (
|
||||||
// rather than use the embedded UI.
|
// rather than use the embedded UI.
|
||||||
CustomUILocation = "custom_ui_location"
|
CustomUILocation = "custom_ui_location"
|
||||||
|
|
||||||
|
// Gallery Cover Regex
|
||||||
|
GalleryCoverRegex = "gallery_cover_regex"
|
||||||
|
galleryCoverRegexDefault = `(poster|cover|folder|board)\.[^\.]+$`
|
||||||
|
|
||||||
// Interface options
|
// Interface options
|
||||||
MenuItems = "menu_items"
|
MenuItems = "menu_items"
|
||||||
|
|
||||||
|
|
@ -642,6 +646,18 @@ func (i *Instance) GetVideoFileNamingAlgorithm() models.HashAlgorithm {
|
||||||
return models.HashAlgorithm(ret)
|
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 {
|
func (i *Instance) GetScrapersPath() string {
|
||||||
return i.getString(ScrapersPath)
|
return i.getString(ScrapersPath)
|
||||||
}
|
}
|
||||||
|
|
@ -1468,6 +1484,9 @@ func (i *Instance) setDefaultValues(write bool) error {
|
||||||
i.main.SetDefault(ScrapersPath, defaultScrapersPath)
|
i.main.SetDefault(ScrapersPath, defaultScrapersPath)
|
||||||
i.main.SetDefault(PluginsPath, defaultPluginsPath)
|
i.main.SetDefault(PluginsPath, defaultPluginsPath)
|
||||||
|
|
||||||
|
// Set default gallery cover regex
|
||||||
|
i.main.SetDefault(GalleryCoverRegex, galleryCoverRegexDefault)
|
||||||
|
|
||||||
// Set NoProxy default
|
// Set NoProxy default
|
||||||
i.main.SetDefault(NoProxy, noProxyDefault)
|
i.main.SetDefault(NoProxy, noProxyDefault)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,6 @@ import (
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
coverFilename = "cover.jpg"
|
|
||||||
coverFilenameSearchString = "%" + coverFilename
|
|
||||||
)
|
|
||||||
|
|
||||||
type Queryer interface {
|
type Queryer interface {
|
||||||
Query(ctx context.Context, options models.ImageQueryOptions) (*models.ImageQueryResult, error)
|
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)
|
}, &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
|
const useCoverJpg = true
|
||||||
img, err := findGalleryCover(ctx, r, galleryID, useCoverJpg)
|
img, err := findGalleryCover(ctx, r, galleryID, useCoverJpg, galleryCoverRegex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 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
|
// try to find cover.jpg in the gallery
|
||||||
perPage := 1
|
perPage := 1
|
||||||
sortBy := "path"
|
sortBy := "path"
|
||||||
|
|
@ -138,8 +133,8 @@ func findGalleryCover(ctx context.Context, r Queryer, galleryID int, useCoverJpg
|
||||||
|
|
||||||
if useCoverJpg {
|
if useCoverJpg {
|
||||||
imageFilter.Path = &models.StringCriterionInput{
|
imageFilter.Path = &models.StringCriterionInput{
|
||||||
Value: coverFilenameSearchString,
|
Value: "(?i)" + galleryCoverRegex,
|
||||||
Modifier: models.CriterionModifierEquals,
|
Modifier: models.CriterionModifierMatchesRegex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,14 @@ export const SettingsLibraryPanel: React.FC = () => {
|
||||||
checked={general.writeImageThumbnails ?? false}
|
checked={general.writeImageThumbnails ?? false}
|
||||||
onChange={(v) => saveGeneral({ writeImageThumbnails: v })}
|
onChange={(v) => saveGeneral({ writeImageThumbnails: v })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<StringSetting
|
||||||
|
id="gallery-cover-regex"
|
||||||
|
headingID="config.general.gallery_cover_regex_label"
|
||||||
|
subHeadingID="config.general.gallery_cover_regex_desc"
|
||||||
|
value={general.galleryCoverRegex ?? ""}
|
||||||
|
onChange={(v) => saveGeneral({ galleryCoverRegex: v })}
|
||||||
|
/>
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
|
|
||||||
<SettingSection headingID="config.ui.delete_options.heading">
|
<SettingSection headingID="config.ui.delete_options.heading">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
### ✨ New Features
|
### ✨ 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 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))
|
* Added toggleable favorite button to Performer cards. ([#3369](https://github.com/stashapp/stash/pull/3369))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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. |
|
| `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. |
|
| `max_upload_size` | Maximum file upload size for import files. Defaults to 1GB. |
|
||||||
| `theme_color` | Sets the `theme-color` property in the UI. |
|
| `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 |
|
| `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 |
|
| `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 |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.",
|
"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_desc": "Wenn ausgewählt, erzeuge Galerien aus Verzeichnissen, welche Bilder enthalten.",
|
||||||
"create_galleries_from_folders_label": "Erzeuge Galerien aus Verzeichnissen mit Bilder darin",
|
"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",
|
"db_path_head": "Datenbank Pfad",
|
||||||
"directory_locations_to_your_content": "Verzeichnis zu Ihren Inhalten",
|
"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",
|
"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",
|
||||||
|
|
|
||||||
|
|
@ -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.",
|
"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_desc": "If true, creates galleries from folders containing images.",
|
||||||
"create_galleries_from_folders_label": "Create 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",
|
"db_path_head": "Database Path",
|
||||||
"directory_locations_to_your_content": "Directory locations to your content",
|
"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",
|
"excluded_image_gallery_patterns_desc": "Regexps of image and gallery files/paths to exclude from Scan and add to Clean",
|
||||||
|
|
|
||||||
|
|
@ -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.",
|
"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_desc": "Se spuntato, crea gallerie dalle cartelle che contengono immagini.",
|
||||||
"create_galleries_from_folders_label": "Crea gallerie dalle cartelle con 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",
|
"db_path_head": "Percorso del Database",
|
||||||
"directory_locations_to_your_content": "Percorso della Cartella del tuo contenuto",
|
"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",
|
"excluded_image_gallery_patterns_desc": "Espressioni Regolari di file/percorsi di immagini e gallerie per escluderle dalla Scansione e aggiungerle alla Pulizia",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue