Install
$ agentstack add skill-damionrashford-media-os-ffmpeg-capture ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Ffmpeg Capture
Context: $ARGUMENTS
Quick start
- Record the screen: → Step 1 (list devices) → Step 2 (pick source) → Step 3 (run)
- Record webcam + mic: → Step 1 → Step 3 with
webcamsubcommand - Record a region: → Step 3 with
--crop(mac/linux) or-offset_x/-offset_y/-video_size(Windows gdigrab) - Record system audio (mac): install BlackHole first → Step 2 (route via virtual device)
- Just list devices:
python3 scripts/capture.py list-devices
When to use
- User wants a screencast, demo recording, or desktop capture to a file.
- User wants to record webcam/microphone/system audio to disk.
- User wants to enumerate which cameras/mics/screens ffmpeg can see.
- User wants low-latency or crash-safe recording (MKV-first, remux to MP4 after).
Do NOT use for: streaming to RTMP/HLS (use ffmpeg-streaming), applying filters/effects (use ffmpeg-video-filter), or converting an existing file (use ffmpeg-transcode).
Step 1 — Detect platform + list devices
Always list devices first. Names and indices are shown in the format ffmpeg expects them.
macOS (avfoundation):
ffmpeg -f avfoundation -list_devices true -i ""
Output has two sections: AVFoundation video devices: (cameras + screens, screen is usually the last one with Capture screen 0) and AVFoundation audio devices:. Indices reset between the two lists — video 1 and audio 0 are unrelated.
Windows (dshow for camera/mic, gdigrab for desktop):
ffmpeg -list_devices true -f dshow -i dummy
ffmpeg -list_options true -f dshow -i video="HD Webcam"
gdigrab needs no enumeration — input name is always desktop (or title=Window Name).
Linux:
# Cameras
v4l2-ctl --list-devices
ffmpeg -f v4l2 -list_formats all -i /dev/video0
# Audio (Pulse)
pactl list sources short
# Audio (ALSA)
arecord -L
# Display (X11)
echo "$DISPLAY" # usually :0.0 or :1.0
Or run the helper: python3 scripts/capture.py list-devices (auto-detects platform).
Step 2 — Pick source + audio route
Decide:
- Video source: screen? camera? region? window?
- Audio route: mic? system audio? both? none?
- Container: MKV for long/crash-safe sessions, MP4 for quick clips.
- Encoder:
libx264 -preset ultrafast -pix_fmt yuv420pfor CPU; platform HW encoder for 60fps+ (see [references/devices.md](references/devices.md)).
System audio notes:
- macOS has no native loopback — install BlackHole (or use Loopback / Rogue Amoeba). Route system output to BlackHole, then use
-i ":". To hear it too, build a Multi-Output Device in Audio MIDI Setup. - Windows: use
-f dshow -i audio="Stereo Mix"(if enabled) or VB-Cable. - Linux:
pactl list sources shortshows a*.monitorsource for each sink — that's system audio.
Step 3 — Run capture with proper encoder
macOS — screen + default mic (most common)
ffmpeg -f avfoundation -framerate 30 -capture_cursor 1 -i "1:0" \
-c:v libx264 -preset ultrafast -pix_fmt yuv420p -c:a aac -b:a 160k out.mp4
"1:0" = video device index 1 (screen), audio device index 0 (default mic). Replace with your actual indices from Step 1.
macOS — screen only, no audio, 60fps, HW-encoded
ffmpeg -f avfoundation -framerate 60 -capture_cursor 1 -i "1" \
-c:v h264_videotoolbox -b:v 8M -pix_fmt yuv420p out.mp4
macOS — crop a region from full-screen capture
ffmpeg -f avfoundation -framerate 30 -i "1" \
-vf "crop=1280:720:100:100" -c:v libx264 -preset ultrafast -pix_fmt yuv420p out.mp4
avfoundation has no native region mode; always full-screen then crop via -vf crop=W:H:X:Y.
macOS — built-in camera + mic
ffmpeg -f avfoundation -framerate 30 -video_size 1280x720 -i "0:0" \
-c:v libx264 -preset ultrafast -pix_fmt yuv420p -c:a aac out.mp4
macOS — screen + mic + system audio (BlackHole)
Assume BlackHole is audio index 2, mic is index 0:
ffmpeg -f avfoundation -framerate 30 -capture_cursor 1 -i "1:0" \
-f avfoundation -i ":2" \
-filter_complex "[0:a][1:a]amix=inputs=2:duration=longest[a]" \
-map 0:v -map "[a]" -c:v libx264 -preset ultrafast -pix_fmt yuv420p -c:a aac out.mp4
Windows — full desktop
ffmpeg -f gdigrab -framerate 30 -i desktop \
-c:v libx264 -preset ultrafast -pix_fmt yuv420p out.mp4
Windows — region of desktop
ffmpeg -f gdigrab -framerate 30 -offset_x 100 -offset_y 100 \
-video_size 1280x720 -i desktop -c:v libx264 -preset ultrafast -pix_fmt yuv420p out.mp4
Windows — specific window
ffmpeg -f gdigrab -framerate 30 -i title="Mozilla Firefox" -c:v libx264 out.mp4
Windows — webcam + mic (dshow)
ffmpeg -f dshow -framerate 30 -video_size 1280x720 \
-i video="HD Pro Webcam C920":audio="Microphone (Realtek)" \
-c:v libx264 -preset ultrafast -pix_fmt yuv420p -c:a aac out.mp4
Linux — X11 full display
ffmpeg -f x11grab -framerate 30 -video_size 1920x1080 -i :0.0 \
-c:v libx264 -preset ultrafast -pix_fmt yuv420p out.mp4
Linux — X11 region
ffmpeg -f x11grab -framerate 30 -video_size 1280x720 -i :0.0+100,100 out.mp4
Linux — webcam (v4l2)
ffmpeg -f v4l2 -input_format mjpeg -video_size 1280x720 -framerate 30 \
-i /dev/video0 -c:v libx264 -preset ultrafast -pix_fmt yuv420p out.mp4
Linux — audio (Pulse or ALSA)
ffmpeg -f pulse -i default out.wav # Pulse default mic
ffmpeg -f pulse -i alsa_output.monitor out.wav # system audio (monitor)
ffmpeg -f alsa -i hw:0 out.wav
Linux — DRM/KMS (Wayland / no X11)
sudo ffmpeg -device /dev/dri/card0 -f kmsgrab -i - \
-vf 'hwdownload,format=bgr0' -c:v libx264 -preset ultrafast -pix_fmt yuv420p out.mp4
Requires root or sudo setcap cap_sys_admin+ep $(which ffmpeg).
Step 4 — Stop cleanly
- Press
qin the ffmpeg terminal. ffmpeg flushes the mux and finalizes the MOOV atom for MP4. - Do NOT Ctrl+C unless recording MKV. Ctrl+C on MP4 often leaves an unreadable file (no MOOV). MKV is crash-safe and can be remuxed later:
ffmpeg -i recording.mkv -c copy recording.mp4. - For unattended timed captures use
-t 60(stop after 60s) rather than killing the process. - For long sessions use
-f segment -segment_time 600 -reset_timestamps 1 out_%03d.mkvso crashes cost at most one segment.
Available scripts
scripts/capture.py— cross-platform wrapper:list-devices,screen,webcam,audio-only. Auto-detects the platform and builds the right ffmpeg command.--dry-runprints the command without running it.
Workflow
# 1. What devices exist?
python3 ${CLAUDE_SKILL_DIR}/scripts/capture.py list-devices
# 2. Dry-run to confirm the command
python3 ${CLAUDE_SKILL_DIR}/scripts/capture.py screen --output out.mp4 --fps 30 --dry-run
# 3. Record
python3 ${CLAUDE_SKILL_DIR}/scripts/capture.py screen --output out.mp4 --fps 30 --audio auto
Reference docs
- Read [
references/devices.md](references/devices.md) for the full per-platform option tables (avfoundation, gdigrab, dshow, x11grab, kmsgrab, v4l2, alsa, pulse, decklink), virtual-audio devices, TCC/permission reset, crash-safe recording, and bitrate/FPS recommendations.
Gotchas
- macOS permissions: ffmpeg needs Screen Recording AND Microphone permission in System Settings → Privacy & Security. First run prompts the dialog and ffmpeg exits immediately with empty/
EAGAINcapture — grant permission to the parent terminal/IDE (Terminal.app, iTerm, VS Code) and re-run. Permission is per-binary-path: reinstalling ffmpeg to a new location re-prompts. - avfoundation indices are not arbitrary.
"1:0"means "video device 1, audio device 0" exactly as listed by-list_devices true. Video and audio lists are numbered independently. - avfoundation has no system-audio input. Install BlackHole / Loopback and chain it as a second
-f avfoundation -i ":N"input, then-filter_complex amixor-mapto combine. - dshow device names need exact case and quoting on Windows.
video="HD Webcam"(curly-quote will break). Audio and video are separate arguments in the same-i:video="X":audio="Y". -pix_fmt yuv420pis required for libx264 output that plays in QuickTime/Safari/iOS. Leave it off and you get yuv444p, which many players refuse.- 60fps screen capture saturates libx264-ultrafast on most CPUs. Use
h264_videotoolbox(mac),h264_nvenc(NVIDIA),h264_qsv(Intel),h264_amf(AMD) — seeffmpeg-hwaccel. - gdigrab cursor:
-draw_mouse 1(default on). Some builds ignore it; cursor overlay is unreliable in virtual display setups (RDP). - x11grab
:0.0must match the running$DISPLAY. On multi-monitor X, use:0.0+X,Yto pick the origin. - v4l2
-input_format mjpegis required for most UVC webcams at 720p/1080p 30fps. Raw YUYV falls back to 5–10fps at full res. - kmsgrab requires root or
setcap cap_sys_admin+ep. Outputs DRM prime frames — always chainhwdownload,format=bgr0before encoding on CPU. - Pulse source names change between reboots on some systems —
pactl list sources shorteach session. - Stopping:
qin terminal = clean shutdown. Ctrl+C on MP4 = corrupted file. Record MKV for anything over a minute, remux after. - Audio drift: on long sessions audio can drift several hundred ms. Use
-async 1or record audio to a separate file and mux after.
Examples
Example 1: quick 30-second screen recording on macOS
# List devices
ffmpeg -f avfoundation -list_devices true -i ""
# → video index 1 = "Capture screen 0", audio index 0 = "MacBook Pro Microphone"
ffmpeg -f avfoundation -framerate 30 -capture_cursor 1 -i "1:0" \
-t 30 -c:v libx264 -preset ultrafast -pix_fmt yuv420p -c:a aac demo.mp4
Example 2: crash-safe 2-hour tutorial recording on Windows
ffmpeg -f gdigrab -framerate 30 -i desktop \
-f dshow -i audio="Microphone (USB Audio)" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a aac \
-f segment -segment_time 600 -reset_timestamps 1 tutorial_%03d.mkv
# Press q to stop. Then remux:
ffmpeg -f concat -safe 0 -i <(for f in tutorial_*.mkv; do echo "file '$PWD/$f'"; done) -c copy tutorial.mp4
Example 3: webcam-only on Linux for a video call recording
ffmpeg -f v4l2 -input_format mjpeg -video_size 1920x1080 -framerate 30 -i /dev/video0 \
-f pulse -i default \
-c:v libx264 -preset ultrafast -pix_fmt yuv420p -c:a aac call.mp4
Troubleshooting
Error: Input/output error or zero bytes (macOS)
Cause: Screen Recording permission not granted to the parent terminal. Solution: System Settings → Privacy & Security → Screen Recording → enable Terminal/iTerm/VS Code. Fully quit and relaunch the terminal (not just re-run). If still failing, reset TCC: tccutil reset ScreenCapture.
Error: Could not find video device with name [...] (Windows dshow)
Cause: Device name mismatch (case, trailing spaces, truncated). Solution: Run ffmpeg -list_devices true -f dshow -i dummy and copy the name verbatim, including spaces. Wrap in double quotes.
Error: x11grab ... BadMatch or black frame
Cause: $DISPLAY doesn't match, or running over SSH without -Y. Solution: export DISPLAY=:0.0 locally, or use ssh -Y and target the remote display.
Error: kmsgrab: Permission denied
Cause: kmsgrab requires CAPSYSADMIN. Solution: sudo ffmpeg ... or sudo setcap cap_sys_admin+ep $(which ffmpeg).
Output MP4 won't open / "moov atom not found"
Cause: ffmpeg was killed with SIGKILL / Ctrl+C mid-recording; MOOV never written. Solution: Always press q to stop. For future recordings use MKV, then ffmpeg -i in.mkv -c copy out.mp4. Try recovery: untrunc or ffmpeg -err_detect ignore_err -i broken.mp4 -c copy fixed.mp4.
Webcam stuck at 5fps (Linux)
Cause: v4l2 defaulted to raw YUYV; USB 2.0 bandwidth cap. Solution: Add -input_format mjpeg before -i /dev/video0.
Audio and video out of sync after a long recording
Cause: Clock drift between capture devices. Solution: Add -use_wallclock_as_timestamps 1 on each input, or record audio separately and mux after with -itsoffset to align.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: damionrashford
- Source: damionrashford/media-os
- License: MIT
- Homepage: https://damionrashford.github.io/media-os/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.