script to use ffmpeg to animate images with the "Ken Burns Effect"
What it's about:
Especially after christmas i was 'spammed' with a lot of photos by some family members, asking if i could make a video-slide-show of them. Of course, i can. Problem is that nowadays most people take photos with a mobile phone in portrait mode...
Add black borders to fill vertical hd to horizontal hd :/ The result is more than dissapointing.
I recall on my imac there was a screensaver zooming and panning a picture. Did some search on the internet and found it is called the Ken-Burns-Effect. Further searching led to some few results for using ffmpeg on linux to achieve this animation effect.
Threw together a script to get what i wanted.
Not at least because of the few search results i thought about posting a script to give some more advanced options to use the zoompan filter of ffmpeg (not only in Puppy Linux...).
Code: Select all
#!/bin/bash
# Abhängigkeiten: bc, ffprobe, ffmpeg, urxvt, mtpaint (optional)
GEOMETRY=1280x720
TIME=5
FPS=30
INTRO=1
STILL=1
ACC="time"
[ -e "$1" ] && INFILE="$1" && shift # dnd
# rudimental:
chkarg() { [ "$(tr -d [:digit:]$3 <<< "$2")" = "" ] || { echo "invalid argument to option: $1 $2"; DRYRUN="error"; }; }
while [ "$1" ]; do
case $1 in
--debug) set -x; HOLD="-hold";;
-i) [ "$2" ] && shift && INFILE="$1";;
-o) [ "$2" ] && shift && OUTFILE="$1";;
-fps) chkarg "$1" "$2" "" && shift && FPS=$1;;
-d) chkarg "$1" "$2" "" && shift && TIME=$1;;
-u) chkarg "$1" "$2" "" && shift && UPSCALE="$1";;
-p) chkarg "$1" "$2" "" && shift && INTRO=$1;;
-w) chkarg "$1" "$2" "" && shift && STILL=$1;;
-f) chkarg "$1" "$2" "" && shift && F_IN="$1";;
-b) chkarg "$1" "$2" "" && shift && F_OUT="$1";;
-x|-x1) chkarg "$1" "$2" "-" && shift && X1="$1";;
-x2) chkarg "$1" "$2" "-" && shift && X2="$1";;
-y|-y1) chkarg "$1" "$2" "" && shift && Y1=$1;;
-y2) chkarg "$1" "$2" "" && shift && Y2=$1;;
-z|-z1) chkarg "$1" "$2" "." && shift && Z1=$1;;
-z2) chkarg "$1" "$2" "." && shift && Z2=$1;;
-g) chkarg "$1" "$2" "x" && shift && GEOMETRY=$1;;
-s) chkarg "$1" "$2" "xy" && shift && SYNC=$1;;
-a) ACC="zoom";;
-e) OVR="-y";;
-v) VERBOSE=true;;
-m) DRYRUN="true";VERBOSE=true;PREVIEW=true;;
-n) DRYRUN="true";VERBOSE=true;;
-h|--help) echo "usage: ${0##*/} -i FILE OPTIONS
Options:
-i input file
-o output file name (can be full path and|or file name)
-a force acceleration method zoom
-b I blank time (fade out)
-d I duration - total length of output video
-e overwrite existing file
-f I fade in time
-fps I frames per second (default: 30)
-g WxH geometry of output video (default: 1280x720)
-h show this help
-m preview zoom areas in mtpaint
-n dryrun - show some info and exit
-p I pause time after zoompan
-s [xy] sync pan and zoom speed for x and/or y
-u I upscale (default: width+4000)
-v verbose output
-w I wait time before zoompan
-x I horizontal start position (default: 0)
-x2 I horizontal stop position (default: 0)
-y I vertical start position (default: 0)
-y2 I vertical stop position (default: bottom of image)
-z D start zoom factor (default: fit to image width)
-z2 D stop zoom factor (default: fit to image width)
"; exit;;
esac
shift
done
[ -z "$INFILE" ] && exit
[ -d "$OUTFILE" ] && OUTNAME="${INFILE##*/}" && OUTFILE="$OUTFILE/${OUTNAME%.*}_zoompan.mp4"
SIZE="$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0:s=x "$INFILE")"
INWIDTH=${SIZE%%x*}
INHEIGHT=${SIZE##*x}
# upscaling
(( INWIDTH > INHEIGHT )) && {
(( INHEIGHT < 4000 || UPSCALE )) && SCALE="scale=-2:${UPSCALE:-ih+400$((INHEIGHT % 2))}," || :
} || {
(( INWIDTH < 4000 || UPSCALE )) && SCALE="scale=${UPSCALE:-iw+400$((INWIDTH % 2))}:-2,"
}
# padding
OUTHEIGHT=${GEOMETRY##*x}
OUTWIDTH=${GEOMETRY%%x*}
OUTASPECT="$(bc <<< "scale=4;$OUTWIDTH / $OUTHEIGHT")"
(( $(bc <<< "scale=4;$INWIDTH / $INHEIGHT > $OUTASPECT") )) && {
echo "vertical padding not available..."
exit
}
PAD="pad=ih*${OUTWIDTH}/${OUTHEIGHT}:ih:(ow-iw)/2:(oh-ih)/2,"
PADWIDTH="$((INHEIGHT * OUTWIDTH / OUTHEIGHT))"
OUTWIDTH=$(( OUTWIDTH - (OUTWIDTH % 2) ))
OUTHEIGHT=$(( OUTHEIGHT - (OUTHEIGHT % 2) ))
# ffmpeg zoompan filter
# zoom
[ -z "$Z1" ] && Z1=$(bc <<< "scale=6;$PADWIDTH / $INWIDTH")
[ -z "$Z2" ] && Z2=$(bc <<< "scale=6;$PADWIDTH / $INWIDTH")
DURATION=$((TIME * FPS))
PAUSE=$((INTRO * FPS))
WAIT=$((STILL * FPS))
NET=$((DURATION - PAUSE - WAIT))
(( $(bc <<< "scale=4;$Z1 > $Z2") )) && { # zoom out
STEP=$(bc <<< "scale=6;($Z1 - $Z2) / $NET")
[ "$ACC" = "time" ] && {
DIRECTION="if(lte(on,$WAIT),$Z1,max(zoom-$STEP,$Z2))"
} || {
DIRECTION="if(lte(on,$WAIT),$Z1,zoom-$STEP*max((2-(on-$WAIT)/($NET+1)*2),0))" # S-Kurve linksrechtsmitte
}
} || { # zoom in
STEP=$(bc <<< "scale=6;(($Z2-1) - ($Z1-1)) / $NET")
[ "$ACC" = "time" ] && {
DIRECTION="if(lte(on,$WAIT),$Z1,min(zoom+$STEP,$Z2))"
} || {
DIRECTION="if(lte(on,$WAIT),$Z1,min(zoom+$STEP*(on-$WAIT)/$NET*2,$Z2))" # S-Kurve linksrechtsmitte
}
}
# pan
# engine
[ "$Z1" = "$Z2" -o "$ACC" = "time" ] && {
ENGINE="if(lte(on,$WAIT),0,min((on-$WAIT)/$NET,1))"
} || {
ENGINE="(zoom-$Z1)/($Z2-$Z1)"
}
[[ $SYNC = *x* ]] && SX="*$Z2/zoom"
OFFSET=$(( (PADWIDTH - INWIDTH) / 2 ))
XDIFF=$(bc <<< "scale=6;(($OFFSET + ${X2:-0}) - ($OFFSET + ${X1:-0})) / $PADWIDTH")
PAN_X="iw*$(bc <<< "scale=6;($OFFSET + ${X1:-0}) / $PADWIDTH")+iw*$XDIFF*$ENGINE$SX"
[[ $SYNC = *y* ]] && SY="*$Z2/zoom"
[ -z "$Y2" ] && Y2=$(bc <<< "$INHEIGHT - $INHEIGHT / $Z2")
YDIFF=$(bc <<< "scale=6;($Y2 - ${Y1:-0}) / $INHEIGHT")
PAN_Y="ih*$(bc <<< "scale=6;${Y1:-0} / $INHEIGHT")+ih*$YDIFF*$ENGINE$SY"
# fade
[ "$F_IN" ] && FADE_IN=",fade=t=in:st=0:d=$F_IN"
[ "$F_OUT" ] && FADE_OUT=",fade=t=out:st=$((TIME - F_OUT)):d=$F_OUT"
# info
[ "$VERBOSE" ] && {
PX1=$(( $OFFSET + ${X1:-0} ))
PX2=$(( $OFFSET + ${X2:-0} ))
PZ1="$(bc <<< "$PADWIDTH / $Z1"):$(bc <<< "$PADWIDTH * $OUTHEIGHT / $OUTWIDTH / $Z1")"
PZ2="$(bc <<< "$PADWIDTH / $Z2"):$(bc <<< "$PADWIDTH * $OUTHEIGHT / $OUTWIDTH / $Z2")"
echo "Input image geometry: $SIZE"
echo "Output video geometry: ${OUTWIDTH}x${OUTHEIGHT}"
echo "Pad window width: $PADWIDTH"
echo "Left padding is $OFFSET"
echo "Zoom is $Z1 to $Z2"
echo "Zoom window geometry: $PZ1:$PX1:${Y1:-0} to $PZ2:$PX2:$Y2"
echo "Acceleration method is $ACC"
}
[ "$PREVIEW" ] && {
[ -z "$(which mtpaint)" ] && echo "mtpaint not found. Exiting..." && exit
PREVIEW="/tmp/ffzoompan_tmp/${INFILE##*/}"
[ ! -d "${PREVIEW%/*}" ] && mkdir -p "${PREVIEW%/*}"
mtpaint --cmd -f/open="$INFILE" -s/all -e/copy -f/new w=$PADWIDTH h=$INHEIGHT =24 -e/centre -e/Brush Size=3 -e/col A=2 -s/all \($PX1,${Y1:-0},${PZ1%:*},${PZ1##*:}\) -s/"Outline Selection" -e/col A=1 -s/all \($PX2,$Y2,${PZ2%:*},${PZ2##*:}\) -s/"Outline Selection" -f/as="${PREVIEW%.*}_preview.jpg" f=jpeg 1>/dev/null
echo
echo "NOTICE:"
echo "When using coordinates from preview image keep in mind that x is absolute to the original image and you have to substract padding... "
echo "e.g. -x \$((coord - $OFFSET))"
echo
mtpaint "${PREVIEW%.*}_preview.jpg" & exit
}
[ "$DRYRUN" ] && echo "Exiting now (dryrun)" && exit
# run in separate window
urxvt $HOLD -e ffmpeg $OVR -i "$INFILE" -vf "${SCALE}${PAD}zoompan=z='${DIRECTION}':d='${DURATION}':x='${PAN_X}':y='${PAN_Y}':s=${OUTWIDTH}x${OUTHEIGHT}:fps=${FPS}${FADE_IN}${FADE_OUT}" -pix_fmt yuv420p -c:v libx264 -crf 24 -preset veryfast "${OUTFILE:-${INFILE%.*}_zoompan.mp4}"
Notice the amount of options. Try out what works for you...
Have fun and stay curious