Skip to content

Instantly share code, notes, and snippets.

@ata4 ata4/makegif.pl
Last active Dec 26, 2015

Embed
What would you like to do?
Video to GIF converter script.
#!/usr/bin/perl
use strict;
use warnings;
use Math::Round;
use JSON;
use Getopt::Std;
# program paths
my $ffprobePath = "ffprobe";
my $ffmpegPath = "ffmpeg";
my $convertPath = "imconvert";
my $gifsiclePath = "gifsicle";
# subroutines
sub remove_extension {
my $str = shift;
$str =~ s{\.[^.]+$}{};
return $str;
}
sub get_file_size {
my $file = shift;
sprintf("%.2f MiB", (-s $file) / 1048576);
}
# argument parsing
my %opts = ();
getopts('ogvrq:f:w:h:s:k:', \%opts);
if (@ARGV < 1) {
print "usage: makegif.pl <options> <video file> [gif file]\n\n";
print "-q <float> Sets the color fuzzing value in percent. Higher values remove more similar pixels from following frames to lower the file size at cost of more artifacts. Default is 2.\n";
print "-r Re-use .rgb file from previous call and don't delete it.\n";
print "-o Use ordered dithering with reduced colors. Ugly, but reduces file size significantly.\n";
print "-g Create GIF file with a global color table.\n";
print "-w <int> Override video width.\n";
print "-h <int> Override video height.\n";
print "-s <float> Override video width and height using this multiplier.\n";
print "-f <float> Override video frame rate.\n";
print "-k <string> Add comment to GIF file.\n";
exit;
}
# set options
my $fuzz = defined $opts{q} ? $opts{q} : 2;
my $verbose = defined $opts{v} ? $opts{v} : 0;
my $orderedDither = defined $opts{o} ? $opts{o} : undef;
my $globalColors = defined $opts{g} ? $opts{g} : 0;
my $reuseRGB = defined $opts{r} ? $opts{r} : 0;
my $customFramerate = defined $opts{f} ? $opts{f} : undef;
my $customWidth = defined $opts{w} ? int $opts{w} : undef;
my $customHeight = defined $opts{h} ? int $opts{h} : undef;
my $sizeMulti = defined $opts{s} ? $opts{s} : undef;
my $resize = $customWidth || $customHeight || $sizeMulti;
my $comment = defined $opts{k} ? $opts{k} : undef;
my $colors = 256;
# set files
my $videoFile = $ARGV[0];
my $videoFileName = remove_extension($videoFile);
my $gifFile = defined $ARGV[1] ? $ARGV[1] : $videoFileName . ".gif";
my $gifFileName = remove_extension($gifFile);
my $rgbFile = $videoFileName . ".rgb";
# probe video file
#if (not -f $videoFile) {
# die "Video file not found";
#}
my $ffprobe = `$ffprobePath -v quiet -print_format json -show_streams "$videoFile" 2>&1`;
my $ffprobeJson = decode_json $ffprobe;
my $videoStream;
# scan for video stream
foreach my $stream (@{$ffprobeJson->{streams}}) {
if ($stream->{codec_type} eq 'video') {
$videoStream = $stream;
last;
}
}
if (!$videoStream) {
die "No video stream found in source file";
}
# get final resolution
my $width = $customWidth ? $customWidth : int $videoStream->{width};
my $height = $customHeight ? $customHeight : int $videoStream->{height};
my $framerate = $customFramerate ? $customFramerate : $videoStream->{r_frame_rate};
my $frames = $videoStream->{nb_frames} ? $videoStream->{nb_frames} : $videoStream->{duration_ts};
if ($sizeMulti) {
$width *= $sizeMulti;
$height *= $sizeMulti;
}
# convert numerator/denominator to decimal
if ($framerate =~ /^\d+\/\d+$/) {
$framerate = eval($framerate);
}
my $delay = round (100 / $framerate);
# gif needs at least 1ms delay, most browsers have a minimum of 2ms
if ($delay < 2) {
$delay = 2;
}
printf "Input: %d frames, %dx%d @ %.2f FPS (%d ms)\n", $frames, $width, $height, $framerate, $delay;
my @args;
# create RGB image array
if (!$reuseRGB || !(-f $rgbFile)) {
print "Converting video to RGB...\n";
@args = ();
push @args, "-loglevel", "quiet" if !$verbose;
push @args, "-y";
push @args, "-i", $videoFile;
push @args, "-pix_fmt", "rgb24";
push @args, "-s", $width . "x" . $height if $resize;
push @args, $rgbFile;
system $ffmpegPath, @args;
print "RGB size: " . get_file_size($rgbFile) . "\n";
}
print "Converting RGB to GIF...\n";
@args = ();
# memory limits
push @args, "-limit", "memory", "8GiB";
push @args, "-limit", "map", "6GiB";
# output control
push @args, "-verbose" if $verbose;
# animation
push @args, "-delay", $delay;
# input file and control
push @args, "-size", $width . "x" . $height;
push @args, "-depth", "8";
push @args, "rgb:$rgbFile";
# optimization
push @args, "-fuzz", $fuzz . "%" if $fuzz > 0;
push @args, "-layers", "OptimizePlus";
push @args, "-layers", "OptimizeTransparency";
# color reduction and dithering
push @args, "-quantize", "YUV";
push @args, "-dither", "FloydSteinberg";
push @args, "-ordered-dither", "o8x8,32" if $orderedDither;
if ($globalColors) {
push @args, "-colors", $colors ;
push @args, "+map";
}
# output file
push @args, $gifFile;
system $convertPath, @args;
print "GIF size: " . get_file_size($gifFile) . " (raw)\n";
unlink $rgbFile if !$reuseRGB;
# optimize and add comment
@args = ();
push @args, "--batch";
push @args, "--optimize=3";
push @args, "--comment", $comment if $comment;
push @args, $gifFile;
system $gifsiclePath, @args;
print "GIF size: " . get_file_size($gifFile) . " (optimized)\n";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.