Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sonarr/Radarr Integration

direct-play-nice can be wired as a custom script in Sonarr/Radarr pipelines.

High-level flow

  1. Arr service imports media.
  2. Custom script invokes direct_play_nice.
  3. Successful conversion output replaces source according to configured behavior.

Event behavior

The binary auto-detects Sonarr/Radarr custom-script invocations:

  • sonarr_eventtype=Download and radarr_eventtype=Download trigger conversion.
  • Non-download events (for example Test, Grab, Rename) exit cleanly.

Naming and replacement notes

  • Use --servarr-output-extension and --servarr-output-suffix to control output naming.
  • --delete-source applies to direct CLI usage.
  • In Sonarr/Radarr mode, replacement/rollback logic is handled by integration flow.
  • --servarr-output-extension match-input keeps 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 as Anime-multi-audio or anime-multi-sub.
  • custom-format-or-title: custom formats plus strong title tokens such as Dual-Audio, Multi-Audio, Multi-Subs, or MSubs.
  • 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:

  1. 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
    
  2. Use latest-missing dry-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
    
  3. 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
    
  4. 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, or Multi-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.

  5. Apply only a focused batch. Use Sonarr episode IDs from the dry-run logs to constrain the destructive run. Keep --servarr-language-audit-max-searches at 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
    
  6. Watch Arr’s queue and history. A successful Sonarr language replacement usually goes grabbeddownloadFolderImported; 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.

  7. Prioritize recent items when needed. Prefer latest-missing for a built-in newest-missing priority lane. DPN can also target a caller-supplied list of Sonarr episode IDs with --servarr-language-audit-episode-ids for fully custom Sonarr batches.

  8. 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: