How youtube-dl Handles HLS and DASH Streaming Formats for Downloads
youtube-dl processes HLS and DASH streams through specialized fragment downloaders that download individual media segments, decrypt AES-128 encrypted content, and assemble the final file, falling back to ffmpeg when encountering unsupported manifest features.
The youtube-dl project (ytdl-org/youtube-dl) implements a robust architecture for downloading modern adaptive bitrate streams. When an extractor identifies a streaming manifest—either an HLS .m3u8 or DASH .mpd—it populates info_dict['url'] with the manifest location and sets format_id values such as hls, dash, or dashsegments. During the download phase, the engine delegates to protocol-specific downloaders that handle the complexities of fragmented media delivery.
Fragmented Download Architecture
Both HLS and DASH downloaders inherit from FragmentFD, defined in youtube_dl/downloader/fragment.py. This base class implements the generic lifecycle for all fragmented downloads:
- Preparation – Creates a temporary file, an optional
.ytdlbookkeeping file for resumption, and instantiates anHttpQuietDownloaderfor lightweight fragment fetching. - Progress Handling – Registers a fragment-progress hook that updates the global progress bar and ETA as individual segments complete.
- Fragment Fetching – Repeatedly calls
_download_fragmentwhile respectingfragment_retriesandskip_unavailable_fragmentssettings. - Assembly – Appends raw fragment bytes to the destination stream, optionally cleaning up temporary files immediately after appending.
- Finalization – Renames the temporary file to its final name, removes the
.ytdlbookkeeping file, and reports completion.
Native HLS Implementation (HlsFD)
The native HLS downloader resides in youtube_dl/downloader/hls.py and handles standard HTTP Live Streaming manifests through the HlsFD class.
Feature Detection and FFmpeg Fallback
Before downloading, HlsFD.can_download (lines 68-78) scans the manifest for unsupported features. The native downloader falls back to FFmpegFD when it encounters:
#EXT-X-KEYwith encryption methods other than AES-128- Byte-range playlists (indicated by
#EXT-X-BYTERANGE) - Live-stream heuristics that suggest ongoing broadcasts
Users can override this behavior with --hls-prefer-native to force the native implementation or --hls-prefer-ffmpeg to always use the external tool.
Manifest Parsing and Decryption
The downloader fetches the manifest via urlh.read(), then processes each line:
- Non-comment lines are treated as fragment URLs relative to the manifest base
#EXT-X-KEYlines are parsed usingparse_m3u8_attributesfromyoutube_dl/utils.pyto extract decryption parameters- When
METHOD=AES-128is detected, the key is downloaded (unless cached) and the fragment is decrypted using PyCrypto (AES.new(...).decrypt) #EXT-X-BYTERANGEspecifications are converted into HTTPRangeheaders for the subsequent request
The decryption step is automatically skipped during unit testing when test=True.
Ad Segment Filtering
HlsFD recognizes proprietary advertising markers such as #ANVATO-… and #UPLYNK-… in the manifest. These segments are silently dropped from the download queue and counted separately in progress reports rather than being processed as media fragments.
DASH Streaming Support (DashSegmentsFD)
Dynamic Adaptive Streaming over HTTP is handled by DashSegmentsFD in youtube_dl/downloader/dash.py.
Fragment List Structure
DASH extractors populate info_dict['fragments'] with a list of dictionaries containing:
url: The direct segment address, orpathcombined withfragment_base_urlrange: Optional byte-range specifications for partial segment downloads
Initialization Segment Handling
The first segment in a DASH stream typically contains the MP4 initialization box required to make the file playable. DashSegmentsFD treats the first fragment as fatal (fatal = frag_index == 1); if this download fails, the entire operation aborts immediately rather than continuing with subsequent segments.
Retry Logic and Byte Ranges
The downloader implements a retry loop beginning at line 51 of youtube_dl/downloader/dash.py. On any HTTPError, the fragment is retried up to fragment_retries times. When a fragment dictionary contains a range entry, the downloader constructs a Range header with the format 'bytes=%s' to request only the specified portion of the resource.
Command-Line Configuration Options
youtube-dl exposes fine-grained control over streaming behavior through options defined in youtube_dl/options.py:
--hls-prefer-native/--hls-prefer-ffmpeg– Explicitly choose between the native Python implementation and the external ffmpeg tool.--hls-use-mpegts– Download HLS fragments as MPEG-TS instead of MP4, affecting how fragments are concatenated during assembly.--fragment-retries– Set the number of retry attempts for failed fragments (applies to both HLS and DASH).--skip-unavailable-fragments– Continue downloading if individual fragments return 404 or other errors, rather than aborting.
Practical Usage Examples
Force ffmpeg for HLS when the native downloader fails to handle the encryption method:
youtube-dl --hls-prefer-ffmpeg -f best https://example.com/stream.m3u8
Download a DASH manifest with aggressive retry settings and skip missing segments:
youtube-dl --fragment-retries 5 --skip-unavailable-fragments -f best https://example.com/manifest.mpd
Using the Python API to configure HLS preferences:
from youtube_dl import YoutubeDL
ydl_opts = {
'format': 'bestvideo+bestaudio',
'hls_prefer_native': True,
'fragment_retries': 3,
'skip_unavailable_fragments': True,
}
with YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info('https://example.com/video.m3u8', download=False)
ydl.download([info['webpage_url']])
Summary
- youtube-dl delegates HLS and DASH streams to specialized fragment downloaders (
HlsFDandDashSegmentsFD) that inherit from the baseFragmentFDclass. - The native HLS downloader in
youtube_dl/downloader/hls.pyhandles AES-128 decryption via PyCrypto, filters ad markers, and falls back to ffmpeg for unsupported features like non-AES encryption or live streams. - The DASH downloader in
youtube_dl/downloader/dash.pyprocesses explicit fragment lists, treats the initialization segment as fatal if missing, and supports byte-range requests for partial segments. - Both downloaders respect
--fragment-retriesand--skip-unavailable-fragmentsfor resilient downloads across unstable network conditions.
Frequently Asked Questions
When does youtube-dl use ffmpeg instead of the native HLS downloader?
youtube-dl falls back to ffmpeg when HlsFD.can_download detects unsupported manifest features in lines 68-78 of youtube_dl/downloader/hls.py. These include encryption methods other than AES-128 (such as SAMPLE-AES), byte-range playlists, or heuristics indicating a live stream. You can force the native downloader with --hls-prefer-native or always use ffmpeg with --hls-prefer-ffmpeg.
How does youtube-dl handle encrypted HLS streams?
When the manifest contains #EXT-X-KEY with METHOD=AES-128, the HlsFD class downloads the specified key (caching it for reuse) and decrypts each fragment using PyCrypto's AES.new(...).decrypt before appending it to the output file. This occurs in youtube_dl/downloader/hls.py and relies on parse_m3u8_attributes from youtube_dl/utils.py to parse the key parameters.
What happens if a DASH initialization segment fails to download?
The DASH downloader treats the first fragment (index 1) as fatal. If the initialization segment fails, the download aborts immediately (fatal = frag_index == 1 in youtube_dl/downloader/dash.py) because the MP4 file would be unplayable without this metadata. Subsequent fragments can be skipped if --skip-unavailable-fragments is set, but the initialization box is mandatory.
Can I force MPEG-TS format instead of MP4 for HLS downloads?
Yes. Use the --hls-use-mpegts flag to keep fragments in MPEG-TS transport stream format rather than converting them to MP4 during assembly. This affects how HlsFD writes data to the destination file and is useful when you need the original broadcast format without container conversion.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →