#------------------------------------------------------------------------------ # File: TimezoneLookUp.config # # Description: Looks up Timezone data via Google Maps api. It uses cURL to make an Time Zone API call to Google Maps and # parses the results. It attemps to create a simple cache so similar GPS coordinates and time stamps will # not make excessive calls to the Google API. # # Requires cURL to be installed. # # A Google Maps Time Zone API key is required. # A Time Zone API key can be generated at: # https://developers.google.com/maps/documentation/timezone/get-api-key # Once generated, it must replace the "InsertTimeZoneAPIKeyHere" text below. # # Example command: # exiftool -config TimezoneLookUp.config" -GoogleLookUpTimeZone -GoogleLookUpTimeZoneDaylightSavings # -GoogleLookUpTimeZoneName -GoogleLookUpTimeZoneID -GoogleLookUpTimeZoneRaw # # Available tags: # # GoogleLookUpTimeZone: Returns the time zone based upon DateTimeOriginal and GPS Coordinates # in ±HH:MM format. This can be used to add on to or replace faulty XMP timestamps tags # or added to EXIF:OffsetTimeOriginal, EXIF:OffsetTime, and EXIF:OffsetTimeDigitized tags. # It cannot be added to the EXIF:TimeZoneOffset tag without modification as that tag is # required to be an integer value. # # GoogleLookUpTimeZoneDaylightSavings: Returns True/False depending upon whether Daylight Savings Time # was in effect at that location and time. # # GoogleLookUpTimeZoneName: Returns the time zone name for that location and time, e.g. Pacific Daylight Time, # China Standard Time, or Central European Standard Time. # # GoogleLookUpTimeZoneID: Returns the time zone id, such as America/Los_Angeles, Asia/Shanghai, or Europe/Paris # # GoogleLookUpTimeZoneRaw: Returns the raw time zone data as a list of KEY=VALUE pairs. # # Revisions: 2020/04/03 - Bryan K. Williams (aka StarGeek) created, based upon a previous error prone config I made a few # years ago. Uses cURL to make calls to Google instead of complex mess needed to use CPAN modules. # # Todo: Possibly add other time zone lookup APIs, add some UserParam options so the API key can be set on the command line and # GPS blur can be overriden and exact GPS coordinates can be used for each image #------------------------------------------------------------------------------ use POSIX; # Path to cURL. If cURL is already in the Windows PATH, then it should work as is. Otherwise, the full path to the # program needs to be set here. my $CurlPath = '/bin/curl'; my $timezonekey = 'AIzaSyAuuwIpNbh4HT2M7nG6d01Ul7VC84n802o'; # Google TimeZone API Key my $timezoneapi = 'https://maps.googleapis.com/maps/api/timezone/'; # Google API URL my $debug = 0; #change to 1 to print message when cache has been used sub MyProcessJson{ my ($jsonresult) = (@_); return Image::ExifTool::ImageInfo(\$jsonresult); } sub GetTZByGeo{ my ($DTO, $LatitudeLongitudeString) = (@_); my %TZHash; # Truncates DateTimeOriginal to YYY:MM:DD HH to create entry for the CacheHash my $shortDTO = $DTO; $shortDTO=~s/(^\d{4}:\d\d:\d\d \d\d).*/$1/; if (defined $CacheHash{$shortDTO.$LatitudeLongitudeString}) { print "------->Result found in cache\n" if $debug; return $CacheHash{$shortDTO.$LatitudeLongitudeString}; } #Convert DateTimeOriginal to epoch my $epoch = Image::ExifTool::GetUnixTime($DTO); # Create and excute cURL command my $tzurl = $timezoneapi . 'json?key='.$timezonekey . "&location=$LatitudeLongitudeString×tamp=$epoch"; my $JsonResult = `$CurlPath "$tzurl"`; # Create hash from returned json # $ResultHash hash will have the following values set # $ResultHash->{'TimeZoneName'} : Name of the time zone, e.g. Eastern Daylight Time, Pacific Standard Time # $ResultHash->{'TimeZoneId'} : Time zone id, e.g. America/New_York, Europe/London # $ResultHash->{'RawOffset'} : Time zone offset in seconds # $ResultHash->{'DstOffset'} : Dayling Savings time offset in seconds # $ResultHash->{'Status'} : Status of the command will be 'OK' if successful my $ResultHash = MyProcessJson($JsonResult); if ($ResultHash->{'Status'} ne "OK" or not defined $ResultHash){ return undef; } # Add rawOffset and dstOffset to get actual TZ offset and convert to string my $tzSec = $ResultHash->{'DstOffset'}+$ResultHash->{'RawOffset'}; $TZHash{'timezone'} = (($tzSec<0) ? '-' : '' ) . strftime("\%H:\%M", gmtime(abs($tzSec))); # Creating additional entry to indicate if Daylight Savings Time is in effect $TZHash{'dst'} = ($ResultHash->{'DstOffset'} != 0 ) ? 'True' : 'False'; $TZHash{'timezoneid'} = $ResultHash->{'TimeZoneId'} ; $TZHash{'timezonename'} = $ResultHash->{'TimeZoneName'}; $CacheHash{$shortDTO.$LatitudeLongitudeString}=\%TZHash; return \%TZHash } # Rounds GPS Coords to 4 decimal places. This brings the accuracy to 10 meter or so range # Reduces accuracy but increases chance of using the cache so there won't be as many calls to Google # # Approximate distances between coordinates according to the GPS calculator I found. If you wish to # increase the accuracy (more calls to Google)or decrease the accuracy (less calls to Google), then # you can change the sprintf statement with the values in the first column # %.1f = 0.10000 : ~ 11,119.49 meters ~ 11.1 k # %.2f = 0.01000 : ~ 1,111.95 meters ~ 1.1 k # %.3f = 0.00100 : ~ 111.19 meters # %.4f = 0.00010 : ~ 11.12 meters # %.5f = 0.00001 : ~ 1.1 meters sub BlurGPS{ my ($lat,$long) = @_; return (sprintf("%.4f", $lat).','.sprintf("%.4f", $long)); } %Image::ExifTool::UserDefined = ( 'Image::ExifTool::Composite' => { # $dref hash will have the following values set # $dref->{'timezonename'} : Name of the time zone, e.g. Eastern Daylight Time, Pacific Standard Time # $dref->{'timezoneid'} : Time zone id, e.g. America/New_York, Europe/London # $dref->{'timezone'} : Time zone in the format of ±HH:MM # $dref->{'dst'} : True or False if Daylight Savings Time is in effect # ----------------------------------------------- # Returns the time zone result, this is grouped as a Time tag GoogleLookUpTimeZone => { PrintConv => '$self->ConvertDateTime($val)', Groups => { 2 => 'Time' }, Require => { 0 => 'GPSLatitude', 1 => 'GPSLongitude', }, Desire => { 2 => 'DateTimeOriginal', 3 => 'CreateDate', }, ValueConv => q{ my $dateTime = $val[2] || $val[3]; #Use DateTimeOriginal if available otherwise use CreateDate return undef unless defined $dateTime; my $LatLongStr = BlurGPS($val[0],$val[1]); my $dref = GetTZByGeo($dateTime,$LatLongStr); return (defined $dref->{'timezone'} and $dref->{'timezone'} ne '') ? $dref->{'timezone'} : undef; }, }, # ----------------------------------------------- # Returns the all the data returned as a List type tag of Key=Value pairs GoogleLookUpTimeZoneRaw => { Require => { 0 => 'GPSLatitude', 1 => 'GPSLongitude', }, Desire => { 2 => 'DateTimeOriginal', 3 => 'CreateDate', }, ValueConv => q{ my $dateTime = $val[2] || $val[3]; #Use DateTimeOriginal if available otherwise use CreateDate return undef unless defined $dateTime; my $LatLongStr = BlurGPS($val[0],$val[1]); my $dref = GetTZByGeo($dateTime,$LatLongStr); # Creates array to return values as a List type tag my @ReturnArray; if (defined $dref){ for my $key ( keys %{$dref} ) { push @ReturnArray, (defined $dref->{$key}) ? "$key=$dref->{$key}" : ''; } } return @ReturnArray ? \@ReturnArray : undef; }, }, # ----------------------------------------------- # Returns True/False if Daylight Savings Time is in effect GoogleLookUpTimeZoneDaylightSavings => { Require => { 0 => 'GPSLatitude', 1 => 'GPSLongitude', }, Desire => { 2 => 'DateTimeOriginal', 3 => 'CreateDate', }, ValueConv => q{ my $dateTime = $val[2] || $val[3]; #Use DateTimeOriginal if available otherwise use CreateDate return undef unless defined $dateTime; my $LatLongStr = BlurGPS($val[0],$val[1]); my $dref = GetTZByGeo($dateTime,$LatLongStr); return (defined $dref->{'dst'} and $dref->{'dst'} ne '') ? $dref->{'dst'} : undef; }, }, # ----------------------------------------------- # Returns the name of the time zone GoogleLookUpTimeZoneName => { Require => { 0 => 'GPSLatitude', 1 => 'GPSLongitude', }, Desire => { 2 => 'DateTimeOriginal', 3 => 'CreateDate', }, ValueConv => q{ my $dateTime = $val[2] || $val[3]; #Use DateTimeOriginal if available otherwise use CreateDate return undef unless defined $dateTime; my $LatLongStr = BlurGPS($val[0],$val[1]); my $dref = GetTZByGeo($dateTime,$LatLongStr); return (defined $dref->{'timezonename'} and $dref->{'timezonename'} ne '') ? $dref->{'timezonename'} : undef; }, }, # ----------------------------------------------- # Returns the ID of the time zone GoogleLookUpTimeZoneID => { Require => { 0 => 'GPSLatitude', 1 => 'GPSLongitude', }, Desire => { 2 => 'DateTimeOriginal', 3 => 'CreateDate', }, ValueConv => q{ my $dateTime = $val[2] || $val[3]; #Use DateTimeOriginal if available otherwise use CreateDate return undef unless defined $dateTime; my $LatLongStr = BlurGPS($val[0],$val[1]); my $dref = GetTZByGeo($dateTime,$LatLongStr); return (defined $dref->{'timezoneid'} and $dref->{'timezoneid'} ne '') ? $dref->{'timezoneid'} : undef; }, }, # ----------------------------------------------- }, ); #------------------------------------------------------------------------------ 1; #end