Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support for image orientation filter #4404

Merged
merged 3 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions graphql/schema/types/filters.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ input ResolutionCriterionInput {
modifier: CriterionModifier!
}

enum OrientationEnum {
"Landscape"
LANDSCAPE
"Portrait"
PORTRAIT
"Square"
SQUARE
}

input OrientationCriterionInput {
value: [OrientationEnum!]!
}

input PHashDuplicationCriterionInput {
duplicated: Boolean
"Currently unimplemented"
Expand Down Expand Up @@ -212,6 +225,8 @@ input SceneFilterType {
duplicated: PHashDuplicationCriterionInput
"Filter by resolution"
resolution: ResolutionCriterionInput
"Filter by orientation"
orientation: OrientationCriterionInput
"Filter by frame rate"
framerate: IntCriterionInput
"Filter by video codec"
Expand Down Expand Up @@ -465,6 +480,8 @@ input ImageFilterType {
o_counter: IntCriterionInput
"Filter by resolution"
resolution: ResolutionCriterionInput
"Filter by orientation"
orientation: OrientationCriterionInput
"Filter to only include images missing this property"
is_missing: String
"Filter to only include images with this studio"
Expand Down
4 changes: 4 additions & 0 deletions pkg/models/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,7 @@ type PhashDistanceCriterionInput struct {
Modifier CriterionModifier `json:"modifier"`
Distance *int `json:"distance"`
}

type OrientationCriterionInput struct {
Value []OrientationEnum `json:"value"`
}
2 changes: 2 additions & 0 deletions pkg/models/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type ImageFilterType struct {
OCounter *IntCriterionInput `json:"o_counter"`
// Filter by resolution
Resolution *ResolutionCriterionInput `json:"resolution"`
// Filter by landscape/portrait
Orientation *OrientationCriterionInput `json:"orientation"`
// Filter to only include images missing this property
IsMissing *string `json:"is_missing"`
// Filter to only include images with this studio
Expand Down
17 changes: 17 additions & 0 deletions pkg/models/orientation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package models

type OrientationEnum string

const (
OrientationLandscape OrientationEnum = "LANDSCAPE"
OrientationPortrait OrientationEnum = "PORTRAIT"
OrientationSquare OrientationEnum = "SQUARE"
)

func (e OrientationEnum) IsValid() bool {
switch e {
case OrientationLandscape, OrientationPortrait, OrientationSquare:
return true
}
return false
}
2 changes: 2 additions & 0 deletions pkg/models/scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type SceneFilterType struct {
Duplicated *PHashDuplicationCriterionInput `json:"duplicated"`
// Filter by resolution
Resolution *ResolutionCriterionInput `json:"resolution"`
// Filter by orientation
Orientation *OrientationCriterionInput `json:"orientation"`
// Filter by framerate
Framerate *IntCriterionInput `json:"framerate"`
// Filter by video codec
Expand Down
43 changes: 43 additions & 0 deletions pkg/sqlite/criterion_handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package sqlite

import (
"context"
"fmt"

"github.com/stashapp/stash/pkg/models"
)

// shared criterion handlers go here

func orientationCriterionHandler(orientation *models.OrientationCriterionInput, heightColumn string, widthColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) {
if orientation != nil {
if addJoinFn != nil {
addJoinFn(f)
}

var clauses []sqlClause

for _, v := range orientation.Value {
// width mod height
mod := ""
switch v {
case models.OrientationPortrait:
mod = "<"
case models.OrientationLandscape:
mod = ">"
case models.OrientationSquare:
mod = "="
}

if mod != "" {
clauses = append(clauses, makeClause(fmt.Sprintf("%s %s %s", widthColumn, mod, heightColumn)))
}
}

if len(clauses) > 0 {
f.whereClauses = append(f.whereClauses, orClauses(clauses...))
}
}
}
}
1 change: 1 addition & 0 deletions pkg/sqlite/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,7 @@ func (qb *ImageStore) makeFilter(ctx context.Context, imageFilter *models.ImageF
query.handleCriterion(ctx, imageURLsCriterionHandler(imageFilter.URL))

query.handleCriterion(ctx, resolutionCriterionHandler(imageFilter.Resolution, "image_files.height", "image_files.width", qb.addImageFilesTable))
query.handleCriterion(ctx, orientationCriterionHandler(imageFilter.Orientation, "image_files.height", "image_files.width", qb.addImageFilesTable))
query.handleCriterion(ctx, imageIsMissingCriterionHandler(qb, imageFilter.IsMissing))

query.handleCriterion(ctx, imageTagsCriterionHandler(qb, imageFilter.Tags))
Expand Down
1 change: 1 addition & 0 deletions pkg/sqlite/scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,7 @@ func (qb *SceneStore) makeFilter(ctx context.Context, sceneFilter *models.SceneF

query.handleCriterion(ctx, floatIntCriterionHandler(sceneFilter.Duration, "video_files.duration", qb.addVideoFilesTable))
query.handleCriterion(ctx, resolutionCriterionHandler(sceneFilter.Resolution, "video_files.height", "video_files.width", qb.addVideoFilesTable))
query.handleCriterion(ctx, orientationCriterionHandler(sceneFilter.Orientation, "video_files.height", "video_files.width", qb.addVideoFilesTable))
query.handleCriterion(ctx, floatIntCriterionHandler(sceneFilter.Framerate, "ROUND(video_files.frame_rate)", qb.addVideoFilesTable))
query.handleCriterion(ctx, codecCriterionHandler(sceneFilter.VideoCodec, "video_files.video_codec", qb.addVideoFilesTable))
query.handleCriterion(ctx, codecCriterionHandler(sceneFilter.AudioCodec, "video_files.audio_codec", qb.addVideoFilesTable))
Expand Down
1 change: 1 addition & 0 deletions ui/v2.5/src/locales/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,7 @@
"none": "None",
"o_counter": "O-Counter",
"operations": "Operations",
"orientation": "Orientation",
"organized": "Organised",
"package_manager": {
"add_source": "Add Source",
Expand Down
32 changes: 32 additions & 0 deletions ui/v2.5/src/models/list-filter/criteria/orientation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { orientationStrings, stringToOrientation } from "src/utils/orientation";
import { CriterionType } from "../types";
import { CriterionOption, MultiStringCriterion } from "./criterion";
import {
OrientationCriterionInput,
OrientationEnum,
} from "src/core/generated-graphql";

export class OrientationCriterion extends MultiStringCriterion {
protected toCriterionInput(): OrientationCriterionInput {
return {
value: this.value
.map((v) => stringToOrientation(v))
.filter((v) => v) as OrientationEnum[],
};
}
}

class BaseOrientationCriterionOption extends CriterionOption {
constructor(value: CriterionType) {
super({
messageID: value,
type: value,
options: orientationStrings,
makeCriterion: () => new OrientationCriterion(this),
});
}
}

export const OrientationCriterionOption = new BaseOrientationCriterionOption(
"orientation"
);
2 changes: 2 additions & 0 deletions ui/v2.5/src/models/list-filter/images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { PathCriterionOption } from "./criteria/path";
import { PerformersCriterionOption } from "./criteria/performers";
import { RatingCriterionOption } from "./criteria/rating";
import { ResolutionCriterionOption } from "./criteria/resolution";
import { OrientationCriterionOption } from "./criteria/orientation";
import { StudiosCriterionOption } from "./criteria/studios";
import {
PerformerTagsCriterionOption,
Expand Down Expand Up @@ -41,6 +42,7 @@ const criterionOptions = [
OrganizedCriterionOption,
createMandatoryNumberCriterionOption("o_counter"),
ResolutionCriterionOption,
OrientationCriterionOption,
ImageIsMissingCriterionOption,
TagsCriterionOption,
RatingCriterionOption,
Expand Down
2 changes: 2 additions & 0 deletions ui/v2.5/src/models/list-filter/scenes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { CaptionsCriterionOption } from "./criteria/captions";
import { StashIDCriterionOption } from "./criteria/stash-ids";
import { RatingCriterionOption } from "./criteria/rating";
import { PathCriterionOption } from "./criteria/path";
import { OrientationCriterionOption } from "./criteria/orientation";

const defaultSortBy = "date";
const sortByOptions = [
Expand Down Expand Up @@ -72,6 +73,7 @@ const criterionOptions = [
RatingCriterionOption,
createMandatoryNumberCriterionOption("o_counter"),
ResolutionCriterionOption,
OrientationCriterionOption,
createMandatoryNumberCriterionOption("framerate"),
createStringCriterionOption("video_codec"),
createStringCriterionOption("audio_codec"),
Expand Down
1 change: 1 addition & 0 deletions ui/v2.5/src/models/list-filter/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export type CriterionType =
| "details"
| "title"
| "oshash"
| "orientation"
| "checksum"
| "phash_distance"
| "director"
Expand Down
32 changes: 32 additions & 0 deletions ui/v2.5/src/utils/orientation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { OrientationEnum } from "src/core/generated-graphql";

const stringOrientationMap = new Map<string, OrientationEnum>([
["Landscape", OrientationEnum.Landscape],
["Portrait", OrientationEnum.Portrait],
["Square", OrientationEnum.Square],
]);

export const stringToOrientation = (
value?: string | null,
caseInsensitive?: boolean
) => {
if (!value) {
return undefined;
}

const ret = stringOrientationMap.get(value);
if (ret || !caseInsensitive) {
return ret;
}

const asUpper = value.toUpperCase();
const foundEntry = Array.from(stringOrientationMap.entries()).find((e) => {
return e[0].toUpperCase() === asUpper;
});

if (foundEntry) {
return foundEntry[1];
}
};

export const orientationStrings = Array.from(stringOrientationMap.keys());