1a69fcfd04
- Updated the installation script to clarify that production wrappers do not include fixed test secrets, improving user guidance for local testing. - Modified the jiggablend-manager and jiggablend-runner scripts to remove fixed test configurations, emphasizing the use of local test credentials via `make init-test`. - Enhanced error handling in the runner script to ensure that an API key is provided, improving security and user feedback. - Added new flags and options for the runner, including sandboxing capabilities and GPU ray tracing control, enhancing flexibility for users. - Improved README documentation to reflect changes in usage and configuration, ensuring users have clear instructions for setup and execution.
846 lines
23 KiB
Go
846 lines
23 KiB
Go
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,
|
|
}
|
|
|
|
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, " ")
|
|
|
|
// EXR always uses HDR path: 10-bit, HLG, full range
|
|
checks := []struct {
|
|
name string
|
|
expected string
|
|
}{
|
|
{"-y flag", "-y"},
|
|
{"image2 format", "-f image2"},
|
|
{"start number", "-start_number 1"},
|
|
{"framerate", "-framerate 24.00"},
|
|
{"input color tag", "-color_trc linear"},
|
|
{"input pattern", "-i frame_%04d.exr"},
|
|
{"codec", "-c:v libx264"},
|
|
{"pixel format", "-pix_fmt yuv420p10le"},
|
|
{"frame rate", "-r 24.00"},
|
|
{"color primaries", "-color_primaries bt709"},
|
|
{"color trc", "-color_trc arib-std-b67"},
|
|
{"colorspace", "-colorspace bt709"},
|
|
{"color range", "-color_range pc"},
|
|
{"video filter", "-vf"},
|
|
{"preset", "-preset veryslow"},
|
|
{"crf", "-crf 15"},
|
|
{"profile", "-profile:v high10"},
|
|
{"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)
|
|
}
|
|
}
|
|
|
|
// EXR: linear -> sRGB -> HLG filter
|
|
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")
|
|
}
|
|
if !strings.Contains(argsStr, "transfer=18") {
|
|
t.Error("Expected sRGB to HLG conversion for EXR HDR, but not found")
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
cmd := encoder.BuildCommand(config)
|
|
args := cmd.Args[1:]
|
|
argsStr := strings.Join(args, " ")
|
|
|
|
// EXR with alpha: 10-bit HDR path
|
|
if !strings.Contains(argsStr, "-pix_fmt yuva420p10le") {
|
|
t.Error("Expected yuva420p10le pixel format for EXR 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 (10-bit for EXR)
|
|
if !strings.Contains(argsStr, "format=yuva420p10le") {
|
|
t.Error("Expected tonemap filter to output yuva420p10le for EXR alpha, but not found")
|
|
}
|
|
// Alpha VP9/AV1 need -strict experimental on modern FFmpeg
|
|
if !strings.Contains(argsStr, "-strict experimental") {
|
|
t.Error("Expected -strict experimental for alpha encode")
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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 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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
cmd := encoder.BuildCommand(config)
|
|
args := cmd.Args[1:]
|
|
argsStr := strings.Join(args, " ")
|
|
|
|
// EXR always uses HDR path: bt709 primaries, HLG, full range
|
|
colorspaceArgs := []string{
|
|
"-color_primaries bt709",
|
|
"-color_trc arib-std-b67",
|
|
"-colorspace bt709",
|
|
"-color_range pc",
|
|
}
|
|
|
|
for _, arg := range colorspaceArgs {
|
|
if !strings.Contains(argsStr, arg) {
|
|
t.Errorf("Missing colorspace metadata: %s", arg)
|
|
}
|
|
}
|
|
|
|
if !strings.Contains(argsStr, "-pix_fmt yuv420p10le") {
|
|
t.Error("EXR encoding should use yuv420p10le pixel format")
|
|
}
|
|
if !strings.Contains(argsStr, "-profile:v high10") {
|
|
t.Error("EXR encoding should 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,
|
|
}
|
|
|
|
cmd := encoder.BuildCommand(config)
|
|
args := cmd.Args[1:]
|
|
argsStr := strings.Join(args, " ")
|
|
|
|
// Verify all HDR colorspace metadata is present for EXR (full range to match zscale output)
|
|
colorspaceArgs := []string{
|
|
"-color_primaries bt709",
|
|
"-color_trc arib-std-b67",
|
|
"-colorspace bt709",
|
|
"-color_range pc",
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 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_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
|
|
}
|
|
|
|
// 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,
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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)
|
|
}
|