#!/usr/bin/perl

# ************************************************************
# * gifmaker - A tool for making animated GIFs from video
#              files.   This works as a wrapper around
#              FFmpeg and ImageMagick's convert.
#
#              For more details see:
# http://www.leshylabs.com/blog/dev/2013-08-04-Making_Animated_GIFs_from_the_Linux_Command_Line.html
# ************************************************************

#
# Copyright (c) 2013, Leshy Labs LLC
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
#       copyright notice, this list of conditions and the following
#       disclaimer in the documentation and/or other materials provided
#       with the distribution.
#     * The names names of the authors may not be used to endorse or
#       promote products derived from this software without specific
#       prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS
# CONTRIBUTERS ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS
# CONTRIBUTERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#

# Changes
#  8/9/13 - Added some default optimizations to convert

use strict;
use File::Temp qw/ tempdir /;

my $CONFIG = {
    ffmpeg => 'ffmpeg',       # ffmpeg binary
    convert => 'convert',     # ImageMagick convert binary
    output_file => 'out.gif', # Default file to write to
    default_fps => 10,        # Default frames per second
    ffmpeg_args => '',        # Extra arguments for ffmpeg
    convert_args => '-layers removeDups -layers Optimize' # Extra arguments for convert
};

sub usage {
    die "USAGE: $0 [input_file] [output_file] [frames_per_second]\n" .
	"    input_file - The input video file to convert (Required)\n" .
	"    output_file - The GIF file to be created (Default=$CONFIG->{output_file})\n" .
	"    frames_per_second - The GIF's frame rate. (Default=$CONFIG->{default_fps})\n\n" .
	"ENVIRONMENT:\n" .
	"    FFMPEG_ARGS - Extra arguments for ffmpeg\n" .
	"    CONVERT_ARGS - Extra arguments for ImageMagick's convert command\n";
}

sub verify {
    # Verify all parameters and settings before proceeding
    my ($input_file, $output_file, $fps) = @_;

    if (! -e $input_file) {
	die "ERROR: Input file '$input_file' does not exist.\n";
    }
    elsif ($fps !~ /^\d{1,2}$/ || $fps == 0) {
	die "ERROR: FPS value '$fps' is not valid.  A number from 1 to 99 must be provided\n";
    }
    elsif ($output_file !~ /\.gif$/i) {
	die "ERROR: Output file must use the '.gif' extension\n";
    }
    elsif (-e $output_file) {
	die "ERROR: Output file '$output_file' already exists\n";
    }
}

sub run_command {
    my ($command) = @_;

    print "*** $command\n";
    system($command);
    print "\n";

    if ($? >> 8 != 0) {
	die "ERROR: Command did not return a 0 exit status.  Aborting.\n";
    }
}

sub make_gif {
    my ($input_file, $output_file, $fps) = @_;
    my $temp_dir = tempdir(CLEANUP => 1);
    my $real_output_file = $output_file;
    my $delay = sprintf("%.0f", (100 / $fps)); # Delay value for convert in hundreths of second and rounded

     # Protect some special characters
    $output_file =~ s/([;\$\\`])/\\\1/g;

    run_command("$CONFIG->{ffmpeg} $CONFIG->{ffmpeg_args}  $ENV{FFMPEG_ARGS} " .
		"-i $input_file -r $fps $temp_dir/frame.\%05d.gif");
    run_command("$CONFIG->{convert} $CONFIG->{convert_args} -loop 0 $ENV{CONVERT_ARGS} " .
		"-delay $delay $temp_dir/frame.*.gif $output_file");

    print "Animated gif saved to '$real_output_file'\n";
}

sub main {
    my ($input_file, $output_file, $fps) = @ARGV;

    if ($#ARGV < 0 || $#ARGV > 2) {
	usage();
    }

    # If the arguments are not provided, set the default values
    $fps         ||= $CONFIG->{default_fps};
    $output_file ||= $CONFIG->{output_file};

    verify($input_file, $output_file, $fps);   # Verify all parameters
    make_gif($input_file, $output_file, $fps); # Create the animated GIF
}

main();