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

127 lines
5.1 KiB
Python

"""视频解析器单元测试。"""
from unittest.mock import MagicMock, patch
import pytest
from yuxi.knowledge.parser.base import DocumentProcessorException
from yuxi.knowledge.parser.ffmpeg import FFmpegError
from yuxi.knowledge.parser.video import VideoProcessor
@pytest.fixture
def video():
return VideoProcessor()
class TestVideoProcessorBasics:
def test_service_name(self, video):
assert video.get_service_name() == "video"
def test_supported_extensions(self, video):
exts = video.get_supported_extensions()
assert ".mp4" in exts
assert ".avi" in exts
assert ".mov" in exts
assert ".mkv" in exts
def test_supports_file_type(self, video):
assert video.supports_file_type(".mp4")
assert not video.supports_file_type(".mp3")
class TestProcessFile:
@patch(
"yuxi.knowledge.parser.video.VideoProcessor._process_keyframes",
return_value="## 视频画面文字\n\n**[00:00]** text",
)
@patch(
"yuxi.knowledge.parser.video.VideoProcessor._process_audio_track",
return_value="# 音频转写\n\n**[00:00]** hello",
)
def test_success_both_tracks(self, mock_audio, mock_keyframes, video, tmp_path):
test_file = tmp_path / "test.mp4"
test_file.write_bytes(b"fake video data")
result = video.process_file(str(test_file))
assert "音频转写" in result
assert "视频画面文字" in result
assert "---" in result
@patch("yuxi.knowledge.parser.video.VideoProcessor._process_keyframes", return_value=None)
@patch("yuxi.knowledge.parser.video.VideoProcessor._process_audio_track", return_value="# 音频转写")
def test_audio_only(self, mock_audio, mock_keyframes, video, tmp_path):
test_file = tmp_path / "test.mp4"
test_file.write_bytes(b"fake video data")
result = video.process_file(str(test_file))
assert "音频转写" in result
@patch("yuxi.knowledge.parser.video.VideoProcessor._process_keyframes", return_value="## 视频画面文字")
@patch("yuxi.knowledge.parser.video.VideoProcessor._process_audio_track", return_value=None)
def test_keyframes_only(self, mock_audio, mock_keyframes, video, tmp_path):
test_file = tmp_path / "test.mp4"
test_file.write_bytes(b"fake video data")
result = video.process_file(str(test_file))
assert "视频画面文字" in result
@patch("yuxi.knowledge.parser.video.VideoProcessor._process_keyframes", return_value=None)
@patch("yuxi.knowledge.parser.video.VideoProcessor._process_audio_track", return_value=None)
def test_no_content_raises(self, mock_audio, mock_keyframes, video, tmp_path):
test_file = tmp_path / "test.mp4"
test_file.write_bytes(b"fake video data")
with pytest.raises(DocumentProcessorException, match="未产生任何内容"):
video.process_file(str(test_file))
def test_file_too_large(self, video, tmp_path):
test_file = tmp_path / "big.mp4"
test_file.write_bytes(b"x" * (16 * 1024 * 1024 + 1))
with pytest.raises(DocumentProcessorException, match="大小"):
video.process_file(str(test_file))
class TestProcessAudioTrack:
@patch("yuxi.knowledge.parser.factory.DocumentProcessorFactory")
@patch("yuxi.knowledge.parser.video.extract_audio_from_video")
def test_success(self, mock_extract, mock_factory, video):
mock_factory.process_file.return_value = "# 音频转写"
result = video._process_audio_track("video.mp4", {})
assert result == "# 音频转写"
@patch("yuxi.knowledge.parser.video.extract_audio_from_video", side_effect=FFmpegError("no audio"))
def test_no_audio_track_returns_none(self, mock_extract, video):
result = video._process_audio_track("video.mp4", {})
assert result is None
class TestProcessKeyframes:
@patch("yuxi.knowledge.parser.video.extract_keyframes", return_value=[])
def test_no_frames(self, mock_extract, video):
result = video._process_keyframes("video.mp4", {})
assert result is None
def test_ocr_disabled(self, video):
result = video._process_keyframes("video.mp4", {"ocr_engine": "disable"})
assert result is None
@patch("yuxi.knowledge.parser.factory.DocumentProcessorFactory")
@patch("yuxi.knowledge.parser.video.extract_keyframes")
def test_with_frames(self, mock_extract, mock_factory, video, tmp_path):
frame_path = str(tmp_path / "frame_0001.png")
mock_extract.return_value = [(frame_path, 1.0)]
mock_processor = MagicMock()
mock_processor.process_file.return_value = "OCR text"
mock_factory.get_processor.return_value = mock_processor
result = video._process_keyframes("video.mp4", {})
assert result is not None
assert "## 视频画面文字" in result
assert "**[00:01]** OCR text" in result
class TestCheckHealth:
@patch("yuxi.knowledge.parser.ffmpeg.run_ffmpeg", side_effect=FFmpegError("not found"))
def test_ffmpeg_unavailable(self, mock_ffmpeg, video):
result = video.check_health()
assert result["status"] == "unavailable"