Skip to content

Instantly share code, notes, and snippets.

@devinschumacher
Last active October 17, 2025 21:04
Show Gist options
  • Select an option

  • Save devinschumacher/8024bc4693d79aef641b2c281e45d6cb to your computer and use it in GitHub Desktop.

Select an option

Save devinschumacher/8024bc4693d79aef641b2c281e45d6cb to your computer and use it in GitHub Desktop.
How to download Vimeo Videos (streaming via HLS / streamlink ... on skool)
tags: ['streamlink', 'ffmpeg', 'vimeo']

How to download Vimeo Videos (streaming via HLS / streamlink)

Vimeo embeds on Skool are streaming via HLS.

There are 2 methods for downloading these.

  1. Streamlink at the player.vimeo.com URL (w/ ffmpeg)
  2. yt-dlp at the m3u8 stream

Method 1: Streamlink at the player.vimeo.com URL (w/ ffmpeg)

Streamlink already knows that plugin and can grab the master manifest https://player.vimeo.com/video/<id>.

In this situation, the Vimeo URL is either in the DOM or maybe can be constructed just from the video ID.

And u use streamlink

👉 Or just get the Vimeo Video Downloader: https://serp.ly/vimeo-video-downloader

How to download a Vimeo video from the player.vimeo.com URL

  1. Get the player.vimeo.com URL from the DOM
  2. Use streamlink to download the media
  3. Use ffmpeg to remux the mp4 (optional)
  4. BONUS: Chain the commands together

❯ streamlink https://player.vimeo.com/video/1056875977 best -o ~/Desktop/vimeo_video.mp4

[cli][info] Found matching plugin vimeo for URL https://player.vimeo.com/video/1056875977
[stream.hls][warning] Unrecognized language for media playlist: language='en-x-autogen' name='English (auto-generated)'
[cli][info] Available streams: 240p (worst), 360p, 540p, 720p, 1080p (best)
[cli][info] Opening stream: 1080p (hls-multi)
[cli][info] Writing output to
/Users/devin/Desktop/vimeo_video.mp4
[utils.named_pipe][info] Creating pipe streamlinkpipe-5915-1-3021
[utils.named_pipe][info] Creating pipe streamlinkpipe-5915-2-7551
[cli][info] Stream ended
[cli][info] Closing currently open stream...
[download] Written 22.24 MiB to /Users/devin/Desktop/vimeo_video.mp4 (5s @ 4.26 MiB/s)

→ yields a transport stream (MPEG-TS) file.

And at least on a Mac that gives me an MP4 that WORKS but has no "preview" image on the .mp4 file like most videos.

What’s happening is that Streamlink is just writing the transport stream as-is into an MP4 container, but it doesn’t fully rebuild the metadata/index (the “moov atom”) that players rely on to show a thumbnail/preview and allow proper seeking.

It needs to be re-encoded or "remux'd. This can be done with with ffmpeg, which rewrites the container properly, which is why you get the preview.

❯ ffmpeg -i vimeo_video.mp4 output.mp4

ffmpeg version 7.1.1 Copyright (c) 2000-2025 the FFmpeg developers
  built with Apple clang version 17.0.0 (clang-1700.0.13.3)
  configuration: --prefix=/opt/homebrew/Cellar/ffmpeg/7.1.1_3 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags='-Wl,-ld_classic' --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libaribb24 --enable-libbluray --enable-libdav1d --enable-libharfbuzz --enable-libjxl --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libssh --enable-libsvtav1 --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox --enable-audiotoolbox --enable-neon
  libavutil      59. 39.100 / 59. 39.100
  libavcodec     61. 19.101 / 61. 19.101
  libavformat    61.  7.100 / 61.  7.100
  libavdevice    61.  3.100 / 61.  3.100
  libavfilter    10.  4.100 / 10.  4.100
  libswscale      8.  3.100 /  8.  3.100
  libswresample   5.  3.100 /  5.  3.100
  libpostproc    58.  3.100 / 58.  3.100
Input #0, mpegts, from 'vimeo_video.mp4':
  Duration: 00:00:56.45, start: 1.525000, bitrate: 3306 kb/s
  Program 1
    Metadata:
      service_name    : Service01
      service_provider: FFmpeg
  Stream #0:0[0x100]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(tv, bt709, progressive), 1920x1080, 24 fps, 24 tbr, 90k tbn
  Stream #0:1[0x101]: Audio: aac (LC) ([15][0][0][0] / 0x000F), 48000 Hz, mono, fltp, 192 kb/s
Stream mapping:
  Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))
  Stream #0:1 -> #0:1 (aac (native) -> aac (native))
Press [q] to stop, [?] for help
[libx264 @ 0x132005100] using cpu capabilities: ARMv8 NEON
[libx264 @ 0x132005100] profile High, level 4.0, 4:2:0, 8-bit
[libx264 @ 0x132005100] 264 - core 164 r3108 31e19f9 - H.264/MPEG-4 AVC codec - Copyleft 2003-2023 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=24 lookahead_threads=4 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=24 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
Output #0, mp4, to 'output.mp4':
  Metadata:
    encoder         : Lavf61.7.100
  Stream #0:0: Video: h264 (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1920x1080, q=2-31, 24 fps, 12288 tbn
      Metadata:
        encoder         : Lavc61.19.101 libx264
      Side data:
        cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A
  Stream #0:1: Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, mono, fltp, 69 kb/s
      Metadata:
        encoder         : Lavc61.19.101 aac
[out#0/mp4 @ 0x6000034d8000] video:13954KiB audio:486KiB subtitle:0KiB other streams:0KiB global headers:0KiB muxing overhead: 0.268337%
frame= 1351 fps=269 q=-1.0 Lsize=   14479KiB time=00:00:56.20 bitrate=2110.2kbits/s speed=11.2x
[libx264 @ 0x132005100] frame I:6     Avg QP:15.36  size: 83066
[libx264 @ 0x132005100] frame P:420   Avg QP:17.98  size: 22378
[libx264 @ 0x132005100] frame B:925   Avg QP:24.00  size:  4747
[libx264 @ 0x132005100] consecutive B-frames:  4.3%  6.5% 20.2% 69.0%
[libx264 @ 0x132005100] mb I  I16..4: 25.3% 57.5% 17.2%
[libx264 @ 0x132005100] mb P  I16..4:  5.0% 11.5%  0.5%  P16..4: 17.6%  7.1%  3.8%  0.0%  0.0%    skip:54.5%
[libx264 @ 0x132005100] mb B  I16..4:  0.2%  0.4%  0.0%  B16..8: 21.5%  2.2%  0.5%  direct: 0.5%  skip:74.7%  L0:50.1% L1:45.7% BI: 4.2%
[libx264 @ 0x132005100] 8x8 transform intra:66.9% inter:67.7%
[libx264 @ 0x132005100] coded y,uvDC,uvAC intra: 28.1% 20.1% 3.2% inter: 4.0% 3.2% 0.0%
[libx264 @ 0x132005100] i16 v,h,dc,p: 47% 20% 22% 11%
[libx264 @ 0x132005100] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 36% 11% 43%  1%  2%  2%  2%  1%  1%
[libx264 @ 0x132005100] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 31% 18% 21%  4%  6%  6%  5%  4%  5%
[libx264 @ 0x132005100] i8c dc,h,v,p: 75% 11% 13%  1%
[libx264 @ 0x132005100] Weighted P-Frames: Y:0.0% UV:0.0%
[libx264 @ 0x132005100] ref P L0: 74.5%  9.0% 12.0%  4.5%
[libx264 @ 0x132005100] ref B L0: 91.8%  7.0%  1.1%
[libx264 @ 0x132005100] ref B L1: 97.3%  2.7%
[libx264 @ 0x132005100] kb/s:2021.68
[aac @ 0x132190030] Qavg: 251.876

→ remux/transcode into a playable MP4.

and it works!

This particular video is public (or playable without cookies); streamlink’s Vimeo plugin can fetch the manifest via Vimeo’s API without your session.

streamlink itself handles the necessary requests under the hood, so you didn’t need to supply headers for this case.

For private or cookie-dependent videos, streamlink does need the headers/cookies (via --http-header or --http-cookie). So while the bare command happened to succeed here,

We’ll still want our automation to capture and forward headers for the locked-down cases.

👉 Get Vimeo Video Downloader: https://serp.ly/vimeo-video-downloader

Bonus: Chain the commands together

streamlink -O "URL" best --stream-segment-threads 5 | ffmpeg -i pipe:0 -c copy -movflags +faststart ~/Desktop/vimeo.mp4

👉 Try the Vimeo Video Downloader

Method 2: yt-dlp at the m3u8 stream

1. Open DevTools & Play the Video

  • Open the page with the Vimeo embed (or player.vimeo.com/video/...).
  • Open Chrome DevTools → Network tab.
  • Click Media filter (optional but helpful).
  • Play the video so the actual streaming requests fire.

2. Search for the Right Requests

  • In the filter box, type m3u8.

    • You’ll see requests like:

      .../playlist/av/primary/prot/.../playlist.m3u8?...sf=fmp4
      
  • Optional: search vtt if you want subtitles.

  • Optional: search json if you want the DASH manifests (but m3u8 is usually simpler).

3. Copy the URL

  • Right-click the .m3u8 request → Copy → Copy link address.
  • If you need headers (for referer/cookies), use Copy → Copy as cURL (bash).

4. Run yt-dlp

Paste the .m3u8 URL into this command:

yt-dlp --referer "https://player.vimeo.com/" \
  -f "bv*+ba/best" \
  --merge-output-format mp4 \
  --remux-video mp4 \
  --postprocessor-args "ffmpeg:-movflags +faststart" \
  "PASTE_M3U8_URL_HERE"
  • This grabs the best AVC video + audio, merges them, and saves as MP4.
  • Swap in -f "bv*[height=1080][vcodec*=avc1]+ba/best" if you want a specific quality.

👉 Try the Vimeo Video Downloader

📥 Downloading Long Vimeo Videos (DASH Streams) with yt-dlp

Vimeo streams video in two possible ways:

  • HLS → uses .m3u8 playlists and .ts/fragmented MP4 segments.
  • DASH → uses a playlist.json manifest and .m4s segments.

🔍 Step 1 — Identify What You’re Dealing With

  • Open DevTools → Network.
  • If you filter for m3u8 and see manifests → that video is using HLS.
  • If you only see playlist.json and .m4s segment requests → that video is using DASH.

👉 In my case:

  • I didn’t see any .m3u8 in the Network tab.
  • I did see playlist.json and lots of .m4s segments.
  • That means the embed is DASH-only.
  • ffmpeg can’t parse Vimeo’s JSON directly, but yt-dlp can (it knows how to read the DASH JSON and reassemble the streams).

🔑 Step 2 — Get the Player Page URL

  • Filter for config in DevTools.

  • You’ll find a request like:

    https://player.vimeo.com/video/519981982/config?...
    
  • The video ID here is 519981982.

  • Strip the /config?... part → the stable player URL is:

    https://player.vimeo.com/video/519981982
    

This URL doesn’t expire, unlike the signed segment URLs.

Screenshot 2025-09-26 at 09 58 01

🛠 Step 3 — Download with yt-dlp

Run yt-dlp against the player page with a referer and concurrency:

yt-dlp --referer "https://player.vimeo.com/video/519981982" \
  -N 15 -S "codec:avc,res,ext" \
  --merge-output-format mp4 --remux-video mp4 \
  --postprocessor-args "ffmpeg:-movflags +faststart" \
  "https://player.vimeo.com/video/519981982"

⚙️ Step 4 — What Each Flag Does

  • --referer → Vimeo requires this header.
  • -N 15 → download 15 fragments in parallel (much faster for long videos).
  • -S "codec:avc,res,ext" → prefer AVC (MP4) over VP9/WebM.
  • --merge-output-format mp4 → final file will always be MP4.
  • --remux-video mp4 → repackage without re-encoding.
  • --postprocessor-args "ffmpeg:-movflags +faststart" → optimize MP4 for instant playback.

⚡ Tips

  • If it fails: signed URLs (exp=...) expired → reload and grab a fresh /video/<ID>/config.

  • Private videos: use your browser cookies:

    yt-dlp --cookies-from-browser chrome "https://player.vimeo.com/video/<ID>"
  • Maximum speed: install aria2c and run with:

    yt-dlp --downloader aria2c \
      --downloader-args "aria2c:-x 16 -s 16 -k 1M" \
      "https://player.vimeo.com/video/<ID>"

Summary:

  • Filter for config in DevTools to get the video ID.
  • Build the stable /video/<ID> URL.
  • Since no .m3u8 appears, this is DASH (playlist.json + .m4s).
  • Use yt-dlp with concurrency to fetch and merge into MP4.

ARIA 2 🔥

brew install aria2

That gives you the aria2c binary, which yt-dlp can use as an external downloader.

Then you can run your Vimeo command with aria2c for maximum speed:

yt-dlp --referer "https://player.vimeo.com/video/519981982" \
  --downloader aria2c \
  --downloader-args "aria2c:-x 16 -s 16 -k 1M" \
  -S "codec:avc,res,ext" \
  --merge-output-format mp4 --remux-video mp4 \
  --postprocessor-args "ffmpeg:-movflags +faststart" \
  "https://player.vimeo.com/video/519981982"

What those args mean:

  • -x 16 → up to 16 connections per download
  • -s 16 → split into 16 segments
  • -k 1M → segment size (1 MB)

⚡ This will usually max out your bandwidth on long Vimeo videos.

ytdlp

vs.

aria2c

👉 Try the Vimeo Video Downloader

📥 Downloading Password-Protected Vimeo Videos with yt-dlp

Vimeo can serve video in a few different ways:

  • HLS (.m3u8 playlists + .ts/fragmented MP4 segments)
  • DASH (playlist.json + .m4s segments)
  • Inline config (window.playerConfig JSON in the DOM)

1. Player Page vs. Manifests

Normally, you’d find the video ID by filtering for /config, .m3u8, or playlist.json in DevTools → Network. But:

⚠️ Sometimes Vimeo embeds don’t request /config, .m3u8, or playlist.json over the network. Instead, the player bootstraps with inline JSON in the DOM (often under window.playerConfig). In that case, you won’t see a manifest in the Network tab — but yt-dlp can still parse it cleanly if you point it at the /video/<ID> page.

Example player page:

https://player.vimeo.com/video/1097353467

This URL is stable and is what you should use with yt-dlp.


2. Password-Protected Videos

If the video is protected, yt-dlp will error with:

ERROR: This video is protected by a password, use the --video-password option

You must provide the password that unlocks the video on the embed page.


3. Correct yt-dlp Command

In zsh, watch out for special characters in passwords (like !). Wrap them in single quotes so the shell doesn’t interpret them.

yt-dlp \
  --video-password 'XXXXXXXXXXXX' \
  --referer 'https://shiatsuapos.com/55-convegno-nazionale-apos' \
  -N 20 -S 'codec:avc,res,ext' \
  --merge-output-format mp4 --remux-video mp4 \
  --postprocessor-args "ffmpeg:-movflags +faststart" \
  'https://player.vimeo.com/video/1097353467'

4. What Each Flag Does

  • --video-password → unlocks password-protected videos.
  • --referer → required when the video is embedded on another site.
  • -N 20 → downloads 20 fragments in parallel for speed.
  • -S "codec:avc,res,ext" → prefers AVC/MP4 over WebM/VP9.
  • --merge-output-format mp4 --remux-video mp4 → ensures clean MP4 output.
  • --postprocessor-args "ffmpeg:-movflags +faststart" → optimizes MP4 for instant playback.

5. Troubleshooting

  • Password wrong → Vimeo won’t serve the manifest, and yt-dlp will stall or error.

  • No manifests in Network → that’s expected for inline JSON embeds. Use the /video/<ID> URL with yt-dlp.

  • Private or login-only videos → add cookies:

    yt-dlp --cookies-from-browser chrome 'https://player.vimeo.com/video/<ID>'
  • Slow downloads → increase -N or install aria2c for external downloading:

    brew install aria2
    yt-dlp --downloader aria2c --downloader-args "aria2c:-x 16 -s 16 -k 1M" ...

Summary:

  • Sometimes Vimeo videos hide manifests (config, .m3u8, .json) and instead use inline playerConfig JSON in the DOM.
  • You won’t see streams in Network — but yt-dlp handles this automatically if you give it the player page URL.
  • For password-protected videos, add --video-password 'PASSWORD'.
  • Use concurrency (-N or aria2c) for faster downloads.

Here’s a write-up focused on your last question — how to add speed to your yt-dlp command when downloading a password-protected Vimeo video:


⚡ Speeding Up Password-Protected Vimeo Downloads with yt-dlp

By default, yt-dlp downloads HLS/DASH video one fragment at a time. For long Vimeo videos this can feel very slow. You can dramatically accelerate downloads using concurrency or an external downloader.


1. Baseline Command (Password + Referer)

yt-dlp \
  --video-password 'CNVG_55_APOS2025!' \
  --referer 'https://shiatsuapos.com/55-convegno-nazionale-apos' \
  'https://player.vimeo.com/video/1097413677'

That works, but it’s single-threaded.


2. Add Parallel Fragment Downloads

Use the -N flag (number of parallel fragment fetches):

yt-dlp \
  --video-password 'CNVG_55_APOS2025!' \
  --referer 'https://shiatsuapos.com/55-convegno-nazionale-apos' \
  -N 20 \
  'https://player.vimeo.com/video/1097413677'
  • -N 20 → up to 20 fragments at once (safe sweet spot: 8–32).
  • More concurrency = faster downloads, but too high can cause throttling or errors.

3. Use an External Downloader (aria2c)

For even better performance, let yt-dlp hand fragments to aria2c, a high-speed segmented downloader.

Install aria2 (macOS/Homebrew):

brew install aria2

Run with yt-dlp:

yt-dlp \
  --video-password 'CNVG_55_APOS2025!' \
  --referer 'https://shiatsuapos.com/55-convegno-nazionale-apos' \
  --downloader aria2c \
  --downloader-args "aria2c:-x 16 -s 16 -k 1M" \
  'https://player.vimeo.com/video/1097413677'
  • -x 16 → max 16 connections per file.
  • -s 16 → split into 16 segments.
  • -k 1M → piece size (1 MB chunks).

This usually maxes out your available bandwidth.


4. Keep MP4 Optimized

If you want to ensure smooth playback (seekable file):

yt-dlp \
  --video-password 'CNVG_55_APOS2025!' \
  --referer 'https://shiatsuapos.com/55-convegno-nazionale-apos' \
  -N 20 \
  --merge-output-format mp4 --remux-video mp4 \
  --postprocessor-args "ffmpeg:-movflags +faststart" \
  'https://player.vimeo.com/video/1097413677'
  • --merge-output-format mp4 --remux-video mp4 → ensures final MP4.
  • --postprocessor-args "ffmpeg:-movflags +faststart" → moves metadata to front for instant playback.

Vimeo download post-mortem: what went wrong & the fix

TL;DR (what finally worked)

yt-dlp -N 16 \
  --cookies-from-browser "chrome:Profile 197" \
  --referer "https://vimeo.com/1118190054/173a644b6d" \
  -S "codec:avc,res,ext" \
  --merge-output-format mp4 --remux-video mp4 \
  --postprocessor-args "ffmpeg:-movflags +faststart" \
  "https://vimeo.com/1118190054/173a644b6d"

Why: the video plays on Vimeo only when logged in, so yt-dlp needed your logged-in cookies and the exact page referer.


What each error really meant

1) zsh: no matches found: https://player.vimeo.com/video/1118190054?h=...

  • Cause: Unquoted URL with ? in zsh → shell tried to glob.
  • Fix: Always quote URLs with ? (single or double quotes).

2) streamlink ... 403 Forbidden

  • Cause: Streamlink hit player.vimeo.com/video/<id> without the right referer/cookies (and the video wasn’t public to unauthenticated clients).
  • Fix: Streamlink can work if you supply the embedding page referer and cookies, but Vimeo auth flows are more reliable with yt-dlp.

3) You passed the /config URL to yt-dlp

https://player.vimeo.com/video/1118190054/config?... 
  • Cause: /config is a JSON bootstrap (player settings), not a media URL. The generic extractor downloaded a tiny JSON and failed to remux it → ERROR: Postprocessing: Invalid data found when processing input.
  • Fix: Don’t feed /config to downloaders. Use the public Vimeo page (https://vimeo.com/<id>/<hash>) or player.vimeo.com/video/<id> (only if embed-only and you have the embedding page referer).

4) Cannot download embed-only video without embedding URL

  • Meaning: yt-dlp detected settings that look like embed-only and demanded the embedding page.
  • In your case: the clip does play on Vimeo.com, so it isn’t strictly embed-only; the message appeared because the input/headers didn’t match a valid public Vimeo flow.
  • Fix: Use the public vimeo.com page URL with the correct referer/cookies (see final command).

5) The web client only works when logged-in. Use --cookies...

  • Meaning: The Vimeo page is viewable only when you’re signed in. yt-dlp, running headless, has no session by default.

  • Fix: Provide your browser session cookies:

    • --cookies-from-browser "chrome:Profile 197" (or Default, Profile 1, etc.; check chrome://profile-internals).

6) Impersonation warning

The extractor is attempting impersonation, but no impersonate target is available...
  • Meaning: yt-dlp optionally uses a “browser-like” HTTP stack (impersonation) for some sites. Your install lacked the optional deps.

  • Usually you don’t need it once cookies + referer are correct. If you ever do:

    • python3 -m pip install -U "yt-dlp[default]" curl_cffi brotli certifi
    • Then you can add --impersonate chrome124 if extraction fails without it.

The correct way to tackle this class of Vimeo videos

  1. Decide the right URL to pass to yt-dlp

    • If it plays on vimeo.com while you’re logged in → use the public page URL https://vimeo.com/<id>/<hash>.
    • If it doesn’t play on vimeo.com but plays embedded on some site → you must use the embedding page URL and set it as both the URL and --referer.
  2. Send the exact referer

    • --referer "https://vimeo.com/<id>/<hash>" (or the real embedding page URL).
  3. Provide auth if required

    • Login-required → --cookies-from-browser "chrome:Profile X".
    • Password-protected → add --video-password '...' (you’ll still want the cookies if the page requires login too).
  4. Quality & output

    • -S "codec:avc,res,ext" biases toward MP4/AVC (good for compatibility).
    • --remux-video mp4 --merge-output-format mp4 --postprocessor-args "ffmpeg:-movflags +faststart" → single, seekable MP4 without re-encoding.
    • -N 16 → parallel fragments for speed.
  5. (Optional) Inspect formats first

    yt-dlp --cookies-from-browser "chrome:Profile 197" \
      --referer "https://vimeo.com/<id>/<hash>" \
      -F "https://vimeo.com/<id>/<hash>"

Quick “decision tree” for Vimeo

  • Public on vimeo.com → use the vimeo.com URL + (if needed) cookies.
  • 🔑 Password-protected → add --video-password.
  • 🔒 Login-required → add --cookies-from-browser "chrome:Profile X".
  • 🌐 Embed-only → you must use the actual embedding page URL as the main URL and referer (cookies if gated).
  • Don’t feed /config?... to downloaders.

Copy-paste templates

Public / Login-required on vimeo.com

yt-dlp -N 16 \
  --cookies-from-browser "chrome:Profile 197" \
  --referer "https://vimeo.com/<ID>/<HASH>" \
  -S "codec:avc,res,ext" \
  --merge-output-format mp4 --remux-video mp4 \
  --postprocessor-args "ffmpeg:-movflags +faststart" \
  "https://vimeo.com/<ID>/<HASH>"

Password-protected (add password)

yt-dlp -N 16 \
  --cookies-from-browser "chrome:Profile 197" \
  --video-password 'PASSWORD' \
  --referer "https://vimeo.com/<ID>/<HASH>" \
  -S "codec:avc,res,ext" \
  --merge-output-format mp4 --remux-video mp4 \
  --postprocessor-args "ffmpeg:-movflags +faststart" \
  "https://vimeo.com/<ID>/<HASH>"

Embed-only (needs the actual site that embeds it)

EMBED_URL='https://allowed-site.example/video-page'
yt-dlp -N 16 \
  --cookies-from-browser "chrome:Profile 197" \
  --referer "$EMBED_URL" \
  --add-header "Origin: https://allowed-site.example" \
  -S "codec:avc,res,ext" \
  --merge-output-format mp4 --remux-video mp4 \
  --postprocessor-args "ffmpeg:-movflags +faststart" \
  "$EMBED_URL"

Why this one felt “weird”

  • You hopped across three different URL types (player, /config, vimeo.com/<id>/<hash>), each with different rules.
  • The video required login on Vimeo, so cookies were mandatory.
  • The tools’ messages were accurate but easy to misread out of order (embed-only vs. login-required).
  • Quoting/headers matter: zsh needed quotes; Vimeo needed referer (and sometimes origin) to pass checks.

Once you give yt-dlp the right page URL, plus your browser cookies and the exact referer, Vimeo behaves.

🔍 Vimeo delivery modes

  1. Public (unlisted or listed)

    • You can hit the vimeo.com/<id>/<hash> page and play without an account.
    • But internally, Vimeo still calls their “web client” API (which often requires a session cookie, even for public videos).
    • If yt-dlp only uses that API, it sees “login required” unless cookies are present.
  2. Config/Embed API

    • The player.vimeo.com/video/<id>/config JSON contains stream manifests.
    • This works for public videos without login.
    • yt-dlp normally knows how to grab this, but sometimes it defaults to the web client path first.

✅ Why --cookies-from-browser fixed it

Even though you weren’t logged in, by giving yt-dlp your browser cookies, you also gave it:

  • Session info showing you had accepted Vimeo’s cookie banner.
  • Region/consent headers Vimeo expects from a real browser.
  • Any tracking/session IDs Vimeo needs to let the “web client” flow succeed.

So yt-dlp stopped complaining, because with cookies it could complete the API call.


📌 Key point

  • You didn’t need a Vimeo account login.
  • yt-dlp just needed some browser cookies to satisfy Vimeo’s API (consent/session stuff).

That’s why it was weird: the video looked public, but Vimeo’s backend still enforced “web client requires cookies” logic.


Alright, here’s the refined Vimeo write-up that clears up the “required login” confusion and explains the distinction 👇


📥 Vimeo Download Cases with yt-dlp

Vimeo videos can behave differently depending on the uploader’s privacy settings. Sometimes a video is public (anyone can watch it on vimeo.com), but yt-dlp still errors out with messages like:

The web client only works when logged-in. Use --cookies...

Here’s why that happens, and how to handle each case.


🔑 Vimeo privacy modes & what yt-dlp needs

1. Public (listed/unlisted)

  • ✅ Plays directly on https://vimeo.com/<id>/<hash> with no account.

  • ⚠️ yt-dlp may still need browser cookies because Vimeo’s “web client” API refuses to serve streams without a session cookie (even for public videos).

  • Fix:

    yt-dlp -N 16 \
      --cookies-from-browser "chrome:Profile 197" \
      --referer "https://vimeo.com/<id>/<hash>" \
      -S "codec:avc,res,ext" \
      --merge-output-format mp4 --remux-video mp4 \
      --postprocessor-args "ffmpeg:-movflags +faststart" \
      "https://vimeo.com/<id>/<hash>"

2. Password-protected

  • 🔒 Plays only after you type a password.

  • Fix: add --video-password 'PASSWORD' (cookies still help).

    yt-dlp -N 16 \
      --cookies-from-browser "chrome:Profile 197" \
      --video-password 'SECRET' \
      --referer "https://vimeo.com/<id>/<hash>" \
      "https://vimeo.com/<id>/<hash>"

3. Login-required

  • 🔑 Plays only when signed in with a Vimeo account that has access.

  • Fix: you must supply logged-in cookies from your browser profile.

    yt-dlp --cookies-from-browser "chrome:Profile 197" "https://vimeo.com/<id>/<hash>"

4. Embed-only

  • 🌐 Doesn’t play on vimeo.com at all. Only plays embedded on specific whitelisted domains.

  • Fix: you need the embedding site’s URL and use it as both the URL and referer:

    EMBED_URL="https://example.com/page-with-vimeo-embed"
    yt-dlp --referer "$EMBED_URL" "$EMBED_URL"

⚡ Why “cookies” helped even though no login

  • Vimeo’s API often refuses to serve streams without basic browser state (consent banner, session tokens, geo headers).
  • Even for public videos, yt-dlp without cookies = blocked API.
  • With --cookies-from-browser, yt-dlp looks like your browser and passes those small session values, so it works.
  • 👉 You weren’t required to log in; you just had to look like a real viewer.

📝 Best practice for Vimeo

  1. Always start with the public page URL (https://vimeo.com/<id>/<hash>).
  2. Add --referer pointing to that same page.
  3. Use --cookies-from-browser "chrome:Profile X" if you get login/cookie errors — even if you’re not logged in.
  4. Add --video-password if it prompts for one.
  5. For embed-only, use the real embedding site’s URL.

✅ That’s why your case felt “weird”: the video was publicly viewable, but yt-dlp still needed cookies from your browser to satisfy Vimeo’s API. Once you passed those, it downloaded fine.

How to download password protected Vimeo videos - not embedded, stream

Steps

  1. visit page & enter the password
  2. find your chrome profile number
  3. construct the correct command

1. visit page & enter the password

Go to the Vimeo URL & enter the password.

2. find your chrome profile number

  1. visit chrome://profile-internals/
  2. expand your profile & grab the number
image

3. construct the correct command

You will need:

  1. https://vimeo.com/{VIDEO_ID}
  2. chrome profile number
  3. password

Your command syntax:

yt-dlp \
  'https://vimeo.com/{VIDEO_ID}' \
  --cookies-from-browser "chrome:Profile {NUMBER}" \
  --video-password '{PASSWORD}' \
  -N 20 \
  -S 'codec:avc,res,ext' \
  --merge-output-format mp4 \
  --remux-video mp4 \
  --postprocessor-args "ffmpeg:-movflags +faststart"

et voila!

image

it hath downloaded

image
Start
  ↓
Can you play the video on vimeo.com without logging in?
  ├─ Yes → Public (but may need cookies)
  │    Command:
  │    yt-dlp -N 16 \
  │      --cookies-from-browser "chrome:Profile 197" \
  │      --referer "https://vimeo.com/<id>/<hash>" \
  │      -S "codec:avc,res,ext" \
  │      --merge-output-format mp4 --remux-video mp4 \
  │      --postprocessor-args "ffmpeg:-movflags +faststart" \
  │      "https://vimeo.com/<id>/<hash>"
  │
  └─ No → Continue
        ↓
Does Vimeo ask for a password?
  ├─ Yes → Password-protected
  │    Command:
  │    yt-dlp -N 16 \
  │      --cookies-from-browser "chrome:Profile 197" \
  │      --video-password 'PASSWORD' \
  │      --referer "https://vimeo.com/<id>/<hash>" \
  │      "https://vimeo.com/<id>/<hash>"
  │
  └─ No → Continue
        ↓
Does Vimeo play only when logged into your Vimeo account?
  ├─ Yes → Login-required
  │    Command:
  │    yt-dlp -N 16 \
  │      --cookies-from-browser "chrome:Profile 197" \
  │      --referer "https://vimeo.com/<id>/<hash>" \
  │      "https://vimeo.com/<id>/<hash>"
  │
  └─ No → Continue
        ↓
Does the video not play on Vimeo at all but plays embedded on another site?
  ├─ Yes → Embed-only
  │    Command:
  │    EMBED_URL="https://example.com/page-with-vimeo-embed"
  │    yt-dlp -N 16 \
  │      --cookies-from-browser "chrome:Profile 197" \
  │      --referer "$EMBED_URL" \
  │      --add-header "Origin: https://example.com" \
  │      "$EMBED_URL"
  │
  └─ End
@devinschumacher
Copy link
Author

👉 Get Vimeo Video Downloader: https://serp.ly/vimeo-video-downloader

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment