Skip to content

DavoInMelbourne/BulkVideoCompressor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bulk Video Compressor

A lightweight macOS desktop app that batch-compresses video files using ffmpeg. Scan a folder, review the detected settings, and let it encode — all with a clean progress UI.

Built for people who want to compress a library of movies or TV shows quickly without manually adding files to an encoder one by one. Point it at a folder and walk away.

Python PyQt6 Platform


Author

Made with 💛 by Paul Davies

Like this project? Help keep the lights on at weluvbeer.com by buying me a Ko-fi!

ko-fi


Features

  • Recursively scans a source folder for video files across any number of subdirectories
  • Handles files with or without a dedicated container folder
  • Auto-detects resolution, FPS, audio tracks and subtitles per file
  • Review all files before encoding — see exactly what will be processed
  • Encoding presets — separate presets for 1080p and 4K content:
    • Fast H.264 (Hardware) — VideoToolbox h264, quality 50
    • Fast H.265 (Hardware) — VideoToolbox HEVC, quality 50 (default)
    • Fast H.264 — x264, fast preset, RF 22
    • Balanced H.265 — x265, medium preset, RF 20
    • Quality H.265 12-bit — x265 12-bit, medium preset, RF 20
    • Quality AV1 — SVT-AV1, preset 4, RF 30
  • Audio bitexact stream copy — the original audio is copied byte-for-byte, preserving codec, channels, channel layout and bitrate
  • Smart audio track selection — configurable language preference with automatic fallback; prefers DTS/TrueHD within the chosen language, then falls back to the fallback language, then the first available track
  • Language Preferences — independent dropdowns for audio track language, subtitle language, and a fallback language used when the preferred language is not found:
    • Original Language (default) — uses the language of the first audio track in each file, so a mixed batch of English, Korean, French films each picks its own native language automatically
    • Non-English — always picks the first non-English track, regardless of language; useful for batches of foreign-language content
    • Specific language — force a particular language (English, Korean, Japanese, Thai, Vietnamese, French, German, Italian, Spanish, Dutch, Portuguese, Chinese, Polish, Danish, Swedish, Finnish, Norwegian) for the whole batch
  • Prioritise DTS — when checked, DTS and TrueHD tracks are preferred within the selected language
  • Subtitle language — independently selectable; always defaults to English; falls back to the fallback language, then English if the chosen language is not present
  • Subtitles passed through at most 1 Forced + 1 Regular + 1 SDH per file, never burned in
  • Mirrors source folder structure in the output directory
  • Copies non-video files (subtitles, artwork, NFO etc.) per-directory as each directory is encoded
  • Post-encode verification — two-stage check using ffprobe:
    1. Duration check — output must match source within 2 seconds
    2. Audio packet scan — streams every audio packet timestamp looking for gaps (>1s) and backwards jumps that cause loud-bang / desync issues
  • Reverse compression detection — if the output is larger than the source, the original is copied to the output path instead
  • Post-processing — automatically rename and clean up after encoding:
    • Successful encode: output folder (or file) renamed with a custom suffix, e.g. Movie.Done
    • Failed encode or zero compression (output no smaller than source): source folder renamed with the problem suffix, e.g. Movie.Check; source file is copied to the output so nothing is lost
    • Orphaned extras (subtitles, artwork etc.) are cleaned up if an encode fails partway through
    • Optionally delete the source file/folder after a verified successful encode
  • Scrollable per-file progress panel with FPS and ETA
  • Cancel individual files or stop everything immediately
  • Dismiss completed rows individually or clear all finished files
  • Add more files to the queue while encoding is already running
  • Thermal throttle safeguards — monitors encoding FPS and automatically pauses to let the machine cool down when performance degrades, plus proactive scheduled cooldowns to prevent overheating during long batches
  • Process safety — graceful SIGTERM shutdown for ffmpeg (preserves VideoToolbox GPU encoder sessions), orphan process cleanup on startup, bounded memory buffers, explicit pipe closure

Download

Don't want to use the terminal? Grab the latest built app from the Releases page.

  1. Download BulkVideoCompressor.app.zip
  2. Unzip and drag to your Applications folder
  3. Right-click → Open the first time to get past macOS Gatekeeper
  4. Grant Full Disk Access in System Settings → Privacy & Security → Full Disk Access

ffmpeg is still required: brew install ffmpeg


Prerequisites

  • Python 3.9+
  • ffmpeg and ffprobe — install via Homebrew: brew install ffmpeg
  • MediaInfo (optional, falls back to ffprobe) — brew install mediainfo

Quick Start

git clone https://github.com/DavoInMelbourne/BulkVideoCompressor.git
cd BulkVideoCompressor

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

python3 main.py

Commands

Run the app

python3 main.py

Run the tests

pip install pytest
python3 -m pytest tests/ -v

Build a standalone macOS app (optional)

pip install pyinstaller
pyinstaller --onefile --windowed --name "BulkVideoCompressor" main.py

The app will be in dist/.


Usage

  1. Source Directory — folder to scan (recursive)
  2. Output Directory — where encoded files are written, mirroring the source structure
  3. 1080p Preset — encoding preset for content below 4K
  4. 4K Preset — encoding preset for 2160p/3840p+ content
  5. Audio Language — preferred audio track language for the batch (see Language Preferences above)
  6. Subtitles — preferred subtitle language (default: English)
  7. Fallback — language used for both audio and subtitles if the preferred language is not found in a file
  8. Prioritise DTS — when checked, DTS and TrueHD tracks are preferred within the selected language
  9. RF Quality — Constant Quality value (set automatically by preset; lower = better quality / larger file)
  10. Success suffix — text appended to the output folder/file name on success (e.g. DoneMovie.Done); leave blank to skip
  11. Problem suffix — text appended to the source folder/file name on failure (e.g. CheckMovie.Check); leave blank to skip
  12. Delete source files — if checked, deletes the source file and its folder after a verified successful encode
  13. Min FPS — minimum expected encoding FPS at 10% progress (see Thermal Safeguards below)
  14. Cool every / for — proactive cooldown interval (see Thermal Safeguards below)
  15. ffmpeg Path — auto-detected; override if needed
  16. Click Scan & Review to inspect detected settings
  17. Click Add to Queue to start encoding

Post-processing behaviour

Scenario has container folder Result
Success Yes Output folder renamed, e.g. MovieMovie.Done
Success No (file in root) Output file renamed, e.g. Movie.mkvMovie.Done.mkv
Failed/error Yes Source folder renamed, e.g. MovieMovie.Check
Failed/error No (file in root) Source file renamed, e.g. Movie.mkvMovie.Check.mkv
Delete source Yes Source file deleted, then source folder deleted
Delete source No (file in root) Source file deleted only (root folder is never deleted)

Status badges

Badge Colour Meaning
Waiting Grey Queued, not yet started
Encoding Blue Currently being encoded
Running large Orange Output is tracking larger than source at 25%
Scanning... Orange Post-encode audio packet scan in progress
Safe to delete Green Verified — original can be removed (shows final FPS)
Keep original Red Verification failed — do not delete source
Reverse compression Purple Output was larger — original copied to output
Problem file Orange File cannot reach minimum FPS threshold
Failed Red Encode failed
ERROR Red Unexpected crash (caught and logged)
Cancelled Grey Manually cancelled

Encoding Settings

Setting Value
Video encoder Selectable per resolution — x264, x265, AV1, or VideoToolbox
Quality mode Constant Quality (RF) or VideoToolbox quality level
Framerate Same as source; 50 fps is halved to 25 fps
Resolution / crop No change (pass-through)
Audio Bitexact stream copy — codec, channels and layout preserved
Subtitles Forced, Regular, SDH — stream copy, never burned in
Container MKV

Thermal Safeguards

Long batch encodes are vulnerable to two things that silently kill FPS: thermal throttling and macOS power management. The app handles both automatically.

App Nap prevention (caffeinate)

When you lock your screen or leave your Mac idle, macOS aggressively throttles background processes via App Nap — FPS can drop from 250 to single digits even though the machine is cold. The app automatically runs caffeinate -dims while encoding is active, which prevents display sleep, idle sleep, disk sleep, and system sleep. This keeps ffmpeg running at full speed even when you walk away. caffeinate is stopped automatically when encoding finishes or the app is closed.

How it works

  1. Baseline capture — on the first file, at 10% progress, the app records the current FPS as the baseline and displays it on the form (e.g. 250 Base FPS). If the baseline is below your Min FPS setting, encoding stops with a prompt to lower the value.

  2. Problem file detection — if a file can't reach the Min FPS threshold at 10% progress, it's retried after a 10-second pause. If it still can't hit the threshold, it's marked as a problem file (orange) and skipped.

  3. Proactive cooldown — a scheduled pause is inserted every N files (default: 10) for a configurable duration (default: 2 minutes). This prevents thermal buildup before it starts affecting FPS.

  4. Batch summary — at the end of a run, a summary shows total files processed, problem files, and total cooldown time.

Recommended settings

Setting Default Notes
Min FPS 200 Suitable for most modern machines (less than ~2 years old). Lower this if your machine consistently runs below 200 FPS — the baseline will tell you where your machine sits.
Cool every 10 files How often to insert a proactive cooldown. Increase if your machine stays cool, reduce if it runs hot.
Cool for 2.0 min Duration of each proactive cooldown. The defaults are intentionally proactive to keep throughput high, but can be tailored per machine.

Process Safety

The app is designed for unattended overnight batch encoding. Key safeguards:

  • Graceful process shutdown — ffmpeg is sent SIGTERM first, with a 5-second window to release VideoToolbox hardware encoder sessions. SIGKILL is only used as a fallback. This prevents GPU encoder slot exhaustion that causes progressive FPS degradation.
  • Orphan cleanup — on startup, any leftover ffmpeg/ffprobe processes from a previous crash are detected and killed.
  • Stall detection — if ffmpeg produces no measurable encoding progress for 10 minutes, the process is terminated. Files with lossless audio (DTS-HD MA, TrueHD) that report time=N/A are handled correctly — activity is detected via FPS output so they are never incorrectly killed.
  • Bounded buffers — the progress-reading buffer is capped at 8KB to prevent unbounded memory growth.
  • Pipe cleanup — stdout pipes are explicitly closed after every encode to prevent file descriptor leaks.
  • Streaming verification — audio packet scanning streams ffprobe output line-by-line instead of loading it all into memory.
  • Memory monitoring — each ffmpeg process is watched via psutil; if RSS exceeds 12GB the encode is killed with a clear error.
  • App Nap preventioncaffeinate -dims runs during encoding to prevent macOS from throttling ffmpeg when the screen is locked or idle.
  • Large file support — the encode queue correctly handles files of any size; post-processing runs asynchronously so a slow verification scan on a large output file does not stall the queue.

Supported Input Formats

.mkv .mp4 .avi .mov .ts .m2ts


Project Structure

BulkVideoCompressor/
  main.py                  # App entry point
  requirements.txt         # Python dependencies
  core/
    handbrake.py           # ffmpeg process management, verification, orphan cleanup
    languages.py           # Language enum (audio/subtitle language preferences)
    mediainfo.py           # Video file probing (metadata extraction)
    queue_builder.py       # Audio/subtitle track selection logic
    scanner.py             # Directory scanning and output path mapping
  ui/
    main_window.py         # Main UI, queue management, state machine
    workers.py             # Background threads (ProbeWorker, EncodeWorker)
    review_dialog.py       # Pre-encoding review modal
  tests/
    test_process_cleanup.py  # 33 tests covering process safety and thermal safeguards

Cutting a Release

When you're ready to publish a new version:

# 1. Build the app
pip install pyinstaller
pyinstaller --onefile --windowed --name "BulkVideoCompressor" main.py

# 2. Zip it (GitHub needs a zip — .app files are actually folders)
cd dist && zip -r BulkVideoCompressor.app.zip BulkVideoCompressor.app && cd ..

# 3. Tag the release in git
git tag v1.0.0
git push origin v1.0.0

Then on GitHub:

  • Go to ReleasesDraft a new release
  • Pick the tag you just pushed
  • Attach dist/BulkVideoCompressor.app.zip
  • Publish

Local use: drag dist/BulkVideoCompressor.app straight to your Applications folder — no need to zip.

GitHub Release: attach the .zip — when users download and unzip it they get the .app.

The download link in this README will point users straight to the Releases page.


License

MIT

About

Batch compress video files on macOS using ffmpeg — scan a folder, review settings, and encode with thermal safeguards and post-encode verification.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages