MuxMaster (muxm) — a single-command video repacking and encoding utility that handles Dolby Vision, HDR10, audio track selection, subtitle processing, and container muxing so you don't have to. Pick a profile, point it at a file, and get a properly encoded output without memorizing ffmpeg flags.
# Install via Homebrew (macOS)
brew install TheBluWiz/muxm/muxm
# Encode for Apple TV Direct Play — that's it
muxm --profile atv-directplay-hq movie.mkv- Why MuxMaster?
- Format Profiles
- How It Works
- Installation
- Upgrading
- Usage
- Configuration
- Additional Features
- FAQ
- License
- Bug Reports
- Contact
- Author
Getting a Blu-ray rip to direct-play correctly on an Apple TV, a Roku, or through Plex — without the server transcoding on the fly — is a surprisingly deep problem. The video might be Dolby Vision Profile 7 that needs conversion to Profile 8.1. The audio might be TrueHD, which your player can't direct-play, so you need E-AC-3 — but you also want a stereo AAC fallback for when you're watching on a phone. The forced subtitles need to be burned in because MP4 containers don't handle PGS bitmaps. And the color space metadata needs to survive the whole process.
You can solve all of this with raw ffmpeg, but the command will be 15+ flags long, different for every source file, and you'll need to inspect the source with ffprobe first to decide what half of those flags should be. Every new file is a new puzzle.
HandBrake is the go-to GUI for video encoding, and it's excellent for what it does. But its preset system doesn't adapt to what's actually in the file. It can't detect that your source is already Apple TV-compliant and skip the encode. It doesn't extract Dolby Vision RPUs, convert between DV profiles, or inject them back into re-encoded video. It won't selectively OCR your PGS subtitles to SRT when the output container can't carry bitmaps. And it won't generate a JSON report of everything it did for your records. HandBrake gives you a good encode; MuxMaster gives you an opinionated pipeline that understands the relationship between your source, your target device, and every stream in the file.
Tdarr solves the batch-processing and automation problem well, especially at library scale. But it requires a server, a database, a web UI, and Node.js — it's infrastructure. If you want to process a single file, or a handful of files, with precise control over DV handling, audio track selection, and subtitle policy, Tdarr's plugin system means writing JavaScript to configure what muxm handles with a single --profile flag. Tdarr is a media library manager; MuxMaster is a per-file encoding tool that aims to make every decision correctly so you don't have to inspect the output.
MuxMaster sits in the gap between "I know ffmpeg well enough to do this manually" and "I need a server-based automation platform." It's a single Bash script with only three required dependencies (ffmpeg, jq, and bc) and optional tooling for Dolby Vision and subtitle OCR. It understands Dolby Vision at the RPU level, and its profile system encodes the tribal knowledge of what actually works on real hardware into repeatable, overridable presets.
Configuration is where the design philosophy comes together. Most CLI tools expect you to read the source code to learn which variables exist, then hand-build a dotfile from scratch. muxm --create-config generates a complete, commented config file pre-seeded with a real profile's values — you start from a working baseline and customize, not from a blank page. Configs cascade through three tiers (system, user, project) so an encoding team can lock organization defaults in /etc/.muxmrc while individuals override their preferred CRF or audio settings in ~/.muxmrc and specific project directories can pin a streaming profile. And --print-effective-config shows you the fully resolved result of all those layers before you commit to an encode, so you always know exactly what's about to happen.
Profiles are named presets that configure muxm for a specific use case in a single flag. Every setting a profile changes can be individually overridden with CLI flags.
muxm --profile <n> input.mkv
| Profile | Goal | Container | Video | Audio | DV |
|---|---|---|---|---|---|
dv-archival |
Lossless preservation | MKV | Copy (no re-encode) | Lossless passthrough | Preserve |
hdr10-hq |
Max HDR10 quality | MKV | HEVC CRF 17 | Lossless + stereo fallback | Strip |
atv-directplay-hq |
Apple TV Direct Play | MP4 | HEVC Main10 (copy if compliant) | E-AC-3 + AAC stereo | P8.1 auto |
streaming |
Modern HEVC streaming | MP4 | HEVC CRF 20 | E-AC-3 448k + AAC stereo | Strip |
animation |
Anime/cartoon optimized | MKV | HEVC CRF 16, 10-bit | Lossless + stereo fallback | Strip |
universal |
Play anywhere | MP4 | H.264 SDR (tone-map HDR) | AAC stereo | Strip |
For collectors who want bit-perfect preservation. Copies video without re-encoding, passes lossless audio through, keeps all subtitles and chapters, and generates a JSON report. Skips processing entirely if the source already matches.
muxm --profile dv-archival movie.mkv
Strips Dolby Vision layers and re-encodes to clean HDR10 HEVC at CRF 17. Preserves lossless audio (TrueHD, DTS-HD MA, FLAC) and adds a stereo fallback track. MKV output.
muxm --profile hdr10-hq movie.mkv
Targets true Direct Play on Apple TV 4K via Plex: MP4 container, HEVC Main10 with DV Profile 8.1 when possible, E-AC-3 surround with AAC stereo fallback, and forced subtitle burn-in. Copies compliant video without re-encoding. Skips processing if source is already ATV-compliant.
muxm --profile atv-directplay-hq movie.mkv
Optimized for Plex, Jellyfin, and Emby on modern clients: Shield, Fire TV, Roku Ultra, smart TVs, and web browsers. HEVC CRF 20 with E-AC-3 surround at streaming-friendly bitrates, AAC stereo fallback, and soft subtitles. Strips DV and keeps HDR10. Balances quality with file size.
muxm --profile streaming movie.mkv
Tuned for animation content: lower psy-rd/psy-rdoq to avoid ringing on hard cel edges, 10-bit even for SDR to eliminate banding in gradients, and lossless audio passthrough. MKV container preserves styled ASS/SSA subtitles. Keeps all subtitle tracks (up to 6) and chapter markers.
muxm --profile animation movie.mkv
Plays on everything: old Rokus, mobile devices, web browsers, non-HDR TVs. Tone-maps HDR to SDR, encodes to H.264, forces AAC stereo audio, burns forced subtitles, exports others as external SRT, and strips chapters and non-essential metadata.
muxm --profile universal movie.mkv
Profiles are starting points — every setting can be overridden with CLI flags:
# Use hdr10-hq but with a different CRF and no stereo fallback
muxm --profile hdr10-hq --crf 20 --no-stereo-fallback movie.mkv
# Use universal but keep chapters
muxm --profile universal --keep-chapters movie.mkv
# Use atv-directplay-hq but output to MKV (you'll get a warning)
muxm --profile atv-directplay-hq --output-ext mkv movie.mkv
Expand to see the full encoding pipeline
When you run muxm, the script executes a multi-stage pipeline that inspects the source file, makes codec and container decisions based on the active profile, processes each stream type independently, and assembles the final output. Here's what happens under the hood:
1. Source Inspection. muxm calls ffprobe once and caches the full JSON metadata for the source file. Every subsequent decision — codec detection, color space identification, audio channel layout, subtitle format — reads from this cache rather than re-probing.
2. Profile Resolution. Settings are resolved through a layered precedence chain: hardcoded defaults → system config (/etc/.muxmrc) → user config (~/.muxmrc) → project config (./.muxmrc) → profile → CLI flags. Contradictory combinations (like --profile dv-archival --no-dv) trigger warnings but never errors — CLI flags always win.
3. Video Pipeline. The video stage handles the most complexity. It detects Dolby Vision by probing both stream metadata and frame-level side data, then identifies the DV profile (5, 7, or 8) and compatibility ID. For profiles that preserve DV, it extracts the RPU (Reference Processing Unit) via dovi_tool, converts between DV profiles when necessary (e.g., Profile 7 dual-layer → Profile 8.1 single-layer), encodes the base layer with x265, and injects the RPU back into the encoded stream. For non-DV profiles, it detects the source color space (BT.2020 PQ, BT.2020 HLG, or BT.709 SDR), sets the matching x265 color parameters and pixel format, and applies tone-mapping when the profile targets SDR output.
4. Audio Pipeline. Audio track selection is language-preference-aware and codec-aware. When multiple tracks exist, muxm scores each one based on language match, channel count, surround layout, codec preference, and bitrate — with configurable weights in .muxmrc (see man muxm for details). The pipeline picks the best available track (honoring --audio-lang-pref), decides whether to copy it through or transcode it based on the profile's codec requirements, and optionally generates a stereo AAC fallback track from the surround source. Lossless codecs (TrueHD, DTS-HD MA, FLAC) are passed through untouched when the profile and container support it.
5. Subtitle Pipeline. Subtitles are categorized into forced, full, and SDH tracks. PGS bitmap subtitles are OCR'd to SRT (via pgsrip or sub2srt) when the output container can't carry them natively. Forced subtitles can be burned into the video stream; other tracks can be embedded or exported as external .srt files. The pipeline respects language preferences and can exclude SDH tracks.
6. Final Mux. All processed streams are assembled into the target container (MP4, MKV, M4V, or MOV) with correct codec tagging, chapter markers, subtitle disposition flags, and metadata. For Dolby Vision in MP4, muxm verifies that the dvcC/dvvC container signaling record is present via MP4Box.
7. Verification. The output file is validated with ffprobe to confirm it's non-empty and parseable. Optionally, a SHA-256 checksum is written and a JSON report is generated documenting every decision, warning, and stream mapping from the run.
If any stage fails, muxm logs the failure, cleans up incomplete temp files, and exits with a descriptive error code. The --dry-run flag executes the entire decision pipeline without writing real output, so you can preview exactly what muxm would do before committing to an encode.
muxm requires Bash 4.3+ and runs on macOS (10.15 Catalina or later) and modern Linux distributions (Ubuntu 20.04+, Fedora 33+, Debian 11+, Arch). It is tested primarily on macOS with Homebrew-installed ffmpeg builds.
brew install TheBluWiz/muxm/muxm
muxm --install-completions # bash/zsh tab completionThis installs muxm with its required dependencies (bash 4.3+, ffmpeg, jq) and the man page automatically.
Optional dependencies — for Dolby Vision, subtitle OCR, and subtitle burn-in:
muxm --install-dependencies # installs everything missing in one passOr install individually as needed:
brew install dovi_tool # Dolby Vision RPU handling
brew install gpac # DV container signaling (MP4Box)
brew install tesseract # PGS subtitle OCR engine
brew install ffmpeg-full # ffmpeg with libass (for --sub-burn-forced) + tesseractgit clone https://github.com/TheBluWiz/MuxMaster.git
cd MuxMaster
chmod +x muxm
sudo cp muxm /usr/local/bin/muxm
muxm --setup # installs dependencies, man page, and tab completionRequired:
- ffmpeg and ffprobe – core encoding and media inspection
- jq – JSON parsing for stream metadata and reporting
- bc – arithmetic evaluation for frame-rate and bitrate calculations
Optional (auto-disabled if missing):
- ffmpeg with libass (
ffmpeg-fullon Homebrew) – required for subtitle burn-in (--sub-burn-forced); the standardffmpegpackage works for all other features - dovi_tool – Dolby Vision RPU extraction/injection; DV handling is automatically disabled if missing
- MP4Box (gpac) – DV container-level signaling verification
- tesseract – OCR engine required by
pgsripfor PGS bitmap subtitle conversion - sub2srt or pgsrip – PGS bitmap subtitle OCR to SRT (configurable via
--ocr-tool)
# First-time setup (manual installs only): dependencies, man page, and tab completion
muxm --setup
# Or run individually:
muxm --install-dependencies
muxm --install-man
muxm --install-completions
# Generate a config file pre-filled with a profile's defaults
muxm --create-config user atv-directplay-hq # ~/.muxmrc
muxm --create-config project streaming # ./.muxmrc
# Remove installed components
muxm --uninstall-man
muxm --uninstall-completionsHomebrew:
brew upgrade muxmThe man page and tab completions are refreshed automatically on every upgrade — no extra steps needed.
Manual installs:
Pull the latest version and re-run setup to update the man page, completions, and dependencies:
cd MuxMaster && git pull
sudo cp muxm /usr/local/bin/muxm
muxm --setupmuxm [options] <source> [target.mp4]
<source>– Input media file (e.g.,movie.mkv)[target]– Output file (optional; defaults to<source>.<output-ext>). If the derived output path would overwrite the source file,muxmauto-appends a version number:movie(1).mp4,movie(2).mp4, etc.
| Flag | Description |
|---|---|
--profile NAME |
Apply a format profile (dv-archival, hdr10-hq, atv-directplay-hq, streaming, animation, universal) |
--dry-run |
Simulate without writing output |
--crf N |
Set video CRF value |
-p, --preset NAME |
x265 encoder preset (e.g., slow, medium) |
--video-codec libx265|libx264 |
Video encoder |
--tonemap |
Tone-map HDR to SDR |
--audio-lang-pref LANGS |
Audio language preference (comma-separated, e.g., eng,jpn) |
--audio-force-codec CODEC |
Force all audio to a specific codec |
--audio-lossless-passthrough |
Allow lossless codecs to pass through |
--sub-lang-pref LANGS |
Subtitle language preference (comma-separated) |
--sub-burn-forced |
Burn forced subtitles into video |
--output-ext mp4|mkv|m4v|mov |
Output container |
--report-json |
Generate a JSON report alongside the output file |
--checksum |
Write a SHA-256 checksum file for the output |
--strip-metadata |
Strip non-essential metadata |
--skip-if-ideal |
Skip processing if source matches target |
--replace-source |
Replace the original source file (interactive confirmation) |
--force-replace-source |
Replace the original source file (no prompt; scripting-friendly) |
--print-effective-config |
Show resolved config after config file imports |
| Flag | Description |
|---|---|
--setup |
Full first-time setup: dependencies, man page, and tab completion |
--install-dependencies |
Install required and optional tools via Homebrew/pipx |
--install-man |
Install the muxm(1) manual page |
--install-completions |
Install bash/zsh tab completion |
--uninstall-man |
Remove the installed manual page |
--uninstall-completions |
Remove installed tab completion |
--create-config SCOPE [PROFILE] |
Generate a .muxmrc config file (system, user, or project) |
--force-create-config SCOPE [PROFILE] |
Same as --create-config but overwrites an existing file |
Run muxm --help for the full flag reference.
muxm has a layered configuration system designed so you set your preferences once and override only when you need to.
Settings are resolved through multiple levels, applied in this order (lowest → highest precedence):
Hardcoded defaults
→ /etc/.muxmrc (system-wide)
→ ~/.muxmrc (user defaults)
→ ./.muxmrc (project-specific)
→ --profile <n> (format profile)
→ CLI flags (highest — always wins)
Add to any .muxmrc file:
# ~/.muxmrc — always use Apple TV Direct Play unless overridden
PROFILE_NAME="atv-directplay-hq"
CLI --profile always overrides a config-file PROFILE_NAME.
Use --create-config to generate a full .muxmrc file pre-configured for a specific profile:
muxm --create-config <scope> [profile]
| Scope | Path | Use case |
|---|---|---|
system |
/etc/.muxmrc |
Organization-wide defaults (requires sudo) |
user |
~/.muxmrc |
Personal defaults across all projects |
project |
$PWD/.muxmrc |
Per-project settings |
The generated file contains the complete config template. Variables set by the chosen profile are uncommented and active; everything else is commented with defaults for easy customization.
Profile defaults to atv-directplay-hq if omitted. Use --force-create-config to overwrite an existing file.
Example workflow: You generate a user config seeded with the streaming profile (muxm --create-config user streaming). Now every encode defaults to HEVC CRF 20 with E-AC-3 audio — no flags needed. Later you create a project config in your anime directory seeded with animation (muxm --create-config project animation). Files encoded from that directory automatically get animation-tuned settings. And when you need a one-off override, muxm --crf 14 on the command line wins over everything without touching any config file.
When configs cascade through multiple layers, it's easy to lose track of what's actually active. --print-effective-config resolves every layer and shows you the final result before anything is encoded:
# See what the resolved config looks like after all sources merge
muxm --profile hdr10-hq --crf 20 --print-effective-config
Every variable is displayed grouped by section, with the active profile name and the source of each override (CLI, config file, or profile default). No more guessing what a run will do.
Beyond profiles and the core encoding pipeline, muxm ships with a set of operational features that make it safer and easier to use in practice:
- Skip-if-Ideal – Before encoding,
muxminspects the source to determine if it already matches the target profile. If it does, the file is linked or copied without re-encoding, saving time and avoiding generation loss. Enabled per-profile or via--skip-if-ideal. - Collision Handling – When the derived output filename matches the source (e.g., encoding
movie.mp4with the default.mp4extension),muxmauto-versions the output tomovie(1).mp4,movie(2).mp4, etc. instead of failing. Use--replace-sourcefor interactive in-place replacement or--force-replace-sourcefor scripted workflows. - Conflict Warnings – Running
--profile dv-archival --no-dvdoesn't error out — it warns you that the combination is contradictory and proceeds with your explicit flags taking precedence. The tool trusts you but lets you know when something looks wrong. - Dry-Run Mode –
--dry-runexecutes the entire decision pipeline (profile resolution, codec detection, DV identification, audio selection) and prints what it would do, without writing any output files. - JSON Reporting –
--report-jsongenerates a machine-readable JSON report alongside the output file, documenting every decision, warning, codec mapping, and stream disposition from the run. - Checksum Verification – Optionally writes a SHA-256 checksum file for the output to verify integrity after transfer or archival.
- Man Page –
muxm --install-maninstalls a fullmuxm(1)manual page with complete flag reference, profile documentation, and examples accessible viaman muxm. - Tab Completion –
muxm --install-completionsinstalls bash/zsh tab completion for all flags, profiles, presets, and config scopes. Completes media file extensions when providing input files.
muxm says it requires Bash 4.3+ but I'm on macOS.
macOS ships Bash 3.2 (2007) due to licensing. If you installed via Homebrew (brew install TheBluWiz/muxm/muxm), this is handled automatically — the formula rewrites the shebang to use Homebrew's bash. For manual installs, run brew install bash and make sure /opt/homebrew/bin/bash (Apple Silicon) or /usr/local/bin/bash (Intel) appears before /bin/bash in your $PATH.
Dolby Vision handling seems to be disabled / I don't see DV in my output.
DV processing requires dovi_tool and, for MP4 container signaling, MP4Box (gpac). If either is missing, muxm silently disables DV features rather than failing. Run muxm --install-dependencies to install them, or check with muxm --dry-run — the output will show whether DV was detected and what the pipeline plans to do with it.
My file was copied instead of re-encoded. Is that a bug?
Probably not. If the source already matches the target profile (correct codec, container, color space, and audio layout), muxm skips the encode and copies/links the file. This is the Skip-if-Ideal feature — it saves time and avoids generation loss. You'll see a message indicating the skip. To force a re-encode, omit --skip-if-ideal or override a setting (e.g., --crf 18) so the source no longer matches.
Which profile should I use?
If you're playing through Plex on an Apple TV 4K: atv-directplay-hq. If you want broad device compatibility across Plex/Jellyfin/Emby clients: streaming. If you're archiving a Dolby Vision disc rip: dv-archival. If you need it to play on everything including old hardware and phones: universal. For anime or cartoons: animation. For clean HDR10 without DV complexity: hdr10-hq. When in doubt, start with --dry-run to preview what a profile will do to your file.
Can I process a batch of files?
muxm is a per-file tool by design. For a batch, a simple shell loop works:
for f in *.mkv; do muxm --profile streaming "$f"; done
For library-scale automation, consider pairing muxm with find, xargs, or a job runner like GNU Parallel.
How do I know what settings are actually active?
Run muxm --print-effective-config (with any profile and flags you plan to use). It resolves every config layer — defaults, system/user/project .muxmrc files, profile, and CLI flags — and prints the final result. Nothing is encoded; it just shows you exactly what would happen.
The output file is larger than I expected.
CRF-based encoding targets quality, not file size. A visually complex source (grain, fast motion, high detail) will produce a larger file at the same CRF than a clean digital source. You can lower quality slightly with a higher --crf value (e.g., --crf 22 instead of the default), or use a slower --preset (slow or slower) which achieves better compression at the same quality — at the cost of longer encode time.
MuxMaster is freeware for personal, non-commercial use. Any business, government, or organizational use requires a paid license.
Full license text available in LICENSE.md
Found a bug? Please open an issue on GitHub. Include the output of muxm --version, the profile and flags you used, and any relevant log output. A --dry-run dump or --report-json output is especially helpful.
This is a solo-maintained project and I'm not accepting outside code contributions at this time. See CONTRIBUTING.md for details.
If you're using MuxMaster, I'd love to hear about it — what's working, what's not, what workflows you're using it for. This is a solo project and real-world feedback shapes what gets built next.
- Bug reports → GitHub Issues
- Everything else (feedback, licensing, questions) → thebluwiz@thoughtspace.place
Jamey Wicklund (@TheBluWiz)
