Sonarr/Radarr Integration
direct-play-nice can be wired as a custom script in Sonarr/Radarr pipelines.
High-level flow
- Arr service imports media.
- Custom script invokes
direct_play_nice. - Successful conversion output replaces source according to configured behavior.
Event behavior
The binary auto-detects Sonarr/Radarr custom-script invocations:
sonarr_eventtype=Downloadandradarr_eventtype=Downloadtrigger conversion.- Non-download events (for example
Test,Grab,Rename) exit cleanly.
Naming and replacement notes
- Use
--servarr-output-extensionand--servarr-output-suffixto control output naming. --delete-sourceapplies to direct CLI usage.- In Sonarr/Radarr mode, replacement/rollback logic is handled by integration flow.
--servarr-output-extension match-inputkeeps the source container.
Example command in Sonarr custom script:
/path/to/direct_play_nice --config-file /path/to/direct-play-nice-sonarr.toml
Optional language mismatch replacement
Language checks are off by default. When enabled on a Sonarr/Radarr Download
event, direct-play-nice inspects the imported file before conversion. If any
configured audio or subtitle language is missing, conversion is skipped and DPN
asks Sonarr/Radarr for manual-search release results. It only grabs a specific
replacement release when the returned release metadata or configured candidate
policy indicates the required languages. Existing-file/cutoff rejections may be
overridden for language upgrades, but unrelated rejection reasons are still
honored. If no verified candidate is available, DPN leaves the current file
untouched and does not request a blind redownload.
CLI examples
Dry-run first while tuning your matching rules. This inspects the imported file, queries Arr for replacement candidates when requirements are missing, and reports what would happen without grabbing or blocklisting anything:
/path/to/direct_play_nice \
--servarr-language-check \
--servarr-language-dry-run \
--servarr-language-candidate-policy custom-format-or-title \
--required-audio-languages eng \
--servarr-api-url http://127.0.0.1:8989 \
--servarr-api-key "$SONARR_API_KEY"
After dry-run output looks correct, remove --servarr-language-dry-run to allow
DPN to grab the selected replacement and blocklist the old history item:
/path/to/direct_play_nice \
--servarr-language-check \
--servarr-language-candidate-policy custom-format-or-title \
--required-audio-languages eng \
--servarr-api-url http://127.0.0.1:8989 \
--servarr-api-key "$SONARR_API_KEY"
Config-file example
In a Sonarr/Radarr custom script, prefer keeping the command short and putting policy in the DPN config file:
/path/to/direct_play_nice --config-file /path/to/direct-play-nice-sonarr.toml
servarr_language_check = true
servarr_language_audit = true
servarr_language_audit_scope = "history"
servarr_language_audit_lookback_days = 30
servarr_language_audit_max_searches = 20
# Optional: prevent persistent no-candidate items from consuming every audit run.
servarr_language_audit_no_candidate_cooldown_days = 14
# Optional: limit a Sonarr audit to specific episode IDs.
# servarr_language_audit_episode_ids = "123,456"
required_audio_languages = "eng"
# Leave empty unless subtitle completeness is a goal.
required_subtitle_languages = ""
# Optional: for trusted English-native libraries, retag untagged audio before
# deciding the file is missing English audio.
servarr_untagged_audio_language = "eng"
servarr_api_url = "http://127.0.0.1:8989"
servarr_api_key = "..."
# Recommended while tuning rules. Logs the selected candidate but does not grab
# or blocklist anything.
servarr_language_dry_run = true
# strict only trusts explicit Arr language/subtitle metadata. custom-format-or-title
# also trusts matching custom formats and strong tokens like Dual-Audio/Multi-Subs.
servarr_language_candidate_policy = "custom-format-or-title"
For Radarr, use the Radarr URL/key instead:
servarr_api_url = "http://127.0.0.1:7878"
servarr_api_key = "..."
servarr_api_url and servarr_api_key can also be supplied with CLI flags or
environment variables. Supported env names include SONARR_URL,
SONARR_API_KEY, RADARR_URL, and RADARR_API_KEY.
Before grabbing a verified replacement, DPN marks the current Arr history item as
failed so the old release is blocklisted. It resolves that history item from the
Arr download id when available, or from DIRECT_PLAY_NICE_SONARR_HISTORY_ID /
DIRECT_PLAY_NICE_RADARR_HISTORY_ID if your wrapper provides it.
For Sonarr/Radarr, a language-better file can be stuck as a completed queue item when Arr considers it a quality downgrade. Audit mode checks those pending imports; when the pending file satisfies DPN’s language policy, apply mode deletes the old episode/movie-file entry, posts manual import, and removes the completed queue item without deleting the downloaded replacement.
The default examples above require English audio only. Extra audio languages are
allowed; a file with English plus Japanese audio still satisfies
required_audio_languages = "eng". Use stricter audio requirements such as
eng,jpn only for libraries where preserving the original language alongside the
dub is required. Leave required_subtitle_languages empty if missing subtitles
are acceptable.
Untagged streams are handled conservatively. By default, und/empty language
metadata does not satisfy a required language. If a library is known to be
English-native, set servarr_untagged_audio_language = "eng" or pass
--servarr-untagged-audio-language eng to remux unknown audio streams with an
English tag before DPN searches for replacement releases. The remux uses stream
copy, writes through a temporary file, and never overwrites an explicit
non-unknown language tag. servarr_language_dry_run = true only reports the
retag action. Use servarr_untagged_subtitle_language only for trusted subtitle
streams; DPN does not run speech recognition or globally infer und = eng.
Candidate policies control how much DPN infers before a replacement is grabbed:
strict: only explicit Arr language/subtitle metadata.custom-format: explicit metadata plus matching Arr custom format names such asAnime-multi-audiooranime-multi-sub.custom-format-or-title: custom formats plus strong title tokens such asDual-Audio,Multi-Audio,Multi-Subs, orMSubs.title-guess: looser title matching. Use dry-run first.
DPN always treats the already-imported file differently from candidate releases:
actual file compliance is based on FFmpeg stream metadata, then cached by DPN.
The best-effort cache defaults to
$XDG_CACHE_HOME/direct-play-nice/servarr-language-cache.json or
~/.cache/direct-play-nice/servarr-language-cache.json; override it with
DIRECT_PLAY_NICE_LANGUAGE_CACHE.
Periodic language audit
The Download-event hook only runs when Arr imports a file. To catch delayed dubs or subtitles that appear days later, run an audit from cron/systemd/launchd with no Arr custom-script environment variables.
Language upgrade process
The periodic audit is language-first. It treats queue remediation, delayed dub/sub searches, and conservative torrent cleanup as separate stages so a bad or blocked queue item does not permanently prevent a better language candidate from importing.
flowchart TD
A[Start periodic Servarr language audit] --> B[Fetch current Arr queue]
B --> C[Log Sonarr queue health before cleanup]
C --> D{Bad queue item?}
D -->|Invalid/wrong episode or multi-episode pack| E[Remove and blocklist]
D -->|Completed importPending/importBlocked language upgrade| F[Manual import if candidate satisfies language policy]
D -->|Stale zero-size torrent older than configured days| G{Within stale cleanup cap?}
G -->|Yes| H[Remove, blocklist, and search replacement]
G -->|No| I[Leave queued torrent for later run]
D -->|No| I
E --> J[Fetch queue again]
F --> J
H --> J
I --> J
J --> K[Log queue health after cleanup/import]
K --> L{Audit scope}
L -->|latest-missing| M[Scan inventory, filter non-compliant items, newest first]
L -->|inventory| N[Scan inventory backlog]
L -->|history| O[Scan recent import history]
M --> P{Required languages present?}
N --> P
O --> P
P -->|Yes| Q[Cache compliant language report]
P -->|No and cooldown active| R[Skip search this run]
P -->|No and cooldown expired| S[Search Arr releases]
S --> T{Verified language candidate?}
T -->|Yes| U[Grab replacement and blocklist old history item]
T -->|No| V[Record no-candidate cooldown]
U --> W[Next run imports or force-imports pending replacement]
Q --> X[Log audit summary]
R --> X
V --> X
W --> X
Queue health is logged at the start of Sonarr audits and after cleanup/import stages. The log includes total queue items, completed import-pending/import-blocked items, stale zero-size torrents, queued nonzero torrents, active downloads, and invalid/mismatch warning counts. Use those counters to distinguish genuine bad queue entries from a saturated torrent client. For example, many old zero-size queued torrents may indicate Deluge’s active download queue is too small rather than a bad release.
Run an audit with:
/path/to/direct_play_nice \
--config-file /path/to/direct-play-nice-sonarr.toml \
--servarr-language-audit \
--servarr-language-audit-lookback-days 30 \
--servarr-language-audit-max-searches 20 \
--servarr-language-dry-run
By default, audit mode uses --servarr-language-audit-scope history: it queries
recent Sonarr/Radarr imports, inspects the actual imported file language
metadata, updates DPN’s cache, and release-searches only missing-language items
up to --servarr-language-audit-max-searches. Use
--servarr-language-audit-scope inventory with Sonarr to inspect the current
library inventory instead of only recent import history. Inventory scope walks
series episode files, checks current media metadata, then uses each missing
item’s latest import history entry before any apply-mode grab/blocklist action.
Use --servarr-language-audit-scope latest-missing to inspect inventory, keep
only currently non-compliant files, sort those by newest air/release date first,
and spend the search budget on recent delayed dubs/subs before older backlog.
If early inventory items repeatedly return no approved replacement, set
--servarr-language-audit-no-candidate-cooldown-days (or
servarr_language_audit_no_candidate_cooldown_days) so those no-candidate items
are not release-searched again until the cooldown expires. For weekly delayed
sub/dub drops, use
--servarr-language-audit-latest-missing-no-candidate-cooldown-days (or
servarr_language_audit_latest_missing_no_candidate_cooldown_days) to give
latest-missing a shorter retry window than broad backlog sweeps. DPN stores
this best-effort state in
$XDG_CACHE_HOME/direct-play-nice/servarr-language-no-candidate-cache.json or
~/.cache/direct-play-nice/servarr-language-no-candidate-cache.json; override it
with DIRECT_PLAY_NICE_LANGUAGE_NO_CANDIDATE_CACHE. This lets later inventory
items get searched on subsequent capped audit runs instead of reprocessing the
same no-candidate edge every day.
For focused Sonarr batches, pass --servarr-language-audit-episode-ids with a
comma-separated episode ID list.
DPN also checks completed pending imports that Sonarr/Radarr refused for quality hierarchy reasons. When deciding whether to force-import a pending language upgrade, DPN inspects the existing media file’s actual stream metadata instead of trusting Arr’s language label. This lets a lower-quality pending file replace a higher-quality current file only when the current file is missing a required language and the pending file satisfies the language policy.
Sonarr auto-grabs are limited to releases that map back to the single requested
episode; multi-episode and season-pack results are skipped even when their titles
contain strong language hints. During audits, DPN can also clean up hopeless
queue entries: completed warning items with invalid season/episode or wrong
multi-episode mappings are removed and blocklisted, while quality-only warnings
are left for possible force-import. Set
--servarr-language-audit-stale-queue-days to also remove and blocklist
zero-size torrent queue items that have not acquired metadata after the given
number of days. DPN immediately searches for a replacement after stale cleanup,
preferring non-torrent candidates when possible; use
--servarr-language-audit-stale-queue-max-removals to cap churn per run. Keep
dry-run enabled while reviewing reports; remove
--servarr-language-dry-run only when you want DPN to grab selected
language-upgrade candidates, blocklist the old history item, force-import
eligible pending replacements, and clean bad queue entries.
This feature assumes DPN is the authority for language upgrades. To avoid Arr and DPN fighting each other, keep ordinary Arr quality-only upgrades conservative or disabled for libraries where DPN should make language-first replacement decisions.
Safe language upgrade runbook
Language replacement is intentionally opt-in and should be rolled out in small, observable batches. A safe operator workflow is:
-
Start with a dry-run history audit. This catches delayed dubs/subs for recent imports without scanning the whole library:
/path/to/direct_play_nice \ --config-file /path/to/direct-play-nice-sonarr.toml \ --servarr-language-audit \ --servarr-language-audit-scope history \ --servarr-language-audit-lookback-days 30 \ --servarr-language-audit-max-searches 20 \ --servarr-language-dry-run -
Use
latest-missingdry-run for delayed dub/sub follow-up. This prioritizes newest aired/released non-compliant files before older backlog:/path/to/direct_play_nice \ --config-file /path/to/direct-play-nice-sonarr.toml \ --servarr-language-audit \ --servarr-language-audit-scope latest-missing \ --servarr-language-audit-max-searches 25 \ --servarr-language-audit-latest-missing-no-candidate-cooldown-days 2 \ --servarr-language-dry-run -
Use inventory dry-run for backlog discovery. This can be slow on large libraries, so keep the search cap low while tuning:
/path/to/direct_play_nice \ --config-file /path/to/direct-play-nice-sonarr.toml \ --servarr-language-audit \ --servarr-language-audit-scope inventory \ --servarr-language-audit-max-searches 25 \ --servarr-language-dry-run -
Review candidate evidence before apply mode. Prefer candidates with explicit Arr language metadata or strong custom-format/title evidence such as
Anime-multi-audio,anime-multi-sub,Dual-Audio,Multi-Audio, orMulti-Subs. Treat unrelated rejections such as blocked indexers, unknown series, or seed/availability failures as blockers. Do not add subtitle requirements unless missing subtitles should trigger redownloads. -
Apply only a focused batch. Use Sonarr episode IDs from the dry-run logs to constrain the destructive run. Keep
--servarr-language-audit-max-searchesat or below the number of intended items:/path/to/direct_play_nice \ --config-file /path/to/direct-play-nice-sonarr.toml \ --servarr-language-audit \ --servarr-language-audit-scope inventory \ --servarr-language-audit-episode-ids "12345,12346,12347" \ --servarr-language-audit-max-searches 3 -
Watch Arr’s queue and history. A successful Sonarr language replacement usually goes
grabbed→downloadFolderImported; the imported file should then show the required audio/subtitle languages in Arr media info. Some candidates may remain queued/downloading for a while, and failed alternates in history do not necessarily mean the current queued candidate failed. -
Prioritize recent items when needed. Prefer
latest-missingfor a built-in newest-missing priority lane. DPN can also target a caller-supplied list of Sonarr episode IDs with--servarr-language-audit-episode-idsfor fully custom Sonarr batches. -
Verify and repeat. Re-run the same command with
--servarr-language-dry-run. Completed items should no longer be searched; remaining missing items should either show a queued candidate, no candidate, or a rejection reason to fix before another apply batch.
For Sonarr/Radarr, keep the same dry-run-first workflow. In apply mode, DPN may force-import completed pending replacements that satisfy the language policy by deleting the old episode/movie-file entry, posting manual import, and removing the stale completed queue item.
Practical wrapper pattern
For GPU OCR environments, keep a stable wrapper script as the command Sonarr or Radarr calls. This keeps runtime paths and OCR flags centralized.
Example wrapper:
#!/usr/bin/env bash
set -euo pipefail
export ORT_DYLIB_PATH=\"/opt/onnxruntime/lib/libonnxruntime.so\"
export LD_LIBRARY_PATH=\"/opt/onnxruntime/lib:${LD_LIBRARY_PATH:-}\"
exec /path/to/direct_play_nice --config-file /path/to/direct-play-nice-sonarr.toml
This avoids drift between manual shell runs and Arr-triggered runs.
For service-specific script setup, see the Servarr docs: