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
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:
@@ -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_URL = process.env.LITELLM_URL || 'https://llm-dev.meghsakha.com'
|
||||||
const LITELLM_API_KEY = process.env.LITELLM_API_KEY || ''
|
const LITELLM_API_KEY = process.env.LITELLM_API_KEY || ''
|
||||||
|
|
||||||
// OVH AI Endpoints TTS via the LiteLLM passthrough.
|
// English via OVH is opt-in (set OVH_TTS_URL_EN). German always uses the
|
||||||
// Path on the LiteLLM side: /tts-ovh/audio/* → https://nvr-tts-<lang>.endpoints.kepler.ai.cloud.ovh.net/api/*
|
// compliance TTS service (Edge TTS de-DE-ConradNeural → Piper fallback).
|
||||||
// OVH DE is opt-in: set OVH_TTS_ENABLED_DE=true to activate.
|
const OVH_EN = process.env.OVH_TTS_URL_EN
|
||||||
// When disabled, German falls through to the compliance TTS service (Edge TTS → Piper).
|
? {
|
||||||
const OVH_TTS = {
|
url: process.env.OVH_TTS_URL_EN,
|
||||||
de: process.env.OVH_TTS_ENABLED_DE === 'true'
|
voice: process.env.OVH_TTS_VOICE_EN || 'English-US.Female-1',
|
||||||
? {
|
languageCode: 'en-US',
|
||||||
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',
|
: null
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
const SAMPLE_RATE_HZ = parseInt(process.env.OVH_TTS_SAMPLE_RATE || '16000', 10)
|
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 })
|
return NextResponse.json({ error: 'Text is required' }, { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const ovh = language === 'de' ? OVH_TTS.de : OVH_TTS.en
|
if (language === 'en' && OVH_EN) {
|
||||||
if (ovh) {
|
return await synthesizeViaOvh(text, OVH_EN)
|
||||||
return await synthesizeViaOvh(text, ovh)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await synthesizeViaComplianceService(text, language)
|
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.
|
// 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 {
|
function wrapPcmAsWav(pcm: Buffer, sampleRateHz: number): Buffer {
|
||||||
const numChannels = 1
|
const numChannels = 1
|
||||||
const bitsPerSample = 16
|
const bitsPerSample = 16
|
||||||
@@ -128,8 +114,8 @@ function wrapPcmAsWav(pcm: Buffer, sampleRateHz: number): Buffer {
|
|||||||
header.writeUInt32LE(36 + dataSize, 4)
|
header.writeUInt32LE(36 + dataSize, 4)
|
||||||
header.write('WAVE', 8)
|
header.write('WAVE', 8)
|
||||||
header.write('fmt ', 12)
|
header.write('fmt ', 12)
|
||||||
header.writeUInt32LE(16, 16) // PCM subchunk size
|
header.writeUInt32LE(16, 16)
|
||||||
header.writeUInt16LE(1, 20) // PCM format
|
header.writeUInt16LE(1, 20)
|
||||||
header.writeUInt16LE(numChannels, 22)
|
header.writeUInt16LE(numChannels, 22)
|
||||||
header.writeUInt32LE(sampleRateHz, 24)
|
header.writeUInt32LE(sampleRateHz, 24)
|
||||||
header.writeUInt32LE(byteRate, 28)
|
header.writeUInt32LE(byteRate, 28)
|
||||||
|
|||||||
Reference in New Issue
Block a user