Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Voice Ovarr

Finds episodes and films that arrived with an audio description track — the narrator who describes what is happening on screen — and replaces them.

Some releases carry the visually-impaired narration mix instead of the normal soundtrack. You do not find out until you sit down to watch, and by then the "good" copy has been deleted as a duplicate.

Nothing in a normal *arr setup can catch this:

  • The release title need not say so. Many are named identically to a clean release, so no regex, quality profile or release filter can see it.
  • The file metadata need not say so either. A real described file measured during development had a single AAC track with visual_impaired=0 and no title tag. Everything Sonarr can inspect looked perfect.
  • The narration is mixed into the only audio track, so there is no clean track to switch to. The file has to be replaced, not repaired.

Only the audio itself knows. So Voice Ovarr listens.


How it decides

Subtitles transcribe the programme's dialogue. Audio description is inserted exactly where that dialogue is not, and is never itself subtitled. That gives a free, precise map of where narration would have to live.

Voice Ovarr pulls out the dialogue-free gaps, transcribes only those, and applies two tests. Both must pass:

Gate Question Clean Narrated
Gap speech Is anyone talking where nobody should be? 0–2 words/min 125–143 words/min
Narration Is what they say description, not conversation? 0.00 0.29–0.48

The second gate is the one that makes this safe to act on. Gap speech alone false-positives on unscripted television whose subtitles miss dialogue — a cooking show scored 85.8 words/min, and a children's show scored 127, higher than genuinely narrated episodes. Both scored 0.00 on narration, because contestants and puppets speak in the first and second person and ask questions. A describer never does.

Files with no subtitles

About one file in six has no subtitle track at all. Voice Ovarr handles those three ways, cheapest first:

  1. Sidecar subtitles next to the file, if present.
  2. Any text track — and it picks the fullest one, not the first. Many releases put a tiny forced/signs-only track first; taking that one makes a file look like it has no subtitles at all.
  3. Bitmap subtitles (PGS/VobSub) with no OCR. Voice Ovarr never reads subtitle text, only when dialogue starts and stops, so a picture-based track works just as well: in PGS a cue is a large "display" packet followed by a tiny "clear" packet, which gives the interval exactly.

If there is genuinely no subtitle track, it falls back to sampling the whole soundtrack and counting narration lines per minute — an absolute rate rather than a ratio, so a talkative show does not dilute it. Reference: narrated 0.60–1.87/min, clean 0.00–0.20/min. This is weaker evidence, so such detections are flagged "needs a human" and are never deleted automatically.

Files that cannot be judged are reported as "could not check". They are never quietly counted as clean.

What it does when it finds one

Deletes the file, blocklists the release so the identical copy cannot be grabbed back, and lets Sonarr/Radarr search for a replacement.

Order matters: the file is deleted before the release is failed. The other way round, the *arr still sees a satisfied episode and never searches for a replacement.

ACTION_MODE defaults to manual — it finds and reports, and you press the button. Switch it to auto once it has proved itself on your own library.


Running it

git clone https://github.com/katalyst88/voice-ovarr
cd voice-ovarr
docker compose up -d --build

There is no prebuilt image yet, so it builds from source. First build takes a few minutes: it installs ffmpeg and bakes the speech model into the image so the container needs no network access afterwards.

services:
  voice-ovarr:
    build: .
    container_name: voice-ovarr
    restart: unless-stopped
    ports: ["8099:8099"]
    environment:
      SONARR_URL: "http://sonarr:8989"
      SONARR_API_KEY: "your-key"
      RADARR_URL: "http://radarr:7878"
      RADARR_API_KEY: "your-key"
      PATH_MAPS: "/data/media=>/media"
      ACTION_MODE: "manual"
    volumes:
      - ./config:/config
      - /path/to/your/media:/media:ro

Then open http://localhost:8099.

Settings

Variable Default What it does
SONARR_URL / SONARR_API_KEY Your Sonarr. Omit for a film-only setup.
RADARR_URL / RADARR_API_KEY Your Radarr. Omit for a TV-only setup.
PATH_MAPS arr path=>container path, semicolon separated. Fixes "file not found".
VOICEOVARR_PIN unset Unset means no login. Set it if this is reachable from the internet — the UI can delete media.
ACTION_MODE manual manual reports and waits for you; auto deletes the file and blocklists the release.
MAX_ACTIONS_PER_RUN 6 Cap on deletions per run. Anything beyond is reported instead.
WPM_GATE 40 Gap-speech threshold.
NARR_GATE 0.15 Narration threshold. The safety catch.
NO_SUBS_RATE_GATE 0.5 Fallback threshold for files with no subtitles.
MAX_GAP_AUDIO 300 Seconds of audio transcribed per file (~40s of CPU).
SCHEDULE_ENABLED true Run daily over recent imports.
SCAN_CRON_HOUR / SCAN_CRON_MINUTE 3 / 45 When.
WHISPER_MODEL base.en Speech model. small.en is slower and more accurate.

Path mapping

The most common problem. Sonarr reports a file as /data/media/TV/Show/Season 01/ep.mkv; inside this container it might be /media/TV/Show/Season 01/ep.mkv. Then:

PATH_MAPS=/data/media=>/media

Windows-style *arr paths work too: PATH_MAPS=\\nas\public=>/media.


What it costs

It transcribes about five minutes of audio per episode, not the whole file, and speech recognition runs locally (faster-whisper, baked into the image) — no API, no account, nothing leaves your network.

The cost is dominated by reading the file, not by listening to it. Extracting the subtitle timings and slicing the audio both mean pulling the file through ffmpeg, so where your media lives matters far more than your CPU:

Media location Measured, per episode
Local disk ~1 minute
Network share (SMB/NFS), via Docker ~4 to 5 minutes

Plan the library sweep around your own number rather than an advertised one — start it, watch the History tab, and set the batch size from what you see.


Things that do not work

Recorded so nobody repeats them:

  1. ffprobe disposition flags (visual_impaired) or track titles — clean on genuinely described files.
  2. Release-title matching — the title carries no marker.
  3. Silence or loudness density — a described episode measured 14.0% quiet against 4.2% for a clean one. Backwards, because clean episodes run near-continuous score. Loudness cannot separate narration from music.
  4. Subtitle-gap energy with no transcription (a fully signal-processing approach, no ML). Tested across 6 described and 7 clean files: described scored 0.560–0.891, clean scored 0.485–0.613. Overlapping — no threshold exists. Narration and dialogue are both "a person talking in a quiet mix"; only the words tell them apart.
  5. A couple of fixed sample windows — caught one described episode and missed two others in the same release. Aim at the gaps instead.

Licence

MIT.

About

Finds episodes that arrived with an audio description track - the narrator describing what is on screen - and replaces them. Self-hosted companion for Sonarr and Radarr.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages