Skip to content

Instantly share code, notes, and snippets.

@JohnCoates
Last active September 27, 2021 20:24
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JohnCoates/e55b3b064ae82935b8d96828e94333b7 to your computer and use it in GitHub Desktop.
Save JohnCoates/e55b3b064ae82935b8d96828e94333b7 to your computer and use it in GitHub Desktop.
HEVC HLS Creation

Install Requirements

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install ffmpeg --with-x265

HTTP Live Streaming tools from https://developer.apple.com/streaming/ or https://www.dropbox.com/s/y48h70xk0nnt1vm/HTTPLiveStreamingTools368.dmg?dl=1

Usage

Note: Audio should be split into its own stream, script doesn't do this currently

(curl -fsSL -o in.sh https://gist.githubusercontent.com/JohnCoates/e55b3b064ae82935b8d96828e94333b7/raw/encodingFunctions.sh) && source in.sh && rm in.sh
INPUT_FILE=/path/to/input

createStandardEncodes "$INPUT_FILE"

If you'd like to choose your encodings the argument list is: (input file) (maximum width) (bitrate in Kbps) (audio bitrate in Kbps) (video codec: 265 or 264) Below are the commands that createStandardEncodes runs

captureEncodes
encodeAtBitrate "$INPUT_FILE" 1920 5800 192 265
encodeAtBitrate "$INPUT_FILE" 1920 4500 160 265
encodeAtBitrate "$INPUT_FILE" 1920 3200 128 265
encodeAtBitrate "$INPUT_FILE" 1280 2400 128 265
encodeAtBitrate "$INPUT_FILE" 960 1700 128 265
encodeAtBitrate "$INPUT_FILE" 768 990 128 265
encodeAtBitrate "$INPUT_FILE" 640 660 128 265
encodeAtBitrate "$INPUT_FILE" 480 350 128 265
encodeAtBitrate "$INPUT_FILE" 416 145 128 265

encodeAtBitrate "$INPUT_FILE" 1920 7800 128 264
encodeAtBitrate "$INPUT_FILE" 1920 6000 128 264
encodeAtBitrate "$INPUT_FILE" 1920 4500 128 264
encodeAtBitrate "$INPUT_FILE" 1280 3000 128 264
encodeAtBitrate "$INPUT_FILE" 960 2000 128 264
encodeAtBitrate "$INPUT_FILE" 768 1100 128 264
encodeAtBitrate "$INPUT_FILE" 640 730 128 264
encodeAtBitrate "$INPUT_FILE" 480 730 128 264
encodeAtBitrate "$INPUT_FILE" 416 730 128 264
createMasterPlaylist

Bonus points - Validate Playlist

brew install python@2
pip install python-dateutil

mediastreamvalidator master.m3u8
python2 /usr/local/bin/hlsreport.py validation_data.json
open validation_data.html

Test In Quicktime

Download Docker for macOS from: https://store.docker.com/editions/community/docker-ce-desktop-mac Run the following command to serve the files: docker run --name serveHLS -v $PWD:/usr/share/nginx/html:ro -p 9099:80 -d nginx

Load this URL in Quicktime: http://localhost:9099/master.m3u8

function captureEncodes {
echo -n > /tmp/capture.encodes
}
function encodeAtBitrate {
INPUT_FILE=$1
SCALE_DOWN_TO_WIDTH=$2
VIDEO_BITRATE=$3
AUDIO_BITRATE=$4
VIDEO_CODEC=$5
IDENTIFIER="${SCALE_DOWN_TO_WIDTH}.${VIDEO_BITRATE}.${VIDEO_CODEC}"
echo -n "Encoding file $INPUT_FILE - "
echo "max width: ${SCALE_DOWN_TO_WIDTH} video: ${VIDEO_BITRATE}kbps, audio: ${AUDIO_BITRATE}kbps with video codec H.$VIDEO_CODEC"
if [ "$VIDEO_CODEC" == "265" ]
then
HEVC_PROFILE="-profile:v main10"
VIDEO_TAG="-vtag hvc1"
X265_LOGLEVEL="-x265-params log-level=error"
else
HEVC_PROFILE=""
VIDEO_TAG=""
X265_LOGLEVEL=""
fi
BUFFER_SIZE=$(($VIDEO_BITRATE * 2))
FILENAME="${IDENTIFIER}.mp4"
options=(
# don't take over input, so we can paste multiple commands
-nostdin
# quiet
-loglevel panic
# don't show ffmpeg version
-hide_banner
# input file
-i "$INPUT_FILE"
# overwrite existing output file
-y
# Use HEVC codec
-vcodec libx$VIDEO_CODEC
# HEVC Profile Level
$HEVC_PROFILE
$X265_LOGLEVEL
-level 4.0
# Use AAC codec for audio
-acodec aac
# scale down only
-vf "scale='min(${SCALE_DOWN_TO_WIDTH},iw)':-1"
# Video bitrate
-b:v ${VIDEO_BITRATE}k
# Maximum video bitrate
-maxrate ${VIDEO_BITRATE}k
# Video buffer size - Should be twice maxrate
-bufsize ${BUFFER_SIZE}k
# Audio bitrate
-b:a ${AUDIO_BITRATE}k
# Create key frame every 48 frames
-g 48 -keyint_min 48
# Don't create key frames on scene change
-sc_threshold 0
# Don't use edit lists, messes up Apple tools
-use_editlist 0
# Create fragmented mp4
-movflags faststart+frag_keyframe
$VIDEO_TAG
$FILENAME
)
ffmpeg "${options[@]}"
mkdir -p "$IDENTIFIER"
mediafilesegmenter -q -r -s -t 10 -f "$IDENTIFIER/" $FILENAME
# with the file segmented, we no longer need the original encode
rm $FILENAME
echo -n "${IDENTIFIER} " >> /tmp/capture.encodes
}
function createMasterPlaylist {
read -r -a ENCODES < /tmp/capture.encodes
PLAYLIST=""
for IDENTIFIER in "${ENCODES[@]}"
do
PLAYLIST+="$IDENTIFIER/prog_index.m3u8 $IDENTIFIER.plist -iframe-url $IDENTIFIER/iframe_index.m3u8 "
done
variantplaylistcreator -o master.m3u8 $PLAYLIST
}
function createStandardEncodes {
INPUT_FILE=$1
captureEncodes
encodeAtBitrate "$INPUT_FILE" 1920 5800 192 265
encodeAtBitrate "$INPUT_FILE" 1920 4500 160 265
encodeAtBitrate "$INPUT_FILE" 1920 3200 128 265
encodeAtBitrate "$INPUT_FILE" 1280 2400 128 265
encodeAtBitrate "$INPUT_FILE" 960 1700 128 265
encodeAtBitrate "$INPUT_FILE" 768 990 128 265
encodeAtBitrate "$INPUT_FILE" 640 660 128 265
encodeAtBitrate "$INPUT_FILE" 480 350 128 265
encodeAtBitrate "$INPUT_FILE" 416 145 128 265
encodeAtBitrate "$INPUT_FILE" 1920 7800 128 264
encodeAtBitrate "$INPUT_FILE" 1920 6000 128 264
encodeAtBitrate "$INPUT_FILE" 1920 4500 128 264
encodeAtBitrate "$INPUT_FILE" 1280 3000 128 264
encodeAtBitrate "$INPUT_FILE" 960 2000 128 264
encodeAtBitrate "$INPUT_FILE" 768 1100 128 264
encodeAtBitrate "$INPUT_FILE" 640 730 128 264
encodeAtBitrate "$INPUT_FILE" 480 730 128 264
encodeAtBitrate "$INPUT_FILE" 416 730 128 264
createMasterPlaylist
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment