[+] Connect to source separation server
This commit is contained in:
@@ -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
@@ -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'
|
||||
|
||||
@@ -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") {
|
||||
|
||||
Reference in New Issue
Block a user