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