diff --git a/CloudServer/public/generator.html b/CloudServer/public/generator.html index 78f7aab..6ccd947 100644 --- a/CloudServer/public/generator.html +++ b/CloudServer/public/generator.html @@ -857,14 +857,48 @@ } async function _generateSingleTTS(text, reference_id) { - const res = await fetch('/api/tts/generate', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ text, reference_id, model: TTS_MODEL }) - }); - const data = await res.json(); - if (!data.success) throw new Error(data.message); - return data; + 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('网络请求失败或跨域被拦截(CORS)。如果启用了本地代理,请确保浏览器能够通过代理正常访问外网接口。'); + } + throw err; + } } async function _importSingleAudio(code, zhTemp, jaTemp) { @@ -1141,46 +1175,20 @@ status.textContent = "Generating Chinese audio..."; try { - // Generate Chinese via Server-Side Proxy - 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); + // Generate Chinese via Client-Side Fetch + const dataZh = await _generateSingleTTS(zhText, ZH_REFERENCE_ID); status.textContent = "Generating Japanese audio..."; - // Generate Japanese via Server-Side Proxy - 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); + // Generate Japanese via Client-Side Fetch + const dataJa = await _generateSingleTTS(jaText, JA_REFERENCE_ID); // Success zhTempFile = dataZh.tempName; - jaTempFile = dataJa.jaTempName || dataJa.enTempName || dataJa.tempName; + jaTempFile = dataJa.tempName; document.getElementById('zh-audio').src = dataZh.url; - document.getElementById('en-audio').src = dataJa.jaUrl || dataJa.enUrl || dataJa.url; + document.getElementById('en-audio').src = dataJa.url; document.getElementById('preview-section').style.display = 'block'; status.textContent = "Generation complete!"; diff --git a/CloudServer/public/tempbroadcast.html b/CloudServer/public/tempbroadcast.html index 5347e58..6883116 100644 --- a/CloudServer/public/tempbroadcast.html +++ b/CloudServer/public/tempbroadcast.html @@ -322,6 +322,51 @@ .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() { const text = document.getElementById("tb_text").value.trim(); if (!text) { @@ -331,20 +376,24 @@ document.getElementById("btn_publish").disabled = true; document.getElementById("btn_compose").disabled = true; document.getElementById("build_badge").textContent = "合成中…"; - setStatus("合成临时广播中…"); + setStatus("合成临时广播中(1/2: 生成语音)…"); try { - const resp = await fetch("/api/temp-broadcast/compose", { + // 1. 本地生成 TTS 并上传获得临时文件名 + const ttsData = await _generateTTSLocal(text, ZH_REFERENCE_ID); + + setStatus("合成临时广播中(2/2: 服务端混音)…"); + // 2. 告诉服务端进行混音合并 + const resp = await fetch("/api/temp-broadcast/build", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - text, - reference_id: ZH_REFERENCE_ID, - model: TTS_MODEL, + contentTempName: ttsData.tempName, prefixName: "lsts" }) }); const data = await resp.json(); if (!data || !data.success) throw new Error(data?.message || "合成失败"); + const preview = document.getElementById("preview_area"); preview.style.display = "block"; state.broadcastId = data.broadcastId; diff --git a/CloudServer/server.js b/CloudServer/server.js index e5b47b0..1941086 100644 --- a/CloudServer/server.js +++ b/CloudServer/server.js @@ -2464,6 +2464,25 @@ async function generateTtsWavToTemp(text, reference_id, model = DEFAULT_TTS_MODE 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) => { const text = String(req.body?.text || '').trim(); const reference_id = String(req.body?.reference_id || '').trim();