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/$1";
125 return send_file($r,$filename);
127 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
130 # Lookup the file in the cache and scale the image if the cached
131 # image does not exist
132 if ($r->uri =~ m/\.cache\//i) {
134 my $filename = $r->filename().$r->path_info();
135 $filename =~ s/\.cache//;
137 $filename =~ m/\/(\d+)x(\d+)\-/;
138 my $image_width = $1;
139 my $image_height = $2;
141 $filename =~ s/\/(\d+)x(\d+)\-//;
143 my ($width, $height, $type) = imgsize($filename);
145 my $imageinfo = get_imageinfo($r, $filename, $type, $width, $height);
147 my $cached = scale_picture($r, $filename, $image_width, $image_height, $imageinfo);
149 my $file = cache_dir($r, 0);
150 $file =~ s/\.cache//;
152 return send_file($r,$file);
160 unless (-f $filename or -d $filename) {
161 show_error($r, 404, "404!", "No such file or directory: ".uri_escape($r->uri, $escape_rule));
162 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
165 my $doc_pattern = $r->dir_config('GalleryDocFile');
166 unless ($doc_pattern) {
167 $doc_pattern = '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|mp4|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
169 my $img_pattern = $r->dir_config('GalleryImgFile') unless ($img_pattern) {
170 $img_pattern = '\.(jpe?g|png|tiff?|ppm)$'
173 # Let Apache serve files we don't know how to handle anyway
174 if (-f $filename && $filename !~ m/$img_pattern/i) {
175 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
180 unless (-d cache_dir($r, 0)) {
181 unless (create_cache($r, cache_dir($r, 0))) {
182 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
186 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
188 # Instead of reading the templates every single time
189 # we need them, create a hash of template names and
190 # the associated Text::Template objects.
191 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
192 index => "$tpl_dir/index.tpl",
193 directory => "$tpl_dir/directory.tpl",
194 picture => "$tpl_dir/picture.tpl",
195 file => "$tpl_dir/file.tpl",
196 comment => "$tpl_dir/dircomment.tpl",
197 nocomment => "$tpl_dir/nodircomment.tpl",
198 rss => "$tpl_dir/rss.tpl",
199 rss_item => "$tpl_dir/rss_item.tpl",
200 navdirectory => "$tpl_dir/navdirectory.tpl",
208 $tpl_vars{TITLE} = "Index of: $uri";
210 if ($media_rss_enabled) {
211 # Put the RSS feed on all directory listings
212 $tpl_vars{META} = '<link rel="alternate" href="?rss=1" type="application/rss+xml" title="" id="gallery" />';
215 unless (opendir (DIR, $filename)) {
216 show_error ($r, 500, $!, "Unable to access directory $filename: $!");
217 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
220 $tpl_vars{MENU} = generate_menu($r);
222 $tpl_vars{FORM_BEGIN} = $select_mode?'<form method="post">':'';
223 $tpl_vars{FORM_END} = $select_mode?'<input type="submit" name="Get list" value="Get list"></form>':'';
225 # Read, sort, and filter files
226 my @files = grep { !/^\./ && -f "$filename/$_" } readdir (DIR);
228 @files=gallerysort($r, @files);
230 my @downloadable_files;
233 # Remove unwanted files from list
235 foreach my $picture (@files) {
237 my $file = $topdir."/".$picture;
239 if ($file =~ /$img_pattern/i) {
240 push (@new_files, $picture);
243 if ($file =~ /$doc_pattern/i) {
244 push (@downloadable_files, $picture);
251 # Read and sort directories
253 my @directories = grep { !/^\./ && -d "$filename/$_" } readdir (DIR);
255 if (defined($r->dir_config('GalleryDirSortBy'))) {
256 $dirsortby=$r->dir_config('GalleryDirSortBy');
258 $dirsortby=$r->dir_config('GallerySortBy');
260 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
261 @directories = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$dirsortby()." $_", @directories));
263 @directories = sort @directories;
269 # Combine directories and files to one listing
271 push (@listing, @directories);
272 push (@listing, @files);
273 push (@listing, @downloadable_files);
279 my $file_counter = 0;
281 my $max_files = $r->dir_config('GalleryMaxThumbnailsPerPage');
283 if (defined($cgi->param('start'))) {
284 $start_at = $cgi->param('start');
290 my $browse_links = "";
291 if (defined($max_files)) {
293 for (my $i=1; $i<=scalar(@listing); $i++) {
297 my $to = $i+$max_files-1;
298 if ($to > scalar(@listing)) {
299 $to = scalar(@listing);
302 if ($start_at < $from || $start_at > $to) {
303 $browse_links .= "<a href=\"?start=$from\">$from - ".$to."</a> ";
306 $browse_links .= "$from - $to ";
315 $tpl_vars{BROWSELINKS} = $browse_links;
318 foreach my $file (@listing) {
322 if ($file_counter < $start_at) {
326 if (defined($max_files) && $file_counter > $max_files+$start_at-1) {
330 my $thumbfilename = $topdir."/".$file;
332 my $fileurl = $uri."/".$file;
334 # Debian bug #619625 <http://bugs.debian.org/619625>
335 if (-d $thumbfilename && ! -e $thumbfilename . ".ignore") {
337 if (-e $thumbfilename . ".folder") {
338 $dirtitle = get_filecontent($thumbfilename . ".folder");
341 $dirtitle = $dirtitle ? $dirtitle : $file;
342 $dirtitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
345 $templates{directory}->fill_in(HASH=> {FILEURL => uri_escape($fileurl, $escape_rule),
351 # Debian bug #619625 <http://bugs.debian.org/619625>
352 elsif (-f $thumbfilename && $thumbfilename =~ /$doc_pattern/i && $thumbfilename !~ /$img_pattern/i && ! -e $thumbfilename . ".ignore") {
354 my $stat = stat($thumbfilename);
355 my $size = $stat->size;
358 if ($thumbfilename =~ m/\.(mpe?g|avi|mov|asf|wmv)$/i) {
359 $filetype = "video-$type";
360 } elsif ($thumbfilename =~ m/\.(txt|html?)$/i) {
361 $filetype = "text-$type";
362 } elsif ($thumbfilename =~ m/\.(mp3|ogg|wav)$/i) {
363 $filetype = "sound-$type";
364 } elsif ($thumbfilename =~ m/$doc_pattern/i) {
365 $filetype = "application-$type";
367 $filetype = "unknown";
370 # Debian bug #348724 <http://bugs.debian.org/348724>
372 my $filetitle = $file;
373 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
376 $templates{file}->fill_in(HASH => {%tpl_vars,
377 FILEURL => uri_escape($fileurl, $escape_rule),
378 ALT => "Size: $size Bytes",
381 FILETYPE => $filetype,
385 # Debian bug #619625 <http://bugs.debian.org/619625>
386 elsif (-f $thumbfilename && ! -e $thumbfilename . ".ignore") {
388 my ($width, $height, $type) = imgsize($thumbfilename);
389 next if $type eq 'Data stream is not a known image file format';
391 my @filetypes = qw(JPG TIF PNG PPM GIF);
393 next unless (grep $type eq $_, @filetypes);
394 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);
395 my $imageinfo = get_imageinfo($r, $thumbfilename, $type, $width, $height);
396 my $cached = get_scaled_picture_name($thumbfilename, $thumbnailwidth, $thumbnailheight);
398 my $rotate = readfile_getnum($r, $imageinfo, $thumbfilename.".rotate");
400 # Debian bug #348724 <http://bugs.debian.org/348724>
401 # HTML <img> tag, alt attribute
402 my $filetitle = $file;
403 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
405 my %file_vars = (FILEURL => uri_escape($fileurl, $escape_rule),
407 DATE => $imageinfo->{DateTimeOriginal} ? $imageinfo->{DateTimeOriginal} : '', # should this really be a stat of the file instead of ''?
408 SRC => uri_escape($uri."/.cache/$cached", $escape_rule),
409 HEIGHT => (grep($rotate==$_, (1, 3)) ? $thumbnailwidth : $thumbnailheight),
410 WIDTH => (grep($rotate==$_, (1, 3)) ? $thumbnailheight : $thumbnailwidth),
411 SELECT => $select_mode?'<input type="checkbox" name="selection" value="'.$file.'"> ':'',);
412 $tpl_vars{FILES} .= $templates{picture}->fill_in(HASH => {%tpl_vars,
417 if ($media_rss_enabled) {
418 my ($content_image_width, undef, $content_image_height) = get_image_display_size($cgi, $r, $width, $height);
420 THUMBNAIL => uri_escape($uri."/.cache/$cached", $escape_rule),
421 LINK => uri_escape($fileurl, $escape_rule),
423 CONTENT => uri_escape($uri."/.cache/".$content_image_width."x".$content_image_height."-".$file, $escape_rule)
425 $tpl_vars{ITEMS} .= $templates{rss_item}->fill_in(HASH => {
433 $tpl_vars{FILES} = "No files found";
434 $tpl_vars{BROWSELINKS} = "";
437 # Generate prev and next directory menu items
438 $filename =~ m/(.*)\/.*?$/;
439 my $parent_filename = $1;
441 $r->document_root =~ m/(.*)\/$/;
443 print STDERR "$filename vs $root_path\n";
444 if ($filename ne $root_path) {
445 unless (opendir (PARENT_DIR, $parent_filename)) {
446 show_error ($r, 500, $!, "Unable to access parent directory $parent_filename: $!");
447 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
450 # Debian bug #619625 <http://bugs.debian.org/619625>
451 my @neighbour_directories = grep { !/^\./ && -d "$parent_filename/$_" && ! -e "$parent_filename/$_" . ".ignore" } readdir (PARENT_DIR);
453 if (defined($r->dir_config('GalleryDirSortBy'))) {
454 $dirsortby=$r->dir_config('GalleryDirSortBy');
456 $dirsortby=$r->dir_config('GallerySortBy');
458 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
459 @neighbour_directories = map(/^\d+ (.*)/, sort map(stat("$parent_filename/$_")->$dirsortby()." $_", @neighbour_directories));
461 @neighbour_directories = sort @neighbour_directories;
464 closedir(PARENT_DIR);
466 my $neightbour_counter = 0;
467 foreach my $neighbour_directory (@neighbour_directories) {
468 if ($parent_filename.'/'.$neighbour_directory eq $filename) {
469 if ($neightbour_counter > 0) {
470 print STDERR "prev directory is " .$neighbour_directories[$neightbour_counter-1] ."\n";
471 my $linktext = $neighbour_directories[$neightbour_counter-1];
472 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
473 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder");
476 URL => "../".$neighbour_directories[$neightbour_counter-1],
477 LINK_NAME => "<<< $linktext",
480 $tpl_vars{PREV_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
481 print STDERR $tpl_vars{PREV_DIR_FILES} ."\n";
484 if ($neightbour_counter < scalar @neighbour_directories - 1) {
485 my $linktext = $neighbour_directories[$neightbour_counter+1];
486 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder") {
487 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder");
490 URL => "../".$neighbour_directories[$neightbour_counter+1],
491 LINK_NAME => "$linktext >>>",
494 $tpl_vars{NEXT_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
495 print STDERR "next directory is " .$neighbour_directories[$neightbour_counter+1] ."\n";
498 $neightbour_counter++;
502 if (-f $topdir . '.comment') {
503 my $comment_ref = get_comment($topdir . '.comment');
505 $comment_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
506 $comment_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
507 $tpl_vars{DIRCOMMENT} = $templates{comment}->fill_in(HASH => \%comment_vars);
508 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
510 $tpl_vars{DIRCOMMENT} = $templates{nocomment}->fill_in(HASH=>\%tpl_vars);
513 if ($cgi->param('rss')) {
514 $tpl_vars{MAIN} = $templates{rss}->fill_in(HASH => \%tpl_vars);
515 $r->content_type('application/rss+xml');
517 $tpl_vars{MAIN} = $templates{index}->fill_in(HASH => \%tpl_vars);
518 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
519 $r->content_type('text/html');
522 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
525 $r->send_http_header;
528 $r->print($tpl_vars{MAIN});
529 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
535 if (defined($ENV{QUERY_STRING}) && $ENV{QUERY_STRING} eq 'orig') {
536 if ($r->dir_config('GalleryAllowOriginal') ? 1 : 0) {
537 $r->filename($filename);
538 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
540 return $::MP2 ? Apache2::Const::FORBIDDEN() : Apache::Constants::FORBIDDEN();
543 if (defined $ENV{QUERY_STRING} && $ENV{QUERY_STRING} eq 'thumbonly' &&
544 $r->dir_config('GalleryAllowThumbonly') &&
547 my ($width, $height, $type) = imgsize($filename);
548 my @filetypes = qw(JPG TIF PNG PPM GIF);
549 if (grep $type eq $_, @filetypes) {
550 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);
551 my $imageinfo = get_imageinfo($r, $filename, $type, $width, $height);
552 my $cached = get_scaled_picture_name($filename, $thumbnailwidth, $thumbnailheight);
553 $r->headers_out->set(Location => uri_escape(".cache/$cached", $escape_rule));
554 return $::MP2 ? Apache2::Const::REDIRECT() : Apache::Constants::REDIRECT();
558 # Create cache dir if not existing
559 my @tmp = split (/\//, $filename);
560 my $picfilename = pop @tmp;
561 my $path = (join "/", @tmp)."/";
562 my $cache_path = cache_dir($r, 1);
564 unless (-d $cache_path) {
565 unless (create_cache($r, $cache_path)) {
566 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
570 my ($orig_width, $orig_height, $type) = imgsize($filename);
572 my $imageinfo = get_imageinfo($r, $filename, $type, $orig_width, $orig_height);
574 my ($image_width, $width, $height, $original_size) = get_image_display_size($cgi, $r, $orig_width, $orig_height);
576 my $cached = get_scaled_picture_name($filename, $image_width, $height);
578 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
580 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
581 picture => "$tpl_dir/showpicture.tpl",
582 navpicture => "$tpl_dir/navpicture.tpl",
583 info => "$tpl_dir/info.tpl",
584 scale => "$tpl_dir/scale.tpl",
585 scaleactive => "$tpl_dir/scaleactive.tpl",
586 orig => "$tpl_dir/orig.tpl",
587 refresh => "$tpl_dir/refresh.tpl",
588 interval => "$tpl_dir/interval.tpl",
589 intervalactive => "$tpl_dir/intervalactive.tpl",
590 slideshowisoff => "$tpl_dir/slideshowisoff.tpl",
591 slideshowoff => "$tpl_dir/slideshowoff.tpl",
592 pictureinfo => "$tpl_dir/pictureinfo.tpl",
593 nopictureinfo => "$tpl_dir/nopictureinfo.tpl",
598 my $resolution = (($image_width > $orig_width) && ($height > $orig_height)) ?
599 "$orig_width x $orig_height" : "$image_width x $height";
601 $tpl_vars{TITLE} = "Viewing ".$r->uri()." at $image_width x $height";
602 $tpl_vars{META} = " ";
603 $tpl_vars{RESOLUTION} = $resolution;
604 $tpl_vars{MENU} = generate_menu($r);
605 $tpl_vars{SRC} = uri_escape(".cache/$cached", $escape_rule);
606 $tpl_vars{URI} = $r->uri();
608 my $exif_mode = $r->dir_config('GalleryEXIFMode');
609 unless ($exif_mode) {
610 $exif_mode = 'namevalue';
613 unless (opendir(DATADIR, $path)) {
614 show_error($r, 500, "Unable to access directory", "Unable to access directory $path");
615 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
617 my @pictures = grep { /$img_pattern/i && ! -e "$path/$_" . ".ignore" } readdir (DATADIR);
619 @pictures = gallerysort($r, @pictures);
621 $tpl_vars{TOTAL} = scalar @pictures;
626 for (my $i=0; $i <= $#pictures; $i++) {
627 if ($pictures[$i] eq $picfilename) {
629 $tpl_vars{NUMBER} = $i+1;
631 $prevpicture = $pictures[$i-1];
632 my $displayprev = ($i>0 ? 1 : 0);
634 if ($r->dir_config("GalleryWrapNavigation")) {
635 $prevpicture = $pictures[$i>0 ? $i-1 : $#pictures];
638 if ($prevpicture and $displayprev) {
639 my ($orig_width, $orig_height, $type) = imgsize($path.$prevpicture);
640 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
641 my $imageinfo = get_imageinfo($r, $path.$prevpicture, $type, $orig_width, $orig_height);
642 my $cached = get_scaled_picture_name($path.$prevpicture, $thumbnailwidth, $thumbnailheight);
644 $nav_vars{URL} = uri_escape($prevpicture, $escape_rule);
645 $nav_vars{FILENAME} = $prevpicture;
646 $nav_vars{WIDTH} = $width;
647 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
648 $nav_vars{DIRECTION} = "« <u>p</u>rev";
649 $nav_vars{ACCESSKEY} = "P";
650 $tpl_vars{BACK} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
653 $tpl_vars{BACK} = " ";
656 $nextpicture = $pictures[$i+1];
657 if ($r->dir_config("GalleryWrapNavigation")) {
658 $nextpicture = $pictures[$i == $#pictures ? 0 : $i+1];
662 my ($orig_width, $orig_height, $type) = imgsize($path.$nextpicture);
663 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
664 my $imageinfo = get_imageinfo($r, $path.$nextpicture, $type, $thumbnailwidth, $thumbnailheight);
665 my $cached = get_scaled_picture_name($path.$nextpicture, $thumbnailwidth, $thumbnailheight);
667 $nav_vars{URL} = uri_escape($nextpicture, $escape_rule);
668 $nav_vars{FILENAME} = $nextpicture;
669 $nav_vars{WIDTH} = $width;
670 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
671 $nav_vars{DIRECTION} = "<u>n</u>ext »";
672 $nav_vars{ACCESSKEY} = "N";
674 $tpl_vars{NEXT} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
675 $tpl_vars{NEXTURL} = uri_escape($nextpicture, $escape_rule);
678 $tpl_vars{NEXT} = " ";
679 $tpl_vars{NEXTURL} = '#';
684 my $foundcomment = 0;
685 if (-f $path . '/' . $picfilename . '.comment') {
686 my $comment_ref = get_comment($path . '/' . $picfilename . '.comment');
688 $tpl_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
689 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
690 } elsif ($r->dir_config('GalleryCommentExifKey')) {
691 my $comment = decode("utf8", $imageinfo->{$r->dir_config('GalleryCommentExifKey')});
692 $tpl_vars{COMMENT} = encode("iso-8859-1", $comment);
694 $tpl_vars{COMMENT} = '';
697 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
702 my ($human_key, $exif_key) = (split " => ")[0,1];
703 my $value = $imageinfo->{$human_key};
704 if (defined($value)) {
708 if ($exif_mode eq 'namevalue') {
710 $info_vars{KEY} = $human_key;
711 $info_vars{VALUE} = $value;
712 $tpl_vars{INFO} .= $templates{info}->fill_in(HASH => \%info_vars);
715 if ($exif_mode eq 'variables') {
716 $tpl_vars{"EXIF_".uc($exif_key)} = $value;
719 if ($exif_mode eq 'values') {
720 $exifvalues .= "| ".$value." ";
727 if ($exif_mode eq 'values') {
728 if (defined($exifvalues)) {
729 $tpl_vars{EXIFVALUES} = $exifvalues;
732 $tpl_vars{EXIFVALUES} = "";
736 if ($foundcomment and !$foundinfo) {
737 $tpl_vars{INFO} = "";
740 if ($exif_mode ne 'namevalue') {
741 $tpl_vars{INFO} = "";
744 if ($exif_mode eq 'namevalue' && $foundinfo or $foundcomment) {
746 $tpl_vars{PICTUREINFO} = $templates{pictureinfo}->fill_in(HASH => \%tpl_vars);
748 unless (defined($exifvalues)) {
749 $tpl_vars{EXIFVALUES} = "";
754 $tpl_vars{PICTUREINFO} = $templates{nopictureinfo}->fill_in(HASH => \%tpl_vars);
757 # Fill in sizes and determine if any are smaller than the
758 # actual image. If they are, $scaleable=1
760 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
761 foreach my $size (@sizes) {
762 if ($size<=$original_size) {
764 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
765 $sizes_vars{SIZE} = $size;
766 $sizes_vars{WIDTH} = $size;
767 if ($width == $size) {
768 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
771 $tpl_vars{SIZES} .= $templates{scale}->fill_in(HASH => \%sizes_vars);
777 unless ($scaleable) {
779 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
780 $sizes_vars{SIZE} = $original_size;
781 $sizes_vars{WIDTH} = $original_size;
782 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
785 $tpl_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
787 if ($r->dir_config('GalleryAllowOriginal')) {
788 $tpl_vars{SIZES} .= $templates{orig}->fill_in(HASH => \%tpl_vars);
791 my @slideshow_intervals = split (/ /, $r->dir_config('GallerySlideshowIntervals') ? $r->dir_config('GallerySlideshowIntervals') : '3 5 10 15 30');
792 foreach my $interval (@slideshow_intervals) {
795 $slideshow_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
796 $slideshow_vars{SECONDS} = $interval;
797 $slideshow_vars{WIDTH} = ($width > $height ? $width : $height);
799 if ($cgi->param('slideshow') && $cgi->param('slideshow') == $interval and $nextpicture) {
800 $tpl_vars{SLIDESHOW} .= $templates{intervalactive}->fill_in(HASH => \%slideshow_vars);
804 $tpl_vars{SLIDESHOW} .= $templates{interval}->fill_in(HASH => \%slideshow_vars);
809 if ($cgi->param('slideshow') and $nextpicture) {
811 $tpl_vars{SLIDESHOW} .= $templates{slideshowoff}->fill_in(HASH => \%tpl_vars);
813 unless ((grep $cgi->param('slideshow') == $_, @slideshow_intervals)) {
814 show_error($r, 200, "Invalid interval", "Invalid slideshow interval choosen");
815 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
818 $tpl_vars{URL} = uri_escape($nextpicture, $escape_rule);
819 $tpl_vars{WIDTH} = ($width > $height ? $width : $height);
820 $tpl_vars{INTERVAL} = $cgi->param('slideshow');
821 $tpl_vars{META} .= $templates{refresh}->fill_in(HASH => \%tpl_vars);
825 $tpl_vars{SLIDESHOW} .= $templates{slideshowisoff}->fill_in(HASH => \%tpl_vars);
828 $tpl_vars{MAIN} = $templates{picture}->fill_in(HASH => \%tpl_vars);
829 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
831 $r->content_type('text/html');
832 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
835 $r->send_http_header;
838 $r->print($tpl_vars{MAIN});
839 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
847 my $subr = $r->lookup_file($file);
848 $r->content_type($subr->content_type());
850 my $fileinfo = stat($file);
852 my $nonce = md5_base64($fileinfo->ino.$fileinfo->mtime);
853 if ($r->headers_in->{"If-None-Match"} eq $nonce) {
854 return Apache2::Const::HTTP_NOT_MODIFIED();
857 if ($r->headers_in->{"If-Modified-Since"} && str2time($r->headers_in->{"If-Modified-Since"}) < $fileinfo->mtime) {
858 return Apache2::Const::HTTP_NOT_MODIFIED();
861 $r->headers_out->{"Content-Length"} = $fileinfo->size;
862 $r->headers_out->{"Last-Modified-Date"} = time2str($fileinfo->mtime);
863 $r->headers_out->{"ETag"} = $nonce;
865 return Apache2::Const::OK();
870 return Apache::Constants::DECLINED();
876 my ($r, $strip_filename) = @_;
880 unless ($r->dir_config('GalleryCacheDir')) {
882 $cache_root = '/var/cache/www/';
883 if ($r->server->is_virtual) {
884 $cache_root = File::Spec->catdir($cache_root, $r->server->server_hostname);
886 $cache_root = File::Spec->catdir($cache_root, $r->location);
891 $cache_root = $r->dir_config('GalleryCacheDir');
895 # If the uri contains .cache we need to remove it
899 my (undef, $dirs, $filename) = File::Spec->splitpath($uri);
900 # We don't need a volume as this is a relative path
902 if ($strip_filename) {
903 return(File::Spec->canonpath(File::Spec->catdir($cache_root, $dirs)));
905 return(File::Spec->canonpath(File::Spec->catfile($cache_root, $dirs, $filename)));
913 unless (mkdirhier ($path)) {
914 show_error($r, 500, $!, "Unable to create cache directory in $path: $!");
927 unless (mkdir($dir, 0755)) {
929 $parent =~ s/\/[^\/]*$//;
938 sub get_scaled_picture_name {
940 my ($fullpath, $width, $height) = @_;
942 my (undef, undef, $type) = imgsize($fullpath);
944 my @dirs = split(/\//, $fullpath);
945 my $filename = pop(@dirs);
948 if (grep $type eq $_, qw(PPM TIF GIF)) {
949 $newfilename = $width."x".$height."-".$filename;
950 # needs to be configurable
951 $newfilename =~ s/\.(\w+)$/-$1\.jpg/;
953 $newfilename = $width."x".$height."-".$filename;
962 my ($r, $fullpath, $width, $height, $imageinfo) = @_;
964 my @dirs = split(/\//, $fullpath);
965 my $filename = pop(@dirs);
967 my ($orig_width, $orig_height, $type) = imgsize($fullpath);
969 my $cache = cache_dir($r, 1);
971 my $newfilename = get_scaled_picture_name($fullpath, $width, $height);
973 if (($width > $orig_width) && ($height > $orig_height)) {
974 # Run it through the resize code anyway to get watermarks
975 $width = $orig_width;
976 $height = $orig_height;
979 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
981 # Do we want to generate a new file in the cache?
984 if (-f $cache."/".$newfilename) {
987 # Check to see if the image has changed
988 my $filestat = stat($fullpath);
989 my $cachestat = stat($cache."/".$newfilename);
990 if ($filestat->mtime >= $cachestat->mtime) {
994 # Check to see if the .rotate file has been added or changed
995 if (-f $fullpath . ".rotate") {
996 my $rotatestat = stat($fullpath . ".rotate");
997 if ($rotatestat->mtime > $cachestat->mtime) {
1001 # Check to see if the copyrightimage has been added or changed
1002 if ($r->dir_config('GalleryCopyrightImage') && -f $r->dir_config('GalleryCopyrightImage')) {
1003 unless ($width == $thumbnailwidth or $width == $thumbnailheight) {
1004 my $copyrightstat = stat($r->dir_config('GalleryCopyrightImage'));
1005 if ($copyrightstat->mtime > $cachestat->mtime) {
1015 my $newpath = $cache."/".$newfilename;
1016 my $rotate = readfile_getnum($r, $imageinfo, $fullpath . ".rotate");
1017 my $quality = $r->dir_config('GalleryQuality');
1019 if ($width == $thumbnailwidth or $width == $thumbnailheight) {
1021 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, '', '', '', '', '', '');
1025 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate,
1026 ($r->dir_config('GalleryCopyrightImage') ? $r->dir_config('GalleryCopyrightImage') : ''),
1027 ($r->dir_config('GalleryTTFDir') ? $r->dir_config('GalleryTTFDir') : ''),
1028 ($r->dir_config('GalleryCopyrightText') ? $r->dir_config('GalleryCopyrightText') : ''),
1029 ($r->dir_config('GalleryCopyrightColor') ? $r->dir_config('GalleryCopyrightColor') : ''),
1030 ($r->dir_config('GalleryTTFFile') ? $r->dir_config('GalleryTTFFile') : ''),
1031 ($r->dir_config('GalleryTTFSize') ? $r->dir_config('GalleryTTFSize') : ''),
1032 ($r->dir_config('GalleryCopyrightBackgroundColor') ? $r->dir_config('GalleryCopyrightBackgroundColor') : ''),
1038 return $newfilename;
1042 sub get_thumbnailsize {
1043 my ($r, $orig_width, $orig_height) = @_;
1045 my $gallerythumbnailsize=$r->dir_config('GalleryThumbnailSize');
1047 if (defined($gallerythumbnailsize)) {
1048 warn("Invalid setting for GalleryThumbnailSize") unless
1049 $gallerythumbnailsize =~ /^\s*\d+\s*x\s*\d+\s*$/i;
1052 my ($thumbnailwidth, $thumbnailheight) = split(/x/i, ($gallerythumbnailsize) ? $gallerythumbnailsize : "100x75");
1054 my $width = $thumbnailwidth;
1055 my $height = $thumbnailheight;
1057 # If the image is rotated, flip everything around.
1058 if (defined $r->dir_config('GalleryThumbnailSizeLS')
1059 and $r->dir_config('GalleryThumbnailSizeLS') eq '1'
1060 and $orig_width < $orig_height) {
1062 $width = $thumbnailheight;
1063 $height = $thumbnailwidth;
1066 my $scale = ($orig_width ? $width/$orig_width : 1);
1069 if ($orig_height * $scale > $thumbnailheight) {
1070 $scale = $height/$orig_height;
1071 $width = $orig_width * $scale;
1075 $height = $orig_height * $scale;
1077 $height = floor($height);
1078 $width = floor($width);
1080 return ($width, $height);
1083 sub get_image_display_size {
1084 my ($cgi, $r, $orig_width, $orig_height) = @_;
1086 my $width = $orig_width;
1088 my $original_size=$orig_height;
1089 if ($orig_width>$orig_height) {
1090 $original_size=$orig_width;
1093 # Check if the selected width is allowed
1094 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
1096 my %cookies = fetch CGI::Cookie;
1098 if ($cgi->param('width')) {
1099 unless ((grep $cgi->param('width') == $_, @sizes) or ($cgi->param('width') == $original_size)) {
1100 show_error($r, 200, "Invalid width", "The specified width is invalid");
1101 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
1104 $width = $cgi->param('width');
1105 my $cookie = new CGI::Cookie(-name => 'GallerySize', -value => $width, -expires => '+6M');
1106 $r->headers_out->{'Set-Cookie'} = $cookie;
1108 } elsif ($cookies{'GallerySize'} && (grep $cookies{'GallerySize'}->value == $_, @sizes)) {
1110 $width = $cookies{'GallerySize'}->value;
1118 if ($orig_width<$orig_height) {
1119 $scale = ($orig_height ? $width/$orig_height: 1);
1120 $image_width=$width*$orig_width/$orig_height;
1123 $scale = ($orig_width ? $width/$orig_width : 1);
1124 $image_width = $width;
1127 my $height = $orig_height * $scale;
1129 $image_width = floor($image_width);
1130 $width = floor($width);
1131 $height = floor($height);
1133 return ($image_width, $width, $height, $original_size);
1137 my ($r, $file, $type, $width, $height) = @_;
1139 if ($type eq 'Data stream is not a known image file format') {
1140 # should never be reached, this is supposed to be handled outside of here
1141 log_error("Something was fishy with the type of the file $file\n");
1144 # Some files, like TIFF, PNG, GIF do not have EXIF info
1145 # embedded but use .thm files instead.
1146 $imageinfo = get_imageinfo_from_thm_file($file, $width, $height);
1148 # If there is no .thm file and our file is a JPEG file we try to extract the EXIf
1149 # info using Image::Info
1150 unless (defined($imageinfo) && (grep $type eq $_, qw(JPG))) {
1151 # Only for files that natively keep the EXIF info in the same file
1152 $imageinfo = image_info($file);
1156 unless (defined($imageinfo->{width}) and defined($imageinfo->{height})) {
1157 $imageinfo->{width} = $width;
1158 $imageinfo->{height} = $height;
1161 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
1164 my ($human_key, $exif_key) = (split " => ")[0,1];
1165 if (defined($exif_key) && defined($imageinfo->{$exif_key})) {
1167 if (ref($imageinfo->{$exif_key}) eq 'Image::TIFF::Rational') {
1168 $value = $imageinfo->{$exif_key}->as_string;
1170 elsif (ref($imageinfo->{$exif_key}) eq 'ARRAY') {
1171 foreach my $element (@{$imageinfo->{$exif_key}}) {
1172 if (ref($element) eq 'ARRAY') {
1173 foreach (@{$element}) {
1177 elsif (ref($element) eq 'HASH') {
1178 $value .= "<br />{ ";
1179 foreach (sort keys %{$element}) {
1180 $value .= "$_ = " . $element->{$_} . ' ';
1191 my $exif_value = $imageinfo->{$exif_key};
1192 if ($human_key eq 'Flash' && $exif_value =~ m/\d/) {
1197 "16" => "No (Compulsory) Should be External Flash",
1198 "17" => "Yes (External)",
1200 "25" => "Yes (Auto)",
1201 "73" => "Yes (Compulsory, Red Eye Reducing)",
1202 "89" => "Yes (Auto, Red Eye Reducing)"
1204 $exif_value = defined $flashmodes{$exif_value} ? $flashmodes{$exif_value} : 'unknown flash mode';
1206 $value = $exif_value;
1208 if ($exif_key eq 'MeteringMode') {
1209 my $exif_value = $imageinfo->{$exif_key};
1210 if ($exif_value =~ /^\d+$/) {
1211 my %meteringmodes = (
1214 '2' => 'CenterWeightedAverage',
1221 $exif_value = defined $meteringmodes{$exif_value} ? $meteringmodes{$exif_value} : 'unknown metering mode';
1223 $value = $exif_value;
1226 if ($exif_key eq 'LightSource') {
1227 my $exif_value = $imageinfo->{$exif_key};
1228 if ($exif_value =~ /^\d+$/) {
1229 my %lightsources = (
1232 '2' => 'Fluorescent',
1233 '3' => 'Tungsten (incandescent light)',
1235 '9' => 'Fine weather',
1236 '10' => 'Cloudy weather',
1238 '12' => 'Daylight fluorescent',
1239 '13' => 'Day white fluorescent',
1240 '14' => 'Cool white fluorescent',
1241 '15' => 'White fluorescent',
1242 '17' => 'Standard light A',
1243 '18' => 'Standard light B',
1244 '19' => 'Standard light C',
1249 '24' => 'ISO studio tungsten',
1250 '255' => 'other light source'
1252 $exif_value = defined $lightsources{$exif_value} ? $lightsources{$exif_value} : 'unknown light source';
1254 $value = $exif_value;
1256 if ($exif_key eq 'FocalLength') {
1257 if ($value =~ /^(\d+)\/(\d+)$/) {
1258 $value = eval { $1 / $2 };
1262 $value = int($value + 0.5) . "mm";
1267 if ($exif_key eq 'ShutterSpeedValue') {
1268 if ($value =~ /^((?:\-)?\d+)\/(\d+)$/) {
1269 $value = eval { $1 / $2 };
1274 $value = 1/(exp($value*log(2)));
1276 $value = "1/" . (int((1/$value)));
1278 $value = int($value*10)/10;
1284 $value = $value . " sec";
1289 if ($exif_key eq 'ApertureValue') {
1290 if ($value =~ /^(\d+)\/(\d+)$/) {
1291 $value = eval { $1 / $2 };
1295 # poor man's rounding
1296 $value = int(exp($value*log(2)*0.5)*10)/10;
1297 $value = "f" . $value;
1301 if ($exif_key eq 'FNumber') {
1302 if ($value =~ /^(\d+)\/(\d+)$/) {
1303 $value = eval { $1 / $2 };
1307 $value = int($value*10+0.5)/10;
1308 $value = "f" . $value;
1312 $imageinfo->{$human_key} = $value;
1316 if ($r->dir_config('GalleryUseFileDate') &&
1317 ($r->dir_config('GalleryUseFileDate') eq '1'
1318 || !$imageinfo->{"Picture Taken"} )) {
1320 my $st = stat($file);
1321 $imageinfo->{"DateTimeOriginal"} = $imageinfo->{"Picture Taken"} = scalar localtime($st->mtime) if $st;
1327 sub get_imageinfo_from_thm_file {
1329 my ($file, $width, $height) = @_;
1331 my $imageinfo = undef;
1332 # Windows based file extensions are often .THM, so check
1333 # for both .thm and .THM
1334 my $unix_file = $file;
1335 my $windows_file = $file;
1336 $unix_file =~ s/\.(\w+)$/.thm/;
1337 $windows_file =~ s/\.(\w+)$/.THM/;
1339 if (-e $unix_file && -f $unix_file && -r $unix_file) {
1340 $imageinfo = image_info($unix_file);
1341 $imageinfo->{width} = $width;
1342 $imageinfo->{height} = $height;
1344 elsif (-e $windows_file && -f $windows_file && -r $windows_file) {
1345 $imageinfo = image_info($windows_file);
1346 $imageinfo->{width} = $width;
1347 $imageinfo->{height} = $height;
1354 sub readfile_getnum {
1355 my ($r, $imageinfo, $filename) = @_;
1359 print STDERR "orientation: ".$imageinfo->{Orientation}."\n";
1360 # Check to see if the image contains the Orientation EXIF key,
1361 # but allow user to override using rotate
1362 if (!defined($r->dir_config("GalleryAutoRotate"))
1363 || $r->dir_config("GalleryAutoRotate") eq "1") {
1364 if (defined($imageinfo->{Orientation})) {
1365 print STDERR $imageinfo->{Orientation}."\n";
1366 if ($imageinfo->{Orientation} eq 'right_top') {
1369 elsif ($imageinfo->{Orientation} eq 'left_bot') {
1375 if (open(FH, "<$filename")) {
1379 unless ($temp =~ /^\d$/) {
1382 unless ($temp == 1 || $temp == 2 || $temp == 3) {
1391 sub get_filecontent {
1393 open(FH, $file) or return undef;
1404 my $filename = shift;
1405 my $comment_ref = {};
1406 $comment_ref->{TITLE} = undef;
1407 $comment_ref->{COMMENT} = '';
1409 open(FH, $filename) or return $comment_ref;
1411 if ($title =~ m/^TITLE: (.*)$/) {
1412 chomp($comment_ref->{TITLE} = $1);
1415 $comment_ref->{COMMENT} = $title;
1420 $comment_ref->{COMMENT} .= $_;
1424 return $comment_ref;
1429 my ($r, $statuscode, $errortitle, $error) = @_;
1431 my $tpl = $r->dir_config('GalleryTemplateDir');
1433 my %templates = create_templates({layout => "$tpl/layout.tpl",
1434 error => "$tpl/error.tpl",
1438 $tpl_vars{TITLE} = "Error! $errortitle";
1439 $tpl_vars{META} = "";
1440 $tpl_vars{ERRORTITLE} = "Error! $errortitle";
1441 $tpl_vars{ERROR} = $error;
1443 $tpl_vars{MAIN} = $templates{error}->fill_in(HASH => \%tpl_vars);
1445 $tpl_vars{PAGE} = $templates{layout}->fill_in(HASH => \%tpl_vars);
1447 $r->status($statuscode);
1448 $r->content_type('text/html');
1450 $r->print($tpl_vars{PAGE});
1458 my $root_text = (defined($r->dir_config('GalleryRootText')) ? $r->dir_config('GalleryRootText') : "root:" );
1459 my $root_path = (defined($r->dir_config('GalleryRootPath')) ? $r->dir_config('GalleryRootPath') : "" );
1461 my $subr = $r->lookup_uri($r->uri);
1462 my $filename = $subr->filename;
1464 my @links = split (/\//, $r->uri);
1466 $uri =~ s/^$root_path//g;
1468 @links = split (/\//, $uri);
1470 # Get the full path of the base directory
1473 my @direlem = split (/\//, $filename);
1474 for my $i ( 0 .. ( scalar(@direlem) - scalar(@links) ) ) {
1475 $dirname .= shift(@direlem) . '/';
1482 $picturename = pop(@links);
1485 if ($r->uri eq $root_path) {
1486 return qq{ <a href="$root_path">$root_text</a> };
1490 my $menuurl = $root_path;
1491 foreach my $link (@links) {
1493 $menuurl .= $link."/";
1494 my $linktext = $link;
1495 unless (length($link)) {
1496 $linktext = "$root_text ";
1500 $dirname = File::Spec->catdir($dirname, $link);
1502 if (-e $dirname . ".folder") {
1503 $linktext = get_filecontent($dirname . ".folder");
1507 if ("$root_path$uri" eq $menuurl) {
1508 $menu .= "$linktext / ";
1511 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule)."\">$linktext</a> / ";
1517 $menu .= $picturename;
1521 if ($r->dir_config('GallerySelectionMode') && $r->dir_config('GallerySelectionMode') eq '1') {
1522 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule);
1523 $menu .= "?select=1\">[select]</a> ";
1531 my ($r, $infile, $outfile, $x, $y, $rotate, $copyrightfile, $GalleryTTFDir, $GalleryCopyrightText, $text_color, $GalleryTTFFile, $GalleryTTFSize, $GalleryCopyrightBackgroundColor, $quality) = @_;
1534 my $image = Image::Imlib2->load($infile) or warn("Unable to open file $infile, $!");
1537 $image=$image->create_scaled_image($x, $y) or warn("Unable to scale image $infile. Are you running out of memory?");
1541 $image->image_orientate($rotate);
1544 # blend copyright image onto image
1545 if ($copyrightfile ne '') {
1546 if (-f $copyrightfile and (my $logo=Image::Imlib2->load($copyrightfile))) {
1547 my $x = $image->get_width();
1548 my $y = $image->get_height();
1549 my $logox = $logo->get_width();
1550 my $logoy = $logo->get_height();
1551 $image->blend($logo, 0, 0, 0, $logox, $logoy, $x-$logox, $y-$logoy, $logox, $logoy);
1554 log_error("GalleryCopyrightImage $copyrightfile was not found");
1558 if ($GalleryTTFDir && $GalleryCopyrightText && $GalleryTTFFile && $text_color) {
1559 if (!-d $GalleryTTFDir) {
1561 log_error("GalleryTTFDir $GalleryTTFDir is not a dir\n");
1563 } elsif ($GalleryCopyrightText eq '') {
1565 log_error("GalleryCopyrightText is empty. No text inserted to picture\n");
1567 } elsif (!-e "$GalleryTTFDir/$GalleryTTFFile") {
1569 log_error("GalleryTTFFile $GalleryTTFFile was not found\n");
1573 $GalleryTTFFile =~ s/\.TTF$//i;
1574 $image->add_font_path("$GalleryTTFDir");
1576 $image->load_font("$GalleryTTFFile/$GalleryTTFSize");
1577 my($text_x, $text_y) = $image->get_text_size("$GalleryCopyrightText");
1578 my $x = $image->get_width();
1579 my $y = $image->get_height();
1583 if (($text_x < $x - $offset) && ($text_y < $y - $offset)) {
1584 if ($GalleryCopyrightBackgroundColor =~ /^\d+,\d+,\d+,\d+$/) {
1585 my ($br_val, $bg_val, $bb_val, $ba_val) = split (/,/, $GalleryCopyrightBackgroundColor);
1586 $image->set_colour($br_val, $bg_val, $bb_val, $ba_val);
1587 $image->fill_rectangle ($x-$text_x-$offset, $y-$text_y-$offset, $text_x, $text_y);
1589 my ($r_val, $g_val, $b_val, $a_val) = split (/,/, $text_color);
1590 $image->set_colour($r_val, $g_val, $b_val, $a_val);
1591 $image->draw_text($x-$text_x-$offset, $y-$text_y-$offset, "$GalleryCopyrightText");
1593 log_error("Text is to big for the picture.\n");
1598 if ($quality && $quality =~ m/^\d+$/) {
1599 $image->set_quality($quality);
1602 $image->save($outfile);
1609 my $sortby = $r->dir_config('GallerySortBy');
1610 my $filename=$r->lookup_uri($r->uri)->filename;
1611 $filename=(File::Spec->splitpath($filename))[1] if (-f $filename);
1612 if ($sortby && $sortby =~ m/^(size|atime|mtime|ctime)$/) {
1613 @files = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$sortby()." $_", @files));
1615 @files = sort @files;
1620 # Create Text::Template objects used by Apache::Gallery. Takes a
1621 # hashref of template_name, template_filename pairs, and returns a
1622 # list of template_name, texttemplate_object pairs.
1623 sub create_templates {
1624 my $templates = shift;
1626 # This routine is called whenever a template has an error. Prints
1627 # the error to STDERR and sticks the error in the output
1630 # Pull out the name and filename from the arg option [see
1631 # Text::Template for details]
1632 @args{qw(name file)} = @{$args{arg}};
1633 print STDERR qq(Template $args{name} ("$args{file}") is broken: $args{error});
1634 # Don't include the file name in the output, as the user can see this.
1635 return qq(<!-- Template $args{name} is broken: $args{error} -->);
1640 my %texttemplate_objects;
1642 for my $template_name (keys %$templates) {
1643 my $tt_obj = Text::Template->new(TYPE => 'FILE',
1644 SOURCE => $$templates{$template_name},
1645 BROKEN => \&tt_broken,
1646 BROKEN_ARG => [$template_name, $$templates{$template_name}],
1648 or die "Unable to create new Text::Template object for $template_name: $Text::Template::ERROR";
1649 $texttemplate_objects{$template_name} = $tt_obj;
1651 return %texttemplate_objects;
1656 Apache2::RequestUtil->request->log_error(shift());
1658 Apache->request->log_error(shift());
1666 Apache::Gallery - mod_perl handler to create an image gallery
1670 See the INSTALL file in the distribution for installation instructions.
1674 Apache::Gallery creates an thumbnail index of each directory and allows
1675 viewing pictures in different resolutions. Pictures are resized on the
1676 fly and cached. The gallery can be configured and customized in many ways
1677 and a custom copyright image can be added to all the images without
1678 modifying the original.
1680 =head1 CONFIGURATION
1682 In your httpd.conf you set the global options for the gallery. You can
1683 also override each of the options in .htaccess files in your gallery
1686 The options are set in the httpd.conf/.htaccess file using the syntax:
1687 B<PerlSetVar OptionName 'value'>
1689 Example: B<PerlSetVar GalleryCacheDir '/var/cache/www/'>
1693 =item B<GalleryAutoRotate>
1695 Some cameras, like the Canon G3, can detect the orientation of a
1696 the pictures you take and will save this information in the
1697 'Orientation' EXIF field. Apache::Gallery will then automatically
1700 This behavior is default but can be disabled by setting GalleryAutoRotate
1703 =item B<GalleryCacheDir>
1705 Directory where Apache::Gallery should create its cache with scaled
1706 pictures. The default is /var/cache/www/ . Here, a directory for each
1707 virtualhost or location will be created automatically. Make sure your
1708 webserver has write access to the CacheDir.
1710 =item B<GalleryTemplateDir>
1712 Full path to the directory where you placed the templates. This option
1713 can be used both in your global configuration and in .htaccess files,
1714 this way you can have different layouts in different parts of your
1717 No default value, this option is required.
1719 =item B<GalleryInfo>
1721 With this option you can define which EXIF information you would like
1722 to present from the image. The format is: '<MyName => KeyInEXIF,
1723 MyOtherName => OtherKeyInEXIF'
1725 Examples of keys: B<ShutterSpeedValue>, B<ApertureValue>, B<SubjectDistance>,
1728 You can view all the keys from the EXIF header using this perl-oneliner:
1730 perl C<-e> 'use Data::Dumper; use Image::Info qw(image_info); print Dumper(image_info(shift));' filename.jpg
1732 Default is: 'Picture Taken => DateTimeOriginal, Flash => Flash'
1734 =item B<GallerySizes>
1736 Defines which widths images can be scaled to. Images cannot be
1737 scaled to other widths than the ones you define with this option.
1739 The default is '640 800 1024 1600'
1741 =item B<GalleryThumbnailSize>
1743 Defines the width and height of the thumbnail images.
1745 Defaults to '100x75'
1747 =item B<GalleryThumbnailSizeLS>
1749 If set to '1', B<GalleryThumbnailSize> is the long and the short side of
1750 the thumbnail image instead of the width and height.
1754 =item B<GalleryCopyrightImage>
1756 Image you want to blend into your images in the lower right
1757 corner. This could be a transparent png saying "copyright
1762 =item B<GalleryWrapNavigation>
1764 Make the navigation in the picture view wrap around (So Next
1765 at the end displays the first picture, etc.)
1767 Set to 1 or 0, default is 0
1769 =item B<GalleryAllowOriginal>
1771 Allow the user to download the Original picture without
1772 resizing or putting the CopyrightImage on it.
1774 Set to 1 or 0, default is 0
1776 =item B<GalleryAllowThumbOnly>
1778 If true, B<GalleryAllowThumbOnly> allows fooimg.jpg?thumbonly urls
1779 to output the thumbnail of the image. This is useful when including
1780 images in a blog (or similar).
1782 Defaults to '0' (false).
1784 =item B<GallerySlideshowIntervals>
1786 With this option you can configure which intervals can be selected for
1787 a slideshow. The default is '3 5 10 15 30'
1789 =item B<GallerySortBy>
1791 Instead of the default filename ordering you can sort by any
1792 stat attribute. For example size, atime, mtime, ctime.
1794 =item B<GalleryDirSortBy>
1796 Set this variable to sort directories differently than other items,
1797 can be set to size, atime, mtime and ctime; setting any other value
1798 will revert to sorting by name.
1800 =item B<GalleryMemoize>
1802 Cache EXIF data using Memoize - this will make Apache::Gallery faster
1803 when many people access the same images, but it will also cache EXIF
1804 data until the current Apache child dies.
1806 =item B<GalleryUseFileDate>
1808 Set this option to 1 to make A::G show the files timestamp
1809 instead of the EXIF value for "Picture taken".
1811 =item B<GallerySelectionMode>
1813 Enable the selection mode. Select images with checkboxes and
1814 get a list of filenames.
1816 =item B<GalleryEXIFMode>
1818 You can choose how Apache::Gallery should display EXIF info
1821 The default setting is 'namevalue'. This setting will make
1822 Apache::Gallery print out the names and values of the EXIF values
1823 you configure with GalleryInfo. The information will be parsed into
1824 $INFO in pictureinfo.tpl.
1826 You can also set it to 'values' which will make A::G parse
1827 the configured values into the var $EXIFVALUES as 'value | value | value'
1829 If you set this option to 'variables' the items you configure in GalleryInfo
1830 will be available to your templates as $EXIF_<KEYNAME> (in all uppercase).
1831 That means that with the default setting "Picture Taken => DateTimeOriginal,
1832 Flash => Flash" you will have the variables $EXIF_DATETIMEORIGINAL and
1833 $EXIF_FLASH available to your templates. You can place them
1836 =item B<GalleryRootPath>
1838 Change the location of gallery root. The default is ""
1840 =item B<GalleryRootText>
1842 Change the name that appears as the root element in the menu. The
1845 =item B<GalleryMaxThumbnailsPerPage>
1847 This options controls how many thumbnails should be displayed in a
1848 page. It requires $BROWSELINKS to be in the index.tpl template file.
1850 =item B<GalleryImgFile>
1852 Pattern matching the files you want Apache::Gallery to view in the
1853 index as thumbnails.
1855 The default is '\.(jpe?g|png|tiff?|ppm)$'
1857 =item B<GalleryDocFile>
1859 Pattern matching the files you want Apache::Gallery to view in the index
1860 as normal files. All other filetypes will still be served by Apache::Gallery
1861 but are not visible in the index.
1863 The default is '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|mp4|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
1865 =item B<GalleryTTFDir>
1867 To use the GalleryCopyrightText feature you must set this option to the
1868 directory where your True Type fonts are stored. No default is set.
1872 PerlSetVar GalleryTTFDir '/usr/share/fonts/'
1874 =item B<GalleryTTFFile>
1876 To use the GalleryCopyrightText feature this option must be set to the
1877 name of the True Type font you wish to use. Example:
1879 PerlSetVar GalleryTTFFile 'verdanab.ttf'
1881 =item B<GalleryTTFSize>
1883 Configure the size of the CopyrightText that will be inserted as
1884 copyright notice in the corner of your pictures.
1888 PerlSetVar GalleryTTFSize '10'
1890 =item B<GalleryCopyrightText>
1892 The text that will be inserted as copyright notice.
1896 PerlSetVar GalleryCopyrightText '(c) Michael Legart'
1898 =item B<GalleryCopyrightColor>
1900 The text color of your copyright notice.
1905 PerlSetVar GalleryCopyrightColor '255,255,255,255'
1908 PerlSetVar GalleryCopyrightColor '0,0,0,255'
1911 PerlSetVar GalleryCopyrightColor '255,0,0,255'
1914 PerlSetVar GalleryCopyrightColor '0,255,0,255'
1917 PerlSetVar GalleryCopyrightColor '0,0,255,255'
1920 PerlSetVar GalleryCopyrightColor '255,127,0,127'
1922 =item B<GalleryCopyrightBackgroundColor>
1924 The background-color of a GalleryCopyrightText
1926 r,g,b,a - for examples, see GalleryCopyrightColor
1928 =item B<GalleryQuality>
1930 The quality (1-100) of scaled images
1932 This setting affects the quality of the scaled images.
1933 Set this to a low number to reduce the size of the scaled images.
1934 Remember to clear out your cache if you change this setting.
1935 Quality seems to default to 75, at least in the jpeg and png loader code in
1941 PerlSetVar GalleryQuality '50'
1943 =item B<GalleryUnderscoresToSpaces>
1945 Set this option to 1 to convert underscores to spaces in the listing
1946 of directory and file names, as well as in the alt attribute for HTML
1953 =item B<GalleryCommentExifKey>
1955 Set this option to e.g. ImageDescription to use this field as comments
1958 =item B<GalleryEnableMediaRss>
1960 Set this option to 1 to enable generation of a media RSS feed. This
1961 can be used e.g. together with the PicLens plugin from http://piclens.com
1969 =item B<Rotate images>
1971 Some cameras, like the Canon G3, detects the orientation of a picture
1972 and adds this info to the EXIF header. Apache::Gallery detects this
1973 and automatically rotates images with this info.
1975 If your camera does not support this, you can rotate the images
1976 manually, This can also be used to override the rotate information
1977 from a camera that supports that. You can also disable this behavior
1978 with the GalleryAutoRotate option.
1980 To use this functionality you have to create file with the name of the
1981 picture you want rotated appended with ".rotate". The file should include
1982 a number where these numbers are supported:
1984 "1", rotates clockwise by 90 degree
1985 "2", rotates clockwise by 180 degrees
1986 "3", rotates clockwise by 270 degrees
1988 So if we want to rotate "Picture1234.jpg" 90 degrees clockwise we would
1989 create a file in the same directory called "Picture1234.jpg.rotate" with
1990 the number 1 inside of it.
1992 =item B<Ignore directories/files>
1994 To ignore a directory or a file (of any kind, not only images) you
1995 create a <directory|file>.ignore file.
1999 To include comments for a directory you create a <directory>.comment
2000 file where the first line can contain "TITLE: New title" which
2001 will be the title of the page, and a comment on the following
2003 To include comments for each picture you create files called
2004 picture.jpg.comment where the first line can contain "TITLE: New
2005 title" which will be the title of the page, and a comment on the
2010 TITLE: This is the new title of the page
2011 And this is the comment.<br />
2012 And this is line two of the comment.
2014 The visible name of the folder is by default identical to the name of
2015 the folder, but can be changed by creating a file <directory>.folder
2016 with the visible name of the folder.
2018 It is also possible to set GalleryCommentExifKey to the name of an EXIF
2019 field containing the comment, e.g. ImageDescription. The EXIF comment is
2020 overridden by the .comment file if it exists.
2030 =item B<Apache with mod_perl>
2032 =item B<URI::Escape>
2034 =item B<Image::Info>
2036 =item B<Image::Size>
2038 =item B<Text::Template>
2040 =item B<Image::Imlib2>
2042 =item B<X11 libraries>
2046 Remember the -dev package when using rpm, deb or other package formats!
2052 Michael Legart <michael@legart.dk>
2054 =head1 COPYRIGHT AND LICENSE
2056 Copyright (C) 2001-2011 Michael Legart <michael@legart.dk>
2058 Templates designed by Thomas Kjaer <tk@lnx.dk>
2060 Apache::Gallery is free software and is released under the Artistic License.
2061 See B<http://www.perl.com/language/misc/Artistic.html> for details.
2063 The video icons are from the GNOME project. B<http://www.gnome.org/>
2067 Thanks to Thomas Kjaer for templates and design of B<http://apachegallery.dk>
2068 Thanks to Thomas Eibner and other for patches. (See the Changes file)
2072 L<perl>, L<mod_perl>, L<Image::Imlib2>, L<CGI::FastTemplate>,
2073 L<Image::Info>, and L<Image::Size>.