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).
- Python 3.10+
ffmpegandffprobeonPATH(https://ffmpeg.org/download.html)
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.
# 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 fallbackrun.py forwards every argument after the script name straight to
compress.py, so all the flags below also work when invoked through
run.py.
ffprobereads the input's duration, video stream, and audio stream.- Total bitrate = target size / duration. Audio bitrate is subtracted (auto-scaled for small targets), leaving the video budget.
- ffmpeg runs a 2-pass CBR encode using the chosen codec and container.
A
--marginfactor (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).
--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.
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 ./outpython 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.
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:
- Grab
mpv-2.dll,libmpv-2.dll, ormpv-1.dllfrom any recent mpv Windows build (for example the same zhongfly release the launcher uses). - Drop it into
.venv/mpv/next to this repo. - 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
PATHonly for the GUI subprocess, so the systemPATHis left alone.
macOS
brew install mpvThis 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 prefixLinux
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.