#!/bin/bash # ------------------------------------------------------- # Video Stabilizer based on MLT and vid.stab # For installation instructions, please check # http://bernaerts.dyndns.org/linux/74-ubuntu/329-ubuntu-trusty-rotate-stabilize-video-melt-vidstab # # Depends on : # * melt (from ppa:sunab/kdenlive-release) # * vidstab (libvidstab1.0 package from ppa:sunab/kdenlive-release) # * yad (from ppa:webupd8team/y-ppa-manager) # * exiftool (from libimage-exiftool-perl package) # * avconv # * x264 # # Revision history : # 04/03/2012, V1.0 - Creation by N. Bernaerts # 04/04/2012, V1.1 - Add codecs as parameters # 17/01/2015, V2.0 - Completle rewrite for Ubuntu 14.04 LTS # Handle rotation as well thanks to Guy Eagling # 19/01/2015, V2.1 - Manage per process .trf file to allow parallel execution # 19/01/2015, V2.2 - Add .pid file to handle processing cancellation # 23/01/2015, V3.0 - Check tools availability, # Detect available stabilization filters (vidstab, videostab2 or videostab) # Add Fedora compatibility (mlt-melt) thanks to Guy Eagling # 06/03/2015, V3.1 - Externalize parameters to ~/.config/video-stabilize.conf # Add multiple files processing thanks to Hingo's idea # 10/03/2015, V3.2 - Update exiftool handling for MKV files # 12/03/2015, V3.3 - Add extra parameters to dialog box # Change progress calculation to avoid errors # 29/05/2015, V3.4 - Check presence of ~/.config/video-stabilize.conf # 26/07/2015, V3.5 - Change options of first pass to avoid some errors (thanks to Gustavo Lapido Loureiro) # Add number of thread as encoder option # Kill process and parent process in case of cancellation # 22/09/2015, V3.6 - Correction of a bug in final encoding options (thanks to Ted Bartlett) # 21/02/2016, V3.7 - Handle scientific notation for video rate (thanks to Aslanex) # ------------------------------------------------------- # ------------------------------------------------- # Check tools availability # ------------------------------------------------- # check melt (ubuntu) or mlt-melt (fedora) CMD_MELT="melt" command -v $CMD_MELT >/dev/null 2>&1 || { CMD_MELT="mlt-melt"; } command -v $CMD_MELT >/dev/null 2>&1 || { zenity --error --text="Please install Melt from MLT framework"; exit 1; } # check yad, exiftool and x264 command -v yad >/dev/null 2>&1 || { zenity --error --text="Please install Yad"; exit 1; } command -v exiftool >/dev/null 2>&1 || { zenity --error --text="Please install ExifTool"; exit 1; } command -v x264 >/dev/null 2>&1 || { zenity --error --text="Please install x264"; exit 1; } # ------------------------------------------------------- # Read parameters from configuration file # ------------------------------------------------------- # Configuration file : ~/.config/video-stabilize.conf FILE_CONF="$HOME/.config/video-stabilize.conf" # check configuration file [ -f "$FILE_CONF" ] || { zenity --error --text="Please create and configure ${FILE_CONF}"; exit 1; } # Load configuration file ARR_EXT=($(cat "${FILE_CONF}" | grep "extension" | cut -d'=' -f 2- | cut -d ";" --output-delimiter=" " -f 1-)) STAB_SHAKINESS=$(cat "${FILE_CONF}" | grep "shakiness" | cut -d'=' -f2) STAB_SMOOTHING=$(cat "${FILE_CONF}" | grep "smoothing" | cut -d'=' -f2) STAB_OPTZOOM=$(cat "${FILE_CONF}" | grep "optzoom" | cut -d'=' -f2) STAB_EXTRA=$(cat "${FILE_CONF}" | grep "extra" | cut -d'=' -f2-) VIDEO_CODEC=$(cat "${FILE_CONF}" | grep "video-codec" | cut -d'=' -f2) AUDIO_CODEC=$(cat "${FILE_CONF}" | grep "audio-codec" | cut -d'=' -f2) VIDEO_RATE=$(cat "${FILE_CONF}" | grep "video-rate" | cut -d'=' -f2) AUDIO_RATE=$(cat "${FILE_CONF}" | grep "audio-rate" | cut -d'=' -f2) ENCODE_OPTION=$(cat "${FILE_CONF}" | grep "option" | cut -d'=' -f2-) ENCODE_THREAD=$(cat "${FILE_CONF}" | grep "thread" | cut -d'=' -f2-) # ------------------------------------------------------- # Retrieve or select video file # ------------------------------------------------------- IFS=$'\n' # loop thru arguments to check that they are video files for arg do # get file name and extension FILE_PATH="$arg" FILE_EXT=$(echo "${FILE_PATH}" | sed 's/^.*\.\(.*\)$/\1/') # check if file extension given in parameter is in the allowed extension list [ -f "$FILE_PATH" ] && EXT_OK=$(echo "${ARR_EXT[@]}" | grep --ignore-case ${FILE_EXT}) || EXT_OK="" # if ok, add it to the video files array [ -n "$EXT_OK" ] && ARR_VIDEO=("${ARR_VIDEO[@]}" "${FILE_PATH}") done # if there is no candidate files if [ ${#ARR_VIDEO[@]} -eq 0 ] then # generate allowed extension list LIST_EXT=$(echo "*.${ARR_EXT[@]}" | sed 's/ / *\./g') # open multiple files selection dialog box LST_VIDEO=$(yad --center --width=800 --height=500 --window-icon "video" --image "stabilizer" \ --file --multiple --file-filter="Video file (${LIST_EXT[@]})|${LIST_EXT[@]}" \ --title="Select video file to stabilize") # generate video files array ARR_VIDEO=($(echo "${LST_VIDEO}" | tr "|" "\n")) fi # --------------------------------------------------------- # Analyse video files and select processing parameters # --------------------------------------------------------- # loop thru selected video files for FILE_PATH in "${ARR_VIDEO[@]}" do # generate temporary exif file FILE_EXIF=$(mktemp -t stab-XXXXXXXX.exif) # get video metadata exiftool "${FILE_PATH}" > ${FILE_EXIF} FILE_WIDTH=$(cat ${FILE_EXIF} | grep "^Image Width" | cut -d':' -f2 | xargs) FILE_HEIGHT=$(cat ${FILE_EXIF} | grep "^Image Height" | cut -d':' -f2 | xargs) FILE_BITRATE=$(cat ${FILE_EXIF} | grep "^Avg Bitrate" | cut -d':' -f2 | xargs) FILE_ROTATE=$(cat ${FILE_EXIF} | grep "^Rotation" | cut -d':' -f2 | xargs) [ "${FILE_BITRATE}" == "" ] && FILE_BITRATE="Unknown" [ "${FILE_ROTATE}" == "" ] && FILE_ROTATE="0" # remove temporary exif file rm ${FILE_EXIF} # get encoding parameters using local number decimal separator (, or .) SEPARATOR=$(printf "%'.2f" 1 | sed 's/^1\(.\).*$/\1/') VIDEO_RATIO=$(echo "scale=2; ${FILE_WIDTH} / ${FILE_HEIGHT}" | bc | sed 's/[\.\,]/'${SEPARATOR}'/g') [ "$FILE_ROTATE" = "0" -o "$FILE_ROTATE" = "180" ] && LST_RATIO="1|${VIDEO_RATIO}" || LST_RATIO="${VIDEO_RATIO}|1" # detect if rotation needed [ "$FILE_ROTATE" = "0" ] && CHECK_ROTATE="FALSE" || CHECK_ROTATE="TRUE" # set title and text of dialog box TITLE="${FILE_PATH}" TEXT="Select transformation parameters :\n ( size = ${FILE_WIDTH}x${FILE_HEIGHT}, rate = ${FILE_BITRATE} )\n" # get list of stabilization filters available from current melt version ARR_FILTER=$(${CMD_MELT} -query filters | grep stab | awk '{print $2}' | sort -r) LST_FILTER=$(echo ${ARR_FILTER} | sed 's/ /|/g') # display dialog box CHOICE=$(yad --title "${TITLE}" --text "${TEXT}" --center --window-icon "video" --image "stabilizer" --width 500 \ --form --item-separator='|' \ --field="Rotate:CHK" "${CHECK_ROTATE}" \ --field=" - Angle (${FILE_ROTATE}° detected):NUM" "${FILE_ROTATE}|0..359" \ --field=" - Resize ratio (${VIDEO_RATIO} for 90°):CB" "${LST_RATIO}" \ --field="Stabilize:CHK" "TRUE" \ --field=" - Stabilization filter:CB" "${LST_FILTER}" \ --field=" - Shakiness [ 0 ... 10 ]:NUM" "${STAB_SHAKINESS}|1..10" \ --field=" - Smoothing [ 0 ... 100 ]:NUM" "${STAB_SMOOTHING}|0..100" \ --field=" - Optimal Zoom [ 0, 1, 2 ]:NUM" "${STAB_OPTZOOM}|0..2" \ --field=" - Extra stabilize options" "${STAB_EXTRA}" \ --field="Encoder:LBL" "final" \ --field=" - ${VIDEO_CODEC} video bitrate (Kbits/s):NUM" "${VIDEO_RATE}|1..50000|100" \ --field=" - ${AUDIO_CODEC} audio bitrate (Kbits/s):NUM" "${AUDIO_RATE}|1..320" \ --field=" - Extra encoder parameters" "${ENCODE_OPTION}" ) # retrieve parameters ROTATE=$(echo ${CHOICE} | cut -d'|' -f1) ROTATE_ANGLE=$(echo ${CHOICE} | cut -d'|' -f2) ROTATE_RATIO=$(echo ${CHOICE} | cut -d'|' -f3) STABILIZE=$(echo ${CHOICE} | cut -d'|' -f4) STAB_FILTER=$(echo ${CHOICE} | cut -d'|' -f5) STAB_SHAKINESS=$(echo ${CHOICE} | cut -d'|' -f6) STAB_SMOOTHING=$(echo ${CHOICE} | cut -d'|' -f7) STAB_OPTZOOM=$(echo ${CHOICE} | cut -d'|' -f8) STAB_EXTRA=$(echo ${CHOICE} | cut -d'|' -f9) VIDEO_RATE=$(echo ${CHOICE} | cut -d'|' -f11 | tr ',' '.' | awk '{ print sprintf("%.0f", $1); }') AUDIO_RATE=$(echo ${CHOICE} | cut -d'|' -f12) ENCODE_OPTION=$(echo ${CHOICE} | cut -d'|' -f13) # if it is needed, add file to processing queue if [ "$STABILIZE" = "TRUE" ] || [ "$ROTATE" = "TRUE" ]; then ARR_FILE=("${ARR_FILE[@]}" "${FILE_PATH}") ARR_ROTATE=("${ARR_ROTATE[@]}" "${ROTATE}") ARR_ROTATE_ANGLE=("${ARR_ROTATE_ANGLE[@]}" "${ROTATE_ANGLE}") ARR_ROTATE_RATIO=("${ARR_ROTATE_RATIO[@]}" "${ROTATE_RATIO}") ARR_STABILIZE=("${ARR_STABILIZE[@]}" "${STABILIZE}") ARR_STAB_FILTER=("${ARR_STAB_FILTER[@]}" "${STAB_FILTER}") ARR_STAB_SHAKINESS=("${ARR_STAB_SHAKINESS[@]}" "${STAB_SHAKINESS}") ARR_STAB_SMOOTHING=("${ARR_STAB_SMOOTHING[@]}" "${STAB_SMOOTHING}") ARR_STAB_OPTZOOM=("${ARR_STAB_OPTZOOM[@]}" "${STAB_OPTZOOM}") ARR_STAB_EXTRA=("${ARR_STAB_EXTRA[@]}" "${STAB_EXTRA}") ARR_VIDEO_RATE=("${ARR_VIDEO_RATE[@]}" "${VIDEO_RATE}") ARR_AUDIO_RATE=("${ARR_AUDIO_RATE[@]}" "${AUDIO_RATE}") ARR_ENCODE_OPTION=("${ARR_ENCODE_OPTION[@]}" "${ENCODE_OPTION}") fi done # ------------------------------------------------------- # Process files for rotation and stabilization # ------------------------------------------------------- # loop thru video files to process NUM_FILE=${#ARR_FILE[@]} for ((INDEX=0; INDEX < NUM_FILE; INDEX++)) do # -------------------------------- # Get file data # -------------------------------- # get current file path FILE_PATH="${ARR_FILE[$INDEX]}" # generate the filenames FILE_BASE="$(echo "${FILE_PATH}" | sed 's/^\(.*\)\..*$/\1/')" FILE_STAB="${FILE_BASE}-stab.mp4" # generate temporary files DIR_TMP=$(mktemp -d "$HOME/.stab-XXXXXXXX") FILE_TRF="${DIR_TMP}/video.trf" FILE_PID="${DIR_TMP}/video.pid" FILE_MLT="${DIR_TMP}/video.mlt" FILE_LOG="${DIR_TMP}/video.log" # get parameters ROTATE="${ARR_ROTATE[$INDEX]}" ROTATE_ANGLE="${ARR_ROTATE_ANGLE[$INDEX]}" ROTATE_RATIO="${ARR_ROTATE_RATIO[$INDEX]}" STABILIZE="${ARR_STABILIZE[$INDEX]}" STAB_FILTER="${ARR_STAB_FILTER[$INDEX]}" STAB_SHAKINESS="${ARR_STAB_SHAKINESS[$INDEX]}" STAB_SMOOTHING="${ARR_STAB_SMOOTHING[$INDEX]}" STAB_OPTZOOM="${ARR_STAB_OPTZOOM[$INDEX]}" STAB_EXTRA="${ARR_STAB_EXTRA[$INDEX]}" VIDEO_RATE="${ARR_VIDEO_RATE[$INDEX]}" AUDIO_RATE="${ARR_AUDIO_RATE[$INDEX]}" ENCODE_OPTION="${ARR_ENCODE_OPTION[$INDEX]}" ( # initilize transformation arrays unset ARR_STABILIZE unset ARR_ROTATE unset ARR_ENCODE # ------------------------------------------------ # PREPARATION : Setup processing parameters # ------------------------------------------------ # initial display echo "# Computing parameters" echo "0" # if needed, generate stabilizer parameters array ARR_EXTRA=($(echo "${STAB_EXTRA}" | cut -d ";" --output-delimiter=" " -f 1-)) [ "$STABILIZE" = "TRUE" ] && ARR_STABILIZE=("-filter" "${STAB_FILTER}" "filename=${FILE_TRF}" "shakiness=${STAB_SHAKINESS}" \ "smoothing=${STAB_SMOOTHING}" "optzoom=${STAB_OPTZOOM}" "${ARR_EXTRA[@]}") # if needed, set rotation filter if [ "$ROTATE" = "TRUE" ] then # convert angle for filter use X_ANGLE=$((10 * ${ROTATE_ANGLE} / 2)) # set rotation filter ARR_ROTATE=("-filter" "affine" "transition.fix_rotate_x=${X_ANGLE}") # if different than 1, set transformation ratio [ "${ROTATE_RATIO}" != "1" ] && ARR_ROTATE=("${ARR_ROTATE[@]}" "transition.scale_x=${ROTATE_RATIO}" "transition.scale_y=${ROTATE_RATIO}") fi # ---------------------------------------------- # PASS 1 : Rotation / Stabilization analysis # ---------------------------------------------- # information display echo "# Analysing file" # launch analysis command ${CMD_MELT} -progress "${FILE_PATH}" ${ARR_ROTATE[@]} ${ARR_STABILIZE[@]} -consumer xml:"${FILE_MLT}" all=1 2>"${FILE_LOG}" & # save current process and melt command process id PID=$(ps aux | grep "${CMD_MELT}" | grep "${FILE_PATH}" | awk '{print $2}') echo "${BASHPID}" > "${FILE_PID}" echo "${PID}" >> "${FILE_PID}" while [ "$PID" != "" ] do # check if process is still running PID=$(ps aux | awk '{print $2}' | grep ${PID}) # calculate process completion LOG_LINE="$(tail --bytes=50 ${FILE_LOG} | grep 'Frame' | grep 'percentage')" LOG_FRAME=$(echo "${LOG_LINE}" | sed 's/^.*Frame[ :]*\([0-9]*\).*$/\1/') LOG_PERCENT=$(echo "${LOG_LINE}" | sed 's/^.*percentage[ :]*\([0-9]*\).*$/\1/') # if percentage has been retrieved from log, display process completion [[ ${LOG_FRAME} == +([0-9]) ]] && echo "# Analysing frame ${LOG_FRAME}" [[ ${LOG_PERCENT} == +([0-9]) ]] && echo $((${LOG_PERCENT} / 2)) # wait for 2 seconds sleep 2 done # ------------------------------------------ # PASS 2 : Final result encoding # ------------------------------------------ # information display echo "# Generating file" # generate encoder parameters array ARR_OPTION=($(echo "${ENCODE_OPTION}" | cut -d ";" --output-delimiter=" " -f 1-)) ARR_ENCODE=("vcodec=${VIDEO_CODEC}" "b=${VIDEO_RATE}k" "acodec=${AUDIO_CODEC}" "ab=${AUDIO_RATE}k" "${ARR_OPTION[@]}" ) # launch generation command ${CMD_MELT} -progress "${FILE_MLT}" -audio-track "${FILE_PATH}" -consumer avformat:"${FILE_STAB}" ${ARR_ENCODE[@]} real_time=-${ENCODE_THREAD} 2>"${FILE_LOG}" & # save current process and melt command process id PID=$(ps aux | grep "${CMD_MELT}" | grep "${FILE_MLT}" | awk '{print $2}') echo "${BASHPID}" > "${FILE_PID}" echo "${PID}" >> "${FILE_PID}" # follow the stabilization progress while [ "$PID" != "" ] do # check if process is still running PID=$(ps aux | awk '{print $2}' | grep ${PID}) # calculate process completion LOG_LINE="$(tail --bytes=50 ${FILE_LOG} | grep 'Frame' | grep 'percentage')" LOG_FRAME=$(echo "${LOG_LINE}" | sed 's/^.*Frame[ :]*\([0-9]*\).*$/\1/') LOG_PERCENT=$(echo "${LOG_LINE}" | sed 's/^.*percentage[ :]*\([0-9]*\).*$/\1/') # if percentage has been retrieved from log, display process completion [[ ${LOG_FRAME} == +([0-9]) ]] && echo "# Generating frame ${LOG_FRAME}" [[ ${LOG_PERCENT} == +([0-9]) ]] && echo $((50 + (${LOG_PERCENT} / 2))) # wait for 2 seconds sleep 2 done # remove PID file rm "${FILE_PID}" # declare end of processing echo "# File processed and available" echo "100" ) | yad --center --width=600 --window-icon "video" --image "stabilizer" --progress --auto-close --title "[$((${INDEX} + 1))/${NUM_FILE}] ${FILE_PATH}" # ----------------------------------------- # END : Final process and files cleanup # ----------------------------------------- # if process is still running (operation has been canceled), kill the process [ -f "${FILE_PID}" ] && kill -9 $( cat "${FILE_PID}" ) # cleaning-up of all temporary files rm -R ${DIR_TMP} done