Advanced FFmpeg Operations for M3U8 Streams: Slicing, Merging, Transcoding, and Live Streaming
Hey, I’m back. I recently fell into the streaming rabbit hole again and have been going head-to-head with the M3U8 format. I won’t go on about how amazing FFmpeg is, but every time I run into a new scenario, I end up buried in the documentation. So I figured I’d write down the common operations I use—partly as a memo for myself, partly as a reference for anyone who needs it.
I’ll admit: the first time I saw a .m3u8 file, I had no clue what it was. I thought, “Why is this video made of a bunch of .ts files?” Later I learned it’s Apple’s HLS streaming protocol—basically an index plus a bunch of segments. But hey, if you just want to watch the content of an M3U8 link quickly, you don’t need to install FFmpeg and mess with the command line like I did. Just throw the link at M3U8Player online and it’ll play—no fuss. But if you, like me, want to handle, download, transcode, or stream it yourself, then read on.
Slicing
The most common operation is cutting your own video into an HLS stream. Here’s what I usually do:
ffmpeg -i input.mp4 -c copy -hls_time 10 -hls_list_size 0 -hls_segment_filename "seg%d.ts" playlist.m3u8
Here’s a quick breakdown of the parameters:
-hls_time 10→ cuts every 10 seconds-hls_list_size 0→ keeps all segments (for live streaming you’d set a number to keep only the latest few)-hls_segment_filename→ lets you customize filenames; otherwise it defaults to something likeoutput%03d.ts-c copy→ no re-encoding, super fast
One catch: sometimes the original video’s audio stream isn’t standard, so copying directly gives you no sound. You’d need to add -bsf:a aac_adtstoasc in that case, but for normal mp4 slicing it’s usually not required.
I once sliced a long video and forgot to change the output directory—the new filenames overwrote the previous batch. A painful lesson. Now I always put each slicing session into its own folder.
Merging and Downloading
These days many online videos give you a direct M3U8 URL. Converting it to an MP4 is straightforward:
ffmpeg -i "https://...playlist.m3u8" -c copy -bsf:a aac_adtstoasc output.mp4
One line does it. By default it picks the highest bitrate. But if there’s hotlink protection, you’ll need to add -user_agent "Mozilla/5.0", otherwise you’ll keep getting 403 errors. I once spent an hour debugging only to find the default user-agent was banned—changing it fixed it instantly.
If you have a multi-bitrate master playlist (which includes several resolutions), the command above just picks the highest one. To choose a specific version, first run ffmpeg -i "master.m3u8" to see the info—it’ll list the stream indices under #EXT-X-STREAM-INF. Then use -map 0:v:0 -map 0:a:0? to pick the video and audio streams you want, and merge everything into a single file.
Advanced Transcoding
Sometimes the original M3U8 has too high a resolution or the wrong format, and you want to re-encode it into a new M3U8:
ffmpeg -i "input.m3u8" -vf scale=1280:720 -c:v libx264 -crf 23 -c:a aac -hls_time 10 out.m3u8
The input can be an M3U8 URL, and FFmpeg will download and process on the fly. Keep in mind this is slow and uses a lot of temporary disk space—it’s better to merge into a temp file first, then slice. But the one‑liner is handy.
If you want to create adaptive multi-bitrate streams, you generally need to transcode the original into several resolutions/bitrates, slice each, and then manually write a master playlist. FFmpeg can’t generate adaptive streams in a single step; you have to orchestrate it with scripts.
One gotcha in transcoding: keyframes must be aligned across different bitrates, otherwise the video glitches when switching resolutions. I add -g 48 to force a keyframe every 48 frames (about 2 seconds at 24 fps), and pair it with -hls_time 2 to match that duration. That way segment boundaries line up cleanly.
Advanced Live Streaming
I’ve only done this for testing/fun. With pure FFmpeg, you can push HLS directly to a local folder:
ffmpeg -re -i input.mp4 -c copy -f hls -hls_time 4 -hls_list_size 10 -hls_flags delete_segments live.m3u8
-re forces reading at native framerate (otherwise it goes as fast as possible). This creates constantly updated m3u8 and ts files in the current directory. Expose that directory with nginx and you’ve got a simple live source—great for small‑scale internal tests.
The more common workflow is to push RTMP to a server (e.g., nginx-rtmp-module) and let the server convert to HLS. But if you don’t want to set up a server, FFmpeg can push directly to cloud platforms too—the parameters are similar.
When I first ran local streaming, the player kept buffering. I cut -hls_time to 2 seconds and -hls_list_size to 3, and latency dropped significantly. Shorter segments mean heavier server load though—a trade‑off you have to decide on.
Another pitfall: timestamps. If you loop the input (e.g., with -stream_loop -1), timestamps may reset and break the HLS stream. Fix it by adding -fflags +genpts or by using -re with the correct time base. Figured that out after two hours of head‑scratching.
Honestly, these days I just use OBS for most live streaming. But when I need to automate with scripts, FFmpeg is my go‑to—no way around it.
Alright, that’s the rundown of common M3U8 and FFmpeg operations. Each trick uses only a handful of key parameters, but the combinations are endless. Oh, and if you just want to quickly preview an m3u8 to check if yours is correct, I sometimes drop the link into M3U8Player rather than firing up a local player. Anyway, give it a try and you’ll get the hang of it. If you run into any new pitfalls, feel free to share!