Skip to content

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

  1. Add Local Media Sources (one per show folder):
  2. Media Sources → Add Local
  3. Path: /media/toonami/organized/cowboy-bebop
  4. Name: Cowboy Bebop Bumps
  5. Type: Other Videos
  6. Repeat for each show folder you want

  7. Create Collections:

  8. Lists → Collections → Add
  9. Add items from each bump library

  10. Create Filler Presets:

  11. Lists → Filler Presets → Add
  12. Select collection, choose type (pre-roll, post-roll, tail)
  13. Configure mode (count, duration, or pad)

  14. Use in Schedules:

  15. When adding schedule items, attach filler presets
  16. 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:

# Verify GPU
nvidia-smi

# Container toolkit installed
nvidia-ctk runtime configure --runtime=docker

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

ssh root@10.89.97.50
cat /opt/arr-stack/docker-compose.yml

Restart Service

ssh root@10.89.97.50 "cd /opt/arr-stack && docker compose restart ersatztv"

View Logs

ssh root@10.89.97.50 "docker logs ersatztv --tail 50"

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)

ssh root@10.89.97.50 "cd /opt/arr-stack && docker compose restart ersatztv"

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