harry0703/MoneyPrinterTurbo · error · SoniloError

Sonilo returned an invalid streaming event

Error message

Sonilo returned an invalid streaming event

What it means

Raised by _parse_event when a line parses as valid JSON but is not a JSON object, or lacks a string 'type' field. Every Sonilo streaming event is required to carry a string discriminator (error, title, complete, audio_chunk); a top-level array, a bare string or number, or an event with a missing or non-string type is treated as a protocol violation.

Source

Thrown at app/services/sonilo.py:210

        raise SoniloError(f"failed to generate Sonilo video proxy: {detail}")
    proxy_size = os.path.getsize(proxy_path) if os.path.isfile(proxy_path) else 0
    if proxy_size <= 0 or proxy_size > MAX_PROXY_BYTES:
        _remove_file(proxy_path)
        raise SoniloError("Sonilo video proxy is empty or exceeds the 300 MB limit")
    logger.info(
        f"Sonilo video proxy prepared: source={video_path}, size={proxy_size} bytes"
    )
    return proxy_path


def _parse_event(raw_line: bytes) -> dict[str, Any]:
    """严格解析单条 NDJSON,禁止静默忽略截断或非对象响应。"""
    try:
        event = json.loads(raw_line.decode("utf-8"))
    except (UnicodeDecodeError, json.JSONDecodeError) as exc:
        raise SoniloError("Sonilo returned malformed streaming data") from exc
    if not isinstance(event, dict) or not isinstance(event.get("type"), str):
        raise SoniloError("Sonilo returned an invalid streaming event")
    return event


def _stream_audio(response: requests.Response, temp_audio_path: str) -> tuple[int, str]:
    """
    把第一条配乐流按事件顺序写入临时文件,并限制最大体积。

    API 可能同时返回多条候选流;当前产品只需要一条 BGM,所以固定选择
    stream_index=0。只有收到 complete 事件并通过 FFmpeg 完整解码后才会发布。
    """
    total_bytes = 0
    title = ""
    completed = False
    with open(temp_audio_path, "wb") as output:
        for raw_line in response.iter_lines():
            if not raw_line:
                continue
            event = _parse_event(raw_line)

View on GitHub (pinned to 1f9f19c202)

Solutions

  1. Log the offending parsed value (temporarily log the event before the isinstance checks) to see its actual shape.
  2. Check the Sonilo changelog and docs for a streaming schema change (for example type renamed) and update the checks in _parse_event accordingly.
  3. Fix test fixtures to include a string type field on every streamed line.
  4. If a keep-alive or comment line is the cause, skip it explicitly by exact match instead of loosening the object check.
Defensive patterns

Strategy: type-guard

Type guard

def is_valid_sonilo_event(event: object) -> bool:
    return isinstance(event, dict) and isinstance(event.get("type"), str)

Try / catch

try:
    event = _parse_event(raw_line)
except SoniloError as exc:
    if "invalid streaming event" in str(exc):
        logger.warning(f"protocol drift, raw line: {raw_line[:200]!r}")
        raise  # strict policy: never skip unknown shapes silently
    raise

Prevention

When it happens

Trigger: The provider sends bare payloads such as the string ok or a JSON array on the stream; an event object where type is null or numeric; a partial JSON document that happens to parse to a non-dict; an API version change renaming type to event or kind.

Common situations: Provider protocol evolution; test harnesses streaming fixtures without the type field; middleware injecting keep-alive JSON scalars between events.

Related errors


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