CLI: Improve "photoprism video" subcommands

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2025-12-21 18:47:19 +01:00
parent 4b8c41b96d
commit 898f6bc69b
9 changed files with 61 additions and 19 deletions

View file

@ -56,7 +56,6 @@ var PhotoPrism = []*cli.Command{
StatusCommand,
IndexCommand,
FindCommand,
VideoCommands,
ImportCommand,
CopyCommand,
DownloadCommand,
@ -69,6 +68,7 @@ var PhotoPrism = []*cli.Command{
MomentsCommand,
ConvertCommand,
ThumbsCommand,
VideosCommands,
MigrateCommand,
MigrationsCommands,
BackupCommand,

View file

@ -34,7 +34,7 @@ var VideoInfoCommand = &cli.Command{
func videoInfoAction(ctx *cli.Context) error {
return CallWithDependencies(ctx, func(conf *config.Config) error {
filter := videoNormalizeFilter(ctx.Args().Slice())
results, err := videoSearchResults(filter, ctx.Uint(videoCountFlag.Name), ctx.Int(OffsetFlag.Name), false)
results, err := videoSearchResults(filter, ctx.Int(videoCountFlag.Name), ctx.Int(OffsetFlag.Name), false)
if err != nil {
return err
}

View file

@ -38,7 +38,7 @@ func videoListAction(ctx *cli.Context) error {
filter := videoNormalizeFilter(ctx.Args().Slice())
includeSidecar := ctx.Bool(videoIncludeSidecarFlag.Name)
results, err := videoSearchResults(filter, ctx.Uint(videoCountFlag.Name), ctx.Int(OffsetFlag.Name), includeSidecar)
results, err := videoSearchResults(filter, ctx.Int(videoCountFlag.Name), ctx.Int(OffsetFlag.Name), includeSidecar)
if err != nil {
return err
}

View file

@ -45,7 +45,7 @@ func videoRemuxAction(ctx *cli.Context) error {
}
filter := videoNormalizeFilter(ctx.Args().Slice())
results, err := videoSearchResults(filter, ctx.Uint(videoCountFlag.Name), ctx.Int(OffsetFlag.Name), false)
results, err := videoSearchResults(filter, ctx.Int(videoCountFlag.Name), ctx.Int(OffsetFlag.Name), false)
if err != nil {
return err
}

View file

@ -7,12 +7,12 @@ import (
)
// videoSearchResults runs a video-only search and applies offset/count after sidecar filtering.
func videoSearchResults(query string, count uint, offset int, includeSidecar bool) ([]search.Photo, error) {
func videoSearchResults(query string, count int, offset int, includeSidecar bool) ([]search.Photo, error) {
if offset < 0 {
offset = 0
}
if count == 0 {
if count <= 0 {
return []search.Photo{}, nil
}
@ -25,20 +25,20 @@ func videoSearchResults(query string, count uint, offset int, includeSidecar boo
}
if includeSidecar {
frm.Count = int(count)
frm.Count = count
frm.Offset = offset
results, _, err := search.Photos(frm)
return results, err
}
target := int(count) + offset
target := count + offset
if target < 0 {
target = 0
}
collected := make([]search.Photo, 0, target)
searchOffset := 0
batchSize := int(count)
batchSize := count
if batchSize < 200 {
batchSize = 200
}
@ -73,7 +73,7 @@ func videoSearchResults(query string, count uint, offset int, includeSidecar boo
return []search.Photo{}, nil
}
end := offset + int(count)
end := offset + count
if end > len(collected) {
end = len(collected)
}

View file

@ -39,7 +39,7 @@ func videoTranscodeAction(ctx *cli.Context) error {
}
filter := videoNormalizeFilter(ctx.Args().Slice())
results, err := videoSearchResults(filter, ctx.Uint(videoCountFlag.Name), ctx.Int(OffsetFlag.Name), false)
results, err := videoSearchResults(filter, ctx.Int(videoCountFlag.Name), ctx.Int(OffsetFlag.Name), false)
if err != nil {
return err
}

View file

@ -54,7 +54,7 @@ func videoTrimAction(ctx *cli.Context) error {
}
filter := videoNormalizeFilter(filterArgs)
results, err := videoSearchResults(filter, ctx.Uint(videoCountFlag.Name), ctx.Int(OffsetFlag.Name), false)
results, err := videoSearchResults(filter, ctx.Int(videoCountFlag.Name), ctx.Int(OffsetFlag.Name), false)
if err != nil {
return err
}
@ -216,7 +216,15 @@ func videoTrimFile(conf *config.Config, convert *photoprism.Convert, plan videoT
}
destDir := filepath.Dir(plan.DestPath)
tempPath, err := videoTempPath(destDir, ".trim-*.tmp")
ext := filepath.Ext(plan.DestPath)
if ext == "" {
ext = filepath.Ext(plan.SrcPath)
}
if ext == "" {
ext = ".tmp"
}
tempPath, err := videoTempPath(destDir, ".trim-*"+ext)
if err != nil {
return err
}
@ -224,6 +232,8 @@ func videoTrimFile(conf *config.Config, convert *photoprism.Convert, plan videoT
cmd := videoTrimCmd(conf.FFmpegBin(), plan.SrcPath, tempPath, start, remaining)
cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", conf.CmdCachePath()))
log.Debugf("ffmpeg: %s", clean.Log(cmd.String()))
var stderr bytes.Buffer
cmd.Stderr = &stderr
@ -308,9 +318,24 @@ func videoTrimCmd(ffmpegBin, srcName, destName string, start, duration time.Dura
"-ignore_unknown",
"-codec", "copy",
"-avoid_negative_ts", "make_zero",
destName,
)
if videoTrimFastStart(destName) {
args = append(args, "-movflags", "+faststart")
}
args = append(args, destName)
// #nosec G204 -- arguments are built from validated inputs and config.
return exec.Command(ffmpegBin, args...)
}
// videoTrimFastStart reports whether the trim output should enable faststart for MP4/MOV containers.
func videoTrimFastStart(destName string) bool {
switch strings.ToLower(filepath.Ext(destName)) {
case fs.ExtMp4, fs.ExtMov, fs.ExtQT, ".m4v":
return true
default:
return false
}
}

View file

@ -0,0 +1,17 @@
package commands
import (
"testing"
"github.com/stretchr/testify/assert"
)
// TestVideoTrimFastStart verifies which extensions get the faststart flag.
func TestVideoTrimFastStart(t *testing.T) {
assert.True(t, videoTrimFastStart("clip.mp4"))
assert.True(t, videoTrimFastStart("clip.MOV"))
assert.True(t, videoTrimFastStart("clip.m4v"))
assert.True(t, videoTrimFastStart("clip.qt"))
assert.False(t, videoTrimFastStart("clip.mkv"))
assert.False(t, videoTrimFastStart(""))
}

View file

@ -2,10 +2,10 @@ package commands
import "github.com/urfave/cli/v2"
// VideoCommands configures the CLI subcommands for working with indexed videos.
var VideoCommands = &cli.Command{
Name: "video",
Usage: "Video subcommands",
// VideosCommands configures the CLI subcommands for working with indexed videos.
var VideosCommands = &cli.Command{
Name: "videos",
Usage: "Video troubleshooting and editing subcommands",
Subcommands: []*cli.Command{
VideoListCommand,
VideoTrimCommand,
@ -16,7 +16,7 @@ var VideoCommands = &cli.Command{
}
// videoCountFlag limits the number of results returned by video commands.
var videoCountFlag = &cli.UintFlag{
var videoCountFlag = &cli.IntFlag{
Name: "count",
Aliases: []string{"n"},
Usage: "maximum `NUMBER` of results",