"Fossies" - the Fresh Open Source Software Archive 
Member "Apache-Gallery-1.0.2/lib/Apache/Gallery.pm" (8 Jun 2011, 58965 Bytes) of package /linux/www/old/Apache-Gallery-1.0.2.tar.gz:
The requested HTML page contains a <FORM> tag that is unusable on "Fossies" in "automatic" (rendered) mode so that page is shown as HTML source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
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 use strict;
7
8 use vars qw($VERSION);
9
10 $VERSION = "1.0.2";
11
12 BEGIN {
13
14 if (exists($ENV{MOD_PERL_API_VERSION})
15 and ($ENV{MOD_PERL_API_VERSION}==2)) {
16 require mod_perl2;
17 if ($mod_perl::VERSION >= 1.99 && $mod_perl::VERSION < 2.0) {
18 die "mod_perl 2.0.0 or later is now required";
19 }
20 require Apache2::ServerRec;
21 require Apache2::RequestRec;
22 require Apache2::Log;
23 require APR::Table;
24 require Apache2::RequestIO;
25 require Apache2::SubRequest;
26 require Apache2::Const;
27
28 Apache2::Const->import(-compile => 'OK','DECLINED','FORBIDDEN','NOT_FOUND','HTTP_NOT_MODIFIED');
29
30 $::MP2 = 1;
31 } else {
32 require mod_perl;
33
34 require Apache;
35 require Apache::Constants;
36 require Apache::Request;
37
38 Apache::Constants->import('OK','DECLINED','FORBIDDEN','NOT_FOUND');
39 $::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 "<br />\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} = '<link rel="alternate" href="?rss=1" type="application/rss+xml" title="" id="gallery" />';
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?'<form method="post">':'';
243 $tpl_vars{FORM_END} = $select_mode?'<input type="submit" name="Get list" value="Get list"></form>':'';
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 .= "<a href=\"?start=$from\">$from - ".$to."</a> ";
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 <http://bugs.debian.org/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 <http://bugs.debian.org/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 <http://bugs.debian.org/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 <http://bugs.debian.org/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 <http://bugs.debian.org/348724>
421 # HTML <img> 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?'<input type="checkbox" name="selection" value="'.$file.'"> ':'',);
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 <http://bugs.debian.org/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} . '<br />' 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} = "« <u>p</u>rev";
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} = "<u>n</u>ext »";
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} . '<br />' 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 .= "<br />{ ";
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 = <FH>;
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 = <FH>;
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 = <FH>;
1388 if ($title =~ m/^TITLE: (.*)$/) {
1389 chomp($comment_ref->{TITLE} = $1);
1390 }
1391 else {
1392 $comment_ref->{COMMENT} = $title;
1393 }
1394
1395 while (<FH>) {
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{ <a href="$root_path">$root_text</a> };
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 .= "<a href=\"".uri_escape($menuurl, $escape_rule)."\">$linktext</a> / ";
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 .= "<a href=\"".uri_escape($menuurl, $escape_rule);
1500 $menu .= "?select=1\">[select]</a> ";
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(<!-- Template $args{name} is broken: $args{error} -->);
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<PerlSetVar OptionName 'value'>
1665
1666 Example: B<PerlSetVar GalleryCacheDir '/var/cache/www/'>
1667
1668 =over 4
1669
1670 =item B<GalleryAutoRotate>
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<GalleryCacheDir>
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<GalleryTemplateDir>
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<GalleryInfo>
1697
1698 With this option you can define which EXIF information you would like
1699 to present from the image. The format is: '<MyName => KeyInEXIF,
1700 MyOtherName => OtherKeyInEXIF'
1701
1702 Examples of keys: B<ShutterSpeedValue>, B<ApertureValue>, B<SubjectDistance>,
1703 and B<Camera>
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<GallerySizes>
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<GalleryThumbnailSize>
1719
1720 Defines the width and height of the thumbnail images.
1721
1722 Defaults to '100x75'
1723
1724 =item B<GalleryThumbnailSizeLS>
1725
1726 If set to '1', B<GalleryThumbnailSize> 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<GalleryCopyrightImage>
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<GalleryWrapNavigation>
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<GalleryAllowOriginal>
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<GallerySlideshowIntervals>
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<GallerySortBy>
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<GalleryDirSortBy>
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<GalleryMemoize>
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<GalleryUseFileDate>
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<GallerySelectionMode>
1781
1782 Enable the selection mode. Select images with checkboxes and
1783 get a list of filenames.
1784
1785 =item B<GalleryEXIFMode>
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_<KEYNAME> (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<GalleryRootPath>
1806
1807 Change the location of gallery root. The default is ""
1808
1809 =item B<GalleryRootText>
1810
1811 Change the name that appears as the root element in the menu. The
1812 default is "root:"
1813
1814 =item B<GalleryMaxThumbnailsPerPage>
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<GalleryImgFile>
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<GalleryDocFile>
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<GalleryTTFDir>
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<GalleryTTFFile>
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<GalleryTTFSize>
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<GalleryCopyrightText>
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<GalleryCopyrightColor>
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<GalleryCopyrightBackgroundColor>
1892
1893 The background-color of a GalleryCopyrightText
1894
1895 r,g,b,a - for examples, see GalleryCopyrightColor
1896
1897 =item B<GalleryQuality>
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<GalleryUnderscoresToSpaces>
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 <img> tags.
1917
1918 =back
1919
1920 =over 4
1921
1922 =item B<GalleryCommentExifKey>
1923
1924 Set this option to e.g. ImageDescription to use this field as comments
1925 for images.
1926
1927 =item B<GalleryEnableMediaRss>
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<Rotate images>
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<Ignore directories/files>
1962
1963 To ignore a directory or a file (of any kind, not only images) you
1964 create a <directory|file>.ignore file.
1965
1966 =item B<Comments>
1967
1968 To include comments for a directory you create a <directory>.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.<br />
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 <directory>.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<Perl 5>
1998
1999 =item B<Apache with mod_perl>
2000
2001 =item B<URI::Escape>
2002
2003 =item B<Image::Info>
2004
2005 =item B<Image::Size>
2006
2007 =item B<Text::Template>
2008
2009 =item B<Image::Imlib2>
2010
2011 =item B<X11 libraries>
2012 (ie, XFree86)
2013
2014 =item B<Imlib2>
2015 Remember the -dev package when using rpm, deb or other package formats!
2016
2017 =back
2018
2019 =head1 AUTHOR
2020
2021 Michael Legart <michael@legart.dk>
2022
2023 =head1 COPYRIGHT AND LICENSE
2024
2025 Copyright (C) 2001-2011 Michael Legart <michael@legart.dk>
2026
2027 Templates designed by Thomas Kjaer <tk@lnx.dk>
2028
2029 Apache::Gallery is free software and is released under the Artistic License.
2030 See B<http://www.perl.com/language/misc/Artistic.html> for details.
2031
2032 The video icons are from the GNOME project. B<http://www.gnome.org/>
2033
2034 =head1 THANKS
2035
2036 Thanks to Thomas Kjaer for templates and design of B<http://apachegallery.dk>
2037 Thanks to Thomas Eibner and other for patches. (See the Changes file)
2038
2039 =head1 SEE ALSO
2040
2041 L<perl>, L<mod_perl>, L<Image::Imlib2>, L<CGI::FastTemplate>,
2042 L<Image::Info>, and L<Image::Size>.
2043
2044 =cut