merge sorting + add URLs

This commit is contained in:
Gykes 2025-11-29 15:10:50 -08:00
parent e0de8e7c7c
commit e057b2899b
8 changed files with 56 additions and 0 deletions

View file

@ -89,6 +89,10 @@ input TitleDuplicationCriterionInput {
duplicated: Boolean duplicated: Boolean
} }
input URLDuplicationCriterionInput {
duplicated: Boolean
}
input StashIDCriterionInput { input StashIDCriterionInput {
""" """
If present, this value is treated as a predicate. If present, this value is treated as a predicate.
@ -262,6 +266,8 @@ input SceneFilterType {
duplicated_stash_id: StashIDDuplicationCriterionInput duplicated_stash_id: StashIDDuplicationCriterionInput
"Filter Scenes that have the same title" "Filter Scenes that have the same title"
duplicated_title: TitleDuplicationCriterionInput duplicated_title: TitleDuplicationCriterionInput
"Filter Scenes that have the same URL"
duplicated_url: URLDuplicationCriterionInput
"Filter by resolution" "Filter by resolution"
resolution: ResolutionCriterionInput resolution: ResolutionCriterionInput
"Filter by orientation" "Filter by orientation"

View file

@ -16,6 +16,10 @@ type TitleDuplicationCriterionInput struct {
Duplicated *bool `json:"duplicated"` Duplicated *bool `json:"duplicated"`
} }
type URLDuplicationCriterionInput struct {
Duplicated *bool `json:"duplicated"`
}
type SceneFilterType struct { type SceneFilterType struct {
OperatorFilter[SceneFilterType] OperatorFilter[SceneFilterType]
ID *IntCriterionInput `json:"id"` ID *IntCriterionInput `json:"id"`
@ -47,6 +51,8 @@ type SceneFilterType struct {
DuplicatedStashID *StashIDDuplicationCriterionInput `json:"duplicated_stash_id"` DuplicatedStashID *StashIDDuplicationCriterionInput `json:"duplicated_stash_id"`
// Filter Scenes that have the same title // Filter Scenes that have the same title
DuplicatedTitle *TitleDuplicationCriterionInput `json:"duplicated_title"` DuplicatedTitle *TitleDuplicationCriterionInput `json:"duplicated_title"`
// Filter Scenes that have the same URL
DuplicatedURL *URLDuplicationCriterionInput `json:"duplicated_url"`
// Filter by resolution // Filter by resolution
Resolution *ResolutionCriterionInput `json:"resolution"` Resolution *ResolutionCriterionInput `json:"resolution"`
// Filter by orientation // Filter by orientation

View file

@ -158,6 +158,7 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
qb.phashDuplicatedCriterionHandler(sceneFilter.Duplicated, qb.addSceneFilesTable), qb.phashDuplicatedCriterionHandler(sceneFilter.Duplicated, qb.addSceneFilesTable),
qb.stashIDDuplicatedCriterionHandler(sceneFilter.DuplicatedStashID), qb.stashIDDuplicatedCriterionHandler(sceneFilter.DuplicatedStashID),
qb.titleDuplicatedCriterionHandler(sceneFilter.DuplicatedTitle), qb.titleDuplicatedCriterionHandler(sceneFilter.DuplicatedTitle),
qb.urlDuplicatedCriterionHandler(sceneFilter.DuplicatedURL),
&dateCriterionHandler{sceneFilter.Date, "scenes.date", nil}, &dateCriterionHandler{sceneFilter.Date, "scenes.date", nil},
&timestampCriterionHandler{sceneFilter.CreatedAt, "scenes.created_at", nil}, &timestampCriterionHandler{sceneFilter.CreatedAt, "scenes.created_at", nil},
&timestampCriterionHandler{sceneFilter.UpdatedAt, "scenes.updated_at", nil}, &timestampCriterionHandler{sceneFilter.UpdatedAt, "scenes.updated_at", nil},
@ -331,6 +332,22 @@ func (qb *sceneFilterHandler) titleDuplicatedCriterionHandler(duplicatedFilter *
} }
} }
func (qb *sceneFilterHandler) urlDuplicatedCriterionHandler(duplicatedFilter *models.URLDuplicationCriterionInput) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) {
if duplicatedFilter != nil && duplicatedFilter.Duplicated != nil {
var v string
if *duplicatedFilter.Duplicated {
v = ">"
} else {
v = "="
}
// Find URLs that appear on more than one scene
f.addInnerJoin("(SELECT scene_id FROM scene_urls INNER JOIN (SELECT url FROM scene_urls GROUP BY url HAVING COUNT(DISTINCT scene_id) "+v+" 1) dupes ON scene_urls.url = dupes.url)", "scurl", "scenes.id = scurl.scene_id")
}
}
}
func (qb *sceneFilterHandler) codecCriterionHandler(codec *models.StringCriterionInput, codecColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc { func (qb *sceneFilterHandler) codecCriterionHandler(codec *models.StringCriterionInput, codecColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) { return func(ctx context.Context, f *filterBuilder) {
if codec != nil { if codec != nil {

View file

@ -6,6 +6,7 @@ import { Option, SelectedList } from "./SidebarListFilter";
import { DuplicatedCriterionOption } from "src/models/list-filter/criteria/phash"; import { DuplicatedCriterionOption } from "src/models/list-filter/criteria/phash";
import { DuplicatedStashIDCriterionOption } from "src/models/list-filter/criteria/stash-ids"; import { DuplicatedStashIDCriterionOption } from "src/models/list-filter/criteria/stash-ids";
import { DuplicatedTitleCriterionOption } from "src/models/list-filter/criteria/title"; import { DuplicatedTitleCriterionOption } from "src/models/list-filter/criteria/title";
import { DuplicatedURLCriterionOption } from "src/models/list-filter/criteria/url";
import { SidebarSection } from "src/components/Shared/Sidebar"; import { SidebarSection } from "src/components/Shared/Sidebar";
import { Icon } from "src/components/Shared/Icon"; import { Icon } from "src/components/Shared/Icon";
import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { faPlus } from "@fortawesome/free-solid-svg-icons";
@ -16,6 +17,7 @@ const DUPLICATE_TYPES = {
phash: DuplicatedCriterionOption, phash: DuplicatedCriterionOption,
stash_id: DuplicatedStashIDCriterionOption, stash_id: DuplicatedStashIDCriterionOption,
title: DuplicatedTitleCriterionOption, title: DuplicatedTitleCriterionOption,
url: DuplicatedURLCriterionOption,
} as const; } as const;
type DuplicateTypeId = keyof typeof DUPLICATE_TYPES; type DuplicateTypeId = keyof typeof DUPLICATE_TYPES;
@ -32,6 +34,7 @@ const DUPLICATE_TYPE_MESSAGE_IDS: Record<DuplicateTypeId, string> = {
phash: "media_info.phash", phash: "media_info.phash",
stash_id: "stash_id", stash_id: "stash_id",
title: "title", title: "title",
url: "url",
}; };
export const SidebarDuplicateFilter: React.FC<ISidebarDuplicateFilterProps> = ({ export const SidebarDuplicateFilter: React.FC<ISidebarDuplicateFilterProps> = ({

View file

@ -1063,6 +1063,7 @@
"duplicated_phash": "Duplicated (pHash)", "duplicated_phash": "Duplicated (pHash)",
"duplicated_stash_id": "Duplicated (Stash ID)", "duplicated_stash_id": "Duplicated (Stash ID)",
"duplicated_title": "Duplicated (Title)", "duplicated_title": "Duplicated (Title)",
"duplicated_url": "Duplicated (URL)",
"duration": "Duration", "duration": "Duration",
"effect_filters": { "effect_filters": {
"aspect": "Aspect", "aspect": "Aspect",

View file

@ -0,0 +1,20 @@
import { UrlDuplicationCriterionInput } from "src/core/generated-graphql";
import { BooleanCriterionOption, StringCriterion } from "./criterion";
export const DuplicatedURLCriterionOption = new BooleanCriterionOption(
"duplicated_url",
"duplicated_url",
() => new DuplicatedURLCriterion()
);
export class DuplicatedURLCriterion extends StringCriterion {
constructor() {
super(DuplicatedURLCriterionOption);
}
public toCriterionInput(): UrlDuplicationCriterionInput {
return {
duplicated: this.value === "true",
};
}
}

View file

@ -36,6 +36,7 @@ import {
StashIDCriterionOption, StashIDCriterionOption,
} from "./criteria/stash-ids"; } from "./criteria/stash-ids";
import { DuplicatedTitleCriterionOption } from "./criteria/title"; import { DuplicatedTitleCriterionOption } from "./criteria/title";
import { DuplicatedURLCriterionOption } from "./criteria/url";
import { RatingCriterionOption } from "./criteria/rating"; import { RatingCriterionOption } from "./criteria/rating";
import { PathCriterionOption } from "./criteria/path"; import { PathCriterionOption } from "./criteria/path";
import { OrientationCriterionOption } from "./criteria/orientation"; import { OrientationCriterionOption } from "./criteria/orientation";
@ -106,6 +107,7 @@ const criterionOptions = [
DuplicatedCriterionOption, DuplicatedCriterionOption,
DuplicatedStashIDCriterionOption, DuplicatedStashIDCriterionOption,
DuplicatedTitleCriterionOption, DuplicatedTitleCriterionOption,
DuplicatedURLCriterionOption,
OrganizedCriterionOption, OrganizedCriterionOption,
RatingCriterionOption, RatingCriterionOption,
createMandatoryNumberCriterionOption("o_counter", "o_count", { createMandatoryNumberCriterionOption("o_counter", "o_count", {

View file

@ -199,6 +199,7 @@ export type CriterionType =
| "duplicated" | "duplicated"
| "duplicated_stash_id" | "duplicated_stash_id"
| "duplicated_title" | "duplicated_title"
| "duplicated_url"
| "ignore_auto_tag" | "ignore_auto_tag"
| "file_count" | "file_count"
| "stash_id_endpoint" | "stash_id_endpoint"