MPDGrab: A Free DASH/MPD Downloader for Windows, macOS & Linux

Give MPDGrab .mpd manifest 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:

  1. SegmentTemplate — including the $RepresentationID$, $Number$, $Time$, $Bandwidth$ placeholders and printf-style widths like $Number%05d$, with full SegmentTimeline support (explicit $Time$ maps, not just fixed numbering).
  2. SegmentList — explicit <SegmentURL> / <Initialization> lists.
  3. BaseURL single-file — representations whose effective BaseURL is the media file (progressive download inside a DASH wrapper).
  4. Nested manifestsPeriod@xlink:href is 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-macos once.

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 from file://. And since the player runs on zwplayer.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.

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

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