Skip to content

Instantly share code, notes, and snippets.

@rkfg
Last active February 4, 2021 21:01
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save rkfg/4463793 to your computer and use it in GitHub Desktop.
Save rkfg/4463793 to your computer and use it in GitHub Desktop.
GIF animation script to cut a piece of video and produce a frame-accurate optimized GIF with a precise framerate.
#!/bin/sh
help () {
log "Usage: `basename $0` [-h] [-n] [-a] [-u] [-s hh:mm:ss] [-d ss] [-w px] [-f n] [-S n] [-b n] [-t subtitle.sub] [-c default | basic | fontsize | fontstyle | full] <filename> [result.gif]
-h show this help
-n turn off subtitles
-a don't open directory with frames in filemanager
-u update this script from gist. Create .gifupd file in the script's directory to automatically check for updates once per day or put the number of seconds in the file to check with that period.
-s start time in seconds or as hours:minutes:seconds, default: 0
-d duration in seconds or as hh:mm:ss, default: 5
-w resulting width in pixels (height is set according to the aspect), default: 320
-f use every nth frame to get smaller file; use 2-3 for anime, default: 2
-S use nth subtitles track
-b use n fake (dithered) bits for color, you can also use form r,g,b specifying number of levels per channel (0-255), default: 24
-t add subtitles from the file
-c create (and replace) the subtitles file with a template of the chosen type
-p set FPS manually
<filename> input filename
result.gif optional output filename. If not specified, random name will be used.
"
update
exit 0
}
log () {
if [ -z "$2" ]
then
echo "$1" >&2
else
if [ $1 -le $DEBUG ]
then
echo "$2" >&2
fi
fi
}
update () {
PERIOD=86400
if [ "$1" != "do" ]
then
if [ ! -f "$UPDFILE" ]
then
log 5 "No update file found"
return
fi
FILEPERIOD=$( cat "$UPDFILE" )
if [ -n "$FILEPERIOD" ]
then
PERIOD=$FILEPERIOD
fi
log 5 "Update file found, PERIOD=$PERIOD"
NOW=$( date +%s )
THEN=$( stat -c %Y "$UPDFILE" )
DIFF=$(( NOW - THEN ))
log 5 "Now: $NOW, Then: $THEN, Diff: $DIFF"
else
DIFF=86401
fi
if [ $DIFF -ge $PERIOD ]
then
touch "$UPDFILE"
NEWFILE=`mktemp "/tmp/gcupdXXXXXX"`
trap "rm $NEWFILE 2>/dev/null" INT TERM EXIT
log 5 "NEWFILE: $NEWFILE"
wget -qO "$NEWFILE" "$UPDURL"
if [ $? -eq 0 -a -n "`cat $NEWFILE`" ]
then
DIFFTEXT=$( diff -u "$FULLSELFPATH" "$NEWFILE" )
if [ -n "$DIFFTEXT" ]
then
log "\033[1m***New version available***\033[0m"
if [ "$1" = "do" ]
then
log "Diff:\n\n$DIFFTEXT"
read -p "Perform updating? (y/N)" UPD
if [ "$UPD" = "y" -o "$UPD" = "Y" ]
then
log "Updating..."
cp "$NEWFILE" "$FULLSELFPATH"
log "Done."
fi
exit 0
else
log 5 "Update found, doing nothing."
fi
else
log 5 "No difference found in update"
fi
else
log 2 "Error downloading update."
fi
else
log 5 "Diff is less than $PERIOD seconds"
fi
rm $NEWFILE 2>/dev/null
}
subtitle () {
sed -e "s/^\s*\(.*\)$/\1/" -e "/^$/d" -e "/^;.*/d" $SUBTITLE | (
FONTSIZE=20
FONT=DejaVu-Sans-Bold
COLOR=white
STROKECOLOR='#000c'
STROKEWIDTH=2
read TYPE
log 5 "Subtitles type is: $TYPE"
case $TYPE in
basic)
read FONT FONTSIZE COLOR STROKECOLOR STROKEWIDTH
log 5 "Set up basic subtitles defaults."
;;
fontsize)
read FONT COLOR STROKECOLOR STROKEWIDTH
log 5 "Set up font subtitles defaults."
;;
fontstyle)
read STROKECOLOR STROKEWIDTH
log 5 "Set up font subtitles defaults"
;;
esac
log 5 "Set up subtitles defaults. FONT: $FONT FONTSIZE: $FONTSIZE COLOR: $COLOR STROKECOLOR: $STROKECOLOR STROKEWIDTH: $STROKEWIDTH"
FROM=
for FRAME in `ls "$TEMP"`
do
FRAMENUM=${FRAME%.png}
log 5 "Processing frame $FRAMENUM"
if [ -z "$FROM" ]
then
case $TYPE in
basic | default)
log 5 "Reading subline of default or basic type"
read FROM TO GRAVITY GEOMETRY TEXT
;;
fontsize)
log 5 "Reading subline of fontsize type"
read FROM TO GRAVITY GEOMETRY FONTSIZE TEXT
;;
fontstyle)
log 5 "Reading subline of fontstyle type"
read FROM TO GRAVITY GEOMETRY FONT FONTSIZE COLOR TEXT
;;
full)
log 5 "Reading subline of full type"
read FROM TO GRAVITY GEOMETRY FONT FONTSIZE COLOR STROKECOLOR STROKEWIDTH TEXT
;;
esac
log 5 "FROM: $FROM TO: $TO GRAVITY: $GRAVITY GEOMETRY: $GEOMETRY TEXT: $TEXT"
fi
if [ -n "$FROM" ]
then
if [ $FRAMENUM -ge $FROM -a $FRAMENUM -le $TO ]
then
log 5 "Adding subtitle to $FRAMENUM"
mogrify -gravity $GRAVITY -pointsize $FONTSIZE -font $FONT -stroke $STROKECOLOR -strokewidth $STROKEWIDTH -annotate $GEOMETRY "$TEXT" -stroke none -fill $COLOR -annotate $GEOMETRY "$TEXT" $FRAME
fi
if [ $FRAMENUM -ge $TO ]
then
# load next line
FROM=
fi
fi
done
)
}
gentemplate () {
TEMPLATE=";This is a subtitle file. Comments start with semicolon.\n;The first non-comment line sets dynamic parameters which can be specified for each subtitle line.\n;The second non-comment line sets other (static) parameters which can't be changed elsewhere.\n;Then your actual subtitles go. Fields are separated by spaces. From/to frame numbers are actual PNG file names in the temporary directory.\n;Refer to http://www.imagemagick.org/script/command-line-options.php and look for parameters -fill, -gravity, -pointsize, -font, -stroke, -strokewidth and -annotate.\n"
case $SUBTITLETYPE in
default)
TEMPLATE="${TEMPLATE}default\n;Defaults are: fontsize 20, font DejaVu-Sans-Bold, color white, strokecolor '#000c', strokewidth 2\n;Fields order: from to gravity geometry text\n0 30 south 0 Hello world!\n35 50 north -50+50 Subtitles are working!"
;;
basic)
TEMPLATE="${TEMPLATE}basic\n;In basic type we should specify the font, its size, its color and stroke color and width\nDejaVu-Sans-Bold 25 yellow #000066aa 5\n;Fields order: from to gravity geometry text\n0 30 south 0 Hello world!\n35 50 north -50+50 Subtitles are working!"
;;
fontsize)
TEMPLATE="${TEMPLATE}fontsize\n;In fontsize type we should specify the font, its color and stroke color and width\nDejaVu-Sans-Bold yellow #000066aa 5\n;Fields order: from to gravity geometry fontsize text\n0 30 south 0 20 Hello world!\n35 50 north -50+50 30 Subtitles are working!"
;;
fontstyle)
TEMPLATE="${TEMPLATE}fontstyle\n;In fontstyle type we should only specify the stroke color and width\n#000066aa 5\n;Fields order: from to gravity geometry font fontsize color text\n0 30 south 0 DejaVu-Sans-Condensed 20 yellow Hello world!\n35 50 north -50+50 DejaVu-Serif-Italic 30 green Subtitles are working!"
;;
full)
TEMPLATE="${TEMPLATE}full\n;In full type we should not specify anything after the style type\n\n;Fields order: from to gravity geometry font fontsize color strokecolor strokewidth text\n0 30 south 0 DejaVu-Sans-Condensed 20 yellow #000c 2 Hello world!\n35 50 north -50+50 DejaVu-Serif-Italic 30 green blue 5 Subtitles are working!"
;;
*)
log "Invalid template type. Valid types are: default, basic, fontsize, fontstyle and full"
exit 1
;;
esac
echo "$TEMPLATE" > $SUBTITLE
}
if [ -z $DEBUG ]
then
DEBUG=1
fi
FPS=
UPDFILENAME=".gifupd"
UPDURL=https://gist.github.com/raw/4463793/gifcreate
FULLSELFPATH=$( readlink -f "$0" )
UPDFILE=$( dirname "$FULLSELFPATH" )/$UPDFILENAME
STARTTIME=0
DURATION=5
WIDTH=320
FRAMESTEP=2
RUNFM=1
FAKEBITS=24
OUTPUT=$( mktemp -u /tmp/videoXXXXXX.gif )
if [ $# -eq 0 ]
then
help
fi
OPTS=
while getopts hnaus:d:w:f:o:S:b:t:c:p: flag
do
case $flag in
h)
help
;;
n)
OPTS="-nosub -noautosub $OPTS"
;;
a)
RUNFM=
;;
u)
update "do"
exit 0
;;
s)
STARTTIME=$OPTARG
;;
d)
DURATION=$OPTARG
;;
w)
WIDTH=$OPTARG
;;
f)
FRAMESTEP=$OPTARG
;;
S)
OPTS="-sid $OPTARG $OPTS"
;;
b)
FAKEBITS=$OPTARG
;;
t)
SUBTITLE=$OPTARG
;;
c)
SUBTITLETYPE=$OPTARG
;;
p)
FPS=$OPTARG
;;
esac
done
update & # do background update check
shift $(( OPTIND-1 ))
if [ -n "$SUBTITLETYPE" -a -n "$SUBTITLE" ]
then
gentemplate
exit
fi
if [ -n "$SUBTITLETYPE" ]
then
log "Specify the subtitle file with -t to create a template."
exit 1
fi
if [ -z "$1" ]
then
log "No input file specified."
exit 1
fi
if [ -z "$2" ]
then
log "No output file specified. The resulting GIF will be saved as $OUTPUT"
else
OUTPUT=$( readlink -f "$2" )
fi
SUBTITLE=$( readlink -f "$SUBTITLE" )
TEMP=`mktemp -d /tmp/mpgifXXXXXX`
trap "rm -rf \"$TEMP\"; exit" INT TERM EXIT
FILENAME=$( readlink -f "$1" )
cd "$TEMP"
MPOUT=$( ffmpeg "$FILENAME" $OPTS -ss $STARTTIME -endpos $DURATION -vf scale=$WIDTH:-2,framestep=$FRAMESTEP -nosound -vo png:z=9 -speed 100 2>&1 )
log 5 "Mplayer output: $MPOUT"
[ -z "$FPS" ] && FPS=$( echo "$MPOUT" | sed -n 's/.* \([0-9]*\)\.[0-9]* fps .*/\1/p' | head -n 1 )
log 5 "FPS: $FPS"
DELAY=$(( 100 * FRAMESTEP / FPS ))
log 5 "Delay: $DELAY"
if [ -n "$RUNFM" ]
then
xdg-open "$TEMP"
read -p "Now remove any excessive frames and press Enter to combine them to a nice gif." null
fi
if [ -n "$SUBTITLE" ]
then
log "Adding subtitles..."
subtitle
fi
log "Rendering the final gif..."
convert -delay $DELAY "$TEMP/*.png" -ordered-dither o8x8,$FAKEBITS -layers optimize-transparency "$OUTPUT"
@rkfg
Copy link
Author

rkfg commented Jan 6, 2013

Let's add options, help and stuff.

@rkfg
Copy link
Author

rkfg commented Jan 12, 2013

Speed it up!

@rkfg
Copy link
Author

rkfg commented Jan 13, 2013

Adding ordered dithering to fight black artifacts and decrease the GIF size a lot.

@Ky6uk
Copy link

Ky6uk commented Jan 13, 2013

HOROSHO SDELALY BATYA DOVOLEN

@rkfg
Copy link
Author

rkfg commented Jan 13, 2013

Logs and autoupdating. WTF am I doing, huh?

@mvasilkov
Copy link

Now publish it on Steam and make truckload of money.

@koteq
Copy link

koteq commented Jan 13, 2013

>autoupdating
You're do it wrong.

@rkfg
Copy link
Author

rkfg commented Feb 3, 2013

That's ok though autoupdating was implemented a little too late. I dunno what to add to this. Anyway, new features always appear when you're using the tool to its max.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment