{"record":{"id":"a20cfb844e109751","repo":"decolua/9router","slug":"google-tts-returned-empty-audio","errorCode":null,"errorMessage":"Google TTS returned empty audio","messagePattern":"Google TTS returned empty audio","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"open-sse/handlers/ttsProviders/googleTts.js","lineNumber":51,"sourceCode":"      bl: token.bl,\n      hl: lang,\n      \"soc-app\": 1, \"soc-platform\": 1, \"soc-device\": 1,\n      _reqid: reqId,\n      rt: \"c\",\n    });\n    const payload = [cleanText, lang, null, \"undefined\", [0]];\n    const body = new URLSearchParams();\n    body.append(\"f.req\", JSON.stringify([[[rpcId, JSON.stringify(payload), null, \"generic\"]]]));\n    const res = await fetch(`https://translate.google.com/_/TranslateWebserverUi/data/batchexecute?${query}`, {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/x-www-form-urlencoded\", \"Referer\": \"https://translate.google.com/\" },\n      body: body.toString(),\n    });\n    if (!res.ok) throw new Error(`Google TTS failed: ${res.status}`);\n    const data = await res.text();\n    const split = JSON.parse(data.split(\"\\n\")[3]);\n    const base64 = JSON.parse(split[0][2])[0];\n    if (!base64 || base64.length < 100) throw new Error(\"Google TTS returned empty audio\");\n    return { base64, format: \"mp3\" };\n  },\n};\n","sourceCodeStart":33,"sourceCodeEnd":55,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/handlers/ttsProviders/googleTts.js#L33-L55","documentation":"A 200 response from batchexecute is parsed line-wise: `data.split(\"\\n\")[3]` → JSON → `[0][2]` → JSON → first element must be a base64 string of at least 100 chars. If the audio payload is missing, empty, or suspiciously short, this error is thrown. It means Google responded OK but without usable audio.","triggerScenarios":"The RPC returns an in-body error envelope (e.g. unknown text encoding, unsupported language, empty text) rather than audio; the batchexecute response layout changed so index [3]/[0][2] now points at the wrong chunk.","commonSituations":"Empty or whitespace-only text passed to synthesize; very long text exceeding the RPC limit; Google changing the batchexecute response line format (breaking the fixed index parse).","solutions":["Validate `text` is non-empty before calling synthesize and keep it under the length limit (chunk long text)","Log the raw response to check whether line 3 / [0][2] still holds the audio array; update the parse indices if the layout changed","Check the in-body status/error inside the parsed split payload — batchexecute often reports errors with HTTP 200","Fall back to another TTS provider (the handler layer supports per-provider fallback)"],"exampleFix":"// before\nif (!base64 || base64.length < 100) throw new Error('Google TTS returned empty audio');\n// after\nif (!base64 || base64.length < 100) {\n  throw new Error('Google TTS returned empty audio; raw=' + data.slice(0, 300));\n}","handlingStrategy":"validation","validationCode":"if (!text?.trim()) throw new Error('text required for Google TTS');\nif (text.length > 200) text = text.slice(0, 200); // or chunk","typeGuard":"const parsed = JSON.parse(data.split('\\n')[3]); const okShape = Array.isArray(parsed?.[0]?.[2]) && JSON.parse(parsed[0][2])?.[0]?.length >= 100;","tryCatchPattern":"try { return await googleTts.synthesize(text); } catch (e) { if (e.message === 'Google TTS returned empty audio') return fallbackProvider(text); throw e; }","preventionTips":["Never pass empty/whitespace text; validate at the API boundary","Wrap JSON.parse in try/catch and log raw responses to catch layout changes","Keep a non-scraping TTS provider as fallback"],"tags":["parsing","google","tts","empty-response"],"backgroundTag":"empty-response-body","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}