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();
544 # Create cache dir if not existing
545 my @tmp = split (/\//, $filename);
546 my $picfilename = pop @tmp;
547 my $path = (join "/", @tmp)."/";
548 my $cache_path = cache_dir($r, 1);
550 unless (-d $cache_path) {
551 unless (create_cache($r, $cache_path)) {
552 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
556 my ($orig_width, $orig_height, $type) = imgsize($filename);
558 my $imageinfo = get_imageinfo($r, $filename, $type, $orig_width, $orig_height);
560 my ($image_width, $width, $height, $original_size) = get_image_display_size($cgi, $r, $orig_width, $orig_height);
562 my $cached = get_scaled_picture_name($filename, $image_width, $height);
564 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
566 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
567 picture => "$tpl_dir/showpicture.tpl",
568 navpicture => "$tpl_dir/navpicture.tpl",
569 info => "$tpl_dir/info.tpl",
570 scale => "$tpl_dir/scale.tpl",
571 scaleactive => "$tpl_dir/scaleactive.tpl",
572 orig => "$tpl_dir/orig.tpl",
573 refresh => "$tpl_dir/refresh.tpl",
574 interval => "$tpl_dir/interval.tpl",
575 intervalactive => "$tpl_dir/intervalactive.tpl",
576 slideshowisoff => "$tpl_dir/slideshowisoff.tpl",
577 slideshowoff => "$tpl_dir/slideshowoff.tpl",
578 pictureinfo => "$tpl_dir/pictureinfo.tpl",
579 nopictureinfo => "$tpl_dir/nopictureinfo.tpl",
584 my $resolution = (($image_width > $orig_width) && ($height > $orig_height)) ?
585 "$orig_width x $orig_height" : "$image_width x $height";
587 $tpl_vars{TITLE} = "Viewing ".$r->uri()." at $image_width x $height";
588 $tpl_vars{META} = " ";
589 $tpl_vars{RESOLUTION} = $resolution;
590 $tpl_vars{MENU} = generate_menu($r);
591 $tpl_vars{SRC} = uri_escape(".cache/$cached", $escape_rule);
592 $tpl_vars{URI} = $r->uri();
594 my $exif_mode = $r->dir_config('GalleryEXIFMode');
595 unless ($exif_mode) {
596 $exif_mode = 'namevalue';
599 unless (opendir(DATADIR, $path)) {
600 show_error($r, 500, "Unable to access directory", "Unable to access directory $path");
601 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
603 my @pictures = grep { /$img_pattern/i && ! -e "$path/$_" . ".ignore" } readdir (DATADIR);
605 @pictures = gallerysort($r, @pictures);
607 $tpl_vars{TOTAL} = scalar @pictures;
612 for (my $i=0; $i <= $#pictures; $i++) {
613 if ($pictures[$i] eq $picfilename) {
615 $tpl_vars{NUMBER} = $i+1;
617 $prevpicture = $pictures[$i-1];
618 my $displayprev = ($i>0 ? 1 : 0);
620 if ($r->dir_config("GalleryWrapNavigation")) {
621 $prevpicture = $pictures[$i>0 ? $i-1 : $#pictures];
624 if ($prevpicture and $displayprev) {
625 my ($orig_width, $orig_height, $type) = imgsize($path.$prevpicture);
626 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
627 my $imageinfo = get_imageinfo($r, $path.$prevpicture, $type, $orig_width, $orig_height);
628 my $cached = get_scaled_picture_name($path.$prevpicture, $thumbnailwidth, $thumbnailheight);
630 $nav_vars{URL} = uri_escape($prevpicture, $escape_rule);
631 $nav_vars{FILENAME} = $prevpicture;
632 $nav_vars{WIDTH} = $width;
633 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
634 $nav_vars{DIRECTION} = "« <u>p</u>rev";
635 $nav_vars{ACCESSKEY} = "P";
636 $tpl_vars{BACK} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
639 $tpl_vars{BACK} = " ";
642 $nextpicture = $pictures[$i+1];
643 if ($r->dir_config("GalleryWrapNavigation")) {
644 $nextpicture = $pictures[$i == $#pictures ? 0 : $i+1];
648 my ($orig_width, $orig_height, $type) = imgsize($path.$nextpicture);
649 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
650 my $imageinfo = get_imageinfo($r, $path.$nextpicture, $type, $thumbnailwidth, $thumbnailheight);
651 my $cached = get_scaled_picture_name($path.$nextpicture, $thumbnailwidth, $thumbnailheight);
653 $nav_vars{URL} = uri_escape($nextpicture, $escape_rule);
654 $nav_vars{FILENAME} = $nextpicture;
655 $nav_vars{WIDTH} = $width;
656 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
657 $nav_vars{DIRECTION} = "<u>n</u>ext »";
658 $nav_vars{ACCESSKEY} = "N";
660 $tpl_vars{NEXT} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
661 $tpl_vars{NEXTURL} = uri_escape($nextpicture, $escape_rule);
664 $tpl_vars{NEXT} = " ";
665 $tpl_vars{NEXTURL} = '#';
670 my $foundcomment = 0;
671 if (-f $path . '/' . $picfilename . '.comment') {
672 my $comment_ref = get_comment($path . '/' . $picfilename . '.comment');
674 $tpl_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
675 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
676 } elsif ($r->dir_config('GalleryCommentExifKey')) {
677 my $comment = decode("utf8", $imageinfo->{$r->dir_config('GalleryCommentExifKey')});
678 $tpl_vars{COMMENT} = encode("iso-8859-1", $comment);
680 $tpl_vars{COMMENT} = '';
683 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
688 my ($human_key, $exif_key) = (split " => ")[0,1];
689 my $value = $imageinfo->{$human_key};
690 if (defined($value)) {
694 if ($exif_mode eq 'namevalue') {
696 $info_vars{KEY} = $human_key;
697 $info_vars{VALUE} = $value;
698 $tpl_vars{INFO} .= $templates{info}->fill_in(HASH => \%info_vars);
701 if ($exif_mode eq 'variables') {
702 $tpl_vars{"EXIF_".uc($exif_key)} = $value;
705 if ($exif_mode eq 'values') {
706 $exifvalues .= "| ".$value." ";
713 if ($exif_mode eq 'values') {
714 if (defined($exifvalues)) {
715 $tpl_vars{EXIFVALUES} = $exifvalues;
718 $tpl_vars{EXIFVALUES} = "";
722 if ($foundcomment and !$foundinfo) {
723 $tpl_vars{INFO} = "";
726 if ($exif_mode ne 'namevalue') {
727 $tpl_vars{INFO} = "";
730 if ($exif_mode eq 'namevalue' && $foundinfo or $foundcomment) {
732 $tpl_vars{PICTUREINFO} = $templates{pictureinfo}->fill_in(HASH => \%tpl_vars);
734 unless (defined($exifvalues)) {
735 $tpl_vars{EXIFVALUES} = "";
740 $tpl_vars{PICTUREINFO} = $templates{nopictureinfo}->fill_in(HASH => \%tpl_vars);
743 # Fill in sizes and determine if any are smaller than the
744 # actual image. If they are, $scaleable=1
746 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
747 foreach my $size (@sizes) {
748 if ($size<=$original_size) {
750 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
751 $sizes_vars{SIZE} = $size;
752 $sizes_vars{WIDTH} = $size;
753 if ($width == $size) {
754 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
757 $tpl_vars{SIZES} .= $templates{scale}->fill_in(HASH => \%sizes_vars);
763 unless ($scaleable) {
765 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
766 $sizes_vars{SIZE} = $original_size;
767 $sizes_vars{WIDTH} = $original_size;
768 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
771 $tpl_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
773 if ($r->dir_config('GalleryAllowOriginal')) {
774 $tpl_vars{SIZES} .= $templates{orig}->fill_in(HASH => \%tpl_vars);
777 my @slideshow_intervals = split (/ /, $r->dir_config('GallerySlideshowIntervals') ? $r->dir_config('GallerySlideshowIntervals') : '3 5 10 15 30');
778 foreach my $interval (@slideshow_intervals) {
781 $slideshow_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
782 $slideshow_vars{SECONDS} = $interval;
783 $slideshow_vars{WIDTH} = ($width > $height ? $width : $height);
785 if ($cgi->param('slideshow') && $cgi->param('slideshow') == $interval and $nextpicture) {
786 $tpl_vars{SLIDESHOW} .= $templates{intervalactive}->fill_in(HASH => \%slideshow_vars);
790 $tpl_vars{SLIDESHOW} .= $templates{interval}->fill_in(HASH => \%slideshow_vars);
795 if ($cgi->param('slideshow') and $nextpicture) {
797 $tpl_vars{SLIDESHOW} .= $templates{slideshowoff}->fill_in(HASH => \%tpl_vars);
799 unless ((grep $cgi->param('slideshow') == $_, @slideshow_intervals)) {
800 show_error($r, 200, "Invalid interval", "Invalid slideshow interval choosen");
801 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
804 $tpl_vars{URL} = uri_escape($nextpicture, $escape_rule);
805 $tpl_vars{WIDTH} = ($width > $height ? $width : $height);
806 $tpl_vars{INTERVAL} = $cgi->param('slideshow');
807 $tpl_vars{META} .= $templates{refresh}->fill_in(HASH => \%tpl_vars);
811 $tpl_vars{SLIDESHOW} .= $templates{slideshowisoff}->fill_in(HASH => \%tpl_vars);
814 $tpl_vars{MAIN} = $templates{picture}->fill_in(HASH => \%tpl_vars);
815 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
817 $r->content_type('text/html');
818 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
821 $r->send_http_header;
824 $r->print($tpl_vars{MAIN});
825 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
833 my $subr = $r->lookup_file($file);
834 $r->content_type($subr->content_type());
836 my $fileinfo = stat($file);
838 my $nonce = md5_base64($fileinfo->ino.$fileinfo->mtime);
839 if ($r->headers_in->{"If-None-Match"} eq $nonce) {
840 return Apache2::Const::HTTP_NOT_MODIFIED();
843 if ($r->headers_in->{"If-Modified-Since"} && str2time($r->headers_in->{"If-Modified-Since"}) < $fileinfo->mtime) {
844 return Apache2::Const::HTTP_NOT_MODIFIED();
847 $r->headers_out->{"Content-Length"} = $fileinfo->size;
848 $r->headers_out->{"Last-Modified-Date"} = time2str($fileinfo->mtime);
849 $r->headers_out->{"ETag"} = $nonce;
851 return Apache2::Const::OK();
856 return Apache::Constants::DECLINED();
862 my ($r, $strip_filename) = @_;
866 unless ($r->dir_config('GalleryCacheDir')) {
868 $cache_root = '/var/cache/www/';
869 if ($r->server->is_virtual) {
870 $cache_root = File::Spec->catdir($cache_root, $r->server->server_hostname);
872 $cache_root = File::Spec->catdir($cache_root, $r->location);
877 $cache_root = $r->dir_config('GalleryCacheDir');
881 # If the uri contains .cache we need to remove it
885 my (undef, $dirs, $filename) = File::Spec->splitpath($uri);
886 # We don't need a volume as this is a relative path
888 if ($strip_filename) {
889 return(File::Spec->canonpath(File::Spec->catdir($cache_root, $dirs)));
891 return(File::Spec->canonpath(File::Spec->catfile($cache_root, $dirs, $filename)));
899 unless (mkdirhier ($path)) {
900 show_error($r, 500, $!, "Unable to create cache directory in $path: $!");
913 unless (mkdir($dir, 0755)) {
915 $parent =~ s/\/[^\/]*$//;
924 sub get_scaled_picture_name {
926 my ($fullpath, $width, $height) = @_;
928 my (undef, undef, $type) = imgsize($fullpath);
930 my @dirs = split(/\//, $fullpath);
931 my $filename = pop(@dirs);
934 if (grep $type eq $_, qw(PPM TIF GIF)) {
935 $newfilename = $width."x".$height."-".$filename;
936 # needs to be configurable
937 $newfilename =~ s/\.(\w+)$/-$1\.jpg/;
939 $newfilename = $width."x".$height."-".$filename;
948 my ($r, $fullpath, $width, $height, $imageinfo) = @_;
950 my @dirs = split(/\//, $fullpath);
951 my $filename = pop(@dirs);
953 my ($orig_width, $orig_height, $type) = imgsize($fullpath);
955 my $cache = cache_dir($r, 1);
957 my $newfilename = get_scaled_picture_name($fullpath, $width, $height);
959 if (($width > $orig_width) && ($height > $orig_height)) {
960 # Run it through the resize code anyway to get watermarks
961 $width = $orig_width;
962 $height = $orig_height;
965 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
967 # Do we want to generate a new file in the cache?
970 if (-f $cache."/".$newfilename) {
973 # Check to see if the image has changed
974 my $filestat = stat($fullpath);
975 my $cachestat = stat($cache."/".$newfilename);
976 if ($filestat->mtime >= $cachestat->mtime) {
980 # Check to see if the .rotate file has been added or changed
981 if (-f $fullpath . ".rotate") {
982 my $rotatestat = stat($fullpath . ".rotate");
983 if ($rotatestat->mtime > $cachestat->mtime) {
987 # Check to see if the copyrightimage has been added or changed
988 if ($r->dir_config('GalleryCopyrightImage') && -f $r->dir_config('GalleryCopyrightImage')) {
989 unless ($width == $thumbnailwidth or $width == $thumbnailheight) {
990 my $copyrightstat = stat($r->dir_config('GalleryCopyrightImage'));
991 if ($copyrightstat->mtime > $cachestat->mtime) {
1001 my $newpath = $cache."/".$newfilename;
1002 my $rotate = readfile_getnum($r, $imageinfo, $fullpath . ".rotate");
1003 my $quality = $r->dir_config('GalleryQuality');
1005 if ($width == $thumbnailwidth or $width == $thumbnailheight) {
1007 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, '', '', '', '', '', '');
1011 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate,
1012 ($r->dir_config('GalleryCopyrightImage') ? $r->dir_config('GalleryCopyrightImage') : ''),
1013 ($r->dir_config('GalleryTTFDir') ? $r->dir_config('GalleryTTFDir') : ''),
1014 ($r->dir_config('GalleryCopyrightText') ? $r->dir_config('GalleryCopyrightText') : ''),
1015 ($r->dir_config('GalleryCopyrightColor') ? $r->dir_config('GalleryCopyrightColor') : ''),
1016 ($r->dir_config('GalleryTTFFile') ? $r->dir_config('GalleryTTFFile') : ''),
1017 ($r->dir_config('GalleryTTFSize') ? $r->dir_config('GalleryTTFSize') : ''),
1018 ($r->dir_config('GalleryCopyrightBackgroundColor') ? $r->dir_config('GalleryCopyrightBackgroundColor') : ''),
1024 return $newfilename;
1028 sub get_thumbnailsize {
1029 my ($r, $orig_width, $orig_height) = @_;
1031 my $gallerythumbnailsize=$r->dir_config('GalleryThumbnailSize');
1033 if (defined($gallerythumbnailsize)) {
1034 warn("Invalid setting for GalleryThumbnailSize") unless
1035 $gallerythumbnailsize =~ /^\s*\d+\s*x\s*\d+\s*$/i;
1038 my ($thumbnailwidth, $thumbnailheight) = split(/x/i, ($gallerythumbnailsize) ? $gallerythumbnailsize : "100x75");
1040 my $width = $thumbnailwidth;
1041 my $height = $thumbnailheight;
1043 # If the image is rotated, flip everything around.
1044 if (defined $r->dir_config('GalleryThumbnailSizeLS')
1045 and $r->dir_config('GalleryThumbnailSizeLS') eq '1'
1046 and $orig_width < $orig_height) {
1048 $width = $thumbnailheight;
1049 $height = $thumbnailwidth;
1052 my $scale = ($orig_width ? $width/$orig_width : 1);
1055 if ($orig_height * $scale > $thumbnailheight) {
1056 $scale = $height/$orig_height;
1057 $width = $orig_width * $scale;
1061 $height = $orig_height * $scale;
1063 $height = floor($height);
1064 $width = floor($width);
1066 return ($width, $height);
1069 sub get_image_display_size {
1070 my ($cgi, $r, $orig_width, $orig_height) = @_;
1072 my $width = $orig_width;
1074 my $original_size=$orig_height;
1075 if ($orig_width>$orig_height) {
1076 $original_size=$orig_width;
1079 # Check if the selected width is allowed
1080 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
1082 my %cookies = fetch CGI::Cookie;
1084 if ($cgi->param('width')) {
1085 unless ((grep $cgi->param('width') == $_, @sizes) or ($cgi->param('width') == $original_size)) {
1086 show_error($r, 200, "Invalid width", "The specified width is invalid");
1087 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
1090 $width = $cgi->param('width');
1091 my $cookie = new CGI::Cookie(-name => 'GallerySize', -value => $width, -expires => '+6M');
1092 $r->headers_out->{'Set-Cookie'} = $cookie;
1094 } elsif ($cookies{'GallerySize'} && (grep $cookies{'GallerySize'}->value == $_, @sizes)) {
1096 $width = $cookies{'GallerySize'}->value;
1104 if ($orig_width<$orig_height) {
1105 $scale = ($orig_height ? $width/$orig_height: 1);
1106 $image_width=$width*$orig_width/$orig_height;
1109 $scale = ($orig_width ? $width/$orig_width : 1);
1110 $image_width = $width;
1113 my $height = $orig_height * $scale;
1115 $image_width = floor($image_width);
1116 $width = floor($width);
1117 $height = floor($height);
1119 return ($image_width, $width, $height, $original_size);
1123 my ($r, $file, $type, $width, $height) = @_;
1125 if ($type eq 'Data stream is not a known image file format') {
1126 # should never be reached, this is supposed to be handled outside of here
1127 log_error("Something was fishy with the type of the file $file\n");
1130 # Some files, like TIFF, PNG, GIF do not have EXIF info
1131 # embedded but use .thm files instead.
1132 $imageinfo = get_imageinfo_from_thm_file($file, $width, $height);
1134 # If there is no .thm file and our file is a JPEG file we try to extract the EXIf
1135 # info using Image::Info
1136 unless (defined($imageinfo) && (grep $type eq $_, qw(JPG))) {
1137 # Only for files that natively keep the EXIF info in the same file
1138 $imageinfo = image_info($file);
1142 unless (defined($imageinfo->{width}) and defined($imageinfo->{height})) {
1143 $imageinfo->{width} = $width;
1144 $imageinfo->{height} = $height;
1147 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
1150 my ($human_key, $exif_key) = (split " => ")[0,1];
1151 if (defined($exif_key) && defined($imageinfo->{$exif_key})) {
1153 if (ref($imageinfo->{$exif_key}) eq 'Image::TIFF::Rational') {
1154 $value = $imageinfo->{$exif_key}->as_string;
1156 elsif (ref($imageinfo->{$exif_key}) eq 'ARRAY') {
1157 foreach my $element (@{$imageinfo->{$exif_key}}) {
1158 if (ref($element) eq 'ARRAY') {
1159 foreach (@{$element}) {
1163 elsif (ref($element) eq 'HASH') {
1164 $value .= "<br />{ ";
1165 foreach (sort keys %{$element}) {
1166 $value .= "$_ = " . $element->{$_} . ' ';
1177 my $exif_value = $imageinfo->{$exif_key};
1178 if ($human_key eq 'Flash' && $exif_value =~ m/\d/) {
1183 "16" => "No (Compulsory) Should be External Flash",
1184 "17" => "Yes (External)",
1186 "25" => "Yes (Auto)",
1187 "73" => "Yes (Compulsory, Red Eye Reducing)",
1188 "89" => "Yes (Auto, Red Eye Reducing)"
1190 $exif_value = defined $flashmodes{$exif_value} ? $flashmodes{$exif_value} : 'unknown flash mode';
1192 $value = $exif_value;
1194 if ($exif_key eq 'MeteringMode') {
1195 my $exif_value = $imageinfo->{$exif_key};
1196 if ($exif_value =~ /^\d+$/) {
1197 my %meteringmodes = (
1200 '2' => 'CenterWeightedAverage',
1207 $exif_value = defined $meteringmodes{$exif_value} ? $meteringmodes{$exif_value} : 'unknown metering mode';
1209 $value = $exif_value;
1212 if ($exif_key eq 'LightSource') {
1213 my $exif_value = $imageinfo->{$exif_key};
1214 if ($exif_value =~ /^\d+$/) {
1215 my %lightsources = (
1218 '2' => 'Fluorescent',
1219 '3' => 'Tungsten (incandescent light)',
1221 '9' => 'Fine weather',
1222 '10' => 'Cloudy weather',
1224 '12' => 'Daylight fluorescent',
1225 '13' => 'Day white fluorescent',
1226 '14' => 'Cool white fluorescent',
1227 '15' => 'White fluorescent',
1228 '17' => 'Standard light A',
1229 '18' => 'Standard light B',
1230 '19' => 'Standard light C',
1235 '24' => 'ISO studio tungsten',
1236 '255' => 'other light source'
1238 $exif_value = defined $lightsources{$exif_value} ? $lightsources{$exif_value} : 'unknown light source';
1240 $value = $exif_value;
1242 if ($exif_key eq 'FocalLength') {
1243 if ($value =~ /^(\d+)\/(\d+)$/) {
1244 $value = eval { $1 / $2 };
1248 $value = int($value + 0.5) . "mm";
1253 if ($exif_key eq 'ShutterSpeedValue') {
1254 if ($value =~ /^((?:\-)?\d+)\/(\d+)$/) {
1255 $value = eval { $1 / $2 };
1260 $value = 1/(exp($value*log(2)));
1262 $value = "1/" . (int((1/$value)));
1264 $value = int($value*10)/10;
1270 $value = $value . " sec";
1275 if ($exif_key eq 'ApertureValue') {
1276 if ($value =~ /^(\d+)\/(\d+)$/) {
1277 $value = eval { $1 / $2 };
1281 # poor man's rounding
1282 $value = int(exp($value*log(2)*0.5)*10)/10;
1283 $value = "f" . $value;
1287 if ($exif_key eq 'FNumber') {
1288 if ($value =~ /^(\d+)\/(\d+)$/) {
1289 $value = eval { $1 / $2 };
1293 $value = int($value*10+0.5)/10;
1294 $value = "f" . $value;
1298 $imageinfo->{$human_key} = $value;
1302 if ($r->dir_config('GalleryUseFileDate') &&
1303 ($r->dir_config('GalleryUseFileDate') eq '1'
1304 || !$imageinfo->{"Picture Taken"} )) {
1306 my $st = stat($file);
1307 $imageinfo->{"DateTimeOriginal"} = $imageinfo->{"Picture Taken"} = scalar localtime($st->mtime) if $st;
1313 sub get_imageinfo_from_thm_file {
1315 my ($file, $width, $height) = @_;
1317 my $imageinfo = undef;
1318 # Windows based file extensions are often .THM, so check
1319 # for both .thm and .THM
1320 my $unix_file = $file;
1321 my $windows_file = $file;
1322 $unix_file =~ s/\.(\w+)$/.thm/;
1323 $windows_file =~ s/\.(\w+)$/.THM/;
1325 if (-e $unix_file && -f $unix_file && -r $unix_file) {
1326 $imageinfo = image_info($unix_file);
1327 $imageinfo->{width} = $width;
1328 $imageinfo->{height} = $height;
1330 elsif (-e $windows_file && -f $windows_file && -r $windows_file) {
1331 $imageinfo = image_info($windows_file);
1332 $imageinfo->{width} = $width;
1333 $imageinfo->{height} = $height;
1340 sub readfile_getnum {
1341 my ($r, $imageinfo, $filename) = @_;
1345 print STDERR "orientation: ".$imageinfo->{Orientation}."\n";
1346 # Check to see if the image contains the Orientation EXIF key,
1347 # but allow user to override using rotate
1348 if (!defined($r->dir_config("GalleryAutoRotate"))
1349 || $r->dir_config("GalleryAutoRotate") eq "1") {
1350 if (defined($imageinfo->{Orientation})) {
1351 print STDERR $imageinfo->{Orientation}."\n";
1352 if ($imageinfo->{Orientation} eq 'right_top') {
1355 elsif ($imageinfo->{Orientation} eq 'left_bot') {
1361 if (open(FH, "<$filename")) {
1365 unless ($temp =~ /^\d$/) {
1368 unless ($temp == 1 || $temp == 2 || $temp == 3) {
1377 sub get_filecontent {
1379 open(FH, $file) or return undef;
1390 my $filename = shift;
1391 my $comment_ref = {};
1392 $comment_ref->{TITLE} = undef;
1393 $comment_ref->{COMMENT} = '';
1395 open(FH, $filename) or return $comment_ref;
1397 if ($title =~ m/^TITLE: (.*)$/) {
1398 chomp($comment_ref->{TITLE} = $1);
1401 $comment_ref->{COMMENT} = $title;
1406 $comment_ref->{COMMENT} .= $_;
1410 return $comment_ref;
1415 my ($r, $statuscode, $errortitle, $error) = @_;
1417 my $tpl = $r->dir_config('GalleryTemplateDir');
1419 my %templates = create_templates({layout => "$tpl/layout.tpl",
1420 error => "$tpl/error.tpl",
1424 $tpl_vars{TITLE} = "Error! $errortitle";
1425 $tpl_vars{META} = "";
1426 $tpl_vars{ERRORTITLE} = "Error! $errortitle";
1427 $tpl_vars{ERROR} = $error;
1429 $tpl_vars{MAIN} = $templates{error}->fill_in(HASH => \%tpl_vars);
1431 $tpl_vars{PAGE} = $templates{layout}->fill_in(HASH => \%tpl_vars);
1433 $r->status($statuscode);
1434 $r->content_type('text/html');
1436 $r->print($tpl_vars{PAGE});
1444 my $root_text = (defined($r->dir_config('GalleryRootText')) ? $r->dir_config('GalleryRootText') : "root:" );
1445 my $root_path = (defined($r->dir_config('GalleryRootPath')) ? $r->dir_config('GalleryRootPath') : "" );
1447 my $subr = $r->lookup_uri($r->uri);
1448 my $filename = $subr->filename;
1450 my @links = split (/\//, $r->uri);
1452 $uri =~ s/^$root_path//g;
1454 @links = split (/\//, $uri);
1456 # Get the full path of the base directory
1459 my @direlem = split (/\//, $filename);
1460 for my $i ( 0 .. ( scalar(@direlem) - scalar(@links) ) ) {
1461 $dirname .= shift(@direlem) . '/';
1468 $picturename = pop(@links);
1471 if ($r->uri eq $root_path) {
1472 return qq{ <a href="$root_path">$root_text</a> };
1476 my $menuurl = $root_path;
1477 foreach my $link (@links) {
1479 $menuurl .= $link."/";
1480 my $linktext = $link;
1481 unless (length($link)) {
1482 $linktext = "$root_text ";
1486 $dirname = File::Spec->catdir($dirname, $link);
1488 if (-e $dirname . ".folder") {
1489 $linktext = get_filecontent($dirname . ".folder");
1493 if ("$root_path$uri" eq $menuurl) {
1494 $menu .= "$linktext / ";
1497 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule)."\">$linktext</a> / ";
1503 $menu .= $picturename;
1507 if ($r->dir_config('GallerySelectionMode') && $r->dir_config('GallerySelectionMode') eq '1') {
1508 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule);
1509 $menu .= "?select=1\">[select]</a> ";
1517 my ($r, $infile, $outfile, $x, $y, $rotate, $copyrightfile, $GalleryTTFDir, $GalleryCopyrightText, $text_color, $GalleryTTFFile, $GalleryTTFSize, $GalleryCopyrightBackgroundColor, $quality) = @_;
1520 my $image = Image::Imlib2->load($infile) or warn("Unable to open file $infile, $!");
1523 $image=$image->create_scaled_image($x, $y) or warn("Unable to scale image $infile. Are you running out of memory?");
1527 $image->image_orientate($rotate);
1530 # blend copyright image onto image
1531 if ($copyrightfile ne '') {
1532 if (-f $copyrightfile and (my $logo=Image::Imlib2->load($copyrightfile))) {
1533 my $x = $image->get_width();
1534 my $y = $image->get_height();
1535 my $logox = $logo->get_width();
1536 my $logoy = $logo->get_height();
1537 $image->blend($logo, 0, 0, 0, $logox, $logoy, $x-$logox, $y-$logoy, $logox, $logoy);
1540 log_error("GalleryCopyrightImage $copyrightfile was not found");
1544 if ($GalleryTTFDir && $GalleryCopyrightText && $GalleryTTFFile && $text_color) {
1545 if (!-d $GalleryTTFDir) {
1547 log_error("GalleryTTFDir $GalleryTTFDir is not a dir\n");
1549 } elsif ($GalleryCopyrightText eq '') {
1551 log_error("GalleryCopyrightText is empty. No text inserted to picture\n");
1553 } elsif (!-e "$GalleryTTFDir/$GalleryTTFFile") {
1555 log_error("GalleryTTFFile $GalleryTTFFile was not found\n");
1559 $GalleryTTFFile =~ s/\.TTF$//i;
1560 $image->add_font_path("$GalleryTTFDir");
1562 $image->load_font("$GalleryTTFFile/$GalleryTTFSize");
1563 my($text_x, $text_y) = $image->get_text_size("$GalleryCopyrightText");
1564 my $x = $image->get_width();
1565 my $y = $image->get_height();
1569 if (($text_x < $x - $offset) && ($text_y < $y - $offset)) {
1570 if ($GalleryCopyrightBackgroundColor =~ /^\d+,\d+,\d+,\d+$/) {
1571 my ($br_val, $bg_val, $bb_val, $ba_val) = split (/,/, $GalleryCopyrightBackgroundColor);
1572 $image->set_colour($br_val, $bg_val, $bb_val, $ba_val);
1573 $image->fill_rectangle ($x-$text_x-$offset, $y-$text_y-$offset, $text_x, $text_y);
1575 my ($r_val, $g_val, $b_val, $a_val) = split (/,/, $text_color);
1576 $image->set_colour($r_val, $g_val, $b_val, $a_val);
1577 $image->draw_text($x-$text_x-$offset, $y-$text_y-$offset, "$GalleryCopyrightText");
1579 log_error("Text is to big for the picture.\n");
1584 if ($quality && $quality =~ m/^\d+$/) {
1585 $image->set_quality($quality);
1588 $image->save($outfile);
1595 my $sortby = $r->dir_config('GallerySortBy');
1596 my $filename=$r->lookup_uri($r->uri)->filename;
1597 $filename=(File::Spec->splitpath($filename))[1] if (-f $filename);
1598 if ($sortby && $sortby =~ m/^(size|atime|mtime|ctime)$/) {
1599 @files = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$sortby()." $_", @files));
1601 @files = sort @files;
1606 # Create Text::Template objects used by Apache::Gallery. Takes a
1607 # hashref of template_name, template_filename pairs, and returns a
1608 # list of template_name, texttemplate_object pairs.
1609 sub create_templates {
1610 my $templates = shift;
1612 # This routine is called whenever a template has an error. Prints
1613 # the error to STDERR and sticks the error in the output
1616 # Pull out the name and filename from the arg option [see
1617 # Text::Template for details]
1618 @args{qw(name file)} = @{$args{arg}};
1619 print STDERR qq(Template $args{name} ("$args{file}") is broken: $args{error});
1620 # Don't include the file name in the output, as the user can see this.
1621 return qq(<!-- Template $args{name} is broken: $args{error} -->);
1626 my %texttemplate_objects;
1628 for my $template_name (keys %$templates) {
1629 my $tt_obj = Text::Template->new(TYPE => 'FILE',
1630 SOURCE => $$templates{$template_name},
1631 BROKEN => \&tt_broken,
1632 BROKEN_ARG => [$template_name, $$templates{$template_name}],
1634 or die "Unable to create new Text::Template object for $template_name: $Text::Template::ERROR";
1635 $texttemplate_objects{$template_name} = $tt_obj;
1637 return %texttemplate_objects;
1642 Apache2::RequestUtil->request->log_error(shift());
1644 Apache->request->log_error(shift());
1652 Apache::Gallery - mod_perl handler to create an image gallery
1656 See the INSTALL file in the distribution for installation instructions.
1660 Apache::Gallery creates an thumbnail index of each directory and allows
1661 viewing pictures in different resolutions. Pictures are resized on the
1662 fly and cached. The gallery can be configured and customized in many ways
1663 and a custom copyright image can be added to all the images without
1664 modifying the original.
1666 =head1 CONFIGURATION
1668 In your httpd.conf you set the global options for the gallery. You can
1669 also override each of the options in .htaccess files in your gallery
1672 The options are set in the httpd.conf/.htaccess file using the syntax:
1673 B<PerlSetVar OptionName 'value'>
1675 Example: B<PerlSetVar GalleryCacheDir '/var/cache/www/'>
1679 =item B<GalleryAutoRotate>
1681 Some cameras, like the Canon G3, can detect the orientation of a
1682 the pictures you take and will save this information in the
1683 'Orientation' EXIF field. Apache::Gallery will then automatically
1686 This behavior is default but can be disabled by setting GalleryAutoRotate
1689 =item B<GalleryCacheDir>
1691 Directory where Apache::Gallery should create its cache with scaled
1692 pictures. The default is /var/cache/www/ . Here, a directory for each
1693 virtualhost or location will be created automatically. Make sure your
1694 webserver has write access to the CacheDir.
1696 =item B<GalleryTemplateDir>
1698 Full path to the directory where you placed the templates. This option
1699 can be used both in your global configuration and in .htaccess files,
1700 this way you can have different layouts in different parts of your
1703 No default value, this option is required.
1705 =item B<GalleryInfo>
1707 With this option you can define which EXIF information you would like
1708 to present from the image. The format is: '<MyName => KeyInEXIF,
1709 MyOtherName => OtherKeyInEXIF'
1711 Examples of keys: B<ShutterSpeedValue>, B<ApertureValue>, B<SubjectDistance>,
1714 You can view all the keys from the EXIF header using this perl-oneliner:
1716 perl C<-e> 'use Data::Dumper; use Image::Info qw(image_info); print Dumper(image_info(shift));' filename.jpg
1718 Default is: 'Picture Taken => DateTimeOriginal, Flash => Flash'
1720 =item B<GallerySizes>
1722 Defines which widths images can be scaled to. Images cannot be
1723 scaled to other widths than the ones you define with this option.
1725 The default is '640 800 1024 1600'
1727 =item B<GalleryThumbnailSize>
1729 Defines the width and height of the thumbnail images.
1731 Defaults to '100x75'
1733 =item B<GalleryThumbnailSizeLS>
1735 If set to '1', B<GalleryThumbnailSize> is the long and the short side of
1736 the thumbnail image instead of the width and height.
1740 =item B<GalleryCopyrightImage>
1742 Image you want to blend into your images in the lower right
1743 corner. This could be a transparent png saying "copyright
1748 =item B<GalleryWrapNavigation>
1750 Make the navigation in the picture view wrap around (So Next
1751 at the end displays the first picture, etc.)
1753 Set to 1 or 0, default is 0
1755 =item B<GalleryAllowOriginal>
1757 Allow the user to download the Original picture without
1758 resizing or putting the CopyrightImage on it.
1760 Set to 1 or 0, default is 0
1762 =item B<GallerySlideshowIntervals>
1764 With this option you can configure which intervals can be selected for
1765 a slideshow. The default is '3 5 10 15 30'
1767 =item B<GallerySortBy>
1769 Instead of the default filename ordering you can sort by any
1770 stat attribute. For example size, atime, mtime, ctime.
1772 =item B<GalleryDirSortBy>
1774 Set this variable to sort directories differently than other items,
1775 can be set to size, atime, mtime and ctime; setting any other value
1776 will revert to sorting by name.
1778 =item B<GalleryMemoize>
1780 Cache EXIF data using Memoize - this will make Apache::Gallery faster
1781 when many people access the same images, but it will also cache EXIF
1782 data until the current Apache child dies.
1784 =item B<GalleryUseFileDate>
1786 Set this option to 1 to make A::G show the files timestamp
1787 instead of the EXIF value for "Picture taken".
1789 =item B<GallerySelectionMode>
1791 Enable the selection mode. Select images with checkboxes and
1792 get a list of filenames.
1794 =item B<GalleryEXIFMode>
1796 You can choose how Apache::Gallery should display EXIF info
1799 The default setting is 'namevalue'. This setting will make
1800 Apache::Gallery print out the names and values of the EXIF values
1801 you configure with GalleryInfo. The information will be parsed into
1802 $INFO in pictureinfo.tpl.
1804 You can also set it to 'values' which will make A::G parse
1805 the configured values into the var $EXIFVALUES as 'value | value | value'
1807 If you set this option to 'variables' the items you configure in GalleryInfo
1808 will be available to your templates as $EXIF_<KEYNAME> (in all uppercase).
1809 That means that with the default setting "Picture Taken => DateTimeOriginal,
1810 Flash => Flash" you will have the variables $EXIF_DATETIMEORIGINAL and
1811 $EXIF_FLASH available to your templates. You can place them
1814 =item B<GalleryRootPath>
1816 Change the location of gallery root. The default is ""
1818 =item B<GalleryRootText>
1820 Change the name that appears as the root element in the menu. The
1823 =item B<GalleryMaxThumbnailsPerPage>
1825 This options controls how many thumbnails should be displayed in a
1826 page. It requires $BROWSELINKS to be in the index.tpl template file.
1828 =item B<GalleryImgFile>
1830 Pattern matching the files you want Apache::Gallery to view in the
1831 index as thumbnails.
1833 The default is '\.(jpe?g|png|tiff?|ppm)$'
1835 =item B<GalleryDocFile>
1837 Pattern matching the files you want Apache::Gallery to view in the index
1838 as normal files. All other filetypes will still be served by Apache::Gallery
1839 but are not visible in the index.
1841 The default is '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|mp4|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
1843 =item B<GalleryTTFDir>
1845 To use the GalleryCopyrightText feature you must set this option to the
1846 directory where your True Type fonts are stored. No default is set.
1850 PerlSetVar GalleryTTFDir '/usr/share/fonts/'
1852 =item B<GalleryTTFFile>
1854 To use the GalleryCopyrightText feature this option must be set to the
1855 name of the True Type font you wish to use. Example:
1857 PerlSetVar GalleryTTFFile 'verdanab.ttf'
1859 =item B<GalleryTTFSize>
1861 Configure the size of the CopyrightText that will be inserted as
1862 copyright notice in the corner of your pictures.
1866 PerlSetVar GalleryTTFSize '10'
1868 =item B<GalleryCopyrightText>
1870 The text that will be inserted as copyright notice.
1874 PerlSetVar GalleryCopyrightText '(c) Michael Legart'
1876 =item B<GalleryCopyrightColor>
1878 The text color of your copyright notice.
1883 PerlSetVar GalleryCopyrightColor '255,255,255,255'
1886 PerlSetVar GalleryCopyrightColor '0,0,0,255'
1889 PerlSetVar GalleryCopyrightColor '255,0,0,255'
1892 PerlSetVar GalleryCopyrightColor '0,255,0,255'
1895 PerlSetVar GalleryCopyrightColor '0,0,255,255'
1898 PerlSetVar GalleryCopyrightColor '255,127,0,127'
1900 =item B<GalleryCopyrightBackgroundColor>
1902 The background-color of a GalleryCopyrightText
1904 r,g,b,a - for examples, see GalleryCopyrightColor
1906 =item B<GalleryQuality>
1908 The quality (1-100) of scaled images
1910 This setting affects the quality of the scaled images.
1911 Set this to a low number to reduce the size of the scaled images.
1912 Remember to clear out your cache if you change this setting.
1913 Quality seems to default to 75, at least in the jpeg and png loader code in
1919 PerlSetVar GalleryQuality '50'
1921 =item B<GalleryUnderscoresToSpaces>
1923 Set this option to 1 to convert underscores to spaces in the listing
1924 of directory and file names, as well as in the alt attribute for HTML
1931 =item B<GalleryCommentExifKey>
1933 Set this option to e.g. ImageDescription to use this field as comments
1936 =item B<GalleryEnableMediaRss>
1938 Set this option to 1 to enable generation of a media RSS feed. This
1939 can be used e.g. together with the PicLens plugin from http://piclens.com
1947 =item B<Rotate images>
1949 Some cameras, like the Canon G3, detects the orientation of a picture
1950 and adds this info to the EXIF header. Apache::Gallery detects this
1951 and automatically rotates images with this info.
1953 If your camera does not support this, you can rotate the images
1954 manually, This can also be used to override the rotate information
1955 from a camera that supports that. You can also disable this behavior
1956 with the GalleryAutoRotate option.
1958 To use this functionality you have to create file with the name of the
1959 picture you want rotated appended with ".rotate". The file should include
1960 a number where these numbers are supported:
1962 "1", rotates clockwise by 90 degree
1963 "2", rotates clockwise by 180 degrees
1964 "3", rotates clockwise by 270 degrees
1966 So if we want to rotate "Picture1234.jpg" 90 degrees clockwise we would
1967 create a file in the same directory called "Picture1234.jpg.rotate" with
1968 the number 1 inside of it.
1970 =item B<Ignore directories/files>
1972 To ignore a directory or a file (of any kind, not only images) you
1973 create a <directory|file>.ignore file.
1977 To include comments for a directory you create a <directory>.comment
1978 file where the first line can contain "TITLE: New title" which
1979 will be the title of the page, and a comment on the following
1981 To include comments for each picture you create files called
1982 picture.jpg.comment where the first line can contain "TITLE: New
1983 title" which will be the title of the page, and a comment on the
1988 TITLE: This is the new title of the page
1989 And this is the comment.<br />
1990 And this is line two of the comment.
1992 The visible name of the folder is by default identical to the name of
1993 the folder, but can be changed by creating a file <directory>.folder
1994 with the visible name of the folder.
1996 It is also possible to set GalleryCommentExifKey to the name of an EXIF
1997 field containing the comment, e.g. ImageDescription. The EXIF comment is
1998 overridden by the .comment file if it exists.
2008 =item B<Apache with mod_perl>
2010 =item B<URI::Escape>
2012 =item B<Image::Info>
2014 =item B<Image::Size>
2016 =item B<Text::Template>
2018 =item B<Image::Imlib2>
2020 =item B<X11 libraries>
2024 Remember the -dev package when using rpm, deb or other package formats!
2030 Michael Legart <michael@legart.dk>
2032 =head1 COPYRIGHT AND LICENSE
2034 Copyright (C) 2001-2011 Michael Legart <michael@legart.dk>
2036 Templates designed by Thomas Kjaer <tk@lnx.dk>
2038 Apache::Gallery is free software and is released under the Artistic License.
2039 See B<http://www.perl.com/language/misc/Artistic.html> for details.
2041 The video icons are from the GNOME project. B<http://www.gnome.org/>
2045 Thanks to Thomas Kjaer for templates and design of B<http://apachegallery.dk>
2046 Thanks to Thomas Eibner and other for patches. (See the Changes file)
2050 L<perl>, L<mod_perl>, L<Image::Imlib2>, L<CGI::FastTemplate>,
2051 L<Image::Info>, and L<Image::Size>.