package transcoder import ( "context" "os" "os/exec" "path/filepath" "strings" "testing" "galahad2lcd/internal/config" ) func TestBuildFilter(t *testing.T) { tests := []struct { rotate int fps float64 want string }{ {0, 30, "scale=480:480:flags=lanczos,format=yuv420p"}, {90, 30, "scale=480:480:flags=lanczos,transpose=1,format=yuv420p"}, {180, 30, "scale=480:480:flags=lanczos,transpose=1,transpose=1,format=yuv420p"}, {270, 30, "scale=480:480:flags=lanczos,transpose=2,format=yuv420p"}, } for _, tt := range tests { if got := buildFilter(tt.rotate, tt.fps); got != tt.want { t.Errorf("buildFilter(%d, %v) = %q, want %q", tt.rotate, tt.fps, got, tt.want) } } } func TestParseFPSRational(t *testing.T) { tests := []struct { in string want float64 err bool }{ {"30000/1001", 30000.0 / 1001, false}, {"25", 25, false}, {"0/0", 0, true}, {"", 0, true}, {"bogus", 0, true}, {"10/2", 5, false}, } for _, tt := range tests { got, err := parseFPSRational(tt.in) if (err != nil) != tt.err { t.Errorf("parseFPSRational(%q) error = %v, wantErr = %v", tt.in, err, tt.err) continue } if !tt.err && got != tt.want { t.Errorf("parseFPSRational(%q) = %v, want %v", tt.in, got, tt.want) } } } func TestClampFPS(t *testing.T) { if got := clampFPS(0); got != defaultFPS { t.Errorf("clampFPS(0) = %v, want %v", got, defaultFPS) } if got := clampFPS(300); got != 120 { t.Errorf("clampFPS(300) = %v, want 120", got) } if got := clampFPS(24); got != 24 { t.Errorf("clampFPS(24) = %v, want 24", got) } } // TestTranscodeIntegration exercises the real ffmpeg path when available. func TestTranscodeIntegration(t *testing.T) { if _, err := exec.LookPath("ffmpeg"); err != nil { t.Skip("ffmpeg not installed") } dir := t.TempDir() input := filepath.Join(dir, "src.gif") cache := filepath.Join(dir, "out.h264") // Build a tiny valid GIF with ffmpeg's testsrc2 source. if err := run(context.Background(), "ffmpeg", "-y", "-v", "error", "-f", "lavfi", "-i", "testsrc2=size=480x480:rate=10:duration=1", input); err != nil { t.Fatalf("generate source gif: %v", err) } tc := NewWithBinaries("ffmpeg", "ffprobe") cfg := config.Default() cfg.Display.Input = input cfg.Display.Rotate = 90 cfg.Stream.CachePath = cache fps, err := tc.Transcode(context.Background(), cfg) if err != nil { t.Fatalf("Transcode error = %v", err) } if fps != 10 { t.Errorf("fps = %v, want 10", fps) } st, err := os.Stat(cache) if err != nil { t.Fatalf("cache file missing: %v", err) } if st.Size() == 0 { t.Error("cache file empty") } if !strings.HasPrefix(readFirstBytes(t, cache), "\x00\x00\x00\x01") { t.Error("cache file does not start with an Annex-B start code") } } func readFirstBytes(t *testing.T, path string) string { t.Helper() data, err := os.ReadFile(path) if err != nil { t.Fatalf("read cache: %v", err) } if len(data) < 4 { return string(data) } return string(data[:4]) }