MPDGrab: A Free DASH/MPD Downloader for Windows, macOS & Linux
Give MPDGrab
.mpdmanifest URL and it mirrors the entire DASH presentation — every video representation, every audio track, every segment and initialization file — into a fully self-contained folder, with the manifest rewritten to local paths. Serve it with ZWServe and any DASH-capable player (like ZWPlayer) plays it offline.
📺 Working with HLS (.m3u8) streams instead? See the companion tool M3U8Grab — Free M3U8/HLS Downloader.
Download (pre-built, no install):
MPDGrab (the downloader):
ZWServe (the local play server):
Free · Open source single-file · No ads · No sign-up · Nothing is uploaded
The Problem: DASH Streams Don’t “Save”
DASH (Dynamic Adaptive Streaming over HTTP) is the other half of the modern streaming world — the .mpd (Media Presentation Description) is an XML manifest that describes Periods, AdaptationSets and Representations, and points at hundreds of segmented media files (.m4v/.m4s/.mp4 chunks, each with an initialization segment). Like HLS, it streams beautifully and saves terribly:
- You can’t “right-click → Save” a DASH video; the manifest references content the browser fetches piece by piece.
- Standard downloaders pick representation, merge it, and quietly discard the rest — ABR switching, alternate audio and multi-period structure are gone.
- If the origin goes away, so does your test fixture.
MPDGrab mirrors the entire DASH tree — the manifest, every representation (video and audio), every segment and init file, and any nested manifests linked via Period@xlink:href — then rewrites all references to local relative paths. The result is a folder that behaves exactly like the live stream when served over HTTP.
How MPDGrab Reads a Manifest (DASH addressing, done properly)
DASH manifests are more varied than m3u8 playlists. MPDGrab implements the full addressing stack:
- SegmentTemplate — including the
$RepresentationID$,$Number$,$Time$,$Bandwidth$placeholders andprintf-style widths like$Number%05d$, with full SegmentTimeline support (explicit$Time$maps, not just fixed numbering). - SegmentList — explicit
<SegmentURL>/<Initialization>lists. - BaseURL single-file — representations whose effective BaseURL is the media file (progressive download inside a DASH wrapper).
- Nested manifests —
Period@xlink:hrefis followed recursively (with a seen-set, so circular references can’t loop).
BaseURL resolution follows the spec chain — MPD → Period → AdaptationSet → Representation — each level able to override the above it. Absolute and cross-origin references (BaseURL element text, SegmentURL@media, Initialization@sourceURL, xlink:href) are rewritten to local paths; resources on a different host than the manifest land under external/<host>/… so a single folder stays self-contained.
Encrypted DASH? MPDGrab targets clear (unencrypted) DASH — which is what virtually all public test vectors and self-published content use. DRM-protected presentations (Widevine/PlayReady
ContentProtection) are out of scope: their licenses/keys can’t be fetched as plain files, by design.
Why Another DASH Downloader?
Most DASH tools are remuxers: they pick representation, concatenate segments, and output an MP4. Fine for watching a movie offline; useless when you need the stream to keep behaving like a stream.
MPDGrab takes the same mirror-don’t-merge approach as its sibling M3U8Grab:
- All representations — the manifest keeps every video bitrate, so adaptive-bitrate (ABR) switching can actually be exercised locally.
- All audio AdaptationSets — alternate languages and audio-described tracks stay in their own representations.
- Multi-Period structure — periods (and xlink-nested sub-manifests) are preserved as authored.
- The manifest itself, rewritten — every reference points at a local file. Nothing is merged into a single container.
That makes the output ideal for player regression testing (ABR, representation switching, multi-period edge cases), reproducing a playback issue offline, or building a reproducible test fixture that no longer depends on someone else’s CDN.
Core Features
| Feature | Details |
|---|---|
| Complete mirror | Manifest + every representation + audio tracks + init segments + nested (xlink) manifests. |
| Full DASH addressing | SegmentTemplate (incl. SegmentTimeline and printf widths), SegmentList, BaseURL single-file. |
| Offline-playable | All absolute/cross-origin references rewritten to local relative paths. |
| Cross-domain | Not constrained by browser CORS; foreign-host resources map to external/<host>/.... |
| Resume on interruption | Re-running skips files that already exist and are non-empty — missing pieces are fetched. |
| Atomic writes | Each segment is written to a .part temp file and renamed 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 — download and run. |
Using MPDGrab on Each Platform
Windows
Open PowerShell or Command Prompt in the folder where you saved the .exe:
# Mirror a DASH presentation into a folder named "myvideo"
.\mpdgrab-win.exe -url "https://example.com/vod/manifest.mpd" -out "myvideo"
macOS (Apple Silicon)
chmod +x mpdgrab-macos
./mpdgrab-macos -url "https://example.com/vod/manifest.mpd" -out "myvideo"
On first launch, macOS Gatekeeper may block an unsigned binary. Right-click → Open → confirm, or run
xattr -d com.apple.quarantine mpdgrab-macosonce.
Linux
chmod +x mpdgrab-linux
./mpdgrab-linux -url "https://example.com/vod/manifest.mpd" -out "myvideo"
Command-Line Options
| Option | Default | Description |
|---|---|---|
-url |
(required) | Manifest MPD 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 "K: V" |
— | Custom request header (repeatable). Set Referer/Origin/User-Agent to match server-side origin/referer checks. |
-h |
— | Show usage and examples |
Common Recipes
# Max concurrency for a big presentation
./mpdgrab-win.exe -url "https://.../manifest.mpd" -out "media/v" -workers 32
# Resume after an interruption — just re-run, existing files are skipped
./mpdgrab-win.exe -url "https://.../manifest.mpd" -out "media/v"
# Force a full re-download
./mpdgrab-win.exe -url "https://.../manifest.mpd" -out "media/v" -force
# Self-signed internal CDN
./mpdgrab-win.exe -url "https://internal-cdn/manifest.mpd" -out "media/v" -insecure
# Match server-side origin/referer checks (repeat -H as needed)
./mpdgrab-win.exe -url "https://.../manifest.mpd" -out "media/v" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" \
-H "Referer: https://example.com/" \
-H "Origin: https://example.com"
Output Structure
A real example — mirroring Akamai’s classic Big Buck Bunny DASH test stream:
$ mpdgrab-win.exe -url "https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd" -out bbb_30fps
[3/3] done. summary:
files: 1761
size : 2314.1 MB
entry: bbb_30fps/bbb_30fps.mpd (rewritten, locally playable)
The folder mirrors the presentation:
bbb_30fps/
├── bbb_30fps.mpd ← play entry (rewritten), open this
├── bbb_30fps_320x180_200k/ ← video 180p (init + .m4v segments)
├── bbb_30fps_480x270_600k/ ← video 270p
├── bbb_30fps_640x360_1000k/ ← video 360p
├── ... ← 11 video representations in total
├── bbb_30fps_audio_400k/ ← audio track
└── external/<host>/... ← cross-domain resources, if any
Every representation keeps its own folder with its initialization and media segments, exactly as the manifest addresses them — which is why ABR switching still works when the folder is served locally.
Now Play It Offline with ZWPlayer
Same two-step story as the HLS workflow: serve the folder over HTTP with ZWServe, then open the rewritten .mpd in ZWPlayer.
Why a server? A DASH player fetches the manifest and its segments via
fetch/XHR, which doesn’t work fromfile://. And since the player runs onzwplayer.com, reaching your local machine is a cross-origin request the server must explicitly allow. ZWServe does both: HTTP serving + permissive CORS headers on every response.
1. Serve the folder with ZWServe
# Windows (PowerShell) — serve the output folder on port 8000
.\zwserv-win.exe -port 8000 -dir ./bbb_30fps
# macOS / Linux
chmod +x zwserv-macos && ./zwserv-macos -port 8000 -dir ./bbb_30fps
Copy the URL ending in /bbb_30fps.mpd — that’s your playback link.
2. Play the link in ZWPlayer
Open ZWPlayer, paste http://localhost:8000/bbb_30fps.mpd into the URL field, and press play. ZWPlayer ships full DASH support (adaptive bitrate, multi-period, alternate audio), so the mirrored folder behaves exactly like the live stream — every representation switchable, nothing merged away.
📺 For the HLS twin of this workflow (m3u8 → ZWServe → ZWPlayer), see M3U8Grab — Free M3U8/HLS Downloader.
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 test streams). MPDGrab is a tool; respecting copyright and terms of service is your responsibility.
Why do I need ZWServe? Can’t I just open the .mpd file?
No. A DASH player loads the manifest 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 solves both in command.
Does it convert to MP4?
No — it preserves the original DASH structure (manifest + representations + segments), which is what keeps all bitrates, audio tracks and ABR behavior intact. If you need a single MP4, that’s a separate remux step.
The download was interrupted. Do I start over?
No. Re-run the exact same command. Already-downloaded, non-empty files are skipped; the missing pieces are fetched.
I see .part files in the output folder. Are segments failing?
No. .part is the in-flight temp name: each segment downloads to <name>.part and is atomically renamed when complete. With the default 16 workers you’ll see up to 16 .part files at any moment — that’s normal. Failed attempts delete their .part and retry (5×, with backoff). a killed process leaves .part files behind, and a re-run heals that.
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
MPDGrab turns a streaming-only DASH presentation into a permanent, self-contained folder — every representation, audio track and segment preserved, manifest rewritten for local playback. ZWServe exposes that folder over a CORS-enabled local URL in command, and ZWPlayer plays it with full DASH behavior (ABR, multi-period, alternate audio). Together with its HLS sibling M3U8Grab, the two tools cover both halves of the adaptive-streaming world for offline playback, archiving and player testing.
Download the binaries for your platform, mirror a stream you’re allowed to keep, and try it now.
MPDGrab (downloader): 🪟 Windows · 🍎 macOS · 🐧 Linux · 🐧 Linux ARM64
ZWServe (play server): 🪟 Windows · 🍎 macOS · 🐧 Linux
M3U8Grab (HLS twin): Free M3U8/HLS Downloader
- ▶️ Open ZWPlayer to play the served stream
- 🔎 New to DASH manifests? Read the DASH/MPD deep-dive