Facebook Videos Downloader
Download a public Facebook video (or a Page's videos) into a user-specified folder, named by upload datetime.
Facebook is a single-engine yt-dlp target — like youtube-videos, not instagram-videos. No browser mirror and no third-party fallback API. The one real difference: Facebook is the most login-walled platform, so the primary resilience path is --cookies-from-browser (your own logged-in session), NOT a second engine.
Inputs
| Parameter | Required | Example |
|---|---|---|
| Facebook URL (watch, video, reel, fb.watch, or Page videos tab) | yes | https://www.facebook.com/watch/?v=10154325234224113 |
| Output directory | yes | out/facebook |
| Max height | no | 1080 (default) |
| Timezone for filenames | no | America/Santiago (default) |
Accepted URL shapes (the script handles them via yt-dlp):
- Watch link:
facebook.com/watch/?v=ID→ single video - Video permalink:
facebook.com/<page>/videos/ID/→ single video - Reel:
facebook.com/reel/ID→ single video - Share short link:
fb.watch/xxxx/→ yt-dlp resolves it - Page videos tab:
facebook.com/<page>/videos→ a playlist (use--max 1for just the first)
Prerequisites
yt-dlp AND ffmpeg must be installed (both present on this machine). ffmpeg is needed when Facebook serves separate video+audio (DASH) streams that must be merged. Keep yt-dlp fresh — an outdated yt-dlp is the #1 cause of Facebook failures:
yt-dlp --version || python3 -m pip install -U yt-dlp
ffmpeg -version | head -1
Workflow checklist
- [ ] Step 1: Verify the video is public
- [ ] Step 2: (optional) Pre-fetch listing to review before downloading
- [ ] Step 3: Run download script
- [ ] Step 4: Report results
Step 1: Verify it's public
Open the URL. If it sits behind a login wall, is friends-only, or is unavailable in the region, this skill can still work if you pass your own cookies (see Step 3 / cookies note). For truly private content you don't have access to, stop.
Step 2 (optional): Pre-fetch the listing
The script lists internally, but for a Page you can inspect first:
yt-dlp --flat-playlist -J "https://www.facebook.com/<page>/videos" > listing.json
listing.json has an entries array of {id, url, title} (dates are resolved during download). Pass it back with --listing-json to download exactly that set.
Step 3: Download and rename
python3 .cursor/skills/facebook-videos/scripts/download_facebook_videos.py \
--url "https://www.facebook.com/watch/?v=10154325234224113" \
--output-dir out/facebook \
--max-height 1080 \
--timezone America/Santiago
The script:
- Lists entries via
yt-dlp --flat-playlist(a single watch/video/reel URL → one entry; a Page videos tab → a playlist), dedupes by id/URL - Downloads each video (best ≤
--max-height, falling back to absolute best so Facebook'shd/sdprogressive formats always download), merged to mp4 - Reads the exact upload
timestamp(orupload_date) from each video'sinfo.json - Renames to
YYYY-MM-DD_HH-MM-SS.mp4in the chosen timezone (date-only →..._00-00-00; no date →unknown_<id>.mp4) - Maintains
download-archive.txtin the output dir so re-runs resume instead of re-downloading - Writes
{output_dir}/videos_manifest.json(id, url, title, timestamp, uploadDate, height, file)
To grab only the first video of a Page's videos tab, add --max 1.
Useful flags: --max N (cap count), --cookies-from-browser chrome, --max-height 720, --no-archive, --delay-ms.
Collisions: two videos at the same second get _2, _3 suffixes.
Step 4: Report to user
Summarize: URL, output path, count downloaded / skipped / failed, and list filenames sorted newest-first. Datetime naming means a long Facebook caption never becomes the filename (the caption is kept in the manifest title instead).
Login-walled videos (the Facebook gotcha)
If the run prints No videos found or yt-dlp errors with "log in" / "cookies" / "not available", the video is gated to logged-in users. Pass cookies from a browser where you're signed in to Facebook:
python3 .cursor/skills/facebook-videos/scripts/download_facebook_videos.py \
--url "https://www.facebook.com/watch/?v=ID" \
--output-dir out/facebook \
--cookies-from-browser chrome
--cookies-from-browser accepts chrome, firefox, safari, edge, brave. The script auto-appends a hint to error messages reminding you to retry with cookies.
Why no fallback engine
| Platform | Listing | Download | Resilience |
|---|---|---|---|
| browser scrape (Picnob) | Picnob/ImgInn | direct tools 401 | |
| TikTok | yt-dlp flat-playlist | yt-dlp + tikwm fallback | yt-dlp signing breaks intermittently |
| YouTube | yt-dlp | yt-dlp | keep yt-dlp fresh; cookies for bot-gate |
| yt-dlp | yt-dlp | --cookies-from-browser for login walls |
Anti-patterns
- Do not hand-scrape the Facebook page HTML —
yt-dlpalready extracts the video. (Unlike Instagram.) - Do not run with a stale yt-dlp. Update it before blaming the site.
- Do not assume failure means impossible — try
--cookies-from-browserfirst; Facebook gates much more aggressively than YouTube. - Do not download private / friends-only content you don't have access to.
- Do not forget ffmpeg — without it, DASH merges fail.
- Do not commit large
out/video folders — add the output dir to.gitignore.
Utility scripts
| Script | Purpose |
|---|---|
scripts/download_facebook_videos.py |
List + download (yt-dlp, mp4) + datetime rename + archive resume + manifest (main entry point) |
Additional resources
- Real run example: examples.md
- For YouTube (channels/playlists/Shorts), use the
youtube-videosskill. - For TikTok (yt-dlp + tikwm fallback), use the
tiktok-videosskill. - For Instagram (needs a browser mirror), use the
instagram-videosskill.