Skip to content

Latest commit

 

History

41 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Shrinkbox

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.


Table of Contents


Features

  • 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.

Screenshots / Quick Tour

image

Compress entire folders of images and videos to hit a target size.

image

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

Download & Run (End Users)

  1. Go to the Releases page and download the latest Shrinkbox.zip.
  2. Extract the zip anywhere — no installer required.
  3. Run Shrinkbox.exe inside the extracted folder.

Requirements: Windows 10/11 (64-bit). Everything else — Python, Qt, ffmpeg — is bundled inside the zip.


Building from Source

Prerequisites

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 libsvtav1 or libaom-av1. Most BtbN GPL builds include at least libaom-av1. The bundled exe uses libsvtav1, which is significantly faster.

Clone and install

git clone https://github.com/dliu04/shrinkbox.git
cd shrinkbox
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt

Run

python main.py

Packaging as a Standalone .exe

The repo ships with a ready-made PyInstaller spec (shrinkbox.spec) that bundles Python, all dependencies, and ffmpeg into a single folder.

1 — Get ffmpeg binaries for bundling

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.

2 — Install PyInstaller

pip install "pyinstaller>=6.0"

3 — Build

pyinstaller shrinkbox.spec

4 — Output

dist\
  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.


Project Structure

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()

How It Works

Budget distribution (core/budget.py)

Given a target total size T and N files with original sizes s₁…sₙ:

$$\text{budget}_i = T \times \frac{s_i}{\sum_{j=1}^{N} s_j}$$

Files already smaller than their budget are excluded from the pool and their unused budget is redistributed to the remaining files (iteratively).

Image compression (core/image_compressor.py)

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.

Video compression (core/video_compressor.py)

Two-pass encoding via ffmpeg:

$$\text{video kbps} = \frac{\text{target bytes} \times 8}{\text{duration seconds} \times 1000} - 128$$

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

Parallel image processing (core/worker.py)

Images are dispatched to a ThreadPoolExecutor sized to the logical CPU count. Videos are kept sequential because each ffmpeg subprocess already saturates all cores.

Per-file video progress

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.

Cancellation

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)".


Configuration & Limits

Setting Default Location
Minimum video bitrate 100 kbps core/video_compressor.pyMIN_VIDEO_BITRATE_KBPS
Audio track bitrate 128 kbps core/video_compressor.pyAUDIO_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.pyPREVIEW_CLIP_SECONDS
Target size debounce 400 ms ui/main_window.py_target_debounce

Contributing

  1. Fork the repo and create a feature branch.
  2. Run the app from source (python main.py) and verify your change works end-to-end.
  3. Keep new modules in the appropriate core/, ui/, or utils/ package.
  4. 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.


License

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.


Acknowledgements

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.


Table of Contents


Features

  • 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.

Screenshots / Quick Tour

image

Compress entire folders of images and videos to hit a target size.

image

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

Download & Run (End Users)

  1. Go to the Releases page and download the latest Shrinkbox.zip.
  2. Extract the zip anywhere — no installer required.
  3. Run Shrinkbox.exe inside the extracted folder.

Requirements: Windows 10/11 (64-bit). Everything else — Python, Qt, ffmpeg — is bundled inside the zip.


Building from Source

Prerequisites

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.

Clone and install

git clone https://github.com/dliu04/shrinkbox.git
cd shrinkbox
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt

Run

python main.py

Packaging as a Standalone .exe

The repo ships with a ready-made PyInstaller spec (shrinkbox.spec) that bundles Python, all dependencies, and ffmpeg into a single folder.

1 — Get ffmpeg binaries for bundling

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.

2 — Install PyInstaller

pip install "pyinstaller>=6.0"

3 — Build

pyinstaller shrinkbox.spec

4 — Output

dist\
  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.


Project Structure

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()

How It Works

Budget distribution (core/budget.py)

Given a target total size T and N files with original sizes s₁…sₙ:

$$\text{budget}_i = T \times \frac{s_i}{\sum_{j=1}^{N} s_j}$$

Files already smaller than their budget are excluded from the pool and their unused budget is redistributed to the remaining files (iteratively).

Image compression (core/image_compressor.py)

  • JPEG / WebP — binary search on the Pillow quality parameter (1–95) until the encoded size lands at or below the budget.
  • PNG — lossless optimize=True first; 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).

Video compression (core/video_compressor.py)

Two-pass libx264 encoding via ffmpeg:

$$\text{video kbps} = \frac{\text{target bytes} \times 8}{\text{duration seconds} \times 1000} - 128$$

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).

Cancellation

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)".


Configuration & Limits

Setting Default Location
Minimum video bitrate 100 kbps core/video_compressor.pyMIN_VIDEO_BITRATE_KBPS
Audio track bitrate 128 kbps core/video_compressor.pyAUDIO_BITRATE_KBPS
JPEG quality search range 1–95 core/image_compressor.py_QUALITY_MIN/_MAX

Contributing

  1. Fork the repo and create a feature branch.
  2. Run the app from source (python main.py) and verify your change works end-to-end.
  3. Keep new modules in the appropriate core/, ui/, or utils/ package.
  4. 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.


License

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.


Acknowledgements

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.

About

An application to shrink your image and video files to hit a target folder size.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages