Server-side YouTube Audio Mixer with ad-hoc video injection, LUFS normalization, and podcast-style ducking
Project description
YT Mixer
Server-side YouTube audio/video mixer with professional podcast-style processing, ad-hoc video injection, and iOS-native playback.
Features
- 🎵 Mix two YouTube playlists (music + speech/podcast) into continuous 1-hour chunks
- 🎚️ Podcast-quality audio: Automatic sidechain ducking (music lowers for voice), LUFS normalization to broadcast standards (-16 for speech, -23 for music), and a final mastering limiter
- 🎬 Ad-hoc video injection — queue any YouTube video and it plays with a freshly mixed background track, then seamlessly returns to your stream
- 📱 iOS-native video playback with HTTP 206 range support and a dedicated video page (no competing
<audio>session) - 🔁 Persistent server-side job state — close the browser mid-render and the Play button is waiting when you come back
- 📦 Session-based mixing with bookmarkable URLs
- ⚡ Optional omnipkg integration for pinned yt-dlp worker processes (~2ms probe vs ~400ms cold import)
Installation
From Source
git clone https://github.com/1minds3t/yt-mixer.git
cd yt-mixer
pip install -e .
With omnipkg daemon support (optional, recommended on Linux):
pip install -e ".[full]"
Requirements
- Python 3.8+
- FFmpeg
# Ubuntu/Debian
sudo apt install ffmpeg
# macOS
brew install ffmpeg
Quick Start
yt-mixer serve
Access at http://localhost:5052
Install as a systemd service
yt-mixer service --install
systemctl --user start yt-mixer
systemctl --user enable yt-mixer
yt-mixer service --logs
CLI
# Configuration
yt-mixer config --list
yt-mixer config --set port=5053
yt-mixer config --get port
# Sessions
yt-mixer sessions
yt-mixer sessions --clean
# Service
yt-mixer service --status
yt-mixer service --start | --stop | --restart | --enable
# Update yt-dlp
yt-mixer update
Environment Variables
export YT_MIXER_HOST=0.0.0.0 # default: 0.0.0.0
export YT_MIXER_PORT=5052 # default: 5052
export YT_MIXER_DATA_DIR=./data # default: ./data
export YT_MIXER_MAX_CHUNKS=3 # default: 3 (chunks kept in memory)
export YT_MIXER_PRUNE_DAYS=7 # default: 7 (days before session cleanup)
export YT_MIXER_CHUNK_DURATION=3600 # default: 3600 (seconds per chunk)
export YT_MIXER_MUSIC_VOLUME=0.4 # default: 0.4
export YT_MIXER_SPEECH_VOLUME=1.0 # default: 1.0
Usage
Background Mix
- Open
http://localhost:5052 - Enter two YouTube playlist IDs or full URLs — one music, one speech/podcast
- Click Create Mix and bookmark the generated
/?sid=...URL
The mixer produces continuous 1-hour chunks. Each chunk goes through three quality tiers as processing completes: Immediate (raw) → Quick (normalized) → Final (LUFS mastered).
Ad-hoc Video Injection
While the background mix is playing, paste any YouTube URL into the Add Video Audio panel and hit Prepare. The server downloads audio and video in parallel in the background — your mix keeps playing uninterrupted. When ready, hit Play video to watch with a freshly mixed background track underneath. On exit, the background stream resumes where it left off.
State is held server-side: close the tab mid-render, reopen it, and the Play video button appears automatically without re-hitting Prepare.
Bookmarking
http://localhost:5052/?sid=abc123def456
Bookmarking this URL restores your session including playlist configuration and playback position.
Audio Processing
Speech:
- High-pass filter (removes rumble)
- Center mono panning
- LUFS normalization to -16 (podcast standard)
- Compression
Music:
- LUFS normalization to -23 (background level)
Mix:
- Sidechain ducking — music automatically lowers when speech plays
- Final limiter prevents clipping
omnipkg Integration
When omnipkg is installed (pip install -e ".[full]"), yt_mixer uses its daemon to keep two tagged persistent worker processes alive:
ytdlp-info— metadata probes (title, duration, livestream check)ytdlp-dl— audio and video downloads
Workers are pinned (survive idle timeout) and kept alive by a keepalive thread that pings every 4 minutes. This cuts yt-dlp startup from ~400ms to ~2ms and avoids repeated Python interpreter cold-starts for every download. Falls back transparently to direct import yt_dlp if the daemon is unavailable.
Data Storage
data/
├── raw_audio/ # Downloaded audio per session
├── mixed_chunks/ # Generated 1-hour mixed chunks per session
├── adhoc_jobs/ # Ad-hoc video job state and final renders
└── config.json # Persistent configuration
Sessions auto-clean after 7 days of inactivity. Ad-hoc jobs GC after 2 hours (ready) or 30 minutes (failed/cancelled).
Development
pip install -e ".[dev]"
pytest
black src/
ruff src/
Troubleshooting
yt-dlp errors — YouTube changes its API frequently; update regularly:
yt-mixer update
Port in use:
yt-mixer config --set port=5053
FFmpeg not found:
sudo apt install ffmpeg # Ubuntu/Debian
brew install ffmpeg # macOS
Video won't play on iOS — This is usually caused by an audio session conflict. The Play video button solves this automatically by navigating to a dedicated player page that has no competing <audio> element. If you've modified the frontend and are trying to play a <video> element on the main mixer page, it will fail on iOS — the dedicated page is required.
License
AGPL-3.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file yt_mixer-0.2.0.tar.gz.
File metadata
- Download URL: yt_mixer-0.2.0.tar.gz
- Upload date:
- Size: 64.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f596e9b42844d03acc47cacde8a2e741fced2c8026bd7595d54273b2a7cd0efe
|
|
| MD5 |
1249c65c972c5c0de2419c626a48a3a8
|
|
| BLAKE2b-256 |
55250a2d3b2b8d805add0af0b6ef9cefb9ee42165fd2ce6a49051260101be3d8
|
Provenance
The following attestation bundles were made for yt_mixer-0.2.0.tar.gz:
Publisher:
publish.yml on 1minds3t/yt-mixer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
yt_mixer-0.2.0.tar.gz -
Subject digest:
f596e9b42844d03acc47cacde8a2e741fced2c8026bd7595d54273b2a7cd0efe - Sigstore transparency entry: 1190661067
- Sigstore integration time:
-
Permalink:
1minds3t/yt-mixer@4c131cb7e294c04b5288a71c3bcacb8660363f1b -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/1minds3t
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4c131cb7e294c04b5288a71c3bcacb8660363f1b -
Trigger Event:
release
-
Statement type:
File details
Details for the file yt_mixer-0.2.0-py3-none-any.whl.
File metadata
- Download URL: yt_mixer-0.2.0-py3-none-any.whl
- Upload date:
- Size: 59.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
072493df546becec11e86e659da647928602f53c32c87b0e5945e41422f962d9
|
|
| MD5 |
9edb4ac086c903430aec30fd511af6ac
|
|
| BLAKE2b-256 |
e63304c0ac9d4aee38759702f7dc24b3e997a886de84b5c5879106081b58d304
|
Provenance
The following attestation bundles were made for yt_mixer-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on 1minds3t/yt-mixer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
yt_mixer-0.2.0-py3-none-any.whl -
Subject digest:
072493df546becec11e86e659da647928602f53c32c87b0e5945e41422f962d9 - Sigstore transparency entry: 1190661071
- Sigstore integration time:
-
Permalink:
1minds3t/yt-mixer@4c131cb7e294c04b5288a71c3bcacb8660363f1b -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/1minds3t
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4c131cb7e294c04b5288a71c3bcacb8660363f1b -
Trigger Event:
release
-
Statement type: