harry0703/MoneyPrinterTurbo · error · BgmUploadError

uploaded file must contain a decodable audio stream

Error message

uploaded file must contain a decodable audio stream

What it means

Raised by _validate_audio in app/services/bgm.py when the FFmpeg full-decode probe exits non-zero. The probe runs FFmpeg with -map 0:a:0 (fails when there is no audio stream) and -xerror (promotes decode errors to failure), decoding to null output; full decoding also catches encrypted or random data that merely carries an audio extension. A file that passes the extension check but fails this probe is rejected as not containing a decodable audio stream (BgmUploadError → HTTP 400).

Source

Thrown at app/services/bgm.py:154

                "-xerror",
                "-i",
                file_path,
                "-map",
                "0:a:0",
                "-f",
                "null",
                "-",
            ],
            capture_output=True,
            timeout=timeout_seconds,
            check=False,
        )
    except subprocess.TimeoutExpired as exc:
        raise BgmServiceError("FFmpeg background music validation timed out") from exc
    except OSError as exc:
        raise BgmServiceError("failed to run FFmpeg for background music validation") from exc
    if decoded.returncode != 0:
        raise BgmUploadError("uploaded file must contain a decodable audio stream")


def validate_audio_file(file_path: str, timeout_seconds: int = 120) -> None:
    """
    校验磁盘上的音频文件可由项目 FFmpeg 完整解码。

    上传预检通常只需 30 秒;Sonilo 生成的配乐最长可达 6 分钟,因此对外提供
    可调整超时的复用入口。服务只依赖 FFmpeg,不要求系统额外安装 FFprobe。
    """
    if not os.path.isfile(file_path) or os.path.getsize(file_path) <= 0:
        raise BgmUploadError("background music file is empty or missing")
    _validate_audio(file_path, timeout_seconds=timeout_seconds)


def _stage_bgm_upload(filename: str, source: BinaryIO) -> tuple[str, str, int]:
    """
    将上传流写入同目录临时文件,并返回安全文件名、临时路径和字节数。

View on GitHub (pinned to 1f9f19c202)

Solutions

  1. Verify locally first with the same test: ffmpeg -v error -i file -map 0:a:0 -xerror -f null - ; a non-zero exit means re-encode or re-download the file.
  2. Re-encode with ffmpeg -i input -vn -c:a libmp3lame output.mp3 to strip anything non-audio and rebuild a clean stream.
  3. If the file is DRM-protected, obtain a DRM-free copy; no server setting will accept it.

Example fix

# before: upload raw, hope for the best
files = {"file": ("maybe-broken.mp3", open("maybe-broken.mp3", "rb"))}

# after: validate locally with the server's own probe, re-encode if needed
import subprocess, sys
rc = subprocess.run(["ffmpeg", "-v", "error", "-i", "maybe-broken.mp3", "-map", "0:a:0", "-xerror", "-f", "null", "-"]).returncode
if rc != 0:
    subprocess.run(["ffmpeg", "-y", "-i", "maybe-broken.mp3", "-vn", "-c:a", "libmp3lane" if False else "libmp3lame", "fixed.mp3"], check=True)
files = {"file": ("fixed.mp3", open("fixed.mp3", "rb"))}
Defensive patterns

Strategy: validation

Validate before calling

import subprocess
def is_decodable_audio(path: str) -> bool:
    return subprocess.run(
        ["ffmpeg", "-v", "error", "-i", path, "-map", "0:a:0", "-xerror", "-f", "null", "-"],
        capture_output=True,
    ).returncode == 0

Try / catch

try:
    validate_upload(name, fh)
except BgmUploadError as e:
    if "decodable audio" in str(e):
        reencode_to_mp3(local_path)  # ffmpeg -i in -vn -c:a libmp3lame out.mp3
        retry_with(reencoded_file)
    else:
        raise

Prevention

When it happens

Trigger: Uploading an image/document renamed to .mp3; DRM-protected audio; a truncated/corrupt audio file where the container header exists but frames are broken; an MP4 with video only (no audio stream).

Common situations: Corrupted downloads (interrupted transfer); music services exporting DRM'd files; users renaming files to force acceptance; files with cover-art-only streams and no audio track.

Related errors


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