script to use ffmpeg to animate images with the "Ken Burns Effect"

interpretive language scripts


Moderator: Forum moderators

Post Reply
HerrBert
Posts: 358
Joined: Mon Jul 13, 2020 6:14 pm
Location: Germany, NRW
Has thanked: 18 times
Been thanked: 126 times

script to use ffmpeg to animate images with the "Ken Burns Effect"

Post by HerrBert »

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 ;)

User avatar
Flash
Moderator
Posts: 981
Joined: Tue Dec 03, 2019 3:13 pm
Location: Arizona, U.S.
Has thanked: 52 times
Been thanked: 127 times

Re: script to use ffmpeg to animate images with the "Ken Burns Effect"

Post by Flash »

Here's an excellent YouTube video describing the 'Ken Burns Effect' and how to make it happen. It's basically just zooming and panning within the still frame.

Chaos coordinator :?
HerrBert
Posts: 358
Joined: Mon Jul 13, 2020 6:14 pm
Location: Germany, NRW
Has thanked: 18 times
Been thanked: 126 times

Re: script to use ffmpeg to animate images with the "Ken Burns Effect"

Post by HerrBert »

Flash wrote: Sat Mar 30, 2024 10:08 pm

Here's an excellent YouTube video describing the 'Ken Burns Effect' and how to make it happen. It's basically just zooming and panning within the still frame.
...

One of my favourites... But how do you do it in Puppyland?

[edit]removed repeated url to youtube video[/edit]

User avatar
rockedge
Site Admin
Posts: 6571
Joined: Mon Dec 02, 2019 1:38 am
Location: Connecticut,U.S.A.
Has thanked: 2779 times
Been thanked: 2650 times
Contact:

Re: script to use ffmpeg to animate images with the "Ken Burns Effect"

Post by rockedge »

part of the Ken Burns Effect is cutting out foreground objects and pasting them back in the original position on a new layer. Possibly several objects on individual layers or alpha channels. Then panning the background separated from the stationary foreground object. You see the full effect used in Ken Burns' documentaries.

Post Reply

Return to “Scripts”