#!/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 = ; 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 (-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 (-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