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.

RichyE

Members
  • Joined

  • Last visited

Everything posted by RichyE

  1. 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. FeaturesDirect 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. PrerequisitesUser Scripts Plugin installed on Unraid. A running LibreTranslate container (or any compatible translation API endpoint). Schedule RecommendationSet 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). CustomizationYou 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. How to change Target LanguagesIf you want to use different target languages (e.g., German, Spanish, French), you need to adjust 3 areas in the script: Define File Extensions (Configuration section): Add/modify the language extensions you want to scan for: DE_EXTS = ['.de.srt', '.ger.srt'] Define Output Filenames (process_folder function): Set the desired output filename format for the new target file: target_de_file = f"{base}.de.srt" Pass the Target Language Code (process_folder function): When calling translate_srt_file(), pass the source file, output file path, and the ISO-639-1 language code (e.g., "de", "es", "fr", "nl", "th"): translate_srt_file(has_en, target_de_file, "de") Bug Fix02-10-2026: small bug fix that gives it more speed as it sometimes didn't finish the previous translattion correctly and thought the next file was part of the previos. also added some better (I hope) info how to change the script. The ScriptGo to Settings \User Scripts in Unraid. Click Add New Script and name it (e.g., Auto_Subtitle_Sync). Click the gear icon next to it and select Edit Script. Paste the following code: #!/bin/bash # Description: Scans media folders for missing target subtitles and translates them automatically using LibreTranslate. # 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 # ====================================================================== API_URL = "http://192.168.1.10:5000/translate" BATCH_SIZE = 25 TIMEOUT = 180 MAX_RETRIES = 3 BATCH_DELAY = 0.2 # Media folders on Unraid 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" ] 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): source_name = os.path.basename(source_path) target_name = os.path.basename(target_path) print(f"\n -> Starting translation: {source_name} -> [{target_lang.upper()}]", flush=True) try: with open(source_path, "r", encoding="utf-8-sig", errors="replace") as handle: raw_content = handle.read() except Exception as e: print(f" [ERROR] Unable to read file: {e}", flush=True) return # Strictly initialize local variables per file blocks = split_blocks(raw_content) output_blocks = list(blocks) translatable_items = [] for idx, block in enumerate(blocks): parsed = parse_block(block) if parsed is not None: header, text = parsed translatable_items.append((idx, header, text)) total = len(translatable_items) if total == 0: print(" [INFO] No translatable subtitle blocks found.", flush=True) return # Process in batches with a fresh counter per file for start in range(0, total, BATCH_SIZE): batch = translatable_items[start:start + BATCH_SIZE] texts = [item[2] for item in batch] current_count = min(start + BATCH_SIZE, total) print(f" Progress [{target_name}]: [{current_count}/{total}] blocks...", flush=True) try: translations = api_translate(texts, target_lang) for item, translated in zip(batch, translations): idx, header, _ = 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) # Write to temp file and rename atomically temp_file = target_path + ".part" try: 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" ✔ [SUCCESS] Created: {target_name}\n", flush=True) except Exception as e: print(f" [SAVE ERROR] Failed to save {target_name}: {e}\n", flush=True) if os.path.exists(temp_file): os.remove(temp_file) def find_existing_sub(base_path, extensions): for ext in extensions: candidate = base_path + ext if os.path.exists(candidate): return candidate return None def process_folder(directory): print(f"\n==================================================", flush=True) print(f" Scanning directory: {directory}", flush=True) print(f"==================================================", flush=True) for root, _, files in os.walk(directory): base_paths = 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_paths.add(os.path.join(root, clean)) for base in sorted(list(base_paths)): has_nl = find_existing_sub(base, NL_EXTS) has_en = find_existing_sub(base, EN_EXTS) has_th = find_existing_sub(base, TH_EXTS) target_th_file = f"{base}.th.srt" target_nl_file = f"{base}.nld.srt" # Case 1: Dutch present, Thai missing -> Translate NL to TH if not has_th and has_nl: print(f"\n[MATCH] Dutch present, Thai missing for: {os.path.basename(base)}", flush=True) translate_srt_file(has_nl, target_th_file, "th") # Case 2: Neither Thai nor Dutch present, but English is -> Translate EN to NL & TH elif not has_th and not has_nl and has_en: print(f"\n[MATCH] Only English present for: {os.path.basename(base)}", flush=True) translate_srt_file(has_en, target_nl_file, "nl") translate_srt_file(has_en, target_th_file, "th") def main(): print("==================================================", flush=True) print(" Automatic Subtitle Sync Started ", flush=True) print("==================================================", flush=True) for folder in MEDIA_FOLDERS: if os.path.exists(folder): process_folder(folder) else: print(f"\n[SKIPPED] Directory does not exist: {folder}", flush=True) print("\n==================================================", flush=True) print(" Scan & Translations Completed Successfully! ", flush=True) print("==================================================", flush=True) if __name__ == "__main__": main() EOF
  2. I wanted to share a Bash script I use for my Unraid setup. I am using a portable power station (Oukitel P1500) as an offline UPS to back up my Unraid server, fiber ONT, ISP router, switch and mini PC's. Since power stations often lack a USB data connection to communicate with Unraid via NUT, this script monitors the power state by continuously pinging an IP address that relies on main grid power (such as your ISP router, a smart plug, or an external IP). Key Features:PID Locking (Prevent Duplicate Execution): Fixes a common issue with User Scripts where background processes stack up after array restarts or GUI triggers, preventing accidental/premature shutdowns. Accurate Failure Timer: Handles temporary network drops gracefully (e.g., during high network loads or Docker updates) without falsely triggering a shutdown. Timezone Fixed Logging: Logs events with exact local time (Europe/Amsterdam by default), making troubleshooting easy alongside Unraid WebGUI logs. SSD-Friendly Logging: Writes logs directly to an NVMe/SSD cache share (e.g., /mnt/user/temp/), keeping your array HDDs spun down and protecting the USB flash drive from wear. WebGUI Notifications: Triggers an official Unraid alert prior to initiating a graceful powerdown. The Script: #!/bin/bash # ============================================================================== # Unraid Ping Based Auto Shutdown Script for Non USB UPS / Power Stations # ============================================================================== # --- CONFIGURATION --- ROUTER_IP="192.168.2.254" # Target IP (Router, Smart Plug on grid, etc.) TIMEOUT_MINUTES=60 # Offline duration before shutdown (in minutes) PING_INTERVAL_SEC=10 # Interval between ping checks (in seconds) # Custom log file path (Recommended: an SSD cache share to prevent HDD spinup) LOG_FILE="/mnt/user/temp/ups_shutdown.log" LOCK_FILE="/var/run/ups_ping_shutdown.pid" # --- CALCULATIONS & VARIABLES --- OFFLINE_LIMIT_SEC=$((TIMEOUT_MINUTES * 60)) ELAPSED_OFFLINE_SEC=0 # Helper function with explicit timezone support for accurate timestamps log_message() { echo "$(TZ='Europe/Amsterdam' date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" } if [ -f "$LOCK_FILE" ]; then PID=$(cat "$LOCK_FILE") if kill -0 "$PID" 2>/dev/null; then log_message "Script is already running (PID: $PID). Terminating this new instance." exit 1 fi fi echo $$ > "$LOCK_FILE" # Clean up lock file on exit trap 'rm -f "$LOCK_FILE"; exit' INT TERM EXIT log_message "UPS Monitor started. Target: $ROUTER_IP (Shutdown set after $TIMEOUT_MINUTES minutes offline)." while true; do # Ping target once with a 2-second timeout if ping -c 1 -W 2 "$ROUTER_IP" &> /dev/null; then # Target responded -> Grid power is UP if [ $ELAPSED_OFFLINE_SEC -gt 0 ]; then log_message "Power/Connection restored! Timer reset from ${ELAPSED_OFFLINE_SEC}s back to 0s." ELAPSED_OFFLINE_SEC=0 fi else # Target unreachable -> Grid power might be DOWN ELAPSED_OFFLINE_SEC=$((ELAPSED_OFFLINE_SEC + PING_INTERVAL_SEC)) MINUTES_OFFLINE=$((ELAPSED_OFFLINE_SEC / 60)) # Log warnings every 60 seconds of downtime to avoid spamming the log if [ $((ELAPSED_OFFLINE_SEC % 60)) -eq 0 ]; then log_message "WARNING: Target ($ROUTER_IP) unreachable for ${MINUTES_OFFLINE} min (${ELAPSED_OFFLINE_SEC}/${OFFLINE_LIMIT_SEC} sec)." fi # Check if timeout threshold has been reached if [ $ELAPSED_OFFLINE_SEC -ge $OFFLINE_LIMIT_SEC ]; then log_message "CRITICAL: Offline limit of $TIMEOUT_MINUTES minutes reached! Initiating clean Unraid shutdown." # Send Unraid WebGUI notification /usr/local/emhttp/webGui/scripts/notify -e "UPS Protection" -s "Power Outage Shutdown" -d "Target $TIMEOUT_MINUTES min unreachable. Shutting down Unraid server." -i "alert" # Remove lock file and trigger graceful shutdown rm -f "$LOCK_FILE" powerdown exit 0 fi fi sleep $PING_INTERVAL_SEC done How to install:Install the User Scripts plugin from Community Applications. Add a new script named UPS_Ping_Shutdown. Paste the code above and adjust ROUTER_IP, TIMEOUT_MINUTES, and your timezone if needed. Set the schedule dropdown to At Startup of Array. Click Apply. Hope this helps anyone running power stations or off grid UPS units without USB monitoring! Feel free to ask if you have any questions or improvements.

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.