Home / Guides / Convert MP4 to HLS with ffmpeg (Command Examples)

Convert MP4 to HLS with ffmpeg (Command Examples)

The short version: one ffmpeg command turns an MP4 into an HLS playlist plus a folder of segments. A working starting point for a video-on-demand file is:

ffmpeg -i input.mp4 \
  -c:v libx264 -crf 21 -preset veryfast \
  -c:a aac -b:a 128k \
  -start_number 0 -hls_time 6 -hls_list_size 0 \
  -f hls index.m3u8

This produces index.m3u8 and index0.ts, index1.ts and so on. Because -hls_list_size 0 keeps every segment in the playlist and ffmpeg appends #EXT-X-ENDLIST, the result is a VOD stream that players can seek through from start to finish.

What each option actually does

Why you cannot always just copy the streams

The tempting shortcut is -c copy, which avoids re-encoding entirely. It only works if the input is already H.264/AAC and has keyframes at regular intervals. If keyframes are sparse, ffmpeg cannot split cleanly and you get segments of wildly uneven length or a playlist that stalls. When you control the source, force a keyframe interval first:

ffmpeg -i input.mp4 -c:v libx264 -g 48 -keyint_min 48 -sc_threshold 0 \
  -c:a aac -hls_time 4 -hls_playlist_type vod -f hls index.m3u8

Here -g 48 at 24 fps puts a keyframe every 2 seconds, and -sc_threshold 0 disables scene-cut keyframes so cuts land predictably. -hls_playlist_type vod marks the output as VOD and makes ffmpeg write #EXT-X-ENDLIST for you.

Producing multiple bitrates (adaptive streaming)

A single rendition works, but adaptive bitrate is the point of HLS. Encode each variant, then write a master playlist that points at them:

#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
v360/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1800000,RESOLUTION=1280x720
v720/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=4000000,RESOLUTION=1920x1080
v1080/index.m3u8

ffmpeg can also generate the master playlist automatically with -master_pl_name master.m3u8 plus -var_stream_map. Declare BANDWIDTH honestly: a player that is told a stream is cheaper than it really is will pick it on a slow connection and buffer constantly.

Switching to fragmented MP4 segments

Modern HLS often uses fMP4 instead of MPEG-TS, which is also required if you want to share segments with DASH:

ffmpeg -i input.mp4 -c copy \
  -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 \
  -hls_time 6 -hls_list_size 0 -f hls index.m3u8

The playlist then references an init.mp4 via #EXT-X-MAP and .m4s segment files. Serve both as video/mp4; a wrong MIME type here is a common cause of a stream that plays in one player but not another.

Check the output before publishing

Related reading