Scene Marker duration filter and sort (#5472)

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
dogwithakeyboard 2024-11-29 05:28:10 +00:00 committed by GitHub
parent e097f2b3f4
commit 6ad0951878
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 155 additions and 2 deletions

View file

@ -193,6 +193,8 @@ input SceneMarkerFilterType {
performers: MultiCriterionInput
"Filter to only include scene markers from these scenes"
scenes: MultiCriterionInput
"Filter by duration (in seconds)"
duration: FloatCriterionInput
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"

View file

@ -11,6 +11,8 @@ type SceneMarkerFilterType struct {
Performers *MultiCriterionInput `json:"performers"`
// Filter to only include scene markers from these scenes
Scenes *MultiCriterionInput `json:"scenes"`
// Filter by duration (in seconds)
Duration *FloatCriterionInput `json:"duration"`
// Filter by created at
CreatedAt *TimestampCriterionInput `json:"created_at"`
// Filter by updated at

View file

@ -367,6 +367,7 @@ var sceneMarkerSortOptions = sortOptions{
"scenes_updated_at",
"seconds",
"updated_at",
"duration",
}
func (qb *SceneMarkerStore) setSceneMarkerSort(query *queryBuilder, findFilter *models.FindFilterType) error {
@ -386,6 +387,9 @@ func (qb *SceneMarkerStore) setSceneMarkerSort(query *queryBuilder, findFilter *
case "title":
query.join(tagTable, "", "scene_markers.primary_tag_id = tags.id")
query.sortAndPagination += " ORDER BY COALESCE(NULLIF(scene_markers.title,''), tags.name) COLLATE NATURAL_CI " + direction
case "duration":
sort = "(scene_markers.end_seconds - scene_markers.seconds)"
query.sortAndPagination += getSort(sort, direction, sceneMarkerTable)
default:
query.sortAndPagination += getSort(sort, direction, sceneMarkerTable)
}

View file

@ -41,6 +41,7 @@ func (qb *sceneMarkerFilterHandler) criterionHandler() criterionHandler {
qb.sceneTagsCriterionHandler(sceneMarkerFilter.SceneTags),
qb.performersCriterionHandler(sceneMarkerFilter.Performers),
qb.scenesCriterionHandler(sceneMarkerFilter.Scenes),
floatCriterionHandler(sceneMarkerFilter.Duration, "COALESCE(scene_markers.end_seconds - scene_markers.seconds, NULL)", nil),
&timestampCriterionHandler{sceneMarkerFilter.CreatedAt, "scene_markers.created_at", nil},
&timestampCriterionHandler{sceneMarkerFilter.UpdatedAt, "scene_markers.updated_at", nil},
&dateCriterionHandler{sceneMarkerFilter.SceneDate, "scenes.date", qb.joinScenes},

View file

@ -391,6 +391,116 @@ func TestMarkerQuerySceneTags(t *testing.T) {
})
}
func markersToIDs(i []*models.SceneMarker) []int {
ret := make([]int, len(i))
for i, v := range i {
ret[i] = v.ID
}
return ret
}
func TestMarkerQueryDuration(t *testing.T) {
type test struct {
name string
markerFilter *models.SceneMarkerFilterType
include []int
exclude []int
}
cases := []test{
{
"is null",
&models.SceneMarkerFilterType{
Duration: &models.FloatCriterionInput{
Modifier: models.CriterionModifierIsNull,
},
},
[]int{markerIdxWithScene},
[]int{markerIdxWithDuration},
},
{
"not null",
&models.SceneMarkerFilterType{
Duration: &models.FloatCriterionInput{
Modifier: models.CriterionModifierNotNull,
},
},
[]int{markerIdxWithDuration},
[]int{markerIdxWithScene},
},
{
"equals",
&models.SceneMarkerFilterType{
Duration: &models.FloatCriterionInput{
Modifier: models.CriterionModifierEquals,
Value: markerIdxWithDuration,
},
},
[]int{markerIdxWithDuration},
[]int{markerIdx2WithDuration, markerIdxWithScene},
},
{
"not equals",
&models.SceneMarkerFilterType{
Duration: &models.FloatCriterionInput{
Modifier: models.CriterionModifierNotEquals,
Value: markerIdx2WithDuration,
},
},
[]int{markerIdxWithDuration},
[]int{markerIdx2WithDuration, markerIdxWithScene},
},
{
"greater than",
&models.SceneMarkerFilterType{
Duration: &models.FloatCriterionInput{
Modifier: models.CriterionModifierGreaterThan,
Value: markerIdxWithDuration,
},
},
[]int{markerIdx2WithDuration},
[]int{markerIdxWithDuration, markerIdxWithScene},
},
{
"less than",
&models.SceneMarkerFilterType{
Duration: &models.FloatCriterionInput{
Modifier: models.CriterionModifierLessThan,
Value: markerIdx2WithDuration,
},
},
[]int{markerIdxWithDuration},
[]int{markerIdx2WithDuration, markerIdxWithScene},
},
}
qb := db.SceneMarker
for _, tt := range cases {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
got, _, err := qb.Query(ctx, tt.markerFilter, nil)
if err != nil {
t.Errorf("SceneMarkerStore.Query() error = %v", err)
return
}
ids := markersToIDs(got)
include := indexesToIDs(markerIDs, tt.include)
exclude := indexesToIDs(markerIDs, tt.exclude)
for _, i := range include {
assert.Contains(ids, i)
}
for _, e := range exclude {
assert.NotContains(ids, e)
}
})
}
}
func queryMarkers(ctx context.Context, t *testing.T, sqb models.SceneMarkerReader, markerFilter *models.SceneMarkerFilterType, findFilter *models.FindFilterType) []*models.SceneMarker {
t.Helper()
result, _, err := sqb.Query(ctx, markerFilter, findFilter)

View file

@ -276,6 +276,8 @@ const (
markerIdxWithScene = iota
markerIdxWithTag
markerIdxWithSceneTag
markerIdxWithDuration
markerIdx2WithDuration
totalMarkers
)
@ -1754,10 +1756,20 @@ func createStudios(ctx context.Context, n int, o int) error {
return nil
}
func getMarkerEndSeconds(index int) *float64 {
if index != markerIdxWithDuration && index != markerIdx2WithDuration {
return nil
}
ret := float64(index)
return &ret
}
func createMarker(ctx context.Context, mqb models.SceneMarkerReaderWriter, markerSpec markerSpec) error {
markerIdx := len(markerIDs)
marker := models.SceneMarker{
SceneID: sceneIDs[markerSpec.sceneIdx],
PrimaryTagID: tagIDs[markerSpec.primaryTagIdx],
EndSeconds: getMarkerEndSeconds(markerIdx),
}
err := mqb.Create(ctx, &marker)

View file

@ -637,7 +637,11 @@ export function createNumberCriterionOption(
}
export class NullNumberCriterionOption extends CriterionOption {
constructor(messageID: string, value: CriterionType) {
constructor(
messageID: string,
value: CriterionType,
makeCriterion?: () => Criterion<CriterionValue>
) {
super({
messageID,
type: value,
@ -653,7 +657,9 @@ export class NullNumberCriterionOption extends CriterionOption {
],
defaultModifier: CriterionModifier.Equals,
inputType: "number",
makeCriterion: () => new NumberCriterion(this),
makeCriterion: makeCriterion
? makeCriterion
: () => new NumberCriterion(this),
});
}
}
@ -780,6 +786,19 @@ export function createDurationCriterionOption(
return new DurationCriterionOption(messageID ?? value, value);
}
export class NullDurationCriterionOption extends NullNumberCriterionOption {
constructor(messageID: string, value: CriterionType) {
super(messageID, value, () => new DurationCriterion(this));
}
}
export function createNullDurationCriterionOption(
value: CriterionType,
messageID?: string
) {
return new NullDurationCriterionOption(messageID ?? value, value);
}
export class DurationCriterion extends Criterion<INumberValue> {
constructor(type: CriterionOption) {
super(type, { value: undefined, value2: undefined });

View file

@ -6,10 +6,12 @@ import { DisplayMode } from "./types";
import {
createDateCriterionOption,
createMandatoryTimestampCriterionOption,
createNullDurationCriterionOption,
} from "./criteria/criterion";
const defaultSortBy = "title";
const sortByOptions = [
"duration",
"title",
"seconds",
"scene_id",
@ -22,6 +24,7 @@ const criterionOptions = [
MarkersScenesCriterionOption,
SceneTagsCriterionOption,
PerformersCriterionOption,
createNullDurationCriterionOption("duration"),
createMandatoryTimestampCriterionOption("created_at"),
createMandatoryTimestampCriterionOption("updated_at"),
createDateCriterionOption("scene_date"),