Batch-compress a folder of images and videos to hit a target total size — with a live quality preview before you commit.
Shrinkbox is a Windows desktop app built with Python + PyQt6. You pick a source folder and a target size (e.g. 500 MB), and Shrinkbox distributes that budget proportionally across every image and video in the folder, compresses them in the background, and saves the results to an output folder — preserving the original directory structure.
- Features
- Screenshots / Quick Tour
- Download & Run (End Users)
- Building from Source
- Packaging as a Standalone .exe
- Project Structure
- How It Works
- Configuration & Limits
- Contributing
- License
- Batch compression — point at any folder (scanned recursively) and set one target size in MB.
- Proportional budget distribution — larger files get a proportionally larger slice of the budget, so quality degrades evenly across the whole folder.
- Live quality preview — scrub a slider to preview what an image or video will look like at the calculated quality before anything is written to disk.
- Scroll-wheel zoom toward cursor and drag to pan on both image and video previews.
[−],[Fit],[+]zoom controls.
- Video codec choice: H.264 (widest compatibility) or AV1 (~40% smaller, requires a modern player).
- Image format choice: keep the original format, or convert everything to JPEG, WebP, or AVIF.
- Supported input formats:
- Images: JPEG, PNG, WebP, BMP, TIFF
- Videos: MP4, MOV, MKV, AVI, WMV, M4V, WebM — any container ffmpeg can read
- Per-file progress bar — video encodes show a smooth 0→100% fill as ffmpeg works through pass 2.
- Parallel image processing — images are compressed simultaneously across all CPU cores for faster batch runs.
- Minimum bitrate safety net — if a video's budget is too tight for a watchable encode, Shrinkbox compresses it at the 100 kbps floor instead of giving up; the log flags it with ⚠ min bitrate applied.
- Live overall progress — file table status column, overall progress bar, scrollable log, and a cancel button (finishes the current file then stops).
- Non-destructive — source files are never modified; output always goes to a separate folder.
- Files already at or below their budget are copied unchanged.
Compress entire folders of images and videos to hit a target size.
Compress with confidence with a live preview before you commit.
| Step | What you do |
|---|---|
| 1 | Choose Source Folder and Output Folder |
| 2 | Set a Target Size in MB |
| 3 | Pick a Video codec and Image format in the encoding panel |
| 4 | Click a file row to open the Quality Preview panel |
| 5 | Adjust the preview slider; click Apply to lock in per-file targets |
| 6 | Click Compress All — a progress dialog shows live per-file and overall status |
| 7 | When done, click Open Output Folder |
- Go to the Releases page and download the latest
Shrinkbox.zip. - Extract the zip anywhere — no installer required.
- Run
Shrinkbox.exeinside the extracted folder.
Requirements: Windows 10/11 (64-bit). Everything else — Python, Qt, ffmpeg — is bundled inside the zip.
| Tool | Version | Notes |
|---|---|---|
| Python | 3.11 or 3.12 | python.org |
| ffmpeg + ffprobe | Any recent GPL build | Must be on PATH — see below |
Install ffmpeg (development only):
Download ffmpeg-master-latest-win64-gpl.zip from BtbN/FFmpeg-Builds. Extract it and add the inner bin\ folder to your system PATH.
For AV1 encoding in development, your ffmpeg build needs
libsvtav1orlibaom-av1. Most BtbN GPL builds include at leastlibaom-av1. The bundled exe useslibsvtav1, which is significantly faster.
git clone https://github.com/dliu04/shrinkbox.git
cd shrinkbox
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txtpython main.pyThe repo ships with a ready-made PyInstaller spec (shrinkbox.spec) that bundles Python, all dependencies, and ffmpeg into a single folder.
Download ffmpeg-master-latest-win64-gpl.zip from BtbN/FFmpeg-Builds.
Copy all files from inside the zip's bin\ folder into a bin\ folder at the project root — this includes ffmpeg.exe, ffprobe.exe, and all shared library DLLs:
shrinkbox\
bin\
ffmpeg.exe
ffprobe.exe
avcodec-*.dll
avformat-*.dll
avutil-*.dll
avfilter-*.dll
avdevice-*.dll
swscale-*.dll
swresample-*.dll
postproc-*.dll ← copy everything; the spec globs bin\* automatically
main.py
shrinkbox.spec
...
bin\is gitignored — large binaries should not be committed.
pip install "pyinstaller>=6.0"pyinstaller shrinkbox.specdist\
Shrinkbox\
Shrinkbox.exe ← launch this
_internal\
ffmpeg.exe
ffprobe.exe
... (Qt, Python, and ffmpeg DLL runtime files)
Zip the entire dist\Shrinkbox\ folder and attach it to a GitHub Release.
shrinkbox/
├── main.py # Entry point; dependency check, then MainWindow
├── requirements.txt # Runtime Python dependencies
├── shrinkbox.spec # PyInstaller build script
├── resources/
│ └── icon.ico # Application icon
│
├── core/
│ ├── compression_settings.py # VideoCodec / ImageFormat / CompressionSettings
│ ├── file_scanner.py # Recursive folder scan → list[FileInfo]
│ ├── budget.py # Proportional size-budget distribution
│ ├── estimator.py # Preview-quality encode (temp file, no original touched)
│ ├── image_compressor.py # Pillow-based image compression (JPEG/WebP/PNG/AVIF)
│ ├── video_compressor.py # ffmpeg two-pass H.264 / AV1 video compression
│ └── worker.py # QThread background worker; images run in parallel
│
├── ui/
│ ├── main_window.py # Main application window + file table + encoding panel
│ ├── preview_panel.py # Inline quality-preview panel (image + video)
│ ├── preview_dialog.py # Standalone preview dialog
│ └── progress_dialog.py # Compression progress dialog with per-file bar
│
└── utils/
├── ffmpeg_utils.py # ffmpeg/ffprobe subprocess wrappers + encoder detection
└── size_utils.py # Byte ↔ MB helpers, human_readable()
Given a target total size T and N files with original sizes s₁…sₙ:
Files already smaller than their budget are excluded from the pool and their unused budget is redistributed to the remaining files (iteratively).
| Output format | Strategy |
|---|---|
| JPEG / WebP | Binary search on quality (1–95) to land at or below the budget |
| AVIF | Binary search on quality (1–90; 100 = lossless) |
| PNG | Lossless optimize=True first; falls back to 256-colour palette quantisation |
| BMP / TIFF | Re-encoded as JPEG (no native lossy compression in these formats) |
When an image format conversion is selected (JPEG / WebP / AVIF), every input image is re-encoded in the chosen format regardless of its original type, and the output file gets the corresponding extension.
Two-pass encoding via ffmpeg:
128 kbps is reserved for the AAC audio track. If the computed bitrate falls below 100 kbps, the encoder clamps it to the floor and notes the overrun in the log.
| Codec | Encoder | Notes |
|---|---|---|
| H.264 | libx264 -preset medium |
Default; plays on every device |
| AV1 | libsvtav1 (preferred) or libaom-av1 (fallback) |
~40% smaller; detected at runtime |
Images are dispatched to a ThreadPoolExecutor sized to the logical CPU count. Videos are kept sequential because each ffmpeg subprocess already saturates all cores.
Pass 2 is launched with ffmpeg -progress pipe:1, which streams structured key=value pairs to stdout. The worker parses out_time_us= events, converts to a 0–100 percentage using the known clip duration, and emits a file_progress signal to update the dialog's progress bar in real time.
Clicking Cancel calls QThread.requestInterruption(). The worker checks between files — a running ffmpeg encode completes before stopping. The UI shows "Cancelling… (finishing the current file, then stopping)".
| Setting | Default | Location |
|---|---|---|
| Minimum video bitrate | 100 kbps | core/video_compressor.py → MIN_VIDEO_BITRATE_KBPS |
| Audio track bitrate | 128 kbps | core/video_compressor.py → AUDIO_BITRATE_KBPS |
| JPEG / WebP quality search range | 1–95 | core/image_compressor.py → _QUALITY_MIN / _QUALITY_MAX |
| AVIF quality search range | 1–90 | core/image_compressor.py → _AVIF_QUALITY_MAX |
| Preview clip length | 5 s | core/estimator.py → PREVIEW_CLIP_SECONDS |
| Target size debounce | 400 ms | ui/main_window.py → _target_debounce |
- Fork the repo and create a feature branch.
- Run the app from source (
python main.py) and verify your change works end-to-end. - Keep new modules in the appropriate
core/,ui/, orutils/package. - Open a pull request with a clear description of what changed and why.
There is no test suite yet — contributions that add one are very welcome.
Shrinkbox is distributed under the GNU General Public License v3.0.
See LICENSE for the full text.
Why GPL v3? Shrinkbox uses PyQt6, which is licensed under GPL v3. Any application that links against PyQt6 must also be GPL v3 (or hold a commercial Riverbank Computing license). The bundled ffmpeg binaries are built with GPL codecs (libx264, libsvtav1); their source is available from the FFmpeg project.
App icon — "Box" by Sergei Kokota, from the Office Vol.7 Icons pack on icon-icons.com. Licensed under CC BY 4.0. Converted to ICO format for use as the application icon.
- Features
- Screenshots / Quick Tour
- Download & Run (End Users)
- Building from Source
- Packaging as a Standalone .exe
- Project Structure
- How It Works
- Configuration & Limits
- Contributing
- License
- Batch compression — point at any folder (scanned recursively) and set one target size in MB.
- Proportional budget distribution — larger files receive a proportionally larger slice of the budget, so quality degrades evenly across the whole folder.
- Live quality preview — before compressing anything, scrub a slider to preview what an image or video will look like at the calculated quality.
- Scroll-wheel zoom toward cursor and drag to pan on both image and video previews.
[−],[Fit],[+]zoom controls.
- Image formats: JPEG, WebP (lossy quality search), PNG (lossless optimize → palette quantize), BMP/TIFF (re-encoded as JPEG).
- Video formats: Any container ffmpeg can read, re-encoded with two-pass libx264 to a target bitrate.
- Pre-flight warnings — if a video's target bitrate would fall below a usable minimum (~100 kbps), you get a warning dialog listing the affected files before compression starts.
- Live progress — per-file status in the file table, an overall progress bar, a scrollable log, and a cancel button (finishes the current file before stopping).
- Non-destructive — source files are never touched; output always goes to a separate folder.
- Files already at or below their budget are copied unchanged.
Compress entire folders of images and videos to hit a target size.
Compress with confidence with a live preview before you commit.
| Step | What you do |
|---|---|
| 1 | Choose Source Folder and Output Folder |
| 2 | Set a Target Size in MB |
| 3 | Click a file row to open the Quality Preview panel |
| 4 | Adjust the preview slider; click Apply if you want to lock in custom settings per-file |
| 5 | Click Compress All — a progress dialog shows live status |
| 6 | When done, click Open Output Folder |
- Go to the Releases page and download the latest
Shrinkbox.zip. - Extract the zip anywhere — no installer required.
- Run
Shrinkbox.exeinside the extracted folder.
Requirements: Windows 10/11 (64-bit). Everything else — Python, Qt, ffmpeg — is bundled inside the zip.
| Tool | Version | Notes |
|---|---|---|
| Python | 3.11 or 3.12 | python.org |
| ffmpeg + ffprobe | Any recent static build | Must be on PATH — see below |
Install ffmpeg (development only):
Download a Windows static build from BtbN/FFmpeg-Builds — pick ffmpeg-master-latest-win64-gpl.zip. Extract it and add the inner bin\ folder to your system PATH.
git clone https://github.com/dliu04/shrinkbox.git
cd shrinkbox
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txtpython main.pyThe repo ships with a ready-made PyInstaller spec (shrinkbox.spec) that bundles Python, all dependencies, and ffmpeg into a single folder.
Download ffmpeg-master-latest-win64-gpl.zip from BtbN/FFmpeg-Builds.
Copy all files from inside the zip's bin\ folder into a bin\ folder at the project root — this includes ffmpeg.exe, ffprobe.exe, and all av*.dll / sw*.dll shared libraries:
shrinkbox\
bin\
ffmpeg.exe
ffprobe.exe
avcodec-*.dll
avformat-*.dll
avutil-*.dll
swscale-*.dll
swresample-*.dll
avfilter-*.dll
avdevice-*.dll
postproc-*.dll ← copy everything; the spec globs bin\* automatically
main.py
shrinkbox.spec
...
bin\is gitignored — these large binaries should not be committed.
pip install "pyinstaller>=6.0"pyinstaller shrinkbox.specdist\
Shrinkbox\
Shrinkbox.exe ← launch this
ffmpeg.exe
ffprobe.exe
... (Qt and Python runtime files)
Zip the entire dist\Shrinkbox\ folder and attach it to a GitHub Release.
shrinkbox/
├── main.py # Entry point; dependency check, then MainWindow
├── requirements.txt # Runtime Python dependencies
├── shrinkbox.spec # PyInstaller build script
│
├── core/
│ ├── file_scanner.py # Recursive folder scan → list[FileInfo]
│ ├── budget.py # Proportional size-budget distribution
│ ├── estimator.py # Preview-quality size estimation (no disk I/O)
│ ├── image_compressor.py # Pillow-based image compression
│ ├── video_compressor.py # ffmpeg two-pass libx264 video compression
│ └── worker.py # QThread background compression worker
│
├── ui/
│ ├── main_window.py # Main application window + file table
│ ├── preview_panel.py # Inline quality-preview panel (image + video)
│ ├── preview_dialog.py # Standalone preview dialog
│ └── progress_dialog.py # Compression progress dialog
│
└── utils/
├── ffmpeg_utils.py # ffmpeg/ffprobe subprocess wrappers
└── size_utils.py # Byte ↔ MB helpers, human_readable()
Given a target total size T and N files with original sizes s₁…sₙ:
Files already smaller than their budget are excluded from the pool and their unused budget is redistributed to the remaining files (iteratively).
- JPEG / WebP — binary search on the Pillow
qualityparameter (1–95) until the encoded size lands at or below the budget. - PNG — lossless
optimize=Truefirst; if still over budget, quantize to a 256-color palette. - BMP / TIFF — converted to JPEG at the output path (these formats have no native lossy compression).
Two-pass libx264 encoding via ffmpeg:
128 kbps is reserved for the AAC audio track. If the resulting video bitrate is below 100 kbps, a ValueError is raised and the worker copies the original unchanged (the UI warns you beforehand).
Clicking Cancel calls QThread.requestInterruption(). The worker checks between files — a running ffmpeg encode is never killed mid-process; it completes first. The UI shows "Cancelling… (waiting for current file to finish)".
| Setting | Default | Location |
|---|---|---|
| Minimum video bitrate | 100 kbps | core/video_compressor.py → MIN_VIDEO_BITRATE_KBPS |
| Audio track bitrate | 128 kbps | core/video_compressor.py → AUDIO_BITRATE_KBPS |
| JPEG quality search range | 1–95 | core/image_compressor.py → _QUALITY_MIN/_MAX |
- Fork the repo and create a feature branch.
- Run the app from source (
python main.py) and verify your change works end-to-end. - Keep new modules in the appropriate
core/,ui/, orutils/package. - Open a pull request with a clear description of what changed and why.
There is no test suite yet — contributions that add one are very welcome.
Shrinkbox is distributed under the GNU General Public License v3.0.
See LICENSE for the full text.
Why GPL v3? Shrinkbox uses PyQt6, which is licensed under GPL v3. Any application that links against PyQt6 must also be GPL v3 (or hold a commercial Riverbank Computing license). The bundled ffmpeg binaries are built with GPL codecs (libx264); their source is available from the FFmpeg project.
App icon — "Box" by Sergei Kokota, from the Office Vol.7 Icons pack on icon-icons.com. Licensed under CC BY 4.0. Converted to ICO format for use as the application icon.