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|txt|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";
388 # Debian bug #348724 <http://bugs.debian.org/348724>
390 my $filetitle = $file;
391 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
394 $templates{file}->fill_in(HASH => {%tpl_vars,
395 FILEURL => uri_escape($fileurl, $escape_rule),
396 ALT => "Size: $size Bytes",
399 FILETYPE => $filetype,
403 elsif (-f $thumbfilename) {
405 my ($width, $height, $type) = imgsize($thumbfilename);
406 next if $type eq 'Data stream is not a known image file format';
408 my @filetypes = qw(JPG TIF PNG PPM GIF);
410 next unless (grep $type eq $_, @filetypes);
411 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);
412 my $imageinfo = get_imageinfo($r, $thumbfilename, $type, $width, $height);
413 my $cached = get_scaled_picture_name($thumbfilename, $thumbnailwidth, $thumbnailheight);
415 my $rotate = readfile_getnum($r, $imageinfo, $thumbfilename.".rotate");
417 # Debian bug #348724 <http://bugs.debian.org/348724>
418 # HTML <img> tag, alt attribute
419 my $filetitle = $file;
420 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
422 my %file_vars = (FILEURL => uri_escape($fileurl, $escape_rule),
424 DATE => $imageinfo->{DateTimeOriginal} ? $imageinfo->{DateTimeOriginal} : '', # should this really be a stat of the file instead of ''?
425 SRC => uri_escape($uri."/.cache/$cached", $escape_rule),
426 HEIGHT => (grep($rotate==$_, (1, 3)) ? $thumbnailwidth : $thumbnailheight),
427 WIDTH => (grep($rotate==$_, (1, 3)) ? $thumbnailheight : $thumbnailwidth),
428 SELECT => $select_mode?'<input type="checkbox" name="selection" value="'.$file.'"> ':'',);
429 $tpl_vars{FILES} .= $templates{picture}->fill_in(HASH => {%tpl_vars,
434 if ($media_rss_enabled) {
435 my ($content_image_width, undef, $content_image_height) = get_image_display_size($cgi, $r, $width, $height);
437 THUMBNAIL => uri_escape($uri."/.cache/$cached", $escape_rule),
438 LINK => uri_escape($fileurl, $escape_rule),
440 CONTENT => uri_escape($uri."/.cache/".$content_image_width."x".$content_image_height."-".$file, $escape_rule)
442 $tpl_vars{ITEMS} .= $templates{rss_item}->fill_in(HASH => {
450 $tpl_vars{FILES} = "No files found";
451 $tpl_vars{BROWSELINKS} = "";
454 # Generate prev and next directory menu items
455 $filename =~ m/(.*)\/.*?$/;
456 my $parent_filename = $1;
458 $r->document_root =~ m/(.*)\/$/;
460 print STDERR "$filename vs $root_path\n";
461 if ($filename ne $root_path) {
462 unless (opendir (PARENT_DIR, $parent_filename)) {
463 show_error ($r, 500, $!, "Unable to access parent directory $parent_filename: $!");
464 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
467 my @neighbour_directories = grep { !/^\./ && -d "$parent_filename/$_" } readdir (PARENT_DIR);
469 if (defined($r->dir_config('GalleryDirSortBy'))) {
470 $dirsortby=$r->dir_config('GalleryDirSortBy');
472 $dirsortby=$r->dir_config('GallerySortBy');
474 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
475 @neighbour_directories = map(/^\d+ (.*)/, sort map(stat("$parent_filename/$_")->$dirsortby()." $_", @neighbour_directories));
477 @neighbour_directories = sort @neighbour_directories;
480 closedir(PARENT_DIR);
482 my $neightbour_counter = 0;
483 foreach my $neighbour_directory (@neighbour_directories) {
484 if ($parent_filename.'/'.$neighbour_directory eq $filename) {
485 if ($neightbour_counter > 0) {
486 print STDERR "prev directory is " .$neighbour_directories[$neightbour_counter-1] ."\n";
487 my $linktext = $neighbour_directories[$neightbour_counter-1];
488 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
489 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder");
492 URL => "../".$neighbour_directories[$neightbour_counter-1],
493 LINK_NAME => "<<< $linktext",
496 $tpl_vars{PREV_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
497 print STDERR $tpl_vars{PREV_DIR_FILES} ."\n";
500 if ($neightbour_counter < scalar @neighbour_directories - 1) {
501 my $linktext = $neighbour_directories[$neightbour_counter+1];
502 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
503 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder");
506 URL => "../".$neighbour_directories[$neightbour_counter+1],
507 LINK_NAME => "$linktext >>>",
510 $tpl_vars{NEXT_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
511 print STDERR "next directory is " .$neighbour_directories[$neightbour_counter+1] ."\n";
514 $neightbour_counter++;
518 if (-f $topdir . '.comment') {
519 my $comment_ref = get_comment($topdir . '.comment');
521 $comment_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
522 $comment_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
523 $tpl_vars{DIRCOMMENT} = $templates{comment}->fill_in(HASH => \%comment_vars);
524 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
526 $tpl_vars{DIRCOMMENT} = $templates{nocomment}->fill_in(HASH=>\%tpl_vars);
529 if ($cgi->param('rss')) {
530 $tpl_vars{MAIN} = $templates{rss}->fill_in(HASH => \%tpl_vars);
531 $r->content_type('application/rss+xml');
533 $tpl_vars{MAIN} = $templates{index}->fill_in(HASH => \%tpl_vars);
534 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
535 $r->content_type('text/html');
538 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
541 $r->send_http_header;
544 $r->print($tpl_vars{MAIN});
545 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
551 if (defined($ENV{QUERY_STRING}) && $ENV{QUERY_STRING} eq 'orig') {
552 if ($r->dir_config('GalleryAllowOriginal') ? 1 : 0) {
553 $r->filename($filename);
554 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
556 return $::MP2 ? Apache2::Const::FORBIDDEN() : Apache::Constants::FORBIDDEN();
560 # Create cache dir if not existing
561 my @tmp = split (/\//, $filename);
562 my $picfilename = pop @tmp;
563 my $path = (join "/", @tmp)."/";
564 my $cache_path = cache_dir($r, 1);
566 unless (-d $cache_path) {
567 unless (create_cache($r, $cache_path)) {
568 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
572 my ($orig_width, $orig_height, $type) = imgsize($filename);
574 my $imageinfo = get_imageinfo($r, $filename, $type, $orig_width, $orig_height);
576 my ($image_width, $width, $height, $original_size) = get_image_display_size($cgi, $r, $orig_width, $orig_height);
578 my $cached = get_scaled_picture_name($filename, $image_width, $height);
580 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
582 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
583 picture => "$tpl_dir/showpicture.tpl",
584 navpicture => "$tpl_dir/navpicture.tpl",
585 info => "$tpl_dir/info.tpl",
586 scale => "$tpl_dir/scale.tpl",
587 scaleactive => "$tpl_dir/scaleactive.tpl",
588 orig => "$tpl_dir/orig.tpl",
589 refresh => "$tpl_dir/refresh.tpl",
590 interval => "$tpl_dir/interval.tpl",
591 intervalactive => "$tpl_dir/intervalactive.tpl",
592 slideshowisoff => "$tpl_dir/slideshowisoff.tpl",
593 slideshowoff => "$tpl_dir/slideshowoff.tpl",
594 pictureinfo => "$tpl_dir/pictureinfo.tpl",
595 nopictureinfo => "$tpl_dir/nopictureinfo.tpl",
600 my $resolution = (($image_width > $orig_width) && ($height > $orig_height)) ?
601 "$orig_width x $orig_height" : "$image_width x $height";
603 $tpl_vars{TITLE} = "Viewing ".$r->uri()." at $image_width x $height";
604 $tpl_vars{META} = " ";
605 $tpl_vars{RESOLUTION} = $resolution;
606 $tpl_vars{MENU} = generate_menu($r);
607 $tpl_vars{SRC} = uri_escape(".cache/$cached", $escape_rule);
608 $tpl_vars{URI} = $r->uri();
610 my $exif_mode = $r->dir_config('GalleryEXIFMode');
611 unless ($exif_mode) {
612 $exif_mode = 'namevalue';
615 unless (opendir(DATADIR, $path)) {
616 show_error($r, 500, "Unable to access directory", "Unable to access directory $path");
617 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
619 my @pictures = grep { /$img_pattern/i } readdir (DATADIR);
621 @pictures = gallerysort($r, @pictures);
623 $tpl_vars{TOTAL} = scalar @pictures;
628 for (my $i=0; $i <= $#pictures; $i++) {
629 if ($pictures[$i] eq $picfilename) {
631 $tpl_vars{NUMBER} = $i+1;
633 $prevpicture = $pictures[$i-1];
634 my $displayprev = ($i>0 ? 1 : 0);
636 if ($r->dir_config("GalleryWrapNavigation")) {
637 $prevpicture = $pictures[$i>0 ? $i-1 : $#pictures];
640 if ($prevpicture and $displayprev) {
641 my ($orig_width, $orig_height, $type) = imgsize($path.$prevpicture);
642 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
643 my $imageinfo = get_imageinfo($r, $path.$prevpicture, $type, $orig_width, $orig_height);
644 my $cached = get_scaled_picture_name($path.$prevpicture, $thumbnailwidth, $thumbnailheight);
646 $nav_vars{URL} = uri_escape($prevpicture, $escape_rule);
647 $nav_vars{FILENAME} = $prevpicture;
648 $nav_vars{WIDTH} = $width;
649 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
650 $nav_vars{DIRECTION} = "« <u>p</u>rev";
651 $nav_vars{ACCESSKEY} = "P";
652 $tpl_vars{BACK} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
655 $tpl_vars{BACK} = " ";
658 $nextpicture = $pictures[$i+1];
659 if ($r->dir_config("GalleryWrapNavigation")) {
660 $nextpicture = $pictures[$i == $#pictures ? 0 : $i+1];
664 my ($orig_width, $orig_height, $type) = imgsize($path.$nextpicture);
665 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
666 my $imageinfo = get_imageinfo($r, $path.$nextpicture, $type, $thumbnailwidth, $thumbnailheight);
667 my $cached = get_scaled_picture_name($path.$nextpicture, $thumbnailwidth, $thumbnailheight);
669 $nav_vars{URL} = uri_escape($nextpicture, $escape_rule);
670 $nav_vars{FILENAME} = $nextpicture;
671 $nav_vars{WIDTH} = $width;
672 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
673 $nav_vars{DIRECTION} = "<u>n</u>ext »";
674 $nav_vars{ACCESSKEY} = "N";
676 $tpl_vars{NEXT} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
677 $tpl_vars{NEXTURL} = uri_escape($nextpicture, $escape_rule);
680 $tpl_vars{NEXT} = " ";
681 $tpl_vars{NEXTURL} = '#';
686 my $foundcomment = 0;
687 if (-f $path . '/' . $picfilename . '.comment') {
688 my $comment_ref = get_comment($path . '/' . $picfilename . '.comment');
690 $tpl_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
691 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
692 } elsif ($r->dir_config('GalleryCommentExifKey')) {
693 my $comment = decode("utf8", $imageinfo->{$r->dir_config('GalleryCommentExifKey')});
694 $tpl_vars{COMMENT} = encode("iso-8859-1", $comment);
696 $tpl_vars{COMMENT} = '';
699 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
704 my ($human_key, $exif_key) = (split " => ")[0,1];
705 my $value = $imageinfo->{$human_key};
706 if (defined($value)) {
710 if ($exif_mode eq 'namevalue') {
712 $info_vars{KEY} = $human_key;
713 $info_vars{VALUE} = $value;
714 $tpl_vars{INFO} .= $templates{info}->fill_in(HASH => \%info_vars);
717 if ($exif_mode eq 'variables') {
718 $tpl_vars{"EXIF_".uc($exif_key)} = $value;
721 if ($exif_mode eq 'values') {
722 $exifvalues .= "| ".$value." ";
729 if ($exif_mode eq 'values') {
730 if (defined($exifvalues)) {
731 $tpl_vars{EXIFVALUES} = $exifvalues;
734 $tpl_vars{EXIFVALUES} = "";
738 if ($foundcomment and !$foundinfo) {
739 $tpl_vars{INFO} = "";
742 if ($exif_mode ne 'namevalue') {
743 $tpl_vars{INFO} = "";
746 if ($exif_mode eq 'namevalue' && $foundinfo or $foundcomment) {
748 $tpl_vars{PICTUREINFO} = $templates{pictureinfo}->fill_in(HASH => \%tpl_vars);
750 unless (defined($exifvalues)) {
751 $tpl_vars{EXIFVALUES} = "";
756 $tpl_vars{PICTUREINFO} = $templates{nopictureinfo}->fill_in(HASH => \%tpl_vars);
759 # Fill in sizes and determine if any are smaller than the
760 # actual image. If they are, $scaleable=1
762 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
763 foreach my $size (@sizes) {
764 if ($size<=$original_size) {
766 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
767 $sizes_vars{SIZE} = $size;
768 $sizes_vars{WIDTH} = $size;
769 if ($width == $size) {
770 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
773 $tpl_vars{SIZES} .= $templates{scale}->fill_in(HASH => \%sizes_vars);
779 unless ($scaleable) {
781 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
782 $sizes_vars{SIZE} = $original_size;
783 $sizes_vars{WIDTH} = $original_size;
784 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
787 $tpl_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
789 if ($r->dir_config('GalleryAllowOriginal')) {
790 $tpl_vars{SIZES} .= $templates{orig}->fill_in(HASH => \%tpl_vars);
793 my @slideshow_intervals = split (/ /, $r->dir_config('GallerySlideshowIntervals') ? $r->dir_config('GallerySlideshowIntervals') : '3 5 10 15 30');
794 foreach my $interval (@slideshow_intervals) {
797 $slideshow_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
798 $slideshow_vars{SECONDS} = $interval;
799 $slideshow_vars{WIDTH} = ($width > $height ? $width : $height);
801 if ($cgi->param('slideshow') && $cgi->param('slideshow') == $interval and $nextpicture) {
802 $tpl_vars{SLIDESHOW} .= $templates{intervalactive}->fill_in(HASH => \%slideshow_vars);
806 $tpl_vars{SLIDESHOW} .= $templates{interval}->fill_in(HASH => \%slideshow_vars);
811 if ($cgi->param('slideshow') and $nextpicture) {
813 $tpl_vars{SLIDESHOW} .= $templates{slideshowoff}->fill_in(HASH => \%tpl_vars);
815 unless ((grep $cgi->param('slideshow') == $_, @slideshow_intervals)) {
816 show_error($r, 200, "Invalid interval", "Invalid slideshow interval choosen");
817 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
820 $tpl_vars{URL} = uri_escape($nextpicture, $escape_rule);
821 $tpl_vars{WIDTH} = ($width > $height ? $width : $height);
822 $tpl_vars{INTERVAL} = $cgi->param('slideshow');
823 $tpl_vars{META} .= $templates{refresh}->fill_in(HASH => \%tpl_vars);
827 $tpl_vars{SLIDESHOW} .= $templates{slideshowisoff}->fill_in(HASH => \%tpl_vars);
830 $tpl_vars{MAIN} = $templates{picture}->fill_in(HASH => \%tpl_vars);
831 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
833 $r->content_type('text/html');
834 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
837 $r->send_http_header;
840 $r->print($tpl_vars{MAIN});
841 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
849 my ($r, $strip_filename) = @_;
853 unless ($r->dir_config('GalleryCacheDir')) {
855 $cache_root = '/var/tmp/Apache-Gallery/';
856 if ($r->server->is_virtual) {
857 $cache_root = File::Spec->catdir($cache_root, $r->server->server_hostname);
859 $cache_root = File::Spec->catdir($cache_root, $r->location);
864 $cache_root = $r->dir_config('GalleryCacheDir');
868 # If the uri contains .cache we need to remove it
872 my (undef, $dirs, $filename) = File::Spec->splitpath($uri);
873 # We don't need a volume as this is a relative path
875 if ($strip_filename) {
876 return(File::Spec->canonpath(File::Spec->catdir($cache_root, $dirs)));
878 return(File::Spec->canonpath(File::Spec->catfile($cache_root, $dirs, $filename)));
886 unless (mkdirhier ($path)) {
887 show_error($r, 500, $!, "Unable to create cache directory in $path: $!");
900 unless (mkdir($dir, 0755)) {
902 $parent =~ s/\/[^\/]*$//;
911 sub get_scaled_picture_name {
913 my ($fullpath, $width, $height) = @_;
915 my (undef, undef, $type) = imgsize($fullpath);
917 my @dirs = split(/\//, $fullpath);
918 my $filename = pop(@dirs);
921 if (grep $type eq $_, qw(PPM TIF GIF)) {
922 $newfilename = $width."x".$height."-".$filename;
923 # needs to be configurable
924 $newfilename =~ s/\.(\w+)$/-$1\.jpg/;
926 $newfilename = $width."x".$height."-".$filename;
935 my ($r, $fullpath, $width, $height, $imageinfo) = @_;
937 my @dirs = split(/\//, $fullpath);
938 my $filename = pop(@dirs);
940 my ($orig_width, $orig_height, $type) = imgsize($fullpath);
942 my $cache = cache_dir($r, 1);
944 my $newfilename = get_scaled_picture_name($fullpath, $width, $height);
946 if (($width > $orig_width) && ($height > $orig_height)) {
947 # Run it through the resize code anyway to get watermarks
948 $width = $orig_width;
949 $height = $orig_height;
952 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
954 # Do we want to generate a new file in the cache?
957 if (-f $cache."/".$newfilename) {
960 # Check to see if the image has changed
961 my $filestat = stat($fullpath);
962 my $cachestat = stat($cache."/".$newfilename);
963 if ($filestat->mtime >= $cachestat->mtime) {
967 # Check to see if the .rotate file has been added or changed
968 if (-f $fullpath . ".rotate") {
969 my $rotatestat = stat($fullpath . ".rotate");
970 if ($rotatestat->mtime > $cachestat->mtime) {
974 # Check to see if the copyrightimage has been added or changed
975 if ($r->dir_config('GalleryCopyrightImage') && -f $r->dir_config('GalleryCopyrightImage')) {
976 unless ($width == $thumbnailwidth or $width == $thumbnailheight) {
977 my $copyrightstat = stat($r->dir_config('GalleryCopyrightImage'));
978 if ($copyrightstat->mtime > $cachestat->mtime) {
988 my $newpath = $cache."/".$newfilename;
989 my $rotate = readfile_getnum($r, $imageinfo, $fullpath . ".rotate");
990 my $quality = $r->dir_config('GalleryQuality');
992 if ($width == $thumbnailwidth or $width == $thumbnailheight) {
994 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, '', '', '', '', '', '');
998 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate,
999 ($r->dir_config('GalleryCopyrightImage') ? $r->dir_config('GalleryCopyrightImage') : ''),
1000 ($r->dir_config('GalleryTTFDir') ? $r->dir_config('GalleryTTFDir') : ''),
1001 ($r->dir_config('GalleryCopyrightText') ? $r->dir_config('GalleryCopyrightText') : ''),
1002 ($r->dir_config('GalleryCopyrightColor') ? $r->dir_config('GalleryCopyrightColor') : ''),
1003 ($r->dir_config('GalleryTTFFile') ? $r->dir_config('GalleryTTFFile') : ''),
1004 ($r->dir_config('GalleryTTFSize') ? $r->dir_config('GalleryTTFSize') : ''),
1005 ($r->dir_config('GalleryCopyrightBackgroundColor') ? $r->dir_config('GalleryCopyrightBackgroundColor') : ''),
1011 return $newfilename;
1015 sub get_thumbnailsize {
1016 my ($r, $orig_width, $orig_height) = @_;
1018 my $gallerythumbnailsize=$r->dir_config('GalleryThumbnailSize');
1020 if (defined($gallerythumbnailsize)) {
1021 warn("Invalid setting for GalleryThumbnailSize") unless
1022 $gallerythumbnailsize =~ /^\s*\d+\s*x\s*\d+\s*$/i;
1025 my ($thumbnailwidth, $thumbnailheight) = split(/x/i, ($gallerythumbnailsize) ? $gallerythumbnailsize : "100x75");
1027 my $width = $thumbnailwidth;
1028 my $height = $thumbnailheight;
1030 # If the image is rotated, flip everything around.
1031 if (defined $r->dir_config('GalleryThumbnailSizeLS')
1032 and $r->dir_config('GalleryThumbnailSizeLS') eq '1'
1033 and $orig_width < $orig_height) {
1035 $width = $thumbnailheight;
1036 $height = $thumbnailwidth;
1039 my $scale = ($orig_width ? $width/$orig_width : 1);
1042 if ($orig_height * $scale > $thumbnailheight) {
1043 $scale = $height/$orig_height;
1044 $width = $orig_width * $scale;
1048 $height = $orig_height * $scale;
1050 $height = floor($height);
1051 $width = floor($width);
1053 return ($width, $height);
1056 sub get_image_display_size {
1057 my ($cgi, $r, $orig_width, $orig_height) = @_;
1059 my $width = $orig_width;
1061 my $original_size=$orig_height;
1062 if ($orig_width>$orig_height) {
1063 $original_size=$orig_width;
1066 # Check if the selected width is allowed
1067 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
1069 my %cookies = fetch CGI::Cookie;
1071 if ($cgi->param('width')) {
1072 unless ((grep $cgi->param('width') == $_, @sizes) or ($cgi->param('width') == $original_size)) {
1073 show_error($r, 200, "Invalid width", "The specified width is invalid");
1074 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
1077 $width = $cgi->param('width');
1078 my $cookie = new CGI::Cookie(-name => 'GallerySize', -value => $width, -expires => '+6M');
1079 $r->headers_out->{'Set-Cookie'} = $cookie;
1081 } elsif ($cookies{'GallerySize'} && (grep $cookies{'GallerySize'}->value == $_, @sizes)) {
1083 $width = $cookies{'GallerySize'}->value;
1091 if ($orig_width<$orig_height) {
1092 $scale = ($orig_height ? $width/$orig_height: 1);
1093 $image_width=$width*$orig_width/$orig_height;
1096 $scale = ($orig_width ? $width/$orig_width : 1);
1097 $image_width = $width;
1100 my $height = $orig_height * $scale;
1102 $image_width = floor($image_width);
1103 $width = floor($width);
1104 $height = floor($height);
1106 return ($image_width, $width, $height, $original_size);
1110 my ($r, $file, $type, $width, $height) = @_;
1112 if ($type eq 'Data stream is not a known image file format') {
1113 # should never be reached, this is supposed to be handled outside of here
1114 log_error("Something was fishy with the type of the file $file\n");
1117 # Some files, like TIFF, PNG, GIF do not have EXIF info
1118 # embedded but use .thm files instead.
1119 $imageinfo = get_imageinfo_from_thm_file($file, $width, $height);
1121 # If there is no .thm file and our file is a JPEG file we try to extract the EXIf
1122 # info using Image::Info
1123 unless (defined($imageinfo) && (grep $type eq $_, qw(JPG))) {
1124 # Only for files that natively keep the EXIF info in the same file
1125 $imageinfo = image_info($file);
1129 unless (defined($imageinfo->{width}) and defined($imageinfo->{height})) {
1130 $imageinfo->{width} = $width;
1131 $imageinfo->{height} = $height;
1134 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
1137 my ($human_key, $exif_key) = (split " => ")[0,1];
1138 if (defined($exif_key) && defined($imageinfo->{$exif_key})) {
1140 if (ref($imageinfo->{$exif_key}) eq 'Image::TIFF::Rational') {
1141 $value = $imageinfo->{$exif_key}->as_string;
1143 elsif (ref($imageinfo->{$exif_key}) eq 'ARRAY') {
1144 foreach my $element (@{$imageinfo->{$exif_key}}) {
1145 if (ref($element) eq 'ARRAY') {
1146 foreach (@{$element}) {
1150 elsif (ref($element) eq 'HASH') {
1151 $value .= "<br />{ ";
1152 foreach (sort keys %{$element}) {
1153 $value .= "$_ = " . $element->{$_} . ' ';
1164 my $exif_value = $imageinfo->{$exif_key};
1165 if ($human_key eq 'Flash' && $exif_value =~ m/\d/) {
1170 "16" => "No (Compulsory) Should be External Flash",
1171 "17" => "Yes (External)",
1173 "25" => "Yes (Auto)",
1174 "73" => "Yes (Compulsory, Red Eye Reducing)",
1175 "89" => "Yes (Auto, Red Eye Reducing)"
1177 $exif_value = defined $flashmodes{$exif_value} ? $flashmodes{$exif_value} : 'unknown flash mode';
1179 $value = $exif_value;
1181 if ($exif_key eq 'MeteringMode') {
1182 my $exif_value = $imageinfo->{$exif_key};
1183 if ($exif_value =~ /^\d+$/) {
1184 my %meteringmodes = (
1187 '2' => 'CenterWeightedAverage',
1194 $exif_value = defined $meteringmodes{$exif_value} ? $meteringmodes{$exif_value} : 'unknown metering mode';
1196 $value = $exif_value;
1199 if ($exif_key eq 'LightSource') {
1200 my $exif_value = $imageinfo->{$exif_key};
1201 if ($exif_value =~ /^\d+$/) {
1202 my %lightsources = (
1205 '2' => 'Fluorescent',
1206 '3' => 'Tungsten (incandescent light)',
1208 '9' => 'Fine weather',
1209 '10' => 'Cloudy weather',
1211 '12' => 'Daylight fluorescent',
1212 '13' => 'Day white fluorescent',
1213 '14' => 'Cool white fluorescent',
1214 '15' => 'White fluorescent',
1215 '17' => 'Standard light A',
1216 '18' => 'Standard light B',
1217 '19' => 'Standard light C',
1222 '24' => 'ISO studio tungsten',
1223 '255' => 'other light source'
1225 $exif_value = defined $lightsources{$exif_value} ? $lightsources{$exif_value} : 'unknown light source';
1227 $value = $exif_value;
1229 if ($exif_key eq 'FocalLength') {
1230 if ($value =~ /^(\d+)\/(\d+)$/) {
1231 $value = eval { $1 / $2 };
1235 $value = int($value + 0.5) . "mm";
1240 if ($exif_key eq 'ShutterSpeedValue') {
1241 if ($value =~ /^((?:\-)?\d+)\/(\d+)$/) {
1242 $value = eval { $1 / $2 };
1247 $value = 1/(exp($value*log(2)));
1249 $value = "1/" . (int((1/$value)));
1251 $value = int($value*10)/10;
1257 $value = $value . " sec";
1262 if ($exif_key eq 'ApertureValue') {
1263 if ($value =~ /^(\d+)\/(\d+)$/) {
1264 $value = eval { $1 / $2 };
1268 # poor man's rounding
1269 $value = int(exp($value*log(2)*0.5)*10)/10;
1270 $value = "f" . $value;
1274 if ($exif_key eq 'FNumber') {
1275 if ($value =~ /^(\d+)\/(\d+)$/) {
1276 $value = eval { $1 / $2 };
1280 $value = int($value*10+0.5)/10;
1281 $value = "f" . $value;
1285 $imageinfo->{$human_key} = $value;
1289 if ($r->dir_config('GalleryUseFileDate') &&
1290 ($r->dir_config('GalleryUseFileDate') eq '1'
1291 || !$imageinfo->{"Picture Taken"} )) {
1293 my $st = stat($file);
1294 $imageinfo->{"DateTimeOriginal"} = $imageinfo->{"Picture Taken"} = scalar localtime($st->mtime) if $st;
1300 sub get_imageinfo_from_thm_file {
1302 my ($file, $width, $height) = @_;
1304 my $imageinfo = undef;
1305 # Windows based file extensions are often .THM, so check
1306 # for both .thm and .THM
1307 my $unix_file = $file;
1308 my $windows_file = $file;
1309 $unix_file =~ s/\.(\w+)$/.thm/;
1310 $windows_file =~ s/\.(\w+)$/.THM/;
1312 if (-e $unix_file && -f $unix_file && -r $unix_file) {
1313 $imageinfo = image_info($unix_file);
1314 $imageinfo->{width} = $width;
1315 $imageinfo->{height} = $height;
1317 elsif (-e $windows_file && -f $windows_file && -r $windows_file) {
1318 $imageinfo = image_info($windows_file);
1319 $imageinfo->{width} = $width;
1320 $imageinfo->{height} = $height;
1327 sub readfile_getnum {
1328 my ($r, $imageinfo, $filename) = @_;
1332 print STDERR "orientation: ".$imageinfo->{Orientation}."\n";
1333 # Check to see if the image contains the Orientation EXIF key,
1334 # but allow user to override using rotate
1335 if (!defined($r->dir_config("GalleryAutoRotate"))
1336 || $r->dir_config("GalleryAutoRotate") eq "1") {
1337 if (defined($imageinfo->{Orientation})) {
1338 print STDERR $imageinfo->{Orientation}."\n";
1339 if ($imageinfo->{Orientation} eq 'right_top') {
1342 elsif ($imageinfo->{Orientation} eq 'left_bot') {
1348 if (open(FH, "<$filename")) {
1352 unless ($temp =~ /^\d$/) {
1355 unless ($temp == 1 || $temp == 2 || $temp == 3) {
1364 sub get_filecontent {
1366 open(FH, $file) or return undef;
1377 my $filename = shift;
1378 my $comment_ref = {};
1379 $comment_ref->{TITLE} = undef;
1380 $comment_ref->{COMMENT} = '';
1382 open(FH, $filename) or return $comment_ref;
1384 if ($title =~ m/^TITLE: (.*)$/) {
1385 chomp($comment_ref->{TITLE} = $1);
1388 $comment_ref->{COMMENT} = $title;
1393 $comment_ref->{COMMENT} .= $_;
1397 return $comment_ref;
1402 my ($r, $statuscode, $errortitle, $error) = @_;
1404 my $tpl = $r->dir_config('GalleryTemplateDir');
1406 my %templates = create_templates({layout => "$tpl/layout.tpl",
1407 error => "$tpl/error.tpl",
1411 $tpl_vars{TITLE} = "Error! $errortitle";
1412 $tpl_vars{META} = "";
1413 $tpl_vars{ERRORTITLE} = "Error! $errortitle";
1414 $tpl_vars{ERROR} = $error;
1416 $tpl_vars{MAIN} = $templates{error}->fill_in(HASH => \%tpl_vars);
1418 $tpl_vars{PAGE} = $templates{layout}->fill_in(HASH => \%tpl_vars);
1420 $r->status($statuscode);
1421 $r->content_type('text/html');
1423 $r->print($tpl_vars{PAGE});
1431 my $root_text = (defined($r->dir_config('GalleryRootText')) ? $r->dir_config('GalleryRootText') : "root:" );
1432 my $root_path = (defined($r->dir_config('GalleryRootPath')) ? $r->dir_config('GalleryRootPath') : "" );
1434 my $subr = $r->lookup_uri($r->uri);
1435 my $filename = $subr->filename;
1437 my @links = split (/\//, $r->uri);
1439 $uri =~ s/^$root_path//g;
1441 @links = split (/\//, $uri);
1443 # Get the full path of the base directory
1446 my @direlem = split (/\//, $filename);
1447 for my $i ( 0 .. ( scalar(@direlem) - scalar(@links) ) ) {
1448 $dirname .= shift(@direlem) . '/';
1455 $picturename = pop(@links);
1458 if ($r->uri eq $root_path) {
1459 return qq{ <a href="$root_path">$root_text</a> };
1463 my $menuurl = $root_path;
1464 foreach my $link (@links) {
1466 $menuurl .= $link."/";
1467 my $linktext = $link;
1468 unless (length($link)) {
1469 $linktext = "$root_text ";
1473 $dirname = File::Spec->catdir($dirname, $link);
1475 if (-e $dirname . ".folder") {
1476 $linktext = get_filecontent($dirname . ".folder");
1480 if ("$root_path$uri" eq $menuurl) {
1481 $menu .= "$linktext / ";
1484 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule)."\">$linktext</a> / ";
1490 $menu .= $picturename;
1494 if ($r->dir_config('GallerySelectionMode') && $r->dir_config('GallerySelectionMode') eq '1') {
1495 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule);
1496 $menu .= "?select=1\">[select]</a> ";
1504 my ($r, $infile, $outfile, $x, $y, $rotate, $copyrightfile, $GalleryTTFDir, $GalleryCopyrightText, $text_color, $GalleryTTFFile, $GalleryTTFSize, $GalleryCopyrightBackgroundColor, $quality) = @_;
1507 my $image = Image::Imlib2->load($infile) or warn("Unable to open file $infile, $!");
1510 $image=$image->create_scaled_image($x, $y) or warn("Unable to scale image $infile. Are you running out of memory?");
1514 $image->image_orientate($rotate);
1517 # blend copyright image onto image
1518 if ($copyrightfile ne '') {
1519 if (-f $copyrightfile and (my $logo=Image::Imlib2->load($copyrightfile))) {
1520 my $x = $image->get_width();
1521 my $y = $image->get_height();
1522 my $logox = $logo->get_width();
1523 my $logoy = $logo->get_height();
1524 $image->blend($logo, 0, 0, 0, $logox, $logoy, $x-$logox, $y-$logoy, $logox, $logoy);
1527 log_error("GalleryCopyrightImage $copyrightfile was not found");
1531 if ($GalleryTTFDir && $GalleryCopyrightText && $GalleryTTFFile && $text_color) {
1532 if (!-d $GalleryTTFDir) {
1534 log_error("GalleryTTFDir $GalleryTTFDir is not a dir\n");
1536 } elsif ($GalleryCopyrightText eq '') {
1538 log_error("GalleryCopyrightText is empty. No text inserted to picture\n");
1540 } elsif (!-e "$GalleryTTFDir/$GalleryTTFFile") {
1542 log_error("GalleryTTFFile $GalleryTTFFile was not found\n");
1546 $GalleryTTFFile =~ s/\.TTF$//i;
1547 $image->add_font_path("$GalleryTTFDir");
1549 $image->load_font("$GalleryTTFFile/$GalleryTTFSize");
1550 my($text_x, $text_y) = $image->get_text_size("$GalleryCopyrightText");
1551 my $x = $image->get_width();
1552 my $y = $image->get_height();
1556 if (($text_x < $x - $offset) && ($text_y < $y - $offset)) {
1557 if ($GalleryCopyrightBackgroundColor =~ /^\d+,\d+,\d+,\d+$/) {
1558 my ($br_val, $bg_val, $bb_val, $ba_val) = split (/,/, $GalleryCopyrightBackgroundColor);
1559 $image->set_colour($br_val, $bg_val, $bb_val, $ba_val);
1560 $image->fill_rectangle ($x-$text_x-$offset, $y-$text_y-$offset, $text_x, $text_y);
1562 my ($r_val, $g_val, $b_val, $a_val) = split (/,/, $text_color);
1563 $image->set_colour($r_val, $g_val, $b_val, $a_val);
1564 $image->draw_text($x-$text_x-$offset, $y-$text_y-$offset, "$GalleryCopyrightText");
1566 log_error("Text is to big for the picture.\n");
1571 if ($quality && $quality =~ m/^\d+$/) {
1572 $image->set_quality($quality);
1575 $image->save($outfile);
1582 my $sortby = $r->dir_config('GallerySortBy');
1583 my $filename=$r->lookup_uri($r->uri)->filename;
1584 $filename=(File::Spec->splitpath($filename))[1] if (-f $filename);
1585 if ($sortby && $sortby =~ m/^(size|atime|mtime|ctime)$/) {
1586 @files = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$sortby()." $_", @files));
1588 @files = sort @files;
1593 # Create Text::Template objects used by Apache::Gallery. Takes a
1594 # hashref of template_name, template_filename pairs, and returns a
1595 # list of template_name, texttemplate_object pairs.
1596 sub create_templates {
1597 my $templates = shift;
1599 # This routine is called whenever a template has an error. Prints
1600 # the error to STDERR and sticks the error in the output
1603 # Pull out the name and filename from the arg option [see
1604 # Text::Template for details]
1605 @args{qw(name file)} = @{$args{arg}};
1606 print STDERR qq(Template $args{name} ("$args{file}") is broken: $args{error});
1607 # Don't include the file name in the output, as the user can see this.
1608 return qq(<!-- Template $args{name} is broken: $args{error} -->);
1613 my %texttemplate_objects;
1615 for my $template_name (keys %$templates) {
1616 my $tt_obj = Text::Template->new(TYPE => 'FILE',
1617 SOURCE => $$templates{$template_name},
1618 BROKEN => \&tt_broken,
1619 BROKEN_ARG => [$template_name, $$templates{$template_name}],
1621 or die "Unable to create new Text::Template object for $template_name: $Text::Template::ERROR";
1622 $texttemplate_objects{$template_name} = $tt_obj;
1624 return %texttemplate_objects;
1629 Apache2::RequestUtil->request->log_error(shift());
1631 Apache->request->log_error(shift());
1639 Apache::Gallery - mod_perl handler to create an image gallery
1643 See the INSTALL file in the distribution for installation instructions.
1647 Apache::Gallery creates an thumbnail index of each directory and allows
1648 viewing pictures in different resolutions. Pictures are resized on the
1649 fly and cached. The gallery can be configured and customized in many ways
1650 and a custom copyright image can be added to all the images without
1651 modifying the original.
1653 =head1 CONFIGURATION
1655 In your httpd.conf you set the global options for the gallery. You can
1656 also override each of the options in .htaccess files in your gallery
1659 The options are set in the httpd.conf/.htaccess file using the syntax:
1660 B<PerlSetVar OptionName 'value'>
1662 Example: B<PerlSetVar GalleryCacheDir '/var/tmp/Apache-Gallery/'>
1666 =item B<GalleryAutoRotate>
1668 Some cameras, like the Canon G3, can detect the orientation of a
1669 the pictures you take and will save this information in the
1670 'Orientation' EXIF field. Apache::Gallery will then automatically
1673 This behavior is default but can be disabled by setting GalleryAutoRotate
1676 =item B<GalleryCacheDir>
1678 Directory where Apache::Gallery should create its cache with scaled
1679 pictures. The default is /var/tmp/Apache-Gallery/ . Here, a directory
1680 for each virtualhost or location will be created automaticly. Make
1681 sure your webserver has write access to the CacheDir.
1683 =item B<GalleryTemplateDir>
1685 Full path to the directory where you placed the templates. This option
1686 can be used both in your global configuration and in .htaccess files,
1687 this way you can have different layouts in different parts of your
1690 No default value, this option is required.
1692 =item B<GalleryInfo>
1694 With this option you can define which EXIF information you would like
1695 to present from the image. The format is: '<MyName => KeyInEXIF,
1696 MyOtherName => OtherKeyInEXIF'
1698 Examples of keys: B<ShutterSpeedValue>, B<ApertureValue>, B<SubjectDistance>,
1701 You can view all the keys from the EXIF header using this perl-oneliner:
1703 perl C<-e> 'use Data::Dumper; use Image::Info qw(image_info); print Dumper(image_info(shift));' filename.jpg
1705 Default is: 'Picture Taken => DateTimeOriginal, Flash => Flash'
1707 =item B<GallerySizes>
1709 Defines which widths images can be scaled to. Images cannot be
1710 scaled to other widths than the ones you define with this option.
1712 The default is '640 800 1024 1600'
1714 =item B<GalleryThumbnailSize>
1716 Defines the width and height of the thumbnail images.
1718 Defaults to '100x75'
1720 =item B<GalleryThumbnailSizeLS>
1722 If set to '1', B<GalleryThumbnailSize> is the long and the short side of
1723 the thumbnail image instead of the width and height.
1727 =item B<GalleryCopyrightImage>
1729 Image you want to blend into your images in the lower right
1730 corner. This could be a transparent png saying "copyright
1735 =item B<GalleryWrapNavigation>
1737 Make the navigation in the picture view wrap around (So Next
1738 at the end displays the first picture, etc.)
1740 Set to 1 or 0, default is 0
1742 =item B<GalleryAllowOriginal>
1744 Allow the user to download the Original picture without
1745 resizing or putting the CopyrightImage on it.
1747 Set to 1 or 0, default is 0
1749 =item B<GallerySlideshowIntervals>
1751 With this option you can configure which intervals can be selected for
1752 a slideshow. The default is '3 5 10 15 30'
1754 =item B<GallerySortBy>
1756 Instead of the default filename ordering you can sort by any
1757 stat attribute. For example size, atime, mtime, ctime.
1759 =item B<GalleryDirSortBy>
1761 Set this variable to sort directories differently than other items,
1762 can be set to size, atime, mtime and ctime; setting any other value
1763 will revert to sorting by name.
1765 =item B<GalleryMemoize>
1767 Cache EXIF data using Memoize - this will make Apache::Gallery faster
1768 when many people access the same images, but it will also cache EXIF
1769 data until the current Apache child dies.
1771 =item B<GalleryUseFileDate>
1773 Set this option to 1 to make A::G show the files timestamp
1774 instead of the EXIF value for "Picture taken".
1776 =item B<GallerySelectionMode>
1778 Enable the selection mode. Select images with checkboxes and
1779 get a list of filenames.
1781 =item B<GalleryEXIFMode>
1783 You can choose how Apache::Gallery should display EXIF info
1786 The default setting is 'namevalue'. This setting will make
1787 Apache::Gallery print out the names and values of the EXIF values
1788 you configure with GalleryInfo. The information will be parsed into
1789 $INFO in pictureinfo.tpl.
1791 You can also set it to 'values' which will make A::G parse
1792 the configured values into the var $EXIFVALUES as 'value | value | value'
1794 If you set this option to 'variables' the items you configure in GalleryInfo
1795 will be available to your templates as $EXIF_<KEYNAME> (in all uppercase).
1796 That means that with the default setting "Picture Taken => DateTimeOriginal,
1797 Flash => Flash" you will have the variables $EXIF_DATETIMEORIGINAL and
1798 $EXIF_FLASH avilable to your templates. You can place them
1801 =item B<GalleryRootPath>
1803 Change the location of gallery root. The default is ""
1805 =item B<GalleryRootText>
1807 Change the name that appears as the root element in the menu. The
1810 =item B<GalleryMaxThumbnailsPerPage>
1812 This options controls how many thumbnails should be displayed in a
1813 page. It requires $BROWSELINKS to be in the index.tpl template file.
1815 =item B<GalleryImgFile>
1817 Pattern matching the files you want Apache::Gallery to view in the
1818 index as thumbnails.
1820 The default is '\.(jpe?g|png|tiff?|ppm)$'
1822 =item B<GalleryDocFile>
1824 Pattern matching the files you want Apache::Gallery to view in the index
1825 as normal files. All other filetypes will still be served by Apache::Gallery
1826 but are not visible in the index.
1828 The default is '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
1830 =item B<GalleryTTFDir>
1832 To use the GalleryCopyrightText feature you must set this option to the
1833 directory where your True Type fonts are stored. No default is set.
1837 PerlSetVar GalleryTTFDir '/usr/share/fonts/'
1839 =item B<GalleryTTFFile>
1841 To use the GalleryCopyrightText feature this option must be set to the
1842 name of the True Type font you wish to use. Example:
1844 PerlSetVar GalleryTTFFile 'verdanab.ttf'
1846 =item B<GalleryTTFSize>
1848 Configure the size of the CopyrightText that will be inserted as
1849 copyright notice in the corner of your pictures.
1853 PerlSetVar GalleryTTFSize '10'
1855 =item B<GalleryCopyrightText>
1857 The text that will be inserted as copyright notice.
1861 PerlSetVar GalleryCopyrightText '(c) Michael Legart'
1863 =item B<GalleryCopyrightColor>
1865 The text color of your copyright notice.
1870 PerlSetVar GalleryCopyrightColor '255,255,255,255'
1873 PerlSetVar GalleryCopyrightColor '0,0,0,255'
1876 PerlSetVar GalleryCopyrightColor '255,0,0,255'
1879 PerlSetVar GalleryCopyrightColor '0,255,0,255'
1882 PerlSetVar GalleryCopyrightColor '0,0,255,255'
1885 PerlSetVar GalleryCopyrightColor '255,127,0,127'
1887 =item B<GalleryCopyrightBackgroundColor>
1889 The background-color of a GalleryCopyrightText
1891 r,g,b,a - for examples, see GalleryCopyrightColor
1893 =item B<GalleryQuality>
1895 The quality (1-100) of scaled images
1897 This setting affects the quality of the scaled images.
1898 Set this to a low number to reduce the size of the scaled images.
1899 Remember to clear out your cache if you change this setting.
1900 Quality seems to default to 75, at least in the jpeg and png loader code in
1906 PerlSetVar GalleryQuality '50'
1908 =item B<GalleryUnderscoresToSpaces>
1910 Set this option to 1 to convert underscores to spaces in the listing
1911 of directory and file names, as well as in the alt attribute for HTML
1916 =item B<GalleryCommentExifKey>
1918 Set this option to e.g. ImageDescription to use this field as comments
1921 =item B<GalleryEnableMediaRss>
1923 Set this option to 1 to enable generation of a media RSS feed. This
1924 can be used e.g. together with the PicLens plugin from http://piclens.com
1930 =item B<Rotate images>
1932 Some cameras, like the Canon G3, detects the orientation of a picture
1933 and adds this info to the EXIF header. Apache::Gallery detects this
1934 and automaticly rotates images with this info.
1936 If your camera does not support this, you can rotate the images
1937 manually, This can also be used to override the rotate information
1938 from a camera that supports that. You can also disable this behavior
1939 with the GalleryAutoRotate option.
1941 To use this functionality you have to create file with the name of the
1942 picture you want rotated appened with ".rotate". The file should include
1943 a number where these numbers are supported:
1945 "1", rotates clockwise by 90 degree
1946 "2", rotates clockwise by 180 degrees
1947 "3", rotates clockwise by 270 degrees
1949 So if we want to rotate "Picture1234.jpg" 90 degrees clockwise we would
1950 create a file in the same directory called "Picture1234.jpg.rotate" with
1951 the number 1 inside of it.
1955 To include comments for a directory you create a <directory>.comment
1956 file where the first line can contain "TITLE: New title" which
1957 will be the title of the page, and a comment on the following
1959 To include comments for each picture you create files called
1960 picture.jpg.comment where the first line can contain "TITLE: New
1961 title" which will be the title of the page, and a comment on the
1966 TITLE: This is the new title of the page
1967 And this is the comment.<br />
1968 And this is line two of the comment.
1970 The visible name of the folder is by default identical to the name of
1971 the folder, but can be changed by creating a file <directory>.folder
1972 with the visible name of the folder.
1974 It is also possible to set GalleryCommentExifKey to the name of an EXIF
1975 field containing the comment, e.g. ImageDescription. The EXIF comment is
1976 overridden by the .comment file if it exists.
1986 =item B<Apache with mod_perl>
1988 =item B<URI::Escape>
1990 =item B<Image::Info>
1992 =item B<Image::Size>
1994 =item B<Text::Template>
1996 =item B<Image::Imlib2>
1998 =item B<X11 libraries>
2002 Remember the -dev package when using rpm, deb or other package formats!
2008 Michael Legart <michael@legart.dk>
2010 =head1 COPYRIGHT AND LICENSE
2012 Copyright (C) 2001-2005 Michael Legart <michael@legart.dk>
2014 Templates designed by Thomas Kjaer <tk@lnx.dk>
2016 Apache::Gallery is free software and is released under the Artistic License.
2017 See B<http://www.perl.com/language/misc/Artistic.html> for details.
2019 The video icons are from the GNOME project. B<http://www.gnome.org/>
2023 Thanks to Thomas Kjaer for templates and design of B<http://apachegallery.dk>
2024 Thanks to Thomas Eibner and other for patches. (See the Changes file)
2028 L<perl>, L<mod_perl>, L<Image::Imlib2>, L<CGI::FastTemplate>,
2029 L<Image::Info>, and L<Image::Size>.