Skip to content

Repository files navigation

CompressTo

Compress video, audio, and image files down to a target file size (for example "compress to under 20 MB"), isntead of just target resolution. The tool computes the required total bitrate from duration, then runs a 2-pass ffmpeg encode so the output lands at the size you asked for, no more fuffing around with parameters to get it down to a size.

The CLI uses only the Python standard library. The GUI adds Qt6 (PySide6) and libmpv for an embedded timeline preview (fallback available).

Requirements

The launcher (run.py) takes care of creating a .venv, installing Python dependencies, and downloading the libmpv DLL on first GUI launch, so you do not need to do any of that by hand.

Quick start

# CLI
python run.py clip.mp4 --to 10MB
python run.py ./videos --to 50MB --codec h265
python run.py clip.mp4 --to discord          # preset -> 25MB

# GUI (Qt6, with a tkinter fallback)
python run.py gui
python run.py gui --tk    # force the tkinter fallback

run.py forwards every argument after the script name straight to compress.py, so all the flags below also work when invoked through run.py.

How it works

  1. ffprobe reads the input's duration, video stream, and audio stream.
  2. Total bitrate = target size / duration. Audio bitrate is subtracted (auto-scaled for small targets), leaving the video budget.
  3. ffmpeg runs a 2-pass CBR encode using the chosen codec and container. A --margin factor (default 0.98) is applied so muxing overhead keeps the result under the target.

If the resulting video bitrate would fall below --min-video-bitrate (default 500k) and look unusable, the tool can split the video into multiple output files, each targeting --to, so quality stays sane. You can opt into automatic splitting with --on-low-quality split, always stay as one file with single, or get asked interactively with ask (the default).

Flags

--codec h264|h265|vp9|av1          video codec (default: av1)
--container auto|mp4|webm|mkv      output container (default: auto)
--audio-bitrate 128k|96k|64k|auto  audio bitrate (default: auto)
--no-audio                         drop audio track
--max-height 720                   downscale tall videos, keep aspect
--scale 50%                        resolution as %% of original (any shape)
--max-fps 30                       cap framerate
--passes 1|2                       ffmpeg passes (default: 2)
--ffmpeg-preset veryfast           speed/quality preset for the encoder
--margin 0.98                      reserve this fraction of the target for mux
--on-low-quality ask|split|single  what to do when quality would be too low
--split-at 1:30,4:00               split video at timestamps, each -> --to
--segments 0:10-0:20,0:35-0:50     keep only these ranges, each -> --to
--plan                             print the plan as JSON, do not encode
--confirm                          show the per-section plan and ask first
--show-ffmpeg                      print full ffmpeg output
-y, --overwrite                    overwrite existing outputs

You can pass any of these directly to --to.

--audio-codec can be overridden by container rules, and ffmpeg-incompatible combos (for example h264 in webm) are rejected up front rather than failing mid-encode.

Batch mode

Passing a directory instead of a file processes every video, audio, and image in that folder. Use -o to point at an output directory; outputs default to <name>_<size>.<ext> next to each input.

python compress.py ./clips --to 25MB --codec h264
python compress.py ./clips --to 25MB -o ./out

GUI

python run.py gui
  • Built with PySide6 (Qt6). Auto-installs on first run.
  • Falls back to a tkinter GUI (gui_tk.py) if Qt is unavailable or you pass --tk. The tkinter build is stdlib-only.
  • Embeds a timeline preview using libmpv via python-mpv. On Windows the launcher downloads a pinned libmpv build on first GUI launch; on macOS and Linux you need libmpv installed system-wide. If libmpv is missing the GUI falls back to ffplay for previews.

libmpv setup (optional but recommended)

The timeline preview is optional, but is very useful if you wish to set custom time splits or segments. The GUI technically works without it but highly recommend you set it up so you have access to more precise segmenting.

Windows

python run.py gui automatically downloads the pinned libmpv build (AMD64 or ARM64) on first run, verifies its SHA-256, extracts the 7z (uses bsdtar; falls back to py7zr), and drops the DLL into .venv/mpv/. Nothing else is required.

If the auto-fetch fails (offline, antivirus blocks the download, unsupported arch, etc.) the GUI prints a [run] WARNING and uses ffplay for previews. You can also install libmpv by hand:

  1. Grab mpv-2.dll, libmpv-2.dll, or mpv-1.dll from any recent mpv Windows build (for example the same zhongfly release the launcher uses).
  2. Drop it into .venv/mpv/ next to this repo.
  3. Re-run python run.py gui. The launcher detects the DLL and skips the download.

Notes:

  • Windows Defender occasionally flags the unsigned mpv-2.dll. Allow it through if prompted.
  • The DLL is added to PATH only for the GUI subprocess, so the system PATH is left alone.

macOS

brew install mpv

This installs libmpv.2.dylib to /usr/local/lib (Apple Silicon) or /opt/homebrew/lib (Intel). python-mpv finds it via the standard ctypes lookup, so no extra env vars are needed in most cases. If it is not picked up:

export MPV_LIB_PATH=/usr/local/lib/libmpv.2.dylib   # adjust to your prefix

Linux

Install the system libmpv package. Names vary by distro:

  • Debian/Ubuntu: sudo apt install libmpv2
  • Fedora: sudo dnf install libmpv
  • Arch: sudo pacman -S mpv

python-mpv needs an API version of 1.108 or higher, which means libmpv 0.33+. If your distro ships an older libmpv, install a newer one (for example from Flatpak mpv) and point at it with MPV_LIB_PATH=/path/to/libmpv.so.2.

Verifying

The bundled mpv Python module loads libmpv at import time. If it loads cleanly, libmpv is wired up. If it raises an OSError like Cannot find mpv-2.dll / libmpv-2.dll / mpv-1.dll ... on Windows or Cannot find libmpv in the usual places ... on macOS/Linux, libmpv is not visible to Python. The GUI still runs in that case; it just falls back to ffplay for previews.

Releases

Packages

Contributors

Languages