package engine import ( "strings" "testing" ) func sliceContains(args []string, want string) bool { for _, a := range args { if a == want { return true } } return false } func sliceContainsPair(args []string, key, val string) bool { for i := 0; i < len(args)-1; i++ { if args[i] == key && args[i+1] == val { return true } } return false } func TestBuildFFmpegArgsPassthroughCopy(t *testing.T) { args := buildFFmpegArgs("/tmp/movie.mp4", TranscodeOpts{ Action: ActionPassthrough, HWAccel: HWAccelNone, FFmpegPath: "ffmpeg", }) if !sliceContainsPair(args, "-c:v", "copy") { t.Errorf("passthrough should keep -c:v copy. args=%v", args) } if !sliceContainsPair(args, "-c:a", "copy") { t.Error("passthrough should keep -c:a copy") } if !sliceContainsPair(args, "-f", "mp4") { t.Error("output container must be mp4") } movflags := "" for i := 0; i < len(args)-1; i++ { if args[i] == "-movflags" { movflags = args[i+1] } } if !strings.Contains(movflags, "frag_keyframe") { t.Errorf("movflags must include frag_keyframe, got %q", movflags) } } func TestBuildFFmpegArgsRemuxAudio(t *testing.T) { args := buildFFmpegArgs("/tmp/movie.mkv", TranscodeOpts{ Action: ActionRemuxAudio, AudioBitrate: "256k", FFmpegPath: "ffmpeg", }) if !sliceContainsPair(args, "-c:v", "copy") { t.Error("remux-audio keeps video copy") } if !sliceContainsPair(args, "-c:a", "aac") { t.Error("remux-audio must transcode audio to aac") } if !sliceContainsPair(args, "-b:a", "256k") { t.Error("audio bitrate override not honored") } } func TestBuildFFmpegArgsTranscodeVideoSoftware(t *testing.T) { args := buildFFmpegArgs("/tmp/movie.mkv", TranscodeOpts{ Action: ActionTranscodeVideo, HWAccel: HWAccelNone, Preset: "fast", VideoBitrate: "6M", FFmpegPath: "ffmpeg", }) if !sliceContainsPair(args, "-c:v", "libx264") { t.Error("software fallback must use libx264") } if !sliceContainsPair(args, "-preset", "fast") { t.Error("custom preset not honored") } if !sliceContainsPair(args, "-b:v", "6M") { t.Error("video bitrate not honored") } } func TestBuildFFmpegArgsTranscodeVideoNVENC(t *testing.T) { args := buildFFmpegArgs("/tmp/movie.mkv", TranscodeOpts{ Action: ActionTranscodeVideo, HWAccel: HWAccelNVENC, FFmpegPath: "ffmpeg", }) if !sliceContainsPair(args, "-hwaccel", "cuda") { t.Error("NVENC must request -hwaccel cuda") } if !sliceContainsPair(args, "-c:v", "h264_nvenc") { t.Error("NVENC must use h264_nvenc encoder") } if sliceContains(args, "-preset") { // HW encoders ignore software preset; we should NOT pass it. t.Error("HW encoder path should not include -preset") } } func TestBuildFFmpegArgsAddsStartSeek(t *testing.T) { args := buildFFmpegArgs("/tmp/movie.mp4", TranscodeOpts{ Action: ActionPassthrough, StartSeconds: 90.5, FFmpegPath: "ffmpeg", }) idxSs, idxIn := -1, -1 for i, a := range args { if a == "-ss" { idxSs = i } if a == "-i" { idxIn = i } } if idxSs < 0 { t.Fatal("missing -ss flag") } if idxIn < 0 { t.Fatal("missing -i flag") } if idxSs >= idxIn { t.Errorf("expected -ss BEFORE -i for fast seek; got -ss@%d -i@%d", idxSs, idxIn) } if args[idxSs+1] != "90.500" { t.Errorf("expected seek 90.500s, got %q", args[idxSs+1]) } } func TestBuildFFmpegArgsDownscale(t *testing.T) { args := buildFFmpegArgs("/tmp/movie.mkv", TranscodeOpts{ Action: ActionTranscodeVideo, HWAccel: HWAccelNone, MaxHeight: 720, FFmpegPath: "ffmpeg", }) hasVF := false for i := 0; i < len(args)-1; i++ { if args[i] == "-vf" && strings.Contains(args[i+1], "720") { hasVF = true } } if !hasVF { t.Errorf("expected -vf scale containing 720; args=%v", args) } }