feat(video): add video upload with remux/transcode playback
Add video as a media kind in photos: streaming upload with per-kind limits, ffprobe metadata, poster frame reusing the image variant pipeline, and a playback object (remux when web-compatible, else h264/yuv420p/aac transcode). Video jobs run on a separate pool with a long lease. Frontend gains a video viewer, grid badge/duration and video upload.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
package video
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func argsContain(args []string, want ...string) bool {
|
||||
for _, w := range want {
|
||||
found := false
|
||||
for _, a := range args {
|
||||
if a == w {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestRemuxArgs(t *testing.T) {
|
||||
args := RemuxArgs("in.mkv", "out.mp4")
|
||||
if !argsContain(args, "-c", "copy", "-movflags", "+faststart", "-map", "0:a:0?") {
|
||||
t.Fatalf("remux args = %v", args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranscodeArgs(t *testing.T) {
|
||||
args := TranscodeArgs("in.mov", "out.mp4", MaxLongSide)
|
||||
if !argsContain(args, "libx264", "yuv420p", "aac", "-movflags", "+faststart") {
|
||||
t.Fatalf("transcode args = %v", args)
|
||||
}
|
||||
var scale string
|
||||
for i, a := range args {
|
||||
if a == "-vf" && i+1 < len(args) {
|
||||
scale = args[i+1]
|
||||
}
|
||||
}
|
||||
if !strings.Contains(scale, "min(1920,iw)") || !strings.Contains(scale, "force_divisible_by=2") {
|
||||
t.Fatalf("scale filter = %q", scale)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPosterArgs(t *testing.T) {
|
||||
args := PosterArgs("in.mp4", "poster.jpg", 1)
|
||||
if !argsContain(args, "-ss", "1", "-frames:v", "1", "-f", "image2") {
|
||||
t.Fatalf("poster args = %v", args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
ok := filepath.Join(dir, "ok")
|
||||
if err := os.WriteFile(ok, []byte("#!/bin/sh\nexit 0\n"), 0o755); err != nil {
|
||||
t.Fatalf("write ok: %v", err)
|
||||
}
|
||||
if err := Run(context.Background(), ok, "-i", "x"); err != nil {
|
||||
t.Fatalf("run ok: %v", err)
|
||||
}
|
||||
bad := filepath.Join(dir, "bad")
|
||||
if err := os.WriteFile(bad, []byte("#!/bin/sh\necho nope >&2\nexit 3\n"), 0o755); err != nil {
|
||||
t.Fatalf("write bad: %v", err)
|
||||
}
|
||||
if err := Run(context.Background(), bad); err == nil || !strings.Contains(err.Error(), "nope") {
|
||||
t.Fatalf("run bad err = %v, want stderr captured", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user