diff --git a/src/api.js b/src/api.js
index a3de257..d0a2645 100644
--- a/src/api.js
+++ b/src/api.js
@@ -656,7 +656,11 @@ export const exportCompileJob = (id) => apiGet(`/api/music/compile/${id}/expo
// --- Music Pipeline ---
export const listPipelines = (status='all') => apiGet(`/api/music/pipeline?status=${status}`);
export const getPipeline = (id) => apiGet(`/api/music/pipeline/${id}`);
-export const createPipeline = (track_id) => apiPost('/api/music/pipeline', { track_id });
+export const createPipeline = (payload) => {
+ // 옛 호출 호환: createPipeline(13) → { track_id: 13 }
+ if (typeof payload === 'number') payload = { track_id: payload };
+ return apiPost('/api/music/pipeline', payload);
+};
export const startPipeline = (id) => apiPost(`/api/music/pipeline/${id}/start`);
export const cancelPipeline = (id) => apiPost(`/api/music/pipeline/${id}/cancel`);
export const publishPipeline = (id) => apiPost(`/api/music/pipeline/${id}/publish`);
diff --git a/src/pages/music/MusicStudio.css b/src/pages/music/MusicStudio.css
index b709d2c..eae6235 100644
--- a/src/pages/music/MusicStudio.css
+++ b/src/pages/music/MusicStudio.css
@@ -3317,3 +3317,16 @@
.modal-actions button { padding:6px 14px; background:rgba(255,255,255,.05); color:var(--ms-text, #f0f0f5);
border:1px solid var(--ms-line, #2a2a3a); border-radius:8px; cursor:pointer; font-size:13px; }
.modal-actions .button.primary { background:rgba(56,189,248,.2); color:#bae6fd; border-color:rgba(56,189,248,.4); }
+
+/* ── CompileTab → Pipeline 영상 만들기 버튼 ─────────────────────── */
+.cmp-btn-video {
+ padding: 6px 12px;
+ background: rgba(56, 189, 248, 0.15);
+ color: #bae6fd;
+ border: 1px solid rgba(56, 189, 248, 0.4);
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 12px;
+ margin-left: 6px;
+}
+.cmp-btn-video:hover { background: rgba(56, 189, 248, 0.25); }
diff --git a/src/pages/music/components/CompileTab.jsx b/src/pages/music/components/CompileTab.jsx
index d709d12..3e54d3c 100644
--- a/src/pages/music/components/CompileTab.jsx
+++ b/src/pages/music/components/CompileTab.jsx
@@ -1,6 +1,7 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import {
createCompileJob, getCompileJobs, deleteCompileJob, exportCompileJob,
+ createPipeline, startPipeline,
} from '../../../api';
const fmtDuration = (sec) => {
@@ -10,7 +11,7 @@ const fmtDuration = (sec) => {
return `${m}분 ${s}초`;
};
-export default function CompileTab({ library }) {
+export default function CompileTab({ library, onSwitchToPipeline }) {
const [jobs, setJobs] = useState([]);
const [selected, setSelected] = useState([]); // [{id, title, audio_url}] in order
const [crossfade, setCrossfade] = useState(3);
@@ -89,6 +90,19 @@ export default function CompileTab({ library }) {
if (exportData && exportData.id === jobId) setExportData(null);
};
+ const handleVideoFromCompile = async (jobId) => {
+ if (!window.confirm('이 mix로 영상 파이프라인을 시작할까요?')) return;
+ try {
+ const p = await createPipeline({ compile_job_id: jobId });
+ await startPipeline(p.id);
+ if (onSwitchToPipeline) {
+ onSwitchToPipeline(p.id);
+ }
+ } catch (e) {
+ alert(`파이프라인 시작 실패: ${e.message || e}`);
+ }
+ };
+
const totalMin = selected.length > 0
? Math.round(selected.reduce((acc, t) => {
const match = library.find(l => l.id === t.id);
@@ -215,6 +229,12 @@ export default function CompileTab({ library }) {
>
{exportingId === j.id ? '...' : '↓ 내보내기'}
+
>
)}
{j.status === 'failed' && (
diff --git a/src/pages/music/components/YoutubeTab.jsx b/src/pages/music/components/YoutubeTab.jsx
index 9aa8f46..44d5d61 100644
--- a/src/pages/music/components/YoutubeTab.jsx
+++ b/src/pages/music/components/YoutubeTab.jsx
@@ -49,7 +49,12 @@ export default function YoutubeTab({ library, initialTrackId, onClearInitialTrac
onClearInitialTrack={onClearInitialTrack}
/>
)}
- {subtab === 'compile' && }
+ {subtab === 'compile' && (
+ setSubtab('pipeline')}
+ />
+ )}
{subtab === 'trends' && }
{subtab === 'revenue' && }
{subtab === 'setup' && }