11 KiB
Media
Scripts that maintain the health, cleanliness, and consistency of your media library. Permissions, junk file removal, and orphaned file cleanup across Lidarr, Sonarr, and Radarr.
These scripts are run sequentially by Orchestrators/media_management.sh — not individually scheduled. The orchestrator handles ordering, pass/fail tracking, and the combined summary.
Why Order Matters
1. media_shares_permissions.sh ← permissions first
2. media_cleaner.sh anime ← clean junk before arr scripts scan
3. media_cleaner.sh media ← same
4. lidarr_cleanup.sh ← arr cleanup last
5. sonarr_cleanup.sh
6. radarr_cleanup.sh
Permissions before everything else — arr cleanup scripts need correct ownership to delete files. If a file is owned by root and the script runs as nobody, the delete fails silently.
Cleaner before arr cleanup — junk files (.sfv, .rar, .txt etc.) mixed in with media files create noise in the orphan detection logic. Clean the junk first so arr cleanup only deals with real media files.
Arr cleanup last — depends on clean folders and correct permissions to work reliably.
Scripts
media_shares_permissions.sh
Applies correct ownership and permissions recursively to all configured media shares.
# Called by media_management.sh — not scheduled directly
# Run manually when needed:
/mnt/user/appdata/unraid_scripts/Media/media_shares_permissions.sh
/mnt/user/appdata/unraid_scripts/Media/media_shares_permissions.sh --dry-run
What it applies:
PERMISSIONS_MODE="777" # chmod applied recursively
PERMISSIONS_OWNER="nobody:users" # chown applied recursively
777 and nobody:users is the standard for unRAID media shares accessible by Docker containers. All media server containers (Emby, Tdarr, arr stack) run as nobody:users — this ensures they can read, write, and delete files without permission errors.
Why recursive takes time:
On a large library with millions of files this can run for 20-30 minutes. This is expected and normal. Run it overnight via the orchestrator — not during peak usage hours.
Add or remove shares in Master.conf under MEDIA_PERMISSION_SHARES. The script reads this list at runtime — no script changes needed.
media_cleaner.sh
Removes junk files from media shares using configurable file pattern lists. Two profiles with independent folder lists and patterns.
# Called by media_management.sh with profile argument
# Run manually with profile required:
/mnt/user/appdata/unraid_scripts/Media/media_cleaner.sh anime
/mnt/user/appdata/unraid_scripts/Media/media_cleaner.sh media
/mnt/user/appdata/unraid_scripts/Media/media_cleaner.sh anime --dry-run
/mnt/user/appdata/unraid_scripts/Media/media_cleaner.sh media --dry-run
Always --dry-run first — especially on first use or after adding new patterns.
Anime Profile
ANIME_CLEAN_FOLDERS=(
/mnt/user/Anime_Movies
/mnt/user/Anime_Movies-Old
/mnt/user/Anime_Shows
/mnt/user/Anime_Shows-Old
)
ANIME_FILE_PATTERNS=(
'*.sfv' '*.md5' '*.sha1' '*.txt' '*.url' '*.lnk'
'*.rar' '*.zip' '*.info' '*.torrent' '*.sample*' '*.proof*'
'*sync-conflict*' '*.scr' '*.srr' '*.exe' '*.webp'
'*.log' '*.json'
)
Anime downloads from groups commonly include verification files (.sfv, .md5), RAR archives after extraction, proof files, and samples. These are safe to delete after the video files have been imported.
Media Profile
MEDIA_CLEAN_FOLDERS=(
/mnt/user/Kids_Movies
/mnt/user/Kids_Tv_Shows
/mnt/user/Movies
/mnt/user/Music
/mnt/user/Sports
/mnt/user/stand-up_comedy
/mnt/user/Tv_Shows
)
The media profile includes *.iso and *.lrc in addition to the anime patterns — ISO disc images after ripping and lyric files that aren't needed in the media folders.
Adding New Patterns
Add to the appropriate array in Master.conf — no script changes needed:
ANIME_FILE_PATTERNS=(
'*.sfv' '*.md5' ...
'*.new-pattern' # ← just add here
)
Safety Note
The cleaner deletes by pattern — it does not check what arr thinks about the files. It runs before arr cleanup specifically so arr cleanup sees clean folders. Do not add patterns that match media files you want to keep (.mkv, .mp4 etc.).
lidarr_cleanup.sh
Removes orphaned music files from the library that Lidarr no longer tracks.
# Called by media_management.sh
# Always test first:
/mnt/user/appdata/unraid_scripts/Media/lidarr_cleanup.sh --dry-run --log
/mnt/user/appdata/unraid_scripts/Media/lidarr_cleanup.sh
How it works:
- Queries the Lidarr API for all tracked file paths
- Walks
LIDARR_MUSIC_ROOTon disk - Classifies every file found:
| Classification | Condition | Action |
|---|---|---|
| TRACKED | Lidarr API knows this exact path | Leave alone |
| PROTECTED | Matches LIDARR_PROTECTED_PATTERNS |
Never delete |
| ORPHAN | Music extension, not tracked, older than LIDARR_ORPHAN_AGE days |
Delete |
| JUNK | Not a music extension, not protected | Delete regardless of age |
| RECENT | Not tracked, under LIDARR_ORPHAN_AGE days |
Skip — may be mid-import |
Why protected patterns are critical:
Lidarr generates cover art (*.jpg), metadata (*.nfo), and lyrics (*.lrc) alongside your music files. These do not appear in Lidarr's tracked file API response — they would be classified as orphans and deleted without the protected patterns list. This would break artwork display in Emby and Lidarr itself.
LIDARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.lrc")
Never remove patterns from this list without understanding what Lidarr generates in your specific setup.
Why the age threshold matters:
When Lidarr downloads a file it exists on disk before it's fully processed and imported. The LIDARR_ORPHAN_AGE=7 day window ensures files that are mid-import are never touched. 7 days is conservative — adjust if your import workflow takes longer than expected.
Configuration:
LIDARR_URL="http://192.168.50.2:8686"
LIDARR_API_KEY="your-api-key"
LIDARR_MUSIC_ROOT="/mnt/user/Music-New" # must match Lidarr root path exactly
LIDARR_ORPHAN_AGE=7
LIDARR_EXTENSIONS=("flac" "mp3" "m4a" "wav" "aac" "ogg" "opus" "wma")
LIDARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.lrc")
LIDARR_MUSIC_ROOT must match the root path configured in Lidarr Settings → Media Management → Root Folders exactly. A mismatch means all files appear untracked and everything gets deleted.
sonarr_cleanup.sh
Removes orphaned TV episode files from the library that Sonarr no longer tracks.
# Always test first:
/mnt/user/appdata/unraid_scripts/Media/sonarr_cleanup.sh --dry-run --log
/mnt/user/appdata/unraid_scripts/Media/sonarr_cleanup.sh
Same classification logic as lidarr_cleanup.sh applied to TV files.
Protected patterns cover:
- Show artwork (
*.jpg,*.png) — Sonarr generates per-series and per-episode artwork - Metadata (
*.nfo) — Sonarr generates NFO files for media center compatibility - Subtitles (
*.srt,*.sub,*.ass,*.ssa) — managed by Bazarr via Sonarr
SONARR_URL="http://192.168.50.2:8989"
SONARR_API_KEY="your-api-key"
SONARR_TV_ROOT="/mnt/user/Tv_Shows"
SONARR_ORPHAN_AGE=7
SONARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "ts" "wmv" "mov")
SONARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.srt" "*.sub" "*.ass" "*.ssa")
.ts in extensions: Transport stream files from Live TV recordings. These are tracked by Sonarr for recorded episodes — include this extension to allow cleanup of orphaned recordings.
radarr_cleanup.sh
Removes orphaned movie files from the library that Radarr no longer tracks.
# Always test first:
/mnt/user/appdata/unraid_scripts/Media/radarr_cleanup.sh --dry-run --log
/mnt/user/appdata/unraid_scripts/Media/radarr_cleanup.sh
Same classification logic applied to movie files.
Protected patterns cover:
- Movie artwork (
*.jpg,*.png) - Metadata (
*.nfo) - Subtitles (
*.srt,*.sub,*.ass,*.ssa) — managed by Bazarr
RADARR_URL="http://192.168.50.2:7878"
RADARR_API_KEY="your-api-key"
RADARR_MOVIES_ROOT="/mnt/user/Movies"
RADARR_ORPHAN_AGE=7
RADARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "wmv" "mov")
RADARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.srt" "*.sub" "*.ass" "*.ssa")
Safe Testing Procedure
The arr cleanup scripts permanently delete files. Always test before running live — especially on first use, after API key changes, or after root path changes.
Step 1 — Dry run with logging:
/mnt/user/appdata/unraid_scripts/Media/lidarr_cleanup.sh --dry-run --log
/mnt/user/appdata/unraid_scripts/Media/sonarr_cleanup.sh --dry-run --log
/mnt/user/appdata/unraid_scripts/Media/radarr_cleanup.sh --dry-run --log
--log enables verbose output showing every file classification decision. Review carefully:
- Are TRACKED files the ones you expect?
- Are ORPHAN files actually orphans or recently downloaded files?
- Are PROTECTED files being correctly identified?
- Is the root path correct — no files showing as orphans that shouldn't be?
Step 2 — Check the numbers make sense:
If dry run shows 50,000 files as orphans on a library you know is healthy — something is wrong. Common causes:
- Root path mismatch between Master.conf and arr settings
- API key incorrect — returns empty tracked list
- Arr library scan not complete — recently added files not yet indexed
Step 3 — Run live:
/mnt/user/appdata/unraid_scripts/Media/lidarr_cleanup.sh
Step 4 — Verify in arr UI:
After running, check the arr's library count hasn't dropped unexpectedly. A healthy cleanup removes a small number of genuinely orphaned files — not a significant percentage of your library.
Configuration Quick Reference
All configuration in Master.conf under ── MEDIA ── section.
# Permissions
PERMISSIONS_MODE="777"
PERMISSIONS_OWNER="nobody:users"
MEDIA_PERMISSION_SHARES=(...)
# Cleaner
ANIME_CLEAN_FOLDERS=(...)
MEDIA_CLEAN_FOLDERS=(...)
ANIME_FILE_PATTERNS=(...)
MEDIA_FILE_PATTERNS=(...)
# Orchestrator job order
MEDIA_MAINTENANCE_JOBS=(
"Media/media_shares_permissions.sh"
"Media/media_cleaner.sh anime"
"Media/media_cleaner.sh media"
"Media/lidarr_cleanup.sh"
"Media/sonarr_cleanup.sh"
"Media/radarr_cleanup.sh"
)
# Arr cleanup — per arr
LIDARR_URL / LIDARR_API_KEY / LIDARR_MUSIC_ROOT
LIDARR_ORPHAN_AGE / LIDARR_EXTENSIONS / LIDARR_PROTECTED_PATTERNS
# (same pattern for SONARR_ and RADARR_)
Adding a New Arr
To add Readarr or any other arr cleanup to the ecosystem:
- Copy
radarr_cleanup.shas the template — same classification logic applies - Update the API endpoint, variable names, and root path
- Add configuration variables to
Master.conf - Add the script to
MEDIA_MAINTENANCE_JOBSinMaster.conf - Test with
--dry-run --logbefore running live
The orchestrator picks it up automatically — no changes to media_management.sh needed.