harry0703/MoneyPrinterTurbo · error · HttpException

{request_id}: Only files with extensions {', '.join(allowed_

Error message

{request_id}: Only files with extensions {', '.join(allowed_suffixes)} can be uploaded

What it means

Raised by the video-material upload endpoint when the sanitized filename's suffix is not in allowed_suffixes. The preceding comment notes a fixed subtlety: suffix is checked as a proper extension (e.g. via Path suffix), so a bare-dots filename can't sneak past as it could with naive endswith('jpg'). Existing files with a matching name are overwritten by design ('If the file already exists, it will be overwritten').

Source

Thrown at app/controllers/v1/video.py:425

    request_id = base.get_task_id(request)
    safe_filename = _sanitize_upload_filename(file.filename, request_id)
    # check file ext
    allowed_suffixes = ("mp4", "mov", "avi", "flv", "mkv", "jpg", "jpeg", "png")
    suffix = pathlib.Path(safe_filename).suffix.lower().lstrip(".")
    # 按完整扩展名校验,既兼容 .MOV 这类大写后缀,也避免 photojpg 这种没有
    # 点号的文件名因为 endswith("jpg") 被误当成合法图片。
    if suffix in allowed_suffixes:
        local_videos_dir = utils.storage_dir("local_videos", create=True)
        save_path = os.path.join(local_videos_dir, safe_filename)
        # save file
        with open(save_path, "wb+") as buffer:
            # If the file already exists, it will be overwritten
            file.file.seek(0)
            buffer.write(file.file.read())
        response = {"file": safe_filename}
        return utils.get_response(200, response)

    raise HttpException(
        "", status_code=400, message=f"{request_id}: Only files with extensions {', '.join(allowed_suffixes)} can be uploaded"
    )

@router.get("/stream/{file_path:path}")
async def stream_video(request: Request, file_path: str):
    request_id = base.get_task_id(request)
    tasks_dir = utils.task_dir()
    video_path = _resolve_path_within_directory(tasks_dir, file_path, request_id)
    range_header = request.headers.get("Range")
    video_size = os.path.getsize(video_path)
    start, end = _parse_byte_range(range_header, video_size, request_id)
    length = end - start + 1

    def file_iterator(file_path, offset=0, bytes_to_read=None):
        with open(file_path, "rb") as f:
            f.seek(offset, os.SEEK_SET)
            remaining = bytes_to_read or video_size
            while remaining > 0:

View on GitHub (pinned to 1f9f19c202)

Solutions

  1. Convert the clip to an allowed format before upload (e.g. ffmpeg -i in.mov -c:v libx264 -c:a aac out.mp4).
  2. Client-side pre-check: Path(filename).suffix.lower() in allowed_suffixes read from the same list the server uses.
  3. Make sure the filename has exactly one intended extension and no trailing dots/spaces.

Example fix

# before
files = {"file": ("holiday.mov", open("holiday.mov", "rb"))}
requests.post(url, files=files, headers=h)

# after
subprocess.run(["ffmpeg", "-y", "-i", "holiday.mov", "-c:v", "libx264", "-c:a", "aac", "holiday.mp4"], check=True)
files = {"file": ("holiday.mp4", open("holiday.mp4", "rb"))}
requests.post(url, files=files, headers=h)
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path
allowed = {".mp4", ".webm", ".jpg", ".jpeg", ".png"}  # keep in sync with server
suffix = Path(filename).suffix.lower()
if suffix not in allowed:
    raise ValueError(f"transcode first; allowed: {sorted(allowed)}")

Type guard

def is_allowed_material(filename: str, allowed_suffixes: set[str]) -> bool:
    return Path((filename or "").strip()).suffix.lower() in allowed_suffixes

Prevention

When it happens

Trigger: POST /api/v1/video_materials with a .mov/.avi/.wmv (or any non-allowed) extension; a filename with no extension at all; a file named 'clip.mp4.exe' style double extensions where the final suffix fails the check.

Common situations: Users exporting clips from phone editors in HEVC (.mov) when only web-friendly formats are allowed; clients not validating extension before upload; uppercase-extension files if allowed_suffixes are lowercase-only.

Related errors


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