#!/bin/bash # Copyright (C) 2008 Sebastian Kemper (sebastian_ml @ gmx net) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # This script converts video files in batch mode to iPod (tm) compatible # MP4s. VERSION="1.0.9" # History: # 1.0.4: - Fixed a typo in comments # - Start version history # - Change FAAC_ENCOPTS comment # - Introduce $MFPS for accurate FPS # - Add '-noskip -mc 0' to mencoder calls # - Add check for frame rates greater than 30 # - Add lock file # - Exit on error # - Set keyint/keyint_min # - Add note regarding MP4Box and file names # - Write down requirements # 1.0.5: - Small change to comments # - Use myidentify instead of midentify; now input pixel aspect # ratio doesn't have to be 1:1 anymore # - Function c; unset variables containing essential file # properties and continue # - Protect mplayer from input while extracting audio # (-nolirc -noconsolecontrols etc.) # - Remove AVI requirement; now any file is possible as input # - Add VFOPTS variable # 1.0.6: - Remove '-noskip -mc 0' again # - Change "Usage: ..." info # - Add rough sketch about what the script does and what it can't # do # - Add note about iPod (tm) import # - Print target bitrate and bpp # - Unset LC_ALL and set LC_NUMERIC to POSIX to make sure we get # dot instead of comma as decimal separator as bc expects a dot # 1.0.7: - Add a little enhancement to function c; ability to return to # the n-th eclosing loop # - Fix two funtion c calls returning to the wrong loops # 1.0.8: - Remove myidentify log when skipping file # - Use rm -f # - Add possibility to add options to mencoder calls through # $MENCODER_OPTS # 1.0.9. - In case aspect ratio is not set simply assume Width:Height # - Add -endpos .1 to myidentify to prevent playback of audio # files; thanks to Reimar Doeffinger for the tip # - Check $VFOPTS for cropping. If detected calculate new aspect # ratio so we get a proper output resolution # Notes: # The script can process multiple input files. For every file the script # tries to calculate a fitting output resolution. If one is found the # file gets converted to H264 video/AAC LC audio and finally wrapped up # in a MP4 container. The only video filter applied throughout the # process is mencoder's "scale" filter (unless the user wants other # filters prepended; read on for more info). # This script can't do framerate conversion. Neither can it handle # variable framerates. Another thing it can't do is select between # available audio/video tracks. # There are different ways to import the resulting files onto your iPod # (tm). For instance you can use amarok or gtkpod. Or you could try # iTunes (tm). It may be necessary to run MP4Box -ipod before # iTunes (tm) accepts the file for transfer. Feedback welcome. # Feedback and fixes are _always_ welcome. Use email address at the top. # You can specify video filter options on a per file basis. To do that # simply put _one_ line of options in a file named like the input file # with the suffix .vf (e.g. input.avi.vf for input.avi). This file has # to be in the same directory as the input file. These options will then # be prepended to the preset options (scale=x:y,harddup). This is useful # for cropping etc. # Example: crop=716:576:2:0 # MP4Box is a great tool but it has its issues. For instance it can't # handle file names very well. So in case MP4Box segfaults try renaming # the input file like this: # a) make the file name shorter # b) make sure the file name consists only of ASCII characters # Make sure you have enough space left in your ${TMP_DIR} and # ${WORK_DIR}! # Requirements: # - coreutils (everyone should have those anyway) # - bc (for floating point calculations) # - findutils (script needs 'xargs') # - faac (audio encoder) # - mplayer # - mencoder (with x264 support) # - aacgain (to ReplayGain the audio, you should at least use v1.7.0) # - MP4Box (part of gpac, v0.4.4 or later required) # Configuration: # # Options can be set in the script itself or (preferably) in a file in # the user's home directory, namely ~/.avi2mp4.conf. # Directory for temporary files. Files created there will be deleted. # Example: # TMP_DIR="${HOME}/rip/tmp" TMP_DIR="" # The directory where the resulting MP4 files will be put. # Example: # WORK_DIR="${HOME}/rip" WORK_DIR="" # The video bitrate you want your videos to have. # Example: # BITRATE="500" BITRATE="" # Your choice for minimum bpp. That's "bits per pixel". # bpp = ( video_bitrate * 1000 ) / ( fps * width * height ) # Sane range is 0.15 - 0.25 (I guess). Higher BPP usually means higher # quality but this is just a rule of thumb and by no means guaranteed. # Anyway, for batch encoding this is a good ruler. # Example: # MIN_BPP="0.19" MIN_BPP="" # faac encoding options. Leaving this empty lets faac use its default # settings. At the moment this means VBR at a quality level of 100 which # usually results in a bitrate of 120 kbit/s. # Example: # FAAC_ENCOPTS="-q 80" FAAC_ENCOPTS="" # x264 encoding options. "nocabac" and "level_idc=30" will be preset. # Same goes for "keyint" and "keyint_min". # Example: # X264_ENCOPTS="frameref=3:turbo=1:partitions=all:mixed_refs=1:fast_pskip=0:threads=auto" # Check the mencoder man page for more options if you like. X264_ENCOPTS="" # Extra options to pass to mencoder. For example I like to use the # following: # MENCODER_OPTS="-sws 1" # The default scaler of mencoder is bicubic and it sharpens the image # which in turn decreases compressibility of the picture. So while # scaling down it so happens that using a bilinear scaler helps improve # the quality of the encoded video. MENCODER_OPTS="" # End of configuration # function e { if [ ${ERROR} != 0 ] then echo echo "An error occurred. Check program output. Exiting." exit 1 fi } function c { unset -v ID_DEMUXER ID_VIDEO_WIDTH ID_VIDEO_HEIGHT ID_VIDEO_FPS ID_VIDEO_ASPECT \ VFOPTS ID_AUDIO_CODEC ID_AUDIO_NCH continue ${1} } function myidentify { mplayer -endpos .1 -vo null -ao null -frames 1 -identify "${1}" 2>/dev/null | sed -ne '/^ID_/ { s/[]()|&;<>`'"'"'\\!$" []/\\&/g;p }' } function remlog { rm -f "${TMP_DIR}"/"${BASENAME}"_myidentify.log } AVI2MP4=`basename "${0}"` echo "${AVI2MP4} v${VERSION}" [[ -f ~/.avi2mp4.conf ]] && source ~/.avi2mp4.conf unset -v VFOPTS LC_ALL export LC_NUMERIC="POSIX" for i in bc xargs faac mplayer mencoder aacgain MP4Box do if ! [ -x "`which ${i} 2>/dev/null`" ] then echo echo "${i} is missing. Exiting." exit 1 fi done for i in WORK_DIR TMP_DIR BITRATE MIN_BPP do eval j=\$${i} if [ -z ${j} ] then echo echo "${i} is not set. Exiting." exit 1 fi done for i in WORK_DIR TMP_DIR do eval j=\$${i} if ! [ -d "${j}" ] then echo echo "${i} does not exist or is not a directory. Exiting." exit 1 fi if ! [ -w "${j}" ] then echo echo "${i} is not writable. Exiting." exit 1 fi done if [ ${#} = 0 ] then echo echo "Usage: ${AVI2MP4} [] ..." exit 0 fi if [ ${X264_ENCOPTS} ] then X264_ENCOPTS=:${X264_ENCOPTS} fi for i in "${@}" do BASENAME=`basename "${i}" | sed 's/\(.*\)\.[a-z,0-9]*/\1/'` FILENAME=`basename "${i}"` DIRNAME=`dirname "${i}"` echo echo -e "File:\t\t\t${FILENAME}" echo if ! [ -e "${i}" ] then echo "Input file does not exist. Skipping." c fi if [ -e "${WORK_DIR}"/"${BASENAME}".mp4 ] then echo "File already converted to MP4. Skipping." c fi if [ -e "${TMP_DIR}"/"${BASENAME}".lock ] then echo "Conversion of input file already underway. If this is not the case" echo "remove the stale lock file and run this script again. Skipping." c fi # Get properties of file myidentify "${i}" > "${TMP_DIR}"/"${BASENAME}"_myidentify.log source "${TMP_DIR}"/"${BASENAME}"_myidentify.log for j in ID_DEMUXER ID_VIDEO_WIDTH ID_VIDEO_HEIGHT ID_VIDEO_FPS ID_VIDEO_ASPECT \ ID_AUDIO_CODEC ID_AUDIO_NCH do eval k=\$${j} if ! [ ${k} ] then echo "Input file not recognized as a valid media file. Skipping." remlog c 2 fi done for j in ID_VIDEO_WIDTH ID_VIDEO_HEIGHT ID_VIDEO_FPS ID_AUDIO_NCH do eval k=\$${j} IS_NULL=`echo "${k}==0" | bc -l` if [ ${IS_NULL} = 1 ] then echo "Input file properties invalid. Skipping." remlog c 2 fi done if [ `echo "${ID_VIDEO_ASPECT}==0" | bc -l` = 1 ] then echo "Video aspect not set. Assuming Width:Height." echo ID_VIDEO_ASPECT=`echo "${ID_VIDEO_WIDTH}/${ID_VIDEO_HEIGHT}" | bc -l | xargs printf "%1.4f\n"` fi [[ -f "${i}".vf ]] && VFOPTS=`head -n1 "${i}".vf` if [ ${VFOPTS} ] then VFOPTS=${VFOPTS}, fi # If we find crop=w:h:x:y in $VFOPTS let's get w and h and figure out # the new aspect ratio: CW=`echo ${VFOPTS} | sed 's/[a-z,:=0-9]*crop=//' | sed 's/:[a-z,:=0-9]*//g'` CH=`echo ${VFOPTS} | sed 's/[a-z,:=0-9]*crop=//' | sed "s/${CW}://" | sed 's/:[a-z,:=0-9]*//g'` if [ ${CW} ] || [ ${CH} ] then if ! [ ${CW} ] then CW=${ID_VIDEO_WIDTH} fi if ! [ ${CH} ] then CH=${ID_VIDEO_HEIGHT} fi ID_VIDEO_ASPECT=`echo "(${CW}*${ID_VIDEO_ASPECT}*${ID_VIDEO_HEIGHT})/(${CH}*${ID_VIDEO_WIDTH})" | bc -l | xargs printf "%1.4f\n"` fi echo -e "Dimension:\t\t${ID_VIDEO_WIDTH}x${ID_VIDEO_HEIGHT}" echo -e "FPS:\t\t\t${ID_VIDEO_FPS}" echo -e "Aspect:\t\t\t${ID_VIDEO_ASPECT}" echo if [ `echo "${ID_VIDEO_FPS}>30" | bc -l` = 1 ] then echo "Input framerate is greater than 30. iPod's (tm) can only" echo "handle up to 30 frames per second. This script can't handle" echo "framerate conversion. Skipping." remlog c fi for ((OUT_HORIZ=640; OUT_HORIZ>=240; OUT_HORIZ-=16)) do MULTIPLIER=`echo "${OUT_HORIZ}/(${ID_VIDEO_ASPECT}*16)" | bc -l | xargs printf "%1.0f\n"` OUT_VERT=`echo "16*${MULTIPLIER}" | bc -l` OUT_BPP=`echo "scale=4;((${BITRATE}*1000)/(${ID_VIDEO_FPS}*${OUT_HORIZ}*${OUT_VERT}))" | bc -l | xargs printf "%1.4f\n"` LEGIT=`echo "${OUT_BPP}>=${MIN_BPP}" | bc -l` if [ ${LEGIT} = 1 ] then break fi done if [ ${LEGIT} = 0 ] then echo "No valid output dimension found. Skipping." remlog c fi echo -e "Output dimension:\t${OUT_HORIZ}x${OUT_VERT}" echo -e "Target video bitrate:\t${BITRATE} kbit/s" echo -e "Target bits per pixel:\t${OUT_BPP}" echo echo "Creating lock file:" touch "${TMP_DIR}"/"${BASENAME}".lock ERROR=${?} e echo "${TMP_DIR}/${BASENAME}.lock" echo "Lock file created." echo # Mencoder likes accurate FPS if [ ${ID_VIDEO_FPS} = 23.976 ] then MFPS='24000/1001' elif [ ${ID_VIDEO_FPS} = 29.970 ] then MFPS='30000/1001' else MFPS=${ID_VIDEO_FPS} fi # Set keyint/keyint_min according to ID_VIDEO_FPS KEYINT_MIN=`echo "${ID_VIDEO_FPS}" | xargs printf "%1.0f\n"` KEYINT=`echo "${ID_VIDEO_FPS}*10" | bc -l | xargs printf "%1.0f\n"` echo "MEncoder 1st pass:" echo mencoder "${i}" -oac copy -ovc x264 -vf ${VFOPTS}scale=${OUT_HORIZ}:${OUT_VERT},harddup -ofps ${MFPS} -x264encopts \ pass=1:bitrate=${BITRATE}:keyint=${KEYINT}:keyint_min=${KEYINT_MIN}:nocabac:level_idc=30${X264_ENCOPTS} \ -o /dev/null -passlogfile "${TMP_DIR}"/"${BASENAME}"_mencoder.log ${MENCODER_OPTS} ERROR=${?} e echo echo "MEncoder 2nd pass:" echo mencoder "${i}" -oac copy -ovc x264 -vf ${VFOPTS}scale=${OUT_HORIZ}:${OUT_VERT},harddup -ofps ${MFPS} -x264encopts \ pass=2:bitrate=${BITRATE}:keyint=${KEYINT}:keyint_min=${KEYINT_MIN}:nocabac:level_idc=30${X264_ENCOPTS} \ -o "${TMP_DIR}"/"${BASENAME}"_mencoder.avi -passlogfile "${TMP_DIR}"/"${BASENAME}"_mencoder.log ${MENCODER_OPTS} ERROR=${?} e echo echo "Extracting audio:" echo mplayer "${TMP_DIR}"/"${BASENAME}"_mencoder.avi -vc null -vo null -ao \ pcm:fast:file="${TMP_DIR}"/"${BASENAME}"_mplayer.wav -noconsolecontrols \ -nolirc -nojoystick -nomouseinput ERROR=${?} e echo echo "Encoding audio:" echo faac -o "${TMP_DIR}"/"${BASENAME}"_faac.m4a ${FAAC_ENCOPTS} "${TMP_DIR}"/"${BASENAME}"_mplayer.wav ERROR=${?} e echo echo "Applying ReplayGain:" echo aacgain -r -k "${TMP_DIR}"/"${BASENAME}"_faac.m4a ERROR=${?} e echo echo "Extracting raw x264 bitstream from video:" echo MP4Box -aviraw video "${TMP_DIR}"/"${BASENAME}"_mencoder.avi ERROR=${?} e echo echo "Muxing:" echo MP4Box -itags name="${BASENAME}" -new -add "${TMP_DIR}"/"${BASENAME}"_faac.m4a \ -add "${TMP_DIR}"/"${BASENAME}"_mencoder_video.h264:fps=${ID_VIDEO_FPS} -tmp "${TMP_DIR}" \ "${WORK_DIR}"/"${BASENAME}".mp4 ERROR=${?} e echo echo "Cleaning up." rm -f "${TMP_DIR}"/"${BASENAME}"_mencoder.log "${TMP_DIR}"/"${BASENAME}"_mencoder.avi \ "${TMP_DIR}"/"${BASENAME}"_mplayer.wav "${TMP_DIR}"/"${BASENAME}"_faac.m4a \ "${TMP_DIR}"/"${BASENAME}"_mencoder_video.h264 "${TMP_DIR}"/"${BASENAME}".lock \ "${TMP_DIR}"/"${BASENAME}"_myidentify.log ERROR=${?} e echo "Encoding ${BASENAME}.mp4 finished." c done