1 package Apache::Gallery;
3 # $Author: mil $ $Rev: 335 $
4 # $Date: 2011-06-08 20:47:46 +0200 (Wed, 08 Jun 2011) $
14 if (exists($ENV{MOD_PERL_API_VERSION})
15 and ($ENV{MOD_PERL_API_VERSION}==2)) {
17 if ($mod_perl::VERSION >= 1.99 && $mod_perl::VERSION < 2.0) {
18 die "mod_perl 2.0.0 or later is now required";
20 require Apache2::ServerRec;
21 require Apache2::RequestRec;
24 require Apache2::RequestIO;
25 require Apache2::SubRequest;
26 require Apache2::Const;
28 Apache2::Const->import(-compile => 'OK','DECLINED','FORBIDDEN',
29 'NOT_FOUND','HTTP_NOT_MODIFIED', 'REDIRECT');
36 require Apache::Constants;
37 require Apache::Request;
39 Apache::Constants->import('OK','DECLINED','FORBIDDEN','NOT_FOUND');
44 use Image::Info qw(image_info);
45 use Image::Size qw(imgsize);
56 use Digest::MD5 qw(md5_base64);
60 # Regexp for escaping URI's
61 my $escape_rule = "^A-Za-z0-9\-_.!~*'()\/";
66 my $r = shift or Apache2::RequestUtil->request();
68 unless (($r->method eq 'HEAD') or ($r->method eq 'GET')) {
69 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
72 if ((not $memoized) and ($r->dir_config('GalleryMemoize'))) {
74 Memoize::memoize('get_imageinfo');
78 $r->headers_out->{"X-Powered-By"} = "apachegallery.dk $VERSION - Hest design!";
79 $r->headers_out->{"X-Gallery-Version"} = '$Rev: 335 $ $Date: 2011-06-08 20:47:46 +0200 (Wed, 08 Jun 2011) $';
81 my $filename = $r->filename;
83 my $topdir = $filename;
85 my $media_rss_enabled = $r->dir_config('GalleryEnableMediaRss');
87 # Just return the http headers if the client requested that
88 if ($r->header_only) {
94 if (-f $filename or -d $filename) {
95 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
98 return $::MP2 ? Apache2::Const::NOT_FOUND() : Apache::Constants::NOT_FOUND();
104 # Handle selected images
105 if ($cgi->param('selection')) {
106 my @selected = $cgi->param('selection');
107 my $content = join "<br />\n",@selected;
108 $r->content_type('text/html');
109 $r->headers_out->{'Content-Length'} = length($content);
112 $r->send_http_header;
116 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
119 # Selectmode providing checkboxes beside all thumbnails
120 my $select_mode = $cgi->param('select');
122 # Let Apache serve icons without us modifying the request
123 if ($r->uri =~ m/^\/icons/i) {
124 if ($r->uri =~ m/^\/icons\/gallery\/([^\/]+$)/i) {
125 $filename = "/usr/share/libapache-gallery-perl/icons/$1";
126 return send_file($r,$filename);
128 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
131 # Lookup the file in the cache and scale the image if the cached
132 # image does not exist
133 if ($r->uri =~ m/\.cache\//i) {
135 my $filename = $r->filename().$r->path_info();
136 $filename =~ s/\.cache//;
138 $filename =~ m/\/(\d+)x(\d+)\-/;
139 my $image_width = $1;
140 my $image_height = $2;
142 $filename =~ s/\/(\d+)x(\d+)\-//;
144 my ($width, $height, $type) = imgsize($filename);
146 my $imageinfo = get_imageinfo($r, $filename, $type, $width, $height);
148 my $cached = scale_picture($r, $filename, $image_width, $image_height, $imageinfo);
150 my $file = cache_dir($r, 0);
151 $file =~ s/\.cache//;
153 return send_file($r,$file);
161 unless (-f $filename or -d $filename) {
162 show_error($r, 404, "404!", "No such file or directory: ".uri_escape($r->uri, $escape_rule));
163 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
166 my $doc_pattern = $r->dir_config('GalleryDocFile');
167 unless ($doc_pattern) {
168 $doc_pattern = '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|mp4|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
170 my $img_pattern = $r->dir_config('GalleryImgFile');
171 unless ($img_pattern) {
172 $img_pattern = '\.(jpe?g|png|tiff?|ppm)$'
175 # Let Apache serve files we don't know how to handle anyway
176 if (-f $filename && $filename !~ m/$img_pattern/i) {
177 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
182 unless (-d cache_dir($r, 0)) {
183 unless (create_cache($r, cache_dir($r, 0))) {
184 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
188 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
190 # Instead of reading the templates every single time
191 # we need them, create a hash of template names and
192 # the associated Text::Template objects.
193 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
194 index => "$tpl_dir/index.tpl",
195 directory => "$tpl_dir/directory.tpl",
196 picture => "$tpl_dir/picture.tpl",
197 file => "$tpl_dir/file.tpl",
198 comment => "$tpl_dir/dircomment.tpl",
199 nocomment => "$tpl_dir/nodircomment.tpl",
200 rss => "$tpl_dir/rss.tpl",
201 rss_item => "$tpl_dir/rss_item.tpl",
202 navdirectory => "$tpl_dir/navdirectory.tpl",
210 $tpl_vars{TITLE} = "Index of: $uri";
212 if ($media_rss_enabled) {
213 # Put the RSS feed on all directory listings
214 $tpl_vars{META} = '<link rel="alternate" href="?rss=1" type="application/rss+xml" title="" id="gallery" />';
217 unless (opendir (DIR, $filename)) {
218 show_error ($r, 500, $!, "Unable to access directory $filename: $!");
219 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
222 $tpl_vars{MENU} = generate_menu($r);
224 $tpl_vars{FORM_BEGIN} = $select_mode?'<form method="post">':'';
225 $tpl_vars{FORM_END} = $select_mode?'<input type="submit" name="Get list" value="Get list"></form>':'';
227 # Read, sort, and filter files
228 my @files = grep { !/^\./ && -f "$filename/$_" } readdir (DIR);
230 @files=gallerysort($r, @files);
232 my @downloadable_files;
235 # Remove unwanted files from list
237 foreach my $picture (@files) {
239 my $file = $topdir."/".$picture;
241 if ($file =~ /$img_pattern/i) {
242 push (@new_files, $picture);
245 if ($file =~ /$doc_pattern/i) {
246 push (@downloadable_files, $picture);
253 # Read and sort directories
255 my @directories = grep { !/^\./ && -d "$filename/$_" } readdir (DIR);
257 if (defined($r->dir_config('GalleryDirSortBy'))) {
258 $dirsortby=$r->dir_config('GalleryDirSortBy');
260 $dirsortby=$r->dir_config('GallerySortBy');
262 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
263 @directories = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$dirsortby()." $_", @directories));
265 @directories = sort @directories;
271 # Combine directories and files to one listing
273 push (@listing, @directories);
274 push (@listing, @files);
275 push (@listing, @downloadable_files);
281 my $file_counter = 0;
283 my $max_files = $r->dir_config('GalleryMaxThumbnailsPerPage');
285 if (defined($cgi->param('start'))) {
286 $start_at = $cgi->param('start');
292 my $browse_links = "";
293 if (defined($max_files)) {
295 for (my $i=1; $i<=scalar(@listing); $i++) {
299 my $to = $i+$max_files-1;
300 if ($to > scalar(@listing)) {
301 $to = scalar(@listing);
304 if ($start_at < $from || $start_at > $to) {
305 $browse_links .= "<a href=\"?start=$from\">$from - ".$to."</a> ";
308 $browse_links .= "$from - $to ";
317 $tpl_vars{BROWSELINKS} = $browse_links;
320 foreach my $file (@listing) {
324 if ($file_counter < $start_at) {
328 if (defined($max_files) && $file_counter > $max_files+$start_at-1) {
332 my $thumbfilename = $topdir."/".$file;
334 my $fileurl = $uri."/".$file;
336 # Debian bug #619625 <http://bugs.debian.org/619625>
337 if (-d $thumbfilename && ! -e $thumbfilename . ".ignore") {
339 if (-e $thumbfilename . ".folder") {
340 $dirtitle = get_filecontent($thumbfilename . ".folder");
343 $dirtitle = $dirtitle ? $dirtitle : $file;
344 $dirtitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
347 $templates{directory}->fill_in(HASH=> {FILEURL => uri_escape($fileurl, $escape_rule),
353 # Debian bug #619625 <http://bugs.debian.org/619625>
354 elsif (-f $thumbfilename && $thumbfilename =~ /$doc_pattern/i && $thumbfilename !~ /$img_pattern/i && ! -e $thumbfilename . ".ignore") {
356 my $stat = stat($thumbfilename);
357 my $size = $stat->size;
360 if ($thumbfilename =~ m/\.(mpe?g|avi|mov|asf|wmv)$/i) {
361 $filetype = "video-$type";
362 } elsif ($thumbfilename =~ m/\.(txt|html?)$/i) {
363 $filetype = "text-$type";
364 } elsif ($thumbfilename =~ m/\.(mp3|ogg|wav)$/i) {
365 $filetype = "sound-$type";
366 } elsif ($thumbfilename =~ m/$doc_pattern/i) {
367 $filetype = "application-$type";
369 $filetype = "unknown";
372 # Debian bug #337012 <http://bugs.debian.org/337012>
374 my $filetitle = $file;
375 if (-e $thumbfilename . ".file") {
376 $filetitle = get_filecontent($thumbfilename . ".file");
379 # Debian bug #348724 <http://bugs.debian.org/348724>
380 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
383 $templates{file}->fill_in(HASH => {%tpl_vars,
384 FILEURL => uri_escape($fileurl, $escape_rule),
385 ALT => "Size: $size Bytes",
388 FILETYPE => $filetype,
392 # Debian bug #619625 <http://bugs.debian.org/619625>
393 elsif (-f $thumbfilename && ! -e $thumbfilename . ".ignore") {
395 my ($width, $height, $type) = imgsize($thumbfilename);
396 next if $type eq 'Data stream is not a known image file format';
398 my @filetypes = qw(JPG TIF PNG PPM GIF);
400 next unless (grep $type eq $_, @filetypes);
401 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);
402 my $imageinfo = get_imageinfo($r, $thumbfilename, $type, $width, $height);
403 my $cached = get_scaled_picture_name($thumbfilename, $thumbnailwidth, $thumbnailheight);
405 my $rotate = readfile_getnum($r, $imageinfo, $thumbfilename.".rotate");
407 # Debian bug #337012 <http://bugs.debian.org/337012>
408 # HTML <img> tag, alt attribute
409 my $filetitle = $file;
410 if (-e $thumbfilename . ".file") {
411 $filetitle = get_filecontent($thumbfilename . ".file");
414 # Debian bug #348724 <http://bugs.debian.org/348724>
415 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
417 my %file_vars = (FILEURL => uri_escape($fileurl, $escape_rule),
419 DATE => $imageinfo->{DateTimeOriginal} ? $imageinfo->{DateTimeOriginal} : '', # should this really be a stat of the file instead of ''?
420 SRC => uri_escape($uri."/.cache/$cached", $escape_rule),
421 HEIGHT => (grep($rotate==$_, (1, 3)) ? $thumbnailwidth : $thumbnailheight),
422 WIDTH => (grep($rotate==$_, (1, 3)) ? $thumbnailheight : $thumbnailwidth),
423 SELECT => $select_mode?'<input type="checkbox" name="selection" value="'.$file.'"> ':'',);
424 $tpl_vars{FILES} .= $templates{picture}->fill_in(HASH => {%tpl_vars,
429 if ($media_rss_enabled) {
430 my ($content_image_width, undef, $content_image_height) = get_image_display_size($cgi, $r, $width, $height);
432 THUMBNAIL => uri_escape($uri."/.cache/$cached", $escape_rule),
433 LINK => uri_escape($fileurl, $escape_rule),
435 CONTENT => uri_escape($uri."/.cache/".$content_image_width."x".$content_image_height."-".$file, $escape_rule)
437 $tpl_vars{ITEMS} .= $templates{rss_item}->fill_in(HASH => {
445 $tpl_vars{FILES} = "No files found";
446 $tpl_vars{BROWSELINKS} = "";
449 # Generate prev and next directory menu items
450 $filename =~ m/(.*)\/.*?$/;
451 my $parent_filename = $1;
453 $r->document_root =~ m/(.*)\/$/;
455 print STDERR "$filename vs $root_path\n";
456 if ($filename ne $root_path) {
457 unless (opendir (PARENT_DIR, $parent_filename)) {
458 show_error ($r, 500, $!, "Unable to access parent directory $parent_filename: $!");
459 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
462 # Debian bug #619625 <http://bugs.debian.org/619625>
463 my @neighbour_directories = grep { !/^\./ && -d "$parent_filename/$_" && ! -e "$parent_filename/$_" . ".ignore" } readdir (PARENT_DIR);
465 if (defined($r->dir_config('GalleryDirSortBy'))) {
466 $dirsortby=$r->dir_config('GalleryDirSortBy');
468 $dirsortby=$r->dir_config('GallerySortBy');
470 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
471 @neighbour_directories = map(/^\d+ (.*)/, sort map(stat("$parent_filename/$_")->$dirsortby()." $_", @neighbour_directories));
473 @neighbour_directories = sort @neighbour_directories;
476 closedir(PARENT_DIR);
478 my $neightbour_counter = 0;
479 foreach my $neighbour_directory (@neighbour_directories) {
480 if ($parent_filename.'/'.$neighbour_directory eq $filename) {
481 if ($neightbour_counter > 0) {
482 print STDERR "prev directory is " .$neighbour_directories[$neightbour_counter-1] ."\n";
483 my $linktext = $neighbour_directories[$neightbour_counter-1];
484 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
485 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder");
488 URL => "../".$neighbour_directories[$neightbour_counter-1],
489 LINK_NAME => "<<< $linktext",
492 $tpl_vars{PREV_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
493 print STDERR $tpl_vars{PREV_DIR_FILES} ."\n";
496 if ($neightbour_counter < scalar @neighbour_directories - 1) {
497 my $linktext = $neighbour_directories[$neightbour_counter+1];
498 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder") {
499 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder");
502 URL => "../".$neighbour_directories[$neightbour_counter+1],
503 LINK_NAME => "$linktext >>>",
506 $tpl_vars{NEXT_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
507 print STDERR "next directory is " .$neighbour_directories[$neightbour_counter+1] ."\n";
510 $neightbour_counter++;
514 if (-f $topdir . '.comment') {
515 my $comment_ref = get_comment($topdir . '.comment');
517 $comment_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
518 $comment_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
519 $tpl_vars{DIRCOMMENT} = $templates{comment}->fill_in(HASH => \%comment_vars);
520 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
522 $tpl_vars{DIRCOMMENT} = $templates{nocomment}->fill_in(HASH=>\%tpl_vars);
525 if ($cgi->param('rss')) {
526 $tpl_vars{MAIN} = $templates{rss}->fill_in(HASH => \%tpl_vars);
527 $r->content_type('application/rss+xml');
529 $tpl_vars{MAIN} = $templates{index}->fill_in(HASH => \%tpl_vars);
530 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
531 $r->content_type('text/html');
534 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
537 $r->send_http_header;
540 $r->print($tpl_vars{MAIN});
541 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
547 if (defined($ENV{QUERY_STRING}) && $ENV{QUERY_STRING} eq 'orig') {
548 if ($r->dir_config('GalleryAllowOriginal') ? 1 : 0) {
549 $r->filename($filename);
550 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
552 return $::MP2 ? Apache2::Const::FORBIDDEN() : Apache::Constants::FORBIDDEN();
555 if (defined $ENV{QUERY_STRING} && $ENV{QUERY_STRING} eq 'thumbonly' &&
556 $r->dir_config('GalleryAllowThumbonly') &&
559 my ($width, $height, $type) = imgsize($filename);
560 my @filetypes = qw(JPG TIF PNG PPM GIF);
561 if (grep $type eq $_, @filetypes) {
562 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);
563 my $imageinfo = get_imageinfo($r, $filename, $type, $width, $height);
564 my $cached = get_scaled_picture_name($filename, $thumbnailwidth, $thumbnailheight);
565 $r->headers_out->set(Location => uri_escape(".cache/$cached", $escape_rule));
566 return $::MP2 ? Apache2::Const::REDIRECT() : Apache::Constants::REDIRECT();
570 # Create cache dir if not existing
571 my @tmp = split (/\//, $filename);
572 my $picfilename = pop @tmp;
573 my $path = (join "/", @tmp)."/";
574 my $cache_path = cache_dir($r, 1);
576 unless (-d $cache_path) {
577 unless (create_cache($r, $cache_path)) {
578 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
582 my ($orig_width, $orig_height, $type) = imgsize($filename);
584 my $imageinfo = get_imageinfo($r, $filename, $type, $orig_width, $orig_height);
586 my ($image_width, $width, $height, $original_size) = get_image_display_size($cgi, $r, $orig_width, $orig_height);
588 my $cached = get_scaled_picture_name($filename, $image_width, $height);
590 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
592 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
593 picture => "$tpl_dir/showpicture.tpl",
594 navpicture => "$tpl_dir/navpicture.tpl",
595 info => "$tpl_dir/info.tpl",
596 scale => "$tpl_dir/scale.tpl",
597 scaleactive => "$tpl_dir/scaleactive.tpl",
598 orig => "$tpl_dir/orig.tpl",
599 refresh => "$tpl_dir/refresh.tpl",
600 interval => "$tpl_dir/interval.tpl",
601 intervalactive => "$tpl_dir/intervalactive.tpl",
602 slideshowisoff => "$tpl_dir/slideshowisoff.tpl",
603 slideshowoff => "$tpl_dir/slideshowoff.tpl",
604 pictureinfo => "$tpl_dir/pictureinfo.tpl",
605 nopictureinfo => "$tpl_dir/nopictureinfo.tpl",
610 my $resolution = (($image_width > $orig_width) && ($height > $orig_height)) ?
611 "$orig_width x $orig_height" : "$image_width x $height";
613 $tpl_vars{TITLE} = "Viewing ".$r->uri()." at $image_width x $height";
614 $tpl_vars{META} = " ";
615 $tpl_vars{RESOLUTION} = $resolution;
616 $tpl_vars{MENU} = generate_menu($r);
617 $tpl_vars{SRC} = uri_escape(".cache/$cached", $escape_rule);
618 $tpl_vars{URI} = $r->uri();
620 my $exif_mode = $r->dir_config('GalleryEXIFMode');
621 unless ($exif_mode) {
622 $exif_mode = 'namevalue';
625 unless (opendir(DATADIR, $path)) {
626 show_error($r, 500, "Unable to access directory", "Unable to access directory $path");
627 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
629 my @pictures = grep { /$img_pattern/i && ! -e "$path/$_" . ".ignore" } readdir (DATADIR);
631 @pictures = gallerysort($r, @pictures);
633 $tpl_vars{TOTAL} = scalar @pictures;
638 for (my $i=0; $i <= $#pictures; $i++) {
639 if ($pictures[$i] eq $picfilename) {
641 $tpl_vars{NUMBER} = $i+1;
643 $prevpicture = $pictures[$i-1];
644 my $displayprev = ($i>0 ? 1 : 0);
646 if ($r->dir_config("GalleryWrapNavigation")) {
647 $prevpicture = $pictures[$i>0 ? $i-1 : $#pictures];
650 if ($prevpicture and $displayprev) {
651 my ($orig_width, $orig_height, $type) = imgsize($path.$prevpicture);
652 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
653 my $imageinfo = get_imageinfo($r, $path.$prevpicture, $type, $orig_width, $orig_height);
654 my $cached = get_scaled_picture_name($path.$prevpicture, $thumbnailwidth, $thumbnailheight);
655 # Debian bug #337012 <http://bugs.debian.org/337012>
656 my $prevpicture_title = $prevpicture;
657 if (-e $path."/".$prevpicture . ".file") {
658 $prevpicture_title = get_filecontent($path."/".$prevpicture . ".file");
661 $nav_vars{URL} = uri_escape($prevpicture, $escape_rule);
662 $nav_vars{FILENAME} = $prevpicture_title;
663 $nav_vars{WIDTH} = $width;
664 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
665 $nav_vars{DIRECTION} = "« <u>p</u>rev";
666 $nav_vars{ACCESSKEY} = "P";
667 $tpl_vars{BACK} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
670 $tpl_vars{BACK} = " ";
673 $nextpicture = $pictures[$i+1];
674 if ($r->dir_config("GalleryWrapNavigation")) {
675 $nextpicture = $pictures[$i == $#pictures ? 0 : $i+1];
679 my ($orig_width, $orig_height, $type) = imgsize($path.$nextpicture);
680 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
681 my $imageinfo = get_imageinfo($r, $path.$nextpicture, $type, $thumbnailwidth, $thumbnailheight);
682 my $cached = get_scaled_picture_name($path.$nextpicture, $thumbnailwidth, $thumbnailheight);
683 # Debian bug #337012 <http://bugs.debian.org/337012>
684 my $nextpicture_title = $nextpicture;
685 if (-e $path."/".$nextpicture . ".file") {
686 $nextpicture_title = get_filecontent($path."/".$nextpicture . ".file");
689 $nav_vars{URL} = uri_escape($nextpicture, $escape_rule);
690 $nav_vars{FILENAME} = $nextpicture_title;
691 $nav_vars{WIDTH} = $width;
692 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
693 $nav_vars{DIRECTION} = "<u>n</u>ext »";
694 $nav_vars{ACCESSKEY} = "N";
696 $tpl_vars{NEXT} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
697 $tpl_vars{NEXTURL} = uri_escape($nextpicture, $escape_rule);
700 $tpl_vars{NEXT} = " ";
701 $tpl_vars{NEXTURL} = '#';
706 my $foundcomment = 0;
707 if (-f $path . '/' . $picfilename . '.comment') {
708 my $comment_ref = get_comment($path . '/' . $picfilename . '.comment');
710 $tpl_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
711 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
712 } elsif ($r->dir_config('GalleryCommentExifKey')) {
713 my $comment = decode("utf8", $imageinfo->{$r->dir_config('GalleryCommentExifKey')});
714 $tpl_vars{COMMENT} = encode("iso-8859-1", $comment);
716 $tpl_vars{COMMENT} = '';
719 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
724 my ($human_key, $exif_key) = (split " => ")[0,1];
725 my $value = $imageinfo->{$human_key};
726 if (defined($value)) {
730 if ($exif_mode eq 'namevalue') {
732 $info_vars{KEY} = $human_key;
733 $info_vars{VALUE} = $value;
734 $tpl_vars{INFO} .= $templates{info}->fill_in(HASH => \%info_vars);
737 if ($exif_mode eq 'variables') {
738 $tpl_vars{"EXIF_".uc($exif_key)} = $value;
741 if ($exif_mode eq 'values') {
742 $exifvalues .= "| ".$value." ";
749 if ($exif_mode eq 'values') {
750 if (defined($exifvalues)) {
751 $tpl_vars{EXIFVALUES} = $exifvalues;
754 $tpl_vars{EXIFVALUES} = "";
758 if ($foundcomment and !$foundinfo) {
759 $tpl_vars{INFO} = "";
762 if ($exif_mode ne 'namevalue') {
763 $tpl_vars{INFO} = "";
766 if ($exif_mode eq 'namevalue' && $foundinfo or $foundcomment) {
768 $tpl_vars{PICTUREINFO} = $templates{pictureinfo}->fill_in(HASH => \%tpl_vars);
770 unless (defined($exifvalues)) {
771 $tpl_vars{EXIFVALUES} = "";
776 $tpl_vars{PICTUREINFO} = $templates{nopictureinfo}->fill_in(HASH => \%tpl_vars);
779 # Fill in sizes and determine if any are smaller than the
780 # actual image. If they are, $scaleable=1
782 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
783 foreach my $size (@sizes) {
784 if ($size<=$original_size) {
786 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
787 $sizes_vars{SIZE} = $size;
788 $sizes_vars{WIDTH} = $size;
789 if ($width == $size) {
790 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
793 $tpl_vars{SIZES} .= $templates{scale}->fill_in(HASH => \%sizes_vars);
799 unless ($scaleable) {
801 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
802 $sizes_vars{SIZE} = $original_size;
803 $sizes_vars{WIDTH} = $original_size;
804 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
807 $tpl_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
809 if ($r->dir_config('GalleryAllowOriginal')) {
810 $tpl_vars{SIZES} .= $templates{orig}->fill_in(HASH => \%tpl_vars);
813 my @slideshow_intervals = split (/ /, $r->dir_config('GallerySlideshowIntervals') ? $r->dir_config('GallerySlideshowIntervals') : '3 5 10 15 30');
814 foreach my $interval (@slideshow_intervals) {
817 $slideshow_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
818 $slideshow_vars{SECONDS} = $interval;
819 $slideshow_vars{WIDTH} = ($width > $height ? $width : $height);
821 if ($cgi->param('slideshow') && $cgi->param('slideshow') == $interval and $nextpicture) {
822 $tpl_vars{SLIDESHOW} .= $templates{intervalactive}->fill_in(HASH => \%slideshow_vars);
826 $tpl_vars{SLIDESHOW} .= $templates{interval}->fill_in(HASH => \%slideshow_vars);
831 if ($cgi->param('slideshow') and $nextpicture) {
833 $tpl_vars{SLIDESHOW} .= $templates{slideshowoff}->fill_in(HASH => \%tpl_vars);
835 unless ((grep $cgi->param('slideshow') == $_, @slideshow_intervals)) {
836 show_error($r, 200, "Invalid interval", "Invalid slideshow interval choosen");
837 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
840 $tpl_vars{URL} = uri_escape($nextpicture, $escape_rule);
841 $tpl_vars{WIDTH} = ($width > $height ? $width : $height);
842 $tpl_vars{INTERVAL} = $cgi->param('slideshow');
843 $tpl_vars{META} .= $templates{refresh}->fill_in(HASH => \%tpl_vars);
847 $tpl_vars{SLIDESHOW} .= $templates{slideshowisoff}->fill_in(HASH => \%tpl_vars);
850 $tpl_vars{MAIN} = $templates{picture}->fill_in(HASH => \%tpl_vars);
851 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
853 $r->content_type('text/html');
854 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
857 $r->send_http_header;
860 $r->print($tpl_vars{MAIN});
861 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
869 my $subr = $r->lookup_file($file);
870 $r->content_type($subr->content_type());
872 my $fileinfo = stat($file);
874 my $nonce = md5_base64($fileinfo->ino.$fileinfo->mtime);
875 if ($r->headers_in->{"If-None-Match"} eq $nonce) {
876 return Apache2::Const::HTTP_NOT_MODIFIED();
879 if ($r->headers_in->{"If-Modified-Since"} && str2time($r->headers_in->{"If-Modified-Since"}) < $fileinfo->mtime) {
880 return Apache2::Const::HTTP_NOT_MODIFIED();
883 $r->headers_out->{"Content-Length"} = $fileinfo->size;
884 $r->headers_out->{"Last-Modified-Date"} = time2str($fileinfo->mtime);
885 $r->headers_out->{"ETag"} = $nonce;
887 return Apache2::Const::OK();
892 return Apache::Constants::DECLINED();
898 my ($r, $strip_filename) = @_;
902 unless ($r->dir_config('GalleryCacheDir')) {
904 $cache_root = '/var/cache/www/';
905 if ($r->server->is_virtual) {
906 $cache_root = File::Spec->catdir($cache_root, $r->server->server_hostname);
908 $cache_root = File::Spec->catdir($cache_root, $r->location);
913 $cache_root = $r->dir_config('GalleryCacheDir');
917 # If the uri contains .cache we need to remove it
921 my (undef, $dirs, $filename) = File::Spec->splitpath($uri);
922 # We don't need a volume as this is a relative path
924 if ($strip_filename) {
925 return(File::Spec->canonpath(File::Spec->catdir($cache_root, $dirs)));
927 return(File::Spec->canonpath(File::Spec->catfile($cache_root, $dirs, $filename)));
935 unless (mkdirhier ($path)) {
936 show_error($r, 500, $!, "Unable to create cache directory in $path: $!");
949 unless (mkdir($dir, 0755)) {
951 $parent =~ s/\/[^\/]*$//;
960 sub get_scaled_picture_name {
962 my ($fullpath, $width, $height) = @_;
964 my (undef, undef, $type) = imgsize($fullpath);
966 my @dirs = split(/\//, $fullpath);
967 my $filename = pop(@dirs);
970 if (grep $type eq $_, qw(PPM TIF GIF)) {
971 $newfilename = $width."x".$height."-".$filename;
972 # needs to be configurable
973 $newfilename =~ s/\.(\w+)$/-$1\.jpg/;
975 $newfilename = $width."x".$height."-".$filename;
984 my ($r, $fullpath, $width, $height, $imageinfo) = @_;
986 my @dirs = split(/\//, $fullpath);
987 my $filename = pop(@dirs);
989 my ($orig_width, $orig_height, $type) = imgsize($fullpath);
991 my $cache = cache_dir($r, 1);
993 my $newfilename = get_scaled_picture_name($fullpath, $width, $height);
995 if (($width > $orig_width) && ($height > $orig_height)) {
996 # Run it through the resize code anyway to get watermarks
997 $width = $orig_width;
998 $height = $orig_height;
1001 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
1003 # Do we want to generate a new file in the cache?
1006 if (-f $cache."/".$newfilename) {
1009 # Check to see if the image has changed
1010 my $filestat = stat($fullpath);
1011 my $cachestat = stat($cache."/".$newfilename);
1012 if ($filestat->mtime >= $cachestat->mtime) {
1016 # Check to see if the .rotate file has been added or changed
1017 if (-f $fullpath . ".rotate") {
1018 my $rotatestat = stat($fullpath . ".rotate");
1019 if ($rotatestat->mtime > $cachestat->mtime) {
1023 # Check to see if the copyrightimage has been added or changed
1024 if ($r->dir_config('GalleryCopyrightImage') && -f $r->dir_config('GalleryCopyrightImage')) {
1025 unless ($width == $thumbnailwidth or $width == $thumbnailheight) {
1026 my $copyrightstat = stat($r->dir_config('GalleryCopyrightImage'));
1027 if ($copyrightstat->mtime > $cachestat->mtime) {
1037 my $newpath = $cache."/".$newfilename;
1038 my $rotate = readfile_getnum($r, $imageinfo, $fullpath . ".rotate");
1039 my $quality = $r->dir_config('GalleryQuality');
1041 if ($width == $thumbnailwidth or $width == $thumbnailheight) {
1043 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, '', '', '', '', '', '');
1047 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate,
1048 ($r->dir_config('GalleryCopyrightImage') ? $r->dir_config('GalleryCopyrightImage') : ''),
1049 ($r->dir_config('GalleryTTFDir') ? $r->dir_config('GalleryTTFDir') : ''),
1050 ($r->dir_config('GalleryCopyrightText') ? $r->dir_config('GalleryCopyrightText') : ''),
1051 ($r->dir_config('GalleryCopyrightColor') ? $r->dir_config('GalleryCopyrightColor') : ''),
1052 ($r->dir_config('GalleryTTFFile') ? $r->dir_config('GalleryTTFFile') : ''),
1053 ($r->dir_config('GalleryTTFSize') ? $r->dir_config('GalleryTTFSize') : ''),
1054 ($r->dir_config('GalleryCopyrightBackgroundColor') ? $r->dir_config('GalleryCopyrightBackgroundColor') : ''),
1060 return $newfilename;
1064 sub get_thumbnailsize {
1065 my ($r, $orig_width, $orig_height) = @_;
1067 my $gallerythumbnailsize=$r->dir_config('GalleryThumbnailSize');
1069 if (defined($gallerythumbnailsize)) {
1070 warn("Invalid setting for GalleryThumbnailSize") unless
1071 $gallerythumbnailsize =~ /^\s*\d+\s*x\s*\d+\s*$/i;
1074 my ($thumbnailwidth, $thumbnailheight) = split(/x/i, ($gallerythumbnailsize) ? $gallerythumbnailsize : "100x75");
1076 my $width = $thumbnailwidth;
1077 my $height = $thumbnailheight;
1079 # If the image is rotated, flip everything around.
1080 if (defined $r->dir_config('GalleryThumbnailSizeLS')
1081 and $r->dir_config('GalleryThumbnailSizeLS') eq '1'
1082 and $orig_width < $orig_height) {
1084 $width = $thumbnailheight;
1085 $height = $thumbnailwidth;
1088 my $scale = ($orig_width ? $width/$orig_width : 1);
1091 if ($orig_height * $scale > $thumbnailheight) {
1092 $scale = $height/$orig_height;
1093 $width = $orig_width * $scale;
1097 $height = $orig_height * $scale;
1099 $height = floor($height);
1100 $width = floor($width);
1102 return ($width, $height);
1105 sub get_image_display_size {
1106 my ($cgi, $r, $orig_width, $orig_height) = @_;
1108 my $width = $orig_width;
1110 my $original_size=$orig_height;
1111 if ($orig_width>$orig_height) {
1112 $original_size=$orig_width;
1115 # Check if the selected width is allowed
1116 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
1118 my %cookies = fetch CGI::Cookie;
1120 if ($cgi->param('width')) {
1121 unless ((grep $cgi->param('width') == $_, @sizes) or ($cgi->param('width') == $original_size)) {
1122 show_error($r, 200, "Invalid width", "The specified width is invalid");
1123 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
1126 $width = $cgi->param('width');
1127 my $cookie = new CGI::Cookie(-name => 'GallerySize', -value => $width, -expires => '+6M');
1128 $r->headers_out->{'Set-Cookie'} = $cookie;
1130 } elsif ($cookies{'GallerySize'} && (grep $cookies{'GallerySize'}->value == $_, @sizes)) {
1132 $width = $cookies{'GallerySize'}->value;
1140 if ($orig_width<$orig_height) {
1141 $scale = ($orig_height ? $width/$orig_height: 1);
1142 $image_width=$width*$orig_width/$orig_height;
1145 $scale = ($orig_width ? $width/$orig_width : 1);
1146 $image_width = $width;
1149 my $height = $orig_height * $scale;
1151 $image_width = floor($image_width);
1152 $width = floor($width);
1153 $height = floor($height);
1155 return ($image_width, $width, $height, $original_size);
1159 my ($r, $file, $type, $width, $height) = @_;
1161 if ($type eq 'Data stream is not a known image file format') {
1162 # should never be reached, this is supposed to be handled outside of here
1163 log_error("Something was fishy with the type of the file $file\n");
1166 # Some files, like TIFF, PNG, GIF do not have EXIF info
1167 # embedded but use .thm files instead.
1168 $imageinfo = get_imageinfo_from_thm_file($file, $width, $height);
1170 # If there is no .thm file and our file is a JPEG file we try to extract the EXIf
1171 # info using Image::Info
1172 unless (defined($imageinfo) && (grep $type eq $_, qw(JPG))) {
1173 # Only for files that natively keep the EXIF info in the same file
1174 $imageinfo = image_info($file);
1178 unless (defined($imageinfo->{width}) and defined($imageinfo->{height})) {
1179 $imageinfo->{width} = $width;
1180 $imageinfo->{height} = $height;
1183 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
1186 my ($human_key, $exif_key) = (split " => ")[0,1];
1187 if (defined($exif_key) && defined($imageinfo->{$exif_key})) {
1189 if (ref($imageinfo->{$exif_key}) eq 'Image::TIFF::Rational') {
1190 $value = $imageinfo->{$exif_key}->as_string;
1192 elsif (ref($imageinfo->{$exif_key}) eq 'ARRAY') {
1193 foreach my $element (@{$imageinfo->{$exif_key}}) {
1194 if (ref($element) eq 'ARRAY') {
1195 foreach (@{$element}) {
1199 elsif (ref($element) eq 'HASH') {
1200 $value .= "<br />{ ";
1201 foreach (sort keys %{$element}) {
1202 $value .= "$_ = " . $element->{$_} . ' ';
1213 my $exif_value = $imageinfo->{$exif_key};
1214 if ($human_key eq 'Flash' && $exif_value =~ m/\d/) {
1219 "16" => "No (Compulsory) Should be External Flash",
1220 "17" => "Yes (External)",
1222 "25" => "Yes (Auto)",
1223 "73" => "Yes (Compulsory, Red Eye Reducing)",
1224 "89" => "Yes (Auto, Red Eye Reducing)"
1226 $exif_value = defined $flashmodes{$exif_value} ? $flashmodes{$exif_value} : 'unknown flash mode';
1228 $value = $exif_value;
1230 if ($exif_key eq 'MeteringMode') {
1231 my $exif_value = $imageinfo->{$exif_key};
1232 if ($exif_value =~ /^\d+$/) {
1233 my %meteringmodes = (
1236 '2' => 'CenterWeightedAverage',
1243 $exif_value = defined $meteringmodes{$exif_value} ? $meteringmodes{$exif_value} : 'unknown metering mode';
1245 $value = $exif_value;
1248 if ($exif_key eq 'LightSource') {
1249 my $exif_value = $imageinfo->{$exif_key};
1250 if ($exif_value =~ /^\d+$/) {
1251 my %lightsources = (
1254 '2' => 'Fluorescent',
1255 '3' => 'Tungsten (incandescent light)',
1257 '9' => 'Fine weather',
1258 '10' => 'Cloudy weather',
1260 '12' => 'Daylight fluorescent',
1261 '13' => 'Day white fluorescent',
1262 '14' => 'Cool white fluorescent',
1263 '15' => 'White fluorescent',
1264 '17' => 'Standard light A',
1265 '18' => 'Standard light B',
1266 '19' => 'Standard light C',
1271 '24' => 'ISO studio tungsten',
1272 '255' => 'other light source'
1274 $exif_value = defined $lightsources{$exif_value} ? $lightsources{$exif_value} : 'unknown light source';
1276 $value = $exif_value;
1278 if ($exif_key eq 'FocalLength') {
1279 if ($value =~ /^(\d+)\/(\d+)$/) {
1280 $value = eval { $1 / $2 };
1284 $value = int($value + 0.5) . "mm";
1289 if ($exif_key eq 'ShutterSpeedValue') {
1290 if ($value =~ /^((?:\-)?\d+)\/(\d+)$/) {
1291 $value = eval { $1 / $2 };
1296 $value = 1/(exp($value*log(2)));
1298 $value = "1/" . (int((1/$value)));
1300 $value = int($value*10)/10;
1306 $value = $value . " sec";
1311 if ($exif_key eq 'ApertureValue') {
1312 if ($value =~ /^(\d+)\/(\d+)$/) {
1313 $value = eval { $1 / $2 };
1317 # poor man's rounding
1318 $value = int(exp($value*log(2)*0.5)*10)/10;
1319 $value = "f" . $value;
1323 if ($exif_key eq 'FNumber') {
1324 if ($value =~ /^(\d+)\/(\d+)$/) {
1325 $value = eval { $1 / $2 };
1329 $value = int($value*10+0.5)/10;
1330 $value = "f" . $value;
1334 $imageinfo->{$human_key} = $value;
1338 if ($r->dir_config('GalleryUseFileDate') &&
1339 ($r->dir_config('GalleryUseFileDate') eq '1'
1340 || !$imageinfo->{"Picture Taken"} )) {
1342 my $st = stat($file);
1343 $imageinfo->{"DateTimeOriginal"} = $imageinfo->{"Picture Taken"} = scalar localtime($st->mtime) if $st;
1349 sub get_imageinfo_from_thm_file {
1351 my ($file, $width, $height) = @_;
1353 my $imageinfo = undef;
1354 # Windows based file extensions are often .THM, so check
1355 # for both .thm and .THM
1356 my $unix_file = $file;
1357 my $windows_file = $file;
1358 $unix_file =~ s/\.(\w+)$/.thm/;
1359 $windows_file =~ s/\.(\w+)$/.THM/;
1361 if (-e $unix_file && -f $unix_file && -r $unix_file) {
1362 $imageinfo = image_info($unix_file);
1363 $imageinfo->{width} = $width;
1364 $imageinfo->{height} = $height;
1366 elsif (-e $windows_file && -f $windows_file && -r $windows_file) {
1367 $imageinfo = image_info($windows_file);
1368 $imageinfo->{width} = $width;
1369 $imageinfo->{height} = $height;
1376 sub readfile_getnum {
1377 my ($r, $imageinfo, $filename) = @_;
1381 print STDERR "orientation: ".$imageinfo->{Orientation}."\n";
1382 # Check to see if the image contains the Orientation EXIF key,
1383 # but allow user to override using rotate
1384 if (!defined($r->dir_config("GalleryAutoRotate"))
1385 || $r->dir_config("GalleryAutoRotate") eq "1") {
1386 if (defined($imageinfo->{Orientation})) {
1387 print STDERR $imageinfo->{Orientation}."\n";
1388 if ($imageinfo->{Orientation} eq 'right_top') {
1391 elsif ($imageinfo->{Orientation} eq 'left_bot') {
1397 if (open(FH, "<$filename")) {
1401 unless ($temp =~ /^\d$/) {
1404 unless ($temp == 1 || $temp == 2 || $temp == 3) {
1413 sub get_filecontent {
1415 open(FH, $file) or return undef;
1426 my $filename = shift;
1427 my $comment_ref = {};
1428 $comment_ref->{TITLE} = undef;
1429 $comment_ref->{COMMENT} = '';
1431 open(FH, $filename) or return $comment_ref;
1433 if ($title =~ m/^TITLE: (.*)$/) {
1434 chomp($comment_ref->{TITLE} = $1);
1437 $comment_ref->{COMMENT} = $title;
1442 $comment_ref->{COMMENT} .= $_;
1446 return $comment_ref;
1451 my ($r, $statuscode, $errortitle, $error) = @_;
1453 my $tpl = $r->dir_config('GalleryTemplateDir');
1455 my %templates = create_templates({layout => "$tpl/layout.tpl",
1456 error => "$tpl/error.tpl",
1460 $tpl_vars{TITLE} = "Error! $errortitle";
1461 $tpl_vars{META} = "";
1462 $tpl_vars{ERRORTITLE} = "Error! $errortitle";
1463 $tpl_vars{ERROR} = $error;
1465 $tpl_vars{MAIN} = $templates{error}->fill_in(HASH => \%tpl_vars);
1467 $tpl_vars{PAGE} = $templates{layout}->fill_in(HASH => \%tpl_vars);
1469 $r->status($statuscode);
1470 $r->content_type('text/html');
1472 $r->print($tpl_vars{PAGE});
1480 my $root_text = (defined($r->dir_config('GalleryRootText')) ? $r->dir_config('GalleryRootText') : "root:" );
1481 my $root_path = (defined($r->dir_config('GalleryRootPath')) ? $r->dir_config('GalleryRootPath') : "" );
1483 my $subr = $r->lookup_uri($r->uri);
1484 my $filename = $subr->filename;
1486 my @links = split (/\//, $r->uri);
1488 $uri =~ s/^$root_path//g;
1490 @links = split (/\//, $uri);
1492 # Get the full path of the base directory
1495 my @direlem = split (/\//, $filename);
1496 for my $i ( 0 .. ( scalar(@direlem) - scalar(@links) ) ) {
1497 $dirname .= shift(@direlem) . '/';
1504 $picturename = pop(@links);
1505 # Debian bug #337012 <http://bugs.debian.org/337012>
1506 if (-e $filename . ".file") {
1507 $picturename = get_filecontent($filename . ".file");
1511 if ($r->uri eq $root_path) {
1512 return qq{ <a href="$root_path">$root_text</a> };
1516 my $menuurl = $root_path;
1517 foreach my $link (@links) {
1519 $menuurl .= $link."/";
1520 my $linktext = $link;
1521 unless (length($link)) {
1522 $linktext = "$root_text ";
1526 $dirname = File::Spec->catdir($dirname, $link);
1528 if (-e $dirname . ".folder") {
1529 $linktext = get_filecontent($dirname . ".folder");
1533 if ("$root_path$uri" eq $menuurl) {
1534 $menu .= "$linktext / ";
1537 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule)."\">$linktext</a> / ";
1543 $menu .= $picturename;
1547 if ($r->dir_config('GallerySelectionMode') && $r->dir_config('GallerySelectionMode') eq '1') {
1548 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule);
1549 $menu .= "?select=1\">[select]</a> ";
1557 my ($r, $infile, $outfile, $x, $y, $rotate, $copyrightfile, $GalleryTTFDir, $GalleryCopyrightText, $text_color, $GalleryTTFFile, $GalleryTTFSize, $GalleryCopyrightBackgroundColor, $quality) = @_;
1560 my $image = Image::Imlib2->load($infile) or warn("Unable to open file $infile, $!");
1563 $image=$image->create_scaled_image($x, $y) or warn("Unable to scale image $infile. Are you running out of memory?");
1567 $image->image_orientate($rotate);
1570 # blend copyright image onto image
1571 if ($copyrightfile ne '') {
1572 if (-f $copyrightfile and (my $logo=Image::Imlib2->load($copyrightfile))) {
1573 my $x = $image->get_width();
1574 my $y = $image->get_height();
1575 my $logox = $logo->get_width();
1576 my $logoy = $logo->get_height();
1577 $image->blend($logo, 0, 0, 0, $logox, $logoy, $x-$logox, $y-$logoy, $logox, $logoy);
1580 log_error("GalleryCopyrightImage $copyrightfile was not found");
1584 if ($GalleryTTFDir && $GalleryCopyrightText && $GalleryTTFFile && $text_color) {
1585 if (!-d $GalleryTTFDir) {
1587 log_error("GalleryTTFDir $GalleryTTFDir is not a dir\n");
1589 } elsif ($GalleryCopyrightText eq '') {
1591 log_error("GalleryCopyrightText is empty. No text inserted to picture\n");
1593 } elsif (!-e "$GalleryTTFDir/$GalleryTTFFile") {
1595 log_error("GalleryTTFFile $GalleryTTFFile was not found\n");
1599 $GalleryTTFFile =~ s/\.TTF$//i;
1600 $image->add_font_path("$GalleryTTFDir");
1602 $image->load_font("$GalleryTTFFile/$GalleryTTFSize");
1603 my($text_x, $text_y) = $image->get_text_size("$GalleryCopyrightText");
1604 my $x = $image->get_width();
1605 my $y = $image->get_height();
1609 if (($text_x < $x - $offset) && ($text_y < $y - $offset)) {
1610 if ($GalleryCopyrightBackgroundColor =~ /^\d+,\d+,\d+,\d+$/) {
1611 my ($br_val, $bg_val, $bb_val, $ba_val) = split (/,/, $GalleryCopyrightBackgroundColor);
1612 $image->set_colour($br_val, $bg_val, $bb_val, $ba_val);
1613 $image->fill_rectangle ($x-$text_x-$offset, $y-$text_y-$offset, $text_x, $text_y);
1615 my ($r_val, $g_val, $b_val, $a_val) = split (/,/, $text_color);
1616 $image->set_colour($r_val, $g_val, $b_val, $a_val);
1617 $image->draw_text($x-$text_x-$offset, $y-$text_y-$offset, "$GalleryCopyrightText");
1619 log_error("Text is to big for the picture.\n");
1624 if ($quality && $quality =~ m/^\d+$/) {
1625 $image->set_quality($quality);
1628 $image->save($outfile);
1635 my $sortby = $r->dir_config('GallerySortBy');
1636 my $filename=$r->lookup_uri($r->uri)->filename;
1637 $filename=(File::Spec->splitpath($filename))[1] if (-f $filename);
1638 if ($sortby && $sortby =~ m/^(size|atime|mtime|ctime)$/) {
1639 @files = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$sortby()." $_", @files));
1641 @files = sort @files;
1646 # Create Text::Template objects used by Apache::Gallery. Takes a
1647 # hashref of template_name, template_filename pairs, and returns a
1648 # list of template_name, texttemplate_object pairs.
1649 sub create_templates {
1650 my $templates = shift;
1652 # This routine is called whenever a template has an error. Prints
1653 # the error to STDERR and sticks the error in the output
1656 # Pull out the name and filename from the arg option [see
1657 # Text::Template for details]
1658 @args{qw(name file)} = @{$args{arg}};
1659 print STDERR qq(Template $args{name} ("$args{file}") is broken: $args{error});
1660 # Don't include the file name in the output, as the user can see this.
1661 return qq(<!-- Template $args{name} is broken: $args{error} -->);
1666 my %texttemplate_objects;
1668 for my $template_name (keys %$templates) {
1669 my $tt_obj = Text::Template->new(TYPE => 'FILE',
1670 SOURCE => $$templates{$template_name},
1671 BROKEN => \&tt_broken,
1672 BROKEN_ARG => [$template_name, $$templates{$template_name}],
1674 or die "Unable to create new Text::Template object for $template_name: $Text::Template::ERROR";
1675 $texttemplate_objects{$template_name} = $tt_obj;
1677 return %texttemplate_objects;
1682 Apache2::RequestUtil->request->log_error(shift());
1684 Apache->request->log_error(shift());
1692 Apache::Gallery - mod_perl handler to create an image gallery
1696 See the INSTALL file in the distribution for installation instructions.
1700 Apache::Gallery creates an thumbnail index of each directory and allows
1701 viewing pictures in different resolutions. Pictures are resized on the
1702 fly and cached. The gallery can be configured and customized in many ways
1703 and a custom copyright image can be added to all the images without
1704 modifying the original.
1706 =head1 CONFIGURATION
1708 In your httpd.conf you set the global options for the gallery. You can
1709 also override each of the options in .htaccess files in your gallery
1712 The options are set in the httpd.conf/.htaccess file using the syntax:
1713 B<PerlSetVar OptionName 'value'>
1715 Example: B<PerlSetVar GalleryCacheDir '/var/cache/www/'>
1719 =item B<GalleryAutoRotate>
1721 Some cameras, like the Canon G3, can detect the orientation of a
1722 the pictures you take and will save this information in the
1723 'Orientation' EXIF field. Apache::Gallery will then automatically
1726 This behavior is default but can be disabled by setting GalleryAutoRotate
1729 =item B<GalleryCacheDir>
1731 Directory where Apache::Gallery should create its cache with scaled
1732 pictures. The default is /var/cache/www/ . Here, a directory for each
1733 virtualhost or location will be created automatically. Make sure your
1734 webserver has write access to the CacheDir.
1736 =item B<GalleryTemplateDir>
1738 Full path to the directory where you placed the templates. This option
1739 can be used both in your global configuration and in .htaccess files,
1740 this way you can have different layouts in different parts of your
1743 No default value, this option is required.
1745 =item B<GalleryInfo>
1747 With this option you can define which EXIF information you would like
1748 to present from the image. The format is: '<MyName => KeyInEXIF,
1749 MyOtherName => OtherKeyInEXIF'
1751 Examples of keys: B<ShutterSpeedValue>, B<ApertureValue>, B<SubjectDistance>,
1754 You can view all the keys from the EXIF header using this perl-oneliner:
1756 perl C<-e> 'use Data::Dumper; use Image::Info qw(image_info); print Dumper(image_info(shift));' filename.jpg
1758 Default is: 'Picture Taken => DateTimeOriginal, Flash => Flash'
1760 =item B<GallerySizes>
1762 Defines which widths images can be scaled to. Images cannot be
1763 scaled to other widths than the ones you define with this option.
1765 The default is '640 800 1024 1600'
1767 =item B<GalleryThumbnailSize>
1769 Defines the width and height of the thumbnail images.
1771 Defaults to '100x75'
1773 =item B<GalleryThumbnailSizeLS>
1775 If set to '1', B<GalleryThumbnailSize> is the long and the short side of
1776 the thumbnail image instead of the width and height.
1780 =item B<GalleryCopyrightImage>
1782 Image you want to blend into your images in the lower right
1783 corner. This could be a transparent png saying "copyright
1788 =item B<GalleryWrapNavigation>
1790 Make the navigation in the picture view wrap around (So Next
1791 at the end displays the first picture, etc.)
1793 Set to 1 or 0, default is 0
1795 =item B<GalleryAllowOriginal>
1797 Allow the user to download the Original picture without
1798 resizing or putting the CopyrightImage on it.
1800 Set to 1 or 0, default is 0
1802 =item B<GalleryAllowThumbOnly>
1804 If true, B<GalleryAllowThumbOnly> allows fooimg.jpg?thumbonly urls
1805 to output the thumbnail of the image. This is useful when including
1806 images in a blog (or similar).
1808 Defaults to '0' (false).
1810 =item B<GallerySlideshowIntervals>
1812 With this option you can configure which intervals can be selected for
1813 a slideshow. The default is '3 5 10 15 30'
1815 =item B<GallerySortBy>
1817 Instead of the default filename ordering you can sort by any
1818 stat attribute. For example size, atime, mtime, ctime.
1820 =item B<GalleryDirSortBy>
1822 Set this variable to sort directories differently than other items,
1823 can be set to size, atime, mtime and ctime; setting any other value
1824 will revert to sorting by name.
1826 =item B<GalleryMemoize>
1828 Cache EXIF data using Memoize - this will make Apache::Gallery faster
1829 when many people access the same images, but it will also cache EXIF
1830 data until the current Apache child dies.
1832 =item B<GalleryUseFileDate>
1834 Set this option to 1 to make A::G show the files timestamp
1835 instead of the EXIF value for "Picture taken".
1837 =item B<GallerySelectionMode>
1839 Enable the selection mode. Select images with checkboxes and
1840 get a list of filenames.
1842 =item B<GalleryEXIFMode>
1844 You can choose how Apache::Gallery should display EXIF info
1847 The default setting is 'namevalue'. This setting will make
1848 Apache::Gallery print out the names and values of the EXIF values
1849 you configure with GalleryInfo. The information will be parsed into
1850 $INFO in pictureinfo.tpl.
1852 You can also set it to 'values' which will make A::G parse
1853 the configured values into the var $EXIFVALUES as 'value | value | value'
1855 If you set this option to 'variables' the items you configure in GalleryInfo
1856 will be available to your templates as $EXIF_<KEYNAME> (in all uppercase).
1857 That means that with the default setting "Picture Taken => DateTimeOriginal,
1858 Flash => Flash" you will have the variables $EXIF_DATETIMEORIGINAL and
1859 $EXIF_FLASH available to your templates. You can place them
1862 =item B<GalleryRootPath>
1864 Change the location of gallery root. The default is ""
1866 =item B<GalleryRootText>
1868 Change the name that appears as the root element in the menu. The
1871 =item B<GalleryMaxThumbnailsPerPage>
1873 This options controls how many thumbnails should be displayed in a
1874 page. It requires $BROWSELINKS to be in the index.tpl template file.
1876 =item B<GalleryImgFile>
1878 Pattern matching the files you want Apache::Gallery to view in the
1879 index as thumbnails.
1881 The default is '\.(jpe?g|png|tiff?|ppm)$'
1883 =item B<GalleryDocFile>
1885 Pattern matching the files you want Apache::Gallery to view in the index
1886 as normal files. All other filetypes will still be served by Apache::Gallery
1887 but are not visible in the index.
1889 The default is '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|mp4|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
1891 =item B<GalleryTTFDir>
1893 To use the GalleryCopyrightText feature you must set this option to the
1894 directory where your True Type fonts are stored. No default is set.
1898 PerlSetVar GalleryTTFDir '/usr/share/fonts/'
1900 =item B<GalleryTTFFile>
1902 To use the GalleryCopyrightText feature this option must be set to the
1903 name of the True Type font you wish to use. Example:
1905 PerlSetVar GalleryTTFFile 'verdanab.ttf'
1907 =item B<GalleryTTFSize>
1909 Configure the size of the CopyrightText that will be inserted as
1910 copyright notice in the corner of your pictures.
1914 PerlSetVar GalleryTTFSize '10'
1916 =item B<GalleryCopyrightText>
1918 The text that will be inserted as copyright notice.
1922 PerlSetVar GalleryCopyrightText '(c) Michael Legart'
1924 =item B<GalleryCopyrightColor>
1926 The text color of your copyright notice.
1931 PerlSetVar GalleryCopyrightColor '255,255,255,255'
1934 PerlSetVar GalleryCopyrightColor '0,0,0,255'
1937 PerlSetVar GalleryCopyrightColor '255,0,0,255'
1940 PerlSetVar GalleryCopyrightColor '0,255,0,255'
1943 PerlSetVar GalleryCopyrightColor '0,0,255,255'
1946 PerlSetVar GalleryCopyrightColor '255,127,0,127'
1948 =item B<GalleryCopyrightBackgroundColor>
1950 The background-color of a GalleryCopyrightText
1952 r,g,b,a - for examples, see GalleryCopyrightColor
1954 =item B<GalleryQuality>
1956 The quality (1-100) of scaled images
1958 This setting affects the quality of the scaled images.
1959 Set this to a low number to reduce the size of the scaled images.
1960 Remember to clear out your cache if you change this setting.
1961 Quality seems to default to 75, at least in the jpeg and png loader code in
1967 PerlSetVar GalleryQuality '50'
1969 =item B<GalleryUnderscoresToSpaces>
1971 Set this option to 1 to convert underscores to spaces in the listing
1972 of directory and file names, as well as in the alt attribute for HTML
1979 =item B<GalleryCommentExifKey>
1981 Set this option to e.g. ImageDescription to use this field as comments
1984 =item B<GalleryEnableMediaRss>
1986 Set this option to 1 to enable generation of a media RSS feed. This
1987 can be used e.g. together with the PicLens plugin from http://piclens.com
1995 =item B<Rotate images>
1997 Some cameras, like the Canon G3, detects the orientation of a picture
1998 and adds this info to the EXIF header. Apache::Gallery detects this
1999 and automatically rotates images with this info.
2001 If your camera does not support this, you can rotate the images
2002 manually, This can also be used to override the rotate information
2003 from a camera that supports that. You can also disable this behavior
2004 with the GalleryAutoRotate option.
2006 To use this functionality you have to create file with the name of the
2007 picture you want rotated appended with ".rotate". The file should include
2008 a number where these numbers are supported:
2010 "1", rotates clockwise by 90 degree
2011 "2", rotates clockwise by 180 degrees
2012 "3", rotates clockwise by 270 degrees
2014 So if we want to rotate "Picture1234.jpg" 90 degrees clockwise we would
2015 create a file in the same directory called "Picture1234.jpg.rotate" with
2016 the number 1 inside of it.
2018 =item B<Ignore directories/files>
2020 To ignore a directory or a file (of any kind, not only images) you
2021 create a <directory|file>.ignore file.
2025 To include comments for a directory you create a <directory>.comment
2026 file where the first line can contain "TITLE: New title" which
2027 will be the title of the page, and a comment on the following
2029 To include comments for each picture you create files called
2030 picture.jpg.comment where the first line can contain "TITLE: New
2031 title" which will be the title of the page, and a comment on the
2036 TITLE: This is the new title of the page
2037 And this is the comment.<br />
2038 And this is line two of the comment.
2040 The visible name of the folder is by default identical to the name of
2041 the folder, but can be changed by creating a file <directory>.folder
2042 with the visible name of the folder.
2044 Similarly, the visible name of any file is by default identical to the
2045 name of the file, but can be changed by creating a file <file>.file
2046 with the visible name of the file.
2048 It is also possible to set GalleryCommentExifKey to the name of an EXIF
2049 field containing the comment, e.g. ImageDescription. The EXIF comment is
2050 overridden by the .comment file if it exists.
2060 =item B<Apache with mod_perl>
2062 =item B<URI::Escape>
2064 =item B<Image::Info>
2066 =item B<Image::Size>
2068 =item B<Text::Template>
2070 =item B<Image::Imlib2>
2072 =item B<X11 libraries>
2076 Remember the -dev package when using rpm, deb or other package formats!
2082 Michael Legart <michael@legart.dk>
2084 =head1 COPYRIGHT AND LICENSE
2086 Copyright (C) 2001-2011 Michael Legart <michael@legart.dk>
2088 Templates designed by Thomas Kjaer <tk@lnx.dk>
2090 Apache::Gallery is free software and is released under the Artistic License.
2091 See B<http://www.perl.com/language/misc/Artistic.html> for details.
2093 The video icons are from the GNOME project. B<http://www.gnome.org/>
2097 Thanks to Thomas Kjaer for templates and design of B<http://apachegallery.dk>
2098 Thanks to Thomas Eibner and other for patches. (See the Changes file)
2102 L<perl>, L<mod_perl>, L<Image::Imlib2>, L<CGI::FastTemplate>,
2103 L<Image::Info>, and L<Image::Size>.