981 lines
27 KiB
Go
981 lines
27 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,
|
|
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)
|
|
}
|