Adjust encryption loop to ensure the IV is based on the segment number rather than a counter. HLS-22
--- a/HLS-Stream-Creator.sh
+++ b/HLS-Stream-Creator.sh
@@ -50,8 +50,8 @@
# Video codec for the output video. Will be used as an value for the -vcodec argument
VIDEO_CODEC=${VIDEO_CODEC:-"libx264"}
-# Video codec for the output video. Will be used as an value for the -acodec argument
-AUDIO_CODEC=${AUDIO_CODEC:-"libfdk_aac"}
+# Audio codec for the output video. Will be used as an value for the -acodec argument
+AUDIO_CODEC=${AUDIO_CODEC:-"aac"}
# Additional flags for ffmpeg
FFMPEG_FLAGS=${FFMPEG_FLAGS:-""}
@@ -94,6 +94,11 @@
-f Foreground encoding only (don't fork the encoding processes into the background - adaptive non-live streams only)
-p Playlist filename
-t Segment filename prefix
+ -S Segment directory name (default none)
+ -e Encrypt the HLS segments (default none)
+ -2 2-pass encoding
+ -q Quality (changes to CRF)
+ -C Constant Bit Rate (CBR as opposed to AVB)
Deprecated Legacy usage:
HLS-Stream-Creator.sh inputfile segmentlength(seconds) [outputdir='./output']
@@ -113,9 +118,28 @@
playlist_name="$1"
output_name="$2"
bitrate="$3"
-
-$FFMPEG -i "$INPUTFILE" \
- -loglevel error -y \
+infile="$4"
+
+local PASSVAR=
+if $TWOPASS; then
+ local LOGFILE="$OUTPUT_DIRECTORY/bitrate$br"
+ PASSVAR="-passlogfile \"$LOGFILE\" -pass 2"
+
+ $FFMPEG -i "$infile" \
+ -pass 1 \
+ -passlogfile "$LOGFILE" \
+ -an \
+ -vcodec libx264 \
+ -f mpegts \
+ $bitrate \
+ $FFMPEG_ADDITIONAL \
+ -loglevel error -y \
+ /dev/null
+fi
+
+$FFMPEG -i "$infile" \
+ $PASSVAR \
+ -loglevel verbose -y \
-vcodec "$VIDEO_CODEC" \
-acodec "$AUDIO_CODEC" \
-threads "$NUMTHREADS" \
@@ -165,28 +189,83 @@
if ! kill -0 ${PIDS[$i]} 2> /dev/null
then
echo "Encoding for bitrate ${BITRATE_PROCESSES[$i]}k completed"
+
+ if [ "$LIVE_STREAM" == "1" ] && [ `grep 'EXT-X-ENDLIST' "$OUTPUT_DIRECTORY/${PLAYLIST_PREFIX}_${BITRATE_PROCESSES[$i]}.m3u8" | wc -l ` == "0" ]
+ then
+ # Correctly terminate the manifest. See HLS-15 for info on why
+ echo "#EXT-X-ENDLIST" >> "$OUTPUT_DIRECTORY/${PLAYLIST_PREFIX}_${BITRATE_PROCESSES[$i]}.m3u8"
+ fi
+
unset BITRATE_PROCESSES[$i]
unset PIDS[$i]
fi
done
PIDS=("${PIDS[@]}") # remove any nulls
+ BITRATE_PROCESSES=("${BITRATE_PROCESSES[@]}") # remove any nulls
sleep 1
done
}
+function encrypt(){
+# Encrypt the generated segments with AES-128 bits
+
+
+ # Only run the encryption routine if it's been enabled (and not blocked)
+ if [ ! "$ENCRYPT" == "1" ] || [ "$LIVE_STREAM" == "1" ]
+ then
+ return
+ fi
+
+
+ KEY_FILE="$OUTPUT_DIRECTORY/${PLAYLIST_PREFIX}.key"
+
+ openssl rand 16 > $KEY_FILE
+ ENCRYPTION_KEY=$(cat $KEY_FILE | hexdump -e '16/1 "%02x"')
+
+ for file in ${OUTPUT_DIRECTORY}/*.ts
+ do
+ SEG_NO=$( echo "$file" | grep -o -P '_[0-9]+\.ts' | tr -dc '0-9' )
+ ENC_FILENAME="$OUTPUT_DIRECTORY/${SEGMENT_PREFIX}_enc_${SEG_NO}".ts
+
+ # Strip leading 0's so printf doesn't think it's octal
+ #SEG_NO=${SEG_NO##+(0)} # Doesn't work for some reason - need to check shopt to look further into it
+ SEG_NO=$(echo $SEG_NO | sed 's/^0*//' )
+
+ # Convert the segment number to an IV.
+ INIT_VECTOR=$(printf '%032x' $SEG_NO)
+ openssl aes-128-cbc -e -in $file -out $ENC_FILENAME -nosalt -iv $INIT_VECTOR -K $ENCRYPTION_KEY
+
+ # Move encrypted file to the original filename, so that the m3u8 file does not have to be changed
+ mv $ENC_FILENAME $file
+
+ done
+
+
+ # this isn't technically correct as we needn't write into the master, but should still work
+ for manifest in ${OUTPUT_DIRECTORY}/*.m3u8
+ do
+ # Insert the KEY at the 5'th line in the m3u8 file
+ sed -i "5i #EXT-X-KEY:METHOD=AES-128,URI="${PLAYLIST_PREFIX}.key "$manifest"
+ done
+}
# This is used internally, if the user wants to specify their own flags they should be
# setting FFMPEG_FLAGS
FFMPEG_ADDITIONAL=''
LIVE_SEGMENT_COUNT=0
-
+IS_FIFO=0
+TMPDIR=${TMPDIR:-"/tmp"}
+MYPID=$$
+TWOPASS=false
+QUALITY=
+CONSTANT=false
# Get the input data
# This exists to maintain b/c
LEGACY_ARGS=1
# If even one argument is supplied, switch off legacy argument style
-while getopts "i:o:s:c:b:p:t:lf" flag
+while getopts "i:o:s:c:b:p:t:S:q:Clfe2" flag
do
LEGACY_ARGS=0
case "$flag" in
@@ -199,6 +278,11 @@
f) NO_FORK=1;;
p) PLAYLIST_PREFIX="$OPTARG";;
t) SEGMENT_PREFIX="$OPTARG";;
+ S) SEGMENT_DIRECTORY="$OPTARG";;
+ e) ENCRYPT=1;;
+ 2) TWOPASS=true;;
+ q) QUALITY="$OPTARG";;
+ C) CONSTANT=true;;
esac
done
@@ -234,6 +318,14 @@
exit 1
fi
+# Check whether the input is a named pipe
+if [ -p "$INPUTFILE" ]
+then
+ echo "Warning: Input is FIFO - EXPERIMENTAL"
+ IS_FIFO=1
+
+fi
+
# Check output directory exists otherwise create it
if [ ! -w $OUTPUT_DIRECTORY ]
then
@@ -247,7 +339,8 @@
if [ "$LIVE_SEGMENT_COUNT" -gt 0 ]
then
- FFMPEG_ADDITIONAL+=" -segment_list_size $LIVE_SEGMENT_COUNT"
+ WRAP_POINT=$(($LIVE_SEGMENT_COUNT * 2)) # Wrap the segment numbering after 2 manifest lengths - prevents disks from filling
+ FFMPEG_ADDITIONAL+=" -segment_list_size $LIVE_SEGMENT_COUNT -segment_wrap $WRAP_POINT"
fi
fi
@@ -258,6 +351,20 @@
# If a prefix hasn't been specified, use the input filename
PLAYLIST_PREFIX=${PLAYLIST_PREFIX:-$INPUTFILENAME}
SEGMENT_PREFIX=${SEGMENT_PREFIX:-$PLAYLIST_PREFIX}
+
+# The 'S' option allows segments and bitrate specific manifests to be placed in a subdir
+SEGMENT_DIRECTORY=${SEGMENT_DIRECTORY:-''}
+
+if [ ! "$SEGMENT_DIRECTORY" == "" ]
+then
+
+ if [ ! -d "${OUTPUT_DIRECTORY}/${SEGMENT_DIRECTORY}" ]
+ then
+ mkdir "${OUTPUT_DIRECTORY}/${SEGMENT_DIRECTORY}"
+ fi
+
+ SEGMENT_DIRECTORY+="/"
+fi
# Set the bitrate
if [ ! "$OP_BITRATES" == "" ]
@@ -273,43 +380,84 @@
createVariantPlaylist "$OUTPUT_DIRECTORY/${PLAYLIST_PREFIX}_master.m3u8"
for br in $OP_BITRATES
do
- appendVariantPlaylistentry "$OUTPUT_DIRECTORY/${PLAYLIST_PREFIX}_master.m3u8" "${PLAYLIST_PREFIX}_${br}.m3u8" "$br"
+ appendVariantPlaylistentry "$OUTPUT_DIRECTORY/${PLAYLIST_PREFIX}_master.m3u8" "${SEGMENT_DIRECTORY}${PLAYLIST_PREFIX}_${br}.m3u8" "$br"
done
+
+ OUTPUT_DIRECTORY+=$SEGMENT_DIRECTORY
# Now for the longer running bit, transcode the video
for br in $OP_BITRATES
do
- BITRATE="-b:v ${br}k -bufsize ${br}k"
+ if [ -z $QUALITY ]; then
+ if $CONSTANT; then
+ BITRATE="-b:v ${br}k -bufsize ${br}k -minrate ${br}k -maxrate ${br}k"
+ else
+ BITRATE="-b:v ${br}k"
+ fi
+ else
+ BITRATE="-crf $QUALITY -maxrate ${br}k -bufsize ${br}k"
+ if [ $VIDEO_CODEC = "libx265" ]; then
+ BITRATE="$BITRATE -x265-params --vbv-maxrate ${br}k --vbv-bufsize ${br}k"
+ fi
+ fi
+ echo "Bitrate options: $BITRATE"
# Finally, lets build the output filename format
OUT_NAME="${SEGMENT_PREFIX}_${br}_%05d.ts"
PLAYLIST_NAME="$OUTPUT_DIRECTORY/${PLAYLIST_PREFIX}_${br}.m3u8"
-
+ SOURCE_FILE="$INPUTFILE"
echo "Generating HLS segments for bitrate ${br}k - this may take some time"
if [ "$NO_FORK" == "0" ] || [ "$LIVE_STREAM" == "1" ]
then
# Processing Starts
- createStream "$PLAYLIST_NAME" "$OUT_NAME" "$BITRATE" &
+ if [ "$IS_FIFO" == "1" ]
+ then
+ # Create a FIFO specially for this bitrate
+ SOURCE_FILE="$TMPDIR/hlsc.encode.$MYPID.$br"
+ mknod "$SOURCE_FILE" p
+ fi
+
+ # Schedule the encode
+ createStream "$PLAYLIST_NAME" "$OUT_NAME" "$BITRATE" "$SOURCE_FILE" &
PID=$!
PIDS=(${PIDS[@]} $PID)
BITRATE_PROCESSES=(${BITRATE_PROCESSES[@]} $br)
else
- createStream "$PLAYLIST_NAME" "$OUT_NAME" "$BITRATE"
+ createStream "$PLAYLIST_NAME" "$OUT_NAME" "$BITRATE" "$SOURCE_FILE"
fi
- # Will deal with exit statuses shortly.
- #|| exit 1
done
+
+ if [ "$IS_FIFO" == "1" ]
+ then
+ # If the input was a FIFO we need to read from it and push into the new FIFOs
+ cat "$INPUTFILE" | tee $(for br in $OP_BITRATES; do echo "$TMPDIR/hlsc.encode.$MYPID.$br"; done) > /dev/null &
+ TEE_PID=$!
+ fi
if [ "$NO_FORK" == "0" ] || [ "$LIVE_STREAM" == "1" ]
then
# Monitor the background tasks for completion
echo "All transcoding processes started, awaiting completion"
awaitCompletion
+
+ # As of HLS-20 encrypt will only run if the relevant vars are set
+ encrypt
fi
+ if [ "$IS_FIFO" == "1" ]
+ then
+ for br in $OP_BITRATES
+ do
+ rm -f "$TMPDIR/hlsc.encode.$MYPID.$br";
+ done
+ # If we were interrupted, tee may still be running
+ kill $TEE_PID 2> /dev/null
+ fi
+
else
+ OUTPUT_DIRECTORY+=$SEGMENT_DIRECTORY
# No bitrate specified
# Finally, lets build the output filename format
@@ -320,8 +468,9 @@
# Processing Starts
- createStream "$PLAYLIST_NAME" "$OUT_NAME" "$BITRATE"
-
-
-fi
-
+ createStream "$PLAYLIST_NAME" "$OUT_NAME" "$BITRATE" "$INPUTFILE"
+
+ # As of HLS-20 encrypt will only run if the relevant vars are set
+ encrypt
+fi
+
--- a/README.md
+++ b/README.md
@@ -36,15 +36,22 @@
Mandatory Arguments:
-i [file] Input file
- -s [s] Segment length (seconds)
+ -s [s] Segment length (seconds)
Optional Arguments:
-o [directory] Output directory (default: ./output)
-c [count] Number of segments to include in playlist (live streams only) - 0 is no limit
+ -e Encrypt the HLS segments (a key will be generated automatically)
-b [bitrates] Output video Bitrates in kb/s (comma seperated list for adaptive streams)
+ -p [name] Playlist filename prefix
+ -t [name] Segment filename prefix
-l Input is a live stream
-f Foreground encoding only (adaptive non-live streams only)
+ -S Name of a subdirectory to put segments into
+ -2 Use two-pass encoding
+ -q [quality] Change encoding to CFR with [quality]
+ -C Use constant bitrate as opposed to variable bitrate
```
@@ -99,6 +106,12 @@
```
+H265 details
+------------
+
+Check has been added for libx265 to enforce bitrate limits for H265 since it uses additional parameters.
+
+
Additional Environment Variables
-------------------------------