MediaButler

A media library organizer. Point it at a folder of messy downloads and it renames, tags, and files everything into place. Scans video files, pulls clean show and episode metadata. Built for people whose media folder looks like a crime scene.

# MediaButler

Automated media renamer and reorganizer. Takes a folder full of messy torrent dumps, cleans the names locally, runs FileBot to grab episode titles and artwork (and optionally subtitles), then moves everything into a Plex-ready library layout.

Built on MindAttic.Vault for settings (%APPDATA%\MindAttic\MediaButler\settings.json) and credential resolution (User Secrets / environment variables for OpenSubtitles).

What it does #

  1. Self-rename pass. Cleans messy folder names (Better.Call.Saul.S05.Complete.1080p...) into FileBot-friendly stems (Better Call Saul - Season 05). Movies become Title (YYYY). Hoists nested Season N subfolders out of multi-season parent dumps onto the source root and pads season numbers with leading zero. Empty disguised folders (no video underneath) are deleted. Extras / Specials / Bonus folders are left in place and surfaced in the final report.
  2. FileBot rename pass. Renames TV episodes via TheTVDB, renames movies via TheMovieDB, fetches show artwork (fn:artwork.tvdb) and movie artwork (fn:artwork, after writing xattr via rename — works around the artwork.tmdb script bug).
  3. Optional subtitle pass. Calls filebot -get-subtitles when EnableSubtitles is on. Credentials come from the MindAttic Vault chain (User Secrets → env vars); see OpenSubtitles credentials.
  4. Move-to-Plex pass. TV folders become M:\TV\<Show>\Season XX\episodes..., movies become M:\Movies\<Title> (YYYY)\.... Show-level artwork is hoisted from each season folder up to the show root and deduplicated.
  5. Final report. Prints a consolidated summary: items renamed, hoisted, moved, FileBot successes, artwork / subtitle counts, errors, and a Needs manual fix list (Unknown, Extras, and any item that hit a pre-existing target).

Library cleanup: relocate #

mediabutler relocate --source <path> scans an already-organized destination and moves out anything that doesn't belong there:

Items already in the right place are left alone. Combine with --dry-run to preview the eviction list before committing:

mediabutler relocate --dry-run --source "M:\Movies" mediabutler relocate           --source "M:\Movies" 

This is the one stage that intentionally runs against a destination, so the source-vs-destination guard doesn't apply.

Safety #

Configuration #

Settings live at %APPDATA%\MindAttic\MediaButler\settings.json and are managed through the in-app Settings menu. Defaults:

Setting Default
sourcePath M:\Torrents
tvDestination M:\TV
moviesDestination M:\Movies
fileBotPath C:\Program Files\FileBot\filebot.exe
subtitleLanguage en
enableSubtitles false (needs OpenSubtitles login)
dryRun false
excludedFolders temp, .temp, incomplete, complete, _unsorted

OpenSubtitles credentials #

Credentials are never stored in settings.json (which lives unencrypted in roaming app-data). Set them via User Secrets so they live under %APPDATA%\Microsoft\UserSecrets\mindattic-vault-shared\secrets.json:

cd MediaButler dotnet user-secrets set "MindAttic:Vault:Subtitles:OpenSubtitles:user"     ryandebraal dotnet user-secrets set "MindAttic:Vault:Subtitles:OpenSubtitles:password" '***' 

Or as environment variables (CI / containers):

$env:MindAttic__Vault__Subtitles__OpenSubtitles__user     = 'ryandebraal' $env:MindAttic__Vault__Subtitles__OpenSubtitles__password = '***' 

When both values resolve, MediaButler passes them to FileBot per call as --def osdb.user=… osdb.pwd=…. If they're missing the pipeline still runs — FileBot falls back to whatever is configured in its own Preferences and MediaButler reports the auth failure (and which key to set) on a 401.

Why a console app and not PowerShell #

Earlier prototyping happened in PowerShell. Switched to .NET because MediaButler needs MindAttic.Vault for shared credential resolution (OpenSubtitles, plus future cloud storage). The Vault chain (User Secrets → environment variables → providers.json) is the same one every other MindAttic app uses.

Build and run #

dotnet build MediaButler.slnx dotnet run --project MediaButler              # interactive menu dotnet run --project MediaButler -- --dry-run # force dry-run for the session 

Tests #

NUnit test project at MediaButler.Tests/. Coverage:

dotnet test MediaButler.slnx 

Pitfalls MediaButler already defends against #

These came from a manual run on a 50-folder library; the code now handles them automatically: