Files
FrameTour-RenderWorker/tests/test_ffmpeg_builder/test_ffmpeg_command_builder.py
2025-09-24 09:21:03 +08:00

244 lines
8.6 KiB
Python

"""测试FFmpeg命令构建器"""
import pytest
from unittest.mock import Mock, patch
from entity.ffmpeg_command_builder import FFmpegCommandBuilder
from entity.render_task import RenderTask
from config.settings import Config
from tests.utils.test_helpers import MockRenderTask, FFmpegValidator
class TestFFmpegCommandBuilder:
"""测试FFmpeg命令构建器"""
@pytest.fixture
def mock_config(self):
"""模拟配置"""
return Config(
encoder_args="-c:v h264",
video_args="-preset fast",
template_dir="tests/test_data/templates",
api_endpoint="http://test.local",
access_key="test_key"
)
@pytest.fixture
def simple_task(self):
"""简单的渲染任务"""
task = MockRenderTask()
task.input_files = ["input1.mp4", "input2.mp4"]
task.output_path = "output.mp4"
task.effects = []
return task
@pytest.fixture
def task_with_effects(self):
"""带特效的渲染任务"""
task = MockRenderTask()
task.input_files = ["input.mp4"]
task.output_path = "output.mp4"
task.effects = ["zoom:0,2.0,3.0", "ospeed:1.5"]
task.ext_data = {
"posJson": '{"ltX": 100, "ltY": 100, "rbX": 200, "rbY": 200, "imgWidth": 300, "imgHeight": 300}'
}
return task
def test_init(self, simple_task, mock_config):
"""测试初始化"""
builder = FFmpegCommandBuilder(simple_task, mock_config)
assert builder.task == simple_task
assert builder.config == mock_config
def test_build_copy_command(self, simple_task, mock_config):
"""测试构建复制命令"""
builder = FFmpegCommandBuilder(simple_task, mock_config)
command = builder._build_copy_command()
# 验证命令结构
validation = FFmpegValidator.validate_ffmpeg_command(command)
assert validation["valid"], f"Invalid command: {validation['errors']}"
assert validation["has_input"]
assert validation["has_output"]
# 验证具体内容
assert command[0] == "ffmpeg"
assert "-i" in command
assert "input1.mp4" in command
assert "output.mp4" in command
assert "-c" in command and "copy" in command
def test_build_concat_command_multiple_files(self, mock_config):
"""测试构建多文件拼接命令"""
task = MockRenderTask()
task.input_files = ["file1.mp4", "file2.mp4", "file3.mp4"]
task.output_path = "concat_output.mp4"
builder = FFmpegCommandBuilder(task, mock_config)
command = builder._build_concat_command()
validation = FFmpegValidator.validate_ffmpeg_command(command)
assert validation["valid"]
assert validation["has_filter"]
# 验证拼接相关参数
assert "concat=n=3:v=1:a=1" in " ".join(command)
assert all(f"file{i}.mp4" in command for i in range(1, 4))
def test_build_encode_command_with_effects(self, task_with_effects, mock_config):
"""测试构建带特效的编码命令"""
builder = FFmpegCommandBuilder(task_with_effects, mock_config)
command = builder._build_encode_command()
validation = FFmpegValidator.validate_ffmpeg_command(command)
assert validation["valid"]
assert validation["has_filter"]
command_str = " ".join(command)
# 应该包含特效相关的滤镜
assert "-filter_complex" in command
# 应该包含编码参数
assert "-c:v" in command and "h264" in command
def test_add_effects_single_effect(self, mock_config):
"""测试添加单个特效"""
task = MockRenderTask()
task.effects = ["zoom:0,2.0,3.0"]
task.ext_data = {"posJson": "{}"}
builder = FFmpegCommandBuilder(task, mock_config)
filter_args = []
result_input, result_index = builder._add_effects(filter_args, "[0:v]", 1)
# 验证结果
assert len(filter_args) > 0 # 应该有滤镜被添加
assert result_input == "[v_eff1]" # 输出流应该更新
assert result_index == 2 # 索引应该递增
def test_add_effects_multiple_effects(self, mock_config):
"""测试添加多个特效"""
task = MockRenderTask()
task.effects = ["zoom:0,2.0,3.0", "ospeed:1.5"]
task.ext_data = {"posJson": "{}"}
builder = FFmpegCommandBuilder(task, mock_config)
filter_args = []
result_input, result_index = builder._add_effects(filter_args, "[0:v]", 1)
# 验证特效链
assert len(filter_args) >= 2 # 应该有两个特效的滤镜
assert result_input == "[v_eff2]" # 最终输出流
assert result_index == 3 # 索引应该递增两次
def test_add_effects_invalid_effect(self, mock_config):
"""测试添加无效特效"""
task = MockRenderTask()
task.effects = ["invalid_effect:params"]
builder = FFmpegCommandBuilder(task, mock_config)
filter_args = []
result_input, result_index = builder._add_effects(filter_args, "[0:v]", 1)
# 无效特效应该被忽略
assert result_input == "[0:v]" # 输入流不变
assert result_index == 1 # 索引不变
def test_add_effects_no_effects(self, mock_config):
"""测试无特效情况"""
task = MockRenderTask()
task.effects = []
builder = FFmpegCommandBuilder(task, mock_config)
filter_args = []
result_input, result_index = builder._add_effects(filter_args, "[0:v]", 1)
assert result_input == "[0:v]" # 输入流不变
assert result_index == 1 # 索引不变
assert len(filter_args) == 0 # 无滤镜添加
def test_build_command_copy_mode(self, simple_task, mock_config):
"""测试构建复制模式命令"""
simple_task.input_files = ["single_file.mp4"]
builder = FFmpegCommandBuilder(simple_task, mock_config)
command = builder.build_command()
validation = FFmpegValidator.validate_ffmpeg_command(command)
assert validation["valid"]
# 应该是复制模式
command_str = " ".join(command)
assert "-c copy" in command_str
def test_build_command_concat_mode(self, mock_config):
"""测试构建拼接模式命令"""
task = MockRenderTask()
task.input_files = ["file1.mp4", "file2.mp4"]
task.effects = []
builder = FFmpegCommandBuilder(task, mock_config)
command = builder.build_command()
validation = FFmpegValidator.validate_ffmpeg_command(command)
assert validation["valid"]
assert validation["has_filter"]
# 应该包含拼接滤镜
command_str = " ".join(command)
assert "concat=" in command_str
def test_build_command_encode_mode(self, task_with_effects, mock_config):
"""测试构建编码模式命令"""
builder = FFmpegCommandBuilder(task_with_effects, mock_config)
command = builder.build_command()
validation = FFmpegValidator.validate_ffmpeg_command(command)
assert validation["valid"]
assert validation["has_filter"]
# 应该包含编码参数和特效滤镜
command_str = " ".join(command)
assert "-c:v h264" in command_str
@patch('entity.effects.registry.get_processor')
def test_effect_processor_integration(self, mock_get_processor, mock_config):
"""测试与特效处理器的集成"""
# 模拟特效处理器
mock_processor = Mock()
mock_processor.frame_rate = 25
mock_processor.generate_filter_args.return_value = (
["[0:v]zoompan=z=2.0:x=iw/2:y=ih/2:d=1[v_eff1]"],
"[v_eff1]"
)
mock_get_processor.return_value = mock_processor
task = MockRenderTask()
task.effects = ["zoom:0,2.0,3.0"]
task.frame_rate = 30
builder = FFmpegCommandBuilder(task, mock_config)
filter_args = []
builder._add_effects(filter_args, "[0:v]", 1)
# 验证处理器被正确调用
mock_processor.generate_filter_args.assert_called_once_with("[0:v]", 1)
assert mock_processor.frame_rate == 30 # 帧率应该被设置
def test_error_handling_missing_input(self, mock_config):
"""测试缺少输入文件的错误处理"""
task = MockRenderTask()
task.input_files = []
builder = FFmpegCommandBuilder(task, mock_config)
# 构建命令时应该处理错误情况
# 具体的错误处理依赖于实现
command = builder.build_command()
# 验证至少返回了基本的ffmpeg命令结构
assert isinstance(command, list)
assert len(command) > 0