1 package Apache::Gallery;
3 # $Author: mil $ $Rev: 324 $
4 # $Date: 2011-02-22 21:56:06 +0100 (Tue, 22 Feb 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','NOT_FOUND','HTTP_NOT_MODIFIED');
35 require Apache::Constants;
36 require Apache::Request;
38 Apache::Constants->import('OK','DECLINED','FORBIDDEN','NOT_FOUND');
43 use Image::Info qw(image_info);
44 use Image::Size qw(imgsize);
55 use Digest::MD5 qw(md5_base64);
59 # Regexp for escaping URI's
60 my $escape_rule = "^A-Za-z0-9\-_.!~*'()\/";
65 my $r = shift or Apache2::RequestUtil->request();
67 unless (($r->method eq 'HEAD') or ($r->method eq 'GET')) {
68 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
71 if ((not $memoized) and ($r->dir_config('GalleryMemoize'))) {
73 Memoize::memoize('get_imageinfo');
77 $r->headers_out->{"X-Powered-By"} = "apachegallery.dk $VERSION - Hest design!";
78 $r->headers_out->{"X-Gallery-Version"} = '$Rev: 324 $ $Date: 2011-02-22 21:56:06 +0100 (Tue, 22 Feb 2011) $';
80 my $filename = $r->filename;
82 my $topdir = $filename;
84 my $media_rss_enabled = $r->dir_config('GalleryEnableMediaRss');
86 # Just return the http headers if the client requested that
87 if ($r->header_only) {
93 if (-f $filename or -d $filename) {
94 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
97 return $::MP2 ? Apache2::Const::NOT_FOUND() : Apache::Constants::NOT_FOUND();
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);
111 $r->send_http_header;
115 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
118 # Selectmode providing checkboxes beside all thumbnails
119 my $select_mode = $cgi->param('select');
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();
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) {
129 my $filename = $r->filename().$r->path_info();
130 $filename =~ s/\.cache//;
132 $filename =~ m/\/(\d+)x(\d+)\-/;
133 my $image_width = $1;
134 my $image_height = $2;
136 $filename =~ s/\/(\d+)x(\d+)\-//;
138 my ($width, $height, $type) = imgsize($filename);
140 my $imageinfo = get_imageinfo($r, $filename, $type, $width, $height);
142 my $cached = scale_picture($r, $filename, $image_width, $image_height, $imageinfo);
144 my $file = cache_dir($r, 0);
145 $file =~ s/\.cache//;
147 my $subr = $r->lookup_file($file);
148 $r->content_type($subr->content_type());
151 my $fileinfo = stat($file);
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();
158 if ($r->headers_in->{"If-Modified-Since"} && str2time($r->headers_in->{"If-Modified-Since"}) < $fileinfo->mtime) {
159 return Apache2::Const::HTTP_NOT_MODIFIED();
162 $r->headers_out->{"Content-Length"} = $fileinfo->size;
163 $r->headers_out->{"Last-Modified-Date"} = time2str($fileinfo->mtime);
164 $r->headers_out->{"ETag"} = $nonce;
166 return Apache2::Const::OK();
171 return Apache::Constants::DECLINED();
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();
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|html?|csv|eps)$'
188 my $img_pattern = $r->dir_config('GalleryImgFile');
189 unless ($img_pattern) {
190 $img_pattern = '\.(jpe?g|png|tiff?|ppm)$'
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();
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();
206 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
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",
228 $tpl_vars{TITLE} = "Index of: $uri";
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" />';
235 unless (opendir (DIR, $filename)) {
236 show_error ($r, 500, $!, "Unable to access directory $filename: $!");
237 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
240 $tpl_vars{MENU} = generate_menu($r);
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>':'';
245 # Read, sort, and filter files
246 my @files = grep { !/^\./ && -f "$filename/$_" } readdir (DIR);
248 @files=gallerysort($r, @files);
250 my @downloadable_files;
253 # Remove unwanted files from list
255 foreach my $picture (@files) {
257 my $file = $topdir."/".$picture;
259 if ($file =~ /$img_pattern/i) {
260 push (@new_files, $picture);
263 if ($file =~ /$doc_pattern/i) {
264 push (@downloadable_files, $picture);
271 # Read and sort directories
273 my @directories = grep { !/^\./ && -d "$filename/$_" } readdir (DIR);
275 if (defined($r->dir_config('GalleryDirSortBy'))) {
276 $dirsortby=$r->dir_config('GalleryDirSortBy');
278 $dirsortby=$r->dir_config('GallerySortBy');
280 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
281 @directories = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$dirsortby()." $_", @directories));
283 @directories = sort @directories;
289 # Combine directories and files to one listing
291 push (@listing, @directories);
292 push (@listing, @files);
293 push (@listing, @downloadable_files);
299 my $file_counter = 0;
301 my $max_files = $r->dir_config('GalleryMaxThumbnailsPerPage');
303 if (defined($cgi->param('start'))) {
304 $start_at = $cgi->param('start');
310 my $browse_links = "";
311 if (defined($max_files)) {
313 for (my $i=1; $i<=scalar(@listing); $i++) {
317 my $to = $i+$max_files-1;
318 if ($to > scalar(@listing)) {
319 $to = scalar(@listing);
322 if ($start_at < $from || $start_at > $to) {
323 $browse_links .= "<a href=\"?start=$from\">$from - ".$to."</a> ";
326 $browse_links .= "$from - $to ";
335 $tpl_vars{BROWSELINKS} = $browse_links;
338 foreach my $file (@listing) {
342 if ($file_counter < $start_at) {
346 if (defined($max_files) && $file_counter > $max_files+$start_at-1) {
350 my $thumbfilename = $topdir."/".$file;
352 my $fileurl = $uri."/".$file;
354 if (-d $thumbfilename) {
356 if (-e $thumbfilename . ".folder") {
357 $dirtitle = get_filecontent($thumbfilename . ".folder");
360 $dirtitle = $dirtitle ? $dirtitle : $file;
361 $dirtitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
364 $templates{directory}->fill_in(HASH=> {FILEURL => uri_escape($fileurl, $escape_rule),
370 elsif (-f $thumbfilename && $thumbfilename =~ /$doc_pattern/i && $thumbfilename !~ /$img_pattern/i) {
372 my $stat = stat($thumbfilename);
373 my $size = $stat->size;
376 if ($thumbfilename =~ m/\.(mpe?g|avi|mov|asf|wmv)$/i) {
377 $filetype = "video-$type";
378 } elsif ($thumbfilename =~ m/\.(txt|html?)$/i) {
379 $filetype = "text-$type";
380 } elsif ($thumbfilename =~ m/\.(mp3|ogg|wav)$/i) {
381 $filetype = "sound-$type";
382 } elsif ($thumbfilename =~ m/$doc_pattern/i) {
383 $filetype = "application-$type";
385 $filetype = "unknown";
389 $templates{file}->fill_in(HASH => {%tpl_vars,
390 FILEURL => uri_escape($fileurl, $escape_rule),
391 ALT => "Size: $size Bytes",
394 FILETYPE => $filetype,
398 elsif (-f $thumbfilename) {
400 my ($width, $height, $type) = imgsize($thumbfilename);
401 next if $type eq 'Data stream is not a known image file format';
403 my @filetypes = qw(JPG TIF PNG PPM GIF);
405 next unless (grep $type eq $_, @filetypes);
406 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);
407 my $imageinfo = get_imageinfo($r, $thumbfilename, $type, $width, $height);
408 my $cached = get_scaled_picture_name($thumbfilename, $thumbnailwidth, $thumbnailheight);
410 my $rotate = readfile_getnum($r, $imageinfo, $thumbfilename.".rotate");
411 my %file_vars = (FILEURL => uri_escape($fileurl, $escape_rule),
413 DATE => $imageinfo->{DateTimeOriginal} ? $imageinfo->{DateTimeOriginal} : '', # should this really be a stat of the file instead of ''?
414 SRC => uri_escape($uri."/.cache/$cached", $escape_rule),
415 HEIGHT => (grep($rotate==$_, (1, 3)) ? $thumbnailwidth : $thumbnailheight),
416 WIDTH => (grep($rotate==$_, (1, 3)) ? $thumbnailheight : $thumbnailwidth),
417 SELECT => $select_mode?'<input type="checkbox" name="selection" value="'.$file.'"> ':'',);
418 $tpl_vars{FILES} .= $templates{picture}->fill_in(HASH => {%tpl_vars,
423 if ($media_rss_enabled) {
424 my ($content_image_width, undef, $content_image_height) = get_image_display_size($cgi, $r, $width, $height);
426 THUMBNAIL => uri_escape($uri."/.cache/$cached", $escape_rule),
427 LINK => uri_escape($fileurl, $escape_rule),
429 CONTENT => uri_escape($uri."/.cache/".$content_image_width."x".$content_image_height."-".$file, $escape_rule)
431 $tpl_vars{ITEMS} .= $templates{rss_item}->fill_in(HASH => {
439 $tpl_vars{FILES} = "No files found";
440 $tpl_vars{BROWSELINKS} = "";
443 # Generate prev and next directory menu items
444 $filename =~ m/(.*)\/.*?$/;
445 my $parent_filename = $1;
447 $r->document_root =~ m/(.*)\/$/;
449 print STDERR "$filename vs $root_path\n";
450 if ($filename ne $root_path) {
451 unless (opendir (PARENT_DIR, $parent_filename)) {
452 show_error ($r, 500, $!, "Unable to access parent directory $parent_filename: $!");
453 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
456 my @neighbour_directories = grep { !/^\./ && -d "$parent_filename/$_" } readdir (PARENT_DIR);
458 if (defined($r->dir_config('GalleryDirSortBy'))) {
459 $dirsortby=$r->dir_config('GalleryDirSortBy');
461 $dirsortby=$r->dir_config('GallerySortBy');
463 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
464 @neighbour_directories = map(/^\d+ (.*)/, sort map(stat("$parent_filename/$_")->$dirsortby()." $_", @neighbour_directories));
466 @neighbour_directories = sort @neighbour_directories;
469 closedir(PARENT_DIR);
471 my $neightbour_counter = 0;
472 foreach my $neighbour_directory (@neighbour_directories) {
473 if ($parent_filename.'/'.$neighbour_directory eq $filename) {
474 if ($neightbour_counter > 0) {
475 print STDERR "prev directory is " .$neighbour_directories[$neightbour_counter-1] ."\n";
476 my $linktext = $neighbour_directories[$neightbour_counter-1];
477 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
478 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder");
481 URL => "../".$neighbour_directories[$neightbour_counter-1],
482 LINK_NAME => "<<< $linktext",
485 $tpl_vars{PREV_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
486 print STDERR $tpl_vars{PREV_DIR_FILES} ."\n";
489 if ($neightbour_counter < scalar @neighbour_directories - 1) {
490 my $linktext = $neighbour_directories[$neightbour_counter+1];
491 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
492 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder");
495 URL => "../".$neighbour_directories[$neightbour_counter+1],
496 LINK_NAME => "$linktext >>>",
499 $tpl_vars{NEXT_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
500 print STDERR "next directory is " .$neighbour_directories[$neightbour_counter+1] ."\n";
503 $neightbour_counter++;
507 if (-f $topdir . '.comment') {
508 my $comment_ref = get_comment($topdir . '.comment');
510 $comment_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
511 $comment_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
512 $tpl_vars{DIRCOMMENT} = $templates{comment}->fill_in(HASH => \%comment_vars);
513 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
515 $tpl_vars{DIRCOMMENT} = $templates{nocomment}->fill_in(HASH=>\%tpl_vars);
518 if ($cgi->param('rss')) {
519 $tpl_vars{MAIN} = $templates{rss}->fill_in(HASH => \%tpl_vars);
520 $r->content_type('application/rss+xml');
522 $tpl_vars{MAIN} = $templates{index}->fill_in(HASH => \%tpl_vars);
523 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
524 $r->content_type('text/html');
527 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
530 $r->send_http_header;
533 $r->print($tpl_vars{MAIN});
534 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
540 if (defined($ENV{QUERY_STRING}) && $ENV{QUERY_STRING} eq 'orig') {
541 if ($r->dir_config('GalleryAllowOriginal') ? 1 : 0) {
542 $r->filename($filename);
543 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
545 return $::MP2 ? Apache2::Const::FORBIDDEN() : Apache::Constants::FORBIDDEN();
549 # Create cache dir if not existing
550 my @tmp = split (/\//, $filename);
551 my $picfilename = pop @tmp;
552 my $path = (join "/", @tmp)."/";
553 my $cache_path = cache_dir($r, 1);
555 unless (-d $cache_path) {
556 unless (create_cache($r, $cache_path)) {
557 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
561 my ($orig_width, $orig_height, $type) = imgsize($filename);
563 my $imageinfo = get_imageinfo($r, $filename, $type, $orig_width, $orig_height);
565 my ($image_width, $width, $height, $original_size) = get_image_display_size($cgi, $r, $orig_width, $orig_height);
567 my $cached = get_scaled_picture_name($filename, $image_width, $height);
569 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
571 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
572 picture => "$tpl_dir/showpicture.tpl",
573 navpicture => "$tpl_dir/navpicture.tpl",
574 info => "$tpl_dir/info.tpl",
575 scale => "$tpl_dir/scale.tpl",
576 scaleactive => "$tpl_dir/scaleactive.tpl",
577 orig => "$tpl_dir/orig.tpl",
578 refresh => "$tpl_dir/refresh.tpl",
579 interval => "$tpl_dir/interval.tpl",
580 intervalactive => "$tpl_dir/intervalactive.tpl",
581 slideshowisoff => "$tpl_dir/slideshowisoff.tpl",
582 slideshowoff => "$tpl_dir/slideshowoff.tpl",
583 pictureinfo => "$tpl_dir/pictureinfo.tpl",
584 nopictureinfo => "$tpl_dir/nopictureinfo.tpl",
589 my $resolution = (($image_width > $orig_width) && ($height > $orig_height)) ?
590 "$orig_width x $orig_height" : "$image_width x $height";
592 $tpl_vars{TITLE} = "Viewing ".$r->uri()." at $image_width x $height";
593 $tpl_vars{META} = " ";
594 $tpl_vars{RESOLUTION} = $resolution;
595 $tpl_vars{MENU} = generate_menu($r);
596 $tpl_vars{SRC} = uri_escape(".cache/$cached", $escape_rule);
597 $tpl_vars{URI} = $r->uri();
599 my $exif_mode = $r->dir_config('GalleryEXIFMode');
600 unless ($exif_mode) {
601 $exif_mode = 'namevalue';
604 unless (opendir(DATADIR, $path)) {
605 show_error($r, 500, "Unable to access directory", "Unable to access directory $path");
606 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
608 my @pictures = grep { /$img_pattern/i } readdir (DATADIR);
610 @pictures = gallerysort($r, @pictures);
612 $tpl_vars{TOTAL} = scalar @pictures;
617 for (my $i=0; $i <= $#pictures; $i++) {
618 if ($pictures[$i] eq $picfilename) {
620 $tpl_vars{NUMBER} = $i+1;
622 $prevpicture = $pictures[$i-1];
623 my $displayprev = ($i>0 ? 1 : 0);
625 if ($r->dir_config("GalleryWrapNavigation")) {
626 $prevpicture = $pictures[$i>0 ? $i-1 : $#pictures];
629 if ($prevpicture and $displayprev) {
630 my ($orig_width, $orig_height, $type) = imgsize($path.$prevpicture);
631 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
632 my $imageinfo = get_imageinfo($r, $path.$prevpicture, $type, $orig_width, $orig_height);
633 my $cached = get_scaled_picture_name($path.$prevpicture, $thumbnailwidth, $thumbnailheight);
635 $nav_vars{URL} = uri_escape($prevpicture, $escape_rule);
636 $nav_vars{FILENAME} = $prevpicture;
637 $nav_vars{WIDTH} = $width;
638 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
639 $nav_vars{DIRECTION} = "« <u>p</u>rev";
640 $nav_vars{ACCESSKEY} = "P";
641 $tpl_vars{BACK} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
644 $tpl_vars{BACK} = " ";
647 $nextpicture = $pictures[$i+1];
648 if ($r->dir_config("GalleryWrapNavigation")) {
649 $nextpicture = $pictures[$i == $#pictures ? 0 : $i+1];
653 my ($orig_width, $orig_height, $type) = imgsize($path.$nextpicture);
654 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
655 my $imageinfo = get_imageinfo($r, $path.$nextpicture, $type, $thumbnailwidth, $thumbnailheight);
656 my $cached = get_scaled_picture_name($path.$nextpicture, $thumbnailwidth, $thumbnailheight);
658 $nav_vars{URL} = uri_escape($nextpicture, $escape_rule);
659 $nav_vars{FILENAME} = $nextpicture;
660 $nav_vars{WIDTH} = $width;
661 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
662 $nav_vars{DIRECTION} = "<u>n</u>ext »";
663 $nav_vars{ACCESSKEY} = "N";
665 $tpl_vars{NEXT} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
666 $tpl_vars{NEXTURL} = uri_escape($nextpicture, $escape_rule);
669 $tpl_vars{NEXT} = " ";
670 $tpl_vars{NEXTURL} = '#';
675 my $foundcomment = 0;
676 if (-f $path . '/' . $picfilename . '.comment') {
677 my $comment_ref = get_comment($path . '/' . $picfilename . '.comment');
679 $tpl_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
680 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
681 } elsif ($r->dir_config('GalleryCommentExifKey')) {
682 my $comment = decode("utf8", $imageinfo->{$r->dir_config('GalleryCommentExifKey')});
683 $tpl_vars{COMMENT} = encode("iso-8859-1", $comment);
685 $tpl_vars{COMMENT} = '';
688 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
693 my ($human_key, $exif_key) = (split " => ")[0,1];
694 my $value = $imageinfo->{$human_key};
695 if (defined($value)) {
699 if ($exif_mode eq 'namevalue') {
701 $info_vars{KEY} = $human_key;
702 $info_vars{VALUE} = $value;
703 $tpl_vars{INFO} .= $templates{info}->fill_in(HASH => \%info_vars);
706 if ($exif_mode eq 'variables') {
707 $tpl_vars{"EXIF_".uc($exif_key)} = $value;
710 if ($exif_mode eq 'values') {
711 $exifvalues .= "| ".$value." ";
718 if ($exif_mode eq 'values') {
719 if (defined($exifvalues)) {
720 $tpl_vars{EXIFVALUES} = $exifvalues;
723 $tpl_vars{EXIFVALUES} = "";
727 if ($foundcomment and !$foundinfo) {
728 $tpl_vars{INFO} = "";
731 if ($exif_mode ne 'namevalue') {
732 $tpl_vars{INFO} = "";
735 if ($exif_mode eq 'namevalue' && $foundinfo or $foundcomment) {
737 $tpl_vars{PICTUREINFO} = $templates{pictureinfo}->fill_in(HASH => \%tpl_vars);
739 unless (defined($exifvalues)) {
740 $tpl_vars{EXIFVALUES} = "";
745 $tpl_vars{PICTUREINFO} = $templates{nopictureinfo}->fill_in(HASH => \%tpl_vars);
748 # Fill in sizes and determine if any are smaller than the
749 # actual image. If they are, $scaleable=1
751 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
752 foreach my $size (@sizes) {
753 if ($size<=$original_size) {
755 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
756 $sizes_vars{SIZE} = $size;
757 $sizes_vars{WIDTH} = $size;
758 if ($width == $size) {
759 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
762 $tpl_vars{SIZES} .= $templates{scale}->fill_in(HASH => \%sizes_vars);
768 unless ($scaleable) {
770 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
771 $sizes_vars{SIZE} = $original_size;
772 $sizes_vars{WIDTH} = $original_size;
773 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
776 $tpl_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
778 if ($r->dir_config('GalleryAllowOriginal')) {
779 $tpl_vars{SIZES} .= $templates{orig}->fill_in(HASH => \%tpl_vars);
782 my @slideshow_intervals = split (/ /, $r->dir_config('GallerySlideshowIntervals') ? $r->dir_config('GallerySlideshowIntervals') : '3 5 10 15 30');
783 foreach my $interval (@slideshow_intervals) {
786 $slideshow_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
787 $slideshow_vars{SECONDS} = $interval;
788 $slideshow_vars{WIDTH} = ($width > $height ? $width : $height);
790 if ($cgi->param('slideshow') && $cgi->param('slideshow') == $interval and $nextpicture) {
791 $tpl_vars{SLIDESHOW} .= $templates{intervalactive}->fill_in(HASH => \%slideshow_vars);
795 $tpl_vars{SLIDESHOW} .= $templates{interval}->fill_in(HASH => \%slideshow_vars);
800 if ($cgi->param('slideshow') and $nextpicture) {
802 $tpl_vars{SLIDESHOW} .= $templates{slideshowoff}->fill_in(HASH => \%tpl_vars);
804 unless ((grep $cgi->param('slideshow') == $_, @slideshow_intervals)) {
805 show_error($r, 200, "Invalid interval", "Invalid slideshow interval choosen");
806 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
809 $tpl_vars{URL} = uri_escape($nextpicture, $escape_rule);
810 $tpl_vars{WIDTH} = ($width > $height ? $width : $height);
811 $tpl_vars{INTERVAL} = $cgi->param('slideshow');
812 $tpl_vars{META} .= $templates{refresh}->fill_in(HASH => \%tpl_vars);
816 $tpl_vars{SLIDESHOW} .= $templates{slideshowisoff}->fill_in(HASH => \%tpl_vars);
819 $tpl_vars{MAIN} = $templates{picture}->fill_in(HASH => \%tpl_vars);
820 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
822 $r->content_type('text/html');
823 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
826 $r->send_http_header;
829 $r->print($tpl_vars{MAIN});
830 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
838 my ($r, $strip_filename) = @_;
842 unless ($r->dir_config('GalleryCacheDir')) {
844 $cache_root = '/var/tmp/Apache-Gallery/';
845 if ($r->server->is_virtual) {
846 $cache_root = File::Spec->catdir($cache_root, $r->server->server_hostname);
848 $cache_root = File::Spec->catdir($cache_root, $r->location);
853 $cache_root = $r->dir_config('GalleryCacheDir');
857 # If the uri contains .cache we need to remove it
861 my (undef, $dirs, $filename) = File::Spec->splitpath($uri);
862 # We don't need a volume as this is a relative path
864 if ($strip_filename) {
865 return(File::Spec->canonpath(File::Spec->catdir($cache_root, $dirs)));
867 return(File::Spec->canonpath(File::Spec->catfile($cache_root, $dirs, $filename)));
875 unless (mkdirhier ($path)) {
876 show_error($r, 500, $!, "Unable to create cache directory in $path: $!");
889 unless (mkdir($dir, 0755)) {
891 $parent =~ s/\/[^\/]*$//;
900 sub get_scaled_picture_name {
902 my ($fullpath, $width, $height) = @_;
904 my (undef, undef, $type) = imgsize($fullpath);
906 my @dirs = split(/\//, $fullpath);
907 my $filename = pop(@dirs);
910 if (grep $type eq $_, qw(PPM TIF GIF)) {
911 $newfilename = $width."x".$height."-".$filename;
912 # needs to be configurable
913 $newfilename =~ s/\.(\w+)$/-$1\.jpg/;
915 $newfilename = $width."x".$height."-".$filename;
924 my ($r, $fullpath, $width, $height, $imageinfo) = @_;
926 my @dirs = split(/\//, $fullpath);
927 my $filename = pop(@dirs);
929 my ($orig_width, $orig_height, $type) = imgsize($fullpath);
931 my $cache = cache_dir($r, 1);
933 my $newfilename = get_scaled_picture_name($fullpath, $width, $height);
935 if (($width > $orig_width) && ($height > $orig_height)) {
936 # Run it through the resize code anyway to get watermarks
937 $width = $orig_width;
938 $height = $orig_height;
941 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
943 # Do we want to generate a new file in the cache?
946 if (-f $cache."/".$newfilename) {
949 # Check to see if the image has changed
950 my $filestat = stat($fullpath);
951 my $cachestat = stat($cache."/".$newfilename);
952 if ($filestat->mtime >= $cachestat->mtime) {
956 # Check to see if the .rotate file has been added or changed
957 if (-f $fullpath . ".rotate") {
958 my $rotatestat = stat($fullpath . ".rotate");
959 if ($rotatestat->mtime > $cachestat->mtime) {
963 # Check to see if the copyrightimage has been added or changed
964 if ($r->dir_config('GalleryCopyrightImage') && -f $r->dir_config('GalleryCopyrightImage')) {
965 unless ($width == $thumbnailwidth or $width == $thumbnailheight) {
966 my $copyrightstat = stat($r->dir_config('GalleryCopyrightImage'));
967 if ($copyrightstat->mtime > $cachestat->mtime) {
977 my $newpath = $cache."/".$newfilename;
978 my $rotate = readfile_getnum($r, $imageinfo, $fullpath . ".rotate");
979 my $quality = $r->dir_config('GalleryQuality');
981 if ($width == $thumbnailwidth or $width == $thumbnailheight) {
983 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, '', '', '', '', '', '');
987 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate,
988 ($r->dir_config('GalleryCopyrightImage') ? $r->dir_config('GalleryCopyrightImage') : ''),
989 ($r->dir_config('GalleryTTFDir') ? $r->dir_config('GalleryTTFDir') : ''),
990 ($r->dir_config('GalleryCopyrightText') ? $r->dir_config('GalleryCopyrightText') : ''),
991 ($r->dir_config('GalleryCopyrightColor') ? $r->dir_config('GalleryCopyrightColor') : ''),
992 ($r->dir_config('GalleryTTFFile') ? $r->dir_config('GalleryTTFFile') : ''),
993 ($r->dir_config('GalleryTTFSize') ? $r->dir_config('GalleryTTFSize') : ''),
994 ($r->dir_config('GalleryCopyrightBackgroundColor') ? $r->dir_config('GalleryCopyrightBackgroundColor') : ''),
1000 return $newfilename;
1004 sub get_thumbnailsize {
1005 my ($r, $orig_width, $orig_height) = @_;
1007 my $gallerythumbnailsize=$r->dir_config('GalleryThumbnailSize');
1009 if (defined($gallerythumbnailsize)) {
1010 warn("Invalid setting for GalleryThumbnailSize") unless
1011 $gallerythumbnailsize =~ /^\s*\d+\s*x\s*\d+\s*$/i;
1014 my ($thumbnailwidth, $thumbnailheight) = split(/x/i, ($gallerythumbnailsize) ? $gallerythumbnailsize : "100x75");
1016 my $width = $thumbnailwidth;
1017 my $height = $thumbnailheight;
1019 # If the image is rotated, flip everything around.
1020 if (defined $r->dir_config('GalleryThumbnailSizeLS')
1021 and $r->dir_config('GalleryThumbnailSizeLS') eq '1'
1022 and $orig_width < $orig_height) {
1024 $width = $thumbnailheight;
1025 $height = $thumbnailwidth;
1028 my $scale = ($orig_width ? $width/$orig_width : 1);
1031 if ($orig_height * $scale > $thumbnailheight) {
1032 $scale = $height/$orig_height;
1033 $width = $orig_width * $scale;
1037 $height = $orig_height * $scale;
1039 $height = floor($height);
1040 $width = floor($width);
1042 return ($width, $height);
1045 sub get_image_display_size {
1046 my ($cgi, $r, $orig_width, $orig_height) = @_;
1048 my $width = $orig_width;
1050 my $original_size=$orig_height;
1051 if ($orig_width>$orig_height) {
1052 $original_size=$orig_width;
1055 # Check if the selected width is allowed
1056 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
1058 my %cookies = fetch CGI::Cookie;
1060 if ($cgi->param('width')) {
1061 unless ((grep $cgi->param('width') == $_, @sizes) or ($cgi->param('width') == $original_size)) {
1062 show_error($r, 200, "Invalid width", "The specified width is invalid");
1063 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
1066 $width = $cgi->param('width');
1067 my $cookie = new CGI::Cookie(-name => 'GallerySize', -value => $width, -expires => '+6M');
1068 $r->headers_out->{'Set-Cookie'} = $cookie;
1070 } elsif ($cookies{'GallerySize'} && (grep $cookies{'GallerySize'}->value == $_, @sizes)) {
1072 $width = $cookies{'GallerySize'}->value;
1080 if ($orig_width<$orig_height) {
1081 $scale = ($orig_height ? $width/$orig_height: 1);
1082 $image_width=$width*$orig_width/$orig_height;
1085 $scale = ($orig_width ? $width/$orig_width : 1);
1086 $image_width = $width;
1089 my $height = $orig_height * $scale;
1091 $image_width = floor($image_width);
1092 $width = floor($width);
1093 $height = floor($height);
1095 return ($image_width, $width, $height, $original_size);
1099 my ($r, $file, $type, $width, $height) = @_;
1101 if ($type eq 'Data stream is not a known image file format') {
1102 # should never be reached, this is supposed to be handled outside of here
1103 log_error("Something was fishy with the type of the file $file\n");
1106 # Some files, like TIFF, PNG, GIF do not have EXIF info
1107 # embedded but use .thm files instead.
1108 $imageinfo = get_imageinfo_from_thm_file($file, $width, $height);
1110 # If there is no .thm file and our file is a JPEG file we try to extract the EXIf
1111 # info using Image::Info
1112 unless (defined($imageinfo) && (grep $type eq $_, qw(JPG))) {
1113 # Only for files that natively keep the EXIF info in the same file
1114 $imageinfo = image_info($file);
1118 unless (defined($imageinfo->{width}) and defined($imageinfo->{height})) {
1119 $imageinfo->{width} = $width;
1120 $imageinfo->{height} = $height;
1123 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
1126 my ($human_key, $exif_key) = (split " => ")[0,1];
1127 if (defined($exif_key) && defined($imageinfo->{$exif_key})) {
1129 if (ref($imageinfo->{$exif_key}) eq 'Image::TIFF::Rational') {
1130 $value = $imageinfo->{$exif_key}->as_string;
1132 elsif (ref($imageinfo->{$exif_key}) eq 'ARRAY') {
1133 foreach my $element (@{$imageinfo->{$exif_key}}) {
1134 if (ref($element) eq 'ARRAY') {
1135 foreach (@{$element}) {
1139 elsif (ref($element) eq 'HASH') {
1140 $value .= "<br />{ ";
1141 foreach (sort keys %{$element}) {
1142 $value .= "$_ = " . $element->{$_} . ' ';
1153 my $exif_value = $imageinfo->{$exif_key};
1154 if ($human_key eq 'Flash' && $exif_value =~ m/\d/) {
1159 "16" => "No (Compulsory) Should be External Flash",
1160 "17" => "Yes (External)",
1162 "25" => "Yes (Auto)",
1163 "73" => "Yes (Compulsory, Red Eye Reducing)",
1164 "89" => "Yes (Auto, Red Eye Reducing)"
1166 $exif_value = defined $flashmodes{$exif_value} ? $flashmodes{$exif_value} : 'unknown flash mode';
1168 $value = $exif_value;
1170 if ($exif_key eq 'MeteringMode') {
1171 my $exif_value = $imageinfo->{$exif_key};
1172 if ($exif_value =~ /^\d+$/) {
1173 my %meteringmodes = (
1176 '2' => 'CenterWeightedAverage',
1183 $exif_value = defined $meteringmodes{$exif_value} ? $meteringmodes{$exif_value} : 'unknown metering mode';
1185 $value = $exif_value;
1188 if ($exif_key eq 'LightSource') {
1189 my $exif_value = $imageinfo->{$exif_key};
1190 if ($exif_value =~ /^\d+$/) {
1191 my %lightsources = (
1194 '2' => 'Fluorescent',
1195 '3' => 'Tungsten (incandescent light)',
1197 '9' => 'Fine weather',
1198 '10' => 'Cloudy weather',
1200 '12' => 'Daylight fluorescent',
1201 '13' => 'Day white fluorescent',
1202 '14' => 'Cool white fluorescent',
1203 '15' => 'White fluorescent',
1204 '17' => 'Standard light A',
1205 '18' => 'Standard light B',
1206 '19' => 'Standard light C',
1211 '24' => 'ISO studio tungsten',
1212 '255' => 'other light source'
1214 $exif_value = defined $lightsources{$exif_value} ? $lightsources{$exif_value} : 'unknown light source';
1216 $value = $exif_value;
1218 if ($exif_key eq 'FocalLength') {
1219 if ($value =~ /^(\d+)\/(\d+)$/) {
1220 $value = eval { $1 / $2 };
1224 $value = int($value + 0.5) . "mm";
1229 if ($exif_key eq 'ShutterSpeedValue') {
1230 if ($value =~ /^((?:\-)?\d+)\/(\d+)$/) {
1231 $value = eval { $1 / $2 };
1236 $value = 1/(exp($value*log(2)));
1238 $value = "1/" . (int((1/$value)));
1240 $value = int($value*10)/10;
1246 $value = $value . " sec";
1251 if ($exif_key eq 'ApertureValue') {
1252 if ($value =~ /^(\d+)\/(\d+)$/) {
1253 $value = eval { $1 / $2 };
1257 # poor man's rounding
1258 $value = int(exp($value*log(2)*0.5)*10)/10;
1259 $value = "f" . $value;
1263 if ($exif_key eq 'FNumber') {
1264 if ($value =~ /^(\d+)\/(\d+)$/) {
1265 $value = eval { $1 / $2 };
1269 $value = int($value*10+0.5)/10;
1270 $value = "f" . $value;
1274 $imageinfo->{$human_key} = $value;
1278 if ($r->dir_config('GalleryUseFileDate') &&
1279 ($r->dir_config('GalleryUseFileDate') eq '1'
1280 || !$imageinfo->{"Picture Taken"} )) {
1282 my $st = stat($file);
1283 $imageinfo->{"DateTimeOriginal"} = $imageinfo->{"Picture Taken"} = scalar localtime($st->mtime) if $st;
1289 sub get_imageinfo_from_thm_file {
1291 my ($file, $width, $height) = @_;
1293 my $imageinfo = undef;
1294 # Windows based file extensions are often .THM, so check
1295 # for both .thm and .THM
1296 my $unix_file = $file;
1297 my $windows_file = $file;
1298 $unix_file =~ s/\.(\w+)$/.thm/;
1299 $windows_file =~ s/\.(\w+)$/.THM/;
1301 if (-e $unix_file && -f $unix_file && -r $unix_file) {
1302 $imageinfo = image_info($unix_file);
1303 $imageinfo->{width} = $width;
1304 $imageinfo->{height} = $height;
1306 elsif (-e $windows_file && -f $windows_file && -r $windows_file) {
1307 $imageinfo = image_info($windows_file);
1308 $imageinfo->{width} = $width;
1309 $imageinfo->{height} = $height;
1316 sub readfile_getnum {
1317 my ($r, $imageinfo, $filename) = @_;
1321 print STDERR "orientation: ".$imageinfo->{Orientation}."\n";
1322 # Check to see if the image contains the Orientation EXIF key,
1323 # but allow user to override using rotate
1324 if (!defined($r->dir_config("GalleryAutoRotate"))
1325 || $r->dir_config("GalleryAutoRotate") eq "1") {
1326 if (defined($imageinfo->{Orientation})) {
1327 print STDERR $imageinfo->{Orientation}."\n";
1328 if ($imageinfo->{Orientation} eq 'right_top') {
1331 elsif ($imageinfo->{Orientation} eq 'left_bot') {
1337 if (open(FH, "<$filename")) {
1341 unless ($temp =~ /^\d$/) {
1344 unless ($temp == 1 || $temp == 2 || $temp == 3) {
1353 sub get_filecontent {
1355 open(FH, $file) or return undef;
1366 my $filename = shift;
1367 my $comment_ref = {};
1368 $comment_ref->{TITLE} = undef;
1369 $comment_ref->{COMMENT} = '';
1371 open(FH, $filename) or return $comment_ref;
1373 if ($title =~ m/^TITLE: (.*)$/) {
1374 chomp($comment_ref->{TITLE} = $1);
1377 $comment_ref->{COMMENT} = $title;
1382 $comment_ref->{COMMENT} .= $_;
1386 return $comment_ref;
1391 my ($r, $statuscode, $errortitle, $error) = @_;
1393 my $tpl = $r->dir_config('GalleryTemplateDir');
1395 my %templates = create_templates({layout => "$tpl/layout.tpl",
1396 error => "$tpl/error.tpl",
1400 $tpl_vars{TITLE} = "Error! $errortitle";
1401 $tpl_vars{META} = "";
1402 $tpl_vars{ERRORTITLE} = "Error! $errortitle";
1403 $tpl_vars{ERROR} = $error;
1405 $tpl_vars{MAIN} = $templates{error}->fill_in(HASH => \%tpl_vars);
1407 $tpl_vars{PAGE} = $templates{layout}->fill_in(HASH => \%tpl_vars);
1409 $r->status($statuscode);
1410 $r->content_type('text/html');
1412 $r->print($tpl_vars{PAGE});
1420 my $root_text = (defined($r->dir_config('GalleryRootText')) ? $r->dir_config('GalleryRootText') : "root:" );
1421 my $root_path = (defined($r->dir_config('GalleryRootPath')) ? $r->dir_config('GalleryRootPath') : "" );
1423 my $subr = $r->lookup_uri($r->uri);
1424 my $filename = $subr->filename;
1426 my @links = split (/\//, $r->uri);
1428 $uri =~ s/^$root_path//g;
1430 @links = split (/\//, $uri);
1432 # Get the full path of the base directory
1435 my @direlem = split (/\//, $filename);
1436 for my $i ( 0 .. ( scalar(@direlem) - scalar(@links) ) ) {
1437 $dirname .= shift(@direlem) . '/';
1444 $picturename = pop(@links);
1447 if ($r->uri eq $root_path) {
1448 return qq{ <a href="$root_path">$root_text</a> };
1452 my $menuurl = $root_path;
1453 foreach my $link (@links) {
1455 $menuurl .= $link."/";
1456 my $linktext = $link;
1457 unless (length($link)) {
1458 $linktext = "$root_text ";
1462 $dirname = File::Spec->catdir($dirname, $link);
1464 if (-e $dirname . ".folder") {
1465 $linktext = get_filecontent($dirname . ".folder");
1469 if ("$root_path$uri" eq $menuurl) {
1470 $menu .= "$linktext / ";
1473 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule)."\">$linktext</a> / ";
1479 $menu .= $picturename;
1483 if ($r->dir_config('GallerySelectionMode') && $r->dir_config('GallerySelectionMode') eq '1') {
1484 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule);
1485 $menu .= "?select=1\">[select]</a> ";
1493 my ($r, $infile, $outfile, $x, $y, $rotate, $copyrightfile, $GalleryTTFDir, $GalleryCopyrightText, $text_color, $GalleryTTFFile, $GalleryTTFSize, $GalleryCopyrightBackgroundColor, $quality) = @_;
1496 my $image = Image::Imlib2->load($infile) or warn("Unable to open file $infile, $!");
1499 $image=$image->create_scaled_image($x, $y) or warn("Unable to scale image $infile. Are you running out of memory?");
1503 $image->image_orientate($rotate);
1506 # blend copyright image onto image
1507 if ($copyrightfile ne '') {
1508 if (-f $copyrightfile and (my $logo=Image::Imlib2->load($copyrightfile))) {
1509 my $x = $image->get_width();
1510 my $y = $image->get_height();
1511 my $logox = $logo->get_width();
1512 my $logoy = $logo->get_height();
1513 $image->blend($logo, 0, 0, 0, $logox, $logoy, $x-$logox, $y-$logoy, $logox, $logoy);
1516 log_error("GalleryCopyrightImage $copyrightfile was not found");
1520 if ($GalleryTTFDir && $GalleryCopyrightText && $GalleryTTFFile && $text_color) {
1521 if (!-d $GalleryTTFDir) {
1523 log_error("GalleryTTFDir $GalleryTTFDir is not a dir\n");
1525 } elsif ($GalleryCopyrightText eq '') {
1527 log_error("GalleryCopyrightText is empty. No text inserted to picture\n");
1529 } elsif (!-e "$GalleryTTFDir/$GalleryTTFFile") {
1531 log_error("GalleryTTFFile $GalleryTTFFile was not found\n");
1535 $GalleryTTFFile =~ s/\.TTF$//i;
1536 $image->add_font_path("$GalleryTTFDir");
1538 $image->load_font("$GalleryTTFFile/$GalleryTTFSize");
1539 my($text_x, $text_y) = $image->get_text_size("$GalleryCopyrightText");
1540 my $x = $image->get_width();
1541 my $y = $image->get_height();
1545 if (($text_x < $x - $offset) && ($text_y < $y - $offset)) {
1546 if ($GalleryCopyrightBackgroundColor =~ /^\d+,\d+,\d+,\d+$/) {
1547 my ($br_val, $bg_val, $bb_val, $ba_val) = split (/,/, $GalleryCopyrightBackgroundColor);
1548 $image->set_colour($br_val, $bg_val, $bb_val, $ba_val);
1549 $image->fill_rectangle ($x-$text_x-$offset, $y-$text_y-$offset, $text_x, $text_y);
1551 my ($r_val, $g_val, $b_val, $a_val) = split (/,/, $text_color);
1552 $image->set_colour($r_val, $g_val, $b_val, $a_val);
1553 $image->draw_text($x-$text_x-$offset, $y-$text_y-$offset, "$GalleryCopyrightText");
1555 log_error("Text is to big for the picture.\n");
1560 if ($quality && $quality =~ m/^\d+$/) {
1561 $image->set_quality($quality);
1564 $image->save($outfile);
1571 my $sortby = $r->dir_config('GallerySortBy');
1572 my $filename=$r->lookup_uri($r->uri)->filename;
1573 $filename=(File::Spec->splitpath($filename))[1] if (-f $filename);
1574 if ($sortby && $sortby =~ m/^(size|atime|mtime|ctime)$/) {
1575 @files = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$sortby()." $_", @files));
1577 @files = sort @files;
1582 # Create Text::Template objects used by Apache::Gallery. Takes a
1583 # hashref of template_name, template_filename pairs, and returns a
1584 # list of template_name, texttemplate_object pairs.
1585 sub create_templates {
1586 my $templates = shift;
1588 # This routine is called whenever a template has an error. Prints
1589 # the error to STDERR and sticks the error in the output
1592 # Pull out the name and filename from the arg option [see
1593 # Text::Template for details]
1594 @args{qw(name file)} = @{$args{arg}};
1595 print STDERR qq(Template $args{name} ("$args{file}") is broken: $args{error});
1596 # Don't include the file name in the output, as the user can see this.
1597 return qq(<!-- Template $args{name} is broken: $args{error} -->);
1602 my %texttemplate_objects;
1604 for my $template_name (keys %$templates) {
1605 my $tt_obj = Text::Template->new(TYPE => 'FILE',
1606 SOURCE => $$templates{$template_name},
1607 BROKEN => \&tt_broken,
1608 BROKEN_ARG => [$template_name, $$templates{$template_name}],
1610 or die "Unable to create new Text::Template object for $template_name: $Text::Template::ERROR";
1611 $texttemplate_objects{$template_name} = $tt_obj;
1613 return %texttemplate_objects;
1618 Apache2::RequestUtil->request->log_error(shift());
1620 Apache->request->log_error(shift());
1628 Apache::Gallery - mod_perl handler to create an image gallery
1632 See the INSTALL file in the distribution for installation instructions.
1636 Apache::Gallery creates an thumbnail index of each directory and allows
1637 viewing pictures in different resolutions. Pictures are resized on the
1638 fly and cached. The gallery can be configured and customized in many ways
1639 and a custom copyright image can be added to all the images without
1640 modifying the original.
1642 =head1 CONFIGURATION
1644 In your httpd.conf you set the global options for the gallery. You can
1645 also override each of the options in .htaccess files in your gallery
1648 The options are set in the httpd.conf/.htaccess file using the syntax:
1649 B<PerlSetVar OptionName 'value'>
1651 Example: B<PerlSetVar GalleryCacheDir '/var/tmp/Apache-Gallery/'>
1655 =item B<GalleryAutoRotate>
1657 Some cameras, like the Canon G3, can detect the orientation of a
1658 the pictures you take and will save this information in the
1659 'Orientation' EXIF field. Apache::Gallery will then automatically
1662 This behavior is default but can be disabled by setting GalleryAutoRotate
1665 =item B<GalleryCacheDir>
1667 Directory where Apache::Gallery should create its cache with scaled
1668 pictures. The default is /var/tmp/Apache-Gallery/ . Here, a directory
1669 for each virtualhost or location will be created automaticly. Make
1670 sure your webserver has write access to the CacheDir.
1672 =item B<GalleryTemplateDir>
1674 Full path to the directory where you placed the templates. This option
1675 can be used both in your global configuration and in .htaccess files,
1676 this way you can have different layouts in different parts of your
1679 No default value, this option is required.
1681 =item B<GalleryInfo>
1683 With this option you can define which EXIF information you would like
1684 to present from the image. The format is: '<MyName => KeyInEXIF,
1685 MyOtherName => OtherKeyInEXIF'
1687 Examples of keys: B<ShutterSpeedValue>, B<ApertureValue>, B<SubjectDistance>,
1690 You can view all the keys from the EXIF header using this perl-oneliner:
1692 perl C<-e> 'use Data::Dumper; use Image::Info qw(image_info); print Dumper(image_info(shift));' filename.jpg
1694 Default is: 'Picture Taken => DateTimeOriginal, Flash => Flash'
1696 =item B<GallerySizes>
1698 Defines which widths images can be scaled to. Images cannot be
1699 scaled to other widths than the ones you define with this option.
1701 The default is '640 800 1024 1600'
1703 =item B<GalleryThumbnailSize>
1705 Defines the width and height of the thumbnail images.
1707 Defaults to '100x75'
1709 =item B<GalleryThumbnailSizeLS>
1711 If set to '1', B<GalleryThumbnailSize> is the long and the short side of
1712 the thumbnail image instead of the width and height.
1716 =item B<GalleryCopyrightImage>
1718 Image you want to blend into your images in the lower right
1719 corner. This could be a transparent png saying "copyright
1724 =item B<GalleryWrapNavigation>
1726 Make the navigation in the picture view wrap around (So Next
1727 at the end displays the first picture, etc.)
1729 Set to 1 or 0, default is 0
1731 =item B<GalleryAllowOriginal>
1733 Allow the user to download the Original picture without
1734 resizing or putting the CopyrightImage on it.
1736 Set to 1 or 0, default is 0
1738 =item B<GallerySlideshowIntervals>
1740 With this option you can configure which intervals can be selected for
1741 a slideshow. The default is '3 5 10 15 30'
1743 =item B<GallerySortBy>
1745 Instead of the default filename ordering you can sort by any
1746 stat attribute. For example size, atime, mtime, ctime.
1748 =item B<GalleryDirSortBy>
1750 Set this variable to sort directories differently than other items,
1751 can be set to size, atime, mtime and ctime; setting any other value
1752 will revert to sorting by name.
1754 =item B<GalleryMemoize>
1756 Cache EXIF data using Memoize - this will make Apache::Gallery faster
1757 when many people access the same images, but it will also cache EXIF
1758 data until the current Apache child dies.
1760 =item B<GalleryUseFileDate>
1762 Set this option to 1 to make A::G show the files timestamp
1763 instead of the EXIF value for "Picture taken".
1765 =item B<GallerySelectionMode>
1767 Enable the selection mode. Select images with checkboxes and
1768 get a list of filenames.
1770 =item B<GalleryEXIFMode>
1772 You can choose how Apache::Gallery should display EXIF info
1775 The default setting is 'namevalue'. This setting will make
1776 Apache::Gallery print out the names and values of the EXIF values
1777 you configure with GalleryInfo. The information will be parsed into
1778 $INFO in pictureinfo.tpl.
1780 You can also set it to 'values' which will make A::G parse
1781 the configured values into the var $EXIFVALUES as 'value | value | value'
1783 If you set this option to 'variables' the items you configure in GalleryInfo
1784 will be available to your templates as $EXIF_<KEYNAME> (in all uppercase).
1785 That means that with the default setting "Picture Taken => DateTimeOriginal,
1786 Flash => Flash" you will have the variables $EXIF_DATETIMEORIGINAL and
1787 $EXIF_FLASH avilable to your templates. You can place them
1790 =item B<GalleryRootPath>
1792 Change the location of gallery root. The default is ""
1794 =item B<GalleryRootText>
1796 Change the name that appears as the root element in the menu. The
1799 =item B<GalleryMaxThumbnailsPerPage>
1801 This options controls how many thumbnails should be displayed in a
1802 page. It requires $BROWSELINKS to be in the index.tpl template file.
1804 =item B<GalleryImgFile>
1806 Pattern matching the files you want Apache::Gallery to view in the
1807 index as thumbnails.
1809 The default is '\.(jpe?g|png|tiff?|ppm)$'
1811 =item B<GalleryDocFile>
1813 Pattern matching the files you want Apache::Gallery to view in the index
1814 as normal files. All other filetypes will still be served by Apache::Gallery
1815 but are not visible in the index.
1817 The default is '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|ogg|pdf|rtf|wav|dlt|html?|csv|eps)$'
1819 =item B<GalleryTTFDir>
1821 To use the GalleryCopyrightText feature you must set this option to the
1822 directory where your True Type fonts are stored. No default is set.
1826 PerlSetVar GalleryTTFDir '/usr/share/fonts/'
1828 =item B<GalleryTTFFile>
1830 To use the GalleryCopyrightText feature this option must be set to the
1831 name of the True Type font you wish to use. Example:
1833 PerlSetVar GalleryTTFFile 'verdanab.ttf'
1835 =item B<GalleryTTFSize>
1837 Configure the size of the CopyrightText that will be inserted as
1838 copyright notice in the corner of your pictures.
1842 PerlSetVar GalleryTTFSize '10'
1844 =item B<GalleryCopyrightText>
1846 The text that will be inserted as copyright notice.
1850 PerlSetVar GalleryCopyrightText '(c) Michael Legart'
1852 =item B<GalleryCopyrightColor>
1854 The text color of your copyright notice.
1859 PerlSetVar GalleryCopyrightColor '255,255,255,255'
1862 PerlSetVar GalleryCopyrightColor '0,0,0,255'
1865 PerlSetVar GalleryCopyrightColor '255,0,0,255'
1868 PerlSetVar GalleryCopyrightColor '0,255,0,255'
1871 PerlSetVar GalleryCopyrightColor '0,0,255,255'
1874 PerlSetVar GalleryCopyrightColor '255,127,0,127'
1876 =item B<GalleryCopyrightBackgroundColor>
1878 The background-color of a GalleryCopyrightText
1880 r,g,b,a - for examples, see GalleryCopyrightColor
1882 =item B<GalleryQuality>
1884 The quality (1-100) of scaled images
1886 This setting affects the quality of the scaled images.
1887 Set this to a low number to reduce the size of the scaled images.
1888 Remember to clear out your cache if you change this setting.
1889 Quality seems to default to 75, at least in the jpeg and png loader code in
1895 PerlSetVar GalleryQuality '50'
1897 =item B<GalleryUnderscoresToSpaces>
1899 Set this option to 1 to convert underscores to spaces in the listing
1904 =item B<GalleryCommentExifKey>
1906 Set this option to e.g. ImageDescription to use this field as comments
1909 =item B<GalleryEnableMediaRss>
1911 Set this option to 1 to enable generation of a media RSS feed. This
1912 can be used e.g. together with the PicLens plugin from http://piclens.com
1918 =item B<Rotate images>
1920 Some cameras, like the Canon G3, detects the orientation of a picture
1921 and adds this info to the EXIF header. Apache::Gallery detects this
1922 and automaticly rotates images with this info.
1924 If your camera does not support this, you can rotate the images
1925 manually, This can also be used to override the rotate information
1926 from a camera that supports that. You can also disable this behavior
1927 with the GalleryAutoRotate option.
1929 To use this functionality you have to create file with the name of the
1930 picture you want rotated appened with ".rotate". The file should include
1931 a number where these numbers are supported:
1933 "1", rotates clockwise by 90 degree
1934 "2", rotates clockwise by 180 degrees
1935 "3", rotates clockwise by 270 degrees
1937 So if we want to rotate "Picture1234.jpg" 90 degrees clockwise we would
1938 create a file in the same directory called "Picture1234.jpg.rotate" with
1939 the number 1 inside of it.
1943 To include comments for a directory you create a <directory>.comment
1944 file where the first line can contain "TITLE: New title" which
1945 will be the title of the page, and a comment on the following
1947 To include comments for each picture you create files called
1948 picture.jpg.comment where the first line can contain "TITLE: New
1949 title" which will be the title of the page, and a comment on the
1954 TITLE: This is the new title of the page
1955 And this is the comment.<br />
1956 And this is line two of the comment.
1958 The visible name of the folder is by default identical to the name of
1959 the folder, but can be changed by creating a file <directory>.folder
1960 with the visible name of the folder.
1962 It is also possible to set GalleryCommentExifKey to the name of an EXIF
1963 field containing the comment, e.g. ImageDescription. The EXIF comment is
1964 overridden by the .comment file if it exists.
1974 =item B<Apache with mod_perl>
1976 =item B<URI::Escape>
1978 =item B<Image::Info>
1980 =item B<Image::Size>
1982 =item B<Text::Template>
1984 =item B<Image::Imlib2>
1986 =item B<X11 libraries>
1990 Remember the -dev package when using rpm, deb or other package formats!
1996 Michael Legart <michael@legart.dk>
1998 =head1 COPYRIGHT AND LICENSE
2000 Copyright (C) 2001-2005 Michael Legart <michael@legart.dk>
2002 Templates designed by Thomas Kjaer <tk@lnx.dk>
2004 Apache::Gallery is free software and is released under the Artistic License.
2005 See B<http://www.perl.com/language/misc/Artistic.html> for details.
2007 The video icons are from the GNOME project. B<http://www.gnome.org/>
2011 Thanks to Thomas Kjaer for templates and design of B<http://apachegallery.dk>
2012 Thanks to Thomas Eibner and other for patches. (See the Changes file)
2016 L<perl>, L<mod_perl>, L<Image::Imlib2>, L<CGI::FastTemplate>,
2017 L<Image::Info>, and L<Image::Size>.