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');
170 unless ($img_pattern) {
171 $img_pattern = '\.(jpe?g|png|tiff?|ppm)$'
174 # Let Apache serve files we don't know how to handle anyway
175 if (-f $filename && $filename !~ m/$img_pattern/i) {
176 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
181 unless (-d cache_dir($r, 0)) {
182 unless (create_cache($r, cache_dir($r, 0))) {
183 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
187 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
189 # Instead of reading the templates every single time
190 # we need them, create a hash of template names and
191 # the associated Text::Template objects.
192 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
193 index => "$tpl_dir/index.tpl",
194 directory => "$tpl_dir/directory.tpl",
195 picture => "$tpl_dir/picture.tpl",
196 file => "$tpl_dir/file.tpl",
197 comment => "$tpl_dir/dircomment.tpl",
198 nocomment => "$tpl_dir/nodircomment.tpl",
199 rss => "$tpl_dir/rss.tpl",
200 rss_item => "$tpl_dir/rss_item.tpl",
201 navdirectory => "$tpl_dir/navdirectory.tpl",
209 $tpl_vars{TITLE} = "Index of: $uri";
211 if ($media_rss_enabled) {
212 # Put the RSS feed on all directory listings
213 $tpl_vars{META} = '<link rel="alternate" href="?rss=1" type="application/rss+xml" title="" id="gallery" />';
216 unless (opendir (DIR, $filename)) {
217 show_error ($r, 500, $!, "Unable to access directory $filename: $!");
218 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
221 $tpl_vars{MENU} = generate_menu($r);
223 $tpl_vars{FORM_BEGIN} = $select_mode?'<form method="post">':'';
224 $tpl_vars{FORM_END} = $select_mode?'<input type="submit" name="Get list" value="Get list"></form>':'';
226 # Read, sort, and filter files
227 my @files = grep { !/^\./ && -f "$filename/$_" } readdir (DIR);
229 @files=gallerysort($r, @files);
231 my @downloadable_files;
234 # Remove unwanted files from list
236 foreach my $picture (@files) {
238 my $file = $topdir."/".$picture;
240 if ($file =~ /$img_pattern/i) {
241 push (@new_files, $picture);
244 if ($file =~ /$doc_pattern/i) {
245 push (@downloadable_files, $picture);
252 # Read and sort directories
254 my @directories = grep { !/^\./ && -d "$filename/$_" } readdir (DIR);
256 if (defined($r->dir_config('GalleryDirSortBy'))) {
257 $dirsortby=$r->dir_config('GalleryDirSortBy');
259 $dirsortby=$r->dir_config('GallerySortBy');
261 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
262 @directories = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$dirsortby()." $_", @directories));
264 @directories = sort @directories;
270 # Combine directories and files to one listing
272 push (@listing, @directories);
273 push (@listing, @files);
274 push (@listing, @downloadable_files);
280 my $file_counter = 0;
282 my $max_files = $r->dir_config('GalleryMaxThumbnailsPerPage');
284 if (defined($cgi->param('start'))) {
285 $start_at = $cgi->param('start');
291 my $browse_links = "";
292 if (defined($max_files)) {
294 for (my $i=1; $i<=scalar(@listing); $i++) {
298 my $to = $i+$max_files-1;
299 if ($to > scalar(@listing)) {
300 $to = scalar(@listing);
303 if ($start_at < $from || $start_at > $to) {
304 $browse_links .= "<a href=\"?start=$from\">$from - ".$to."</a> ";
307 $browse_links .= "$from - $to ";
316 $tpl_vars{BROWSELINKS} = $browse_links;
319 foreach my $file (@listing) {
323 if ($file_counter < $start_at) {
327 if (defined($max_files) && $file_counter > $max_files+$start_at-1) {
331 my $thumbfilename = $topdir."/".$file;
333 my $fileurl = $uri."/".$file;
335 # Debian bug #619625 <http://bugs.debian.org/619625>
336 if (-d $thumbfilename && ! -e $thumbfilename . ".ignore") {
338 if (-e $thumbfilename . ".folder") {
339 $dirtitle = get_filecontent($thumbfilename . ".folder");
342 $dirtitle = $dirtitle ? $dirtitle : $file;
343 $dirtitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
346 $templates{directory}->fill_in(HASH=> {FILEURL => uri_escape($fileurl, $escape_rule),
352 # Debian bug #619625 <http://bugs.debian.org/619625>
353 elsif (-f $thumbfilename && $thumbfilename =~ /$doc_pattern/i && $thumbfilename !~ /$img_pattern/i && ! -e $thumbfilename . ".ignore") {
355 my $stat = stat($thumbfilename);
356 my $size = $stat->size;
359 if ($thumbfilename =~ m/\.(mpe?g|avi|mov|asf|wmv)$/i) {
360 $filetype = "video-$type";
361 } elsif ($thumbfilename =~ m/\.(txt|html?)$/i) {
362 $filetype = "text-$type";
363 } elsif ($thumbfilename =~ m/\.(mp3|ogg|wav)$/i) {
364 $filetype = "sound-$type";
365 } elsif ($thumbfilename =~ m/$doc_pattern/i) {
366 $filetype = "application-$type";
368 $filetype = "unknown";
371 # Debian bug #337012 <http://bugs.debian.org/337012>
373 my $filetitle = $file;
374 if (-e $thumbfilename . ".file") {
375 $filetitle = get_filecontent($thumbfilename . ".file");
378 # Debian bug #348724 <http://bugs.debian.org/348724>
379 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
382 $templates{file}->fill_in(HASH => {%tpl_vars,
383 FILEURL => uri_escape($fileurl, $escape_rule),
384 ALT => "Size: $size Bytes",
387 FILETYPE => $filetype,
391 # Debian bug #619625 <http://bugs.debian.org/619625>
392 elsif (-f $thumbfilename && ! -e $thumbfilename . ".ignore") {
394 my ($width, $height, $type) = imgsize($thumbfilename);
395 next if $type eq 'Data stream is not a known image file format';
397 my @filetypes = qw(JPG TIF PNG PPM GIF);
399 next unless (grep $type eq $_, @filetypes);
400 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);
401 my $imageinfo = get_imageinfo($r, $thumbfilename, $type, $width, $height);
402 my $cached = get_scaled_picture_name($thumbfilename, $thumbnailwidth, $thumbnailheight);
404 my $rotate = readfile_getnum($r, $imageinfo, $thumbfilename.".rotate");
406 # Debian bug #337012 <http://bugs.debian.org/337012>
407 # HTML <img> tag, alt attribute
408 my $filetitle = $file;
409 if (-e $thumbfilename . ".file") {
410 $filetitle = get_filecontent($thumbfilename . ".file");
413 # Debian bug #348724 <http://bugs.debian.org/348724>
414 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
416 my %file_vars = (FILEURL => uri_escape($fileurl, $escape_rule),
418 DATE => $imageinfo->{DateTimeOriginal} ? $imageinfo->{DateTimeOriginal} : '', # should this really be a stat of the file instead of ''?
419 SRC => uri_escape($uri."/.cache/$cached", $escape_rule),
420 HEIGHT => (grep($rotate==$_, (1, 3)) ? $thumbnailwidth : $thumbnailheight),
421 WIDTH => (grep($rotate==$_, (1, 3)) ? $thumbnailheight : $thumbnailwidth),
422 SELECT => $select_mode?'<input type="checkbox" name="selection" value="'.$file.'"> ':'',);
423 $tpl_vars{FILES} .= $templates{picture}->fill_in(HASH => {%tpl_vars,
428 if ($media_rss_enabled) {
429 my ($content_image_width, undef, $content_image_height) = get_image_display_size($cgi, $r, $width, $height);
431 THUMBNAIL => uri_escape($uri."/.cache/$cached", $escape_rule),
432 LINK => uri_escape($fileurl, $escape_rule),
434 CONTENT => uri_escape($uri."/.cache/".$content_image_width."x".$content_image_height."-".$file, $escape_rule)
436 $tpl_vars{ITEMS} .= $templates{rss_item}->fill_in(HASH => {
444 $tpl_vars{FILES} = "No files found";
445 $tpl_vars{BROWSELINKS} = "";
448 # Generate prev and next directory menu items
449 $filename =~ m/(.*)\/.*?$/;
450 my $parent_filename = $1;
452 $r->document_root =~ m/(.*)\/$/;
454 print STDERR "$filename vs $root_path\n";
455 if ($filename ne $root_path) {
456 unless (opendir (PARENT_DIR, $parent_filename)) {
457 show_error ($r, 500, $!, "Unable to access parent directory $parent_filename: $!");
458 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
461 # Debian bug #619625 <http://bugs.debian.org/619625>
462 my @neighbour_directories = grep { !/^\./ && -d "$parent_filename/$_" && ! -e "$parent_filename/$_" . ".ignore" } readdir (PARENT_DIR);
464 if (defined($r->dir_config('GalleryDirSortBy'))) {
465 $dirsortby=$r->dir_config('GalleryDirSortBy');
467 $dirsortby=$r->dir_config('GallerySortBy');
469 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
470 @neighbour_directories = map(/^\d+ (.*)/, sort map(stat("$parent_filename/$_")->$dirsortby()." $_", @neighbour_directories));
472 @neighbour_directories = sort @neighbour_directories;
475 closedir(PARENT_DIR);
477 my $neightbour_counter = 0;
478 foreach my $neighbour_directory (@neighbour_directories) {
479 if ($parent_filename.'/'.$neighbour_directory eq $filename) {
480 if ($neightbour_counter > 0) {
481 print STDERR "prev directory is " .$neighbour_directories[$neightbour_counter-1] ."\n";
482 my $linktext = $neighbour_directories[$neightbour_counter-1];
483 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
484 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder");
487 URL => "../".$neighbour_directories[$neightbour_counter-1],
488 LINK_NAME => "<<< $linktext",
491 $tpl_vars{PREV_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
492 print STDERR $tpl_vars{PREV_DIR_FILES} ."\n";
495 if ($neightbour_counter < scalar @neighbour_directories - 1) {
496 my $linktext = $neighbour_directories[$neightbour_counter+1];
497 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder") {
498 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder");
501 URL => "../".$neighbour_directories[$neightbour_counter+1],
502 LINK_NAME => "$linktext >>>",
505 $tpl_vars{NEXT_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
506 print STDERR "next directory is " .$neighbour_directories[$neightbour_counter+1] ."\n";
509 $neightbour_counter++;
513 if (-f $topdir . '.comment') {
514 my $comment_ref = get_comment($topdir . '.comment');
516 $comment_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
517 $comment_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
518 $tpl_vars{DIRCOMMENT} = $templates{comment}->fill_in(HASH => \%comment_vars);
519 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
521 $tpl_vars{DIRCOMMENT} = $templates{nocomment}->fill_in(HASH=>\%tpl_vars);
524 if ($cgi->param('rss')) {
525 $tpl_vars{MAIN} = $templates{rss}->fill_in(HASH => \%tpl_vars);
526 $r->content_type('application/rss+xml');
528 $tpl_vars{MAIN} = $templates{index}->fill_in(HASH => \%tpl_vars);
529 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
530 $r->content_type('text/html');
533 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
536 $r->send_http_header;
539 $r->print($tpl_vars{MAIN});
540 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
546 if (defined($ENV{QUERY_STRING}) && $ENV{QUERY_STRING} eq 'orig') {
547 if ($r->dir_config('GalleryAllowOriginal') ? 1 : 0) {
548 $r->filename($filename);
549 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
551 return $::MP2 ? Apache2::Const::FORBIDDEN() : Apache::Constants::FORBIDDEN();
554 if (defined $ENV{QUERY_STRING} && $ENV{QUERY_STRING} eq 'thumbonly' &&
555 $r->dir_config('GalleryAllowThumbonly') &&
558 my ($width, $height, $type) = imgsize($filename);
559 my @filetypes = qw(JPG TIF PNG PPM GIF);
560 if (grep $type eq $_, @filetypes) {
561 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);
562 my $imageinfo = get_imageinfo($r, $filename, $type, $width, $height);
563 my $cached = get_scaled_picture_name($filename, $thumbnailwidth, $thumbnailheight);
564 $r->headers_out->set(Location => uri_escape(".cache/$cached", $escape_rule));
565 return $::MP2 ? Apache2::Const::REDIRECT() : Apache::Constants::REDIRECT();
569 # Create cache dir if not existing
570 my @tmp = split (/\//, $filename);
571 my $picfilename = pop @tmp;
572 my $path = (join "/", @tmp)."/";
573 my $cache_path = cache_dir($r, 1);
575 unless (-d $cache_path) {
576 unless (create_cache($r, $cache_path)) {
577 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
581 my ($orig_width, $orig_height, $type) = imgsize($filename);
583 my $imageinfo = get_imageinfo($r, $filename, $type, $orig_width, $orig_height);
585 my ($image_width, $width, $height, $original_size) = get_image_display_size($cgi, $r, $orig_width, $orig_height);
587 my $cached = get_scaled_picture_name($filename, $image_width, $height);
589 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
591 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
592 picture => "$tpl_dir/showpicture.tpl",
593 navpicture => "$tpl_dir/navpicture.tpl",
594 info => "$tpl_dir/info.tpl",
595 scale => "$tpl_dir/scale.tpl",
596 scaleactive => "$tpl_dir/scaleactive.tpl",
597 orig => "$tpl_dir/orig.tpl",
598 refresh => "$tpl_dir/refresh.tpl",
599 interval => "$tpl_dir/interval.tpl",
600 intervalactive => "$tpl_dir/intervalactive.tpl",
601 slideshowisoff => "$tpl_dir/slideshowisoff.tpl",
602 slideshowoff => "$tpl_dir/slideshowoff.tpl",
603 pictureinfo => "$tpl_dir/pictureinfo.tpl",
604 nopictureinfo => "$tpl_dir/nopictureinfo.tpl",
609 my $resolution = (($image_width > $orig_width) && ($height > $orig_height)) ?
610 "$orig_width x $orig_height" : "$image_width x $height";
612 $tpl_vars{TITLE} = "Viewing ".$r->uri()." at $image_width x $height";
613 $tpl_vars{META} = " ";
614 $tpl_vars{RESOLUTION} = $resolution;
615 $tpl_vars{MENU} = generate_menu($r);
616 $tpl_vars{SRC} = uri_escape(".cache/$cached", $escape_rule);
617 $tpl_vars{URI} = $r->uri();
619 my $exif_mode = $r->dir_config('GalleryEXIFMode');
620 unless ($exif_mode) {
621 $exif_mode = 'namevalue';
624 unless (opendir(DATADIR, $path)) {
625 show_error($r, 500, "Unable to access directory", "Unable to access directory $path");
626 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
628 my @pictures = grep { /$img_pattern/i && ! -e "$path/$_" . ".ignore" } readdir (DATADIR);
630 @pictures = gallerysort($r, @pictures);
632 $tpl_vars{TOTAL} = scalar @pictures;
637 for (my $i=0; $i <= $#pictures; $i++) {
638 if ($pictures[$i] eq $picfilename) {
640 $tpl_vars{NUMBER} = $i+1;
642 $prevpicture = $pictures[$i-1];
643 my $displayprev = ($i>0 ? 1 : 0);
645 if ($r->dir_config("GalleryWrapNavigation")) {
646 $prevpicture = $pictures[$i>0 ? $i-1 : $#pictures];
649 if ($prevpicture and $displayprev) {
650 my ($orig_width, $orig_height, $type) = imgsize($path.$prevpicture);
651 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
652 my $imageinfo = get_imageinfo($r, $path.$prevpicture, $type, $orig_width, $orig_height);
653 my $cached = get_scaled_picture_name($path.$prevpicture, $thumbnailwidth, $thumbnailheight);
654 # Debian bug #337012 <http://bugs.debian.org/337012>
655 my $prevpicture_title = $prevpicture;
656 if (-e $path."/".$prevpicture . ".file") {
657 $prevpicture_title = get_filecontent($path."/".$prevpicture . ".file");
660 $nav_vars{URL} = uri_escape($prevpicture, $escape_rule);
661 $nav_vars{FILENAME} = $prevpicture_title;
662 $nav_vars{WIDTH} = $width;
663 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
664 $nav_vars{DIRECTION} = "« <u>p</u>rev";
665 $nav_vars{ACCESSKEY} = "P";
666 $tpl_vars{BACK} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
669 $tpl_vars{BACK} = " ";
672 $nextpicture = $pictures[$i+1];
673 if ($r->dir_config("GalleryWrapNavigation")) {
674 $nextpicture = $pictures[$i == $#pictures ? 0 : $i+1];
678 my ($orig_width, $orig_height, $type) = imgsize($path.$nextpicture);
679 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
680 my $imageinfo = get_imageinfo($r, $path.$nextpicture, $type, $thumbnailwidth, $thumbnailheight);
681 my $cached = get_scaled_picture_name($path.$nextpicture, $thumbnailwidth, $thumbnailheight);
682 # Debian bug #337012 <http://bugs.debian.org/337012>
683 my $nextpicture_title = $nextpicture;
684 if (-e $path."/".$nextpicture . ".file") {
685 $nextpicture_title = get_filecontent($path."/".$nextpicture . ".file");
688 $nav_vars{URL} = uri_escape($nextpicture, $escape_rule);
689 $nav_vars{FILENAME} = $nextpicture_title;
690 $nav_vars{WIDTH} = $width;
691 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
692 $nav_vars{DIRECTION} = "<u>n</u>ext »";
693 $nav_vars{ACCESSKEY} = "N";
695 $tpl_vars{NEXT} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
696 $tpl_vars{NEXTURL} = uri_escape($nextpicture, $escape_rule);
699 $tpl_vars{NEXT} = " ";
700 $tpl_vars{NEXTURL} = '#';
705 my $foundcomment = 0;
706 if (-f $path . '/' . $picfilename . '.comment') {
707 my $comment_ref = get_comment($path . '/' . $picfilename . '.comment');
709 $tpl_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
710 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
711 } elsif ($r->dir_config('GalleryCommentExifKey')) {
712 my $comment = decode("utf8", $imageinfo->{$r->dir_config('GalleryCommentExifKey')});
713 $tpl_vars{COMMENT} = encode("iso-8859-1", $comment);
715 $tpl_vars{COMMENT} = '';
718 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
723 my ($human_key, $exif_key) = (split " => ")[0,1];
724 my $value = $imageinfo->{$human_key};
725 if (defined($value)) {
729 if ($exif_mode eq 'namevalue') {
731 $info_vars{KEY} = $human_key;
732 $info_vars{VALUE} = $value;
733 $tpl_vars{INFO} .= $templates{info}->fill_in(HASH => \%info_vars);
736 if ($exif_mode eq 'variables') {
737 $tpl_vars{"EXIF_".uc($exif_key)} = $value;
740 if ($exif_mode eq 'values') {
741 $exifvalues .= "| ".$value." ";
748 if ($exif_mode eq 'values') {
749 if (defined($exifvalues)) {
750 $tpl_vars{EXIFVALUES} = $exifvalues;
753 $tpl_vars{EXIFVALUES} = "";
757 if ($foundcomment and !$foundinfo) {
758 $tpl_vars{INFO} = "";
761 if ($exif_mode ne 'namevalue') {
762 $tpl_vars{INFO} = "";
765 if ($exif_mode eq 'namevalue' && $foundinfo or $foundcomment) {
767 $tpl_vars{PICTUREINFO} = $templates{pictureinfo}->fill_in(HASH => \%tpl_vars);
769 unless (defined($exifvalues)) {
770 $tpl_vars{EXIFVALUES} = "";
775 $tpl_vars{PICTUREINFO} = $templates{nopictureinfo}->fill_in(HASH => \%tpl_vars);
778 # Fill in sizes and determine if any are smaller than the
779 # actual image. If they are, $scaleable=1
781 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
782 foreach my $size (@sizes) {
783 if ($size<=$original_size) {
785 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
786 $sizes_vars{SIZE} = $size;
787 $sizes_vars{WIDTH} = $size;
788 if ($width == $size) {
789 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
792 $tpl_vars{SIZES} .= $templates{scale}->fill_in(HASH => \%sizes_vars);
798 unless ($scaleable) {
800 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
801 $sizes_vars{SIZE} = $original_size;
802 $sizes_vars{WIDTH} = $original_size;
803 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
806 $tpl_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
808 if ($r->dir_config('GalleryAllowOriginal')) {
809 $tpl_vars{SIZES} .= $templates{orig}->fill_in(HASH => \%tpl_vars);
812 my @slideshow_intervals = split (/ /, $r->dir_config('GallerySlideshowIntervals') ? $r->dir_config('GallerySlideshowIntervals') : '3 5 10 15 30');
813 foreach my $interval (@slideshow_intervals) {
816 $slideshow_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
817 $slideshow_vars{SECONDS} = $interval;
818 $slideshow_vars{WIDTH} = ($width > $height ? $width : $height);
820 if ($cgi->param('slideshow') && $cgi->param('slideshow') == $interval and $nextpicture) {
821 $tpl_vars{SLIDESHOW} .= $templates{intervalactive}->fill_in(HASH => \%slideshow_vars);
825 $tpl_vars{SLIDESHOW} .= $templates{interval}->fill_in(HASH => \%slideshow_vars);
830 if ($cgi->param('slideshow') and $nextpicture) {
832 $tpl_vars{SLIDESHOW} .= $templates{slideshowoff}->fill_in(HASH => \%tpl_vars);
834 unless ((grep $cgi->param('slideshow') == $_, @slideshow_intervals)) {
835 show_error($r, 200, "Invalid interval", "Invalid slideshow interval choosen");
836 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
839 $tpl_vars{URL} = uri_escape($nextpicture, $escape_rule);
840 $tpl_vars{WIDTH} = ($width > $height ? $width : $height);
841 $tpl_vars{INTERVAL} = $cgi->param('slideshow');
842 $tpl_vars{META} .= $templates{refresh}->fill_in(HASH => \%tpl_vars);
846 $tpl_vars{SLIDESHOW} .= $templates{slideshowisoff}->fill_in(HASH => \%tpl_vars);
849 $tpl_vars{MAIN} = $templates{picture}->fill_in(HASH => \%tpl_vars);
850 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
852 $r->content_type('text/html');
853 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
856 $r->send_http_header;
859 $r->print($tpl_vars{MAIN});
860 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
868 my $subr = $r->lookup_file($file);
869 $r->content_type($subr->content_type());
871 my $fileinfo = stat($file);
873 my $nonce = md5_base64($fileinfo->ino.$fileinfo->mtime);
874 if ($r->headers_in->{"If-None-Match"} eq $nonce) {
875 return Apache2::Const::HTTP_NOT_MODIFIED();
878 if ($r->headers_in->{"If-Modified-Since"} && str2time($r->headers_in->{"If-Modified-Since"}) < $fileinfo->mtime) {
879 return Apache2::Const::HTTP_NOT_MODIFIED();
882 $r->headers_out->{"Content-Length"} = $fileinfo->size;
883 $r->headers_out->{"Last-Modified-Date"} = time2str($fileinfo->mtime);
884 $r->headers_out->{"ETag"} = $nonce;
886 return Apache2::Const::OK();
891 return Apache::Constants::DECLINED();
897 my ($r, $strip_filename) = @_;
901 unless ($r->dir_config('GalleryCacheDir')) {
903 $cache_root = '/var/cache/www/';
904 if ($r->server->is_virtual) {
905 $cache_root = File::Spec->catdir($cache_root, $r->server->server_hostname);
907 $cache_root = File::Spec->catdir($cache_root, $r->location);
912 $cache_root = $r->dir_config('GalleryCacheDir');
916 # If the uri contains .cache we need to remove it
920 my (undef, $dirs, $filename) = File::Spec->splitpath($uri);
921 # We don't need a volume as this is a relative path
923 if ($strip_filename) {
924 return(File::Spec->canonpath(File::Spec->catdir($cache_root, $dirs)));
926 return(File::Spec->canonpath(File::Spec->catfile($cache_root, $dirs, $filename)));
934 unless (mkdirhier ($path)) {
935 show_error($r, 500, $!, "Unable to create cache directory in $path: $!");
948 unless (mkdir($dir, 0755)) {
950 $parent =~ s/\/[^\/]*$//;
959 sub get_scaled_picture_name {
961 my ($fullpath, $width, $height) = @_;
963 my (undef, undef, $type) = imgsize($fullpath);
965 my @dirs = split(/\//, $fullpath);
966 my $filename = pop(@dirs);
969 if (grep $type eq $_, qw(PPM TIF GIF)) {
970 $newfilename = $width."x".$height."-".$filename;
971 # needs to be configurable
972 $newfilename =~ s/\.(\w+)$/-$1\.jpg/;
974 $newfilename = $width."x".$height."-".$filename;
983 my ($r, $fullpath, $width, $height, $imageinfo) = @_;
985 my @dirs = split(/\//, $fullpath);
986 my $filename = pop(@dirs);
988 my ($orig_width, $orig_height, $type) = imgsize($fullpath);
990 my $cache = cache_dir($r, 1);
992 my $newfilename = get_scaled_picture_name($fullpath, $width, $height);
994 if (($width > $orig_width) && ($height > $orig_height)) {
995 # Run it through the resize code anyway to get watermarks
996 $width = $orig_width;
997 $height = $orig_height;
1000 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
1002 # Do we want to generate a new file in the cache?
1005 if (-f $cache."/".$newfilename) {
1008 # Check to see if the image has changed
1009 my $filestat = stat($fullpath);
1010 my $cachestat = stat($cache."/".$newfilename);
1011 if ($filestat->mtime >= $cachestat->mtime) {
1015 # Check to see if the .rotate file has been added or changed
1016 if (-f $fullpath . ".rotate") {
1017 my $rotatestat = stat($fullpath . ".rotate");
1018 if ($rotatestat->mtime > $cachestat->mtime) {
1022 # Check to see if the copyrightimage has been added or changed
1023 if ($r->dir_config('GalleryCopyrightImage') && -f $r->dir_config('GalleryCopyrightImage')) {
1024 unless ($width == $thumbnailwidth or $width == $thumbnailheight) {
1025 my $copyrightstat = stat($r->dir_config('GalleryCopyrightImage'));
1026 if ($copyrightstat->mtime > $cachestat->mtime) {
1036 my $newpath = $cache."/".$newfilename;
1037 my $rotate = readfile_getnum($r, $imageinfo, $fullpath . ".rotate");
1038 my $quality = $r->dir_config('GalleryQuality');
1040 if ($width == $thumbnailwidth or $width == $thumbnailheight) {
1042 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, '', '', '', '', '', '');
1046 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate,
1047 ($r->dir_config('GalleryCopyrightImage') ? $r->dir_config('GalleryCopyrightImage') : ''),
1048 ($r->dir_config('GalleryTTFDir') ? $r->dir_config('GalleryTTFDir') : ''),
1049 ($r->dir_config('GalleryCopyrightText') ? $r->dir_config('GalleryCopyrightText') : ''),
1050 ($r->dir_config('GalleryCopyrightColor') ? $r->dir_config('GalleryCopyrightColor') : ''),
1051 ($r->dir_config('GalleryTTFFile') ? $r->dir_config('GalleryTTFFile') : ''),
1052 ($r->dir_config('GalleryTTFSize') ? $r->dir_config('GalleryTTFSize') : ''),
1053 ($r->dir_config('GalleryCopyrightBackgroundColor') ? $r->dir_config('GalleryCopyrightBackgroundColor') : ''),
1059 return $newfilename;
1063 sub get_thumbnailsize {
1064 my ($r, $orig_width, $orig_height) = @_;
1066 my $gallerythumbnailsize=$r->dir_config('GalleryThumbnailSize');
1068 if (defined($gallerythumbnailsize)) {
1069 warn("Invalid setting for GalleryThumbnailSize") unless
1070 $gallerythumbnailsize =~ /^\s*\d+\s*x\s*\d+\s*$/i;
1073 my ($thumbnailwidth, $thumbnailheight) = split(/x/i, ($gallerythumbnailsize) ? $gallerythumbnailsize : "100x75");
1075 my $width = $thumbnailwidth;
1076 my $height = $thumbnailheight;
1078 # If the image is rotated, flip everything around.
1079 if (defined $r->dir_config('GalleryThumbnailSizeLS')
1080 and $r->dir_config('GalleryThumbnailSizeLS') eq '1'
1081 and $orig_width < $orig_height) {
1083 $width = $thumbnailheight;
1084 $height = $thumbnailwidth;
1087 my $scale = ($orig_width ? $width/$orig_width : 1);
1090 if ($orig_height * $scale > $thumbnailheight) {
1091 $scale = $height/$orig_height;
1092 $width = $orig_width * $scale;
1096 $height = $orig_height * $scale;
1098 $height = floor($height);
1099 $width = floor($width);
1101 return ($width, $height);
1104 sub get_image_display_size {
1105 my ($cgi, $r, $orig_width, $orig_height) = @_;
1107 my $width = $orig_width;
1109 my $original_size=$orig_height;
1110 if ($orig_width>$orig_height) {
1111 $original_size=$orig_width;
1114 # Check if the selected width is allowed
1115 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
1117 my %cookies = fetch CGI::Cookie;
1119 if ($cgi->param('width')) {
1120 unless ((grep $cgi->param('width') == $_, @sizes) or ($cgi->param('width') == $original_size)) {
1121 show_error($r, 200, "Invalid width", "The specified width is invalid");
1122 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
1125 $width = $cgi->param('width');
1126 my $cookie = new CGI::Cookie(-name => 'GallerySize', -value => $width, -expires => '+6M');
1127 $r->headers_out->{'Set-Cookie'} = $cookie;
1129 } elsif ($cookies{'GallerySize'} && (grep $cookies{'GallerySize'}->value == $_, @sizes)) {
1131 $width = $cookies{'GallerySize'}->value;
1139 if ($orig_width<$orig_height) {
1140 $scale = ($orig_height ? $width/$orig_height: 1);
1141 $image_width=$width*$orig_width/$orig_height;
1144 $scale = ($orig_width ? $width/$orig_width : 1);
1145 $image_width = $width;
1148 my $height = $orig_height * $scale;
1150 $image_width = floor($image_width);
1151 $width = floor($width);
1152 $height = floor($height);
1154 return ($image_width, $width, $height, $original_size);
1158 my ($r, $file, $type, $width, $height) = @_;
1160 if ($type eq 'Data stream is not a known image file format') {
1161 # should never be reached, this is supposed to be handled outside of here
1162 log_error("Something was fishy with the type of the file $file\n");
1165 # Some files, like TIFF, PNG, GIF do not have EXIF info
1166 # embedded but use .thm files instead.
1167 $imageinfo = get_imageinfo_from_thm_file($file, $width, $height);
1169 # If there is no .thm file and our file is a JPEG file we try to extract the EXIf
1170 # info using Image::Info
1171 unless (defined($imageinfo) && (grep $type eq $_, qw(JPG))) {
1172 # Only for files that natively keep the EXIF info in the same file
1173 $imageinfo = image_info($file);
1177 unless (defined($imageinfo->{width}) and defined($imageinfo->{height})) {
1178 $imageinfo->{width} = $width;
1179 $imageinfo->{height} = $height;
1182 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
1185 my ($human_key, $exif_key) = (split " => ")[0,1];
1186 if (defined($exif_key) && defined($imageinfo->{$exif_key})) {
1188 if (ref($imageinfo->{$exif_key}) eq 'Image::TIFF::Rational') {
1189 $value = $imageinfo->{$exif_key}->as_string;
1191 elsif (ref($imageinfo->{$exif_key}) eq 'ARRAY') {
1192 foreach my $element (@{$imageinfo->{$exif_key}}) {
1193 if (ref($element) eq 'ARRAY') {
1194 foreach (@{$element}) {
1198 elsif (ref($element) eq 'HASH') {
1199 $value .= "<br />{ ";
1200 foreach (sort keys %{$element}) {
1201 $value .= "$_ = " . $element->{$_} . ' ';
1212 my $exif_value = $imageinfo->{$exif_key};
1213 if ($human_key eq 'Flash' && $exif_value =~ m/\d/) {
1218 "16" => "No (Compulsory) Should be External Flash",
1219 "17" => "Yes (External)",
1221 "25" => "Yes (Auto)",
1222 "73" => "Yes (Compulsory, Red Eye Reducing)",
1223 "89" => "Yes (Auto, Red Eye Reducing)"
1225 $exif_value = defined $flashmodes{$exif_value} ? $flashmodes{$exif_value} : 'unknown flash mode';
1227 $value = $exif_value;
1229 if ($exif_key eq 'MeteringMode') {
1230 my $exif_value = $imageinfo->{$exif_key};
1231 if ($exif_value =~ /^\d+$/) {
1232 my %meteringmodes = (
1235 '2' => 'CenterWeightedAverage',
1242 $exif_value = defined $meteringmodes{$exif_value} ? $meteringmodes{$exif_value} : 'unknown metering mode';
1244 $value = $exif_value;
1247 if ($exif_key eq 'LightSource') {
1248 my $exif_value = $imageinfo->{$exif_key};
1249 if ($exif_value =~ /^\d+$/) {
1250 my %lightsources = (
1253 '2' => 'Fluorescent',
1254 '3' => 'Tungsten (incandescent light)',
1256 '9' => 'Fine weather',
1257 '10' => 'Cloudy weather',
1259 '12' => 'Daylight fluorescent',
1260 '13' => 'Day white fluorescent',
1261 '14' => 'Cool white fluorescent',
1262 '15' => 'White fluorescent',
1263 '17' => 'Standard light A',
1264 '18' => 'Standard light B',
1265 '19' => 'Standard light C',
1270 '24' => 'ISO studio tungsten',
1271 '255' => 'other light source'
1273 $exif_value = defined $lightsources{$exif_value} ? $lightsources{$exif_value} : 'unknown light source';
1275 $value = $exif_value;
1277 if ($exif_key eq 'FocalLength') {
1278 if ($value =~ /^(\d+)\/(\d+)$/) {
1279 $value = eval { $1 / $2 };
1283 $value = int($value + 0.5) . "mm";
1288 if ($exif_key eq 'ShutterSpeedValue') {
1289 if ($value =~ /^((?:\-)?\d+)\/(\d+)$/) {
1290 $value = eval { $1 / $2 };
1295 $value = 1/(exp($value*log(2)));
1297 $value = "1/" . (int((1/$value)));
1299 $value = int($value*10)/10;
1305 $value = $value . " sec";
1310 if ($exif_key eq 'ApertureValue') {
1311 if ($value =~ /^(\d+)\/(\d+)$/) {
1312 $value = eval { $1 / $2 };
1316 # poor man's rounding
1317 $value = int(exp($value*log(2)*0.5)*10)/10;
1318 $value = "f" . $value;
1322 if ($exif_key eq 'FNumber') {
1323 if ($value =~ /^(\d+)\/(\d+)$/) {
1324 $value = eval { $1 / $2 };
1328 $value = int($value*10+0.5)/10;
1329 $value = "f" . $value;
1333 $imageinfo->{$human_key} = $value;
1337 if ($r->dir_config('GalleryUseFileDate') &&
1338 ($r->dir_config('GalleryUseFileDate') eq '1'
1339 || !$imageinfo->{"Picture Taken"} )) {
1341 my $st = stat($file);
1342 $imageinfo->{"DateTimeOriginal"} = $imageinfo->{"Picture Taken"} = scalar localtime($st->mtime) if $st;
1348 sub get_imageinfo_from_thm_file {
1350 my ($file, $width, $height) = @_;
1352 my $imageinfo = undef;
1353 # Windows based file extensions are often .THM, so check
1354 # for both .thm and .THM
1355 my $unix_file = $file;
1356 my $windows_file = $file;
1357 $unix_file =~ s/\.(\w+)$/.thm/;
1358 $windows_file =~ s/\.(\w+)$/.THM/;
1360 if (-e $unix_file && -f $unix_file && -r $unix_file) {
1361 $imageinfo = image_info($unix_file);
1362 $imageinfo->{width} = $width;
1363 $imageinfo->{height} = $height;
1365 elsif (-e $windows_file && -f $windows_file && -r $windows_file) {
1366 $imageinfo = image_info($windows_file);
1367 $imageinfo->{width} = $width;
1368 $imageinfo->{height} = $height;
1375 sub readfile_getnum {
1376 my ($r, $imageinfo, $filename) = @_;
1380 print STDERR "orientation: ".$imageinfo->{Orientation}."\n";
1381 # Check to see if the image contains the Orientation EXIF key,
1382 # but allow user to override using rotate
1383 if (!defined($r->dir_config("GalleryAutoRotate"))
1384 || $r->dir_config("GalleryAutoRotate") eq "1") {
1385 if (defined($imageinfo->{Orientation})) {
1386 print STDERR $imageinfo->{Orientation}."\n";
1387 if ($imageinfo->{Orientation} eq 'right_top') {
1390 elsif ($imageinfo->{Orientation} eq 'left_bot') {
1396 if (open(FH, "<$filename")) {
1400 unless ($temp =~ /^\d$/) {
1403 unless ($temp == 1 || $temp == 2 || $temp == 3) {
1412 sub get_filecontent {
1414 open(FH, $file) or return undef;
1425 my $filename = shift;
1426 my $comment_ref = {};
1427 $comment_ref->{TITLE} = undef;
1428 $comment_ref->{COMMENT} = '';
1430 open(FH, $filename) or return $comment_ref;
1432 if ($title =~ m/^TITLE: (.*)$/) {
1433 chomp($comment_ref->{TITLE} = $1);
1436 $comment_ref->{COMMENT} = $title;
1441 $comment_ref->{COMMENT} .= $_;
1445 return $comment_ref;
1450 my ($r, $statuscode, $errortitle, $error) = @_;
1452 my $tpl = $r->dir_config('GalleryTemplateDir');
1454 my %templates = create_templates({layout => "$tpl/layout.tpl",
1455 error => "$tpl/error.tpl",
1459 $tpl_vars{TITLE} = "Error! $errortitle";
1460 $tpl_vars{META} = "";
1461 $tpl_vars{ERRORTITLE} = "Error! $errortitle";
1462 $tpl_vars{ERROR} = $error;
1464 $tpl_vars{MAIN} = $templates{error}->fill_in(HASH => \%tpl_vars);
1466 $tpl_vars{PAGE} = $templates{layout}->fill_in(HASH => \%tpl_vars);
1468 $r->status($statuscode);
1469 $r->content_type('text/html');
1471 $r->print($tpl_vars{PAGE});
1479 my $root_text = (defined($r->dir_config('GalleryRootText')) ? $r->dir_config('GalleryRootText') : "root:" );
1480 my $root_path = (defined($r->dir_config('GalleryRootPath')) ? $r->dir_config('GalleryRootPath') : "" );
1482 my $subr = $r->lookup_uri($r->uri);
1483 my $filename = $subr->filename;
1485 my @links = split (/\//, $r->uri);
1487 $uri =~ s/^$root_path//g;
1489 @links = split (/\//, $uri);
1491 # Get the full path of the base directory
1494 my @direlem = split (/\//, $filename);
1495 for my $i ( 0 .. ( scalar(@direlem) - scalar(@links) ) ) {
1496 $dirname .= shift(@direlem) . '/';
1503 $picturename = pop(@links);
1504 # Debian bug #337012 <http://bugs.debian.org/337012>
1505 if (-e $filename . ".file") {
1506 $picturename = get_filecontent($filename . ".file");
1510 if ($r->uri eq $root_path) {
1511 return qq{ <a href="$root_path">$root_text</a> };
1515 my $menuurl = $root_path;
1516 foreach my $link (@links) {
1518 $menuurl .= $link."/";
1519 my $linktext = $link;
1520 unless (length($link)) {
1521 $linktext = "$root_text ";
1525 $dirname = File::Spec->catdir($dirname, $link);
1527 if (-e $dirname . ".folder") {
1528 $linktext = get_filecontent($dirname . ".folder");
1532 if ("$root_path$uri" eq $menuurl) {
1533 $menu .= "$linktext / ";
1536 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule)."\">$linktext</a> / ";
1542 $menu .= $picturename;
1546 if ($r->dir_config('GallerySelectionMode') && $r->dir_config('GallerySelectionMode') eq '1') {
1547 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule);
1548 $menu .= "?select=1\">[select]</a> ";
1556 my ($r, $infile, $outfile, $x, $y, $rotate, $copyrightfile, $GalleryTTFDir, $GalleryCopyrightText, $text_color, $GalleryTTFFile, $GalleryTTFSize, $GalleryCopyrightBackgroundColor, $quality) = @_;
1559 my $image = Image::Imlib2->load($infile) or warn("Unable to open file $infile, $!");
1562 $image=$image->create_scaled_image($x, $y) or warn("Unable to scale image $infile. Are you running out of memory?");
1566 $image->image_orientate($rotate);
1569 # blend copyright image onto image
1570 if ($copyrightfile ne '') {
1571 if (-f $copyrightfile and (my $logo=Image::Imlib2->load($copyrightfile))) {
1572 my $x = $image->get_width();
1573 my $y = $image->get_height();
1574 my $logox = $logo->get_width();
1575 my $logoy = $logo->get_height();
1576 $image->blend($logo, 0, 0, 0, $logox, $logoy, $x-$logox, $y-$logoy, $logox, $logoy);
1579 log_error("GalleryCopyrightImage $copyrightfile was not found");
1583 if ($GalleryTTFDir && $GalleryCopyrightText && $GalleryTTFFile && $text_color) {
1584 if (!-d $GalleryTTFDir) {
1586 log_error("GalleryTTFDir $GalleryTTFDir is not a dir\n");
1588 } elsif ($GalleryCopyrightText eq '') {
1590 log_error("GalleryCopyrightText is empty. No text inserted to picture\n");
1592 } elsif (!-e "$GalleryTTFDir/$GalleryTTFFile") {
1594 log_error("GalleryTTFFile $GalleryTTFFile was not found\n");
1598 $GalleryTTFFile =~ s/\.TTF$//i;
1599 $image->add_font_path("$GalleryTTFDir");
1601 $image->load_font("$GalleryTTFFile/$GalleryTTFSize");
1602 my($text_x, $text_y) = $image->get_text_size("$GalleryCopyrightText");
1603 my $x = $image->get_width();
1604 my $y = $image->get_height();
1608 if (($text_x < $x - $offset) && ($text_y < $y - $offset)) {
1609 if ($GalleryCopyrightBackgroundColor =~ /^\d+,\d+,\d+,\d+$/) {
1610 my ($br_val, $bg_val, $bb_val, $ba_val) = split (/,/, $GalleryCopyrightBackgroundColor);
1611 $image->set_colour($br_val, $bg_val, $bb_val, $ba_val);
1612 $image->fill_rectangle ($x-$text_x-$offset, $y-$text_y-$offset, $text_x, $text_y);
1614 my ($r_val, $g_val, $b_val, $a_val) = split (/,/, $text_color);
1615 $image->set_colour($r_val, $g_val, $b_val, $a_val);
1616 $image->draw_text($x-$text_x-$offset, $y-$text_y-$offset, "$GalleryCopyrightText");
1618 log_error("Text is to big for the picture.\n");
1623 if ($quality && $quality =~ m/^\d+$/) {
1624 $image->set_quality($quality);
1627 $image->save($outfile);
1634 my $sortby = $r->dir_config('GallerySortBy');
1635 my $filename=$r->lookup_uri($r->uri)->filename;
1636 $filename=(File::Spec->splitpath($filename))[1] if (-f $filename);
1637 if ($sortby && $sortby =~ m/^(size|atime|mtime|ctime)$/) {
1638 @files = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$sortby()." $_", @files));
1640 @files = sort @files;
1645 # Create Text::Template objects used by Apache::Gallery. Takes a
1646 # hashref of template_name, template_filename pairs, and returns a
1647 # list of template_name, texttemplate_object pairs.
1648 sub create_templates {
1649 my $templates = shift;
1651 # This routine is called whenever a template has an error. Prints
1652 # the error to STDERR and sticks the error in the output
1655 # Pull out the name and filename from the arg option [see
1656 # Text::Template for details]
1657 @args{qw(name file)} = @{$args{arg}};
1658 print STDERR qq(Template $args{name} ("$args{file}") is broken: $args{error});
1659 # Don't include the file name in the output, as the user can see this.
1660 return qq(<!-- Template $args{name} is broken: $args{error} -->);
1665 my %texttemplate_objects;
1667 for my $template_name (keys %$templates) {
1668 my $tt_obj = Text::Template->new(TYPE => 'FILE',
1669 SOURCE => $$templates{$template_name},
1670 BROKEN => \&tt_broken,
1671 BROKEN_ARG => [$template_name, $$templates{$template_name}],
1673 or die "Unable to create new Text::Template object for $template_name: $Text::Template::ERROR";
1674 $texttemplate_objects{$template_name} = $tt_obj;
1676 return %texttemplate_objects;
1681 Apache2::RequestUtil->request->log_error(shift());
1683 Apache->request->log_error(shift());
1691 Apache::Gallery - mod_perl handler to create an image gallery
1695 See the INSTALL file in the distribution for installation instructions.
1699 Apache::Gallery creates an thumbnail index of each directory and allows
1700 viewing pictures in different resolutions. Pictures are resized on the
1701 fly and cached. The gallery can be configured and customized in many ways
1702 and a custom copyright image can be added to all the images without
1703 modifying the original.
1705 =head1 CONFIGURATION
1707 In your httpd.conf you set the global options for the gallery. You can
1708 also override each of the options in .htaccess files in your gallery
1711 The options are set in the httpd.conf/.htaccess file using the syntax:
1712 B<PerlSetVar OptionName 'value'>
1714 Example: B<PerlSetVar GalleryCacheDir '/var/cache/www/'>
1718 =item B<GalleryAutoRotate>
1720 Some cameras, like the Canon G3, can detect the orientation of a
1721 the pictures you take and will save this information in the
1722 'Orientation' EXIF field. Apache::Gallery will then automatically
1725 This behavior is default but can be disabled by setting GalleryAutoRotate
1728 =item B<GalleryCacheDir>
1730 Directory where Apache::Gallery should create its cache with scaled
1731 pictures. The default is /var/cache/www/ . Here, a directory for each
1732 virtualhost or location will be created automatically. Make sure your
1733 webserver has write access to the CacheDir.
1735 =item B<GalleryTemplateDir>
1737 Full path to the directory where you placed the templates. This option
1738 can be used both in your global configuration and in .htaccess files,
1739 this way you can have different layouts in different parts of your
1742 No default value, this option is required.
1744 =item B<GalleryInfo>
1746 With this option you can define which EXIF information you would like
1747 to present from the image. The format is: '<MyName => KeyInEXIF,
1748 MyOtherName => OtherKeyInEXIF'
1750 Examples of keys: B<ShutterSpeedValue>, B<ApertureValue>, B<SubjectDistance>,
1753 You can view all the keys from the EXIF header using this perl-oneliner:
1755 perl C<-e> 'use Data::Dumper; use Image::Info qw(image_info); print Dumper(image_info(shift));' filename.jpg
1757 Default is: 'Picture Taken => DateTimeOriginal, Flash => Flash'
1759 =item B<GallerySizes>
1761 Defines which widths images can be scaled to. Images cannot be
1762 scaled to other widths than the ones you define with this option.
1764 The default is '640 800 1024 1600'
1766 =item B<GalleryThumbnailSize>
1768 Defines the width and height of the thumbnail images.
1770 Defaults to '100x75'
1772 =item B<GalleryThumbnailSizeLS>
1774 If set to '1', B<GalleryThumbnailSize> is the long and the short side of
1775 the thumbnail image instead of the width and height.
1779 =item B<GalleryCopyrightImage>
1781 Image you want to blend into your images in the lower right
1782 corner. This could be a transparent png saying "copyright
1787 =item B<GalleryWrapNavigation>
1789 Make the navigation in the picture view wrap around (So Next
1790 at the end displays the first picture, etc.)
1792 Set to 1 or 0, default is 0
1794 =item B<GalleryAllowOriginal>
1796 Allow the user to download the Original picture without
1797 resizing or putting the CopyrightImage on it.
1799 Set to 1 or 0, default is 0
1801 =item B<GalleryAllowThumbOnly>
1803 If true, B<GalleryAllowThumbOnly> allows fooimg.jpg?thumbonly urls
1804 to output the thumbnail of the image. This is useful when including
1805 images in a blog (or similar).
1807 Defaults to '0' (false).
1809 =item B<GallerySlideshowIntervals>
1811 With this option you can configure which intervals can be selected for
1812 a slideshow. The default is '3 5 10 15 30'
1814 =item B<GallerySortBy>
1816 Instead of the default filename ordering you can sort by any
1817 stat attribute. For example size, atime, mtime, ctime.
1819 =item B<GalleryDirSortBy>
1821 Set this variable to sort directories differently than other items,
1822 can be set to size, atime, mtime and ctime; setting any other value
1823 will revert to sorting by name.
1825 =item B<GalleryMemoize>
1827 Cache EXIF data using Memoize - this will make Apache::Gallery faster
1828 when many people access the same images, but it will also cache EXIF
1829 data until the current Apache child dies.
1831 =item B<GalleryUseFileDate>
1833 Set this option to 1 to make A::G show the files timestamp
1834 instead of the EXIF value for "Picture taken".
1836 =item B<GallerySelectionMode>
1838 Enable the selection mode. Select images with checkboxes and
1839 get a list of filenames.
1841 =item B<GalleryEXIFMode>
1843 You can choose how Apache::Gallery should display EXIF info
1846 The default setting is 'namevalue'. This setting will make
1847 Apache::Gallery print out the names and values of the EXIF values
1848 you configure with GalleryInfo. The information will be parsed into
1849 $INFO in pictureinfo.tpl.
1851 You can also set it to 'values' which will make A::G parse
1852 the configured values into the var $EXIFVALUES as 'value | value | value'
1854 If you set this option to 'variables' the items you configure in GalleryInfo
1855 will be available to your templates as $EXIF_<KEYNAME> (in all uppercase).
1856 That means that with the default setting "Picture Taken => DateTimeOriginal,
1857 Flash => Flash" you will have the variables $EXIF_DATETIMEORIGINAL and
1858 $EXIF_FLASH available to your templates. You can place them
1861 =item B<GalleryRootPath>
1863 Change the location of gallery root. The default is ""
1865 =item B<GalleryRootText>
1867 Change the name that appears as the root element in the menu. The
1870 =item B<GalleryMaxThumbnailsPerPage>
1872 This options controls how many thumbnails should be displayed in a
1873 page. It requires $BROWSELINKS to be in the index.tpl template file.
1875 =item B<GalleryImgFile>
1877 Pattern matching the files you want Apache::Gallery to view in the
1878 index as thumbnails.
1880 The default is '\.(jpe?g|png|tiff?|ppm)$'
1882 =item B<GalleryDocFile>
1884 Pattern matching the files you want Apache::Gallery to view in the index
1885 as normal files. All other filetypes will still be served by Apache::Gallery
1886 but are not visible in the index.
1888 The default is '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|mp4|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
1890 =item B<GalleryTTFDir>
1892 To use the GalleryCopyrightText feature you must set this option to the
1893 directory where your True Type fonts are stored. No default is set.
1897 PerlSetVar GalleryTTFDir '/usr/share/fonts/'
1899 =item B<GalleryTTFFile>
1901 To use the GalleryCopyrightText feature this option must be set to the
1902 name of the True Type font you wish to use. Example:
1904 PerlSetVar GalleryTTFFile 'verdanab.ttf'
1906 =item B<GalleryTTFSize>
1908 Configure the size of the CopyrightText that will be inserted as
1909 copyright notice in the corner of your pictures.
1913 PerlSetVar GalleryTTFSize '10'
1915 =item B<GalleryCopyrightText>
1917 The text that will be inserted as copyright notice.
1921 PerlSetVar GalleryCopyrightText '(c) Michael Legart'
1923 =item B<GalleryCopyrightColor>
1925 The text color of your copyright notice.
1930 PerlSetVar GalleryCopyrightColor '255,255,255,255'
1933 PerlSetVar GalleryCopyrightColor '0,0,0,255'
1936 PerlSetVar GalleryCopyrightColor '255,0,0,255'
1939 PerlSetVar GalleryCopyrightColor '0,255,0,255'
1942 PerlSetVar GalleryCopyrightColor '0,0,255,255'
1945 PerlSetVar GalleryCopyrightColor '255,127,0,127'
1947 =item B<GalleryCopyrightBackgroundColor>
1949 The background-color of a GalleryCopyrightText
1951 r,g,b,a - for examples, see GalleryCopyrightColor
1953 =item B<GalleryQuality>
1955 The quality (1-100) of scaled images
1957 This setting affects the quality of the scaled images.
1958 Set this to a low number to reduce the size of the scaled images.
1959 Remember to clear out your cache if you change this setting.
1960 Quality seems to default to 75, at least in the jpeg and png loader code in
1966 PerlSetVar GalleryQuality '50'
1968 =item B<GalleryUnderscoresToSpaces>
1970 Set this option to 1 to convert underscores to spaces in the listing
1971 of directory and file names, as well as in the alt attribute for HTML
1978 =item B<GalleryCommentExifKey>
1980 Set this option to e.g. ImageDescription to use this field as comments
1983 =item B<GalleryEnableMediaRss>
1985 Set this option to 1 to enable generation of a media RSS feed. This
1986 can be used e.g. together with the PicLens plugin from http://piclens.com
1994 =item B<Rotate images>
1996 Some cameras, like the Canon G3, detects the orientation of a picture
1997 and adds this info to the EXIF header. Apache::Gallery detects this
1998 and automatically rotates images with this info.
2000 If your camera does not support this, you can rotate the images
2001 manually, This can also be used to override the rotate information
2002 from a camera that supports that. You can also disable this behavior
2003 with the GalleryAutoRotate option.
2005 To use this functionality you have to create file with the name of the
2006 picture you want rotated appended with ".rotate". The file should include
2007 a number where these numbers are supported:
2009 "1", rotates clockwise by 90 degree
2010 "2", rotates clockwise by 180 degrees
2011 "3", rotates clockwise by 270 degrees
2013 So if we want to rotate "Picture1234.jpg" 90 degrees clockwise we would
2014 create a file in the same directory called "Picture1234.jpg.rotate" with
2015 the number 1 inside of it.
2017 =item B<Ignore directories/files>
2019 To ignore a directory or a file (of any kind, not only images) you
2020 create a <directory|file>.ignore file.
2024 To include comments for a directory you create a <directory>.comment
2025 file where the first line can contain "TITLE: New title" which
2026 will be the title of the page, and a comment on the following
2028 To include comments for each picture you create files called
2029 picture.jpg.comment where the first line can contain "TITLE: New
2030 title" which will be the title of the page, and a comment on the
2035 TITLE: This is the new title of the page
2036 And this is the comment.<br />
2037 And this is line two of the comment.
2039 The visible name of the folder is by default identical to the name of
2040 the folder, but can be changed by creating a file <directory>.folder
2041 with the visible name of the folder.
2043 Similarly, the visible name of any file is by default identical to the
2044 name of the file, but can be changed by creating a file <file>.file
2045 with the visible name of the file.
2047 It is also possible to set GalleryCommentExifKey to the name of an EXIF
2048 field containing the comment, e.g. ImageDescription. The EXIF comment is
2049 overridden by the .comment file if it exists.
2059 =item B<Apache with mod_perl>
2061 =item B<URI::Escape>
2063 =item B<Image::Info>
2065 =item B<Image::Size>
2067 =item B<Text::Template>
2069 =item B<Image::Imlib2>
2071 =item B<X11 libraries>
2075 Remember the -dev package when using rpm, deb or other package formats!
2081 Michael Legart <michael@legart.dk>
2083 =head1 COPYRIGHT AND LICENSE
2085 Copyright (C) 2001-2011 Michael Legart <michael@legart.dk>
2087 Templates designed by Thomas Kjaer <tk@lnx.dk>
2089 Apache::Gallery is free software and is released under the Artistic License.
2090 See B<http://www.perl.com/language/misc/Artistic.html> for details.
2092 The video icons are from the GNOME project. B<http://www.gnome.org/>
2096 Thanks to Thomas Kjaer for templates and design of B<http://apachegallery.dk>
2097 Thanks to Thomas Eibner and other for patches. (See the Changes file)
2101 L<perl>, L<mod_perl>, L<Image::Imlib2>, L<CGI::FastTemplate>,
2102 L<Image::Info>, and L<Image::Size>.