1 package Apache::Gallery;
3 # $Author: mil $ $Rev: 335 $
4 # $Date: 2011-06-08 20:47:46 +0200 (Wed, 08 Jun 2011) $
14 if (exists($ENV{MOD_PERL_API_VERSION})
15 and ($ENV{MOD_PERL_API_VERSION}==2)) {
17 if ($mod_perl::VERSION >= 1.99 && $mod_perl::VERSION < 2.0) {
18 die "mod_perl 2.0.0 or later is now required";
20 require Apache2::ServerRec;
21 require Apache2::RequestRec;
24 require Apache2::RequestIO;
25 require Apache2::SubRequest;
26 require Apache2::Const;
28 Apache2::Const->import(-compile => 'OK','DECLINED','FORBIDDEN','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: 335 $ $Date: 2011-06-08 20:47:46 +0200 (Wed, 08 Jun 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 if ($r->uri =~ m/^\/icons\/gallery\/([^\/]+$)/i) {
124 $filename = "/usr/share/libapache-gallery-perl/icons/$filename";
125 $r->filename($filename);
127 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
129 # Lookup the file in the cache and scale the image if the cached
130 # image does not exist
131 if ($r->uri =~ m/\.cache\//i) {
133 my $filename = $r->filename().$r->path_info();
134 $filename =~ s/\.cache//;
136 $filename =~ m/\/(\d+)x(\d+)\-/;
137 my $image_width = $1;
138 my $image_height = $2;
140 $filename =~ s/\/(\d+)x(\d+)\-//;
142 my ($width, $height, $type) = imgsize($filename);
144 my $imageinfo = get_imageinfo($r, $filename, $type, $width, $height);
146 my $cached = scale_picture($r, $filename, $image_width, $image_height, $imageinfo);
148 my $file = cache_dir($r, 0);
149 $file =~ s/\.cache//;
151 my $subr = $r->lookup_file($file);
152 $r->content_type($subr->content_type());
155 my $fileinfo = stat($file);
157 my $nonce = md5_base64($fileinfo->ino.$fileinfo->mtime);
158 if ($r->headers_in->{"If-None-Match"} eq $nonce) {
159 return Apache2::Const::HTTP_NOT_MODIFIED();
162 if ($r->headers_in->{"If-Modified-Since"} && str2time($r->headers_in->{"If-Modified-Since"}) < $fileinfo->mtime) {
163 return Apache2::Const::HTTP_NOT_MODIFIED();
166 $r->headers_out->{"Content-Length"} = $fileinfo->size;
167 $r->headers_out->{"Last-Modified-Date"} = time2str($fileinfo->mtime);
168 $r->headers_out->{"ETag"} = $nonce;
170 return Apache2::Const::OK();
175 return Apache::Constants::DECLINED();
183 unless (-f $filename or -d $filename) {
184 show_error($r, 404, "404!", "No such file or directory: ".uri_escape($r->uri, $escape_rule));
185 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
188 my $doc_pattern = $r->dir_config('GalleryDocFile');
189 unless ($doc_pattern) {
190 $doc_pattern = '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
192 my $img_pattern = $r->dir_config('GalleryImgFile');
193 unless ($img_pattern) {
194 $img_pattern = '\.(jpe?g|png|tiff?|ppm)$'
197 # Let Apache serve files we don't know how to handle anyway
198 if (-f $filename && $filename !~ m/$img_pattern/i) {
199 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
204 unless (-d cache_dir($r, 0)) {
205 unless (create_cache($r, cache_dir($r, 0))) {
206 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
210 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
212 # Instead of reading the templates every single time
213 # we need them, create a hash of template names and
214 # the associated Text::Template objects.
215 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
216 index => "$tpl_dir/index.tpl",
217 directory => "$tpl_dir/directory.tpl",
218 picture => "$tpl_dir/picture.tpl",
219 file => "$tpl_dir/file.tpl",
220 comment => "$tpl_dir/dircomment.tpl",
221 nocomment => "$tpl_dir/nodircomment.tpl",
222 rss => "$tpl_dir/rss.tpl",
223 rss_item => "$tpl_dir/rss_item.tpl",
224 navdirectory => "$tpl_dir/navdirectory.tpl",
232 $tpl_vars{TITLE} = "Index of: $uri";
234 if ($media_rss_enabled) {
235 # Put the RSS feed on all directory listings
236 $tpl_vars{META} = '<link rel="alternate" href="?rss=1" type="application/rss+xml" title="" id="gallery" />';
239 unless (opendir (DIR, $filename)) {
240 show_error ($r, 500, $!, "Unable to access directory $filename: $!");
241 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
244 $tpl_vars{MENU} = generate_menu($r);
246 $tpl_vars{FORM_BEGIN} = $select_mode?'<form method="post">':'';
247 $tpl_vars{FORM_END} = $select_mode?'<input type="submit" name="Get list" value="Get list"></form>':'';
249 # Read, sort, and filter files
250 my @files = grep { !/^\./ && -f "$filename/$_" } readdir (DIR);
252 @files=gallerysort($r, @files);
254 my @downloadable_files;
257 # Remove unwanted files from list
259 foreach my $picture (@files) {
261 my $file = $topdir."/".$picture;
263 if ($file =~ /$img_pattern/i) {
264 push (@new_files, $picture);
267 if ($file =~ /$doc_pattern/i) {
268 push (@downloadable_files, $picture);
275 # Read and sort directories
277 my @directories = grep { !/^\./ && -d "$filename/$_" } readdir (DIR);
279 if (defined($r->dir_config('GalleryDirSortBy'))) {
280 $dirsortby=$r->dir_config('GalleryDirSortBy');
282 $dirsortby=$r->dir_config('GallerySortBy');
284 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
285 @directories = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$dirsortby()." $_", @directories));
287 @directories = sort @directories;
293 # Combine directories and files to one listing
295 push (@listing, @directories);
296 push (@listing, @files);
297 push (@listing, @downloadable_files);
303 my $file_counter = 0;
305 my $max_files = $r->dir_config('GalleryMaxThumbnailsPerPage');
307 if (defined($cgi->param('start'))) {
308 $start_at = $cgi->param('start');
314 my $browse_links = "";
315 if (defined($max_files)) {
317 for (my $i=1; $i<=scalar(@listing); $i++) {
321 my $to = $i+$max_files-1;
322 if ($to > scalar(@listing)) {
323 $to = scalar(@listing);
326 if ($start_at < $from || $start_at > $to) {
327 $browse_links .= "<a href=\"?start=$from\">$from - ".$to."</a> ";
330 $browse_links .= "$from - $to ";
339 $tpl_vars{BROWSELINKS} = $browse_links;
342 foreach my $file (@listing) {
346 if ($file_counter < $start_at) {
350 if (defined($max_files) && $file_counter > $max_files+$start_at-1) {
354 my $thumbfilename = $topdir."/".$file;
356 my $fileurl = $uri."/".$file;
358 # Debian bug #619625 <http://bugs.debian.org/619625>
359 if (-d $thumbfilename && ! -e $thumbfilename . ".ignore") {
361 if (-e $thumbfilename . ".folder") {
362 $dirtitle = get_filecontent($thumbfilename . ".folder");
365 $dirtitle = $dirtitle ? $dirtitle : $file;
366 $dirtitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
369 $templates{directory}->fill_in(HASH=> {FILEURL => uri_escape($fileurl, $escape_rule),
375 # Debian bug #619625 <http://bugs.debian.org/619625>
376 elsif (-f $thumbfilename && $thumbfilename =~ /$doc_pattern/i && $thumbfilename !~ /$img_pattern/i && ! -e $thumbfilename . ".ignore") {
378 my $stat = stat($thumbfilename);
379 my $size = $stat->size;
382 if ($thumbfilename =~ m/\.(mpe?g|avi|mov|asf|wmv)$/i) {
383 $filetype = "video-$type";
384 } elsif ($thumbfilename =~ m/\.(txt|html?)$/i) {
385 $filetype = "text-$type";
386 } elsif ($thumbfilename =~ m/\.(mp3|ogg|wav)$/i) {
387 $filetype = "sound-$type";
388 } elsif ($thumbfilename =~ m/$doc_pattern/i) {
389 $filetype = "application-$type";
391 $filetype = "unknown";
394 # Debian bug #348724 <http://bugs.debian.org/348724>
396 my $filetitle = $file;
397 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
400 $templates{file}->fill_in(HASH => {%tpl_vars,
401 FILEURL => uri_escape($fileurl, $escape_rule),
402 ALT => "Size: $size Bytes",
405 FILETYPE => $filetype,
409 # Debian bug #619625 <http://bugs.debian.org/619625>
410 elsif (-f $thumbfilename && ! -e $thumbfilename . ".ignore") {
412 my ($width, $height, $type) = imgsize($thumbfilename);
413 next if $type eq 'Data stream is not a known image file format';
415 my @filetypes = qw(JPG TIF PNG PPM GIF);
417 next unless (grep $type eq $_, @filetypes);
418 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);
419 my $imageinfo = get_imageinfo($r, $thumbfilename, $type, $width, $height);
420 my $cached = get_scaled_picture_name($thumbfilename, $thumbnailwidth, $thumbnailheight);
422 my $rotate = readfile_getnum($r, $imageinfo, $thumbfilename.".rotate");
424 # Debian bug #348724 <http://bugs.debian.org/348724>
425 # HTML <img> tag, alt attribute
426 my $filetitle = $file;
427 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
429 my %file_vars = (FILEURL => uri_escape($fileurl, $escape_rule),
431 DATE => $imageinfo->{DateTimeOriginal} ? $imageinfo->{DateTimeOriginal} : '', # should this really be a stat of the file instead of ''?
432 SRC => uri_escape($uri."/.cache/$cached", $escape_rule),
433 HEIGHT => (grep($rotate==$_, (1, 3)) ? $thumbnailwidth : $thumbnailheight),
434 WIDTH => (grep($rotate==$_, (1, 3)) ? $thumbnailheight : $thumbnailwidth),
435 SELECT => $select_mode?'<input type="checkbox" name="selection" value="'.$file.'"> ':'',);
436 $tpl_vars{FILES} .= $templates{picture}->fill_in(HASH => {%tpl_vars,
441 if ($media_rss_enabled) {
442 my ($content_image_width, undef, $content_image_height) = get_image_display_size($cgi, $r, $width, $height);
444 THUMBNAIL => uri_escape($uri."/.cache/$cached", $escape_rule),
445 LINK => uri_escape($fileurl, $escape_rule),
447 CONTENT => uri_escape($uri."/.cache/".$content_image_width."x".$content_image_height."-".$file, $escape_rule)
449 $tpl_vars{ITEMS} .= $templates{rss_item}->fill_in(HASH => {
457 $tpl_vars{FILES} = "No files found";
458 $tpl_vars{BROWSELINKS} = "";
461 # Generate prev and next directory menu items
462 $filename =~ m/(.*)\/.*?$/;
463 my $parent_filename = $1;
465 $r->document_root =~ m/(.*)\/$/;
467 print STDERR "$filename vs $root_path\n";
468 if ($filename ne $root_path) {
469 unless (opendir (PARENT_DIR, $parent_filename)) {
470 show_error ($r, 500, $!, "Unable to access parent directory $parent_filename: $!");
471 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
474 # Debian bug #619625 <http://bugs.debian.org/619625>
475 my @neighbour_directories = grep { !/^\./ && -d "$parent_filename/$_" && ! -e "$parent_filename/$_" . ".ignore" } readdir (PARENT_DIR);
477 if (defined($r->dir_config('GalleryDirSortBy'))) {
478 $dirsortby=$r->dir_config('GalleryDirSortBy');
480 $dirsortby=$r->dir_config('GallerySortBy');
482 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
483 @neighbour_directories = map(/^\d+ (.*)/, sort map(stat("$parent_filename/$_")->$dirsortby()." $_", @neighbour_directories));
485 @neighbour_directories = sort @neighbour_directories;
488 closedir(PARENT_DIR);
490 my $neightbour_counter = 0;
491 foreach my $neighbour_directory (@neighbour_directories) {
492 if ($parent_filename.'/'.$neighbour_directory eq $filename) {
493 if ($neightbour_counter > 0) {
494 print STDERR "prev directory is " .$neighbour_directories[$neightbour_counter-1] ."\n";
495 my $linktext = $neighbour_directories[$neightbour_counter-1];
496 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
497 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder");
500 URL => "../".$neighbour_directories[$neightbour_counter-1],
501 LINK_NAME => "<<< $linktext",
504 $tpl_vars{PREV_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
505 print STDERR $tpl_vars{PREV_DIR_FILES} ."\n";
508 if ($neightbour_counter < scalar @neighbour_directories - 1) {
509 my $linktext = $neighbour_directories[$neightbour_counter+1];
510 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder") {
511 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder");
514 URL => "../".$neighbour_directories[$neightbour_counter+1],
515 LINK_NAME => "$linktext >>>",
518 $tpl_vars{NEXT_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
519 print STDERR "next directory is " .$neighbour_directories[$neightbour_counter+1] ."\n";
522 $neightbour_counter++;
526 if (-f $topdir . '.comment') {
527 my $comment_ref = get_comment($topdir . '.comment');
529 $comment_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
530 $comment_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
531 $tpl_vars{DIRCOMMENT} = $templates{comment}->fill_in(HASH => \%comment_vars);
532 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
534 $tpl_vars{DIRCOMMENT} = $templates{nocomment}->fill_in(HASH=>\%tpl_vars);
537 if ($cgi->param('rss')) {
538 $tpl_vars{MAIN} = $templates{rss}->fill_in(HASH => \%tpl_vars);
539 $r->content_type('application/rss+xml');
541 $tpl_vars{MAIN} = $templates{index}->fill_in(HASH => \%tpl_vars);
542 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
543 $r->content_type('text/html');
546 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
549 $r->send_http_header;
552 $r->print($tpl_vars{MAIN});
553 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
559 if (defined($ENV{QUERY_STRING}) && $ENV{QUERY_STRING} eq 'orig') {
560 if ($r->dir_config('GalleryAllowOriginal') ? 1 : 0) {
561 $r->filename($filename);
562 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
564 return $::MP2 ? Apache2::Const::FORBIDDEN() : Apache::Constants::FORBIDDEN();
568 # Create cache dir if not existing
569 my @tmp = split (/\//, $filename);
570 my $picfilename = pop @tmp;
571 my $path = (join "/", @tmp)."/";
572 my $cache_path = cache_dir($r, 1);
574 unless (-d $cache_path) {
575 unless (create_cache($r, $cache_path)) {
576 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
580 my ($orig_width, $orig_height, $type) = imgsize($filename);
582 my $imageinfo = get_imageinfo($r, $filename, $type, $orig_width, $orig_height);
584 my ($image_width, $width, $height, $original_size) = get_image_display_size($cgi, $r, $orig_width, $orig_height);
586 my $cached = get_scaled_picture_name($filename, $image_width, $height);
588 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
590 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
591 picture => "$tpl_dir/showpicture.tpl",
592 navpicture => "$tpl_dir/navpicture.tpl",
593 info => "$tpl_dir/info.tpl",
594 scale => "$tpl_dir/scale.tpl",
595 scaleactive => "$tpl_dir/scaleactive.tpl",
596 orig => "$tpl_dir/orig.tpl",
597 refresh => "$tpl_dir/refresh.tpl",
598 interval => "$tpl_dir/interval.tpl",
599 intervalactive => "$tpl_dir/intervalactive.tpl",
600 slideshowisoff => "$tpl_dir/slideshowisoff.tpl",
601 slideshowoff => "$tpl_dir/slideshowoff.tpl",
602 pictureinfo => "$tpl_dir/pictureinfo.tpl",
603 nopictureinfo => "$tpl_dir/nopictureinfo.tpl",
608 my $resolution = (($image_width > $orig_width) && ($height > $orig_height)) ?
609 "$orig_width x $orig_height" : "$image_width x $height";
611 $tpl_vars{TITLE} = "Viewing ".$r->uri()." at $image_width x $height";
612 $tpl_vars{META} = " ";
613 $tpl_vars{RESOLUTION} = $resolution;
614 $tpl_vars{MENU} = generate_menu($r);
615 $tpl_vars{SRC} = uri_escape(".cache/$cached", $escape_rule);
616 $tpl_vars{URI} = $r->uri();
618 my $exif_mode = $r->dir_config('GalleryEXIFMode');
619 unless ($exif_mode) {
620 $exif_mode = 'namevalue';
623 unless (opendir(DATADIR, $path)) {
624 show_error($r, 500, "Unable to access directory", "Unable to access directory $path");
625 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
627 my @pictures = grep { /$img_pattern/i && ! -e "$path/$_" . ".ignore" } readdir (DATADIR);
629 @pictures = gallerysort($r, @pictures);
631 $tpl_vars{TOTAL} = scalar @pictures;
636 for (my $i=0; $i <= $#pictures; $i++) {
637 if ($pictures[$i] eq $picfilename) {
639 $tpl_vars{NUMBER} = $i+1;
641 $prevpicture = $pictures[$i-1];
642 my $displayprev = ($i>0 ? 1 : 0);
644 if ($r->dir_config("GalleryWrapNavigation")) {
645 $prevpicture = $pictures[$i>0 ? $i-1 : $#pictures];
648 if ($prevpicture and $displayprev) {
649 my ($orig_width, $orig_height, $type) = imgsize($path.$prevpicture);
650 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
651 my $imageinfo = get_imageinfo($r, $path.$prevpicture, $type, $orig_width, $orig_height);
652 my $cached = get_scaled_picture_name($path.$prevpicture, $thumbnailwidth, $thumbnailheight);
654 $nav_vars{URL} = uri_escape($prevpicture, $escape_rule);
655 $nav_vars{FILENAME} = $prevpicture;
656 $nav_vars{WIDTH} = $width;
657 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
658 $nav_vars{DIRECTION} = "« <u>p</u>rev";
659 $nav_vars{ACCESSKEY} = "P";
660 $tpl_vars{BACK} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
663 $tpl_vars{BACK} = " ";
666 $nextpicture = $pictures[$i+1];
667 if ($r->dir_config("GalleryWrapNavigation")) {
668 $nextpicture = $pictures[$i == $#pictures ? 0 : $i+1];
672 my ($orig_width, $orig_height, $type) = imgsize($path.$nextpicture);
673 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
674 my $imageinfo = get_imageinfo($r, $path.$nextpicture, $type, $thumbnailwidth, $thumbnailheight);
675 my $cached = get_scaled_picture_name($path.$nextpicture, $thumbnailwidth, $thumbnailheight);
677 $nav_vars{URL} = uri_escape($nextpicture, $escape_rule);
678 $nav_vars{FILENAME} = $nextpicture;
679 $nav_vars{WIDTH} = $width;
680 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
681 $nav_vars{DIRECTION} = "<u>n</u>ext »";
682 $nav_vars{ACCESSKEY} = "N";
684 $tpl_vars{NEXT} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
685 $tpl_vars{NEXTURL} = uri_escape($nextpicture, $escape_rule);
688 $tpl_vars{NEXT} = " ";
689 $tpl_vars{NEXTURL} = '#';
694 my $foundcomment = 0;
695 if (-f $path . '/' . $picfilename . '.comment') {
696 my $comment_ref = get_comment($path . '/' . $picfilename . '.comment');
698 $tpl_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
699 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
700 } elsif ($r->dir_config('GalleryCommentExifKey')) {
701 my $comment = decode("utf8", $imageinfo->{$r->dir_config('GalleryCommentExifKey')});
702 $tpl_vars{COMMENT} = encode("iso-8859-1", $comment);
704 $tpl_vars{COMMENT} = '';
707 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
712 my ($human_key, $exif_key) = (split " => ")[0,1];
713 my $value = $imageinfo->{$human_key};
714 if (defined($value)) {
718 if ($exif_mode eq 'namevalue') {
720 $info_vars{KEY} = $human_key;
721 $info_vars{VALUE} = $value;
722 $tpl_vars{INFO} .= $templates{info}->fill_in(HASH => \%info_vars);
725 if ($exif_mode eq 'variables') {
726 $tpl_vars{"EXIF_".uc($exif_key)} = $value;
729 if ($exif_mode eq 'values') {
730 $exifvalues .= "| ".$value." ";
737 if ($exif_mode eq 'values') {
738 if (defined($exifvalues)) {
739 $tpl_vars{EXIFVALUES} = $exifvalues;
742 $tpl_vars{EXIFVALUES} = "";
746 if ($foundcomment and !$foundinfo) {
747 $tpl_vars{INFO} = "";
750 if ($exif_mode ne 'namevalue') {
751 $tpl_vars{INFO} = "";
754 if ($exif_mode eq 'namevalue' && $foundinfo or $foundcomment) {
756 $tpl_vars{PICTUREINFO} = $templates{pictureinfo}->fill_in(HASH => \%tpl_vars);
758 unless (defined($exifvalues)) {
759 $tpl_vars{EXIFVALUES} = "";
764 $tpl_vars{PICTUREINFO} = $templates{nopictureinfo}->fill_in(HASH => \%tpl_vars);
767 # Fill in sizes and determine if any are smaller than the
768 # actual image. If they are, $scaleable=1
770 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
771 foreach my $size (@sizes) {
772 if ($size<=$original_size) {
774 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
775 $sizes_vars{SIZE} = $size;
776 $sizes_vars{WIDTH} = $size;
777 if ($width == $size) {
778 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
781 $tpl_vars{SIZES} .= $templates{scale}->fill_in(HASH => \%sizes_vars);
787 unless ($scaleable) {
789 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
790 $sizes_vars{SIZE} = $original_size;
791 $sizes_vars{WIDTH} = $original_size;
792 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
795 $tpl_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
797 if ($r->dir_config('GalleryAllowOriginal')) {
798 $tpl_vars{SIZES} .= $templates{orig}->fill_in(HASH => \%tpl_vars);
801 my @slideshow_intervals = split (/ /, $r->dir_config('GallerySlideshowIntervals') ? $r->dir_config('GallerySlideshowIntervals') : '3 5 10 15 30');
802 foreach my $interval (@slideshow_intervals) {
805 $slideshow_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
806 $slideshow_vars{SECONDS} = $interval;
807 $slideshow_vars{WIDTH} = ($width > $height ? $width : $height);
809 if ($cgi->param('slideshow') && $cgi->param('slideshow') == $interval and $nextpicture) {
810 $tpl_vars{SLIDESHOW} .= $templates{intervalactive}->fill_in(HASH => \%slideshow_vars);
814 $tpl_vars{SLIDESHOW} .= $templates{interval}->fill_in(HASH => \%slideshow_vars);
819 if ($cgi->param('slideshow') and $nextpicture) {
821 $tpl_vars{SLIDESHOW} .= $templates{slideshowoff}->fill_in(HASH => \%tpl_vars);
823 unless ((grep $cgi->param('slideshow') == $_, @slideshow_intervals)) {
824 show_error($r, 200, "Invalid interval", "Invalid slideshow interval choosen");
825 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
828 $tpl_vars{URL} = uri_escape($nextpicture, $escape_rule);
829 $tpl_vars{WIDTH} = ($width > $height ? $width : $height);
830 $tpl_vars{INTERVAL} = $cgi->param('slideshow');
831 $tpl_vars{META} .= $templates{refresh}->fill_in(HASH => \%tpl_vars);
835 $tpl_vars{SLIDESHOW} .= $templates{slideshowisoff}->fill_in(HASH => \%tpl_vars);
838 $tpl_vars{MAIN} = $templates{picture}->fill_in(HASH => \%tpl_vars);
839 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
841 $r->content_type('text/html');
842 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
845 $r->send_http_header;
848 $r->print($tpl_vars{MAIN});
849 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
857 my ($r, $strip_filename) = @_;
861 unless ($r->dir_config('GalleryCacheDir')) {
863 $cache_root = '/var/cache/www/';
864 if ($r->server->is_virtual) {
865 $cache_root = File::Spec->catdir($cache_root, $r->server->server_hostname);
867 $cache_root = File::Spec->catdir($cache_root, $r->location);
872 $cache_root = $r->dir_config('GalleryCacheDir');
876 # If the uri contains .cache we need to remove it
880 my (undef, $dirs, $filename) = File::Spec->splitpath($uri);
881 # We don't need a volume as this is a relative path
883 if ($strip_filename) {
884 return(File::Spec->canonpath(File::Spec->catdir($cache_root, $dirs)));
886 return(File::Spec->canonpath(File::Spec->catfile($cache_root, $dirs, $filename)));
894 unless (mkdirhier ($path)) {
895 show_error($r, 500, $!, "Unable to create cache directory in $path: $!");
908 unless (mkdir($dir, 0755)) {
910 $parent =~ s/\/[^\/]*$//;
919 sub get_scaled_picture_name {
921 my ($fullpath, $width, $height) = @_;
923 my (undef, undef, $type) = imgsize($fullpath);
925 my @dirs = split(/\//, $fullpath);
926 my $filename = pop(@dirs);
929 if (grep $type eq $_, qw(PPM TIF GIF)) {
930 $newfilename = $width."x".$height."-".$filename;
931 # needs to be configurable
932 $newfilename =~ s/\.(\w+)$/-$1\.jpg/;
934 $newfilename = $width."x".$height."-".$filename;
943 my ($r, $fullpath, $width, $height, $imageinfo) = @_;
945 my @dirs = split(/\//, $fullpath);
946 my $filename = pop(@dirs);
948 my ($orig_width, $orig_height, $type) = imgsize($fullpath);
950 my $cache = cache_dir($r, 1);
952 my $newfilename = get_scaled_picture_name($fullpath, $width, $height);
954 if (($width > $orig_width) && ($height > $orig_height)) {
955 # Run it through the resize code anyway to get watermarks
956 $width = $orig_width;
957 $height = $orig_height;
960 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
962 # Do we want to generate a new file in the cache?
965 if (-f $cache."/".$newfilename) {
968 # Check to see if the image has changed
969 my $filestat = stat($fullpath);
970 my $cachestat = stat($cache."/".$newfilename);
971 if ($filestat->mtime >= $cachestat->mtime) {
975 # Check to see if the .rotate file has been added or changed
976 if (-f $fullpath . ".rotate") {
977 my $rotatestat = stat($fullpath . ".rotate");
978 if ($rotatestat->mtime > $cachestat->mtime) {
982 # Check to see if the copyrightimage has been added or changed
983 if ($r->dir_config('GalleryCopyrightImage') && -f $r->dir_config('GalleryCopyrightImage')) {
984 unless ($width == $thumbnailwidth or $width == $thumbnailheight) {
985 my $copyrightstat = stat($r->dir_config('GalleryCopyrightImage'));
986 if ($copyrightstat->mtime > $cachestat->mtime) {
996 my $newpath = $cache."/".$newfilename;
997 my $rotate = readfile_getnum($r, $imageinfo, $fullpath . ".rotate");
998 my $quality = $r->dir_config('GalleryQuality');
1000 if ($width == $thumbnailwidth or $width == $thumbnailheight) {
1002 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, '', '', '', '', '', '');
1006 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate,
1007 ($r->dir_config('GalleryCopyrightImage') ? $r->dir_config('GalleryCopyrightImage') : ''),
1008 ($r->dir_config('GalleryTTFDir') ? $r->dir_config('GalleryTTFDir') : ''),
1009 ($r->dir_config('GalleryCopyrightText') ? $r->dir_config('GalleryCopyrightText') : ''),
1010 ($r->dir_config('GalleryCopyrightColor') ? $r->dir_config('GalleryCopyrightColor') : ''),
1011 ($r->dir_config('GalleryTTFFile') ? $r->dir_config('GalleryTTFFile') : ''),
1012 ($r->dir_config('GalleryTTFSize') ? $r->dir_config('GalleryTTFSize') : ''),
1013 ($r->dir_config('GalleryCopyrightBackgroundColor') ? $r->dir_config('GalleryCopyrightBackgroundColor') : ''),
1019 return $newfilename;
1023 sub get_thumbnailsize {
1024 my ($r, $orig_width, $orig_height) = @_;
1026 my $gallerythumbnailsize=$r->dir_config('GalleryThumbnailSize');
1028 if (defined($gallerythumbnailsize)) {
1029 warn("Invalid setting for GalleryThumbnailSize") unless
1030 $gallerythumbnailsize =~ /^\s*\d+\s*x\s*\d+\s*$/i;
1033 my ($thumbnailwidth, $thumbnailheight) = split(/x/i, ($gallerythumbnailsize) ? $gallerythumbnailsize : "100x75");
1035 my $width = $thumbnailwidth;
1036 my $height = $thumbnailheight;
1038 # If the image is rotated, flip everything around.
1039 if (defined $r->dir_config('GalleryThumbnailSizeLS')
1040 and $r->dir_config('GalleryThumbnailSizeLS') eq '1'
1041 and $orig_width < $orig_height) {
1043 $width = $thumbnailheight;
1044 $height = $thumbnailwidth;
1047 my $scale = ($orig_width ? $width/$orig_width : 1);
1050 if ($orig_height * $scale > $thumbnailheight) {
1051 $scale = $height/$orig_height;
1052 $width = $orig_width * $scale;
1056 $height = $orig_height * $scale;
1058 $height = floor($height);
1059 $width = floor($width);
1061 return ($width, $height);
1064 sub get_image_display_size {
1065 my ($cgi, $r, $orig_width, $orig_height) = @_;
1067 my $width = $orig_width;
1069 my $original_size=$orig_height;
1070 if ($orig_width>$orig_height) {
1071 $original_size=$orig_width;
1074 # Check if the selected width is allowed
1075 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
1077 my %cookies = fetch CGI::Cookie;
1079 if ($cgi->param('width')) {
1080 unless ((grep $cgi->param('width') == $_, @sizes) or ($cgi->param('width') == $original_size)) {
1081 show_error($r, 200, "Invalid width", "The specified width is invalid");
1082 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
1085 $width = $cgi->param('width');
1086 my $cookie = new CGI::Cookie(-name => 'GallerySize', -value => $width, -expires => '+6M');
1087 $r->headers_out->{'Set-Cookie'} = $cookie;
1089 } elsif ($cookies{'GallerySize'} && (grep $cookies{'GallerySize'}->value == $_, @sizes)) {
1091 $width = $cookies{'GallerySize'}->value;
1099 if ($orig_width<$orig_height) {
1100 $scale = ($orig_height ? $width/$orig_height: 1);
1101 $image_width=$width*$orig_width/$orig_height;
1104 $scale = ($orig_width ? $width/$orig_width : 1);
1105 $image_width = $width;
1108 my $height = $orig_height * $scale;
1110 $image_width = floor($image_width);
1111 $width = floor($width);
1112 $height = floor($height);
1114 return ($image_width, $width, $height, $original_size);
1118 my ($r, $file, $type, $width, $height) = @_;
1120 if ($type eq 'Data stream is not a known image file format') {
1121 # should never be reached, this is supposed to be handled outside of here
1122 log_error("Something was fishy with the type of the file $file\n");
1125 # Some files, like TIFF, PNG, GIF do not have EXIF info
1126 # embedded but use .thm files instead.
1127 $imageinfo = get_imageinfo_from_thm_file($file, $width, $height);
1129 # If there is no .thm file and our file is a JPEG file we try to extract the EXIf
1130 # info using Image::Info
1131 unless (defined($imageinfo) && (grep $type eq $_, qw(JPG))) {
1132 # Only for files that natively keep the EXIF info in the same file
1133 $imageinfo = image_info($file);
1137 unless (defined($imageinfo->{width}) and defined($imageinfo->{height})) {
1138 $imageinfo->{width} = $width;
1139 $imageinfo->{height} = $height;
1142 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
1145 my ($human_key, $exif_key) = (split " => ")[0,1];
1146 if (defined($exif_key) && defined($imageinfo->{$exif_key})) {
1148 if (ref($imageinfo->{$exif_key}) eq 'Image::TIFF::Rational') {
1149 $value = $imageinfo->{$exif_key}->as_string;
1151 elsif (ref($imageinfo->{$exif_key}) eq 'ARRAY') {
1152 foreach my $element (@{$imageinfo->{$exif_key}}) {
1153 if (ref($element) eq 'ARRAY') {
1154 foreach (@{$element}) {
1158 elsif (ref($element) eq 'HASH') {
1159 $value .= "<br />{ ";
1160 foreach (sort keys %{$element}) {
1161 $value .= "$_ = " . $element->{$_} . ' ';
1172 my $exif_value = $imageinfo->{$exif_key};
1173 if ($human_key eq 'Flash' && $exif_value =~ m/\d/) {
1178 "16" => "No (Compulsory) Should be External Flash",
1179 "17" => "Yes (External)",
1181 "25" => "Yes (Auto)",
1182 "73" => "Yes (Compulsory, Red Eye Reducing)",
1183 "89" => "Yes (Auto, Red Eye Reducing)"
1185 $exif_value = defined $flashmodes{$exif_value} ? $flashmodes{$exif_value} : 'unknown flash mode';
1187 $value = $exif_value;
1189 if ($exif_key eq 'MeteringMode') {
1190 my $exif_value = $imageinfo->{$exif_key};
1191 if ($exif_value =~ /^\d+$/) {
1192 my %meteringmodes = (
1195 '2' => 'CenterWeightedAverage',
1202 $exif_value = defined $meteringmodes{$exif_value} ? $meteringmodes{$exif_value} : 'unknown metering mode';
1204 $value = $exif_value;
1207 if ($exif_key eq 'LightSource') {
1208 my $exif_value = $imageinfo->{$exif_key};
1209 if ($exif_value =~ /^\d+$/) {
1210 my %lightsources = (
1213 '2' => 'Fluorescent',
1214 '3' => 'Tungsten (incandescent light)',
1216 '9' => 'Fine weather',
1217 '10' => 'Cloudy weather',
1219 '12' => 'Daylight fluorescent',
1220 '13' => 'Day white fluorescent',
1221 '14' => 'Cool white fluorescent',
1222 '15' => 'White fluorescent',
1223 '17' => 'Standard light A',
1224 '18' => 'Standard light B',
1225 '19' => 'Standard light C',
1230 '24' => 'ISO studio tungsten',
1231 '255' => 'other light source'
1233 $exif_value = defined $lightsources{$exif_value} ? $lightsources{$exif_value} : 'unknown light source';
1235 $value = $exif_value;
1237 if ($exif_key eq 'FocalLength') {
1238 if ($value =~ /^(\d+)\/(\d+)$/) {
1239 $value = eval { $1 / $2 };
1243 $value = int($value + 0.5) . "mm";
1248 if ($exif_key eq 'ShutterSpeedValue') {
1249 if ($value =~ /^((?:\-)?\d+)\/(\d+)$/) {
1250 $value = eval { $1 / $2 };
1255 $value = 1/(exp($value*log(2)));
1257 $value = "1/" . (int((1/$value)));
1259 $value = int($value*10)/10;
1265 $value = $value . " sec";
1270 if ($exif_key eq 'ApertureValue') {
1271 if ($value =~ /^(\d+)\/(\d+)$/) {
1272 $value = eval { $1 / $2 };
1276 # poor man's rounding
1277 $value = int(exp($value*log(2)*0.5)*10)/10;
1278 $value = "f" . $value;
1282 if ($exif_key eq 'FNumber') {
1283 if ($value =~ /^(\d+)\/(\d+)$/) {
1284 $value = eval { $1 / $2 };
1288 $value = int($value*10+0.5)/10;
1289 $value = "f" . $value;
1293 $imageinfo->{$human_key} = $value;
1297 if ($r->dir_config('GalleryUseFileDate') &&
1298 ($r->dir_config('GalleryUseFileDate') eq '1'
1299 || !$imageinfo->{"Picture Taken"} )) {
1301 my $st = stat($file);
1302 $imageinfo->{"DateTimeOriginal"} = $imageinfo->{"Picture Taken"} = scalar localtime($st->mtime) if $st;
1308 sub get_imageinfo_from_thm_file {
1310 my ($file, $width, $height) = @_;
1312 my $imageinfo = undef;
1313 # Windows based file extensions are often .THM, so check
1314 # for both .thm and .THM
1315 my $unix_file = $file;
1316 my $windows_file = $file;
1317 $unix_file =~ s/\.(\w+)$/.thm/;
1318 $windows_file =~ s/\.(\w+)$/.THM/;
1320 if (-e $unix_file && -f $unix_file && -r $unix_file) {
1321 $imageinfo = image_info($unix_file);
1322 $imageinfo->{width} = $width;
1323 $imageinfo->{height} = $height;
1325 elsif (-e $windows_file && -f $windows_file && -r $windows_file) {
1326 $imageinfo = image_info($windows_file);
1327 $imageinfo->{width} = $width;
1328 $imageinfo->{height} = $height;
1335 sub readfile_getnum {
1336 my ($r, $imageinfo, $filename) = @_;
1340 print STDERR "orientation: ".$imageinfo->{Orientation}."\n";
1341 # Check to see if the image contains the Orientation EXIF key,
1342 # but allow user to override using rotate
1343 if (!defined($r->dir_config("GalleryAutoRotate"))
1344 || $r->dir_config("GalleryAutoRotate") eq "1") {
1345 if (defined($imageinfo->{Orientation})) {
1346 print STDERR $imageinfo->{Orientation}."\n";
1347 if ($imageinfo->{Orientation} eq 'right_top') {
1350 elsif ($imageinfo->{Orientation} eq 'left_bot') {
1356 if (open(FH, "<$filename")) {
1360 unless ($temp =~ /^\d$/) {
1363 unless ($temp == 1 || $temp == 2 || $temp == 3) {
1372 sub get_filecontent {
1374 open(FH, $file) or return undef;
1385 my $filename = shift;
1386 my $comment_ref = {};
1387 $comment_ref->{TITLE} = undef;
1388 $comment_ref->{COMMENT} = '';
1390 open(FH, $filename) or return $comment_ref;
1392 if ($title =~ m/^TITLE: (.*)$/) {
1393 chomp($comment_ref->{TITLE} = $1);
1396 $comment_ref->{COMMENT} = $title;
1401 $comment_ref->{COMMENT} .= $_;
1405 return $comment_ref;
1410 my ($r, $statuscode, $errortitle, $error) = @_;
1412 my $tpl = $r->dir_config('GalleryTemplateDir');
1414 my %templates = create_templates({layout => "$tpl/layout.tpl",
1415 error => "$tpl/error.tpl",
1419 $tpl_vars{TITLE} = "Error! $errortitle";
1420 $tpl_vars{META} = "";
1421 $tpl_vars{ERRORTITLE} = "Error! $errortitle";
1422 $tpl_vars{ERROR} = $error;
1424 $tpl_vars{MAIN} = $templates{error}->fill_in(HASH => \%tpl_vars);
1426 $tpl_vars{PAGE} = $templates{layout}->fill_in(HASH => \%tpl_vars);
1428 $r->status($statuscode);
1429 $r->content_type('text/html');
1431 $r->print($tpl_vars{PAGE});
1439 my $root_text = (defined($r->dir_config('GalleryRootText')) ? $r->dir_config('GalleryRootText') : "root:" );
1440 my $root_path = (defined($r->dir_config('GalleryRootPath')) ? $r->dir_config('GalleryRootPath') : "" );
1442 my $subr = $r->lookup_uri($r->uri);
1443 my $filename = $subr->filename;
1445 my @links = split (/\//, $r->uri);
1447 $uri =~ s/^$root_path//g;
1449 @links = split (/\//, $uri);
1451 # Get the full path of the base directory
1454 my @direlem = split (/\//, $filename);
1455 for my $i ( 0 .. ( scalar(@direlem) - scalar(@links) ) ) {
1456 $dirname .= shift(@direlem) . '/';
1463 $picturename = pop(@links);
1466 if ($r->uri eq $root_path) {
1467 return qq{ <a href="$root_path">$root_text</a> };
1471 my $menuurl = $root_path;
1472 foreach my $link (@links) {
1474 $menuurl .= $link."/";
1475 my $linktext = $link;
1476 unless (length($link)) {
1477 $linktext = "$root_text ";
1481 $dirname = File::Spec->catdir($dirname, $link);
1483 if (-e $dirname . ".folder") {
1484 $linktext = get_filecontent($dirname . ".folder");
1488 if ("$root_path$uri" eq $menuurl) {
1489 $menu .= "$linktext / ";
1492 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule)."\">$linktext</a> / ";
1498 $menu .= $picturename;
1502 if ($r->dir_config('GallerySelectionMode') && $r->dir_config('GallerySelectionMode') eq '1') {
1503 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule);
1504 $menu .= "?select=1\">[select]</a> ";
1512 my ($r, $infile, $outfile, $x, $y, $rotate, $copyrightfile, $GalleryTTFDir, $GalleryCopyrightText, $text_color, $GalleryTTFFile, $GalleryTTFSize, $GalleryCopyrightBackgroundColor, $quality) = @_;
1515 my $image = Image::Imlib2->load($infile) or warn("Unable to open file $infile, $!");
1518 $image=$image->create_scaled_image($x, $y) or warn("Unable to scale image $infile. Are you running out of memory?");
1522 $image->image_orientate($rotate);
1525 # blend copyright image onto image
1526 if ($copyrightfile ne '') {
1527 if (-f $copyrightfile and (my $logo=Image::Imlib2->load($copyrightfile))) {
1528 my $x = $image->get_width();
1529 my $y = $image->get_height();
1530 my $logox = $logo->get_width();
1531 my $logoy = $logo->get_height();
1532 $image->blend($logo, 0, 0, 0, $logox, $logoy, $x-$logox, $y-$logoy, $logox, $logoy);
1535 log_error("GalleryCopyrightImage $copyrightfile was not found");
1539 if ($GalleryTTFDir && $GalleryCopyrightText && $GalleryTTFFile && $text_color) {
1540 if (!-d $GalleryTTFDir) {
1542 log_error("GalleryTTFDir $GalleryTTFDir is not a dir\n");
1544 } elsif ($GalleryCopyrightText eq '') {
1546 log_error("GalleryCopyrightText is empty. No text inserted to picture\n");
1548 } elsif (!-e "$GalleryTTFDir/$GalleryTTFFile") {
1550 log_error("GalleryTTFFile $GalleryTTFFile was not found\n");
1554 $GalleryTTFFile =~ s/\.TTF$//i;
1555 $image->add_font_path("$GalleryTTFDir");
1557 $image->load_font("$GalleryTTFFile/$GalleryTTFSize");
1558 my($text_x, $text_y) = $image->get_text_size("$GalleryCopyrightText");
1559 my $x = $image->get_width();
1560 my $y = $image->get_height();
1564 if (($text_x < $x - $offset) && ($text_y < $y - $offset)) {
1565 if ($GalleryCopyrightBackgroundColor =~ /^\d+,\d+,\d+,\d+$/) {
1566 my ($br_val, $bg_val, $bb_val, $ba_val) = split (/,/, $GalleryCopyrightBackgroundColor);
1567 $image->set_colour($br_val, $bg_val, $bb_val, $ba_val);
1568 $image->fill_rectangle ($x-$text_x-$offset, $y-$text_y-$offset, $text_x, $text_y);
1570 my ($r_val, $g_val, $b_val, $a_val) = split (/,/, $text_color);
1571 $image->set_colour($r_val, $g_val, $b_val, $a_val);
1572 $image->draw_text($x-$text_x-$offset, $y-$text_y-$offset, "$GalleryCopyrightText");
1574 log_error("Text is to big for the picture.\n");
1579 if ($quality && $quality =~ m/^\d+$/) {
1580 $image->set_quality($quality);
1583 $image->save($outfile);
1590 my $sortby = $r->dir_config('GallerySortBy');
1591 my $filename=$r->lookup_uri($r->uri)->filename;
1592 $filename=(File::Spec->splitpath($filename))[1] if (-f $filename);
1593 if ($sortby && $sortby =~ m/^(size|atime|mtime|ctime)$/) {
1594 @files = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$sortby()." $_", @files));
1596 @files = sort @files;
1601 # Create Text::Template objects used by Apache::Gallery. Takes a
1602 # hashref of template_name, template_filename pairs, and returns a
1603 # list of template_name, texttemplate_object pairs.
1604 sub create_templates {
1605 my $templates = shift;
1607 # This routine is called whenever a template has an error. Prints
1608 # the error to STDERR and sticks the error in the output
1611 # Pull out the name and filename from the arg option [see
1612 # Text::Template for details]
1613 @args{qw(name file)} = @{$args{arg}};
1614 print STDERR qq(Template $args{name} ("$args{file}") is broken: $args{error});
1615 # Don't include the file name in the output, as the user can see this.
1616 return qq(<!-- Template $args{name} is broken: $args{error} -->);
1621 my %texttemplate_objects;
1623 for my $template_name (keys %$templates) {
1624 my $tt_obj = Text::Template->new(TYPE => 'FILE',
1625 SOURCE => $$templates{$template_name},
1626 BROKEN => \&tt_broken,
1627 BROKEN_ARG => [$template_name, $$templates{$template_name}],
1629 or die "Unable to create new Text::Template object for $template_name: $Text::Template::ERROR";
1630 $texttemplate_objects{$template_name} = $tt_obj;
1632 return %texttemplate_objects;
1637 Apache2::RequestUtil->request->log_error(shift());
1639 Apache->request->log_error(shift());
1647 Apache::Gallery - mod_perl handler to create an image gallery
1651 See the INSTALL file in the distribution for installation instructions.
1655 Apache::Gallery creates an thumbnail index of each directory and allows
1656 viewing pictures in different resolutions. Pictures are resized on the
1657 fly and cached. The gallery can be configured and customized in many ways
1658 and a custom copyright image can be added to all the images without
1659 modifying the original.
1661 =head1 CONFIGURATION
1663 In your httpd.conf you set the global options for the gallery. You can
1664 also override each of the options in .htaccess files in your gallery
1667 The options are set in the httpd.conf/.htaccess file using the syntax:
1668 B<PerlSetVar OptionName 'value'>
1670 Example: B<PerlSetVar GalleryCacheDir '/var/cache/www/'>
1674 =item B<GalleryAutoRotate>
1676 Some cameras, like the Canon G3, can detect the orientation of a
1677 the pictures you take and will save this information in the
1678 'Orientation' EXIF field. Apache::Gallery will then automatically
1681 This behavior is default but can be disabled by setting GalleryAutoRotate
1684 =item B<GalleryCacheDir>
1686 Directory where Apache::Gallery should create its cache with scaled
1687 pictures. The default is /var/cache/www/ . Here, a directory for each
1688 virtualhost or location will be created automatically. Make sure your
1689 webserver has write access to the CacheDir.
1691 =item B<GalleryTemplateDir>
1693 Full path to the directory where you placed the templates. This option
1694 can be used both in your global configuration and in .htaccess files,
1695 this way you can have different layouts in different parts of your
1698 No default value, this option is required.
1700 =item B<GalleryInfo>
1702 With this option you can define which EXIF information you would like
1703 to present from the image. The format is: '<MyName => KeyInEXIF,
1704 MyOtherName => OtherKeyInEXIF'
1706 Examples of keys: B<ShutterSpeedValue>, B<ApertureValue>, B<SubjectDistance>,
1709 You can view all the keys from the EXIF header using this perl-oneliner:
1711 perl C<-e> 'use Data::Dumper; use Image::Info qw(image_info); print Dumper(image_info(shift));' filename.jpg
1713 Default is: 'Picture Taken => DateTimeOriginal, Flash => Flash'
1715 =item B<GallerySizes>
1717 Defines which widths images can be scaled to. Images cannot be
1718 scaled to other widths than the ones you define with this option.
1720 The default is '640 800 1024 1600'
1722 =item B<GalleryThumbnailSize>
1724 Defines the width and height of the thumbnail images.
1726 Defaults to '100x75'
1728 =item B<GalleryThumbnailSizeLS>
1730 If set to '1', B<GalleryThumbnailSize> is the long and the short side of
1731 the thumbnail image instead of the width and height.
1735 =item B<GalleryCopyrightImage>
1737 Image you want to blend into your images in the lower right
1738 corner. This could be a transparent png saying "copyright
1743 =item B<GalleryWrapNavigation>
1745 Make the navigation in the picture view wrap around (So Next
1746 at the end displays the first picture, etc.)
1748 Set to 1 or 0, default is 0
1750 =item B<GalleryAllowOriginal>
1752 Allow the user to download the Original picture without
1753 resizing or putting the CopyrightImage on it.
1755 Set to 1 or 0, default is 0
1757 =item B<GallerySlideshowIntervals>
1759 With this option you can configure which intervals can be selected for
1760 a slideshow. The default is '3 5 10 15 30'
1762 =item B<GallerySortBy>
1764 Instead of the default filename ordering you can sort by any
1765 stat attribute. For example size, atime, mtime, ctime.
1767 =item B<GalleryDirSortBy>
1769 Set this variable to sort directories differently than other items,
1770 can be set to size, atime, mtime and ctime; setting any other value
1771 will revert to sorting by name.
1773 =item B<GalleryMemoize>
1775 Cache EXIF data using Memoize - this will make Apache::Gallery faster
1776 when many people access the same images, but it will also cache EXIF
1777 data until the current Apache child dies.
1779 =item B<GalleryUseFileDate>
1781 Set this option to 1 to make A::G show the files timestamp
1782 instead of the EXIF value for "Picture taken".
1784 =item B<GallerySelectionMode>
1786 Enable the selection mode. Select images with checkboxes and
1787 get a list of filenames.
1789 =item B<GalleryEXIFMode>
1791 You can choose how Apache::Gallery should display EXIF info
1794 The default setting is 'namevalue'. This setting will make
1795 Apache::Gallery print out the names and values of the EXIF values
1796 you configure with GalleryInfo. The information will be parsed into
1797 $INFO in pictureinfo.tpl.
1799 You can also set it to 'values' which will make A::G parse
1800 the configured values into the var $EXIFVALUES as 'value | value | value'
1802 If you set this option to 'variables' the items you configure in GalleryInfo
1803 will be available to your templates as $EXIF_<KEYNAME> (in all uppercase).
1804 That means that with the default setting "Picture Taken => DateTimeOriginal,
1805 Flash => Flash" you will have the variables $EXIF_DATETIMEORIGINAL and
1806 $EXIF_FLASH available to your templates. You can place them
1809 =item B<GalleryRootPath>
1811 Change the location of gallery root. The default is ""
1813 =item B<GalleryRootText>
1815 Change the name that appears as the root element in the menu. The
1818 =item B<GalleryMaxThumbnailsPerPage>
1820 This options controls how many thumbnails should be displayed in a
1821 page. It requires $BROWSELINKS to be in the index.tpl template file.
1823 =item B<GalleryImgFile>
1825 Pattern matching the files you want Apache::Gallery to view in the
1826 index as thumbnails.
1828 The default is '\.(jpe?g|png|tiff?|ppm)$'
1830 =item B<GalleryDocFile>
1832 Pattern matching the files you want Apache::Gallery to view in the index
1833 as normal files. All other filetypes will still be served by Apache::Gallery
1834 but are not visible in the index.
1836 The default is '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
1838 =item B<GalleryTTFDir>
1840 To use the GalleryCopyrightText feature you must set this option to the
1841 directory where your True Type fonts are stored. No default is set.
1845 PerlSetVar GalleryTTFDir '/usr/share/fonts/'
1847 =item B<GalleryTTFFile>
1849 To use the GalleryCopyrightText feature this option must be set to the
1850 name of the True Type font you wish to use. Example:
1852 PerlSetVar GalleryTTFFile 'verdanab.ttf'
1854 =item B<GalleryTTFSize>
1856 Configure the size of the CopyrightText that will be inserted as
1857 copyright notice in the corner of your pictures.
1861 PerlSetVar GalleryTTFSize '10'
1863 =item B<GalleryCopyrightText>
1865 The text that will be inserted as copyright notice.
1869 PerlSetVar GalleryCopyrightText '(c) Michael Legart'
1871 =item B<GalleryCopyrightColor>
1873 The text color of your copyright notice.
1878 PerlSetVar GalleryCopyrightColor '255,255,255,255'
1881 PerlSetVar GalleryCopyrightColor '0,0,0,255'
1884 PerlSetVar GalleryCopyrightColor '255,0,0,255'
1887 PerlSetVar GalleryCopyrightColor '0,255,0,255'
1890 PerlSetVar GalleryCopyrightColor '0,0,255,255'
1893 PerlSetVar GalleryCopyrightColor '255,127,0,127'
1895 =item B<GalleryCopyrightBackgroundColor>
1897 The background-color of a GalleryCopyrightText
1899 r,g,b,a - for examples, see GalleryCopyrightColor
1901 =item B<GalleryQuality>
1903 The quality (1-100) of scaled images
1905 This setting affects the quality of the scaled images.
1906 Set this to a low number to reduce the size of the scaled images.
1907 Remember to clear out your cache if you change this setting.
1908 Quality seems to default to 75, at least in the jpeg and png loader code in
1914 PerlSetVar GalleryQuality '50'
1916 =item B<GalleryUnderscoresToSpaces>
1918 Set this option to 1 to convert underscores to spaces in the listing
1919 of directory and file names, as well as in the alt attribute for HTML
1926 =item B<GalleryCommentExifKey>
1928 Set this option to e.g. ImageDescription to use this field as comments
1931 =item B<GalleryEnableMediaRss>
1933 Set this option to 1 to enable generation of a media RSS feed. This
1934 can be used e.g. together with the PicLens plugin from http://piclens.com
1942 =item B<Rotate images>
1944 Some cameras, like the Canon G3, detects the orientation of a picture
1945 and adds this info to the EXIF header. Apache::Gallery detects this
1946 and automatically rotates images with this info.
1948 If your camera does not support this, you can rotate the images
1949 manually, This can also be used to override the rotate information
1950 from a camera that supports that. You can also disable this behavior
1951 with the GalleryAutoRotate option.
1953 To use this functionality you have to create file with the name of the
1954 picture you want rotated appended with ".rotate". The file should include
1955 a number where these numbers are supported:
1957 "1", rotates clockwise by 90 degree
1958 "2", rotates clockwise by 180 degrees
1959 "3", rotates clockwise by 270 degrees
1961 So if we want to rotate "Picture1234.jpg" 90 degrees clockwise we would
1962 create a file in the same directory called "Picture1234.jpg.rotate" with
1963 the number 1 inside of it.
1965 =item B<Ignore directories/files>
1967 To ignore a directory or a file (of any kind, not only images) you
1968 create a <directory|file>.ignore file.
1972 To include comments for a directory you create a <directory>.comment
1973 file where the first line can contain "TITLE: New title" which
1974 will be the title of the page, and a comment on the following
1976 To include comments for each picture you create files called
1977 picture.jpg.comment where the first line can contain "TITLE: New
1978 title" which will be the title of the page, and a comment on the
1983 TITLE: This is the new title of the page
1984 And this is the comment.<br />
1985 And this is line two of the comment.
1987 The visible name of the folder is by default identical to the name of
1988 the folder, but can be changed by creating a file <directory>.folder
1989 with the visible name of the folder.
1991 It is also possible to set GalleryCommentExifKey to the name of an EXIF
1992 field containing the comment, e.g. ImageDescription. The EXIF comment is
1993 overridden by the .comment file if it exists.
2003 =item B<Apache with mod_perl>
2005 =item B<URI::Escape>
2007 =item B<Image::Info>
2009 =item B<Image::Size>
2011 =item B<Text::Template>
2013 =item B<Image::Imlib2>
2015 =item B<X11 libraries>
2019 Remember the -dev package when using rpm, deb or other package formats!
2025 Michael Legart <michael@legart.dk>
2027 =head1 COPYRIGHT AND LICENSE
2029 Copyright (C) 2001-2011 Michael Legart <michael@legart.dk>
2031 Templates designed by Thomas Kjaer <tk@lnx.dk>
2033 Apache::Gallery is free software and is released under the Artistic License.
2034 See B<http://www.perl.com/language/misc/Artistic.html> for details.
2036 The video icons are from the GNOME project. B<http://www.gnome.org/>
2040 Thanks to Thomas Kjaer for templates and design of B<http://apachegallery.dk>
2041 Thanks to Thomas Eibner and other for patches. (See the Changes file)
2045 L<perl>, L<mod_perl>, L<Image::Imlib2>, L<CGI::FastTemplate>,
2046 L<Image::Info>, and L<Image::Size>.