ErsatzTV¶
Custom IPTV channel server that creates "live TV" experiences from your media library with EPG support.
See also: - Channel Creation Guide - Step-by-step guide to creating channels with Toonami bumps - Infrastructure as Code - YAML-based scheduling, collections, and day parting
Quick Reference¶
| Item | Value |
|---|---|
| UI | https://ersatztv.bogocat.com |
| Direct | http://10.89.97.50:8409 |
| M3U Playlist | http://10.89.97.50:8409/iptv/channels.m3u |
| EPG (XMLTV) | http://10.89.97.50:8409/iptv/xmltv.xml |
| Auth | Authentik forward-auth (arr-admins group) |
| Location | arr-stack VM (VM 100) |
| Version | 25.9.0 |
Architecture¶
User → ersatztv.bogocat.com → K8s Ingress → Authentik Forward Auth
↓ (authenticated)
arr-stack VM
(10.89.97.50:8409)
↓
Jellyfin (10.89.97.97:8096)
↓
NFS Media (/mnt/media)
Media Sources¶
Connected via Jellyfin¶
- TV Shows, Movies, Music from Jellyfin libraries
- Metadata and artwork pulled automatically
Local Paths (in container)¶
| Path | Content |
|---|---|
/media/tv |
TV Shows |
/media/movies |
Movies |
/media/toonami/Toonami_Bumps |
1,143 Toonami/Adult Swim bumps |
Toonami Bumps Integration¶
Bump Collection¶
- Source: bumpworthy.com
- Count: 1,143 clips
- Types: Show intros, "Next" bumps, "To Ads", "Back From Ads", Adult Swim interstitials
Shows with Matching Bumps (READY)¶
| Bumps | Show | Library Match |
|---|---|---|
| 126 | Inuyasha | InuYasha |
| 113 | FMA/FMAB | Fullmetal Alchemist - Brotherhood |
| 113 | Cowboy Bebop | Cowboy Bebop |
| 87 | Naruto | Naruto Shippuden |
| 56 | Bleach | Bleach |
| 45 | One Piece | One Pace |
| 41 | Soul Eater | Soul Eater |
| 30 | Space Dandy | Space Dandy |
| 29 | Evangelion | Neon Genesis Evangelion |
| 28 | Samurai Jack | Samurai Jack |
| 25 | Boondocks | The Boondocks |
| 23 | Trigun | Trigun |
| 23 | Blue Exorcist | Blue Exorcist |
| 20 | Sword Art | Sword Art Online |
| 20 | Black Lagoon | Black Lagoon |
| 18 | FLCL | FLCL |
| 7 | Attack on Titan | Attack on Titan |
| 5 | King of the Hill | King of the Hill |
| 4 | Samurai Champloo | Samurai Champloo |
Shows with Bumps (Missing from Library)¶
| Bumps | Show | Notes |
|---|---|---|
| 54 | GITS | Ghost in the Shell: SAC |
| 48 | IGPX | Racing anime |
| 44 | Clone Wars | Star Wars: Clone Wars |
| 37 | Eureka | Eureka Seven |
| 30 | Thundercats | 2011 reboot |
| 27 | Tenchi | Tenchi Muyo |
| 18 | Big O | Giant robot noir |
| 18 | Akira | Movie (check movies folder) |
Organized Bump Folders¶
Bumps are organized by show at /media/toonami/organized/:
| Folder | Count | Use For |
|---|---|---|
| cowboy-bebop | 113 | Cowboy Bebop filler |
| naruto | 87 | Naruto/Shippuden filler |
| inuyasha | 126 | Inuyasha filler |
| fma | 112 | FMA Brotherhood filler |
| bleach | 56 | Bleach filler |
| one-piece | 45 | One Pace filler |
| soul-eater | 41 | Soul Eater filler |
| space-dandy | 30 | Space Dandy filler |
| evangelion | 29 | Evangelion filler |
| samurai-jack | 28 | Samurai Jack filler |
| adult-swim | 197 | General AS interstitials |
| generic | 6408 | Generic Toonami bumps |
Adding Bumps to ErsatzTV¶
- Add Local Media Sources (one per show folder):
- Media Sources → Add Local
- Path:
/media/toonami/organized/cowboy-bebop - Name:
Cowboy Bebop Bumps - Type: Other Videos
-
Repeat for each show folder you want
-
Create Collections:
- Lists → Collections → Add
-
Add items from each bump library
-
Create Filler Presets:
- Lists → Filler Presets → Add
- Select collection, choose type (pre-roll, post-roll, tail)
-
Configure mode (count, duration, or pad)
-
Use in Schedules:
- When adding schedule items, attach filler presets
- Pre-roll plays before episodes, post-roll after
Sample Channel: Toonami Saturdays¶
8:00 PM Cowboy Bebop (shuffle) + Cowboy Bebop bumps
8:30 PM Bleach (sequential) + Bleach bumps
9:00 PM Naruto Shippuden (seq) + Naruto bumps
9:30 PM Attack on Titan (seq) + Generic bumps
10:00 PM Fullmetal Alchemist (seq) + FMA bumps
Configuration¶
GPU Transcoding (Enabled)¶
GPU passthrough configured with NVIDIA Quadro M2000 (4GB VRAM).
| Setting | Value |
|---|---|
| GPU | Quadro M2000 |
| VRAM | 4GB |
| Driver | 535.247.01 |
| CUDA | 12.2 |
| Encoders | h264_nvenc, hevc_nvenc |
| Decoders | h264_cuvid, hevc_cuvid, vp9_cuvid, av1_cuvid |
Container image: jasongdove/ersatztv:latest (GPU support included in default image)
FFmpeg Profiles¶
| Mode | Use Case | Subtitles |
|---|---|---|
| HLS Direct | Most content, low CPU | External only (not burned in) |
| HLS Segmenter | Foreign audio with subs | Burns subtitles into video (GPU accelerated) |
To burn in subtitles:
1. Go to Settings → FFmpeg Profiles
2. Edit or create a profile
3. Set Video Encoder to h264_nvenc or hevc_nvenc
4. Enable Burn Subtitles
5. Assign profile to channel in Channels → Edit → FFmpeg Profile
GPU Setup Details¶
The GPU passthrough was configured on Proxmox host:
# Verify IOMMU
dmesg | grep -i iommu
# GPU in IOMMU group 30
find /sys/kernel/iommu_groups/30 -type l
# Added to VM 100
qm set 100 -hostpci0 0b:00,pcie=1
qm set 100 -machine q35
Inside VM:
Client Setup¶
xTeVe (HDHomeRun Proxy)¶
xTeVe sits between ErsatzTV and Plex/Jellyfin, emulating an HDHomeRun tuner with embedded EPG data. This provides better compatibility than direct M3U integration.
| Item | Value |
|---|---|
| Web UI | http://10.89.97.50:34400/web/ |
| HDHomeRun Address | 10.89.97.50:34400 |
| Config | /opt/arr-stack/configs/xteve/ |
xTeVe Configuration:
1. Access http://10.89.97.50:34400/web/
2. Add M3U Playlist: http://localhost:8409/iptv/channels.m3u
3. Add XMLTV: http://localhost:8409/iptv/xmltv.xml
4. Set tuner count (2-4)
5. Map channels to EPG
Plex Live TV¶
Use xTeVe for best results:
1. Settings → Live TV & DVR → Set Up Plex DVR
2. Enter HDHomeRun address: 10.89.97.50:34400
3. Plex will detect xTeVe as HDHomeRun with EPG
⚠️ Plex XMLTV Limitation:
Plex has a critical restriction with XMLTV guides: - Once you use Plex's guide provider for ANY tuner, the XMLTV option is permanently hidden - The XMLTV link only appears on first DVR setup, before entering a postal code - If you've already set up DVR with Plex guide data, you'll see: "This location isn't covered by Plex DVR guide data. Because you already set up a tuner using Plex DVR guide data, we are unable to offer the option to use a XMLTV guide."
Workaround: Delete your entire DVR setup and start fresh:
1. Settings → Live TV & DVR → Delete DVR
2. Set Up Plex DVR again
3. Before entering postal code, look for the small "XMLTV" link
4. Enter your XMLTV URL: http://10.89.97.50:34400/xmltv/xteve.xml
Alternative: Use xTeVe with Plex's built-in EPG (limited guide data) or switch to Channels DVR ($8/mo) for the best IPTV experience on Apple TV.
Jellyfin Live TV¶
Can use either xTeVe or direct M3U:
Option 1 - Via xTeVe (recommended):
1. Dashboard → Live TV → Add Tuner Device
2. Tuner Type: HD Homerun
3. Tuner IP: 10.89.97.50:34400
Option 2 - Direct M3U:
1. Dashboard → Live TV → Add Tuner Device
2. Tuner Type: M3U
3. URL: http://10.89.97.50:8409/iptv/channels.m3u
4. Add TV Guide: XMLTV → http://10.89.97.50:8409/iptv/xmltv.xml
Note: Jellyfin Apple TV app does not support Live TV. Use Plex or other clients for Apple TV.
Apple TV Options¶
| App | Works? | Notes |
|---|---|---|
| Plex | Yes | Via xTeVe HDHomeRun |
| Channels DVR | Yes | $8/mo, best IPTV experience |
| Infuse | No | No direct IPTV support |
| Jellyfin | No | Apple TV app lacks Live TV |
| VLC | Yes | Direct M3U playback |
| Apple TV app | No | Requires real HDHomeRun hardware |
Any IPTV Client¶
Direct URLs (no xTeVe):
- M3U: http://10.89.97.50:8409/iptv/channels.m3u
- EPG: http://10.89.97.50:8409/iptv/xmltv.xml
HDHomeRun emulation (built into ErsatzTV):
- Discover: http://10.89.97.50:8409/discover.json
- Lineup: http://10.89.97.50:8409/lineup.json
Management¶
Docker Compose Location¶
Restart Service¶
View Logs¶
Config Location¶
- Container:
/root/.local/share/ersatztv/ - Host:
/opt/arr-stack/configs/ersatztv/
Database Access (SQLite)¶
ErsatzTV stores all configuration in a SQLite database.
Location:
- Host: /opt/arr-stack/configs/ersatztv/ersatztv.sqlite3
- Container: /root/.local/share/ersatztv/ersatztv.sqlite3
Option 1 - Copy locally and query (recommended for complex queries):
# Copy to Proxmox host
scp root@10.89.97.50:/opt/arr-stack/configs/ersatztv/ersatztv.sqlite3 /tmp/
# Query locally
sqlite3 /tmp/ersatztv.sqlite3 ".tables"
sqlite3 /tmp/ersatztv.sqlite3 "SELECT * FROM Channel;"
Option 2 - Install sqlite3 on arr-stack VM:
# Install sqlite3 on the VM
ssh root@10.89.97.50 "apt update && apt install -y sqlite3"
# Query directly on VM
ssh root@10.89.97.50 "sqlite3 /opt/arr-stack/configs/ersatztv/ersatztv.sqlite3 '.tables'"
Option 3 - Interactive session:
ssh root@10.89.97.50 "sqlite3 /opt/arr-stack/configs/ersatztv/ersatztv.sqlite3"
# Then use .tables, .schema, SELECT queries, etc.
Useful queries:
-- List all tables
.tables
-- Show table schema
.schema Channel
-- List channels
SELECT Id, Number, Name FROM Channel;
-- List libraries (media sources)
SELECT Id, Name, MediaKind FROM Library;
-- MediaKind: 1=Movies, 2=TV, 4=OtherVideos
-- List schedules
SELECT Id, Name FROM ProgramSchedule;
-- List library paths
SELECT l.Name, lp.Path
FROM Library l
JOIN LibraryPath lp ON l.Id = lp.LibraryId;
-- List playout items for a channel
SELECT pi.Start, mi.Id as MediaItemId
FROM PlayoutItem pi
JOIN Playout p ON pi.PlayoutId = p.Id
WHERE p.ChannelId = 2
ORDER BY pi.Start
LIMIT 10;
Note: When the database is actively in use, there may be .sqlite3-shm and .sqlite3-wal files (write-ahead log). For a clean copy, either stop the container first or copy all three files.
K8s Resources¶
# Ingress and services
kubectl get ingress,svc,endpoints -n arr-stack | grep ersatztv
# Manifests
cat /root/tower-fleet/manifests/arr-stack/ersatztv.yaml
Troubleshooting¶
Connection Refused to Jellyfin¶
Jellyfin runs at 10.89.97.97:8096, NOT on arr-stack VM.
502 Bad Gateway¶
# Check container is running
ssh root@10.89.97.50 "docker ps | grep ersatztv"
# Check logs
ssh root@10.89.97.50 "docker logs ersatztv --tail 20"
Auth Loop¶
Verify Authentik application is assigned to embedded outpost.
Stuck Scan / Can't Delete Library¶
If a library scan is stuck and you can't delete it:
# Nuclear option: stop container, delete database, restart fresh
ssh root@10.89.97.50 "cd /opt/arr-stack && docker compose stop ersatztv && rm -rf /opt/arr-stack/configs/ersatztv/*.sqlite3 && docker compose start ersatztv"
After reset, you must reconfigure: 1. Reconnect Jellyfin (Settings → Media Sources → Jellyfin) 2. Re-add local libraries 3. Recreate collections, filler presets, schedules, channels
Restart Container (Soft)¶
xTeVe Management¶
# Restart xTeVe
ssh root@10.89.97.50 "cd /opt/arr-stack && docker compose restart xteve"
# View logs
ssh root@10.89.97.50 "docker logs xteve --tail 50"
# Config location
/opt/arr-stack/configs/xteve/
Smart Collections API¶
ErsatzTV has an API for creating smart collections programmatically:
# List all smart collections
curl -s "http://10.89.97.50:8409/api/collections/smart"
# Create a smart collection
curl -X POST "http://10.89.97.50:8409/api/collections/smart/new" \
-H "Content-Type: application/json" \
-d '{"name": "[bumps] Show Name", "query": "library_name:\"[bumps] Show Name\""}'
# Delete a smart collection
curl -X DELETE "http://10.89.97.50:8409/api/collections/smart/delete/{id}"
Query syntax examples:
- library_name:"[bumps] Cowboy Bebop" - Match by library name
- show_title:"Breaking Bad" - Match TV show episodes
- title:Akira OR title:Ghost - OR queries
- title:Star Wars* - Wildcards