Download an HLS .m3u8 stream, clean and prepare its media segments locally,
then package the result as an .mp4 with ffmpeg.
(Different to ffmpeg: Support m3u8 with fake image headers, and cleanup segments with invalid prefix.)
The repository now includes three ways to use the downloader:
- A native SwiftUI macOS app in
M3U8Downloader/. - A Python command-line downloader in
download_m3u8.py. - A small local Flask web UI in
web_app.py.
The downloader is intended for streams you are allowed to download. It does not bypass DRM or other access controls.
M3U8Downloader is a SwiftUI app for macOS that wraps the downloader workflow in
a desktop interface. It accepts either a remote playlist URL or a local .m3u8
file, downloads and cleans the HLS segments, and calls ffmpeg to package the
final MP4.
- Remote URL and local
.m3u8file sources. - Output filename and save-folder selection.
- Custom HTTP headers, one
Name: Valueheader per line. - Preferred quality selection for master playlists: Auto / Best, 1080p, or 720p.
- Segment worker, retry, timeout, and overwrite controls.
- Download queue sidebar with search.
- Per-download progress, segment counts, FFmpeg logs, retry, cancel, Show in Finder, and Open Video actions.
- Preferences for
ffmpegpath and the default download folder.
- macOS 14 or newer.
- Xcode with the macOS SDK.
ffmpeginstalled locally.
Install ffmpeg with Homebrew if needed:
brew install ffmpegThe app auto-detects common ffmpeg locations:
/opt/homebrew/bin/ffmpeg/usr/local/bin/ffmpeg/usr/bin/ffmpegffmpegfromPATH
You can override the path in the app Preferences screen.
The Xcode project is checked in at:
M3U8Downloader/M3U8Downloader.xcodeproj
Build a Release app from the terminal:
./scripts/build-macos-app.shThe script builds the M3U8Downloader scheme from source and leaves Xcode build
products under .build/macos/:
.build/macos/DerivedData/Build/Products/Release/M3U8Downloader.app
Generated app bundles and build output are intentionally ignored by Git. The
repository should contain source, project files, scripts, docs, and tests rather
than checked-in .app bundles or local Xcode build directories.
You can also open the project in Xcode:
open M3U8Downloader/M3U8Downloader.xcodeprojThen select the M3U8Downloader scheme and run it.
Before the first notarized release, store your Apple notarization credentials
in Keychain under a profile name. For example, create the develop profile:
xcrun notarytool store-credentials developCreate a signed and notarized distributable DMG with that profile:
NOTARYTOOL_PROFILE=develop ./scripts/package-macos-release.shThe artifact is written to dist/ using the current Git version, for example:
dist/M3U8Downloader-<version>-macOS.dmg
The mounted DMG contains M3U8Downloader.app and an Applications link, so
users can install the app by dragging it onto Applications.
To produce a ZIP instead:
NOTARYTOOL_PROFILE=develop ./scripts/package-macos-release.sh --format zipSet VERSION to override the artifact name:
NOTARYTOOL_PROFILE=develop \
VERSION=1.0.0 \
./scripts/package-macos-release.shRelease packages are signed with the first valid Developer ID Application identity found in your keychain. You can provide an explicit identity when needed:
CODESIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
NOTARYTOOL_PROFILE=develop \
VERSION=1.0.0 \
./scripts/package-macos-release.shThe packaging script submits the ZIP or DMG with notarytool --wait. After
Apple accepts it, the script staples and validates the ticket. ZIP artifacts
are rebuilt from the stapled app before the final artifact path is printed.
Any submission, stapling, or validation failure stops the release.
For a signed local package that should not be submitted to Apple, set
SKIP_NOTARIZATION=1. For an unsigned local package, set SKIP_CODESIGN=1;
unsigned packages also skip notarization.
After building, launch the app from the build output:
open ./.build/macos/DerivedData/Build/Products/Release/M3U8Downloader.appTo download a stream:
- Choose
Remote URLorLocal .m3u8 File. - Enter the playlist URL or browse to a local playlist.
- Choose an output filename and save folder.
- Expand Advanced Settings when you need headers, quality, worker, retry, or timeout options.
- Click
Download Stream.
Completed jobs can be opened in Finder or played directly from the job detail screen.
In Chrome browser, press F12, click the tab to the Network page, enter "m3u8" in the Filter box. Press F5 to refresh the page, if the video in the web page uses an HLS source, you can capture the video stream address there. Right click and select "Copy -> Copy as cURL" to get the full URL with headers.
The app also includes an XcodeGen spec:
M3U8Downloader/project.yml
If you update the project structure and want to regenerate the .xcodeproj,
install XcodeGen and run:
cd M3U8Downloader
xcodegen generateThe CLI downloads remote playlists segment-by-segment, strips a small fake image
header from segments when present, writes a cleaned local playlist, then uses
ffmpeg to combine the local playlist into the final MP4.
- Python 3.
ffmpegavailable on yourPATH.
Download a remote playlist:
python3 download_m3u8.py "https://cdn3.turboviplay.com/data1/685f4c3d5bc66/685f4c3d5bc66.m3u8"By default, MP4s are saved in your user Downloads folder, such as
/Users/ericchan/Downloads/, using the playlist filename.
Save with a custom output filename:
python3 download_m3u8.py "https://cdn3.turboviplay.com/data1/685f4c3d5bc66/685f4c3d5bc66.m3u8" -o sample.mp4Download from a local .m3u8 playlist file:
python3 download_m3u8.py ./video.m3u8 -o video.mp4Pass headers when a stream requires them:
python3 download_m3u8.py "https://example.com/video.m3u8" \
--header "Referer: https://example.com" \
--header "User-Agent: Mozilla/5.0" \
-o video.mp4Avoid overwriting an existing file:
python3 download_m3u8.py "https://example.com/video.m3u8" -o video.mp4 --no-overwritePrefer a 720p or 1080p stream when the master playlist offers one:
python3 download_m3u8.py "https://example.com/video.m3u8" --quality 1080p -o video.mp4Tune segment downloading:
python3 download_m3u8.py "https://example.com/video.m3u8" \
--segment-workers 12 \
--retries 4 \
--timeout 20 \
-o video.mp4Segments are downloaded concurrently and timeout failures are retried.
Install the UI dependency:
python3 -m pip install -r requirements.txtStart the local server:
python3 web_app.pyOpen http://127.0.0.1:5000 in your browser. The UI lets you queue downloads
from either a remote m3u8 URL or an uploaded local .m3u8 file, set the same
options as the CLI, watch live logs, see the local saved path, and download a
completed MP4 through the browser.
The web app is intended for local use and binds to 127.0.0.1.
Use the repository virtual environment for tests:
./.venv/bin/python3 -m pytestm3u8Downloader is released under the MIT License. See LICENSE.


