#!/usr/bin/perl -w # # captivate: read photo metadata from jpeg files using exiftool or epinfo, # display the images and prompt the user for captions, then write # the combined metadata to RDF and HTML files for each image. # # Gerald Oskoboiny, 4 Sep 2000 # contribution Matthieu Fuzellier, since Jan 2006 # # original source: http://impressive.net/software/photo/source/captivate # modified version source: http://www.mattiouz.com/software/photo/source/captivate # # license: GPL # # $Id: captivate.pl 143 2008-04-08 20:23:54Z matthieu $ use strict; use lib "/home/matthieu/bin/Exiftool/lib"; use Image::ExifTool; my $debuggin = 0; my $exiftool = 'exiftool'; my $temp = "/tmp/captivate.$$"; my $edit_instructions = qq{# Enter text for these photos in the space above. Lines in this file starting # with '#' will be ignored; leading and trailing whitespace will be removed # from field values. Field values may NOT yet span multiple lines. }; my $revision = q$Revision: 143 $; $revision =~ s/Revision: ([\d\.]+) /$1/; my @field_order = ( "dc:title", "dc:description", "dc:coverage", "dc:date", "dc:creator", "dc:publisher", "dc:type", "dc:format", "dc:source", "dc:identifier", "dc:relation", "exif:Artist", "exif:Copyright", "exif:ImageDescription", "exif:UserComment", "exif:Make", "exif:Model", "exif:Orientation", "exif:XResolution", "exif:YResolution", "exif:ResolutionUnit", "exif:ExposureTime", "exif:FNumber", "exif:ISOSpeedRatings", "exif:MeteringMode", "exif:LightSource", "exif:Flash", "exif:FocalLength", "exif:ExifImageWidth", "exif:ExifImageLength", # GPS tags info? "exif:GPSLatitude", "exif:GPSLatitudeRef", "exif:GPSLongitude", "exif:GPSLongitudeRef", "exif:GPSAltitude", "exif:GPSAltitudeRef", # @@ anything else interesting in exif info? # IPTC tags info ? "iptc:City", "iptc:Province-State", "iptc:Country-PrimaryLocationName", # Other "cap:depicts", "cap:visibility", "cap:quality", "cap:GPSLatitudeNum", "cap:GPSLongitudeNum", "cap:local-time", "cap:coord", "t:camera" ); my %uri_fields = ( "cap:header" => 1, "cap:footer" => 1, "cap:adsense-bottom" => 1, "cap:adsense-right" => 1, "dc:publisher" => 1, "dc:creator" => 1, "t:camera" => 1 ); my @wanted_fields= ( "dc:title", "dc:description", "dc:coverage", "cap:depicts", "cap:visibility", "cap:quality" ); our $variant_pat = '-(sq|tn|sm|med)'; # pattern that matches variants my $defaults_file = ".captivate.rdf"; my $global_defaults = $ENV{HOME} . "/.captivate/" . $defaults_file; my $already_printed_header = 0; my $already_printed_footer = 0; my $already_printed_index_in_htaccess = 0; my $curr_image_num = 1; my $curr_vid_num = 1; my $privateUsers = "matthieu"; my $friendsUsers = "valid-user"; (my $second, my $minute, my $hour, my $dayOfMonth, my $month, my $yearOffset, my $dayOfWeek, my $dayOfYear, my $daylightSavings) = localtime(); my $currentYear = 1900 + $yearOffset; my $currentMonth = $month + 1; my $exifTool = new Image::ExifTool; my %metadata; # global hash to keep track of image metadata my %collection; # global hash to keep track of data about the collection my %opt; # global hash to keep track of command line options # parse command line arguments my @files = &parse_args( \%opt, \@ARGV ); # create an index file if needed if ( $opt{index} ) { my $temp = $opt{indexfile} . ".tmp"; open( INDEX, "> $temp" ) or do { print STDERR "error creating index file, $temp: $!\n"; print STDERR "Skipping index file generation.\n"; $opt{index} = 0; } } # create a public index file if needed if ( $opt{index} && $opt{publicIndex} ) { my $temp = $opt{indexfile}; $temp =~ s/\.html$/.public.html.tmp/; open( PUBLICINDEX, "> $temp" ) or do { print STDERR "error creating public index file, $temp: $!\n"; print STDERR "Skipping index file generation.\n"; $opt{publicIndex} = 0; } } # create a .htaccess file if needed if ( $opt{generateHtaccess} ) { my $temp = ".htaccess"; open( HTACCESS, "> $temp" ) or do { print STDERR "error creating .htaccess file, $temp: $!\n"; print STDERR "Skipping htaccess file generation.\n"; $opt{generateHtaccess} = 0; } } &find_metadata_for_file( \%metadata, "___bogus" ); # Read header, footer and ads from text files &read_header_from_file; &read_footer_from_file; &read_adsense_from_file; # Generate header for index file if needed &print_index_header if $opt{index}; # remember some stuff for next/prev links on web pages my $prev_file; foreach my $file (@files) { undef %metadata; &find_metadata_for_file( \%metadata, $file ); if ( defined $prev_file and ($prev_file ne "") ) { $collection{$file}{prevfile} = $prev_file; $collection{$prev_file}{nextfile} = $file; } $collection{$file}{title} = $metadata{"dc:title"}; my $visibility = "friends"; $visibility = "public" if ($metadata{"exif:UserComment"} =~ m/visibility:public/); $visibility = "private" if ($metadata{"exif:UserComment"} =~ m/visibility:private/); $collection{$file}{visibility} = $visibility; $prev_file = $file; } # generate output in misc formats for each file specified foreach my $file (@files) { undef %metadata; # Read metadata for the specified files (from rdf or exiftool) &find_metadata_for_file( \%metadata, $file ); my $isPublicPhoto = ($metadata{"exif:UserComment"} =~ m/visibility:public/); &generate_rdf( \%metadata, $file ); $metadata{"infotext"} = &generate_infotext( \%metadata, $file ); &generate_html( \%metadata, $file, $isPublicPhoto ); if ($opt{index}) { my $indexEntry = &generate_index_entry( \%metadata, $file ); print INDEX $indexEntry; if ( $opt{publicIndex} && $isPublicPhoto ) { $indexEntry =~s/\.html/.public.html/g; print PUBLICINDEX $indexEntry; } } print HTACCESS &generate_htaccess_entry( \%metadata, $file ); } # generate output for videos if ( $opt{videos} ) { # Get a list of all the videos opendir(VIDEOSDIR, $opt{videosDir}); my @videos = grep(/\.flv$/,readdir(VIDEOSDIR)); closedir(VIDEOSDIR); my $videosCount = @videos; if ( $opt{index} and ($videosCount > 0) ) { my $videosHeader = qq{
\n}; $videosHeader .= qq{\n}; $videosHeader .= qq{

Videos:

\n\n}; print INDEX $videosHeader; print "Video count: $videosCount\n" if $debuggin; } # Loop foreach video foreach my $video (@videos) { print "Video: $video\n" if $debuggin; my $videoDir = $opt{videosDir}; $videoDir =~ s,/$,,; # strip trailing '/' if it exists my $videoJpg = $videoDir . "/" . $video; $videoJpg =~ s/\.flv$/.jpg/; # We NEED a jpg thumbnail for each video. metadata will be extracted from that thumbnail's exif undef %metadata; &find_metadata_for_file( \%metadata, $videoJpg ); $metadata{"infotext"} = &generate_infotext( \%metadata, $videoDir."/".$videoJpg ); &generate_rdf( \%metadata, $videoJpg ); # Generate html for videos &generate_video_html( $video ); # If $opt{index} generate index entry fo video. if ($opt{index}) { my $indexVideoEntry = &generate_index_video_entry( $video ); print INDEX $indexVideoEntry; } } } # Generate footer for index file if needed and close file(s) if ( $opt{index} ) { &print_index_footer; close( INDEX ) or warn "error closing index file: $!"; &replace_file_iff_different( $opt{indexfile}, $opt{indexfile} . ".tmp" ); if ($opt{publicIndex}) { close( PUBLICINDEX ) or warn "error closing public index file: $!"; my $pubIndex = $opt{indexfile}; $pubIndex =~ s/\.html$/.public.html/; &replace_file_iff_different( $pubIndex, $pubIndex . ".tmp" ); } } # Close .htaccess file if ( $opt{generateHtaccess} ) { close( HTACCESS ) or warn "error closing htaccess file: $!"; } unlink $temp; exit; ############################### Functions ################################### ### Args ### sub parse_args { # parse command line arguments, canonicalize image filenames, etc. my ($opt_ref,$args_ref) = @_; my $opt = %$opt_ref; # deref my @args = @$args_ref; # deref my %seen; my @files; $opt{mapZoom} = 14; $opt{generateHtaccess} = 1; while (my $arg = shift @args) { # @@ should use Getopt::Long instead of parsing args manually if (( $arg eq "--publicIndex" ) || ( $arg eq "-p" )) { $opt{publicIndex} = 1; } elsif (( $arg eq "--satellite" ) || ( $arg eq "-s" )) { $opt{satellite} = 1; } elsif (( $arg eq "--videos" ) || ( $arg eq "-V" )) { $opt{videos} = 1; $opt{videosDir} = shift @args; if ( ! length $opt{videosDir} ) { print STDERR "ignoring --videos; no directory specified!?"; $opt{videos} = 0; } } elsif (( $arg eq "--index" ) || ( $arg eq "-i" )) { $opt{index} = 1; $opt{indexfile} = shift @args; if ( ! length $opt{indexfile} ) { print STDERR "ignoring --index; no filename specified!?"; $opt{index} = 0; } } elsif (( $arg eq "--nohtaccess" ) || ( $arg eq "-A" )) { $opt{generateHtaccess} = 0; } elsif (( $arg eq "--title" ) || ( $arg eq "-t" )) { $opt{indextitle} = shift @args; if ( ! length $opt{indextitle} ) { print STDERR "ignoring --title; no title specified!?"; } } elsif (( $arg eq "--mapzoom" ) || ( $arg eq "-z" )) { $opt{mapZoom} = shift @args; if ( ! length $opt{mapZoom} ) { print STDERR "ignoring --mapzoom; no zoom specified!?"; } } elsif ( $arg =~ /^-/ ) { # unknown option specified? &print_usage; } else { my $basefile = $arg; $basefile =~ s,\.[^\.]+$,,; # strip file extension $basefile =~ s,$variant_pat$,,; # strip '-sm'/'-med'/... my $file = $basefile . ".jpg"; if ( ! -f $file ) { print "skipping $arg because $file is not a file\n" if $debuggin; next; } if ( $seen{$file} ) { print "skipping $arg because we have already seen file $file\n" if $debuggin; } else { $seen{$file}++; push( @files, $file ); # add this file to the list } } } return @files; } sub print_usage { # @@ write a real usage blurb sometime print STDERR "\nSYNTAX ERROR! "; print STDERR "http://www.mnftiu.cc/mnftiu.cc/images/filing.029.gif\n\n"; exit -1; } ### Metadata ### sub find_metadata_for_file { my ($metadata_ref, $file) = @_; my $metadata = %$metadata_ref; # deref &read_rdf_from_file( \%metadata, $global_defaults ) && &error_missing_global_defaults; # @@ inherit defaults from parent directories, too &read_rdf_from_file( \%metadata, $defaults_file ); my $rdffile = $file; $rdffile =~ s,\.[^\.]+$,.rdf,; # name of the .rdf file associated with $file # read in any previously-stored RDF values &read_rdf_from_file( \%metadata, $rdffile ); # read metadata from image file using exiftool &read_exiftool_from_file( \%metadata, $file ) unless ( defined $metadata{"dc:date"} and length( $metadata{"dc:date"} ) == 20 ); # already got metadata } sub read_rdf_from_file { my ($metadata_ref, $rdf_file) = @_; my $metadata = %$metadata_ref; # deref if ( ! -r $rdf_file ) { return -1; } print "Reading rdf values from $rdf_file...\n" if $debuggin; open( DEFAULTS, "< $rdf_file" ) or warn "could not read from rdf file $rdf_file because $!"; while () { # should maybe set $/ = ">" to parse this? (or use a real parser) # @@ should probably preserve unknown stuff instead of nuking it chomp; next if /^\s*<\?xml version/; #skip xml decl next if /^\s*<\/?rdf/; next if /^\s*xmlns:/; # sample: # if (my($key,$value)=(/^\s*<([^ >]+)\s+rdf:resource="([^"]*)"\/>\s*$/)){ if ( ! defined $uri_fields{$key} ) { print STDERR "Error in rdf file $rdf_file:\n"; print STDERR "$key is not a URI-happy field.\n"; print STDERR "I'll just pretend I never saw that...\n\n"; next; } $metadata{$key} = $value; print "r_d: assigned key [$key] value [$value]\n" if $debuggin; } # sample: # Gerald looking really cool if (my ($key,$value) = (/^\s*<([^ >]+)>([^<]*)<\//)) { $metadata{$key} = $value; print "r_d: assigned key [$key] value [$value]\n" if $debuggin; } } close DEFAULTS or warn "error closing rdf file $rdf_file: $!"; return 0; } sub error_missing_global_defaults { print STDERR "You need to create $global_defaults with some default values.\n"; print STDERR "Sample: http://impressive.net/people/gerald/misc/dotfiles/captivate.rdf\n\n"; print STDERR "At a minimum, cap:viewer and cap:editor need to be defined.\n\n"; exit; } sub read_exiftool_from_file { my ($metadata_ref, $file) = @_; my $metadata = %$metadata_ref; # deref if ( ! -r $file ) { return -1; } print "Reading exiftool from $file...\n" if $debuggin; undef my %exifToolInfo; my $exifToolInfo = $exifTool->ImageInfo($file); foreach (keys %$exifToolInfo) { print "$_ => $$exifToolInfo{$_}\n" if $debuggin; } # preserve/convert the info we are interested in if ( defined $$exifToolInfo{DateTimeOriginal} ) { $metadata{"dc:date"} = &exif_date_to_iso8601($$exifToolInfo{DateTimeOriginal}); # Local time nicer print $exifTool->Options(DateFormat => '%Y-%m-%d %H:%M'); my $info = $exifTool->ImageInfo($file, 'DateTimeOriginal'); $metadata{"cap:local-time"} = $$info{'DateTimeOriginal'}; } if ( defined $$exifToolInfo{ImageDescription} ) { $metadata{"dc:title"} = $$exifToolInfo{ImageDescription}; $metadata{"dc:description"} = $$exifToolInfo{ImageDescription}; } if ( defined $$exifToolInfo{City} or defined $$exifToolInfo{'Province-State'} or defined $$exifToolInfo{'Country-PrimaryLocationName'}) { my $blurb = ""; $blurb .= $$exifToolInfo{City} if defined $$exifToolInfo{City}; if (defined $$exifToolInfo{'Province-State'}){ $blurb .= ", " if $blurb ne ""; $blurb .= $$exifToolInfo{'Province-State'} ; } if (defined $$exifToolInfo{'Country-PrimaryLocationName'}){ $blurb .= ", " if $blurb ne ""; $blurb .= $$exifToolInfo{'Country-PrimaryLocationName'} ; } $metadata{"dc:coverage"} = $blurb; } if ( defined $$exifToolInfo{UserComment} and $$exifToolInfo{UserComment} =~ m/visibility:/) { if ( $$exifToolInfo{UserComment} =~ m/visibility:public/ ) { $metadata{"cap:visibility"} = 'public'; } elsif ( $$exifToolInfo{UserComment} =~ m/visibility:friends/ ) { $metadata{"cap:visibility"} = 'friends'; } else { $metadata{"cap:visibility"} = 'private'; } } # copy any interesting exif data we found into %metadata foreach my $field (@field_order) { next unless $field =~ /^exif:/ or $field =~ /^iptc:/; my $shortfield = $field; $shortfield =~ s/^exif:|^iptc://; $metadata{$field} = $$exifToolInfo{$shortfield} if defined $$exifToolInfo{$shortfield}; } # if we have GPS coord, add numeric value for it into %metadata if (defined( $metadata{"exif:GPSLatitudeRef"} ) and defined( $metadata{"exif:GPSLatitude"} ) and defined( $metadata{"exif:GPSLongitudeRef"} ) and defined( $metadata{"exif:GPSLongitude"} ) ) { $exifTool->Options(CoordFormat => '%.15f'); my $exifToolInfo = $exifTool->ImageInfo($file, 'GPSLatitude', 'GPSLongitude', 'GPSLatitudeRef', 'GPSLongitudeRef'); my $lat = $$exifToolInfo{'GPSLatitude'}; my $long = $$exifToolInfo{'GPSLongitude'}; if ( $$exifToolInfo{'GPSLatitudeRef'} eq "South") { $lat *= -1; } if ( $$exifToolInfo{'GPSLongitudeRef'} eq "West") { $long *= -1; } $metadata{'cap:GPSLatitudeNum'} = $lat; $metadata{'cap:GPSLongitudeNum'} = $long; # Nicer for html print my $coord = ""; $coord = sprintf('%.2f', $$exifToolInfo{'GPSLatitude'}); $coord .= "° " . $$exifToolInfo{'GPSLatitudeRef'}; $coord .= " - " . sprintf('%.2f', $$exifToolInfo{'GPSLongitude'}); $coord .= "° " . $$exifToolInfo{'GPSLongitudeRef'}; $metadata{'cap:coord'} = $coord; } return 0; } sub exif_date_to_iso8601 { # convert an exif-format date to an iso-8601 format date # e.g.: "2001:09:08 15:21:29" -> 2001-09-08T15:21:29Z my $date = shift; $date =~ s/"//g; $date =~ s/:/-/; $date =~ s/:/-/; # just the first two, not all of them $date =~ s/ /T/; $date =~ s/$/Z/; return $date; } ### Index ### sub print_index_header { my $disclaimer = ""; $disclaimer .= qq{

This is the public version of this page.\n}; $disclaimer .= qq{ There is another version with more pictures for my family and friends\n}; $disclaimer .= qq{ (Password required\n}; $disclaimer .= qq{ Password protected).
\n}; $disclaimer .= qq{ If you need a password request one using the\n}; $disclaimer .= qq{ Account request form. \n}; $disclaimer .= qq{ You can also browse my public photos\n}; $disclaimer .= qq{ by date or \n}; $disclaimer .= qq{ by location.\n}; $disclaimer .= qq{

\n}; if ( defined $collection{headerText} ) { my $indexHeader = $collection{headerText}; $indexHeader .= qq{
\n}; $indexHeader .= qq{
\n}; $indexHeader .= qq{
\n}; $indexHeader .= qq{
\n}; # adsense bottom $indexHeader .= $collection{adBottomText} if ( defined $collection{adBottomText} ); $indexHeader .= qq{\n} if ($opt{publicIndex}); $indexHeader .= qq{

$opt{indextitle}

\n\n}; $indexHeader .= qq{
\n\n}; $indexHeader =~ s/%%title%%/$opt{indextitle}/g; $indexHeader =~ s/%%year%%/$currentYear/g; $indexHeader =~ s/%%date%%/$hour:$minute:$second $dayOfMonth\/$currentMonth\/$currentYear/g; my $keywords = "photo,travel,video," . $opt{indextitle}; $indexHeader =~ s/%%keywords%%/$keywords/g; print INDEX $indexHeader; if ($opt{publicIndex}){ my $publicIndexHeader = $indexHeader; $publicIndexHeader =~ s//$disclaimer/g; # Replace the Overview link with public one for the year albums $publicIndexHeader =~ s/([0-9][0-9][0-9][0-9])\/Overview\.html/$1\/Overview.public.html/g; print PUBLICINDEX $publicIndexHeader; } } } sub generate_index_entry { my ($metadata_ref, $file) = @_; my $metadata = %$metadata_ref; # deref $file = &relativize_dir_path( $opt{indexfile} ) . $file; # @@ should get this stuff from user prefs my $src = $file; $src =~ s/\.jpg$/-sq.jpg/; my $href = $file; $href =~ s/\.jpg$/-sm.html/; my $text = ""; $text .= qq{
\n}; $text .= qq{

\n}; $text .= qq{

}; $text .= $metadata{"dc:title"} . qq{

\n}; $text .= qq{
\n\n}; $curr_image_num++; return $text; } sub generate_index_video_entry { my ($video) = @_; my $videoDir = $opt{videosDir}; $videoDir =~ s,/$,,; # strip trailing '/' my $videoHtml = $video; $videoHtml =~ s/\.flv$/.html/; my $videoJpg = $video; $videoJpg =~ s/\.flv$/-sq.jpg/; my $title = $metadata{"dc:title"} || "No title specified"; my $videoIndexText = ""; $videoIndexText .= qq{
\n}; $videoIndexText .= qq{

\n}; $videoIndexText .= qq{

}; $videoIndexText .= qq{ $title

\n}; $videoIndexText .= qq{
\n\n}; $curr_vid_num++; return $videoIndexText; } sub print_index_footer { if ( defined $collection{footerText} ) { my $indexFooter = ""; $indexFooter .= qq{
\n}; $indexFooter .= qq{

\n}; $indexFooter .= qq{
\n\n}; $indexFooter .= qq{
\n}; $indexFooter .= $collection{adRightText} if ( defined $collection{adRightText} ); $indexFooter .= qq{
\n}; $indexFooter .= qq{
\n}; $indexFooter .= qq{
\n}; $indexFooter .= qq{
\n}; $indexFooter .= $collection{footerText}; &update_time(); # Update value for current time $indexFooter =~ s/%%year%%/$currentYear/g; $indexFooter =~ s/%%date%%/$hour:$minute:$second $dayOfMonth\/$currentMonth\/$currentYear/g; print INDEX $indexFooter; print PUBLICINDEX $indexFooter if ($opt{publicIndex}); } } ### .htaccess ### sub generate_htaccess_entry { my ($metadata_ref, $file) = @_; my $metadata = %$metadata_ref; # deref my $text = ""; if (!$already_printed_index_in_htaccess) { $text .= qq{AuthType Digest\n}; $text .= qq{AuthName "Family and friends restricted files"\n}; $text .= qq{AuthUserFile /usr/local/apache2/passwd/passwords.digest\n\n}; if ($opt{index}) { $text .= qq{\n}; $text .= qq{ Require valid-user\n}; $text .= qq{\n\n}; } $already_printed_index_in_htaccess = 1; } my $visibility = "friends"; $visibility = "public" if ($metadata{"exif:UserComment"} =~ m/visibility:public/); $visibility = "private" if ($metadata{"exif:UserComment"} =~ m/visibility:private/); my $html = $file; $html =~ s/\.jpg$/.html/; my $htmlVariants = $file; if ($visibility =~ m/^public$/) { $htmlVariants =~ s/\.jpg$/-(sm|med).html/; } else { $htmlVariants =~ s/\.jpg$/-(sq|tn|sm|med)/; } $text .= qq{\n}; if ($visibility =~ m/^private$/) { $text .= qq{ Require user $privateUsers\n}; } else { $text .= qq{ Require $friendsUsers\n}; } $text .= qq{\n\n}; return $text; } ### Output for photos ### sub generate_rdf { my ($metadata_ref, $file) = @_; my $metadata = %$metadata_ref; # deref my $rdffile = $file; $rdffile =~ s/\.jpg$/.rdf/; my $basefile = &basename( $file ); my $temp = $rdffile . ".tmp"; open( RDFFILE, "> $temp" ) or warn "could not write to RDF file $temp because $!"; print RDFFILE <<"EOHD"; EOHD foreach my $field (@field_order) { if ( (defined $metadata{$field}) and length( $metadata{$field} ) ) { if ( defined $uri_fields{$field} ) { print RDFFILE " <$field rdf:resource=\"$metadata{$field}\"/>\n"; } else { print RDFFILE " <$field>$metadata{$field}\n"; } } } print RDFFILE <<"EOHD"; EOHD close RDFFILE or warn "error closing RDF file $temp: $!"; &replace_file_iff_different( $rdffile, $temp ); } sub read_header_from_file { my $headerFile = $metadata{"cap:header"}; $headerFile =~ s,^file://,,; # convert URIs to filenames &read_from_file ($headerFile, "headerText"); } sub read_footer_from_file { my $footerFile = $metadata{"cap:footer"}; $footerFile =~ s,^file://,,; # convert URIs to filenames &read_from_file ($footerFile, "footerText"); } sub read_adsense_from_file { my $adsenseBottomFile = $metadata{"cap:adsense-bottom"}; $adsenseBottomFile =~ s,^file://,,; # convert URIs to filenames &read_from_file ($adsenseBottomFile, "adBottomText"); my $adsenseRightFile = $metadata{"cap:adsense-right"}; $adsenseRightFile =~ s,^file://,,; # convert URIs to filenames &read_from_file ($adsenseRightFile, "adRightText"); } sub read_from_file { my ($fileToRead,$content) = @_; $collection{$content} = ""; open( HEADER, "< $fileToRead" ) or warn "error reading from index header file, $fileToRead: $!"; while (
) { $collection{$content} .= $_; } close ( HEADER ) or warn "error closing index header file, $fileToRead: $!"; } sub generate_html { # generate an HTML file for each variant of the specified image my ($metadata_ref, $file, $isPublicPhoto) = @_; # generate foo.html iff foo.jpg exists # No more large photos html page, ling to the jpg instead #&generate_html_for_file( $metadata_ref, $file ) if -f $file; # generate foo-med.html iff foo-med.jpg exists and foo-med.public.html if required $file =~ s/\.jpg$/-med.jpg/; &generate_html_for_file( $metadata_ref, $file ) if -f $file; &generate_html_for_file( $metadata_ref, $file, $isPublicPhoto ) if ((-f $file) && $isPublicPhoto ); # generate foo-sm.html iff foo-sm.jpg exists and foo-sm.public.html if required $file =~ s/-med\.jpg$/-sm.jpg/; &generate_html_for_file( $metadata_ref, $file ) if -f $file; &generate_html_for_file( $metadata_ref, $file, $isPublicPhoto ) if ((-f $file) && $isPublicPhoto ); } sub generate_html_for_file { # generate an HTML file for the specified image, with human-readable metadata my ($metadata_ref, $file, $isPublicPhoto) = @_; my $metadata = %$metadata_ref; # deref my $html_file = $file; $html_file =~ s/\.jpg$/.html/; $html_file =~ s/\.html$/.public.html/ if $isPublicPhoto; my $rdf_file = &basename( $file ); $rdf_file =~ s/($variant_pat)?\.jpg$/.rdf/; my $temp = $html_file . ".tmp"; print "Writing HTML to file ", $html_file, "\n" if $debuggin; open( TEMP, "> $temp" ) or warn "error trying to write to temp html file, $temp: $!"; # Generate HTML my $headerText = &generate_html_head( \%metadata, $file, $isPublicPhoto ); my $bodyText = &generate_html_body( \%metadata, $file, $isPublicPhoto ); my $footerText = &generate_html_footer( \%metadata, $file, $isPublicPhoto ); # Print it in file print TEMP $headerText; print TEMP $bodyText; print TEMP $footerText; close( TEMP ) or warn "error closing temp file, $temp: $!"; &replace_file_iff_different( $html_file, $temp ); } sub generate_html_head { # generate the html head for a file my ($metadata_ref, $file, $isPublicPhoto) = @_; my $metadata = %$metadata_ref; # deref my $rdf_file = &basename( $file ); $rdf_file =~ s/($variant_pat)?\.jpg$/.rdf/; $rdf_file =~ s/\.flv$/.rdf/; # If we have a video file my $title = $metadata{"dc:title"} || "No title specified"; my $date = $metadata{"dc:date"} || "No date specified"; my $keywords = ""; $keywords .= qq{photo,travel}; $keywords .= qq[,video] if $opt{videos}; $keywords .= qq[,$metadata{"iptc:City"}] if ( defined $metadata{"iptc:City"} ); $keywords .= qq[,$metadata{"iptc:Province-State"}] if ( defined $metadata{"iptc:Province-State"} ); $keywords .= qq[,$metadata{"iptc:Country-PrimaryLocationName"}] if ( defined $metadata{"iptc:Country-PrimaryLocationName"} ); my $googleMapKey = ""; if ( $file =~ /-sm\.jpg$/ and defined $metadata{'cap:googleMapKey'}) { $googleMapKey = ''; } my $headerText = ""; if ( defined $collection{headerText} ) { $headerText .= $collection{headerText}; $headerText =~ s/%%title%%/$title/g; $headerText =~ s/%%year%%/$currentYear/g; $headerText =~ s/%%date%%/$date/g; $headerText =~ s/%%keywords%%/$keywords/g; $headerText =~ s/Overview\.html/Overview.public.html/g if ($isPublicPhoto); # Extra style sheets and meta information depending on file my $headerExtra = ""; $headerExtra .= qq{ \n }; $headerExtra .= qq{ \n } if ($file =~ /-med\.jpg$/); $headerExtra .= qq{ \n } if ($file =~ /-med\.jpg$/); $headerExtra .= qq{ $googleMapKey\n }; $headerText =~ s//$headerExtra/g; my $nextprevText = &generate_nextprev( \%metadata, $file, $isPublicPhoto ); $headerText =~ s//$nextprevText/g; my $nav = &generate_nav_list( \%metadata, $file, $isPublicPhoto ); $headerText =~ s//$nav/g; if ($file =~ /-med\.jpg$/) { my $toggleText = qq{\n }; $headerText =~ s//$toggleText/g; } } return $headerText; } sub generate_nextprev { # generate the next and previous photo links in the header image my ($metadata_ref, $file, $isPublicPhoto) = @_; my $origfile = $file; $origfile =~ s/($variant_pat)?\.jpg$/.jpg/; my $nextprevText = ""; if ( $opt{index} && !($file =~ /\.flv$/)) { $nextprevText .= qq{
\n}; my $prevfile = $collection{$origfile}{prevfile}; $prevfile = get_previous_public_file($origfile) if ($isPublicPhoto); if ( defined( $prevfile ) ) { my $link = $prevfile; if ($isPublicPhoto) { $link =~ s/\.jpg$/-sm.public.html/; $link =~ s/-sm/-med/ if ($file =~ /-med\.jpg$/); } else { $link =~ s/\.jpg$/-sm.html/; $link =~ s/-sm/-med/ if ($file =~ /-med\.jpg$/); } my $sqfile = $prevfile; $sqfile =~ s/\.jpg$/-sq.jpg/; my $prevtitle = $collection{$prevfile}{title}; $nextprevText .= qq{ \n}; } my $nextfile = $collection{$origfile}{nextfile}; $nextfile = get_next_public_file($origfile) if ($isPublicPhoto); if ( defined( $nextfile ) ) { my $link = $nextfile; if ($isPublicPhoto) { $link =~ s/\.jpg$/-sm.public.html/; $link =~ s/-sm/-med/ if ($file =~ /-med\.jpg$/); } else { $link =~ s/\.jpg$/-sm.html/; $link =~ s/-sm/-med/ if ($file =~ /-med\.jpg$/); } my $sqfile = $nextfile; $sqfile =~ s/\.jpg$/-sq.jpg/; my $nexttitle = $collection{$nextfile}{title}; $nextprevText .= qq{ \n}; } $nextprevText .= qq{
\n\n}; } return $nextprevText; } sub get_previous_public_file { my ($file) = @_; return unless ( defined($collection{$file}{prevfile}) ); my $prevfile = $collection{$file}{prevfile}; if ($collection{$prevfile}{visibility} =~ m/^public$/) { return $prevfile; } else { return get_previous_public_file($prevfile); } } sub get_next_public_file { my ($file) = @_; return unless ( defined($collection{$file}{nextfile}) ); my $nextfile = $collection{$file}{nextfile}; if ($collection{$nextfile}{visibility} =~ m/^public$/) { return $nextfile; } else { return get_next_public_file($nextfile); } } sub generate_nav_list { my ($metadata_ref, $file, $isPublicPhoto) = @_; my $navList = ""; my $indexfile = $opt{indexfile}; $indexfile =~ s/\.html/.public.html/ if ($isPublicPhoto); $navList .= qq{
  • $opt{indextitle}
  • \n}; return $navList; } sub generate_html_body { # generate the main html with the photo, the info and the map for a file my ($metadata_ref, $file, $isPublicPhoto) = @_; my $bodyText = ""; $bodyText .= qq{
    \n}; $bodyText .= qq{
    \n}; $bodyText .= qq{
    \n}; # Main column $bodyText .= qq{
    \n}; if ( $file =~ /-sm\.jpg$/ or $file =~ /-med\.jpg$/ or $file =~ /\.flv$/) { # Adsense Bottom $bodyText .= $collection{adBottomText} if ( defined $collection{adBottomText} ); } $bodyText .= &generate_html_body_small( \%metadata, $file, $isPublicPhoto ) if ( $file =~ /-sm\.jpg$/); $bodyText .= &generate_html_body_med( \%metadata, $file, $isPublicPhoto ) if ( $file =~ /-med\.jpg$/); $bodyText .= &generate_html_body_large( \%metadata, $file, $isPublicPhoto ) if ( ($file =~ /.jpg$/) and !($file =~ /-(sm|med)\.jpg$/) ); $bodyText .= &generate_html_body_video( \%metadata, $file, $isPublicPhoto ) if ( $file =~ /.flv$/); $bodyText .= qq{
    \n\n}; # Adsense Right $bodyText .= qq{
    \n}; $bodyText .= $collection{adRightText} if ( defined $collection{adRightText} ); $bodyText .= qq{
    \n}; $bodyText .= qq{
    \n}; $bodyText .= qq{
    \n}; $bodyText .= qq{
    \n}; return $bodyText; } sub generate_html_body_small { my ($metadata_ref, $file, $isPublicPhoto) = @_; my $basefile = &basename( $file ); my $html =""; $html .= qq{
    \n}; $html .= qq{

    } . $metadata{"dc:title"} . qq{

    \n\n}; # Photo my $href = &basename( $file ); $href =~ s/-sm\.jpg$/-med.html/; $href =~ s/\.html$/.public.html/ if ($isPublicPhoto); $html .= qq{
    \n}; $html .= qq{
    \n}; $html .= qq{ Photo: } . $metadata{\n}; $html .= qq{
    \n\n}; $html .= qq{
    \n\n}; # Google map if gps coordinates available if (defined( $metadata{"cap:GPSLatitudeNum"} ) and defined( $metadata{"cap:GPSLongitudeNum"} ) ) { $html .= qq{
    \n}; $html .= qq{
    \n}; $html .= qq{
    \n\n}; } $html .= qq{
    \n\n}; #Add details and links $html .= qq{
    \n}; $html .= $metadata{"infotext"}; my $navbar = &build_html_navbar( $file ); $navbar =~ s/med\.html/med.public.html/ if ($isPublicPhoto); $html .= qq{

    • $navbar •

    \n}; $html .= qq{
    \n\n}; $html .= qq{
    \n\n}; return $html; } sub generate_html_body_med { my ($metadata_ref, $file, $isPublicPhoto) = @_; my $title = $metadata{"dc:title"} || "No title specified"; my $basefile = &basename( $file ); my $coord = ""; $coord = $metadata{"cap:coord"} if (defined( $metadata{"cap:coord"} ) ); my $html =""; $html .= qq{
    \n}; $html .= qq{

    } . $metadata{"dc:title"} . qq{

    \n\n}; # Photo my $href = &basename( $file ); $href =~ s/-med\.jpg$/-sm.html/; $href =~ s/\.html$/.public.html/ if ($isPublicPhoto); $html .= qq{

    \n}; $html .= qq{ $title\n}; $html .= qq{

    \n\n}; # Add details and links $html .= qq{
    \n}; $html .= $metadata{"infotext"}; my $navbar = &build_html_navbar( $file ); $navbar =~ s/sm\.html/sm.public.html/ if ($isPublicPhoto); $html .= qq{

    • $navbar •

    \n}; $html .= qq{
    \n\n}; #@@ table $html .= qq{
    \n}; $html .= qq{

    Click here to see EXIF and IPTC info

    \n\n}; $html .= &generate_html_fields_list( \%metadata ); $html .= qq{
    \n\n}; # Close post $html .= qq{
    \n\n}; return $html; } sub generate_html_body_large { # deprecated my ($metadata_ref, $file, $isPublicPhoto) = @_; my $title = $metadata{"dc:title"} || "No title specified"; my $basefile = &basename( $file ); my $html =""; $html .= qq{

    \n}; $html .= qq{ $title\n}; $html .= qq{

    \n\n}; $html .= qq{

    \n}; my $navbar = &build_html_navbar( $file ); $html .= qq{ • $navbar •\n}; $html .= qq{

    \n\n}; #@@ table $html .= &generate_html_fields_list( \%metadata ); return $html; } sub generate_html_body_video { my ($metadata_ref, $file, $isPublicPhoto) = @_; my $html =""; my $videoJpg = $file; $videoJpg =~ s/\.flv$/.jpg/; my $videoJpgWm = $videoJpg; $videoJpgWm =~ s/\.jpg$/-wm.jpg/; my $videoDir = $opt{videosDir}; $videoDir =~ s,/$,,; # strip trailing '/' my $pathToVideo = $ENV{PWD} . "/" . $videoDir; $pathToVideo =~ s/\/web\/www//; print "Path to video : ".$pathToVideo."\n" if $debuggin; $html .= qq{
    \n}; # Photo my $href = &basename( $file ); $href =~ s/-med\.jpg$/-sm.html/; $href =~ s/\.html$/.public.html/ if ($isPublicPhoto); $html .= qq{

    Click to start video

    \n}; $html .= qq{

    \n}; $html .= qq{ \n}; $html .= qq{ \n}; $html .= qq{ \n}; $html .= qq{ \n}; $html .= qq{ \n}; $html .= qq{ \n}; $html .= qq[ \n]; $html .= qq{ \n\n}; $html .= qq{

    \n\n}; # Details $html .= qq{
    \n}; $html .= $metadata{"infotext"}; $html .= qq{
    \n\n}; # Close post $html .= qq{
    \n\n}; return $html; } sub generate_html_footer { my ($metadata_ref, $file, $isPublicPhoto) = @_; my $footerText = ""; if ( defined $collection{footerText} ) { $footerText .= $collection{footerText}; &update_time(); # Update value for current time $footerText =~ s/%%year%%/$currentYear/g; $footerText =~ s/%%date%%/$hour:$minute:$second $dayOfMonth\/$currentMonth\/$currentYear/g; # After everything because of supid IE bug -> http://vidmar.net/weblog/archive/2005/08/22/2121.aspx if ( defined $file and $file =~ /-sm\.jpg$/ and defined( $metadata{"cap:GPSLatitudeNum"} ) and defined( $metadata{"cap:GPSLongitudeNum"} ) ) { my $mapText .= qq{ \n\n}; $mapText .= qq{\n}; $footerText =~ s/<\/body>/$mapText/g; } } return $footerText; } sub build_html_navbar { # generate HTML code to be used as the navbar for individual HTML images my $file = shift; my $navbar = ""; my $basefile = &basename( $file ); my $origfile = $basefile; $origfile =~ s/($variant_pat)?\.jpg$/.jpg/; my $smfile = $basefile; $smfile =~ s/($variant_pat)?\.jpg$/-sm.html/; my $medfile = $basefile; $medfile =~ s/($variant_pat)?\.jpg$/-med.html/; my $original_hw = ""; if ( defined $metadata{"exif:ExifImageWidth"} and defined $metadata{"exif:ExifImageLength"} ) { $original_hw .= $metadata{"exif:ExifImageWidth"} . "x" . $metadata{"exif:ExifImageLength"}; } $navbar .= qq{Versions: }; $navbar .= qq{ } unless $file =~ /-sm\.jpg$/; $navbar .= qq{Small}; $navbar .= qq{} unless $file =~ /-sm\.jpg$/; $navbar .= qq{ |\n}; $navbar .= qq{ } unless $file =~ /-med\.jpg$/; $navbar .= qq{Medium}; $navbar .= qq{} unless $file =~ /-med\.jpg$/; $navbar .= qq{ |\n}; $navbar .= qq{ } unless $file =~ /[0-9][0-9]-[0-9][0-9]-[0-9][0-9]\.jpg$/; $navbar .= qq{Large}; $navbar .= qq{} unless $file =~ /[0-9][0-9]-[0-9][0-9]-[0-9][0-9]\.jpg$/; $navbar .= qq{ ($original_hw)}; $navbar .= qq{ ()}; return $navbar; } sub generate_infotext { my ($metadata_ref, $file) = @_; my $href = &basename( $file ); # to fix sm/med and public or not $href =~ s/-sm\.jpg$/-med.html/; my $coord = ""; $coord = $metadata{"cap:coord"} if (defined( $metadata{"cap:coord"} ) ); my $local_time = 'No time'; $local_time = $metadata{"cap:local-time"} if ( defined($metadata{"cap:local-time"}) ); # added test on $metadata{"exif:Model"} for digitalized pictures not from digital cameras if ( ! defined( $metadata{"cap:cameraShortName"} ) and defined( $metadata{"exif:Model"} )) { $metadata{"cap:cameraShortName"} = $metadata{"exif:Model"}; $metadata{"cap:cameraShortName"} =~ s/"//g; } if ( ! defined( $metadata{"t:camera"} ) and defined( $metadata{"exif:Model"} )) { my $camera_model = $metadata{"exif:Model"}; $camera_model =~ s/"//g; $metadata{"t:camera"} = "http://www.amazon.com/exec/obidos/external-search?index=photo&keyword=" . &uri_escape( $camera_model ) . "&tag=geraldoskoboiny&Search=Search"; # -> PROFIT! } my $infotext = ""; # Title $infotext .= qq{

    } . $metadata{"dc:title"} . qq{

    \n}; $infotext .= qq{

    \n}; # Place if ( defined($metadata{"iptc:City"}) ) { my $city = $metadata{"iptc:City"}; $infotext .= qq{ $city, \n }; } if ( defined($metadata{"iptc:Province-State"}) ) { my $state = $metadata{"iptc:Province-State"}; $infotext .= qq{ $state, \n }; } if ( defined($metadata{"iptc:Country-PrimaryLocationName"}) ) { my $country = $metadata{"iptc:Country-PrimaryLocationName"}; $infotext .= qq{ $country\n }; } #@@ replace with appropriate link ??? #$infotext .= qq{ ($coord)\n} if ( !($coord eq "") ); $infotext .= qq{ ($coord)\n} if ( !($coord eq "") ); $infotext .= qq{
    \n}; # Time $infotext .= qq{ Taken $local_time\n}; # Camera, focal and exposure info if sm file and available if ( defined($metadata{"cap:cameraShortName"}) and defined($metadata{"t:camera"}) ) { $infotext .= qq{ , with }; $infotext .= $metadata{"cap:cameraShortName"} . qq{}; } $infotext .= qq{

    \n}; return $infotext; } sub generate_html_fields_list { my ($metadata_ref) = @_; my $metadata = %$metadata_ref; # deref my $fieldListText = ""; # Output all the fields $fieldListText .= qq{\n}; foreach my $field (@field_order) { if ( defined $metadata{$field} ) { next if $field =~ /^cap:/; # don't output captivate prefs next if $field =~ /^dc:title/; # title is already printed my $short_field = $field; $short_field =~ s/^[^:]+://; # strip ns prefix if ( $short_field =~ m/^[a-z]/ ) { # starts with lowercase char? $short_field =~ s/./\U$&/; # initial-case it } if ( defined $uri_fields{$field} ) { $fieldListText .= qq[ \n]; $fieldListText .= qq[ \n]; $fieldListText .= qq[ \n]; $fieldListText .= qq[ \n]; } else { $fieldListText .= qq[ \n]; $fieldListText .= qq[ \n]; if ($short_field =~ /^Date/) { # nicer print for the date my $newDate = $metadata{$field}; $newDate =~ tr/[TZ]/ /; $fieldListText .= qq[ \n]; } else { $fieldListText .= qq[ \n]; } $fieldListText .= qq[ \n]; } } } $fieldListText .= qq{
    $short_field$metadata{$field}
    $short_field$newDate$metadata{$field}
    \n\n}; return $fieldListText; } ### Output for videos ### sub generate_video_html { my ($video) = @_; my $videoDir = $opt{videosDir}; $videoDir =~ s,/$,,; # strip trailing '/' my $videoHtml = $video; $videoHtml =~ s/\.flv$/.html/; # Create Html page for current video my $html_file = $videoDir . "/" . $videoHtml; my $temp = $html_file . ".tmp"; print "Writing HTML to file ", $html_file, "\n" if $debuggin; open( TEMP, "> $temp" ) or warn "error trying to write to temp html file, $temp: $!"; # Generate HTML my $headerText = &generate_html_head( \%metadata, $video, 0 ); my $bodyText = &generate_html_body( \%metadata, $video, 0 ); my $footerText = &generate_html_footer; # Print it in file print TEMP $headerText; print TEMP $bodyText; print TEMP $footerText; close( TEMP ) or warn "error closing temp file, $temp: $!"; &replace_file_iff_different( $html_file, $temp ); } ### Utils functions ### sub replace_file_iff_different { # replace $oldfile with $newfile only if its contents differ # (to avoid updating files if nothing has changed) my ($oldfile,$newfile) = @_; # @@ should probably replace this with something else, in perl my $rc = system( "cmp -s $oldfile $newfile" ); if ( $rc ) { rename( $newfile, $oldfile ) or warn "error renaming file from $newfile to $oldfile: $!"; } else { # no diff from $oldfile, so nuke $newfile and keep $oldfile unlink( $newfile ) or warn "error unlinking newfile $newfile: $!"; } return $rc; } sub basename { my $file = shift; $file =~ s,.*/,,; return $file; } sub dirname { my $dir = shift; $dir =~ s,[^/]+$,,; return $dir; } sub prune_redundant_dirs { my $path = shift; 1 while $path =~ s,[^\./][^/]+/\.\./,,; 1 while $path =~ s,\.\./[^\.][^/]+/,,; return $path; } sub relativize_dir_path { # return ../ or ../../ etc as needed to get from $path to $PWD my $path = &prune_redundant_dirs( shift ); $path =~ s,^$ENV{PWD}/,,; return "../" x $path =~ tr,/,,; # return a ../ for each dir remaining } sub uri_escape { # buggy, needs work/replacement my $text = shift; $text =~ s/ /+/g; return $text; } sub update_time { ($second, $minute, $hour, $dayOfMonth, $month, $yearOffset, $dayOfWeek, $dayOfYear, $daylightSavings) = localtime(); }