- Shell 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Moved out of sulaco-playbook (files/radarr), with a README covering both scripts, the queue and the systemd setup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| radarr-recode | ||
| README.md | ||
| smart-recode-enqueue | ||
radarr-recode
Post-processing for Radarr imports. Every movie file Radarr imports is:
- moved into the
09/bucket if its title starts with a digit, - re-encoded with
smart-recode(720p HEVC on the GPU), replacing the original, - rescanned in Radarr, so it tracks the new file,
- linked into a link farm on the NAS with
manage-linkfarm, - announced to Jellyfin, which reads that farm.
Two scripts share the work, because Radarr runs in a container that has none of the tools the encode needs (fish, gum, the VAAPI ffmpeg, the GPU):
| Script | Runs | Job |
|---|---|---|
smart-recode-enqueue |
inside the Radarr container, as a Custom Script connection | appends each imported file to a queue file in Radarr's /config |
radarr-recode |
on the host, started by a systemd path unit when the queue exists | works through the queue: steps 1–5 above |
Radarr import ──▶ smart-recode-enqueue ──▶ <config>/smart-recode/queue
│ (radarr-recode.path)
▼
radarr-recode: 09/ bucket ─▶ smart-recode ─▶ RescanMovie
─▶ manage-linkfarm (ssh) ─▶ Jellyfin
Library layout it expects
Radarr's library (mounted at /movies in the container) is canonical storage,
split by language and bucketed by initial:
/movies/<language>/<initial>/<movie folder>/<file>
english A, B, … Z, 09
french
Radarr creates new folders with the naming format
{Movie TitleFirstCharacter}/{Movie Title} ({Release Year})[m]. That token puts
"1408" in 1/, but the library and manage-linkfarm keep every title that
starts with a digit in 09/, so radarr-recode moves those folders (step 1).
The link farm mirrors the languages: <farm>/<language>/<link>, where each
link points at a movie folder and is named after it, minus any trailing [m].
smart-recode-enqueue
smart-recode-enqueue # called by Radarr, no arguments
Reads Radarr's radarr_eventtype environment variable:
Download(sent for new imports and upgrades): appends<radarr_movie_id>\t<radarr_moviefile_path>to/config/smart-recode/queue.Test(Radarr's "Test" button): succeeds only if/config/smart-recodeexists, so a missing queue directory shows up when the connection is saved.
Install it somewhere under the container's /config and add it in Radarr under
Settings → Connect → Custom Script, with On File Import and
On File Upgrade enabled.
radarr-recode
radarr-recode --queue-dir DIR --movies-dir DIR
--config CONFIG_XML --port PORT
--nas-host HOST --linkfarm-script PATH
--farm-dir DIR
[--jellyfin-url URL --jellyfin-farm-dir DIR]
| Option | Meaning |
|---|---|
--queue-dir |
host path of the container's /config/smart-recode |
--movies-dir |
host path of the container's /movies |
--config |
Radarr's config.xml, for its API key |
--port |
Radarr's port on localhost |
--nas-host |
ssh destination where manage-linkfarm runs (batch mode, no prompts) |
--linkfarm-script |
path of manage-linkfarm.py on that host |
--farm-dir |
the movie link farm on that host; links go in <farm-dir>/<language> |
--jellyfin-url |
Jellyfin's base URL (optional) |
--jellyfin-farm-dir |
the same farm as Jellyfin sees it (optional) |
Environment:
JELLYFIN_API_KEY: the key for Jellyfin's API. Kept out of the command line, wherepswould show it. Without it, or without the two Jellyfin options, step 5 is skipped with a log message.
radarr-recode loops until the queue is empty, then exits.
What happens to each queued file
Queue lines are <movie id>\t<path inside the container>. The /movies/
prefix is mapped to --movies-dir. A file that no longer exists is skipped.
-
09/ bucket. If the movie folder sits in a single-digit bucket (
english/1/…), it is moved toenglish/09/…. Radarr is given the new path (PUT movie/<id>?moveFiles=false), and the empty digit bucket is removed. If09/already has a folder of that name, the movie stays where it is. -
Recode. Files already named
*.yify.mp4are left alone. Otherwisesmart-recode --yes <file>runs in the movie folder. The original is deleted only when smart-recode succeeds and exactly one new.yify.mp4appeared next to it. -
Rescan. Radarr is asked to
RescanMovie, so it swaps the deleted original for the.yify.mp4. -
Link. On
--nas-host, in<farm-dir>/<language>:manage-linkfarm -y --only <folder>. This runs whether or not the recode worked, so a movie still reaches the farm when its recode failed. Its exit status is logged:Status Meaning 0 linked (or already linked) 2 not linked: the folder was rejected earlier in that farm 3 not found: the folder isn't in 09/or a letter bucket -
Jellyfin. After status 0,
POST <jellyfin-url>/Library/Media/Updatedwith<jellyfin-farm-dir>/<language>/<link>. Jellyfin can't see links made on the NAS through its SMB mount, so without this a new movie only shows up after its scheduled library scan. Jellyfin picks the change up after its library monitor delay, about a minute.
A failure in steps 1, 3, 4 or 5 is logged and doesn't stop the rest.
Queue files
All in --queue-dir:
| File | Purpose |
|---|---|
queue |
written by smart-recode-enqueue |
queue.incoming |
queue, moved aside while radarr-recode takes it over |
queue.work |
the lines being processed; removed once all are handled |
failed |
lines whose recode failed; their originals were kept |
radarr-recode moves queue aside, waits a second for any write already in
progress, and appends it to queue.work. If a run is interrupted,
queue.work is still there and the path unit starts radarr-recode again to
retry it. A retried line whose recode already happened is skipped (the
original is gone, or it's a .yify.mp4).
Retrying a failed recode
Lines in failed are not retried automatically. Once the cause is fixed,
put the line back in the queue and remove it from failed:
cat <queue-dir>/failed >> <queue-dir>/queue
rm <queue-dir>/failed
Writing a line to the queue by hand is also a cheap way to test the pipeline. For a movie that is already recoded, it only relinks and notifies Jellyfin.
Running it under systemd
radarr-recode is meant to be started by a path unit that watches the queue.
Use PathExists, not PathModified: systemd checks again when the service
exits, so a line queued during a run starts another one.
# radarr-recode.path
[Path]
PathExists=/srv/radarr/config/smart-recode/queue
PathExists=/srv/radarr/config/smart-recode/queue.work
[Install]
WantedBy=paths.target
# radarr-recode.service
[Service]
Type=oneshot
User=paul
Nice=15
IOSchedulingClass=idle
Environment=PATH=/home/paul/local/bin:/home/linuxbrew/.linuxbrew/bin:/usr/bin:/bin
# JELLYFIN_API_KEY=..., readable by root only
EnvironmentFile=/etc/radarr-recode.env
ExecStart=/home/paul/local/bin/radarr-recode \
--queue-dir /srv/radarr/config/smart-recode \
--movies-dir /mnt/movies \
--config /srv/radarr/config/config.xml \
--port 7878 \
--nas-host root@nas \
--linkfarm-script /mnt/user/appdata/manage-linkfarm/manage-linkfarm.py \
--farm-dir /mnt/user/paul/media/video/movies \
--jellyfin-url http://localhost:8096 \
--jellyfin-farm-dir /media/paul/video/movies
Logs go to the journal: journalctl -u radarr-recode.
On sulaco, the units, the env file and the Radarr side (quality caps, naming,
root folders, the Custom Script connection) are set up by the radarr.yml
playbook in sulaco-playbook,
which also checks out this repo.
Requirements
On the host:
- fish 3.5 or later (for
path) smart-recodeand what it needs (recode-video-yify-gpu, the VAAPI ffmpeg,gum)curl,jq,ssh- ssh access to
--nas-hostwithout a prompt
On the NAS: manage-linkfarm.py and python3.
In the container: bash.