[+] Connect to source separation server

This commit is contained in:
2025-11-22 20:48:45 +08:00
parent ebe322f950
commit 00feb57da9
3 changed files with 77 additions and 20 deletions
+66
View File
@@ -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)
}
}
+10 -19
View File
@@ -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'
+1 -1
View File
@@ -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") {