#!/usr/bin/perl -w
# NAME: genthumbs.pl
# AIM: Given an imput file, or folder, generate a thumbnail image using imagemagik
# 18/06/2013 geoff mclane http://geoffair.net/mperl
use strict;
use warnings;
use File::Basename;  # split path ($name,$dir,$ext) = fileparse($file [, qr/\.[^.]*/] )
use Cwd;
my $os = $^O;
my $perl_dir = '/home/geoff/bin';
my $PATH_SEP = '/';
my $temp_dir = '/tmp';
if ($os =~ /win/i) {
    $perl_dir = 'C:\GTools\perl';
    $temp_dir = $perl_dir;
    $PATH_SEP = "\\";
}
unshift(@INC, $perl_dir);
require 'lib_utils.pl' or die "Unable to load 'lib_utils.pl' Check paths in \@INC...\n";
require 'lib_imgsize.pl'  or die "Unable to load 'lib_imgsize.pl' Check paths in \@INC...\n";
# log file stuff
our ($LF);
my $pgmname = $0;
if ($pgmname =~ /(\\|\/)/) {
    my @tmpsp = split(/(\\|\/)/,$pgmname);
    $pgmname = $tmpsp[-1];
}
my $outfile = $temp_dir.$PATH_SEP."temp.$pgmname.txt";
open_log($outfile);

# user variables
my $VERS = "0.0.1 2013-06-18";
my $load_log = 0;
my $in_file = '';
my $verbosity = 0;
my $out_file = '';
my $recursive = 0;
my $keep_aspect = 1;
my $overwrite = 0;
my $allimagemagik = 0;
my $out_ext = ".jpg";   # default output extension

###############################################################
### WHAT IS THE BEST thumbnail SIZE?
# Assume an 'average' display is 1024 x 768, and using 4 table columns
# that is each image should be 256 pixels wide to fill the width
##my $targ_wid = 200; # this produced an average 40-70K imaqes
my $targ_wid = 256; # this produced an average 42-90K imaqes
##my $targ_wid = 300; # this produced an average 80-110K imaqes
##my $targ_wid = 480;     # this produced an average 90-210K images

my $out_dir = $temp_dir.$PATH_SEP."tempthumbs";

my @in_files = ();
my @in_dirs = ();
my @exclude_files = ();

# ### DEBUG ###
my $debug_on = 0;
my $def_file = 'c:\HOMEPAGE\GA\travel\sicily\images';

### program variables
my @warnings = ();
my $cwd = cwd();

sub VERB1() { return $verbosity >= 1; }
sub VERB2() { return $verbosity >= 2; }
sub VERB5() { return $verbosity >= 5; }
sub VERB9() { return $verbosity >= 9; }

sub show_warnings($) {
    my ($val) = @_;
    if (@warnings) {
        prt( "\nGot ".scalar @warnings." WARNINGS...\n" );
        foreach my $itm (@warnings) {
           prt("$itm\n");
        }
        prt("\n");
    } else {
        prt( "\nNo warnings issued.\n\n" ) if (VERB9());
    }
}

sub pgm_exit($$) {
    my ($val,$msg) = @_;
    if (length($msg)) {
        $msg .= "\n" if (!($msg =~ /\n$/));
        prt($msg);
    }
    show_warnings($val);
    close_log($outfile,$load_log);
    exit($val);
}


sub prtw($) {
   my ($tx) = shift;
   $tx =~ s/\n$//;
   prt("$tx\n");
   push(@warnings,$tx);
}

sub process_in_file($) {
    my ($inf) = @_;
    if (! open INF, "<$inf") {
        pgm_exit(1,"ERROR: Unable to open file [$inf]\n"); 
    }
    my @lines = <INF>;
    close INF;
    my $lncnt = scalar @lines;
    prt("Processing $lncnt lines, from [$inf]...\n");
    my ($line,$inc,$lnn);
    $lnn = 0;
    foreach $line (@lines) {
        chomp $line;
        $lnn++;
        if ($line =~ /\s*#\s*include\s+(.+)$/) {
            $inc = $1;
            prt("$lnn: $inc\n");
        }
    }
}


# list from - identify -list format
my %IMReadFormats = (
      '3FR' => 'Hasselblad CFV/H3D39II',
        'A' => 'Raw alpha samples',
      'AAI' => 'AAI Dune image',
       'AI' => 'Adobe Illustrator CS2',
      'ART' => 'PFS: 1st Publisher Clip Art',
      'ARW' => 'Sony Alpha Raw Image Format',
      'AVI' => 'Microsoft Audio/Visual Interleaved',
      'AVS' => 'AVS X image',
        'B' => 'Raw blue samples',
      'BIE' => 'Joint Bi-level Image experts Group interchange format (1.6)',
      'BMP' => 'Microsoft Windows bitmap image',
        'C' => 'Raw cyan samples',
      'CAL' => 'Continuous Acquisition and Life-cycle Support Type 1',
     'CALS' => 'Continuous Acquisition and Life-cycle Support Type 1',
   'CANVAS' => 'Constant image uniform color',
  'CAPTION' => 'Caption',
      'CIN' => 'Cineon Image File',
'CLIPBOARD' => 'The system clipboard',
     'CMYK' => 'Raw cyan, magenta, yellow, and black samples',
    'CMYKA' => 'Raw cyan, magenta, yellow, black, and alpha samples',
      'CR2' => 'Canon Digital Camera Raw Image Format',
      'CRW' => 'Canon Digital Camera Raw Image Format',
      'CUR' => 'Microsoft icon',
      'CUT' => 'DR Halo',
      'DCM' => 'Digital Imaging and Communications in Medicine image',
      'DCR' => 'Kodak Digital Camera Raw Image File',
      'DCX' => 'ZSoft IBM PC multi-page Paintbrush',
      'DDS' => 'Microsoft DirectDraw Surface',
    'DFONT' => 'Multi-face font package (Freetype 2.1.5)',
      'DNG' => 'Digital Negative',
      'DPX' => 'SMPTE 268M-2003 (DPX 2.0)',
     'EPDF' => 'Encapsulated Portable Document Format',
      'EPI' => 'Encapsulated PostScript Interchange format',
      'EPS' => 'Encapsulated PostScript',
     'EPSF' => 'Encapsulated PostScript',
     'EPSI' => 'Encapsulated PostScript Interchange format',
      'EPT' => 'Encapsulated PostScript with TIFF preview',
     'EPT2' => 'Encapsulated PostScript Level II with TIFF preview',
     'EPT3' => 'Encapsulated PostScript Level III with TIFF preview',
      'ERF' => 'Epson RAW Format',
      'FAX' => 'Group 3 FAX',
     'FITS' => 'Flexible Image Transport System',
  'FRACTAL' => 'Plasma fractal image',
      'FTS' => 'Flexible Image Transport System',
        'G' => 'Raw green samples',
       'G3' => 'Group 3 FAX',
      'GIF' => 'CompuServe graphics interchange format',
    'GIF87' => 'CompuServe graphics interchange format (version 87a)',
 'GRADIENT' => 'Gradual linear passing from one shade to another',
     'GRAY' => 'Raw gray samples',
   'GROUP4' => 'Raw CCITT Group4',
     'HALD' => 'Identity Hald color lookup table image',
      'HDR' => 'Radiance RGBE image format',
      'HRZ' => 'Slow Scan TeleVision',
      'ICB' => 'Truevision Targa image',
      'ICO' => 'Microsoft icon',
     'ICON' => 'Microsoft icon',
   'INLINE' => 'Base64-encoded inline images',
      'IPL' => 'IPL Image Sequence',
      'J2C' => 'JPEG-2000 Code Stream Syntax',
      'J2K' => 'JPEG-2000 Code Stream Syntax',
      'JBG' => 'Joint Bi-level Image experts Group interchange format (1.6)',
     'JBIG' => 'Joint Bi-level Image experts Group interchange format (1.6)',
      'JNG' => 'JPEG Network Graphics',
      'JP2' => 'JPEG-2000 File Format Syntax',
      'JPC' => 'JPEG-2000 Code Stream Syntax',
     'JPEG' => 'Joint Photographic Experts Group JFIF format (80)',
      'JPG' => 'Joint Photographic Experts Group JFIF format (80)',
      'JPX' => 'JPEG-2000 File Format Syntax',
        'K' => 'Raw black samples',
      'K25' => 'Kodak Digital Camera Raw Image Format',
      'KDC' => 'Kodak Digital Camera Raw Image Format',
    'LABEL' => 'Image label',
        'M' => 'Raw magenta samples',
      'M2V' => 'MPEG Video Stream',
      'M4V' => 'Raw MPEG-4 Video',
      'MAC' => 'MAC Paint',
      'MAP' => 'Colormap intensities and indices',
      'MAT' => 'MATLAB level 5 image format',
      'MEF' => 'Mamiya Raw Image File',
     'MIFF' => 'Magick Image File Format',
      'MNG' => 'Multiple-image Network Graphics (libpng 1.5.10)',
     'MONO' => 'Raw bi-level bitmap',
      'MOV' => 'MPEG Video Stream',
      'MP4' => 'MPEG-4 Video Stream',
      'MPC' => 'Magick Persistent Cache image format',
     'MPEG' => 'MPEG Video Stream',
      'MPG' => 'MPEG Video Stream',
      'MRW' => 'Sony (Minolta) Raw Image File',
      'MSL' => 'Magick Scripting Language',
     'MSVG' => 'ImageMagicks own SVG internal renderer',
      'MTV' => 'MTV Raytracing image format',
      'MVG' => 'Magick Vector Graphics',
      'NEF' => 'Nikon Digital SLR Camera Raw Image File',
      'NRW' => 'Nikon Digital SLR Camera Raw Image File',
     'NULL' => 'Constant image of uniform color',
        'O' => 'Raw opacity samples',
      'ORF' => 'Olympus Digital Camera Raw Image File',
      'OTB' => 'On-the-air bitmap',
      'OTF' => 'Open Type font (Freetype 2.1.5)',
      'PAL' => '16bit/pixel interleaved YUV',
     'PALM' => 'Palm pixmap',
      'PAM' => 'Common 2-dimensional bitmap format',
  'PATTERN' => 'Predefined pattern',
      'PBM' => 'Portable bitmap format (black and white)',
      'PCD' => 'Photo CD',
     'PCDS' => 'Photo CD',
      'PCL' => 'Printer Control Language',
      'PCT' => 'Apple Macintosh QuickDraw/PICT',
      'PCX' => 'ZSoft IBM PC Paintbrush',
      'PDB' => 'Palm Database ImageViewer Format',
      'PDF' => 'Portable Document Format',
     'PDFA' => 'Portable Document Archive Format',
      'PEF' => 'Pentax Electronic File',
      'PES' => 'Embrid Embroidery Format',
      'PFA' => 'Postscript Type 1 font (ASCII) (Freetype 2.1.5)',
      'PFB' => 'Postscript Type 1 font (binary) (Freetype 2.1.5)',
      'PFM' => 'Portable float format',
      'PGM' => 'Portable graymap format (gray scale)',
      'PGX' => 'JPEG-2000 VM Format',
    'PICON' => 'Personal Icon',
     'PICT' => 'Apple Macintosh QuickDraw/PICT',
      'PIX' => 'Alias/Wavefront RLE image format',
    'PJPEG' => 'Joint Photographic Experts Group JFIF format (80)',
   'PLASMA' => 'Plasma fractal image',
      'PNG' => 'Portable Network Graphics (libpng 1.5.10)',
    'PNG24' => 'opaque 24-bit RGB (zlib 1.2.6)',
    'PNG32' => 'opaque or transparent 32-bit RGBA',
     'PNG8' => '8-bit indexed with optional binary transparency',
      'PNM' => 'Portable anymap',
      'PPM' => 'Portable pixmap format (color)',
       'PS' => 'PostScript',
      'PSB' => 'Adobe Large Document Format',
      'PSD' => 'Adobe Photoshop bitmap',
     'PTIF' => 'Pyramid encoded TIFF',
      'PWP' => 'Seattle Film Works',
      'RAF' => 'Fuji CCD-RAW Graphic File',
      'RAS' => 'SUN Rasterfile',
      'RGB' => 'Raw red, green, and blue samples',
     'RGBA' => 'Raw red, green, blue, and alpha samples',
     'RGBO' => 'Raw red, green, blue, and opacity samples',
      'RLA' => 'Alias/Wavefront image',
      'RLE' => 'Utah Run length encoded image',
      'SCR' => 'ZX-Spectrum SCREEN',
      'SCT' => 'Scitex HandShake',
      'SFW' => 'Seattle Film Works',
      'SGI' => 'Irix RGB image',
      'SR2' => 'Sony Raw Format 2',
      'SRF' => 'Sony Raw Format',
  'STEGANO' => 'Steganographic image',
      'SUN' => 'SUN Rasterfile',
      'SVG' => 'Scalable Vector Graphics (XML 2.7.8)',
     'SVGZ' => 'Compressed Scalable Vector Graphics (XML 2.7.8)',
     'TEXT' => 'Text',
      'TGA' => 'Truevision Targa image',
     'TIFF' => 'Tagged Image File Format (LIBTIFF, Version 4.0.1)',
   'TIFF64' => 'Tagged Image File Format (64-bit) (LIBTIFF, Version 4.0.1)',
     'TILE' => 'Tile image with a texture',
      'TIM' => 'PSX TIM',
      'TTC' => 'TrueType font collection (Freetype 2.1.5)',
      'TTF' => 'TrueType font (Freetype 2.1.5)',
      #'TXT' => 'Text',
     'UYVY' => '16bit/pixel interleaved YUV',
      'VDA' => 'Truevision Targa image',
    'VICAR' => 'VICAR rasterfile format',
      'VID' => 'Visual Image Directory',
     'VIFF' => 'Khoros Visualization image',
      'VST' => 'Truevision Targa image',
     'WBMP' => 'Wireless Bitmap (level 0) image',
      'WMV' => 'Windows Media Video',
      'WPG' => 'Word Perfect Graphics',
        'X' => 'X Image',
      'X3F' => 'Sigma Camera RAW Picture File',
      'XBM' => 'X Windows system bitmap (black and white)',
       'XC' => 'Constant image uniform color',
      'XCF' => 'GIMP image',
      'XPM' => 'X Windows system pixmap (color)',
      'XPS' => 'Microsoft XML Paper Specification',
       'XV' => 'Khoros Visualization image',
      'XWD' => 'X Windows system window dump (color)',
        'Y' => 'Raw yellow samples',
    'YCBCR' => 'Raw Y, Cb, and Cr samples',
   'YCBCRA' => 'Raw Y, Cb, Cr, and alpha samples',
      'YUV' => 'CCIR 601 4:1:1 or 4:2:2'
);


sub is_IM_format($) {
    my $e = shift;
    $e =~ s/^\.//;
    $e = uc($e);
    return 1 if (defined $IMReadFormats{$e});
    return 0;
}

sub is_image_ext($) {
    my $e = shift;
    #return is_IM_format($e) if ($allimagemagik);
    return 1 if ($e =~ /^\.JPG$/i);
    return 2 if ($e =~ /^\.JPEG$/i);
    return 3 if ($e =~ /^\.GIF$/i);
    return 4 if ($e =~ /^\.PNG$/i);
    return 5 if ($e =~ /^\.BMP$/i);
    return 6 if ($e =~ /^\.TIFF$/i);
    return 7 if ($e =~ /^\.PPM$/i);
    return 0;
}

sub is_image_file($) {
    my $fil = shift;
    my ($n,$d,$e) = fileparse($fil, qr/\.[^.]*/);
    return 1 if (is_image_ext($e));
    return 0;
}


sub process_a_dir($$) {
    my ($dir,$ra) = @_;
    my ($fil,$ff);
    if (opendir(DIR,$dir)) {
        my @files = readdir(DIR);
        closedir(DIR);
        ut_fix_directory(\$dir);
        foreach $fil (@files) {
            next if ($fil eq '.');
            next if ($fil eq '..');
            $ff = $dir.$fil;
            if (-f $ff) {
                if (is_image_file($ff)) {
                    push(@in_files,$ff);
                }
            } elsif (-d $ff) {
                push(@{$ra},$ff);
            } else {
                prtw("WARNING: What is THIS??? [$ff] Skipping...\n");
            }
        }
    } else {
        prtw("WARNING: Failed to open directory [$dir]\n");
    }
}

sub process_in_dirs($) {
    my $ra = shift;
    my $max = scalar @{$ra};
    prt("Got $max directories to process...\n");
    my ($dir);
    my @dirs = ();
    foreach $dir (@{$ra}) {
        process_a_dir($dir,\@dirs);
    }
    if ($recursive) {
        foreach $dir (@dirs) {
            process_a_dir($dir,@dirs);
        }
    }
}

sub process_in_files($) {
    my $ra = shift;
    my $max = scalar @{$ra};
    prt("Got $max images files to process... using target width $targ_wid\n");
    my ($fil,$is,$width,$height,$ratio,$out,$cmd,$xwid,$yhgt,$msg,$tcnt,$dcnt);
    my $od = $out_dir;
    ut_fix_directory(\$od);
    $tcnt = 0;
    $dcnt = 0;
    foreach $fil (@{$ra}) {
        $tcnt++;
        $is = im_get_image_size($fil);
        $width = im_get_image_width($is);
        $height = im_get_image_height($is);
        $ratio = $width / $height;
        #prt("$fil w=$width h=$height asp.ratio=$ratio\n");
        my ($n,$d,$e) = fileparse($fil, qr/\.[^.]*/);
        $out = $od.$n.$out_ext; # def = .jpg
        if ((-f $out) && !$overwrite) {
            $msg = "$fil w=$width h=$height asp.ratio=$ratio\n";
            $msg .= "$out ALREADY exists and overwrite is OFF\n";
            prt($msg) if (VERB9());
            next;
        }
        if ($ratio > 1) {   # width > height
            $xwid = $targ_wid; # set target width
            $yhgt = int($targ_wid / $ratio); # and calculate NEW height
	    } else {
			$xwid = int($targ_wid * $ratio); # calculate width
			$yhgt = $targ_wid; # and set target width as height
        }

        # TODO: what about if the image is SMALLER than the target size?

        $cmd = "convert $fil -resize ".$xwid."x"."$yhgt $out";
        system($cmd);
        if (-f $out) {
            prt("Done: '$cmd'\n");
            $dcnt++;
        } else {
            prtw("WARNING: Appears '$cmd' FAILED!\n");
        }

    }
    prt("Written $dcnt converted images, of a total of $tcnt\n");
    if ($dcnt < $tcnt) {
        if ($overwrite) {
            prt("Note some image generations FAILED!\n");
        } else {
            prt("Note 'overwrite' is OFF. Use -w to re-write all thumbnails.\n");
        }
    }
}

#########################################
### MAIN ###
parse_args(@ARGV);
process_in_dirs(\@in_dirs);
process_in_files(\@in_files);
pgm_exit(0,"");
########################################

sub need_arg {
    my ($arg,@av) = @_;
    pgm_exit(1,"ERROR: [$arg] must have a following argument!\n") if (!@av);
}

sub add_2_inputs($) {
    my $arg = shift;
    if (-f $arg) {
        push(@in_files,$arg);
        prt("Added input file [$in_file]\n") if (VERB1());
    } elsif (-d $arg) {
        push(@in_dirs,$arg);
        prt("Added input directoury [$in_file]\n") if (VERB1());
    } else {
        pgm_exit(1,"ERROR: Raw input [$arg] is NOT a file or directory\n")
    }
}

sub parse_args {
    my (@av) = @_;
    my ($arg,$sarg);
    while (@av) {
        $arg = $av[0];
        if ($arg =~ /^-/) {
            $sarg = substr($arg,1);
            $sarg = substr($sarg,1) while ($sarg =~ /^-/);
            if (($sarg =~ /^h/i)||($sarg eq '?')) {
                give_help();
                pgm_exit(0,"Help exit(0)");
            } elsif ($sarg =~ /^e/) {
                need_arg(@av);
                shift @av;
                $sarg = $av[0];
                if ( !($sarg =~ /\./) ) {
                    $sarg = '.'.$sarg;
                }
                $out_ext = $sarg;
                prt("Set output extension to [$out_ext].\n") if (VERB1());
            } elsif ($sarg =~ /^v/) {
                if ($sarg =~ /^v.*(\d+)$/) {
                    $verbosity = $1;
                } else {
                    while ($sarg =~ /^v/) {
                        $verbosity++;
                        $sarg = substr($sarg,1);
                    }
                }
                prt("Verbosity = $verbosity\n") if (VERB1());
            } elsif ($sarg =~ /^w/) {
                $overwrite = 1;
                prt("Set overwrite ON\n") if (VERB1());
            } elsif ($sarg =~ /^l/) {
                if ($sarg =~ /^ll/) {
                    $load_log = 2;
                } else {
                    $load_log = 1;
                }
                prt("Set to load log at end. ($load_log)\n") if (VERB1());
            } elsif ($sarg =~ /^o/) {
                need_arg(@av);
                shift @av;
                $sarg = $av[0];
                $out_dir = $sarg;
                prt("Set output directory to [$out_dir].\n") if (VERB1());
                if (! -d $out_dir) {
                    pgm_exit(1,"ERROR: Output directory $out_dir DOES NOT EXIST!\nCreate it first, then re-run\n");
                }
            } else {
                pgm_exit(1,"ERROR: Invalid argument [$arg]! Try -?\n");
            }
        } else {
            $in_file = $arg;
            add_2_inputs($arg);
        }
        shift @av;
    }

    if ($debug_on) {
        prtw("WARNING: DEBUG is ON!\n");
        if (length($in_file) ==  0) {
            $in_file = $def_file;
            add_2_inputs($def_file);
            prt("Set DEFAULT input to [$in_file]\n");
        }
    }
    if (length($in_file) ==  0) {
        pgm_exit(1,"ERROR: No input files or directories found in command!\n");
    }
    if ((! -f $in_file)&&(! -d $in_file)) {
        pgm_exit(1,"ERROR: Unable to find input item [$in_file]! Check name, location...\n");
    }
}

sub give_help {
    prt("$pgmname: version $VERS\n");
    prt("Usage: $pgmname [options] in-file\n");
    prt("Options:\n");
    prt(" --help    (-h or -?) = This help, and exit 0.\n");
    prt(" --verb[n]       (-v) = Bump [or set] verbosity. def=$verbosity\n");
    prt(" --load          (-l) = Load LOG at end. ($outfile)\n");
    prt(" --out <dir>     (-o) = Set output directory for the thumbs. (def=$out_dir)\n");
    prt(" --writeover     (-w) = Overwrite any existing files in output folder. (def=$overwrite)\n");
    # TODO: prt(" --xclude <file> (-x) = Exclude this file from processing.\n");
    # TODO: prt(" If \@file given, then exclude list of file is list file.\n");
    prt(" --extent <.ext> (-e) = Set the default out extension. (def=$out_ext)\n");
}

# eof - genthumbs.pl
