harry0703/MoneyPrinterTurbo · error · ValueError

invalid voice name: {voice_name}

Error message

invalid voice name: {voice_name}

What it means

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: '.

Source

Thrown at app/services/voice.py:999

    return (
        '<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" '
        f'xml:lang="{voice_locale}">'
        f'<voice name="{escaped_voice_name}">'
        f'<prosody rate="{normalized_rate:g}">{escaped_text}</prosody>'
        "</voice></speak>"
    )


def azure_tts_v2(
    text: str,
    voice_name: str,
    voice_file: str,
    voice_rate: float = 1.0,
) -> Union[SubMaker, None]:
    voice_name = is_azure_v2_voice(voice_name)
    if not voice_name:
        logger.error(f"invalid voice name: {voice_name}")
        raise ValueError(f"invalid voice name: {voice_name}")
    text = text.strip()
    ssml = _build_azure_v2_ssml(text, voice_name, voice_rate)

    def _format_duration_to_offset(duration) -> int:
        if isinstance(duration, str):
            time_obj = datetime.strptime(duration, "%H:%M:%S.%f")
            milliseconds = (
                (time_obj.hour * 3600000)
                + (time_obj.minute * 60000)
                + (time_obj.second * 1000)
                + (time_obj.microsecond // 1000)
            )
            return milliseconds * 10000

        if isinstance(duration, int):
            return duration

        return 0

View on GitHub (pinned to 1f9f19c202)

Solutions

  1. Pass an Azure V2 voice name, e.g. 'zh-CN-XiaoxiaoNeural-V2'.
  2. If the name comes from user config, validate/normalize it with is_azure_v2_voice() before calling azure_tts_v2.
  3. Fix the log/message to interpolate the original input (before reassignment) so failures are diagnosable: currently it always prints an empty name.

Example fix

# before
azure_tts_v2(text, "zh-CN-XiaoxiaoNeural", voice_file)

# after
azure_tts_v2(text, "zh-CN-XiaoxiaoNeural-V2", voice_file)
Defensive patterns

Strategy: validation

Validate before calling

from app.services.voice import is_azure_v2_voice

base = is_azure_v2_voice(voice_name)
if not base:
    raise ValueError(f"{voice_name!r} must be an Azure V2 voice ending in '-V2'")

Type guard

def is_azure_v2_voice_name(name: str) -> bool:
    """True when name (after parsing) carries the '-V2' suffix."""
    return is_azure_v2_voice(name) != ""

Try / catch

try:
    azure_tts_v2(text, voice_name, voice_file)
except ValueError as exc:
    if "invalid voice name" in str(exc):
        raise ValueError(f"use an Azure V2 voice like 'zh-CN-XiaoxiaoNeural-V2', got {voice_name!r}") from exc
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of harry0703/MoneyPrinterTurbo@1f9f19c202 (2026-08-14). Data as JSON: /api/errors/e1eebbce90b8c23a. Report an issue: GitHub.