{"record":{"id":"edad1fe92d09c255","repo":"harry0703/MoneyPrinterTurbo","slug":"sonilo-returned-an-invalid-streaming-event","errorCode":null,"errorMessage":"Sonilo returned an invalid streaming event","messagePattern":"Sonilo returned an invalid streaming event","errorType":"exception","errorClass":"SoniloError","httpStatus":null,"severity":"error","filePath":"app/services/sonilo.py","lineNumber":210,"sourceCode":"        raise SoniloError(f\"failed to generate Sonilo video proxy: {detail}\")\n    proxy_size = os.path.getsize(proxy_path) if os.path.isfile(proxy_path) else 0\n    if proxy_size <= 0 or proxy_size > MAX_PROXY_BYTES:\n        _remove_file(proxy_path)\n        raise SoniloError(\"Sonilo video proxy is empty or exceeds the 300 MB limit\")\n    logger.info(\n        f\"Sonilo video proxy prepared: source={video_path}, size={proxy_size} bytes\"\n    )\n    return proxy_path\n\n\ndef _parse_event(raw_line: bytes) -> dict[str, Any]:\n    \"\"\"严格解析单条 NDJSON，禁止静默忽略截断或非对象响应。\"\"\"\n    try:\n        event = json.loads(raw_line.decode(\"utf-8\"))\n    except (UnicodeDecodeError, json.JSONDecodeError) as exc:\n        raise SoniloError(\"Sonilo returned malformed streaming data\") from exc\n    if not isinstance(event, dict) or not isinstance(event.get(\"type\"), str):\n        raise SoniloError(\"Sonilo returned an invalid streaming event\")\n    return event\n\n\ndef _stream_audio(response: requests.Response, temp_audio_path: str) -> tuple[int, str]:\n    \"\"\"\n    把第一条配乐流按事件顺序写入临时文件，并限制最大体积。\n\n    API 可能同时返回多条候选流；当前产品只需要一条 BGM，所以固定选择\n    stream_index=0。只有收到 complete 事件并通过 FFmpeg 完整解码后才会发布。\n    \"\"\"\n    total_bytes = 0\n    title = \"\"\n    completed = False\n    with open(temp_audio_path, \"wb\") as output:\n        for raw_line in response.iter_lines():\n            if not raw_line:\n                continue\n            event = _parse_event(raw_line)","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/harry0703/MoneyPrinterTurbo/blob/1f9f19c2021a68d04df228f33e9099a0c947f6f8/app/services/sonilo.py#L192-L228","documentation":"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.","triggerScenarios":"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.","commonSituations":"Provider protocol evolution; test harnesses streaming fixtures without the type field; middleware injecting keep-alive JSON scalars between events.","solutions":["Log the offending parsed value (temporarily log the event before the isinstance checks) to see its actual shape.","Check the Sonilo changelog and docs for a streaming schema change (for example type renamed) and update the checks in _parse_event accordingly.","Fix test fixtures to include a string type field on every streamed line.","If a keep-alive or comment line is the cause, skip it explicitly by exact match instead of loosening the object check."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"def is_valid_sonilo_event(event: object) -> bool:\n    return isinstance(event, dict) and isinstance(event.get(\"type\"), str)","tryCatchPattern":"try:\n    event = _parse_event(raw_line)\nexcept SoniloError as exc:\n    if \"invalid streaming event\" in str(exc):\n        logger.warning(f\"protocol drift, raw line: {raw_line[:200]!r}\")\n        raise  # strict policy: never skip unknown shapes silently\n    raise","preventionTips":["Keep streaming fixtures in tests that cover every documented event type plus a malformed line.","Subscribe to the provider changelog and update _parse_event the same day the stream schema changes."],"tags":["sonilo","streaming","protocol","validation"],"backgroundTag":null,"analyzedSha":"1f9f19c2021a68d04df228f33e9099a0c947f6f8","analyzedAt":"2026-08-14T19:41:05.568Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}