mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Add Several Media Performer Detail Filters, Scene Filters and Sort (#2257)
Co-authored-by: bnkai <48220860+bnkai@users.noreply.github.com> Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
1d68492a5b
commit
4dd0bbc294
@@ -33,6 +33,12 @@ input ResolutionCriterionInput {
|
||||
modifier: CriterionModifier!
|
||||
}
|
||||
|
||||
input PHashDuplicationCriterionInput {
|
||||
duplicated: Boolean
|
||||
"""Currently unimplemented"""
|
||||
distance: Int
|
||||
}
|
||||
|
||||
input PerformerFilterType {
|
||||
AND: PerformerFilterType
|
||||
OR: PerformerFilterType
|
||||
@@ -130,6 +136,8 @@ input SceneFilterType {
|
||||
organized: Boolean
|
||||
"""Filter by o-counter"""
|
||||
o_counter: IntCriterionInput
|
||||
"""Filter Scenes that have an exact phash match available"""
|
||||
duplicated: PHashDuplicationCriterionInput
|
||||
"""Filter by resolution"""
|
||||
resolution: ResolutionCriterionInput
|
||||
"""Filter by duration (in seconds)"""
|
||||
@@ -148,6 +156,10 @@ input SceneFilterType {
|
||||
tag_count: IntCriterionInput
|
||||
"""Filter to only include scenes with performers with these tags"""
|
||||
performer_tags: HierarchicalMultiCriterionInput
|
||||
"""Filter scenes that have performers that have been favorited"""
|
||||
performer_favorite: Boolean
|
||||
"""Filter scenes by performer age at time of scene"""
|
||||
performer_age: IntCriterionInput
|
||||
"""Filter to only include scenes with these performers"""
|
||||
performers: MultiCriterionInput
|
||||
"""Filter by performer count"""
|
||||
@@ -243,6 +255,10 @@ input GalleryFilterType {
|
||||
performers: MultiCriterionInput
|
||||
"""Filter by performer count"""
|
||||
performer_count: IntCriterionInput
|
||||
"""Filter galleries that have performers that have been favorited"""
|
||||
performer_favorite: Boolean
|
||||
"""Filter galleries by performer age at time of gallery"""
|
||||
performer_age: IntCriterionInput
|
||||
"""Filter by number of images in this gallery"""
|
||||
image_count: IntCriterionInput
|
||||
"""Filter by url"""
|
||||
@@ -324,6 +340,8 @@ input ImageFilterType {
|
||||
performers: MultiCriterionInput
|
||||
"""Filter by performer count"""
|
||||
performer_count: IntCriterionInput
|
||||
"""Filter images that have performers that have been favorited"""
|
||||
performer_favorite: Boolean
|
||||
"""Filter to only include images with these galleries"""
|
||||
galleries: MultiCriterionInput
|
||||
}
|
||||
|
||||
@@ -391,13 +391,7 @@ func stringCriterionHandler(c *models.StringCriterionInput, column string) crite
|
||||
case models.CriterionModifierNotNull:
|
||||
f.addWhere("(" + column + " IS NOT NULL AND TRIM(" + column + ") != '')")
|
||||
default:
|
||||
clause, count := getSimpleCriterionClause(modifier, "?")
|
||||
|
||||
if count == 1 {
|
||||
f.addWhere(column+" "+clause, c.Value)
|
||||
} else {
|
||||
f.addWhere(column + " " + clause)
|
||||
}
|
||||
panic("unsupported string filter modifier")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,6 +220,8 @@ func (qb *galleryQueryBuilder) makeFilter(galleryFilter *models.GalleryFilterTyp
|
||||
query.handleCriterion(galleryPerformerTagsCriterionHandler(qb, galleryFilter.PerformerTags))
|
||||
query.handleCriterion(galleryAverageResolutionCriterionHandler(qb, galleryFilter.AverageResolution))
|
||||
query.handleCriterion(galleryImageCountCriterionHandler(qb, galleryFilter.ImageCount))
|
||||
query.handleCriterion(galleryPerformerFavoriteCriterionHandler(galleryFilter.PerformerFavorite))
|
||||
query.handleCriterion(galleryPerformerAgeCriterionHandler(galleryFilter.PerformerAge))
|
||||
|
||||
return query
|
||||
}
|
||||
@@ -421,6 +423,43 @@ INNER JOIN (` + valuesClause + `) t ON t.column2 = pt.tag_id
|
||||
}
|
||||
}
|
||||
|
||||
func galleryPerformerFavoriteCriterionHandler(performerfavorite *bool) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if performerfavorite != nil {
|
||||
f.addLeftJoin("performers_galleries", "", "galleries.id = performers_galleries.gallery_id")
|
||||
|
||||
if *performerfavorite {
|
||||
// contains at least one favorite
|
||||
f.addLeftJoin("performers", "", "performers.id = performers_galleries.performer_id")
|
||||
f.addWhere("performers.favorite = 1")
|
||||
} else {
|
||||
// contains zero favorites
|
||||
f.addLeftJoin(`(SELECT performers_galleries.gallery_id as id FROM performers_galleries
|
||||
JOIN performers ON performers.id = performers_galleries.performer_id
|
||||
GROUP BY performers_galleries.gallery_id HAVING SUM(performers.favorite) = 0)`, "nofaves", "galleries.id = nofaves.id")
|
||||
f.addWhere("performers_galleries.gallery_id IS NULL OR nofaves.id IS NOT NULL")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func galleryPerformerAgeCriterionHandler(performerAge *models.IntCriterionInput) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if performerAge != nil {
|
||||
f.addInnerJoin("performers_galleries", "", "galleries.id = performers_galleries.gallery_id")
|
||||
f.addInnerJoin("performers", "", "performers_galleries.performer_id = performers.id")
|
||||
|
||||
f.addWhere("galleries.date != '' AND performers.birthdate != ''")
|
||||
f.addWhere("galleries.date IS NOT NULL AND performers.birthdate IS NOT NULL")
|
||||
f.addWhere("galleries.date != '0001-01-01' AND performers.birthdate != '0001-01-01'")
|
||||
|
||||
ageCalc := "cast(strftime('%Y.%m%d', galleries.date) - strftime('%Y.%m%d', performers.birthdate) as int)"
|
||||
whereClause, args := getIntWhereClause(ageCalc, performerAge.Modifier, performerAge.Value, performerAge.Value2)
|
||||
f.addWhere(whereClause, args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func galleryAverageResolutionCriterionHandler(qb *galleryQueryBuilder, resolution *models.ResolutionCriterionInput) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if resolution != nil && resolution.Value.IsValid() {
|
||||
|
||||
@@ -248,6 +248,7 @@ func (qb *imageQueryBuilder) makeFilter(imageFilter *models.ImageFilterType) *fi
|
||||
query.handleCriterion(imagePerformerCountCriterionHandler(qb, imageFilter.PerformerCount))
|
||||
query.handleCriterion(imageStudioCriterionHandler(qb, imageFilter.Studios))
|
||||
query.handleCriterion(imagePerformerTagsCriterionHandler(qb, imageFilter.PerformerTags))
|
||||
query.handleCriterion(imagePerformerFavoriteCriterionHandler(imageFilter.PerformerFavorite))
|
||||
|
||||
return query
|
||||
}
|
||||
@@ -446,6 +447,26 @@ func imagePerformerCountCriterionHandler(qb *imageQueryBuilder, performerCount *
|
||||
return h.handler(performerCount)
|
||||
}
|
||||
|
||||
func imagePerformerFavoriteCriterionHandler(performerfavorite *bool) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if performerfavorite != nil {
|
||||
f.addLeftJoin("performers_images", "", "images.id = performers_images.image_id")
|
||||
|
||||
if *performerfavorite {
|
||||
// contains at least one favorite
|
||||
f.addLeftJoin("performers", "", "performers.id = performers_images.performer_id")
|
||||
f.addWhere("performers.favorite = 1")
|
||||
} else {
|
||||
// contains zero favorites
|
||||
f.addLeftJoin(`(SELECT performers_images.image_id as id FROM performers_images
|
||||
JOIN performers ON performers.id = performers_images.performer_id
|
||||
GROUP BY performers_images.image_id HAVING SUM(performers.favorite) = 0)`, "nofaves", "images.id = nofaves.id")
|
||||
f.addWhere("performers_images.image_id IS NULL OR nofaves.id IS NOT NULL")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func imageStudioCriterionHandler(qb *imageQueryBuilder, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
h := hierarchicalMultiCriterionHandlerBuilder{
|
||||
tx: qb.tx,
|
||||
|
||||
@@ -392,6 +392,9 @@ func (qb *sceneQueryBuilder) makeFilter(sceneFilter *models.SceneFilterType) *fi
|
||||
query.handleCriterion(sceneStudioCriterionHandler(qb, sceneFilter.Studios))
|
||||
query.handleCriterion(sceneMoviesCriterionHandler(qb, sceneFilter.Movies))
|
||||
query.handleCriterion(scenePerformerTagsCriterionHandler(qb, sceneFilter.PerformerTags))
|
||||
query.handleCriterion(scenePerformerFavoriteCriterionHandler(sceneFilter.PerformerFavorite))
|
||||
query.handleCriterion(scenePerformerAgeCriterionHandler(sceneFilter.PerformerAge))
|
||||
query.handleCriterion(scenePhashDuplicatedCriterionHandler(sceneFilter.Duplicated))
|
||||
|
||||
return query
|
||||
}
|
||||
@@ -504,6 +507,21 @@ func phashCriterionHandler(phashFilter *models.StringCriterionInput) criterionHa
|
||||
}
|
||||
}
|
||||
|
||||
func scenePhashDuplicatedCriterionHandler(duplicatedFilter *models.PHashDuplicationCriterionInput) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
// TODO: Wishlist item: Implement Distance matching
|
||||
if duplicatedFilter != nil {
|
||||
var v string
|
||||
if *duplicatedFilter.Duplicated {
|
||||
v = ">"
|
||||
} else {
|
||||
v = "="
|
||||
}
|
||||
f.addInnerJoin("(SELECT id FROM scenes JOIN (SELECT phash FROM scenes GROUP BY phash HAVING COUNT(phash) "+v+" 1) dupes on scenes.phash = dupes.phash)", "scph", "scenes.id = scph.id")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func durationCriterionHandler(durationFilter *models.IntCriterionInput, column string) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if durationFilter != nil {
|
||||
@@ -642,6 +660,43 @@ func scenePerformerCountCriterionHandler(qb *sceneQueryBuilder, performerCount *
|
||||
return h.handler(performerCount)
|
||||
}
|
||||
|
||||
func scenePerformerFavoriteCriterionHandler(performerfavorite *bool) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if performerfavorite != nil {
|
||||
f.addLeftJoin("performers_scenes", "", "scenes.id = performers_scenes.scene_id")
|
||||
|
||||
if *performerfavorite {
|
||||
// contains at least one favorite
|
||||
f.addLeftJoin("performers", "", "performers.id = performers_scenes.performer_id")
|
||||
f.addWhere("performers.favorite = 1")
|
||||
} else {
|
||||
// contains zero favorites
|
||||
f.addLeftJoin(`(SELECT performers_scenes.scene_id as id FROM performers_scenes
|
||||
JOIN performers ON performers.id = performers_scenes.performer_id
|
||||
GROUP BY performers_scenes.scene_id HAVING SUM(performers.favorite) = 0)`, "nofaves", "scenes.id = nofaves.id")
|
||||
f.addWhere("performers_scenes.scene_id IS NULL OR nofaves.id IS NOT NULL")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scenePerformerAgeCriterionHandler(performerAge *models.IntCriterionInput) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if performerAge != nil {
|
||||
f.addInnerJoin("performers_scenes", "", "scenes.id = performers_scenes.scene_id")
|
||||
f.addInnerJoin("performers", "", "performers_scenes.performer_id = performers.id")
|
||||
|
||||
f.addWhere("scenes.date != '' AND performers.birthdate != ''")
|
||||
f.addWhere("scenes.date IS NOT NULL AND performers.birthdate IS NOT NULL")
|
||||
f.addWhere("scenes.date != '0001-01-01' AND performers.birthdate != '0001-01-01'")
|
||||
|
||||
ageCalc := "cast(strftime('%Y.%m%d', scenes.date) - strftime('%Y.%m%d', performers.birthdate) as int)"
|
||||
whereClause, args := getIntWhereClause(ageCalc, performerAge.Modifier, performerAge.Value, performerAge.Value2)
|
||||
f.addWhere(whereClause, args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sceneStudioCriterionHandler(qb *sceneQueryBuilder, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
h := hierarchicalMultiCriterionHandlerBuilder{
|
||||
tx: qb.tx,
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
@@ -66,6 +65,10 @@ func getSort(sort string, direction string, tableName string) string {
|
||||
case strings.Compare(sort, "filesize") == 0:
|
||||
colName := getColumn(tableName, "size")
|
||||
return " ORDER BY cast(" + colName + " as integer) " + direction
|
||||
case strings.Compare(sort, "perceptual_similarity") == 0:
|
||||
colName := getColumn(tableName, "phash")
|
||||
secondaryColName := getColumn(tableName, "size")
|
||||
return " ORDER BY " + colName + " " + direction + ", " + secondaryColName + " DESC"
|
||||
case strings.HasPrefix(sort, randomSeedPrefix):
|
||||
// seed as a parameter from the UI
|
||||
// turn the provided seed into a float
|
||||
@@ -149,54 +152,39 @@ func getInBinding(length int) string {
|
||||
return "(" + bindings + ")"
|
||||
}
|
||||
|
||||
func getSimpleCriterionClause(criterionModifier models.CriterionModifier, rhs string) (string, int) {
|
||||
if modifier := criterionModifier.String(); criterionModifier.IsValid() {
|
||||
switch modifier {
|
||||
case "EQUALS":
|
||||
return "= " + rhs, 1
|
||||
case "NOT_EQUALS":
|
||||
return "!= " + rhs, 1
|
||||
case "GREATER_THAN":
|
||||
return "> " + rhs, 1
|
||||
case "LESS_THAN":
|
||||
return "< " + rhs, 1
|
||||
case "IS_NULL":
|
||||
return "IS NULL", 0
|
||||
case "NOT_NULL":
|
||||
return "IS NOT NULL", 0
|
||||
case "BETWEEN":
|
||||
return "BETWEEN (" + rhs + ") AND (" + rhs + ")", 2
|
||||
case "NOT_BETWEEN":
|
||||
return "NOT BETWEEN (" + rhs + ") AND (" + rhs + ")", 2
|
||||
default:
|
||||
logger.Errorf("todo")
|
||||
return "= ?", 1 // TODO
|
||||
}
|
||||
}
|
||||
|
||||
return "= ?", 1 // TODO
|
||||
func getIntCriterionWhereClause(column string, input models.IntCriterionInput) (string, []interface{}) {
|
||||
return getIntWhereClause(column, input.Modifier, input.Value, input.Value2)
|
||||
}
|
||||
|
||||
func getIntCriterionWhereClause(column string, input models.IntCriterionInput) (string, []interface{}) {
|
||||
binding, _ := getSimpleCriterionClause(input.Modifier, "?")
|
||||
var args []interface{}
|
||||
|
||||
switch input.Modifier {
|
||||
case "EQUALS", "NOT_EQUALS":
|
||||
args = []interface{}{input.Value}
|
||||
case "LESS_THAN":
|
||||
args = []interface{}{input.Value}
|
||||
case "GREATER_THAN":
|
||||
args = []interface{}{input.Value}
|
||||
case "BETWEEN", "NOT_BETWEEN":
|
||||
upper := 0
|
||||
if input.Value2 != nil {
|
||||
upper = *input.Value2
|
||||
}
|
||||
args = []interface{}{input.Value, upper}
|
||||
func getIntWhereClause(column string, modifier models.CriterionModifier, value int, upper *int) (string, []interface{}) {
|
||||
if upper == nil {
|
||||
u := 0
|
||||
upper = &u
|
||||
}
|
||||
|
||||
return column + " " + binding, args
|
||||
args := []interface{}{value}
|
||||
betweenArgs := []interface{}{value, *upper}
|
||||
|
||||
switch modifier {
|
||||
case models.CriterionModifierIsNull:
|
||||
return fmt.Sprintf("%s IS NULL", column), nil
|
||||
case models.CriterionModifierNotNull:
|
||||
return fmt.Sprintf("%s IS NOT NULL", column), nil
|
||||
case models.CriterionModifierEquals:
|
||||
return fmt.Sprintf("%s = ?", column), args
|
||||
case models.CriterionModifierNotEquals:
|
||||
return fmt.Sprintf("%s != ?", column), args
|
||||
case models.CriterionModifierBetween:
|
||||
return fmt.Sprintf("%s BETWEEN ? AND ?", column), betweenArgs
|
||||
case models.CriterionModifierNotBetween:
|
||||
return fmt.Sprintf("%s NOT BETWEEN ? AND ?", column), betweenArgs
|
||||
case models.CriterionModifierLessThan:
|
||||
return fmt.Sprintf("%s < ?", column), args
|
||||
case models.CriterionModifierGreaterThan:
|
||||
return fmt.Sprintf("%s > ?", column), args
|
||||
}
|
||||
|
||||
panic("unsupported int modifier type")
|
||||
}
|
||||
|
||||
// returns where clause and having clause
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
### ✨ New Features
|
||||
* Added support for filtering scenes, images and galleries featuring favourite performers and performer age at time of production. ([#2257](https://github.com/stashapp/stash/pull/2257))
|
||||
* Added support for filtering scenes with (or without) phash duplicates. ([#2257](https://github.com/stashapp/stash/pull/2257))
|
||||
* Added support for sorting scenes by phash. ([#2257](https://github.com/stashapp/stash/pull/2257))
|
||||
* Open stash in system tray on Windows/MacOS when not running via terminal. ([#2073](https://github.com/stashapp/stash/pull/2073))
|
||||
* Optionally send desktop notifications when a task completes. ([#2073](https://github.com/stashapp/stash/pull/2073))
|
||||
* Added button to image card to view image in Lightbox. ([#2275](https://github.com/stashapp/stash/pull/2275))
|
||||
|
||||
@@ -653,6 +653,7 @@
|
||||
"search_accuracy_label": "Search Accuracy",
|
||||
"title": "Duplicate Scenes"
|
||||
},
|
||||
"duplicated_phash": "Duplicated (phash)",
|
||||
"duration": "Duration",
|
||||
"effect_filters": {
|
||||
"aspect": "Aspect",
|
||||
@@ -760,9 +761,12 @@
|
||||
"parent_tags": "Parent Tags",
|
||||
"part_of": "Part of {parent}",
|
||||
"path": "Path",
|
||||
"perceptual_similarity": "Perceptual Similarity (phash)",
|
||||
"performer": "Performer",
|
||||
"performerTags": "Performer Tags",
|
||||
"performer_count": "Performer Count",
|
||||
"performer_favorite": "Performer Favourited",
|
||||
"performer_age": "Performer Age",
|
||||
"performer_image": "Performer Image",
|
||||
"performers": "Performers",
|
||||
"piercings": "Piercings",
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"ignore_organized": "Ignore organized scenes"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"performer_favorite": "Performer Favorited"
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
HierarchicalMultiCriterionInput,
|
||||
IntCriterionInput,
|
||||
MultiCriterionInput,
|
||||
PHashDuplicationCriterionInput,
|
||||
} from "src/core/generated-graphql";
|
||||
import DurationUtils from "src/utils/duration";
|
||||
import {
|
||||
@@ -521,3 +522,11 @@ export class DurationCriterion extends Criterion<INumberValue> {
|
||||
: "?";
|
||||
}
|
||||
}
|
||||
|
||||
export class PhashDuplicateCriterion extends StringCriterion {
|
||||
protected toCriterionInput(): PHashDuplicationCriterionInput {
|
||||
return {
|
||||
duplicated: this.value === "true",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ILabeledIdCriterion,
|
||||
} from "./criterion";
|
||||
import { OrganizedCriterion } from "./organized";
|
||||
import { FavoriteCriterion } from "./favorite";
|
||||
import { FavoriteCriterion, PerformerFavoriteCriterion } from "./favorite";
|
||||
import { HasMarkersCriterion } from "./has-markers";
|
||||
import {
|
||||
PerformerIsMissingCriterionOption,
|
||||
@@ -40,7 +40,7 @@ import { GalleriesCriterion } from "./galleries";
|
||||
import { CriterionType } from "../types";
|
||||
import { InteractiveCriterion } from "./interactive";
|
||||
import { RatingCriterionOption } from "./rating";
|
||||
import { PhashCriterionOption } from "./phash";
|
||||
import { DuplicatedCriterion, PhashCriterionOption } from "./phash";
|
||||
|
||||
export function makeCriteria(type: CriterionType = "none") {
|
||||
switch (type) {
|
||||
@@ -67,6 +67,7 @@ export function makeCriteria(type: CriterionType = "none") {
|
||||
case "image_count":
|
||||
case "gallery_count":
|
||||
case "performer_count":
|
||||
case "performer_age":
|
||||
case "tag_count":
|
||||
return new NumberCriterion(
|
||||
new MandatoryNumberCriterionOption(type, type)
|
||||
@@ -107,6 +108,8 @@ export function makeCriteria(type: CriterionType = "none") {
|
||||
return new TagsCriterion(ChildTagsCriterionOption);
|
||||
case "performers":
|
||||
return new PerformersCriterion();
|
||||
case "performer_favorite":
|
||||
return new PerformerFavoriteCriterion();
|
||||
case "studios":
|
||||
return new StudiosCriterion();
|
||||
case "parent_studios":
|
||||
@@ -132,6 +135,8 @@ export function makeCriteria(type: CriterionType = "none") {
|
||||
);
|
||||
case "phash":
|
||||
return new StringCriterion(PhashCriterionOption);
|
||||
case "duplicated":
|
||||
return new DuplicatedCriterion();
|
||||
case "ethnicity":
|
||||
case "country":
|
||||
case "hair_color":
|
||||
|
||||
@@ -11,3 +11,15 @@ export class FavoriteCriterion extends BooleanCriterion {
|
||||
super(FavoriteCriterionOption);
|
||||
}
|
||||
}
|
||||
|
||||
export const PerformerFavoriteCriterionOption = new BooleanCriterionOption(
|
||||
"performer_favorite",
|
||||
"performer_favorite",
|
||||
"performer_favorite"
|
||||
);
|
||||
|
||||
export class PerformerFavoriteCriterion extends BooleanCriterion {
|
||||
constructor() {
|
||||
super(PerformerFavoriteCriterionOption);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { CriterionModifier } from "src/core/generated-graphql";
|
||||
import { CriterionOption, StringCriterion } from "./criterion";
|
||||
import {
|
||||
BooleanCriterionOption,
|
||||
CriterionOption,
|
||||
PhashDuplicateCriterion,
|
||||
StringCriterion,
|
||||
} from "./criterion";
|
||||
|
||||
export const PhashCriterionOption = new CriterionOption({
|
||||
messageID: "media_info.phash",
|
||||
@@ -19,3 +24,15 @@ export class PhashCriterion extends StringCriterion {
|
||||
super(PhashCriterionOption);
|
||||
}
|
||||
}
|
||||
|
||||
export const DuplicatedCriterionOption = new BooleanCriterionOption(
|
||||
"duplicated_phash",
|
||||
"duplicated",
|
||||
"duplicated"
|
||||
);
|
||||
|
||||
export class DuplicatedCriterion extends PhashDuplicateCriterion {
|
||||
constructor() {
|
||||
super(DuplicatedCriterionOption);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { createStringCriterionOption } from "./criteria/criterion";
|
||||
import {
|
||||
createMandatoryNumberCriterionOption,
|
||||
createStringCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { PerformerFavoriteCriterionOption } from "./criteria/favorite";
|
||||
import { GalleryIsMissingCriterionOption } from "./criteria/is-missing";
|
||||
import { OrganizedCriterionOption } from "./criteria/organized";
|
||||
import { PerformersCriterionOption } from "./criteria/performers";
|
||||
@@ -47,6 +51,8 @@ const criterionOptions = [
|
||||
PerformerTagsCriterionOption,
|
||||
PerformersCriterionOption,
|
||||
createStringCriterionOption("performer_count"),
|
||||
createMandatoryNumberCriterionOption("performer_age"),
|
||||
PerformerFavoriteCriterionOption,
|
||||
createStringCriterionOption("image_count"),
|
||||
StudiosCriterionOption,
|
||||
createStringCriterionOption("url"),
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
createMandatoryStringCriterionOption,
|
||||
createStringCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { PerformerFavoriteCriterionOption } from "./criteria/favorite";
|
||||
import { ImageIsMissingCriterionOption } from "./criteria/is-missing";
|
||||
import { OrganizedCriterionOption } from "./criteria/organized";
|
||||
import { PerformersCriterionOption } from "./criteria/performers";
|
||||
@@ -37,6 +38,8 @@ const criterionOptions = [
|
||||
PerformerTagsCriterionOption,
|
||||
PerformersCriterionOption,
|
||||
createMandatoryNumberCriterionOption("performer_count"),
|
||||
createMandatoryNumberCriterionOption("performer_age"),
|
||||
PerformerFavoriteCriterionOption,
|
||||
StudiosCriterionOption,
|
||||
];
|
||||
export const ImageListFilterOptions = new ListFilterOptions(
|
||||
|
||||
@@ -18,7 +18,11 @@ import {
|
||||
} from "./criteria/tags";
|
||||
import { ListFilterOptions, MediaSortByOptions } from "./filter-options";
|
||||
import { DisplayMode } from "./types";
|
||||
import { PhashCriterionOption } from "./criteria/phash";
|
||||
import {
|
||||
DuplicatedCriterionOption,
|
||||
PhashCriterionOption,
|
||||
} from "./criteria/phash";
|
||||
import { PerformerFavoriteCriterionOption } from "./criteria/favorite";
|
||||
|
||||
const defaultSortBy = "date";
|
||||
const sortByOptions = [
|
||||
@@ -32,6 +36,7 @@ const sortByOptions = [
|
||||
"movie_scene_number",
|
||||
"interactive",
|
||||
"interactive_speed",
|
||||
"perceptual_similarity",
|
||||
...MediaSortByOptions,
|
||||
].map(ListFilterOptions.createSortBy);
|
||||
|
||||
@@ -53,6 +58,7 @@ const criterionOptions = [
|
||||
"checksum"
|
||||
),
|
||||
PhashCriterionOption,
|
||||
DuplicatedCriterionOption,
|
||||
RatingCriterionOption,
|
||||
OrganizedCriterionOption,
|
||||
createMandatoryNumberCriterionOption("o_counter"),
|
||||
@@ -65,6 +71,8 @@ const criterionOptions = [
|
||||
PerformerTagsCriterionOption,
|
||||
PerformersCriterionOption,
|
||||
createMandatoryNumberCriterionOption("performer_count"),
|
||||
createMandatoryNumberCriterionOption("performer_age"),
|
||||
PerformerFavoriteCriterionOption,
|
||||
StudiosCriterionOption,
|
||||
MoviesCriterionOption,
|
||||
createStringCriterionOption("url"),
|
||||
|
||||
@@ -28,6 +28,11 @@ export interface INumberValue {
|
||||
value2: number | undefined;
|
||||
}
|
||||
|
||||
export interface IPHashDuplicationValue {
|
||||
duplicated: boolean;
|
||||
distance?: number; // currently not implemented
|
||||
}
|
||||
|
||||
export function criterionIsHierarchicalLabelValue(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any
|
||||
@@ -119,4 +124,7 @@ export type CriterionType =
|
||||
| "director"
|
||||
| "synopsis"
|
||||
| "parent_tag_count"
|
||||
| "child_tag_count";
|
||||
| "child_tag_count"
|
||||
| "performer_favorite"
|
||||
| "performer_age"
|
||||
| "duplicated";
|
||||
|
||||
Reference in New Issue
Block a user