#!/bin/sh
#
VIDEO_FMT="pal" # set to ntsc if you want so
AUDIO_FMT="mp2" # set to ac3 if you want so
#
# ----------------------------------------------------------------------------
# pimpmymenu (c) Martin Stoll 2008, take it or leave it (no warranties).
#
# Please use the diascope mailing list (diascope-user@lists.sf.net) for
# bug reports.
# ----------------------------------------------------------------------------
#
PMM_VERSION="0.1"
if [ ${VIDEO_FMT} = "pal" ]; then
FRAME_RATE=25
VIDEO_TARGET=pal-dvd
VIDEO_W=720
VIDEO_H=576
PPMTOY4M_OPT="-v 0 -F 25:1 -A 59:54"
else
FRAME_RATE=29.97
VIDEO_TARGET=ntsc-dvd
VIDEO_W=720
VIDEO_H=480
PPMTOY4M_OPT="-v 0 -F 30000:1001 -A 10:11"
fi
AUDIO_ENC="-acodec ${AUDIO_FMT} -ab 128k -ar 48000 -ac 2"
PPMFILTER=$(which ppmfilter 2> /dev/null); HAVE_PPMFILTER=0
if [ ! -z $PPMFILTER ]; then test ! -x $PPMFILTER; HAVE_PPMFILTER=$?; fi
function error
{
echo $*
exit 1
}
# Function findfile filename optdir, returns in ff
findfile_ret=""
function findfile
{
local inf=$1
local dir=$2
if [ ! -f ${inf} ]; then
[ -f ${dir}/${inf} ] || error "File ${dir}/${inf} not found"
inf=${dir}/${inf}
fi
findfile_ret=${inf}
}
# Function duration returns the number of seconds that a file provides,
# and the number of frames at FRAME_RATE. If a second parameter, maxlens,
# is given then a maximum of maxlens seconds is returned.
duration_sec=""
duration_frm=""
function duration
{
local tmp=($(ffmpeg -i $1 2>&1 | grep "Duration:"))
[ ${tmp[1]:0:3} = "N/A" ] && error "Cannot determine length of file $1. Consider re-multiplexing the file."
local longdur=${tmp[1]:0:10}
local maxlens=$2
local dur=($(echo $longdur | awk -v maxlens=${maxlens} \
'{ split($0,TMP,":"); durs=int((TMP[3]+60*(TMP[2]+60*TMP[1]))); \
if (maxlens!="") { durs=(maxlens < durs ? maxlens : durs) }
print durs " " int(ENVIRON["FRAME_RATE"]*durs) }'))
duration_sec=${dur[0]}
duration_frm=${dur[1]}
}
# Function position calculates the ppmfilter-style position from an
# X,Y,W,H information
position_ret=""
function position
{
position_ret=$(echo $1 \
| awk '{split($0,TMP,","); print TMP[1]/ENVIRON["VIDEO_W"]*100 "," TMP[2]/ENVIRON["VIDEO_H"]*100 ":" \
TMP[3]/ENVIRON["VIDEO_W"]*100 "x" TMP[4]/ENVIRON["VIDEO_H"]*100}')
}
echo ""
echo "$(basename $0) ${PMM_VERSION} (ms 2008)"
echo "See http://diascope.sf.net/related.php for updates"
echo "Video: ${VIDEO_FMT}, audio: ${AUDIO_FMT}. Edit $0 to change."
menulist=$1
if [ -z ${menulist} ]; then
echo ""
echo "- add audio tracks to a dvdstyler menu,"
echo "- replace a static menu background by a video track,"
echo "- generate animated thumbnails."
echo "- requires dvdstyler, ppmfilter and ffmpeg."
echo ""
echo "Instructions for use"
echo ""
echo "1 - Set dvdstyler to keep temporary files by ticking the box"
echo " Configuration/Settings.../Core/Don't remove temp files."
echo "2 - Run dvdstyler on your dvd design. You can abort it after the preview."
echo "3 - Switch to dvdstyler's temporary directory (that's the directory where"
echo " the menu*.mpg files have been generated)."
echo "4 - run $(basename $0) "
echo "5 - wait and follow the instructions."
echo ""
echo ""
echo "General syntax of the input file:"
echo ""
echo "menuX-Y audio audiofile [over=videofile pos=X,Y,W,H [hold=F]]*"
echo "menuU-V video videofile rgb=RRGGBB [over=overlay pos=X,Y,W,H [hold=S] [len=S]]*"
echo "..."
echo ""
echo "where menuX-Y is the number of the menu file (play menuX-Y*.mpg to check)."
echo "audiofile is background audio using the static background that dvdstyler"
echo "has generated."
echo ""
echo "over=overlay pos=X,Y,W,H specifies to overlay file \"overlay\" at position X,Y"
echo "with given width W and height H (to fit it into a dvdstyler button outline)."
echo "If hold=S is given, start of the video overlay will be delayed by S seconds."
echo "If len=S is given, a maximum of S seconds will be used for the overlay."
echo "Repeat for as many overlays as desired."
echo ""
echo "In order to find the correct X,Y,W,H open menuX-Y.mpg_highlight.png in your"
echo "favourite image editor and look up the positions."
echo ""
echo "videofile is a background video (including audio). To replace the background"
echo "of a menu with videofile but keep the rest of your design, like labels etc,"
echo "do as follows: in dvdstyler, assign the background a color (e.g. A029F0) but"
echo "no image. Then specify rgb=A029F0, and the colour is replaced by videofile."
echo "If your design contains this colour natively then that will become transparent,"
echo "too. In that case choose a different colour (e.g. rgb=40FABA). If your design"
echo "contains both A029F0 and 40FABA you sure have a special taste :-)"
echo "Due to the mpeg compression the result of this operation may not be perfect."
error ""
fi
export VIDEO_W VIDEO_H FRAME_RATE
[ -f ${menulist} ] || error "Error: cannot find input file ${menulist}"
optdir=$(dirname ${menulist})
# Create ppmpump.sh: The script repeats the first frame of ${movie} to ${holdf1} times,
# then plays ${movie} up to length ${durs} seconds, and then repeats the last frame
# of ${movie} ${holdfN} times.
cat < ppmpump.sh
#!/bin/bash
# pimpmymenu ppmpump script
# call: \$0 movie holdf1 durs holdfN
read movie holdf1 durs holdfN < <(echo \$@)
ff=\${movie}.1.ppm
lf=\${movie}.N.ppm
# tmp=(\$(ffmpeg -i \${movie} 2>&1 | grep "Duration:"))
# duration_sec=\${tmp[1]:0:10}
# Extract first frame at 00:00:00.0
ffmpeg -vframes 1 -ss 0 -i \${movie} -f image2pipe -vcodec ppm - > \${ff} 2>/dev/null
# Extract last frame at \${durs}
ffmpeg -vframes 1 -ss \${durs} -i \${movie} -f image2pipe -vcodec ppm - > \${lf} 2>/dev/null
# repeat first frame holdf1 times
j=1; while [ \$((j<=\$holdf1)) == 1 ]; do cat \${ff}; let j=\$((j+1)); done
# play movie
ffmpeg -i \${movie} -t \${durs} -f image2pipe -vcodec ppm - 2>/dev/null
# repeat last frame holdfN times
j=1; while [ \$((j<=\$holdfN)) == 1 ]; do cat \${lf}; let j=\$((j+1)); done
EOF
chmod u+x ppmpump.sh
# loop over all menu mpg's that dvdstyler has created
for mpg in menu*-*.mpg_bg.mpg; do
base=$(basename $mpg .mpg_bg.mpg)
ma=${base}.${AUDIO_FMT}
m=${base}.mpg
bg=${base}.mpg_bg.jpg
# older dvdstyler creates .jpg, newer creates static .mpg
# in case of newer extract a frame from the .mpg and create the .jpg
[ -f ${bg} ] || ffmpeg -i ${mpg} -vframes 1 -ss 0.3 -an -vcodec mjpeg -f image2 -y ${bg} 2> /dev/null
# if present in our input file this menu will be pimped, otherwise skipped
entry=($(grep -n "^${base}" ${menulist}))
type=${entry[1]}
[ -z ${type} ] && continue
# go...
echo "** Menu ${base}"
# Get audio or video background filename
#
findfile ${entry[2]} ${optdir}
avin=${findfile_ret}
duration ${avin}
duration_bg=${duration_sec}
duration_bgf=${duration_frm}
echo " Background $(basename ${avin}): ${duration_bg} seconds (${duration_bgf} frames)."
# If video, see if a colour to be replaced is provided.
# Then generate transparent button file.
#
if [ ${type} = "video" ]; then
[ $HAVE_PPMFILTER = 1 ] || error "Error: need ppmfilter from the smilutils package for video overlays."
colour=""; [ ${entry[3]:0:4} = "rgb=" ] && colour=${entry[3]:4}
oin=${base}.png
if [ -z ${colour} ]; then
echo " No colour to replace provided, flood-filling from top left corner."
convert ${bg} -fill none -fuzz 20% -draw 'matte 0,0 floodfill' ${oin}
else
echo " Replacing colour ${colour} by transparency"
convert ${bg} -fuzz 20% -transparent "#${colour}" ${oin}
fi
fi
of="_ppmfilter.sh"
echo "#!/bin/bash" > ${of}
chmod u+x ${of}
have_overlay=0
echo -n "${PPMFILTER} " >> ${of}
if [ ${type} = "video" ]; then
have_overlay=1
echo -n "--overlay file=${oin} position=0,0:100x100 " >> ${of}
fi
i=3
while [ ! -z ${entry[$i]} ]; do
if [ ${entry[$i]:0:4} = "len=" ]; then # the parser is quite simple...
error "Error: if both hold= and len= are given, hold= must come first. ";
fi
if [ ${entry[$i]:0:5} != "over=" ]; then i=$((i+1)); continue; fi
have_overlay=1
[ ${entry[$((i+1))]:0:4} = "pos=" ] || error "Error: over= must be followed by pos="
findfile ${entry[$i]:5} ${optdir}
overlay=${findfile_ret}
position ${entry[$((i+1))]:4}
i=$((i+2))
holdf1=0; if [ "${entry[$i]:0:5}" = "hold=" ]; then
holds1=${entry[$i]:5}
holdf1=$(echo "${holds1} * ${FRAME_RATE}" | bc -l)
i=$((i+1));
fi
maxlens=0; if [ "${entry[$i]:0:4}" = "len=" ]; then maxlens=${entry[$i]:4}; i=$((i+1)); fi
duration ${overlay} ${maxlens}
holdfN=$((duration_bgf-holdf1-duration_frm))
holdfN=$((holdfN > 0 ? holdfN : 0))
echo " Overlay $(basename ${overlay}): ${duration_sec} seconds (${duration_frm} frames), using ${holdf1}:${duration_frm}:${holdfN}."
echo -n "--plugin-producer command=\"./ppmpump.sh ${overlay} ${holdf1} ${duration_sec} ${holdfN}\" --overlay position=${position_ret} " >> ${of}
done
echo " Preparing audio ${ma}"
ffmpeg -i ${avin} ${AUDIO_ENC} -y ${ma} 2> /dev/null
vp="_videoprovider.sh"
echo "#!/bin/bash" > ${vp}
chmod u+x ${vp}
if [ ${type} = "video" ]; then
echo "ffmpeg -i ${avin} -f image2pipe -vcodec ppm - 2>/dev/null | ./${of}" >> ${vp}
elif [ ${type} = "audio" ]; then
echo "convert -depth 8 ${bg} ${bg}.ppm" >> ${vp}
echo -n "j=1; while [ \$((j<=${duration_bgf})) == 1 ]; do cat ${bg}.ppm; let j=\$((j+1)); done " >> ${vp}
if [ ${have_overlay} = "1" ]; then echo " | ./${of}" >> ${vp}; fi
else error "Error in ${menulist}: unknown type ${type}."
fi
echo " Preparing video ${m}"
./${vp} \
| ppmtoy4m ${PPMTOY4M_OPT} -S 420mpeg2 \
| ffmpeg -f yuv4mpegpipe -i - -i ${ma} -target ${VIDEO_TARGET} ${AUDIO_ENC} -y ${m} 2>/dev/null
rm -f ${ma}
done
echo "Done. You can now run mkisofs to build the DVD like so: "
echo "rm -rf VIDEO_TS AUDIO_TS"
for mx in menu*-*.mpg_spumux.xml; do
base=$(basename ${mx} _spumux.xml)
old=${base}
new=${base}.spm
echo "spumux ${mx} < ${old} > ${new}"
echo "mv -f ${new} ${old}";
done
echo "dvdauthor -o ${PWD} -x dvdauthor.xml"
echo "mkisofs -V DVD -o dvd.iso -dvd-video ${PWD}"