Initial lip-sync service with command backend
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from digital_human_lipsync_service.backends import (
|
||||
LipSyncError,
|
||||
quote_for_shell,
|
||||
render_command_template,
|
||||
run_command_backend,
|
||||
)
|
||||
from digital_human_lipsync_service.config import Settings
|
||||
|
||||
|
||||
def make_settings(tmp_path: Path, command_template: str) -> Settings:
|
||||
output_dir = tmp_path / "outputs"
|
||||
work_dir = tmp_path / "work"
|
||||
output_dir.mkdir()
|
||||
work_dir.mkdir()
|
||||
return Settings(
|
||||
host="127.0.0.1",
|
||||
port=7860,
|
||||
share=False,
|
||||
backend="command",
|
||||
ffmpeg="ffmpeg",
|
||||
output_dir=output_dir,
|
||||
work_dir=work_dir,
|
||||
command_cwd=work_dir,
|
||||
command_template=command_template,
|
||||
command_timeout_seconds=10,
|
||||
concurrency_limit=1,
|
||||
)
|
||||
|
||||
|
||||
class CommandBackendTests(unittest.TestCase):
|
||||
def test_render_command_template_supports_raw_and_quoted_paths(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
settings = make_settings(root, "model --audio {audio} --face {video_q} --out {output_q} --cwd {cwd}")
|
||||
audio = root / "input audio.wav"
|
||||
video = root / "input video.mp4"
|
||||
output = root / "out video.mp4"
|
||||
|
||||
command = render_command_template(settings, audio, video, output)
|
||||
|
||||
self.assertIn(str(audio), command)
|
||||
self.assertIn("input video.mp4", command)
|
||||
self.assertIn("out video.mp4", command)
|
||||
self.assertIn(str(settings.command_cwd), command)
|
||||
|
||||
def test_render_command_template_rejects_unknown_variable(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
settings = make_settings(root, "model --bad {missing}")
|
||||
|
||||
with self.assertRaisesRegex(LipSyncError, "Unknown LIPSYNC_COMMAND_TEMPLATE variable"):
|
||||
render_command_template(settings, root / "a.wav", root / "v.mp4", root / "o.mp4")
|
||||
|
||||
def test_run_command_backend_reports_exit_code_and_stderr_tail(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
script = root / "fail_backend.py"
|
||||
script.write_text(
|
||||
"import sys\n"
|
||||
"sys.stderr.write('api_key=super-secret\\nmodel failed near frame 12\\n')\n"
|
||||
"raise SystemExit(7)\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
settings = make_settings(root, f"{quote_for_shell(sys.executable)} {quote_for_shell(script)}")
|
||||
audio = root / "a.wav"
|
||||
video = root / "v.mp4"
|
||||
output = root / "out.mp4"
|
||||
audio.write_bytes(b"audio")
|
||||
video.write_bytes(b"video")
|
||||
|
||||
with self.assertRaises(LipSyncError) as context:
|
||||
run_command_backend(settings, audio, video, output)
|
||||
|
||||
message = str(context.exception)
|
||||
self.assertIn("exit_code=7", message)
|
||||
self.assertIn("model failed near frame 12", message)
|
||||
self.assertIn("api_key=***", message)
|
||||
self.assertNotIn("super-secret", message)
|
||||
|
||||
def test_run_command_backend_accepts_non_empty_output_file(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
script = root / "ok_backend.py"
|
||||
script.write_text(
|
||||
"import pathlib, sys\n"
|
||||
"pathlib.Path(sys.argv[1]).write_bytes(b'fake mp4')\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
settings = make_settings(root, f"{quote_for_shell(sys.executable)} {quote_for_shell(script)} {{output_q}}")
|
||||
audio = root / "a.wav"
|
||||
video = root / "v.mp4"
|
||||
output = root / "out.mp4"
|
||||
audio.write_bytes(b"audio")
|
||||
video.write_bytes(b"video")
|
||||
|
||||
result = run_command_backend(settings, audio, video, output)
|
||||
|
||||
self.assertEqual(output, result)
|
||||
self.assertGreater(output.stat().st_size, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user