From 412f2721096c9fc1a26d833a6d46a3f40b5147d7 Mon Sep 17 00:00:00 2001 From: Julian-o Date: Mon, 22 Dec 2025 16:06:00 +1100 Subject: [PATCH 1/4] Reviewed and corrected IDE warnings. Mostly trivial changes that have no effect on running code, such as: * Removing redundant brackets. * Making implicit "return None" explicit * Making names follow PEP8 rules * Corrections to comments In gps.py, a large chunk of unused code was removed. The only change that should affect any behaviour was in display.py. smart_unit didn't cover the case where no prefix was required. (Presumably never noticed because no photo less than 1KB has been encountered.) --- lrselect.py | 10 ++-- lrsmart.py | 4 +- lrtools/criterlexer.py | 4 +- lrtools/display.py | 5 +- lrtools/gps.py | 95 -------------------------------------- lrtools/lrcat.py | 25 +++++----- lrtools/lrkeyword.py | 2 +- lrtools/lrselectgeneric.py | 17 +++---- lrtools/lrselectphoto.py | 3 ++ lrtools/lrsmartcoll.py | 18 ++++---- lrtools/lrtoolconfig.py | 5 +- lrtools/slpp.py | 11 +++-- 12 files changed, 56 insertions(+), 143 deletions(-) diff --git a/lrselect.py b/lrselect.py index 1220a2b..5260234 100644 --- a/lrselect.py +++ b/lrselect.py @@ -13,7 +13,7 @@ from argparse import RawTextHelpFormatter import sqlite3 -from lrtools import VERSION as lr_version +from lrtools import VERSION as LR_VERSION # config is loaded on import from lrtools.lrtoolconfig import lrt_config, LRConfigException @@ -150,7 +150,7 @@ def main(): if args.version: print( - f"lrselect version : {lr_version} , using python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" + f"lrselect version : {LR_VERSION} , using python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" ) return @@ -164,7 +164,7 @@ def main(): handler.setFormatter(logging.Formatter("%(asctime)s - %(message)s")) log.addHandler(handler) log.info("lrselect start") - log.info("lrtools version : %s", lr_version) + log.info("lrtools version : %s", LR_VERSION) log.info("arguments: %s", " ".join(sys.argv[1:])) # --max_lines option implies --results @@ -181,9 +181,9 @@ def main(): if "filesize" in columns: args.filesize = True if args.filesize: - if not "filesize" in columns: + if "filesize" not in columns: columns.append("filesize") - if not "name=full" in columns: + if "name=full" not in columns: columns.append("name=full") columns_lr = list(columns) if "filesize" in columns_lr: diff --git a/lrsmart.py b/lrsmart.py index 57007db..cc9a91e 100644 --- a/lrsmart.py +++ b/lrsmart.py @@ -12,7 +12,7 @@ import argparse from sqlite3 import OperationalError -from lrtools import VERSION as lr_version +from lrtools import VERSION as LR_VERSION # config is loaded on import from lrtools.lrtoolconfig import lrt_config, LRConfigException @@ -144,7 +144,7 @@ def main(): log.addHandler(handler) log = logging.getLogger() log.info("lrsmart start") - log.info("lrtools version : %s", lr_version) + log.info("lrtools version : %s", LR_VERSION) # # for lrsmart validation : diff --git a/lrtools/criterlexer.py b/lrtools/criterlexer.py index f7de2c0..d8e2b43 100644 --- a/lrtools/criterlexer.py +++ b/lrtools/criterlexer.py @@ -56,7 +56,7 @@ def _parse_keyval(self): # regex return quote type ("') and string value = _m.group(2) else: - _m = re.match(r" *([^,\|\)\()]+)", self.criters) + _m = re.match(r" *([^,\|\)\(]+)", self.criters) if _m: value = _m.group(1) else: @@ -150,7 +150,7 @@ def check_syntax(self): par_level = 0 for token, _ in self.tokens: allowed_tokens = self.RULES_FOLLOW[prev_token] - if not token in allowed_tokens: + if token not in allowed_tokens: self.last_error = f'"{token}" not allowed after "{prev_token}"' return False if token == "LPAR": diff --git a/lrtools/display.py b/lrtools/display.py index b77bcba..e1b0901 100644 --- a/lrtools/display.py +++ b/lrtools/display.py @@ -29,7 +29,8 @@ def smart_unit(value, unit): if value > 1000 * 1000: return f"{(value / (1000 * 1000.0)):.2f} M{unit}" if value > 1000: - return f"{(value / (1000.0)):.2f} K{unit}" + return f"{(value / 1000.0):.2f} K{unit}" + return f"{value:.2f} {unit}" # @@ -46,7 +47,7 @@ def display_keywords(value): def display_aperture(value): """format aperture in F value""" - return f"F{2**((float(value)/2)):.1f}" + return f"F{2**(float(value)/2):.1f}" def display_iso(value): diff --git a/lrtools/gps.py b/lrtools/gps.py index b64fa7a..7740a90 100644 --- a/lrtools/gps.py +++ b/lrtools/gps.py @@ -6,7 +6,6 @@ """ import math -import fractions import geopy from geopy.exc import GeocoderTimedOut @@ -16,100 +15,6 @@ from .lrselectgeneric import LRSelectException -class Fraction(fractions.Fraction): - """Only create Fractions from floats. - - >>> Fraction(0.3) - Fraction(3, 10) - >>> Fraction(1.1) - Fraction(11, 10) - """ - - def __new__(cls, value): - """Should be compatible with Python 2.6, though untested.""" - return fractions.Fraction.from_float(value).limit_denominator(99999) - - -def dms_to_decimal(degrees, minutes, seconds, sign=" "): - """Convert degrees, minutes, seconds into decimal degrees. - - >>> dms_to_decimal(10, 10, 10) - 10.169444444444444 - >>> dms_to_decimal(8, 9, 10, 'S') - -8.152777777777779 - """ - return (-1 if sign[0] in "SWsw" else 1) * ( - float(degrees) + float(minutes) / 60 + float(seconds) / 3600 - ) - - -def decimal_to_dms(decimal): - """Convert decimal degrees into degrees, minutes, seconds. - - >>> decimal_to_dms(50.445891) - [Fraction(50, 1), Fraction(26, 1), Fraction(113019, 2500)] - >>> decimal_to_dms(-125.976893) - [Fraction(125, 1), Fraction(58, 1), Fraction(92037, 2500)] - """ - remainder, degrees = math.modf(abs(decimal)) - remainder, minutes = math.modf(remainder * 60) - return [Fraction(n) for n in (degrees, minutes, remainder * 60)] - - -def lambert932WGPS(lambertE, lambertN): - """ - Convert a "Coordonnées géographique en projection légale" to GPS - - Code OK, not used because geo.api.gouv.fr returns GPS infos in fields geometry - """ - - class constantes: - """GPS contants""" - - GRS80E = 0.081819191042816 - LONG_0 = 3 - XS = 700000 - YS = 12655612.0499 - n = 0.7256077650532670 - C = 11754255.4261 - - delX = lambertE - constantes.XS - delY = lambertN - constantes.YS - - gamma = math.atan(-delX / delY) - R = math.sqrt(delX * delX + delY * delY) - latiso = math.log(constantes.C / R) / constantes.n - sinPhiit0 = math.tanh( - latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * math.sin(1)) - ) - sinPhiit1 = math.tanh( - latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * sinPhiit0) - ) - sinPhiit2 = math.tanh( - latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * sinPhiit1) - ) - sinPhiit3 = math.tanh( - latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * sinPhiit2) - ) - sinPhiit4 = math.tanh( - latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * sinPhiit3) - ) - sinPhiit5 = math.tanh( - latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * sinPhiit4) - ) - sinPhiit6 = math.tanh( - latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * sinPhiit5) - ) - - longRad = math.asin(sinPhiit6) - latRad = gamma / constantes.n + constantes.LONG_0 / 180 * math.pi - - longitude = latRad / math.pi * 180 - latitude = longRad / math.pi * 180 - - return longitude, latitude - - def square_around_location(lat, lon, width): """ Return square region around GPS point diff --git a/lrtools/lrcat.py b/lrtools/lrcat.py index 78513f2..cc12dea 100644 --- a/lrtools/lrcat.py +++ b/lrtools/lrcat.py @@ -275,7 +275,7 @@ def hierarchical_collections(self): hierachical_colls = [] for ( id_local, - creationId, + creation_id, genealogy, name, parent, @@ -286,21 +286,21 @@ def hierarchical_collections(self): 'creationId="com.adobe.ag.library.collection") AND ' "systemOnly=0" ).fetchall(): - id2coll[genealogy] = (id_local, creationId, name, parent) - for genealogy, (id_local, creationId, name, parent) in id2coll.items(): + id2coll[genealogy] = (id_local, creation_id, name, parent) + for genealogy, (id_local, creation_id, name, parent) in id2coll.items(): hname = [] parts = genealogy.split("/")[1:] genkey = "" for part in parts: genkey += f"/{part}" - id_local, creationId, name, parent = id2coll[genkey] + id_local, creation_id, name, parent = id2coll[genkey] hname.append(name) - hierachical_colls.append((hname, id_local, creationId)) + hierachical_colls.append((hname, id_local, creation_id)) return sorted(hierachical_colls) def select_collections(self, what, collname=""): """ - Select collections name for standard and dynamic type) + Select collections name for standard and dynamic type what = [ALL_COLL, STND_COLL, SMART_COLL] : collections type to retrieve collname : partial (including a %) or complete name of collection, or empty for all collections """ @@ -330,7 +330,7 @@ def hierarchical_published_collections(self): hierachical_colls = [] for ( id_local, - creationId, + creation_id, genealogy, name, parent, @@ -342,16 +342,16 @@ def hierarchical_published_collections(self): 'creationId="com.adobe.ag.library.collection.published") AND ' "systemOnly=0" ).fetchall(): - id2coll[genealogy] = (id_local, creationId, name, parent) - for genealogy, (id_local, creationId, name, parent) in id2coll.items(): + id2coll[genealogy] = (id_local, creation_id, name, parent) + for genealogy, (id_local, creation_id, name, parent) in id2coll.items(): hname = [] parts = genealogy.split("/")[1:] genkey = "" for part in parts: genkey += f"/{part}" - id_local, creationId, name, parent = id2coll[genkey] + id_local, creation_id, name, parent = id2coll[genkey] hname.append(name) - hierachical_colls.append((hname, id_local, creationId)) + hierachical_colls.append((hname, id_local, creation_id)) return sorted(hierachical_colls) def select_metadatas_to_archive(self): @@ -438,7 +438,8 @@ def prodpath_from_xmp(self, xmp, prod_basepath, date_fmt): ) return prodpath, capturetime - def xtract_prop_key(self, xmp, key): + @staticmethod + def xtract_prop_key(xmp, key): """ Extract value for key in xmp string (exif:DateTimeOriginal='VALUE') """ diff --git a/lrtools/lrkeyword.py b/lrtools/lrkeyword.py index 9c03d29..b05adfc 100644 --- a/lrtools/lrkeyword.py +++ b/lrtools/lrkeyword.py @@ -156,7 +156,7 @@ def photo_keys(self, idphoto, include_persons=True): def hierachical_indexes(self, key_part, operation): """ - Find keywords containing key_part, and returns all keyword indexes hierachically under theses keywords + Find keywords containing key_part, and returns all keyword indexes hierachically under these keywords - key_part : (str) keyword or part of keyword without joker '%' - operation : operation of smart function. Can be: all, any, noneOf, words, beginsWith, endsWith * words : find complete word in keywords (ex: key_part="sport", returns "sport", "professional sport" , but not "sporting", "transport") diff --git a/lrtools/lrselectgeneric.py b/lrtools/lrselectgeneric.py index 7b7f4d3..6a9628f 100644 --- a/lrtools/lrselectgeneric.py +++ b/lrtools/lrselectgeneric.py @@ -154,6 +154,7 @@ def func_oper_dateloc_to_lrstamp(self, value): def func_oper_dateutc_to_lrstamp(self, value): """value is a lightrom timestamp""" + oper = None for index, char in enumerate(value): if char.isnumeric(): oper = value[:index] @@ -230,8 +231,8 @@ def _keyval_to_keys(self, strlist): pairs = list() if not strlist: return pairs - try: - for keyval in strlist.split(","): + for keyval in strlist.split(","): + try: if not keyval: continue # skip double commas keyval = keyval.strip() @@ -249,10 +250,10 @@ def _keyval_to_keys(self, strlist): else: raise LRSelectException(f"No value after = on {keyval}") pairs.append({_k: _v}) - except Exception as _e: - raise LRSelectException( - f"Invalid key/value syntax ({keyval} in {strlist})" - ) from _e + except Exception as _e: + raise LRSelectException( + f"Invalid key/value syntax ({keyval} in {strlist})" + ) from _e return pairs def _add_from(self, sql, froms): @@ -417,11 +418,11 @@ def _finalize(sql): raise LRSelectException(f'Invalid Token : "{token}"') key, value = data value = self.remove_quotes(value) - if not key in nb_wheres: + if key not in nb_wheres: nb_wheres[key] = 1 else: nb_wheres[key] += 1 - if not key in self.criteria_description: + if key not in self.criteria_description: raise LRSelectException(f'No existent criterion "{key}"') criter_desc = self.criteria_description[key] if len(criter_desc) == 2: diff --git a/lrtools/lrselectphoto.py b/lrtools/lrselectphoto.py index 0b14e05..f5d54d1 100644 --- a/lrtools/lrselectphoto.py +++ b/lrtools/lrselectphoto.py @@ -795,6 +795,7 @@ def func_aperture(self, value): convert aperture value (as 5.6, F8) to LR value : 2 * ( log base 2 of F number) take care with operator '=' because it works on float """ + oper = None for index, char in enumerate(value): if char.isnumeric(): oper = value[:index] @@ -813,6 +814,7 @@ def func_speed(self, value): convert speed value in seconds to LR value : log base 2 of Nth of speed ex: ">=1/1000" for 1/1000s, "<5" for 5 seconds """ + oper = None for index, char in enumerate(value): if char.isnumeric(): oper = value[:index] @@ -1003,6 +1005,7 @@ def _todt(date): match = re.match(r"duplicated_names(.+)", columns) if match: return self.lrdb.select_duplicates(sql=True) + return None def select_generic(self, columns, criters="", **kwargs): """ diff --git a/lrtools/lrsmartcoll.py b/lrtools/lrsmartcoll.py index 3cd4bfc..442512f 100644 --- a/lrtools/lrsmartcoll.py +++ b/lrtools/lrsmartcoll.py @@ -234,7 +234,7 @@ def criteria_filename(self): self.build_string_value("", "name") def criteria_fileFormat(self): - '''criteria file format (dng, video...). Operation "==" or "!="''' + """criteria file format (dng, video...). Operation "==" or "!=" """ self.sql += self._complete_sql( "", f' WHERE i.fileFormat {self.func["operation"]} "{self.func["value"]}"', @@ -408,7 +408,7 @@ def criteria_rating(self): ) def criteria_camera(self): - '''criteria camera : operations on strings + "==' or "!="''' + """criteria camera : operations on strings + "==" or "!=" """ if self.func["operation"] == "!=": column = "em.cameraModelRef IS NULL OR cm.value" elif self.func["operation"] == "==": @@ -422,7 +422,7 @@ def criteria_camera(self): ) def criteria_lens(self): - '''criteria camera : operations on strings + "==' or "!="''' + """criteria camera : operations on strings + "==" or "!=" """ if self.func["operation"] == "!=": column = "em.lensRef IS NULL OR el.value" elif self.func["operation"] == "==": @@ -452,7 +452,7 @@ def criteria_focalLength(self): def criteria_aperture(self): """criteria aperture 2 * ( log base 2 of F number) - value = 2 * log(F/x], 2) and [F/x] = 2**(value/2) + value = 2 * log([F/x], 2) and [F/x] = 2**(value/2) f/1.0 : 0 f/1.4 : 0.997085365434048 f/2.0 : 2 @@ -518,8 +518,8 @@ def criteria_all(self): Find in earchindex, keywords, collections, creator, coptright, caption, colorprofile, full pathname """ rules = { - "any": (" OR "), - "all": (" AND "), + "any": " OR ", + "all": " AND ", } if self.func["operation"] not in rules: raise SmartException( @@ -590,8 +590,8 @@ def criteria_metadata(self): TODO """ rules = { - "any": ("OR"), - "all": ("AND"), + "any": "OR", + "all": "AND", } if self.func["operation"] not in rules: raise SmartException( @@ -783,7 +783,7 @@ def _add_joins_from_select(self, basesql): """ add join tables from a sql select request to self.joins Return select part - basesql doesnt contains WHERE statement + basesql doesn't contain WHERE statement """ ijoin = basesql.find("LEFT JOIN ") sjoins = basesql[ijoin:] diff --git a/lrtools/lrtoolconfig.py b/lrtools/lrtoolconfig.py index d62b133..99d9bfb 100644 --- a/lrtools/lrtoolconfig.py +++ b/lrtools/lrtoolconfig.py @@ -9,8 +9,7 @@ import sys import os -from configparser import ConfigParser, Error -from dateutil import parser as dateparser +from configparser import ConfigParser class Singleton(type): @@ -93,7 +92,7 @@ def load(self, filename): self.dayfirst = parser.getboolean(CONFIG_MAIN, "DayFirst") self.geocoder = parser.get(CONFIG_MAIN, "GeoCoder") - except Error as _e: + except Exception as _e: raise LRConfigException( f'Failed to read config file "{filename}"' ) from _e diff --git a/lrtools/slpp.py b/lrtools/slpp.py index a5d9a8b..19dba10 100644 --- a/lrtools/slpp.py +++ b/lrtools/slpp.py @@ -20,7 +20,7 @@ def __init__(self): def decode(self, text): if not text or type(text).__name__ != "str": - return + return None text = re.sub("---.*$", "", text, 0, re.M) self.text = text self.at, self.ch, self.depth = 0, "", 0 @@ -28,12 +28,12 @@ def decode(self, text): self.next_chr() result = self.value() if not result: - return + return None return result def encode(self, obj): if not obj: - return + return None self.depth = 0 return self.__encode(obj) @@ -98,7 +98,7 @@ def next_chr(self): def value(self): self.white() if not self.ch or self.ch == "": - return + return None if self.ch == "{": return self.object() if self.ch == '"': @@ -117,6 +117,7 @@ def string(self): else: s += self.ch log.error("Unexpected end of string while parsing Lua string") + return None def object(self): o = {} @@ -171,6 +172,7 @@ def object(self): log.error( "Unexpected end of table while parsing Lua string." ) # Bad exit here + return None def word(self): s = "" @@ -185,6 +187,7 @@ def word(self): elif re.match("^false$", s, re.I): return False return str(s) + return None def number(self): n = "" From d40ad12a4b42a057b978264d51b001163d7a0db8 Mon Sep 17 00:00:00 2001 From: Julian-o Date: Mon, 22 Dec 2025 16:13:21 +1100 Subject: [PATCH 2/4] Corrected header used above table, because often results are not photos (e.g. a list of cameras). Also grammar correction: on -> of. --- lrtools/display.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lrtools/display.py b/lrtools/display.py index e1b0901..fc88ee7 100644 --- a/lrtools/display.py +++ b/lrtools/display.py @@ -221,12 +221,12 @@ def display_results(rows, columns, **kwargs): if wanted_lines >= len(rows): max_lines = len(rows) if kwargs.get("header", True): - print(f" * Photo results ({len(rows)} photos) :") + print(f" * Results ({len(rows)} entries) :") else: max_lines = wanted_lines if kwargs.get("header", True): print( - f" * Photo results (first {wanted_lines} photos on {len(rows)}) :" + f" * Results (first {wanted_lines} entries of {len(rows)}) :" ) column_spec = prepare_display_columns(columns, widths) From 8430156a84fb6a97083a1c6106fd37cc3f84fc74 Mon Sep 17 00:00:00 2001 From: Julian-o Date: Tue, 23 Dec 2025 06:10:35 +1100 Subject: [PATCH 3/4] Updated README to match new printed text --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a2f73c3..a661756 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ It builds an SQL SELECT query from two strings describing informations to displa * photos taken around Paris, with camera RX100, between May and August 2018, and modified in Lightroom since August 2019: lrselect.py "name, focal, speed, aperture, keywords" "gps=paris+10, camera=%rx100%, datecapt=>=1-5-2018, datecapt=<=1-7-2018, datemod=>=1-8-2019" - * Photo results (2 photos) : + * Results (2 entries) : name | focal | speed | apert | keywords =========================================================== RX100_01399.tif | 10.89 | 1/160 | F5.6 | family,paris @@ -84,7 +84,7 @@ It builds an SQL SELECT query from two strings describing informations to displa * photos with specific name, iso > 1600, focal > 200mm and aperture > F/8 : lrselect.py "name,datecapt,iso,focal,aperture,speed,lens" "name=D7K_%,iso=>=1600,focal=>=200,aperture=>8" --max_lines 2 - * Photo results (first 2 photos on 8) : + * Results (first 2 entries of 8) : name | datecapt | iso | focal | apert | speed | lens =========================================================================================================== D7K_01977.JPG | 2013-09-04T15:55:01 | 1600 | 280.0 | F20.0 | 1/500 | 55.0-300.0 mm f/4.5-5.6 @@ -94,7 +94,7 @@ It builds an SQL SELECT query from two strings describing informations to displa lrselect.py "camera,lens" "camera=nikon D8%, lens=%70%, distinct, sort=-1" --results --sql * SQL query = SELECT DISTINCT cm.value, el.value FROM Adobe_images i LEFT JOIN AgHarvestedExifMetadata em on i.id_local = em.image LEFT JOIN AgInternedExifCameraModel cm on cm.id_local = em.cameraModelRef LEFT JOIN AgInternedExifLens el on el.id_local = em.lensRef WHERE cm.value LIKE "nikon D8%" AND el.value LIKE "%70%" ORDER BY 1 ASC - * Photo results (2 photos) : + * Results (2 entries) : camera | lens ============================================== NIKON D80 | 17.0-70.0 mm f/2.8-4.0 @@ -103,7 +103,7 @@ It builds an SQL SELECT query from two strings describing informations to displa * list of Canon cameras used : lrselect.py "camera" "camera=canon%, distinct" -r - * Photo results (4 photos) : + * Results (4 entries) : camera ===================== Canon PowerShot G2 @@ -122,7 +122,7 @@ It builds an SQL SELECT query from two strings describing informations to displa * duplicates photo name (name with virtual copies) lrselect.py "name=basext_vc, countby(name=basext_vc)", "count=name>1" -r - * Photo results (2 photos) : + * Results (2 entries) : name=b | countb =============== IMG_102.jpg | 2 @@ -381,7 +381,7 @@ Unfortunately : combine = intersect * SQL query: SELECT DISTINCT fi.baseName || "." || fi.extension AS name, i.captureTime AS datecapt FROM Adobe_images i LEFT JOIN AgLibraryFile fi ON i.rootFile = fi.id_local LEFT JOIN AgLibraryCollectionimage ci0 ON ci0.image = i.id_local LEFT JOIN AgLibraryCollection col0 ON col0.id_local = ci0.Collection WHERE col0.name LIKE "Holidays%" INTERSECT SELECT fi.baseName || "." || fi.extension AS name, i.captureTime AS datecapt FROM Adobe_images i JOIN AgLibraryFile fi ON i.rootFile = fi.id_local LEFT JOIN AgHarvestedExifMetadata em on i.id_local = em.image WHERE em.hasGps == 0 * Count results: 1880 - * Photo results (first 2 photos on 1880) : + * Results (first 2 entries of 1880) : name | datecapt ============================================= 103-0332_IMG.JPG | 2002-03-07T17:53:03 From 92e02555e6867ef38cccdf58639a003b7ba95b96 Mon Sep 17 00:00:00 2001 From: Julian-o Date: Tue, 23 Dec 2025 06:18:57 +1100 Subject: [PATCH 4/4] Move as much as can be done quickly out of __init__.py. --- lrtools/__init__.py | 8 -------- lrtools/lrcat.py | 9 ++++++--- lrtools/lrsmartcoll.py | 8 +++++--- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/lrtools/__init__.py b/lrtools/__init__.py index fde4a58..f76f0d8 100644 --- a/lrtools/__init__.py +++ b/lrtools/__init__.py @@ -1,16 +1,8 @@ import os -from datetime import datetime, timedelta, timezone import pytz from tzlocal import get_localzone -# date reference of lightroom (at least for timestamp of photos modified) -DATE_REF = datetime(2001, 1, 1, 0, 0, 0, tzinfo=timezone.utc) - -# unix timestamp for LR date reference (2001,1,1,0,0,0) -TIMESTAMP_LRBASE = 978307200 - - # work around on cygwin problem : # env_tz = os.getenv( diff --git a/lrtools/lrcat.py b/lrtools/lrcat.py index cc12dea..8978994 100644 --- a/lrtools/lrcat.py +++ b/lrtools/lrcat.py @@ -8,10 +8,10 @@ import os import sqlite3 import logging -from datetime import datetime +from datetime import datetime, timezone from dateutil import parser -from . import DATE_REF, localzone, utczone +from . import utczone, localzone # config is loaded on import from .lrtoolconfig import lrt_config @@ -20,6 +20,9 @@ log = logging.getLogger(__name__) +# date reference of lightroom (at least for timestamp of photos modified) +LIGHTROOM_EPOCH = datetime(2001, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + def date_to_lrstamp(mydate, localtz=True): """ @@ -36,7 +39,7 @@ def date_to_lrstamp(mydate, localtz=True): dtdate = mydate else: return None - ts = (dtdate - DATE_REF).total_seconds() + ts = (dtdate - LIGHTROOM_EPOCH).total_seconds() return ts if ts >= 0 else 0 diff --git a/lrtools/lrsmartcoll.py b/lrtools/lrsmartcoll.py index 442512f..8bd8c12 100644 --- a/lrtools/lrsmartcoll.py +++ b/lrtools/lrsmartcoll.py @@ -10,13 +10,15 @@ import logging from datetime import datetime, timedelta -from . import TIMESTAMP_LRBASE from .lrkeyword import LRKeywords from .lrselectcollection import LRSelectCollection from .slpp import SLPP log = logging.getLogger(__name__) +# unix timestamp for LR epoch (2001,1,1,0,0,0) +TIMESTAMP_LR_EPOCH = 978307200 + class SmartException(Exception): """SQLSmartColl Exception""" @@ -182,7 +184,7 @@ def criteria_captureTime(self): def criteria_touchTime(self): """criteria touchTime""" - db_touchtime = f'date(i.touchTime + {TIMESTAMP_LRBASE}, "unixepoch")' + db_touchtime = f'date(i.touchTime + {TIMESTAMP_LR_EPOCH}, "unixepoch")' if self.func["operation"] == "in": self.sql += self._complete_sql( "", @@ -206,7 +208,7 @@ def criteria_touchTime(self): elif self.func["operation"] == "thisYear": self.sql += self._complete_sql( "", - f' WHERE date(i.touchTime + {TIMESTAMP_LRBASE}, "unixepoch", "start of year") = date("now","start of year")', + f' WHERE date(i.touchTime + {TIMESTAMP_LR_EPOCH}, "unixepoch", "start of year") = date("now","start of year")', ) elif self.func["operation"] == "today": self.sql += self._complete_sql(