Code review found: non-200 response from /audio/ endpoint was silently written as MP3 body → corrupt file. Match T5 suno.py download pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
107 lines
4.1 KiB
Python
107 lines
4.1 KiB
Python
"""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)
|
|
dl.raise_for_status()
|
|
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))
|