Release v1.0.10: shared media_assets and screencast media_assets_root override
All checks were successful
Publish Python Package to Gitea / publish (push) Successful in 17s
All checks were successful
Publish Python Package to Gitea / publish (push) Successful in 17s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -81,6 +81,27 @@ class TestConfig(unittest.TestCase):
|
||||
self.assertEqual(config.get_float("BAD", 9.9), 9.9)
|
||||
self.assertEqual(config.get_int("BAD", 7), 7)
|
||||
|
||||
def test_merge_missing_env_keys_appends_only_new(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
example = os.path.join(tmp, ".env.example")
|
||||
dest = os.path.join(tmp, ".env")
|
||||
with open(example, "w", encoding="utf-8") as f:
|
||||
f.write("EXISTING=1\nNEW_KEY=abc\n")
|
||||
with open(dest, "w", encoding="utf-8") as f:
|
||||
f.write("EXISTING=keep\n")
|
||||
|
||||
added = config.merge_missing_env_keys(
|
||||
example,
|
||||
dest,
|
||||
comment_skill="demo-skill",
|
||||
)
|
||||
self.assertEqual(added, ["NEW_KEY"])
|
||||
with open(dest, encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
self.assertIn("EXISTING=keep", content)
|
||||
self.assertIn("NEW_KEY=abc", content)
|
||||
self.assertIn("Added by demo-skill", content)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -15,6 +15,7 @@ from jiangchang_skill_core import media_assets as ma
|
||||
|
||||
def _make_complete_assets(root: Path, *, with_ffmpeg: bool = True, with_ffprobe: bool = True) -> None:
|
||||
(root / "music" / "calm").mkdir(parents=True)
|
||||
(root / "music" / "calm" / "track.mp3").write_bytes(b"x" * 256)
|
||||
(root / "fonts").mkdir(parents=True)
|
||||
(root / "watermark").mkdir(parents=True)
|
||||
bin_key = ma._platform_bin_key()
|
||||
@@ -117,9 +118,9 @@ def test_pick_background_music_stable_first(tmp_path: Path, monkeypatch: pytest.
|
||||
root = tmp_path / "data" / "shared" / "media-assets"
|
||||
_make_complete_assets(root)
|
||||
music = root / "music"
|
||||
(music / "b-upbeat.mp3").write_bytes(b"mp3")
|
||||
(music / "calm" / "a-calm.mp3").write_bytes(b"mp3")
|
||||
(music / "z-last.wav").write_bytes(b"wav")
|
||||
(music / "b-upbeat.mp3").write_bytes(b"x" * 256)
|
||||
(music / "calm" / "a-calm.mp3").write_bytes(b"x" * 256)
|
||||
(music / "z-last.wav").write_bytes(b"x" * 256)
|
||||
|
||||
monkeypatch.setattr(
|
||||
ma,
|
||||
@@ -136,7 +137,7 @@ def _build_archive_zip(dest: Path, inner_name: str) -> None:
|
||||
with zipfile.ZipFile(dest, "w") as zf:
|
||||
zf.writestr(f"{inner_name}/README.md", "# media-assets\n")
|
||||
zf.writestr(f"{inner_name}/manifest.json", json.dumps(manifest, ensure_ascii=False))
|
||||
zf.writestr(f"{inner_name}/music/.keep", "")
|
||||
zf.writestr(f"{inner_name}/music/calm/track.mp3", b"x" * 256)
|
||||
zf.writestr(f"{inner_name}/fonts/.keep", "")
|
||||
zf.writestr(f"{inner_name}/watermark/.keep", "")
|
||||
|
||||
@@ -213,9 +214,117 @@ def test_windows_default_data_root() -> None:
|
||||
assert root == Path(r"D:\jiangchang-data") / "shared" / "media-assets"
|
||||
|
||||
|
||||
def test_probe_media_assets_does_not_download(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def _fail_download(*args, **kwargs):
|
||||
raise AssertionError("download should not be called")
|
||||
|
||||
monkeypatch.setattr(ma, "_try_download_via_zip", _fail_download)
|
||||
monkeypatch.setattr(ma, "_try_download_via_git", _fail_download)
|
||||
|
||||
status = ma.probe_media_assets(env={"JIANGCHANG_DATA_ROOT": str(tmp_path / "data")})
|
||||
assert status.exists is False
|
||||
assert status.ffmpeg_path is None
|
||||
|
||||
|
||||
def test_pick_background_music_skips_lfs_pointer(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
root = tmp_path / "data" / "shared" / "media-assets"
|
||||
_make_complete_assets(root, with_ffmpeg=True, with_ffprobe=True)
|
||||
music = root / "music"
|
||||
(music / "calm" / "track.mp3").unlink()
|
||||
(music / "real.mp3").write_bytes(b"x" * 256)
|
||||
lfs = music / "lfs-only.mp3"
|
||||
lfs.write_text("version https://git-lfs.github.com/spec/v1\noid sha256:abc\nsize 123\n")
|
||||
|
||||
monkeypatch.setattr(
|
||||
ma,
|
||||
"ensure_media_assets",
|
||||
lambda *a, **k: ma._inspect_media_assets(root, source="local"),
|
||||
)
|
||||
|
||||
picked = ma.pick_background_music(env={"JIANGCHANG_DATA_ROOT": str(tmp_path / "data")})
|
||||
assert picked == music / "real.mp3"
|
||||
|
||||
|
||||
def test_pick_background_music_no_valid_files(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
root = tmp_path / "data" / "shared" / "media-assets"
|
||||
_make_complete_assets(root)
|
||||
music = root / "music"
|
||||
(music / "calm" / "track.mp3").unlink()
|
||||
(music / "pointer.mp3").write_text(
|
||||
"version https://git-lfs.github.com/spec/v1\noid sha256:abc\nsize 123\n"
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
ma,
|
||||
"ensure_media_assets",
|
||||
lambda *a, **k: ma._inspect_media_assets(root, source="local"),
|
||||
)
|
||||
|
||||
picked = ma.pick_background_music(env={"JIANGCHANG_DATA_ROOT": str(tmp_path / "data")})
|
||||
assert picked is None
|
||||
|
||||
|
||||
def test_video_finalize_without_music_fallback(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from jiangchang_skill_core import config
|
||||
from jiangchang_skill_core.rpa.video_session import RpaVideoSession
|
||||
|
||||
ffmpeg_exe = tmp_path / "ffmpeg.exe"
|
||||
ffmpeg_exe.write_bytes(b"ffmpeg")
|
||||
|
||||
def _fake_resolve_ffmpeg(*args, **kwargs):
|
||||
return ffmpeg_exe
|
||||
|
||||
def _fake_pick_music(*args, **kwargs):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(
|
||||
"jiangchang_skill_core.rpa.video_session.resolve_ffmpeg",
|
||||
_fake_resolve_ffmpeg,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"jiangchang_skill_core.rpa.video_session.pick_background_music",
|
||||
_fake_pick_music,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"jiangchang_skill_core.rpa.video_session.background_music_issue",
|
||||
lambda *a, **k: None,
|
||||
)
|
||||
|
||||
os.environ["OPENCLAW_RECORD_VIDEO"] = "1"
|
||||
try:
|
||||
config.reset_cache()
|
||||
with tempfile.TemporaryDirectory() as skill_data:
|
||||
session = RpaVideoSession(
|
||||
skill_slug="receive-order",
|
||||
skill_data_dir=skill_data,
|
||||
batch_id="batch_no_music",
|
||||
)
|
||||
capture = Path(session._capture_path)
|
||||
capture.parent.mkdir(parents=True, exist_ok=True)
|
||||
capture.write_bytes(b"\x00" * 64)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"jiangchang_skill_core.rpa.video_session._compose_capture_mp4",
|
||||
lambda *a, **k: (True, ""),
|
||||
)
|
||||
|
||||
asyncio.run(session.finalize())
|
||||
assert "background_music_missing" in session.warnings
|
||||
assert session.output_video_path is not None
|
||||
finally:
|
||||
os.environ.pop("OPENCLAW_RECORD_VIDEO", None)
|
||||
config.reset_cache()
|
||||
|
||||
|
||||
def test_manifest_ffmpeg_download(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
root = tmp_path / "data" / "shared" / "media-assets"
|
||||
(root / "music").mkdir(parents=True)
|
||||
(root / "music" / "calm").mkdir(parents=True)
|
||||
(root / "music" / "calm" / "track.mp3").write_bytes(b"x" * 256)
|
||||
(root / "fonts").mkdir(parents=True)
|
||||
(root / "watermark").mkdir(parents=True)
|
||||
(root / "manifest.json").write_text(
|
||||
|
||||
252
tests/test_screencast_composer.py
Normal file
252
tests/test_screencast_composer.py
Normal file
@@ -0,0 +1,252 @@
|
||||
# -*- 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"
|
||||
78
tests/test_screencast_runner.py
Normal file
78
tests/test_screencast_runner.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""screencast runner 媒体资源参数传递测试。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from screencast.runner import run_screencast
|
||||
|
||||
|
||||
def test_run_screencast_passes_media_assets_root_to_compose_video(tmp_path: Path) -> None:
|
||||
custom_root = tmp_path / "media-assets"
|
||||
custom_root.mkdir()
|
||||
captured: dict = {}
|
||||
|
||||
mock_recorder = MagicMock()
|
||||
mock_recorder.frame_count = 1
|
||||
|
||||
def _fake_compose_video(**kwargs):
|
||||
captured.update(kwargs)
|
||||
Path(kwargs["output_path"]).write_bytes(b"mp4")
|
||||
from screencast.composer import ComposeVideoResult
|
||||
|
||||
return ComposeVideoResult(kwargs["output_path"])
|
||||
|
||||
with patch("screencast.runner.activate_and_maximize_jiangchang_window", return_value=True), patch(
|
||||
"screencast.runner.ScreenRecorder",
|
||||
return_value=mock_recorder,
|
||||
), patch("screencast.runner.SubtitleEngine") as subtitle_cls, patch(
|
||||
"screencast.runner.subprocess.Popen",
|
||||
) as popen_cls, patch(
|
||||
"screencast.runner.compose_video",
|
||||
side_effect=_fake_compose_video,
|
||||
):
|
||||
subtitle = subtitle_cls.return_value
|
||||
proc = popen_cls.return_value
|
||||
proc.stdout = iter([])
|
||||
proc.wait.return_value = 0
|
||||
|
||||
out = run_screencast(
|
||||
skill_slug="demo-skill",
|
||||
subtitle_script=[("ok", "done")],
|
||||
pytest_args=["-q", "tests/test_demo.py"],
|
||||
output_dir=str(tmp_path / "out"),
|
||||
media_assets_root=str(custom_root),
|
||||
)
|
||||
|
||||
assert Path(out).is_file()
|
||||
assert captured.get("media_assets_root") == str(custom_root)
|
||||
|
||||
|
||||
def test_run_screencast_music_subdir_deprecated_warning() -> None:
|
||||
with pytest.warns(DeprecationWarning, match="music_subdir is deprecated"):
|
||||
with patch("screencast.runner.activate_and_maximize_jiangchang_window", return_value=False), patch(
|
||||
"screencast.runner.ScreenRecorder",
|
||||
) as rec_cls, patch("screencast.runner.SubtitleEngine"), patch(
|
||||
"screencast.runner.subprocess.Popen",
|
||||
) as popen_cls, patch(
|
||||
"screencast.runner.compose_video",
|
||||
) as compose_mock:
|
||||
rec = rec_cls.return_value
|
||||
rec.frame_count = 0
|
||||
proc = popen_cls.return_value
|
||||
proc.stdout = iter([])
|
||||
proc.wait.return_value = 0
|
||||
from screencast.composer import ComposeVideoResult
|
||||
|
||||
compose_mock.return_value = ComposeVideoResult("out.mp4")
|
||||
|
||||
run_screencast(
|
||||
skill_slug="demo",
|
||||
subtitle_script=[],
|
||||
pytest_args=["-q"],
|
||||
output_dir=".",
|
||||
music_subdir="calm",
|
||||
)
|
||||
@@ -7,8 +7,8 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from jiangchang_skill_core import config
|
||||
from jiangchang_skill_core.rpa.video_session import RpaVideoSession
|
||||
|
||||
@@ -70,9 +70,10 @@ class TestRpaVideoSession(unittest.TestCase):
|
||||
config.reset_cache()
|
||||
|
||||
@unittest.skipUnless(sys.platform == "win32", "gdigrab desktop capture")
|
||||
@patch("jiangchang_skill_core.rpa.video_session.shutil.which", return_value="ffmpeg")
|
||||
@patch("jiangchang_skill_core.rpa.video_session._resolve_ffmpeg_exe")
|
||||
@patch("jiangchang_skill_core.rpa.video_session.subprocess.Popen")
|
||||
def test_enter_starts_ffmpeg_gdigrab(self, mock_popen: MagicMock, _which: MagicMock) -> None:
|
||||
def test_enter_starts_ffmpeg_gdigrab(self, mock_popen: MagicMock, mock_resolve: MagicMock) -> None:
|
||||
mock_resolve.return_value = Path(r"C:\media\bin\win32-x64\ffmpeg.exe")
|
||||
proc = MagicMock()
|
||||
proc.poll.return_value = None
|
||||
proc.stdin = MagicMock()
|
||||
@@ -96,7 +97,7 @@ class TestRpaVideoSession(unittest.TestCase):
|
||||
|
||||
self.assertTrue(mock_popen.called)
|
||||
cmd = mock_popen.call_args[0][0]
|
||||
self.assertIn("ffmpeg", cmd)
|
||||
self.assertTrue(str(cmd[0]).endswith("ffmpeg.exe"))
|
||||
self.assertIn("gdigrab", cmd)
|
||||
self.assertIn("desktop", cmd)
|
||||
kwargs = mock_popen.call_args[1]
|
||||
@@ -105,8 +106,8 @@ class TestRpaVideoSession(unittest.TestCase):
|
||||
os.environ.pop("OPENCLAW_RECORD_VIDEO", None)
|
||||
config.reset_cache()
|
||||
|
||||
@patch("jiangchang_skill_core.rpa.video_session.shutil.which", return_value=None)
|
||||
def test_ffmpeg_not_found_warning(self, _which: MagicMock) -> None:
|
||||
@patch("jiangchang_skill_core.rpa.video_session._resolve_ffmpeg_exe", return_value=None)
|
||||
def test_ffmpeg_not_found_warning(self, _resolve: MagicMock) -> None:
|
||||
os.environ["OPENCLAW_RECORD_VIDEO"] = "1"
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
|
||||
Reference in New Issue
Block a user