1 package Apache::Gallery;
3 # $Author: mil $ $Rev: 335 $
4 # $Date: 2011-06-08 20:47:46 +0200 (Wed, 08 Jun 2011) $
14 if (exists($ENV{MOD_PERL_API_VERSION})
15 and ($ENV{MOD_PERL_API_VERSION}==2)) {
17 if ($mod_perl::VERSION >= 1.99 && $mod_perl::VERSION < 2.0) {
18 die "mod_perl 2.0.0 or later is now required";
20 require Apache2::ServerRec;
21 require Apache2::RequestRec;
24 require Apache2::RequestIO;
25 require Apache2::SubRequest;
26 require Apache2::Const;
28 Apache2::Const->import(-compile => 'OK','DECLINED','FORBIDDEN','NOT_FOUND','HTTP_NOT_MODIFIED');
35 require Apache::Constants;
36 require Apache::Request;
38 Apache::Constants->import('OK','DECLINED','FORBIDDEN','NOT_FOUND');
43 use Image::Info qw(image_info);
44 use Image::Size qw(imgsize);
55 use Digest::MD5 qw(md5_base64);
59 # Regexp for escaping URI's
60 my $escape_rule = "^A-Za-z0-9\-_.!~*'()\/";
65 my $r = shift or Apache2::RequestUtil->request();
67 unless (($r->method eq 'HEAD') or ($r->method eq 'GET')) {
68 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
71 if ((not $memoized) and ($r->dir_config('GalleryMemoize'))) {
73 Memoize::memoize('get_imageinfo');
77 $r->headers_out->{"X-Powered-By"} = "apachegallery.dk $VERSION - Hest design!";
78 $r->headers_out->{"X-Gallery-Version"} = '$Rev: 335 $ $Date: 2011-06-08 20:47:46 +0200 (Wed, 08 Jun 2011) $';
80 my $filename = $r->filename;
82 my $topdir = $filename;
84 my $media_rss_enabled = $r->dir_config('GalleryEnableMediaRss');
86 # Just return the http headers if the client requested that
87 if ($r->header_only) {
93 if (-f $filename or -d $filename) {
94 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
97 return $::MP2 ? Apache2::Const::NOT_FOUND() : Apache::Constants::NOT_FOUND();
103 # Handle selected images
104 if ($cgi->param('selection')) {
105 my @selected = $cgi->param('selection');
106 my $content = join "<br />\n",@selected;
107 $r->content_type('text/html');
108 $r->headers_out->{'Content-Length'} = length($content);
111 $r->send_http_header;
115 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
118 # Selectmode providing checkboxes beside all thumbnails
119 my $select_mode = $cgi->param('select');
121 # Let Apache serve icons without us modifying the request
122 if ($r->uri =~ m/^\/icons/i) {
123 if ($r->uri =~ m/^\/icons\/gallery\/([^\/]+$)/i) {
124 $filename = "/usr/share/libapache-gallery-perl/icons/$filename";
125 $r->filename($filename);
127 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
129 # Lookup the file in the cache and scale the image if the cached
130 # image does not exist
131 if ($r->uri =~ m/\.cache\//i) {
133 my $filename = $r->filename().$r->path_info();
134 $filename =~ s/\.cache//;
136 $filename =~ m/\/(\d+)x(\d+)\-/;
137 my $image_width = $1;
138 my $image_height = $2;
140 $filename =~ s/\/(\d+)x(\d+)\-//;
142 my ($width, $height, $type) = imgsize($filename);
144 my $imageinfo = get_imageinfo($r, $filename, $type, $width, $height);
146 my $cached = scale_picture($r, $filename, $image_width, $image_height, $imageinfo);
148 my $file = cache_dir($r, 0);
149 $file =~ s/\.cache//;
151 my $subr = $r->lookup_file($file);
152 $r->content_type($subr->content_type());
155 my $fileinfo = stat($file);
157 my $nonce = md5_base64($fileinfo->ino.$fileinfo->mtime);
158 if ($r->headers_in->{"If-None-Match"} eq $nonce) {
159 return Apache2::Const::HTTP_NOT_MODIFIED();
162 if ($r->headers_in->{"If-Modified-Since"} && str2time($r->headers_in->{"If-Modified-Since"}) < $fileinfo->mtime) {
163 return Apache2::Const::HTTP_NOT_MODIFIED();
166 $r->headers_out->{"Content-Length"} = $fileinfo->size;
167 $r->headers_out->{"Last-Modified-Date"} = time2str($fileinfo->mtime);
168 $r->headers_out->{"ETag"} = $nonce;
170 return Apache2::Const::OK();
175 return Apache::Constants::DECLINED();
183 unless (-f $filename or -d $filename) {
184 show_error($r, 404, "404!", "No such file or directory: ".uri_escape($r->uri, $escape_rule));
185 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
188 my $doc_pattern = $r->dir_config('GalleryDocFile');
189 unless ($doc_pattern) {
190 $doc_pattern = '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|mp4|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
192 my $img_pattern = $r->dir_config('GalleryImgFile') unless ($img_pattern) {
193 $img_pattern = '\.(jpe?g|png|tiff?|ppm)$'
196 # Let Apache serve files we don't know how to handle anyway
197 if (-f $filename && $filename !~ m/$img_pattern/i) {
198 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
203 unless (-d cache_dir($r, 0)) {
204 unless (create_cache($r, cache_dir($r, 0))) {
205 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
209 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
211 # Instead of reading the templates every single time
212 # we need them, create a hash of template names and
213 # the associated Text::Template objects.
214 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
215 index => "$tpl_dir/index.tpl",
216 directory => "$tpl_dir/directory.tpl",
217 picture => "$tpl_dir/picture.tpl",
218 file => "$tpl_dir/file.tpl",
219 comment => "$tpl_dir/dircomment.tpl",
220 nocomment => "$tpl_dir/nodircomment.tpl",
221 rss => "$tpl_dir/rss.tpl",
222 rss_item => "$tpl_dir/rss_item.tpl",
223 navdirectory => "$tpl_dir/navdirectory.tpl",
231 $tpl_vars{TITLE} = "Index of: $uri";
233 if ($media_rss_enabled) {
234 # Put the RSS feed on all directory listings
235 $tpl_vars{META} = '<link rel="alternate" href="?rss=1" type="application/rss+xml" title="" id="gallery" />';
238 unless (opendir (DIR, $filename)) {
239 show_error ($r, 500, $!, "Unable to access directory $filename: $!");
240 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
243 $tpl_vars{MENU} = generate_menu($r);
245 $tpl_vars{FORM_BEGIN} = $select_mode?'<form method="post">':'';
246 $tpl_vars{FORM_END} = $select_mode?'<input type="submit" name="Get list" value="Get list"></form>':'';
248 # Read, sort, and filter files
249 my @files = grep { !/^\./ && -f "$filename/$_" } readdir (DIR);
251 @files=gallerysort($r, @files);
253 my @downloadable_files;
256 # Remove unwanted files from list
258 foreach my $picture (@files) {
260 my $file = $topdir."/".$picture;
262 if ($file =~ /$img_pattern/i) {
263 push (@new_files, $picture);
266 if ($file =~ /$doc_pattern/i) {
267 push (@downloadable_files, $picture);
274 # Read and sort directories
276 my @directories = grep { !/^\./ && -d "$filename/$_" } readdir (DIR);
278 if (defined($r->dir_config('GalleryDirSortBy'))) {
279 $dirsortby=$r->dir_config('GalleryDirSortBy');
281 $dirsortby=$r->dir_config('GallerySortBy');
283 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
284 @directories = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$dirsortby()." $_", @directories));
286 @directories = sort @directories;
292 # Combine directories and files to one listing
294 push (@listing, @directories);
295 push (@listing, @files);
296 push (@listing, @downloadable_files);
302 my $file_counter = 0;
304 my $max_files = $r->dir_config('GalleryMaxThumbnailsPerPage');
306 if (defined($cgi->param('start'))) {
307 $start_at = $cgi->param('start');
313 my $browse_links = "";
314 if (defined($max_files)) {
316 for (my $i=1; $i<=scalar(@listing); $i++) {
320 my $to = $i+$max_files-1;
321 if ($to > scalar(@listing)) {
322 $to = scalar(@listing);
325 if ($start_at < $from || $start_at > $to) {
326 $browse_links .= "<a href=\"?start=$from\">$from - ".$to."</a> ";
329 $browse_links .= "$from - $to ";
338 $tpl_vars{BROWSELINKS} = $browse_links;
341 foreach my $file (@listing) {
345 if ($file_counter < $start_at) {
349 if (defined($max_files) && $file_counter > $max_files+$start_at-1) {
353 my $thumbfilename = $topdir."/".$file;
355 my $fileurl = $uri."/".$file;
357 # Debian bug #619625 <http://bugs.debian.org/619625>
358 if (-d $thumbfilename && ! -e $thumbfilename . ".ignore") {
360 if (-e $thumbfilename . ".folder") {
361 $dirtitle = get_filecontent($thumbfilename . ".folder");
364 $dirtitle = $dirtitle ? $dirtitle : $file;
365 $dirtitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
368 $templates{directory}->fill_in(HASH=> {FILEURL => uri_escape($fileurl, $escape_rule),
374 # Debian bug #619625 <http://bugs.debian.org/619625>
375 elsif (-f $thumbfilename && $thumbfilename =~ /$doc_pattern/i && $thumbfilename !~ /$img_pattern/i && ! -e $thumbfilename . ".ignore") {
377 my $stat = stat($thumbfilename);
378 my $size = $stat->size;
381 if ($thumbfilename =~ m/\.(mpe?g|avi|mov|asf|wmv)$/i) {
382 $filetype = "video-$type";
383 } elsif ($thumbfilename =~ m/\.(txt|html?)$/i) {
384 $filetype = "text-$type";
385 } elsif ($thumbfilename =~ m/\.(mp3|ogg|wav)$/i) {
386 $filetype = "sound-$type";
387 } elsif ($thumbfilename =~ m/$doc_pattern/i) {
388 $filetype = "application-$type";
390 $filetype = "unknown";
393 # Debian bug #348724 <http://bugs.debian.org/348724>
395 my $filetitle = $file;
396 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
399 $templates{file}->fill_in(HASH => {%tpl_vars,
400 FILEURL => uri_escape($fileurl, $escape_rule),
401 ALT => "Size: $size Bytes",
404 FILETYPE => $filetype,
408 # Debian bug #619625 <http://bugs.debian.org/619625>
409 elsif (-f $thumbfilename && ! -e $thumbfilename . ".ignore") {
411 my ($width, $height, $type) = imgsize($thumbfilename);
412 next if $type eq 'Data stream is not a known image file format';
414 my @filetypes = qw(JPG TIF PNG PPM GIF);
416 next unless (grep $type eq $_, @filetypes);
417 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);
418 my $imageinfo = get_imageinfo($r, $thumbfilename, $type, $width, $height);
419 my $cached = get_scaled_picture_name($thumbfilename, $thumbnailwidth, $thumbnailheight);
421 my $rotate = readfile_getnum($r, $imageinfo, $thumbfilename.".rotate");
423 # Debian bug #348724 <http://bugs.debian.org/348724>
424 # HTML <img> tag, alt attribute
425 my $filetitle = $file;
426 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
428 my %file_vars = (FILEURL => uri_escape($fileurl, $escape_rule),
430 DATE => $imageinfo->{DateTimeOriginal} ? $imageinfo->{DateTimeOriginal} : '', # should this really be a stat of the file instead of ''?
431 SRC => uri_escape($uri."/.cache/$cached", $escape_rule),
432 HEIGHT => (grep($rotate==$_, (1, 3)) ? $thumbnailwidth : $thumbnailheight),
433 WIDTH => (grep($rotate==$_, (1, 3)) ? $thumbnailheight : $thumbnailwidth),
434 SELECT => $select_mode?'<input type="checkbox" name="selection" value="'.$file.'"> ':'',);
435 $tpl_vars{FILES} .= $templates{picture}->fill_in(HASH => {%tpl_vars,
440 if ($media_rss_enabled) {
441 my ($content_image_width, undef, $content_image_height) = get_image_display_size($cgi, $r, $width, $height);
443 THUMBNAIL => uri_escape($uri."/.cache/$cached", $escape_rule),
444 LINK => uri_escape($fileurl, $escape_rule),
446 CONTENT => uri_escape($uri."/.cache/".$content_image_width."x".$content_image_height."-".$file, $escape_rule)
448 $tpl_vars{ITEMS} .= $templates{rss_item}->fill_in(HASH => {
456 $tpl_vars{FILES} = "No files found";
457 $tpl_vars{BROWSELINKS} = "";
460 # Generate prev and next directory menu items
461 $filename =~ m/(.*)\/.*?$/;
462 my $parent_filename = $1;
464 $r->document_root =~ m/(.*)\/$/;
466 print STDERR "$filename vs $root_path\n";
467 if ($filename ne $root_path) {
468 unless (opendir (PARENT_DIR, $parent_filename)) {
469 show_error ($r, 500, $!, "Unable to access parent directory $parent_filename: $!");
470 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
473 # Debian bug #619625 <http://bugs.debian.org/619625>
474 my @neighbour_directories = grep { !/^\./ && -d "$parent_filename/$_" && ! -e "$parent_filename/$_" . ".ignore" } readdir (PARENT_DIR);
476 if (defined($r->dir_config('GalleryDirSortBy'))) {
477 $dirsortby=$r->dir_config('GalleryDirSortBy');
479 $dirsortby=$r->dir_config('GallerySortBy');
481 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
482 @neighbour_directories = map(/^\d+ (.*)/, sort map(stat("$parent_filename/$_")->$dirsortby()." $_", @neighbour_directories));
484 @neighbour_directories = sort @neighbour_directories;
487 closedir(PARENT_DIR);
489 my $neightbour_counter = 0;
490 foreach my $neighbour_directory (@neighbour_directories) {
491 if ($parent_filename.'/'.$neighbour_directory eq $filename) {
492 if ($neightbour_counter > 0) {
493 print STDERR "prev directory is " .$neighbour_directories[$neightbour_counter-1] ."\n";
494 my $linktext = $neighbour_directories[$neightbour_counter-1];
495 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
496 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder");
499 URL => "../".$neighbour_directories[$neightbour_counter-1],
500 LINK_NAME => "<<< $linktext",
503 $tpl_vars{PREV_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
504 print STDERR $tpl_vars{PREV_DIR_FILES} ."\n";
507 if ($neightbour_counter < scalar @neighbour_directories - 1) {
508 my $linktext = $neighbour_directories[$neightbour_counter+1];
509 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder") {
510 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder");
513 URL => "../".$neighbour_directories[$neightbour_counter+1],
514 LINK_NAME => "$linktext >>>",
517 $tpl_vars{NEXT_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
518 print STDERR "next directory is " .$neighbour_directories[$neightbour_counter+1] ."\n";
521 $neightbour_counter++;
525 if (-f $topdir . '.comment') {
526 my $comment_ref = get_comment($topdir . '.comment');
528 $comment_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
529 $comment_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
530 $tpl_vars{DIRCOMMENT} = $templates{comment}->fill_in(HASH => \%comment_vars);
531 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
533 $tpl_vars{DIRCOMMENT} = $templates{nocomment}->fill_in(HASH=>\%tpl_vars);
536 if ($cgi->param('rss')) {
537 $tpl_vars{MAIN} = $templates{rss}->fill_in(HASH => \%tpl_vars);
538 $r->content_type('application/rss+xml');
540 $tpl_vars{MAIN} = $templates{index}->fill_in(HASH => \%tpl_vars);
541 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
542 $r->content_type('text/html');
545 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
548 $r->send_http_header;
551 $r->print($tpl_vars{MAIN});
552 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
558 if (defined($ENV{QUERY_STRING}) && $ENV{QUERY_STRING} eq 'orig') {
559 if ($r->dir_config('GalleryAllowOriginal') ? 1 : 0) {
560 $r->filename($filename);
561 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
563 return $::MP2 ? Apache2::Const::FORBIDDEN() : Apache::Constants::FORBIDDEN();
567 # Create cache dir if not existing
568 my @tmp = split (/\//, $filename);
569 my $picfilename = pop @tmp;
570 my $path = (join "/", @tmp)."/";
571 my $cache_path = cache_dir($r, 1);
573 unless (-d $cache_path) {
574 unless (create_cache($r, $cache_path)) {
575 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
579 my ($orig_width, $orig_height, $type) = imgsize($filename);
581 my $imageinfo = get_imageinfo($r, $filename, $type, $orig_width, $orig_height);
583 my ($image_width, $width, $height, $original_size) = get_image_display_size($cgi, $r, $orig_width, $orig_height);
585 my $cached = get_scaled_picture_name($filename, $image_width, $height);
587 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
589 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
590 picture => "$tpl_dir/showpicture.tpl",
591 navpicture => "$tpl_dir/navpicture.tpl",
592 info => "$tpl_dir/info.tpl",
593 scale => "$tpl_dir/scale.tpl",
594 scaleactive => "$tpl_dir/scaleactive.tpl",
595 orig => "$tpl_dir/orig.tpl",
596 refresh => "$tpl_dir/refresh.tpl",
597 interval => "$tpl_dir/interval.tpl",
598 intervalactive => "$tpl_dir/intervalactive.tpl",
599 slideshowisoff => "$tpl_dir/slideshowisoff.tpl",
600 slideshowoff => "$tpl_dir/slideshowoff.tpl",
601 pictureinfo => "$tpl_dir/pictureinfo.tpl",
602 nopictureinfo => "$tpl_dir/nopictureinfo.tpl",
607 my $resolution = (($image_width > $orig_width) && ($height > $orig_height)) ?
608 "$orig_width x $orig_height" : "$image_width x $height";
610 $tpl_vars{TITLE} = "Viewing ".$r->uri()." at $image_width x $height";
611 $tpl_vars{META} = " ";
612 $tpl_vars{RESOLUTION} = $resolution;
613 $tpl_vars{MENU} = generate_menu($r);
614 $tpl_vars{SRC} = uri_escape(".cache/$cached", $escape_rule);
615 $tpl_vars{URI} = $r->uri();
617 my $exif_mode = $r->dir_config('GalleryEXIFMode');
618 unless ($exif_mode) {
619 $exif_mode = 'namevalue';
622 unless (opendir(DATADIR, $path)) {
623 show_error($r, 500, "Unable to access directory", "Unable to access directory $path");
624 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
626 my @pictures = grep { /$img_pattern/i && ! -e "$path/$_" . ".ignore" } readdir (DATADIR);
628 @pictures = gallerysort($r, @pictures);
630 $tpl_vars{TOTAL} = scalar @pictures;
635 for (my $i=0; $i <= $#pictures; $i++) {
636 if ($pictures[$i] eq $picfilename) {
638 $tpl_vars{NUMBER} = $i+1;
640 $prevpicture = $pictures[$i-1];
641 my $displayprev = ($i>0 ? 1 : 0);
643 if ($r->dir_config("GalleryWrapNavigation")) {
644 $prevpicture = $pictures[$i>0 ? $i-1 : $#pictures];
647 if ($prevpicture and $displayprev) {
648 my ($orig_width, $orig_height, $type) = imgsize($path.$prevpicture);
649 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
650 my $imageinfo = get_imageinfo($r, $path.$prevpicture, $type, $orig_width, $orig_height);
651 my $cached = get_scaled_picture_name($path.$prevpicture, $thumbnailwidth, $thumbnailheight);
653 $nav_vars{URL} = uri_escape($prevpicture, $escape_rule);
654 $nav_vars{FILENAME} = $prevpicture;
655 $nav_vars{WIDTH} = $width;
656 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
657 $nav_vars{DIRECTION} = "« <u>p</u>rev";
658 $nav_vars{ACCESSKEY} = "P";
659 $tpl_vars{BACK} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
662 $tpl_vars{BACK} = " ";
665 $nextpicture = $pictures[$i+1];
666 if ($r->dir_config("GalleryWrapNavigation")) {
667 $nextpicture = $pictures[$i == $#pictures ? 0 : $i+1];
671 my ($orig_width, $orig_height, $type) = imgsize($path.$nextpicture);
672 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
673 my $imageinfo = get_imageinfo($r, $path.$nextpicture, $type, $thumbnailwidth, $thumbnailheight);
674 my $cached = get_scaled_picture_name($path.$nextpicture, $thumbnailwidth, $thumbnailheight);
676 $nav_vars{URL} = uri_escape($nextpicture, $escape_rule);
677 $nav_vars{FILENAME} = $nextpicture;
678 $nav_vars{WIDTH} = $width;
679 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
680 $nav_vars{DIRECTION} = "<u>n</u>ext »";
681 $nav_vars{ACCESSKEY} = "N";
683 $tpl_vars{NEXT} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
684 $tpl_vars{NEXTURL} = uri_escape($nextpicture, $escape_rule);
687 $tpl_vars{NEXT} = " ";
688 $tpl_vars{NEXTURL} = '#';
693 my $foundcomment = 0;
694 if (-f $path . '/' . $picfilename . '.comment') {
695 my $comment_ref = get_comment($path . '/' . $picfilename . '.comment');
697 $tpl_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
698 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
699 } elsif ($r->dir_config('GalleryCommentExifKey')) {
700 my $comment = decode("utf8", $imageinfo->{$r->dir_config('GalleryCommentExifKey')});
701 $tpl_vars{COMMENT} = encode("iso-8859-1", $comment);
703 $tpl_vars{COMMENT} = '';
706 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
711 my ($human_key, $exif_key) = (split " => ")[0,1];
712 my $value = $imageinfo->{$human_key};
713 if (defined($value)) {
717 if ($exif_mode eq 'namevalue') {
719 $info_vars{KEY} = $human_key;
720 $info_vars{VALUE} = $value;
721 $tpl_vars{INFO} .= $templates{info}->fill_in(HASH => \%info_vars);
724 if ($exif_mode eq 'variables') {
725 $tpl_vars{"EXIF_".uc($exif_key)} = $value;
728 if ($exif_mode eq 'values') {
729 $exifvalues .= "| ".$value." ";
736 if ($exif_mode eq 'values') {
737 if (defined($exifvalues)) {
738 $tpl_vars{EXIFVALUES} = $exifvalues;
741 $tpl_vars{EXIFVALUES} = "";
745 if ($foundcomment and !$foundinfo) {
746 $tpl_vars{INFO} = "";
749 if ($exif_mode ne 'namevalue') {
750 $tpl_vars{INFO} = "";
753 if ($exif_mode eq 'namevalue' && $foundinfo or $foundcomment) {
755 $tpl_vars{PICTUREINFO} = $templates{pictureinfo}->fill_in(HASH => \%tpl_vars);
757 unless (defined($exifvalues)) {
758 $tpl_vars{EXIFVALUES} = "";
763 $tpl_vars{PICTUREINFO} = $templates{nopictureinfo}->fill_in(HASH => \%tpl_vars);
766 # Fill in sizes and determine if any are smaller than the
767 # actual image. If they are, $scaleable=1
769 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
770 foreach my $size (@sizes) {
771 if ($size<=$original_size) {
773 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
774 $sizes_vars{SIZE} = $size;
775 $sizes_vars{WIDTH} = $size;
776 if ($width == $size) {
777 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
780 $tpl_vars{SIZES} .= $templates{scale}->fill_in(HASH => \%sizes_vars);
786 unless ($scaleable) {
788 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
789 $sizes_vars{SIZE} = $original_size;
790 $sizes_vars{WIDTH} = $original_size;
791 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
794 $tpl_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
796 if ($r->dir_config('GalleryAllowOriginal')) {
797 $tpl_vars{SIZES} .= $templates{orig}->fill_in(HASH => \%tpl_vars);
800 my @slideshow_intervals = split (/ /, $r->dir_config('GallerySlideshowIntervals') ? $r->dir_config('GallerySlideshowIntervals') : '3 5 10 15 30');
801 foreach my $interval (@slideshow_intervals) {
804 $slideshow_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
805 $slideshow_vars{SECONDS} = $interval;
806 $slideshow_vars{WIDTH} = ($width > $height ? $width : $height);
808 if ($cgi->param('slideshow') && $cgi->param('slideshow') == $interval and $nextpicture) {
809 $tpl_vars{SLIDESHOW} .= $templates{intervalactive}->fill_in(HASH => \%slideshow_vars);
813 $tpl_vars{SLIDESHOW} .= $templates{interval}->fill_in(HASH => \%slideshow_vars);
818 if ($cgi->param('slideshow') and $nextpicture) {
820 $tpl_vars{SLIDESHOW} .= $templates{slideshowoff}->fill_in(HASH => \%tpl_vars);
822 unless ((grep $cgi->param('slideshow') == $_, @slideshow_intervals)) {
823 show_error($r, 200, "Invalid interval", "Invalid slideshow interval choosen");
824 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
827 $tpl_vars{URL} = uri_escape($nextpicture, $escape_rule);
828 $tpl_vars{WIDTH} = ($width > $height ? $width : $height);
829 $tpl_vars{INTERVAL} = $cgi->param('slideshow');
830 $tpl_vars{META} .= $templates{refresh}->fill_in(HASH => \%tpl_vars);
834 $tpl_vars{SLIDESHOW} .= $templates{slideshowisoff}->fill_in(HASH => \%tpl_vars);
837 $tpl_vars{MAIN} = $templates{picture}->fill_in(HASH => \%tpl_vars);
838 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
840 $r->content_type('text/html');
841 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
844 $r->send_http_header;
847 $r->print($tpl_vars{MAIN});
848 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
856 my ($r, $strip_filename) = @_;
860 unless ($r->dir_config('GalleryCacheDir')) {
862 $cache_root = '/var/cache/www/';
863 if ($r->server->is_virtual) {
864 $cache_root = File::Spec->catdir($cache_root, $r->server->server_hostname);
866 $cache_root = File::Spec->catdir($cache_root, $r->location);
871 $cache_root = $r->dir_config('GalleryCacheDir');
875 # If the uri contains .cache we need to remove it
879 my (undef, $dirs, $filename) = File::Spec->splitpath($uri);
880 # We don't need a volume as this is a relative path
882 if ($strip_filename) {
883 return(File::Spec->canonpath(File::Spec->catdir($cache_root, $dirs)));
885 return(File::Spec->canonpath(File::Spec->catfile($cache_root, $dirs, $filename)));
893 unless (mkdirhier ($path)) {
894 show_error($r, 500, $!, "Unable to create cache directory in $path: $!");
907 unless (mkdir($dir, 0755)) {
909 $parent =~ s/\/[^\/]*$//;
918 sub get_scaled_picture_name {
920 my ($fullpath, $width, $height) = @_;
922 my (undef, undef, $type) = imgsize($fullpath);
924 my @dirs = split(/\//, $fullpath);
925 my $filename = pop(@dirs);
928 if (grep $type eq $_, qw(PPM TIF GIF)) {
929 $newfilename = $width."x".$height."-".$filename;
930 # needs to be configurable
931 $newfilename =~ s/\.(\w+)$/-$1\.jpg/;
933 $newfilename = $width."x".$height."-".$filename;
942 my ($r, $fullpath, $width, $height, $imageinfo) = @_;
944 my @dirs = split(/\//, $fullpath);
945 my $filename = pop(@dirs);
947 my ($orig_width, $orig_height, $type) = imgsize($fullpath);
949 my $cache = cache_dir($r, 1);
951 my $newfilename = get_scaled_picture_name($fullpath, $width, $height);
953 if (($width > $orig_width) && ($height > $orig_height)) {
954 # Run it through the resize code anyway to get watermarks
955 $width = $orig_width;
956 $height = $orig_height;
959 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
961 # Do we want to generate a new file in the cache?
964 if (-f $cache."/".$newfilename) {
967 # Check to see if the image has changed
968 my $filestat = stat($fullpath);
969 my $cachestat = stat($cache."/".$newfilename);
970 if ($filestat->mtime >= $cachestat->mtime) {
974 # Check to see if the .rotate file has been added or changed
975 if (-f $fullpath . ".rotate") {
976 my $rotatestat = stat($fullpath . ".rotate");
977 if ($rotatestat->mtime > $cachestat->mtime) {
981 # Check to see if the copyrightimage has been added or changed
982 if ($r->dir_config('GalleryCopyrightImage') && -f $r->dir_config('GalleryCopyrightImage')) {
983 unless ($width == $thumbnailwidth or $width == $thumbnailheight) {
984 my $copyrightstat = stat($r->dir_config('GalleryCopyrightImage'));
985 if ($copyrightstat->mtime > $cachestat->mtime) {
995 my $newpath = $cache."/".$newfilename;
996 my $rotate = readfile_getnum($r, $imageinfo, $fullpath . ".rotate");
997 my $quality = $r->dir_config('GalleryQuality');
999 if ($width == $thumbnailwidth or $width == $thumbnailheight) {
1001 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, '', '', '', '', '', '');
1005 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate,
1006 ($r->dir_config('GalleryCopyrightImage') ? $r->dir_config('GalleryCopyrightImage') : ''),
1007 ($r->dir_config('GalleryTTFDir') ? $r->dir_config('GalleryTTFDir') : ''),
1008 ($r->dir_config('GalleryCopyrightText') ? $r->dir_config('GalleryCopyrightText') : ''),
1009 ($r->dir_config('GalleryCopyrightColor') ? $r->dir_config('GalleryCopyrightColor') : ''),
1010 ($r->dir_config('GalleryTTFFile') ? $r->dir_config('GalleryTTFFile') : ''),
1011 ($r->dir_config('GalleryTTFSize') ? $r->dir_config('GalleryTTFSize') : ''),
1012 ($r->dir_config('GalleryCopyrightBackgroundColor') ? $r->dir_config('GalleryCopyrightBackgroundColor') : ''),
1018 return $newfilename;
1022 sub get_thumbnailsize {
1023 my ($r, $orig_width, $orig_height) = @_;
1025 my $gallerythumbnailsize=$r->dir_config('GalleryThumbnailSize');
1027 if (defined($gallerythumbnailsize)) {
1028 warn("Invalid setting for GalleryThumbnailSize") unless
1029 $gallerythumbnailsize =~ /^\s*\d+\s*x\s*\d+\s*$/i;
1032 my ($thumbnailwidth, $thumbnailheight) = split(/x/i, ($gallerythumbnailsize) ? $gallerythumbnailsize : "100x75");
1034 my $width = $thumbnailwidth;
1035 my $height = $thumbnailheight;
1037 # If the image is rotated, flip everything around.
1038 if (defined $r->dir_config('GalleryThumbnailSizeLS')
1039 and $r->dir_config('GalleryThumbnailSizeLS') eq '1'
1040 and $orig_width < $orig_height) {
1042 $width = $thumbnailheight;
1043 $height = $thumbnailwidth;
1046 my $scale = ($orig_width ? $width/$orig_width : 1);
1049 if ($orig_height * $scale > $thumbnailheight) {
1050 $scale = $height/$orig_height;
1051 $width = $orig_width * $scale;
1055 $height = $orig_height * $scale;
1057 $height = floor($height);
1058 $width = floor($width);
1060 return ($width, $height);
1063 sub get_image_display_size {
1064 my ($cgi, $r, $orig_width, $orig_height) = @_;
1066 my $width = $orig_width;
1068 my $original_size=$orig_height;
1069 if ($orig_width>$orig_height) {
1070 $original_size=$orig_width;
1073 # Check if the selected width is allowed
1074 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
1076 my %cookies = fetch CGI::Cookie;
1078 if ($cgi->param('width')) {
1079 unless ((grep $cgi->param('width') == $_, @sizes) or ($cgi->param('width') == $original_size)) {
1080 show_error($r, 200, "Invalid width", "The specified width is invalid");
1081 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
1084 $width = $cgi->param('width');
1085 my $cookie = new CGI::Cookie(-name => 'GallerySize', -value => $width, -expires => '+6M');
1086 $r->headers_out->{'Set-Cookie'} = $cookie;
1088 } elsif ($cookies{'GallerySize'} && (grep $cookies{'GallerySize'}->value == $_, @sizes)) {
1090 $width = $cookies{'GallerySize'}->value;
1098 if ($orig_width<$orig_height) {
1099 $scale = ($orig_height ? $width/$orig_height: 1);
1100 $image_width=$width*$orig_width/$orig_height;
1103 $scale = ($orig_width ? $width/$orig_width : 1);
1104 $image_width = $width;
1107 my $height = $orig_height * $scale;
1109 $image_width = floor($image_width);
1110 $width = floor($width);
1111 $height = floor($height);
1113 return ($image_width, $width, $height, $original_size);
1117 my ($r, $file, $type, $width, $height) = @_;
1119 if ($type eq 'Data stream is not a known image file format') {
1120 # should never be reached, this is supposed to be handled outside of here
1121 log_error("Something was fishy with the type of the file $file\n");
1124 # Some files, like TIFF, PNG, GIF do not have EXIF info
1125 # embedded but use .thm files instead.
1126 $imageinfo = get_imageinfo_from_thm_file($file, $width, $height);
1128 # If there is no .thm file and our file is a JPEG file we try to extract the EXIf
1129 # info using Image::Info
1130 unless (defined($imageinfo) && (grep $type eq $_, qw(JPG))) {
1131 # Only for files that natively keep the EXIF info in the same file
1132 $imageinfo = image_info($file);
1136 unless (defined($imageinfo->{width}) and defined($imageinfo->{height})) {
1137 $imageinfo->{width} = $width;
1138 $imageinfo->{height} = $height;
1141 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
1144 my ($human_key, $exif_key) = (split " => ")[0,1];
1145 if (defined($exif_key) && defined($imageinfo->{$exif_key})) {
1147 if (ref($imageinfo->{$exif_key}) eq 'Image::TIFF::Rational') {
1148 $value = $imageinfo->{$exif_key}->as_string;
1150 elsif (ref($imageinfo->{$exif_key}) eq 'ARRAY') {
1151 foreach my $element (@{$imageinfo->{$exif_key}}) {
1152 if (ref($element) eq 'ARRAY') {
1153 foreach (@{$element}) {
1157 elsif (ref($element) eq 'HASH') {
1158 $value .= "<br />{ ";
1159 foreach (sort keys %{$element}) {
1160 $value .= "$_ = " . $element->{$_} . ' ';
1171 my $exif_value = $imageinfo->{$exif_key};
1172 if ($human_key eq 'Flash' && $exif_value =~ m/\d/) {
1177 "16" => "No (Compulsory) Should be External Flash",
1178 "17" => "Yes (External)",
1180 "25" => "Yes (Auto)",
1181 "73" => "Yes (Compulsory, Red Eye Reducing)",
1182 "89" => "Yes (Auto, Red Eye Reducing)"
1184 $exif_value = defined $flashmodes{$exif_value} ? $flashmodes{$exif_value} : 'unknown flash mode';
1186 $value = $exif_value;
1188 if ($exif_key eq 'MeteringMode') {
1189 my $exif_value = $imageinfo->{$exif_key};
1190 if ($exif_value =~ /^\d+$/) {
1191 my %meteringmodes = (
1194 '2' => 'CenterWeightedAverage',
1201 $exif_value = defined $meteringmodes{$exif_value} ? $meteringmodes{$exif_value} : 'unknown metering mode';
1203 $value = $exif_value;
1206 if ($exif_key eq 'LightSource') {
1207 my $exif_value = $imageinfo->{$exif_key};
1208 if ($exif_value =~ /^\d+$/) {
1209 my %lightsources = (
1212 '2' => 'Fluorescent',
1213 '3' => 'Tungsten (incandescent light)',
1215 '9' => 'Fine weather',
1216 '10' => 'Cloudy weather',
1218 '12' => 'Daylight fluorescent',
1219 '13' => 'Day white fluorescent',
1220 '14' => 'Cool white fluorescent',
1221 '15' => 'White fluorescent',
1222 '17' => 'Standard light A',
1223 '18' => 'Standard light B',
1224 '19' => 'Standard light C',
1229 '24' => 'ISO studio tungsten',
1230 '255' => 'other light source'
1232 $exif_value = defined $lightsources{$exif_value} ? $lightsources{$exif_value} : 'unknown light source';
1234 $value = $exif_value;
1236 if ($exif_key eq 'FocalLength') {
1237 if ($value =~ /^(\d+)\/(\d+)$/) {
1238 $value = eval { $1 / $2 };
1242 $value = int($value + 0.5) . "mm";
1247 if ($exif_key eq 'ShutterSpeedValue') {
1248 if ($value =~ /^((?:\-)?\d+)\/(\d+)$/) {
1249 $value = eval { $1 / $2 };
1254 $value = 1/(exp($value*log(2)));
1256 $value = "1/" . (int((1/$value)));
1258 $value = int($value*10)/10;
1264 $value = $value . " sec";
1269 if ($exif_key eq 'ApertureValue') {
1270 if ($value =~ /^(\d+)\/(\d+)$/) {
1271 $value = eval { $1 / $2 };
1275 # poor man's rounding
1276 $value = int(exp($value*log(2)*0.5)*10)/10;
1277 $value = "f" . $value;
1281 if ($exif_key eq 'FNumber') {
1282 if ($value =~ /^(\d+)\/(\d+)$/) {
1283 $value = eval { $1 / $2 };
1287 $value = int($value*10+0.5)/10;
1288 $value = "f" . $value;
1292 $imageinfo->{$human_key} = $value;
1296 if ($r->dir_config('GalleryUseFileDate') &&
1297 ($r->dir_config('GalleryUseFileDate') eq '1'
1298 || !$imageinfo->{"Picture Taken"} )) {
1300 my $st = stat($file);
1301 $imageinfo->{"DateTimeOriginal"} = $imageinfo->{"Picture Taken"} = scalar localtime($st->mtime) if $st;
1307 sub get_imageinfo_from_thm_file {
1309 my ($file, $width, $height) = @_;
1311 my $imageinfo = undef;
1312 # Windows based file extensions are often .THM, so check
1313 # for both .thm and .THM
1314 my $unix_file = $file;
1315 my $windows_file = $file;
1316 $unix_file =~ s/\.(\w+)$/.thm/;
1317 $windows_file =~ s/\.(\w+)$/.THM/;
1319 if (-e $unix_file && -f $unix_file && -r $unix_file) {
1320 $imageinfo = image_info($unix_file);
1321 $imageinfo->{width} = $width;
1322 $imageinfo->{height} = $height;
1324 elsif (-e $windows_file && -f $windows_file && -r $windows_file) {
1325 $imageinfo = image_info($windows_file);
1326 $imageinfo->{width} = $width;
1327 $imageinfo->{height} = $height;
1334 sub readfile_getnum {
1335 my ($r, $imageinfo, $filename) = @_;
1339 print STDERR "orientation: ".$imageinfo->{Orientation}."\n";
1340 # Check to see if the image contains the Orientation EXIF key,
1341 # but allow user to override using rotate
1342 if (!defined($r->dir_config("GalleryAutoRotate"))
1343 || $r->dir_config("GalleryAutoRotate") eq "1") {
1344 if (defined($imageinfo->{Orientation})) {
1345 print STDERR $imageinfo->{Orientation}."\n";
1346 if ($imageinfo->{Orientation} eq 'right_top') {
1349 elsif ($imageinfo->{Orientation} eq 'left_bot') {
1355 if (open(FH, "<$filename")) {
1359 unless ($temp =~ /^\d$/) {
1362 unless ($temp == 1 || $temp == 2 || $temp == 3) {
1371 sub get_filecontent {
1373 open(FH, $file) or return undef;
1384 my $filename = shift;
1385 my $comment_ref = {};
1386 $comment_ref->{TITLE} = undef;
1387 $comment_ref->{COMMENT} = '';
1389 open(FH, $filename) or return $comment_ref;
1391 if ($title =~ m/^TITLE: (.*)$/) {
1392 chomp($comment_ref->{TITLE} = $1);
1395 $comment_ref->{COMMENT} = $title;
1400 $comment_ref->{COMMENT} .= $_;
1404 return $comment_ref;
1409 my ($r, $statuscode, $errortitle, $error) = @_;
1411 my $tpl = $r->dir_config('GalleryTemplateDir');
1413 my %templates = create_templates({layout => "$tpl/layout.tpl",
1414 error => "$tpl/error.tpl",
1418 $tpl_vars{TITLE} = "Error! $errortitle";
1419 $tpl_vars{META} = "";
1420 $tpl_vars{ERRORTITLE} = "Error! $errortitle";
1421 $tpl_vars{ERROR} = $error;
1423 $tpl_vars{MAIN} = $templates{error}->fill_in(HASH => \%tpl_vars);
1425 $tpl_vars{PAGE} = $templates{layout}->fill_in(HASH => \%tpl_vars);
1427 $r->status($statuscode);
1428 $r->content_type('text/html');
1430 $r->print($tpl_vars{PAGE});
1438 my $root_text = (defined($r->dir_config('GalleryRootText')) ? $r->dir_config('GalleryRootText') : "root:" );
1439 my $root_path = (defined($r->dir_config('GalleryRootPath')) ? $r->dir_config('GalleryRootPath') : "" );
1441 my $subr = $r->lookup_uri($r->uri);
1442 my $filename = $subr->filename;
1444 my @links = split (/\//, $r->uri);
1446 $uri =~ s/^$root_path//g;
1448 @links = split (/\//, $uri);
1450 # Get the full path of the base directory
1453 my @direlem = split (/\//, $filename);
1454 for my $i ( 0 .. ( scalar(@direlem) - scalar(@links) ) ) {
1455 $dirname .= shift(@direlem) . '/';
1462 $picturename = pop(@links);
1465 if ($r->uri eq $root_path) {
1466 return qq{ <a href="$root_path">$root_text</a> };
1470 my $menuurl = $root_path;
1471 foreach my $link (@links) {
1473 $menuurl .= $link."/";
1474 my $linktext = $link;
1475 unless (length($link)) {
1476 $linktext = "$root_text ";
1480 $dirname = File::Spec->catdir($dirname, $link);
1482 if (-e $dirname . ".folder") {
1483 $linktext = get_filecontent($dirname . ".folder");
1487 if ("$root_path$uri" eq $menuurl) {
1488 $menu .= "$linktext / ";
1491 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule)."\">$linktext</a> / ";
1497 $menu .= $picturename;
1501 if ($r->dir_config('GallerySelectionMode') && $r->dir_config('GallerySelectionMode') eq '1') {
1502 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule);
1503 $menu .= "?select=1\">[select]</a> ";
1511 my ($r, $infile, $outfile, $x, $y, $rotate, $copyrightfile, $GalleryTTFDir, $GalleryCopyrightText, $text_color, $GalleryTTFFile, $GalleryTTFSize, $GalleryCopyrightBackgroundColor, $quality) = @_;
1514 my $image = Image::Imlib2->load($infile) or warn("Unable to open file $infile, $!");
1517 $image=$image->create_scaled_image($x, $y) or warn("Unable to scale image $infile. Are you running out of memory?");
1521 $image->image_orientate($rotate);
1524 # blend copyright image onto image
1525 if ($copyrightfile ne '') {
1526 if (-f $copyrightfile and (my $logo=Image::Imlib2->load($copyrightfile))) {
1527 my $x = $image->get_width();
1528 my $y = $image->get_height();
1529 my $logox = $logo->get_width();
1530 my $logoy = $logo->get_height();
1531 $image->blend($logo, 0, 0, 0, $logox, $logoy, $x-$logox, $y-$logoy, $logox, $logoy);
1534 log_error("GalleryCopyrightImage $copyrightfile was not found");
1538 if ($GalleryTTFDir && $GalleryCopyrightText && $GalleryTTFFile && $text_color) {
1539 if (!-d $GalleryTTFDir) {
1541 log_error("GalleryTTFDir $GalleryTTFDir is not a dir\n");
1543 } elsif ($GalleryCopyrightText eq '') {
1545 log_error("GalleryCopyrightText is empty. No text inserted to picture\n");
1547 } elsif (!-e "$GalleryTTFDir/$GalleryTTFFile") {
1549 log_error("GalleryTTFFile $GalleryTTFFile was not found\n");
1553 $GalleryTTFFile =~ s/\.TTF$//i;
1554 $image->add_font_path("$GalleryTTFDir");
1556 $image->load_font("$GalleryTTFFile/$GalleryTTFSize");
1557 my($text_x, $text_y) = $image->get_text_size("$GalleryCopyrightText");
1558 my $x = $image->get_width();
1559 my $y = $image->get_height();
1563 if (($text_x < $x - $offset) && ($text_y < $y - $offset)) {
1564 if ($GalleryCopyrightBackgroundColor =~ /^\d+,\d+,\d+,\d+$/) {
1565 my ($br_val, $bg_val, $bb_val, $ba_val) = split (/,/, $GalleryCopyrightBackgroundColor);
1566 $image->set_colour($br_val, $bg_val, $bb_val, $ba_val);
1567 $image->fill_rectangle ($x-$text_x-$offset, $y-$text_y-$offset, $text_x, $text_y);
1569 my ($r_val, $g_val, $b_val, $a_val) = split (/,/, $text_color);
1570 $image->set_colour($r_val, $g_val, $b_val, $a_val);
1571 $image->draw_text($x-$text_x-$offset, $y-$text_y-$offset, "$GalleryCopyrightText");
1573 log_error("Text is to big for the picture.\n");
1578 if ($quality && $quality =~ m/^\d+$/) {
1579 $image->set_quality($quality);
1582 $image->save($outfile);
1589 my $sortby = $r->dir_config('GallerySortBy');
1590 my $filename=$r->lookup_uri($r->uri)->filename;
1591 $filename=(File::Spec->splitpath($filename))[1] if (-f $filename);
1592 if ($sortby && $sortby =~ m/^(size|atime|mtime|ctime)$/) {
1593 @files = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$sortby()." $_", @files));
1595 @files = sort @files;
1600 # Create Text::Template objects used by Apache::Gallery. Takes a
1601 # hashref of template_name, template_filename pairs, and returns a
1602 # list of template_name, texttemplate_object pairs.
1603 sub create_templates {
1604 my $templates = shift;
1606 # This routine is called whenever a template has an error. Prints
1607 # the error to STDERR and sticks the error in the output
1610 # Pull out the name and filename from the arg option [see
1611 # Text::Template for details]
1612 @args{qw(name file)} = @{$args{arg}};
1613 print STDERR qq(Template $args{name} ("$args{file}") is broken: $args{error});
1614 # Don't include the file name in the output, as the user can see this.
1615 return qq(<!-- Template $args{name} is broken: $args{error} -->);
1620 my %texttemplate_objects;
1622 for my $template_name (keys %$templates) {
1623 my $tt_obj = Text::Template->new(TYPE => 'FILE',
1624 SOURCE => $$templates{$template_name},
1625 BROKEN => \&tt_broken,
1626 BROKEN_ARG => [$template_name, $$templates{$template_name}],
1628 or die "Unable to create new Text::Template object for $template_name: $Text::Template::ERROR";
1629 $texttemplate_objects{$template_name} = $tt_obj;
1631 return %texttemplate_objects;
1636 Apache2::RequestUtil->request->log_error(shift());
1638 Apache->request->log_error(shift());
1646 Apache::Gallery - mod_perl handler to create an image gallery
1650 See the INSTALL file in the distribution for installation instructions.
1654 Apache::Gallery creates an thumbnail index of each directory and allows
1655 viewing pictures in different resolutions. Pictures are resized on the
1656 fly and cached. The gallery can be configured and customized in many ways
1657 and a custom copyright image can be added to all the images without
1658 modifying the original.
1660 =head1 CONFIGURATION
1662 In your httpd.conf you set the global options for the gallery. You can
1663 also override each of the options in .htaccess files in your gallery
1666 The options are set in the httpd.conf/.htaccess file using the syntax:
1667 B<PerlSetVar OptionName 'value'>
1669 Example: B<PerlSetVar GalleryCacheDir '/var/cache/www/'>
1673 =item B<GalleryAutoRotate>
1675 Some cameras, like the Canon G3, can detect the orientation of a
1676 the pictures you take and will save this information in the
1677 'Orientation' EXIF field. Apache::Gallery will then automatically
1680 This behavior is default but can be disabled by setting GalleryAutoRotate
1683 =item B<GalleryCacheDir>
1685 Directory where Apache::Gallery should create its cache with scaled
1686 pictures. The default is /var/cache/www/ . Here, a directory for each
1687 virtualhost or location will be created automatically. Make sure your
1688 webserver has write access to the CacheDir.
1690 =item B<GalleryTemplateDir>
1692 Full path to the directory where you placed the templates. This option
1693 can be used both in your global configuration and in .htaccess files,
1694 this way you can have different layouts in different parts of your
1697 No default value, this option is required.
1699 =item B<GalleryInfo>
1701 With this option you can define which EXIF information you would like
1702 to present from the image. The format is: '<MyName => KeyInEXIF,
1703 MyOtherName => OtherKeyInEXIF'
1705 Examples of keys: B<ShutterSpeedValue>, B<ApertureValue>, B<SubjectDistance>,
1708 You can view all the keys from the EXIF header using this perl-oneliner:
1710 perl C<-e> 'use Data::Dumper; use Image::Info qw(image_info); print Dumper(image_info(shift));' filename.jpg
1712 Default is: 'Picture Taken => DateTimeOriginal, Flash => Flash'
1714 =item B<GallerySizes>
1716 Defines which widths images can be scaled to. Images cannot be
1717 scaled to other widths than the ones you define with this option.
1719 The default is '640 800 1024 1600'
1721 =item B<GalleryThumbnailSize>
1723 Defines the width and height of the thumbnail images.
1725 Defaults to '100x75'
1727 =item B<GalleryThumbnailSizeLS>
1729 If set to '1', B<GalleryThumbnailSize> is the long and the short side of
1730 the thumbnail image instead of the width and height.
1734 =item B<GalleryCopyrightImage>
1736 Image you want to blend into your images in the lower right
1737 corner. This could be a transparent png saying "copyright
1742 =item B<GalleryWrapNavigation>
1744 Make the navigation in the picture view wrap around (So Next
1745 at the end displays the first picture, etc.)
1747 Set to 1 or 0, default is 0
1749 =item B<GalleryAllowOriginal>
1751 Allow the user to download the Original picture without
1752 resizing or putting the CopyrightImage on it.
1754 Set to 1 or 0, default is 0
1756 =item B<GallerySlideshowIntervals>
1758 With this option you can configure which intervals can be selected for
1759 a slideshow. The default is '3 5 10 15 30'
1761 =item B<GallerySortBy>
1763 Instead of the default filename ordering you can sort by any
1764 stat attribute. For example size, atime, mtime, ctime.
1766 =item B<GalleryDirSortBy>
1768 Set this variable to sort directories differently than other items,
1769 can be set to size, atime, mtime and ctime; setting any other value
1770 will revert to sorting by name.
1772 =item B<GalleryMemoize>
1774 Cache EXIF data using Memoize - this will make Apache::Gallery faster
1775 when many people access the same images, but it will also cache EXIF
1776 data until the current Apache child dies.
1778 =item B<GalleryUseFileDate>
1780 Set this option to 1 to make A::G show the files timestamp
1781 instead of the EXIF value for "Picture taken".
1783 =item B<GallerySelectionMode>
1785 Enable the selection mode. Select images with checkboxes and
1786 get a list of filenames.
1788 =item B<GalleryEXIFMode>
1790 You can choose how Apache::Gallery should display EXIF info
1793 The default setting is 'namevalue'. This setting will make
1794 Apache::Gallery print out the names and values of the EXIF values
1795 you configure with GalleryInfo. The information will be parsed into
1796 $INFO in pictureinfo.tpl.
1798 You can also set it to 'values' which will make A::G parse
1799 the configured values into the var $EXIFVALUES as 'value | value | value'
1801 If you set this option to 'variables' the items you configure in GalleryInfo
1802 will be available to your templates as $EXIF_<KEYNAME> (in all uppercase).
1803 That means that with the default setting "Picture Taken => DateTimeOriginal,
1804 Flash => Flash" you will have the variables $EXIF_DATETIMEORIGINAL and
1805 $EXIF_FLASH available to your templates. You can place them
1808 =item B<GalleryRootPath>
1810 Change the location of gallery root. The default is ""
1812 =item B<GalleryRootText>
1814 Change the name that appears as the root element in the menu. The
1817 =item B<GalleryMaxThumbnailsPerPage>
1819 This options controls how many thumbnails should be displayed in a
1820 page. It requires $BROWSELINKS to be in the index.tpl template file.
1822 =item B<GalleryImgFile>
1824 Pattern matching the files you want Apache::Gallery to view in the
1825 index as thumbnails.
1827 The default is '\.(jpe?g|png|tiff?|ppm)$'
1829 =item B<GalleryDocFile>
1831 Pattern matching the files you want Apache::Gallery to view in the index
1832 as normal files. All other filetypes will still be served by Apache::Gallery
1833 but are not visible in the index.
1835 The default is '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|mp4|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
1837 =item B<GalleryTTFDir>
1839 To use the GalleryCopyrightText feature you must set this option to the
1840 directory where your True Type fonts are stored. No default is set.
1844 PerlSetVar GalleryTTFDir '/usr/share/fonts/'
1846 =item B<GalleryTTFFile>
1848 To use the GalleryCopyrightText feature this option must be set to the
1849 name of the True Type font you wish to use. Example:
1851 PerlSetVar GalleryTTFFile 'verdanab.ttf'
1853 =item B<GalleryTTFSize>
1855 Configure the size of the CopyrightText that will be inserted as
1856 copyright notice in the corner of your pictures.
1860 PerlSetVar GalleryTTFSize '10'
1862 =item B<GalleryCopyrightText>
1864 The text that will be inserted as copyright notice.
1868 PerlSetVar GalleryCopyrightText '(c) Michael Legart'
1870 =item B<GalleryCopyrightColor>
1872 The text color of your copyright notice.
1877 PerlSetVar GalleryCopyrightColor '255,255,255,255'
1880 PerlSetVar GalleryCopyrightColor '0,0,0,255'
1883 PerlSetVar GalleryCopyrightColor '255,0,0,255'
1886 PerlSetVar GalleryCopyrightColor '0,255,0,255'
1889 PerlSetVar GalleryCopyrightColor '0,0,255,255'
1892 PerlSetVar GalleryCopyrightColor '255,127,0,127'
1894 =item B<GalleryCopyrightBackgroundColor>
1896 The background-color of a GalleryCopyrightText
1898 r,g,b,a - for examples, see GalleryCopyrightColor
1900 =item B<GalleryQuality>
1902 The quality (1-100) of scaled images
1904 This setting affects the quality of the scaled images.
1905 Set this to a low number to reduce the size of the scaled images.
1906 Remember to clear out your cache if you change this setting.
1907 Quality seems to default to 75, at least in the jpeg and png loader code in
1913 PerlSetVar GalleryQuality '50'
1915 =item B<GalleryUnderscoresToSpaces>
1917 Set this option to 1 to convert underscores to spaces in the listing
1918 of directory and file names, as well as in the alt attribute for HTML
1925 =item B<GalleryCommentExifKey>
1927 Set this option to e.g. ImageDescription to use this field as comments
1930 =item B<GalleryEnableMediaRss>
1932 Set this option to 1 to enable generation of a media RSS feed. This
1933 can be used e.g. together with the PicLens plugin from http://piclens.com
1941 =item B<Rotate images>
1943 Some cameras, like the Canon G3, detects the orientation of a picture
1944 and adds this info to the EXIF header. Apache::Gallery detects this
1945 and automatically rotates images with this info.
1947 If your camera does not support this, you can rotate the images
1948 manually, This can also be used to override the rotate information
1949 from a camera that supports that. You can also disable this behavior
1950 with the GalleryAutoRotate option.
1952 To use this functionality you have to create file with the name of the
1953 picture you want rotated appended with ".rotate". The file should include
1954 a number where these numbers are supported:
1956 "1", rotates clockwise by 90 degree
1957 "2", rotates clockwise by 180 degrees
1958 "3", rotates clockwise by 270 degrees
1960 So if we want to rotate "Picture1234.jpg" 90 degrees clockwise we would
1961 create a file in the same directory called "Picture1234.jpg.rotate" with
1962 the number 1 inside of it.
1964 =item B<Ignore directories/files>
1966 To ignore a directory or a file (of any kind, not only images) you
1967 create a <directory|file>.ignore file.
1971 To include comments for a directory you create a <directory>.comment
1972 file where the first line can contain "TITLE: New title" which
1973 will be the title of the page, and a comment on the following
1975 To include comments for each picture you create files called
1976 picture.jpg.comment where the first line can contain "TITLE: New
1977 title" which will be the title of the page, and a comment on the
1982 TITLE: This is the new title of the page
1983 And this is the comment.<br />
1984 And this is line two of the comment.
1986 The visible name of the folder is by default identical to the name of
1987 the folder, but can be changed by creating a file <directory>.folder
1988 with the visible name of the folder.
1990 It is also possible to set GalleryCommentExifKey to the name of an EXIF
1991 field containing the comment, e.g. ImageDescription. The EXIF comment is
1992 overridden by the .comment file if it exists.
2002 =item B<Apache with mod_perl>
2004 =item B<URI::Escape>
2006 =item B<Image::Info>
2008 =item B<Image::Size>
2010 =item B<Text::Template>
2012 =item B<Image::Imlib2>
2014 =item B<X11 libraries>
2018 Remember the -dev package when using rpm, deb or other package formats!
2024 Michael Legart <michael@legart.dk>
2026 =head1 COPYRIGHT AND LICENSE
2028 Copyright (C) 2001-2011 Michael Legart <michael@legart.dk>
2030 Templates designed by Thomas Kjaer <tk@lnx.dk>
2032 Apache::Gallery is free software and is released under the Artistic License.
2033 See B<http://www.perl.com/language/misc/Artistic.html> for details.
2035 The video icons are from the GNOME project. B<http://www.gnome.org/>
2039 Thanks to Thomas Kjaer for templates and design of B<http://apachegallery.dk>
2040 Thanks to Thomas Eibner and other for patches. (See the Changes file)
2044 L<perl>, L<mod_perl>, L<Image::Imlib2>, L<CGI::FastTemplate>,
2045 L<Image::Info>, and L<Image::Size>.