#!/usr/bin/perl # # captivate: read photo metadata from jpeg files using 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 # # typical usage: # # captivate --index foo.html --title 'Album Title' */*.jpg # # then once foo.html has been created, use this to re-edit: # # captivate foo.html # # to set a specific field: # # captivate --set cap:visibility public *.jpg # captivate --set dc:coverage 'Whistler, BC, Canada' *.jpg # # source: http://impressive.net/software/photo/source/captivate # # license: GPL # # see also: # # Digital photo manipulation software # http://impressive.net/software/photo/ # # requirements: # - Perl 5.8.0 or higher (for List::Util) # - epinfo (from photopc) # # bugs: # - doesn't really parse XML/RDF, just pretends to # - doesn't really grok namespaces, just pretends to # - doesn't re-read .captivate.rdf defaults when args span multiple dirs # - has too many hardcoded assumptions related to things that are specific # to my setup (variants have same basename, always named '-med.jpg', etc.) # - erases any values it doesn't understand in RDF files # - doesn't deal well with photos and index files at varying rel paths # (but works OK if invoked from a parent dir that is shared by all args) # - assumes videos are encoded at 30 frames per second # # changelog: # # $Log: captivate,v $ # Revision 1.93 2009-06-03 00:28:33 gerald # omit 'license/buy' links and text from images whose dc:creator is not me # # Revision 1.92 2009-05-03 22:13:27 gerald # fixed wrong path when trying to obtain h/w from images using identify # # Revision 1.91 2008-11-16 19:52:37 gerald # added --set to set a specific field to some value; added usage docs # # Revision 1.90 2008-09-17 03:03:47 gerald # prepend 'photo, picture', etc to meta keywords # # Revision 1.89 2008-05-22 21:14:54 gerald # allow photos to be removed and reordered while editing metadata # # Revision 1.88 2007/09/25 19:20:04 gerald # stop centering h1's on med.html and .html pages due to clash with nav bar # # Revision 1.87 2007/09/23 03:28:19 gerald # tidied up med.html files; added meta noindex to med and large html files # # Revision 1.86 2007/07/30 10:47:28 gerald # changed layout of foo-sm.html pages to include google 300x250 ad; # added "license/buy print" links, copyright in footer # # Revision 1.85 2007/05/16 03:02:02 gerald # added ads to med and large HTML pages; added horiz link ads to sm.html pages # # Revision 1.84 2007/04/30 01:35:16 gerald # cap:indexfile needs to change according to the directory path of each photo # # Revision 1.83 2007/03/27 10:54:27 gerald # added --randomize option to shuffle file order before output # for more aesthetically pleasing indexes # # Revision 1.82 2007/01/31 11:17:46 gerald # added animation of video frames using javascript # # Revision 1.81 2007/01/30 13:35:34 gerald # first pass at support for videos (probably still buggy) # # Revision 1.80 2007/01/27 10:30:34 gerald # made parse_args grok index filenames like canon-sd800.html # # Revision 1.79 2007/01/04 18:01:21 gerald # bug fix in arg passing in &return_nav_links # # Revision 1.78 2007/01/02 15:49:31 gerald # improved layout when no index link and next/prev icons # # Revision 1.77 2006/11/29 16:23:50 gerald # oops, must check for digital rebel xti before digital rebel # # Revision 1.76 2006/11/29 14:22:23 gerald # omit link to 'previous' photo when only one photo in set # # Revision 1.75 2006/11/17 03:39:50 gerald # added URIs for my new XTI and SD800 cameras # # Revision 1.74 2006/11/14 06:23:47 gerald # treat & as word separator when looking for keywords (avoid B&H -> 'bamph') # # Revision 1.73 2006/10/11 05:38:47 gerald # added new URI for my old Canon S230 # # Revision 1.72 2006/09/16 02:25:50 gerald # added a newline after each keyword link so cvs diffs will be smaller # # Revision 1.71 2006/09/16 02:20:06 gerald # prompt for dc:subject for extra keywords # # Revision 1.70 2006/09/14 02:59:45 gerald # added links from photo pages to keyword indexes # # Revision 1.69 2006/09/14 00:37:38 gerald # oops, previous commit was buggy # # Revision 1.68 2006/09/14 00:07:15 gerald # treat / as word separator when looking for keywords # # Revision 1.67 2006/09/13 22:44:50 gerald # added meta keywords to each photo HTML page # # Revision 1.66 2006/09/11 23:44:17 gerald # look for *-tn.jpg as well as *-sq.jpg when scanning previously generated indexes # # Revision 1.65 2006/09/11 09:28:00 gerald # preserve custom text on index files (e.g. ads in the middle of thumbindex divs) # # Revision 1.64 2006/07/27 08:33:00 gerald # added rotating color palettes to google ads on sm.html pages # # Revision 1.63 2006/07/24 04:46:04 gerald # avoid trying to diff against oldfiles that do not exist # # Revision 1.62 2006/07/24 02:03:44 gerald # oops, bug in new dotdots function # # Revision 1.61 2006/07/22 21:36:50 gerald # include links to google maps if geo:lat and geo:long are defined # # Revision 1.60 2006/07/22 08:05:01 gerald # oops, need to include dotdots in refs to indexfiles within rdf files # # Revision 1.59 2006/07/22 02:09:11 gerald # started storing indexfile and indextitle in rdf files; # output an error if no image files specified/found; # ignore cvs Date stamps when comparing old and new HTML files; # default to including ads on sm.html pages unless --nosmads specified; # include nav bar on med and large html pages # # Revision 1.58 2006/07/16 22:00:52 gerald # better nav bar with ul/li markup # # Revision 1.57 2006/07/16 20:22:21 gerald # updated color scheme of ads on sm.html pages # # Revision 1.56 2006/06/21 06:47:00 gerald # only include ads on *-sm.html files if --smads option specified # # Revision 1.55 2006/06/15 07:48:29 gerald # really made to work in the absence of original photos # (pretty kludgy; assumes foo-med.jpg has all the original exif info) # # Revision 1.54 2006/06/15 04:30:01 gerald # include ads on the right hand side of foo-sm.html pages # # Revision 1.53 2006/06/14 17:53:09 gerald # made to work in the absence of original photos (when removed to save space) # # Revision 1.52 2006/06/14 01:54:26 gerald # hardcoded nicer names and URIs for a few of my cameras # # Revision 1.51 2006/05/03 04:29:42 gerald # use rel=nofollow on links to large photos # # Revision 1.50 2006/04/18 20:02:56 gerald # don't spew errors when original image has no exif info # # Revision 1.49 2006/04/14 08:16:17 gerald # improved sm.html pages when images have no exif info # # Revision 1.48 2006/04/02 23:53:25 gerald # added geo namespace and properties; also, minor bug fix # # Revision 1.47 2006/03/15 09:24:08 gerald # link each component of dc:coverage to the appropriate page within # http://impressive.net/people/gerald/photos/by/location # # Revision 1.46 2006/01/19 07:03:40 gerald # allow index and nextprev buttons to be included/omitted independently # # Revision 1.45 2006/01/16 00:16:56 gerald # split nogen param into nohtml and nordf; better handling of index titles # # Revision 1.44 2006/01/09 07:29:14 gerald # added a list of known variant suffixes instead of assuming anything # matching -[a-z]+.jpg is a variant # # Revision 1.43 2005/12/23 01:38:03 gerald # rdf files should not include paths when referring to originals # # Revision 1.42 2005/12/17 19:33:21 gerald # added accesskeys for quick navigation (next/prev/index, small/med/large); # removed human-visible "generated by captivate" # # Revision 1.41 2005/12/12 07:18:43 gerald # if an HTML file is specified on the command line, parse it for the list of # images to be captivated, then regenerate the index, preserving any custom # header/footer stuff in the index file. Also, hopefully improved handling # of relative URIs. # # Revision 1.40 2005/12/11 00:16:40 gerald # use mini square icons in next/prev links on foo-sm.html pages a la flickr; # generate xhtml strict; various small tweaks # # Revision 1.39 2005/12/09 18:55:24 gerald # allow command line options to be editable along with metadata # (committing rev on laptop dated 2004-03-15 17:41:44 -0500) # # Revision 1.38 2003/09/29 05:18:06 gerald # removed presentation markup from indexes to make them xhtml1strict-happy # # Revision 1.37 2003/02/12 19:27:14 gerald # only include navbar in *-sm.html files if --index option was given # # Revision 1.36 2003/02/01 08:20:07 gerald # doh, forgot a closing HTML anchor # # Revision 1.35 2003/02/01 08:11:43 gerald # added next/prev/up links on each image-sm.html page; # started displaying captions underneath thumbnails; # moved all style stuff to a stylesheet # # Revision 1.34 2003/01/18 18:36:01 gerald # improved the format of the temp file used for editing captions; # added cap:depicts, cap:visibility, and cap:quality fields # (committing changes dated Dec 22 22:31) # # Revision 1.33 2002/11/08 09:20:21 gerald # bug fix: --nogen was not correctly supressing generation of RDF files # # Revision 1.32 2002/10/22 09:21:38 gerald # bug fix: add closing / on img elements (required in xhtml) # # Revision 1.31 2002/10/22 07:53:35 gerald # bug fix wrt leading dots: now handles "captivate ../foo/*.jpg" correctly; # also, added --nogen option to prevent it from generating *rdf, *html files # # Revision 1.30 2002/10/22 06:02:01 gerald # omit height/width attrs from images on html pages since they are not set # properly for rotated images # # Revision 1.29 2002/10/22 03:54:39 gerald # omit dirnames from alternate versions on image HTML pages; # removed broken Next/Previous button generation for now # # Revision 1.28 2002/09/16 00:30:54 gerald # misc changes from the last while: # collect metadata from user in a single editor session instead of once per file; # allow user to specify default metadata items to apply to all photos; # edit metadata for each image by default (unless --noedit specified); # stop refusing to clobber index files (and replace them only if different); # start generating HTML pages for sm variants, with a different output format; # omit dirnames from href and src attrs on image HTML pages; # start linking to amazon searches for cameras if no camera URI specified # # Revision 1.27 2002/08/08 03:58:03 gerald # added a pointer to the source url # # Revision 1.26 2002/04/29 05:25:53 gerald # moved icons to the web instead of file:// URIs used for testing # # Revision 1.25 2002/04/29 05:23:27 gerald # misc changes near Dec 30, 2001: # - change HTML output to slideshow format (should offer a choice of formats) # - allow for a command to be run before captioning starts # (e.g. to generate alternate sizes, to preview while captioning) # - display the actual height/width of the original image, not hardcoded values # # Revision 1.24 2001/11/19 23:20:04 gerald # - moved user defaults (e.g., editor, viewer) into .captivate.rdf # - added index generation feature (--index filename.html) # - added user-customizable index headers/footers # - code cleanup: moved some globals into %opt hash, added parse_args function # # Revision 1.23 2001/11/16 19:29:32 gerald # oops, forgot to remove temp file from caption editing # # Revision 1.22 2001/11/16 19:25:44 gerald # committing changes dated Nov 12 15:58 CET: moved metadata editing, # rdf generating code to separate functions, added --noedit parameter # # Revision 1.20 2001/11/10 22:43:04 gerald # bug fix for temp files in the case of e.g. "captivate foo/bar*" # # Revision 1.19 2001/10/29 06:21:00 gerald # added changelog # # # $Id: captivate,v 1.93 2009-06-03 00:28:33 gerald Exp $ # use strict; use warnings; use URI::Escape; use List::Util 'shuffle'; my $debuggin = 0; my $epinfo = 'epinfo'; my $temp; 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 $generator = "http://impressive.net/software/photo/"; my $revision = q$Revision: 1.93 $; $revision =~ s/Revision: ([\d\.]+) /$1/; my @field_order = ( "dc:title", "dc:description", "dc:subject", "dc:coverage", "dc:date", "dc:creator", "dc:publisher", "dc:type", "dc:format", "dc:source", "dc:identifier", "dc:relation", "geo:lat", "geo:long", "geo:alt", "exif:Model", "exif:Orientation", "exif:ExposureTime", "exif:FNumber", "exif:ISOSpeedRatings", "exif:MeteringMode", "exif:LightSource", "exif:Flash", "exif:FocalLength", "exif:ExifImageWidth", "exif:ExifImageLength", # @@ anything else interesting in exif info? "cap:indexfile", "cap:indextitle", "cap:depicts", "cap:visibility", "cap:quality", "t:camera" ); my %uri_fields = ( "cap:header" => 1, "cap:footer" => 1, "cap:indexfile" => 1, "dc:publisher" => 1, "dc:creator" => 1, "t:camera" => 1 ); my @wanted_fields= ( "dc:title", "dc:description", "dc:subject", "dc:coverage", "cap:depicts", "cap:visibility", "cap:quality" ); my @displayed_fields= ( "dc:title", "dc:description", "dc:coverage", "dc:date", "dc:creator", "exif:ExposureTime", "exif:FNumber", "t:camera", "dc:subject" ); our $variant_pat = '-(sq|(f[0-9]{8}-)?tn|sm|med|vid)'; our $curyear = (gmtime)[5]+1900; my $defaults_file = ".captivate.rdf"; my $global_defaults = $ENV{HOME} . "/" . $defaults_file; my $already_printed_header = 0; my $already_printed_footer = 0; my $curr_image_num = 1; 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 my @text_before_thumb; # custom text before thumbnail number 'n' in index # parse command line arguments my @files = &parse_args( \%opt, \@ARGV ); if ( $#files == -1 ) { print STDERR "No image files specified/found.\n"; exit(-1); } # special handling for captivate --set if ( $opt{set} ) { my $key = $opt{setkey}; my $value = $opt{setvalue}; foreach my $file (@files) { undef %metadata; &find_metadata_for_file( \%metadata, $file ); $metadata{$key} = $value; &generate_rdf( \%metadata, $file ); } exit; } # 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; } } # edit metadata for the specified files # build a temp file with editable metadata my $editfile = "captivate.$$.txt"; open( TEMP, "> $editfile" ) or warn "error trying to write to temp file for editing, $editfile: $!"; &find_metadata_for_file( \%metadata, "___bogus" ); print TEMP &build_editable_metadata_blurb( \%opt, "_options" ), "\n"; print TEMP &build_editable_metadata_blurb( \%metadata, "_default" ), "\n"; foreach my $file (@files) { undef %metadata; &find_metadata_for_file( \%metadata, $file ); print TEMP &build_editable_metadata_blurb( \%metadata, $file ), "\n"; } print TEMP $edit_instructions; close( TEMP ) or warn "error closing temp file, $editfile: $!"; # invoke editor on that temp file if ( ! defined ( $metadata{"cap:editor"} ) ) { $metadata{"cap:editor"} = $ENV{EDITOR} || "vi"; } system $metadata{"cap:editor"}, $editfile if $opt{edit}; undef @files; # allow the list to be edited/reordered while editing metadata # read in freshly-edited metadata from temp file &read_edited_metadata( \%metadata, $editfile ); unlink $editfile or warn "error unlinking temp file $editfile: $!"; @files = shuffle( @files ) if $opt{randomize}; # generate output in misc formats for each file specified foreach my $file (@files) { undef %metadata; &find_metadata_for_file( \%metadata, $file ); # this is kind of an awkward spot to be doing this, but it needs to come # after the metadata parsing stuff above, which specifies the header file &print_index_header if $opt{index}; # @@ what to do with this now? # &run_edit_pre_commands( \%metadata, $file ) if $opt{edit}; &display_image( $file ) if $opt{view}; &generate_rdf( \%metadata, $file ); &generate_html( \%metadata, $file ); print INDEX &generate_index_entry( \%metadata, $file ) if $opt{index}; } if ( $opt{index} ) { &print_index_footer; close( INDEX ) or warn "error closing index file: $!"; &replace_file_iff_different( $opt{indexfile}, $opt{indexfile} . ".tmp" ); } exit; 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; } 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 read_epinfo_from_file { my ($metadata_ref, $file) = @_; my $metadata = %$metadata_ref; # deref $file =~ s/\.avi$/.thm/; if ( ! -r $file ) { my $medfile = $file; $medfile =~ s/\.jpg$/-med.jpg/; if ( ! -r $medfile ) { return -1; } else { $file = $medfile } } print "Reading epinfo from $file...\n" if $debuggin; open( EPINFO, "$epinfo $file 2>&1 |" ) or do { warn "could not open pipe to epinfo for file $file because $!"; return -1; }; undef my %epinfo; while () { chomp; next if /^No camera-specific information in the file$/; my ($key,$value) = split(/=/,$_,2); print "read epinfo key [$key] with value [$value]\n" if $debuggin; $epinfo{$key} = $value; } close EPINFO; # preserve/convert the epinfo we are interested in if ( defined $epinfo{DateTimeOriginal} ) { $metadata{"dc:date"} = &exif_date_to_iso8601($epinfo{DateTimeOriginal}); } # copy any interesting exif data we found into %metadata foreach my $field (@field_order) { next unless $field =~ /^exif:/; my $shortfield = $field; $shortfield =~ s/^exif://; $metadata{$field} = $epinfo{$shortfield} if defined $epinfo{$shortfield}; } 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; } sub display_image { # display the image for the user using $metadata{"cap:viewer"}, # so they can view it while entering a caption my $file = shift; my $viewfile; my $smfile = $file; $smfile =~ s/\.jpg$/-sm.jpg/; if ( -r $smfile ) { $viewfile = $smfile; # use foo-sm.jpg if it exists } else { $viewfile = $file; } # absolutify $viewfile if it isn't already absolute $viewfile = $ENV{PWD} . "/" . $viewfile unless $viewfile =~ m,^/,; if ( ! length( $metadata{"cap:viewer"} ) ) { $metadata{"cap:viewer"} = "netscape-remote"; } system $metadata{"cap:viewer"}, $viewfile; # display the image } sub generate_html { # generate an HTML file for each variant of the specified image return unless $opt{html}; my ($metadata_ref, $file) = @_; # generate foo.html even if it does not exist (in case rm'd to save space) &generate_html_for_file( $metadata_ref, $file ); # generate foo-med.html iff foo-med.jpg exists $file =~ s/\.jpg$/-med.jpg/; &generate_html_for_file( $metadata_ref, $file ) if -f $file; # generate foo-sm.html iff foo-sm.jpg exists $file =~ s/-med\.jpg$/-sm.jpg/; &generate_html_for_file( $metadata_ref, $file ) if -f $file; # @@ others? (shouldn't be hardcoded like this, oh well) } 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) = @_; if ( ! -f $oldfile ) { rename( $newfile, $oldfile ) or warn "error renaming file from $newfile to $oldfile: $!"; } elsif (system( "diff -q -I '\$Date' $oldfile $newfile > /dev/null" )) { 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; } sub html_intro { my ($title,$rdf_file,$keywords) = @_; my $onload = ""; my $js = ""; if ( $title =~ m,^Video, ) { $onload = qq{ onload='nextframe()'}; $js = qq{ \n}; $keywords = "video, " . $keywords; } else { $keywords = "photo, picture, foto, pic, " . $keywords; } return <<"EOHD"; $title $js EOHD } sub generate_variant_navlinks { # generate HTML code to be used to switch between web pages for each variant my $file = shift; my $navbar = ""; my $basefile = &basename( $file ); $basefile =~ s/($variant_pat)?\.jpg$/.jpg/; my $origfile = $basefile; $origfile =~ s/\.jpg$/.html/; my $smfile = $basefile; $smfile =~ s/\.jpg$/-sm.html/; my $medfile = $basefile; $medfile =~ s/\.jpg$/-med.html/; my $original_hw; if ( defined $metadata{"exif:ExifImageWidth"} ) { $original_hw = $metadata{"exif:ExifImageWidth"} . "x" . $metadata{"exif:ExifImageLength"}; } else { my $idfile = $file; if ( ! -f $idfile ) { $idfile = $basefile; $idfile =~ s/\.jpg$/-med.jpg/; } $original_hw = `identify $idfile | cut -d' ' -f3`; chomp($original_hw); } $navbar .= qq{Sizes: }; $navbar .= qq{ } unless $file =~ /-sm\.jpg$/; $navbar .= qq{Small}; $navbar .= qq{} unless $file =~ /-sm\.jpg$/; $navbar .= qq{ (400x300) |\n}; $navbar .= qq{ } unless $file =~ /-med\.jpg$/; $navbar .= qq{Medium}; $navbar .= qq{} unless $file =~ /-med\.jpg$/; $navbar .= qq{ (640x480) |\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{ (}; $navbar .= qq{/\$} if &is_licensable( \%metadata ); $navbar .= qq{)}; return $navbar; } sub generate_html_for_file { # generate an HTML file for the specified image, with human-readable metadata my ($metadata_ref, $file) = @_; my $metadata = %$metadata_ref; # deref my $html_file = $file; $html_file =~ s/\.jpg$/.html/; $html_file =~ s/\.avi$/-vid.html/; my $rdf_file = &basename( $file ); $rdf_file =~ s/($variant_pat)?\.jpg$/.rdf/; my $temp = $html_file . ".tmp"; my $hwattrs = ""; if ( $file =~ /-med\.jpg$/ ) { $hwattrs = qq{width="640" height="480" }; } if ( $file =~ /-sm\.jpg$/ ) { $hwattrs = qq{width="400" height="300" }; } if ( $file =~ /[0-9][0-9]-[0-9][0-9]-[0-9][0-9]\.jpg$/ ) { my $w = $metadata{"exif:ExifImageWidth"}; my $h = $metadata{"exif:ExifImageLength"}; $hwattrs = qq{width="$w" height="$h" }; } # @@ this stuff doesn't work any more since *med.jpg can be rotated. # so just kludge it to null for now. $hwattrs = ""; print "Writing HTML to file ", $html_file, "\n" if $debuggin; open( TEMP, "> $temp" ) or warn "error trying to write to temp html file, $temp: $!"; my $title = $metadata{"dc:title"} || "no title specified"; my $keywords = join( ', ', &generate_keyword_list( \%metadata ) ); if ( $file =~ /\.avi$/ ) { # special output fmt for videos print TEMP &html_intro( "Video: ".$title, $rdf_file, $keywords ); print TEMP &generate_html_for_video( \%metadata, $file ); } elsif ( $file =~ /-sm\.jpg$/ ) { # these too print TEMP &html_intro( "Photo: ".$title, $rdf_file, $keywords ); my $navbar = &generate_variant_navlinks( $file ); print TEMP &generate_html_for_sm_file( \%metadata, $file ); } else { my $intro = &html_intro( "Photo: ".$title, $rdf_file, $keywords ); $intro =~ s/-sm.css/-sq.css/g; # temporary kludge $intro =~ s,\n $title\n}; } else { print TEMP qq{

$title

\n}; } print TEMP qq{\n}; print TEMP qq{

\n}; print TEMP qq{ $title\n}; print TEMP qq{

\n}; print TEMP qq{\n}; print TEMP qq{

\n}, $navbar, qq{\n

\n}; print TEMP qq{\n}; &kludge_camera_details( \%metadata ); foreach my $field (@displayed_fields) { if ( defined $metadata{$field} ) { next if $field =~ /^cap:/; # don't output captivate prefs 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 ( $field eq "t:camera" ) { print TEMP qq{ \n}; print TEMP qq{ \n}; if ( defined $metadata{"t:camera"} and defined $metadata{"cap:cameraShortName"} ) { print TEMP qq{ \n}; } else { $metadata{"t:camera"} = &default_camera_link( $metadata{"exif:Model"} ); print TEMP qq{ \n}; } print TEMP qq{ \n}; } elsif ( defined $uri_fields{$field} and $metadata{$field} =~ m,^http, ) { print TEMP " \n"; print TEMP " \n"; print TEMP " \n"; print TEMP " \n"; } else { print TEMP " \n"; print TEMP " \n"; print TEMP " \n"; print TEMP " \n"; } } } print TEMP " \n"; print TEMP " \n"; print TEMP " \n}; print TEMP qq{ \n}; my $year = substr( $metadata{"dc:date"}, 0, 4 ); my $image_id = &obtain_full_image_id( $file ); print TEMP qq{ \n}; print TEMP qq{ \n}; if ( &is_licensable( \%metadata ) ) { print TEMP qq{ \n}; } else { print TEMP qq{ \n}; } print TEMP qq{ \n}; print TEMP qq{
Camera}; print TEMP $metadata{"cap:cameraShortName"}; print TEMP qq{}.$metadata{"exif:Model"}.qq{
$short_field"; print TEMP "$metadata{$field}
$short_field$metadata{$field}
Keywords"; my @kwlinks; foreach my $word ( &generate_keyword_list( \%metadata ) ) { push( @kwlinks, qq{$word} ); } print TEMP join(", \n\t", @kwlinks); print TEMP qq{\n
RightsCopyright $year Gerald Oskoboiny (License / buy print)Unknown
\n}; } print TEMP <<"EOHD";
EOHD close( TEMP ) or warn "error closing temp file, $temp: $!"; &replace_file_iff_different( $html_file, $temp ); } sub build_editable_metadata_blurb { my ($metadata_ref, $file) = @_; my $blurb; my %hash = %$metadata_ref; # deref my @keys = @wanted_fields; if ( $file eq "_options" ) { @keys = sort keys %opt; } $blurb .= $file . ":\n"; foreach my $field (@keys) { $blurb .= " " . $field . ": " . ($hash{$field} or "") . "\n"; } return $blurb; } sub read_edited_metadata { my ($metadata_ref, $file) = @_; my $metadata = %$metadata_ref; # deref my %defaults; my $prev_image; my $first_image = 0; open( TEMP, "< $file" ) or die "couldn't read from temp file $file because $!"; my ($image,$key,$value) = undef; while () { # read and parse captivated metadata chomp; next if /^#/; # skip comments next if /^\s*$/; # skip blank lines if ( /^(\S+):\s*$/ ) { # line starts with a filename? $image = $1; push( @files, $image ) unless $image =~ m/^_/; } elsif ( /^\s+([a-z:]+)\s*:\s*(.*)\s*$/ ) { $key = $1; $value = $2; } else { print "read_edited_metadata can't grok this line; skipping: $_\n"; next; } if ( $image eq "_options" ) { if ( (! defined $value) or ($value eq "") ) { delete $opt{$key} if defined $key; } else { $opt{$key} = $value; } next; } # copy index title into metadata array $metadata{"cap:indextitle"} = $opt{indextitle} if defined $opt{indextitle}; if ( $image eq "_default" ) { if ( (! defined $value) or ($value eq "") ) { delete $defaults{$key}; } else { $defaults{$key} = $value; } next; } &find_metadata_for_file( \%metadata, $image ) unless $first_image++; if ( ! defined $prev_image or $image ne $prev_image ) { # new image; wrap up with previous one # remember some stuff for next/prev links on web pages $collection{$image}{prevfile} = $prev_image; if ( defined $prev_image ) { $collection{$prev_image}{nextfile} = $image; $collection{$prev_image}{title} = $metadata{"dc:title"}; # store metadata for the previous image &generate_rdf( \%metadata, $prev_image ); } $prev_image = $image; undef %metadata; &find_metadata_for_file( \%metadata, $image ); } if ( defined( $value ) ) { if ( length( $value ) ) { $metadata{$key} = $value; } else { $metadata{$key} = ""; } } if ( !length( $metadata{$key} ) ) { if ( defined( $defaults{$key} ) ) { $metadata{$key} = $defaults{$key}; } } } # finish up for the last image if ( defined $prev_image ) { &generate_rdf( \%metadata, $prev_image ); $collection{$prev_image}{nextfile} = undef; $collection{$prev_image}{title} = $metadata{"dc:title"}; } $collection{$image}{prevfile} = $prev_image unless defined $collection{$prev_image}{prevfile} or $#files == 0; close( TEMP ) or warn "error closing temp file $file: $!"; } sub generate_rdf { return unless $opt{rdf}; my ($metadata_ref, $file) = @_; my $metadata = %$metadata_ref; # deref my $rdffile = $file; $rdffile =~ s/\.(jpg|avi)$/.rdf/; my $basefile = &basename( $file ); $metadata{"cap:indexfile"} = &dotdots( $file ) . $opt{indexfile} if defined $opt{indexfile}; 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 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{edit} = 1; $opt{rdf} = 1; $opt{html} = 1; $opt{nextprev} = 1; $opt{smads} = 1; $opt{randomize} = 0; while (my $arg = shift @args) { # @@ should use Getopt::Long instead of parsing args manually if (( $arg eq "--view" ) || ( $arg eq "-v" )) { $opt{view} = 1; } elsif (( $arg eq "--noedit" ) || ( $arg eq "-N" )) { $opt{edit} = 0; } elsif (( $arg eq "--nordf" ) || ( $arg eq "-R" )) { $opt{rdf} = 0; } elsif (( $arg eq "--nohtml" ) || ( $arg eq "-H" )) { $opt{html} = 0; } elsif (( $arg eq "--nonextprev" ) || ( $arg eq "-L" )) { $opt{nextprev} = 0; } elsif ( $arg eq "--nosmads" ) { $opt{smads} = 0; } elsif ( $arg eq "--randomize" ) { $opt{randomize} = 1; } elsif ( $arg eq "--set" ) { $opt{set} = 1; $opt{setkey} = shift @args; $opt{setvalue} = shift @args; if ( ! length $opt{setkey} or ! length $opt{setvalue} ) { print STDERR "bogus use of --set; no key/value specified!?"; exit(-1); } } 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 "--title" ) || ( $arg eq "-t" )) { $opt{indextitle} = shift @args; if ( ! length $opt{indextitle} ) { print STDERR "ignoring --title; no title specified!?"; } } elsif ( $arg =~ /^-/ ) { # unknown option specified? &print_usage; } elsif ( ( $arg =~ m,[a-z][a-z0-9]+\.html$,i ) and ( -f $arg ) and ( $arg !~ /(-(sm|med|vid)|[0-9]{2}-[0-9]{2}-[0-9]{2})\.html$/)){ # HTML index filename specified? If so, parse for image list, and regen. $opt{index} = 1; $opt{indexfile} = $arg; my $indexdir = &dirname( $opt{indexfile} ); open( INDEX, "< $opt{indexfile}" ) or warn "error reading from index file $opt{indexfile}: $!"; local $/ = ''; my $count = 1; while () { if ( /

([^<]+)/ ) { $opt{indextitle} = $1; $opt{indextitle} =~ s/^\s*Photos,\s*//; } if ( /\s+src=["']([^'"]+-(sq|tn).jpg)/ ) { my $basefile = $1; $basefile =~ s,\.[^\.]+$,,; # strip file extension $basefile =~ s,$variant_pat$,,; # strip '-sm'/'-med'/... my $file = &prune_redundant_dirs( $indexdir . $basefile . ".jpg"); my $medfile = $file; $medfile =~ s/\.jpg$/-med.jpg/; my $vidfile = $file; $vidfile =~ s/\.jpg$/.avi/; my $vidframe1file = $file; $vidframe1file =~ s/\.jpg$/-f00000001-tn.jpg/; if ( -f $vidfile or -f $vidframe1file ) { $file = $vidfile; } if ( -f $file or -f $medfile or -f $vidframe1file ) { push( @files, $file ); my ($pre,$thumb,$post) = (m,(.*)(
.*?
)(.*),); $text_before_thumb[$count] .= $pre if defined $pre; $count++; $text_before_thumb[$count] .= $post if defined $post; } } else { $text_before_thumb[$count] .= $_; } } close( INDEX ) or warn "error closing index $opt{indexfile}: $!"; } else { my $basefile = $arg; $basefile =~ s,\.[^\.]+$,,; # strip file extension $basefile =~ s,$variant_pat$,,; # strip '-sm'/'-med'/... my $file = $basefile . ".jpg"; my $medfile = $basefile . "-med.jpg"; my $vidfile = $basefile . ".avi"; next if $seen{$basefile}; if ( -f $file ) { $seen{$basefile}++; push( @files, $file ); } elsif ( -f $medfile ) { # original rm'd to save space? $seen{$basefile}++; push( @files, $file ); } elsif ( -f $vidfile ) { # video? $seen{$basefile}++; push( @files, $vidfile ); } else { next; } } } return @files; } 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 generate_index_entry { my ($metadata_ref, $file) = @_; my $metadata = %$metadata_ref; # deref $file = &relativize_dir_path( $opt{indexfile} ) . $file; my $src = $file; $src =~ s/\.(jpg|avi)$/-sq.jpg/; my $href = $file; $href =~ s/\.jpg$/-sm.html/; my $type = "Photo"; if ( $file =~ /\.avi$/ ) { $href =~ s/\.avi$/-vid.html/; $type = "Video"; } my $text = ""; if ( $curr_image_num != 1 and defined $text_before_thumb[$curr_image_num] ) { $text .= $text_before_thumb[$curr_image_num]; } $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_html_for_sm_file { my ($metadata_ref, $file) = @_; my $metadata = %$metadata_ref; # deref # @@ should get this stuff from user prefs my $src = &basename( $file ); my $href = &basename( $file ); $href =~ s/-sm\.jpg$/-med.html/; my $origfile = $file; $origfile =~ s/($variant_pat)?\.jpg$/.jpg/; my $image_id = &obtain_full_image_id( $file ); # need to include ../../ in next/prev links when files span multiple dirs my $dotdots = &dotdots( $file ); # get e.g. "12:34pm" from the filename # @@ should get this from %metadata instead? but how to manage tz setting? my $local_time = ""; if (my ($hh,$mm) = ($file =~ /([0-9][0-9])-([0-9][0-9])-[0-9][0-9]/)) { my $ampm = $hh > 11 ? "pm" : "am"; $hh -= 12 if $hh > 12; $hh = 12 if $hh == 0; $hh =~ s/^0//; # 09:12 -> 9:12 $local_time = $hh . ":" . $mm . $ampm; } else { # @@ set $local_time to the date instead? } &kludge_camera_details( \%metadata ); my $text = ""; $text .= qq{
\n}; $text .= qq{\n}; $text .= qq{
\n}; $text .= qq{

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

\n}; $text .= qq{

Photo: } . $metadata{

\n}; $text .= qq{ \n\n}; $text .= qq{
\n}; if ( defined( $metadata{"dc:description"} ) and length( $metadata{"dc:description"} ) ) { $text .= qq{

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

\n}; } $text .= qq{

}; $text .= $local_time . qq{, } if length ($local_time); $text .= &add_links_to_dc_coverage($metadata{"dc:coverage"}); $text .= &generate_map_link($metadata{"geo:lat"},$metadata{"geo:long"}) if defined $metadata{"geo:lat"} and defined $metadata{"geo:long"}; $text .= qq{
\n}; if ( defined $metadata{"t:camera"} and defined $metadata{"cap:cameraShortName"} and defined $metadata{"exif:FNumber"} and defined $metadata{"exif:ExposureTime"} ) { $text .= qq{ }; $text .= $metadata{"cap:cameraShortName"} . qq{,\n}; $text .= " F" . eval($metadata{"exif:FNumber"}) . ", "; $text .= $metadata{"exif:ExposureTime"}; } $text .= qq{

\n}; $text .= qq{
\n}; $text .= qq{
\n}; $text .= &return_nav_links( $dotdots ) . "\n"; if ( $opt{index} && $opt{nextprev} ) { $text .= qq{ \n\n}; $text .= &return_ad_horiz_links if $opt{smads}; $text .= qq{
\n}; return $text; } # most of this should be generalized and reused between # generate_html_for_sm_file and generate_html_for_video sub generate_html_for_video { my ($metadata_ref, $file) = @_; my $metadata = %$metadata_ref; # deref my $src = &basename( $file ); $src =~ s,\.avi,-f00000001-tn.jpg,; # use frame one instead of foo-sm.jpg my $href = &basename( $file ); my $origfile = $file; # need to include ../../ in next/prev links when files span multiple dirs my $dotdots = &dotdots( $file ); # get e.g. "12:34pm" from the filename # @@ should get this from %metadata instead? but how to manage tz setting? my $local_time = ""; if (my ($hh,$mm) = ($file =~ /([0-9][0-9])-([0-9][0-9])-[0-9][0-9]/)) { my $ampm = $hh > 11 ? "pm" : "am"; $hh -= 12 if $hh > 12; $hh = 12 if $hh == 0; $hh =~ s/^0//; # 09:12 -> 9:12 $local_time = $hh . ":" . $mm . $ampm; } else { # @@ set $local_time to the date instead? } &kludge_camera_details( \%metadata ); my $text = ""; $text .= qq{
\n}; $text .= qq{
\n}; $text .= qq{

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

\n}; $text .= qq{

Video: };
    $text .= $metadata{

\n}; $text .= qq{

Download video file (AVI, }; $text .= `du -sh $origfile | cut -f1 | tr -d '\012'` . qq{B)

\n}; $text .= qq{
\n}; if ( defined( $metadata{"dc:description"} ) and length( $metadata{"dc:description"} ) ) { $text .= qq{

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

\n}; } $text .= qq{

}; $text .= $local_time . qq{, } if length ($local_time); $text .= &add_links_to_dc_coverage($metadata{"dc:coverage"}); $text .= &generate_map_link($metadata{"geo:lat"},$metadata{"geo:long"}) if defined $metadata{"geo:lat"} and defined $metadata{"geo:long"}; $text .= qq{
\n}; if ( defined $metadata{"t:camera"} and defined $metadata{"cap:cameraShortName"} and defined $metadata{"exif:FNumber"} and defined $metadata{"exif:ExposureTime"} ) { $text .= qq{ }; $text .= $metadata{"cap:cameraShortName"} . qq{,\n}; $text .= " F" . eval($metadata{"exif:FNumber"}) . ", "; $text .= $metadata{"exif:ExposureTime"}; } $text .= qq{

\n}; $text .= qq{
\n}; $text .= qq{
\n}; $text .= &return_nav_links( $dotdots ); if ( $opt{index} && $opt{nextprev} ) { $text .= qq{ \n\n}; } else { $text .= qq{
\n}; } $text .= qq{
\n}; my $framepat = $origfile; $framepat =~ s,\.avi,-f*-tn.jpg,; foreach my $vidframe (split(/\s+/,`/bin/ls $framepat`)) { my ($n,$mmss); if ( $vidframe =~ m/-f([0-9]{8})-tn\.jpg/ ) { $n = $1 + 0; # strip leading 0's $mmss = sprintf( "%02d:%02d", int($n/30/60), ($n/30)%60 ); # @@ assumes 30 frames per sec; should get that from video file } else { $n = "??"; $mmss = "?:??"; } $text .= qq{
\n}; $text .= qq{

frame $n

\n}; $text .= qq{

$mmss

\n}; $text .= qq{
\n}; $n++; } $text .= qq{
\n}; $text .= qq{
\n}; $text .= qq{
\n}; return $text; } sub print_index_header { return if $already_printed_header++; if ( defined $text_before_thumb[1] ) { print INDEX $text_before_thumb[1]; } else { my $h = $metadata{"cap:header"}; $h =~ s,^file://,,; # convert URIs to filenames open( HEADER, "< $h" ) or warn "error reading from index header file, $h: $!"; while (
) { s/%%title%%/Photos, $opt{indextitle}/g; print INDEX; } close ( HEADER ) or warn "error closing index header file, $h: $!"; print INDEX qq{
\n\n}; } } sub print_index_footer { return if $already_printed_footer++; if ( defined $text_before_thumb[$curr_image_num] ) { print INDEX $text_before_thumb[$curr_image_num]; } else { print INDEX qq{
\n}; my $f = $metadata{"cap:footer"}; $f =~ s,^file://,,; # convert URIs to filenames open( FOOTER, "< $f" ) or warn "error reading from index footer file, $f: $!"; print INDEX