#!/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