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