14 KiB
Tdarr Local Flow Plugin Suite
A collection of Tdarr Flow plugins for automated media transcoding with x265 2-pass encoding, audio track selection, subtitle extraction and OCR fallback.
Server: Docker (Linux) Workers: Windows (mapped drives)
⚡ Quick Install / Update via curl
Recommended — install.sh (no ZIP, no clone)
The repo includes an install.sh script that downloads only the index.js
files you need directly from Gitea raw URLs.
Update all plugins at once:
curl -fsSL https://git.cdtech.freeddns.org/PickleRick/Tdarr-plugin/raw/branch/main/install.sh \
| bash -s -- /your/tdarr/plugins/FlowPlugins
By default, the installer downloads only the latest version of every plugin.
Use --all to download every version retained in the repository:
# Latest versions of all plugins
./install.sh /your/tdarr/plugins/FlowPlugins --latest
# All available versions of all plugins
./install.sh /your/tdarr/plugins/FlowPlugins --all
# All available versions of selected plugins
./install.sh /your/tdarr/plugins/FlowPlugins --all replaceOriginalFile copyToWorkDir
Update a single plugin (e.g. after a bug fix):
curl -fsSL https://git.cdtech.freeddns.org/PickleRick/Tdarr-plugin/raw/branch/main/install.sh \
| bash -s -- /your/tdarr/plugins/FlowPlugins transcode2PassX265
Update two specific plugins:
curl -fsSL https://git.cdtech.freeddns.org/PickleRick/Tdarr-plugin/raw/branch/main/install.sh \
| bash -s -- /your/tdarr/plugins/FlowPlugins transcode2PassX265 extractSubtitlesAndOcr
Then restart Tdarr:
docker restart tdarr-server
Alternative — Full ZIP (first install)
PLUGIN_DIR=/path/to/tdarr/plugins/FlowPlugins
curl -fsSL https://git.cdtech.freeddns.org/PickleRick/Tdarr-plugin/archive/main.zip \
-o /tmp/tdarr-plugins.zip
unzip -q -o /tmp/tdarr-plugins.zip -d /tmp/tdarr-extract
cp -r /tmp/tdarr-extract/Tdarr-plugin/LocalFlowPlugins "${PLUGIN_DIR}/"
cp -r /tmp/tdarr-extract/Tdarr-plugin/FlowHelpers "${PLUGIN_DIR}/"
rm -rf /tmp/tdarr-plugins.zip /tmp/tdarr-extract
docker restart tdarr-server
Alternative — Single raw file
BASE=https://git.cdtech.freeddns.org/PickleRick/Tdarr-plugin/raw/branch/main
PLUGIN=transcode2PassX265
CATEGORY=video
DEST=/path/to/tdarr/plugins/FlowPlugins/LocalFlowPlugins/${CATEGORY}/${PLUGIN}/1.0.0
mkdir -p "${DEST}"
curl -fsSL "${BASE}/LocalFlowPlugins/${CATEGORY}/${PLUGIN}/1.0.0/index.js" -o "${DEST}/index.js"
The progress-enabled copyToWorkDir/2.0.0 and replaceOriginalFile/2.0.0
plugins also require this shared helper:
mkdir -p /path/to/tdarr/plugins/FlowPlugins/FlowHelpers/1.0.0
curl -fsSL "${BASE}/FlowHelpers/1.0.0/copyWithProgress.js" \
-o /path/to/tdarr/plugins/FlowHelpers/1.0.0/copyWithProgress.js
| Plugin | Category |
|---|---|
mediaAnalyzerGOP |
utility |
setAnimationFlag |
utility |
extractChaptersXml |
utility |
finalizeMedia |
utility |
copyToWorkDir |
utility |
replaceOriginalFile |
utility |
audioSelectorClamping |
audio |
backupSourceAudio |
audio |
extractSubtitlesAndOcr |
subtitles |
transcode2PassX265 |
video |
Tagged Releases (versioned ZIPs)
Once a release is published by the CI workflow, versioned ZIPs are also available:
VERSION=v1.0.1
curl -L https://git.cdtech.freeddns.org/PickleRick/Tdarr-plugin/releases/download/${VERSION}/Tdarr-plugins-${VERSION}.zip \
-o tdarr-plugins.zip
unzip -o tdarr-plugins.zip -d "${PLUGIN_DIR}"
rm tdarr-plugins.zip
📁 Directory Layout
LocalFlowPlugins/
├── utility/
│ ├── mediaAnalyzerGOP/1.0.0/index.js
│ ├── setAnimationFlag/1.0.0/index.js
│ ├── extractChaptersXml/1.0.0/index.js
│ ├── finalizeMedia/1.0.0/index.js
│ ├── copyToWorkDir/2.0.0/index.js
│ ├── replaceOriginalFile/1.0.0/index.js
│ └── replaceOriginalFile/2.0.0/index.js
├── audio/
│ ├── audioSelectorClamping/1.0.0/index.js
│ └── backupSourceAudio/1.0.0/index.js
├── subtitles/
│ └── extractSubtitlesAndOcr/1.0.0/index.js
└── video/
└── transcode2PassX265/1.0.0/index.js
🔌 Plugin Reference
1. mediaAnalyzerGOP — Media Analyzer & GOP Calculator
Category: utility · Step: 1st in flow
Reads video stream metadata and populates args.variables for all downstream plugins.
| Input | Default | Description |
|---|---|---|
moviePathPatterns |
/Radarr |
Pipe-separated path fragments that identify a Film library (e.g. /Radarr|/films) |
animeSeries |
(empty) | Pipe-separated anime series names to auto-detect (e.g. Naruto|One Piece) |
filmBitrate |
3000 |
Target kbps for live-action films |
seriesBitrate |
3000 |
Target kbps for TV series |
animeBitrateHd |
2000 |
Target kbps for anime ≥720p |
animeBitrateSd |
1000 |
Target kbps for anime <720p |
Outputs: 1 → continue
Variables set: gopKeyint (4 s max GOP), gopMinKeyint (1 s), gopSceneCut (40), isAnimation, isMovie, vBitrate, vMaxRate, vBufSize, x265Tune, videoHeight, videoFps
2. setAnimationFlag — Set Animation Flag
Category: utility · Step: optional, after mediaAnalyzerGOP
Manually forces isAnimation=true and x265Tune=animation when a file was not auto-detected as anime. Optionally recalculates bitrate targets.
| Input | Default | Description |
|---|---|---|
animeBitrateHd |
2000 |
Bitrate (kbps) for anime ≥720p |
animeBitrateSd |
1000 |
Bitrate (kbps) for anime <720p |
Outputs: 1 → continue
3. extractChaptersXml — Extract Chapters to XML
Category: utility · Step: before transcode
Runs mkvextract to dump chapter markers to an XML file next to the original source file. Stores the path in variables.chaptersXmlPath for downstream use. Safe to run even when the source has no chapters (mkvextract exit 1 is treated as "no chapters").
No inputs required.
Outputs: 1 → continue
Requires: MKVToolNix (
mkvextract) — automatically located from themkvpropeditPathnode setting.
4. audioSelectorClamping — Audio Selector & Opus Bitrate Clamping
Category: audio · Step: after mediaAnalyzerGOP
Selects the best ITA and ENG audio tracks by codec+bitrate scoring, then calculates clamped Opus target bitrates. No upcoding — output bitrate never exceeds source.
Codec scoring (higher = better): TrueHD 100 · DTS-HD MA 95 · FLAC 90 · EAC3 75 · DTS 60 · AC3 45 · AAC 35 · Opus 30
| Input | Default | Description |
|---|---|---|
filmMultiChannelBitrate |
640 |
Max kbps for 5.1+ film audio |
seriesMultiChannelThreshold |
384 |
kbps threshold: ≥threshold → 384k, else → 256k |
stereoBitrateMax |
128 |
Max kbps for stereo tracks |
Outputs: 1 → ITA or ENG track found · 2 → no target tracks found
Variables set: audioHasIta, audioItaStreamIndex, audioItaOpusBitrate, audioItaChannels, audioItaCodecName, audioHasEng, audioEngStreamIndex, audioEngOpusBitrate, audioEngCodecName
5. backupSourceAudio — Backup Source Audio (DTS / TrueHD ITA)
Category: audio · Step: after audioSelectorClamping
Extracts the ITA audio track as a lossless bitstream copy (-c:a copy) only when the codec is DTS or TrueHD. Output is saved next to the source file. Silently skips if no backup is needed.
No inputs required.
Outputs: 1 → continue
Variables set: audioBackupPath (path of the extracted file, if created)
6. extractSubtitlesAndOcr — Extract Subtitles & OCR Fallback
Category: subtitles · Step: after audioSelectorClamping
Extracts ITA and ENG subtitle streams to SRT files next to the source file.
- Text-based codecs (
subrip,srt,ass,ssa,webvtt,mov_text): direct FFmpeg extraction - Bitmap-based codecs (
hdmv_pgs_subtitle,dvd_subtitle,pgssub,dvbsub): FFmpeg.supextract → SubtitleEdit CLI OCR →.srt
OCR failures are logged as warnings without stopping the pipeline.
Output naming: <basename>.<lang>[.forced][.default][.sdh].srt
Text tracks take precedence over bitmap tracks with the same language and flags.
Two-letter it/en tags are normalized to ita/eng; an Italian forced track
is always marked default.
| Input | Default | Description |
|---|---|---|
subtitleEditPath |
SubtitleEdit |
Name or full path of SubtitleEdit CLI. Must be in PATH or provide absolute path. |
languages |
ita,eng |
Comma-separated ISO 639-2 language codes to extract |
Outputs: 1 → continue (warnings in log if OCR failed)
Requires: SubtitleEdit CLI for PGS/VOBSUB OCR (Windows workers only).
7. transcode2PassX265 — 2-Pass x265 Transcode
Category: video · Step: before finalizeMedia
Performs 2-pass libx265 encoding using VBV parameters from args.variables.
- Video: HEVC already within 1080p and strictly below 3000 kbps is copied; all other video is bound-1080p Lanczos scaled and x265-encoded (no letterbox, even dimensions)
- Audio: Opus re-encode with per-track indexed bitrate (ITA default, ENG secondary)
- Subtitles: stripped (
-sn) - Chapters: preserved (
-map_chapters 0) - 2-pass logs: automatically cleaned up after encode
| Input | Default | Description |
|---|---|---|
x265Preset |
slow |
x265 preset (ultrafast … veryslow). Slower = better quality. |
outputContainer |
mkv |
Output file extension |
copyHevcBelowKbps |
3000 |
Copy compatible HEVC video below this bitrate instead of re-encoding it. 0 disables copying. |
Outputs: 1 → transcode complete
Reads from variables: gopKeyint, gopMinKeyint, gopSceneCut, vBitrate, vMaxRate, vBufSize, x265Tune, audioHasIta, audioItaStreamIndex, audioItaOpusBitrate, audioHasEng, audioEngStreamIndex, audioEngOpusBitrate
8. copyToWorkDir — Copy Source to Work Dir (Progress)
Category: utility · Step: before transcode
Streams the source file to the worker's local workDir, verifies the byte count,
reports throttled copy progress, and retries failed writes twice after removing
the partial destination. Its Copy method uses the operating system's native file-copy path, reports interval speed,
verifies the destination size, and retries failed writes. It stores the
original library path in
variables.originalLibraryFile for replaceOriginalFile.
9. replaceOriginalFile — Replace Original File
Category: utility · Step: after transcode
The 1.0.0 version performs the same verified, atomic replacement as the
progress version but does not emit transfer progress callbacks or progress logs.
It is useful when the standard Tdarr replacement behavior is desired without
additional progress reporting.
10. replaceOriginalFile — Replace Original File (Progress)
Category: utility · Step: after transcode
Streams the processed file to a temporary file beside the original, verifies its
size, and retries failed or incomplete writes twice before giving up. It then
atomically replaces the original. An existing original is moved to .partial.old
during the operation and restored if replacement fails. The processed work file
is deleted after a successful replacement.
11. finalizeMedia — Finalize Media & Sidecars
Run after Move To Directory. It normalizes subtitle language codes, adds
.default to ita.forced, removes duplicate _chapters sidecars, and removes
the original source directory only when it is empty. It also canonicalizes
chapter XML sidecars to <media>_chapters.xml and removes duplicate copies.
🔗 Flow Variable Map
mediaAnalyzerGOP ──► gopKeyint, gopMinKeyint, gopSceneCut, vBitrate, vMaxRate, vBufSize
──► isAnimation, isMovie, x265Tune, videoHeight, videoFps
│
(optional) setAnimationFlag ──► overrides vBitrate, vMaxRate, vBufSize, x265Tune
│
audioSelectorClamping ──► audioHasIta, audioItaStreamIndex, audioItaOpusBitrate, audioItaCodecName
──► audioHasEng, audioEngStreamIndex, audioEngOpusBitrate
│
backupSourceAudio ──► audioBackupPath
│
transcode2PassX265 ◄── all of the above
🛠 Requirements
Tdarr Server (Docker/Linux)
- Tdarr ≥ 2.11.01
- Plugins folder mounted at the path configured in Tdarr settings
Tdarr Worker (Windows)
| Tool | Purpose | Download |
|---|---|---|
| FFmpeg (with libopus) | Video/audio/subtitle processing | https://ffmpeg.org/download.html |
| MKVToolNix | Chapter extraction (mkvextract) |
https://mkvtoolnix.download/ |
| SubtitleEdit CLI | PGS/VOBSUB OCR to SRT | https://www.nikse.dk/subtitleedit |
Add to Windows system PATH: ffmpeg\bin, MKVToolNix, SubtitleEdit
Configure in Tdarr node settings:
- FFmpeg Path:
C:\Tools\ffmpeg\bin\ffmpeg.exe - FFprobe Path:
C:\Tools\ffmpeg\bin\ffprobe.exe - MKVPropedit Path:
C:\Program Files\MKVToolNix\mkvpropedit.exe
🧪 Local Testing
# Prerequisites: Node.js, FFmpeg in PATH
cd /path/to/Tdarr-plugin
# 1. Generate sample test files (requires FFmpeg)
node tests/generate-test-files.js
# 2. Run logic tests (no real encode)
node tests/quick-logic-test.js
# 3. Run full integration tests
node tests/run-tests.js
# 4. Run integration tests skipping real x265 encode
node tests/run-tests.js --skip-transcode
📦 Release & Update
Releases are created automatically via GitHub Actions on every push to main or on git tag v*.
To trigger a manual release from the Actions tab, use "Run workflow" and specify the version (e.g. v1.0.1).
To update plugins on the server to the latest release:
VERSION=latest
PLUGIN_DIR=/path/to/tdarr/plugins/FlowPlugins
curl -L https://git.cdtech.freeddns.org/PickleRick/Tdarr-plugin/releases/download/${VERSION}/Tdarr-plugins-${VERSION}.zip \
-o tdarr-plugins.zip
unzip -o tdarr-plugins.zip -d "${PLUGIN_DIR}"
rm tdarr-plugins.zip
docker restart tdarr-server