ForcePilot/backend/test/unit/agents/toolkits/buildin/test_ffmpeg.py
Kris b3bdb4fa2f feat: 新增TTS语音合成、音视频解析能力
1. 新增内置TTS工具,支持OpenAI兼容语音合成服务
2. 新增ASR音频解析和视频解析处理器
3. 扩展文件解析支持范围,添加音视频格式支持
4. 新增ffmpeg音视频处理基础工具库
5. 补充相关单元测试用例
2026-06-13 20:33:40 +08:00

131 lines
4.4 KiB
Python

"""ffmpeg 基础设施模块单元测试。"""
import json
import subprocess
from unittest.mock import MagicMock, patch
import pytest
from yuxi.knowledge.parser.ffmpeg import (
FFMPEG_MAX_AUDIO_DURATION_SECS,
FFMPEG_MAX_BUFFER_BYTES,
FFMPEG_MAX_CONCURRENT,
FFMPEG_TIMEOUT_MS,
FFPROBE_TIMEOUT_MS,
FFmpegError,
MAX_AUDIO_BYTES,
MAX_VIDEO_BYTES,
convert_to_wav,
extract_audio_from_video,
extract_keyframes,
probe_audio_duration,
run_ffmpeg,
run_ffprobe,
)
class TestFFmpegError:
def test_error_attributes(self):
err = FFmpegError("test error", returncode=1)
assert str(err) == "test error"
assert err.returncode == 1
assert err.message == "test error"
def test_default_returncode(self):
err = FFmpegError("test")
assert err.returncode == -1
class TestRunFFmpeg:
@patch("yuxi.knowledge.parser.ffmpeg.subprocess.run")
def test_success(self, mock_run):
mock_run.return_value = MagicMock(returncode=0, stdout=b"output", stderr=b"")
result = run_ffmpeg(["-version"])
assert result == "output"
mock_run.assert_called_once()
@patch("yuxi.knowledge.parser.ffmpeg.subprocess.run")
def test_timeout(self, mock_run):
mock_run.side_effect = subprocess.TimeoutExpired(cmd="ffmpeg", timeout=45)
with pytest.raises(FFmpegError, match="超时"):
run_ffmpeg(["-i", "input.mp4"])
@patch("yuxi.knowledge.parser.ffmpeg.subprocess.run")
def test_nonzero_returncode(self, mock_run):
mock_run.return_value = MagicMock(returncode=1, stdout=b"", stderr=b"error msg")
with pytest.raises(FFmpegError, match="非零退出码"):
run_ffmpeg(["-i", "bad.mp4"])
class TestRunFFprobe:
@patch("yuxi.knowledge.parser.ffmpeg.subprocess.run")
def test_success(self, mock_run):
mock_run.return_value = MagicMock(returncode=0, stdout=b'{"format":{}}', stderr=b"")
result = run_ffprobe(["-show_format", "test.mp3"])
assert result == '{"format":{}}'
class TestProbeAudioDuration:
@patch("yuxi.knowledge.parser.ffmpeg.run_ffprobe")
def test_success(self, mock_ffprobe):
mock_ffprobe.return_value = json.dumps({"format": {"duration": "120.5"}})
duration = probe_audio_duration("test.mp3")
assert duration == 120.5
@patch("yuxi.knowledge.parser.ffmpeg.run_ffprobe")
def test_failure_returns_none(self, mock_ffprobe):
mock_ffprobe.side_effect = FFmpegError("failed")
duration = probe_audio_duration("bad.mp3")
assert duration is None
class TestConvertToWav:
@patch("yuxi.knowledge.parser.ffmpeg.run_ffmpeg")
def test_calls_ffmpeg_with_correct_args(self, mock_ffmpeg):
convert_to_wav("input.mp3", "output.wav")
mock_ffmpeg.assert_called_once()
args = mock_ffmpeg.call_args[0][0]
assert "-i" in args
assert "input.mp3" in args
assert "-ar" in args
assert "16000" in args
assert "-ac" in args
assert "1" in args
class TestExtractAudioFromVideo:
@patch("yuxi.knowledge.parser.ffmpeg.run_ffmpeg")
def test_calls_ffmpeg_with_correct_args(self, mock_ffmpeg):
extract_audio_from_video("video.mp4", "audio.wav")
mock_ffmpeg.assert_called_once()
args = mock_ffmpeg.call_args[0][0]
assert "-i" in args
assert "video.mp4" in args
assert "-vn" in args
class TestExtractKeyframes:
@patch("yuxi.knowledge.parser.ffmpeg._detect_scene_change_pts", return_value=[])
def test_no_frames(self, mock_detect, tmp_path):
frames = extract_keyframes("video.mp4", str(tmp_path))
assert frames == []
@patch("yuxi.knowledge.parser.ffmpeg.run_ffmpeg")
@patch("yuxi.knowledge.parser.ffmpeg._detect_scene_change_pts", return_value=[5.0, 12.5])
def test_with_frames(self, mock_detect, mock_ffmpeg, tmp_path):
frames = extract_keyframes("video.mp4", str(tmp_path))
assert len(frames) == 2
assert frames[0][1] == 5.0
assert frames[1][1] == 12.5
class TestConstants:
def test_constants_values(self):
assert FFMPEG_MAX_BUFFER_BYTES == 10 * 1024 * 1024
assert FFPROBE_TIMEOUT_MS == 10_000
assert FFMPEG_TIMEOUT_MS == 45_000
assert FFMPEG_MAX_AUDIO_DURATION_SECS == 1200
assert MAX_AUDIO_BYTES == 16 * 1024 * 1024
assert MAX_VIDEO_BYTES == 16 * 1024 * 1024
assert FFMPEG_MAX_CONCURRENT == 4