Ffmpeg capture
Record from capture devices with ffmpeg: screen/desktop (avfoundation on macOS, gdigrab/dshow on Windows, x11grab/kmsgrab on Linux), webcam (avfoundation/dshow/v4l2), microphone, system audio, and list available devices. Use when the user asks to record the screen, capture desktop, record webcam, record microphone, screencast, grab a region of the screen, capture system audio, or list input devices.From its SKILL.md
npx -y skills add damionrashford/media-os --skill ffmpeg-captureAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 17 stars17 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
12.6 KB, as published. Nobody here has run it
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+ (seereferences/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 ":<blackhole_index>". 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.mdfor 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 CAP_SYS_ADMIN.
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.