Skip to main content
FFmpeg Tutorials

How to convert M3U8 to MP4 using FFmpeg: A complete command-line guide

HTTP Live Streaming (HLS) delivers video in tiny chunks via an .m3u8 playlist. While ideal for streaming, you cannot simply "save as" to get a local video file. FFmpeg is the industry-standard, open-source tool capable of parsing M3U8 playlists, downloading the segments, and multiplexing them into a single, highly compatible MP4 file.

Whether you are archiving a VOD asset, debugging stream packaging, or trying to save a live broadcast, FFmpeg provides the most robust set of commands for interacting with HLS streams. This guide will walk you through the basic conversion process, advanced audio handling, decryption, and protocol-specific configurations to ensure a lossless extraction.

Why Use FFmpeg?

Most "M3U8 downloaders" found online are essentially graphical wrappers around FFmpeg. Using FFmpeg directly gives you total control over the download process, allowing you to bypass aggressive rate limiting, specify custom HTTP headers, choose precise audio tracks, and remux without unnecessary quality loss.

The Basic M3U8 to MP4 Command

The simplest way to convert an HLS stream into an MP4 file is to let FFmpeg read the manifest URL directly. Assuming you have FFmpeg installed, open your terminal and run:

ffmpeg -i "https://example.com/stream/playlist.m3u8" -c copy output.mp4

Let's break down exactly what this command does:

  • -i "url" specifies the input. The quotes are important if your URL contains query parameters (like ?token=123) which might break the shell command.
  • -c copy instructs FFmpeg to copy the video and audio codecs directly (remuxing). It does not re-encode the video. This guarantees zero quality loss and makes the process incredibly fast—limited only by your download speed.
  • output.mp4 is the resulting file. The .mp4 extension tells FFmpeg to wrap the downloaded H264/AAC data into an MP4 container.

Handling Live Streams and Timeouts

If you execute the basic command on a live stream, FFmpeg will continuously download segments indefinitely. To record a specific duration of a live broadcast, use the -t flag to specify the time in seconds or HH:MM:SS format.

ffmpeg -i "https://live-server.com/live.m3u8" -t 00:30:00 -c copy live_capture.mp4

Avoid Disconnections

Live streams can drop segments or pause. If you want FFmpeg to keep trying instead of failing immediately upon a network hiccup, add the -reconnect flags.

To make your live stream download highly resilient against network errors, use this advanced command:

ffmpeg -reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 5 -i "https://live-server.com/live.m3u8" -c copy output.mp4

Decrypting AES-128 Encrypted Streams

HLS natively supports AES-128 encryption. If the playlist contains an #EXT-X-KEY:METHOD=AES-128,URI="key.bin" tag, FFmpeg handles decryption automatically, provided it can access the key URI.

If the key is protected behind authentication or requires specific cookies, FFmpeg will fail with an HTTP 403 Forbidden error. You can pass HTTP headers and cookies directly to FFmpeg using the -headers argument.

ffmpeg -headers "Cookie: session_id=abc123xyz" -i "https://protected-site.com/video.m3u8" -c copy output.mp4
Note: FFmpeg cannot bypass DRM schemes like Widevine, FairPlay, or PlayReady (indicated by SAMPLE-AES). It only decrypts standard clear-key AES-128.

Fixing Audio Sync Issues

Sometimes, the raw TS (Transport Stream) segments have slightly misaligned Presentation Time Stamps (PTS). When remuxing into an MP4 container, this can lead to the audio slowly drifting out of sync with the video.

To force FFmpeg to rebuild the timestamps correctly during the demuxing stage, use the -bsf:a aac_adtstoasc bitstream filter. This converts ADTS AAC audio into MPEG-4 Audio Specific Configuration, which often fixes playback issues in QuickTime and standard browsers.

ffmpeg -i "https://example.com/playlist.m3u8" -c copy -bsf:a aac_adtstoasc output.mp4

Selecting Specific Video/Audio Tracks

A master M3U8 playlist often contains multiple resolutions (360p, 720p, 1080p). If you pass a master playlist directly to FFmpeg, it will usually pick the first available or the highest quality stream. However, to guarantee you get the exact version you want, you should use the -map function.

To see all available streams without downloading the file, run:

ffmpeg -i "https://example.com/master.m3u8"

Look for lines indicating Program 1, Program 2, etc. If you want to download Program 2 (perhaps the 1080p track with English audio), specify the map:

ffmpeg -i "https://example.com/master.m3u8" -map p:2 -c copy output.mp4

Alternatively, the most robust method is to inspect the master manifest in a text editor (or browser DevTools), find the URL of the specific media playlist (e.g., 1080p.m3u8), and feed that direct URL into FFmpeg instead of the master URL.

Batch Processing Local M3U8 Files

If you have already downloaded the .m3u8 manifest and the .ts segments to a local folder, you can still use FFmpeg to combine them. Navigate to the directory containing the local .m3u8 file and run:

ffmpeg -allowed_extensions ALL -i local_playlist.m3u8 -c copy output.mp4

The -allowed_extensions ALL flag is crucial when reading local playlists, as FFmpeg's default security model restricts it from reading arbitrary local files referenced inside text documents.