1. 新增内置TTS工具,支持OpenAI兼容语音合成服务 2. 新增ASR音频解析和视频解析处理器 3. 扩展文件解析支持范围,添加音视频格式支持 4. 新增ffmpeg音视频处理基础工具库 5. 补充相关单元测试用例
127 lines
5.1 KiB
Python
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"
|