#/bin/bash SERVERS=""; # List your servers here (separated by spaces) RATE2160=5000K; # 4K Bitrate RATE1080=2500K; # 1080P Bitrate RATE720=2200K; # 720P Bitrate AUDIOCHANNELS=6; # Set this to 6 for 5.1 surround AUDIOBITRATE=384K; if [ "$SERVERS" == "" ]; then echo "You must add a list of space-separated servers to the SERVERS variable." exit fi FILENAME=$(basename "$1") FILE="$1" FOLDER=$(echo "$1" | sed -e 's/'"$FILENAME"'//') LASTSECONDS=0 if [ -f $FOLDER/ffmpeg.benchmark ]; then BENCHMARK=$(cat "$FOLDER/ffmpeg.benchmark") fi SERVERCOUNT=$(echo $SERVERS | wc -w) get_resolution() { RESOLUTION=$(ffmpeg -i "$FOLDER/$FILENAME" 2>&1 | grep "Video" | cut -d',' -f2- | sed -e 's/^.*[ ,][0-9][0-9]*x\([0-9][0-9]*\)[ ,].*$/\1/') } if [ "$2" == "" ]; then echo "Processing file $1; Folder: $FOLDER; File: $FILENAME" echo "" > "$FOLDER/$FILENAME.txt" echo "" > "$FOLDER/$FILENAME.join" get_resolution if [ "$RESOLUTION" == "" ]; then echo "Error getting resolution from video." RESOLUTION=1080 fi INTERLACED=$(/usr/bin/ffmpeg -i "$FILE" 2>&1 | grep "top first" | wc -l) if [ $INTERLACED -eq 1 ]; then OPTIONS="-vf yadif" fi if [ $RESOLUTION -le 720 ]; then BITRATE=$RATE720 elif [ $RESOLUTION -le 1080 ]; then BITRATE=$RATE1080 elif [ $RESOLUTION -gt 1080 ]; then BITRATE=$RATE2160 fi echo "Resolution is $RESOLUTION; Setting bitrate to $BITRATE" fi TO_SECS() { HOURS=$(echo $DURATION | cut -d':' -f1 | sed -e 's/^0*//') if [ "$HOURS" == "" ]; then HOURS="0" fi MINUTES=$(echo $DURATION | cut -d':' -f2 | sed -e 's/^0*//') if [ "$MINUTES" == "" ]; then MINUTES="0" fi SEC=$(echo $DURATION | cut -d':' -f3 | cut -d'.' -f1 | sed -e 's/^0*//') if [ "$SEC" == "" ]; then SEC="0" fi } get_benchmark() { echo "" > "$FOLDER/ffmpeg.benchmark" for ITEM in $SERVERS do SERVER=$(echo "$ITEM" | cut -d':' -f1) echo "$SERVER:0" >> "$FOLDER/ffmpeg.benchmark" FPS=$(ssh $SERVER "/usr/bin/ffmpeg -y -i '$FILE' -c:v libvpx-vp9 -c:a libopus -ac $AUDIOCHANNELS -t 2 -vf yadif -f null -benchmark /dev/null 2>&1 | sed -e 's/\r//g' | sed -e 's/.*fps//' | grep '^=' | cut -d'=' -f2 | cut -d' ' -f1 | sed -e 's/\.//' | sed -e 's/^0*//'") && sed -i "$FOLDER/ffmpeg.benchmark" -e "s/"$SERVER":0/"$SERVER":"$FPS"/" & RUNNING=1 TIMER=0 done while [ $RUNNING -gt 0 ] || [ $TIMER -gt 300 ] do echo -n "." RUNNING=$(cat "$FOLDER/ffmpeg.benchmark" | grep ":0" | wc -l) TIMER=$((TIMER+1)) sleep 1 done echo "" cat "$FOLDER/ffmpeg.benchmark" } FROM_SECS() { OLDSEC=$SEC FRACTION=1 HOURS=0 MINUTES=0 HOURS=$(($SEC/3600)) SEC=$(($SEC-$HOURS*3600)) MINUTES=$(($SEC/60)) SEC=$((SEC-$MINUTES*60)) if [ $HOURS -lt 1 ]; then HOURS="00" elif [ $HOURS -lt 10 ]; then HOURS="0$HOURS" fi if [ $MINUTES -lt 1 ]; then MINUTES="00" elif [ $MINUTES -lt 10 ]; then MINUTES="0$MINUTES" fi if [ $SEC -lt 1 ]; then SEC="00" elif [ $SEC -lt 10 ]; then SEC="0$SEC" fi TIME="$HOURS:$MINUTES:$SEC" LASTSECONDS=$SEC SEC=$OLDSEC } START_ENCODING() { echo "$SERVER: $START - $ENDSECS (Seconds)" PASS1="/usr/bin/nice -n 19 /usr/bin/ffmpeg -y -ss $START -t $ENDSECS -i \"$FILE\" -c:v libvpx-vp9 -b:v $BITRATE -pass 1 -passlogfile /tmp/$SERVER -an -f matroska -threads 4 -speed 4 -tile-columns 4 -frame-parallel 1 -deadline good -r 30 $OPTIONS /dev/null < /dev/null > /tmp/ffmpeg1.log 2>&1" PASS2="/usr/bin/nice -n 19 /usr/bin/ffmpeg -y -ss $START -t $ENDSECS -i \"$FILE\" -c:v libvpx-vp9 -b:v $BITRATE -pass 2 -passlogfile /tmp/$SERVER -ac $AUDIOCHANNELS -c:a libopus -b:a $AUDIOBITRATE $OPTIONS -threads 4 -speed 3 -tile-columns 4 -frame-parallel 1 -deadline good -auto-alt-ref 1 -lag-in-frames 25 -r 30 $OPTIONS \"$FOLDER/$FILENAME-$SERVER.mkv\" < /dev/null > /tmp/ffmpeg2.log 2>&1" PASS1="$PASS1 && sed -i \"$FOLDER/$FILENAME.txt\" -e \"s/\(\"$SERVER\".*\)0$/\11/\""; # Toggle status bit PASS2="$PASS2 && sed -i \"$FOLDER/$FILENAME.txt\" -e \"s/\(\"$SERVER\".*\)1$/\12/\" && sed -i \"$FOLDER/$FILENAME.txt\" -e \"s/END:.*/END:\"\$(date '+%s')\"/\""; # Toggle status bit echo "$SERVER:$ENDSECS:0" >> "$FOLDER/$FILENAME.txt" echo "file $FOLDER/$FILENAME-$SERVER.mkv" >> "$FOLDER/$FILENAME.join" ssh root@$SERVER "$PASS1 && $PASS2" & } join_videos() { echo "What's the joined file path/name?" read OUTNAME /usr/bin/ffmpeg -f concat -safe 0 -i "$FOLDER/$FILENAME.join" -c copy "$OUTNAME" } get_status() { RUNNING=$(cat "$FOLDER/$FILENAME.txt" | grep -E -v "START|END|:2$|^$" | wc -l) if [ $RUNNING -eq 0 ]; then START=$(cat "$FOLDER/$FILENAME.txt" | grep "START" | cut -d':' -f2) END=$(cat "$FOLDER/$FILENAME.txt" | grep "END" | cut -d':' -f2) SEC=$((END-START)) FROM_SECS echo "COMPLETE - $TIME ($SEC Seconds)" else PASS0=$(cat "$FOLDER/$FILENAME.txt" | grep -E ":0$") PASS1=$(cat "$FOLDER/$FILENAME.txt" | grep -E ":1$") PASS2=$(cat "$FOLDER/$FILENAME.txt" | grep -E ":2$") if [ ! "$PASS0" == "" ]; then echo echo "Pass 1 - In Progress" for NODE in $PASS0 do SEC=$(echo $NODE | cut -d':' -f2) FROM_SECS SERVER=$(echo $NODE | cut -d':' -f1) echo " $SERVER (Encoding $TIME)" done fi if [ ! "$PASS1" == "" ]; then echo echo "Pass 2 - In Progress" for NODE in $PASS1 do SEC=$(echo $NODE | cut -d':' -f2) FROM_SECS SERVER=$(echo $NODE | cut -d':' -f1) echo " $SERVER (Encoding $TIME)" done fi if [ ! "$PASS2" == "" ]; then echo echo "Finished" for NODE in $PASS2 do SEC=$(echo $NODE | cut -d':' -f2) FROM_SECS SERVER=$(echo $NODE | cut -d':' -f1) echo " $SERVER (Encoding $TIME)" done fi fi } encode() { TOTALUNITS=0 COUNT=0 TOTALCOUNT=0 ALLOCATED=0 for ITEM in $SERVERS do SERVER=$(echo $ITEM | cut -d':' -f1) if [ $(echo $BENCHMARK | grep $SERVER | wc -l) -eq 1 ]; then UNIT=$(echo "$BENCHMARK" | grep "$SERVER" | cut -d':' -f2) else UNIT=$(echo $ITEM | cut -d':' -f2) fi if [ "$UNIT" == "" ]; then UNIT=1 fi TOTALUNITS=$((UNIT+TOTALUNITS)) TOTALCOUNT=$((TOTALCOUNT+1)) done echo "START:$(date '+%s')" >> "$FOLDER/$FILENAME.txt" echo "END:" >> "$FOLDER/$FILENAME.txt" for ITEM in $SERVERS do COUNT=$((COUNT+1)) SERVER=$(echo $ITEM | cut -d':' -f1) if [ $(echo $BENCHMARK | grep $SERVER | wc -l) -eq 1 ]; then UNIT=$(echo "$BENCHMARK" | grep "$SERVER" | cut -d':' -f2) else UNIT=$(echo $ITEM | cut -d':' -f2) fi if [ "$UNIT" == "" ]; then UNIT=1 fi if [ $COUNT -eq $TOTALCOUNT ]; then SLICE=$((TOTALSECONDS-ALLOCATED)) else SLICE=$((UNIT*TOTALSECONDS)) SLICE=$((SLICE/TOTALUNITS)) ALLOCATED=$((ALLOCATED+SLICE)) fi if [ $COUNT -eq 1 ]; then SEC=$SLICE; FROM_SECS; END=$TIME; START="00:00:00.00"; ENDSECS=$SLICE START_ENCODING elif [ $COUNT -lt $SERVERCOUNT ]; then START="$END.01"; SEC=$ALLOCATED; FROM_SECS; END=$TIME; ENDSECS=$SLICE START_ENCODING else START="$END.01"; SEC=$ALLOCATED; END="$DURATION"; ENDSECS=$SLICE START_ENCODING fi done } if [ "$2" == "" ]; then DURATION=$(/usr/bin/ffmpeg -i "$FILE" 2>&1 | grep Duration | cut -d':' -f2- | cut -d',' -f1 | cut -d' ' -f2) echo Duration: $DURATION TO_SECS TOTALSECONDS=$SEC TOTALSECONDS=$(($SEC + $MINUTES*60 + $HOURS*3600)) echo "Total seconds: $TOTALSECONDS" encode elif [ "$2" == "status" ]; then get_status elif [ "$2" == "join" ]; then join_videos elif [ "$2" == "benchmark" ]; then get_benchmark else echo "Usage:" echo "./vp9-encode-distributed [benchmark|status|join]" echo "Examples:" echo " vp9-encode-distributed test.ts benchmark; # This memorizes the load distribution for optimal encoding" echo " vp9-encode-distributed test.ts; # This starts the encoding" echo " vp9-encode-distributed test.ts status; # This checks on a running encoding job" echo " vp9-encode-distributed test.ts join; # This stitches the re-encoded fragments back together after encoding completes" echo fi