# Tdarr Local Flow Plugin Suite A collection of [Tdarr](https://tdarr.io/) 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`](install.sh) script that downloads only the `index.js` files you need directly from Gitea raw URLs. **Update all plugins at once:** ```bash curl -fsSL https://git.cdtech.freeddns.org/PickleRick/Tdarr-plugin/raw/branch/main/install.sh \ | bash -s -- /your/tdarr/plugins/FlowPlugins ``` **Update a single plugin** (e.g. after a bug fix): ```bash 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:** ```bash 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: ```bash docker restart tdarr-server ``` --- ### Alternative β€” Full ZIP (first install) ```bash 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 ```bash 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" ``` | Plugin | Category | |---|---| | `mediaAnalyzerGOP` | `utility` | | `setAnimationFlag` | `utility` | | `extractChaptersXml` | `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: ```bash 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 β”œβ”€β”€ 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`, `gopMinKeyint`, `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 the `mkvpropeditPath` node 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 `.sup` extract β†’ SubtitleEdit CLI OCR β†’ `.srt` OCR failures are logged as **warnings** without stopping the pipeline. **Output naming**: `.[.forced][.default][.sdh].srt` | 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**: last 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`, `vBitrate`, `vMaxRate`, `vBufSize`, `x265Tune`, `audioHasIta`, `audioItaStreamIndex`, `audioItaOpusBitrate`, `audioHasEng`, `audioEngStreamIndex`, `audioEngOpusBitrate` --- ## πŸ”— Flow Variable Map ``` mediaAnalyzerGOP ──► gopKeyint, gopMinKeyint, 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 ```bash # 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: ```bash 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 ```