Turn your Plex library into a TV channel — episodes cycle through your shows in order with commercial breaks in between.
Features · Quick Start · Portable App · Web UI · CLI · How It Works
Pick the shows you want, add some commercials if you like, and run rtv generate. You get a Plex playlist that plays like a cable channel — an episode of Seinfeld, a vintage commercial, an episode of The Office, another commercial, and so on. Each show picks up where it left off, so the next time you generate you get the next episodes in order.
- Four ways to use it — portable desktop app, browser-based Web UI, full-screen terminal TUI, or the CLI
- Portable desktop app — standalone executable for Windows, macOS, and Linux with no Python installation required
- Automatic episode ordering — cycles through your shows oldest-to-newest, one episode at a time, picking up where you left off
- Commercial breaks — one random clip per break, or blocks that fill a time window like real TV. Configurable no-repeat window so you don't see the same one twice
- Multiple playlists — create "Real TV", "90s Night", "Anime Block", each with their own shows and positions
- Fuzzy matching — type
rtv add-show "the office"and it finds "The Office (US)" in your Plex library - Commercial library builder — search and download clips with built-in yt-dlp integration, or just drop MP4s in a folder
- Remote server support — SSH/SFTP for managing commercials on a separate Plex server
- Works over SSH — the terminal TUI runs on headless servers
- Python 3.11 or newer — check with
python --version(orpython3 --versionon macOS/Linux) - Plex Media Server running and accessible on your network
- Your Plex token — how to find it
# Download the project
git clone https://github.com/travisjneuman/plex-real-tv.git
cd plex-real-tv
# Install
pip install .
# Set up your Plex connection
rtv init
# Add some shows
rtv add-show "Seinfeld"
rtv add-show "The Office"
# Generate your playlist
rtv generateOpen Plex, go to Playlists in the sidebar, find "Real TV", and hit play.
Tip
Commercials are optional. The Quick Start above creates a playlist with just episodes. To add commercial breaks, see Commercial Library Setup below.
Note
Don't have git? You can also download the ZIP, extract it, and run pip install . from inside the folder.
For developers — editable install
If you plan to modify the source code, install in editable mode:
pip install -e ".[dev]"This also installs test dependencies (pytest, httpx, etc.).
cd plex-real-tv
git pull
pip install .Download a standalone executable — no Python installation required. The portable app bundles everything needed and works fully offline.
Get the latest release from GitHub Releases:
| Platform | File | Size |
|---|---|---|
| Windows | RealTV-Windows.zip |
~30 MB |
| macOS | RealTV-macOS.zip |
~60 MB |
| Linux | RealTV-Linux.tar.gz |
~50 MB |
- Download the archive for your platform
- Extract it anywhere
- Run the executable:
- Windows: Double-click
RealTV.exe - macOS: Open
RealTV.app(may need to right-click → Open on first run) - Linux: Run
./RealTVfrom terminal
- Windows: Double-click
- Fully offline — all fonts, styling, and JavaScript bundled locally
- No installation — just download and run
- Same UI as Web — identical interface and functionality
- Auto-discovery — finds Plex servers on your network automatically
The portable app stores configuration in your system's AppData folder:
| Platform | Config Location |
|---|---|
| Windows | %APPDATA%\RealTV\config.yaml |
| macOS | ~/Library/Application Support/RealTV/config.yaml |
| Linux | ~/.config/rtv/config.yaml |
Tip
To transfer your settings to another computer, copy the config.yaml file to the same location on the new machine.
- Commercial search/download requires internet (yt-dlp)
- First run on macOS may require: System Settings → Privacy & Security → "Open Anyway"
rtv web # Launch on port 8080
rtv web --port 3000 # Custom port
rtv web --no-open # Don't auto-open browserAccessible from any device on your network at http://<your-ip>:8080.
Note
The Web UI is served from your machine, but your browser needs internet access to load styling and interactivity from CDNs: Tailwind CSS, htmx, and Google Fonts.
For fully offline use, download the Portable Desktop App which bundles all assets locally.
rtv add-show "the office" # Fuzzy match against Plex
rtv add-show "Dragon Ball Z" --library "Anime"
rtv list-shows # Pool with status + membership
rtv remove-show "Seinfeld"
rtv enable-show "The Office (US)"
rtv disable-show "Friends"rtv create-playlist "Late Night"
rtv playlist-add "Late Night" "Seinfeld"
rtv playlist-remove "Late Night" "Friends"
rtv list-playlists
rtv delete-playlist "Late Night"
rtv set-default "90s Night"rtv generate # Default playlist
rtv generate "90s Night" # Specific playlist
rtv generate -e 50 # Custom episode count
rtv generate --from-start # Reset all shows to S01E01
rtv generate --rescan # Rescan Plex library first
rtv generate --export # Auto-export to CSV after generatingrtv find-commercials -c "80s" # Search YouTube
rtv download-commercials URL -c "90s" # Download by URL
rtv download-commercials --from-search -c "80s" # Download from last search
rtv add-category "PSAs" -s "vintage PSA" -w 0.5
rtv list-commercialsrtv doctor # Check everything
rtv status # Plex connection + inventory
rtv preview -e 10 # Dry-run preview (no changes)
rtv history # Last 5 generations
rtv export # Export playlist to CSV
rtv export --format json -n "90s Night"rtv tuiFull-screen terminal interface. Works over SSH for headless servers.
| Key | Screen |
|---|---|
| d | Dashboard — Plex status, stats, last generation |
| s | Shows — searchable table, toggle enabled/disabled |
| p | Playlists — create, edit, generate, set default |
| q | Quit |
When you run rtv generate, the tool builds a Plex playlist by cycling through your shows one episode at a time:
Episode 1: Seinfeld S03E12
[Commercial: random 80s clip]
Episode 2: The Office S01E01
[Commercial: different clip — no repeats]
Episode 3: Friends S05E08
[Commercial: another unique clip]
Episode 4: Seinfeld S03E13
...
Each show keeps its own position (season + episode) per playlist, so "Real TV" can be at Seinfeld S05E03 while "90s Night" is still at S01E01. Positions are saved after each generation. If a show runs out of episodes, it drops out and the rest continue.
Each playlist can use a different commercial break style:
| Style | What it does |
|---|---|
single |
One random commercial between episodes (default) |
block |
Multiple commercials filling a time window — like a real TV break |
disabled |
No commercials at all |
Architecture
Three interfaces share one core engine:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Web UI │ │ Terminal TUI│ │ CLI │
│ FastAPI + │ │ Textual │ │ Click │
│ htmx/Jinja2 │ │ │ │ │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────────────────┼────────────────┘
│
┌─────────┴─────────┐
│ Core Layer │
│ config.py │
│ playlist.py │
│ plex_client.py │
│ commercial.py │
│ matcher.py │
└───────────────────┘
rtv init walks you through setup interactively. All settings are stored in config.yaml.
Note
RTV looks for config.yaml in your current directory first, then in your system's AppData folder. For the portable desktop app, config is stored in:
- Windows:
%APPDATA%\RealTV\config.yaml - macOS:
~/Library/Application Support/RealTV/config.yaml - Linux:
~/.config/rtv/config.yaml
Full config reference
config_version: 2
plex:
url: "http://192.168.1.100:32400"
token: "your-plex-token-here"
tv_libraries:
- "TV Shows"
tv_show_paths:
- "D:\\TV Shows"
- "E:\\TV Shows"
# Global show pool
shows:
- name: "Seinfeld"
library: "TV Shows"
year: 1989
enabled: true
- name: "The Office (US)"
library: "TV Shows"
year: 2005
enabled: true
# Named playlists with independent settings and positions
playlists:
- name: "Real TV"
shows:
- { name: "Seinfeld", current_season: 3, current_episode: 7 }
- { name: "The Office (US)", current_season: 1, current_episode: 1 }
breaks:
enabled: true
style: single # single | block | disabled
frequency: 1 # break every N episodes
min_gap: 50 # no-repeat window for commercials
block_duration: { min: 30, max: 120 }
episodes_per_generation: 30
sort_by: premiere_year # premiere_year | premiere_year_desc | alphabetical
default_playlist: "Real TV"
# Optional SSH for remote Plex servers
ssh:
enabled: false
host: ""
port: 22
username: ""
key_path: ""
remote_commercial_path: ""
commercials:
library_name: "RealTV Commercials"
library_path: "D:\\Media\\Commercials"
block_duration: { min: 120, max: 300 }
categories: []
history: []Existing v1 configs auto-migrate on first load (backs up to config.yaml.v1.bak).
Commercials are optional but fun. You need two things: MP4 files in a folder, and a Plex library pointing to that folder.
Organize by decade, theme, or however you like — each subfolder becomes a category:
D:\Media\Commercials\
├── 80s/
│ ├── Coca Cola 1985.mp4
│ └── Nintendo NES Commercial.mp4
├── 90s/
│ └── Got Milk 1993.mp4
└── PSAs/
└── This Is Your Brain On Drugs.mp4
- Plex → Settings → Libraries → Add Library
- Type: Movies (not TV Shows — commercials are single files)
- Name it
RealTV Commercials(must match your config) - Point it to your commercial folder
- Let Plex scan the library
Tip
Hide the commercial library from your Plex home screen: Settings → Libraries → RealTV Commercials → gear icon → uncheck "Include in dashboard".
rtv find-commercials -c "80s" # Search YouTube
rtv download-commercials --from-search -c "80s" # Download resultsOr just drop any MP4 files into your commercial folder and let Plex scan.
Important
You're responsible for ensuring your use of yt-dlp and any downloaded content complies with applicable terms of service and copyright law. RTV does not include or distribute any media files.
Multi-drive setup — TV shows spread across multiple drives
plex:
tv_show_paths:
- "D:\\TV Shows"
- "E:\\TV Shows"
- "F:\\TV Shows"
- "K:\\TV Shows"rtv add-show searches all configured libraries automatically.
Remote server (SSH) — Plex on a different machine
Configure via the Web UI Setup page, or in config.yaml:
ssh:
enabled: true
host: "192.168.1.10"
port: 22
username: "admin"
key_path: "~/.ssh/id_rsa"
remote_commercial_path: "F:\\Commercials"SSH enables remote commercial directory scanning, SFTP file uploads, and remote command execution.
Dependencies — what gets installed
All installed automatically via pip install .:
| Category | Package | Purpose |
|---|---|---|
| Core | click |
CLI framework |
PlexAPI |
Plex server communication | |
yt-dlp |
Commercial search & download | |
pyyaml |
Config parsing | |
pydantic |
Config validation | |
rapidfuzz |
Fuzzy show matching | |
rich |
Terminal formatting | |
| Web UI | fastapi |
Web framework |
uvicorn |
ASGI server | |
jinja2 |
HTML templates | |
sse-starlette |
Live progress events | |
| TUI | textual |
Terminal UI framework |
| Remote | paramiko |
SSH/SFTP |
The Web UI also loads from CDN (browser-side, not installed): Tailwind CSS, htmx, htmx SSE extension, and Google Fonts (Dela Gothic One + JetBrains Mono).
"Could not connect to Plex"
- Is Plex Media Server running?
- Is the URL correct? Try
http://localhost:32400if Plex is on the same machine, orhttp://<server-ip>:32400for a remote server - Is the token valid? Tokens can expire — get a new one
- Firewall blocking port 32400?
- Run
rtv doctorfor a full diagnostic
"No match found" when adding a show
- Try the exact name as it appears in Plex
- Check which libraries are configured:
rtv status - Use
--libraryto search a specific library:rtv add-show "Show" --library "Anime"
"No commercials found"
- Have you downloaded any?
rtv find-commercials -c "80s" - Is the commercial path correct? Check with
rtv doctor - Did you create the Movies library in Plex and let it scan?
- Commercial files must be
.mp4format
"rtv: command not found" after installing
This means Python's script directory is not in your system PATH.
Windows:
python -m rtv.cli --help
Or add Python's Scripts folder to PATH: search "Environment Variables" in Windows settings, edit Path, add %APPDATA%\Python\Python3XX\Scripts (replace XX with your Python version).
macOS / Linux:
python3 -m rtv.cli --helpOr add ~/.local/bin to your PATH:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrcrtv doctor checks everything: Plex connection, libraries, shows, commercial path, yt-dlp availability.
| Command | Description |
|---|---|
rtv init |
Create config interactively |
rtv status |
Test Plex connection, show inventory |
rtv doctor |
Full diagnostic check |
| Shows | |
rtv add-show NAME |
Add show to pool (fuzzy matched) |
rtv remove-show NAME |
Remove show |
rtv list-shows |
List pool with status |
rtv enable-show NAME |
Enable a show |
rtv disable-show NAME |
Disable a show |
| Playlists | |
rtv create-playlist NAME |
Create playlist |
rtv delete-playlist NAME |
Delete playlist |
rtv list-playlists |
List all playlists |
rtv playlist-add PLAYLIST SHOW |
Add show at S01E01 |
rtv playlist-remove PLAYLIST SHOW |
Remove show |
rtv set-default NAME |
Set default playlist |
| Generation | |
rtv generate [NAME] |
Generate Plex playlist |
rtv generate --from-start |
Reset to S01E01 |
rtv generate --rescan |
Rescan Plex first |
rtv generate --export |
Export after generating |
rtv preview [NAME] |
Dry-run preview |
rtv export |
Export to CSV/JSON |
rtv history |
Last 5 generations |
| Commercials | |
rtv find-commercials -c CAT |
Search for commercials |
rtv download-commercials URL |
Download by URL |
rtv add-category NAME |
Add category |
rtv list-commercials |
Show inventory |
| Interfaces | |
rtv web |
Launch Web UI |
rtv tui |
Launch Terminal TUI |
rtv-desktop |
Launch desktop app (requires [desktop] install) |
This tool generates playlists from media you already own in your Plex library. It does not distribute, stream, or share any media content. Commercial clips are sourced and stored locally by the user — RTV does not include or distribute any media files.





