Initial checkin of script
This commit is contained in:
541
bin/reverse_geolocate.py
Executable file
541
bin/reverse_geolocate.py
Executable file
@@ -0,0 +1,541 @@
|
||||
#!/opt/local/bin/python3
|
||||
|
||||
# AUTHOR : Clemens Schwaighofer
|
||||
# DATE : 2018/2/20
|
||||
# LICENSE: GPLv3
|
||||
# DESC : Set the reverse Geo location (name) from Lat/Long data in XMP files in a lightroom catalogue
|
||||
# * tries to get pre-set geo location from LR catalog
|
||||
# * if not found tries to get data from Google
|
||||
# * all data is translated into English with long vowl system (aka ou or oo is ō)
|
||||
# MUST HAVE: Python XMP Toolkit (http://python-xmp-toolkit.readthedocs.io/)
|
||||
|
||||
import argparse
|
||||
import os, sys, re
|
||||
# Note XMPFiles does not work with sidecar files, need to read via XMPMeta
|
||||
from libxmp import XMPMeta, XMPError, consts
|
||||
import sqlite3
|
||||
import requests
|
||||
from shutil import copyfile
|
||||
|
||||
##############################################################
|
||||
### FUNCTIONS
|
||||
##############################################################
|
||||
|
||||
### ARGPARSE HELPERS
|
||||
|
||||
# call: writable_dir_folder
|
||||
# checks if this is a writeable folder OR file
|
||||
# AND it works on nargs *
|
||||
class writable_dir_folder(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string = None):
|
||||
# we loop through list (this is because of nargs *)
|
||||
for prospective_dir in values:
|
||||
# if valid and writeable (dir or file)
|
||||
if os.access(prospective_dir, os.W_OK):
|
||||
# init new output array
|
||||
out = []
|
||||
# if we have a previous list in the namespace extend current list
|
||||
if type(namespace.xmp_sources) is list:
|
||||
out.extend(namespace.xmp_sources)
|
||||
# add the new dir to it
|
||||
out.append(prospective_dir)
|
||||
# and write that list back to the self.dest in the namespace
|
||||
setattr(namespace, self.dest, out)
|
||||
else:
|
||||
raise argparse.ArgumentTypeError("writable_dir_folder: {0} is not a writable dir".format(prospective_dir))
|
||||
|
||||
# call: readable_dir
|
||||
# custom define to check if it is a valid directory
|
||||
class readable_dir(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string = None):
|
||||
prospective_dir=values
|
||||
if not os.path.isdir(prospective_dir):
|
||||
raise argparse.ArgumentTypeError("readable_dir:{0} is not a valid path".format(prospective_dir))
|
||||
if os.access(prospective_dir, os.R_OK):
|
||||
setattr(namespace,self.dest,prospective_dir)
|
||||
else:
|
||||
raise argparse.ArgumentTypeError("readable_dir:{0} is not a readable dir".format(prospective_dir))
|
||||
|
||||
### MAIN FUNCTIONS
|
||||
|
||||
# METHOD: reverseGeolocate
|
||||
# PARAMS: latitude, longitude
|
||||
# RETURN: dict with location, city, state, country, country code
|
||||
# if not fillable, entry is empty
|
||||
# google images lookup base settings
|
||||
# SAMPLE: http://maps.googleapis.com/maps/api/geocode/json?latlng=35.6671355,139.7419185&sensor=false
|
||||
def reverseGeolocate(longitude, latitude):
|
||||
# clean up long/lat
|
||||
# they are stored with N/S/E/W if they come from an XMP
|
||||
# format: Deg,Min.Sec[NSEW]
|
||||
# NOTE: lat is N/S, long is E/W
|
||||
# detect and convert
|
||||
lat_long = longLatReg(longitude = longitude, latitude = latitude)
|
||||
# sensor (why?)
|
||||
sensor = 'false'
|
||||
# request to google
|
||||
base = "http://maps.googleapis.com/maps/api/geocode/json?"
|
||||
params = "latlng={lat},{lon}&sensor={sensor}".format(lon = lat_long['longitude'], lat = lat_long['latitude'], sensor = sensor)
|
||||
key = "&key={}".format(args.google_api_key) if args.google_api_key else ''
|
||||
url = "{base}{params}{key}".format(base = base, params = params, key = key)
|
||||
response = requests.get(url)
|
||||
# sift through the response to get the best matching entry
|
||||
geolocation = {
|
||||
'CountryCode': '',
|
||||
'Country': '',
|
||||
'State': '',
|
||||
'City': '',
|
||||
'Location': ''
|
||||
}
|
||||
# print("Google response: {} => TEXT: {} JSON: {}".format(response, response.text, response.json()))
|
||||
# print("Error: {}".format(response.json()['status']))
|
||||
if response.json()['status'] is not 'INVALID_REQUEST':
|
||||
# first entry for type = premise
|
||||
for entry in response.json()['results']:
|
||||
for sub_entry in entry:
|
||||
if sub_entry == 'types' and 'premise' in entry[sub_entry]:
|
||||
# print("Entry {}: {}".format(sub_entry, entry[sub_entry]))
|
||||
# print("Address {}".format(entry['address_components']))
|
||||
# type
|
||||
# -> country,
|
||||
# -> administrative_area (1),
|
||||
# -> locality,
|
||||
# -> sublocality (_level_1 or 2 first found)
|
||||
for addr in entry['address_components']:
|
||||
# print("Addr: {}".format(addr))
|
||||
# country code + country
|
||||
if 'country' in addr['types'] and not geolocation['CountryCode']:
|
||||
geolocation['CountryCode'] = addr['short_name']
|
||||
geolocation['Country'] = addr['long_name']
|
||||
# print("Code: {}, Country: {}".format(country_code, country))
|
||||
# state
|
||||
if 'administrative_area_level_1' in addr['types'] and not geolocation['State']:
|
||||
geolocation['State'] = addr['long_name']
|
||||
# print("State (1): {}".format(state))
|
||||
if 'administrative_area_level_2' in addr['types'] and not geolocation['State']:
|
||||
geolocation['State'] = addr['long_name']
|
||||
# print("State (2): {}".format(state))
|
||||
# city
|
||||
if 'locality' in addr['types'] and not geolocation['City']:
|
||||
geolocation['City'] = addr['long_name']
|
||||
# print("City: {}".format(city))
|
||||
# location
|
||||
if 'sublocality_level_1' in addr['types'] and not geolocation['Location']:
|
||||
geolocation['Location'] = addr['long_name']
|
||||
# print("Location (1): {}".format(location))
|
||||
if 'sublocality_level_2' in addr['types'] and not geolocation['Location']:
|
||||
geolocation['Location'] = addr['long_name']
|
||||
# print("Location (1): {}".format(location))
|
||||
# if all failes try route
|
||||
if 'route' in addr['types'] and not geolocation['Location']:
|
||||
geolocation['Location'] = addr['long_name']
|
||||
# print("Location (R): {}".format(location))
|
||||
else:
|
||||
print("Error in request: {}".format(response.json()['error_message']))
|
||||
# return
|
||||
return geolocation
|
||||
|
||||
# METHOD: convertLatLongToDMS
|
||||
# PARAMS: latLong in (-)N.N format, lat or long flag (else we can't set N/S)
|
||||
# RETURN: Deg,Min.Sec(NESW) format
|
||||
# DESC : convert the LR format of N.N to the Exif GPS format
|
||||
def convertLatLongToDMS(lat_long, is_latitude = False, is_longitude = False):
|
||||
# minus part before . and then multiply rest by 60
|
||||
degree = int(abs(lat_long))
|
||||
minutes = round((float(abs(lat_long)) - int(abs(lat_long))) * 60, 10)
|
||||
if is_latitude == True:
|
||||
direction = 'S' if int(lat_long) < 0 else 'N'
|
||||
elif is_longitude == True:
|
||||
direction = 'W' if int(lat_long) < 0 else 'E'
|
||||
else:
|
||||
direction = '(INVALID)'
|
||||
return "{},{}{}".format(degree, minutes, direction)
|
||||
|
||||
# wrapper functions for Long/Lat calls
|
||||
def convertLatToDMS(lat_long):
|
||||
return convertLatLongToDMS(lat_long, is_latitude = True)
|
||||
def convertLongToDMS(lat_long):
|
||||
return convertLatLongToDMS(lat_long, is_longitude = True)
|
||||
|
||||
# METHOD: longLatReg
|
||||
# PARAMS: latitude, longitude
|
||||
# RETURN: dict with converted lat/long
|
||||
# DESC : converts the XMP/EXIF formatted GPS Long/Lat coordinates
|
||||
# from the <Degree>,<Minute.Second><NSEW> to the normal float
|
||||
# number used in google/lr internal
|
||||
def longLatReg(longitude, latitude):
|
||||
# regex
|
||||
latlong_re = re.compile('^(\d+),(\d+\.\d+)([NESW]{1})$')
|
||||
# dict for loop
|
||||
lat_long = {
|
||||
'longitude': longitude,
|
||||
'latitude': latitude
|
||||
}
|
||||
for element in lat_long:
|
||||
# match if it is exif GPS format
|
||||
m = latlong_re.match(lat_long[element])
|
||||
if m is not None:
|
||||
# convert from Degree, Min.Sec into float format
|
||||
lat_long[element] = float(m.group(1)) + (float(m.group(2)) / 60)
|
||||
# if S or W => inverse to negative
|
||||
if m.group(3) == 'S' or m.group(3) == 'W':
|
||||
lat_long[element] *= -1
|
||||
return lat_long
|
||||
|
||||
# METHOD: checkOverwrite
|
||||
# PARAMS: data: value field, key: XMP key, field_controls: array from args
|
||||
# RETURN: true/false
|
||||
# DESC : checks with field control flags if given data for key should be written
|
||||
# 1) data is not set
|
||||
# 2) data is set or not and field_control: overwrite only set
|
||||
# 3) data for key is not set, but only for key matches field_control
|
||||
# 4) data for key is set or not, but only for key matches field_control and overwrite is set
|
||||
def checkOverwrite(data, key, field_controls):
|
||||
status = False
|
||||
# init field controls for empty
|
||||
if not field_controls:
|
||||
field_controls = []
|
||||
if not data and (len(field_controls) == 0 or ('overwrite' in field_controls and len(field_controls) == 1)):
|
||||
status = True
|
||||
elif not data and key.lower() in field_controls:
|
||||
status = True
|
||||
elif data and 'overwrite' in field_controls and len(field_controls) == 1:
|
||||
status = True
|
||||
elif data and key.lower() in field_controls and 'overwrite' in field_controls:
|
||||
status = True
|
||||
if args.debug:
|
||||
print("Data set: {}, Key: {}, Field Controls len: {}, Overwrite: {}, Key in Field Controls: {}, OVERWRITE: {}".format(
|
||||
'YES' if data else 'NO',
|
||||
key.lower(),
|
||||
len(field_controls),
|
||||
'OVERWRITE' if 'overwrite' in field_controls else 'NOT OVERWRITE',
|
||||
'KEY OK' if key.lower() in field_controls else 'KEY NOT MATCHING',
|
||||
status
|
||||
))
|
||||
return status
|
||||
|
||||
##############################################################
|
||||
### ARGUMENT PARSNING
|
||||
##############################################################
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description = 'Reverse Geoencoding based on set Latitude/Longitude data in XMP files',
|
||||
# formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog = 'Sample: (todo)'
|
||||
)
|
||||
|
||||
# xmp folder (or folders), or file (or files)
|
||||
# note that the target directory or file needs to be writeable
|
||||
parser.add_argument('-x', '--xmp',
|
||||
required = True,
|
||||
nargs = '*',
|
||||
action = writable_dir_folder,
|
||||
dest = 'xmp_sources',
|
||||
metavar = 'XMP SOURCE FOLDER',
|
||||
help = 'The source folder or folders with the XMP files that need reverse geo encoding to be set. Single XMP files can be given here'
|
||||
)
|
||||
|
||||
# LR database (base folder)
|
||||
# get .lrcat file in this folder
|
||||
parser.add_argument('-l', '--lightroom',
|
||||
# required = True,
|
||||
action = readable_dir,
|
||||
dest = 'lightroom_folder',
|
||||
metavar = 'LIGHTROOM FOLDER',
|
||||
help = 'Lightroom catalogue base folder'
|
||||
)
|
||||
|
||||
# set behaviour override
|
||||
# FLAG: default: only set not filled
|
||||
# other: overwrite all or overwrite if one is missing, overwrite specifc field (as defined below)
|
||||
# fields: Location, City, State, Country, CountryCode
|
||||
parser.add_argument('-f', '--field',
|
||||
action = 'append',
|
||||
type = str.lower, # make it lowercase for check
|
||||
choices = ['overwrite', 'location', 'city', 'state', 'country', 'countrycode'],
|
||||
dest = 'field_controls',
|
||||
metavar = 'FIELD CONTROLS',
|
||||
help = 'On default only set fields that are not set yet. Options are: Overwrite (write all new), Location, City, State, Country, CountryCode. Multiple can be given. If with overwrite the field will be overwritten if already set, else it will be always skipped'
|
||||
)
|
||||
|
||||
# Google Maps API key to overcome restrictions
|
||||
parser.add_argument('-g', '--google',
|
||||
dest = 'google_api_key',
|
||||
metavar = 'GOOGLE_API_KEY',
|
||||
help = 'Set a Google API Maps key to overcome the default lookup limitations'
|
||||
)
|
||||
|
||||
# verbose args for more detailed output
|
||||
parser.add_argument('-v', '--verbose',
|
||||
action = 'count',
|
||||
dest = 'verbose',
|
||||
help = 'Set verbose output level'
|
||||
)
|
||||
|
||||
# debug flag
|
||||
parser.add_argument('--debug', action = 'store_true', dest = 'debug', help = 'Set detailed debug output')
|
||||
# test flag
|
||||
parser.add_argument('--test', action = 'store_true', dest = 'test', help = 'Do not write data back to file')
|
||||
|
||||
# read in the argumens
|
||||
args = parser.parse_args()
|
||||
|
||||
##############################################################
|
||||
### MAIN CODE
|
||||
##############################################################
|
||||
|
||||
if args.debug:
|
||||
print("### ARGUMENT VARS: X: {}, L: {}, F: {}, G: {}, V: {}, D: {}, T: {}".format(args.xmp_sources, args.lightroom_folder, args.field_controls, args.google_api_key, args.verbose, args.debug, args.test))
|
||||
|
||||
# The XMP fields const lookup values
|
||||
# XML/XMP
|
||||
# READ:
|
||||
# exif:GPSLatitude
|
||||
# exif:GPSLongitude
|
||||
# READ for if filled
|
||||
# Iptc4xmpCore:Location
|
||||
# photoshop:City
|
||||
# photoshop:State
|
||||
# photoshop:Country
|
||||
# Iptc4xmpCore:CountryCode
|
||||
xmp_fields = {
|
||||
'GPSLatitude': consts.XMP_NS_EXIF, # EXIF GPSLat/Long are stored in Degree,Min.Sec[NESW] format
|
||||
'GPSLongitude': consts.XMP_NS_EXIF,
|
||||
'Location': consts.XMP_NS_IPTCCore,
|
||||
'City': consts.XMP_NS_Photoshop,
|
||||
'State': consts.XMP_NS_Photoshop,
|
||||
'Country': consts.XMP_NS_Photoshop,
|
||||
'CountryCode': consts.XMP_NS_IPTCCore
|
||||
}
|
||||
# non lat/long fields (for loc loops)
|
||||
data_set_loc = ('Location', 'City', 'State', 'Country', 'CountryCode')
|
||||
# one xmp data set
|
||||
data_set = {
|
||||
'GPSLatitude': '',
|
||||
'GPSLongitude': '',
|
||||
'Location': '',
|
||||
'City': '',
|
||||
'State': '',
|
||||
'Country': '',
|
||||
'CountryCode': ''
|
||||
}
|
||||
# original set for compare (is constant unchanged)
|
||||
data_set_original = {}
|
||||
# cache set to avoid double lookups for identical Lat/Ling
|
||||
data_cache = {}
|
||||
# work files, all files + folders we need to work on
|
||||
work_files = []
|
||||
# error flag
|
||||
error = False
|
||||
# use lightroom
|
||||
use_lightroom = False
|
||||
# cursors & query
|
||||
query = ''
|
||||
cur = ''
|
||||
# count variables
|
||||
count = {
|
||||
'all': 0,
|
||||
'google': 0,
|
||||
'cache': 0,
|
||||
'lightroom': 0,
|
||||
'changed': 0,
|
||||
'failed': 0,
|
||||
'skipped': 0,
|
||||
'not_found': 0
|
||||
}
|
||||
|
||||
# do lightroom stuff only if we have the lightroom folder
|
||||
if args.lightroom_folder:
|
||||
# query string for lightroom DB check
|
||||
query = 'SELECT Adobe_images.id_local, AgLibraryFile.baseName, AgLibraryRootFolder.absolutePath, AgLibraryRootFolder.name as realtivePath, AgLibraryFolder.pathFromRoot, AgLibraryFile.originalFilename, AgHarvestedExifMetadata.gpsLatitude, AgHarvestedExifMetadata.gpsLongitude, AgHarvestedIptcMetadata.locationDataOrigination, AgInternedIptcLocation.value as Location, AgInternedIptcCity.value as City, AgInternedIptcState.value as State, AgInternedIptcCountry.value as Country, AgInternedIptcIsoCountryCode.value as CountryCode '
|
||||
query += 'FROM AgLibraryFile, AgHarvestedExifMetadata, AgLibraryFolder, AgLibraryRootFolder, Adobe_images '
|
||||
query += 'LEFT JOIN AgHarvestedIptcMetadata ON Adobe_images.id_local = AgHarvestedIptcMetadata.image '
|
||||
query += 'LEFT JOIN AgInternedIptcLocation ON AgHarvestedIptcMetadata.locationRef = AgInternedIptcLocation.id_local '
|
||||
query += 'LEFT JOIN AgInternedIptcCity ON AgHarvestedIptcMetadata.cityRef = AgInternedIptcCity.id_local '
|
||||
query += 'LEFT JOIN AgInternedIptcState ON AgHarvestedIptcMetadata.stateRef = AgInternedIptcState.id_local '
|
||||
query += 'LEFT JOIN AgInternedIptcCountry ON AgHarvestedIptcMetadata.countryRef = AgInternedIptcCountry.id_local '
|
||||
query += 'LEFT JOIN AgInternedIptcIsoCountryCode ON AgHarvestedIptcMetadata.isoCountryCodeRef = AgInternedIptcIsoCountryCode.id_local '
|
||||
query += 'WHERE Adobe_images.rootFile = AgLibraryFile.id_local AND Adobe_images.id_local = AgHarvestedExifMetadata.image AND AgLibraryFile.folder = AgLibraryFolder.id_local AND AgLibraryFolder.rootFolder = AgLibraryRootFolder.id_local '
|
||||
query += 'AND AgLibraryFile.baseName = ?'
|
||||
|
||||
# connect to LR database for reading
|
||||
# open the folder and look for the first lrcat file in there
|
||||
for file in os.listdir(args.lightroom_folder):
|
||||
if file.endswith('.lrcat'):
|
||||
lightroom_database = os.path.join(args.lightroom_folder, file)
|
||||
lrdb = sqlite3.connect(lightroom_database)
|
||||
if not lightroom_database or not lrdb:
|
||||
print("(!) We could not find a lrcat file in the given lightroom folder or DB connection failed: {}".format(args.lightroom_folder))
|
||||
# flag for end
|
||||
error = True
|
||||
else:
|
||||
# set row so we can access each element by the name
|
||||
lrdb.row_factory = sqlite3.Row
|
||||
# set cursor
|
||||
cur = lrdb.cursor()
|
||||
# flag that we have Lightroom DB
|
||||
use_lightroom = True
|
||||
|
||||
# on error exit here
|
||||
if error:
|
||||
sys.exit(1)
|
||||
|
||||
# init the XML meta for handling
|
||||
xmp = XMPMeta()
|
||||
|
||||
# loop through the xmp_sources (folder or files) and read in the XMP data for LAT/LONG, other data
|
||||
for xmp_file_source in args.xmp_sources:
|
||||
# if folder, open and loop
|
||||
# NOTE: we do check for folders in there, if there are we recourse traverse them
|
||||
if os.path.isdir(xmp_file_source):
|
||||
# open folder and look for any .xmp files and push them into holding array
|
||||
# if there are folders, dive into them
|
||||
# or glob glob all .xmp files + directory
|
||||
for root, dirs, files in os.walk(xmp_file_source):
|
||||
for file in files:
|
||||
if file.endswith(".xmp"):
|
||||
if "{}/{}".format(root, file) not in work_files:
|
||||
work_files.append("{}/{}".format(root, file))
|
||||
count['all'] += 1
|
||||
else:
|
||||
if xmp_file_source not in work_files:
|
||||
work_files.append(xmp_file_source)
|
||||
count['all'] += 1
|
||||
|
||||
if args.debug:
|
||||
print("### Work Files {}".format(work_files))
|
||||
# now we just loop through each file and work on them
|
||||
for xmp_file in work_files:
|
||||
print("---> {}".format(xmp_file))
|
||||
#### ACTION FLAGs
|
||||
write_file = False
|
||||
lightroom_data_ok = True
|
||||
#### LIGHTROOM DB READING
|
||||
# read in data from DB if we uave lightroom folder
|
||||
if use_lightroom:
|
||||
# get the base file name, we need this for lightroom
|
||||
xmp_file_basename = os.path.splitext(os.path.split(xmp_file)[1])[0]
|
||||
# try to get this file name from the DB
|
||||
# NOTE: We should search here with folder too in case for double same name entries
|
||||
cur.execute(query, [xmp_file_basename])
|
||||
# get the row data
|
||||
lrdb_row = cur.fetchone()
|
||||
# Notify if we couldn't find one
|
||||
if not lrdb_row:
|
||||
print("(!) Could not get data from Lightroom DB")
|
||||
lightroom_data_ok = False
|
||||
count['not_found'] += 1
|
||||
if args.debug and lrdb_row:
|
||||
print("### LightroomDB: {} / {}".format(tuple(lrdb_row), lrdb_row.keys()))
|
||||
|
||||
#### XMP FILE READING
|
||||
# open file & read all into buffer
|
||||
with open(xmp_file, 'r') as fptr:
|
||||
strbuffer = fptr.read()
|
||||
# read fields from the XMP file and store in hash
|
||||
xmp.parse_from_str(strbuffer)
|
||||
for xmp_field in xmp_fields:
|
||||
data_set[xmp_field] = xmp.get_property(xmp_fields[xmp_field], xmp_field)
|
||||
if args.debug:
|
||||
print("### => XMP: {}:{} => {}".format(xmp_fields[xmp_field], xmp_field, data_set[xmp_field]))
|
||||
# create a duplicate copy for later checking if something changed
|
||||
data_set_original = data_set.copy()
|
||||
|
||||
# check if LR exists and use this to compare to XMP data
|
||||
# is LR GPS and no XMP GPS => use LR and set XMP
|
||||
# same for location names
|
||||
# if missing in XMP but in LR -> set in XMP
|
||||
# if missing in both do lookup in Google
|
||||
if use_lightroom and lightroom_data_ok:
|
||||
# check lat/long separate
|
||||
if lrdb_row['gpsLatitude'] and not data_set['GPSLatitude']:
|
||||
# we need to convert to the Degree,Min.sec[NSEW] format
|
||||
data_set['GPSLatitude'] = convertLatToDMS(lrdb_row['gpsLatitude'])
|
||||
if lrdb_row['gpsLongitude'] and not data_set['GPSLongitude']:
|
||||
data_set['GPSLongitude'] = convertLongToDMS(lrdb_row['gpsLongitude'])
|
||||
# now check Location, City, etc
|
||||
for loc in data_set_loc:
|
||||
# overwrite original set (read from XMP) with LR data if original data is missing
|
||||
if lrdb_row[loc] and not data_set[loc]:
|
||||
data_set[loc] = lrdb_row[loc]
|
||||
if args.debug:
|
||||
print("### -> LR: {} => {}".format(loc, lrdb_row[loc]))
|
||||
# base set done, now check if there is anything unset in the data_set, if yes do a lookup in google
|
||||
# run this through the overwrite checker to get unset if we have a forced overwrite
|
||||
has_unset = False
|
||||
for loc in data_set_loc:
|
||||
if checkOverwrite(data_set[loc], loc, args.field_controls):
|
||||
has_unset = True
|
||||
if has_unset:
|
||||
# check if lat/long is in cache
|
||||
cache_key = '{}.#.{}'.format(data_set['GPSLatitude'], data_set['GPSLongitude'])
|
||||
if args.debug:
|
||||
print("### *** CACHE: {}: {}".format(cache_key, 'NO' if cache_key not in data_cache else 'YES'))
|
||||
if cache_key not in data_cache:
|
||||
# get location from google
|
||||
google_location = reverseGeolocate(latitude = data_set['GPSLatitude'], longitude = data_set['GPSLongitude'])
|
||||
# cache data with Lat/Long
|
||||
data_cache[cache_key] = google_location
|
||||
else:
|
||||
# load location from cache
|
||||
google_location = data_cache[cache_key]
|
||||
count['cache'] += 1
|
||||
# overwrite sets (note options check here)
|
||||
if args.debug:
|
||||
print("### Google Location: {}".format(google_location))
|
||||
if google_location['Country']:
|
||||
for loc in data_set_loc:
|
||||
# only write to XMP if overwrite check passes
|
||||
if checkOverwrite(data_set[loc], loc, args.field_controls):
|
||||
xmp.set_property(xmp_fields[loc], loc, google_location[loc])
|
||||
write_file = True
|
||||
if write_file:
|
||||
count['google'] += 1
|
||||
else:
|
||||
print("(!) Could not geo loaction for: {}".format(xmp_file))
|
||||
count['failed'] += 1
|
||||
else:
|
||||
if args.debug:
|
||||
print("Lightroom data use: {}, Lightroom data ok: {}".format(use_lightroom, lightroom_data_ok))
|
||||
# check if the data_set differs from the original (LR db load)
|
||||
# if yes write, else skip
|
||||
if use_lightroom and lightroom_data_ok:
|
||||
for key in data_set:
|
||||
# if not the same (to original data) and passes overwrite check
|
||||
if data_set[key] != data_set_original[key] and checkOverwrite(data_set[key], key, args.field_controls):
|
||||
xmp.set_property(xmp_fields[key], key, data_set[key])
|
||||
write_file = True;
|
||||
if write_file:
|
||||
count['lightroom'] += 1
|
||||
# if we have the write flag set, write data
|
||||
if write_file:
|
||||
if not args.test:
|
||||
# use copyfile to create a backup copy
|
||||
copyfile(xmp_file, "{}.BK.{}".format(os.path.splitext(xmp_file)[0], os.path.splitext(xmp_file)[1]))
|
||||
# write back to riginal file
|
||||
with open(xmp_file, 'w') as fptr:
|
||||
fptr.write(xmp.serialize_to_str(omit_packet_wrapper=True))
|
||||
else:
|
||||
print("[TEST] Would write {} to file {}".format(data_set, xmp_file))
|
||||
count['changed'] += 1
|
||||
else:
|
||||
print(". Data exists: SKIP")
|
||||
count['skipped'] += 1
|
||||
|
||||
# close DB connection
|
||||
lrdb.close()
|
||||
|
||||
# end stats
|
||||
print("{}".format('=' * 30))
|
||||
print("Found XMP Files : {:,}".format(count['all']))
|
||||
print("Updated : {:,}".format(count['changed']))
|
||||
print("Skipped : {:,}".format(count['skipped']))
|
||||
print("New GeoLocation Google: {:,}".format(count['google']))
|
||||
print("GeoLocation from Cache: {:,}".format(count['cache']))
|
||||
print("Failed for Reverse Geo: {:,}".format(count['failed']))
|
||||
if use_lightroom:
|
||||
print("Geo from Lightroom : {:,}".format(count['lightroom']))
|
||||
print("No Lightroom data : {:,}".format(count['not_found']))
|
||||
|
||||
|
||||
# __END__
|
||||
Reference in New Issue
Block a user