{"record":{"id":"1b907f143c7872d8","repo":"harry0703/MoneyPrinterTurbo","slug":"sonilo-generation-failed-response-status-code","errorCode":null,"errorMessage":"Sonilo generation failed ({response.status_code}): {_safe_response_error(response)}","messagePattern":"Sonilo generation failed \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"SoniloError","httpStatus":null,"severity":"error","filePath":"app/services/sonilo.py","lineNumber":298,"sourceCode":"    os.close(descriptor)\n    try:\n        logger.info(\n            f\"requesting Sonilo background music: video={video_path}, \"\n            f\"prompt_provided={bool(prompt)}\"\n        )\n        try:\n            with open(video_path, \"rb\") as video_file:\n                response = requests.post(\n                    f\"{_base_url()}{VIDEO_TO_MUSIC_PATH}\",\n                    headers={\"Authorization\": f\"Bearer {get_api_key()}\"},\n                    files={\"video\": (Path(video_path).name, video_file, \"video/mp4\")},\n                    data={\"prompt\": prompt} if prompt else None,\n                    stream=True,\n                    timeout=_request_timeout(),\n                )\n                with response:\n                    if not response.ok:\n                        raise SoniloError(\n                            f\"Sonilo generation failed ({response.status_code}): \"\n                            f\"{_safe_response_error(response)}\"\n                        )\n                    total_bytes, title = _stream_audio(response, temp_audio_path)\n        except requests.RequestException as exc:\n            # iter_lines() 期间的网络中断同样属于 requests 异常，不能只捕获\n            # 建立连接阶段，否则半条音频可能让任务直接异常退出而无法降级。\n            raise SoniloError(f\"failed to request Sonilo music: {exc}\") from exc\n\n        try:\n            bgm_service.validate_audio_file(temp_audio_path, timeout_seconds=120)\n        except (bgm_service.BgmUploadError, bgm_service.BgmServiceError) as exc:\n            raise SoniloError(\"Sonilo returned audio that FFmpeg cannot decode\") from exc\n        os.replace(temp_audio_path, output_path)\n        temp_audio_path = \"\"\n        logger.info(\n            f\"Sonilo background music generated: output={output_path}, \"\n            f\"size={total_bytes} bytes, title={title or '-'}\"","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/harry0703/MoneyPrinterTurbo/blob/1f9f19c2021a68d04df228f33e9099a0c947f6f8/app/services/sonilo.py#L280-L316","documentation":"Raised when POST /v1/video-to-music returns a non-2xx HTTP status. The message includes the status code and up to 500 characters of the response body (via _safe_response_error, falling back to the reason phrase). Common codes: 401 bad key, 402/429 quota or rate limit, 413 proxy too large, 422 validation, 5xx provider.","triggerScenarios":"Invalid or revoked API key (401); expired credits (402) or rate limit (429); video proxy exceeding the provider's upload limit (413); video longer than 360 seconds or prompt over 2000 chars (422); provider outage (5xx). Each surfaces with its status code in the message.","commonSituations":"Key rotated but the WebUI config still holds the old value; sudden 429s after increasing task concurrency; large source videos producing proxies over the provider limit; provider-side incidents.","solutions":["Read the status code in the message: 401 fix the key; 402/429 quota or rate, back off or top up; 413 shrink the proxy; 422 check video duration (max 360) and prompt length (max 2000); 5xx retry later.","Re-run the connection test to verify the key independently of the upload path.","For 413, tighten proxy encoding (see error 85) or check Sonilo's documented upload cap.","For 5xx, add retry with backoff around the generation call rather than failing the whole task."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"# pre-flight the cheap endpoint and bounds before the expensive upload\nsonilo.test_connection()  # catches 401/entitlement early\nassert probe_duration_seconds(video) <= sonilo.MAX_VIDEO_DURATION_SECONDS\nassert len(prompt or \"\") <= sonilo.MAX_PROMPT_LENGTH","typeGuard":null,"tryCatchPattern":"import re\n\ntry:\n    audio = request_bgm(video, out, prompt)\nexcept SoniloError as exc:\n    m = re.search(r\"Sonilo generation failed \\((\\d{3})\\)\", str(exc))\n    if m:\n        status = int(m.group(1))\n        if status in (429, 500, 502, 503):\n            schedule_retry_with_backoff()      # transient\n        elif status in (401, 402):\n            fail_task_and_alert_owner(status)  # credential/quota: never retry\n        elif status == 413:\n            re_encode_proxy_smaller()\n    raise","preventionTips":["Map every status code to a policy (retry, fix input, or alert) instead of a blanket retry.","Run the connection test at task submission so 401s surface before minutes of proxy generation are spent."],"tags":["sonilo","http","api-key","quota","upload"],"backgroundTag":null,"analyzedSha":"1f9f19c2021a68d04df228f33e9099a0c947f6f8","analyzedAt":"2026-08-14T19:41:05.568Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}