diff --git a/src/lib/server/separator.ts b/src/lib/server/separator.ts new file mode 100644 index 0000000..e769ada --- /dev/null +++ b/src/lib/server/separator.ts @@ -0,0 +1,66 @@ +import { error } from 'console' +import { promises as fs } from 'fs' +import path from 'path' + +const API_URL = process.env.AUDIO_SEPARATOR_API + +/** + * Separates a song into vocals and instrumental tracks using the Python API server. + * @param inputPath Absolute path to the input audio file. + * @param outputDir Directory where the output files (vocals.mp3, instrumental.mp3) will be saved. + */ +export async function separateSong(inputPath: string, outputDir: string) { + const vocalsPath = path.join(outputDir, 'vocals.opus') + const instrumentalPath = path.join(outputDir, 'instrumental.opus') + if (await fs.exists(vocalsPath) && await fs.exists(instrumentalPath)) return + + // Read file and create FormData + const fileBuffer = await fs.readFile(inputPath) + const formData = new FormData() + formData.append('file', new Blob([fileBuffer]), 'input.mp3') + + const res = await fetch(`${API_URL}/separate`, { method: 'POST', body: formData }) + if (!res.ok) throw error(500, '无法连接到分离服务') + const { task_id } = await res.json() + + // Poll for completion + while (true) { + await new Promise(r => setTimeout(r, 1000)) + const statusRes = await fetch(`${API_URL}/status/${task_id}`) + const status = await statusRes.json() + + if (status.status === 'completed') { + // Download results + const downloadStem = async (stem: string, dest: string) => { + const fileRes = await fetch(`${API_URL}/result/${task_id}/${stem}`) + if (!fileRes.ok) throw error(500, `无法下载 ${stem}`) + const buffer = await fileRes.arrayBuffer() + await fs.writeFile(dest, Buffer.from(buffer)) + } + + await downloadStem('vocals', vocalsPath) + await downloadStem('instrumental', instrumentalPath) + + // Clean up task on server + try { + await fetch(`${API_URL}/task/${task_id}`, { method: 'DELETE' }) + } catch (e) { + console.warn(`无法清理任务 ${task_id}:`, e) + } + break + } + if (status.status === 'error') throw error(500, status.error || '分离失败') + if (status.status === 'not_found') throw error(500, '任务丢失') + } +} + +export async function checkAudioSeparator() { + if (!API_URL) return console.warn('未设置音频分离服务地址,跳过连接性检查') + try { + const res = await fetch(API_URL) + if (res.ok) console.log(`音频分离服务连接成功 ${API_URL}`) + else console.warn(`音频分离服务返回状态 ${res.status} at ${API_URL}`) + } catch (e) { + console.error(`无法连接到音频分离服务 ${API_URL}:`, e) + } +} diff --git a/src/lib/server/songs.ts b/src/lib/server/songs.ts index 20a8361..6096224 100644 --- a/src/lib/server/songs.ts +++ b/src/lib/server/songs.ts @@ -8,6 +8,8 @@ import type { ObjectId } from 'mongodb' import '../ext' import { promises as fs } from 'fs' import path from 'path' +import { waitFor } from '../utils' +import { separateSong } from './separator' const CACHE_DIR = path.resolve('static/audio') @@ -171,26 +173,15 @@ export const prepareSong = async (songId: number) => { // 4. Source Separation const taskSeparation = addTask('AI 人声分离') - const vocalsPath = path.join(CACHE_DIR, `${songId}/vocals.mp3`) - - let retries = 0 - // Wait up to 10 minutes (separation can be slow on CPU) - while (retries < 600) { - try { - await fs.access(vocalsPath) - taskSeparation.progress = 1 - break - } catch { - // File doesn't exist yet - await new Promise(r => setTimeout(r, 1000)) - retries++ - // Update progress to show it's alive - if (retries % 5 === 0) taskSeparation.progress = Math.min(0.99, retries / 600) - } - } + const inputPath = path.join(CACHE_DIR, `${songId}/exhigh.mp3`) + const outputDir = path.join(CACHE_DIR, `${songId}`) - if (taskSeparation.progress < 1) { - addTask('警告: AI 人声分离超时,请检查后台脚本').progress = -1 + try { + await separateSong(inputPath, outputDir) + taskSeparation.progress = 1 + } catch (e: any) { + addTask(`错误: ${e.message}`).progress = -1 + // Don't fail the whole process, just this step } state.status = 'done' diff --git a/src/routes/song/[id]/+page.svelte b/src/routes/song/[id]/+page.svelte index 5ccc875..dafeabb 100644 --- a/src/routes/song/[id]/+page.svelte +++ b/src/routes/song/[id]/+page.svelte @@ -32,7 +32,7 @@ })) const totalProgress = state.items.reduce((acc: number, cur: any) => acc + Math.max(0, cur.progress), 0) - progressPercentage = Math.min(100, Math.round((totalProgress / 3) * 100)) + progressPercentage = Math.min(100, Math.round((totalProgress / 4) * 100)) } if (state.status === "done") {