Skip to content

Instantly share code, notes, and snippets.

@BETLOG
Last active December 21, 2020 02:30
Show Gist options
  • Save BETLOG/c10a578a5b4f382fbd350ce1b6381334 to your computer and use it in GitHub Desktop.
Save BETLOG/c10a578a5b4f382fbd350ce1b6381334 to your computer and use it in GitHub Desktop.
ffmpeg zoom directly at target (more useful/visually expected than the typical examples)
#!/bin/bash
# betlog - 2020-12-20--23-01-02
# https://superuser.com/questions/1087685/ffmpeg-zoompan-image-to-center-position
# http://dragonquest64.blogspot.com/2019/02/ffmpeg-zoom.html <<<<------ THIS
#
# syntax:
# video-zoompanOnImage.sh [geometry(${sizeX}x${sizeY}+${posX}+${posY}] [zoom|zoompan] imageFilename
# omitting geometry or mode will query with kdialog
# eg:
# video-zoompanOnImage.sh image.png
# video-zoompanOnImage.sh 66x38+808+330 image.png
# video-zoompanOnImage.sh 66x38+808+330 zoom image.png # >10x so force-limited
# video-zoompanOnImage.sh 66x38+808+330 zoompan image.png # >10x so force-limited
#
# NOTE: assumes video res = screen res = image res
#
# TODO: padding might be useful for some zoompans
# TODO: trying to set a specific pos seems to always fail (animate quickly from 0,0) if there is a zoom involved
# TODO: only works on $screenRes (1920x1080) images
screenRes=$(xrandr|grep primary|grep -Eo '[0-9]{3,}x[0-9]{3,}')
enlargedScale=10
zoomDuration=2
framerate=60
arg=$(grep -Eo '[0-9]{2,}x[0-9]{2,}\+[0-9]{1,}\+[0-9]{1,}'<<<"$1")
if [[ -n $arg ]];then
geom=$arg
# echo "geom: $geom"
sizeX=$(awk -F'x|+' '{print $1}'<<<$geom)
sizeY=$(awk -F'x|+' '{print $2}'<<<$geom)
posX=$(awk -F'x|+' '{print $3}'<<<$geom)
posY=$(awk -F'x|+' '{print $4}'<<<$geom)
# echo "sizeX:$sizeX, sizeY:$sizeY, posX:$posX, posY:$posY"
shift
if [[ $1 == zoompan ]];then
mode=zoompan
shift
elif [[ $1 == zoom ]];then
mode=zoom
shift
fi
# echo "remaining input: $@"
fi
# -------------------------
for input in "$@"; do
input=$(printf "%b\n" "${input//%/\\x}"| sed 's/file:\/\///g') #also strip special characters in nfbc2 folder name
if [[ -f "$input" && $(mimetype -ab $(realpath "$input")) =~ image ]] ;then
# echo "input: $input"
baseName="${input%.*}"
baseExt="${input##*.}"
if [[ -z $geom ]];then
# geeqie --fullscreen "$input" & 2>&1 /dev/null
geeqie --fullscreen "$input" &
pid=$!
# echo "pid: $pid $(ps -p$pid -o cmd=)"
source /home/user/documents/scripts/video/f_getClickGeom
# outputs:
# $geom
# $sizeX
# $sizeY
# $posX
# $posY
# $resX
# $resY
# sleep 1s
kill $pid 2> /dev/null
else
# imgRes=$(file "$input"|grep -Eo '[0-9]{3,}( )?x( )?[0-9]{3,}'|sed 's/ //g') #actual image res, not screen res
# resX=$(awk -F'x' '{print $1}'<<<$imgRes)
# resY=$(awk -F'x' '{print $2}'<<<$imgRes)
resX=$(awk -F'x' '{print $1}'<<<$screenRes)
resY=$(awk -F'x' '{print $2}'<<<$screenRes)
fi
if [[ -z $mode ]];then
mode=$(kdialog --radiolist "Select mode:" zoom "direct zoom" on zoompan "zoom and pan" off)
[[ $? -ne 0 ]] && exit
fi
if [[ -z $mode ]];then
echo "ERROR: mode 'zoom' or 'zoompan' must be specified"
kdialog --title "ERROR:" --passivepopup "mode 'zoom' or 'zoompan' must be specified" 5
exit
fi
exportName="${baseName}_${mode}${geom}.mp4"
echo -ne "=============== ENCODING ZOOMPAN - $mode - $geom ===============\\n"
echo -ne "input : ${input}\\noutput: ${exportName}\\n"
echo "imgRes : $imgRes"
echo "screenRes : $screenRes"
echo "enlargedScale: $enlargedScale"
echo "zoomDuration : $zoomDuration"
echo "framerate : $framerate"
echo "posX : $posX"
echo "posY : $posY"
echo "sizeX : $sizeX"
echo "sizeY : $sizeY"
echo "resX : $resX"
echo "resY : $resY"
zoomFactor=$( echo "scale=3;$resX/$sizeX"|bc)
if (( $(echo "$zoomFactor > 10" |bc -l) ));then
z=$zoomFactor
zoomFactor=10
echo "zoomFactor :: $zoomFactor (WAS $z NOW FORCE LIMITED TO 10)"
else
echo "zoomFactor : $zoomFactor"
fi
centerX=$((posX+(sizeX/2)))
centerY=$((posY+(sizeY/2)))
echo "centerX :: $centerX"
echo "centerY :: $centerY"
posX=$((centerX-(resX/20)))
posY=$((centerY-(resY/20)))
echo "posX :: $posX"
echo "posY :: $posY"
zoomFrames=$(echo "scale=1;$zoomDuration*$framerate"|bc)
echo "zoomFrames : $zoomFrames"
zoomIncrement=$(echo "scale=3;($zoomFactor-1)/($zoomDuration*$framerate)"|bc) # CORRECT: starts at zoomFactor=1
echo "zoomIncrement: $zoomIncrement"
enlargedX=$((enlargedScale * resX))
enlargedY=$((enlargedScale * resY))
echo "enlargedX: $enlargedX"
echo "enlargedY: $enlargedY"
enlargedPosX=$((enlargedScale * posX))
enlargedPosY=$((enlargedScale * posY))
echo "enlargedPosX: $enlargedPosX"
echo "enlargedPosY: $enlargedPosY"
enlargedCenterX=$((enlargedScale * centerX))
enlargedCenterY=$((enlargedScale * centerY))
echo "enlargedCenterX: $enlargedCenterX"
echo "enlargedCenterY: $enlargedCenterY"
enlargedPanIncrementX=$(echo "scale=3;$enlargedPosX/$zoomFrames"|bc)
enlargedPanIncrementY=$(echo "scale=3;$enlargedPosY/$zoomFrames"|bc)
echo "enlargedPanIncrementX: $enlargedPanIncrementX"
echo "enlargedPanIncrementY: $enlargedPanIncrementY"
#
# cmd="ffmpeg -hide_banner "
cmd="ffmpeg-bar -hide_banner "
cmd+="-i \"${input}\" "
cmd+="-filter_complex "
cmd+="\"[0:v]scale=${enlargedX}x${enlargedY}"
if [[ $mode == zoom ]];then
cmd+=",zoompan"
cmd+="=z='min(max(zoom,pzoom)+${zoomIncrement},${zoomFactor})'"
cmd+=":x='${enlargedCenterX}-(${enlargedCenterX}/zoom)'"
cmd+=":y='${enlargedCenterY}-(${enlargedCenterY}/zoom)'"
elif [[ $mode == zoompan ]];then
cmd+=",zoompan"
cmd+="=z='zoom+${zoomIncrement}'"
# cmd+="=z=zoom+${zoomIncrement},${zoomFactor}"
cmd+=":x='if(gte(zoom,$zoomFactor),x,x+${enlargedPanIncrementX})'"
cmd+=":y='if(gte(zoom,$zoomFactor),y,y+${enlargedPanIncrementY})'"
fi
cmd+=":d=${zoomFrames}"
cmd+=":s=${screenRes}"
cmd+=":fps=${framerate}"
cmd+="[v]"
cmd+="\" "
cmd+="-map [v] "
cmd+="-map 0:a? "
cmd+="-c:v libx264 "
cmd+="-pix_fmt yuv420p "
cmd+="-c:a copy "
cmd+="-y \"${exportName}\" </dev/null"
source "${0%/*}/f_echoCmd"
[[ -f "$exportName" ]] && rm "$exportName"
eval $cmd
[[ -f "$exportName" ]] && smplayer "$exportName" &
fi
done
#!/bin/bash
# betlog - 2020-10-28--18-08-03
#
# NOTE: get clicked rectangle, longest edge is used to match both sides to screen aspect ratio
# inputs:
# none (user clicks)
# outputs:
# $geom
# $sizeX
# $sizeY
# $posX
# $posY
# $resX
# $resY
# if [[ $(xprop -id $(xprop -root _NET_ACTIVE_WINDOW | cut -d ' ' -f 5)|grep '_NET_WM_STATE(ATOM)') =~ _NET_WM_STATE_FULLSCREEN ]];then
state=$(xprop -id $(xprop -root _NET_ACTIVE_WINDOW | cut -d ' ' -f 5)|grep '_NET_WM_STATE(ATOM)')
# echo -ne "\\n\\n===============\\n${state}\\n===============\\n\\n"
if [[ $state =~ _NET_WM_STATE_STAYS_ON_TOP || $state =~ _NET_WM_STATE_FULLSCREEN ]];then
sudo --background --user $USER kdialog --title "${0##*/}" --passivepopup "click TOP-LEFT and DRAG to set geometry\\n!NOTE!\\naspect will be forced along longest edge\\nto SCREEN (potentially XScreen) aspect" 5 2> /dev/null
else
kdialog --title "${0##*/}" --passivepopup "click TOP-LEFT and DRAG to set geometry\\n!NOTE!\\naspect will be forced along longest edge\\nto SCREEN (potentially XScreen) aspect" 5 2> /dev/null
fi
data=$(import -identify /dev/null 2>/dev/null)
[[ -z $data ]] && exit
# echo "data: $data"
size=$(awk '{print $3}'<<<"$data")
# echo "size: $size"
sizeX=$(awk -F'x|+' '{print $1}'<<<"$size")
sizeY=$(awk -F'x|+' '{print $2}'<<<"$size")
echo "size: ${sizeX}x${sizeY} (actual)"
# echo "sizeX: $sizeX"
# echo "sizeY: $sizeY"
xscreenSizeAndClickedPos=$(awk '{print $4}'<<<"$data") #unadjusted, as picked, no aspect ratio correction
# echo "xscreenSizeAndClickedPos (as clicked): $xscreenSizeAndClickedPos"
posX=$(awk -F'x|+' '{print $3}'<<<"$xscreenSizeAndClickedPos")
posY=$(awk -F'x|+' '{print $4}'<<<"$xscreenSizeAndClickedPos")
echo "pos: $posX,$posY"
# echo "posX: $posX"
# echo "posY: $posY"
xScreenResX=$(awk -F'x|+' '{print $1}'<<<"$xscreenSizeAndClickedPos")
xScreenResY=$(awk -F'x|+' '{print $2}'<<<"$xscreenSizeAndClickedPos")
# echo "xScreenRes: ${xScreenResX}x${xScreenResY}"
# echo "xScreenResX: $xScreenResX"
# echo "xScreenResY: $xScreenResY"
# echo
data=$(xrandr|grep -Eo '[0-9]{3,}x[0-9]{3,}\+[0-9]{1,}\+[0-9]{1,}'|sed -E 's/\ {1,}/ /g; s/\*//g')
# echo -ne "data:\\n$data\\n\\n"
declare -a displayGeomArray=( $(echo "$data"|grep -Eo '[0-9]{3,}x[0-9]{3,}\+[0-9]{1,}\+[0-9]{1,}'|tr '\n' ' ') )
total=${#displayGeomArray[@]}
for ((i=0;i<$total;i++));do
region=${displayGeomArray[$i]}
# echo "i: $i"
# echo "region: $region"
minX=$(sed -E 's/[0-9]{1,}x[0-9]{1,}\+([0-9]{1,})\+[0-9]{1,}/\1/'<<<$region)
resX=$(sed -E 's/([0-9]{1,})x[0-9]{1,}\+[0-9]{1,}\+[0-9]{1,}/\1/'<<<$region)
maxX=$(( minX + resX ))
# echo "minX: $minX"
# echo "resY: $resY"
# echo "maxX: $maxX"
minY=$(sed -E 's/[0-9]{1,}x[0-9]{1,}\+[0-9]{1,}\+([0-9]{1,})/\1/'<<<$region)
resY=$(sed -E 's/[0-9]{1,}x([0-9]{1,})\+[0-9]{1,}\+[0-9]{1,}/\1/'<<<$region)
maxY=$(( minY + resY ))
# echo "minY: $minY"
# echo "resY: $resY"
# echo "maxY: $maxY"
if [[ $posX -gt $minX && $posX -lt $maxX && $posY -gt $minY && $posY -lt $maxY ]];then # first click point
clickedRegion=$i
break
fi
# echo
done
# echo "clickedRegion: $clickedRegion"
displayResArray=( $(xrandr|grep ' connected '|sed -E 's/^.* ([0-9]{1,}x[0-9]{1,})\+[0-9]{1,}\+[0-9]{1,} .*$/\1/') )
# echo -ne "displayResArray:\\n${displayResArray[@]}\\n"
res=${displayResArray[$i]} #reassign
resX=${res%x*}
resY=${res#*x}
echo "res: $res"
# echo "resX: $resX"
# echo "resY: $resY"
# use longest selected edge to limit selection t same aspect ratio as total (single, not combined) screen resolution
# TODO: ?loop compare mod2 to get power of 2 size? can record without, BUT CANNOT reencode until i do this
let sizeX++
let sizeY++
if (( $(echo "$sizeY/$resY > $sizeX/$resX" |bc -l) ));then
echo "Y (vertical) edge is primary"
sizeX=$(echo "scale=3;$sizeY/$resY*$resX"|bc)
sizeX=$(echo "scale=0;($sizeX+0.5)/1"|bc) #ceil
# echo "sizeX: $sizeX"
if [[ $((posX + sizeX)) -gt $xScreenResX ]];then
echo "$((posX+sizeX)) > $xScreenResX : exceeds bounds on X - rescaling Y"
sizeX=$((xScreenResX-posX))
sizeY=$(echo "scale=3;$sizeX/$resX*$resY"|bc)
sizeY=$(echo "scale=0;($sizeY+0.5)/1"|bc) #ceil
fi
else
echo "X (horizontal) edge is primary"
sizeY=$(echo "scale=3;$sizeX/$resX*$resY"|bc)
sizeY=$(echo "scale=0;($sizeY+0.5)/1"|bc) #ceil
if [[ $((posY + sizeY)) -gt $xScreenResY ]];then
echo "$((posY+sizeY)) > $xScreenResY : exceeds bounds on Y - rescaling X"
sizeY=$((xScreenResY-posY))
sizeX=$(echo "scale=3;$sizeY/$resY*$resX"|bc)
sizeX=$(echo "scale=0;($sizeX+0.5)/1"|bc) #ceil
fi
fi
if [[ -z $sizeX || -z $sizeY || -z $posX || -z $posY ]];then
echo "GEOMETRY FAILURE - ${sizeX}x${sizeY}+${posX}+${posY}"
kdialog --title "${0##*/}" --passivepopup "GEOMETRY FAILURE\\n${sizeX}x${sizeY}+${posX}+${posY}" 5
unset data size sizeX sizeY geom resX resY posX posY totalRes totalResX totalResY # cleanup because fail
exit
fi
# NOTE: make sure it's divisible by 2 or cant re-encode it later
# echo "$((sizeX % 2))"
# echo "$((sizeY % 2))"
# echo "size: ${sizeX}x${sizeY} (actual)"
[[ 0 -ne $((sizeX % 2)) ]] && let sizeX--
[[ 0 -ne $((sizeY % 2)) ]] && let sizeY--
echo "size: ${sizeX}x${sizeY} (screen aspect adjusted)"
geom="${sizeX}x${sizeY}+${posX}+${posY}" #adjusted to longest side dictates aspect ratio matching current screen
echo "geom (aspect adjusted): $geom"
kdialog --title "${0##*/}" --passivepopup "GEOMETRY:\\n$geom" 5
unset data size
# 1920/1080=1.777777777777778
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment