#!/bin/bash # convert recording to avi compatable with San Disk Sansa Fuze # Based on and/or inspired by myth2xvid, the video4fuze project: # http://code.google.com/p/video4fuze # and the fuzemux project: # http://code.google.com/p/fuzemux # # version 0.2 # # usage: # first parameter must be %DIR%/%FILE% of the recording # second parameter must be the %TITLE% for the output filename # third parameter must be the %SUBTITLE% for the output filename # fourth parameter must be %CHANID% if you set USECUTLIST=Y # fifth parameter must be %STARTTIME% for USECUTLIST=Y and output filename # sixth parameter must be %JOBID% for the User Job status to be updated # in MythTV # In the mythtv setup screen invoke this script like this: # MYTHTV User Job Command: # /usr/LH/bin/myth2fuze "%DIR%/%FILE%" "%TITLE%" "%SUBTITLE%" "%CHANID%" "%STARTTIME%" "%JOBID%" # options: USECUTLIST=Y # Y or N # check prerequesites for APP in {fuzemux,mencoder,ffmpeg,mythcommflag,mythtranscode,mysql}; do /usr/bin/which ${APP} &>/dev/null ERROR=$? if [ $ERROR -ne 0 ]; then echo "${APP} not found in your PATH! Aborting! ERROR: ${ERROR}" >> ${LOGFILE} update_status 304 update_comment "${APP} not found in your PATH! Aborting! Exit status: ${ERROR}" exit 1 fi done # where the converted video is stored OUT_DIR=/myth/video/fuze # Ensure output directory exists if [ ! -d ${OUT_DIR} ] ; then mkdir -p -m777 ${OUT_DIR} fi # database settings BACKEND_HOSTNAME=${BACKEND_HOSTNAME:-"localhost"} DBUSERNAME=${DBUSERNAME:-"mythtv"} DBPASSWORD=${DBPASSWORD:-"mythtv"} SQLCMD="mysql -u ${DBUSERNAME} --password=${DBPASSWORD} -h ${BACKEND_HOSTNAME} mythconverg -e" # create temp filename so multiple instances won't conflict TMPNAME=toFUZE-$$ TMPDIR=/myth/tmp TMPFILE=${TMPDIR}/${TMPNAME}.avi TMPCUTFILE=${TMPDIR}/${TMPNAME}.mpg MENINPUTFILE=${1} TWOPASSFILE=/tmp/${TMPNAME}-2pass.log STATUSFILE=/tmp/${TMPNAME}-status.log MENCODER_RETURN_CODE=/tmp/${TMPNAME}-mencoder_return_code MENCODER_RETURN_CODE_2=/tmp/${TMPNAME}-mencoder_return_code_2 # Ensure temp directory exists if [ ! -d ${TMPDIR} ] ; then mkdir -p -m777 ${TMPDIR} fi #------FUNCTIONS--------------- update_comment() # Arg_1 = COMMENT { if [ ${NO_JOBID} = 0 ]; then `${SQLCMD} "update jobqueue set comment=\"${1}\" where id=\"${JOBID}\";"` fi } update_status() # Arg_1 = status code { if [ ${NO_JOBID} = 0 ]; then `${SQLCMD} "update jobqueue set status=\"${1}\" where id=\"${JOBID}\";"` fi } check_background_progress() # check mencoder progress in background # Arg_1 = PROGRESS CALCULATION { while [ `tail -1 ${STATUSFILE} | grep -cE "^Audio stream:|^Video stream:"` = 0 ] do sleep 10 check_myth_jobcmds current_status=`tail -1 ${STATUSFILE} | grep "([ 0-9]\{1,\}%)"` prog_percent=`echo ${current_status} | sed 's_.*(\([ 0-9][ 0-9]\)%).*_\1_'` current_FPS=`echo ${current_status} | sed 's_.*\([ 0-9][ 0-9].[ 0-9][ 0-9]\)fps.*_\1_'` if [ -n ${prog_percent} ]; then prog_percent=`expr ${prog_percent} / ${1}` echo "${prog_percent}% Completed @ ${current_FPS} fps" update_comment "${prog_percent}% Completed @ ${current_FPS} fps" fi sleep 10 done } check_myth_jobcmds() # check the myth database for stop pause or resume commands { if [ ${NO_JOBID} = 0 ]; then CURRENT_CMD=`${SQLCMD} "select cmds from jobqueue where id=\"${JOBID}\";" | sed '/[0-9]/!d'` case ${CURRENT_CMD} in # JOB_RUN 0) ;; # JOB_PAUSE 1) update_status 6 kill -s STOP ${mencoder_pid} ;; # JOB_RESUME 2) update_status 4 `${SQLCMD} "update jobqueue set cmds=\"0\" where id=\"${JOBID}\";"` kill -s CONT ${mencoder_pid} ;; # JOB_STOP 4) update_status 5 `${SQLCMD} "update jobqueue set cmds=\"0\" where id=\"${JOBID}\";"` kill -9 ${mencoder_pid} ${command_pid} clean_up_files echo "Encode Cancelled" >> ${LOGFILE} update_status 320 exit ;; esac fi } get_mencoder_pid() { process_name="" i1=1 while [ "$process_name" != "found" ]; do if [ "`ps ${mencoder_pid} | grep mencoder | sed 's_.*\(mencoder\).*_\1_'`" = "mencoder" ]; then process_name="found" else mencoder_pid=`expr ${mencoder_pid} + 1` fi i1=`expr $i1 + 1` if [ $i1 -gt 20 ]; then break fi done } run_fuzemux() # Remux avi to fuze compatable avi { /usr/bin/nice -n19 /usr/bin/fuzemux ${TMPFILE} ${OUTPUTFILE} >> ${LOGFILE} || return 1 } create_thumbnail() # Create a thumbnail image for the fuze { /usr/bin/nice -n19 /usr/bin/ffmpeg -y -v -1 -i ${OUTPUTFILE} -t 1 -ss 3 \ -s 224x176 -f image2 ${OUTPUTFILE%.*}.thm >> ${LOGFILE} } 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 ${TWOPASSFILE} 2> /dev/null unlink ${TWOPASSFILE}.tmp 2> /dev/null unlink ${STATUSFILE} 2> /dev/null unlink ${MENCODER_RETURN_CODE} 2> /dev/null unlink ${MENCODER_RETURN_CODE_2} 2> /dev/null } #-------MAIN SCRIPT------------ # check if %JOBID% is passed from command line JOBID=${6} if [ -z ${JOBID} ]; then NO_JOBID=1 else NO_JOBID=0 fi # log file location LOGFILE=/var/log/mythtv/myth2fuze.log CDate="`date`" echo "" >> ${LOGFILE} echo ${CDate} >> ${LOGFILE} echo "File to encode: ${MENINPUTFILE} Name: ${2} - ${3}" >> ${LOGFILE} # start timer beforetime="$(date +%s)" check_myth_jobcmds # check if using cutlist if [ ${USECUTLIST} = Y ]; then MYTHCOMMFRAMES=$( mythcommflag --getcutlist -f ${MENINPUTFILE} | grep 'Cutlist:' | cut -d \ -f 2 ) if [ -n "$MYTHCOMMFRAMES" ]; then echo "Extracting Cutlist..." >> ${LOGFILE} update_comment "Extracting Cutlist" /usr/bin/nice -n19 /usr/bin/mythtranscode --chanid ${4} --starttime ${5} --outfile ${TMPCUTFILE} --mpeg2 --honorcutlist MENINPUTFILE=${TMPCUTFILE} fi fi # run mencoder in background to do 1st pass conversion echo "Encoding 1st Pass for the Fuze..." >> ${LOGFILE} ( /usr/bin/nice -n19 /usr/bin/mencoder ${MENINPUTFILE} -passlogfile \ ${TWOPASSFILE} -ffourcc DX50 -ofps 20 \ -vf pp=li,expand=:::::224/176,scale=224:176,harddup -ovc lavc \ -lavcopts vcodec=mpeg4:vbitrate=683:vmax_b_frames=0:keyint=15:turbo:vpass=1 \ -nosound -o /dev/null > ${STATUSFILE} 2>&1 ; echo $? > \ ${MENCODER_RETURN_CODE} ) & mencoder_pid=$! command_pid=${mencoder_pid} get_mencoder_pid check_background_progress "2" if [ `cat ${MENCODER_RETURN_CODE}` -ne 0 ]; then echo "Mencoder pass #1 exited with error ${MENCODER_RETURN_CODE}. Encoding failed!" update_status 304 update_comment "ERROR: Mencoder pass #1 exited with error ${MENCODER_RETURN_CODE}" clean_up_files exit 1 fi # run mencoder in background to do 2nd pass conversion echo "Encoding 2nd Pass for the Fuze..." >> ${LOGFILE} ( /usr/bin/nice -n19 /usr/bin/mencoder ${MENINPUTFILE} -passlogfile \ ${TWOPASSFILE} -ffourcc DX50 -ofps 20 \ -vf pp=li,expand=:::::224/176,scale=224:176,harddup -ovc lavc \ -lavcopts vcodec=mpeg4:vbitrate=683:vmax_b_frames=0:keyint=15:vpass=2 \ -srate 44100 -af resample=44100:0:1,format=s16le -oac mp3lame \ -lameopts cbr:br=128 -o ${TMPFILE} > ${STATUSFILE} 2>&1 ; echo $? \ > ${MENCODER_RETURN_CODE_2} ) & mencoder_pid=$! command_pid=${mencoder_pid} get_mencoder_pid check_background_progress "2 + 50" if [ `cat ${MENCODER_RETURN_CODE_2}` -ne 0 ]; then echo "Mencoder pass #1 exited with error ${MENCODER_RETURN_CODE}. Encoding failed!" update_status 304 update_comment "ERROR: Mencoder pass #1 exited with error ${MENCODER_RETURN_CODE_2}" clean_up_files exit 1 fi # make output filename unique, remove punctuation and "special" characters, # replace spaces with underscores and force lowercase names OAD=$( echo ${5} | cut -b 1-8 ) FILENAME=$( echo "${2} ${OAD} ${3}" | tr -d '[:punct:]' \ | tr -s '[:blank:]' '[_]' | tr '[:upper:]' '[:lower:]' )_fuze.avi OUTPUTFILE=${OUT_DIR}/${FILENAME} ERROR=$? if [ ${ERROR} -ne 0 ]; then echo "Setting output file name exited with error ${ERROR}" >> ${LOGFILE} update_status 304 update_comment "ERROR: Setting output file name exited with error ${ERROR} Job failed!" clean_up_files exit 1 fi run_fuzemux ERROR=$? if [ ${ERROR} -ne 0 ]; then echo "Fuzemux exited with error ${ERROR}" >> ${LOGFILE} update_status 304 update_comment "ERROR: Fuzemux exited with error ${ERROR} Job Failed!" clean_up_files exit 1 fi # ensure mythtv owns the final avi chown mythtv ${OUTPUTFILE} ERROR=$? if [ ${ERROR} -ne 0 ]; then echo "Changing ownership of avi to mythtv exited with error ${ERROR}" update_status 304 update_comment "ERROR: Changing ownership of avi to mythtv exited with error ${ERROR} Job failed!" clean_up_files exit 1 fi create_thumbnail # ensure mythtv owns the thumbnail chown mythtv ${OUTPUTFILE%.*}.thm ERROR=$? if [ ${ERROR} -ne 0 ]; then echo "Changing ownership of thumbnail to mythtv exited with error ${ERROR}" update_status 304 update_comment "ERROR: Changing ownership of thumbnail to mythtv exited with error ${ERROR} Job failed!" clean_up_files exit 1 fi # stop timer aftertime="$(date +%s)" seconds="$(expr $aftertime - $beforetime)" echo "File Encoded Successfully: ${OUTPUTFILE}" >> ${LOGFILE} hours=$((seconds / 3600)) seconds=$((seconds % 3600)) minutes=$((seconds / 60)) seconds=$((seconds % 60)) echo "Encoding took $hours hour(s) $minutes minute(s) $seconds second(s) @ $current_FPS fps." >> ${LOGFILE} update_status 272 update_comment "Encode Successful. Encoding Time: $hours hour(s) $minutes minute(s) $seconds second(s)" clean_up_files