| name | video-obs-youtube-music |
| description | Remux OBS recordings to MP4, add YouTube music, generate intro/outro videos with WebGL shaders, and song credit overlays. Use when preparing screen recordings for YouTube or X/Twitter upload. |
video-obs-youtube-music — OBS YouTube Music
Remux OBS MKV → MP4, add stunning intro/outro videos with WebGL shaders (FloquetTopoCA, smeared_life, SpinGlassCA), replace audio with YouTube music, and overlay animated song credits.
Quick Start
ffmpeg -i "recording.mkv" -i "song.mp3" \
-map 0:v -map 1:a -c:v copy -c:a aac -b:a 192k \
-movflags +faststart "output.mp4"
Workflow
Platform Export Requirements
YouTube (Maximum Quality)
- Resolution: Up to 8K
- Frame rate: Up to 60fps
- Codec: H.264 or HEVC
- No special requirements beyond
-movflags +faststart
X/Twitter (CRITICAL - Different Limits!)
| Spec | Limit |
|---|
| Resolution | Max 1920x1200 (NOT 4K!) |
| Frame rate | Up to 60fps |
| Bitrate | Max 25 Mbps |
| Duration | 2:20 regular, 4 hours Premium |
| File size | 512MB regular, 16GB Premium |
| Codec | H.264 + AAC only |
X-Compatible Encoding Command:
ffmpeg -i source_4k.mp4 \
-vf "scale=1920:1080:flags=lanczos" \
-c:v libx264 -preset medium -crf 18 \
-profile:v high -level:v 4.2 -pix_fmt yuv420p \
-b:v 20M -maxrate 25M -bufsize 25M \
-c:a aac -b:a 192k \
-movflags +faststart \
output_for_x.mp4
QuickTime Compatibility
- HEVC may not play - always provide H.264 version
- Use standard resolutions (3840x2160, 1920x1080)
- Avoid non-standard aspect ratios like 4096x2304
Intro Video
Generate stunning 5-second intro videos with:
- WebGL Background: Animated cellular automata (cosmic, plasma, aurora, neon, nebula effects)
- 3D Particle Effects: Three.js floating particles with additive blending
- Animated Text: Title, description, author info with smooth fade transitions
- Professional Layout: Date in corner, social handles at bottom
Generate Intro
./scripts/generate-intro.py \
--title "Building a CLI Tool in Rust" \
--output intro.mp4
./scripts/generate-intro.py \
--title "React Performance Deep Dive" \
--description "Optimizing renders and reducing bundle size" \
--effect plasma \
--duration 5 \
--width 1920 --height 1080 \
--output intro.mp4
Effect Types
| Effect | Description |
|---|
cosmic | Default - deep purples and blues, organic flow |
plasma | Warm oranges and reds, energetic movement |
aurora | Greens and cyans, smooth northern lights feel |
neon | High contrast pinks and cyans, electric vibe |
nebula | Rich colors with energy glow, space-like |
Options
| Option | Default | Description |
|---|
--title | required | Video title (large, centered) |
--description | "" | Subtitle text |
--name | Jeffrey Emanuel | Author name |
--twitter | @doodlestein | X/Twitter handle |
--github | Dicklesworthstone | GitHub username |
--date | today | Date displayed |
--effect | cosmic | Background effect type |
--random-effect | false | Choose random effect |
--duration | 5 | Duration in seconds |
--width | 1920 | Video width |
--height | 1080 | Video height |
--fps | 60 | Frames per second |
Prepend Intro to Recording
ffmpeg -i intro.mp4 -i recording.mp4 \
-filter_complex "[0:v][1:v]concat=n=2:v=1:a=0[v]" \
-map "[v]" \
-c:v libx264 -preset fast -crf 18 \
"with_intro.mp4"
ffmpeg -i intro.mp4 -i recording.mp4 -i song.mp3 \
-filter_complex "[0:v][1:v]concat=n=2:v=1:a=0[v]" \
-map "[v]" -map 2:a \
-c:v libx264 -preset fast -crf 18 -c:a aac -b:a 192k \
-shortest \
"final.mp4"
Song Credit Cards
Animated "now playing" cards with polished visual effects:
- Scale animation: Cards grow from 85% with overshoot bounce on entry/exit
- Floating motion: Subtle up/down floating while displayed
- Pulsing music icon: Rhythmic scale animation on the music note
- Shimmer highlight: Animated gradient sweep across top edge
- Glow pulse: Subtle ambient glow that pulses
- Custom branding: "Doodlestein's Choice" text with Pacifico font
Card Dimensions
| Use Case | Width | Height | Notes |
|---|
| Standard | 2280px | 480px | Full-width overlay |
| Narrow (transparent) | 1900px | 480px | Avoids white rectangle artifacts |
| 4K compatible | 1900px | 480px | Scales well at any resolution |
Important: Use narrower cards (1900px) to avoid visible white rectangles on sides.
Generate Cards with Playwright
const { chromium } = require("playwright");
const fs = require("fs");
const path = require("path");
async function renderCard(cardNum) {
const cardPath = "./cards/0" + cardNum + "_card.html";
const outDir = "./cards/frames_0" + cardNum;
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.setViewportSize({ width: 1900, height: 480 });
await page.goto("file://" + path.resolve(cardPath));
await page.waitForTimeout(500);
const FPS = 60, DURATION = 6, TOTAL = * ;
( f = ; f < ; f++) {
framePath = path.(outDir, + (f).(, ) + );
page.({ : framePath, : });
page.( .());
}
browser.();
}
(process.[] || );
Encode Cards with Alpha (WebM VP9)
ffmpeg -framerate 60 -i "frames/frame_%04d.png" \
-c:v libvpx-vp9 -pix_fmt yuva420p -b:v 2M \
card.webm
Card Overlay FFmpeg
Position cards in upper-right corner (not bottom-right) for better visibility:
ffmpeg -i "video.mp4" -i "song.mp3" -i "card.webm" \
-filter_complex \
"[0:v][2:v]overlay=x=W-w-50:y=50:enable='between(t,10,16)'[v]" \
-map "[v]" -map 1:a -c:v libx264 -preset fast -crf 18 -c:a aac -b:a 192k \
-movflags +faststart "output.mp4"
ffmpeg -i intro.mp4 -i recording.mp4 -i outro.mp4 -i songs.m4a \
-i card1.webm -i card2.webm \
-filter_complex "
[0:v][1:v][2:v]concat=n=3:v=1:a=0[base];
[4:v]setpts=PTS+10/TB[c1];
[5:v]setpts=PTS+205/TB[c2];
[base][c1]overlay=x=W-w-50:y=50:enable='between(t,10,16)'[tmp1];
[tmp1][c2]overlay=x=W-w-50:y=50:enable='between(t,205,211)'[vout]
" \
-map "[vout]" -map 3:a \
-c:v libx264 -preset medium -crf 18 -c:a aac -b:a 192k \
final.mp4
Card Timing Formula
| Song | Card Appears | Card Duration | Animation |
|---|
| 1 | 0s | 6s | Baked into video |
| 2 | song1_duration | 6s | Baked into video |
| 3 | song1 + song2 duration | 6s | Baked into video |
Overlay pattern: enable='between(t,{start},{start+6})':eof_action=pass
Multi-Song (No Cards)
ffmpeg -i "video.mkv" -i "s1.mp3" -i "s2.mp3" \
-filter_complex "[1:a][2:a]concat=n=2:v=0:a=1[music]" \
-map 0:v -map "[music]" \
-c:v copy -c:a aac -b:a 192k \
-movflags +faststart "output.mp4"
Core Commands
| Task | Command |
|---|
| Duration | ffprobe -v error -show_entries format=duration -of csv=p=0 "file" |
| Download song | yt-dlp -x --audio-format mp3 "ytsearch1:Artist Song" |
| Get metadata | yt-dlp --print-json --no-download "ytsearch1:query" |
| Download thumbnail | yt-dlp --write-thumbnail --convert-thumbnails jpg "URL" |
Dependencies
ffmpeg + ffprobe
yt-dlp
node + playwright npm package (for intro and card video recording)
Install dependencies:
cd /path/to/video-obs-youtube-music
npm install playwright
npx playwright install chromium
Check: ./scripts/check-deps.sh
Remote Machine Parallel Rendering
For complex projects with long intro/outro sequences, distribute rendering across multiple machines.
Available Machines
| Host | CPU | GPU | Best For |
|---|
trj | High core | Dual 4090 | NVENC encoding |
jain | High core | None | CPU encoding, fast |
csd | Medium | None | Frame rendering |
css | Medium | None | Frame rendering |
fmd | Medium | None | Frame rendering |
Setup Remote Machines
ssh machine "npm install playwright && npx playwright install chromium"
scp intro.html machine:/tmp/
scp render_script.js machine:/tmp/
Robust Render Script (with timeouts)
const { chromium } = require("playwright");
const fs = require("fs");
const path = require("path");
const START_FRAME = parseInt(process.argv[2]) || 0;
const END_FRAME = parseInt(process.argv[3]) || 100;
const HTML_FILE = process.argv[4] || '/tmp/intro.html';
const OUTPUT_DIR = process.argv[5] || '/tmp/frames';
const WIDTH = parseInt(process.argv[6]) || 1920;
const HEIGHT = parseInt(process.argv[7]) || 1080;
async function render() {
if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { : });
browser = chromium.({
: ,
:
});
page = browser.();
page.();
page.({ : , : });
page.( + path.(), { : });
page.();
( i = ; i < ; i++) {
page.( . && .());
}
.();
startTime = .();
( f = ; f < ; f++) {
framePath = path.(, );
( retry = ; retry < ; retry++) {
{
page.({ : framePath });
;
} (e) {
(retry === ) e;
page.();
}
}
page.( . && .());
((f - ) % === ) {
elapsed = (.() - startTime) / ;
fps = (f - ) / elapsed;
.();
}
}
browser.();
.();
}
().( { .(e); process.(); });
Parallel Frame Rendering
Split frame ranges across machines and render in parallel:
TOTAL_FRAMES=5430
ssh css "node /tmp/render.js 0 1086 /tmp/intro.html /tmp/frames" &
ssh csd "node /tmp/render.js 1086 2172 /tmp/intro.html /tmp/frames" &
ssh jain "node /tmp/render.js 2172 3258 /tmp/intro.html /tmp/frames" &
ssh fmd "node /tmp/render.js 3258 4344 /tmp/intro.html /tmp/frames" &
ssh trj "node /tmp/render.js 4344 5430 /tmp/intro.html /tmp/frames" &
wait
Reallocating Slow Machines
If a machine is too slow, stop it and redistribute:
ssh slow_machine 'ls /tmp/frames/*.png | wc -l'
ssh slow_machine 'pkill -f node'
ssh fast1 "node /tmp/render.js $STOPPED_AT $MIDPOINT /tmp/intro.html /tmp/frames" &
ssh fast2 "node /tmp/render.js $MIDPOINT $END /tmp/intro.html /tmp/frames" &
Collect and Assemble Frames
rsync -avz css:/tmp/frames/ ./frames/
rsync -avz csd:/tmp/frames/ ./frames/
rsync -avz jain:/tmp/frames/ ./frames/
rsync -avz fmd:/tmp/frames/ ./frames/
rsync -avz trj:/tmp/frames/ ./frames/
ls frames/*.png | wc -l
ffmpeg -framerate 60 -i "frames/frame_%05d.png" \
-c:v libx264 -pix_fmt yuv420p -crf 18 \
output.mp4
GPU-Accelerated Encoding on Remote
For machines with NVIDIA GPUs (e.g., dual 4090s), use NVENC:
ssh trj 'ffmpeg -i input.mp4 -c:v hevc_nvenc -preset p4 -b:v 20M output.mp4'
ssh trj 'ffmpeg -i input.mp4 \
-c:v h264_nvenc -preset p4 -profile:v high -b:v 20M output.mp4'
Frame Rate Matching
Critical: When concatenating videos, all inputs must have the same frame rate:
ffmpeg -i source_30fps.mkv -r 60 -c:v libx264 -crf 18 source_60fps.mp4
ffmpeg -i intro_60fps.mp4 -i source_60fps.mp4 -i outro_60fps.mp4 \
-filter_complex "[0:v][1:v][2:v]concat=n=3:v=1:a=0[v]" \
-map "[v]" output.mp4
4K Video Production
Resolution Considerations
| Resolution | H.264 Support | HEVC Support | QuickTime | X/Twitter |
|---|
| 1920x1080 | Yes | Yes | Yes | Yes |
| 3840x2160 | Yes (level 5.2) | Yes | Yes | NO |
| 4096x2304 | No (exceeds level) | Yes | Maybe | NO |
Recommended 4K Workflow
ffmpeg -i source.mkv -r 60 -c:v libx264 -crf 18 source_60fps.mp4
ffmpeg -i intro.mp4 -i source_60fps.mp4 -i outro.mp4 -i audio.m4a \
-filter_complex "[0:v][1:v][2:v]concat=n=3:v=1:a=0[v]" \
-map "[v]" -map 3:a \
-c:v libx264 -crf 18 -c:a aac -b:a 192k \
master.mp4
cp master.mp4 youtube_4k.mp4
ffmpeg -i master.mp4 \
-vf "scale=1920:1080:flags=lanczos" \
-c:v libx264 -preset medium -crf 18 \
-profile:v high -level:v 4.2 -pix_fmt yuv420p \
-c:a aac -b:a 192k \
-movflags +faststart \
x_twitter_1080p60.mp4
Gotchas
Encoding
ytsearch1: returns first result — include artist name for accuracy
- Cards/intros require video re-encode (
-c:v libx264), basic remux can use -c:v copy
-movflags +faststart required for YouTube/X streaming
- Frame rate mismatch: Concat of different fps causes bitrate/quality issues - convert first!
- H.264 level limits: 4096x2304 exceeds level 5.2, use HEVC or scale to 3840x2160
Platform Compatibility
- X/Twitter rejects 4K - Always encode 1080p version for X, even if video plays in preview
- QuickTime HEVC: May not play - provide H.264 version for compatibility
- X Premium allows 4 hour videos, but still max 1920x1200 resolution
Card Overlays
- Card overlay position:
x=W-w-50:y=50 = upper-right corner (50px margins)
- Card videos are 6 seconds with animation baked in (no ffmpeg fade needed)
- WebM format supports transparency for clean overlay compositing
- Card transparency: Use narrower cards (1900px) to avoid white rectangle artifacts
Audio
- Audio timing: Start audio at intro (t=0), cards appear when screen recording starts
Remote Rendering
- Remote machines may need SwiftShader for software WebGL rendering
- Always use timeout and retry logic in render scripts
- Monitor render speed - reallocate slow machines' work to faster ones
- Check disk space on remote machines before large frame transfers