music-lab: Suno API + MusicGen 듀얼 프로바이더 구조 구현
- suno_provider.py: Suno REST API 클라이언트 (곡 생성, 가사, 2변형 저장) - local_provider.py: 기존 MusicGen 로직 분리 - main.py: provider 라우팅, /providers·/lyrics 엔드포인트 추가 - db.py: provider, lyrics, image_url, suno_id 컬럼 마이그레이션 - docker-compose.yml: SUNO_API_KEY 환경변수 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
122
music-lab/app/local_provider.py
Normal file
122
music-lab/app/local_provider.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""
|
||||
Local MusicGen Provider — Windows AI 서버(MusicGen)를 통한 음악 생성
|
||||
기존 _run_generation 로직을 그대로 분리.
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from .db import update_task, add_track
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MUSIC_AI_SERVER_URL = os.getenv("MUSIC_AI_SERVER_URL", "")
|
||||
MUSIC_DATA_DIR = "/app/data/music"
|
||||
MUSIC_MEDIA_BASE = os.getenv("MUSIC_MEDIA_BASE", "/media/music")
|
||||
|
||||
|
||||
def run_local_generation(task_id: str, params: dict) -> None:
|
||||
"""BackgroundTask: Windows AI 서버(MusicGen)에 생성 요청 → 파일 저장 → 라이브러리 등록"""
|
||||
try:
|
||||
update_task(task_id, "processing", 10, "AI 서버에 연결 중...")
|
||||
|
||||
if not MUSIC_AI_SERVER_URL:
|
||||
update_task(task_id, "failed", 0, "",
|
||||
error="MUSIC_AI_SERVER_URL이 설정되지 않았습니다")
|
||||
return
|
||||
|
||||
update_task(task_id, "processing", 30, "음악 생성 중... (수 분 소요될 수 있습니다)")
|
||||
|
||||
# 1단계: 생성 요청 → ai_task_id 반환
|
||||
resp = requests.post(
|
||||
f"{MUSIC_AI_SERVER_URL}/generate",
|
||||
json=params,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
if resp.status_code != 200:
|
||||
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:
|
||||
update_task(task_id, "failed", 0, "",
|
||||
error="AI 서버 응답에 task_id가 없습니다")
|
||||
return
|
||||
|
||||
# 2단계: 상태 폴링 (최대 10분, 5초 간격)
|
||||
remote_url = None
|
||||
for _ in range(120):
|
||||
time.sleep(5)
|
||||
status_resp = requests.get(
|
||||
f"{MUSIC_AI_SERVER_URL}/status/{ai_task_id}", timeout=10,
|
||||
)
|
||||
status_data = status_resp.json()
|
||||
ai_status = status_data.get("status")
|
||||
|
||||
ai_progress = status_data.get("progress", 0)
|
||||
ai_message = status_data.get("message", "음악 생성 중...")
|
||||
scaled = 30 + int(ai_progress * 0.49) # 30% ~ 79%
|
||||
update_task(task_id, "processing", scaled, ai_message)
|
||||
|
||||
if ai_status == "succeeded":
|
||||
remote_url = status_data.get("audio_url")
|
||||
break
|
||||
elif ai_status == "failed":
|
||||
update_task(task_id, "failed", 0, "",
|
||||
error=status_data.get("error", "AI 서버 생성 실패"))
|
||||
return
|
||||
|
||||
if not remote_url:
|
||||
update_task(task_id, "failed", 0, "",
|
||||
error="AI 서버 타임아웃 (10분 초과)")
|
||||
return
|
||||
|
||||
update_task(task_id, "processing", 80, "파일 저장 중...")
|
||||
|
||||
filename = f"{task_id}.mp3"
|
||||
file_path = os.path.join(MUSIC_DATA_DIR, filename)
|
||||
|
||||
# 3단계: 오디오 파일 다운로드
|
||||
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"
|
||||
)
|
||||
|
||||
add_track({
|
||||
"title": title,
|
||||
"genre": genre,
|
||||
"moods": params.get("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": file_path,
|
||||
"task_id": task_id,
|
||||
"provider": "local",
|
||||
})
|
||||
|
||||
update_task(task_id, "succeeded", 100, "생성 완료", audio_url=audio_url)
|
||||
|
||||
except requests.Timeout:
|
||||
update_task(task_id, "failed", 0, "",
|
||||
error="AI 서버 타임아웃 (10분 초과)")
|
||||
except Exception as e:
|
||||
logger.exception("Local generation error for task %s", task_id)
|
||||
update_task(task_id, "failed", 0, "", error=str(e))
|
||||
Reference in New Issue
Block a user