#!/bin/bash
#
# Convert video to AVC-1 / h264
#
# version 0.27-001
#
# Prerequisites:
#   - mythtv >= 0.27
#   - handbrake-cli
#   - mplayer
#   - mkvtoolnix
#   - jobqueue_helper.py
#
# Arguments
# $1 must be the directory/file of the recording
# $2 must be chanid
# $3 must be starttime
# $4 must be title
# $5 must be subtitle
# $6 must be jobid
# $7 must be quality of encode
#
# As a MythTV user job:
# myth2mkv "%DIR%/%FILE%" "%CHANID%" "%STARTTIME%" "%TITLE%" "%SUBTITLE%" "%JOBID%" "HP|HQ|MQ|LQ"
# Select only 1 quality setting
# HP is similar to the HandBrake built-in preset High Profile

########################
#                      #
# Adjustable variables #
#                      #
########################

OUTDIR=/myth/video
LOGPATH=/var/log/mythtv
LOGFILE=${LOGPATH}/myth2mkv-$$.log

# TMPDIR is for large transient files
TMPDIR=/myth/tmp

# x264 tuning:
# Tune x264 based on content. Valid options for TUNING are:
# film, animation, grain, stillimage, psnr, ssim, fastdecode, zerolatency
# Separate multiple options with a comma. DEFAULT: none
TUNING=""

# Custom cropping. Useful if you have a 4:3 image in a HD frame or if
# HandBrake's autocrop smarts fail you.
# Crop 240 pixels off the left and right for 4:3 image in 1920x1080 frame
# Crop 160 pixels off the left and right for 4:3 image in 1280x720 frame
# <T:B:L:R>
# i.e. 0:0:240:240
# Default: In HP and HQ: CROP="0:0:0:0" (no cropping).
#          IN MQ and LQ: autocrop.
CROP=""

# Force custom output resolution.
# Default: Keep same resolution as input file (less any cropping).
# The HP quality setting always keeps the same resolution as the input file.
WIDTH=""
HEIGHT=""

# Force use/non-use of deinterlacing filter. Y|N|G (Yes, No, Guess)
# Default: G - Guess based on source resolution.
# If the source video width is 1920, 1440, 852, 704, 640 or 528 pixels
# "G" will deinterlace the video. Change to "Y" to force use of deinterlacing
# filter or to "N" to NOT use deinterlace filter no matter the resolution.
DEINT="G"

############################
#                          #
# End adjustable variables #
#                          #
############################

if [[ ! -d ${TMPDIR} ]] ; then
  mkdir -p ${TMPDIR}
fi

if [[ ! -d ${OUTDIR} ]] ; then
  mkdir -p ${OUTDIR}
fi

#------FUNCTIONS---------------
update_comment()
# Arg_1 = COMMENT
{
if [ ${NO_JOBID} -eq 0 ]; then
    `jobqueue_helper.py -j ${JOBID} -cs "${1}"`
fi
}

check_background_progress()
# check handbrake progress in background
{
while [ ! -f ${HB_RETURN_CODE} ]
do
    sleep 15
    check_myth_jobcmds
    pass=`tail -1 ${STATUSFILE} | egrep -o -e 'task [0-9] of [0-9], ' | tail -1 | sed 's/task\ /Pass\ /g'`
    prog_percent=`tail -1 ${STATUSFILE} | egrep -o -e '[0-9]*\.[0-9]. %' | tail -1 | sed 's/\ %//g'`
    current_FPS=`tail -1 ${STATUSFILE} | egrep -o -e 'avg [0-9.]*\.[0-9]* fps' | tail -1 | sed -e 's/avg\ //g' -e 's/\ fps//g'`
    current_ETA=`tail -1 ${STATUSFILE} | egrep -o -e 'ETA [0-9.][0-9.]h[0-9.][0-9.]m[0-9.][0-9.]s' | tail -1`
    if [ -n "$prog_percent" ]; then
        echo "${pass}${prog_percent}% @ ${current_FPS} fps. ${current_ETA}"
        update_comment "${pass}${prog_percent}% @ ${current_FPS} fps. ${current_ETA}"
    fi
done
}

check_myth_jobcmds()
# check the myth database for stop pause or resume commands
{
if [[ ${NO_JOBID} -eq 0 ]] ; then
    CURRENT_CMD=`jobqueue_helper.py -m "select cmds from jobqueue where id = ${JOBID}"`
    case "${CURRENT_CMD}" in
        # JOB_RUN
        0) ;;
        # JOB_PAUSE
        1) `jobqueue_helper.py -j ${JOBID} -ss 6`
           kill -s STOP ${handbrake_pid} ;;
        # JOB_RESUME
        2) `jobqueue_helper.py -j ${JOBID} -ss 4`
           `jobqueue_helper.py -j ${JOBID} -cmds 0`
           kill -s CONT ${handbrake_pid} ;;
        # JOB_STOP
        4) `jobqueue_helper.py -j ${JOBID} -ss 5`
           `jobqueue_helper.py -j ${JOBID} -cmds 0`
           kill -9 ${handbrake_pid} ${command_pid}
           clean_up_files
           echo "Encode Cancelled" >> ${LOGFILE}
           `jobqueue_helper.py -j ${JOBID} -ss 320`
           exit ;;
    esac
fi
}

get_info_for_hb() {
# Collect some info about source file

/usr/bin/mplayer -nolirc -identify -frames 0 "${HBINPUTFILE}" \
  2>/dev/null 1>"${IDFILE}"

VIDEOW=$( grep ID_VIDEO_WIDTH= "${IDFILE}" | awk -F= '{ print $NF }' )
FPS=$( grep ID_VIDEO_FPS= "${IDFILE}" | awk -F= '{ print $NF }' )

# HandBrake does not like a framerate of 29.970, so let's drop the 0
if [[ ${FPS} = "29.970" ]] ; then
  FPS="29.97"
fi

# HandBrake does not like a framerate of 59.940, so let's drop the 0
if [[ ${FPS} = "59.940" ]] ; then
  FPS="59.94"
fi

# A rough guestimation that if the video width is 1920, 1440, 852, 704, 640 or
# 528 pixels it is probably interlaced.
if [[ ${DEINT} = Y ]] ; then
  DEINT="-d slow"
else
  if [[ ${DEINT} = N ]] ; then
    DEINT=""
  else
    if [[ ${VIDEOW} = 1920 || ${VIDEOW} = 1440 || ${VIDEOW} = 852 || \
        ${VIDEOW} = 704 || ${VIDEOW} = 640 || ${VIDEOW} = 528 ]] ; then
      DEINT="-d slow"
    else
      DEINT=""
    fi
  fi
fi

if [[ -n ${DEINT} ]] ; then
  if [[ ${QUALITY} = LQ ]] ; then
    DEINT="-d fast"
  fi
fi

if [[ -n ${TUNING} ]] ; then
  TUNING="--x264-tune ${TUNING}"
fi

if [[ -n ${CROP} ]] ; then
  CROP="--crop ${CROP}"
fi

if [[ -n ${WIDTH} ]] ; then
  WIDTH="-w ${WIDTH} -X ${WIDTH}"
fi

if [[ -n ${HEIGHT} ]] ; then
  HEIGHT="-l ${HEIGHT} -Y ${HEIGHT}"
fi

if [[ ${QUALITY} = HP ]] ; then
  if [[ -n ${CROP} ]] ; then
    CROP="--crop ${CROP}"
  else
    CROP="--crop 0:0:0:0 --auto-anamorphic"
  fi
  HB_OPTS="-o ${TMPFILE} -e x264 ${TUNING} -q 20.0 -a 1,1 -E copy:ac3,faac -B 160,160 -6 dpl2,auto -R Auto,Auto -D 0.0,0.0 --audio-copy-mask aac,ac3,dtshd,dts,mp3 --audio-fallback ffac3 -f mkv --decomb --loose-anamorphic --modulus 2 -m --x264-preset medium --h264-profile high --h264-level 4.1 ${CROP} -s 1"
elif [[ ${QUALITY} = HQ ]] ; then
  if [[ -n ${CROP} ]] ; then
    CROP="--crop ${CROP}"
  else
    CROP="--crop 0:0:0:0 --auto-anamorphic"
  fi
  HB_OPTS="-o ${TMPFILE} -f mkv -m -e x264 ${TUNING} -x b-adapt=2:rc-lookahead=50 -b 5000 -2 -T ${WIDTH} ${HEIGHT} -r ${FPS} --cfr ${CROP} ${DEINT} -a 1 -E copy -s 1"
else
  if [[ ${CROP} = "--crop 0:0:0:0" ]] ; then
    CROP="${CROP} --auto-anamorphic"
  fi
  if [[ ${QUALITY} = LQ ]] ; then
    HB_OPTS="-o ${TMPFILE} -f mkv -m -e x264 ${TUNING} -b 1250 ${WIDTH} ${HEIGHT} -r ${FPS} --pfr ${CROP} ${DEINT} -a 1 -E lame -B 128 -Q 8 -6 stereo -s 1"
  else
    # Fallback to "MQ"
    HB_OPTS="-o ${TMPFILE} -f mkv -m -e x264 ${TUNING} -b 2500 -2 -T ${WIDTH} ${HEIGHT} -r ${FPS} --pfr ${CROP} ${DEINT} -a 1 -E lame -B 256 -Q 3 -6 stereo -s 1"
  fi
fi
}

get_handbrake_pid()
{
process_name=""
i1=1
while [ "${process_name}" != "found" ]; do
   handbrake_pid=`expr ${handbrake_pid} + 1`
   i1=`expr ${i1} + 1`
   if [ "`ps ${handbrake_pid} | grep HandBrakeCLI | sed 's_.*\(HandBrakeCLI\).*_\1_'`" = "HandBrakeCLI" ]; then
      process_name="found"
   fi
   if [ ${i1} -gt 20 ]; then
      break
   fi
done
}

tag_file() {
DATE=`date`

# Create a tag file here
echo "<?xml version='1.0' encoding='ISO-8859-1'?>" > "${TAG_FILE}"
echo ""  >> "${TAG_FILE}"
echo "<!DOCTYPE Tags SYSTEM 'matroskatags.dtd'>" >> "${TAG_FILE}"
echo "" >> "${TAG_FILE}"
echo "<Tags>" >> "${TAG_FILE}"
echo " <Tag>" >> "${TAG_FILE}"
echo "   <Simple>" >> "${TAG_FILE}"
echo "     <Name>TITLE</Name>" >> "${TAG_FILE}"
echo "     <String>${TITLE}</String>" >> "${TAG_FILE}"
echo "     <Simple>" >> "${TAG_FILE}"
echo "       <Name>SUBTITLE</Name>" >> "${TAG_FILE}"
echo "       <String>${SUBTITLE}</String>" >> "${TAG_FILE}"
echo "       <Simple>" >> "${TAG_FILE}"
echo "         <Name>SUMMARY</Name>" >> "${TAG_FILE}"
echo "         <String>${DESCR}</String>" >> "${TAG_FILE}"
echo "       </Simple>" >> "${TAG_FILE}"
echo "       <Simple>" >> "${TAG_FILE}"
echo "         <Name>DATE_RELEASED</Name>" >> "${TAG_FILE}"
echo "         <String>${OAD}</String>" >> "${TAG_FILE}"
echo "       </Simple>" >> "${TAG_FILE}"
echo "       <Simple>" >> "${TAG_FILE}"
echo "         <Name>SEASON</Name>" >> "${TAG_FILE}"
echo "         <String>${SEASON}</String>" >> "${TAG_FILE}"
echo "       </Simple>" >> "${TAG_FILE}"
echo "       <Simple>" >> "${TAG_FILE}"
echo "         <Name>EPISODE</Name>" >> "${TAG_FILE}"
echo "         <String>${EPISODE}</String>" >> "${TAG_FILE}"
echo "       </Simple>" >> "${TAG_FILE}"
echo "     </Simple>" >> "${TAG_FILE}"
echo "   </Simple>" >> "${TAG_FILE}"
echo "   <Simple>" >> "${TAG_FILE}"
echo "       <Name>ENCODED_BY</Name>" >> "${TAG_FILE}"
echo "       <String>HandBrakeCLI ${HBCLIVER}</String>" >> "${TAG_FILE}"
echo "   </Simple>" >> "${TAG_FILE}"
echo "   <Simple>" >> "${TAG_FILE}"
echo "       <Name>DATE_TAGGED</Name>" >> "${TAG_FILE}"
echo "       <String>${DATE}</String>" >> "${TAG_FILE}"
echo "   </Simple>" >> "${TAG_FILE}"
echo " </Tag>" >> "${TAG_FILE}"
echo "</Tags>" >> "${TAG_FILE}"

# Add tag info into MKV file
echo "Adding tag info to ${TITLE} - ${SUBTITLE} ..." >> ${LOGFILE}

/usr/bin/mkvpropedit -r ${LOGFILE} -t all:"${TAG_FILE}" "${TMPFILE}"
}

clean_up_files()
# clean up left over files
{
unlink ${TMPFILE} 2> /dev/null
unlink ${TMPCUTFILE} 2> /dev/null
unlink ${TMPCUTFILE}.map 2> /dev/null
unlink ${STATUSFILE} 2> /dev/null
unlink ${IDFILE} 2> /dev/null
unlink ${HB_RETURN_CODE} 2> /dev/null
unlink ${TAG_FILE} 2> /dev/null
}

#-------MAIN SCRIPT------------

# create temp filename so multiple instances won't conflict
TMPNAME=toX264-$$
TMPFILE=${TMPDIR}/${TMPNAME}.mkv
TMPCUTFILE=${TMPDIR}/${TMPNAME}.mpg
HBINPUTFILE="${1}"
TITLE="${4}"
SUBTITLE="${5}"
JOBID="${6}"
QUALITY="${7}"
BASE=`basename ${HBINPUTFILE}`
HBCLIVER=`pacman -Q | grep handbrake-cli | awk '{ print $NF }' | awk -F"-" '{ print $1 }'`
STATUSFILE=/tmp/${TMPNAME}-status.log
HB_RETURN_CODE=/tmp/${TMPNAME}-hb_return_code
IDFILE=/tmp/${TMPNAME}-id.txt
TAG_FILE=/tmp/${TMPNAME}.xml
SEASON=`jobqueue_helper.py -m "select season from recorded where basename LIKE '${BASE}'"`
SEASON=`printf "%02d" $SEASON`
EPISODE=`jobqueue_helper.py -m "select episode from recorded where basename LIKE '${BASE}'"`
EPISODE=`printf "%02d" $EPISODE`
OAD=`jobqueue_helper.py -m "select originalairdate from recorded where basename LIKE '${BASE}'"`
DESCR=`jobqueue_helper.py -m "select description from recorded where basename LIKE '${BASE}'" | sed 's/\&/and/g'`
USER=`whoami`

# check if %JOBID% is passed from command line
if [ -z ${JOBID} ]; then
    NO_JOBID=1
else
    NO_JOBID=0
fi

# log file location
CDate="`date`"
echo "" >> ${LOGFILE}
echo $CDate >> ${LOGFILE}
echo "File to encode: ${HBINPUTFILE}" >> ${LOGFILE}
echo "    --> Name: ${TITLE} - ${SUBTITLE}" >> ${LOGFILE}
echo "        --> Temporary Files: ${TMPNAME}.*" >> ${LOGFILE}
echo "" >> ${LOGFILE}

get_info_for_hb
ERROR=$?

if [[ ${ERROR} != 0 ]] ; then
  echo "Error parsing source file information!" >> ${LOGFILE}
  cat ${IDFILE} >> ${LOGFILE}
  clean_up_files
  exit 1
fi

# start timer
beforetime="$(date +%s)"

check_myth_jobcmds

# If there is a cutlist, use it:
if [[ -n `mythutil --getcutlist --chanid "${2}" --starttime "${3}" | grep \
  Cutlist: | awk -F": " '{ print $NF }'` ]] ; then
  echo "Applying cutlist for ${TITLE} - ${SUBTITLE} ..." >> ${LOGFILE}
  mythtranscode --chanid "${2}" --starttime "${3}" -m --honorcutlist \
    -q --loglevel info --logpath "${LOGPATH}" -o "${TMPCUTFILE}"
  mythtrans_pid=$!
  ERROR=$?
  HBINPUTFILE=${TMPCUTFILE}
fi

if [[ ${ERROR} != 0 ]] ; then
  echo "MythTranscode error!" >> ${LOGFILE}
  echo "Check ${LOGPATH}/mythtranscode.date.${mythtrans_pid}.log for mythtranscode error" >> ${LOGFILE}
  clean_up_files
  exit 1
fi

# run handbrake in background to do conversion
echo "Encoding ${TITLE} - ${SUBTITLE} ..." >> ${LOGFILE}

( /usr/bin/nice -n19 nohup /usr/bin/HandBrakeCLI -i ${HBINPUTFILE} ${HB_OPTS} \
  > ${STATUSFILE} 2>&1 ; echo $? > ${HB_RETURN_CODE} ) &
handbrake_pid=$!
command_pid=${handbrake_pid}
get_handbrake_pid

check_background_progress

if [[ `cat ${HB_RETURN_CODE}` != 0 ]] ; then
  echo "HandBrakeCLI error!" >> ${LOGFILE}
  cat ${STATUSFILE} >> ${LOGFILE}
  clean_up_files
  exit 1
fi

tag_file
ERROR=$?

if [[ ${ERROR} != 0 ]] ; then
  echo "Error creating tag file!" >> ${LOGFILE}
  cat ${TAG_FILE} >> ${LOGFILE}
  clean_up_files
  exit 1
fi

# make output filename unique and do not clobber an existing file
# Build a final file name
if [[ $SEASON != "00" && $EPISODE != "00" ]]; then
    FILE=$( echo "${TITLE,,} s${SEASON}e${EPISODE} ${SUBTITLE,,}" | tr -d [:punct:] | tr [:blank:] "_" | tr -s "_" )
else
    FILE=$( echo "${TITLE,,} ${OAD} ${SUBTITLE,,}" | tr -d [:punct:] | tr [:blank:] "_" | tr -s "_" )
fi
OUTPUTFILE="${OUTDIR}/${FILE}.mkv"
i=1
while [ -e "${OUTPUTFILE}" ]
do
    OUTPUTFILE="${OUTDIR}/${FILE}-${i}.mkv"
    i=`expr $i + 1`
done

# move temp file to output location
chown -v "${USER}" "${TMPFILE}" >> ${LOGFILE}
mv -v "${TMPFILE}" "$OUTPUTFILE" >> ${LOGFILE}
ERROR=$?

if [[ ${ERROR} != 0 ]] ; then
  echo "Error moving ${TMPFILE} to ${OUTPUTFILE} !" >> ${LOGFILE}
  clean_up_files
  exit 1
fi

# stop timer
aftertime="$(date +%s)"
seconds="$(expr ${aftertime} - ${beforetime})"

if [ ${ERROR} -eq 0 ]; then
    echo "File Encoded Successfully: ${OUTPUTFILE}" >> ${LOGFILE}
    hours=$((seconds / 3600))
    seconds=$((seconds % 3600))
    minutes=$((seconds / 60))
    seconds=$((seconds % 60))
    if [ $hours -eq 0 ]; then
        hours=""
    elif [ $hours -eq 1 ]; then
        hours=" $hours hour"
    else
        hours=" $hours hours"
    fi
    if [ $minutes -eq 1 ]; then
        minutes="$minutes minute"
    else
        minutes="$minutes minutes"
    fi
    if [ $seconds -eq 1 ]; then
        seconds="$seconds second"
    else
        seconds="$seconds seconds"
    fi

    echo "Encoding took${hours} ${minutes} ${seconds} @ ${current_FPS} fps." >> ${LOGFILE}
    `jobqueue_helper.py -j ${JOBID} -ss 272`
    update_comment "Encode Successful. Encoding Time:${hours} ${minutes} ${seconds}"
else
    echo "ERROR: ${ERROR}" >> ${LOGFILE}
fi

# Clean up
clean_up_files