Slopsoil is a Discord self-bot that streams live TV, IPTV playlists, YouTube videos, Jellyfin media, and any HTTP/HLS/RTSP stream directly into a Discord voice channel - as a screenshare that all server members can watch together.
Features include TVheadend integration, Jellyfin integration, M3U/IPTV playlist management with live EPG (now-playing), YouTube playback via yt-dlp, and hardware-accelerated H.264 encoding via VA-API or NVIDIA NVENC.
Disclaimer:
Slopsoil is a self-bot. It runs on a real Discord user account, not a bot application.
Self-bots violate Discord's Terms of Service.
Use it on an account you are willing to lose, and do not use it in a way that disrupts other users or servers.
The authors take no responsibility for account terminations or other consequences.Keywords: discord iptv bot, discord youtube stream, stream tv to discord, discord live stream bot, discord voice channel video, iptv discord, self-bot streaming, tvheadend discord, hls discord bot
| Channel List | Stream Running |
|---|---|
![]() |
![]() |
.cgi video URLs (IP cameras, legacy streaming servers) and MJPEG-over-HTTP streams directlyslopsoil sends H.264 video and Opus audio directly over Discord's voice UDP protocol, appearing to other users as a screenshare (go-live stream). It patches discord.py-self at runtime to add video capability negotiation and implements the full RTP packetization pipeline including SPS/VUI rewriting, RFC 6184 FU-A fragmentation, and DAVE E2EE encryption.
For a detailed technical explanation of the streaming pipeline, the discord.py-self patches, and the esoteric protocol-level discoveries made while building this, see STREAMING.md.
| Requirement | Notes |
|---|---|
| Python 3.11+ | |
| FFmpeg | Fedora: ffmpeg-free (not RPM Fusion's ffmpeg). See Hardware Acceleration. |
| libdave / dave.py | The official Discord DAVE E2EE C library; dave.py wraps it |
| A Discord account token | Not a bot token - slopsoil runs as a self-bot on a real user account |
/dev/dri) or NVIDIA GPU for hardware encodinggit clone https://github.com/topsoil/slopsoil.git
cd slopsoil
Fedora / RHEL:
sudo dnf install ffmpeg-free
Ubuntu / Debian:
sudo apt install ffmpeg
Important: Do not use RPM Fusion's
ffmpegpackage on Fedora. It ships libx264, whose output causes Discord to drop the stream after one frame. Fedora's built-inffmpeg-freepackage (libopenh264) is required. See STREAMING.md for the full explanation.
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
This installs:
- discord.py-self - self-bot library with voice support
- PyNaCl - libsodium bindings for RTP encryption
- python-dotenv - .env file loading
- davey - stub package (replaced at runtime by the DAVE compatibility shim)
- dave.py - DisnakeDev's official libdave Python bindings (working DAVE/E2EE)
- yt-dlp - YouTube and media downloader
Copy the example environment file and fill in your values:
cp .env.example .env
See Configuration for details on each variable.
python3 bot.py
git clone https://github.com/topsoil/slopsoil.git
cd slopsoil
cp .env.example .env
Edit .env with your Discord token and other settings. See Configuration.
docker compose up -d --build
The container will build automatically on first run. IPTV source data is persisted in a named Docker volume (slopsoil-data) so your M3U sources survive container restarts and updates.
docker compose logs -f
All configuration is done via environment variables in .env:
# Required - your Discord account token (not a bot token)
DISCORD_TOKEN=your_token_here
# Comma-separated Discord user IDs that can control the bot
ALLOWED_USER_IDS=123456789012345678,987654321098765432
# Optional - TVheadend server (all three must be set to enable TV commands)
TVHEADEND_URL=http://192.168.1.100:9981
TVHEADEND_USER=admin
TVHEADEND_PASS=yourpassword
# Optional - IANA timezone for showtimes displayed by !search (e.g. America/New_York)
# If unset, the system timezone of the machine running the bot is used.
TIMEZONE=
# Optional - Jellyfin server (both must be set to enable Jellyfin commands)
JELLYFIN_URL=http://192.168.1.100:8096
JELLYFIN_API_KEY=your_api_key_here
# Optional - yt-dlp format selector for downloaded !play <URL> VODs.
# Defaults to "bestvideo+bestaudio/best". On hardware that can't decode AV1/4K
# in real time (e.g. older GPUs), pin to H.264 <=1080p to avoid stutter and to
# skip downloading 4K only to downscale it:
# YTDLP_FORMAT=bv*[vcodec^=avc1][height<=1080]+ba/b[height<=1080]
YTDLP_FORMAT=
# Optional - output stream quality preset (resolution/fps/bitrate together).
# One of: 720p, 1080p (default), 4k. Discord enforces per-account resolution/fps
# caps (e.g. 720p30 up to 4K60 by tier), so pick one your account supports.
STREAM_QUALITY=
# Optional - fine-grained overrides for the preset above (set only to deviate).
# STREAM_RESOLUTION=1280:720 # W:H (or WxH)
# STREAM_FPS=30 # 1-60
# STREAM_VIDEO_BITRATE=2500k # -b:v/-maxrate/-bufsize (CBR)
STREAM_RESOLUTION=
STREAM_FPS=
STREAM_VIDEO_BITRATE=
# Optional - pace each frame's RTP packets across this fraction of the frame
# interval instead of bursting them (0.0 = off/default; e.g. 0.75). Can reduce
# freezes from dropped packet bursts on some networks. Clamped to 0.0-0.95.
STREAM_PACKET_PACE=
# Optional - lip-sync correction in ms (default 0). Positive advances audio
# (fixes "audio behind"); negative delays audio (fixes "audio ahead"). Dial in
# by testing; clamped to +/-5000.
STREAM_AV_SYNC_MS=
| Variable | Required | Description |
|---|---|---|
DISCORD_TOKEN |
Yes | Your Discord account token |
ALLOWED_USER_IDS |
Yes | Comma-separated user IDs with admin access |
TVHEADEND_URL |
No | Base URL of your TVheadend server |
TVHEADEND_USER |
No | TVheadend username |
TVHEADEND_PASS |
No | TVheadend password |
TIMEZONE |
No | IANA timezone name for !search showtimes (e.g. America/Chicago). Defaults to the system timezone. |
JELLYFIN_URL |
No | Base URL of your Jellyfin server |
JELLYFIN_API_KEY |
No | Jellyfin API key (generate in Dashboard → API Keys) |
YTDLP_FORMAT |
No | yt-dlp format selector for downloaded !play <URL> VODs. Defaults to bestvideo+bestaudio/best. Pin a codec/resolution (e.g. bv*[vcodec^=avc1][height<=1080]+ba/b[height<=1080]) on hardware that can't decode AV1/4K in real time. |
STREAM_QUALITY |
No | Output quality preset (default 1080p): 720p→1280×720/30/2500k, 1080p→1920×1080/60/6000k, 4k→3840×2160/60/12000k (resolution/fps/bitrate set together). Discord enforces per-account resolution/fps caps, so pick a tier your account supports. |
STREAM_RESOLUTION |
No | Override the preset resolution as W:H (or WxH), e.g. 1280:720. |
STREAM_FPS |
No | Override the preset frame rate, 1–60. Matching the source fps avoids wasteful frame duplication. |
STREAM_VIDEO_BITRATE |
No | Override the preset video bitrate (used for -b:v/-maxrate/-bufsize, CBR), e.g. 2500k. |
STREAM_PACKET_PACE |
No | Spread each frame's RTP packets across this fraction of the frame interval instead of bursting them (default 0.0 = off; e.g. 0.75). Can reduce freezes caused by dropped packet bursts on some networks. Clamped to 0.0–0.95. |
STREAM_AV_SYNC_MS |
No | Lip-sync correction in ms (default 0). Audio and video send on independent threads, so a constant offset can appear. Positive advances audio (fixes "audio behind"); negative delays audio (fixes "audio ahead"). Dial in by testing; clamped to ±5000. |
TVheadend is optional. If any of the three TVHEADEND_* variables are missing, the !channels, !search, and TVheadend-backed !play commands are not loaded.
discord.com/apiAuthorization header on any request - that value is your tokenSecurity: Keep your token private. Anyone with your token can access your Discord account.
| Command | Role | Description |
|---|---|---|
!join |
Friend | Join your current voice channel |
!leave |
Friend | Leave the voice channel |
!stop |
Friend | Stop the active stream |
| Command | Role | Description |
|---|---|---|
!play <channel number> |
Friend | Play a TVheadend channel by number |
!play <channel name> |
Friend | Play a channel by name (case-insensitive substring match; searches TVheadend and IPTV) |
!play <URL> |
Friend | Play any URL — YouTube VODs, YouTube Live streams, direct HLS/HTTP/RTSP streams, .cgi MJPEG feeds, etc. Live streams are detected automatically and streamed without downloading. |
!channels |
Viewer | List all available channels with live now-playing info (paginated) |
!search <show title> |
Friend | Search EPG for a show — plays immediately if airing now, or schedules for upcoming airtime. Showtimes are shown in the TIMEZONE configured in .env. |
| Command | Role | Description |
|---|---|---|
!media <title> |
Friend | Search Jellyfin for a movie or series and stream it into your voice channel. A single movie match plays immediately. For series, the bot walks you through season → episode selection. Accepts an optional sXXeYY suffix (e.g. !media breaking bad s03e05) to jump directly to a specific episode. Transcoding is handled server-side by Jellyfin; subtitles are always suppressed. |
| Command | Role | Description |
|---|---|---|
!add-source <name> <url> |
Admin | Add an M3U playlist source |
!sources |
Admin | List all sources with enabled/disabled status |
!sources enable <name> |
Admin | Enable a source |
!sources disable <name> |
Admin | Disable a source |
!delete-source |
Admin | Interactively delete a source |
| Command | Role | Description |
|---|---|---|
!ping |
Any | Check if the bot is responding |
!help |
Any | List available commands |
| Role | Who qualifies |
|---|---|
| Admin | User IDs listed in ALLOWED_USER_IDS |
| Friend | Users on the bot account's friends list |
| Viewer | Members of any guild the bot account is in |
| None | Everyone else |
When you pull new changes to the project, rebuild the container image:
# Pull latest changes
git pull
# Rebuild the image and restart the container
docker compose up -d --build
If you only changed .env (no code changes), a restart is enough - no rebuild need
$ claude mcp add slopsoil \
-- python -m otcore.mcp_server <graph>