All checks were successful
Publish Python Package to Gitea / publish (push) Successful in 17s
Co-authored-by: Cursor <cursoragent@cursor.com>
253 lines
7.8 KiB
Python
253 lines
7.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""screencast composer 媒体资源集成测试。"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
from screencast.composer import ComposeVideoResult, compose_video
|
|
|
|
|
|
def _write_frame(frames_dir: Path) -> None:
|
|
frames_dir.mkdir(parents=True, exist_ok=True)
|
|
(frames_dir / "frame_000001.png").write_bytes(b"png")
|
|
|
|
|
|
def _write_srt(path: Path) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text("1\n00:00:00,000 --> 00:00:02,000\nhello\n", encoding="utf-8")
|
|
|
|
|
|
def test_compose_video_uses_absolute_ffmpeg(tmp_path: Path) -> None:
|
|
frames = tmp_path / "frames"
|
|
srt = tmp_path / "out.srt"
|
|
output = tmp_path / "final.mp4"
|
|
ffmpeg = tmp_path / "ffmpeg.exe"
|
|
ffmpeg.write_bytes(b"ffmpeg")
|
|
_write_frame(frames)
|
|
_write_srt(srt)
|
|
|
|
captured: list[list[str]] = []
|
|
|
|
def _fake_run(ffmpeg_exe: Path, cmd_tail: list[str]) -> tuple[bool, str]:
|
|
captured.append([str(ffmpeg_exe), *cmd_tail])
|
|
output.write_bytes(b"mp4")
|
|
return True, ""
|
|
|
|
with patch("screencast.composer.resolve_ffmpeg", return_value=ffmpeg), patch(
|
|
"screencast.composer.pick_background_music",
|
|
return_value=None,
|
|
), patch("screencast.composer.background_music_issue", return_value="background_music_missing"), patch(
|
|
"screencast.composer._run_ffmpeg",
|
|
side_effect=_fake_run,
|
|
):
|
|
result = compose_video(
|
|
frames_dir=str(frames),
|
|
fps=10,
|
|
subtitle_path=str(srt),
|
|
output_path=str(output),
|
|
)
|
|
|
|
assert isinstance(result, ComposeVideoResult)
|
|
assert captured
|
|
assert captured[0][0] == str(ffmpeg)
|
|
assert "background_music_missing" in result.warnings
|
|
|
|
|
|
def test_compose_video_with_music(tmp_path: Path) -> None:
|
|
frames = tmp_path / "frames"
|
|
srt = tmp_path / "out.srt"
|
|
output = tmp_path / "final.mp4"
|
|
ffmpeg = tmp_path / "ffmpeg.exe"
|
|
music = tmp_path / "music.mp3"
|
|
ffmpeg.write_bytes(b"ffmpeg")
|
|
music.write_bytes(b"x" * 256)
|
|
_write_frame(frames)
|
|
_write_srt(srt)
|
|
|
|
with patch("screencast.composer.resolve_ffmpeg", return_value=ffmpeg), patch(
|
|
"screencast.composer.pick_background_music",
|
|
return_value=music,
|
|
), patch(
|
|
"screencast.composer._run_ffmpeg",
|
|
return_value=(True, ""),
|
|
):
|
|
result = compose_video(
|
|
frames_dir=str(frames),
|
|
fps=10,
|
|
subtitle_path=str(srt),
|
|
output_path=str(output),
|
|
)
|
|
output.write_bytes(b"mp4")
|
|
assert result.warnings == []
|
|
|
|
|
|
def test_compose_video_music_failure_fallback(tmp_path: Path) -> None:
|
|
frames = tmp_path / "frames"
|
|
srt = tmp_path / "out.srt"
|
|
output = tmp_path / "final.mp4"
|
|
ffmpeg = tmp_path / "ffmpeg.exe"
|
|
music = tmp_path / "music.mp3"
|
|
ffmpeg.write_bytes(b"ffmpeg")
|
|
music.write_bytes(b"x" * 256)
|
|
_write_frame(frames)
|
|
_write_srt(srt)
|
|
|
|
calls = {"count": 0}
|
|
|
|
def _fake_run(ffmpeg_exe: Path, cmd_tail: list[str]) -> tuple[bool, str]:
|
|
calls["count"] += 1
|
|
if calls["count"] == 1:
|
|
return False, "music mux failed"
|
|
output.write_bytes(b"mp4")
|
|
return True, ""
|
|
|
|
with patch("screencast.composer.resolve_ffmpeg", return_value=ffmpeg), patch(
|
|
"screencast.composer.pick_background_music",
|
|
return_value=music,
|
|
), patch(
|
|
"screencast.composer._run_ffmpeg",
|
|
side_effect=_fake_run,
|
|
):
|
|
result = compose_video(
|
|
frames_dir=str(frames),
|
|
fps=10,
|
|
subtitle_path=str(srt),
|
|
output_path=str(output),
|
|
)
|
|
|
|
assert any("ffmpeg_with_music_failed" in w for w in result.warnings)
|
|
assert calls["count"] == 2
|
|
|
|
|
|
def test_compose_video_passes_media_assets_root_to_media_apis(tmp_path: Path) -> None:
|
|
frames = tmp_path / "frames"
|
|
srt = tmp_path / "out.srt"
|
|
output = tmp_path / "final.mp4"
|
|
custom_root = tmp_path / "custom-media-assets"
|
|
custom_root.mkdir()
|
|
ffmpeg = custom_root / "ffmpeg.exe"
|
|
ffmpeg.write_bytes(b"ffmpeg")
|
|
_write_frame(frames)
|
|
_write_srt(srt)
|
|
|
|
captured_env: list[dict[str, str] | None] = []
|
|
|
|
def _capture_env(*args, **kwargs):
|
|
captured_env.append(kwargs.get("env"))
|
|
if len(captured_env) == 1:
|
|
return ffmpeg
|
|
if len(captured_env) == 2:
|
|
return None
|
|
return "background_music_missing"
|
|
|
|
with patch("screencast.composer.resolve_ffmpeg", side_effect=_capture_env), patch(
|
|
"screencast.composer.pick_background_music",
|
|
side_effect=_capture_env,
|
|
), patch(
|
|
"screencast.composer.background_music_issue",
|
|
side_effect=_capture_env,
|
|
), patch(
|
|
"screencast.composer._run_ffmpeg",
|
|
return_value=(True, ""),
|
|
):
|
|
compose_video(
|
|
frames_dir=str(frames),
|
|
fps=10,
|
|
subtitle_path=str(srt),
|
|
output_path=str(output),
|
|
media_assets_root=str(custom_root),
|
|
)
|
|
output.write_bytes(b"mp4")
|
|
|
|
assert len(captured_env) == 3
|
|
for env in captured_env:
|
|
assert env is not None
|
|
assert env["MEDIA_ASSETS_ROOT"] == str(custom_root)
|
|
|
|
|
|
def test_compose_video_default_does_not_set_media_assets_root(tmp_path: Path) -> None:
|
|
frames = tmp_path / "frames"
|
|
srt = tmp_path / "out.srt"
|
|
output = tmp_path / "final.mp4"
|
|
ffmpeg = tmp_path / "ffmpeg.exe"
|
|
ffmpeg.write_bytes(b"ffmpeg")
|
|
_write_frame(frames)
|
|
_write_srt(srt)
|
|
|
|
captured_env: list[dict[str, str] | None] = []
|
|
|
|
def _capture_env(*args, **kwargs):
|
|
captured_env.append(kwargs.get("env"))
|
|
if len(captured_env) == 1:
|
|
return ffmpeg
|
|
if len(captured_env) == 2:
|
|
return None
|
|
return "background_music_missing"
|
|
|
|
with patch("screencast.composer.resolve_ffmpeg", side_effect=_capture_env), patch(
|
|
"screencast.composer.pick_background_music",
|
|
side_effect=_capture_env,
|
|
), patch(
|
|
"screencast.composer.background_music_issue",
|
|
side_effect=_capture_env,
|
|
), patch(
|
|
"screencast.composer._run_ffmpeg",
|
|
return_value=(True, ""),
|
|
):
|
|
compose_video(
|
|
frames_dir=str(frames),
|
|
fps=10,
|
|
subtitle_path=str(srt),
|
|
output_path=str(output),
|
|
)
|
|
|
|
assert all(env is None for env in captured_env)
|
|
|
|
|
|
def test_compose_video_media_assets_root_overrides_env(tmp_path: Path) -> None:
|
|
frames = tmp_path / "frames"
|
|
srt = tmp_path / "out.srt"
|
|
output = tmp_path / "final.mp4"
|
|
custom_root = tmp_path / "override-root"
|
|
custom_root.mkdir()
|
|
ffmpeg = custom_root / "ffmpeg.exe"
|
|
ffmpeg.write_bytes(b"ffmpeg")
|
|
_write_frame(frames)
|
|
_write_srt(srt)
|
|
|
|
original_env = {"MEDIA_ASSETS_ROOT": "/old/path", "OTHER": "keep"}
|
|
captured_env: list[dict[str, str] | None] = []
|
|
|
|
def _capture_env(*args, **kwargs):
|
|
captured_env.append(kwargs.get("env"))
|
|
if len(captured_env) == 1:
|
|
return ffmpeg
|
|
if len(captured_env) == 2:
|
|
return None
|
|
return "background_music_missing"
|
|
|
|
with patch("screencast.composer.resolve_ffmpeg", side_effect=_capture_env), patch(
|
|
"screencast.composer.pick_background_music",
|
|
side_effect=_capture_env,
|
|
), patch(
|
|
"screencast.composer.background_music_issue",
|
|
side_effect=_capture_env,
|
|
), patch(
|
|
"screencast.composer._run_ffmpeg",
|
|
return_value=(True, ""),
|
|
):
|
|
compose_video(
|
|
frames_dir=str(frames),
|
|
fps=10,
|
|
subtitle_path=str(srt),
|
|
output_path=str(output),
|
|
media_assets_root=str(custom_root),
|
|
media_assets_env=original_env,
|
|
)
|
|
output.write_bytes(b"mp4")
|
|
|
|
assert original_env["MEDIA_ASSETS_ROOT"] == "/old/path"
|
|
assert captured_env[0]["MEDIA_ASSETS_ROOT"] == str(custom_root)
|
|
assert captured_env[0]["OTHER"] == "keep"
|