File Coverage

blib/lib/Apache/Gallery.pm
Criterion Covered Total %
statement 8 25 32.0
branch 1 4 25.0
condition 1 6 16.6
subroutine 3 3 100.0
pod n/a
total 13 38 34.2


line stmt bran cond sub pod time code
1             package Apache::Gallery;
2              
3             # $Author: mil $ $Rev: 335 $
4             # $Date: 2011-06-08 20:47:46 +0200 (Wed, 08 Jun 2011) $
5              
6 6     6   75293 use strict;
  6         12  
  6         361  
7              
8 6     6   33 use vars qw($VERSION);
  6         11  
  6         1552  
9              
10             $VERSION = "1.0.2";
11              
12             BEGIN {
13              
14 6 50 33 6   46 if (exists($ENV{MOD_PERL_API_VERSION})
15             and ($ENV{MOD_PERL_API_VERSION}==2)) {
16 0         0 require mod_perl2;
17 0 0 0     0 if ($mod_perl::VERSION >= 1.99 && $mod_perl::VERSION < 2.0) {
18 0         0 die "mod_perl 2.0.0 or later is now required";
19             }
20 0         0 require Apache2::ServerRec;
21 0         0 require Apache2::RequestRec;
22 0         0 require Apache2::Log;
23 0         0 require APR::Table;
24 0         0 require Apache2::RequestIO;
25 0         0 require Apache2::SubRequest;
26 0         0 require Apache2::Const;
27            
28 0         0 Apache2::Const->import(-compile => 'OK','DECLINED','FORBIDDEN','NOT_FOUND','HTTP_NOT_MODIFIED');
29              
30 0         0 $::MP2 = 1;
31             } else {
32 6         8912 require mod_perl;
33              
34 0           require Apache;
35 0           require Apache::Constants;
36 0           require Apache::Request;
37            
38 0           Apache::Constants->import('OK','DECLINED','FORBIDDEN','NOT_FOUND');
39 0           $::MP2 = 0;
40             }
41             }
42              
43             use Image::Info qw(image_info);
44             use Image::Size qw(imgsize);
45             use Image::Imlib2;
46             use Text::Template;
47             use File::stat;
48             use File::Spec;
49             use POSIX qw(floor);
50             use URI::Escape;
51             use CGI;
52             use CGI::Cookie;
53             use Encode;
54             use HTTP::Date;
55             use Digest::MD5 qw(md5_base64);
56              
57             use Data::Dumper;
58              
59             # Regexp for escaping URI's
60             my $escape_rule = "^A-Za-z0-9\-_.!~*'()\/";
61             my $memoized;
62              
63             sub handler {
64              
65             my $r = shift or Apache2::RequestUtil->request();
66              
67             unless (($r->method eq 'HEAD') or ($r->method eq 'GET')) {
68             return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
69             }
70              
71             if ((not $memoized) and ($r->dir_config('GalleryMemoize'))) {
72             require Memoize;
73             Memoize::memoize('get_imageinfo');
74             $memoized=1;
75             }
76              
77             $r->headers_out->{"X-Powered-By"} = "apachegallery.dk $VERSION - Hest design!";
78             $r->headers_out->{"X-Gallery-Version"} = '$Rev: 335 $ $Date: 2011-06-08 20:47:46 +0200 (Wed, 08 Jun 2011) $';
79              
80             my $filename = $r->filename;
81             $filename =~ s/\/$//;
82             my $topdir = $filename;
83              
84             my $media_rss_enabled = $r->dir_config('GalleryEnableMediaRss');
85              
86             # Just return the http headers if the client requested that
87             if ($r->header_only) {
88              
89             if (!$::MP2) {
90             $r->send_http_header;
91             }
92              
93             if (-f $filename or -d $filename) {
94             return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
95             }
96             else {
97             return $::MP2 ? Apache2::Const::NOT_FOUND() : Apache::Constants::NOT_FOUND();
98             }
99             }
100              
101             my $cgi = new CGI;
102              
103             # Handle selected images
104             if ($cgi->param('selection')) {
105             my @selected = $cgi->param('selection');
106             my $content = join "
\n",@selected;
107             $r->content_type('text/html');
108             $r->headers_out->{'Content-Length'} = length($content);
109              
110             if (!$::MP2) {
111             $r->send_http_header;
112             }
113              
114             $r->print($content);
115             return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
116             }
117            
118             # Selectmode providing checkboxes beside all thumbnails
119             my $select_mode = $cgi->param('select');
120            
121             # Let Apache serve icons without us modifying the request
122             if ($r->uri =~ m/^\/icons/i) {
123             return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
124             }
125             # Lookup the file in the cache and scale the image if the cached
126             # image does not exist
127             if ($r->uri =~ m/\.cache\//i) {
128              
129             my $filename = $r->filename().$r->path_info();
130             $filename =~ s/\.cache//;
131              
132             $filename =~ m/\/(\d+)x(\d+)\-/;
133             my $image_width = $1;
134             my $image_height = $2;
135              
136             $filename =~ s/\/(\d+)x(\d+)\-//;
137              
138             my ($width, $height, $type) = imgsize($filename);
139              
140             my $imageinfo = get_imageinfo($r, $filename, $type, $width, $height);
141            
142             my $cached = scale_picture($r, $filename, $image_width, $image_height, $imageinfo);
143              
144             my $file = cache_dir($r, 0);
145             $file =~ s/\.cache//;
146              
147             my $subr = $r->lookup_file($file);
148             $r->content_type($subr->content_type());
149              
150             if ($::MP2) {
151             my $fileinfo = stat($file);
152              
153             my $nonce = md5_base64($fileinfo->ino.$fileinfo->mtime);
154             if ($r->headers_in->{"If-None-Match"} eq $nonce) {
155             return Apache2::Const::HTTP_NOT_MODIFIED();
156             }
157              
158             if ($r->headers_in->{"If-Modified-Since"} && str2time($r->headers_in->{"If-Modified-Since"}) < $fileinfo->mtime) {
159             return Apache2::Const::HTTP_NOT_MODIFIED();
160             }
161              
162             $r->headers_out->{"Content-Length"} = $fileinfo->size;
163             $r->headers_out->{"Last-Modified-Date"} = time2str($fileinfo->mtime);
164             $r->headers_out->{"ETag"} = $nonce;
165             $r->sendfile($file);
166             return Apache2::Const::OK();
167             }
168             else {
169             $r->path_info('');
170             $r->filename($file);
171             return Apache::Constants::DECLINED();
172             }
173            
174             }
175              
176             my $uri = $r->uri;
177             $uri =~ s/\/$//;
178              
179             unless (-f $filename or -d $filename) {
180             show_error($r, 404, "404!", "No such file or directory: ".uri_escape($r->uri, $escape_rule));
181             return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
182             }
183              
184             my $doc_pattern = $r->dir_config('GalleryDocFile');
185             unless ($doc_pattern) {
186             $doc_pattern = '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
187             }
188             my $img_pattern = $r->dir_config('GalleryImgFile');
189             unless ($img_pattern) {
190             $img_pattern = '\.(jpe?g|png|tiff?|ppm)$'
191             }
192              
193             # Let Apache serve files we don't know how to handle anyway
194             if (-f $filename && $filename !~ m/$img_pattern/i) {
195             return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
196             }
197              
198             if (-d $filename) {
199              
200             unless (-d cache_dir($r, 0)) {
201             unless (create_cache($r, cache_dir($r, 0))) {
202             return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
203             }
204             }
205              
206             my $tpl_dir = $r->dir_config('GalleryTemplateDir');
207              
208             # Instead of reading the templates every single time
209             # we need them, create a hash of template names and
210             # the associated Text::Template objects.
211             my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
212             index => "$tpl_dir/index.tpl",
213             directory => "$tpl_dir/directory.tpl",
214             picture => "$tpl_dir/picture.tpl",
215             file => "$tpl_dir/file.tpl",
216             comment => "$tpl_dir/dircomment.tpl",
217             nocomment => "$tpl_dir/nodircomment.tpl",
218             rss => "$tpl_dir/rss.tpl",
219             rss_item => "$tpl_dir/rss_item.tpl",
220             navdirectory => "$tpl_dir/navdirectory.tpl",
221             });
222              
223              
224              
225              
226             my %tpl_vars;
227              
228             $tpl_vars{TITLE} = "Index of: $uri";
229              
230             if ($media_rss_enabled) {
231             # Put the RSS feed on all directory listings
232             $tpl_vars{META} = '';
233             }
234              
235             unless (opendir (DIR, $filename)) {
236             show_error ($r, 500, $!, "Unable to access directory $filename: $!");
237             return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
238             }
239              
240             $tpl_vars{MENU} = generate_menu($r);
241              
242             $tpl_vars{FORM_BEGIN} = $select_mode?'
':'';
243             $tpl_vars{FORM_END} = $select_mode?'':'';
244              
245             # Read, sort, and filter files
246             my @files = grep { !/^\./ && -f "$filename/$_" } readdir (DIR);
247              
248             @files=gallerysort($r, @files);
249              
250             my @downloadable_files;
251              
252             if (@files) {
253             # Remove unwanted files from list
254             my @new_files = ();
255             foreach my $picture (@files) {
256              
257             my $file = $topdir."/".$picture;
258              
259             if ($file =~ /$img_pattern/i) {
260             push (@new_files, $picture);
261             }
262              
263             if ($file =~ /$doc_pattern/i) {
264             push (@downloadable_files, $picture);
265             }
266              
267             }
268             @files = @new_files;
269             }
270              
271             # Read and sort directories
272             rewinddir (DIR);
273             my @directories = grep { !/^\./ && -d "$filename/$_" } readdir (DIR);
274             my $dirsortby;
275             if (defined($r->dir_config('GalleryDirSortBy'))) {
276             $dirsortby=$r->dir_config('GalleryDirSortBy');
277             } else {
278             $dirsortby=$r->dir_config('GallerySortBy');
279             }
280             if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
281             @directories = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$dirsortby()." $_", @directories));
282             } else {
283             @directories = sort @directories;
284             }
285              
286             closedir(DIR);
287              
288              
289             # Combine directories and files to one listing
290             my @listing;
291             push (@listing, @directories);
292             push (@listing, @files);
293             push (@listing, @downloadable_files);
294            
295             if (@listing) {
296              
297             my $filelist;
298              
299             my $file_counter = 0;
300             my $start_at = 1;
301             my $max_files = $r->dir_config('GalleryMaxThumbnailsPerPage');
302              
303             if (defined($cgi->param('start'))) {
304             $start_at = $cgi->param('start');
305             if ($start_at < 1) {
306             $start_at = 1;
307             }
308             }
309              
310             my $browse_links = "";
311             if (defined($max_files)) {
312            
313             for (my $i=1; $i<=scalar(@listing); $i++) {
314              
315             my $from = $i;
316              
317             my $to = $i+$max_files-1;
318             if ($to > scalar(@listing)) {
319             $to = scalar(@listing);
320             }
321              
322             if ($start_at < $from || $start_at > $to) {
323             $browse_links .= "$from - ".$to." ";
324             }
325             else {
326             $browse_links .= "$from - $to ";
327             }
328              
329             $i+=$max_files-1;
330              
331             }
332              
333             }
334              
335             $tpl_vars{BROWSELINKS} = $browse_links;
336              
337             DIRLOOP:
338             foreach my $file (@listing) {
339              
340             $file_counter++;
341              
342             if ($file_counter < $start_at) {
343             next;
344             }
345              
346             if (defined($max_files) && $file_counter > $max_files+$start_at-1) {
347             last DIRLOOP;
348             }
349              
350             my $thumbfilename = $topdir."/".$file;
351              
352             my $fileurl = $uri."/".$file;
353              
354             # Debian bug #619625
355             if (-d $thumbfilename && ! -e $thumbfilename . ".ignore") {
356             my $dirtitle = '';
357             if (-e $thumbfilename . ".folder") {
358             $dirtitle = get_filecontent($thumbfilename . ".folder");
359             }
360              
361             $dirtitle = $dirtitle ? $dirtitle : $file;
362             $dirtitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
363              
364             $tpl_vars{FILES} .=
365             $templates{directory}->fill_in(HASH=> {FILEURL => uri_escape($fileurl, $escape_rule),
366             FILE => $dirtitle,
367             }
368             );
369              
370             }
371             # Debian bug #619625
372             elsif (-f $thumbfilename && $thumbfilename =~ /$doc_pattern/i && $thumbfilename !~ /$img_pattern/i && ! -e $thumbfilename . ".ignore") {
373             my $type = lc($1);
374             my $stat = stat($thumbfilename);
375             my $size = $stat->size;
376             my $filetype;
377              
378             if ($thumbfilename =~ m/\.(mpe?g|avi|mov|asf|wmv)$/i) {
379             $filetype = "video-$type";
380             } elsif ($thumbfilename =~ m/\.(txt|html?)$/i) {
381             $filetype = "text-$type";
382             } elsif ($thumbfilename =~ m/\.(mp3|ogg|wav)$/i) {
383             $filetype = "sound-$type";
384             } elsif ($thumbfilename =~ m/$doc_pattern/i) {
385             $filetype = "application-$type";
386             } else {
387             $filetype = "unknown";
388             }
389              
390             # Debian bug #348724
391             # not images
392             my $filetitle = $file;
393             $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
394              
395             $tpl_vars{FILES} .=
396             $templates{file}->fill_in(HASH => {%tpl_vars,
397             FILEURL => uri_escape($fileurl, $escape_rule),
398             ALT => "Size: $size Bytes",
399             FILE => $filetitle,
400             TYPE => $type,
401             FILETYPE => $filetype,
402             }
403             );
404             }
405             # Debian bug #619625
406             elsif (-f $thumbfilename && ! -e $thumbfilename . ".ignore") {
407              
408             my ($width, $height, $type) = imgsize($thumbfilename);
409             next if $type eq 'Data stream is not a known image file format';
410              
411             my @filetypes = qw(JPG TIF PNG PPM GIF);
412              
413             next unless (grep $type eq $_, @filetypes);
414             my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);
415             my $imageinfo = get_imageinfo($r, $thumbfilename, $type, $width, $height);
416             my $cached = get_scaled_picture_name($thumbfilename, $thumbnailwidth, $thumbnailheight);
417              
418             my $rotate = readfile_getnum($r, $imageinfo, $thumbfilename.".rotate");
419              
420             # Debian bug #348724
421             # HTML tag, alt attribute
422             my $filetitle = $file;
423             $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
424              
425             my %file_vars = (FILEURL => uri_escape($fileurl, $escape_rule),
426             FILE => $filetitle,
427             DATE => $imageinfo->{DateTimeOriginal} ? $imageinfo->{DateTimeOriginal} : '', # should this really be a stat of the file instead of ''?
428             SRC => uri_escape($uri."/.cache/$cached", $escape_rule),
429             HEIGHT => (grep($rotate==$_, (1, 3)) ? $thumbnailwidth : $thumbnailheight),
430             WIDTH => (grep($rotate==$_, (1, 3)) ? $thumbnailheight : $thumbnailwidth),
431             SELECT => $select_mode?'  ':'',);
432             $tpl_vars{FILES} .= $templates{picture}->fill_in(HASH => {%tpl_vars,
433             %file_vars,
434             },
435             );
436              
437             if ($media_rss_enabled) {
438             my ($content_image_width, undef, $content_image_height) = get_image_display_size($cgi, $r, $width, $height);
439             my %item_vars = (
440             THUMBNAIL => uri_escape($uri."/.cache/$cached", $escape_rule),
441             LINK => uri_escape($fileurl, $escape_rule),
442             TITLE => $file,
443             CONTENT => uri_escape($uri."/.cache/".$content_image_width."x".$content_image_height."-".$file, $escape_rule)
444             );
445             $tpl_vars{ITEMS} .= $templates{rss_item}->fill_in(HASH => {
446             %item_vars
447             });
448             }
449             }
450             }
451             }
452             else {
453             $tpl_vars{FILES} = "No files found";
454             $tpl_vars{BROWSELINKS} = "";
455             }
456              
457             # Generate prev and next directory menu items
458             $filename =~ m/(.*)\/.*?$/;
459             my $parent_filename = $1;
460              
461             $r->document_root =~ m/(.*)\/$/;
462             my $root_path = $1;
463             print STDERR "$filename vs $root_path\n";
464             if ($filename ne $root_path) {
465             unless (opendir (PARENT_DIR, $parent_filename)) {
466             show_error ($r, 500, $!, "Unable to access parent directory $parent_filename: $!");
467             return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
468             }
469            
470             # Debian bug #619625
471             my @neighbour_directories = grep { !/^\./ && -d "$parent_filename/$_" && ! -e "$parent_filename/$_" . ".ignore" } readdir (PARENT_DIR);
472             my $dirsortby;
473             if (defined($r->dir_config('GalleryDirSortBy'))) {
474             $dirsortby=$r->dir_config('GalleryDirSortBy');
475             } else {
476             $dirsortby=$r->dir_config('GallerySortBy');
477             }
478             if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
479             @neighbour_directories = map(/^\d+ (.*)/, sort map(stat("$parent_filename/$_")->$dirsortby()." $_", @neighbour_directories));
480             } else {
481             @neighbour_directories = sort @neighbour_directories;
482             }
483              
484             closedir(PARENT_DIR);
485              
486             my $neightbour_counter = 0;
487             foreach my $neighbour_directory (@neighbour_directories) {
488             if ($parent_filename.'/'.$neighbour_directory eq $filename) {
489             if ($neightbour_counter > 0) {
490             print STDERR "prev directory is " .$neighbour_directories[$neightbour_counter-1] ."\n";
491             my $linktext = $neighbour_directories[$neightbour_counter-1];
492             if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
493             $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder");
494             }
495             my %info = (
496             URL => "../".$neighbour_directories[$neightbour_counter-1],
497             LINK_NAME => "<<< $linktext",
498             DIR_FILES => "",
499             );
500             $tpl_vars{PREV_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
501             print STDERR $tpl_vars{PREV_DIR_FILES} ."\n";
502              
503             }
504             if ($neightbour_counter < scalar @neighbour_directories - 1) {
505             my $linktext = $neighbour_directories[$neightbour_counter+1];
506             if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder") {
507             $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder");
508             }
509             my %info = (
510             URL => "../".$neighbour_directories[$neightbour_counter+1],
511             LINK_NAME => "$linktext >>>",
512             DIR_FILES => "",
513             );
514             $tpl_vars{NEXT_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
515             print STDERR "next directory is " .$neighbour_directories[$neightbour_counter+1] ."\n";
516             }
517             }
518             $neightbour_counter++;
519             }
520             }
521              
522             if (-f $topdir . '.comment') {
523             my $comment_ref = get_comment($topdir . '.comment');
524             my %comment_vars;
525             $comment_vars{COMMENT} = $comment_ref->{COMMENT} . '
' if $comment_ref->{COMMENT};
526             $comment_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
527             $tpl_vars{DIRCOMMENT} = $templates{comment}->fill_in(HASH => \%comment_vars);
528             $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
529             } else {
530             $tpl_vars{DIRCOMMENT} = $templates{nocomment}->fill_in(HASH=>\%tpl_vars);
531             }
532              
533             if ($cgi->param('rss')) {
534             $tpl_vars{MAIN} = $templates{rss}->fill_in(HASH => \%tpl_vars);
535             $r->content_type('application/rss+xml');
536             } else {
537             $tpl_vars{MAIN} = $templates{index}->fill_in(HASH => \%tpl_vars);
538             $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
539             $r->content_type('text/html');
540             }
541              
542             $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
543              
544             if (!$::MP2) {
545             $r->send_http_header;
546             }
547              
548             $r->print($tpl_vars{MAIN});
549             return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
550              
551             }
552             else {
553              
554             # original size
555             if (defined($ENV{QUERY_STRING}) && $ENV{QUERY_STRING} eq 'orig') {
556             if ($r->dir_config('GalleryAllowOriginal') ? 1 : 0) {
557             $r->filename($filename);
558             return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
559             } else {
560             return $::MP2 ? Apache2::Const::FORBIDDEN() : Apache::Constants::FORBIDDEN();
561             }
562             }
563            
564             # Create cache dir if not existing
565             my @tmp = split (/\//, $filename);
566             my $picfilename = pop @tmp;
567             my $path = (join "/", @tmp)."/";
568             my $cache_path = cache_dir($r, 1);
569              
570             unless (-d $cache_path) {
571             unless (create_cache($r, $cache_path)) {
572             return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
573             }
574             }
575              
576             my ($orig_width, $orig_height, $type) = imgsize($filename);
577              
578             my $imageinfo = get_imageinfo($r, $filename, $type, $orig_width, $orig_height);
579              
580             my ($image_width, $width, $height, $original_size) = get_image_display_size($cgi, $r, $orig_width, $orig_height);
581              
582             my $cached = get_scaled_picture_name($filename, $image_width, $height);
583            
584             my $tpl_dir = $r->dir_config('GalleryTemplateDir');
585              
586             my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
587             picture => "$tpl_dir/showpicture.tpl",
588             navpicture => "$tpl_dir/navpicture.tpl",
589             info => "$tpl_dir/info.tpl",
590             scale => "$tpl_dir/scale.tpl",
591             scaleactive => "$tpl_dir/scaleactive.tpl",
592             orig => "$tpl_dir/orig.tpl",
593             refresh => "$tpl_dir/refresh.tpl",
594             interval => "$tpl_dir/interval.tpl",
595             intervalactive => "$tpl_dir/intervalactive.tpl",
596             slideshowisoff => "$tpl_dir/slideshowisoff.tpl",
597             slideshowoff => "$tpl_dir/slideshowoff.tpl",
598             pictureinfo => "$tpl_dir/pictureinfo.tpl",
599             nopictureinfo => "$tpl_dir/nopictureinfo.tpl",
600             });
601              
602             my %tpl_vars;
603              
604             my $resolution = (($image_width > $orig_width) && ($height > $orig_height)) ?
605             "$orig_width x $orig_height" : "$image_width x $height";
606              
607             $tpl_vars{TITLE} = "Viewing ".$r->uri()." at $image_width x $height";
608             $tpl_vars{META} = " ";
609             $tpl_vars{RESOLUTION} = $resolution;
610             $tpl_vars{MENU} = generate_menu($r);
611             $tpl_vars{SRC} = uri_escape(".cache/$cached", $escape_rule);
612             $tpl_vars{URI} = $r->uri();
613            
614             my $exif_mode = $r->dir_config('GalleryEXIFMode');
615             unless ($exif_mode) {
616             $exif_mode = 'namevalue';
617             }
618              
619             unless (opendir(DATADIR, $path)) {
620             show_error($r, 500, "Unable to access directory", "Unable to access directory $path");
621             return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
622             }
623             my @pictures = grep { /$img_pattern/i && ! -e "$path/$_" . ".ignore" } readdir (DATADIR);
624             closedir(DATADIR);
625             @pictures = gallerysort($r, @pictures);
626              
627             $tpl_vars{TOTAL} = scalar @pictures;
628              
629             my $prevpicture;
630             my $nextpicture;
631            
632             for (my $i=0; $i <= $#pictures; $i++) {
633             if ($pictures[$i] eq $picfilename) {
634              
635             $tpl_vars{NUMBER} = $i+1;
636              
637             $prevpicture = $pictures[$i-1];
638             my $displayprev = ($i>0 ? 1 : 0);
639              
640             if ($r->dir_config("GalleryWrapNavigation")) {
641             $prevpicture = $pictures[$i>0 ? $i-1 : $#pictures];
642             $displayprev = 1;
643             }
644             if ($prevpicture and $displayprev) {
645             my ($orig_width, $orig_height, $type) = imgsize($path.$prevpicture);
646             my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
647             my $imageinfo = get_imageinfo($r, $path.$prevpicture, $type, $orig_width, $orig_height);
648             my $cached = get_scaled_picture_name($path.$prevpicture, $thumbnailwidth, $thumbnailheight);
649             my %nav_vars;
650             $nav_vars{URL} = uri_escape($prevpicture, $escape_rule);
651             $nav_vars{FILENAME} = $prevpicture;
652             $nav_vars{WIDTH} = $width;
653             $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
654             $nav_vars{DIRECTION} = "« prev";
655             $nav_vars{ACCESSKEY} = "P";
656             $tpl_vars{BACK} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
657             }
658             else {
659             $tpl_vars{BACK} = " ";
660             }
661              
662             $nextpicture = $pictures[$i+1];
663             if ($r->dir_config("GalleryWrapNavigation")) {
664             $nextpicture = $pictures[$i == $#pictures ? 0 : $i+1];
665             }
666              
667             if ($nextpicture) {
668             my ($orig_width, $orig_height, $type) = imgsize($path.$nextpicture);
669             my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
670             my $imageinfo = get_imageinfo($r, $path.$nextpicture, $type, $thumbnailwidth, $thumbnailheight);
671             my $cached = get_scaled_picture_name($path.$nextpicture, $thumbnailwidth, $thumbnailheight);
672             my %nav_vars;
673             $nav_vars{URL} = uri_escape($nextpicture, $escape_rule);
674             $nav_vars{FILENAME} = $nextpicture;
675             $nav_vars{WIDTH} = $width;
676             $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
677             $nav_vars{DIRECTION} = "next »";
678             $nav_vars{ACCESSKEY} = "N";
679              
680             $tpl_vars{NEXT} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
681             $tpl_vars{NEXTURL} = uri_escape($nextpicture, $escape_rule);
682             }
683             else {
684             $tpl_vars{NEXT} = " ";
685             $tpl_vars{NEXTURL} = '#';
686             }
687             }
688             }
689              
690             my $foundcomment = 0;
691             if (-f $path . '/' . $picfilename . '.comment') {
692             my $comment_ref = get_comment($path . '/' . $picfilename . '.comment');
693             $foundcomment = 1;
694             $tpl_vars{COMMENT} = $comment_ref->{COMMENT} . '
' if $comment_ref->{COMMENT};
695             $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
696             } elsif ($r->dir_config('GalleryCommentExifKey')) {
697             my $comment = decode("utf8", $imageinfo->{$r->dir_config('GalleryCommentExifKey')});
698             $tpl_vars{COMMENT} = encode("iso-8859-1", $comment);
699             } else {
700             $tpl_vars{COMMENT} = '';
701             }
702              
703             my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
704             my $foundinfo = 0;
705             my $exifvalues;
706             foreach (@infos) {
707            
708             my ($human_key, $exif_key) = (split " => ")[0,1];
709             my $value = $imageinfo->{$human_key};
710             if (defined($value)) {
711              
712             $foundinfo = 1;
713              
714             if ($exif_mode eq 'namevalue') {
715             my %info_vars;
716             $info_vars{KEY} = $human_key;
717             $info_vars{VALUE} = $value;
718             $tpl_vars{INFO} .= $templates{info}->fill_in(HASH => \%info_vars);
719             }
720              
721             if ($exif_mode eq 'variables') {
722             $tpl_vars{"EXIF_".uc($exif_key)} = $value;
723             }
724              
725             if ($exif_mode eq 'values') {
726             $exifvalues .= "| ".$value." ";
727             }
728              
729             }
730              
731             }
732              
733             if ($exif_mode eq 'values') {
734             if (defined($exifvalues)) {
735             $tpl_vars{EXIFVALUES} = $exifvalues;
736             }
737             else {
738             $tpl_vars{EXIFVALUES} = "";
739             }
740             }
741              
742             if ($foundcomment and !$foundinfo) {
743             $tpl_vars{INFO} = "";
744             }
745              
746             if ($exif_mode ne 'namevalue') {
747             $tpl_vars{INFO} = "";
748             }
749              
750             if ($exif_mode eq 'namevalue' && $foundinfo or $foundcomment) {
751              
752             $tpl_vars{PICTUREINFO} = $templates{pictureinfo}->fill_in(HASH => \%tpl_vars);
753              
754             unless (defined($exifvalues)) {
755             $tpl_vars{EXIFVALUES} = "";
756             }
757              
758             }
759             else {
760             $tpl_vars{PICTUREINFO} = $templates{nopictureinfo}->fill_in(HASH => \%tpl_vars);
761             }
762              
763             # Fill in sizes and determine if any are smaller than the
764             # actual image. If they are, $scaleable=1
765             my $scaleable = 0;
766             my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
767             foreach my $size (@sizes) {
768             if ($size<=$original_size) {
769             my %sizes_vars;
770             $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
771             $sizes_vars{SIZE} = $size;
772             $sizes_vars{WIDTH} = $size;
773             if ($width == $size) {
774             $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
775             }
776             else {
777             $tpl_vars{SIZES} .= $templates{scale}->fill_in(HASH => \%sizes_vars);
778             }
779             $scaleable = 1;
780             }
781             }
782              
783             unless ($scaleable) {
784             my %sizes_vars;
785             $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
786             $sizes_vars{SIZE} = $original_size;
787             $sizes_vars{WIDTH} = $original_size;
788             $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
789             }
790              
791             $tpl_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
792              
793             if ($r->dir_config('GalleryAllowOriginal')) {
794             $tpl_vars{SIZES} .= $templates{orig}->fill_in(HASH => \%tpl_vars);
795             }
796              
797             my @slideshow_intervals = split (/ /, $r->dir_config('GallerySlideshowIntervals') ? $r->dir_config('GallerySlideshowIntervals') : '3 5 10 15 30');
798             foreach my $interval (@slideshow_intervals) {
799              
800             my %slideshow_vars;
801             $slideshow_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
802             $slideshow_vars{SECONDS} = $interval;
803             $slideshow_vars{WIDTH} = ($width > $height ? $width : $height);
804              
805             if ($cgi->param('slideshow') && $cgi->param('slideshow') == $interval and $nextpicture) {
806             $tpl_vars{SLIDESHOW} .= $templates{intervalactive}->fill_in(HASH => \%slideshow_vars);
807             }
808             else {
809              
810             $tpl_vars{SLIDESHOW} .= $templates{interval}->fill_in(HASH => \%slideshow_vars);
811              
812             }
813             }
814              
815             if ($cgi->param('slideshow') and $nextpicture) {
816              
817             $tpl_vars{SLIDESHOW} .= $templates{slideshowoff}->fill_in(HASH => \%tpl_vars);
818              
819             unless ((grep $cgi->param('slideshow') == $_, @slideshow_intervals)) {
820             show_error($r, 200, "Invalid interval", "Invalid slideshow interval choosen");
821             return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
822             }
823              
824             $tpl_vars{URL} = uri_escape($nextpicture, $escape_rule);
825             $tpl_vars{WIDTH} = ($width > $height ? $width : $height);
826             $tpl_vars{INTERVAL} = $cgi->param('slideshow');
827             $tpl_vars{META} .= $templates{refresh}->fill_in(HASH => \%tpl_vars);
828              
829             }
830             else {
831             $tpl_vars{SLIDESHOW} .= $templates{slideshowisoff}->fill_in(HASH => \%tpl_vars);
832             }
833              
834             $tpl_vars{MAIN} = $templates{picture}->fill_in(HASH => \%tpl_vars);
835             $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
836              
837             $r->content_type('text/html');
838             $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
839              
840             if (!$::MP2) {
841             $r->send_http_header;
842             }
843              
844             $r->print($tpl_vars{MAIN});
845             return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
846              
847             }
848              
849             }
850              
851             sub cache_dir {
852              
853             my ($r, $strip_filename) = @_;
854              
855             my $cache_root;
856              
857             unless ($r->dir_config('GalleryCacheDir')) {
858              
859             $cache_root = '/var/cache/www/';
860             if ($r->server->is_virtual) {
861             $cache_root = File::Spec->catdir($cache_root, $r->server->server_hostname);
862             } else {
863             $cache_root = File::Spec->catdir($cache_root, $r->location);
864             }
865              
866             } else {
867              
868             $cache_root = $r->dir_config('GalleryCacheDir');
869              
870             }
871              
872             # If the uri contains .cache we need to remove it
873             my $uri = $r->uri;
874             $uri =~ s/\.cache//;
875              
876             my (undef, $dirs, $filename) = File::Spec->splitpath($uri);
877             # We don't need a volume as this is a relative path
878              
879             if ($strip_filename) {
880             return(File::Spec->canonpath(File::Spec->catdir($cache_root, $dirs)));
881             } else {
882             return(File::Spec->canonpath(File::Spec->catfile($cache_root, $dirs, $filename)));
883             }
884             }
885              
886             sub create_cache {
887              
888             my ($r, $path) = @_;
889              
890             unless (mkdirhier ($path)) {
891             show_error($r, 500, $!, "Unable to create cache directory in $path: $!");
892             return 0;
893             }
894              
895             return 1;
896             }
897              
898             sub mkdirhier {
899              
900             my $dir = shift;
901              
902             unless (-d $dir) {
903              
904             unless (mkdir($dir, 0755)) {
905             my $parent = $dir;
906             $parent =~ s/\/[^\/]*$//;
907              
908             mkdirhier($parent);
909              
910             mkdir($dir, 0755);
911             }
912             }
913             }
914              
915             sub get_scaled_picture_name {
916              
917             my ($fullpath, $width, $height) = @_;
918              
919             my (undef, undef, $type) = imgsize($fullpath);
920              
921             my @dirs = split(/\//, $fullpath);
922             my $filename = pop(@dirs);
923             my $newfilename;
924              
925             if (grep $type eq $_, qw(PPM TIF GIF)) {
926             $newfilename = $width."x".$height."-".$filename;
927             # needs to be configurable
928             $newfilename =~ s/\.(\w+)$/-$1\.jpg/;
929             } else {
930             $newfilename = $width."x".$height."-".$filename;
931             }
932              
933             return $newfilename;
934            
935             }
936              
937             sub scale_picture {
938              
939             my ($r, $fullpath, $width, $height, $imageinfo) = @_;
940              
941             my @dirs = split(/\//, $fullpath);
942             my $filename = pop(@dirs);
943              
944             my ($orig_width, $orig_height, $type) = imgsize($fullpath);
945              
946             my $cache = cache_dir($r, 1);
947              
948             my $newfilename = get_scaled_picture_name($fullpath, $width, $height);
949              
950             if (($width > $orig_width) && ($height > $orig_height)) {
951             # Run it through the resize code anyway to get watermarks
952             $width = $orig_width;
953             $height = $orig_height;
954             }
955              
956             my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
957              
958             # Do we want to generate a new file in the cache?
959             my $scale = 1;
960              
961             if (-f $cache."/".$newfilename) {
962             $scale = 0;
963              
964             # Check to see if the image has changed
965             my $filestat = stat($fullpath);
966             my $cachestat = stat($cache."/".$newfilename);
967             if ($filestat->mtime >= $cachestat->mtime) {
968             $scale = 1;
969             }
970              
971             # Check to see if the .rotate file has been added or changed
972             if (-f $fullpath . ".rotate") {
973             my $rotatestat = stat($fullpath . ".rotate");
974             if ($rotatestat->mtime > $cachestat->mtime) {
975             $scale = 1;
976             }
977             }
978             # Check to see if the copyrightimage has been added or changed
979             if ($r->dir_config('GalleryCopyrightImage') && -f $r->dir_config('GalleryCopyrightImage')) {
980             unless ($width == $thumbnailwidth or $width == $thumbnailheight) {
981             my $copyrightstat = stat($r->dir_config('GalleryCopyrightImage'));
982             if ($copyrightstat->mtime > $cachestat->mtime) {
983             $scale = 1;
984             }
985             }
986             }
987              
988             }
989              
990             if ($scale) {
991              
992             my $newpath = $cache."/".$newfilename;
993             my $rotate = readfile_getnum($r, $imageinfo, $fullpath . ".rotate");
994             my $quality = $r->dir_config('GalleryQuality');
995              
996             if ($width == $thumbnailwidth or $width == $thumbnailheight) {
997              
998             resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, '', '', '', '', '', '');
999              
1000             } else {
1001              
1002             resizepicture($r, $fullpath, $newpath, $width, $height, $rotate,
1003             ($r->dir_config('GalleryCopyrightImage') ? $r->dir_config('GalleryCopyrightImage') : ''),
1004             ($r->dir_config('GalleryTTFDir') ? $r->dir_config('GalleryTTFDir') : ''),
1005             ($r->dir_config('GalleryCopyrightText') ? $r->dir_config('GalleryCopyrightText') : ''),
1006             ($r->dir_config('GalleryCopyrightColor') ? $r->dir_config('GalleryCopyrightColor') : ''),
1007             ($r->dir_config('GalleryTTFFile') ? $r->dir_config('GalleryTTFFile') : ''),
1008             ($r->dir_config('GalleryTTFSize') ? $r->dir_config('GalleryTTFSize') : ''),
1009             ($r->dir_config('GalleryCopyrightBackgroundColor') ? $r->dir_config('GalleryCopyrightBackgroundColor') : ''),
1010             $quality);
1011              
1012             }
1013             }
1014              
1015             return $newfilename;
1016              
1017             }
1018              
1019             sub get_thumbnailsize {
1020             my ($r, $orig_width, $orig_height) = @_;
1021              
1022             my $gallerythumbnailsize=$r->dir_config('GalleryThumbnailSize');
1023              
1024             if (defined($gallerythumbnailsize)) {
1025             warn("Invalid setting for GalleryThumbnailSize") unless
1026             $gallerythumbnailsize =~ /^\s*\d+\s*x\s*\d+\s*$/i;
1027             }
1028              
1029             my ($thumbnailwidth, $thumbnailheight) = split(/x/i, ($gallerythumbnailsize) ? $gallerythumbnailsize : "100x75");
1030              
1031             my $width = $thumbnailwidth;
1032             my $height = $thumbnailheight;
1033              
1034             # If the image is rotated, flip everything around.
1035             if (defined $r->dir_config('GalleryThumbnailSizeLS')
1036             and $r->dir_config('GalleryThumbnailSizeLS') eq '1'
1037             and $orig_width < $orig_height) {
1038            
1039             $width = $thumbnailheight;
1040             $height = $thumbnailwidth;
1041             }
1042              
1043             my $scale = ($orig_width ? $width/$orig_width : 1);
1044              
1045             if ($orig_height) {
1046             if ($orig_height * $scale > $thumbnailheight) {
1047             $scale = $height/$orig_height;
1048             $width = $orig_width * $scale;
1049             }
1050             }
1051              
1052             $height = $orig_height * $scale;
1053              
1054             $height = floor($height);
1055             $width = floor($width);
1056              
1057             return ($width, $height);
1058             }
1059              
1060             sub get_image_display_size {
1061             my ($cgi, $r, $orig_width, $orig_height) = @_;
1062              
1063             my $width = $orig_width;
1064              
1065             my $original_size=$orig_height;
1066             if ($orig_width>$orig_height) {
1067             $original_size=$orig_width;
1068             }
1069              
1070             # Check if the selected width is allowed
1071             my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
1072              
1073             my %cookies = fetch CGI::Cookie;
1074              
1075             if ($cgi->param('width')) {
1076             unless ((grep $cgi->param('width') == $_, @sizes) or ($cgi->param('width') == $original_size)) {
1077             show_error($r, 200, "Invalid width", "The specified width is invalid");
1078             return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
1079             }
1080              
1081             $width = $cgi->param('width');
1082             my $cookie = new CGI::Cookie(-name => 'GallerySize', -value => $width, -expires => '+6M');
1083             $r->headers_out->{'Set-Cookie'} = $cookie;
1084              
1085             } elsif ($cookies{'GallerySize'} && (grep $cookies{'GallerySize'}->value == $_, @sizes)) {
1086              
1087             $width = $cookies{'GallerySize'}->value;
1088              
1089             } else {
1090             $width = $sizes[0];
1091             }
1092              
1093             my $scale;
1094             my $image_width;
1095             if ($orig_width<$orig_height) {
1096             $scale = ($orig_height ? $width/$orig_height: 1);
1097             $image_width=$width*$orig_width/$orig_height;
1098             }
1099             else {
1100             $scale = ($orig_width ? $width/$orig_width : 1);
1101             $image_width = $width;
1102             }
1103              
1104             my $height = $orig_height * $scale;
1105              
1106             $image_width = floor($image_width);
1107             $width = floor($width);
1108             $height = floor($height);
1109              
1110             return ($image_width, $width, $height, $original_size);
1111             }
1112              
1113             sub get_imageinfo {
1114             my ($r, $file, $type, $width, $height) = @_;
1115             my $imageinfo = {};
1116             if ($type eq 'Data stream is not a known image file format') {
1117             # should never be reached, this is supposed to be handled outside of here
1118             log_error("Something was fishy with the type of the file $file\n");
1119             } else {
1120              
1121             # Some files, like TIFF, PNG, GIF do not have EXIF info
1122             # embedded but use .thm files instead.
1123             $imageinfo = get_imageinfo_from_thm_file($file, $width, $height);
1124              
1125             # If there is no .thm file and our file is a JPEG file we try to extract the EXIf
1126             # info using Image::Info
1127             unless (defined($imageinfo) && (grep $type eq $_, qw(JPG))) {
1128             # Only for files that natively keep the EXIF info in the same file
1129             $imageinfo = image_info($file);
1130             }
1131             }
1132              
1133             unless (defined($imageinfo->{width}) and defined($imageinfo->{height})) {
1134             $imageinfo->{width} = $width;
1135             $imageinfo->{height} = $height;
1136             }
1137              
1138             my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
1139             foreach (@infos) {
1140            
1141             my ($human_key, $exif_key) = (split " => ")[0,1];
1142             if (defined($exif_key) && defined($imageinfo->{$exif_key})) {
1143             my $value = "";
1144             if (ref($imageinfo->{$exif_key}) eq 'Image::TIFF::Rational') {
1145             $value = $imageinfo->{$exif_key}->as_string;
1146             }
1147             elsif (ref($imageinfo->{$exif_key}) eq 'ARRAY') {
1148             foreach my $element (@{$imageinfo->{$exif_key}}) {
1149             if (ref($element) eq 'ARRAY') {
1150             foreach (@{$element}) {
1151             $value .= $_ . ' ';
1152             }
1153             }
1154             elsif (ref($element) eq 'HASH') {
1155             $value .= "
{ ";
1156             foreach (sort keys %{$element}) {
1157             $value .= "$_ = " . $element->{$_} . ' ';
1158             }
1159             $value .= "} ";
1160             }
1161             else {
1162             $value .= $element;
1163             }
1164             $value .= ' ';
1165             }
1166             }
1167             else {
1168             my $exif_value = $imageinfo->{$exif_key};
1169             if ($human_key eq 'Flash' && $exif_value =~ m/\d/) {
1170             my %flashmodes = (
1171             "0" => "No",
1172             "1" => "Yes",
1173             "9" => "Yes",
1174             "16" => "No (Compulsory) Should be External Flash",
1175             "17" => "Yes (External)",
1176             "24" => "No",
1177             "25" => "Yes (Auto)",
1178             "73" => "Yes (Compulsory, Red Eye Reducing)",
1179             "89" => "Yes (Auto, Red Eye Reducing)"
1180             );
1181             $exif_value = defined $flashmodes{$exif_value} ? $flashmodes{$exif_value} : 'unknown flash mode';
1182             }
1183             $value = $exif_value;
1184             }
1185             if ($exif_key eq 'MeteringMode') {
1186             my $exif_value = $imageinfo->{$exif_key};
1187             if ($exif_value =~ /^\d+$/) {
1188             my %meteringmodes = (
1189             '0' => 'unknown',
1190             '1' => 'Average',
1191             '2' => 'CenterWeightedAverage',
1192             '3' => 'Spot',
1193             '4' => 'MultiSpot',
1194             '5' => 'Pattern',
1195             '6' => 'Partial',
1196             '255' => 'Other'
1197             );
1198             $exif_value = defined $meteringmodes{$exif_value} ? $meteringmodes{$exif_value} : 'unknown metering mode';
1199             }
1200             $value = $exif_value;
1201            
1202             }
1203             if ($exif_key eq 'LightSource') {
1204             my $exif_value = $imageinfo->{$exif_key};
1205             if ($exif_value =~ /^\d+$/) {
1206             my %lightsources = (
1207             '0' => 'unknown',
1208             '1' => 'Daylight',
1209             '2' => 'Fluorescent',
1210             '3' => 'Tungsten (incandescent light)',
1211             '4' => 'Flash',
1212             '9' => 'Fine weather',
1213             '10' => 'Cloudy weather',
1214             '11' => 'Shade',
1215             '12' => 'Daylight fluorescent',
1216             '13' => 'Day white fluorescent',
1217             '14' => 'Cool white fluorescent',
1218             '15' => 'White fluorescent',
1219             '17' => 'Standard light A',
1220             '18' => 'Standard light B',
1221             '19' => 'Standard light C',
1222             '20' => 'D55',
1223             '21' => 'D65',
1224             '22' => 'D75',
1225             '23' => 'D50',
1226             '24' => 'ISO studio tungsten',
1227             '255' => 'other light source'
1228             );
1229             $exif_value = defined $lightsources{$exif_value} ? $lightsources{$exif_value} : 'unknown light source';
1230             }
1231             $value = $exif_value;
1232             }
1233             if ($exif_key eq 'FocalLength') {
1234             if ($value =~ /^(\d+)\/(\d+)$/) {
1235             $value = eval { $1 / $2 };
1236             if ($@) {
1237             $value = $@;
1238             } else {
1239             $value = int($value + 0.5) . "mm";
1240              
1241             }
1242             }
1243             }
1244             if ($exif_key eq 'ShutterSpeedValue') {
1245             if ($value =~ /^((?:\-)?\d+)\/(\d+)$/) {
1246             $value = eval { $1 / $2 };
1247             if ($@) {
1248             $value = $@;
1249             } else {
1250             eval {
1251             $value = 1/(exp($value*log(2)));
1252             if ($value < 1) {
1253             $value = "1/" . (int((1/$value)));
1254             } else {
1255             $value = int($value*10)/10;
1256             }
1257             };
1258             if ($@) {
1259             $value = $@;
1260             } else {
1261             $value = $value . " sec";
1262             }
1263             }
1264             }
1265             }
1266             if ($exif_key eq 'ApertureValue') {
1267             if ($value =~ /^(\d+)\/(\d+)$/) {
1268             $value = eval { $1 / $2 };
1269             if ($@) {
1270             $value = $@;
1271             } else {
1272             # poor man's rounding
1273             $value = int(exp($value*log(2)*0.5)*10)/10;
1274             $value = "f" . $value;
1275             }
1276             }
1277             }
1278             if ($exif_key eq 'FNumber') {
1279             if ($value =~ /^(\d+)\/(\d+)$/) {
1280             $value = eval { $1 / $2 };
1281             if ($@) {
1282             $value = $@;
1283             } else {
1284             $value = int($value*10+0.5)/10;
1285             $value = "f" . $value;
1286             }
1287             }
1288             }
1289             $imageinfo->{$human_key} = $value;
1290             }
1291             }
1292              
1293             if ($r->dir_config('GalleryUseFileDate') &&
1294             ($r->dir_config('GalleryUseFileDate') eq '1'
1295             || !$imageinfo->{"Picture Taken"} )) {
1296              
1297             my $st = stat($file);
1298             $imageinfo->{"DateTimeOriginal"} = $imageinfo->{"Picture Taken"} = scalar localtime($st->mtime) if $st;
1299             }
1300              
1301             return $imageinfo;
1302             }
1303              
1304             sub get_imageinfo_from_thm_file {
1305              
1306             my ($file, $width, $height) = @_;
1307              
1308             my $imageinfo = undef;
1309             # Windows based file extensions are often .THM, so check
1310             # for both .thm and .THM
1311             my $unix_file = $file;
1312             my $windows_file = $file;
1313             $unix_file =~ s/\.(\w+)$/.thm/;
1314             $windows_file =~ s/\.(\w+)$/.THM/;
1315              
1316             if (-e $unix_file && -f $unix_file && -r $unix_file) {
1317             $imageinfo = image_info($unix_file);
1318             $imageinfo->{width} = $width;
1319             $imageinfo->{height} = $height;
1320             }
1321             elsif (-e $windows_file && -f $windows_file && -r $windows_file) {
1322             $imageinfo = image_info($windows_file);
1323             $imageinfo->{width} = $width;
1324             $imageinfo->{height} = $height;
1325             }
1326              
1327             return $imageinfo;
1328             }
1329              
1330              
1331             sub readfile_getnum {
1332             my ($r, $imageinfo, $filename) = @_;
1333              
1334             my $rotate = 0;
1335              
1336             print STDERR "orientation: ".$imageinfo->{Orientation}."\n";
1337             # Check to see if the image contains the Orientation EXIF key,
1338             # but allow user to override using rotate
1339             if (!defined($r->dir_config("GalleryAutoRotate"))
1340             || $r->dir_config("GalleryAutoRotate") eq "1") {
1341             if (defined($imageinfo->{Orientation})) {
1342             print STDERR $imageinfo->{Orientation}."\n";
1343             if ($imageinfo->{Orientation} eq 'right_top') {
1344             $rotate=1;
1345             }
1346             elsif ($imageinfo->{Orientation} eq 'left_bot') {
1347             $rotate=3;
1348             }
1349             }
1350             }
1351              
1352             if (open(FH, "<$filename")) {
1353             my $temp = ;
1354             chomp($temp);
1355             close(FH);
1356             unless ($temp =~ /^\d$/) {
1357             $rotate = 0;
1358             }
1359             unless ($temp == 1 || $temp == 2 || $temp == 3) {
1360             $rotate = 0;
1361             }
1362             $rotate = $temp;
1363             }
1364              
1365             return $rotate;
1366             }
1367              
1368             sub get_filecontent {
1369             my $file = shift;
1370             open(FH, $file) or return undef;
1371             my $content = '';
1372             {
1373             local $/;
1374             $content = ;
1375             }
1376             close(FH);
1377             return $content;
1378             }
1379              
1380             sub get_comment {
1381             my $filename = shift;
1382             my $comment_ref = {};
1383             $comment_ref->{TITLE} = undef;
1384             $comment_ref->{COMMENT} = '';
1385              
1386             open(FH, $filename) or return $comment_ref;
1387             my $title = ;
1388             if ($title =~ m/^TITLE: (.*)$/) {
1389             chomp($comment_ref->{TITLE} = $1);
1390             }
1391             else {
1392             $comment_ref->{COMMENT} = $title;
1393             }
1394              
1395             while () {
1396             chomp;
1397             $comment_ref->{COMMENT} .= $_;
1398             }
1399             close(FH);
1400              
1401             return $comment_ref;
1402             }
1403              
1404             sub show_error {
1405              
1406             my ($r, $statuscode, $errortitle, $error) = @_;
1407              
1408             my $tpl = $r->dir_config('GalleryTemplateDir');
1409              
1410             my %templates = create_templates({layout => "$tpl/layout.tpl",
1411             error => "$tpl/error.tpl",
1412             });
1413              
1414             my %tpl_vars;
1415             $tpl_vars{TITLE} = "Error! $errortitle";
1416             $tpl_vars{META} = "";
1417             $tpl_vars{ERRORTITLE} = "Error! $errortitle";
1418             $tpl_vars{ERROR} = $error;
1419              
1420             $tpl_vars{MAIN} = $templates{error}->fill_in(HASH => \%tpl_vars);
1421              
1422             $tpl_vars{PAGE} = $templates{layout}->fill_in(HASH => \%tpl_vars);
1423              
1424             $r->status($statuscode);
1425             $r->content_type('text/html');
1426              
1427             $r->print($tpl_vars{PAGE});
1428              
1429             }
1430              
1431             sub generate_menu {
1432              
1433             my $r = shift;
1434              
1435             my $root_text = (defined($r->dir_config('GalleryRootText')) ? $r->dir_config('GalleryRootText') : "root:" );
1436             my $root_path = (defined($r->dir_config('GalleryRootPath')) ? $r->dir_config('GalleryRootPath') : "" );
1437              
1438             my $subr = $r->lookup_uri($r->uri);
1439             my $filename = $subr->filename;
1440              
1441             my @links = split (/\//, $r->uri);
1442             my $uri = $r->uri;
1443             $uri =~ s/^$root_path//g;
1444              
1445             @links = split (/\//, $uri);
1446              
1447             # Get the full path of the base directory
1448             my $dirname;
1449             {
1450             my @direlem = split (/\//, $filename);
1451             for my $i ( 0 .. ( scalar(@direlem) - scalar(@links) ) ) {
1452             $dirname .= shift(@direlem) . '/';
1453             }
1454             chop $dirname;
1455             }
1456              
1457             my $picturename;
1458             if (-f $filename) {
1459             $picturename = pop(@links);
1460             }
1461              
1462             if ($r->uri eq $root_path) {
1463             return qq{ $root_text };
1464             }
1465              
1466             my $menu;
1467             my $menuurl = $root_path;
1468             foreach my $link (@links) {
1469              
1470             $menuurl .= $link."/";
1471             my $linktext = $link;
1472             unless (length($link)) {
1473             $linktext = "$root_text ";
1474             }
1475             else {
1476            
1477             $dirname = File::Spec->catdir($dirname, $link);
1478              
1479             if (-e $dirname . ".folder") {
1480             $linktext = get_filecontent($dirname . ".folder");
1481             }
1482             }
1483              
1484             if ("$root_path$uri" eq $menuurl) {
1485             $menu .= "$linktext / ";
1486             }
1487             else {
1488             $menu .= "$linktext / ";
1489             }
1490              
1491             }
1492              
1493             if (-f $filename) {
1494             $menu .= $picturename;
1495             }
1496             else {
1497              
1498             if ($r->dir_config('GallerySelectionMode') && $r->dir_config('GallerySelectionMode') eq '1') {
1499             $menu .= "
1500             $menu .= "?select=1\">[select] ";
1501             }
1502             }
1503              
1504             return $menu;
1505             }
1506              
1507             sub resizepicture {
1508             my ($r, $infile, $outfile, $x, $y, $rotate, $copyrightfile, $GalleryTTFDir, $GalleryCopyrightText, $text_color, $GalleryTTFFile, $GalleryTTFSize, $GalleryCopyrightBackgroundColor, $quality) = @_;
1509              
1510             # Load image
1511             my $image = Image::Imlib2->load($infile) or warn("Unable to open file $infile, $!");
1512              
1513             # Scale image
1514             $image=$image->create_scaled_image($x, $y) or warn("Unable to scale image $infile. Are you running out of memory?");
1515              
1516             # Rotate image
1517             if ($rotate != 0) {
1518             $image->image_orientate($rotate);
1519             }
1520              
1521             # blend copyright image onto image
1522             if ($copyrightfile ne '') {
1523             if (-f $copyrightfile and (my $logo=Image::Imlib2->load($copyrightfile))) {
1524             my $x = $image->get_width();
1525             my $y = $image->get_height();
1526             my $logox = $logo->get_width();
1527             my $logoy = $logo->get_height();
1528             $image->blend($logo, 0, 0, 0, $logox, $logoy, $x-$logox, $y-$logoy, $logox, $logoy);
1529             }
1530             else {
1531             log_error("GalleryCopyrightImage $copyrightfile was not found");
1532             }
1533             }
1534              
1535             if ($GalleryTTFDir && $GalleryCopyrightText && $GalleryTTFFile && $text_color) {
1536             if (!-d $GalleryTTFDir) {
1537              
1538             log_error("GalleryTTFDir $GalleryTTFDir is not a dir\n");
1539              
1540             } elsif ($GalleryCopyrightText eq '') {
1541              
1542             log_error("GalleryCopyrightText is empty. No text inserted to picture\n");
1543              
1544             } elsif (!-e "$GalleryTTFDir/$GalleryTTFFile") {
1545              
1546             log_error("GalleryTTFFile $GalleryTTFFile was not found\n");
1547              
1548             } else {
1549            
1550             $GalleryTTFFile =~ s/\.TTF$//i;
1551             $image->add_font_path("$GalleryTTFDir");
1552              
1553             $image->load_font("$GalleryTTFFile/$GalleryTTFSize");
1554             my($text_x, $text_y) = $image->get_text_size("$GalleryCopyrightText");
1555             my $x = $image->get_width();
1556             my $y = $image->get_height();
1557              
1558             my $offset = 3;
1559              
1560             if (($text_x < $x - $offset) && ($text_y < $y - $offset)) {
1561             if ($GalleryCopyrightBackgroundColor =~ /^\d+,\d+,\d+,\d+$/) {
1562             my ($br_val, $bg_val, $bb_val, $ba_val) = split (/,/, $GalleryCopyrightBackgroundColor);
1563             $image->set_colour($br_val, $bg_val, $bb_val, $ba_val);
1564             $image->fill_rectangle ($x-$text_x-$offset, $y-$text_y-$offset, $text_x, $text_y);
1565             }
1566             my ($r_val, $g_val, $b_val, $a_val) = split (/,/, $text_color);
1567             $image->set_colour($r_val, $g_val, $b_val, $a_val);
1568             $image->draw_text($x-$text_x-$offset, $y-$text_y-$offset, "$GalleryCopyrightText");
1569             } else {
1570             log_error("Text is to big for the picture.\n");
1571             }
1572             }
1573             }
1574              
1575             if ($quality && $quality =~ m/^\d+$/) {
1576             $image->set_quality($quality);
1577             }
1578              
1579             $image->save($outfile);
1580              
1581             }
1582              
1583             sub gallerysort {
1584             my $r=shift;
1585             my @files=@_;
1586             my $sortby = $r->dir_config('GallerySortBy');
1587             my $filename=$r->lookup_uri($r->uri)->filename;
1588             $filename=(File::Spec->splitpath($filename))[1] if (-f $filename);
1589             if ($sortby && $sortby =~ m/^(size|atime|mtime|ctime)$/) {
1590             @files = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$sortby()." $_", @files));
1591             } else {
1592             @files = sort @files;
1593             }
1594             return @files;
1595             }
1596              
1597             # Create Text::Template objects used by Apache::Gallery. Takes a
1598             # hashref of template_name, template_filename pairs, and returns a
1599             # list of template_name, texttemplate_object pairs.
1600             sub create_templates {
1601             my $templates = shift;
1602              
1603             # This routine is called whenever a template has an error. Prints
1604             # the error to STDERR and sticks the error in the output
1605             sub tt_broken {
1606             my %args = @_;
1607             # Pull out the name and filename from the arg option [see
1608             # Text::Template for details]
1609             @args{qw(name file)} = @{$args{arg}};
1610             print STDERR qq(Template $args{name} ("$args{file}") is broken: $args{error});
1611             # Don't include the file name in the output, as the user can see this.
1612             return qq();
1613             }
1614              
1615              
1616              
1617             my %texttemplate_objects;
1618              
1619             for my $template_name (keys %$templates) {
1620             my $tt_obj = Text::Template->new(TYPE => 'FILE',
1621             SOURCE => $$templates{$template_name},
1622             BROKEN => \&tt_broken,
1623             BROKEN_ARG => [$template_name, $$templates{$template_name}],
1624             )
1625             or die "Unable to create new Text::Template object for $template_name: $Text::Template::ERROR";
1626             $texttemplate_objects{$template_name} = $tt_obj;
1627             }
1628             return %texttemplate_objects;
1629             }
1630              
1631             sub log_error {
1632             if ($::MP2) {
1633             Apache2::RequestUtil->request->log_error(shift());
1634             } else {
1635             Apache->request->log_error(shift());
1636             }
1637             }
1638              
1639             1;
1640              
1641             =head1 NAME
1642              
1643             Apache::Gallery - mod_perl handler to create an image gallery
1644              
1645             =head1 SYNOPSIS
1646              
1647             See the INSTALL file in the distribution for installation instructions.
1648              
1649             =head1 DESCRIPTION
1650              
1651             Apache::Gallery creates an thumbnail index of each directory and allows
1652             viewing pictures in different resolutions. Pictures are resized on the
1653             fly and cached. The gallery can be configured and customized in many ways
1654             and a custom copyright image can be added to all the images without
1655             modifying the original.
1656              
1657             =head1 CONFIGURATION
1658              
1659             In your httpd.conf you set the global options for the gallery. You can
1660             also override each of the options in .htaccess files in your gallery
1661             directories.
1662              
1663             The options are set in the httpd.conf/.htaccess file using the syntax:
1664             B
1665              
1666             Example: B
1667              
1668             =over 4
1669              
1670             =item B
1671              
1672             Some cameras, like the Canon G3, can detect the orientation of a
1673             the pictures you take and will save this information in the
1674             'Orientation' EXIF field. Apache::Gallery will then automatically
1675             rotate your images.
1676              
1677             This behavior is default but can be disabled by setting GalleryAutoRotate
1678             to 0.
1679              
1680             =item B
1681              
1682             Directory where Apache::Gallery should create its cache with scaled
1683             pictures. The default is /var/cache/www/ . Here, a directory for each
1684             virtualhost or location will be created automatically. Make sure your
1685             webserver has write access to the CacheDir.
1686              
1687             =item B
1688              
1689             Full path to the directory where you placed the templates. This option
1690             can be used both in your global configuration and in .htaccess files,
1691             this way you can have different layouts in different parts of your
1692             gallery.
1693              
1694             No default value, this option is required.
1695              
1696             =item B
1697              
1698             With this option you can define which EXIF information you would like
1699             to present from the image. The format is: ' KeyInEXIF,
1700             MyOtherName => OtherKeyInEXIF'
1701              
1702             Examples of keys: B, B, B,
1703             and B
1704              
1705             You can view all the keys from the EXIF header using this perl-oneliner:
1706              
1707             perl C<-e> 'use Data::Dumper; use Image::Info qw(image_info); print Dumper(image_info(shift));' filename.jpg
1708              
1709             Default is: 'Picture Taken => DateTimeOriginal, Flash => Flash'
1710              
1711             =item B
1712              
1713             Defines which widths images can be scaled to. Images cannot be
1714             scaled to other widths than the ones you define with this option.
1715              
1716             The default is '640 800 1024 1600'
1717              
1718             =item B
1719              
1720             Defines the width and height of the thumbnail images.
1721              
1722             Defaults to '100x75'
1723              
1724             =item B
1725              
1726             If set to '1', B is the long and the short side of
1727             the thumbnail image instead of the width and height.
1728              
1729             Defaults to '0'.
1730              
1731             =item B
1732              
1733             Image you want to blend into your images in the lower right
1734             corner. This could be a transparent png saying "copyright
1735             my name 2001".
1736              
1737             Optional.
1738              
1739             =item B
1740              
1741             Make the navigation in the picture view wrap around (So Next
1742             at the end displays the first picture, etc.)
1743              
1744             Set to 1 or 0, default is 0
1745              
1746             =item B
1747              
1748             Allow the user to download the Original picture without
1749             resizing or putting the CopyrightImage on it.
1750              
1751             Set to 1 or 0, default is 0
1752              
1753             =item B
1754              
1755             With this option you can configure which intervals can be selected for
1756             a slideshow. The default is '3 5 10 15 30'
1757              
1758             =item B
1759              
1760             Instead of the default filename ordering you can sort by any
1761             stat attribute. For example size, atime, mtime, ctime.
1762              
1763             =item B
1764              
1765             Set this variable to sort directories differently than other items,
1766             can be set to size, atime, mtime and ctime; setting any other value
1767             will revert to sorting by name.
1768              
1769             =item B
1770              
1771             Cache EXIF data using Memoize - this will make Apache::Gallery faster
1772             when many people access the same images, but it will also cache EXIF
1773             data until the current Apache child dies.
1774              
1775             =item B
1776              
1777             Set this option to 1 to make A::G show the files timestamp
1778             instead of the EXIF value for "Picture taken".
1779              
1780             =item B
1781              
1782             Enable the selection mode. Select images with checkboxes and
1783             get a list of filenames.
1784              
1785             =item B
1786              
1787             You can choose how Apache::Gallery should display EXIF info
1788             from your images.
1789              
1790             The default setting is 'namevalue'. This setting will make
1791             Apache::Gallery print out the names and values of the EXIF values
1792             you configure with GalleryInfo. The information will be parsed into
1793             $INFO in pictureinfo.tpl.
1794              
1795             You can also set it to 'values' which will make A::G parse
1796             the configured values into the var $EXIFVALUES as 'value | value | value'
1797              
1798             If you set this option to 'variables' the items you configure in GalleryInfo
1799             will be available to your templates as $EXIF_ (in all uppercase).
1800             That means that with the default setting "Picture Taken => DateTimeOriginal,
1801             Flash => Flash" you will have the variables $EXIF_DATETIMEORIGINAL and
1802             $EXIF_FLASH available to your templates. You can place them
1803             anywhere you want.
1804              
1805             =item B
1806              
1807             Change the location of gallery root. The default is ""
1808              
1809             =item B
1810              
1811             Change the name that appears as the root element in the menu. The
1812             default is "root:"
1813              
1814             =item B
1815              
1816             This options controls how many thumbnails should be displayed in a
1817             page. It requires $BROWSELINKS to be in the index.tpl template file.
1818              
1819             =item B
1820              
1821             Pattern matching the files you want Apache::Gallery to view in the
1822             index as thumbnails.
1823              
1824             The default is '\.(jpe?g|png|tiff?|ppm)$'
1825              
1826             =item B
1827              
1828             Pattern matching the files you want Apache::Gallery to view in the index
1829             as normal files. All other filetypes will still be served by Apache::Gallery
1830             but are not visible in the index.
1831              
1832             The default is '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
1833              
1834             =item B
1835              
1836             To use the GalleryCopyrightText feature you must set this option to the
1837             directory where your True Type fonts are stored. No default is set.
1838              
1839             Example:
1840              
1841             PerlSetVar GalleryTTFDir '/usr/share/fonts/'
1842              
1843             =item B
1844              
1845             To use the GalleryCopyrightText feature this option must be set to the
1846             name of the True Type font you wish to use. Example:
1847              
1848             PerlSetVar GalleryTTFFile 'verdanab.ttf'
1849              
1850             =item B
1851              
1852             Configure the size of the CopyrightText that will be inserted as
1853             copyright notice in the corner of your pictures.
1854              
1855             Example:
1856              
1857             PerlSetVar GalleryTTFSize '10'
1858              
1859             =item B
1860              
1861             The text that will be inserted as copyright notice.
1862              
1863             Example:
1864              
1865             PerlSetVar GalleryCopyrightText '(c) Michael Legart'
1866              
1867             =item B
1868              
1869             The text color of your copyright notice.
1870              
1871             Examples:
1872              
1873             White:
1874             PerlSetVar GalleryCopyrightColor '255,255,255,255'
1875              
1876             Black:
1877             PerlSetVar GalleryCopyrightColor '0,0,0,255'
1878              
1879             Red:
1880             PerlSetVar GalleryCopyrightColor '255,0,0,255'
1881              
1882             Green:
1883             PerlSetVar GalleryCopyrightColor '0,255,0,255'
1884              
1885             Blue:
1886             PerlSetVar GalleryCopyrightColor '0,0,255,255'
1887              
1888             Transparent orange:
1889             PerlSetVar GalleryCopyrightColor '255,127,0,127'
1890              
1891             =item B
1892              
1893             The background-color of a GalleryCopyrightText
1894              
1895             r,g,b,a - for examples, see GalleryCopyrightColor
1896              
1897             =item B
1898              
1899             The quality (1-100) of scaled images
1900              
1901             This setting affects the quality of the scaled images.
1902             Set this to a low number to reduce the size of the scaled images.
1903             Remember to clear out your cache if you change this setting.
1904             Quality seems to default to 75, at least in the jpeg and png loader code in
1905             Imlib2 1.1.0.
1906              
1907             Examples:
1908              
1909             Quality at 50:
1910             PerlSetVar GalleryQuality '50'
1911              
1912             =item B
1913              
1914             Set this option to 1 to convert underscores to spaces in the listing
1915             of directory and file names, as well as in the alt attribute for HTML
1916             tags.
1917              
1918             =back
1919              
1920             =over 4
1921              
1922             =item B
1923              
1924             Set this option to e.g. ImageDescription to use this field as comments
1925             for images.
1926              
1927             =item B
1928              
1929             Set this option to 1 to enable generation of a media RSS feed. This
1930             can be used e.g. together with the PicLens plugin from http://piclens.com
1931              
1932             =back
1933              
1934             =head1 FEATURES
1935              
1936             =over 4
1937              
1938             =item B
1939              
1940             Some cameras, like the Canon G3, detects the orientation of a picture
1941             and adds this info to the EXIF header. Apache::Gallery detects this
1942             and automatically rotates images with this info.
1943              
1944             If your camera does not support this, you can rotate the images
1945             manually, This can also be used to override the rotate information
1946             from a camera that supports that. You can also disable this behavior
1947             with the GalleryAutoRotate option.
1948              
1949             To use this functionality you have to create file with the name of the
1950             picture you want rotated appended with ".rotate". The file should include
1951             a number where these numbers are supported:
1952              
1953             "1", rotates clockwise by 90 degree
1954             "2", rotates clockwise by 180 degrees
1955             "3", rotates clockwise by 270 degrees
1956              
1957             So if we want to rotate "Picture1234.jpg" 90 degrees clockwise we would
1958             create a file in the same directory called "Picture1234.jpg.rotate" with
1959             the number 1 inside of it.
1960              
1961             =item B
1962              
1963             To ignore a directory or a file (of any kind, not only images) you
1964             create a .ignore file.
1965              
1966             =item B
1967              
1968             To include comments for a directory you create a .comment
1969             file where the first line can contain "TITLE: New title" which
1970             will be the title of the page, and a comment on the following
1971             lines.
1972             To include comments for each picture you create files called
1973             picture.jpg.comment where the first line can contain "TITLE: New
1974             title" which will be the title of the page, and a comment on the
1975             following lines.
1976              
1977             Example:
1978              
1979             TITLE: This is the new title of the page
1980             And this is the comment.
1981             And this is line two of the comment.
1982              
1983             The visible name of the folder is by default identical to the name of
1984             the folder, but can be changed by creating a file .folder
1985             with the visible name of the folder.
1986              
1987             It is also possible to set GalleryCommentExifKey to the name of an EXIF
1988             field containing the comment, e.g. ImageDescription. The EXIF comment is
1989             overridden by the .comment file if it exists.
1990              
1991             =back
1992              
1993             =head1 DEPENDENCIES
1994              
1995             =over 4
1996              
1997             =item B
1998              
1999             =item B
2000              
2001             =item B
2002              
2003             =item B
2004              
2005             =item B
2006              
2007             =item B
2008              
2009             =item B
2010              
2011             =item B
2012             (ie, XFree86)
2013              
2014             =item B
2015             Remember the -dev package when using rpm, deb or other package formats!
2016              
2017             =back
2018              
2019             =head1 AUTHOR
2020              
2021             Michael Legart
2022              
2023             =head1 COPYRIGHT AND LICENSE
2024              
2025             Copyright (C) 2001-2011 Michael Legart
2026              
2027             Templates designed by Thomas Kjaer
2028              
2029             Apache::Gallery is free software and is released under the Artistic License.
2030             See B for details.
2031              
2032             The video icons are from the GNOME project. B
2033              
2034             =head1 THANKS
2035              
2036             Thanks to Thomas Kjaer for templates and design of B
2037             Thanks to Thomas Eibner and other for patches. (See the Changes file)
2038              
2039             =head1 SEE ALSO
2040              
2041             L, L, L, L,
2042             L, and L.
2043              
2044             =cut