All apps · 0 apps
uldas
Docker app from MrCorehh's Repository
Overview
ULDAS (Undefined Language Detector for Audio and Subtitles) scans video and subtitle files for audio and subtitle tracks tagged as undefined or unknown, extracts samples, uses Whisper speech recognition to detect the audio language, detects subtitle language (including FORCED and SDH flags), and writes the correct language codes back to the file metadata. Optionally remuxes non-MKV video to MKV first. Includes a WebUI on port 2119 for settings, scheduler and logs.
Setup:
- Map each media library you want scanned to a path inside the container (e.g. /folder1, /folder2). Add more paths as needed.
- Start the container once, then edit /appdata/uldas/config.yml (or use the WebUI Settings page) so the 'path' list matches the container-side paths you mapped.
- CRON_SCHEDULE and SCHEDULE_HOURS are only read on first launch. After that the schedule is managed in the WebUI under Settings, Scheduler.
- ULDAS does not run on container start by default. Trigger a run from the WebUI dashboard or enable Run on Startup in Settings.
Warning: with remux_to_mkv enabled the original non-MKV file is deleted after a successful remux. Use dry_run: true in config.yml to test first.
Optional NVIDIA GPU: install the Nvidia Driver plugin, set Extra Parameters to --runtime=nvidia and fill in NVIDIA_VISIBLE_DEVICES with your GPU UUID. Set device to cuda in config.yml.
Readme
View on GitHubDo you have Movies or TV shows in your media player for which the audio and/or subtitle tracks are labeled as "undefined" or "unknown"?
ULDAS (Undefined Language Detector for Audio and Subtitles) solves that problem by:
- Scanning your video and subtitle files for undefined tracks
- Extracting audio and subtitle samples
- Using AI speech recognition to detect the audio language
- Detecting subtitle language (can also detect if subtitles are [FORCED] and/or [SDH])
- Updating the file metadata with the correct language codes and flags
The script optionally remuxes non MKV video formats to MKV first.
Requires
- Python >=3.11
- FFmpeg
- MKVToolNix
- Tesseract-OCR for image based subtitles (e.g. PGS)
📑 Table of Contents
- 🛠️ Installation
- ⚙️ Configuration
- 🌐 WebUI
- 🔔 Webhook on Remux
- 📄 Supported File Formats
- ⌨️ CLI Reference
- 🏞️ Example screenshots
- ⚠️ Need Help or have Feedback?
- ❤️ Support the Project
🛠️ Installation
Step 1: Install Docker
- Download Docker Desktop from docker.com
- Install and start Docker Desktop on your computer
- Verify installation: Open a terminal/command prompt and type
docker --version- you should see a version number
Step 2: Create Docker Compose File
- Create a new folder for ULDAS on your computer (e.g.,
C:\ULDASor/home/user/ULDAS) - Download the
docker-compose.ymland place it in that folder, or manually create it by copy pasting this content:
version: "3.8"
services:
uldas:
container_name: uldas
image: netplexflix/uldas:latest
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York # Set your timezone
- CRON_SCHEDULE=0 5 * * 5
# Examples:
# "0 3 * * *" = every day at 3:00 AM
# "0 */6 * * *" = every 6 hours
# "0 5 * * 5" = every Friday at 5:00 AM
# Alternative: use a simple hours interval instead of cron:
# - SCHEDULE_HOURS=24
# Leave all scheduler env vars empty or remove them to run once and exit.
#
# NOTE: CRON_SCHEDULE / SCHEDULE_HOURS are only used as initial defaults
# on first launch. Once saved to config.yml they can be edited live from
# the webUI Settings page (Scheduler section) without a container restart.
ports:
- "2119:2119"
volumes:
- ./config:/app/config
- /path/to/folder1:/folder1
- /path/to/folder2:/folder2
# Optional: mount custom temp directory:
# - /path/to/temp:/tmp/uldas
restart: unless-stopped
Step 3: Update volumes, IDs, port and CRON Schedule
- Volume Mounts:
| Mount | Description |
|---|---|
/app/config |
Config file and tracking data (required) |
/folder1 |
e.g. Your movies library |
/folder2 |
e.g. Your TV shows library |
/tmp/uldas |
optional mount for custom tmp directory |
[!NOTE] Your
config.ymlpaths need to match the paths inside your container
example:volumes: - ./config:/app/config - /media/movies:/folder1 - /media/tv:/folder2Extra paths can be added as long as they are also listed in the config.
[!IMPORTANT] The format is:
your-actual-path:container-path
- Set the timezone (
TZ) e.g.Europe/Brussels,America/New_York,Asia/Tokyo,... - Update the CRON Schedule Tip: Crontab.Guru
CRON_SCHEDULE(andSCHEDULE_HOURS) are only consulted on first start. After that, edit the schedule live from the webUI Settings page → Scheduler section (between General and Advanced). You can switch between a simple hours interval and a CRON expression without restarting the container.- By default ULDAS does not run immediately when the container starts — it waits for the next scheduled time (or a manual Run Now from the dashboard), so you have a chance to review your settings in the webUI first. If you prefer ULDAS to start processing as soon as the container is up, enable Run on Startup in the webUI Settings → Scheduler section (or set
run_on_startup: trueinconfig.yml).
- Update port to xxxx:2119 if you want to run the webUI on a different port than 2119.
Step 4: Create a config
- Create a subfolder named
config - Download
config/config.example.ymland save it asconfig.ymlin your config folder
Step 5: Configure Your Settings
- See ⚙️ Configuration
Step 6: Run ULDAS
- Open a terminal/command prompt in your ULDAS folder
- Type this command and press Enter:
docker-compose up -d - That's it! The latest docker container will be pulled from Dockerhub.
- Update
docker-compose pull
Unraid
ULDAS is available in Community Applications. Search for "ULDAS" or install manually using the template in unraid/.
⚙️ Configuration
Rename config.example.yml to config.yml and change the values where needed:
- path: Main Paths for your media.
- ignore_tags: List of substrings. Any file whose name contains one of these (case-insensitive, matched against the name without extension) is skipped. Useful for trailers, samples, featurettes. Example:
[-trailer, sample] - remux_to_mkv:
trueremuxes non-MKV files so they can be processed too - show_details:
truewill show you more details of what's happening - dry_run:
truewill do a dry run (will show what it would do, without actually altering any files) - run_on_startup:
false(default) — ULDAS will not run immediately when the container starts, giving you time to review settings in the webUI first. Set totrueto run straight away on container start; subsequent runs then follow the configured schedule in either case. - process_subtitles:
truewill process undefined subtitle tracks - process_external_subtitles:
truewill process external subtitle files - analyze_forced_subtitles:
truewill analyze whether a subtitle track has "Forced Subtitles" or not - detect_sdh_subtitles:
truewill analyze whether a subtitle track has 'hearing impaired' support. (e.g.: [Dogs barking], [Narrator:],... )
Expert variables
Only Change these if you know what you're doing.
- vad_filter: Enables Voice Activity Detection to filter out silence and background noise before language analysis (Default: True)
- vad_min_speech_duration_ms: Minimum speech segment length (in milliseconds) to consider as valid speech (Default: 250)
- vad_max_speech_duration_s: Maximum continuous speech segment length (in seconds) before splitting (Default: 30)
- whisper_model: See Model Size Guide below
- device: Hardware acceleration preference (auto, cpu, or cuda). Auto-detects CUDA GPU if available, falls back to CPU (Default: "auto")
- compute_type: Precision/performance trade-off (auto, int8, float16, float32). Auto-selects optimal type based on device (Default: "auto")
- cpu_threads:Number of CPU threads to use. 0 = automatic detection based on system cores (Default: 0)
- confidence_threshold: Minimum confidence level (0.0-1.0) required to accept language detection from audio samples. If sample-based detection falls below this threshold, the entire audio track is analyzed for improved accuracy. Higher values are more conservative but reduce false positives. (Default: 0.9)
- subtitle_confidence_threshold: If subtitle detection confidence falls below confidence, the track is skipped
- reprocess_all :
truewill reprocess ALL audio tracks, even if they already have a language tag. (Default:false) - reprocess_all_subtitles:
truewill reprocess ALL subtitle tracks, even if they already have a language tag. (Default:false) - operation_timeout_seconds: 600, # 10 minutes
- temp_dir: Change temporary directory for audio/subtitle extraction. Leave empty to use system default (/tmp)
Forced subtitle detection thresholds.
Density-based:
- forced_subtitle_low_density_threshold: Below = likely forced
- forced_subtitle_high_density_threshold: Above = likely full
Coverage-based (secondary factor):
- forced_subtitle_low_coverage_threshold: Below = likely forced
- forced_subtitle_high_coverage_threshold: Above = likely full
Absolute count thresholds:
- forced_subtitle_min_count_threshold: Below = likely forced
- forced_subtitle_max_count_threshold: Above = likely full
Model Size Guide
- tiny: Fastest, least accurate
- base: Good balance
- small: More accurate, slower (used during development tests)
- medium: Very accurate, much slower
- large: Most accurate, very slow
🌐 WebUI
You can access the webui via localhost:2119 Here you can edit settings and check your log. Screenshots
🔔 Webhook on Remux
ULDAS can send an HTTP request after each remux. (e.g. for triggering Autoscan/Autopulse/..)
Enable it in the WebUI under Settings → Advanced → Webhook, or in config.yml.
| Variable | Value |
|---|---|
{path} / {path_enc} |
Full path of the remuxed .mkv (raw / URL-encoded) |
{dir} / {dir_enc} |
Parent folder of the file |
{filename} |
File name with extension |
{name_noext} |
File name without extension |
{old_path} / {old_path_enc} |
Original pre-remux file path |
Use the
_encvariants whenever the value goes into a URL query string or a form body, since media paths contain spaces and special characters.
Extra options
| Key | Description |
|---|---|
webhook_headers |
Extra request headers, one Key: Value per line (e.g. an API token) |
webhook_timeout_seconds |
Request timeout (default 10). The call is fire-and-forget — it never blocks or fails a run |
webhook_path_from / webhook_path_to |
Optional path remap, for when ULDAS's container mounts differ from your media server's. The leading from path is replaced with to before the {path} variables are built |
The webhook only fires on an actual remux (the rename event), never on in-place language-tag edits, and is skipped entirely during dry_run.
📄 Supported File Formats
Always Processed:
- MKV files: Primary target format
With remux_to_mkv: true
- MP4, AVI, MOV, WMV, FLV, WebM, M4V, M2TS, MTS, TS, VOB
- Note: Original files are deleted after successful conversion
External subtitle File Formats:
- SRT, ASS, SSA, SUB, VTT, IDX
⌨️ CLI Reference
Utility Commands
| Flag | Description |
|---|---|
--version |
Show the current ULDAS version and exit |
--config PATH |
Path to the configuration file (default: config/config.yml) |
--create-config |
Create a sample configuration file and exit |
--find-mkv |
Locate MKVToolNix installation (Windows only) |
--clear-tracking |
Clear all file processing tracking data and exit |
--skip-update-check |
Skip checking for updates on startup |
Logging & Output
| Flag | Description |
|---|---|
--verbose, -v |
Enable verbose/debug output (sets show_details to true) |
--quiet, -q |
Suppress most console output (sets show_details to false) |
--show-details |
Show detailed processing information |
--no-show-details |
Hide detailed processing information |
Paths & General
| Flag | Description |
|---|---|
--directory DIR [DIR ...] |
Override directory/directories to scan. Accepts multiple paths |
--remux-to-mkv |
Remux non-MKV video files to MKV before processing |
--no-remux-to-mkv |
Disable remuxing non-MKV files to MKV |
--model {tiny,base,small,medium,large} |
Whisper model size to use for audio language detection |
--dry-run |
Simulate all changes without modifying any files |
--temp-dir DIR |
Override the temporary directory used for intermediate files |
Voice Activity Detection (VAD)
| Flag | Description |
|---|---|
--vad |
Enable VAD filter (default) |
--no-vad |
Disable VAD filter |
--vad-min-speech-duration-ms MS |
Minimum speech duration in milliseconds for VAD |
--vad-max-speech-duration-s S |
Maximum speech duration in seconds for VAD |
Device & Compute
| Flag | Description |
|---|---|
--device {auto,cpu,cuda} |
Device for Whisper inference |
--compute-type {auto,int8,int8_float16,int16,float16,float32} |
Compute type for Whisper inference |
--cpu-threads N |
Number of CPU threads (0 = auto) |
Confidence & Reprocessing
| Flag | Description |
|---|---|
--confidence-threshold F |
Confidence threshold for audio language detection (0.0��1.0) |
--reprocess-all |
Reprocess all audio tracks, even those already tagged |
--force-reprocess |
Force reprocessing, ignoring the tracking cache entirely |
Tracking
| Flag | Description |
|---|---|
--tracking |
Enable file processing tracking (default) |
--no-tracking |
Disable file processing tracking |
Subtitle Processing
| Flag | Description |
|---|---|
--process-subtitles |
Process embedded subtitle tracks |
--no-process-subtitles |
Disable processing of embedded subtitle tracks |
--process-external-subtitles |
Process external subtitle files |
--no-process-external-subtitles |
Disable processing of external subtitle files |
--analyze-forced |
Analyze and tag forced subtitles |
--no-analyze-forced |
Disable forced subtitle analysis |
--detect-sdh |
Detect and tag SDH (Subtitles for the Deaf and Hard of Hearing) |
--no-sdh-detection |
Disable SDH subtitle detection |
--subtitle-confidence-threshold F |
Confidence threshold for subtitle language detection (0.0–1.0) |
--reprocess-all-subtitles |
Reprocess all subtitle tracks, even those already tagged |
Timeouts
| Flag | Description |
|---|---|
--operation-timeout S |
Timeout in seconds for long operations such as full audio track extraction |
Forced Subtitle Thresholds
| Flag | Description |
|---|---|
--forced-sub-low-coverage F |
Low coverage threshold (%) — below this, subtitles are likely forced |
--forced-sub-high-coverage F |
High coverage threshold (%) — above this, subtitles are likely full |
--forced-sub-low-density F |
Low density threshold (subtitles/min) — below this, likely forced |
--forced-sub-high-density F |
High density threshold (subtitles/min) — above this, likely full |
--forced-sub-min-count N |
Minimum subtitle count — below this, likely forced |
--forced-sub-max-count N |
Maximum subtitle count — above this, likely full |
CLI ↔ Config File Mapping
Every CLI option corresponds to a key in the YAML configuration file. CLI arguments always take precedence over config file values. For boolean options with toggle pairs (--flag / --no-flag), the positive flag wins if both are specified.
| CLI Flag | Config Key | Default |
|---|---|---|
--directory |
path |
["."] |
--remux-to-mkv |
remux_to_mkv |
false |
--show-details |
show_details |
true |
--model |
whisper_model |
base |
--dry-run |
dry_run |
false |
--temp-dir |
temp_dir |
"" |
--vad / --no-vad |
vad_filter |
true |
--vad-min-speech-duration-ms |
vad_min_speech_duration_ms |
250 |
--vad-max-speech-duration-s |
vad_max_speech_duration_s |
30 |
--device |
device |
auto |
--compute-type |
compute_type |
auto |
--cpu-threads |
cpu_threads |
0 |
--confidence-threshold |
confidence_threshold |
0.9 |
--reprocess-all |
reprocess_all |
false |
--force-reprocess |
force_reprocess |
false |
--tracking / --no-tracking |
use_tracking |
true |
--process-subtitles |
process_subtitles |
false |
--process-external-subtitles |
process_external_subtitles |
false |
--analyze-forced |
analyze_forced_subtitles |
false |
--detect-sdh / --no-sdh-detection |
detect_sdh_subtitles |
true |
--subtitle-confidence-threshold |
subtitle_confidence_threshold |
0.85 |
--reprocess-all-subtitles |
reprocess_all_subtitles |
false |
--operation-timeout |
operation_timeout_seconds |
600 |
--forced-sub-low-coverage |
forced_subtitle_low_coverage_threshold |
25.0 |
--forced-sub-high-coverage |
forced_subtitle_high_coverage_threshold |
50.0 |
--forced-sub-low-density |
forced_subtitle_low_density_threshold |
3.0 |
--forced-sub-high-density |
forced_subtitle_high_density_threshold |
8.0 |
--forced-sub-min-count |
forced_subtitle_min_count_threshold |
50 |
--forced-sub-max-count |
forced_subtitle_max_count_threshold |
300 |
🏞️ Example screenshots:
Audio Processing
- example run with reprocess_all: true: Samsara is indeed a documentary without spoken dialogue.
Subtitle Processing
⚠️ Need Help or have Feedback?
[!NOTE]
Audio Tracks
A warning will be given at the end of a run for any files that were marked as 'zxx' (no linguistic content).
While it is perfectly possible for a video file to have no linguistic content (silent movies, old Disney cartoons, etc), these could also indicate AI 'hallucinations'. You may want to manually check these files.Subtitle Tracks
Tracks with confidence below the
subtitle_confidence_thresholdare automatically skipped and shown in the summary. For image-based (PGS) subtitles without OCR support, language detection will be skipped.Failed Files
If a file is marked as failed, it is likely corrupt. Manually remux or replace it. Renaming of external subtitle files can fail if the targetted name is already in use by another file, or if the file is set to read-only.
- Join our Discord
❤️ Support the Project
If you find this project useful, starring the repository is appreciated! ⭐
Big thanks to DaLeberkasPepi for extensive testing.
Install Uldas on Unraid in a few clicks.
Find Uldas in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.
Categories
Download Statistics
Related apps
Explore more like this
Explore allDetails
netplexflix/uldas:latestRuntime arguments
- Web UI
http://[IP]:[PORT:2119]- Network
bridge- Shell
sh- Privileged
- false
Template configuration
Port for the ULDAS WebUI.
- Target
- 2119
- Default
- 2119
- Value
- 2119
Holds config.yml and tracking data.
- Target
- /app/config
- Default
- /mnt/user/appdata/uldas
- Value
- /mnt/user/appdata/uldas
First media library to scan (e.g. Movies). The container-side path must be listed under 'path' in config.yml.
- Target
- /folder1
- Default
- /mnt/user/media/movies
- Value
- /mnt/user/media/movies
Second media library to scan (e.g. TV Shows). The container-side path must be listed under 'path' in config.yml. Remove if not needed.
- Target
- /folder2
- Default
- /mnt/user/media/tv
- Value
- /mnt/user/media/tv
Optional. Host path for temporary audio and subtitle extraction. Leave empty to use the container's internal /tmp. Point at a fast drive if you process large files.
- Target
- /tmp/uldas
User ID the process runs as. 99 is the Unraid 'nobody' user.
- Default
- 99
- Value
- 99
Group ID the process runs as. 100 is the Unraid 'users' group.
- Default
- 100
- Value
- 100
Timezone, e.g. Europe/Brussels, America/New_York, Australia/Brisbane.
- Default
- Australia/Brisbane
- Value
- Australia/Brisbane
Cron expression for scheduled runs. Only used as the initial default on first launch; afterwards edit in the WebUI (Settings, Scheduler). Leave empty together with SCHEDULE_HOURS to run once and exit.
- Default
- 0 5 * * 5
- Value
- 0 5 * * 5
Alternative to CRON_SCHEDULE: run every N hours (e.g. 24). Only used as the initial default on first launch. Leave empty if using CRON_SCHEDULE.
Optional GPU acceleration. GPU UUID from the Nvidia Driver plugin, or 'all'. Requires --runtime=nvidia in Extra Parameters.
Optional GPU acceleration. Leave as 'all' when using an NVIDIA GPU.
- Default
- all
- Value
- all
