Files
Tdarr-plugin/README.md
T
PickleRick 5d43ba6a26
Build / Lint & Test (push) Successful in 18s
Save uncommitted changes
2026-09-21 12:34:15 +02:00

367 lines
13 KiB
Markdown

# 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` |
| `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:
```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
│ ├── 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 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**: `<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,
and reports throttled copy progress. 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, 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
```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
```