# 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 via curl > **How it works**: Gitea serves a real ZIP of any branch at `/archive/.zip`. > This is a built-in git archive β€” **no release workflow required**, always up to date. ### Full Suite (all 7 plugins) ```bash PLUGIN_DIR=/path/to/tdarr/plugins/FlowPlugins # Download a live ZIP of the main branch directly from Gitea curl -fsSL https://git.cdtech.freeddns.org/PickleRick/Tdarr-plugin/archive/main.zip \ -o /tmp/tdarr-plugins.zip # Extract and copy into your plugin directory 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 ``` After extracting, restart the Tdarr server so it picks up the new plugins: ```bash docker restart tdarr-server ``` ### One-liner (copy-paste for Docker hosts) ```bash PLUGIN_DIR=/your/tdarr/plugins/FlowPlugins && \ curl -fsSL https://git.cdtech.freeddns.org/PickleRick/Tdarr-plugin/archive/main.zip -o /tmp/tp.zip && \ unzip -q -o /tmp/tp.zip -d /tmp/tpx && \ cp -r /tmp/tpx/Tdarr-plugin/LocalFlowPlugins "${PLUGIN_DIR}/" && \ cp -r /tmp/tpx/Tdarr-plugin/FlowHelpers "${PLUGIN_DIR}/" && \ rm -rf /tmp/tp.zip /tmp/tpx && \ docker restart tdarr-server && echo "Done." ``` ### Individual Plugin (single raw file) Each plugin is a single `index.js` file. Download just what you need: ```bash # Example: transcode2PassX265 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 | Path segment | |---|---|---| | `mediaAnalyzerGOP` | `utility` | `LocalFlowPlugins/utility/mediaAnalyzerGOP/1.0.0/index.js` | | `setAnimationFlag` | `utility` | `LocalFlowPlugins/utility/setAnimationFlag/1.0.0/index.js` | | `extractChaptersXml` | `utility` | `LocalFlowPlugins/utility/extractChaptersXml/1.0.0/index.js` | | `audioSelectorClamping` | `audio` | `LocalFlowPlugins/audio/audioSelectorClamping/1.0.0/index.js` | | `backupSourceAudio` | `audio` | `LocalFlowPlugins/audio/backupSourceAudio/1.0.0/index.js` | | `extractSubtitlesAndOcr` | `subtitles` | `LocalFlowPlugins/subtitles/extractSubtitlesAndOcr/1.0.0/index.js` | | `transcode2PassX265` | `video` | `LocalFlowPlugins/video/transcode2PassX265/1.0.0/index.js` | ### Tagged Releases (optional, requires CI push) 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 in `workDir`. 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**: bound-1080p Lanczos scaling (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 | **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 ```