Implement ability to specify resolution with bitrates. See HLS-27
Implement ability to specify resolution with bitrates. See HLS-27

This is just a first pass at this, and I fully expect it to break in a variety of weird and wonderful ways.

There's also some reliance on globals that I don't like (see comments in file), so that's still in need of tidying up. We should probably also think about testing that none of the given resolutions are higher than that of the source.

But, you can now pass a resolution in as part of the bitrate string: {{bitrate-widthxheight}}

As an example, ran the following test encode
{noformat}$ ./HLS-Stream-Creator.sh -i testvid.mp4 -b 2744,1000-1280x720 -s 10
{noformat}
The original is 1080p at 2744k so that should give us a 1080p rendition as well as a 720p.

Master playlist is generated correctly
{noformat}$ cat testvid_master.m3u8
testvid_2744.m3u8
testvid_1000-1280x720.m3u8
{noformat}

Segments report approximately the right bitrate (used vbr to speed up the test), and also the correct resolution
{noformat}$ ffprobe -i testvid_1000_00001.ts
Input #0, mpegts, from 'testvid_1000_00001.ts':
Duration: 00:00:13.70, start: 12.890956, bitrate: 989 kb/s
Program 1
Metadata:
service_name : Service01
service_provider: FFmpeg
Stream #0:0[0x100]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 29.97 fps, 29.97 tbr, 90k tbn, 59.94 tbc
Stream #0:1[0x101](und): Audio: aac (LC) ([15][0][0][0] / 0x000F), 44100 Hz, stereo, fltp, 100 kb/s

$ ffprobe -i testvid_2744_00001.ts
Input #0, mpegts, from 'testvid_2744_00001.ts':
Duration: 00:00:13.70, start: 12.890956, bitrate: 2335 kb/s
Program 1
Metadata:
service_name : Service01
service_provider: FFmpeg
Stream #0:0[0x100]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 29.97 fps, 29.97 tbr, 90k tbn, 59.94 tbc
Stream #0:1[0x101](und): Audio: aac (LC) ([15][0][0][0] / 0x000F), 44100 Hz, stereo, fltp, 100 kb/s
{noformat}

Not been tested with a linear stream yet (though it _should_ work)

--- 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:-""}
@@ -65,6 +65,12 @@
 
 # Determines whether the processing for adaptive streams should run sequentially or not
 NO_FORK=${NO_FORK:-0}
+
+# Path Prefix (Not incl. Key)
+PATH_PREFIX=${PATH_PREFIX:-''}
+
+# Key Path Prefix
+KEY_PREFIX=${KEY_PREFIX:-''}
 
 # Lets put our functions here
 
@@ -95,6 +101,13 @@
 	-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)
+	-u	Path Prefix
+	-k	Key Prefix
+	-K	Key Name
 
 Deprecated Legacy usage:
 	HLS-Stream-Creator.sh inputfile segmentlength(seconds) [outputdir='./output']
@@ -116,8 +129,36 @@
 bitrate="$3"
 infile="$4"
 
+
+# Resolution comes from global $resolution
+#
+# See HLS-27
+#
+# I don't like this use of $resolution when it's not local, but there was already a use 
+# of non-local $br in here so I'll fix this later
+
+
+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 \
+		$resolution \
+		$FFMPEG_ADDITIONAL \
+		-loglevel error -y \
+		/dev/null
+fi
+echo "Using res $resolution aaaa"
 $FFMPEG -i "$infile" \
-    -loglevel error -y \
+    $PASSVAR \
+    -y \
     -vcodec "$VIDEO_CODEC" \
     -acodec "$AUDIO_CODEC" \
     -threads "$NUMTHREADS" \
@@ -128,6 +169,7 @@
     -segment_list "$playlist_name" \
     -segment_time "$SEGLENGTH" \
     -segment_format mpeg_ts \
+    $resolution \
     $bitrate \
     $FFMPEG_ADDITIONAL \
     $FFMPEG_FLAGS \
@@ -144,11 +186,20 @@
 function appendVariantPlaylistentry(){
 playlist_name=$1
 playlist_path=$2
-playlist_bw=$(( $3 * 1000 )) # bits not bytes :)
+bw_statement=$3
+m3u8_resolution=''
+
+if [[ "$bw_statement" == *"-"* ]]
+then
+    m3u8_resolution=",RESOLUTION=$(echo "$bw_statement" | cut -d- -f2)"
+    bw_statement=$(echo "$bw_statement" | cut -d- -f1) 
+fi
+
+playlist_bw=$(( $bw_statement * 1000 )) # bits not bytes :)
 
 cat << EOM >> "$playlist_name"
-#EXT-X-STREAM-INF:BANDWIDTH=$playlist_bw
-$playlist_path
+#EXT-X-STREAM-INF:BANDWIDTH=$playlist_bw$m3u8_resolution
+$PATH_PREFIX$playlist_path
 EOM
 
 }
@@ -179,10 +230,54 @@
 	  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
+
+    echo "Generating Encryption Key"
+    KEY_FILE="$OUTPUT_DIRECTORY/${KEY_NAME}.key"
+
+    openssl rand 16 > $KEY_FILE
+    ENCRYPTION_KEY=$(cat $KEY_FILE | hexdump -e '16/1 "%02x"')
+
+    echo "Encrypting Segments"
+    for SEGMENT_FILE in ${OUTPUT_DIRECTORY}/*.ts
+    do
+        SEG_NO=$( echo "$SEGMENT_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 $SEGMENT_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 $SEGMENT_FILE
+        
+    done
+
+    echo "Updating Manifests"
+    # 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="${KEY_PREFIX}${KEY_NAME}.key "$manifest"
+    done
+}
 
 # This is used internally, if the user wants to specify their own flags they should be
 # setting FFMPEG_FLAGS
@@ -191,13 +286,16 @@
 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:S:lf" flag
+while getopts "i:o:s:c:b:p:t:S:q:u:k:K:Clfe2" flag
 do
 	LEGACY_ARGS=0
         case "$flag" in
@@ -211,6 +309,13 @@
 		p) PLAYLIST_PREFIX="$OPTARG";;
 		t) SEGMENT_PREFIX="$OPTARG";;
 		S) SEGMENT_DIRECTORY="$OPTARG";;
+		e) ENCRYPT=1;;
+		2) TWOPASS=true;;
+		q) QUALITY="$OPTARG";;
+		C) CONSTANT=true;;
+		u) PATH_PREFIX="$OPTARG";;
+		k) KEY_PREFIX="$OPTARG";;
+		K) KEY_NAME="$OPTARG";;
         esac
 done
 
@@ -251,7 +356,17 @@
 then
   echo "Warning: Input is FIFO - EXPERIMENTAL"
   IS_FIFO=1
-
+fi
+
+# Make sure that the trailing slashes are added
+if [ "$PATH_PREFIX" != "" ] && [ "${PATH_PREFIX:$length:1}" != "/" ]
+then
+  PATH_PREFIX+="/"
+fi
+
+if [ "$KEY_PREFIX" != "" ] && [ "${KEY_PREFIX:$length:1}" != "/" ]
+then
+  KEY_PREFIX+="/"
 fi
 
 # Check output directory exists otherwise create it
@@ -267,7 +382,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
 
@@ -278,6 +394,7 @@
 # If a prefix hasn't been specified, use the input filename
 PLAYLIST_PREFIX=${PLAYLIST_PREFIX:-$INPUTFILENAME}
 SEGMENT_PREFIX=${SEGMENT_PREFIX:-$PLAYLIST_PREFIX}
+KEY_NAME=${KEY_NAME:-$PLAYLIST_PREFIX}
 
 # The 'S' option allows segments and bitrate specific manifests to be placed in a subdir
 SEGMENT_DIRECTORY=${SEGMENT_DIRECTORY:-''}
@@ -291,6 +408,7 @@
 	fi
 
 	SEGMENT_DIRECTORY+="/"
+	OUTPUT_DIRECTORY+="/"
 fi
 
 # Set the bitrate
@@ -315,7 +433,29 @@
       # Now for the longer running bit, transcode the video
       for br in $OP_BITRATES
       do
-	      BITRATE="-b:v ${br}k"
+      
+              # Check whether there's a resolution included in the bitrate string
+              #
+              # See HLS-27
+              if [[ "$br" == *"-"* ]]
+              then
+                resolution="-vf scale=$(echo "$br" | cut -d- -f2 | sed 's/x/:/')"
+                br=$(echo "$br" | cut -d- -f1) 
+              fi
+      
+              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"
@@ -355,6 +495,9 @@
 	    # 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" ]
@@ -382,6 +525,7 @@
 
   createStream "$PLAYLIST_NAME" "$OUT_NAME" "$BITRATE" "$INPUTFILE"
 
-
-fi
-
+  # As of HLS-20 encrypt will only run if the relevant vars are set
+  encrypt
+fi
+

file:a/README.md -> file:b/README.md
--- a/README.md
+++ b/README.md
@@ -36,18 +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
 ```
 
 
@@ -71,10 +75,38 @@
 In either case, in accordance with the HLS spec, the audio bitrate will remain unchanged
 
 
+
+Encrypted Streams
+-------------------
+
+HLS-Stream-Creator can also create encrypted HLS streams, it's enabled by passing *-e*
+
+```
+./HLS-Stream-Creator.sh -i example.avi -e -s 10 -b 28,64,128,256
+
+```
+
+The script will generate a 128 bit key and save it to a *.key* file in the same directory as the segments. Each segment will be AES-128 encrypted using an IV which corresponds to it's segment number (the [default behaviour](https://developer.apple.com/library/content/technotes/tn2288/_index.html#//apple_ref/doc/uid/DTS40012238-CH1-ENCRYPT) for HLS).
+
+The manifests will then be updated to include the necessary `EXT-X-KEY` tag:
+
+```
+#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-MEDIA-SEQUENCE:0
+#EXT-X-ALLOW-CACHE:YES
+#EXT-X-KEY:METHOD=AES-128,URI=big_buck_bunny_720p_stereo.avi.key
+#EXT-X-TARGETDURATION:17
+#EXTINF:10.500000,
+big_buck_bunny_720p_stereo.avi_1372_00000.ts
+```
+
+
+
 Output
 -------
 
-As of version 1, the HLS resources will be output to the directory *output*. These will consist of video segments encoded in H.264 with AAC audio and an m3u8 file in the format
+As of version 1, the HLS resources will be output to the directory *output* (unless a different directory has been specified with *-o*). These will consist of video segments encoded in H.264 with AAC audio and an m3u8 file in the format
 
 >\#EXTM3U  
 >\#EXT-X-MEDIA-SEQUENCE:0  
@@ -102,6 +134,41 @@
 ```
 
 
+H265 details
+------------
+
+Check has been added for libx265 to enforce bitrate limits for H265 since it uses additional parameters.
+
+
+
+Audio Codec Availability
+--------------------------
+
+Because *libfdk_aac* is a non-free codec, and is not available in all builds, commit 0796feb switched the default audio codec to *aac*.
+
+However, in older versions of ffmpeg, aac was marked as experimental - this includes the packages currently in the repos for Ubuntu Xenial. As a result, when running the script, you may see the following error
+
+```
+The encoder 'aac' is experimental but experimental codecs are not enabled, add '-strict -2' if you want to use it.
+```
+
+There are two ways to work around this. If you have the libfdk_aac codec installed, you can specify that it should be used instead
+```
+export AUDIO_CODEC="libfdk_aac"
+```
+
+Alternatively, you can update the ffmpeg flags to enable experimental codecs
+```
+export FFMPEG_FLAGS='-strict -2'
+```
+
+And the re-run HLS-Stream-Creator.
+
+[HLS-23](http://projects.bentasker.co.uk/jira_projects/browse/HLS-23.html) will, in future, update the script to check for this automatically.
+
+
+
+
 Additional Environment Variables
 -------------------------------
 
@@ -120,6 +187,18 @@
 ./HLS-Stream-Creator.sh example.avi 10
 ```
 
+
+OS X Users
+------------
+
+Segment encryption won't work out of the box on OS X as it relies on arguments which the BSD `grep` and `sed` commands don't support. In order to use encryption on OS X you must first install their GNU counterparts
+
+```
+brew install gnu-sed --with-default-names
+brew install grep --with-default-names
+```
+
+
 License
 --------
 

--- /dev/null
+++ b/output/false_master.m3u8
@@ -1,1 +1,8 @@
+#EXTM3U
+#EXT-X-STREAM-INF:BANDWIDTH=436000
+false_436.m3u8
+#EXT-X-STREAM-INF:BANDWIDTH=128000
+false_128.m3u8
+#EXT-X-STREAM-INF:BANDWIDTH=512000
+false_512.m3u8