mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Add various filter criteria (#1505)
* Add various filter criteria * Add tag name criterion
This commit is contained in:
@@ -33,6 +33,9 @@ input PerformerFilterType {
|
||||
OR: PerformerFilterType
|
||||
NOT: PerformerFilterType
|
||||
|
||||
name: StringCriterionInput
|
||||
details: StringCriterionInput
|
||||
|
||||
"""Filter by favorite"""
|
||||
filter_favorites: Boolean
|
||||
"""Filter by birth year"""
|
||||
@@ -105,6 +108,15 @@ input SceneFilterType {
|
||||
OR: SceneFilterType
|
||||
NOT: SceneFilterType
|
||||
|
||||
title: StringCriterionInput
|
||||
details: StringCriterionInput
|
||||
|
||||
"""Filter by file oshash"""
|
||||
oshash: StringCriterionInput
|
||||
"""Filter by file checksum"""
|
||||
checksum: StringCriterionInput
|
||||
"""Filter by file phash"""
|
||||
phash: StringCriterionInput
|
||||
"""Filter by path"""
|
||||
path: StringCriterionInput
|
||||
"""Filter by rating"""
|
||||
@@ -144,6 +156,15 @@ input SceneFilterType {
|
||||
}
|
||||
|
||||
input MovieFilterType {
|
||||
|
||||
name: StringCriterionInput
|
||||
director: StringCriterionInput
|
||||
synopsis: StringCriterionInput
|
||||
|
||||
"""Filter by duration (in seconds)"""
|
||||
duration: IntCriterionInput
|
||||
"""Filter by rating"""
|
||||
rating: IntCriterionInput
|
||||
"""Filter to only include movies with this studio"""
|
||||
studios: HierarchicalMultiCriterionInput
|
||||
"""Filter to only include movies missing this property"""
|
||||
@@ -153,6 +174,8 @@ input MovieFilterType {
|
||||
}
|
||||
|
||||
input StudioFilterType {
|
||||
name: StringCriterionInput
|
||||
details: StringCriterionInput
|
||||
"""Filter to only include studios with this parent studio"""
|
||||
parents: MultiCriterionInput
|
||||
"""Filter by StashID"""
|
||||
@@ -176,6 +199,11 @@ input GalleryFilterType {
|
||||
OR: GalleryFilterType
|
||||
NOT: GalleryFilterType
|
||||
|
||||
title: StringCriterionInput
|
||||
details: StringCriterionInput
|
||||
|
||||
"""Filter by file checksum"""
|
||||
checksum: StringCriterionInput
|
||||
"""Filter by path"""
|
||||
path: StringCriterionInput
|
||||
"""Filter to only include galleries missing this property"""
|
||||
@@ -241,6 +269,10 @@ input ImageFilterType {
|
||||
OR: ImageFilterType
|
||||
NOT: ImageFilterType
|
||||
|
||||
title: StringCriterionInput
|
||||
|
||||
"""Filter by file checksum"""
|
||||
checksum: StringCriterionInput
|
||||
"""Filter by path"""
|
||||
path: StringCriterionInput
|
||||
"""Filter by rating"""
|
||||
|
||||
@@ -203,6 +203,9 @@ func (qb *galleryQueryBuilder) makeFilter(galleryFilter *models.GalleryFilterTyp
|
||||
query.not(qb.makeFilter(galleryFilter.Not))
|
||||
}
|
||||
|
||||
query.handleCriterion(stringCriterionHandler(galleryFilter.Title, "galleries.title"))
|
||||
query.handleCriterion(stringCriterionHandler(galleryFilter.Details, "galleries.details"))
|
||||
query.handleCriterion(stringCriterionHandler(galleryFilter.Checksum, "galleries.checksum"))
|
||||
query.handleCriterion(boolCriterionHandler(galleryFilter.IsZip, "galleries.zip"))
|
||||
query.handleCriterion(stringCriterionHandler(galleryFilter.Path, "galleries.path"))
|
||||
query.handleCriterion(intCriterionHandler(galleryFilter.Rating, "galleries.rating"))
|
||||
|
||||
@@ -231,6 +231,8 @@ func (qb *imageQueryBuilder) makeFilter(imageFilter *models.ImageFilterType) *fi
|
||||
query.not(qb.makeFilter(imageFilter.Not))
|
||||
}
|
||||
|
||||
query.handleCriterion(stringCriterionHandler(imageFilter.Checksum, "images.checksum"))
|
||||
query.handleCriterion(stringCriterionHandler(imageFilter.Title, "images.title"))
|
||||
query.handleCriterion(stringCriterionHandler(imageFilter.Path, "images.path"))
|
||||
query.handleCriterion(intCriterionHandler(imageFilter.Rating, "images.rating"))
|
||||
query.handleCriterion(intCriterionHandler(imageFilter.OCounter, "images.o_counter"))
|
||||
|
||||
@@ -118,6 +118,11 @@ func (qb *movieQueryBuilder) All() ([]*models.Movie, error) {
|
||||
func (qb *movieQueryBuilder) makeFilter(movieFilter *models.MovieFilterType) *filterBuilder {
|
||||
query := &filterBuilder{}
|
||||
|
||||
query.handleCriterion(stringCriterionHandler(movieFilter.Name, "movies.name"))
|
||||
query.handleCriterion(stringCriterionHandler(movieFilter.Director, "movies.director"))
|
||||
query.handleCriterion(stringCriterionHandler(movieFilter.Synopsis, "movies.synopsis"))
|
||||
query.handleCriterion(intCriterionHandler(movieFilter.Rating, "movies.rating"))
|
||||
query.handleCriterion(durationCriterionHandler(movieFilter.Duration, "movies.duration"))
|
||||
query.handleCriterion(movieIsMissingCriterionHandler(qb, movieFilter.IsMissing))
|
||||
query.handleCriterion(stringCriterionHandler(movieFilter.URL, "movies.url"))
|
||||
query.handleCriterion(movieStudioCriterionHandler(qb, movieFilter.Studios))
|
||||
|
||||
@@ -239,6 +239,9 @@ func (qb *performerQueryBuilder) makeFilter(filter *models.PerformerFilterType)
|
||||
}
|
||||
|
||||
const tableName = performerTable
|
||||
query.handleCriterion(stringCriterionHandler(filter.Name, tableName+".name"))
|
||||
query.handleCriterion(stringCriterionHandler(filter.Details, tableName+".details"))
|
||||
|
||||
query.handleCriterion(boolCriterionHandler(filter.FilterFavorites, tableName+".favorite"))
|
||||
|
||||
query.handleCriterion(yearFilterCriterionHandler(filter.BirthYear, tableName+".birthdate"))
|
||||
|
||||
@@ -354,6 +354,11 @@ func (qb *sceneQueryBuilder) makeFilter(sceneFilter *models.SceneFilterType) *fi
|
||||
}
|
||||
|
||||
query.handleCriterion(stringCriterionHandler(sceneFilter.Path, "scenes.path"))
|
||||
query.handleCriterion(stringCriterionHandler(sceneFilter.Title, "scenes.title"))
|
||||
query.handleCriterion(stringCriterionHandler(sceneFilter.Details, "scenes.details"))
|
||||
query.handleCriterion(stringCriterionHandler(sceneFilter.Oshash, "scenes.oshash"))
|
||||
query.handleCriterion(stringCriterionHandler(sceneFilter.Checksum, "scenes.checksum"))
|
||||
query.handleCriterion(phashCriterionHandler(sceneFilter.Phash))
|
||||
query.handleCriterion(intCriterionHandler(sceneFilter.Rating, "scenes.rating"))
|
||||
query.handleCriterion(intCriterionHandler(sceneFilter.OCounter, "scenes.o_counter"))
|
||||
query.handleCriterion(boolCriterionHandler(sceneFilter.Organized, "scenes.organized"))
|
||||
@@ -430,6 +435,29 @@ func (qb *sceneQueryBuilder) Query(sceneFilter *models.SceneFilterType, findFilt
|
||||
return scenes, countResult, nil
|
||||
}
|
||||
|
||||
func phashCriterionHandler(phashFilter *models.StringCriterionInput) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if phashFilter != nil {
|
||||
// convert value to int from hex
|
||||
// ignore errors
|
||||
value, _ := utils.StringToPhash(phashFilter.Value)
|
||||
|
||||
if modifier := phashFilter.Modifier; phashFilter.Modifier.IsValid() {
|
||||
switch modifier {
|
||||
case models.CriterionModifierEquals:
|
||||
f.addWhere("scenes.phash = ?", value)
|
||||
case models.CriterionModifierNotEquals:
|
||||
f.addWhere("scenes.phash != ?", value)
|
||||
case models.CriterionModifierIsNull:
|
||||
f.addWhere("scenes.phash IS NULL")
|
||||
case models.CriterionModifierNotNull:
|
||||
f.addWhere("scenes.phash IS NOT NULL")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func durationCriterionHandler(durationFilter *models.IntCriterionInput, column string) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if durationFilter != nil {
|
||||
|
||||
@@ -184,6 +184,8 @@ func (qb *studioQueryBuilder) Query(studioFilter *models.StudioFilterType, findF
|
||||
query.handleCountCriterion(studioFilter.SceneCount, studioTable, sceneTable, studioIDColumn)
|
||||
query.handleCountCriterion(studioFilter.ImageCount, studioTable, imageTable, studioIDColumn)
|
||||
query.handleCountCriterion(studioFilter.GalleryCount, studioTable, galleryTable, studioIDColumn)
|
||||
query.handleStringCriterionInput(studioFilter.Name, "studios.name")
|
||||
query.handleStringCriterionInput(studioFilter.Details, "studios.details")
|
||||
query.handleStringCriterionInput(studioFilter.URL, "studios.url")
|
||||
query.handleStringCriterionInput(studioFilter.StashID, "studio_stash_ids.stash_id")
|
||||
|
||||
|
||||
@@ -55,3 +55,12 @@ func findNeighbors(bucket int, neighbors []int, hashes []*Phash, scenes *[]int)
|
||||
func PhashToString(phash int64) string {
|
||||
return strconv.FormatUint(uint64(phash), 16)
|
||||
}
|
||||
|
||||
func StringToPhash(s string) (int64, error) {
|
||||
ret, err := strconv.ParseUint(s, 16, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int64(ret), nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
### ✨ New Features
|
||||
* Add button to open scene in external player on handheld devices. ([#679](https://github.com/stashapp/stash/pull/679))
|
||||
* Added filter criteria for name, details and hash related fields. ([#1505](https://github.com/stashapp/stash/pull/1505))
|
||||
* Added button to open scene in external player on handheld devices. ([#679](https://github.com/stashapp/stash/pull/679))
|
||||
* Added support for saved and default filters. ([#1474](https://github.com/stashapp/stash/pull/1474))
|
||||
* Added merge tags functionality. ([#1481](https://github.com/stashapp/stash/pull/1481))
|
||||
* Added support for triggering plugin tasks during operations. ([#1452](https://github.com/stashapp/stash/pull/1452))
|
||||
|
||||
@@ -184,8 +184,16 @@ export class StringCriterionOption extends CriterionOption {
|
||||
}
|
||||
}
|
||||
|
||||
export function createStringCriterionOption(value: CriterionType) {
|
||||
return new StringCriterionOption(value, value, value);
|
||||
export function createStringCriterionOption(
|
||||
value: CriterionType,
|
||||
messageID?: string,
|
||||
parameterName?: string
|
||||
) {
|
||||
return new StringCriterionOption(
|
||||
messageID ?? value,
|
||||
value,
|
||||
parameterName ?? messageID ?? value
|
||||
);
|
||||
}
|
||||
|
||||
export class StringCriterion extends Criterion<string> {
|
||||
@@ -236,6 +244,18 @@ export class MandatoryStringCriterionOption extends CriterionOption {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMandatoryStringCriterionOption(
|
||||
value: CriterionType,
|
||||
messageID?: string,
|
||||
parameterName?: string
|
||||
) {
|
||||
return new MandatoryStringCriterionOption(
|
||||
messageID ?? value,
|
||||
value,
|
||||
parameterName ?? messageID ?? value
|
||||
);
|
||||
}
|
||||
|
||||
export class BooleanCriterionOption extends CriterionOption {
|
||||
constructor(messageID: string, value: CriterionType, parameterName?: string) {
|
||||
super({
|
||||
|
||||
@@ -38,12 +38,16 @@ import { GalleriesCriterion } from "./galleries";
|
||||
import { CriterionType } from "../types";
|
||||
import { InteractiveCriterion } from "./interactive";
|
||||
import { RatingCriterionOption } from "./rating";
|
||||
import { PhashCriterionOption } from "./phash";
|
||||
|
||||
export function makeCriteria(type: CriterionType = "none") {
|
||||
switch (type) {
|
||||
case "none":
|
||||
return new NoneCriterion();
|
||||
case "name":
|
||||
case "path":
|
||||
case "checksum":
|
||||
case "oshash":
|
||||
return new StringCriterion(
|
||||
new MandatoryStringCriterionOption(type, type)
|
||||
);
|
||||
@@ -111,6 +115,13 @@ export function makeCriteria(type: CriterionType = "none") {
|
||||
);
|
||||
case "gender":
|
||||
return new GenderCriterion();
|
||||
case "sceneChecksum":
|
||||
case "galleryChecksum":
|
||||
return new StringCriterion(
|
||||
new StringCriterionOption("checksum", type, "checksum")
|
||||
);
|
||||
case "phash":
|
||||
return new StringCriterion(PhashCriterionOption);
|
||||
case "ethnicity":
|
||||
case "country":
|
||||
case "hair_color":
|
||||
@@ -124,6 +135,10 @@ export function makeCriteria(type: CriterionType = "none") {
|
||||
case "aliases":
|
||||
case "url":
|
||||
case "stash_id":
|
||||
case "details":
|
||||
case "title":
|
||||
case "director":
|
||||
case "synopsis":
|
||||
return new StringCriterion(new StringCriterionOption(type, type));
|
||||
case "interactive":
|
||||
return new InteractiveCriterion();
|
||||
|
||||
15
ui/v2.5/src/models/list-filter/criteria/phash.ts
Normal file
15
ui/v2.5/src/models/list-filter/criteria/phash.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { CriterionModifier } from "src/core/generated-graphql";
|
||||
import { CriterionOption } from "./criterion";
|
||||
|
||||
export const PhashCriterionOption = new CriterionOption({
|
||||
messageID: "media_info.phash",
|
||||
type: "phash",
|
||||
parameterName: "phash",
|
||||
inputType: "text",
|
||||
modifierOptions: [
|
||||
CriterionModifier.Equals,
|
||||
CriterionModifier.NotEquals,
|
||||
CriterionModifier.IsNull,
|
||||
CriterionModifier.NotNull,
|
||||
],
|
||||
});
|
||||
@@ -38,7 +38,14 @@ const displayModeOptions = [
|
||||
];
|
||||
|
||||
const criterionOptions = [
|
||||
createStringCriterionOption("title"),
|
||||
createStringCriterionOption("details"),
|
||||
createStringCriterionOption("path"),
|
||||
createStringCriterionOption(
|
||||
"galleryChecksum",
|
||||
"media_info.checksum",
|
||||
"checksum"
|
||||
),
|
||||
RatingCriterionOption,
|
||||
OrganizedCriterionOption,
|
||||
AverageResolutionCriterionOption,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createMandatoryNumberCriterionOption,
|
||||
createMandatoryStringCriterionOption,
|
||||
createStringCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { ImageIsMissingCriterionOption } from "./criteria/is-missing";
|
||||
@@ -31,7 +32,9 @@ const sortByOptions = [
|
||||
|
||||
const displayModeOptions = [DisplayMode.Grid, DisplayMode.Wall];
|
||||
const criterionOptions = [
|
||||
createStringCriterionOption("path"),
|
||||
createStringCriterionOption("title"),
|
||||
createMandatoryStringCriterionOption("checksum", "media_info.checksum"),
|
||||
createMandatoryStringCriterionOption("path"),
|
||||
RatingCriterionOption,
|
||||
OrganizedCriterionOption,
|
||||
createMandatoryNumberCriterionOption("o_counter"),
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { createStringCriterionOption } from "./criteria/criterion";
|
||||
import {
|
||||
createMandatoryNumberCriterionOption,
|
||||
createStringCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { MovieIsMissingCriterionOption } from "./criteria/is-missing";
|
||||
import { RatingCriterionOption } from "./criteria/rating";
|
||||
import { StudiosCriterionOption } from "./criteria/studios";
|
||||
import { ListFilterOptions } from "./filter-options";
|
||||
import { DisplayMode } from "./types";
|
||||
@@ -19,6 +23,11 @@ const criterionOptions = [
|
||||
StudiosCriterionOption,
|
||||
MovieIsMissingCriterionOption,
|
||||
createStringCriterionOption("url"),
|
||||
createStringCriterionOption("name"),
|
||||
createStringCriterionOption("director"),
|
||||
createStringCriterionOption("synopsis"),
|
||||
createMandatoryNumberCriterionOption("duration"),
|
||||
RatingCriterionOption,
|
||||
];
|
||||
|
||||
export const MovieListFilterOptions = new ListFilterOptions(
|
||||
|
||||
@@ -43,6 +43,8 @@ const numberCriteria: CriterionType[] = [
|
||||
];
|
||||
|
||||
const stringCriteria: CriterionType[] = [
|
||||
"name",
|
||||
"details",
|
||||
"ethnicity",
|
||||
"country",
|
||||
"hair_color",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createMandatoryNumberCriterionOption,
|
||||
createMandatoryStringCriterionOption,
|
||||
createStringCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { HasMarkersCriterionOption } from "./criteria/has-markers";
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
} from "./criteria/tags";
|
||||
import { ListFilterOptions } from "./filter-options";
|
||||
import { DisplayMode } from "./types";
|
||||
import { PhashCriterionOption } from "./criteria/phash";
|
||||
|
||||
const defaultSortBy = "date";
|
||||
const sortByOptions = [
|
||||
@@ -46,7 +48,16 @@ const displayModeOptions = [
|
||||
];
|
||||
|
||||
const criterionOptions = [
|
||||
createStringCriterionOption("path"),
|
||||
createStringCriterionOption("title"),
|
||||
createMandatoryStringCriterionOption("path"),
|
||||
createStringCriterionOption("details"),
|
||||
createMandatoryStringCriterionOption("oshash", "media_info.hash"),
|
||||
createStringCriterionOption(
|
||||
"sceneChecksum",
|
||||
"media_info.checksum",
|
||||
"checksum"
|
||||
),
|
||||
PhashCriterionOption,
|
||||
RatingCriterionOption,
|
||||
OrganizedCriterionOption,
|
||||
createMandatoryNumberCriterionOption("o_counter"),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createMandatoryNumberCriterionOption,
|
||||
createMandatoryStringCriterionOption,
|
||||
createStringCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { StudioIsMissingCriterionOption } from "./criteria/is-missing";
|
||||
@@ -28,6 +29,8 @@ const sortByOptions = ["name", "random", "rating"]
|
||||
|
||||
const displayModeOptions = [DisplayMode.Grid];
|
||||
const criterionOptions = [
|
||||
createMandatoryStringCriterionOption("name"),
|
||||
createStringCriterionOption("details"),
|
||||
ParentStudiosCriterionOption,
|
||||
StudioIsMissingCriterionOption,
|
||||
RatingCriterionOption,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createMandatoryNumberCriterionOption,
|
||||
createMandatoryStringCriterionOption,
|
||||
createStringCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { TagIsMissingCriterionOption } from "./criteria/is-missing";
|
||||
@@ -36,6 +37,7 @@ const sortByOptions = [
|
||||
|
||||
const displayModeOptions = [DisplayMode.Grid, DisplayMode.List];
|
||||
const criterionOptions = [
|
||||
createMandatoryStringCriterionOption("name"),
|
||||
TagIsMissingCriterionOption,
|
||||
createStringCriterionOption("aliases"),
|
||||
createMandatoryNumberCriterionOption("scene_count"),
|
||||
|
||||
@@ -92,4 +92,14 @@ export type CriterionType =
|
||||
| "death_year"
|
||||
| "url"
|
||||
| "stash_id"
|
||||
| "interactive";
|
||||
| "interactive"
|
||||
| "name"
|
||||
| "details"
|
||||
| "title"
|
||||
| "oshash"
|
||||
| "checksum"
|
||||
| "sceneChecksum"
|
||||
| "galleryChecksum"
|
||||
| "phash"
|
||||
| "director"
|
||||
| "synopsis";
|
||||
|
||||
Reference in New Issue
Block a user