M3U8Grab: A Free M3U8/HLS Downloader for Windows, macOS & Linux
Download an entire m3u8 stream — every resolution, audio track, and subtitle — to a fully self-contained folder. Bundled with ZWServe, a one-command local CORS server, so you can open the result in any HLS player (like ZWPlayer) with no extra setup.
Download (pre-built, no install):
M3U8Grab (the downloader):
ZWServe (the local play server):
Free · Open source single-file · No ads · No sign-up · Nothing is uploaded
The Problem: m3u8 Streams Don’t “Save”
Most modern video sites serve content as HLS — a .m3u8 playlist that points to hundreds of tiny .ts segment files. The player fetches them one by one as you watch. This is great for streaming, but it means:
- You can’t just “right-click → Save” the video.
- If the source goes down, the video is gone forever.
- You can’t watch it offline, archive it, or study its subtitle tracks at your own pace.
- Standard downloaders often grab only one resolution and quietly drop the subtitle/audio tracks.
M3U8Grab fixes this. It mirrors the entire HLS tree — master playlist, every bitrate variant, every audio track, every subtitle track, and every decryption key — then rewrites all references to local paths. The result is a self-contained folder independent of the original server; serve it locally (with the bundled ZWServe) and play it in any HLS-capable player.
How M3U8Grab Handles Segments and Encrypted Streams (m3u8 capture)
Give it a master .m3u8 URL. It will:
- Discover every playlist referenced (variants, audio, subtitles) and download them.
- Rewrite every
URI="..."and segment reference into a local relative path (relative to that playlist’s own folder, so nothing is misaligned). - Download all segments/keys/subtitles concurrently, then report the entry file you should open to play.
The output is completely self-contained — every reference points to a local file, so nothing depends on the original server anymore. To play it, you serve the folder over HTTP with ZWServe (below) and open the master playlist in a player — no internet connection to the original source required.
Encrypted m3u8? No problem. M3U8Grab follows
#EXT-X-KEYtags and downloads the decryption keys referenced by the stream, so even encrypted (DRM-free AES-128) HLS streams are captured completely — as long as you have legitimate access to the source, the keys come down with the segments and the result plays offline.
Core Features
| Feature | Details |
|---|---|
| Complete mirror | Master + all bitrates + all audio tracks + all subtitle tracks + keys. Nothing dropped. |
| Offline-playable | Every reference rewritten to a local path — the folder is self-contained. |
| Cross-domain | Not constrained by browser CORS, so resources on a different host than the master still download (mapped to external/<host>/...). |
| Resume on interruption | Re-running skips files that already exist and are non-empty — only missing pieces are fetched. |
| Atomic writes | Each segment is written to a .part temp file and renamed only when complete, so you never get half-files. |
| Retry with backoff | Up to 5 retries per file (0.5s/1s/1.5s/2s/2.5s). |
| Concurrent | Default 16 workers; tune with -workers. |
| Zero dependencies | Self-contained pre-built binaries with no runtime requirements — just download and run. |
| Filename sanitization | Strips Windows-illegal characters, so the same folder works on every OS. |
Using M3U8Grab on Each Platform
Download the binary for your OS above, then:
Windows
Double-click is not interactive, so open PowerShell or Command Prompt in the folder where you saved the .exe:
# Download to a folder named "myvideo"
.\m3u8grab-win.exe -url "https://example.com/vod/master.m3u8" -out "myvideo"
macOS (Apple Silicon)
chmod +x m3u8grab-macos
./m3u8grab-macos -url "https://example.com/vod/master.m3u8" -out "myvideo"
On first launch, macOS Gatekeeper may block an unsigned binary. Right-click → Open → confirm, or run
xattr -d com.apple.quarantine m3u8grab-macosonce.
Linux
chmod +x m3u8grab-linux
./m3u8grab-linux -url "https://example.com/vod/master.m3u8" -out "myvideo"
Command-Line Options
| Option | Default | Description |
|---|---|---|
-url |
(required) | Master m3u8 URL (http/https, supports cross-domain refs) |
-out |
(required) | Local output directory (created if missing) |
-workers |
16 |
Concurrent downloads |
-force |
false |
Re-download even if files already exist |
-insecure |
false |
Skip TLS certificate verification (self-signed / internal CDN only) |
-h |
— | Show usage and examples |
Common Recipes
# Custom URL + output folder, max concurrency
./m3u8grab-win.exe -url "https://.../master.m3u8" -out "media/v" -workers 32
# Resume after an interruption — just re-run, existing files are skipped
./m3u8grab-win.exe -url "https://.../master.m3u8" -out "media/v"
# Force a full re-download
./m3u8grab-win.exe -url "https://.../master.m3u8" -out "media/v" -force
# Self-signed internal CDN
./m3u8grab-win.exe -url "https://internal-cdn/master.m3u8" -out "media/v" -insecure
Output Structure
After a run, you get a folder mirroring the stream:
myvideo/
├── master.m3u8 ← play entry — open this
├── manifest0/ ← video 270p (index.m3u8 + index*.ts)
├── manifest1/ ← video 360p
├── manifest2/ ← video 480p
├── manifest3/ ← video 720p
├── manifest4/ ← video 1080p
├── manifest5/ ← audio 64k Main
├── manifest6/ ← audio 128k Main
├── manifest9/ ← subtitle en (captions.en.m3u8 + *.webvtt)
├── manifest10/ ← subtitle sv
└── external/<host>/... ← cross-domain resources, if any
Cross-host resources land under
external/<host>/...and their playlist references are rewritten to point there.
Now Play It Offline with ZWPlayer
You’ve downloaded a complete, self-contained HLS folder. How do you watch it? This is a two-step story: first serve the folder over HTTP with ZWServe, then open it in ZWPlayer — a free online player that needs no install.
Why a server at all? An HLS player (like ZWPlayer) fetches the m3u8 playlist and its
.tssegments viafetch/XHR. That doesn’t work over thefile://protocol — and since the online player runs onzwplayer.com, reaching your local machine is a cross-origin request, which the server must explicitly allow. ZWServe handles both: it serves the files over HTTP and sends permissive CORS headers on every response.
1. Serve the folder with ZWServe
Download ZWServe for your platform (links at the top), then in the folder where you ran M3U8Grab:
# Windows (PowerShell) — serve the current directory on port 8000
.\zwserv-win.exe
# Pick a different port
.\zwserv-win.exe -port 9000
# macOS / Linux
chmod +x zwserv-macos && ./zwserv-macos
chmod +x zwserv-linux && ./zwserv-linux
# Serve a specific output folder
.\zwserv-win.exe -port 8000 -dir ./media/myvideo
On startup it prints ready-to-paste URLs for every local network interface, e.g.:
ZWServe — from zwplayer.com (local CORS web server)
-------------------------------------------------
ZWServe — minimal CORS local web server
---------------------------------------------------------
serving : /home/me/media/ed_hd
port : 8000
cache : off (no-store — every refresh fetches the latest)
Serving at:
http://192.168.1.20:8000/
http://localhost:8000/
Tip: serving m3u8? Append your path, e.g. http://localhost:8000/master.m3u8
Copy the URL that ends in /master.m3u8 — that’s your playback link.
ZWServe options:
| Option | Default | Description |
|---|---|---|
-port |
8000 |
TCP port to listen on |
-dir |
. |
Directory to serve (default: current dir) |
2. Play the link in ZWPlayer
Open ZWPlayer and paste the http://localhost:8000/master.m3u8 URL into the player’s URL field. Because ZWServe returns CORS headers, the online player can reach your local folder across origins — and since the files are on your machine, nothing is uploaded to a third party.
ZWPlayer natively supports HLS (m3u8), DASH, MP4, MKV, MOV, WebM, and more, so it handles whatever M3U8Grab produced.
3. Embedded subtitles load automatically
This is the key reason M3U8Grab rewrites references instead of leaving them as URLs: the subtitle tracks inside the m3u8 now point to local .webvtt files. When you open the folder in ZWPlayer:
- Every subtitle language in the stream (en, sv, ru, ja, ar, …) is auto-mounted as a selectable track.
- Switch tracks from the player’s subtitle menu — no manual file loading needed.
- Because the subtitles are local, they’re available even when you’re offline.
4. Add external subtitles & translate
Want subtitles the stream doesn’t include, or a translation?
- Preload external subtitles: load your own VTT / SRT / BCC / JSON files alongside playback, with multiple tracks at once for bilingual comparison.
- Subtitle translation: a built-in panel translates subtitles into 13 languages in real time, overlaid as a secondary subtitle — no need to pre-produce translated files.
- Subtitle search: search across all lines and jump instantly, with per-line loop playback for language study.
🛠️ Need to author or fine-tune subtitles visually? Try the free Online Subtitle Editor — timeline-based alignment, exports VTT/SRT, then load the result straight into the player.
Going further with WordPress
If you publish video on a WordPress site, the ZWPlayer WordPress plugin embeds all of the above — multi-subtitle, translation, playlists, watermark protection, chapters — as a Gutenberg block or shortcode, with zero code.
FAQ
Is this legal to use?
Only download content you have the right to access (your own media, content you’ve licensed, or explicitly public streams). M3U8Grab is a tool; respecting copyright and terms of service is your responsibility.
Why do I need ZWServe? Can’t I just open the m3u8 file?
No. An HLS player loads the playlist and segments over HTTP (fetch/XHR), which doesn’t work from a file:// URL. And because the player runs on a website (e.g. zwplayer.com), reaching your local machine is a cross-origin request that needs CORS headers. ZWServe is a zero-config server that solves both: it serves the folder over HTTP and sends permissive CORS headers on every response.
Does it convert to MP4?
No — it preserves the original HLS structure (segments + playlists), which is what keeps all bitrates, audio tracks, and subtitles intact. Serve the folder with ZWServe and any HLS-capable player (like ZWPlayer) plays it directly. If you specifically need a single MP4, that’s a separate remux/transcode step.
The download was interrupted. Do I start over?
No. Re-run the exact same command. Already-downloaded, non-empty files are skipped; only the missing pieces are fetched.
One resolution failed. Can I retry just that?
Yes — same as above. Re-run and it fills in the gaps.
It’s blocked by antivirus / Gatekeeper.
Because the binaries are unsigned, some systems flag them. On macOS, right-click → Open → confirm, or xattr -d com.apple.quarantine <file>. On Windows, click “More info” → “Run anyway” in SmartScreen.
Summary
M3U8Grab turns a fragile, streaming-only m3u8 into a permanent, self-contained folder — every resolution, audio track, and subtitle preserved. ZWServe then exposes that folder over a CORS-enabled local URL in one command, so you can paste it straight into ZWPlayer for playback with automatically mounted multi-language subtitles, plus translation and search. Together they’re also an ideal testbed for developers validating HLS streams and CORS behavior.
Download the binaries for your platform, grab a stream you’re allowed to keep, and try it now.
M3U8Grab (downloader): 🪟 Windows · 🍎 macOS · 🐧 Linux
ZWServe (play server): 🪟 Windows · 🍎 macOS · 🐧 Linux
- ▶️ Open ZWPlayer to play the served stream
- 💬 Subtitle Editor · 🔌 WordPress Plugin