From abf475433b2bd02eb280ebe188543d1df393fd2a Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 1 May 2026 11:56:41 +0900 Subject: [PATCH] =?UTF-8?q?fix(music-lab):=20xfade=20offset=20=EB=88=84?= =?UTF-8?q?=EC=A0=81=20=EC=98=A4=EC=B0=A8=20=EC=88=98=EC=A0=95=20+=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _build_slideshow_cmd: offset 공식을 `duration_per_image * i - xd * i`로 수정 (누적 전환 오차 제거) - _generate_metadata: genre 빈 문자열일 때 yt_tags에 빈 문자열 삽입 방지 - test: VIDEO_DATA_DIR 패치를 monkeypatch로 교체 (자동 복원 보장) - test: xfade offset 값 검증 테스트 추가 (29.00, 58.00) - test: 미사용 import 제거 (pytest, sqlite3) Co-Authored-By: Claude Sonnet 4.6 --- music-lab/app/video_producer.py | 4 ++-- music-lab/tests/test_video_producer.py | 21 +++++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/music-lab/app/video_producer.py b/music-lab/app/video_producer.py index af2d033..d70e30a 100644 --- a/music-lab/app/video_producer.py +++ b/music-lab/app/video_producer.py @@ -84,7 +84,7 @@ def _build_slideshow_cmd( filter_str = ";".join(filter_parts) prev = "v0" for i in range(1, n): - offset = max(0.0, duration_per_image * i - xd) + offset = max(0.0, duration_per_image * i - xd * i) nxt = "out" if i == n - 1 else f"xf{i}" filter_str += ( f";[{prev}][v{i}]xfade=transition=fade:" @@ -159,7 +159,7 @@ def _generate_metadata(genre: str, moods: list, lyrics: str, target_countries: l start, end = text.find("{"), text.rfind("}") + 1 return json.loads(text[start:end]) except Exception: - return {"yt_title": f"{genre or 'Music'} - Chill Beats", "yt_description": "", "yt_tags": [genre]} + return {"yt_title": f"{genre or 'Music'} - Chill Beats", "yt_description": "", "yt_tags": [genre] if genre else []} def _render_visualizer(track: dict, proj: dict, output_path: str) -> None: diff --git a/music-lab/tests/test_video_producer.py b/music-lab/tests/test_video_producer.py index 30807b2..8826125 100644 --- a/music-lab/tests/test_video_producer.py +++ b/music-lab/tests/test_video_producer.py @@ -1,7 +1,6 @@ # music-lab/tests/test_video_producer.py import os from unittest.mock import patch, MagicMock -import pytest def test_build_visualizer_cmd(): @@ -59,10 +58,9 @@ def test_build_slideshow_cmd_multiple_images(): assert "/tmp/out.mp4" in cmd -def test_produce_video_visualizer_calls_ffmpeg(tmp_db, tmp_path): +def test_produce_video_visualizer_calls_ffmpeg(tmp_db, tmp_path, monkeypatch): """produce_video가 visualizer 포맷으로 FFmpeg를 호출하는지 확인.""" from app.db import init_db, create_video_project - import sqlite3 init_db() @@ -82,7 +80,7 @@ def test_produce_video_visualizer_calls_ffmpeg(tmp_db, tmp_path): create_video_project({"track_id": 1, "format": "visualizer", "target_countries": ["BR"]}) import app.video_producer as vp - vp.VIDEO_DATA_DIR = str(tmp_path / "videos") + monkeypatch.setattr("app.video_producer.VIDEO_DATA_DIR", str(tmp_path / "videos")) with patch("app.video_producer.subprocess.run") as mock_run, \ patch("app.video_producer._generate_metadata", return_value={ @@ -96,3 +94,18 @@ def test_produce_video_visualizer_calls_ffmpeg(tmp_db, tmp_path): proj = get_video_project(1) assert proj["status"] == "done" assert mock_run.called + + +def test_build_slideshow_cmd_offset_calculation(): + from app.video_producer import _build_slideshow_cmd + imgs = ["/tmp/img0.jpg", "/tmp/img1.jpg", "/tmp/img2.jpg"] + cmd = _build_slideshow_cmd(imgs, "/tmp/audio.mp3", "/tmp/out.mp4", duration_per_image=30.0) + # filter_complex 문자열 추출 + fc_idx = cmd.index("-filter_complex") + fc = cmd[fc_idx + 1] + # xfade이 2번 등장해야 함 (이미지 3개 → 전환 2번) + assert fc.count("xfade") == 2 + # 첫 번째 xfade offset: 30*1 - 1*1 = 29.0 + assert "offset=29.00" in fc + # 두 번째 xfade offset: 30*2 - 1*2 = 58.0 + assert "offset=58.00" in fc