# -*- 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"