📺 Media Automation Stack
1,737 movies and 164 shows, each added with about thirty seconds of work.
Automated movie and TV pipeline, grab to playback · Radarr · Sonarr · rclone · Plex · Jellyfin
Why I built it
I keep my own movie and TV library instead of renting access to someone else's. The catch is upkeep. Every new movie used to mean downloading a file, moving it to the server, renaming it so the media server would recognize it, filing it in the right folder, and kicking off a library scan. That's fine for ten movies. It stops being fine long before 1,737 of them, and TV makes it worse because one show can be hundreds of episodes across a dozen seasons.
The goal was the same one behind my ebook pipeline: shrink the manual work down to the one decision worth making by hand. I pick what enters the library. The downloading, transferring, naming, sorting, and serving all happen without me.
What it is
The pipeline runs across two machines: a rented seedbox that does all the torrenting, and my home server, which stores and serves the library.
It starts with a grab. I find a release on a private tracker and add the torrent to Deluge on the seedbox. My part takes about thirty seconds, and it ends there. A label goes on at add time, and Deluge routes the completed download into a movies folder or a TV folder on its own.
Radarr and Sonarr run on the seedbox too, one managing the movie collection and one managing TV. They search through Prowlarr, my indexer manager and the single place tracker credentials live, and they handle the part of library upkeep I hated most, the naming. Files come out matching the conventions media servers expect, down to season folders and episode numbering. I never rename anything.
From there my home server takes over. Two rclone scripts run on a schedule through Unraid's User Scripts plugin, movies every thirty minutes and TV every forty-five. Each one copies anything new from the seedbox down to the media share on the array. They copy instead of moving, so the seedbox keeps its files and the torrents keep seeding.
Once files land on the array, everything downstream reads from that one share. Plex and Jellyfin both serve it. Tautulli sits next to Plex, tracking play history and stream quality. Tunarr turns pieces of the library into live TV channels, which gets its own section below.
There's also a lazier entry point than the tracker itself. Seerr puts a Netflix-style browse and request screen in front of the whole thing. Hit request on a movie or show, and it flows to Radarr or Sonarr, which take it from there. It works fine from a phone, which is where I mostly use it.
The library this feeds currently sits at 1,737 movies and 164 shows, 6,262 episodes in all.
Why both Plex and Jellyfin
Running two media servers on one library looks like indecision, and for a while it was. Jellyfin went in as an evaluation against Plex, with the assumption that one of the two would eventually go.
The evaluation refused to produce a loser. Jellyfin is faster and snappier in daily use, and its web UI makes Plex feel bloated by comparison. Plex wins on Skip Intro, where its native feature is more reliable than Jellyfin's audio fingerprinting plugin, and its mobile apps are more polished. Six weeks in, neither gap looked big enough to give up the other side's strengths, so I stopped treating it as a competition and made both permanent.
Keeping both costs almost nothing, which made the call easy. The media exists once on disk, both servers read the same share, and the second front end amounts to one more container and a config folder. Anything Radarr or Sonarr delivers shows up in both after a scan. Jellyfin also does work beyond playback: it's the media source Tunarr builds channels from, and it handles hardware transcoding through the server's integrated GPU.
Live TV from the library
Channels are the one part of cable worth missing, and Tunarr rebuilds them. It schedules shows from the library into virtual live TV channels and presents itself to Plex as an HDHomeRun tuner, so the channels show up in Plex's live TV guide the way broadcast ones would. Four channels run right now: The Simpsons, The Office, Rick and Morty, and Forged in Fire.
The appeal is ambient TV. Sometimes nobody wants to pick an episode. They want The Office to just be on, somewhere in the middle of season four, the way it used to be. Tunarr pulls its media through Jellyfin rather than Plex, which is part of why Jellyfin holds a permanent slot.
The hiccups
The sync scripts ran clean while copying nothing. At some point the seedbox provider migrated my account to a different server, and the rclone config on my end kept the old hostname. The scripts kept firing on schedule and exiting with zero errors while transferring nothing at all. I caught it when grabs stopped showing up in the library. One hostname update fixed it, but the scripts had been reporting success the whole time, and that bothered me more than the outage did.
Deluge learned to play dead. The daemon on the seedbox would occasionally die and leave zombie processes behind, and the watchdog that was supposed to catch this used pgrep, which counts a defunct process as a running one. So Deluge could be down while every check swore it was up. The fix was a smarter restart script that verifies the daemon actually responds instead of merely existing in the process table.
Radarr was doubling my storage. Its import step left me with two full copies of each movie, the seeding original and the renamed library file. Hardlinks fixed it. A hardlink is one file with two names, so the torrent keeps seeding under its original name while the library sees a clean renamed one, and the data sits on disk once.
Plex browsing got slow. The library started to drag, with covers and metadata loading late. Plex keeps its metadata as an enormous pile of tiny files, and on Unraid that pile was living behind the user share layer, which adds overhead to every file operation. Pointing the config volume straight at the cache pool cut the share layer out of the path and brought browsing back to normal.
What I'd do differently
I'd build verification into the sync scripts from day one. The stale hostname failure was only possible because a clean exit code was my entire definition of success. A few extra lines, checking whether anything transferred and when the last file landed, would have caught the dead connection on the first run. Instead the library sat quietly frozen until I noticed. The habit I took away applies to every scheduled job I run now: a script that reports success should be reporting an outcome, not an exit code.