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 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
125 # Lookup the file in the cache and scale the image if the cached
126 # image does not exist
127 if ($r->uri =~ m/\.cache\//i) {
129 my $filename = $r->filename().$r->path_info();
130 $filename =~ s/\.cache//;
132 $filename =~ m/\/(\d+)x(\d+)\-/;
133 my $image_width = $1;
134 my $image_height = $2;
136 $filename =~ s/\/(\d+)x(\d+)\-//;
138 my ($width, $height, $type) = imgsize($filename);
140 my $imageinfo = get_imageinfo($r, $filename, $type, $width, $height);
142 my $cached = scale_picture($r, $filename, $image_width, $image_height, $imageinfo);
144 my $file = cache_dir($r, 0);
145 $file =~ s/\.cache//;
147 my $subr = $r->lookup_file($file);
148 $r->content_type($subr->content_type());
151 my $fileinfo = stat($file);
153 my $nonce = md5_base64($fileinfo->ino.$fileinfo->mtime);
154 if ($r->headers_in->{"If-None-Match"} eq $nonce) {
155 return Apache2::Const::HTTP_NOT_MODIFIED();
158 if ($r->headers_in->{"If-Modified-Since"} && str2time($r->headers_in->{"If-Modified-Since"}) < $fileinfo->mtime) {
159 return Apache2::Const::HTTP_NOT_MODIFIED();
162 $r->headers_out->{"Content-Length"} = $fileinfo->size;
163 $r->headers_out->{"Last-Modified-Date"} = time2str($fileinfo->mtime);
164 $r->headers_out->{"ETag"} = $nonce;
166 return Apache2::Const::OK();
171 return Apache::Constants::DECLINED();
179 unless (-f $filename or -d $filename) {
180 show_error($r, 404, "404!", "No such file or directory: ".uri_escape($r->uri, $escape_rule));
181 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
184 my $doc_pattern = $r->dir_config('GalleryDocFile');
185 unless ($doc_pattern) {
186 $doc_pattern = '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
188 my $img_pattern = $r->dir_config('GalleryImgFile');
189 unless ($img_pattern) {
190 $img_pattern = '\.(jpe?g|png|tiff?|ppm)$'
193 # Let Apache serve files we don't know how to handle anyway
194 if (-f $filename && $filename !~ m/$img_pattern/i) {
195 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
200 unless (-d cache_dir($r, 0)) {
201 unless (create_cache($r, cache_dir($r, 0))) {
202 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
206 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
208 # Instead of reading the templates every single time
209 # we need them, create a hash of template names and
210 # the associated Text::Template objects.
211 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
212 index => "$tpl_dir/index.tpl",
213 directory => "$tpl_dir/directory.tpl",
214 picture => "$tpl_dir/picture.tpl",
215 file => "$tpl_dir/file.tpl",
216 comment => "$tpl_dir/dircomment.tpl",
217 nocomment => "$tpl_dir/nodircomment.tpl",
218 rss => "$tpl_dir/rss.tpl",
219 rss_item => "$tpl_dir/rss_item.tpl",
220 navdirectory => "$tpl_dir/navdirectory.tpl",
228 $tpl_vars{TITLE} = "Index of: $uri";
230 if ($media_rss_enabled) {
231 # Put the RSS feed on all directory listings
232 $tpl_vars{META} = '<link rel="alternate" href="?rss=1" type="application/rss+xml" title="" id="gallery" />';
235 unless (opendir (DIR, $filename)) {
236 show_error ($r, 500, $!, "Unable to access directory $filename: $!");
237 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
240 $tpl_vars{MENU} = generate_menu($r);
242 $tpl_vars{FORM_BEGIN} = $select_mode?'<form method="post">':'';
243 $tpl_vars{FORM_END} = $select_mode?'<input type="submit" name="Get list" value="Get list"></form>':'';
245 # Read, sort, and filter files
246 my @files = grep { !/^\./ && -f "$filename/$_" } readdir (DIR);
248 @files=gallerysort($r, @files);
250 my @downloadable_files;
253 # Remove unwanted files from list
255 foreach my $picture (@files) {
257 my $file = $topdir."/".$picture;
259 if ($file =~ /$img_pattern/i) {
260 push (@new_files, $picture);
263 if ($file =~ /$doc_pattern/i) {
264 push (@downloadable_files, $picture);
271 # Read and sort directories
273 my @directories = grep { !/^\./ && -d "$filename/$_" } readdir (DIR);
275 if (defined($r->dir_config('GalleryDirSortBy'))) {
276 $dirsortby=$r->dir_config('GalleryDirSortBy');
278 $dirsortby=$r->dir_config('GallerySortBy');
280 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
281 @directories = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$dirsortby()." $_", @directories));
283 @directories = sort @directories;
289 # Combine directories and files to one listing
291 push (@listing, @directories);
292 push (@listing, @files);
293 push (@listing, @downloadable_files);
299 my $file_counter = 0;
301 my $max_files = $r->dir_config('GalleryMaxThumbnailsPerPage');
303 if (defined($cgi->param('start'))) {
304 $start_at = $cgi->param('start');
310 my $browse_links = "";
311 if (defined($max_files)) {
313 for (my $i=1; $i<=scalar(@listing); $i++) {
317 my $to = $i+$max_files-1;
318 if ($to > scalar(@listing)) {
319 $to = scalar(@listing);
322 if ($start_at < $from || $start_at > $to) {
323 $browse_links .= "<a href=\"?start=$from\">$from - ".$to."</a> ";
326 $browse_links .= "$from - $to ";
335 $tpl_vars{BROWSELINKS} = $browse_links;
338 foreach my $file (@listing) {
342 if ($file_counter < $start_at) {
346 if (defined($max_files) && $file_counter > $max_files+$start_at-1) {
350 my $thumbfilename = $topdir."/".$file;
352 my $fileurl = $uri."/".$file;
354 # Debian bug #619625 <http://bugs.debian.org/619625>
355 if (-d $thumbfilename && ! -e $thumbfilename . ".ignore") {
357 if (-e $thumbfilename . ".folder") {
358 $dirtitle = get_filecontent($thumbfilename . ".folder");
361 $dirtitle = $dirtitle ? $dirtitle : $file;
362 $dirtitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
365 $templates{directory}->fill_in(HASH=> {FILEURL => uri_escape($fileurl, $escape_rule),
371 # Debian bug #619625 <http://bugs.debian.org/619625>
372 elsif (-f $thumbfilename && $thumbfilename =~ /$doc_pattern/i && $thumbfilename !~ /$img_pattern/i && ! -e $thumbfilename . ".ignore") {
374 my $stat = stat($thumbfilename);
375 my $size = $stat->size;
378 if ($thumbfilename =~ m/\.(mpe?g|avi|mov|asf|wmv)$/i) {
379 $filetype = "video-$type";
380 } elsif ($thumbfilename =~ m/\.(txt|html?)$/i) {
381 $filetype = "text-$type";
382 } elsif ($thumbfilename =~ m/\.(mp3|ogg|wav)$/i) {
383 $filetype = "sound-$type";
384 } elsif ($thumbfilename =~ m/$doc_pattern/i) {
385 $filetype = "application-$type";
387 $filetype = "unknown";
390 # Debian bug #348724 <http://bugs.debian.org/348724>
392 my $filetitle = $file;
393 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
396 $templates{file}->fill_in(HASH => {%tpl_vars,
397 FILEURL => uri_escape($fileurl, $escape_rule),
398 ALT => "Size: $size Bytes",
401 FILETYPE => $filetype,
405 # Debian bug #619625 <http://bugs.debian.org/619625>
406 elsif (-f $thumbfilename && ! -e $thumbfilename . ".ignore") {
408 my ($width, $height, $type) = imgsize($thumbfilename);
409 next if $type eq 'Data stream is not a known image file format';
411 my @filetypes = qw(JPG TIF PNG PPM GIF);
413 next unless (grep $type eq $_, @filetypes);
414 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);
415 my $imageinfo = get_imageinfo($r, $thumbfilename, $type, $width, $height);
416 my $cached = get_scaled_picture_name($thumbfilename, $thumbnailwidth, $thumbnailheight);
418 my $rotate = readfile_getnum($r, $imageinfo, $thumbfilename.".rotate");
420 # Debian bug #348724 <http://bugs.debian.org/348724>
421 # HTML <img> tag, alt attribute
422 my $filetitle = $file;
423 $filetitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
425 my %file_vars = (FILEURL => uri_escape($fileurl, $escape_rule),
427 DATE => $imageinfo->{DateTimeOriginal} ? $imageinfo->{DateTimeOriginal} : '', # should this really be a stat of the file instead of ''?
428 SRC => uri_escape($uri."/.cache/$cached", $escape_rule),
429 HEIGHT => (grep($rotate==$_, (1, 3)) ? $thumbnailwidth : $thumbnailheight),
430 WIDTH => (grep($rotate==$_, (1, 3)) ? $thumbnailheight : $thumbnailwidth),
431 SELECT => $select_mode?'<input type="checkbox" name="selection" value="'.$file.'"> ':'',);
432 $tpl_vars{FILES} .= $templates{picture}->fill_in(HASH => {%tpl_vars,
437 if ($media_rss_enabled) {
438 my ($content_image_width, undef, $content_image_height) = get_image_display_size($cgi, $r, $width, $height);
440 THUMBNAIL => uri_escape($uri."/.cache/$cached", $escape_rule),
441 LINK => uri_escape($fileurl, $escape_rule),
443 CONTENT => uri_escape($uri."/.cache/".$content_image_width."x".$content_image_height."-".$file, $escape_rule)
445 $tpl_vars{ITEMS} .= $templates{rss_item}->fill_in(HASH => {
453 $tpl_vars{FILES} = "No files found";
454 $tpl_vars{BROWSELINKS} = "";
457 # Generate prev and next directory menu items
458 $filename =~ m/(.*)\/.*?$/;
459 my $parent_filename = $1;
461 $r->document_root =~ m/(.*)\/$/;
463 print STDERR "$filename vs $root_path\n";
464 if ($filename ne $root_path) {
465 unless (opendir (PARENT_DIR, $parent_filename)) {
466 show_error ($r, 500, $!, "Unable to access parent directory $parent_filename: $!");
467 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
470 # Debian bug #619625 <http://bugs.debian.org/619625>
471 my @neighbour_directories = grep { !/^\./ && -d "$parent_filename/$_" && ! -e "$parent_filename/$_" . ".ignore" } readdir (PARENT_DIR);
473 if (defined($r->dir_config('GalleryDirSortBy'))) {
474 $dirsortby=$r->dir_config('GalleryDirSortBy');
476 $dirsortby=$r->dir_config('GallerySortBy');
478 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
479 @neighbour_directories = map(/^\d+ (.*)/, sort map(stat("$parent_filename/$_")->$dirsortby()." $_", @neighbour_directories));
481 @neighbour_directories = sort @neighbour_directories;
484 closedir(PARENT_DIR);
486 my $neightbour_counter = 0;
487 foreach my $neighbour_directory (@neighbour_directories) {
488 if ($parent_filename.'/'.$neighbour_directory eq $filename) {
489 if ($neightbour_counter > 0) {
490 print STDERR "prev directory is " .$neighbour_directories[$neightbour_counter-1] ."\n";
491 my $linktext = $neighbour_directories[$neightbour_counter-1];
492 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
493 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder");
496 URL => "../".$neighbour_directories[$neightbour_counter-1],
497 LINK_NAME => "<<< $linktext",
500 $tpl_vars{PREV_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
501 print STDERR $tpl_vars{PREV_DIR_FILES} ."\n";
504 if ($neightbour_counter < scalar @neighbour_directories - 1) {
505 my $linktext = $neighbour_directories[$neightbour_counter+1];
506 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder") {
507 $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder");
510 URL => "../".$neighbour_directories[$neightbour_counter+1],
511 LINK_NAME => "$linktext >>>",
514 $tpl_vars{NEXT_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
515 print STDERR "next directory is " .$neighbour_directories[$neightbour_counter+1] ."\n";
518 $neightbour_counter++;
522 if (-f $topdir . '.comment') {
523 my $comment_ref = get_comment($topdir . '.comment');
525 $comment_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
526 $comment_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
527 $tpl_vars{DIRCOMMENT} = $templates{comment}->fill_in(HASH => \%comment_vars);
528 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
530 $tpl_vars{DIRCOMMENT} = $templates{nocomment}->fill_in(HASH=>\%tpl_vars);
533 if ($cgi->param('rss')) {
534 $tpl_vars{MAIN} = $templates{rss}->fill_in(HASH => \%tpl_vars);
535 $r->content_type('application/rss+xml');
537 $tpl_vars{MAIN} = $templates{index}->fill_in(HASH => \%tpl_vars);
538 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
539 $r->content_type('text/html');
542 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
545 $r->send_http_header;
548 $r->print($tpl_vars{MAIN});
549 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
555 if (defined($ENV{QUERY_STRING}) && $ENV{QUERY_STRING} eq 'orig') {
556 if ($r->dir_config('GalleryAllowOriginal') ? 1 : 0) {
557 $r->filename($filename);
558 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
560 return $::MP2 ? Apache2::Const::FORBIDDEN() : Apache::Constants::FORBIDDEN();
564 # Create cache dir if not existing
565 my @tmp = split (/\//, $filename);
566 my $picfilename = pop @tmp;
567 my $path = (join "/", @tmp)."/";
568 my $cache_path = cache_dir($r, 1);
570 unless (-d $cache_path) {
571 unless (create_cache($r, $cache_path)) {
572 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
576 my ($orig_width, $orig_height, $type) = imgsize($filename);
578 my $imageinfo = get_imageinfo($r, $filename, $type, $orig_width, $orig_height);
580 my ($image_width, $width, $height, $original_size) = get_image_display_size($cgi, $r, $orig_width, $orig_height);
582 my $cached = get_scaled_picture_name($filename, $image_width, $height);
584 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
586 my %templates = create_templates({layout => "$tpl_dir/layout.tpl",
587 picture => "$tpl_dir/showpicture.tpl",
588 navpicture => "$tpl_dir/navpicture.tpl",
589 info => "$tpl_dir/info.tpl",
590 scale => "$tpl_dir/scale.tpl",
591 scaleactive => "$tpl_dir/scaleactive.tpl",
592 orig => "$tpl_dir/orig.tpl",
593 refresh => "$tpl_dir/refresh.tpl",
594 interval => "$tpl_dir/interval.tpl",
595 intervalactive => "$tpl_dir/intervalactive.tpl",
596 slideshowisoff => "$tpl_dir/slideshowisoff.tpl",
597 slideshowoff => "$tpl_dir/slideshowoff.tpl",
598 pictureinfo => "$tpl_dir/pictureinfo.tpl",
599 nopictureinfo => "$tpl_dir/nopictureinfo.tpl",
604 my $resolution = (($image_width > $orig_width) && ($height > $orig_height)) ?
605 "$orig_width x $orig_height" : "$image_width x $height";
607 $tpl_vars{TITLE} = "Viewing ".$r->uri()." at $image_width x $height";
608 $tpl_vars{META} = " ";
609 $tpl_vars{RESOLUTION} = $resolution;
610 $tpl_vars{MENU} = generate_menu($r);
611 $tpl_vars{SRC} = uri_escape(".cache/$cached", $escape_rule);
612 $tpl_vars{URI} = $r->uri();
614 my $exif_mode = $r->dir_config('GalleryEXIFMode');
615 unless ($exif_mode) {
616 $exif_mode = 'namevalue';
619 unless (opendir(DATADIR, $path)) {
620 show_error($r, 500, "Unable to access directory", "Unable to access directory $path");
621 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
623 my @pictures = grep { /$img_pattern/i && ! -e "$path/$_" . ".ignore" } readdir (DATADIR);
625 @pictures = gallerysort($r, @pictures);
627 $tpl_vars{TOTAL} = scalar @pictures;
632 for (my $i=0; $i <= $#pictures; $i++) {
633 if ($pictures[$i] eq $picfilename) {
635 $tpl_vars{NUMBER} = $i+1;
637 $prevpicture = $pictures[$i-1];
638 my $displayprev = ($i>0 ? 1 : 0);
640 if ($r->dir_config("GalleryWrapNavigation")) {
641 $prevpicture = $pictures[$i>0 ? $i-1 : $#pictures];
644 if ($prevpicture and $displayprev) {
645 my ($orig_width, $orig_height, $type) = imgsize($path.$prevpicture);
646 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
647 my $imageinfo = get_imageinfo($r, $path.$prevpicture, $type, $orig_width, $orig_height);
648 my $cached = get_scaled_picture_name($path.$prevpicture, $thumbnailwidth, $thumbnailheight);
650 $nav_vars{URL} = uri_escape($prevpicture, $escape_rule);
651 $nav_vars{FILENAME} = $prevpicture;
652 $nav_vars{WIDTH} = $width;
653 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
654 $nav_vars{DIRECTION} = "« <u>p</u>rev";
655 $nav_vars{ACCESSKEY} = "P";
656 $tpl_vars{BACK} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
659 $tpl_vars{BACK} = " ";
662 $nextpicture = $pictures[$i+1];
663 if ($r->dir_config("GalleryWrapNavigation")) {
664 $nextpicture = $pictures[$i == $#pictures ? 0 : $i+1];
668 my ($orig_width, $orig_height, $type) = imgsize($path.$nextpicture);
669 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
670 my $imageinfo = get_imageinfo($r, $path.$nextpicture, $type, $thumbnailwidth, $thumbnailheight);
671 my $cached = get_scaled_picture_name($path.$nextpicture, $thumbnailwidth, $thumbnailheight);
673 $nav_vars{URL} = uri_escape($nextpicture, $escape_rule);
674 $nav_vars{FILENAME} = $nextpicture;
675 $nav_vars{WIDTH} = $width;
676 $nav_vars{PICTURE} = uri_escape(".cache/$cached", $escape_rule);
677 $nav_vars{DIRECTION} = "<u>n</u>ext »";
678 $nav_vars{ACCESSKEY} = "N";
680 $tpl_vars{NEXT} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
681 $tpl_vars{NEXTURL} = uri_escape($nextpicture, $escape_rule);
684 $tpl_vars{NEXT} = " ";
685 $tpl_vars{NEXTURL} = '#';
690 my $foundcomment = 0;
691 if (-f $path . '/' . $picfilename . '.comment') {
692 my $comment_ref = get_comment($path . '/' . $picfilename . '.comment');
694 $tpl_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
695 $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
696 } elsif ($r->dir_config('GalleryCommentExifKey')) {
697 my $comment = decode("utf8", $imageinfo->{$r->dir_config('GalleryCommentExifKey')});
698 $tpl_vars{COMMENT} = encode("iso-8859-1", $comment);
700 $tpl_vars{COMMENT} = '';
703 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
708 my ($human_key, $exif_key) = (split " => ")[0,1];
709 my $value = $imageinfo->{$human_key};
710 if (defined($value)) {
714 if ($exif_mode eq 'namevalue') {
716 $info_vars{KEY} = $human_key;
717 $info_vars{VALUE} = $value;
718 $tpl_vars{INFO} .= $templates{info}->fill_in(HASH => \%info_vars);
721 if ($exif_mode eq 'variables') {
722 $tpl_vars{"EXIF_".uc($exif_key)} = $value;
725 if ($exif_mode eq 'values') {
726 $exifvalues .= "| ".$value." ";
733 if ($exif_mode eq 'values') {
734 if (defined($exifvalues)) {
735 $tpl_vars{EXIFVALUES} = $exifvalues;
738 $tpl_vars{EXIFVALUES} = "";
742 if ($foundcomment and !$foundinfo) {
743 $tpl_vars{INFO} = "";
746 if ($exif_mode ne 'namevalue') {
747 $tpl_vars{INFO} = "";
750 if ($exif_mode eq 'namevalue' && $foundinfo or $foundcomment) {
752 $tpl_vars{PICTUREINFO} = $templates{pictureinfo}->fill_in(HASH => \%tpl_vars);
754 unless (defined($exifvalues)) {
755 $tpl_vars{EXIFVALUES} = "";
760 $tpl_vars{PICTUREINFO} = $templates{nopictureinfo}->fill_in(HASH => \%tpl_vars);
763 # Fill in sizes and determine if any are smaller than the
764 # actual image. If they are, $scaleable=1
766 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
767 foreach my $size (@sizes) {
768 if ($size<=$original_size) {
770 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
771 $sizes_vars{SIZE} = $size;
772 $sizes_vars{WIDTH} = $size;
773 if ($width == $size) {
774 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
777 $tpl_vars{SIZES} .= $templates{scale}->fill_in(HASH => \%sizes_vars);
783 unless ($scaleable) {
785 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
786 $sizes_vars{SIZE} = $original_size;
787 $sizes_vars{WIDTH} = $original_size;
788 $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
791 $tpl_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
793 if ($r->dir_config('GalleryAllowOriginal')) {
794 $tpl_vars{SIZES} .= $templates{orig}->fill_in(HASH => \%tpl_vars);
797 my @slideshow_intervals = split (/ /, $r->dir_config('GallerySlideshowIntervals') ? $r->dir_config('GallerySlideshowIntervals') : '3 5 10 15 30');
798 foreach my $interval (@slideshow_intervals) {
801 $slideshow_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
802 $slideshow_vars{SECONDS} = $interval;
803 $slideshow_vars{WIDTH} = ($width > $height ? $width : $height);
805 if ($cgi->param('slideshow') && $cgi->param('slideshow') == $interval and $nextpicture) {
806 $tpl_vars{SLIDESHOW} .= $templates{intervalactive}->fill_in(HASH => \%slideshow_vars);
810 $tpl_vars{SLIDESHOW} .= $templates{interval}->fill_in(HASH => \%slideshow_vars);
815 if ($cgi->param('slideshow') and $nextpicture) {
817 $tpl_vars{SLIDESHOW} .= $templates{slideshowoff}->fill_in(HASH => \%tpl_vars);
819 unless ((grep $cgi->param('slideshow') == $_, @slideshow_intervals)) {
820 show_error($r, 200, "Invalid interval", "Invalid slideshow interval choosen");
821 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
824 $tpl_vars{URL} = uri_escape($nextpicture, $escape_rule);
825 $tpl_vars{WIDTH} = ($width > $height ? $width : $height);
826 $tpl_vars{INTERVAL} = $cgi->param('slideshow');
827 $tpl_vars{META} .= $templates{refresh}->fill_in(HASH => \%tpl_vars);
831 $tpl_vars{SLIDESHOW} .= $templates{slideshowisoff}->fill_in(HASH => \%tpl_vars);
834 $tpl_vars{MAIN} = $templates{picture}->fill_in(HASH => \%tpl_vars);
835 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
837 $r->content_type('text/html');
838 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
841 $r->send_http_header;
844 $r->print($tpl_vars{MAIN});
845 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
853 my ($r, $strip_filename) = @_;
857 unless ($r->dir_config('GalleryCacheDir')) {
859 $cache_root = '/var/cache/www/';
860 if ($r->server->is_virtual) {
861 $cache_root = File::Spec->catdir($cache_root, $r->server->server_hostname);
863 $cache_root = File::Spec->catdir($cache_root, $r->location);
868 $cache_root = $r->dir_config('GalleryCacheDir');
872 # If the uri contains .cache we need to remove it
876 my (undef, $dirs, $filename) = File::Spec->splitpath($uri);
877 # We don't need a volume as this is a relative path
879 if ($strip_filename) {
880 return(File::Spec->canonpath(File::Spec->catdir($cache_root, $dirs)));
882 return(File::Spec->canonpath(File::Spec->catfile($cache_root, $dirs, $filename)));
890 unless (mkdirhier ($path)) {
891 show_error($r, 500, $!, "Unable to create cache directory in $path: $!");
904 unless (mkdir($dir, 0755)) {
906 $parent =~ s/\/[^\/]*$//;
915 sub get_scaled_picture_name {
917 my ($fullpath, $width, $height) = @_;
919 my (undef, undef, $type) = imgsize($fullpath);
921 my @dirs = split(/\//, $fullpath);
922 my $filename = pop(@dirs);
925 if (grep $type eq $_, qw(PPM TIF GIF)) {
926 $newfilename = $width."x".$height."-".$filename;
927 # needs to be configurable
928 $newfilename =~ s/\.(\w+)$/-$1\.jpg/;
930 $newfilename = $width."x".$height."-".$filename;
939 my ($r, $fullpath, $width, $height, $imageinfo) = @_;
941 my @dirs = split(/\//, $fullpath);
942 my $filename = pop(@dirs);
944 my ($orig_width, $orig_height, $type) = imgsize($fullpath);
946 my $cache = cache_dir($r, 1);
948 my $newfilename = get_scaled_picture_name($fullpath, $width, $height);
950 if (($width > $orig_width) && ($height > $orig_height)) {
951 # Run it through the resize code anyway to get watermarks
952 $width = $orig_width;
953 $height = $orig_height;
956 my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
958 # Do we want to generate a new file in the cache?
961 if (-f $cache."/".$newfilename) {
964 # Check to see if the image has changed
965 my $filestat = stat($fullpath);
966 my $cachestat = stat($cache."/".$newfilename);
967 if ($filestat->mtime >= $cachestat->mtime) {
971 # Check to see if the .rotate file has been added or changed
972 if (-f $fullpath . ".rotate") {
973 my $rotatestat = stat($fullpath . ".rotate");
974 if ($rotatestat->mtime > $cachestat->mtime) {
978 # Check to see if the copyrightimage has been added or changed
979 if ($r->dir_config('GalleryCopyrightImage') && -f $r->dir_config('GalleryCopyrightImage')) {
980 unless ($width == $thumbnailwidth or $width == $thumbnailheight) {
981 my $copyrightstat = stat($r->dir_config('GalleryCopyrightImage'));
982 if ($copyrightstat->mtime > $cachestat->mtime) {
992 my $newpath = $cache."/".$newfilename;
993 my $rotate = readfile_getnum($r, $imageinfo, $fullpath . ".rotate");
994 my $quality = $r->dir_config('GalleryQuality');
996 if ($width == $thumbnailwidth or $width == $thumbnailheight) {
998 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, '', '', '', '', '', '');
1002 resizepicture($r, $fullpath, $newpath, $width, $height, $rotate,
1003 ($r->dir_config('GalleryCopyrightImage') ? $r->dir_config('GalleryCopyrightImage') : ''),
1004 ($r->dir_config('GalleryTTFDir') ? $r->dir_config('GalleryTTFDir') : ''),
1005 ($r->dir_config('GalleryCopyrightText') ? $r->dir_config('GalleryCopyrightText') : ''),
1006 ($r->dir_config('GalleryCopyrightColor') ? $r->dir_config('GalleryCopyrightColor') : ''),
1007 ($r->dir_config('GalleryTTFFile') ? $r->dir_config('GalleryTTFFile') : ''),
1008 ($r->dir_config('GalleryTTFSize') ? $r->dir_config('GalleryTTFSize') : ''),
1009 ($r->dir_config('GalleryCopyrightBackgroundColor') ? $r->dir_config('GalleryCopyrightBackgroundColor') : ''),
1015 return $newfilename;
1019 sub get_thumbnailsize {
1020 my ($r, $orig_width, $orig_height) = @_;
1022 my $gallerythumbnailsize=$r->dir_config('GalleryThumbnailSize');
1024 if (defined($gallerythumbnailsize)) {
1025 warn("Invalid setting for GalleryThumbnailSize") unless
1026 $gallerythumbnailsize =~ /^\s*\d+\s*x\s*\d+\s*$/i;
1029 my ($thumbnailwidth, $thumbnailheight) = split(/x/i, ($gallerythumbnailsize) ? $gallerythumbnailsize : "100x75");
1031 my $width = $thumbnailwidth;
1032 my $height = $thumbnailheight;
1034 # If the image is rotated, flip everything around.
1035 if (defined $r->dir_config('GalleryThumbnailSizeLS')
1036 and $r->dir_config('GalleryThumbnailSizeLS') eq '1'
1037 and $orig_width < $orig_height) {
1039 $width = $thumbnailheight;
1040 $height = $thumbnailwidth;
1043 my $scale = ($orig_width ? $width/$orig_width : 1);
1046 if ($orig_height * $scale > $thumbnailheight) {
1047 $scale = $height/$orig_height;
1048 $width = $orig_width * $scale;
1052 $height = $orig_height * $scale;
1054 $height = floor($height);
1055 $width = floor($width);
1057 return ($width, $height);
1060 sub get_image_display_size {
1061 my ($cgi, $r, $orig_width, $orig_height) = @_;
1063 my $width = $orig_width;
1065 my $original_size=$orig_height;
1066 if ($orig_width>$orig_height) {
1067 $original_size=$orig_width;
1070 # Check if the selected width is allowed
1071 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
1073 my %cookies = fetch CGI::Cookie;
1075 if ($cgi->param('width')) {
1076 unless ((grep $cgi->param('width') == $_, @sizes) or ($cgi->param('width') == $original_size)) {
1077 show_error($r, 200, "Invalid width", "The specified width is invalid");
1078 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
1081 $width = $cgi->param('width');
1082 my $cookie = new CGI::Cookie(-name => 'GallerySize', -value => $width, -expires => '+6M');
1083 $r->headers_out->{'Set-Cookie'} = $cookie;
1085 } elsif ($cookies{'GallerySize'} && (grep $cookies{'GallerySize'}->value == $_, @sizes)) {
1087 $width = $cookies{'GallerySize'}->value;
1095 if ($orig_width<$orig_height) {
1096 $scale = ($orig_height ? $width/$orig_height: 1);
1097 $image_width=$width*$orig_width/$orig_height;
1100 $scale = ($orig_width ? $width/$orig_width : 1);
1101 $image_width = $width;
1104 my $height = $orig_height * $scale;
1106 $image_width = floor($image_width);
1107 $width = floor($width);
1108 $height = floor($height);
1110 return ($image_width, $width, $height, $original_size);
1114 my ($r, $file, $type, $width, $height) = @_;
1116 if ($type eq 'Data stream is not a known image file format') {
1117 # should never be reached, this is supposed to be handled outside of here
1118 log_error("Something was fishy with the type of the file $file\n");
1121 # Some files, like TIFF, PNG, GIF do not have EXIF info
1122 # embedded but use .thm files instead.
1123 $imageinfo = get_imageinfo_from_thm_file($file, $width, $height);
1125 # If there is no .thm file and our file is a JPEG file we try to extract the EXIf
1126 # info using Image::Info
1127 unless (defined($imageinfo) && (grep $type eq $_, qw(JPG))) {
1128 # Only for files that natively keep the EXIF info in the same file
1129 $imageinfo = image_info($file);
1133 unless (defined($imageinfo->{width}) and defined($imageinfo->{height})) {
1134 $imageinfo->{width} = $width;
1135 $imageinfo->{height} = $height;
1138 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
1141 my ($human_key, $exif_key) = (split " => ")[0,1];
1142 if (defined($exif_key) && defined($imageinfo->{$exif_key})) {
1144 if (ref($imageinfo->{$exif_key}) eq 'Image::TIFF::Rational') {
1145 $value = $imageinfo->{$exif_key}->as_string;
1147 elsif (ref($imageinfo->{$exif_key}) eq 'ARRAY') {
1148 foreach my $element (@{$imageinfo->{$exif_key}}) {
1149 if (ref($element) eq 'ARRAY') {
1150 foreach (@{$element}) {
1154 elsif (ref($element) eq 'HASH') {
1155 $value .= "<br />{ ";
1156 foreach (sort keys %{$element}) {
1157 $value .= "$_ = " . $element->{$_} . ' ';
1168 my $exif_value = $imageinfo->{$exif_key};
1169 if ($human_key eq 'Flash' && $exif_value =~ m/\d/) {
1174 "16" => "No (Compulsory) Should be External Flash",
1175 "17" => "Yes (External)",
1177 "25" => "Yes (Auto)",
1178 "73" => "Yes (Compulsory, Red Eye Reducing)",
1179 "89" => "Yes (Auto, Red Eye Reducing)"
1181 $exif_value = defined $flashmodes{$exif_value} ? $flashmodes{$exif_value} : 'unknown flash mode';
1183 $value = $exif_value;
1185 if ($exif_key eq 'MeteringMode') {
1186 my $exif_value = $imageinfo->{$exif_key};
1187 if ($exif_value =~ /^\d+$/) {
1188 my %meteringmodes = (
1191 '2' => 'CenterWeightedAverage',
1198 $exif_value = defined $meteringmodes{$exif_value} ? $meteringmodes{$exif_value} : 'unknown metering mode';
1200 $value = $exif_value;
1203 if ($exif_key eq 'LightSource') {
1204 my $exif_value = $imageinfo->{$exif_key};
1205 if ($exif_value =~ /^\d+$/) {
1206 my %lightsources = (
1209 '2' => 'Fluorescent',
1210 '3' => 'Tungsten (incandescent light)',
1212 '9' => 'Fine weather',
1213 '10' => 'Cloudy weather',
1215 '12' => 'Daylight fluorescent',
1216 '13' => 'Day white fluorescent',
1217 '14' => 'Cool white fluorescent',
1218 '15' => 'White fluorescent',
1219 '17' => 'Standard light A',
1220 '18' => 'Standard light B',
1221 '19' => 'Standard light C',
1226 '24' => 'ISO studio tungsten',
1227 '255' => 'other light source'
1229 $exif_value = defined $lightsources{$exif_value} ? $lightsources{$exif_value} : 'unknown light source';
1231 $value = $exif_value;
1233 if ($exif_key eq 'FocalLength') {
1234 if ($value =~ /^(\d+)\/(\d+)$/) {
1235 $value = eval { $1 / $2 };
1239 $value = int($value + 0.5) . "mm";
1244 if ($exif_key eq 'ShutterSpeedValue') {
1245 if ($value =~ /^((?:\-)?\d+)\/(\d+)$/) {
1246 $value = eval { $1 / $2 };
1251 $value = 1/(exp($value*log(2)));
1253 $value = "1/" . (int((1/$value)));
1255 $value = int($value*10)/10;
1261 $value = $value . " sec";
1266 if ($exif_key eq 'ApertureValue') {
1267 if ($value =~ /^(\d+)\/(\d+)$/) {
1268 $value = eval { $1 / $2 };
1272 # poor man's rounding
1273 $value = int(exp($value*log(2)*0.5)*10)/10;
1274 $value = "f" . $value;
1278 if ($exif_key eq 'FNumber') {
1279 if ($value =~ /^(\d+)\/(\d+)$/) {
1280 $value = eval { $1 / $2 };
1284 $value = int($value*10+0.5)/10;
1285 $value = "f" . $value;
1289 $imageinfo->{$human_key} = $value;
1293 if ($r->dir_config('GalleryUseFileDate') &&
1294 ($r->dir_config('GalleryUseFileDate') eq '1'
1295 || !$imageinfo->{"Picture Taken"} )) {
1297 my $st = stat($file);
1298 $imageinfo->{"DateTimeOriginal"} = $imageinfo->{"Picture Taken"} = scalar localtime($st->mtime) if $st;
1304 sub get_imageinfo_from_thm_file {
1306 my ($file, $width, $height) = @_;
1308 my $imageinfo = undef;
1309 # Windows based file extensions are often .THM, so check
1310 # for both .thm and .THM
1311 my $unix_file = $file;
1312 my $windows_file = $file;
1313 $unix_file =~ s/\.(\w+)$/.thm/;
1314 $windows_file =~ s/\.(\w+)$/.THM/;
1316 if (-e $unix_file && -f $unix_file && -r $unix_file) {
1317 $imageinfo = image_info($unix_file);
1318 $imageinfo->{width} = $width;
1319 $imageinfo->{height} = $height;
1321 elsif (-e $windows_file && -f $windows_file && -r $windows_file) {
1322 $imageinfo = image_info($windows_file);
1323 $imageinfo->{width} = $width;
1324 $imageinfo->{height} = $height;
1331 sub readfile_getnum {
1332 my ($r, $imageinfo, $filename) = @_;
1336 print STDERR "orientation: ".$imageinfo->{Orientation}."\n";
1337 # Check to see if the image contains the Orientation EXIF key,
1338 # but allow user to override using rotate
1339 if (!defined($r->dir_config("GalleryAutoRotate"))
1340 || $r->dir_config("GalleryAutoRotate") eq "1") {
1341 if (defined($imageinfo->{Orientation})) {
1342 print STDERR $imageinfo->{Orientation}."\n";
1343 if ($imageinfo->{Orientation} eq 'right_top') {
1346 elsif ($imageinfo->{Orientation} eq 'left_bot') {
1352 if (open(FH, "<$filename")) {
1356 unless ($temp =~ /^\d$/) {
1359 unless ($temp == 1 || $temp == 2 || $temp == 3) {
1368 sub get_filecontent {
1370 open(FH, $file) or return undef;
1381 my $filename = shift;
1382 my $comment_ref = {};
1383 $comment_ref->{TITLE} = undef;
1384 $comment_ref->{COMMENT} = '';
1386 open(FH, $filename) or return $comment_ref;
1388 if ($title =~ m/^TITLE: (.*)$/) {
1389 chomp($comment_ref->{TITLE} = $1);
1392 $comment_ref->{COMMENT} = $title;
1397 $comment_ref->{COMMENT} .= $_;
1401 return $comment_ref;
1406 my ($r, $statuscode, $errortitle, $error) = @_;
1408 my $tpl = $r->dir_config('GalleryTemplateDir');
1410 my %templates = create_templates({layout => "$tpl/layout.tpl",
1411 error => "$tpl/error.tpl",
1415 $tpl_vars{TITLE} = "Error! $errortitle";
1416 $tpl_vars{META} = "";
1417 $tpl_vars{ERRORTITLE} = "Error! $errortitle";
1418 $tpl_vars{ERROR} = $error;
1420 $tpl_vars{MAIN} = $templates{error}->fill_in(HASH => \%tpl_vars);
1422 $tpl_vars{PAGE} = $templates{layout}->fill_in(HASH => \%tpl_vars);
1424 $r->status($statuscode);
1425 $r->content_type('text/html');
1427 $r->print($tpl_vars{PAGE});
1435 my $root_text = (defined($r->dir_config('GalleryRootText')) ? $r->dir_config('GalleryRootText') : "root:" );
1436 my $root_path = (defined($r->dir_config('GalleryRootPath')) ? $r->dir_config('GalleryRootPath') : "" );
1438 my $subr = $r->lookup_uri($r->uri);
1439 my $filename = $subr->filename;
1441 my @links = split (/\//, $r->uri);
1443 $uri =~ s/^$root_path//g;
1445 @links = split (/\//, $uri);
1447 # Get the full path of the base directory
1450 my @direlem = split (/\//, $filename);
1451 for my $i ( 0 .. ( scalar(@direlem) - scalar(@links) ) ) {
1452 $dirname .= shift(@direlem) . '/';
1459 $picturename = pop(@links);
1462 if ($r->uri eq $root_path) {
1463 return qq{ <a href="$root_path">$root_text</a> };
1467 my $menuurl = $root_path;
1468 foreach my $link (@links) {
1470 $menuurl .= $link."/";
1471 my $linktext = $link;
1472 unless (length($link)) {
1473 $linktext = "$root_text ";
1477 $dirname = File::Spec->catdir($dirname, $link);
1479 if (-e $dirname . ".folder") {
1480 $linktext = get_filecontent($dirname . ".folder");
1484 if ("$root_path$uri" eq $menuurl) {
1485 $menu .= "$linktext / ";
1488 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule)."\">$linktext</a> / ";
1494 $menu .= $picturename;
1498 if ($r->dir_config('GallerySelectionMode') && $r->dir_config('GallerySelectionMode') eq '1') {
1499 $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule);
1500 $menu .= "?select=1\">[select]</a> ";
1508 my ($r, $infile, $outfile, $x, $y, $rotate, $copyrightfile, $GalleryTTFDir, $GalleryCopyrightText, $text_color, $GalleryTTFFile, $GalleryTTFSize, $GalleryCopyrightBackgroundColor, $quality) = @_;
1511 my $image = Image::Imlib2->load($infile) or warn("Unable to open file $infile, $!");
1514 $image=$image->create_scaled_image($x, $y) or warn("Unable to scale image $infile. Are you running out of memory?");
1518 $image->image_orientate($rotate);
1521 # blend copyright image onto image
1522 if ($copyrightfile ne '') {
1523 if (-f $copyrightfile and (my $logo=Image::Imlib2->load($copyrightfile))) {
1524 my $x = $image->get_width();
1525 my $y = $image->get_height();
1526 my $logox = $logo->get_width();
1527 my $logoy = $logo->get_height();
1528 $image->blend($logo, 0, 0, 0, $logox, $logoy, $x-$logox, $y-$logoy, $logox, $logoy);
1531 log_error("GalleryCopyrightImage $copyrightfile was not found");
1535 if ($GalleryTTFDir && $GalleryCopyrightText && $GalleryTTFFile && $text_color) {
1536 if (!-d $GalleryTTFDir) {
1538 log_error("GalleryTTFDir $GalleryTTFDir is not a dir\n");
1540 } elsif ($GalleryCopyrightText eq '') {
1542 log_error("GalleryCopyrightText is empty. No text inserted to picture\n");
1544 } elsif (!-e "$GalleryTTFDir/$GalleryTTFFile") {
1546 log_error("GalleryTTFFile $GalleryTTFFile was not found\n");
1550 $GalleryTTFFile =~ s/\.TTF$//i;
1551 $image->add_font_path("$GalleryTTFDir");
1553 $image->load_font("$GalleryTTFFile/$GalleryTTFSize");
1554 my($text_x, $text_y) = $image->get_text_size("$GalleryCopyrightText");
1555 my $x = $image->get_width();
1556 my $y = $image->get_height();
1560 if (($text_x < $x - $offset) && ($text_y < $y - $offset)) {
1561 if ($GalleryCopyrightBackgroundColor =~ /^\d+,\d+,\d+,\d+$/) {
1562 my ($br_val, $bg_val, $bb_val, $ba_val) = split (/,/, $GalleryCopyrightBackgroundColor);
1563 $image->set_colour($br_val, $bg_val, $bb_val, $ba_val);
1564 $image->fill_rectangle ($x-$text_x-$offset, $y-$text_y-$offset, $text_x, $text_y);
1566 my ($r_val, $g_val, $b_val, $a_val) = split (/,/, $text_color);
1567 $image->set_colour($r_val, $g_val, $b_val, $a_val);
1568 $image->draw_text($x-$text_x-$offset, $y-$text_y-$offset, "$GalleryCopyrightText");
1570 log_error("Text is to big for the picture.\n");
1575 if ($quality && $quality =~ m/^\d+$/) {
1576 $image->set_quality($quality);
1579 $image->save($outfile);
1586 my $sortby = $r->dir_config('GallerySortBy');
1587 my $filename=$r->lookup_uri($r->uri)->filename;
1588 $filename=(File::Spec->splitpath($filename))[1] if (-f $filename);
1589 if ($sortby && $sortby =~ m/^(size|atime|mtime|ctime)$/) {
1590 @files = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$sortby()." $_", @files));
1592 @files = sort @files;
1597 # Create Text::Template objects used by Apache::Gallery. Takes a
1598 # hashref of template_name, template_filename pairs, and returns a
1599 # list of template_name, texttemplate_object pairs.
1600 sub create_templates {
1601 my $templates = shift;
1603 # This routine is called whenever a template has an error. Prints
1604 # the error to STDERR and sticks the error in the output
1607 # Pull out the name and filename from the arg option [see
1608 # Text::Template for details]
1609 @args{qw(name file)} = @{$args{arg}};
1610 print STDERR qq(Template $args{name} ("$args{file}") is broken: $args{error});
1611 # Don't include the file name in the output, as the user can see this.
1612 return qq(<!-- Template $args{name} is broken: $args{error} -->);
1617 my %texttemplate_objects;
1619 for my $template_name (keys %$templates) {
1620 my $tt_obj = Text::Template->new(TYPE => 'FILE',
1621 SOURCE => $$templates{$template_name},
1622 BROKEN => \&tt_broken,
1623 BROKEN_ARG => [$template_name, $$templates{$template_name}],
1625 or die "Unable to create new Text::Template object for $template_name: $Text::Template::ERROR";
1626 $texttemplate_objects{$template_name} = $tt_obj;
1628 return %texttemplate_objects;
1633 Apache2::RequestUtil->request->log_error(shift());
1635 Apache->request->log_error(shift());
1643 Apache::Gallery - mod_perl handler to create an image gallery
1647 See the INSTALL file in the distribution for installation instructions.
1651 Apache::Gallery creates an thumbnail index of each directory and allows
1652 viewing pictures in different resolutions. Pictures are resized on the
1653 fly and cached. The gallery can be configured and customized in many ways
1654 and a custom copyright image can be added to all the images without
1655 modifying the original.
1657 =head1 CONFIGURATION
1659 In your httpd.conf you set the global options for the gallery. You can
1660 also override each of the options in .htaccess files in your gallery
1663 The options are set in the httpd.conf/.htaccess file using the syntax:
1664 B<PerlSetVar OptionName 'value'>
1666 Example: B<PerlSetVar GalleryCacheDir '/var/cache/www/'>
1670 =item B<GalleryAutoRotate>
1672 Some cameras, like the Canon G3, can detect the orientation of a
1673 the pictures you take and will save this information in the
1674 'Orientation' EXIF field. Apache::Gallery will then automatically
1677 This behavior is default but can be disabled by setting GalleryAutoRotate
1680 =item B<GalleryCacheDir>
1682 Directory where Apache::Gallery should create its cache with scaled
1683 pictures. The default is /var/cache/www/ . Here, a directory for each
1684 virtualhost or location will be created automatically. Make sure your
1685 webserver has write access to the CacheDir.
1687 =item B<GalleryTemplateDir>
1689 Full path to the directory where you placed the templates. This option
1690 can be used both in your global configuration and in .htaccess files,
1691 this way you can have different layouts in different parts of your
1694 No default value, this option is required.
1696 =item B<GalleryInfo>
1698 With this option you can define which EXIF information you would like
1699 to present from the image. The format is: '<MyName => KeyInEXIF,
1700 MyOtherName => OtherKeyInEXIF'
1702 Examples of keys: B<ShutterSpeedValue>, B<ApertureValue>, B<SubjectDistance>,
1705 You can view all the keys from the EXIF header using this perl-oneliner:
1707 perl C<-e> 'use Data::Dumper; use Image::Info qw(image_info); print Dumper(image_info(shift));' filename.jpg
1709 Default is: 'Picture Taken => DateTimeOriginal, Flash => Flash'
1711 =item B<GallerySizes>
1713 Defines which widths images can be scaled to. Images cannot be
1714 scaled to other widths than the ones you define with this option.
1716 The default is '640 800 1024 1600'
1718 =item B<GalleryThumbnailSize>
1720 Defines the width and height of the thumbnail images.
1722 Defaults to '100x75'
1724 =item B<GalleryThumbnailSizeLS>
1726 If set to '1', B<GalleryThumbnailSize> is the long and the short side of
1727 the thumbnail image instead of the width and height.
1731 =item B<GalleryCopyrightImage>
1733 Image you want to blend into your images in the lower right
1734 corner. This could be a transparent png saying "copyright
1739 =item B<GalleryWrapNavigation>
1741 Make the navigation in the picture view wrap around (So Next
1742 at the end displays the first picture, etc.)
1744 Set to 1 or 0, default is 0
1746 =item B<GalleryAllowOriginal>
1748 Allow the user to download the Original picture without
1749 resizing or putting the CopyrightImage on it.
1751 Set to 1 or 0, default is 0
1753 =item B<GallerySlideshowIntervals>
1755 With this option you can configure which intervals can be selected for
1756 a slideshow. The default is '3 5 10 15 30'
1758 =item B<GallerySortBy>
1760 Instead of the default filename ordering you can sort by any
1761 stat attribute. For example size, atime, mtime, ctime.
1763 =item B<GalleryDirSortBy>
1765 Set this variable to sort directories differently than other items,
1766 can be set to size, atime, mtime and ctime; setting any other value
1767 will revert to sorting by name.
1769 =item B<GalleryMemoize>
1771 Cache EXIF data using Memoize - this will make Apache::Gallery faster
1772 when many people access the same images, but it will also cache EXIF
1773 data until the current Apache child dies.
1775 =item B<GalleryUseFileDate>
1777 Set this option to 1 to make A::G show the files timestamp
1778 instead of the EXIF value for "Picture taken".
1780 =item B<GallerySelectionMode>
1782 Enable the selection mode. Select images with checkboxes and
1783 get a list of filenames.
1785 =item B<GalleryEXIFMode>
1787 You can choose how Apache::Gallery should display EXIF info
1790 The default setting is 'namevalue'. This setting will make
1791 Apache::Gallery print out the names and values of the EXIF values
1792 you configure with GalleryInfo. The information will be parsed into
1793 $INFO in pictureinfo.tpl.
1795 You can also set it to 'values' which will make A::G parse
1796 the configured values into the var $EXIFVALUES as 'value | value | value'
1798 If you set this option to 'variables' the items you configure in GalleryInfo
1799 will be available to your templates as $EXIF_<KEYNAME> (in all uppercase).
1800 That means that with the default setting "Picture Taken => DateTimeOriginal,
1801 Flash => Flash" you will have the variables $EXIF_DATETIMEORIGINAL and
1802 $EXIF_FLASH available to your templates. You can place them
1805 =item B<GalleryRootPath>
1807 Change the location of gallery root. The default is ""
1809 =item B<GalleryRootText>
1811 Change the name that appears as the root element in the menu. The
1814 =item B<GalleryMaxThumbnailsPerPage>
1816 This options controls how many thumbnails should be displayed in a
1817 page. It requires $BROWSELINKS to be in the index.tpl template file.
1819 =item B<GalleryImgFile>
1821 Pattern matching the files you want Apache::Gallery to view in the
1822 index as thumbnails.
1824 The default is '\.(jpe?g|png|tiff?|ppm)$'
1826 =item B<GalleryDocFile>
1828 Pattern matching the files you want Apache::Gallery to view in the index
1829 as normal files. All other filetypes will still be served by Apache::Gallery
1830 but are not visible in the index.
1832 The default is '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|ogg|pdf|rtf|wav|dlt|txt|html?|csv|eps)$'
1834 =item B<GalleryTTFDir>
1836 To use the GalleryCopyrightText feature you must set this option to the
1837 directory where your True Type fonts are stored. No default is set.
1841 PerlSetVar GalleryTTFDir '/usr/share/fonts/'
1843 =item B<GalleryTTFFile>
1845 To use the GalleryCopyrightText feature this option must be set to the
1846 name of the True Type font you wish to use. Example:
1848 PerlSetVar GalleryTTFFile 'verdanab.ttf'
1850 =item B<GalleryTTFSize>
1852 Configure the size of the CopyrightText that will be inserted as
1853 copyright notice in the corner of your pictures.
1857 PerlSetVar GalleryTTFSize '10'
1859 =item B<GalleryCopyrightText>
1861 The text that will be inserted as copyright notice.
1865 PerlSetVar GalleryCopyrightText '(c) Michael Legart'
1867 =item B<GalleryCopyrightColor>
1869 The text color of your copyright notice.
1874 PerlSetVar GalleryCopyrightColor '255,255,255,255'
1877 PerlSetVar GalleryCopyrightColor '0,0,0,255'
1880 PerlSetVar GalleryCopyrightColor '255,0,0,255'
1883 PerlSetVar GalleryCopyrightColor '0,255,0,255'
1886 PerlSetVar GalleryCopyrightColor '0,0,255,255'
1889 PerlSetVar GalleryCopyrightColor '255,127,0,127'
1891 =item B<GalleryCopyrightBackgroundColor>
1893 The background-color of a GalleryCopyrightText
1895 r,g,b,a - for examples, see GalleryCopyrightColor
1897 =item B<GalleryQuality>
1899 The quality (1-100) of scaled images
1901 This setting affects the quality of the scaled images.
1902 Set this to a low number to reduce the size of the scaled images.
1903 Remember to clear out your cache if you change this setting.
1904 Quality seems to default to 75, at least in the jpeg and png loader code in
1910 PerlSetVar GalleryQuality '50'
1912 =item B<GalleryUnderscoresToSpaces>
1914 Set this option to 1 to convert underscores to spaces in the listing
1915 of directory and file names, as well as in the alt attribute for HTML
1922 =item B<GalleryCommentExifKey>
1924 Set this option to e.g. ImageDescription to use this field as comments
1927 =item B<GalleryEnableMediaRss>
1929 Set this option to 1 to enable generation of a media RSS feed. This
1930 can be used e.g. together with the PicLens plugin from http://piclens.com
1938 =item B<Rotate images>
1940 Some cameras, like the Canon G3, detects the orientation of a picture
1941 and adds this info to the EXIF header. Apache::Gallery detects this
1942 and automatically rotates images with this info.
1944 If your camera does not support this, you can rotate the images
1945 manually, This can also be used to override the rotate information
1946 from a camera that supports that. You can also disable this behavior
1947 with the GalleryAutoRotate option.
1949 To use this functionality you have to create file with the name of the
1950 picture you want rotated appended with ".rotate". The file should include
1951 a number where these numbers are supported:
1953 "1", rotates clockwise by 90 degree
1954 "2", rotates clockwise by 180 degrees
1955 "3", rotates clockwise by 270 degrees
1957 So if we want to rotate "Picture1234.jpg" 90 degrees clockwise we would
1958 create a file in the same directory called "Picture1234.jpg.rotate" with
1959 the number 1 inside of it.
1961 =item B<Ignore directories/files>
1963 To ignore a directory or a file (of any kind, not only images) you
1964 create a <directory|file>.ignore file.
1968 To include comments for a directory you create a <directory>.comment
1969 file where the first line can contain "TITLE: New title" which
1970 will be the title of the page, and a comment on the following
1972 To include comments for each picture you create files called
1973 picture.jpg.comment where the first line can contain "TITLE: New
1974 title" which will be the title of the page, and a comment on the
1979 TITLE: This is the new title of the page
1980 And this is the comment.<br />
1981 And this is line two of the comment.
1983 The visible name of the folder is by default identical to the name of
1984 the folder, but can be changed by creating a file <directory>.folder
1985 with the visible name of the folder.
1987 It is also possible to set GalleryCommentExifKey to the name of an EXIF
1988 field containing the comment, e.g. ImageDescription. The EXIF comment is
1989 overridden by the .comment file if it exists.
1999 =item B<Apache with mod_perl>
2001 =item B<URI::Escape>
2003 =item B<Image::Info>
2005 =item B<Image::Size>
2007 =item B<Text::Template>
2009 =item B<Image::Imlib2>
2011 =item B<X11 libraries>
2015 Remember the -dev package when using rpm, deb or other package formats!
2021 Michael Legart <michael@legart.dk>
2023 =head1 COPYRIGHT AND LICENSE
2025 Copyright (C) 2001-2011 Michael Legart <michael@legart.dk>
2027 Templates designed by Thomas Kjaer <tk@lnx.dk>
2029 Apache::Gallery is free software and is released under the Artistic License.
2030 See B<http://www.perl.com/language/misc/Artistic.html> for details.
2032 The video icons are from the GNOME project. B<http://www.gnome.org/>
2036 Thanks to Thomas Kjaer for templates and design of B<http://apachegallery.dk>
2037 Thanks to Thomas Eibner and other for patches. (See the Changes file)
2041 L<perl>, L<mod_perl>, L<Image::Imlib2>, L<CGI::FastTemplate>,
2042 L<Image::Info>, and L<Image::Size>.