Tip: Auto tag Images with address location via EXIF GPS data

From your noodle to other noodles. Talk about ways to get the most from Hazel. Even exchange recipes for the cool rules you've thought up. DO NOT POST YOUR QUESTIONS HERE.

Moderators: Mr_Noodle, Moderators

Here is the raw hazel script. My only issue with my code is that I have to make 3x calls to Google to get all the info I need. You can use what I return back to hazel (last line) for however you purpose, like creating folder names, or file names.

The folder structure I am using, after many trials is:
Year
Month (06 - June)
Day + State (16 - Sunday Colorado)
Camera Name
(filename) year.month.day.XXXX complete address info.(jpg, raw, mov)

As you can see I'm a bit OCD and get pretty detailed, but I have found that using various cloud services that don't sort pictures well. This way I can drill down to what 'event' I am looking for quickly, without having a preview of every file.

Also the ROUTE and ADDRESS information I am pulling from google gives me 2 very different results regarding a street location. For instance, a recent visit to Zion National Park, ROUTE gave me the hiking trail that I was on, ADDRESS gave me nothing relevant to the park or location I was actually at. Its a crap shoot since I cannot find a way to pull PIO info, so often parks show up as an actual address, and thats not very descriptive for my folder structure.

If you add to or modify the script, please post the results. It took a lot of hours to figure this out and would really like to make this a collaborative effort for whoever uses it.


Thanks to the original author (@dhy8386)for posting this, its been a great help!


Code: Select all
--
--
--              G. R. E. G
--
--    Global Reverse EXIF Geocode
--
--              version 1.41
--
--
--     
--     Based on code by dhy8386
--        via NoodleSoft forums
--    http://tinyurl.com/yam9we8w

--  1.41 by gflinch ( @gflinch on twitter)

-- this script uses TAG command from https://github.com/jdberry/tag
--
-- <tags> is a comma-separated list of tag names; use * to match/find any tag.
-- tag -a | --add <tags> <path>...     Add tags to file
-- tag -r | --remove <tags> <path>...  Remove tags from file
-- tag -s | --set <tags> <path>...     Set tags on file
-- To remove all tags from a file, use the wildcard * to match all tags:
-- tag --remove \* file




set ExifTool to "/usr/local/bin/exiftool"
set ExifGPSLat to "-GPSLatitude"
set ExifGPSLon to "-GPSLongitude"
set ExifToolOption to "-keywords="

set pathList to ""

set TagTool to "/usr/local/bin/tag"
set TagToolOption to "-s" -- The set operation replaces all tags on the specified files with one or more new tags.
set TagToolNOGPS to "NO GPS Information"

set kWordAddress to ""
set kWordState to ""
set kWordRoute to ""
set kWordPostal to ""
set cityTown to ""
set state to ""


-- Get file path for STANDALONE
--try
--   set pathList to quoted form of POSIX path of (choose file with prompt "Please choose a file:" default location (path to desktop))
--end try

-- Get file path for HAZEL
try
   set pathList to quoted form of POSIX path of (theFile as alias)
end try

-- Get Camera Model
set cameraModel to do shell script ExifTool & " -p '${model}' " & pathList
set cameraMake to do shell script ExifTool & " -p '${make}' " & pathList


-- Get GPS Latitude and Longitude from EXIF data
do shell script ExifTool & space & ExifGPSLat & space & "-overwrite_original_in_place -P" & space & pathList
set gpsLat to result

do shell script ExifTool & space & ExifGPSLon & space & "-overwrite_original_in_place -P" & space & pathList
set gpsLon to result

do shell script TagTool & space & TagToolOption & quoted form of TagToolNOGPS & space & pathList


if (gpsLat = "") or (gpsLon = "") then
   -- error number -128
   set state to "no gps"
   set cityTown to " "
   set place to " "
   set country to " "
   return {hazelPassesScript:false, hazelOutputAttributes:{cameraMake, place, country, cityTown, cameraModel, state}}
   
else
   -- Set GPS variables separately
   set latDegree to word 3 of gpsLat
   set latMinute to word 5 of gpsLat
   set latSecond to word 6 of gpsLat
   set latDirection to word 7 of gpsLat
   set lonDegree to word 3 of gpsLon
   set lonMinute to word 5 of gpsLon
   set lonSecond to word 6 of gpsLon
   set lonDirection to word 7 of gpsLon
   
   -- Convert Deg,Sec,Min to Decimals
   set latDecimal to latDegree + latMinute / 60 + latSecond / 3600
   set lonDecimal to lonDegree + lonMinute / 60 + lonSecond / 3600
   
   -- Change Decimals based on Direction
   if (latDirection is equal to "S") then
      set latDecimal to latDecimal * -1 as text
   end if
   
   if (lonDirection is equal to "W") then
      set lonDecimal to lonDecimal * -1 as text
   end if
   
   set locateP to latDecimal & "," & lonDecimal as string
   
   -- Set the Google API variables
   set api_key to "XXXXXXXXXXXXXXXXXXXXXXXXX"
   set apiCommandAddress to "https://maps.googleapis.com/maps/api/geocode/json?latlng=" & locateP & "&result_type=street_address&key=" & api_key
   set apiCommandState to "https://maps.googleapis.com/maps/api/geocode/json?latlng=" & locateP & "&result_type=administrative_area_level_1&key=" & api_key
   set apiCommandRoute to "https://maps.googleapis.com/maps/api/geocode/json?latlng=" & locateP & "&result_type=route&key=" & api_key
   set apiCommandPostal to "https://maps.googleapis.com/maps/api/geocode/json?latlng=" & locateP & "&result_type=postal_code&key=" & api_key
   -- ORIGIONAL -- set apiCommand to "https://maps.googleapis.com/maps/api/geocode/json?latlng=" & locateP & "&result_type=street_address&key=" & api_key
   
   -- Get State Name from Google
   try
      tell application "JSON Helper"
         set geoL to apiCommandState
         set jsonResult to fetch JSON from geoL with cleaning feed
         set kWordState to formatted_address of item 1 of results of jsonResult
      end tell
   end try
   
   -- Get route location from Google
   try
      tell application "JSON Helper"
         set geoL to apiCommandRoute
         set jsonResult to fetch JSON from geoL with cleaning feed
         set kWordRoute to formatted_address of item 1 of results of jsonResult
      end tell
   end try
   
   -- Get postal location from Google
   try
      tell application "JSON Helper"
         set geoL to apiCommandPostal
         set jsonResult to fetch JSON from geoL with cleaning feed
         set AppleScript's text item delimiters to {", "}
         set kWordPostal to postcode_localities of item 1 of results of jsonResult
      end tell
   end try
   
   -- Get address information from Google
   try
      tell application "JSON Helper"
         set geoL to apiCommandAddress
         set jsonResult to fetch JSON from geoL with cleaning feed
         set kWordAddress to formatted_address of item 1 of results of jsonResult
      end tell
   end try
end if

-- seperate kWordxxxxx into lists locationStateData, locationRouteData, locationPostalData, and locationAddressData
set combinedData to {}
set allStateData to {}
set allRouteData to {}
set allAddressData to {}
set allPostalData to {}

set locationStateData to ""
set locationAddressData to ""
set locationRouteData to ""
set locationAddressData to ""


-- State
set allStateData to kWordState & ","
set AppleScript's text item delimiters to ","
set locationStateData to text items of allStateData
set AppleScript's text item delimiters to {""}

repeat with a from 1 to length of locationStateData
   set itemLocal to item a of locationStateData
   -- set combinedData to combinedData & b & " P " & itemLocal & return
   set combinedData to combinedData & itemLocal & ","
   -- Process the current list item
   --log itemLocal & " is item " & a & " in the StateData list." & return
end repeat

-- Route
set allRouteData to kWordRoute & ","
set AppleScript's text item delimiters to ","
set locationRouteData to text items of allRouteData
set AppleScript's text item delimiters to {""}

repeat with a from 1 to length of locationRouteData
   set itemLocal to item a of locationRouteData
   -- set combinedData to combinedData & b & " P " & itemLocal & return
   set combinedData to combinedData & itemLocal & ","
   -- Process the current list item
   --log itemLocal & " is item " & a & " in the RouteData list." & return
end repeat

-- Postal
set allPostalData to kWordPostal & ","
set AppleScript's text item delimiters to ","
set locationPostalData to text items of allPostalData
set AppleScript's text item delimiters to {""}

repeat with a from 1 to length of locationPostalData
   set itemLocal to item a of locationPostalData
   -- set combinedData to combinedData & b & " P " & itemLocal & return
   set combinedData to combinedData & itemLocal & ","
   -- Process the current list item
   --log itemLocal & " is item " & a & " in the PostalData list." & return
end repeat

-- Address
set allAddressData to kWordAddress & ","
set AppleScript's text item delimiters to ","
set locationAddressData to text items of allAddressData
set AppleScript's text item delimiters to {""}

repeat with a from 1 to length of locationAddressData
   set itemLocal to item a of locationAddressData
   -- set combinedData to combinedData & b & " P " & itemLocal & return
   set combinedData to combinedData & itemLocal & ","
   -- Process the current list item
   --log itemLocal & " is item " & a & " in the AddressData list." & return
end repeat


-- Seperate data from lists
set state to item 1 of locationStateData
set country to item 2 of locationStateData
set place to item 1 of locationRouteData
set town to item 2 of locationRouteData
set street to item 1 of locationAddressData
set city to item 2 of locationAddressData


-- choose if city or town is filled in
set cityTown to ""
if city = "" then
   set cityTown to town
else
   set cityTown to city
end if

-- clear Exif Keywords
do shell script ExifTool & space & "-keywords=''" & space & "-overwrite_original_in_place -P" & space & pathList
set TagToolNOGPS to ""

-- create list location, correct if PLACE defined or is it blank
set location to {}
if place = "" then
   set location to {cityTown, state, country}
   
   do shell script TagTool & space & TagToolOption & space & quoted form of country & "," & quoted form of state & "," & quoted form of cityTown & "," & space & pathList
   
   do shell script ExifTool & space & "-keywords+=" & quoted form of cityTown & space & "-overwrite_original_in_place -P" & space & pathList
   do shell script ExifTool & space & "-keywords+=" & quoted form of state & space & "-overwrite_original_in_place -P" & space & pathList
   do shell script ExifTool & space & "-keywords+=" & quoted form of country & space & "-overwrite_original_in_place -P" & space & pathList
else
   set location to {place, cityTown, state, country}
   
   do shell script TagTool & space & TagToolOption & space & quoted form of country & "," & quoted form of state & "," & quoted form of cityTown & "," & quoted form of place & "," & space & pathList
   
   do shell script ExifTool & space & "-keywords+=" & quoted form of place & space & "-overwrite_original_in_place -P" & space & pathList
   do shell script ExifTool & space & "-keywords+=" & quoted form of cityTown & space & "-overwrite_original_in_place -P" & space & pathList
   do shell script ExifTool & space & "-keywords+=" & quoted form of state & space & "-overwrite_original_in_place -P" & space & pathList
   do shell script ExifTool & space & "-keywords+=" & quoted form of country & space & "-overwrite_original_in_place -P" & space & pathList
end if

--repeat with a from 1 to length of location
--   set itemLocal to item a of location
--end repeat

repeat with a from 1 to the count of location
   set itemLocal to itemLocal & item a of location & ","
end repeat


--log return & return
--log "locationAddressData: " & locationAddressData
--log return & return


-- for HAZEL:
-- Add keywords for location for images
-- TAG files with location data 
-- do shell script ExifTool & space & ExifToolOption & space & location & space & "-overwrite_original_in_place -P" & space & pathlist
-- do shell script TagTool & space & TagToolOption & space & quoted form of cityTown & space & pathlist


-- clean up spaces

if (country begins with space) then
   set country to text 2 thru -1 of country
end if

if (place begins with space) then
   set place to text 2 thru -1 of place
end if

if (cityTown begins with space) then
   set cityTown to text 2 thru -1 of cityTown
end if



return {hazelPassesScript:true, hazelOutputAttributes:{cameraMake, place, country, cityTown, cameraModel, state}}
gflinch
 
Posts: 7
Joined: Thu Nov 10, 2016 5:12 pm

So I haven't had any time to work on my hazel rules lately but just noticed your updated script.

First, I am having trouble installing TAG from Github. I thought I had it installed but get and error the directory doesn't exist.

If I comment out lines in your script related to TAG I can run the program and get the output. However, when I pasted this into Hazel and commented the appropriate lines for Hazel to pull in the file opposed to selecting it manually, it worked once and sorted files into subfolders with the country name etc. However, it only worked once. I don't know what I've changed but I get the error that the source and destination are the same.

I am not sure if I am missing a piece of the puzzle since I've commented and not install TAG?

EDIT: In the logs it says "More output attributes exported by script than specified in the rule."
EDIT: In Applescript is says "No valve to add or delete in IPTC:Keyowrds Nothing to do"
jfisher
 
Posts: 55
Joined: Sat Feb 25, 2017 7:47 pm

Yes i figured out the script, and works great.
richardjews
 
Posts: 1
Joined: Mon Oct 02, 2017 7:32 am

I had this working perfectly and was figuring out how I'd like to organize my folder structure. I was curious if the google api could return the abbreviation for "state" so I went looking at this link.

https://developers.google.com/maps/documentation/geocoding/intro

I noticed the there were additional callouts for things such as "point of interest" or "natural feature", "park' etc. I figured I would try swapping out "administrative_area_level_1" for "park" to see if it worked quickly since a lot of my travels are to national parks. It didn't. So I swapped it back to "administrative_area_level_1" thinking it would start working again. However, no my variable for "state" consistently outputs "no gps" and I get "Warning: No value to add or delete in IPTC:Keywords" again.

I've tried just pasting the original code back in, rebuilding the rule etc but I can't get it to work again, even on new files that haven't been touched by the rule yet.

Any idea why its not reverting to its expected behaviour?
jfisher
 
Posts: 55
Joined: Sat Feb 25, 2017 7:47 pm

This is the Hazel script which may save some time (replace any paths to apps with your own of course)

it uses https://github.com/jdberry/tag and http://github.com/alexreisner/geocoder plus of course Exiftool

_________________

OLDIFS=$IFS
IFS="
"

Coordinates=$( /Applications/myCLIApps/exiftool -gpslatitude -gpslongitude -T -n "$1" )

Cityname=$( /usr/local/bin/geocode -s google $Coordinates | /usr/bin/grep "City" | /usr/bin/sed -n -e 's/^.*City: //p' | /usr/bin/tr -d ' ' )

/opt/local/bin/tag "$1" --add "$Cityname"


IFS=$OLDIFS


___________________

So this adds the City name as a tag to the image file and then you can cluster files against that sort.

You can use Hazel rules to prefix photo files with that tag name and have all photo files prefixed with that broad location.

In my rules a) clear pre-existing tags (don't want a complicated prefix!) b) execute the above embedded script c) clear the City tag after its been used to prefix the file. A second benefit is you can see the rules in operation as any existing tags e.g. colour ones are cleared file by file.
rleaver152
 
Posts: 10
Joined: Sun Apr 30, 2017 3:24 pm

The API now need pay, is correct ???
fmlisboajr
 
Posts: 13
Joined: Fri Sep 02, 2016 4:14 pm
Location: Portugal

Previous

Return to Tips & Tricks - DO NOT POST QUESTIONS