hls-grab
v1.0.0
Published
Zero-dependency Node.js CLI to download an HLS (.m3u8) stream from a CDN and mux it into a single video file.
Maintainers
Readme
hls-grab
A small, zero-dependency Node.js CLI that downloads an HLS stream from a CDN —
given the .m3u8 URL — and saves it as a single video file.
It parses the master playlist, picks a variant (quality), downloads every
media segment in parallel, decrypts AES-128 segments if needed, and remuxes the
result into a clean .mp4 with ffmpeg (stream copy — no re-encoding).
Requirements
- Node.js ≥ 18 (uses the built-in
fetch— nonpm installneeded) - ffmpeg on your
PATH(optional but recommended; used to remux to.mp4and to merge a separate audio track). Without it, the raw concatenated stream is kept instead.
Install
Run it directly from the repo:
node index.js <master.m3u8 URL> [options]Or install it globally to get an hls-grab command anywhere:
npm i -g . # from inside the repo
hls-grab <master.m3u8 URL> [options]Usage
node index.js <master.m3u8 URL> [options]Options
| Option | Description |
| --- | --- |
| -o, --output <file> | Output path (default: ./<name>.mp4) |
| -q, --quality <best\|worst> | Variant to pick from a master playlist (default: best) |
| -r, --resolution <px> | Pick a variant by height, e.g. 720 or 1080 (overrides --quality) |
| -c, --concurrency <n> | Parallel segment downloads (default: 6) |
| -H, --header <"K: V"> | Extra request header, repeatable (cookies, auth, referer) |
| --no-ffmpeg | Skip remux; keep the raw concatenated .ts/.mp4 stream |
| --keep-temp | Keep the temp segment directory (debugging) |
| -l, --list | List the variants in a master playlist and exit |
| -h, --help | Show help |
Examples
# Best quality, default output name
node index.js 'https://cdn.example.com/video/master.m3u8'
# Inspect what qualities are available
node index.js 'https://cdn.example.com/master.m3u8' --list
# Pick 720p, custom output, more parallelism
node index.js 'https://cdn.example.com/master.m3u8' -r 720 -o movie.mp4 -c 10
# Pass auth/cookies for a protected CDN
node index.js 'https://cdn.example.com/master.m3u8' \
-H 'Cookie: session=abc123' \
-H 'Referer: https://example.com/'What it handles
- Master playlists (variant selection by
best/worst/ target resolution) - Media playlists (plain MPEG-TS and fragmented-MP4 / CMAF via
EXT-X-MAP) - AES-128 encrypted segments (
EXT-X-KEY), including key fetching/caching and both explicit and sequence-derived IVs - Byte-range segments (
EXT-X-BYTERANGE) - A separate audio rendition (
EXT-X-MEDIA TYPE=AUDIO) — downloaded and merged with the video byffmpeg - Relative segment/key URIs (resolved against the playlist URL)
- A browser-like
User-Agentby default (overridable with-H) - Retries with exponential backoff, and resume of partially-downloaded runs
- Live playlists: downloads the current segment window (no
EXT-X-ENDLIST) and warns
Not supported
SAMPLE-AES/ FairPlay / Widevine DRM (these are protected by design)- Continuously following a live stream until it ends
Working with signed CDN URLs
Many CDNs hand out signed, time-limited playlist URLs (with a hash/token in
the path or query). A few things that save headache:
- Always wrap the URL in single quotes. Signed URLs are full of
=,&,,characters. Double quotes (or no quotes) let the shell mangle them — a stray backslash before=is the most common cause of a sudden403. (The tool now strips stray backslashes defensively and warns, but single-quoting is the fix.) - Copy the URL from the same machine/network you'll download on. Tokens are often bound to your IP, so a link grabbed elsewhere will be rejected.
- Use it promptly. Once past the token's expiry, only a fresh URL works.
- Grab it from the browser's Network tab (filter
m3u8), or use Copy → Copy as cURL to also capture theCookie/Refererheaders, then pass them with-H.
Common errors
| Status | Meaning | Fix |
| --- | --- | --- |
| 403 Forbidden | Bad/expired/missing signature, wrong IP, or a shell-mangled URL | Single-quote the URL (no backslashes); re-copy a fresh link from the same machine |
| 470 (non-standard) | CDN rejected the request: token required / segments unsigned | Feed the signed playlist the browser actually loads, and pass its Cookie/Referer via -H |
Layout
index.js # CLI parsing + orchestration
lib/parser.js # m3u8 parsing (master + media), variant selection
lib/download.js # fetch + retries, concurrency pool, AES-128, assembly
lib/mux.js # ffmpeg remux / audio+video mergeNote
Only download streams you own or are authorized to access. Respect the terms of service of the site you're downloading from and applicable copyright law. This tool does not bypass DRM.
License
MIT © Hossein Seifi
