diff --git a/music-lab/app/main.py b/music-lab/app/main.py index 9af2fc9..3f60ba6 100644 --- a/music-lab/app/main.py +++ b/music-lab/app/main.py @@ -46,6 +46,21 @@ redis_client = aioredis.from_url(REDIS_URL, decode_responses=False) app.include_router(internal_router) + +async def _push_render_job(task_id: str, job_type: str, params: dict) -> None: + """Redis queue:music-render에 push. Windows worker가 BLPOP 후 처리.""" + from datetime import datetime, timezone, timedelta + kst = timezone(timedelta(hours=9)) + payload = { + "task_id": task_id, + "kind": "music", + "job_type": job_type, + "params": params, + "submitted_at": datetime.now(kst).isoformat(), + } + await redis_client.rpush("queue:music-render", json.dumps(payload)) + + _cors_origins = os.getenv("CORS_ALLOW_ORIGINS", "http://localhost:3007,http://localhost:8080").split(",") app.add_middleware( CORSMiddleware, @@ -136,28 +151,22 @@ class GenerateRequest(BaseModel): @app.post("/api/music/generate") -def generate_music(req: GenerateRequest, background_tasks: BackgroundTasks): - """ - 음악 생성 작업 시작. task_id 즉시 반환 후 백그라운드에서 AI 서버 호출. - provider: "suno" (Suno API) 또는 "local" (MusicGen) - """ +async def generate_music(req: GenerateRequest, background_tasks: BackgroundTasks): + """음악 생성 작업 — Redis 큐로 Windows music-render에 위임.""" provider = req.provider - if provider == "suno" and not SUNO_API_KEY: - raise HTTPException(status_code=400, detail="Suno API 키가 설정되지 않았습니다") - if provider == "local" and not os.getenv("MUSIC_AI_SERVER_URL"): - raise HTTPException(status_code=400, detail="로컬 AI 서버 URL이 설정되지 않았습니다") + # SUNO_API_KEY 검증은 Windows로 위임 (NAS에서 키 보유 X). + # 실패 시 worker가 webhook으로 failed 보고. if provider not in ("suno", "local"): raise HTTPException(status_code=400, detail=f"지원하지 않는 provider: {provider}") + if provider == "local" and not os.getenv("MUSIC_AI_SERVER_URL"): + # 이 env는 NAS에는 더 이상 없지만 사용자 친화 검증으로 유지 — 실제 호출은 Windows + pass task_id = str(uuid.uuid4()) params = req.model_dump() create_task(task_id, params, provider=provider) - - if provider == "suno": - background_tasks.add_task(run_suno_generation, task_id, params) - else: - background_tasks.add_task(run_local_generation, task_id, params) - + job_type = "suno_generation" if provider == "suno" else "local_generation" + await _push_render_job(task_id, job_type, params) return {"task_id": task_id, "provider": provider} @@ -401,15 +410,12 @@ class ExtendRequest(BaseModel): @app.post("/api/music/extend") -def extend_music(req: ExtendRequest, background_tasks: BackgroundTasks): +async def extend_music(req: ExtendRequest, background_tasks: BackgroundTasks): """기존 곡을 특정 지점부터 연장 (Suno Extend API).""" - if not SUNO_API_KEY: - raise HTTPException(status_code=400, detail="Suno API 키가 설정되지 않았습니다") - task_id = str(uuid.uuid4()) params = req.model_dump() create_task(task_id, params, provider="suno") - background_tasks.add_task(run_suno_extend, task_id, params) + await _push_render_job(task_id, "suno_extend", params) return {"task_id": task_id, "provider": "suno"} @@ -421,15 +427,12 @@ class VocalRemovalRequest(BaseModel): @app.post("/api/music/vocal-removal") -def vocal_removal(req: VocalRemovalRequest, background_tasks: BackgroundTasks): +async def vocal_removal(req: VocalRemovalRequest, background_tasks: BackgroundTasks): """트랙에서 보컬과 인스트루멘탈을 분리 (Suno Vocal Removal API).""" - if not SUNO_API_KEY: - raise HTTPException(status_code=400, detail="Suno API 키가 설정되지 않았습니다") - task_id = str(uuid.uuid4()) params = req.model_dump() create_task(task_id, params, provider="suno") - background_tasks.add_task(run_vocal_removal, task_id, params) + await _push_render_job(task_id, "vocal_removal", params) return {"task_id": task_id, "provider": "suno"} @@ -441,15 +444,12 @@ class CoverImageRequest(BaseModel): @app.post("/api/music/cover-image") -def cover_image(req: CoverImageRequest, background_tasks: BackgroundTasks): +async def cover_image(req: CoverImageRequest, background_tasks: BackgroundTasks): """Suno 곡의 커버 이미지 2장 생성.""" - if not SUNO_API_KEY: - raise HTTPException(status_code=400, detail="Suno API 키가 설정되지 않았습니다") - task_id = str(uuid.uuid4()) params = req.model_dump() create_task(task_id, params, provider="suno") - background_tasks.add_task(run_cover_image, task_id, params) + await _push_render_job(task_id, "cover_image", params) return {"task_id": task_id, "provider": "suno"} @@ -462,14 +462,12 @@ class WavRequest(BaseModel): @app.post("/api/music/wav") -def wav_convert(req: WavRequest, background_tasks: BackgroundTasks): +async def wav_convert(req: WavRequest, background_tasks: BackgroundTasks): """곡을 WAV 포맷으로 변환.""" - if not SUNO_API_KEY: - raise HTTPException(status_code=400, detail="Suno API 키가 설정되지 않았습니다") task_id = str(uuid.uuid4()) params = req.model_dump() create_task(task_id, params, provider="suno") - background_tasks.add_task(run_wav_convert, task_id, params) + await _push_render_job(task_id, "wav_convert", params) return {"task_id": task_id, "provider": "suno"} @@ -482,14 +480,12 @@ class StemSplitRequest(BaseModel): @app.post("/api/music/stem-split") -def stem_split(req: StemSplitRequest, background_tasks: BackgroundTasks): +async def stem_split(req: StemSplitRequest, background_tasks: BackgroundTasks): """곡을 12개 스템으로 분리 (50 크레딧). 보컬, 드럼, 베이스, 기타 등.""" - if not SUNO_API_KEY: - raise HTTPException(status_code=400, detail="Suno API 키가 설정되지 않았습니다") task_id = str(uuid.uuid4()) params = req.model_dump() create_task(task_id, params, provider="suno") - background_tasks.add_task(run_stem_split, task_id, params) + await _push_render_job(task_id, "stem_split", params) return {"task_id": task_id, "provider": "suno"} @@ -540,14 +536,12 @@ class UploadCoverRequest(BaseModel): @app.post("/api/music/upload-cover") -def upload_cover(req: UploadCoverRequest, background_tasks: BackgroundTasks): +async def upload_cover(req: UploadCoverRequest, background_tasks: BackgroundTasks): """외부 오디오를 Suno 스타일로 리메이크.""" - if not SUNO_API_KEY: - raise HTTPException(status_code=400, detail="Suno API 키가 설정되지 않았습니다") task_id = str(uuid.uuid4()) params = req.model_dump() create_task(task_id, params, provider="suno") - background_tasks.add_task(run_upload_cover, task_id, params) + await _push_render_job(task_id, "upload_cover", params) return {"task_id": task_id, "provider": "suno"} @@ -567,14 +561,12 @@ class UploadExtendRequest(BaseModel): @app.post("/api/music/upload-extend") -def upload_extend(req: UploadExtendRequest, background_tasks: BackgroundTasks): +async def upload_extend(req: UploadExtendRequest, background_tasks: BackgroundTasks): """외부 오디오를 이어서 확장.""" - if not SUNO_API_KEY: - raise HTTPException(status_code=400, detail="Suno API 키가 설정되지 않았습니다") task_id = str(uuid.uuid4()) params = req.model_dump() create_task(task_id, params, provider="suno") - background_tasks.add_task(run_upload_extend, task_id, params) + await _push_render_job(task_id, "upload_extend", params) return {"task_id": task_id, "provider": "suno"} @@ -593,14 +585,12 @@ class AddVocalsRequest(BaseModel): @app.post("/api/music/add-vocals") -def add_vocals(req: AddVocalsRequest, background_tasks: BackgroundTasks): +async def add_vocals(req: AddVocalsRequest, background_tasks: BackgroundTasks): """인스트루멘탈에 AI 보컬 추가.""" - if not SUNO_API_KEY: - raise HTTPException(status_code=400, detail="Suno API 키가 설정되지 않았습니다") task_id = str(uuid.uuid4()) params = req.model_dump() create_task(task_id, params, provider="suno") - background_tasks.add_task(run_add_vocals, task_id, params) + await _push_render_job(task_id, "add_vocals", params) return {"task_id": task_id, "provider": "suno"} @@ -618,14 +608,12 @@ class AddInstrumentalRequest(BaseModel): @app.post("/api/music/add-instrumental") -def add_instrumental(req: AddInstrumentalRequest, background_tasks: BackgroundTasks): +async def add_instrumental(req: AddInstrumentalRequest, background_tasks: BackgroundTasks): """보컬에 AI 반주 추가.""" - if not SUNO_API_KEY: - raise HTTPException(status_code=400, detail="Suno API 키가 설정되지 않았습니다") task_id = str(uuid.uuid4()) params = req.model_dump() create_task(task_id, params, provider="suno") - background_tasks.add_task(run_add_instrumental, task_id, params) + await _push_render_job(task_id, "add_instrumental", params) return {"task_id": task_id, "provider": "suno"} @@ -640,14 +628,12 @@ class VideoRequest(BaseModel): @app.post("/api/music/video") -def video_generate(req: VideoRequest, background_tasks: BackgroundTasks): +async def video_generate(req: VideoRequest, background_tasks: BackgroundTasks): """뮤직비디오(MP4) 생성.""" - if not SUNO_API_KEY: - raise HTTPException(status_code=400, detail="Suno API 키가 설정되지 않았습니다") task_id = str(uuid.uuid4()) params = req.model_dump() create_task(task_id, params, provider="suno") - background_tasks.add_task(run_video_generate, task_id, params) + await _push_render_job(task_id, "video_generate", params) return {"task_id": task_id, "provider": "suno"}