package encoding import ( "os" "os/exec" "path/filepath" "strings" "testing" ) func TestSoftwareEncoder_BuildCommand_H264_EXR(t *testing.T) { encoder := &SoftwareEncoder{codec: "libx264"} config := &EncodeConfig{ InputPattern: "frame_%04d.exr", OutputPath: "output.mp4", StartFrame: 1, FrameRate: 24.0, WorkDir: "/tmp", UseAlpha: false, TwoPass: true, SourceFormat: "exr", } cmd := encoder.BuildCommand(config) if cmd == nil { t.Fatal("BuildCommand returned nil") } if !strings.Contains(cmd.Path, "ffmpeg") { t.Errorf("Expected command path to contain 'ffmpeg', got '%s'", cmd.Path) } if cmd.Dir != "/tmp" { t.Errorf("Expected work dir '/tmp', got '%s'", cmd.Dir) } args := cmd.Args[1:] // Skip "ffmpeg" argsStr := strings.Join(args, " ") // Check required arguments checks := []struct { name string expected string }{ {"-y flag", "-y"}, {"image2 format", "-f image2"}, {"start number", "-start_number 1"}, {"framerate", "-framerate 24.00"}, {"input pattern", "-i frame_%04d.exr"}, {"codec", "-c:v libx264"}, {"pixel format", "-pix_fmt yuv420p"}, // EXR now treated as SDR (like PNG) {"frame rate", "-r 24.00"}, {"color primaries", "-color_primaries bt709"}, // EXR now uses bt709 (SDR) {"color trc", "-color_trc bt709"}, // EXR now uses bt709 (SDR) {"colorspace", "-colorspace bt709"}, {"color range", "-color_range tv"}, {"video filter", "-vf"}, {"preset", "-preset veryslow"}, {"crf", "-crf 15"}, {"profile", "-profile:v high"}, // EXR now uses high profile (SDR) {"pass 2", "-pass 2"}, {"output path", "output.mp4"}, } for _, check := range checks { if !strings.Contains(argsStr, check.expected) { t.Errorf("Missing expected argument: %s", check.expected) } } // Verify filter is present for EXR (linear RGB to sRGB conversion, like Krita does) if !strings.Contains(argsStr, "format=gbrpf32le") { t.Error("Expected format conversion filter for EXR source, but not found") } if !strings.Contains(argsStr, "zscale=transferin=8:transfer=13") { t.Error("Expected linear to sRGB conversion for EXR source, but not found") } } func TestSoftwareEncoder_BuildCommand_H264_PNG(t *testing.T) { encoder := &SoftwareEncoder{codec: "libx264"} config := &EncodeConfig{ InputPattern: "frame_%04d.png", OutputPath: "output.mp4", StartFrame: 1, FrameRate: 24.0, WorkDir: "/tmp", UseAlpha: false, TwoPass: true, SourceFormat: "png", } cmd := encoder.BuildCommand(config) args := cmd.Args[1:] argsStr := strings.Join(args, " ") // PNG should NOT have video filter if strings.Contains(argsStr, "-vf") { t.Error("PNG source should not have video filter, but -vf was found") } // Should still have all other required args if !strings.Contains(argsStr, "-c:v libx264") { t.Error("Missing codec argument") } } func TestSoftwareEncoder_BuildCommand_AV1_WithAlpha(t *testing.T) { encoder := &SoftwareEncoder{codec: "libaom-av1"} config := &EncodeConfig{ InputPattern: "frame_%04d.exr", OutputPath: "output.mp4", StartFrame: 100, FrameRate: 30.0, WorkDir: "/tmp", UseAlpha: true, TwoPass: true, SourceFormat: "exr", } cmd := encoder.BuildCommand(config) args := cmd.Args[1:] argsStr := strings.Join(args, " ") // Check alpha-specific settings if !strings.Contains(argsStr, "-pix_fmt yuva420p") { t.Error("Expected yuva420p pixel format for alpha, but not found") } // Check AV1-specific arguments av1Checks := []string{ "-c:v libaom-av1", "-crf 30", "-b:v 0", "-tiles 2x2", "-g 240", } for _, check := range av1Checks { if !strings.Contains(argsStr, check) { t.Errorf("Missing AV1 argument: %s", check) } } // Check tonemap filter includes alpha format if !strings.Contains(argsStr, "format=yuva420p") { t.Error("Expected tonemap filter to output yuva420p for alpha, but not found") } } func TestSoftwareEncoder_BuildCommand_VP9(t *testing.T) { encoder := &SoftwareEncoder{codec: "libvpx-vp9"} config := &EncodeConfig{ InputPattern: "frame_%04d.exr", OutputPath: "output.webm", StartFrame: 1, FrameRate: 24.0, WorkDir: "/tmp", UseAlpha: true, TwoPass: true, SourceFormat: "exr", } cmd := encoder.BuildCommand(config) args := cmd.Args[1:] argsStr := strings.Join(args, " ") // Check VP9-specific arguments vp9Checks := []string{ "-c:v libvpx-vp9", "-crf 30", "-b:v 0", "-row-mt 1", "-g 240", } for _, check := range vp9Checks { if !strings.Contains(argsStr, check) { t.Errorf("Missing VP9 argument: %s", check) } } } func TestSoftwareEncoder_BuildPass1Command(t *testing.T) { encoder := &SoftwareEncoder{codec: "libx264"} config := &EncodeConfig{ InputPattern: "frame_%04d.exr", OutputPath: "output.mp4", StartFrame: 1, FrameRate: 24.0, WorkDir: "/tmp", UseAlpha: false, TwoPass: true, SourceFormat: "exr", } cmd := encoder.BuildPass1Command(config) args := cmd.Args[1:] argsStr := strings.Join(args, " ") // Pass 1 should have -pass 1 and output to null if !strings.Contains(argsStr, "-pass 1") { t.Error("Pass 1 command should include '-pass 1'") } if !strings.Contains(argsStr, "-f null") { t.Error("Pass 1 command should include '-f null'") } if !strings.Contains(argsStr, "/dev/null") { t.Error("Pass 1 command should output to /dev/null") } // Should NOT have output path if strings.Contains(argsStr, "output.mp4") { t.Error("Pass 1 command should not include output path") } } func TestSoftwareEncoder_BuildPass1Command_AV1(t *testing.T) { encoder := &SoftwareEncoder{codec: "libaom-av1"} config := &EncodeConfig{ InputPattern: "frame_%04d.exr", OutputPath: "output.mp4", StartFrame: 1, FrameRate: 24.0, WorkDir: "/tmp", UseAlpha: false, TwoPass: true, SourceFormat: "exr", } cmd := encoder.BuildPass1Command(config) args := cmd.Args[1:] argsStr := strings.Join(args, " ") // Pass 1 should have -pass 1 and output to null if !strings.Contains(argsStr, "-pass 1") { t.Error("Pass 1 command should include '-pass 1'") } if !strings.Contains(argsStr, "-f null") { t.Error("Pass 1 command should include '-f null'") } if !strings.Contains(argsStr, "/dev/null") { t.Error("Pass 1 command should output to /dev/null") } // Check AV1-specific arguments in pass 1 av1Checks := []string{ "-c:v libaom-av1", "-crf 30", "-b:v 0", "-tiles 2x2", "-g 240", } for _, check := range av1Checks { if !strings.Contains(argsStr, check) { t.Errorf("Missing AV1 argument in pass 1: %s", check) } } } func TestSoftwareEncoder_BuildPass1Command_VP9(t *testing.T) { encoder := &SoftwareEncoder{codec: "libvpx-vp9"} config := &EncodeConfig{ InputPattern: "frame_%04d.exr", OutputPath: "output.webm", StartFrame: 1, FrameRate: 24.0, WorkDir: "/tmp", UseAlpha: false, TwoPass: true, SourceFormat: "exr", } cmd := encoder.BuildPass1Command(config) args := cmd.Args[1:] argsStr := strings.Join(args, " ") // Pass 1 should have -pass 1 and output to null if !strings.Contains(argsStr, "-pass 1") { t.Error("Pass 1 command should include '-pass 1'") } if !strings.Contains(argsStr, "-f null") { t.Error("Pass 1 command should include '-f null'") } if !strings.Contains(argsStr, "/dev/null") { t.Error("Pass 1 command should output to /dev/null") } // Check VP9-specific arguments in pass 1 vp9Checks := []string{ "-c:v libvpx-vp9", "-crf 30", "-b:v 0", "-row-mt 1", "-g 240", } for _, check := range vp9Checks { if !strings.Contains(argsStr, check) { t.Errorf("Missing VP9 argument in pass 1: %s", check) } } } func TestSoftwareEncoder_BuildCommand_NoTwoPass(t *testing.T) { encoder := &SoftwareEncoder{codec: "libx264"} config := &EncodeConfig{ InputPattern: "frame_%04d.exr", OutputPath: "output.mp4", StartFrame: 1, FrameRate: 24.0, WorkDir: "/tmp", UseAlpha: false, TwoPass: false, SourceFormat: "exr", } cmd := encoder.BuildCommand(config) args := cmd.Args[1:] argsStr := strings.Join(args, " ") // Should NOT have -pass flag when TwoPass is false if strings.Contains(argsStr, "-pass") { t.Error("Command should not include -pass flag when TwoPass is false") } } func TestSelector_SelectH264(t *testing.T) { selector := NewSelector() encoder := selector.SelectH264() if encoder == nil { t.Fatal("SelectH264 returned nil") } if encoder.Codec() != "libx264" { t.Errorf("Expected codec 'libx264', got '%s'", encoder.Codec()) } if encoder.Name() != "software" { t.Errorf("Expected name 'software', got '%s'", encoder.Name()) } } func TestSelector_SelectAV1(t *testing.T) { selector := NewSelector() encoder := selector.SelectAV1() if encoder == nil { t.Fatal("SelectAV1 returned nil") } if encoder.Codec() != "libaom-av1" { t.Errorf("Expected codec 'libaom-av1', got '%s'", encoder.Codec()) } } func TestSelector_SelectVP9(t *testing.T) { selector := NewSelector() encoder := selector.SelectVP9() if encoder == nil { t.Fatal("SelectVP9 returned nil") } if encoder.Codec() != "libvpx-vp9" { t.Errorf("Expected codec 'libvpx-vp9', got '%s'", encoder.Codec()) } } func TestTonemapFilter_WithAlpha(t *testing.T) { filter := tonemapFilter(true) // Filter should convert from gbrpf32le to yuva420p10le with proper colorspace conversion if !strings.Contains(filter, "yuva420p10le") { t.Error("Tonemap filter with alpha should output yuva420p10le format for HDR") } if !strings.Contains(filter, "gbrpf32le") { t.Error("Tonemap filter should start with gbrpf32le format") } // Should use zscale for colorspace conversion from linear RGB to bt2020 YUV if !strings.Contains(filter, "zscale") { t.Error("Tonemap filter should use zscale for colorspace conversion") } // Check for HLG transfer function (numeric value 18 or string arib-std-b67) if !strings.Contains(filter, "transfer=18") && !strings.Contains(filter, "transfer=arib-std-b67") { t.Error("Tonemap filter should use HLG transfer function (18 or arib-std-b67)") } } func TestTonemapFilter_WithoutAlpha(t *testing.T) { filter := tonemapFilter(false) // Filter should convert from gbrpf32le to yuv420p10le with proper colorspace conversion if !strings.Contains(filter, "yuv420p10le") { t.Error("Tonemap filter without alpha should output yuv420p10le format for HDR") } if strings.Contains(filter, "yuva420p") { t.Error("Tonemap filter without alpha should not output yuva420p format") } if !strings.Contains(filter, "gbrpf32le") { t.Error("Tonemap filter should start with gbrpf32le format") } // Should use zscale for colorspace conversion from linear RGB to bt2020 YUV if !strings.Contains(filter, "zscale") { t.Error("Tonemap filter should use zscale for colorspace conversion") } // Check for HLG transfer function (numeric value 18 or string arib-std-b67) if !strings.Contains(filter, "transfer=18") && !strings.Contains(filter, "transfer=arib-std-b67") { t.Error("Tonemap filter should use HLG transfer function (18 or arib-std-b67)") } } func TestSoftwareEncoder_Available(t *testing.T) { encoder := &SoftwareEncoder{codec: "libx264"} if !encoder.Available() { t.Error("Software encoder should always be available") } } func TestEncodeConfig_DefaultSourceFormat(t *testing.T) { config := &EncodeConfig{ InputPattern: "frame_%04d.exr", OutputPath: "output.mp4", StartFrame: 1, FrameRate: 24.0, WorkDir: "/tmp", UseAlpha: false, TwoPass: false, // SourceFormat not set, should default to empty string (treated as exr) } encoder := &SoftwareEncoder{codec: "libx264"} cmd := encoder.BuildCommand(config) args := strings.Join(cmd.Args[1:], " ") // Should still have tonemap filter when SourceFormat is empty (defaults to exr behavior) if !strings.Contains(args, "-vf") { t.Error("Empty SourceFormat should default to EXR behavior with tonemap filter") } } func TestCommandOrder(t *testing.T) { encoder := &SoftwareEncoder{codec: "libx264"} config := &EncodeConfig{ InputPattern: "frame_%04d.exr", OutputPath: "output.mp4", StartFrame: 1, FrameRate: 24.0, WorkDir: "/tmp", UseAlpha: false, TwoPass: true, SourceFormat: "exr", } cmd := encoder.BuildCommand(config) args := cmd.Args[1:] // Verify argument order: input should come before codec inputIdx := -1 codecIdx := -1 vfIdx := -1 for i, arg := range args { if arg == "-i" && i+1 < len(args) && args[i+1] == "frame_%04d.exr" { inputIdx = i } if arg == "-c:v" && i+1 < len(args) && args[i+1] == "libx264" { codecIdx = i } if arg == "-vf" { vfIdx = i } } if inputIdx == -1 { t.Fatal("Input pattern not found in command") } if codecIdx == -1 { t.Fatal("Codec not found in command") } if vfIdx == -1 { t.Fatal("Video filter not found in command") } // Input should come before codec if inputIdx >= codecIdx { t.Error("Input pattern should come before codec in command") } // Video filter should come after input (order: input -> codec -> colorspace -> filter -> codec args) // In practice, the filter comes after codec and colorspace metadata but before codec-specific args if vfIdx <= inputIdx { t.Error("Video filter should come after input") } } func TestCommand_ColorspaceMetadata(t *testing.T) { encoder := &SoftwareEncoder{codec: "libx264"} config := &EncodeConfig{ InputPattern: "frame_%04d.exr", OutputPath: "output.mp4", StartFrame: 1, FrameRate: 24.0, WorkDir: "/tmp", UseAlpha: false, TwoPass: false, SourceFormat: "exr", PreserveHDR: false, // SDR encoding } cmd := encoder.BuildCommand(config) args := cmd.Args[1:] argsStr := strings.Join(args, " ") // Verify all SDR colorspace metadata is present for EXR (SDR encoding) colorspaceArgs := []string{ "-color_primaries bt709", // EXR uses bt709 (SDR) "-color_trc bt709", // EXR uses bt709 (SDR) "-colorspace bt709", "-color_range tv", } for _, arg := range colorspaceArgs { if !strings.Contains(argsStr, arg) { t.Errorf("Missing colorspace metadata: %s", arg) } } // Verify SDR pixel format if !strings.Contains(argsStr, "-pix_fmt yuv420p") { t.Error("SDR encoding should use yuv420p pixel format") } // Verify H.264 high profile (not high10) if !strings.Contains(argsStr, "-profile:v high") { t.Error("SDR encoding should use high profile") } if strings.Contains(argsStr, "-profile:v high10") { t.Error("SDR encoding should not use high10 profile") } } func TestCommand_HDR_ColorspaceMetadata(t *testing.T) { encoder := &SoftwareEncoder{codec: "libx264"} config := &EncodeConfig{ InputPattern: "frame_%04d.exr", OutputPath: "output.mp4", StartFrame: 1, FrameRate: 24.0, WorkDir: "/tmp", UseAlpha: false, TwoPass: false, SourceFormat: "exr", PreserveHDR: true, // HDR encoding } cmd := encoder.BuildCommand(config) args := cmd.Args[1:] argsStr := strings.Join(args, " ") // Verify all HDR colorspace metadata is present for EXR (HDR encoding) colorspaceArgs := []string{ "-color_primaries bt709", // bt709 primaries to match PNG color appearance "-color_trc arib-std-b67", // HLG transfer function for HDR/SDR compatibility "-colorspace bt709", // bt709 colorspace to match PNG "-color_range tv", } for _, arg := range colorspaceArgs { if !strings.Contains(argsStr, arg) { t.Errorf("Missing HDR colorspace metadata: %s", arg) } } // Verify HDR pixel format (10-bit) if !strings.Contains(argsStr, "-pix_fmt yuv420p10le") { t.Error("HDR encoding should use yuv420p10le pixel format") } // Verify H.264 high10 profile (for 10-bit) if !strings.Contains(argsStr, "-profile:v high10") { t.Error("HDR encoding should use high10 profile") } // Verify HDR filter chain (linear -> sRGB -> HLG) if !strings.Contains(argsStr, "-vf") { t.Fatal("HDR encoding should have video filter") } vfIdx := -1 for i, arg := range args { if arg == "-vf" && i+1 < len(args) { vfIdx = i + 1 break } } if vfIdx == -1 { t.Fatal("Video filter not found") } filter := args[vfIdx] if !strings.Contains(filter, "transfer=18") { t.Error("HDR filter should convert to HLG (transfer=18)") } if !strings.Contains(filter, "yuv420p10le") { t.Error("HDR filter should output yuv420p10le format") } } // Integration tests using example files func TestIntegration_Encode_EXR_H264(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } // Check if example file exists exampleDir := filepath.Join("..", "..", "..", "examples") exrFile := filepath.Join(exampleDir, "frame_0800.exr") if _, err := os.Stat(exrFile); os.IsNotExist(err) { t.Skipf("Example file not found: %s", exrFile) } // Get absolute paths workspaceRoot, err := filepath.Abs(filepath.Join("..", "..", "..")) if err != nil { t.Fatalf("Failed to get workspace root: %v", err) } exampleDirAbs, err := filepath.Abs(exampleDir) if err != nil { t.Fatalf("Failed to get example directory: %v", err) } tmpDir := filepath.Join(workspaceRoot, "tmp") if err := os.MkdirAll(tmpDir, 0755); err != nil { t.Fatalf("Failed to create tmp directory: %v", err) } encoder := &SoftwareEncoder{codec: "libx264"} config := &EncodeConfig{ InputPattern: filepath.Join(exampleDirAbs, "frame_%04d.exr"), OutputPath: filepath.Join(tmpDir, "test_exr_h264.mp4"), StartFrame: 800, FrameRate: 24.0, WorkDir: tmpDir, UseAlpha: false, TwoPass: false, // Use single pass for faster testing SourceFormat: "exr", } // Build and run command cmd := encoder.BuildCommand(config) if cmd == nil { t.Fatal("BuildCommand returned nil") } // Capture stderr to see what went wrong output, err := cmd.CombinedOutput() if err != nil { t.Errorf("FFmpeg command failed: %v\nCommand output: %s", err, string(output)) return } // Verify output file was created if _, err := os.Stat(config.OutputPath); os.IsNotExist(err) { t.Errorf("Output file was not created: %s\nCommand output: %s", config.OutputPath, string(output)) } else { t.Logf("Successfully created output file: %s", config.OutputPath) // Verify file has content info, _ := os.Stat(config.OutputPath) if info.Size() == 0 { t.Errorf("Output file was created but is empty\nCommand output: %s", string(output)) } else { t.Logf("Output file size: %d bytes", info.Size()) } } } func TestIntegration_Encode_PNG_H264(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } // Check if example file exists exampleDir := filepath.Join("..", "..", "..", "examples") pngFile := filepath.Join(exampleDir, "frame_0800.png") if _, err := os.Stat(pngFile); os.IsNotExist(err) { t.Skipf("Example file not found: %s", pngFile) } // Get absolute paths workspaceRoot, err := filepath.Abs(filepath.Join("..", "..", "..")) if err != nil { t.Fatalf("Failed to get workspace root: %v", err) } exampleDirAbs, err := filepath.Abs(exampleDir) if err != nil { t.Fatalf("Failed to get example directory: %v", err) } tmpDir := filepath.Join(workspaceRoot, "tmp") if err := os.MkdirAll(tmpDir, 0755); err != nil { t.Fatalf("Failed to create tmp directory: %v", err) } encoder := &SoftwareEncoder{codec: "libx264"} config := &EncodeConfig{ InputPattern: filepath.Join(exampleDirAbs, "frame_%04d.png"), OutputPath: filepath.Join(tmpDir, "test_png_h264.mp4"), StartFrame: 800, FrameRate: 24.0, WorkDir: tmpDir, UseAlpha: false, TwoPass: false, // Use single pass for faster testing SourceFormat: "png", } // Build and run command cmd := encoder.BuildCommand(config) if cmd == nil { t.Fatal("BuildCommand returned nil") } // Verify no video filter is used for PNG argsStr := strings.Join(cmd.Args, " ") if strings.Contains(argsStr, "-vf") { t.Error("PNG encoding should not use video filter, but -vf was found in command") } // Run the command cmdOutput, err := cmd.CombinedOutput() if err != nil { t.Errorf("FFmpeg command failed: %v\nCommand output: %s", err, string(cmdOutput)) return } // Verify output file was created if _, err := os.Stat(config.OutputPath); os.IsNotExist(err) { t.Errorf("Output file was not created: %s\nCommand output: %s", config.OutputPath, string(cmdOutput)) } else { t.Logf("Successfully created output file: %s", config.OutputPath) info, _ := os.Stat(config.OutputPath) if info.Size() == 0 { t.Error("Output file was created but is empty") } else { t.Logf("Output file size: %d bytes", info.Size()) } } } func TestIntegration_Encode_EXR_VP9(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } // Check if example file exists exampleDir := filepath.Join("..", "..", "..", "examples") exrFile := filepath.Join(exampleDir, "frame_0800.exr") if _, err := os.Stat(exrFile); os.IsNotExist(err) { t.Skipf("Example file not found: %s", exrFile) } // Check if VP9 encoder is available checkCmd := exec.Command("ffmpeg", "-hide_banner", "-encoders") checkOutput, err := checkCmd.CombinedOutput() if err != nil || !strings.Contains(string(checkOutput), "libvpx-vp9") { t.Skip("VP9 encoder (libvpx-vp9) not available in ffmpeg") } // Get absolute paths workspaceRoot, err := filepath.Abs(filepath.Join("..", "..", "..")) if err != nil { t.Fatalf("Failed to get workspace root: %v", err) } exampleDirAbs, err := filepath.Abs(exampleDir) if err != nil { t.Fatalf("Failed to get example directory: %v", err) } tmpDir := filepath.Join(workspaceRoot, "tmp") if err := os.MkdirAll(tmpDir, 0755); err != nil { t.Fatalf("Failed to create tmp directory: %v", err) } encoder := &SoftwareEncoder{codec: "libvpx-vp9"} config := &EncodeConfig{ InputPattern: filepath.Join(exampleDirAbs, "frame_%04d.exr"), OutputPath: filepath.Join(tmpDir, "test_exr_vp9.webm"), StartFrame: 800, FrameRate: 24.0, WorkDir: tmpDir, UseAlpha: false, TwoPass: false, // Use single pass for faster testing SourceFormat: "exr", } // Build and run command cmd := encoder.BuildCommand(config) if cmd == nil { t.Fatal("BuildCommand returned nil") } // Capture stderr to see what went wrong output, err := cmd.CombinedOutput() if err != nil { t.Errorf("FFmpeg command failed: %v\nCommand output: %s", err, string(output)) return } // Verify output file was created if _, err := os.Stat(config.OutputPath); os.IsNotExist(err) { t.Errorf("Output file was not created: %s\nCommand output: %s", config.OutputPath, string(output)) } else { t.Logf("Successfully created output file: %s", config.OutputPath) // Verify file has content info, _ := os.Stat(config.OutputPath) if info.Size() == 0 { t.Errorf("Output file was created but is empty\nCommand output: %s", string(output)) } else { t.Logf("Output file size: %d bytes", info.Size()) } } } func TestIntegration_Encode_EXR_AV1(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } // Check if example file exists exampleDir := filepath.Join("..", "..", "..", "examples") exrFile := filepath.Join(exampleDir, "frame_0800.exr") if _, err := os.Stat(exrFile); os.IsNotExist(err) { t.Skipf("Example file not found: %s", exrFile) } // Check if AV1 encoder is available checkCmd := exec.Command("ffmpeg", "-hide_banner", "-encoders") output, err := checkCmd.CombinedOutput() if err != nil || !strings.Contains(string(output), "libaom-av1") { t.Skip("AV1 encoder (libaom-av1) not available in ffmpeg") } // Get absolute paths workspaceRoot, err := filepath.Abs(filepath.Join("..", "..", "..")) if err != nil { t.Fatalf("Failed to get workspace root: %v", err) } exampleDirAbs, err := filepath.Abs(exampleDir) if err != nil { t.Fatalf("Failed to get example directory: %v", err) } tmpDir := filepath.Join(workspaceRoot, "tmp") if err := os.MkdirAll(tmpDir, 0755); err != nil { t.Fatalf("Failed to create tmp directory: %v", err) } encoder := &SoftwareEncoder{codec: "libaom-av1"} config := &EncodeConfig{ InputPattern: filepath.Join(exampleDirAbs, "frame_%04d.exr"), OutputPath: filepath.Join(tmpDir, "test_exr_av1.mp4"), StartFrame: 800, FrameRate: 24.0, WorkDir: tmpDir, UseAlpha: false, TwoPass: false, SourceFormat: "exr", } // Build and run command cmd := encoder.BuildCommand(config) cmdOutput, err := cmd.CombinedOutput() if err != nil { t.Errorf("FFmpeg command failed: %v\nCommand output: %s", err, string(cmdOutput)) return } // Verify output file was created if _, err := os.Stat(config.OutputPath); os.IsNotExist(err) { t.Errorf("Output file was not created: %s\nCommand output: %s", config.OutputPath, string(cmdOutput)) } else { t.Logf("Successfully created AV1 output file: %s", config.OutputPath) info, _ := os.Stat(config.OutputPath) if info.Size() == 0 { t.Errorf("Output file was created but is empty\nCommand output: %s", string(cmdOutput)) } else { t.Logf("Output file size: %d bytes", info.Size()) } } } func TestIntegration_Encode_EXR_VP9_WithAlpha(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } // Check if example file exists exampleDir := filepath.Join("..", "..", "..", "examples") exrFile := filepath.Join(exampleDir, "frame_0800.exr") if _, err := os.Stat(exrFile); os.IsNotExist(err) { t.Skipf("Example file not found: %s", exrFile) } // Check if VP9 encoder is available checkCmd := exec.Command("ffmpeg", "-hide_banner", "-encoders") output, err := checkCmd.CombinedOutput() if err != nil || !strings.Contains(string(output), "libvpx-vp9") { t.Skip("VP9 encoder (libvpx-vp9) not available in ffmpeg") } // Get absolute paths workspaceRoot, err := filepath.Abs(filepath.Join("..", "..", "..")) if err != nil { t.Fatalf("Failed to get workspace root: %v", err) } exampleDirAbs, err := filepath.Abs(exampleDir) if err != nil { t.Fatalf("Failed to get example directory: %v", err) } tmpDir := filepath.Join(workspaceRoot, "tmp") if err := os.MkdirAll(tmpDir, 0755); err != nil { t.Fatalf("Failed to create tmp directory: %v", err) } encoder := &SoftwareEncoder{codec: "libvpx-vp9"} config := &EncodeConfig{ InputPattern: filepath.Join(exampleDirAbs, "frame_%04d.exr"), OutputPath: filepath.Join(tmpDir, "test_exr_vp9_alpha.webm"), StartFrame: 800, FrameRate: 24.0, WorkDir: tmpDir, UseAlpha: true, // Test with alpha TwoPass: false, // Use single pass for faster testing SourceFormat: "exr", } // Build and run command cmd := encoder.BuildCommand(config) if cmd == nil { t.Fatal("BuildCommand returned nil") } // Capture stderr to see what went wrong cmdOutput, err := cmd.CombinedOutput() if err != nil { t.Errorf("FFmpeg command failed: %v\nCommand output: %s", err, string(cmdOutput)) return } // Verify output file was created if _, err := os.Stat(config.OutputPath); os.IsNotExist(err) { t.Errorf("Output file was not created: %s\nCommand output: %s", config.OutputPath, string(cmdOutput)) } else { t.Logf("Successfully created VP9 output file with alpha: %s", config.OutputPath) info, _ := os.Stat(config.OutputPath) if info.Size() == 0 { t.Errorf("Output file was created but is empty\nCommand output: %s", string(cmdOutput)) } else { t.Logf("Output file size: %d bytes", info.Size()) } } } // Helper function to copy files func copyFile(src, dst string) error { data, err := os.ReadFile(src) if err != nil { return err } return os.WriteFile(dst, data, 0644) }