{"record":{"id":"490c5b416b339de4","repo":"harry0703/MoneyPrinterTurbo","slug":"sonilo-returned-malformed-streaming-data","errorCode":null,"errorMessage":"Sonilo returned malformed streaming data","messagePattern":"Sonilo returned malformed streaming data","errorType":"exception","errorClass":"SoniloError","httpStatus":null,"severity":"error","filePath":"app/services/sonilo.py","lineNumber":208,"sourceCode":"        _remove_file(proxy_path)\n        detail = (result.stderr or \"\").strip().replace(\"\\n\", \" \")[-500:]\n        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:","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/harry0703/MoneyPrinterTurbo/blob/1f9f19c2021a68d04df228f33e9099a0c947f6f8/app/services/sonilo.py#L190-L226","documentation":"Raised by _parse_event when a single NDJSON line from the Sonilo streaming response cannot be decoded as UTF-8 or parsed as JSON. The parser is deliberately strict: truncated lines (network cut mid-chunk), BOMs, HTML error pages injected by a proxy, or garbage all fail here rather than being silently skipped.","triggerScenarios":"The connection drops mid-line so iter_lines yields a truncated JSON fragment; an intercepting proxy returns an HTML 502 page on the streaming endpoint; the provider sends SSE-style 'data: {...}' framing instead of raw NDJSON; a response compressed or garbled by a misconfigured gateway.","commonSituations":"Unstable networks during long generations; corporate proxies rewriting streaming responses; provider deploys an API change that switches framing; chunked-encoding handling bugs in an intermediary.","solutions":["Capture the failing raw line (add temporary logging of raw_line before the _parse_event call) to see whether it is truncated JSON, HTML, or another framing.","If it is truncated JSON from a flaky network, treat it as retryable: retry the generation request.","If it is HTML from a proxy, bypass or fix the proxy for the streaming endpoint.","If the provider changed framing (for example SSE data prefixes), strip the prefix or adapt _parse_event to the new protocol."],"exampleFix":"# temporary diagnosis\nevent = _parse_event(raw_line)  # before\nlogger.debug(f\"raw sonilo line: {raw_line[:200]!r}\")\nevent = _parse_event(raw_line)  # after (remove once diagnosed)","handlingStrategy":"retry","validationCode":null,"typeGuard":"def looks_like_ndjson(line: str) -> bool:\n    s = line.strip()\n    return s.startswith(\"{\") and s.endswith(\"}\")","tryCatchPattern":"try:\n    audio = request_bgm(video, out, prompt)\nexcept SoniloError as exc:\n    if \"malformed streaming data\" in str(exc):\n        retry_with_backoff(request_bgm, attempts=2)  # truncation is transient\n    raise","preventionTips":["Disable response-rewriting proxies on the streaming endpoint (proxy_buffering off in nginx).","Log the first raw line on failure once, to distinguish provider framing changes from network truncation."],"tags":["sonilo","streaming","json","network"],"backgroundTag":null,"analyzedSha":"1f9f19c2021a68d04df228f33e9099a0c947f6f8","analyzedAt":"2026-08-14T19:41:05.568Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}