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] Batch rename .gre.srt subtitle files to .ell.srt

Featured Replies

What it does

This script recursively scans your Media library and renames subtitle files from the deprecated gre ISO 639-2 language code to the correct ell code for Modern Greek. Most modern media players and tools (Jellyfin, Plex, Kodi, etc.) now expect ell and may not recognise gre.

Example:

Movie.2020.gre.srt  →  Movie.2020.ell.srt

The script is designed to run safely on an Unraid server via the User Scripts plugin, with low I/O and CPU priority so it does not disturb normal operations.

*Feel free to adapt for other language code migrations (e.g. hebiw, iwhe, etc.)


Requirements

  • Unraid with the User Scripts plugin installed (Community Applications)

  • Media stored under a consistent path (e.g. /mnt/user/Media/ or /mnt/user0/Media/)


Setup

  1. Open Settings → User Scripts → Add New Script

  2. Paste the full script into the editor

  3. Edit the variables in the USER CONFIGURATION section at the top (see below)

  4. Save and optionally set a schedule (e.g. monthly)

  5. Run once with --dry-run first to verify what would be renamed


⚙️ User Configuration — Variables to customise

These are the only variables you should need to change. Everything else can be left as-is.


ROOTS — Your media paths

ROOTS=(
  "$BASE/Media/Movies"
  "$BASE/Media/TV Shows"
)

Change these to match your actual folder names. Add or remove lines as needed.

# Examples:
ROOTS=(
  "$BASE/Media/Films"
  "$BASE/Media/Series"
  "$BASE/Media/Documentaries"
)

$BASE is automatically set to /mnt/user or /mnt/user0 depending on your array layout — you typically don't need to change it. If your media is on a specific share or disk, you can override it manually:

BASE="/mnt/user"

LOG — Log file location

LOG="/mnt/user/appdata/srt_rename_gre_to_ell.log"

Change this if you prefer logs in a different location, e.g. inside a dedicated appdata subfolder.


LOG_MAX_LINES — Log rotation size

LOG_MAX_LINES=5000

The log file is trimmed to this many lines at the start of each run. Increase if you have a very large library and want more history, or decrease to keep things lean.


HEARTBEAT_SECS — Progress logging interval

HEARTBEAT_SECS=60

How often (in seconds) a progress line is written to the log while scanning. Useful for monitoring long runs. Set to 300 for less noise, or 15 for very frequent updates.


🚀 Run flags (optional)

You can pass these as Script Arguments in the User Scripts UI, or append them to the script call:

Flag

Description

--dry-run

Preview only — no files are renamed. Always run this first!

--force

Rename even if a .ell.srt file already exists at the destination

--inc

Incremental mode — only scan files newer than the last successful run

--full

Full scan (default) — scan all files every time

--wake

Wake all array disks before scanning (default: enabled)

Recommended first run: Set Script Arguments to --dry-run and check the log at the path defined in LOG.


📋 What gets logged

Each run produces entries like:

[2025-03-08 14:00:01] ---- RUN START ----
[2025-03-08 14:00:01] Mode: FULL=1 | DRY_RUN=0 | FORCE=0 | Wake=1 | Heartbeat=60s
[2025-03-08 14:01:03] Renamed: /mnt/user/Media/Movies/Foo (2020)/Foo.gre.srt -> Foo.ell.srt
[2025-03-08 14:02:00] HEARTBEAT | elapsed=00:02:00 | found=145 renamed=12 skipped=3 errors=0
[2025-03-08 14:05:22] === DONE ===
[2025-03-08 14:05:22] TOTAL: Found=892 | Renamed=47 | Skipped=5 | Errors=0 | Duration=00:05:21

📝 Full Script

#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

renice -n 15 -p $$ >/dev/null 2>&1 || true
ionice -c2 -n7 -p $$ >/dev/null 2>&1 || true

echo "=== STARTING SRT RENAME: .gre.srt -> .ell.srt ==="

# ============================================================
# USER CONFIGURATION — edit these to match your setup
# ============================================================

BASE="/mnt/user"
[[ -d "/mnt/user0/Media" ]] && BASE="/mnt/user0"

ROOTS=(
  "$BASE/Media/Movies"
  "$BASE/Media/TV Shows"
)

LOG="/mnt/user/appdata/srt_rename_gre_to_ell.log"
STATE="/mnt/user/appdata/srt_rename_gre_to_ell.last_run"
SUCCESS_MARK="/mnt/user/appdata/srt_rename_gre_to_ell.last_success"

LOG_MAX_LINES=5000
HEARTBEAT_SECS=60

# ============================================================
# END OF USER CONFIGURATION
# ============================================================

DRY_RUN=0
FORCE=0
FULL=1
WAKE_DISKS=1

while [[ $# -gt 0 ]]; do
  arg="${1//$'\r'/}"
  case "$arg" in
    "") shift ;;
    --dry-run) DRY_RUN=1; shift ;;
    --force)   FORCE=1; shift ;;
    --full)    FULL=1; shift ;;
    --inc)     FULL=0; shift ;;
    --wake)    WAKE_DISKS=1; shift ;;
    *) echo "Unknown option: [$(printf '%q' "$arg")]"; exit 1 ;;
  esac
done

mkdir -p "$(dirname "$LOG")"

if [[ -f "$LOG" ]]; then
  tmp_log="$(mktemp)"
  tail -n "$LOG_MAX_LINES" "$LOG" > "$tmp_log" && mv "$tmp_log" "$LOG"
fi

fmt_secs() {
  local s="$1"
  printf "%02d:%02d:%02d" $((s/3600)) $(((s%3600)/60)) $((s%60))
}

log() {
  local msg="$1"
  local ts
  ts="$(date '+%F %T')"
  echo "[$ts] $msg" | tee -a "$LOG"
  logger -t srt-rename "$msg" 2>/dev/null || true
}

missing=0
log "Using BASE: $BASE"
for r in "${ROOTS[@]}"; do
  if [[ ! -d "$r" ]]; then
    log "ERROR: root not found: $r"
    missing=1
  else
    log "OK: root exists: $r"
  fi
done
if [[ $missing -eq 1 ]]; then
  log "ABORT: Fix ROOTS and rerun."
  exit 1
fi

START_TS="$(date +%s)"
CURRENT_ROOT="(none)"
LAST_FILE="(none)"
LAST_BEAT_TS="$START_TS"

found=0 renamed=0 skipped=0 errors=0

beat() {
  local now elapsed
  now="$(date +%s)"
  elapsed=$((now - START_TS))
  log "HEARTBEAT | elapsed=$(fmt_secs "$elapsed") | root=\"$CURRENT_ROOT\" | found=$found renamed=$renamed skipped=$skipped errors=$errors | last=\"$LAST_FILE\""
  LAST_BEAT_TS="$now"
}

wake_all_disks() {
  log "Waking ALL array disks (/mnt/disk*) ..."
  shopt -s nullglob
  for d in /mnt/disk*; do
    [[ -d "$d" ]] || continue
    ls -1 "$d" >/dev/null 2>&1 || true
  done
  shopt -u nullglob
  log "Disk wake pass done."
}

process_one() {
  local file="$1"
  found=$(( found + 1 ))
  LAST_FILE="$file"

  local new_file
  new_file="$(printf '%s' "$file" | sed -E 's/\.gre\.srt$/.ell.srt/')"

  if [[ "$new_file" == "$file" ]]; then
    skipped=$(( skipped + 1 ))
    return
  fi

  if [[ -e "$new_file" && $FORCE -eq 0 ]]; then
    skipped=$(( skipped + 1 ))
    log "SKIP (target exists): $file -> $new_file"
    return
  fi

  if [[ $DRY_RUN -eq 1 ]]; then
    renamed=$(( renamed + 1 ))
    log "[DRY-RUN] Would rename: $file -> $new_file"
    return
  fi

  if mv -- "$file" "$new_file"; then
    renamed=$(( renamed + 1 ))
    log "Renamed: $file -> $new_file"
  else
    errors=$(( errors + 1 ))
    log "ERROR renaming: $file"
  fi
}

log "---- RUN START ----"
log "Roots: ${ROOTS[*]}"
log "Mode: FULL=$FULL | DRY_RUN=$DRY_RUN | FORCE=$FORCE | Wake=$WAKE_DISKS | Heartbeat=${HEARTBEAT_SECS}s"

if [[ $WAKE_DISKS -eq 1 ]]; then
  wake_all_disks
fi

if [[ $FULL -eq 0 ]]; then
  if [[ -f "$STATE" ]]; then
    log "Incremental since: $(date -r "$STATE")"
  else
    log "Incremental since: (none) -> first run will behave like full for matches"
  fi
else
  log "FULL scan mode"
fi

beat

for root in "${ROOTS[@]}"; do
  CURRENT_ROOT="$root"
  log "Scanning: $root"

  if [[ $FULL -eq 0 && -f "$STATE" ]]; then
    while IFS= read -r -d '' file; do
      now="$(date +%s)"
      if (( now - LAST_BEAT_TS >= HEARTBEAT_SECS )); then
        beat
      fi
      process_one "$file"
    done < <(find "$root" \( -type d -name ".Recycle.Bin" -prune \) -o \
      \( -type f -iname "*.gre.srt" -newer "$STATE" -print0 \))
  else
    while IFS= read -r -d '' file; do
      now="$(date +%s)"
      if (( now - LAST_BEAT_TS >= HEARTBEAT_SECS )); then
        beat
      fi
      process_one "$file"
    done < <(find "$root" \( -type d -name ".Recycle.Bin" -prune \) -o \
      \( -type f -iname "*.gre.srt" -print0 \))
  fi

  beat
  log "Finished root: $root"
done

END_TS="$(date +%s)"
TOTAL_SECS=$((END_TS - START_TS))

log "=== DONE ==="
log "TOTAL: Found=$found | Renamed=$renamed | Skipped=$skipped | Errors=$errors | Duration=$(fmt_secs "$TOTAL_SECS")"

if [[ $DRY_RUN -eq 0 && $errors -eq 0 ]]; then
  touch "$STATE"
  touch "$SUCCESS_MARK"
  log "State updated: $STATE"
  log "Success marker updated: $SUCCESS_MARK"
else
  log "State NOT updated (dry-run or errors present)"
fi

exit 0

Notes

  • The script skips .Recycle.Bin folders automatically

  • If a .ell.srt file already exists at the destination, the original is skipped (use --force to override)

  • State files are only updated if the run completes with zero errors

  • Logs go to both the file and Unraid's syslog (logger)


Tested on Unraid 7.2.+

📁 GitHub Repository: Link

script.png

Edited by Lazaros Chalkidis

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.