Compare commits

..
2 Commits
3 changed files with 50 additions and 125 deletions
+40 -48
View File
@@ -857,48 +857,14 @@
} }
async function _generateSingleTTS(text, reference_id) { async function _generateSingleTTS(text, reference_id) {
try { const res = await fetch('/api/tts/generate', {
// Client-side fetch to use local browser's proxy method: 'POST',
const fishRes = await fetch('https://api.fish.audio/v1/tts', { headers: { 'Content-Type': 'application/json' },
method: 'POST', body: JSON.stringify({ text, reference_id, model: TTS_MODEL })
headers: { });
'Authorization': 'Bearer 233f3a9bcc204b20a1979d59d33cc649', const data = await res.json();
'Content-Type': 'application/json' if (!data.success) throw new Error(data.message);
}, return data;
body: JSON.stringify({
text: text,
reference_id: reference_id,
model: TTS_MODEL,
format: "wav"
})
});
if (!fishRes.ok) {
let msg = 'Fish Audio API Error';
try { msg = await fishRes.text(); } catch(e){}
throw new Error(`[${fishRes.status}] ${msg}`);
}
const audioBlob = await fishRes.blob();
// Upload the blob to the server as a temporary file
const formData = new FormData();
formData.append('audio', audioBlob, 'temp.wav');
const uploadRes = await fetch('/api/tts/upload_temp', {
method: 'POST',
body: formData
});
const data = await uploadRes.json();
if (!data.success) throw new Error(data.message);
return data;
} catch (err) {
if (err.name === 'TypeError' && err.message === 'Failed to fetch') {
throw new Error('网络请求失败或跨域被拦截(CORS)。如果启用了本地代理,请确保浏览器能够通过代理正常访问外网接口。');
}
throw err;
}
} }
async function _importSingleAudio(code, zhTemp, jaTemp) { async function _importSingleAudio(code, zhTemp, jaTemp) {
@@ -1175,20 +1141,46 @@
status.textContent = "Generating Chinese audio..."; status.textContent = "Generating Chinese audio...";
try { try {
// Generate Chinese via Client-Side Fetch // Generate Chinese via Server-Side Proxy
const dataZh = await _generateSingleTTS(zhText, ZH_REFERENCE_ID); const resZh = await fetch('/api/tts/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
text: zhText,
reference_id: ZH_REFERENCE_ID,
model: TTS_MODEL
})
});
const dataZh = await resZh.json();
if (!dataZh.success) throw new Error("ZH TTS failed: " + dataZh.message);
status.textContent = "Generating Japanese audio..."; status.textContent = "Generating Japanese audio...";
// Generate Japanese via Client-Side Fetch // Generate Japanese via Server-Side Proxy
const dataJa = await _generateSingleTTS(jaText, JA_REFERENCE_ID); const resJa = await fetch('/api/tts/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
text: jaText,
reference_id: JA_REFERENCE_ID,
model: TTS_MODEL
})
});
const dataJa = await resJa.json();
if (!dataJa.success) throw new Error("JA TTS failed: " + dataJa.message);
// Success // Success
zhTempFile = dataZh.tempName; zhTempFile = dataZh.tempName;
jaTempFile = dataJa.tempName; jaTempFile = dataJa.jaTempName || dataJa.enTempName || dataJa.tempName;
document.getElementById('zh-audio').src = dataZh.url; document.getElementById('zh-audio').src = dataZh.url;
document.getElementById('en-audio').src = dataJa.url; document.getElementById('en-audio').src = dataJa.jaUrl || dataJa.enUrl || dataJa.url;
document.getElementById('preview-section').style.display = 'block'; document.getElementById('preview-section').style.display = 'block';
status.textContent = "Generation complete!"; status.textContent = "Generation complete!";
+5 -54
View File
@@ -322,51 +322,6 @@
.replaceAll("'", "'"); .replaceAll("'", "'");
} }
async function _generateTTSLocal(text, reference_id) {
try {
// Client-side fetch to use local browser's proxy
const fishRes = await fetch('https://api.fish.audio/v1/tts', {
method: 'POST',
headers: {
'Authorization': 'Bearer 233f3a9bcc204b20a1979d59d33cc649',
'Content-Type': 'application/json'
},
body: JSON.stringify({
text: text,
reference_id: reference_id,
model: TTS_MODEL,
format: "wav"
})
});
if (!fishRes.ok) {
let msg = 'Fish Audio API Error';
try { msg = await fishRes.text(); } catch(e){}
throw new Error(`[${fishRes.status}] ${msg}`);
}
const audioBlob = await fishRes.blob();
// Upload the blob to the server as a temporary file
const formData = new FormData();
formData.append('audio', audioBlob, 'temp.wav');
const uploadRes = await fetch('/api/tts/upload_temp', {
method: 'POST',
body: formData
});
const data = await uploadRes.json();
if (!data.success) throw new Error(data.message);
return data;
} catch (err) {
if (err.name === 'TypeError' && err.message === 'Failed to fetch') {
throw new Error('网络请求失败或跨域被拦截。如果启用了本地代理,请确保浏览器能访问外网接口。');
}
throw err;
}
}
async function composeTempBroadcast() { async function composeTempBroadcast() {
const text = document.getElementById("tb_text").value.trim(); const text = document.getElementById("tb_text").value.trim();
if (!text) { if (!text) {
@@ -376,24 +331,20 @@
document.getElementById("btn_publish").disabled = true; document.getElementById("btn_publish").disabled = true;
document.getElementById("btn_compose").disabled = true; document.getElementById("btn_compose").disabled = true;
document.getElementById("build_badge").textContent = "合成中…"; document.getElementById("build_badge").textContent = "合成中…";
setStatus("合成临时广播中(1/2: 生成语音)…"); setStatus("合成临时广播中…");
try { try {
// 1. 本地生成 TTS 并上传获得临时文件名 const resp = await fetch("/api/temp-broadcast/compose", {
const ttsData = await _generateTTSLocal(text, ZH_REFERENCE_ID);
setStatus("合成临时广播中(2/2: 服务端混音)…");
// 2. 告诉服务端进行混音合并
const resp = await fetch("/api/temp-broadcast/build", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({
contentTempName: ttsData.tempName, text,
reference_id: ZH_REFERENCE_ID,
model: TTS_MODEL,
prefixName: "lsts" prefixName: "lsts"
}) })
}); });
const data = await resp.json(); const data = await resp.json();
if (!data || !data.success) throw new Error(data?.message || "合成失败"); if (!data || !data.success) throw new Error(data?.message || "合成失败");
const preview = document.getElementById("preview_area"); const preview = document.getElementById("preview_area");
preview.style.display = "block"; preview.style.display = "block";
state.broadcastId = data.broadcastId; state.broadcastId = data.broadcastId;
+5 -23
View File
@@ -19,6 +19,7 @@ const PORT = Number(process.env.PORT || '8999');
const DEFAULT_TTS_MODEL = process.env.TTS_MODEL || 's2.1-pro-free'; const DEFAULT_TTS_MODEL = process.env.TTS_MODEL || 's2.1-pro-free';
const DEFAULT_ZH_REFERENCE_ID = process.env.ZH_REFERENCE_ID || '0aee9f0f5683480d987c9e3c5d2de04c'; const DEFAULT_ZH_REFERENCE_ID = process.env.ZH_REFERENCE_ID || '0aee9f0f5683480d987c9e3c5d2de04c';
const DEFAULT_JA_REFERENCE_ID = process.env.JA_REFERENCE_ID || process.env.EN_REFERENCE_ID || '38985e545cff41b79c11c32b0196c9a0'; const DEFAULT_JA_REFERENCE_ID = process.env.JA_REFERENCE_ID || process.env.EN_REFERENCE_ID || '38985e545cff41b79c11c32b0196c9a0';
const FISH_AUDIO_BASE = process.env.FISH_AUDIO_BASE || 'https://api.fish.audio';
const HLS_ENABLE = process.env.HLS_ENABLE !== '0'; const HLS_ENABLE = process.env.HLS_ENABLE !== '0';
const HLS_STREAM_MODE = (process.env.HLS_STREAM_MODE || 'event').toLowerCase(); const HLS_STREAM_MODE = (process.env.HLS_STREAM_MODE || 'event').toLowerCase();
@@ -2445,8 +2446,8 @@ async function generateTtsWavToTemp(text, reference_id, model = DEFAULT_TTS_MODE
}; };
const response = proxyUrl const response = proxyUrl
? await requestViaProxy('https://api.fish.audio/v1/tts', options, proxyUrl) ? await requestViaProxy(`${FISH_AUDIO_BASE}/v1/tts`, options, proxyUrl)
: await requestDirect('https://api.fish.audio/v1/tts', options); : await requestDirect(`${FISH_AUDIO_BASE}/v1/tts`, options);
if (!response.ok) { if (!response.ok) {
const msg = (response.text && String(response.text).trim().length > 0) const msg = (response.text && String(response.text).trim().length > 0)
@@ -2464,25 +2465,6 @@ async function generateTtsWavToTemp(text, reference_id, model = DEFAULT_TTS_MODE
return { tempName, tempPath, url: `/uploads/${tempName}` }; return { tempName, tempPath, url: `/uploads/${tempName}` };
} }
app.post('/api/tts/upload_temp', upload.single('audio'), (req, res) => {
try {
if (!req.file) return res.status(400).json({ success: false, message: 'No audio file uploaded' });
const tempName = `temp_${Date.now()}_${Math.floor(Math.random() * 1000)}.wav`;
const tempPath = path.join(uploadDir, tempName);
fs.renameSync(req.file.path, tempPath);
res.json({
success: true,
tempName: tempName,
url: `/uploads/${tempName}`
});
} catch (e) {
res.status(500).json({ success: false, message: e.message });
}
});
app.post('/api/tts/generate', express.json(), async (req, res) => { app.post('/api/tts/generate', express.json(), async (req, res) => {
const text = String(req.body?.text || '').trim(); const text = String(req.body?.text || '').trim();
const reference_id = String(req.body?.reference_id || '').trim(); const reference_id = String(req.body?.reference_id || '').trim();
@@ -2515,8 +2497,8 @@ app.post('/api/tts/generate', express.json(), async (req, res) => {
}; };
const response = proxyUrl const response = proxyUrl
? await requestViaProxy('https://api.fish.audio/v1/tts', options, proxyUrl) ? await requestViaProxy(`${FISH_AUDIO_BASE}/v1/tts`, options, proxyUrl)
: await requestDirect('https://api.fish.audio/v1/tts', options); : await requestDirect(`${FISH_AUDIO_BASE}/v1/tts`, options);
if (!response.ok) { if (!response.ok) {
const msg = (response.text && String(response.text).trim().length > 0) const msg = (response.text && String(response.text).trim().length > 0)