Files
web-page-backend/docs/superpowers/specs/2026-04-08-music-lab-suno-enhancement-design.md
gahusb 6ffa04f847 docs: music-lab Suno API 전체 기능 확장 설계 스펙
Suno API 미사용 기능 분석 후 3단계 점진 확장 설계.
Phase 1(생성 강화), Phase 2(후처리), Phase 3(리믹스) 구조.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 03:13:41 +09:00

399 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Music Lab Suno API 전체 기능 확장 설계
> 작성일: 2026-04-08
> 범위: 백엔드 (web-backend/music-lab) + 프론트엔드 (web-ui/src/pages/music)
---
## 1. 목표
Suno API의 미사용 기능을 전부 활용하여 music-lab을 완전한 AI 음악 프로덕션 스튜디오로 업그레이드한다. 실용도 기준 3단계로 점진 확장.
## 2. 단계별 기능 목록
### Phase 1: 핵심 생성 강화
| # | 기능 | 설명 |
|---|------|------|
| 1-1 | 크레딧 잔액 상시 표시 | 헤더에 실시간 크레딧 배지, 10 이하 경고 |
| 1-2 | 보컬 성별 선택 | Male / Female / Auto 3버튼 |
| 1-3 | negativeTags | 제외 스타일 텍스트 + 프리셋 칩 |
| 1-4 | V5_5 모델 추가 | SUNO_MODELS 딕셔너리에 추가 |
| 1-5 | styleWeight / audioWeight | 0~1.0 슬라이더 2개 |
| 1-6 | 커버 이미지 생성 | 라이브러리 카드에서 앨범아트 2장 생성 |
### Phase 2: 후처리 파워업
| # | 기능 | 설명 |
|---|------|------|
| 2-1 | WAV 고음질 변환 | MP3→WAV 변환 다운로드 |
| 2-2 | 12스템 분리 | 드럼/베이스/기타 등 12개 개별 추출 |
| 2-3 | 타임스탬프 가사 | 가라오케 스타일 싱크 재생 |
| 2-4 | 스타일 부스트 | AI로 최적 스타일 프롬프트 자동 생성 |
### Phase 3: 고급 크리에이티브
| # | 기능 | 설명 |
|---|------|------|
| 3-1 | 오디오 업로드 + 커버 | 외부 음원을 Suno 스타일로 리메이크 |
| 3-2 | 오디오 업로드 + 확장 | 외부 음원 이어서 만들기 |
| 3-3 | 보컬 추가 | 인스트루멘탈에 AI 보컬 입히기 |
| 3-4 | 인스트루멘탈 추가 | 보컬에 AI 반주 입히기 |
| 3-5 | 뮤직비디오 생성 | MP4 자동 생성 |
---
## 3. 백엔드 API 설계
### 3.1 기존 엔드포인트 수정
#### GenerateRequest 스키마 확장 (main.py)
```python
class GenerateRequest(BaseModel):
# 기존 필드 유지
provider: str = "suno"
model: str = "V4"
title: str = ""
genre: str = ""
moods: list[str] = []
instruments: list[str] = []
duration_sec: int | None = None
bpm: int | None = None
key: str = ""
scale: str = ""
prompt: str = ""
lyrics: str = ""
instrumental: bool = False
# Phase 1 추가
vocal_gender: str | None = None # "m" | "f" | None(auto)
negative_tags: str | None = None # 제외 스타일
style_weight: float | None = None # 0.0~1.0
audio_weight: float | None = None # 0.0~1.0
```
#### SUNO_MODELS 확장 (suno_provider.py)
```python
SUNO_MODELS = {
"V4": {"name": "V4", "max_duration": 240},
"V4_5": {"name": "V4.5", "max_duration": 480},
"V4_5PLUS": {"name": "V4.5+", "max_duration": 480},
"V4_5ALL": {"name": "V4.5 All","max_duration": 480},
"V5": {"name": "V5", "max_duration": 480},
"V5_5": {"name": "V5.5", "max_duration": 480}, # 추가
}
```
#### _build_suno_payload 확장
새 파라미터를 Suno API 페이로드에 매핑:
- `vocal_gender``vocalGender`
- `negative_tags``negativeTags`
- `style_weight``styleWeight`
- `audio_weight``audioWeight`
None이 아닌 경우에만 페이로드에 포함.
### 3.2 신규 엔드포인트
#### Phase 1
```
POST /api/music/cover-image
Request: { "task_id": str, "suno_id": str }
Response: { "task_id": str } → 폴링 → { "images": [url1, url2] }
```
#### Phase 2
```
POST /api/music/wav
Request: { "task_id": str, "suno_id": str }
Response: { "task_id": str } → 폴링 → { "wav_url": str }
POST /api/music/stem-split
Request: { "task_id": str, "suno_id": str }
Response: { "task_id": str } → 폴링 → { "stems": { vocal: url, drums: url, ... } }
GET /api/music/timestamped-lyrics?task_id=...&suno_id=...
Response: { "aligned_words": [...], "waveform_data": [...] }
POST /api/music/style-boost
Request: { "content": str }
Response: { "result": str, "credits_consumed": float }
```
#### Phase 3
```
POST /api/music/upload-cover
Request: { "upload_url": str, "model": str, "custom_mode": bool,
"instrumental": bool, "prompt"?: str, "style"?: str, "title"?: str,
"vocal_gender"?: str, "negative_tags"?: str,
"style_weight"?: float, "audio_weight"?: float }
Response: { "task_id": str }
POST /api/music/upload-extend
Request: { "upload_url": str, "model": str, "continue_at"?: float,
"default_param_flag": bool, "prompt"?: str, "style"?: str, "title"?: str,
"vocal_gender"?: str, "negative_tags"?: str }
Response: { "task_id": str }
POST /api/music/add-vocals
Request: { "upload_url": str, "prompt": str, "title": str, "style": str,
"negative_tags": str, "vocal_gender"?: str, "model"?: str,
"style_weight"?: float, "audio_weight"?: float }
Response: { "task_id": str }
POST /api/music/add-instrumental
Request: { "upload_url": str, "title": str, "tags": str, "negative_tags": str,
"vocal_gender"?: str, "model"?: str,
"style_weight"?: float, "audio_weight"?: float }
Response: { "task_id": str }
POST /api/music/video
Request: { "task_id": str, "suno_id": str, "author"?: str, "domain_name"?: str }
Response: { "task_id": str } → 폴링 → { "video_url": str }
```
### 3.3 suno_provider.py 리팩토링
**공통 폴링 헬퍼 추출:**
```python
def _poll_suno_task(
record_info_url: str,
task_id: str,
max_attempts: int = 40,
interval: int = 8,
success_extractor: Callable[[dict], Any] = None
) -> dict:
"""
범용 Suno 작업 폴링.
record_info_url: 예) "/api/v1/generate/record-info"
success_extractor: SUCCESS 상태일 때 결과 추출 함수
"""
```
기존 `run_suno_generation`, `run_suno_extend`, `run_vocal_removal`도 이 헬퍼를 사용하도록 리팩토링.
**신규 함수 목록:**
| 함수 | Phase | Suno 엔드포인트 | 비동기 |
|------|-------|----------------|--------|
| `run_cover_image` | 1 | `POST /api/v1/suno/cover/generate` | 폴링 |
| `run_wav_convert` | 2 | `POST /api/v1/wav/generate` | 폴링 |
| `run_stem_split` | 2 | `POST /api/v1/vocal-removal/generate` (type=split_stem) | 폴링 |
| `get_timestamped_lyrics` | 2 | `POST /api/v1/generate/get-timestamped-lyrics` | 동기 |
| `generate_style_boost` | 2 | `POST /api/v1/style/generate` | 동기 |
| `run_upload_cover` | 3 | `POST /api/v1/generate/upload-cover` | 폴링 |
| `run_upload_extend` | 3 | `POST /api/v1/generate/upload-extend` | 폴링 |
| `run_add_vocals` | 3 | `POST /api/v1/generate/add-vocals` | 폴링 |
| `run_add_instrumental` | 3 | `POST /api/v1/generate/add-instrumental` | 폴링 |
| `run_video_generate` | 3 | `POST /api/v1/mp4/generate` | 폴링 |
### 3.4 DB 스키마 변경
**music_library 테이블 컬럼 추가 (ALTER TABLE 마이그레이션):**
```sql
ALTER TABLE music_library ADD COLUMN cover_images TEXT NOT NULL DEFAULT '[]';
ALTER TABLE music_library ADD COLUMN wav_url TEXT NOT NULL DEFAULT '';
ALTER TABLE music_library ADD COLUMN video_url TEXT NOT NULL DEFAULT '';
ALTER TABLE music_library ADD COLUMN stem_urls TEXT NOT NULL DEFAULT '{}';
```
**db.py 함수 추가:**
```python
def update_track_cover_images(track_id: int, images: list[str])
def update_track_wav_url(track_id: int, wav_url: str)
def update_track_video_url(track_id: int, video_url: str)
def update_track_stem_urls(track_id: int, stems: dict)
```
---
## 4. 프론트엔드 UI/UX 설계
### 4.1 파일 구조 (컴포넌트 분할)
```
web-ui/src/pages/music/
├── MusicStudio.jsx -- 메인 (탭 라우팅 + 공유 상태)
├── MusicStudio.css -- 전체 스타일
├── components/
│ ├── CreateTab.jsx -- 생성 폼 (6단계 + Phase 1 확장)
│ ├── LyricsTab.jsx -- 가사 관리
│ ├── LibraryTab.jsx -- 라이브러리 + 카드
│ ├── RemixTab.jsx -- Phase 3: 업로드/리믹스
│ ├── AudioPlayer.jsx -- 오디오 플레이어
│ ├── LibraryCard.jsx -- 트랙 카드 + 액션 메뉴
│ ├── StemModal.jsx -- 12스템 결과 모달
│ ├── CoverArtModal.jsx -- 커버 이미지 선택 모달
│ ├── SyncedLyricsPlayer.jsx -- 타임스탬프 가사 오버레이
│ └── CreditsBadge.jsx -- 크레딧 잔액 배지
```
### 4.2 Phase 1 UI 변경
#### 크레딧 배지 (CreditsBadge)
- 위치: 헤더 우측 상단, 탭 옆
- 표시: `⚡ 127 credits`
- 10 이하: 빨간색 + pulse 애니메이션
- 갱신 시점: 페이지 로드, 생성 완료 후, 30초 자동 갱신
#### Create 탭 Step 4 확장
**Vocal Gender (Suno 전용):**
- 3버튼 토글: `♂ Male` | `♀ Female` | `Auto`
- 기본값: Auto
- 스타일: `.ms-gender-toggle` (기존 duration-rail과 유사)
**Negative Tags:**
- 텍스트 입력 필드 + 프리셋 칩
- 프리셋: screaming, autotune, distortion, whisper, falsetto, rap
- 칩 클릭 시 텍스트에 추가/제거
- 스타일: `.ms-negative-tags` (기존 mood-rack과 유사)
**Style Weight / Audio Weight:**
- range 슬라이더 2개 (기존 BPM 슬라이더와 동일 스타일)
- 레이블: "Prompt ↔ Style Balance" / "Original ↔ AI Balance"
- 0~100 표시, API 전송 시 0.0~1.0 변환
- 기본값: 미설정 (슬라이더 중앙, 값 전송 안 함)
#### Library 카드 액션 메뉴 확장
기존 5개 버튼 → 6개 (Cover Art 추가)
4개 초과 시 `•••` 더보기 드롭다운 메뉴로 분기:
- 기본 노출: Play, Download, Delete
- 더보기: Extend, Vocal Split, Cover Art (+ Phase 2/3 추가분)
#### CoverArtModal
- 2장 이미지 좌우 비교 표시
- 각 이미지 아래 "이 이미지 사용" 버튼
- 선택 시 라이브러리 카드 썸네일 업데이트
### 4.3 Phase 2 UI 변경
#### Library 카드 더보기 메뉴 추가
- WAV 다운로드
- Stem Split (12스템)
- Synced Lyrics
- Style Boost (Create 탭 프롬프트로 전달)
#### StemModal
- 3×4 그리드 카드 레이아웃
- 각 스템: 이름 아이콘 + 미니 재생 버튼 + 다운로드 버튼
- 12개 스템: vocal, backing_vocals, drums, bass, guitar, keyboard, strings, brass, woodwinds, percussion, synth, fx
- 스타일: 기존 라이브러리 카드의 축소 버전
#### SyncedLyricsPlayer
- AudioPlayer 교체/오버레이 모드
- 재생 중 현재 단어를 accent 컬러로 하이라이트
- 하단에 waveformData 기반 파형 바
- 닫기 버튼으로 일반 플레이어 복귀
#### Style Boost 버튼
- Create 탭 장르 선택 영역에 `✨ Style Boost` 버튼
- 클릭 시: 현재 genre + moods 조합 → API 호출 → 결과를 프롬프트에 삽입
- 로딩 중 버튼 스피너
### 4.4 Phase 3 UI 변경
#### Remix 탭 (신규 4번째 탭)
- 탭 레이블: `REMIX`
- 상단: 오디오 URL 입력 필드 (또는 라이브러리에서 선택)
- 4개 액션 카드 그리드 (2×2):
- **AI Cover**: 아이콘 + 설명 + 파라미터 폼 (펼침)
- **Extend**: 아이콘 + 설명 + continue_at 입력
- **Add Vocals**: 아이콘 + 설명 + prompt/style 입력
- **Add Instrumental**: 아이콘 + 설명 + tags 입력
- 선택한 카드만 펼쳐서 세부 옵션 표시
- 하단: 뮤직비디오 생성 버튼 (라이브러리에서 선택한 곡 대상)
### 4.5 디자인 토큰 추가
```css
/* Phase 1 추가 토큰 */
--ms-danger: #e74c3c; /* 크레딧 경고 빨간색 */
--ms-male: #4a9eff; /* 남성 보컬 파란색 */
--ms-female: #ff6b9d; /* 여성 보컬 분홍색 */
```
---
## 5. api.js 추가 함수
```javascript
// Phase 1
export const generateCoverImage = (payload) => apiPost('/api/music/cover-image', payload);
// Phase 2
export const convertToWav = (payload) => apiPost('/api/music/wav', payload);
export const splitStems = (payload) => apiPost('/api/music/stem-split', payload);
export const getTimestampedLyrics = (taskId, sunoId) =>
apiGet(`/api/music/timestamped-lyrics?task_id=${taskId}&suno_id=${sunoId}`);
export const generateStyleBoost = (content) => apiPost('/api/music/style-boost', { content });
// Phase 3
export const uploadAndCover = (payload) => apiPost('/api/music/upload-cover', payload);
export const uploadAndExtend = (payload) => apiPost('/api/music/upload-extend', payload);
export const addVocals = (payload) => apiPost('/api/music/add-vocals', payload);
export const addInstrumental = (payload) => apiPost('/api/music/add-instrumental', payload);
export const generateVideo = (payload) => apiPost('/api/music/video', payload);
```
---
## 6. 폴링 패턴
모든 비동기 작업은 기존 음악 생성과 동일한 폴링 패턴:
1. POST 요청 → `{ task_id }` 반환
2. 프론트: 3초 간격 `GET /api/music/status/{task_id}` 폴링
3. 백엔드: BackgroundTask에서 Suno 폴링 → music_tasks 상태 업데이트
4. `status: succeeded` → 결과 반환 + 라이브러리 자동 갱신
동기 API (타임스탬프 가사, 스타일 부스트)는 즉시 응답.
---
## 7. 구현 순서
### Phase 1 (핵심 생성 강화)
1. 백엔드: SUNO_MODELS에 V5_5 추가 + 공통 폴링 헬퍼 추출
2. 백엔드: GenerateRequest 스키마 확장 + _build_suno_payload 매핑
3. 백엔드: 커버 이미지 생성 엔드포인트 + suno_provider 함수
4. 백엔드: DB 마이그레이션 (cover_images, wav_url, video_url, stem_urls)
5. 프론트: 컴포넌트 분할 (MusicStudio → CreateTab, LyricsTab, LibraryTab, etc.)
6. 프론트: CreditsBadge 구현
7. 프론트: Create 탭 Step 4 확장 (vocal gender, negative tags, weight 슬라이더)
8. 프론트: LibraryCard 더보기 메뉴 + CoverArtModal
9. 프론트: api.js 함수 추가
### Phase 2 (후처리 파워업)
10. 백엔드: WAV/스템/타임스탬프/스타일부스트 엔드포인트
11. 프론트: StemModal + SyncedLyricsPlayer + Style Boost 버튼
12. 프론트: Library 카드 Phase 2 액션 추가
### Phase 3 (고급 크리에이티브)
13. 백엔드: upload-cover/upload-extend/add-vocals/add-instrumental/video 엔드포인트
14. 프론트: RemixTab 구현
15. 프론트: Library 카드 Phase 3 액션 (Video)
---
## 8. 제약사항 및 주의점
- **Suno 파일 보관**: 14~15일 후 자동 삭제 → 로컬 다운로드 필수 (기존 패턴 유지)
- **동시 요청 제한**: 10초당 20건 → 배치 작업 시 rate limiting 고려
- **12스템 분리 비용**: 50크레딧 → UI에 비용 경고 표시
- **WAV 중복 변환**: 409 에러 → 이미 변환된 경우 기존 URL 반환
- **뮤직비디오**: taskId + audioId 필요 → 라이브러리에 task_id 저장 필수
- **V5_5 모델**: 커스텀 모델 → 문서상 제한사항 추가 확인 필요
- **크레딧 조회 엔드포인트**: 기존 `/api/v1/get-credits` vs 문서 `/api/v1/generate/credit` → 두 엔드포인트 폴백 시도
- **upload 계열 API**: upload_url은 외부 접근 가능한 URL이어야 함 → 로컬 파일은 NAS nginx URL로 변환