fix: pass inputFileObj to all CLI constructors; add release workflow and README
Release Plugins / Package & Release (push) Successful in 24s
Release Plugins / Package & Release (push) Successful in 24s
This commit is contained in:
1 parent
c2074be826
commit
55a9fc9716
7 files changed
+391
-9
No files matched your search
@@ -0,0 +1,88 @@
|
|||||||
|
name: Release Plugins
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Release version (e.g. v1.0.1). Leave blank to use the tag or "latest".'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Package & Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Determine release tag
|
||||||
|
id: tag
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
||||||
|
echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||||
|
elif [[ -n "${{ github.event.inputs.version }}" ]]; then
|
||||||
|
echo "tag=${{ github.event.inputs.version }}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "tag=latest" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create plugin ZIPs
|
||||||
|
run: |
|
||||||
|
mkdir -p dist
|
||||||
|
|
||||||
|
# Full suite ZIP (all LocalFlowPlugins + FlowHelpers)
|
||||||
|
zip -r dist/Tdarr-plugins-${{ steps.tag.outputs.tag }}.zip \
|
||||||
|
LocalFlowPlugins/ \
|
||||||
|
FlowHelpers/ \
|
||||||
|
-x "*.git*"
|
||||||
|
|
||||||
|
# Individual plugin ZIPs
|
||||||
|
for dir in LocalFlowPlugins/*/*/1.0.0; do
|
||||||
|
plugin_name=$(echo "$dir" | awk -F'/' '{print $(NF-1)}')
|
||||||
|
zip -j "dist/${plugin_name}-${{ steps.tag.outputs.tag }}.zip" "$dir/index.js"
|
||||||
|
done
|
||||||
|
|
||||||
|
ls -lh dist/
|
||||||
|
|
||||||
|
- name: Delete existing "latest" release (if re-releasing)
|
||||||
|
if: steps.tag.outputs.tag == 'latest'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
gh release delete latest --yes --cleanup-tag 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ steps.tag.outputs.tag }}
|
||||||
|
name: "Tdarr Plugins ${{ steps.tag.outputs.tag }}"
|
||||||
|
body: |
|
||||||
|
## Tdarr Local Flow Plugin Suite — ${{ steps.tag.outputs.tag }}
|
||||||
|
|
||||||
|
### Installation (curl)
|
||||||
|
|
||||||
|
Download the full suite:
|
||||||
|
```bash
|
||||||
|
curl -L https://github.com/${{ github.repository }}/releases/download/${{ steps.tag.outputs.tag }}/Tdarr-plugins-${{ steps.tag.outputs.tag }}.zip -o plugins.zip
|
||||||
|
unzip plugins.zip -d /path/to/tdarr/plugins/
|
||||||
|
```
|
||||||
|
|
||||||
|
Or download an individual plugin (example: `transcode2PassX265`):
|
||||||
|
```bash
|
||||||
|
curl -L https://github.com/${{ github.repository }}/releases/download/${{ steps.tag.outputs.tag }}/transcode2PassX265-${{ steps.tag.outputs.tag }}.zip -o transcode2PassX265.zip
|
||||||
|
unzip transcode2PassX265.zip -d /path/to/tdarr/plugins/LocalFlowPlugins/video/transcode2PassX265/1.0.0/
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [README](https://github.com/${{ github.repository }}#readme) for full installation instructions.
|
||||||
|
files: dist/*.zip
|
||||||
|
prerelease: ${{ steps.tag.outputs.tag == 'latest' }}
|
||||||
|
make_latest: ${{ steps.tag.outputs.tag != 'latest' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
/**
|
/**
|
||||||
* Local mock of Tdarr FlowHelpers/1.0.0/cliUtils.js
|
* Local mock of Tdarr FlowHelpers/1.0.0/cliUtils.js
|
||||||
* Used only for local testing — on real Tdarr this file comes from the server.
|
* Used only for local testing — on real Tdarr this file comes from the server.
|
||||||
|
*
|
||||||
|
* Matches the real Tdarr CLI constructor signature:
|
||||||
|
* new CLI({ cli, spawnArgs, WorkDir, jobLog, inputFileObj })
|
||||||
|
* inputFileObj is accepted but unused here (only used by real Tdarr for progress tracking).
|
||||||
*/
|
*/
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
|
|
||||||
@@ -40,3 +44,4 @@ class CLI {
|
|||||||
const getFFmpegVar = () => {};
|
const getFFmpegVar = () => {};
|
||||||
|
|
||||||
module.exports = { CLI, getFFmpegVar };
|
module.exports = { CLI, getFFmpegVar };
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -76,6 +76,7 @@ const plugin = async (args) => {
|
|||||||
],
|
],
|
||||||
WorkDir: workDir,
|
WorkDir: workDir,
|
||||||
jobLog,
|
jobLog,
|
||||||
|
inputFileObj,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { cliExitCode } = await cli.runCli();
|
const { cliExitCode } = await cli.runCli();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -79,9 +79,11 @@ const buildSuffix = (lang, stream) => {
|
|||||||
return parts.join('.');
|
return parts.join('.');
|
||||||
};
|
};
|
||||||
|
|
||||||
const runFFmpeg = async (spawnArgs, workDir, ffmpegPath, jobLog) => {
|
const runFFmpeg = async (spawnArgs, workDir, ffmpegPath, jobLog, inputFileObj) => {
|
||||||
const { CLI } = require('../../../../FlowHelpers/1.0.0/cliUtils');
|
const { CLI } = require('../../../../FlowHelpers/1.0.0/cliUtils');
|
||||||
const cli = new CLI({ cli: ffmpegPath, spawnArgs, WorkDir: workDir, jobLog });
|
const cli = new CLI({
|
||||||
|
cli: ffmpegPath, spawnArgs, WorkDir: workDir, jobLog, inputFileObj,
|
||||||
|
});
|
||||||
return cli.runCli();
|
return cli.runCli();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -127,7 +129,7 @@ const plugin = async (args) => {
|
|||||||
if (TEXT_CODECS.has(codec)) {
|
if (TEXT_CODECS.has(codec)) {
|
||||||
const { cliExitCode } = await runFFmpeg(
|
const { cliExitCode } = await runFFmpeg(
|
||||||
['-y', '-i', inputFile, '-map', `0:${stream.index}`, '-c:s', 'srt', outSrt],
|
['-y', '-i', inputFile, '-map', `0:${stream.index}`, '-c:s', 'srt', outSrt],
|
||||||
workDir, args.ffmpegPath, jobLog,
|
workDir, args.ffmpegPath, jobLog, inputFileObj,
|
||||||
);
|
);
|
||||||
if (cliExitCode !== 0) {
|
if (cliExitCode !== 0) {
|
||||||
jobLog(`[extractSubtitlesAndOcr] WARNING: FFmpeg failed for stream #${stream.index} (exit ${cliExitCode}).\n`);
|
jobLog(`[extractSubtitlesAndOcr] WARNING: FFmpeg failed for stream #${stream.index} (exit ${cliExitCode}).\n`);
|
||||||
@@ -144,7 +146,7 @@ const plugin = async (args) => {
|
|||||||
// Step 1: Extract to .sup
|
// Step 1: Extract to .sup
|
||||||
const { cliExitCode: extractExit } = await runFFmpeg(
|
const { cliExitCode: extractExit } = await runFFmpeg(
|
||||||
['-y', '-i', inputFile, '-map', `0:${stream.index}`, '-c:s', 'copy', tmpSup],
|
['-y', '-i', inputFile, '-map', `0:${stream.index}`, '-c:s', 'copy', tmpSup],
|
||||||
workDir, args.ffmpegPath, jobLog,
|
workDir, args.ffmpegPath, jobLog, inputFileObj,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (extractExit !== 0) {
|
if (extractExit !== 0) {
|
||||||
@@ -164,6 +166,7 @@ const plugin = async (args) => {
|
|||||||
],
|
],
|
||||||
WorkDir: workDir,
|
WorkDir: workDir,
|
||||||
jobLog,
|
jobLog,
|
||||||
|
inputFileObj,
|
||||||
});
|
});
|
||||||
const { cliExitCode: seExit } = await seCli.runCli();
|
const { cliExitCode: seExit } = await seCli.runCli();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -49,6 +49,7 @@ const plugin = async (args) => {
|
|||||||
spawnArgs: [inputFile, 'chapters', xmlPath],
|
spawnArgs: [inputFile, 'chapters', xmlPath],
|
||||||
WorkDir: workDir,
|
WorkDir: workDir,
|
||||||
jobLog,
|
jobLog,
|
||||||
|
inputFileObj,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { cliExitCode } = await cli.runCli();
|
const { cliExitCode } = await cli.runCli();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -177,6 +177,7 @@ const plugin = async (args) => {
|
|||||||
],
|
],
|
||||||
WorkDir: workDir,
|
WorkDir: workDir,
|
||||||
jobLog,
|
jobLog,
|
||||||
|
inputFileObj,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { cliExitCode: pass1Exit } = await pass1Cli.runCli();
|
const { cliExitCode: pass1Exit } = await pass1Cli.runCli();
|
||||||
@@ -207,6 +208,7 @@ const plugin = async (args) => {
|
|||||||
],
|
],
|
||||||
WorkDir: workDir,
|
WorkDir: workDir,
|
||||||
jobLog,
|
jobLog,
|
||||||
|
inputFileObj,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { cliExitCode: pass2Exit } = await pass2Cli.runCli();
|
const { cliExitCode: pass2Exit } = await pass2Cli.runCli();
|
||||||
|
|||||||
@@ -0,0 +1,282 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
### Full Suite (all 7 plugins)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Replace vX.Y.Z with the desired release tag, or use "latest"
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
After extracting, restart the Tdarr server so it picks up the new plugins:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker restart tdarr-server
|
||||||
|
```
|
||||||
|
|
||||||
|
### Individual Plugin
|
||||||
|
|
||||||
|
Each plugin is also released as its own ZIP (e.g. `transcode2PassX265-latest.zip`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PLUGIN=transcode2PassX265
|
||||||
|
VERSION=latest
|
||||||
|
DEST=/path/to/tdarr/plugins/FlowPlugins/LocalFlowPlugins/video/${PLUGIN}/1.0.0
|
||||||
|
|
||||||
|
mkdir -p "${DEST}"
|
||||||
|
curl -L https://git.cdtech.freeddns.org/PickleRick/Tdarr-plugin/releases/download/${VERSION}/${PLUGIN}-${VERSION}.zip \
|
||||||
|
-o ${PLUGIN}.zip
|
||||||
|
unzip -o ${PLUGIN}.zip -d "${DEST}"
|
||||||
|
rm ${PLUGIN}.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Available plugin names: `mediaAnalyzerGOP`, `setAnimationFlag`, `extractChaptersXml`,
|
||||||
|
`audioSelectorClamping`, `backupSourceAudio`, `extractSubtitlesAndOcr`, `transcode2PassX265`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 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**: `<basename>.<lang>[.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
|
||||||
|
```
|
||||||
Reference in new issue
Block a user