Skip to content

Instantly share code, notes, and snippets.

@espeon
Forked from mrbar42/README.md
Last active March 2, 2021 00:14
Show Gist options
  • Select an option

  • Save espeon/a0cd9dbbc586bf52540244107decf26a to your computer and use it in GitHub Desktop.

Select an option

Save espeon/a0cd9dbbc586bf52540244107decf26a to your computer and use it in GitHub Desktop.
bash scripts to create VOD HLS stream with ffmpeg almighty (tested on Linux and OS X) (edited to separate audio/better video presets)

running:

bash create-vod-hls.sh beach.mkv

will produce:

    beach/
      |- playlist.m3u8
      |- 360p.m3u8
      |- 360p_001.ts
      |- 360p_002.ts
      |- 480p.m3u8
      |- 480p_001.ts
      |- 480p_002.ts
      |- 720p.m3u8
      |- 720p_001.ts
      |- 720p_002.ts
      |- 1080p.m3u8
      |- 1080p_001.ts
      |- 1080p_002.ts  

origin: http://docs.peer5.com/guides/production-ready-hls-vod/

#!/usr/bin/env bash
START_TIME=$SECONDS
set -e
echo "-----START GENERATING HLS STREAM-----"
# Usage create-vod-hls.sh SOURCE_FILE [OUTPUT_NAME]
[[ ! "${1}" ]] && echo "Usage: create-vod-hls.sh SOURCE_FILE [OUTPUT_NAME]" && exit 1
# comment/add lines here to control which renditions would be created
renditions=(
# resolution bitrate
"640x360 750k"
"842x480 1000k"
"1280x720 2500k"
"1920x1080 4500k"
"2560x1440 5500k"
"3840x2160 6000k"
)
audioBitRate="128"
# TODO: make this an array
languages="en English"
segment_target_duration=10 # try to create a new segment every 10 seconds
max_bitrate_ratio=1.07 # maximum accepted bitrate fluctuations
rate_monitor_buffer_ratio=1.5 # maximum buffer size between bitrate conformance checks
#########################################################################
source="${1}"
target="${2}"
if [[ ! "${target}" ]]; then
target="${source##*/}" # leave only last component of path
target="${target%.*}" # strip extension
fi
mkdir -p ${target}
# ----CUSTOM----
sourceResolution="$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 ${source})"
# echo ${sourceResolution}
arrIN=(${sourceResolution//x/ })
sourceWidth="${arrIN[0]}"
sourceHeight="${arrIN[1]}"
# ----END CUSTOM----
# Source data parsing
# stolen from https://github.com/b-fuze/batch-encoder/blob/master/encoder.sh
streams=$( ffprobe ${source} 2>&1 )
cur_vid_sub_streams=$( grep -E "Stream #.+Subtitle" <<< "$streams" )
# TODO: DRY
subtitle_stream=$( grep -E 'Subtitle.+default' <<< "$streams" | sed -Ee 's/^.+Stream #0:([0-9]+).*$/\1/' )
subtitle_stream_type=$( grep -E 'Subtitle.+default' <<< "$streams" | sed -Ee 's/^.+Subtitle: ([a-z_]+).*$/\1/' )
# If the default subtitle wasn't detected then just use the first
# subtitle
if [[ -z $subtitle_stream_type ]]; then
subtitle_stream=$( grep -E "Subtitle" -m 1 <<< "$streams" | sed -Ee 's/^.+Stream #0:([0-9]+).*$/\1/' )
subtitle_stream_type=$( grep -E 'Subtitle' -m 1 <<< "$streams" | sed -Ee 's/^.+Subtitle: ([a-z_]+).*$/\1/' )
fi
# deal with PGS subs (ugh)
if [[ $subtitle_stream_type == dvd_subtitle || $subtitle_stream_type == hdmv_pgs_subtitle ]]; then
hardcode_cmd="[0:s]overlay"
else
hardcode_cmd="subtitles=${source}:si=0"
fi
audio_channels=$( grep -E 'Audio' -m 1 <<< "$streams" | sed -Ee "s/^.+Audio: [a-z0-9_]+, [0-9]+\s[a-zA-Z_]+, ([0-9.a-z().]+).*$/\1/" )
key_frames_interval="$(echo `ffprobe ${source} 2>&1 | grep -oE '[[:digit:]]+(.[[:digit:]]+)? fps' | grep -oE '[[:digit:]]+(.[[:digit:]]+)?'`*2 | bc || echo '')"
key_frames_interval=${key_frames_interval:-50}
key_frames_interval=$(echo `printf "%.1f\n" $(bc -l <<<"$key_frames_interval/10")`*10 | bc) # round
key_frames_interval=${key_frames_interval%.*} # truncate to integer
# static parameters that are similar for all renditions
static_params="-c:v h264 -profile:v high -sc_threshold 0"
static_params+=" -g ${key_frames_interval} -keyint_min ${key_frames_interval} -hls_time ${segment_target_duration}"
static_params+=" -hls_playlist_type vod"
# misc params
misc_params="-hide_banner -y"
master_playlist="#EXTM3U
#EXT-X-VERSION:3
"
cmd=""
resolutionValid=0
prevHeight=0
# language fields
languageCode="$(echo ${languages} | cut -d ' ' -f 1)"
languagename="$(echo ${languages} | cut -d ' ' -f 2)"
# add separate audio track
audioName="${audioBitRate}k"
cmd+="-sn -map 0:a -c:a aac -ar 48000 -b:a 128k -vn -sn "
# handle downmixing to stereo
if [[ $audio_channels == "5.1(side)" ]]; then
cmd+="-vol 425 -af pan=stereo|FL=0.5*FC+0.707*FL+0.707*BL+0.5*LFE|FR=0.5*FC+0.707*FR+0.707*BR+0.5*LFE "
fi
cmd+="-hls_time ${segment_target_duration} -hls_playlist_type vod -hls_segment_filename ${target}/${audioName}_%03d.ts ${target}/${audioName}.m3u8 "
master_playlist+="\n#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio\",NAME=\"Japanese\",LANGUAGE=\"ja\",AUTOSELECT=YES,URI=\"${audioName}.m3u8\"\n\n"
for rendition in "${renditions[@]}"; do
# drop extraneous spaces
rendition="${rendition/[[:space:]]+/ }"
# rendition fields
resolution="$(echo ${rendition} | cut -d ' ' -f 1)"
# bitrate not used as we switched to VBR
bitrate="$(echo ${rendition} | cut -d ' ' -f 2)"
# calculated fields
width="$(echo ${resolution} | grep -oE '^[[:digit:]]+')"
height="$(echo ${resolution} | grep -oE '[[:digit:]]+$')"
maxrate="$(echo "`echo ${bitrate} | grep -oE '[[:digit:]]+'`*${max_bitrate_ratio}" | bc)"
bufsize="$(echo "`echo ${bitrate} | grep -oE '[[:digit:]]+'`*${rate_monitor_buffer_ratio}" | bc)"
bandwidth="$(echo ${bitrate} | grep -oE '[[:digit:]]+')000"
name="${height}p"
if [ $sourceHeight -le $prevHeight ]; then
echo "video source has height smaller than output height (${height})"
break
fi
widthParam=0
heightParam=0
if [ $(((width / sourceWidth) * sourceHeight)) -gt $height ]; then
widthParam=-2
heightParam=$height
else
widthParam=$width
heightParam=-2
fi
cmd+=" ${static_params}"
# AUE settings
cmd+=" -c:v libx264 -crf 18 -preset fast -tune animation -subq 10 -me_method umh -pix_fmt yuv420p -level 3.1 -movflags faststart"
cmd+=" -maxrate ${maxrate%.*}k -bufsize ${bufsize%.*}k -an -sn -filter_complex [0:v]${hardcode_cmd},scale=w=${widthParam}:h=${heightParam}[v] -map [v] "
cmd+=" -hls_segment_filename ${target}/${name}_%03d.ts ${target}/${name}.m3u8"
# add rendition entry in the master playlist
master_playlist+="#EXT-X-STREAM-INF:BANDWIDTH=${bandwidth},RESOLUTION=${resolution},AUDIO=\"audio\"\n${name}.m3u8\n"
resolutionValid=1
prevHeight=${height}
done
if [ $resolutionValid -eq 1 ]; then
# start conversion
echo -e "Executing command:\nffmpeg.exe ${misc_params} -i ${source} ${cmd}\n"
ffmpeg.exe ${misc_params} -i ${source} ${cmd}
# create master playlist file
echo -e "${master_playlist}" > ${target}/playlist.m3u8
echo "Done - encoded HLS is at ${target}/"
else
echo "Video source is too small"
exit 1
fi
ELAPSED_TIME=$(($SECONDS - $START_TIME))
echo "Elapsed time: ${ELAPSED_TIME}"
echo "-----FINISHED GENERATING HLS STREAM-----"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment