diff --git a/music-lab/app/pipeline/orchestrator.py b/music-lab/app/pipeline/orchestrator.py index ce2a5ff..ec27ed2 100644 --- a/music-lab/app/pipeline/orchestrator.py +++ b/music-lab/app/pipeline/orchestrator.py @@ -202,12 +202,24 @@ async def _run_video(p, ctx): vd = setup["visual_defaults"] audio_path = ctx["audio_path"] cover_path = _local_path(p["cover_url"]) + + style = p.get("visual_style") or vd.get("default_visual_style", "essential") + bg_mode = p.get("background_mode") or vd.get("default_background_mode", "static") + bg_path = None + if bg_mode == "video_loop": + loop_local = os.path.join(storage.pipeline_dir(p["id"]), "loop.mp4") + bg_path = loop_local if os.path.isfile(loop_local) else None + out = await asyncio.to_thread( video.generate, pipeline_id=p["id"], audio_path=audio_path, cover_path=cover_path, genre=ctx["genre"], duration_sec=ctx["duration_sec"], - resolution=vd["resolution"], style=vd["style"], + resolution=vd.get("resolution", "1920x1080"), + style=style, + background_mode=bg_mode, + background_path=bg_path, + tracks=ctx["tracks"] if len(ctx["tracks"]) > 1 else None, ) return {"next_state": "video_pending", "fields": {"video_url": out["url"]}} @@ -230,6 +242,7 @@ async def _run_meta(p, ctx, feedback): "duration_sec": ctx["duration_sec"], "moods": ctx["moods"]}, template=setup["metadata_template"], trend_keywords=trend_top, feedback=feedback, + tracks=ctx["tracks"] if len(ctx["tracks"]) > 1 else None, ) return {"next_state": "meta_pending", "fields": {"metadata_json": json.dumps(out, ensure_ascii=False)}} diff --git a/music-lab/app/pipeline/video.py b/music-lab/app/pipeline/video.py index e3d06f7..c7b858b 100644 --- a/music-lab/app/pipeline/video.py +++ b/music-lab/app/pipeline/video.py @@ -24,7 +24,10 @@ class VideoGenerationError(Exception): def generate(*, pipeline_id: int, audio_path: str, cover_path: str, genre: str, duration_sec: int, resolution: str = "1920x1080", - style: str = "visualizer") -> dict: + style: str = "essential", + background_mode: str = "static", + background_path: str | None = None, + tracks: list[dict] | None = None) -> dict: """원격 Windows GPU 서버 호출. 다운/실패 시 즉시 예외.""" if not ENCODER_URL: raise VideoGenerationError( @@ -35,6 +38,7 @@ def generate(*, pipeline_id: int, audio_path: str, cover_path: str, nas_audio = _container_to_nas(audio_path) nas_cover = _container_to_nas(cover_path) nas_output = _container_to_nas(out_path) + nas_bg = _container_to_nas(background_path) if background_path else None payload = { "cover_path_nas": nas_cover, @@ -43,9 +47,13 @@ def generate(*, pipeline_id: int, audio_path: str, cover_path: str, "resolution": resolution, "duration_sec": duration_sec, "style": style, + "background_mode": background_mode, + "background_path_nas": nas_bg, + "tracks": tracks or [], } - logger.info("Windows 인코더 호출: pipeline=%d audio=%s", pipeline_id, audio_path) + logger.info("Windows 인코더 호출: pipeline=%d audio=%s style=%s bg_mode=%s", + pipeline_id, audio_path, style, background_mode) try: with httpx.Client(timeout=ENCODER_TIMEOUT_S) as client: resp = client.post(f"{ENCODER_URL}/encode_video", json=payload) diff --git a/music-lab/tests/test_video_thumb.py b/music-lab/tests/test_video_thumb.py index 19ae980..266ea99 100644 --- a/music-lab/tests/test_video_thumb.py +++ b/music-lab/tests/test_video_thumb.py @@ -130,3 +130,33 @@ def test_container_to_nas_music_path(monkeypatch): monkeypatch.setattr(video, "NAS_VIDEOS_ROOT", "/volume1/docker/webpage/data/videos") monkeypatch.setattr(video, "NAS_MUSIC_ROOT", "/volume1/docker/webpage/data/music") assert video._container_to_nas("/app/data/abc.mp3") == "/volume1/docker/webpage/data/music/abc.mp3" + + +@respx.mock +def test_generate_video_passes_essential_params(encoder_env, tmp_path, monkeypatch): + monkeypatch.setattr(storage, "VIDEO_DATA_DIR", str(tmp_path)) + captured = {} + + def hook(req): + import json as _json + captured["body"] = _json.loads(req.content) + return Response(200, json={"ok": True, "duration_ms": 5000, + "output_path_nas": "/v/3/video.mp4", + "output_bytes": 10_000_000, + "encoder": "h264_nvenc", "preset": "p4"}) + + respx.post("http://192.168.45.59:8765/encode_video").mock(side_effect=hook) + out = video.generate( + pipeline_id=3, audio_path="/app/data/x.mp3", + cover_path="/app/data/videos/3/cover.jpg", + genre="mix", duration_sec=3600, resolution="1920x1080", + style="essential", background_mode="video_loop", + background_path="/app/data/videos/3/loop.mp4", + tracks=[{"id": 1, "title": "T1", "start_offset_sec": 0}], + ) + body = captured["body"] + assert body["style"] == "essential" + assert body["background_mode"] == "video_loop" + assert body["background_path_nas"] == "/volume1/docker/webpage/data/videos/3/loop.mp4" + assert body["tracks"][0]["title"] == "T1" + assert out["url"].endswith("/3/video.mp4")