{"record":{"id":"e1eebbce90b8c23a","repo":"harry0703/MoneyPrinterTurbo","slug":"invalid-voice-name-voice-name","errorCode":null,"errorMessage":"invalid voice name: {voice_name}","messagePattern":"invalid voice name: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"app/services/voice.py","lineNumber":999,"sourceCode":"    return (\n        '<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" '\n        f'xml:lang=\"{voice_locale}\">'\n        f'<voice name=\"{escaped_voice_name}\">'\n        f'<prosody rate=\"{normalized_rate:g}\">{escaped_text}</prosody>'\n        \"</voice></speak>\"\n    )\n\n\ndef azure_tts_v2(\n    text: str,\n    voice_name: str,\n    voice_file: str,\n    voice_rate: float = 1.0,\n) -> Union[SubMaker, None]:\n    voice_name = is_azure_v2_voice(voice_name)\n    if not voice_name:\n        logger.error(f\"invalid voice name: {voice_name}\")\n        raise ValueError(f\"invalid voice name: {voice_name}\")\n    text = text.strip()\n    ssml = _build_azure_v2_ssml(text, voice_name, voice_rate)\n\n    def _format_duration_to_offset(duration) -> int:\n        if isinstance(duration, str):\n            time_obj = datetime.strptime(duration, \"%H:%M:%S.%f\")\n            milliseconds = (\n                (time_obj.hour * 3600000)\n                + (time_obj.minute * 60000)\n                + (time_obj.second * 1000)\n                + (time_obj.microsecond // 1000)\n            )\n            return milliseconds * 10000\n\n        if isinstance(duration, int):\n            return duration\n\n        return 0","sourceCodeStart":981,"sourceCodeEnd":1017,"githubUrl":"https://github.com/harry0703/MoneyPrinterTurbo/blob/1f9f19c2021a68d04df228f33e9099a0c947f6f8/app/services/voice.py#L981-L1017","documentation":"azure_tts_v2 expects an Azure voice name ending with the '-V2' suffix; is_azure_v2_voice() strips and returns the base name only when the suffix is present, otherwise it returns an empty string. An empty result makes azure_tts_v2 log and raise ValueError. Note the bug-shaped detail: the message interpolates the already-reassigned (empty) voice_name, so the message always reads 'invalid voice name: '.","triggerScenarios":"Calling azure_tts_v2(text, 'zh-CN-XiaoxiaoNeural', ...) without the '-V2' suffix; passing a voice configured for another provider (e.g. 'azure:...' or a plain name); passing an empty or whitespace voice_name.","commonSituations":"Voice list from config/UI uses standard Azure names without the V2 marker; user migrated settings from an older version that did not require the suffix; typo in the voice name in params.voice_name.","solutions":["Pass an Azure V2 voice name, e.g. 'zh-CN-XiaoxiaoNeural-V2'.","If the name comes from user config, validate/normalize it with is_azure_v2_voice() before calling azure_tts_v2.","Fix the log/message to interpolate the original input (before reassignment) so failures are diagnosable: currently it always prints an empty name."],"exampleFix":"# before\nazure_tts_v2(text, \"zh-CN-XiaoxiaoNeural\", voice_file)\n\n# after\nazure_tts_v2(text, \"zh-CN-XiaoxiaoNeural-V2\", voice_file)","handlingStrategy":"validation","validationCode":"from app.services.voice import is_azure_v2_voice\n\nbase = is_azure_v2_voice(voice_name)\nif not base:\n    raise ValueError(f\"{voice_name!r} must be an Azure V2 voice ending in '-V2'\")","typeGuard":"def is_azure_v2_voice_name(name: str) -> bool:\n    \"\"\"True when name (after parsing) carries the '-V2' suffix.\"\"\"\n    return is_azure_v2_voice(name) != \"\"","tryCatchPattern":"try:\n    azure_tts_v2(text, voice_name, voice_file)\nexcept ValueError as exc:\n    if \"invalid voice name\" in str(exc):\n        raise ValueError(f\"use an Azure V2 voice like 'zh-CN-XiaoxiaoNeural-V2', got {voice_name!r}\") from exc\n    raise","preventionTips":["Normalize voice lists in config so every Azure V2 entry carries the '-V2' suffix.","Validate with is_azure_v2_voice() at UI/API intake, not only inside azure_tts_v2."],"tags":["tts","azure","validation","voice-name"],"backgroundTag":null,"analyzedSha":"1f9f19c2021a68d04df228f33e9099a0c947f6f8","analyzedAt":"2026-08-14T19:41:05.568Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}