1#!/usr/local/bin/perl -w
2#
3# Next Generation Pretty
4#
5# Copyright (C) 2006-2012 Kristian Gunstone
6#
7# A simple thumbnail and page generator which
8# uses common software to do the job.
9
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23#
24# REQUIRED dependencies:
25# - Anything but Windows (fork won't work)
26# - kernel threading support
27# - perl > 5.8.x (untested on anything lower)
28# - fork perl module (use 'force install forks' in cpan)
29# - Image::ExifTool perl module (use 'force install Image::ExifTool' in cpan)
30# - Image::Magic (use 'force install Image::Magick' in cpan)
31# - Getopt::Long
32#
33# SUGGESTED dependencies:
34# mplayer (with as many codecs as possible)
35# (use 1.0pre7try2, 1.0pre9 has broken gif89a output)
36# display (comes with imagemagick)
37# jhead   (for pre-processing)
38#
39# Pre-git Changelog:
40# n3.0/0.9
41# 	remove duplicate slashed in path
42# 	thread bug fix when calling jhead
43# 	template parser minor bugfixes
44# 	Fixed bug in recursive move
45# 	Fixed problem in listcalc
46# 	Incredibly, more bugfixes
47# 	MORE bugfixes
48# 	More bugfixes
49# 	Added --skip-pages
50# 	Replaced old thumb link code with code similar to prevs
51# 	Added side padding for thumb/preview link lists
52# 	Additional argument checks and minor bugfixes
53# n3.0/0.8
54# 	Modified progress output a little
55# 	Added config override support
56# 	Added overwrite support
57# 	Added template selection argument
58# 	Comment code bugfixes and cleanups
59# 	Switched to bsd_glob
60# 	Fixed bug in directory glob code
61# 	New tag NPRETTY_TITLE_NOEXIST and code
62# 	New tag NPRETTY_TITLE_EXIST and code
63# 	Fixed some problems with tag reading without images present
64# 	Fixed yet an other comment bug
65# 	Strip odd chars from filenames (see stripChars)
66# 	Fixed comment file location problem
67# 	Added -title argument
68# 	Fixed bug in thumbs/page calculation from a4
69# 	Added --destination argument
70# 	Added (almost) all new arguments to argument parser
71# 	Argument parser rewrite
72# 	Fixed bug in comment handler (would apply comment even if it was empty)
73# 	Fixed a bug in the delimiter + padding code (thanks uchman)
74# n3.0/0.7
75# 	Skip copying images if --skip-images is set
76# 	Modified preview list index tag code (Ree filed a complaint)
77# 	Modified comment tags and code as requested (thanks Ree)
78# 	Added delimiter padding tag and code (thanks Ree)
79# 	Fixed a problem with sloppy page creation on very small archives
80# 	Added preview list index tag and code (thanks Ree)
81# 	Got rid of zero-indexing on all output (pagenum, thumbnum, etc)
82# 	Added filesize tag
83# 	Resampling algorithm selection
84# 	Quality setting on JPEG/MIFF/PNG
85# 	Use EXIF-w/h if image is jpeg (speeds up thumb/preview creation)
86# 	Optimised use of magick object in thumb dimension read
87# 	Added a 'no video preview available' image and code
88# 	Using 'cp' for copy instead of builtin routines
89# 	Now using Image::Magick directly (not the convert binary)
90# 	width/height check fix
91# n3.0/0.6
92# 	Rewrote template parser
93# 	Added video support for previewing
94# 	--skip-images support (includes video)
95# n3.0/0.5
96# 	Change dimensions of preview image if larger than original
97# 	Preliminary video conversion support
98# 	preview aspect change (no arghandler yet)
99# 	Thumbnail delimiter code
100# 	Preprocessor (jhead) code
101# 	Template code
102# 	Sloppy page info correction (output was broken)
103# 	Preliminary thumb+preview generation
104# 	Proper output filename generation
105# n3.0/0.4
106# 	Page generation modes
107# 	Sloppy pages
108# n3.0/0.3
109# 	File permission read+write checks
110# 	Basic comment store/load
111# 	Configuration file support
112# 	FIX: recursive mode broke if dir did not contain subdirs
113# n3.0/0.2
114# 	Threading job support
115# 	Save/Load comment support
116# 	Preview image support
117# n3.0/0.1
118# 	Extensive permission checks before job starts
119# 	No session file
120# 	Recursive directory parsing
121# 	Argument parser
122# 	Simultaneous job handling
123# 	Comment support
124#
125#Notes:
126#files shouldn't have the same name as a directory
127#
128
129use strict;
130use Image::Magick;
131use Image::ExifTool 'ImageInfo';
132use Getopt::Long;
133use File::Find;
134use forks;
135use Fcntl ':mode';
136$SIG{CHLD} = 'DEFAULT'; # Needs to be set to IGNORE during forks
137
138### Arguments
139
140## Defaults
141my $templatefile		=	"npretty_template.tpl";
142my $vid_converter_path		=	"";
143my $img_path			=	".";
144my $dest			=	".";
145my $img_dest			=	"img";
146my $thumb_dest			=	"thumbs";
147my $prevpage_dest		=	"previews"; #NEW 0.5
148my $preview_dest		=	"$prevpage_dest/preview";
149my $thumbpage_prefix		=	"index"; #Will have number and .html appended
150my $thumb_prefix		=	"_thumb_"; # Will be prepended with index id
151my $preview_prefix		=	"_preview_"; # Will be prepended with index id
152
153my $thumb_dimensions		=	"100x100";
154my $preview_dimensions		=	"800x600";
155my $video_preview_dimensions	=	"320x240";
156my $image_quality		=	"85"; # JPEG/MIFF/PNG only
157my $resize_algorithm		=	"Box";
158					# Point, Box, Triangle,Hermite, Hanning,
159					# Hamming, Blackman, Gaussian, Quadratic,
160					# Cubic, Catrom, Mitchell, Lanczos,Bessel, Sinc
161my $sidepad			=	".. ";
162my $thumb_keep_aspect		=	1;
163my $preview_keep_aspect		=	1;
164my $recursive			=	0;
165my $maxdepth			=	-1;
166my $comments			=	0;
167my $overwrite			=	0;
168my $overwrite_images		=	0;
169my $overwrite_thumbs		=	0;
170my $overwrite_previews		=	0;
171my $jobs			=	2;
172my $max_thumbs_per_page		=	20;
173my $max_pages			=	0;
174my $thumbnail_links		=	9;
175my $preview_links		=	9;
176my $pagebuild_type		=	0; # 0 = number of thumbs, 1 = number of pages
177my $sloppy_mode			=	0;
178my $sloppy_files		=	3;
179my $generation_mode		=	0; # 0 = all, 1 = images only, 2 = pages only
180my $show_preview		=	0; # 0 = yes, 1 = no
181                                         # ^ for viewing images before commenting them
182my $use_vidconverter		=	0;
183my $use_preprocessor		=	0;
184my $pre_remove_cthumbs		=	0; # Remove thumbnails in images
185my $pre_auto_rotate		=	0; # Autorotate images
186
187my $delete_originals		=	0;
188
189my $commentfile			=	".npretty_comments";
190my $no_video_image		=	"novideo.gif";
191
192my $image_view			=	"display -resize 640x480+0+0 ";
193my $video_view			=	"mplayer ";
194my @configuration		=	(".npretty_config", "/home/$ENV{'USER'}/.npretty_config", "/usr/local/etc/npretty/npretty.conf");
195
196## General
197my $VERSION = "n0.9.3-1";
198my $vid_converter = "mplayer";
199my $pre_processor = "jhead";
200
201## Other
202my $title = "";
203my $config = "";
204
205my %filetypes = (
206    "jpg" => "img",
207    "jpeg"=> "img",
208    "png" => "img",
209    "bmp" => "img",
210    "gif" => "img",
211    "tif" => "img",
212    "mpg" => "vid",
213    "mpeg"=> "vid",
214    "avi" => "vid");
215
216my %overwritemodes = ("y" => 0, "n" => 1, "A" => 2, "N" => 3); # yes no All None
217
218my @arglist =(
219    "video-preview-dimensions=  video preview dimensions XxY",
220    "image-quality=             image quality in percent (JPEG/MIFF/PNG only)",
221    "resize-algorithm=          resizing algorithm, set to 'help' for list",
222    "keep-thumb-ratio           keep aspect ratio of thumbnails",
223    "keep-preview-ratio         keep aspect ratio of previews",
224    "use-video                  use video converter",
225    "use-preprocessor           use pre-processor",
226    "pre-remove-cthumbs         remove cam thumbs with pre-processor",
227    "pre-auto-rotate            auto-rotate images with pre-processor",
228    "jobs=                      jobs to run simultaneously",
229    "mplayer-path=              path to 'mplayer' binary",
230    "image-path=                path to images",
231    "image-dest=                destination path for images",
232    "thumb-dest=                destination path for thumbs",
233    "destination=               destination prefix for archive",
234    "preview-dest=              destination path for previews",
235    "thumb-dimensions=          thumbnail dimensions, XxY",
236    "preview-dimensions=        preview dimensions XxY",
237    "thumbs-per-page=           max thumbs per page",
238    "pages=                     max number of pages",
239    "sloppy-files=              be sloppy on page calculation",
240    "title=                     text to prepend to title tag",
241    "config=                    use config instead of config hierarchy",
242    "sidepad=                   string to pad thumb/preview lists with",
243    "delete-originals           delete original images",
244    "overwrite-images           overwrite images",
245    "overwrite-previews         overwrite previews",
246    "overwrite-thumbs           overwrite thumbnails",
247    "overwrite-all              overwrite everything",
248    "overwrite-nothing          overwrite nothing",
249    "template                   template file to use",
250    "skip-images                skip image creation",
251    "skip-pages                 skip page creation",
252    "recursive                  recursively look for images",
253    "maxdepth                   traversal depth if recursive",
254    "comments                   ask for comment on each image",
255    "preview                    show preview before comment",
256    "sloppy                     enable sloppiness",
257    "help                       this text",
258    "version                    version");
259
260# Do not fuck with these
261my $i; #Cough
262my @progmap		= ();
263my @preview_dimension	= (); # index-specific dimensions
264my $useoldcomments	= 0;
265
266
267my @threadlist		= ();
268my $finishedthreads	= 0;
269
270my @badfiles		= ();
271my @badfileperms	= ();
272
273my @CARGV		= ();
274my @page_index		= ();
275my $internal_pages	= (); # Need to be fixed?
276my $internal_thumbs	= ();
277my $NTAG='<!-- Generated by npretty v'.$VERSION.' by gammy. https://github.com/gammy/npretty -->'."\n";
278
279my @filelist		= (); # Original file paths
280my @imglist		= (); # New path and new names
281my @thumblist		= (); # Thumbnail filenames
282my @prevpagelist	= (); # Preview pages
283my @prevlist		= (); # Preview filenames
284my %work;		      # <file> Contains bools, 1 to work, 0 to not.
285my @tmpargs		= ();
286my @ov_list		= ();
287my $mode;
288
289### Subroutines
290sub usage{
291    my $padding;
292    print "Usage: npretty <arguments>\n";
293    print "Example: npretty --image-path=.\n\n";
294    print "Options:\n";
295    foreach (@arglist){
296	print "\t--$_\n";
297    }
298    print "
299This is free software; see the source for copying conditions. There is NO
300warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n";
301
302    exit 0;
303}
304
305sub init_thread{
306    $threadlist[threads->list()] = threads->create("start_thread", @_);
307}
308
309sub start_thread{
310    my ($message, $in, $arguments, $out) = @_;
311    print "$message";
312    my $image = Image::Magick->new;
313    my $ret = $image->Read($in);
314    warn "$ret" if "$ret";
315    #$ret = $image->Thumbnail(geometry=>"$arguments");
316    $ret = $image->Resize(geometry=>"$arguments", filter=>"$resize_algorithm");
317    warn "$ret" if "$ret";
318
319    ## TEST
320    #$ret = $image->Annotate(font=>'kai.ttf', gravity=>"NorthWest", pointsize=>10, fill=>'white', text=>"zomg (C)");
321    #warn "$ret" if "$ret";
322
323    $ret = $image->Set(quality=>$image_quality);
324    warn "$ret" if "$ret";
325    $ret = $image->Write($out);
326    warn "$ret" if "$ret";
327    undef $image;
328    threads->self->detach();
329    return;
330}
331
332sub init_sys_thread{
333    $threadlist[threads->list()] = threads->create(
334	"start_sys_thread",
335	"$_[0]",
336	"$_[1]"
337    );
338}
339
340sub start_sys_thread{
341    print "$_[1]";
342    system("$_[0]");
343    threads->self->detach();
344    return;
345}
346
347sub filesize{
348    my $file = $_[0];
349    open(FILE, "<$_[0]") or die "ERROR: Failed to open \"$_[0]\" for read (filesize check)";
350    seek(FILE, 0, 2);
351    my $filesize = tell(FILE);
352    close(FILE);
353    return $filesize;
354}
355
356sub load{
357    my ($buf, $filesize, $bytesread) = ("", 0, 0);
358    open(LOADDESC, "<$_[0]") or die "ERROR: Failed to open \"$_[0]\" for read";
359    seek(LOADDESC, 0, 2);
360    $filesize = tell(LOADDESC);
361    seek(LOADDESC, 0, 0);
362    $bytesread = read(LOADDESC, $buf, $filesize, 0);
363    close(LOADDESC);
364    return "$buf";
365}
366
367sub save{
368    open(FILE, ">$_[0]") or die "ERROR: Failed to open \"$_[0]\" for write";
369    print FILE $_[1];
370    close(FILE);
371}
372
373sub replaceExifTags{
374    my ($buf, $info) = @_;
375
376    if($buf=~m/\[NPRETTY_EXIF_.+?\]/){
377	# Collect
378	my @exiflist;
379	while($buf=~m/\[NPRETTY_EXIF_(.+?)\]/g){
380	    push(@exiflist, $1);
381	}
382	foreach my $exif (@exiflist){
383	    my $found = 0;
384	    my $replacement = "Missing";
385	    foreach my $cmp (sort keys %$info){
386		if("$cmp" eq "$exif"){
387		    $found = 1;
388		    last;
389		}
390	    }
391	    $replacement = $$info{$exif} if $found;
392	    $buf =~s/\[NPRETTY_EXIF_$exif\]/$replacement/g;
393	}
394    }else{
395	#print "DEBUG: No EXIF tags in block\n";
396    }
397
398    return $buf;
399
400}
401
402sub replaceTags{
403    my ($buf, %tag) = @_;
404    foreach my $r (sort keys %tag){
405	my $repl = $tag{"$r"};
406	$buf =~s/\Q$r\E/$repl/g;
407    }
408    return $buf;
409}
410
411sub setPreviewDimensions{
412    my ($info, $offset, $image) = @_;
413
414    if("$progmap[$offset]" eq "vid"){
415	$preview_dimension[$offset] = $video_preview_dimensions;
416    }else{
417	my ($imgwidth, $imgheight) = (0, 0);
418	if(lc(substr($image, -4)) eq ".jpg" || lc(substr($image, -5)) eq ".jpeg"){
419	    ($imgwidth, $imgheight) = ($$info{'ImageWidth'}, $$info{'ImageHeight'});
420	}else{
421	    my $magick = Image::Magick->new;
422	    $magick->Read("$image");
423	    ($imgwidth, $imgheight) = $magick->Get('width', 'height');
424	    undef $magick;
425	}
426
427	my ($curwidth, $curheight) = (split("x", $preview_dimensions));
428	#my ($imgwidth, $imgheight) = ($$info{"ImageWidth"}, $$info{"ImageHeight"});
429	if($imgwidth < $curwidth && $imgheight < $curheight){
430	    $preview_dimension[$offset] = $imgwidth."x".$imgheight;
431	}else{
432	    $preview_dimension[$offset] = $preview_dimensions;
433	}
434    }
435}
436
437sub getPageName{
438    my $thumbpage = $thumbpage_prefix;
439    $thumbpage .= sprintf("_%03d", $_[0]) if $_[0] != 1;
440    $thumbpage .= ".html";
441    return $thumbpage;
442}
443
444sub stripChars{
445    my $in = $_[0];
446    my $out;
447
448    # Translation map for swedish iso-8859-1 letters
449    $in =~tr/���A��/aaoAAO/;
450
451    # Replace everything else except .
452    for(my $i = 0;$i < length($in); $i++){
453	my $c = substr($in, $i, 1);
454	if("$c" ne "." && $c=~/[[^\W]|[^\D]]/){
455	    $c = "_";
456	}
457	$out .= $c;
458    }
459    $out =~s/_+/_/g;
460    return $out;
461}
462
463sub askOverwrite{
464    print "Overwrite? [y/n/A/N]";
465    my $checkcomment = "";
466    while("$checkcomment" ne 'y' &&
467	"$checkcomment" ne 'n' &&
468	"$checkcomment" ne 'A' &&
469	"$checkcomment" ne 'N'){
470	$checkcomment = <STDIN>;
471	chomp($checkcomment);
472    }
473    return $overwritemodes{$checkcomment};
474}
475
476sub checkOverwrite{
477    my $path = shift;
478    foreach(@_){
479	my $filename = "$path$_";
480	if(-f "$filename"){;
481	    print "\"$filename\" already exists. ";
482	    my $ret = askOverwrite();
483	    if($ret == 0){ # yes
484		push(@ov_list, $_); # With no path
485	    }elsif($ret == 1){ #no
486	    }elsif($ret == 2){ #All
487		foreach my $img (@_){
488		    my $ignore = 0;
489		    foreach my $ignoreimg (@ov_list){
490			if("$img" eq "$ignoreimg"){
491			    $ignore = 1;
492			    last;
493			}
494		    }
495		    $work{$img} = 1 unless $ignore;
496		}
497		last;
498	    }elsif($ret == 3){ #None
499		foreach my $img (@_){
500		    my $ignore = 0;
501		    foreach my $ignoreimg (@ov_list){
502			if("$img" eq "$ignoreimg"){
503			    $ignore = 1;
504			    last;
505			}
506		    }
507		    if(-f "$path$img" && $ignore == 0){
508			$work{$img} = 0;
509		    }else{
510			$work{$img} = 1;
511		    }
512		}
513		last;
514	    }
515	}
516    }
517
518    foreach(@ov_list){
519	$work{$_} = $overwrite;
520    }
521}
522
523### Internal variable checks and setup
524
525### Main
526print "npretty v$VERSION by gamkiller.\n\n";
527
528usage() if @ARGV == 0;
529
530## Extended argument parser (NEW)
531
532foreach my $arg (@ARGV){
533    if("$arg" =~m/--config=(.+)/s){
534	$config = $1;
535	@configuration = ("$1");
536    }else{
537	push(@tmpargs, $arg);
538    }
539}
540@ARGV = @tmpargs;
541@tmpargs = ();
542
543# Check for configfiles and prepend to arglist
544foreach (@configuration){
545    if(-f "$_"){
546	$mode = (stat("$_"))[2];
547	die "Configuration file \"$_\" is unreadble" if ($mode & S_IRUSR) == 0;
548	open(FILE, "<$_");
549	@tmpargs = <FILE>;
550	close(FILE);
551	chomp(@tmpargs);
552	s/\s+$// foreach @tmpargs;
553	@ARGV = (@tmpargs, @ARGV);
554	print "Prepended \"$_\" to argument list.\n";
555    }else{
556	die "Couldn't find config file \"$_\"\n" if "$config" ne "";
557    }
558}
559
560# Note that --config is not in @ARGV since we pre-parsed it
561my $result = GetOptions(
562    "destination=s"	=> \$dest,
563    "jobs=i"		=> \$jobs,
564    "use-video"		=> \$use_vidconverter,
565    "use-preprocessor"	=> \$use_preprocessor,
566    "pre-remove-cthumbs"=> \$pre_remove_cthumbs,
567    "pre-auto-rotate"	=> \$pre_auto_rotate,
568    "image-quality=i"	=> \$image_quality,
569    "resize-algorithm=s"=> \$resize_algorithm,
570    "mplayer-path=s"	=> \$vid_converter_path,
571    "image-path=s"	=> \$img_path,
572    "image-dest=s"	=> \$img_dest,
573    "thumb-dest=s"	=> \$thumb_dest,
574    "preview-dest=s"	=> \$preview_dest,
575    "video-preview-dimensions=s"=> \$video_preview_dimensions,
576    "thumb-dimensions=s"=> \$thumb_dimensions,
577    "preview-dimensions=s"=> \$preview_dimensions,
578    "thumbs-per-page=i"	=> sub{$pagebuild_type = 0; $max_thumbs_per_page=$_[1];},
579    "pages=i"		=> sub{$pagebuild_type = 1; $max_pages=$_[1];},
580    "sloppy-files=i"	=> \$sloppy_files,
581    "title=s"		=> \$title,
582    "template=s"	=> \$templatefile,
583    "sidepad=s"		=> \$sidepad,
584    "delete-originals"	=> \$delete_originals,
585    "overwrite-images"	=> \$overwrite_images,
586    "overwrite-previews"=> \$overwrite_previews,
587    "overwrite-thumbs"	=> \$overwrite_thumbs,
588    "overwrite-all"	=> sub{$overwrite = 1;},
589    "overwrite-nothing"	=> sub{$overwrite = 2;},
590    "skip-images"	=> sub{$generation_mode = 2;},
591    "skip-pages"	=> sub{$generation_mode = 1;},
592    "recursive"		=> \$recursive,
593    "maxdepth=i"	=> \$maxdepth,
594    "keep-thumb-ratio"	=> \$thumb_keep_aspect,
595    "keep-preview-ratio"=> \$preview_keep_aspect,
596    "comments"		=> \$comments,
597    "preview"		=> \$show_preview,
598    "sloppy"		=> \$sloppy_mode,
599    "help"		=> sub{usage(); exit 0;},
600    "version"		=> sub{exit 0;}
601);
602
603## Set commentfile
604$commentfile = "$dest/$commentfile";
605
606## Check that args are within range
607die "Maximum number of jobs is 8 for your own safety" if $jobs < 1 or $jobs > 8;
608die "Incorrect thumbnail dimension format" unless $thumb_dimensions =~m/^[0-9]+?x[0-9]+?$/;
609die "Incorrect preview dimension format" unless $preview_dimensions =~m/^[0-9]+?x[0-9]+?$/;
610die "Incorrect video preview dimension format" unless $video_preview_dimensions =~m/^[0-9]+?x[0-9]+?$/;
611die "Incorrect image quality range" if $image_quality < 0||$image_quality > 100;
612
613if($recursive == 1) {
614	# No traversal limit unless the user explicitly set one using --maxdepth
615	$maxdepth = 0 if $maxdepth == -1;
616} else {
617	$maxdepth = 1;
618}
619
620my @resize_algorithms = qw(Point Box Triangle Hermite Hanning Hamming Blackman Gaussian Quadratic Cubic Catrom Mitchell Lanczos Bessel Sinc);
621if(lc($resize_algorithm) eq "help"){
622    print "\nAvailable resizing algorithms: @resize_algorithms\n";
623    exit 0;
624}
625my $exists = 0;
626foreach (@resize_algorithms){
627    $exists = 1 if "$resize_algorithm" eq "$_";
628}
629die "Incorrect resize algorithm. Algorithms: @resize_algorithms" unless $exists;
630undef $exists;
631undef @resize_algorithms;
632
633# Expand paths
634$img_path=~s/~/$ENV{'HOME'}/g;
635$dest=~s/~/$ENV{'HOME'}/g;
636$commentfile=~s/~/$ENV{'HOME'}/g;
637
638## Check existence
639die "\"$img_path\" is not a directory.\n" unless -d "$img_path";
640$useoldcomments = 1 if -f "$commentfile";
641
642my $found = 0;
643foreach("$templatefile", "$ENV{'HOME'}/$templatefile", "/usr/local/etc/npretty/$templatefile"){
644    if(-f "$_"){
645	$templatefile = $_;
646	$found = 1;
647	last;
648    }
649}
650die "Couldn't find template \"$templatefile\"" if $found == 0;
651print "Using \"$templatefile\" as template.\n\n";
652
653$found = 0;
654foreach("$no_video_image", "$ENV{'HOME'}/$no_video_image", "/usr/local/etc/npretty/$no_video_image"){
655    if(-f "$_"){
656	$no_video_image = $_;
657	$found = 1;
658	last;
659    }
660}
661die "Couldn't find novideo image \"$no_video_image\"" if $found == 0;
662
663## Check external programs
664if($use_vidconverter){
665    if(system("$vid_converter > /dev/null 2>&1") != 0){
666	print "WARN: Failed to testrun video converter \"$vid_converter\", disabling video conversion.\n";
667	$use_vidconverter = 0;
668    }
669}
670
671if($use_preprocessor){
672    if(system("$pre_processor -V > /dev/null 2>&1") != 0){
673	print "WARN: Failed to testrun pre-processor \"$pre_processor\", disabling pre-processing.\n";
674	$use_preprocessor = 0;
675    }
676}
677
678## Check permissions
679
680# We have to create destination here to satisfy tests
681unless(-d "$dest"){
682    print "Creating \"$dest\"...";
683    die "Could not create \"$dest\"" if mkdir("$dest") != 1;
684    print "ok.\n";
685}elsif(-e "$dest"){
686    print "NOTE: \"$dest\" already exists.\n";
687}
688# Prepend destination to destination variables
689my ($abs_img_dest, $abs_prevpage_dest, $abs_preview_dest, $abs_thumb_dest) =
690($img_dest, $prevpage_dest, $preview_dest, $thumb_dest);
691
692$thumb_dest = "$dest/$thumb_dest";
693$img_dest = "$dest/$img_dest";
694$prevpage_dest = "$dest/$prevpage_dest";
695$preview_dest = "$dest/$preview_dest";
696
697# Remove duplicate slashes in paths
698$thumb_dest =~s/\/+/\//g;
699$img_dest =~s/\/+/\//g;
700$prevpage_dest =~s/\/+/\//g;
701$preview_dest =~s/\/+/\//g;
702
703# image, thumb, preview and preview page destination check
704foreach ("$thumb_dest", "$img_dest", "$prevpage_dest", "$preview_dest", "$img_path"){
705    unless(-d "$_"){
706	die "\"$_\" exists but is not a directory" if(-e "$_");
707    }else{
708	$mode = (stat("$_"))[2];
709	die "\"$_\" is not readable" if ($mode & S_IRUSR) == 0;
710	unless("$_" eq "$img_path"){
711	    die "\"$_\" is not writeable" if ($mode & S_IWUSR) == 0;
712	}
713    }
714}
715#FIXME: More checks here?
716#NOTE: File read+write checks are done after file list collection
717
718## Collection with recursive directory support (NEW)
719$| = 1; # Set canonical output
720#Get file list
721print "Compiling file list...";
722$img_path = substr($img_path, 0, -1) if substr($img_path, (length($img_path) - 1), 1) eq "/";
723File::Find::find({wanted => sub {
724			my $depth = $File::Find::name =~ tr#/##; # Count slashes
725			return if $maxdepth > 0 && $depth > $maxdepth;
726			if(-f) {
727				my $name = $File::Find::name;
728				for my $type (keys %filetypes) {
729					push @filelist, $name if
730						$name =~ m/\.\Q$type\E$/i;
731				}
732
733			}
734		},
735	}, $img_path);
736print "ok.\n";
737print @filelist . " files.\n";
738
739#Check permissions on newly added files
740print "\n";
741foreach (@filelist){
742    $mode = (stat("$_"))[2];
743    if( (($mode & S_IRUSR) == 0) || (($mode & S_IWUSR) == 0)){
744	push(@badfiles, "$_");
745	push(@badfileperms, $mode);
746    }
747}
748
749if(@badfiles > 0){
750    print @badfiles . " did not have read+write permissions.\n";
751    print "Correct these files:\n";
752    print "   #  mode\tfile\n";
753    for(my $i = 0; $i < @badfiles; $i++){
754	printf("%4d: %04o\t%s\n", $i, ($badfileperms[$i] & 07777), $badfiles[$i]);
755    }
756    print "Cannot continue. Exit.\n";
757    exit 1;
758}
759
760die "No files means nothing to do" if @filelist == 0;
761
762## Directory creation (except destination which was previously created)
763foreach ("$thumb_dest", "$prevpage_dest", "$preview_dest", "$img_dest"){
764    unless(-d "$_"){
765	print "Creating \"$_\"...";
766	die "Could not create \"$_\"" if mkdir("$_") != 1;
767	print "ok.\n";
768    }elsif(-e "$_"){
769	print "NOTE: \"$_\" already exists.\n";
770    }
771}
772
773## Generate new filenames for images, thumbnails, previews and preview pages
774#  and map filetypes to decoders
775for(my $i = 0; $i < @filelist; $i++){
776    my $s = rindex($filelist[$i], "/") + 1;
777    my ($directory, $filename) = (substr($filelist[$i], 0, $s), substr($filelist[$i], $s));
778    my $prefix = sprintf("%03d", $i + 1);
779
780    $filename = stripChars($filename);
781    push(@progmap, $filetypes{lc(substr($filename, (rindex($filename, ".") + 1)))});
782
783    push(@imglist,	$prefix."_".$filename);
784
785    substr($filename, rindex($filename, "."), length($filename), ".gif") if "$progmap[$i]" eq "vid";
786    push(@thumblist,	"$prefix$thumb_prefix$filename");
787    push(@prevlist,	"$prefix$preview_prefix$filename");
788    push(@prevpagelist,	"$prefix$preview_prefix" . substr($filename, 0, rindex($filename, ".")) . ".html");
789
790}
791
792## Check overwrite stuff, ugly :(.
793foreach(@imglist, @thumblist, @prevlist){
794    if($overwrite == 2){
795	$work{$_} = 0;
796    }else{
797	$work{$_} = 1;
798    }
799}
800
801if($overwrite == 0 && $generation_mode != 2){
802    checkOverwrite("$img_dest/", @imglist) unless $overwrite_images;
803    checkOverwrite("$preview_dest/", @prevlist) unless $overwrite_previews;
804    checkOverwrite("$thumb_dest/", @thumblist) unless $overwrite_thumbs;
805
806    if($overwrite_images){
807	$work{$_} = 1 foreach (@imglist);
808    }
809    if($overwrite_previews){
810	$work{$_} = 1 foreach (@prevlist);
811    }
812    if($overwrite_thumbs){
813	$work{$_} = 1 foreach (@thumblist);
814    }
815}
816
817undef @ov_list;
818
819## Copy files to dest
820if($generation_mode != 2){
821    for(my $i = 0; $i < @filelist; $i++){
822	next unless $work{$imglist[$i]};
823	print "$filelist[$i] -> $img_dest/$imglist[$i]\n";
824	system("cp \"$filelist[$i]\" \"$img_dest/$imglist[$i]\"");
825    }
826}
827
828goto GENERATION if $generation_mode == 1;
829
830## Save/Load comments
831my %commenthash;
832
833if($comments == 0 and $useoldcomments == 1){
834    print "\nA comment file was located but comments have been disabled.\n";
835    print "Would you like to enable comments now? [y/n]";
836    my $checkcomment = "";
837    while(lc($checkcomment) ne 'y' && lc($checkcomment) ne 'n'){
838	$checkcomment = <STDIN>;
839	chomp($checkcomment);
840    }
841    $comments = 1 if "$checkcomment" eq "y";
842}
843if($comments == 1 and $useoldcomments == 1){
844    print "\nFound commentfile, loading...";
845    open(FILE, "<$commentfile");
846    foreach (<FILE>){
847	my $offset = index($_, "\t", 0);
848	if($offset == -1){
849	    print "\nWARN: A line in the commentfile is broken! Skipped.";
850	}else{
851	    $commenthash{substr($_, 0, $offset)} = substr($_, ($offset + 1));
852	    chomp($commenthash{substr($_, 0, $offset)});
853	}
854    }
855    close(FILE);
856    print "ok.\n";
857    print "Looking up files.\n";
858    my $found;
859    for(my $j = 0; $j < @filelist; $j++){
860	$found = 0;
861	foreach(keys %commenthash){
862	    if("$_" eq "$filelist[$j]"){
863		$found = 1;
864		last;
865	    }
866	}
867	if($found == 0){
868	    #FIXME: Replicated code
869	    my $previewer = $image_view;
870	    $previewer = $video_view if "$progmap[$j]" eq "vid";
871	    system("echo -n \"Previewing \\\"$filelist[$j]\\\"...\"; $previewer \"$filelist[$j]\" > /dev/null 2>&1 && echo \"ok\" || echo \"failed\"") if $show_preview == 1;
872	    print "Comment " . ($j+1) . " ($filelist[$j]): ";
873	    my $tmp = <STDIN>;
874	    chomp($tmp);
875	    $commenthash{$filelist[$j]} = $tmp;
876	    # Save directly in case user interrupts
877	    open(FILE, ">>$commentfile");
878	    print FILE "$filelist[$j]\t$tmp\n";
879	    close(FILE);
880	}
881    }
882}elsif($comments == 1 and $useoldcomments == 0){
883    print "\n";
884    for($i = 0; $i < @filelist; $i++){
885	my $previewer = $image_view;
886	$previewer = $video_view if "$progmap[$i]" eq "vid";
887	system("echo -n \"Previewing \\\"$filelist[$i]\\\"...\"; $previewer \"$filelist[$i]\" > /dev/null 2>&1 && echo \"ok\" || echo \"failed\"") if $show_preview == 1;
888	print "Comment " . ($i+1) . " ($filelist[$i]): ";
889	my $tmp = <STDIN>;
890	chomp($tmp);
891	$commenthash{$filelist[$i]} = $tmp;
892	# Save directly in case user interrupts
893	open(FILE, ">>$commentfile") or die "couldn't open \"$commentfile\"!";
894	print FILE "$filelist[$i]\t$tmp\n";
895	close(FILE);
896    }
897}
898
899if($comments){
900    open(FILE, ">$commentfile");
901    foreach (sort keys %commenthash){
902	my $found = 0;
903	for(my $i = 0; $i < @filelist; $i++){
904	    $found = 1 if "$_" eq "$filelist[$i]";
905	}
906	if($found){
907	    print FILE "$_\t$commenthash{$_}\n";
908	}else{
909	    print "Removed $_ from commentfile.\n";
910	}
911    }
912    close(FILE);
913}
914
915
916print "\nCalculating page layout.\n";
917## Page pre-calculation
918if($pagebuild_type == 0){ # --thumbs-per-page
919    my $tmpimgcount = 0;
920    for(my $tmpcalc = 0; $tmpcalc < @imglist; $tmpcalc++){
921        $tmpimgcount++;
922        if(($tmpcalc + 1) % $max_thumbs_per_page == 0){
923            $internal_pages++;
924            push(@page_index, $tmpimgcount);
925            $tmpimgcount = 0;
926        }
927    }
928    if(@page_index * $max_thumbs_per_page != @imglist){
929        push(@page_index, (@imglist - (@page_index * $max_thumbs_per_page)));
930        $internal_pages++;
931    }
932    if($page_index[$#page_index] <= $sloppy_files && $sloppy_mode == 1 && $#imglist > $max_thumbs_per_page){
933	print "Being sloppy, moved $page_index[$#page_index] images to page " . ($internal_pages - 1) . ".\n";
934        $page_index[($#page_index - 1)] += $page_index[$#page_index];
935        pop(@page_index);
936        $internal_pages--;
937    }
938}elsif($pagebuild_type == 1){ # --max_pages
939    my $tmpimgcount = 0;
940    $internal_thumbs = int(@imglist / $max_pages);
941    die "ERROR: Fewer thumbs than requested number of pages! Bluntly assuming you made a mistake " if $internal_thumbs < 1;
942    for(my $tmpcalc = 0; $tmpcalc < $max_pages; $tmpcalc++){
943        push(@page_index, $internal_thumbs);
944        $tmpimgcount += $internal_thumbs;
945    }
946    $page_index[$#page_index] += (@imglist - $tmpimgcount);
947}else{ # Fuckup
948    die "Internal error: Incorrect value in \$pagebuild_type ($pagebuild_type)";
949}
950
951print "Generating preview and thumbnail pages.\n";
952
953## Generate thumbnail and preview pages
954
955my $template_buf = load("$templatefile");
956
957# Read in seperate template blocks
958my %template;
959foreach("THUMB", "BODY", "PREVIEW"){
960    if($template_buf=~m/\[NPRETTY_BLOCK_$_\](.+?)\[\/NPRETTY_BLOCK_$_\]/s){
961	$template{$_} = $1;
962    }else{
963	die "ERROR: Required block $_ was not found in template";
964    }
965}
966
967die "ERROR: Required block BODY not found in THUMB block" unless $template{"THUMB"} =~m/\[NPRETTY_BODY\]/;
968
969# Replace title tag
970$template{"THUMB"} =~s/\[NPRETTY_TITLE\]/$title/gs;
971$template{"BODY"} =~s/\[NPRETTY_TITLE\]/$title/gs;
972$template{"PREVIEW"} =~s/\[NPRETTY_TITLE\]/$title/gs;
973
974# Get image delimiter
975my ($delimiter, $img_delimiter_rep, $img_delimiter) = (0, 0, "");
976if($template{"BODY"} =~s/\[NPRETTY_DEL_([0-9]+)\](.*?)\[\/NPRETTY_DEL\]//s){
977    ($img_delimiter_rep, $img_delimiter) = ($1, $2);
978    last if $img_delimiter_rep == 0;
979    $delimiter = 1;
980    print "Delimiting every $img_delimiter_rep thumbnails.\n";
981}
982
983# Get all extra buffers needed
984my %sub_template;
985foreach(
986    "PREV_EXIST", "PREV_NOEXIST", "LIST", "LIST_IFSEL", "NEXT_EXIST", "NEXT_NOEXIST",
987    "PREV_IMG_EXIST", "PREV_IMG_NOEXIST", "NEXT_IMG_EXIST", "NEXT_IMG_NOEXIST",
988    "COMMENT_EXIST", "PREV_LIST", "PREV_LIST_IFSEL", "PAD_DEL"){ #NPRETTY_TITLE_EXIST
989    foreach my $tplname ("THUMB", "BODY", "PREVIEW"){
990	$sub_template{"$tplname$_"} = ""; # To prevent warnings
991	if($template{$tplname} =~m/\[(NPRETTY_$_\])(.*?)\[\/\1/s){
992	    $sub_template{"$tplname$_"} = $2;
993	}
994    }
995}
996
997# Grab delimiter padding
998my $delimiter_padding = $sub_template{"BODYPAD_DEL"};
999
1000# Prepare the thumbnail linkindex lists
1001my $offset = 0;
1002my @linkindex = ();
1003my @prevlinkindex = ();
1004$offset = 0;
1005
1006my ($tsidepad, $psidepad) = ($sidepad, $sidepad);
1007$tsidepad = "" if $thumbnail_links > $#page_index;
1008$psidepad = "" if $preview_links > $#imglist;
1009
1010$preview_links = @imglist if $preview_links > @imglist;
1011$thumbnail_links = @page_index if $thumbnail_links > @page_index;
1012
1013my ($mincount, $maxcount) = (($preview_links - 1) / 2, ($preview_links + 1) / 2);
1014my ($lmincount, $lmaxcount) = (($thumbnail_links - 1) / 2, ($thumbnail_links + 1) / 2);
1015
1016for(my $pIndex = 1; $pIndex < (@page_index + 1); $pIndex++){
1017    ## Thumbs
1018    my $thumblist = "";
1019    my $linkindexbuf = "";
1020    my $pIndexEx = $pIndex - 1;
1021    my ($lsidepad_left, $lsidepad_right) = ("", "");
1022    my ($lbeg, $lend) = ($pIndexEx - $lmincount, $pIndexEx + $lmaxcount);
1023    if($lbeg < 1){
1024	$lbeg = 0;
1025	$lend = $thumbnail_links;
1026    }else{
1027	$lsidepad_left = $tsidepad;
1028    }
1029    if($lend > @page_index){
1030	$lbeg = @page_index - $thumbnail_links;
1031	$lend = @page_index;
1032    }else{
1033	$lsidepad_right = $tsidepad if $lend < @page_index;
1034    }
1035
1036    for(my $i = $lbeg; $i < $lend; $i++){
1037	my $i_human = $i + 1;
1038	my $thumbpageEx = getPageName($i_human);
1039	my $current;
1040	if($i == $pIndexEx){
1041	    $current = $sub_template{"THUMBLIST_IFSEL"};
1042	}else{
1043	    $current = $sub_template{"THUMBLIST"};
1044	}
1045	$current =~s/\[NPRETTY_LIST_PAGE\]/$thumbpageEx/gs;
1046	$current =~s/\[NPRETTY_LIST_NUM\]/$i_human/gs;
1047	$linkindexbuf .= $current;
1048    }
1049
1050    $linkindexbuf = "$lsidepad_left$linkindexbuf$lsidepad_right";
1051    push(@linkindex, $linkindexbuf);
1052
1053    ## Previews
1054    for(my $iIndex = 0; $iIndex < $page_index[$pIndex-1]; $iIndex++){
1055	my ($sidepad_left, $sidepad_right) = ("", "");
1056	my $prevlistbuf = "";
1057	my ($beg, $end) = ($offset - $mincount, $offset + $maxcount);
1058	if($beg < 1){
1059	    $beg = 0;
1060	    $end = $preview_links;
1061	}else{
1062	    $sidepad_left = $psidepad;
1063	}
1064	if($end > @imglist){
1065	    $beg = @imglist - $preview_links;
1066	    $end = @imglist;
1067	}else{
1068	    $sidepad_right = $psidepad if $end < @imglist;
1069	}
1070	for(my $i = $beg; $i < $end; $i++){
1071	    my $i_human = $i + 1;
1072	    my $current;
1073	    if($i == $offset){
1074		$current = $sub_template{"PREVIEWPREV_LIST_IFSEL"};
1075	    }else{
1076		$current= $sub_template{"PREVIEWPREV_LIST"};
1077	    }
1078	    $current =~s/\[NPRETTY_PREV_LIST_PAGE\]/$prevpagelist[$i]/gs;
1079	    $current =~s/\[NPRETTY_PREV_LIST_NUM\]/$i_human/gs;
1080	    $prevlistbuf .= $current;
1081	}
1082	$prevlistbuf = "$sidepad_left$prevlistbuf$sidepad_right";
1083	push(@prevlinkindex, $prevlistbuf);
1084	$offset++;
1085    }
1086}
1087undef $offset;
1088$template{"THUMB"} =~s/\[(NPRETTY_LIST_IFSEL\]).*?\[\/\1//s;
1089$template{"THUMB"} =~s/\[(NPRETTY_PREV_NOEXIST\]).*?\[\/\1//s;
1090$template{"THUMB"} =~s/\[(NPRETTY_NEXT_NOEXIST\]).*?\[\/\1//s;
1091$template{"PREVIEW"} =~s/\[(NPRETTY_LIST_IFSEL\]).*?\[\/\1//s;
1092$template{"PREVIEW"} =~s/\[(NPRETTY_PREV_IMG_NOEXIST\]).*?\[\/\1//s;
1093$template{"PREVIEW"} =~s/\[(NPRETTY_NEXT_IMG_NOEXIST\]).*?\[\/\1//s;
1094$template{"PREVIEW"} =~s/\[(NPRETTY_PREV_LIST_IFSEL\]).*?\[\/\1//s;
1095$template{"BODY"} =~s/\[(NPRETTY_PAD_DEL\]).*?\[\/\1//s;
1096
1097print "\n";
1098
1099my ($body_write_buf, $thumb_write_buf, $preview_write_buf);
1100
1101$offset = 0;
1102my $offset_human = $offset + 1;
1103for(my $pIndex = 0; $pIndex < @page_index; $pIndex++){
1104    my $pIndex_human = $pIndex + 1;
1105    printf("Page %3d, %3d files:\n", $pIndex_human, $page_index[$pIndex]);
1106
1107    my $thumbpage = getPageName($pIndex_human);
1108
1109    $body_write_buf = "";
1110    $thumb_write_buf	= $template{"THUMB"};
1111    # Add list index
1112    $thumb_write_buf =~s/\[(NPRETTY_LIST\]).*?\[\/\1/$linkindex[$pIndex]/s;
1113
1114    # Add previous/next page
1115    my %subtemplate_tag = (
1116	'[NPRETTY_PREV_PAGE]'		=> getPageName($pIndex_human - 1),
1117	'[NPRETTY_NEXT_PAGE]'		=> getPageName($pIndex_human + 1),
1118	'[NPRETTY_PREV_PAGE_NUM]'	=> ($pIndex_human - 1),
1119	'[NPRETTY_NEXT_PAGE_NUM]'	=> ($pIndex_human + 1));
1120    my ($ppagebuf, $npagebuf);
1121    if($pIndex == 0){
1122	$ppagebuf = $sub_template{"THUMBPREV_NOEXIST"};
1123    }else{
1124	$ppagebuf = $sub_template{"THUMBPREV_EXIST"};
1125    }
1126    if($pIndex == $#page_index){
1127	$npagebuf = $sub_template{"THUMBNEXT_NOEXIST"};
1128    }else{
1129	$npagebuf = $sub_template{"THUMBNEXT_EXIST"};
1130    }
1131    $ppagebuf = replaceTags($ppagebuf, %subtemplate_tag);
1132    $npagebuf = replaceTags($npagebuf, %subtemplate_tag);
1133
1134    $thumb_write_buf =~s/\[NPRETTY_PREV_EXIST\].*?\[\/NPRETTY_PREV_EXIST\]/$ppagebuf/s;
1135    $thumb_write_buf =~s/\[NPRETTY_NEXT_EXIST\].*?\[\/NPRETTY_NEXT_EXIST\]/$npagebuf/s;
1136
1137    #Title if it exists
1138    if("$title" ne ""){
1139	$thumb_write_buf =~s/\[(NPRETTY_TITLE_EXIST\])(.*?)\[\/\1/$2/gs;
1140	$thumb_write_buf =~s/\[(NPRETTY_TITLE_NOEXIST\]).*?\[\/\1//gs;
1141    }else{
1142	$thumb_write_buf =~s/\[(NPRETTY_TITLE_NOEXIST\])(.*?)\[\/\1/$2/gs;
1143	$thumb_write_buf =~s/\[(NPRETTY_TITLE_EXIST\])(.*?)\[\/\1//gs;
1144    }
1145
1146    for(my $iIndex = 0; $iIndex < $page_index[$pIndex]; $iIndex++){
1147	my $iIndex_human = $iIndex + 1;
1148	my $image = $imglist[$offset];
1149
1150        printf("%3d \"%s\"\n", $offset + 1, $image);
1151	# Get EXIF
1152	my $EXIF_info = ImageInfo("$filelist[$offset]");
1153	setPreviewDimensions($EXIF_info, $offset, "$filelist[$offset]"); # FIXME might be moved later
1154	#print " (preview $preview_dimension[$offset])\n";
1155
1156	my $line_buffer		= $template{"BODY"};
1157	$preview_write_buf	= $template{"PREVIEW"};
1158
1159	#Title if it exists
1160	if("$title" ne ""){
1161	    $preview_write_buf =~s/\[(NPRETTY_TITLE_EXIST\])(.*?)\[\/\1/$2/gs;
1162	    $preview_write_buf =~s/\[(NPRETTY_TITLE_NOEXIST\]).*?\[\/\1//gs;
1163	}else{
1164	    $preview_write_buf =~s/\[(NPRETTY_TITLE_NOEXIST\])(.*?)\[\/\1/$2/gs;
1165	    $preview_write_buf =~s/\[(NPRETTY_TITLE_EXIST\]).*?\[\/\1//gs;
1166	}
1167
1168	# Add preview ist index
1169	$preview_write_buf =~s/\[NPRETTY_PREV_LIST\].*?\[\/NPRETTY_PREV_LIST\]/$prevlinkindex[$offset]/s;
1170
1171	# Add previous/next preview page
1172	my %preview_tag;
1173	my ($pprevbuf, $nprevbuf);
1174	if($offset == 0){
1175	    $pprevbuf = $sub_template{"PREVIEWPREV_IMG_NOEXIST"};
1176	    $preview_tag{'[NPRETTY_PREV_IMG]'} 		= "";
1177	    $preview_tag{'[NPRETTY_PREV_IMG_NAME]'} 	= "";
1178	}else{
1179	    $pprevbuf = $sub_template{"PREVIEWPREV_IMG_EXIST"};
1180	    $preview_tag{'[NPRETTY_PREV_IMG]'} 		= $prevpagelist[$offset - 1];
1181	    $preview_tag{'[NPRETTY_PREV_IMG_NAME]'} 	= $imglist[$offset - 1];
1182	}
1183	if($offset == $#imglist){
1184	    $nprevbuf = $sub_template{"PREVIEWNEXT_IMG_NOEXIST"};
1185	    $preview_tag{'[NPRETTY_NEXT_IMG]'}		= "";
1186	    $preview_tag{'[NPRETTY_NEXT_IMG_NAME]'}	= "";
1187	}else{
1188	    $nprevbuf = $sub_template{"PREVIEWNEXT_IMG_EXIST"};
1189	    $preview_tag{'[NPRETTY_NEXT_IMG]'}		= $prevpagelist[$offset + 1];
1190	    $preview_tag{'[NPRETTY_NEXT_IMG_NAME]'} 	= $imglist[$offset + 1];
1191	}
1192	$pprevbuf = replaceTags($pprevbuf, %preview_tag);
1193	$nprevbuf = replaceTags($nprevbuf, %preview_tag);
1194
1195	$preview_write_buf =~s/\[(NPRETTY_PREV_IMG_EXIST\]).*?\[\/\1/$pprevbuf/s;
1196	$preview_write_buf =~s/\[(NPRETTY_NEXT_IMG_EXIST\]).*?\[\/\1/$nprevbuf/s;
1197
1198	my $comment = "";
1199	$comment = $commenthash{$filelist[$offset]} if $comments;
1200
1201	# Insert EXIF data
1202	$line_buffer = replaceExifTags($line_buffer, $EXIF_info);
1203	$preview_write_buf = replaceExifTags($preview_write_buf, $EXIF_info);
1204
1205	my %template_tag =(
1206	    '[NPRETTY_SIZE]'		=> sprintf("%1.1fKiB", filesize("$filelist[$offset]") / 1024),
1207	    '[NPRETTY_FILENAME]'	=> $image,
1208	    '[NPRETTY_INDEX_NUM]'	=> $offset_human,
1209	    '[NPRETTY_PAGE]'		=> $thumbpage,
1210	    '[NPRETTY_THUMB]'		=> "$abs_thumb_dest/$thumblist[$offset]",
1211	    '[NPRETTY_PREVIEW]'		=> "$abs_prevpage_dest/$abs_preview_dest/$prevlist[$offset]",
1212	    '[NPRETTY_PREVIEW_PAGE]'	=> "$abs_prevpage_dest/$prevpagelist[$offset]",
1213	    '[NPRETTY_IMAGE]'		=> "$abs_img_dest/$image",
1214	    '[NPRETTY_COMMENT]'		=> $comment);
1215
1216	if($comments && "$comment" ne ""){
1217	    $preview_write_buf =~s/\[(NPRETTY_COMMENT_EXIST\]).*?\[\/\1/$sub_template{"PREVIEWCOMMENT_EXIST"}/s;
1218	}else{
1219	    $preview_write_buf =~s/\[(NPRETTY_COMMENT_EXIST\]).*?\[\/\1//s;
1220	}
1221
1222    	$line_buffer = replaceTags($line_buffer, %template_tag);
1223	$template_tag{"[NPRETTY_PAGE]"}		= "../$thumbpage";
1224	$template_tag{"[NPRETTY_THUMB]"}	= "../$thumblist[$offset]";
1225	$template_tag{"[NPRETTY_PREVIEW]"}	= "../$abs_preview_dest/$prevlist[$offset]";
1226	$template_tag{"[NPRETTY_IMAGE]"}	= "../$abs_img_dest/$image";
1227	$preview_write_buf = replaceTags($preview_write_buf, %template_tag);
1228
1229	# Write preview page
1230	save("$prevpage_dest/$prevpagelist[$offset]", "$NTAG$preview_write_buf");
1231	$body_write_buf .= $line_buffer;
1232	$body_write_buf .= $img_delimiter if $delimiter && ($iIndex + 1) != $page_index[$pIndex] && (($iIndex + 1) % $img_delimiter_rep) == 0;
1233
1234	$offset++;
1235	$offset_human = $offset + 1;
1236
1237	# Pad remaining blocks if endcount of thumbs is uneven
1238	if($delimiter && $iIndex == $page_index[$pIndex]-1){
1239	    my $remaining = $img_delimiter_rep - ($iIndex + 1) % $img_delimiter_rep;
1240	    unless($remaining == $img_delimiter_rep){
1241		for($i = 0; $i < $remaining; $i++){
1242		    $body_write_buf .= $delimiter_padding;
1243		}
1244	    }
1245	}
1246
1247    }
1248    $thumb_write_buf=~s/\[NPRETTY_BODY\]/$body_write_buf/g;
1249
1250   # Write thumbnail page
1251    print "-> $thumbpage\n";
1252    save("$dest/$thumbpage", "$NTAG$thumb_write_buf");
1253    print "\n";
1254}
1255
1256undef $body_write_buf;
1257undef $thumb_write_buf;
1258undef $preview_write_buf;
1259
1260## --skip-images? If so, we're all done.
1261if($generation_mode == 2){
1262    print "Images skipped.\nDone.\n";
1263    exit 0;
1264}
1265
1266GENERATION:
1267if($generation_mode == 1){
1268    # We missed dimension info when we skipped pages
1269    for($offset = 0; $offset < @filelist; $offset++){
1270	my $EXIF_info = ImageInfo("$filelist[$offset]");
1271	setPreviewDimensions($EXIF_info, $offset, "$filelist[$offset]");
1272    }
1273}
1274
1275# We don't need images any more, delete them if requested.
1276if($delete_originals){
1277    print "Deleting original images.\n\n";
1278    foreach(@filelist){
1279	system("rm \"$_\"");
1280    }
1281}
1282
1283## Main loop should begin somewhere around here.
1284
1285## Argument setup for external programs
1286my ($thumb_conv_args, $pre_conv_args, $video_conv_args);
1287
1288$thumb_conv_args = "$thumb_dimensions";
1289$thumb_conv_args .= "!" unless $thumb_keep_aspect;
1290
1291$pre_conv_args = "-exonly -se ";
1292$pre_conv_args .= "-dt " if $pre_remove_cthumbs;
1293$pre_conv_args .= "-autorot " if $pre_auto_rotate;
1294
1295#$video_conv_args = "-really-quiet -nosound -vo jpeg -frames 1";
1296$video_conv_args = "-really-quiet -nosound -vo gif89a:5.0:$img_dest/tmpvid.gif -frames 50";
1297
1298## Initialisation and running of threads
1299
1300my $PROGRESS = 1;
1301my $PROGRESS_MAX = @imglist;
1302
1303# Video conversion
1304
1305if($use_vidconverter){
1306    print "Converting video frames to images.\n\n";
1307    my $videos = 0;
1308    for(my $i = 0; $i < @imglist; $i++){
1309	next unless("$progmap[$i]" eq "vid");
1310	next unless $work{$imglist[$i]};
1311	printf("%03d vid->img $img_dest/$imglist[$i]\n", $i);
1312	if(system("$vid_converter $video_conv_args \"$img_dest/$imglist[$i]\" > /dev/null 2>&1") == 0){
1313	    substr($imglist[$i], rindex($imglist[$i], "."), length($imglist[$i]), ".gif");
1314	    rename("$img_dest/tmpvid.gif", "$img_dest/$imglist[$i]");
1315	}else{
1316	    substr($imglist[$i], rindex($imglist[$i], "."), length($imglist[$i]), ".gif");
1317	    print "WARN: conversion failed!\n";
1318	    system("cp \"$no_video_image\" \"$img_dest/$imglist[$i]\"");
1319	}
1320	unless(-e "$img_dest/$imglist[$i]"){
1321	    print "WARN: conversion failed!\n";
1322	    system("cp \"$no_video_image\" \"$img_dest/$imglist[$i]\"");
1323	}
1324	$videos++;
1325    }
1326    print "No videos to convert!\n" if $videos == 0;
1327}else{
1328    my $videos = 0;
1329    for(my $i = 0; $i < @imglist; $i++){
1330	next unless("$progmap[$i]" eq "vid");
1331	printf("%03d missing video image ..\n", $i);
1332	substr($imglist[$i], rindex($imglist[$i], "."), length($imglist[$i]), ".gif");
1333	system("cp \"$no_video_image\" \"$img_dest/$imglist[$i]\"");
1334	$videos++;
1335    }
1336    print "No videos to convert!\n" if $videos == 0;
1337
1338}
1339
1340#Set max number of threads to run
1341$jobs = @imglist if $jobs > @imglist;
1342
1343$finishedthreads = 0;
1344if($use_preprocessor){
1345    print "\nPreprocessing.\n\n";
1346    while($finishedthreads < @imglist){
1347	if(threads->list() < $jobs){
1348	    my $cjob = "$pre_processor $pre_conv_args \"$img_dest/$imglist[$finishedthreads]\"";
1349	    if($work{$imglist[$finishedthreads]}){
1350		init_sys_thread("$cjob", "") if $work{$imglist[$finishedthreads]};
1351	    }else{
1352		print  "\"$img_dest/$imglist[$finishedthreads]\", skipped\n";
1353	    }
1354	    $finishedthreads++;
1355	}
1356    }
1357    while(threads->list() != 0){};
1358}
1359
1360$SIG{CHLD} = 'IGNORE';
1361
1362my $workfile;
1363# Previews
1364print "\nGenerating previews.\n\n";
1365$finishedthreads = 0;
1366while($finishedthreads < @imglist){
1367    if(threads->list() < $jobs){
1368	my $workfile = "$preview_dest/$prevlist[$finishedthreads]"; # . shift(@prevlist);
1369	my $prev_conv_args = "$preview_dimension[$finishedthreads]";
1370	$prev_conv_args .= "!" unless $preview_keep_aspect;
1371	my @cjob = ("$img_dest/$imglist[$finishedthreads]", "$prev_conv_args", "$workfile");
1372	my $output = sprintf("[%03d/%03d] %s", $PROGRESS, $PROGRESS_MAX, $workfile);
1373	if($work{$prevlist[$finishedthreads]}){
1374	    init_thread("$output\n", @cjob);
1375	}else{
1376	    print "$output, skipped (already exists)\n";
1377	}
1378
1379        $finishedthreads++;
1380	$PROGRESS++;
1381    }
1382}
1383while(threads->list() != 0){};
1384
1385
1386# Thumbnails
1387print "\nGenerating thumbnails.\n\n";
1388$finishedthreads = 0;
1389$PROGRESS = 1;
1390while($finishedthreads < @imglist){
1391    if(threads->list() < $jobs){
1392	$workfile = "$thumb_dest/$thumblist[$finishedthreads]"; #. shift(@thumblist);
1393	my @cjob = ("$preview_dest/$prevlist[$finishedthreads]", "$thumb_conv_args", "$workfile");
1394	my $output = sprintf("[%03d/%03d] %s", $PROGRESS, $PROGRESS_MAX, $workfile);
1395	if($work{$thumblist[$finishedthreads]}){
1396	    init_thread("$output\n", @cjob);
1397	}else{
1398	    print "$output, skipped (already exists)\n";
1399	}
1400        $finishedthreads++;
1401	$PROGRESS++;
1402    }
1403}
1404while(threads->list() != 0){};
1405
1406
1407$SIG{CHLD} = 'DEFAULT';
1408print "\nDone.\n";
1409