diff --git a/backend/app/api/v1/translate.py b/backend/app/api/v1/translate.py index 774459b..79a33f7 100644 --- a/backend/app/api/v1/translate.py +++ b/backend/app/api/v1/translate.py @@ -111,9 +111,7 @@ async def text_to_speech_get( if not audio: raise HTTPException(status_code=501, detail="TTS not available") - return Response(content=audio, media_type="audio/mpeg", headers={ - "Content-Disposition": f'attachment; filename="tts-{lang}.mp3"', - }) + return Response(content=audio, media_type="audio/mpeg") @router.post("/feedback") diff --git a/backend/app/services/tts.py b/backend/app/services/tts.py index 2309075..2ca7381 100644 --- a/backend/app/services/tts.py +++ b/backend/app/services/tts.py @@ -28,7 +28,7 @@ SUPPORTED_LANGS = list(VOICE_MAP.keys()) class TextToSpeechService: @staticmethod - async def synthesize(text: str, lang: str = "en", rate: str = "0%", pitch: str = "0Hz") -> Optional[bytes]: + async def synthesize(text: str, lang: str = "en", rate: str = "", pitch: str = "") -> Optional[bytes]: if not HAS_EDGE_TTS: logger.warning("edge-tts not available") return None @@ -36,7 +36,10 @@ class TextToSpeechService: voice = VOICE_MAP.get(lang, VOICE_MAP["en"]) try: - communicate = edge_tts.Communicate(text, voice, rate=rate, pitch=pitch) + kwargs = {"voice": voice, "rate": rate} if rate else {"voice": voice} + if pitch: + kwargs["pitch"] = pitch + communicate = edge_tts.Communicate(text, **kwargs) audio_data = b"" async for chunk in communicate.stream(): if chunk["type"] == "audio": diff --git a/uni-app/src/pages/translate/translate.vue b/uni-app/src/pages/translate/translate.vue index 7f241dc..be8d643 100644 --- a/uni-app/src/pages/translate/translate.vue +++ b/uni-app/src/pages/translate/translate.vue @@ -187,29 +187,59 @@ const copyResult = () => { const playTts = () => { if (!result.value) return const lang = targetLangs[targetIndex.value].code - const token = uni.getStorageSync('token') - const url = `${BASE_URL}/translate/tts?text=${encodeURIComponent(result.value)}&lang=${lang}` + const text = result.value uni.showLoading({ title: '语音生成中...' }) - uni.downloadFile({ - url, - header: { Authorization: `Bearer ${token}` }, - success: (res) => { - uni.hideLoading() - if (res.statusCode === 200) { - const audioCtx = uni.createInnerAudioContext() - audioCtx.src = res.tempFilePath - audioCtx.play() - audioCtx.onEnded(() => audioCtx.destroy()) - } else { + + if (typeof window !== 'undefined' && window.Audio) { + const token = uni.getStorageSync('token') + uni.request({ + url: `${BASE_URL}/translate/tts?text=${encodeURIComponent(text)}&lang=${lang}`, + method: 'GET', + header: { Authorization: `Bearer ${token}` }, + responseType: 'arraybuffer', + success: (res) => { + uni.hideLoading() + if (res.statusCode === 200 && res.data) { + const blob = new Blob([res.data], { type: 'audio/mpeg' }) + const url = URL.createObjectURL(blob) + const audio = new Audio(url) + audio.onended = () => { URL.revokeObjectURL(url) } + audio.play().catch(() => { + uni.showToast({ title: '播放失败,请检查音量', icon: 'none' }) + }) + } else { + uni.showToast({ title: '语音生成失败', icon: 'none' }) + } + }, + fail: () => { + uni.hideLoading() uni.showToast({ title: '语音生成失败', icon: 'none' }) - } - }, - fail: () => { - uni.hideLoading() - uni.showToast({ title: '语音生成失败', icon: 'none' }) - }, - }) + }, + }) + } else { + const token = uni.getStorageSync('token') + const url = `${BASE_URL}/translate/tts?text=${encodeURIComponent(text)}&lang=${lang}` + uni.downloadFile({ + url, + header: { Authorization: `Bearer ${token}` }, + success: (res) => { + uni.hideLoading() + if (res.statusCode === 200) { + const audioCtx = uni.createInnerAudioContext() + audioCtx.src = res.tempFilePath + audioCtx.play() + audioCtx.onEnded(() => audioCtx.destroy()) + } else { + uni.showToast({ title: '语音生成失败', icon: 'none' }) + } + }, + fail: () => { + uni.hideLoading() + uni.showToast({ title: '语音生成失败', icon: 'none' }) + }, + }) + } } const handleExtract = async () => {