#!/usr/bin/perl -w # Multi output jpeg tagger and georeferencer # by Marc MERLIN / v1.0 2008/07/14 # many tweaks to support per file/directory GPS mappings / v1.1 2008/12/15 # This script does require a little editting to suit your particular setup, # starting with the EDITME block below. # # This script has 3 functions depending on what name it has (use symlinks): # pictprocess (or addsize) # -> reads an HTML file, and edits IMG SRC urls to add picture sizes and # links to a georeferenced map that you created with # http://www.gpsvisualizer.com/map # and for which you modified the output to add some PHP that receives the # url encoded point and adds a GV_Marker entry for it. # addsize does the same, but only touches picture links that do not already # include gps info # gen_gmaps_gv -> pictprocess # -> This takes a bunch of jpegs and generates javascript code suitable for # pasting as georeferenced photo markers in a map created with gpsvizualiser # gen_rig_gps_mapping -> pictprocess # -> This call generates a .rig_mapping file with pointers from filenames # to text to display and georeferenced link to add to a picture in Rig # http://rig.powerpulsar.com/ | http://sourceforge.net/projects/rig-thumbnail/ # with the georeference patch # # For more details on how this all works, see # http://marc.merlins.org/linux/gps_geotagging/ use strict; use URI::Escape; use vars qw ($HTDOCS $HTDOCS2 ); # globals that are optional depending on which subroutine gets run # but they need to be defined early to make the parser happy. my $backup; my $gv_output_file; my $rig_mapping_file; my $CFG=".gmap.cfg"; my ($album, $library, $lib_dir) = ( "CHANGEME", "CHANGEME", "CHANGEME"); # the block below is shown here for reference, but you should have it # in a file called .gmap.cfg in each of your picture directories ### PARSING -- START ### ## Album name is used for the URL of the php JS google map, the name of ## the window to spawn, and of course where to read the files from #$album = "20080101_Destination"; # ## Where directory the original pictures the thumbnails came from, are, ## note that we link to html files there, not jpegs #$library = "/Pix"; ## lib dir is the subdirectory $album is in $library. It can be "" #$lib_dir = "Flying"; ### PARSING -- END ### # note that in a directory with pictures from mixed sources, you can use this ### PARSING -- START ### #if ( /2\d\d_Day1_CrescentMeadow_LonePine/ or # /3\d\d_Day2_LonePine_HamiltonLake_Bearpaw/ or # /4\d\d_Day3_BearPaw_CrescentMeadow/ ) #{ # $album = "20060701_HighSierraTrail"; # $library = "/Pix"; # $lib_dir = "Hiking"; #} # #elsif ( /20060810_VGT_GCN/ ) #{ # $album = "20060810_VGT_GCN"; # $library = "/Pix"; # $lib_dir = "Flying/Flights"; #} ### PARSING -- END ### # for gen_gmaps_gv and gen_rig_gps_mapping, a config file in the current # diretory is required. pictprocess will read the cfg file where it includes # pictures from eval `cat $CFG` if -f "$CFG"; # Adjust the following as required. Note that there are functions so that # they pick up the current value of the globals they source (i.e. current # value witll be different for pictprocess since each picture directory will # have its own). ### STATIC GLOBALS -- BEGIN ### # Your htdocs root that should be used to read files referenced # in web pages $HTDOCS='/var/www/html'; # local hack $HTDOCS2='/var/www/'; # on my setup, each photo album gets its own JS google map php file so that it # shows the gps track, but if you just want to put the photo on a map, you # could share the same php file sub gpscgi() { return "/perso/gps/gmap/$album.php" }; my $gpscgi = gpscgi(); # MUST HAVE trailing / sub photodir() { return "$library/$lib_dir/$album/" }; my $photodir = photodir(); # Where thumbnails are located in case we run in georeferenced link list # generation mode sub tndir() { "$library/rig-cache/$lib_dir/$album/prev800_" }; my $tndir = tndir(); ### STATIC GLOBALS -- END ### sub gen_gmaps_rig_gps_mapping($$); sub html_pict_process(); my $skip_gps = 0; # mode #2 is to generate photo waypoints in javascript (née livescript) # and the output file is if ($0 =~ /gen_gmaps_gv$/) { die "Didn't find $CFG file" if ($album eq "CHANGEME"); # In gv JS generation mode, where the JS code is output $gv_output_file = "gvoutput"; gen_gmaps_rig_gps_mapping("gv", $gv_output_file); } # mode #3 generates a text file that maps picture names to URLs. Those # files are used by RIG to generate links from the picture album to elsif ($0 =~ /gen_rig_gps_mapping$/) { die "Didn't find $CFG file" if ($album eq "CHANGEME"); $rig_mapping_file = ".rig_mapping"; gen_gmaps_rig_gps_mapping("rig", $rig_mapping_file); } # In basic run mode, this reads an HTML page, finds all the jpegs, adds # WIDTH/HEIGHT data as gathered by jhead, and adds an a href link to # the javascript google map. else { if ($0 =~ /addsize$/) { # if we only add size, we skip pictures that have GPS info $skip_gps = 1; } $backup=0; if (@ARGV and $ARGV[0] eq "-b") { shift @ARGV; $backup=1; } html_pict_process(); } exit; # sub size_and_gps { my ($fullfile) = @_; my ($resline, $posline); my ($sizex, $sizey, $lat, $lon) = (0, 0, "", ""); #print STDERR "Checking '$fullfile'\n"; open(JHEAD, '-|', 'jhead', $fullfile) or die "$!"; @_ = ; close(JHEAD); #print STDERR "Return from jhead $fullfile is ".join("", @_); $resline = join("",grep(/^Resolution/, @_)) or die "Can't read resolution for $fullfile"; $resline =~ m#^Resolution\s+:\s+(\d+) x (\d+)# or die "Can't match resolution for $fullfile"; #print STDERR "got resline $resline\n"; ($sizex, $sizey) = ($1, $2); if ($posline = join("\n",grep(/^Spec. ?Instr./, @_))) { $posline =~ m#Spec. ?Instr.\s+:\s+Lat (\S+), Lon (\S+)# or die "Can't match gps data for $fullfile"; ($lat, $lon) = ($1, $2); } #print "Size: $sizex/$sizey/$lat/$lon\n"; return ($sizex, $sizey, $lat, $lon); } sub printsub { my ($basedir, $filename, $before, $after, $href, $closehref) = @_; my ($sizex, $sizey, $lat, $lon); my $fullfile; # which directory the file is in, which is where we try to read $CFG from my $filepath; my $return; $href = "" if (not $href); $closehref = "" if (not $closehref); die "No filename received in printsub" unless ($filename); #warn "printsub working on image '$filename'\n"; if ($filename =~ m#http://# or $filename !~ m#\.jpg#i) { return "$href$before\"$filename\"$after>$closehref"; } $after =~ s/\s+HEIGHT=\d+//i; $after =~ s/\s+WIDTH=\d+//i; # filename can have a path, if it's full, it's based off the HTDOCS # root if it's not a full path, it's relative to $basedir which is # where the html file was read from. $basedir = $HTDOCS if ($filename =~ m#^/#); # Local hack: $basedir = $HTDOCS2 if ($filename =~ m#^/(Pix|gifs)/#); $fullfile = "$basedir$filename"; if (not -f "$fullfile") { warn "file '$fullfile' not found, skipping\n"; return "$before$filename$after>"; } # back from the system days #$fullfile =~ s#'#\\'#g; #$fullfile =~ s#"#\\"#g; ($sizex, $sizey, $lat, $lon) = size_and_gps($fullfile); $return = "$before\"$filename\"$after WIDTH=$sizex HEIGHT=$sizey>"; if ($lat) { my $photo = $filename; my $photourl; my $tnurl = uri_escape($filename); my $filepath; ($filepath = $fullfile) =~ s#(.*?)[^/]+$#$1#; #print "filepath is $filepath\n"; # get basefile, can be used for per filename pattern matching in $CFG ($_ = $fullfile) =~ s#.*?([^/]+)$#$1#; $album = "CHANGEME"; if (-f "$filepath/$CFG") { eval `cat "$filepath/$CFG"` } else { die "Couldn't parse required config $filepath/$CFG: $!" } if ($album eq "CHANGEME") { warn "Cannot find gmap match for filename $_ in $filepath/$CFG, skipping georef link"; return $return; } my $photodir = uri_escape(photodir()); my $gpscgi = gpscgi(); # remove leading dir in filename and jpeg extension for a lame picture # name generation $photo =~ s#.*/##; $photo =~ s/.jpg//; $photourl = "$photodir$photo.html"; $return = "".$return.""; } #print "$return\n"; return $return; } sub gen_gmaps_rig_gps_mapping($$) { my ($type, $outfile) = @_; die "$0 list of jpegs to scan\n" unless (@ARGV); open(OUT, ">$outfile") or die "Can't open $outfile: $!"; foreach my $file (@ARGV) { my ($lat, $lon); die "$0 is meant to be run in the directory pictures are in, as in $0 *.jpg\n" if ($file =~ m#/#); ($_, $_, $lat, $lon) = size_and_gps($file); if ($lat) { my $photo = $file; $photo =~ s/.jpg//; my $photourl = "$photodir$photo.html"; my $tnurl = "${tndir}$photo.jpg"; if ($type eq "gv") { print OUT <\n"; } print "Wrote $photo / $lat / $lon\n"; } else { print "Missing GPS data for $file\n"; } } close(OUT); if ($type eq "gv") { print "All gv data written to $outfile, merge those waypoints to your JS html file\n"; } else { print "All gps mapping data written to $outfile, Rig should now pick this up\n"; } } sub html_pict_process() { die "$0 list of html pages to scan/edit\n" unless (@ARGV); foreach my $file (@ARGV) { my @filedata; my $filedata; my $oldfiledata; # basedir will be used for computing the path to pictures with # relative pathnames if we're working on an html file not in cwd(). my $basedir = "./"; my $outfile; $file =~ s#'#\\'#g; $file =~ s#"#\\"#g; open (HTML, "$file") or die "Can't open $file: $!"; @filedata = ; close (HTML); print "Working on $file\n"; $filedata = join("", @filedata); $oldfiledata = $filedata; if ($file =~ m#/#) { # note that $basedir is based off the path of the html file # we're reading, not the directory the included image is in. ($basedir = $file) =~ s#(.+/)[^/]*#$1#; } #print "Basedir is $basedir\n"; if ($filedata =~ m#gps/gmap# and $skip_gps) { warn "Not touching $file in addsize mode because it contains gps data\n"; next; } # All the IMG/HREF urls gets replaced in the html file that # was slurped entirely in filedata. # support IMG SRC=/foo and IMG SRC="/foo" # both regexes have to run since they have to support both kinds of # urls over an entire file in one pass. Unquoted comes first so that # we can quote links $filedata =~ s#(]+?gps/gmap[^>]+?>)?(]+)([^>]*?)>()?#printsub($basedir,$3,$2,$4,$1,$5)#eisg; $filedata =~ s#(]+?gps/gmap[^>]+?>)?(]*?)>()?#printsub($basedir,$3,$2,$4,$1,$5)#eisg; if ($oldfiledata ne $filedata) { $outfile = "$file.new.$$"; print "Changing $file\n"; open (HTML, ">$outfile") or die "Can't open $outfile: $!"; print HTML $filedata; close (HTML); if ($backup) { rename ($file,"$file.old.$$") or die "Can't rename $file in $file.old.$$: $!"; } else { unlink ($file) or die "Can't unlink $file: $!"; } rename ($outfile,$file) or die "Can't rename $outfile in $file: $!"; } } }