hot fixes for moving package into main()

This commit is contained in:
2026-02-08 12:47:26 +09:00
parent d771da77e1
commit 34f5ce14bc
+78 -47
View File
@@ -20,6 +20,7 @@ import re
import sqlite3 import sqlite3
from shutil import copyfile, get_terminal_size from shutil import copyfile, get_terminal_size
from math import ceil, radians, sin, cos, atan2, sqrt from math import ceil, radians, sin, cos, atan2, sqrt
from typing import Any
import requests import requests
# Note XMPFiles does not work with sidecar files, need to read via XMPMeta # Note XMPFiles does not work with sidecar files, need to read via XMPMeta
from libxmp import XMPMeta, consts from libxmp import XMPMeta, consts
@@ -30,7 +31,9 @@ from libxmp import XMPMeta, consts
# this is for looking up if string is non latin letters # this is for looking up if string is non latin letters
# this is used by isLatin and onlyLatinChars # this is used by isLatin and onlyLatinChars
cache_latin_letters = {} cache_latin_letters: dict[str, Any] = {}
page_no = 1
# ARGPARSE HELPERS # ARGPARSE HELPERS
@@ -54,7 +57,9 @@ class writable_dir_folder(argparse.Action):
# and write that list back to the self.dest in the namespace # and write that list back to the self.dest in the namespace
setattr(namespace, self.dest, out) setattr(namespace, self.dest, out)
else: else:
raise argparse.ArgumentTypeError("writable_dir_folder: {0} is not a writable dir".format(prospective_dir)) raise argparse.ArgumentTypeError(
"writable_dir_folder: {0} is not a writable dir".format(prospective_dir)
)
# call: readable_dir # call: readable_dir
@@ -90,7 +95,7 @@ class distance_values(argparse.Action):
# PARAMS: latitude, longitude, map search target (google or openstreetmap) # PARAMS: latitude, longitude, map search target (google or openstreetmap)
# RETURN: dict with all data (see below) # RETURN: dict with all data (see below)
# DESC : wrapper to call to either the google or openstreetmap # DESC : wrapper to call to either the google or openstreetmap
def reverseGeolocate(longitude, latitude, map_type): def reverseGeolocate(args, longitude, latitude, map_type):
# clean up long/lat # clean up long/lat
# they are stored with N/S/E/W if they come from an XMP # they are stored with N/S/E/W if they come from an XMP
# format: Deg,Min.Sec[NSEW] # format: Deg,Min.Sec[NSEW]
@@ -99,9 +104,13 @@ def reverseGeolocate(longitude, latitude, map_type):
lat_long = longLatReg(longitude=longitude, latitude=latitude) lat_long = longLatReg(longitude=longitude, latitude=latitude)
# which service to use # which service to use
if map_type == 'google': if map_type == 'google':
return reverseGeolocateGoogle(lat_long['longitude'], lat_long['latitude']) return reverseGeolocateGoogle(
args, lat_long['longitude'], lat_long['latitude']
)
elif map_type == 'openstreetmap': elif map_type == 'openstreetmap':
return reverseGeolocateOpenStreetMap(lat_long['longitude'], lat_long['latitude']) return reverseGeolocateOpenStreetMap(
args, lat_long['longitude'], lat_long['latitude']
)
else: else:
return { return {
'Country': '', 'Country': '',
@@ -141,7 +150,7 @@ def reverseGeolocateInit(longitude, latitude):
# dict with locaiton, city, state, country, country code # dict with locaiton, city, state, country, country code
# if not fillable, entry is empty # if not fillable, entry is empty
# SAMPLE: https://nominatim.openstreetmap.org/reverse.php?format=jsonv2&lat=<latitude>&lon=<longitude>&zoom=21&accept-languge=en-US,en& # SAMPLE: https://nominatim.openstreetmap.org/reverse.php?format=jsonv2&lat=<latitude>&lon=<longitude>&zoom=21&accept-languge=en-US,en&
def reverseGeolocateOpenStreetMap(longitude, latitude): def reverseGeolocateOpenStreetMap(args, longitude, latitude):
# init # init
geolocation = reverseGeolocateInit(longitude, latitude) geolocation = reverseGeolocateInit(longitude, latitude)
if geolocation['status'] == 'ERROR': if geolocation['status'] == 'ERROR':
@@ -201,7 +210,7 @@ def reverseGeolocateOpenStreetMap(longitude, latitude):
# dict with location, city, state, country, country code # dict with location, city, state, country, country code
# if not fillable, entry is empty # if not fillable, entry is empty
# SAMPLE: http://maps.googleapis.com/maps/api/geocode/json?latlng=<latitude>,<longitude>&language=<lang>&sensor=false&key=<api key> # SAMPLE: http://maps.googleapis.com/maps/api/geocode/json?latlng=<latitude>,<longitude>&language=<lang>&sensor=false&key=<api key>
def reverseGeolocateGoogle(longitude, latitude): # noqa: C901 def reverseGeolocateGoogle(args, longitude, latitude): # noqa: C901
# init # init
geolocation = reverseGeolocateInit(longitude, latitude) geolocation = reverseGeolocateInit(longitude, latitude)
temp_geolocation = geolocation.copy() temp_geolocation = geolocation.copy()
@@ -226,7 +235,7 @@ def reverseGeolocateGoogle(longitude, latitude): # noqa: C901
payload['key'] = args.google_api_key payload['key'] = args.google_api_key
# build the full url and send it to google # build the full url and send it to google
url = "{protocol}{base}".format(protocol=protocol, base=base) url = "{protocol}{base}".format(protocol=protocol, base=base)
response = requests.get(url, params=payload) response = requests.get(url, params=payload, timeout=10)
# debug output # debug output
if args.debug: if args.debug:
print("Google search for Lat: {}, Long: {} with {}".format(longitude, latitude, response.url)) print("Google search for Lat: {}, Long: {} with {}".format(longitude, latitude, response.url))
@@ -385,7 +394,7 @@ def getDistance(from_longitude, from_latitude, to_longitude, to_latitude):
# 2) data is set or not and field_control: overwrite only 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 # 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 # 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): def checkOverwrite(args, data, key, field_controls):
status = False status = False
# init field controls for empty # init field controls for empty
if not field_controls: if not field_controls:
@@ -528,7 +537,7 @@ def fileSortNumber(file):
# PARAMS: none # PARAMS: none
# RETURN: format_length dictionary # RETURN: format_length dictionary
# DESC : adjusts the size for the format length for the list output # DESC : adjusts the size for the format length for the list output
def outputListWidthAdjust(): def outputListWidthAdjust(args):
# various string lengths # various string lengths
format_length = { format_length = {
'filename': 35, 'filename': 35,
@@ -545,7 +554,10 @@ def outputListWidthAdjust():
reduce_percent = 40 reduce_percent = 40
# all formats are reduced to a mininum, we cut % off # all formats are reduced to a mininum, we cut % off
for format_key in ['filename', 'latitude', 'longitude', 'country', 'state', 'city', 'location', 'path']: for format_key in ['filename', 'latitude', 'longitude', 'country', 'state', 'city', 'location', 'path']:
format_length[format_key] = ceil(format_length[format_key] - ((format_length[format_key] / 100) * reduce_percent)) format_length[format_key] = ceil(
format_length[format_key] -
((format_length[format_key] / 100) * reduce_percent)
)
else: else:
# minimum resize size for a column # minimum resize size for a column
resize_width_min = 4 resize_width_min = 4
@@ -583,10 +595,17 @@ def outputListWidthAdjust():
resize_width *= -1 resize_width *= -1
resize_width = ceil(format_length[format_key] + resize_width) resize_width = ceil(format_length[format_key] + resize_width)
# in case too small, keep old one # in case too small, keep old one
format_length[format_key] = resize_width if resize_width > resize_width_min else format_length[format_key] format_length[format_key] = (
resize_width
if resize_width > resize_width_min
else format_length[format_key]
)
# calc new width for check if we can abort # calc new width for check if we can abort
current_columns = sum(format_length.values()) + ((len(format_length) - 1) * 3) + 2 current_columns = sum(format_length.values()) + ((len(format_length) - 1) * 3) + 2
if (resize == 1 and current_columns >= get_terminal_size().columns) or (resize == -1 and current_columns < get_terminal_size().columns): if (
(resize == 1 and current_columns >= get_terminal_size().columns) or
(resize == -1 and current_columns < get_terminal_size().columns)
):
# check that we are not OVER but one under # check that we are not OVER but one under
width_up = get_terminal_size().columns - current_columns - 1 width_up = get_terminal_size().columns - current_columns - 1
if (resize == 1 and width_up < 0) or (resize == -1 and width_up != 0): if (resize == 1 and width_up < 0) or (resize == -1 and width_up != 0):
@@ -605,7 +624,7 @@ def outputListWidthAdjust():
# PARAMS: file name # PARAMS: file name
# RETURN: next counter to be used for backup # RETURN: next counter to be used for backup
# DESC : # DESC :
def getBackupFileCounter(xmp_file): def getBackupFileCounter(args, xmp_file):
# set to 1 for if we have no backups yet # set to 1 for if we have no backups yet
bk_file_counter = 1 bk_file_counter = 1
# get PATH from file and look for .BK. data in this folder matching, output is sorted per BK counter key # get PATH from file and look for .BK. data in this folder matching, output is sorted per BK counter key
@@ -635,9 +654,9 @@ def getBackupFileCounter(xmp_file):
# ARGUMENT PARSNING # ARGUMENT PARSNING
############################################################## ##############################################################
def main(): def main():
"""main"""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Reverse Geoencoding based on set Latitude/Longitude data in XMP files', description='Reverse Geoencoding based on set Latitude/Longitude data in XMP files',
# formatter_class=argparse.RawDescriptionHelpFormatter, # formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -700,10 +719,12 @@ def main():
choices=['overwrite', 'location', 'city', 'state', 'country', 'countrycode'], choices=['overwrite', 'location', 'city', 'state', 'country', 'countrycode'],
dest='field_controls', dest='field_controls',
metavar='<overwrite, location, city, state, country, countrycode>', metavar='<overwrite, location, city, state, country, countrycode>',
help='On default only set fields that are not set yet. Options are: '\ help=(
'Overwrite (write all new), Location, City, State, Country, CountryCode. '\ 'On default only set fields that are not set yet. Options are: '
'Multiple can be given for combination overwrite certain fields only or set only certain fields. '\ 'Overwrite (write all new), Location, City, State, Country, CountryCode. '
'Multiple can be given for combination overwrite certain fields only or set only certain fields. '
'If with overwrite the field will be overwritten if already set, else it will be always skipped.' 'If with overwrite the field will be overwritten if already set, else it will be always skipped.'
)
) )
parser.add_argument( parser.add_argument(
@@ -715,9 +736,11 @@ def main():
const='10m', # default is 10m const='10m', # default is 10m
dest='fuzzy_distance', dest='fuzzy_distance',
metavar='FUZZY DISTANCE', metavar='FUZZY DISTANCE',
help='Allow fuzzy distance cache lookup. Optional distance can be given, '\ help=(
'if not set default of 10m is used. '\ 'Allow fuzzy distance cache lookup. Optional distance can be given, '
'if not set default of 10m is used. '
'Allowed argument is in the format of 12m or 12km' 'Allowed argument is in the format of 12m or 12km'
)
) )
# Google Maps API key to overcome restrictions # Google Maps API key to overcome restrictions
@@ -842,25 +865,30 @@ def main():
args.unset_only = 0 args.unset_only = 0
if args.debug: if args.debug:
print("### ARGUMENT VARS: I: {incl}, X: {excl}, L: {lr}, F: {fc}, D: {fdist}, M: {osm}, G: {gp}, E: {em}, R: {read}, U: {us}, A: {adj}, C: {cmp}, N: {nbk}, W: {wrc}, V: {v}, D: {d}, T: {t}".format( print(
incl=args.xmp_sources, "### ARGUMENT VARS: I: {incl}, X: {excl}, L: {lr}, F: {fc}, "
excl=args.exclude_sources, "D: {fdist}, M: {osm}, G: {gp}, E: {em}, R: {read}, U: {us}, "
lr=args.lightroom_folder, "A: {adj}, C: {cmp}, N: {nbk}, W: {wrc}, V: {v}, D: {d}, T: {t}"
fc=args.field_controls, .format(
fdist=args.fuzzy_distance, incl=args.xmp_sources,
osm=args.use_openstreetmap, excl=args.exclude_sources,
gp=args.google_api_key, lr=args.lightroom_folder,
em=args.email, fc=args.field_controls,
read=args.read_only, fdist=args.fuzzy_distance,
us=args.unset_only, osm=args.use_openstreetmap,
adj=args.no_autoadjust, gp=args.google_api_key,
cmp=args.compact_view, em=args.email,
nbk=args.no_xmp_backup, read=args.read_only,
wrc=args.config_write, us=args.unset_only,
v=args.verbose, adj=args.no_autoadjust,
d=args.debug, cmp=args.compact_view,
t=args.test nbk=args.no_xmp_backup,
)) wrc=args.config_write,
v=args.verbose,
d=args.debug,
t=args.test
)
)
# error flag # error flag
error = False error = False
@@ -1033,6 +1061,9 @@ def main():
# init the XML meta for handling # init the XML meta for handling
xmp = XMPMeta() xmp = XMPMeta()
# if xmp_sources is None, set to empty list
if args.xmp_sources is None:
args.xmp_sources = []
# loop through the xmp_sources (folder or files) and read in the XMP data for LAT/LONG, other data # 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: for xmp_file_source in args.xmp_sources:
# if folder, open and loop # if folder, open and loop
@@ -1064,14 +1095,14 @@ def main():
# if we have read only we print list format style # if we have read only we print list format style
if args.read_only: if args.read_only:
# adjust the output width for the list view # adjust the output width for the list view
format_length = outputListWidthAdjust() format_length = outputListWidthAdjust(args)
# after how many lines do we reprint the header # after how many lines do we reprint the header
header_repeat = 50 header_repeat = 50
# how many pages will we have # how many pages will we have
page_all = ceil(len(work_files) / header_repeat) page_all = ceil(len(work_files) / header_repeat)
# current page number # current page number
page_no = 1 # page_no = 1
# the formatted line for the output # the formatted line for the output
# 4 {} => final replace: data (2 pre replaces) # 4 {} => final replace: data (2 pre replaces)
# 1 {} => length replace here # 1 {} => length replace here
@@ -1239,7 +1270,7 @@ def main():
failed = False failed = False
from_cache = False from_cache = False
for loc in data_set_loc: for loc in data_set_loc:
if checkOverwrite(data_set[loc], loc, args.field_controls): if checkOverwrite(args, data_set[loc], loc, args.field_controls):
has_unset = True has_unset = True
if has_unset: if has_unset:
# check if lat/long is in cache # check if lat/long is in cache
@@ -1273,7 +1304,7 @@ def main():
print("### ***= FUZZY CACHE: YES => Best match: {}".format(best_match_latlong)) print("### ***= FUZZY CACHE: YES => Best match: {}".format(best_match_latlong))
if not has_fuzzy_cache: if not has_fuzzy_cache:
# get location from maps (google or openstreetmap) # get location from maps (google or openstreetmap)
maps_location = reverseGeolocate(latitude=data_set['GPSLatitude'], longitude=data_set['GPSLongitude'], map_type=map_type) maps_location = reverseGeolocate(args, latitude=data_set['GPSLatitude'], longitude=data_set['GPSLongitude'], map_type=map_type)
# cache data with Lat/Long # cache data with Lat/Long
data_cache[cache_key] = maps_location data_cache[cache_key] = maps_location
from_cache = False from_cache = False
@@ -1297,7 +1328,7 @@ def main():
if maps_location['Country']: if maps_location['Country']:
for loc in data_set_loc: for loc in data_set_loc:
# only write to XMP if overwrite check passes # only write to XMP if overwrite check passes
if checkOverwrite(data_set_original[loc], loc, args.field_controls): if checkOverwrite(args, data_set_original[loc], loc, args.field_controls):
data_set[loc] = maps_location[loc] data_set[loc] = maps_location[loc]
xmp.set_property(xmp_fields[loc], loc, maps_location[loc]) xmp.set_property(xmp_fields[loc], loc, maps_location[loc])
write_file = True write_file = True
@@ -1314,7 +1345,7 @@ def main():
if use_lightroom and lightroom_data_ok: if use_lightroom and lightroom_data_ok:
for key in data_set: for key in data_set:
# if not the same (to original data) and passes overwrite check # if not the same (to original data) and passes overwrite check
if data_set[key] != data_set_original[key] and checkOverwrite(data_set_original[key], key, args.field_controls): if data_set[key] != data_set_original[key] and checkOverwrite(args, data_set_original[key], key, args.field_controls):
xmp.set_property(xmp_fields[key], key, data_set[key]) xmp.set_property(xmp_fields[key], key, data_set[key])
write_file = True write_file = True
if write_file: if write_file:
@@ -1325,7 +1356,7 @@ def main():
# use copyfile to create a backup copy # use copyfile to create a backup copy
if not args.no_xmp_backup: if not args.no_xmp_backup:
# check if there is another file with .BK. already there, if yes, get the max number and +1 it, if not set to 1 # check if there is another file with .BK. already there, if yes, get the max number and +1 it, if not set to 1
bk_file_counter = getBackupFileCounter(xmp_file) bk_file_counter = getBackupFileCounter(args, xmp_file)
# copy to new backup file # copy to new backup file
copyfile(xmp_file, "{}.BK.{}{}".format(os.path.splitext(xmp_file)[0], bk_file_counter, os.path.splitext(xmp_file)[1])) copyfile(xmp_file, "{}.BK.{}{}".format(os.path.splitext(xmp_file)[0], bk_file_counter, os.path.splitext(xmp_file)[1]))
# write back to riginal file # write back to riginal file