{"record":{"id":"b614ed09c7119e1d","repo":"harry0703/MoneyPrinterTurbo","slug":"request-id-str-exc","errorCode":null,"errorMessage":"{request_id}: {str(exc)}","messagePattern":"\\{request_id\\}: \\{str\\(exc\\)\\}","errorType":"http","errorClass":"HttpException","httpStatus":400,"severity":"error","filePath":"app/controllers/v1/video.py","lineNumber":353,"sourceCode":"        \"Validate an MP3, M4A, AAC, WAV, FLAC, OGG, OPUS, or WMA file up to \"\n        \"30 MB and store it under an immutable UUID filename in storage/bgm.\"\n    ),\n    responses={\n        400: {\"description\": \"The filename, format, size, or audio stream is invalid\"},\n        500: {\"description\": \"FFmpeg validation or persistent storage is unavailable\"},\n    },\n)\ndef upload_bgm_file(request: Request, file: UploadFile = File(...)):\n    request_id = base.get_task_id(request)\n    try:\n        safe_filename = bgm_service.save_bgm_upload(file.filename, file.file)\n    except bgm_service.BgmUploadError as exc:\n        # 上传失败通常可以由用户更换文件后恢复，因此记录 request_id 和明确原因，\n        # 但不输出文件内容或绝对路径，避免日志泄露用户数据。\n        logger.warning(\n            f\"background music upload rejected: request_id={request_id}, error={str(exc)}\"\n        )\n        raise HttpException(\n            task_id=request_id,\n            status_code=400,\n            message=f\"{request_id}: {str(exc)}\",\n        )\n    except bgm_service.BgmServiceError as exc:\n        # 工具链或存储故障属于服务端问题，不能伪装成用户文件错误。日志保留\n        # request_id 和内部原因，HTTP 响应只返回稳定文案，避免暴露服务器路径。\n        logger.error(\n            f\"background music upload failed: request_id={request_id}, error={str(exc)}\"\n        )\n        raise HttpException(\n            task_id=request_id,\n            status_code=500,\n            message=f\"{request_id}: background music validation is unavailable\",\n        )\n\n    response = {\"file\": safe_filename}\n    return utils.get_response(200, response)","sourceCodeStart":335,"sourceCodeEnd":371,"githubUrl":"https://github.com/harry0703/MoneyPrinterTurbo/blob/1f9f19c2021a68d04df228f33e9099a0c947f6f8/app/controllers/v1/video.py#L335-L371","documentation":"Raised by the POST BGM upload endpoint when bgm_service.save_bgm_upload raises BgmUploadError. This error class covers user-attributable problems: invalid or reserved filenames, unsupported extension, undecodable audio stream, non-seekable upload stream, or an empty file. The endpoint maps it to 400 with str(exc) embedded, logging request_id and reason at warning level without dumping file contents or absolute paths.","triggerScenarios":"POST /api/v1/bgm (background music upload) with: filename CON.mp3 or NUL.wav (Windows reserved); extension .wma when unsupported; a text file renamed to .mp3 (FFmpeg decode fails); an empty upload; a SpooledTemporaryFile-backed stream lacking seek().","commonSituations":"Users uploading music bought in DRM'd or exotic formats; clients sending OS-generated names; a frontend uploading before the File object is fully materialized; names colliding with the server's internal upload prefix.","solutions":["Read the 400 body: str(exc) names the exact sub-cause (invalid filename / unsupported format / no decodable audio / etc.) and fix that specific condition.","Re-export the audio to a mainstream format (MP3/M4A/WAV per SUPPORTED_BGM_EXTENSIONS) with ffmpeg before uploading.","Use a plain basename filename free of Windows-reserved stems and control characters.","For streams, ensure the file object is seekable (read the upload into BytesIO first)."],"exampleFix":"# before\nfiles = {\"file\": (\"CON.mp3\", open(\"song.mp3\", \"rb\"))}\n\n# after\nfiles = {\"file\": (\"my-song.mp3\", open(\"song.mp3\", \"rb\"))}","handlingStrategy":"try-catch","validationCode":"from pathlib import Path\nsuffix = Path(filename).suffix.lower()\nif suffix not in SUPPORTED:  # mirror the server's supported extension set\n    raise ValueError(f\"convert to a supported format first: {sorted(SUPPORTED)}\")","typeGuard":"import re\nRESERVED = {\"CON\",\"PRN\",\"AUX\",\"NUL\",*(f\"COM{i}\" for i in range(1,10)),*(f\"LPT{i}\" for i in range(1,10))}\ndef is_valid_bgm_filename(name: str) -> bool:\n    n = (name or \"\").replace(\"\\\\\", \"/\").split(\"/\")[-1].strip()\n    return (\n        bool(n) and n not in {\".\", \"..\"} and len(n) <= 255\n        and all(ord(c) >= 32 for c in n)\n        and n.split(\".\", 1)[0].rstrip(\" .\").upper() not in RESERVED\n        and Path(n).suffix.lower() in SUPPORTED\n    )","tryCatchPattern":"try:\n    resp = requests.post(bgm_url, files={\"file\": (name, fh)}, headers=h)\n    resp.raise_for_status()\nexcept requests.HTTPError as e:\n    if e.response.status_code == 400:\n        detail = e.response.json()[\"message\"]  # embedded reason from BgmUploadError\n        prompt_user_to_fix_file(detail)\n    else:\n        raise  # 500 = server-side BgmServiceError, not a user fix","preventionTips":["Pre-check filename, extension, and local FFmpeg decodability before uploading.","Prefer mainstream formats (MP3/M4A/WAV) for uploads.","Treat 400 as fixable-by-user and 500 as an ops problem; don't mix the handling."],"tags":["upload","audio","http-400","bgm"],"backgroundTag":null,"analyzedSha":"1f9f19c2021a68d04df228f33e9099a0c947f6f8","analyzedAt":"2026-08-14T19:41:05.568Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}