fix(pitch-deck): pin presenter TTS to Edge TTS (de-DE-ConradNeural)
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 44s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 37s

German permanently routes to compliance TTS service (Edge TTS neural
voice, Piper fallback). OVH DE path removed — no env var can flip it
back accidentally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-16 21:44:12 +02:00
parent 6a6b3e8cee
commit 6e6525a416

View File

@@ -4,28 +4,15 @@ const TTS_SERVICE_URL = process.env.TTS_SERVICE_URL || 'http://compliance-tts-se
const LITELLM_URL = process.env.LITELLM_URL || 'https://llm-dev.meghsakha.com'
const LITELLM_API_KEY = process.env.LITELLM_API_KEY || ''
// OVH AI Endpoints TTS via the LiteLLM passthrough.
// Path on the LiteLLM side: /tts-ovh/audio/* → https://nvr-tts-<lang>.endpoints.kepler.ai.cloud.ovh.net/api/*
// OVH DE is opt-in: set OVH_TTS_ENABLED_DE=true to activate.
// When disabled, German falls through to the compliance TTS service (Edge TTS → Piper).
const OVH_TTS = {
de: process.env.OVH_TTS_ENABLED_DE === 'true'
? {
url: process.env.OVH_TTS_URL_DE || `${LITELLM_URL}/tts-ovh/audio/v1/tts/text_to_audio`,
voice: process.env.OVH_TTS_VOICE_DE || 'German-DE-Male-1',
languageCode: 'de-DE',
}
: null,
// Enable by setting OVH_TTS_URL_EN (e.g. pointing at a second LiteLLM
// passthrough that targets nvr-tts-en-us). Keeps EN on the old path until set.
en: process.env.OVH_TTS_URL_EN
? {
url: process.env.OVH_TTS_URL_EN,
voice: process.env.OVH_TTS_VOICE_EN || 'English-US.Female-1',
languageCode: 'en-US',
}
: null,
}
// English via OVH is opt-in (set OVH_TTS_URL_EN). German always uses the
// compliance TTS service (Edge TTS de-DE-ConradNeural → Piper fallback).
const OVH_EN = process.env.OVH_TTS_URL_EN
? {
url: process.env.OVH_TTS_URL_EN,
voice: process.env.OVH_TTS_VOICE_EN || 'English-US.Female-1',
languageCode: 'en-US',
}
: null
const SAMPLE_RATE_HZ = parseInt(process.env.OVH_TTS_SAMPLE_RATE || '16000', 10)
@@ -38,9 +25,8 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Text is required' }, { status: 400 })
}
const ovh = language === 'de' ? OVH_TTS.de : OVH_TTS.en
if (ovh) {
return await synthesizeViaOvh(text, ovh)
if (language === 'en' && OVH_EN) {
return await synthesizeViaOvh(text, OVH_EN)
}
return await synthesizeViaComplianceService(text, language)
@@ -115,7 +101,7 @@ async function synthesizeViaComplianceService(text: string, language: string): P
}
// Prepend a minimal 44-byte WAV header to raw 16-bit mono PCM.
// OVH's Riva HTTP endpoint returns bare PCM samples; browsers need RIFF/WAV framing.
// Used only for OVH EN if enabled — OVH Riva returns bare PCM samples.
function wrapPcmAsWav(pcm: Buffer, sampleRateHz: number): Buffer {
const numChannels = 1
const bitsPerSample = 16
@@ -128,8 +114,8 @@ function wrapPcmAsWav(pcm: Buffer, sampleRateHz: number): Buffer {
header.writeUInt32LE(36 + dataSize, 4)
header.write('WAVE', 8)
header.write('fmt ', 12)
header.writeUInt32LE(16, 16) // PCM subchunk size
header.writeUInt16LE(1, 20) // PCM format
header.writeUInt32LE(16, 16)
header.writeUInt16LE(1, 20)
header.writeUInt16LE(numChannels, 22)
header.writeUInt32LE(sampleRateHz, 24)
header.writeUInt32LE(byteRate, 28)