harry0703/MoneyPrinterTurbo · error · BgmUploadError
background music upload is not seekable
Error message
background music upload is not seekable
What it means
Raised in _stage_bgm_upload when source.seek(0) raises AttributeError (the stream object has no seek) or OSError (seek exists but fails, e.g. a non-regular file descriptor). The seek is mandatory: both the WebUI's upload preflight and the final persistence must read from byte 0 with identical chunking and size limits, otherwise the UI could show a file as valid while the server later rejects it — the state-split the staging function is designed to prevent.
Source
Thrown at app/services/bgm.py:189
将上传流写入同目录临时文件,并返回安全文件名、临时路径和字节数。
WebUI 的上传预检和最终持久化必须使用完全相同的分块读取、大小限制与文件名
规则,否则可能出现界面显示可用、点击生成后却被服务端拒绝的状态分裂。
临时文件由调用方在完成音频探测后删除或原子替换。
"""
safe_name = sanitize_upload_filename(filename)
try:
target_dir = uploaded_bgm_dir(create=True)
except OSError as exc:
raise BgmServiceError("failed to prepare background music storage") from exc
temp_path = ""
total_bytes = 0
try:
try:
source.seek(0)
except (AttributeError, OSError) as exc:
raise BgmUploadError("background music upload is not seekable") from exc
# 保留原始扩展名便于 FFmpeg 针对无容器头的 AAC 等格式选择正确的
# demuxer;临时文件仍放在目标目录,以保证最终 os.replace 是原子操作。
descriptor, temp_path = tempfile.mkstemp(
prefix=_INTERNAL_UPLOAD_PREFIX,
suffix=Path(safe_name).suffix.lower(),
dir=target_dir,
)
with os.fdopen(descriptor, "wb") as output:
while True:
chunk = source.read(_COPY_CHUNK_BYTES)
if not chunk:
break
if not isinstance(chunk, (bytes, bytearray, memoryview)):
raise BgmUploadError("background music upload must be binary")
total_bytes += len(chunk)
if total_bytes > MAX_BGM_UPLOAD_BYTES:
raise BgmUploadError("background music file exceeds the 30 MB limit")View on GitHub (pinned to 1f9f19c202)
Solutions
- Buffer the bytes first: data = source.read() then pass io.BytesIO(data) to the upload.
- If wrapping streams, ensure the wrapper delegates seek/tell and that the underlying object supports random access.
- For framework code, use the framework's spooled upload file object directly rather than an iterator adapter.
Example fix
# before
def upload(source): # source is a forward-only stream
safe = save_bgm_upload(name, source)
# after
import io
def upload(source):
source.seek(0) if hasattr(source, "seek") else None
data = source.read()
safe = save_bgm_upload(name, io.BytesIO(data)) Defensive patterns
Strategy: validation
Validate before calling
import io
def ensure_seekable(source):
if not (hasattr(source, "seek") and source.seekable()):
return io.BytesIO(source.read())
source.seek(0)
return source Type guard
import io
def is_seekable_stream(source) -> bool:
return isinstance(source, (io.BytesIO, io.BufferedIOBase)) and source.seekable() Try / catch
try:
safe_name = save_bgm_upload(name, source)
except BgmUploadError as e:
if "not seekable" in str(e):
source = io.BytesIO(original_bytes) # buffer, then retry once
safe_name = save_bgm_upload(name, source)
else:
raise Prevention
- Buffer streamed bodies into BytesIO before handing them to upload APIs.
- Don't wrap upload files in forward-only iterators.
- seek(0) before any retry that reuses the same file object.
When it happens
Trigger: Passing a non-seekable stream to the upload API: an HTTP request body iterator, a pipe (e.g. subprocess stdout), an open('file') on a stream-only pseudo-file, or a wrapper class without a seek method; also a SpooledTemporaryFile already rolled to disk and closed by a prior error.
Common situations: Clients streaming uploads directly from network objects instead of buffering; middleware wrapping UploadFile.file in a forward-only reader; testing with io.RawIOBase subclasses that don't implement seek.
Related errors
- {request_id}: {str(exc)}
- invalid background music filename
- unsupported background music format; supported formats: {sup
- uploaded file must contain a decodable audio stream
- background music upload must be binary
AI-assisted analysis of harry0703/MoneyPrinterTurbo@1f9f19c202 (2026-08-14).
Data as JSON: /api/errors/d9c0cdba9e60b65e.
Report an issue: GitHub.