From 0de09613d2bd7f3ba3fd8c9ad9150ff3e7f396d6 Mon Sep 17 00:00:00 2001 From: gahusb Date: Tue, 19 May 2026 04:55:09 +0900 Subject: [PATCH] =?UTF-8?q?feat(music-render):=20providers/local.py=20?= =?UTF-8?q?=E2=80=94=20MusicGen=20client=20(SP-5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NAS music-lab/app/local_provider.py 이식. DB 호출 webhook 변환. MusicGen 호스트는 host.docker.internal:8765 (Windows native). 결과 MP3는 /mnt/nas/webpage/data/music/에 직접 저장. Plan-B-Music Phase 2. Co-Authored-By: Claude Opus 4.7 (1M context) --- services/music-render/providers/local.py | 105 +++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 services/music-render/providers/local.py diff --git a/services/music-render/providers/local.py b/services/music-render/providers/local.py new file mode 100644 index 0000000..afc5650 --- /dev/null +++ b/services/music-render/providers/local.py @@ -0,0 +1,105 @@ +"""Local MusicGen Provider — Windows AI 머신의 native MusicGen 서버(:8765) 호출. + +NAS music-lab/app/local_provider.py 이식. DB 호출만 webhook으로 변환. +""" +from __future__ import annotations + +import logging +import os +import time + +import requests + +from nas_client import webhook_update_task, webhook_add_track + +logger = logging.getLogger(__name__) + +MUSIC_AI_SERVER_URL = os.getenv("MUSIC_AI_SERVER_URL", "") +MUSIC_MEDIA_ROOT = os.getenv("MUSIC_MEDIA_ROOT", "/mnt/nas/webpage/data/music") +MUSIC_MEDIA_BASE = os.getenv("MUSIC_MEDIA_URL_PREFIX", "/media/music") + + +def run_local_generation(task_id: str, params: dict) -> None: + """MusicGen 생성 → /mnt/nas/.../music/{task_id}.mp3 저장 → add_track.""" + try: + webhook_update_task(task_id, "processing", 10, "AI 서버에 연결 중...") + if not MUSIC_AI_SERVER_URL: + webhook_update_task(task_id, "failed", 0, "", error="MUSIC_AI_SERVER_URL 미설정") + return + + webhook_update_task(task_id, "processing", 30, "음악 생성 중...") + resp = requests.post(f"{MUSIC_AI_SERVER_URL}/generate", json=params, timeout=30) + if resp.status_code != 200: + webhook_update_task(task_id, "failed", 0, "", + error=f"AI 서버 오류: {resp.status_code} {resp.text[:200]}") + return + + ai_task_id = resp.json().get("task_id") + if not ai_task_id: + webhook_update_task(task_id, "failed", 0, "", error="AI 서버 응답에 task_id 없음") + return + + remote_url = None + for _ in range(120): + time.sleep(5) + sr = requests.get(f"{MUSIC_AI_SERVER_URL}/status/{ai_task_id}", timeout=10) + sd = sr.json() + st = sd.get("status") + prog = sd.get("progress", 0) + msg = sd.get("message", "음악 생성 중...") + scaled = 30 + int(prog * 0.49) + webhook_update_task(task_id, "processing", scaled, msg) + + if st == "succeeded": + remote_url = sd.get("audio_url") + break + elif st == "failed": + webhook_update_task(task_id, "failed", 0, "", + error=sd.get("error", "AI 서버 생성 실패")) + return + + if not remote_url: + webhook_update_task(task_id, "failed", 0, "", error="AI 서버 타임아웃 (10분)") + return + + webhook_update_task(task_id, "processing", 80, "파일 저장 중...") + filename = f"{task_id}.mp3" + os.makedirs(MUSIC_MEDIA_ROOT, exist_ok=True) + file_path = os.path.join(MUSIC_MEDIA_ROOT, filename) + + dl = requests.get(remote_url, timeout=120, stream=True) + with open(file_path, "wb") as f: + for chunk in dl.iter_content(chunk_size=8192): + f.write(chunk) + + audio_url = f"{MUSIC_MEDIA_BASE}/{filename}" + genre = params.get("genre", "") + moods = params.get("moods", []) + mood_str = moods[0] if moods else "Original" + title = params.get("title") or ( + f"{genre} — {mood_str} Mix" if genre else f"{mood_str} Mix" + ) + + track = { + "title": title, + "genre": genre, + "moods": moods, + "instruments": params.get("instruments", []), + "duration_sec": params.get("duration_sec"), + "bpm": params.get("bpm"), + "key": params.get("key", ""), + "scale": params.get("scale", ""), + "prompt": params.get("prompt", ""), + "audio_url": audio_url, + "file_path": f"/app/data/{filename}", + "task_id": task_id, + "provider": "local", + } + webhook_add_track(task_id, "succeeded", 100, "생성 완료", + audio_url=audio_url, track=track) + + except requests.Timeout: + webhook_update_task(task_id, "failed", 0, "", error="AI 서버 타임아웃") + except Exception as e: + logger.exception("local generation error task=%s", task_id) + webhook_update_task(task_id, "failed", 0, "", error=str(e))