|
#!/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-----" |