Skip to content
View in the app

A better way to browse. Learn more.

Unraid

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

[Script] Auto Subtitle Synchronizer & Translator (User Scripts)

Featured Replies

I am using a custom Python script designed to run via the User Scripts plugin (e.g., scheduled once a month at night).
It automatically scans your media libraries, detects missing subtitles in specific target languages, and translates them on the fly using a local LibreTranslate API instance.

Features

  • Direct File Placement: Saves translated .srt files directly next to the original video/subtitle files, maintaining proper naming conventions (.th.srt, .nld.srt, etc.).

  • Non destructive & Efficient: Automatically ignores already translated or existing files.

  • Automated Batch Processing: Handles large .srt files smoothly without overloading the translation API.

Prerequisites

  1. User Scripts Plugin installed on Unraid.

  2. A running LibreTranslate container (or any compatible translation API endpoint).

Schedule Recommendation

Set the schedule to Custom and enter: 0 1 1 * *(This runs the script automatically at 01:00 AM on the 1st day of every month, ensuring zero performance impact during active hours).

Customization

You can easily change NL_EXTS or TH_EXTS to any target languages (e.g. Spanish .es.srt, German .de.srt, French .fr.srt) and update the language codes passed to translate_srt_file() accordingly.

Hope this helps someone else looking to keep multi language subtitle libraries up to date automatically.

The Script

  1. Go to Settings \User Scripts in Unraid.

  2. Click Add New Script and name it (e.g., Auto_Subtitle_Sync).

  3. Click the gear icon next to it and select Edit Script.

  4. Paste the following code:

#!/bin/bash

# Description: Scans media folders for missing target subtitles and translates them automatically.

# Schedule: Custom (0 1 1 * *) - Runs on the 1st of every month at 01:00 AM.

python3 -u - << 'EOF'

import json

import os

import re

import time

import urllib.error

import urllib.request

# ----------------------------------------------------------------------

# CONFIGURATION

# ----------------------------------------------------------------------

# URL to your LibreTranslate API instance

API_URL = "http://192.168.1.10:5000/translate"

BATCH_SIZE = 25

TIMEOUT = 180

MAX_RETRIES = 3

BATCH_DELAY = 0.2

# Add your Unraid media shares here

MEDIA_FOLDERS = [

"/mnt/user/Animation",

"/mnt/user/Animation_Shows",

"/mnt/user/Anime",

"/mnt/user/Anime_Shows",

"/mnt/user/Documentary",

"/mnt/user/Documentary-Shows",

"/mnt/user/Movies",

"/mnt/user/TV_Shows"

]

# Extensions to check for (adjust language codes as needed)

NL_EXTS = ['.nld.srt', '.nl.srt', '.dut.srt']

EN_EXTS = ['.eng.srt', '.en.srt']

TH_EXTS = ['.th.srt']

# ----------------------------------------------------------------------

def api_translate(texts, target_lang):

payload = {

"q": texts,

"source": "auto",

"target": target_lang,

"format": "text",

}

data = json.dumps(payload, ensure_ascii=False).encode("utf-8")

request = urllib.request.Request(

API_URL,

data=data,

headers={"Content-Type": "application/json"},

method="POST",

)

last_error = None

for attempt in range(1, MAX_RETRIES + 1):

try:

with urllib.request.urlopen(request, timeout=TIMEOUT) as response:

result = json.loads(response.read().decode("utf-8"))

translated = result.get("translatedText")

if isinstance(translated, str):

translated = [translated]

if not isinstance(translated, list) or len(translated) != len(texts):

raise RuntimeError("Unexpected API response structure")

return translated

except Exception as exc:

last_error = exc

print(f" [API ERROR] Attempt {attempt}/{MAX_RETRIES}: {exc}", flush=True)

if attempt != MAX_RETRIES:

time.sleep(attempt * 2)

raise RuntimeError(f"Translation failed: {last_error}")

def split_blocks(content):

content = content.replace("\r\n", "\n").replace("\r", "\n")

return re.split(r"\n\s*\n", content.strip())

def parse_block(block):

lines = block.splitlines()

marker = "--" + chr(62)

pos = next((i for i, line in enumerate(lines) if marker in line), None)

if pos is None or not lines[pos + 1:]:

return None

return lines[:pos + 1], "\n".join(lines[pos + 1:])

def translate_srt_file(source_path, target_path, target_lang):

print(f" -> Translating [{os.path.basename(source_path)}] to [{target_lang}]...", flush=True)

with open(source_path, "r", encoding="utf-8-sig", errors="replace") as handle:

blocks = split_blocks(handle.read())

output_blocks = list(blocks)

translatable = []

for idx, block in enumerate(blocks):

parsed = parse_block(block)

if parsed is not None:

header, text = parsed

translatable.append((idx, header, text))

total = len(translatable)

for start in range(0, total, BATCH_SIZE):

batch = translatable[start:start + BATCH_SIZE]

texts = [item[2] for item in batch]

try:

translations = api_translate(texts, target_lang)

for item, translated in zip(batch, translations):

idx, header, original = item

output_blocks[idx] = "\n".join(header) + "\n" + translated

except Exception as exc:

print(f" [BATCH ERROR]: {exc}", flush=True)

if BATCH_DELAY:

time.sleep(BATCH_DELAY)

temp_file = target_path + ".part"

with open(temp_file, "w", encoding="utf-8", newline="\n") as handle:

handle.write("\n\n".join(output_blocks) + "\n")

os.replace(temp_file, target_path)

print(f" [DONE] Created: {target_path}", flush=True)

def find_sub_variant(base_path, extensions):

for ext in extensions:

candidate = base_path + ext

if os.path.exists(candidate):

return candidate

return None

def process_directory(directory):

print(f"\nScanning folder: {directory}...", flush=True)

for root, dirs, files in os.walk(directory):

base_names = set()

for f in files:

if f.lower().endswith('.srt') and not f.startswith('.'):

clean = re.sub(r'\.(nld|nl|dut|eng|en|th)\.srt$', '', f, flags=re.IGNORECASE)

clean = re.sub(r'\.srt$', '', clean, flags=re.IGNORECASE)

base_names.add(os.path.join(root, clean))

for base in base_names:

has_nl = find_sub_variant(base, NL_EXTS)

has_en = find_sub_variant(base, EN_EXTS)

has_th = find_sub_variant(base, TH_EXTS)

target_th = base + ".th.srt"

target_nl = base + ".nld.srt"

# Case 1: Missing Thai, but Dutch exists -> Translate Dutch to Thai

if not has_th and has_nl:

print(f"\n[MATCH] Dutch present, Thai missing for: {os.path.basename(base)}", flush=True)

try:

translate_srt_file(has_nl, target_th, "th")

except Exception as e:

print(f"Error: {e}", flush=True)

# Case 2: Missing Thai & Dutch, but English exists -> Translate English to both Dutch & Thai

elif not has_th and not has_nl and has_en:

print(f"\n[MATCH] Only English present. Dutch & Thai missing for: {os.path.basename(base)}", flush=True)

# First translate to Dutch

try:

translate_srt_file(has_en, target_nl, "nl")

except Exception as e:

print(f"Error translating to Dutch: {e}", flush=True)

# Then translate to Thai

try:

translate_srt_file(has_en, target_th, "th")

except Exception as e:

print(f"Error translating to Thai: {e}", flush=True)

def main():

print("=== Monthly Subtitle Auto-Sync Started ===", flush=True)

for folder in MEDIA_FOLDERS:

if os.path.exists(folder):

process_directory(folder)

else:

print(f"Folder skipped (does not exist): {folder}", flush=True)

print("\n=== Scan Completed Successfully! ===", flush=True)

if name == "__main__":

main()

EOF

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.