Remuxarr
Docker app from thetvliam's Repository
Overview
Readme
View on GitHub
Remuxarr
A Dockerized media-library tool for Unraid (and any Docker host) that cleans up your Sonarr/Radarr library for better playback compatibility - without ever re-encoding video.
If you've used Unmanic or similar tools and found yourself wanting less transcoding, not more, this is built specifically for that: it remuxes containers, drops audio/subtitle tracks you don't want, and fixes broken metadata - all through lossless stream copying, never touching the actual video data.
Screenshots
Why this exists
Most media coming out of Sonarr/Radarr carries far more than you actually need - five or six audio languages, subtitle tracks for every region, sometimes a container your TV or Plex client doesn't handle as cleanly as it could. Re-encoding to "fix" this wastes CPU, time, and quality for no benefit, since the video itself was already fine.
Remuxarr only ever touches what's cheap and lossless to touch:
- Container remuxing - MKV → MP4 when every track inside is already MP4-compatible, so no video re-encode is ever needed for this.
- Track filtering - drops audio/subtitle tracks outside your kept languages, using stream copy, never transcoding anything to do it.
- Metadata correction - fixes missing (
und) language tags, and can correct wrong ones too (e.g. a track mistagged in a completely different language than what's actually spoken). Audio and subtitles are controlled separately, so you can tag undefined audio automatically while holding undefined subtitles for a decision, or the reverse (Settings → Library & Processing → Metadata).
Nothing is ever re-encoded - not video, not audio, under any setting, for any reason. That's not a preference here - it's the entire point of the project. (If you specifically need AAC 5.1 → AC3 for an older AV receiver's bitstream passthrough, that's a deliberate, reviewable, undoable operation in AC3 Forge - never something the main pipeline does automatically to every matching file.)
This application was generated entirely using Claude. I acted as the architect, prompt engineer, and primary tester to solve a specific problem for myself, and wanted to share the functional result with the open-source community!
Features
- Automatic library scanning - full probe on first sight, fast delta (size/mtime) scans afterward.
- Leaves extras alone - trailers, featurettes and other extras named the way Plex or Jellyfin expect, and Plex's optimised versions, are skipped by default, and your own exclude patterns keep Remuxarr away from any other folder or file. Always Include brings back any file either of them catches that you do want processed.
- Sonarr / Radarr webhook integration - reacts to imports and upgrades directly, with automatic path translation between how each *arr sees files and how Remuxarr does.
- Plex integration - refreshes affected library sections after every job. A separate, opt-in backlog can additionally verify Plex's own metadata and force an explicit re-analyze on the rare files Plex's own maintenance misses - most installs won't need this turned on; see Settings → Integrations → Plex Analyze Backlog for why.
- AC3 Forge - AAC 5.1 → AC3 for older receivers, on files you pick, with an undo. Separate from the main pipeline and never automatic; see Screenshots above.
- Manual review, with bulk resolution - ambiguous files are held for a human decision rather than guessed at, resolvable in bulk or by a standing policy (Settings → Library & Processing → Subtitles); see Screenshots above.
- Audio Language Review - search-and-bulk-correct tool for tracks that have a wrong language tag rather than a missing one (common with some release groups) - confirm it's actually correct, or apply the right tag to every matching file at once.
- Dry run mode - see every planned action across your whole library before anything real is touched. On by default for a fresh install - see First-time configuration below.
- Revert to original - keeps the audio and subtitle tracks a job removed, so a processed file can be put back exactly as it was. Off by default and needs a volume mounted; see Reverting a processed file below.
- Email notifications - on job failure, with a circuit breaker so a bad setting doesn't flood your inbox.
- Scheduled scans, manual and orphaned-file cleanup, abort/pause controls, and a live log viewer, all from the web UI.
Installation
Prebuilt images are published to both the GitHub Container Registry (GHCR) and Docker Hub - you do not need to clone this repository to run Remuxarr. The two are identical, built from the same commit in the same workflow; use whichever your setup prefers. Unraid's Community Apps and template ecosystem lean toward Docker Hub, so that's the one to use there unless you have a reason not to.
| Branch | Docker Hub | GHCR |
|---|---|---|
| Main (stable) | thetvliam/remuxarr:latest |
ghcr.io/thetvliam/remuxarr:latest |
| Testing (beta) | thetvliam/remuxarr:testing |
ghcr.io/thetvliam/remuxarr:testing |
Every build also publishes an immutable :<short-commit-sha> tag (e.g. :37a7265) to both registries, so you can pin to an exact build and roll back if a latest/testing update ever misbehaves. A commit that changes only documentation or tests builds no image, so it has no tag of its own; the latest image is the one built from the last commit that changed what goes into it.
Unraid
See UNRAID_DEPLOYMENT.md for a step-by-step GUI walkthrough. Use thetvliam/remuxarr:latest as the Repository value in the template.
Docker Compose
Create a docker-compose.yml anywhere on your Docker host:
services:
remuxarr:
image: thetvliam/remuxarr:latest # :testing for the beta branch, or ghcr.io/thetvliam/remuxarr:latest
container_name: remuxarr
restart: unless-stopped
ports:
- "9191:9191" # Web UI + API
volumes:
# Settings and database. Map this to a host path that survives
# container updates - everything you configure lives here.
- /path/to/appdata/remuxarr/config:/config
# Your media library. Mount it at the same paths Sonarr/Radarr use
# inside their own containers where you can - it makes the path
# translation in Settings simpler (often unnecessary entirely).
- /path/to/your/movies:/media/movies
- /path/to/your/tv:/media/tv
# Optional: recycle bin for "revert to original". Stores the audio
# and subtitle tracks a job removed - never the video - so a file
# can be put back the way it was. Leave it out and the feature
# stays off. Must survive restarts, so not under /tmp.
- /path/to/appdata/remuxarr/recycle:/recycle
environment:
- TZ=America/New_York # see "Setting your time zone" below
# Optional: stage FFmpeg's temp output in RAM instead of on the array.
# Remuxarr checks free space first and falls back to the output file's
# own directory when a file is too large for it, so this is safe to
# leave enabled - and safe to delete if you'd rather not use RAM.
tmpfs:
- /tmp/remuxarr
Then:
Change the volume paths (
/path/to/...) to match your host.Start it:
docker compose up -dOpen
http://<your-host-ip>:9191.
Setting your time zone
TZ sets the container's own clock, and that clock is what decides when scheduled work runs: the scan times under Settings → Maintenance & Logs → Scheduled Scans, and the analyze window under Settings → Integrations → Plex Analyze Backlog (02:00-06:00 by default). Leave TZ unset and the container runs on UTC, so a window you meant for the small hours lands wherever UTC puts it - 02:00-06:00 UTC is 22:00-02:00 in New York and the middle of the afternoon in Sydney.
The timestamps in the log viewer and in job history do not depend on this. Those are stored in UTC and converted by your browser, so they already read correctly in your own zone whether or not TZ is set. That is worth knowing because it removes the symptom you would otherwise notice: every clock in the UI looks right, and the only sign anything is wrong is a scan starting at the wrong end of the evening.
It takes an IANA time zone name, which is an Area/City pair - not a country name, not an abbreviation like EST, and not an offset like GMT+1. Pick the listed city closest to you within your own country; large countries have several, because their regions follow different daylight-saving rules.
| Country | Example values |
|---|---|
| United States | America/New_York, America/Chicago, America/Denver, America/Phoenix (no DST), America/Los_Angeles |
| Canada | America/Toronto, America/Winnipeg, America/Edmonton, America/Vancouver, America/Halifax |
| United Kingdom | Europe/London |
| Ireland | Europe/Dublin |
| Germany | Europe/Berlin |
| France | Europe/Paris |
| Netherlands | Europe/Amsterdam |
| Spain | Europe/Madrid |
| Australia | Australia/Sydney, Australia/Adelaide, Australia/Brisbane (no DST), Australia/Perth |
| New Zealand | Pacific/Auckland |
| Japan | Asia/Tokyo |
| India | Asia/Kolkata |
| Singapore | Asia/Singapore |
| Brazil | America/Sao_Paulo |
| South Africa | Africa/Johannesburg |
| No local time | Etc/UTC |
A few things that trip people up:
- The city is a label, not a location requirement.
Europe/Londonis correct for all of Scotland, Wales, and Northern Ireland too - the city just names the rule set that region follows. - Spelling is exact. Names are case-sensitive and use underscores, so
America/New_Yorkworks andamerica/new_yorkorAmerica/New Yorkdo not. - Daylight saving is automatic. That is the whole reason to use a zone name instead of a fixed offset - the clock shifts itself on the right dates.
- Avoid
Etc/GMT±Nvalues. Their signs are inverted from what you would expect (Etc/GMT+5is actually UTC−5), so they are an easy way to end up an hour or ten wrong.
To find your own, run timedatectl list-timezones on a Linux host (or check Unraid's Settings → Date & Time, which already displays it). After starting the container, docker exec remuxarr date confirms the setting took effect - and that is the check worth doing, since nothing in the web UI will look wrong if you get it incorrect.
On Unraid, set this in the container's TZ variable rather than here; Unraid's own Date & Time page governs the host, not the container, so the two are separate settings. See UNRAID_DEPLOYMENT.md.
SELinux hosts (Fedora, RHEL, some Synology setups): append
:Zto the/configmount and:zto each media mount - the media ones are shared with your Sonarr/Radarr/Plex containers, so they must use the lowercase shared label. Both are harmless no-ops on non-SELinux hosts like stock Unraid. See this repo's owndocker-compose.ymlfor a fully annotated example.
Building from source
Only needed if you want to modify the code:
git clone https://github.com/thetvliam/remuxarr.git
cd remuxarr
cp .env.example .env # required — the bundled compose file reads it
docker compose up -d --build
Every value in .env.example already matches the app's own built-in default, so an untouched copy is fine; edit it only to override something specific (the file's own comments explain each one). The first build takes a few minutes - it installs dependencies, builds the frontend, and fetches FFmpeg. Subsequent starts are fast.
The bundled docker-compose.yml builds the image locally rather than pulling it, and is also the fully annotated reference for the SELinux labels and tmpfs staging mentioned above.
First-time configuration
Everything from here happens in the web UI, not in any config file:
- Go to Settings → Library & Processing → Library and set your scan paths - this is empty on a fresh install, deliberately, so nothing happens until you point it at your actual library. Use the container-side paths (e.g.
/media/movies,/media/tv), not your host paths. Trailers and other extras are skipped by default; anything else Remuxarr should leave alone goes in Exclude Patterns on the same page, and anything those catch that you do want processed goes in Always Include. - If you keep audio/subtitles in a language other than English, set that in Settings → Library & Processing → Audio / Subtitles - both default to English.
- Trigger a scan. Dry run is on by default - this first scan shows you exactly what would happen to every file, without touching anything.
- Review the Dry Run tab. Once the planned actions look right, turn dry run off in Settings → Worker - real processing begins from here.
- Auto-start is on by default, meaning the queue processes itself once dry run is off. If you'd rather review the queue manually before anything runs, turn this off in Settings → Worker.
- Plex, email and the Sonarr/Radarr rescan are all off until you provide real connection details - nothing is assumed enabled. To have Sonarr and Radarr send each import straight to Remuxarr, see Connecting Sonarr and Radarr below.
Connecting Sonarr and Radarr
Remuxarr can process a file as soon as Sonarr or Radarr imports it, rather than at the next scan. In Sonarr, go to Settings → Connect, add a Webhook, and set:
- URL -
http://<address>:9191/api/webhooks/sonarr. Use the host port you mapped if it is not 9191, and an address Sonarr can reach: your server's IP, or the container name if both containers are on the same custom Docker network. Notlocalhost, which inside Sonarr's container is Sonarr itself. - Method - POST, the default. Username and password stay empty.
- Triggers - On File Import, On File Upgrade and On Rename. Remuxarr ignores the rest, so they can stay off. On Rename is what keeps a renamed file's record, history and revert point with it. On Import Complete does no harm if you have it on: it repeats files On File Import has already sent.
Radarr is the same, with http://<address>:9191/api/webhooks/radarr as the URL. The Test button should report success.
If Sonarr or Radarr sees your files at different paths from Remuxarr - Sonarr reports /data/TV/... where Remuxarr sees /media/tv/..., say - set the two Container Paths under Settings → Integrations → Sonarr (or Radarr): /data/TV as Sonarr's Container Path and /media/tv as Remuxarr's Container Path for TV.
Enable Sonarr Integration (with Sonarr's URL and API key) is separate: after each job, Remuxarr asks Sonarr to rescan the series, so Sonarr picks up a file whose name or extension changed. Webhooks are accepted whether it is on or not. Radarr's works the same way.
Reverting a processed file
Remuxarr can keep whatever a job removed, so a file can be put back the way it
was. It is off by default and needs the /recycle volume mounted - see
Installation above.
Nothing it does is destructive on its own - it only ever adds a copy of what a job removed - but treat a revert point as a convenience rather than a backup, and do not rely on it as your only route back.
It exists for the period while you are still working out what your language and subtitle rules should be, which is exactly when a rule turns out to be wrong on a few hundred files. It is not meant to be a permanent archive, and the retention limits are set accordingly.
What is kept. Only the tracks a job actually removed - never the video. A job that drops three foreign-language audio tracks and twenty subtitles stores those and nothing else, so what it costs is a fraction of the file rather than a second copy. A job that removes nothing stores nothing.
What it costs. Bounded twice, in Settings → Recycle Bin: 7 days and 20GB by default. Both apply - whichever is reached first. Set either to 0 to disable that limit. Clear Database in Settings → Danger Zone also empties the recycle bin.
Reverting. Each entry in Settings → Recycle Bin restores the file to its exact original state: every track back, in the original order, with the original language tags, titles, default/forced flags and attachments, in the original container. If the job converted MKV to MP4, reverting converts it back and restores the original file extension. An AC3 track added with AC3 Forge after processing is not part of the original, so reverting removes it.
A reverted file goes back to your rules. The next scan looks at it afresh, so if the rule that changed it still applies, that scan processes it again - and with scheduled scans on, that can happen overnight. Change the rule first, or turn on dry-run mode, before reverting.
When it will refuse. A revert point records the file as the job left it. If something else has written to that file since - Sonarr upgrading the episode is the usual case - the stored tracks belong to a different release, and muxing them in would produce a file that plays and is quietly wrong. Those entries stay listed with the reason shown, so you can discard them, but they will not offer to revert.
If Sonarr or Radarr renames a file and its Remuxarr webhook has On Rename ticked (see Connecting Sonarr and Radarr), the entry stays with the file: the webhook says which old name became which new one, so the file keeps its record, and its revert point, history and review answers with it.
If anything else renames a file, Remuxarr loses track of which file the entry belongs to - a rename looks the same as a deletion from the outside. The entry moves to Unmatched rather than being thrown away, and can be matched back: a renamed file is byte-for-byte identical, so it is identified by its fingerprint rather than guessed at. Reverting it then keeps its new name and folder. A revert will not overwrite another file: if one already has the name it would write, the entry says so and does not offer to revert.
Extracted subtitle files are removed again when you revert, since their
content goes back inside the file - but only the ones that job created, and not
one you have edited since. A .srt that was already there (from Bazarr, say),
or that arrives while the job runs, is not overwritten in the first place: the
extracted one is saved under the next free number, such as Movie.en.2.srt.
The Existing Subtitle Files setting can make extraction replace it instead,
which a revert cannot undo, or give the extracted subtitle the name and move
the existing file to the next free number, where a revert leaves it.
Two limitations worth knowing. A job whose only casualty is an attachment - a font, say - stores nothing, because Matroska cannot hold a file with no tracks. And Matroska cover art comes back as a still-image video track rather than an attachment, with its filename and mimetype intact; nothing is lost, but a player may list it as a second video stream.
Development
The backend has a real test suite covering the decision engine (what happens to each file and why), library scanning and deletion cascades, queue and job lifecycle, job finalisation, Sonarr/Radarr webhook path translation and notification, FFmpeg command construction, AC3 Forge, the scheduler and Plex client, settings persistence, backup/restore, startup recovery, revert-to-original (including real-FFmpeg round trips that capture from a file and restore it, comparing stream by stream), and a sample-library regression suite that runs the real pipeline against a fixed set of probed media files:
pip install -r requirements.txt -r tests/requirements-test.txt
pytest
The frontend has its own suite covering the app's central state hook (routing, toasts, history invalidation), every mutating user action, paginated and history data fetching, the settings load and save paths, integer input handling, the shared formatters that decide how a size, a duration and a UTC timestamp read, the theme serialiser that round-trips a theme between its source text and the object the app renders from, and the developer theme editor's preview isolation, control coverage and export, and the invariants every shipped theme must satisfy:
cd frontend && npm ci && npm test
Both run in CI on every push, alongside pyflakes and eslint. A few backend tests exercise a real FFmpeg/ffprobe and skip if the binaries are missing - CI installs them so they always run.
See tests/README.md for more detail, including how to run this same suite inside a deployed container against production FFmpeg.
License
MIT - see LICENSE.
Media gallery
1 / 5Categories
Download Statistics
Related apps
Explore more like this
Explore allDetails
thetvliam/remuxarr:latestRuntime arguments
- Web UI
http://[IP]:[PORT:9191]- Network
bridge- Shell
bash- Privileged
- false
Template configuration
HTTP port for the web UI and API.
- Target
- 9191
- Default
- 9191
Persistent settings and database. Must survive container updates.
- Target
- /config
- Default
- /mnt/user/appdata/remuxarr/config
Your movies library.
- Target
- /media/movies
- Default
- /mnt/user/media/movies
Your TV library.
- Target
- /media/tv
- Default
- /mnt/user/media/tv
FFmpeg staging directory. Keep under /tmp so it stays RAM-backed.
- Target
- /tmp/remuxarr
- Default
- /tmp/remuxarr-temp
Holds revert-to-original data: the audio and subtitle tracks removed by a job, so files can be put back. Leave empty to disable. Must NOT be under /tmp — that is RAM-backed and would be lost on reboot.
- Target
- /recycle
- Default
- /mnt/user/appdata/remuxarr/recycle
Your IANA time zone, e.g. Europe/London or America/New_York. Leave empty and the container runs on UTC, which means scheduled scans and the Plex analyze window fire on UTC rather than your local time. Timestamps shown in the web UI are converted by your browser and are correct either way.