Refactoring with sourceryAI
This commit is contained in:
@@ -20,11 +20,7 @@ from osxphotos.__main__ import get_photos_db, _list_libraries
|
|||||||
def main():
|
def main():
|
||||||
db = None
|
db = None
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
db = sys.argv[1] if len(sys.argv) > 1 else get_photos_db()
|
||||||
db = sys.argv[1]
|
|
||||||
else:
|
|
||||||
db = get_photos_db()
|
|
||||||
|
|
||||||
if db:
|
if db:
|
||||||
print("loading database")
|
print("loading database")
|
||||||
tic = time.perf_counter()
|
tic = time.perf_counter()
|
||||||
|
|||||||
@@ -534,10 +534,7 @@ def info(ctx, cli_obj, db, json_, photos_library):
|
|||||||
return
|
return
|
||||||
|
|
||||||
pdb = osxphotos.PhotosDB(dbfile=db)
|
pdb = osxphotos.PhotosDB(dbfile=db)
|
||||||
info = {}
|
info = {"database_path": pdb.db_path, "database_version": pdb.db_version}
|
||||||
info["database_path"] = pdb.db_path
|
|
||||||
info["database_version"] = pdb.db_version
|
|
||||||
|
|
||||||
photos = pdb.photos()
|
photos = pdb.photos()
|
||||||
not_shared_photos = [p for p in photos if not p.shared]
|
not_shared_photos = [p for p in photos if not p.shared]
|
||||||
info["photo_count"] = len(not_shared_photos)
|
info["photo_count"] = len(not_shared_photos)
|
||||||
@@ -1175,7 +1172,7 @@ def export(
|
|||||||
(export_as_hardlink, exiftool),
|
(export_as_hardlink, exiftool),
|
||||||
(any(place), no_place),
|
(any(place), no_place),
|
||||||
]
|
]
|
||||||
if any([all(bb) for bb in exclusive]):
|
if any(all(bb) for bb in exclusive):
|
||||||
click.echo("Incompatible export options", err=True)
|
click.echo("Incompatible export options", err=True)
|
||||||
click.echo(cli.commands["export"].get_help(ctx), err=True)
|
click.echo(cli.commands["export"].get_help(ctx), err=True)
|
||||||
return
|
return
|
||||||
@@ -1186,11 +1183,6 @@ def export(
|
|||||||
not x for x in [skip_edited, skip_bursts, skip_live, skip_raw]
|
not x for x in [skip_edited, skip_bursts, skip_live, skip_raw]
|
||||||
]
|
]
|
||||||
|
|
||||||
# though the command line option is current_name, internally all processing
|
|
||||||
# logic uses original_name which is the boolean inverse of current_name
|
|
||||||
# because the original code used --original-name as an option
|
|
||||||
original_name = not current_name
|
|
||||||
|
|
||||||
# verify exiftool installed an in path
|
# verify exiftool installed an in path
|
||||||
if exiftool:
|
if exiftool:
|
||||||
try:
|
try:
|
||||||
@@ -1283,10 +1275,6 @@ def export(
|
|||||||
)
|
)
|
||||||
|
|
||||||
results_exported = []
|
results_exported = []
|
||||||
results_new = []
|
|
||||||
results_updated = []
|
|
||||||
results_skipped = []
|
|
||||||
results_exif_updated = []
|
|
||||||
if photos:
|
if photos:
|
||||||
if export_bursts:
|
if export_bursts:
|
||||||
# add the burst_photos to the export set
|
# add the burst_photos to the export set
|
||||||
@@ -1300,7 +1288,50 @@ def export(
|
|||||||
photo_str = "photos" if num_photos > 1 else "photo"
|
photo_str = "photos" if num_photos > 1 else "photo"
|
||||||
click.echo(f"Exporting {num_photos} {photo_str} to {dest}...")
|
click.echo(f"Exporting {num_photos} {photo_str} to {dest}...")
|
||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
if not verbose_:
|
# though the command line option is current_name, internally all processing
|
||||||
|
# logic uses original_name which is the boolean inverse of current_name
|
||||||
|
# because the original code used --original-name as an option
|
||||||
|
original_name = not current_name
|
||||||
|
|
||||||
|
results_new = []
|
||||||
|
results_updated = []
|
||||||
|
results_skipped = []
|
||||||
|
results_exif_updated = []
|
||||||
|
if verbose_:
|
||||||
|
for p in photos:
|
||||||
|
results = export_photo(
|
||||||
|
photo=p,
|
||||||
|
dest=dest,
|
||||||
|
verbose_=verbose_,
|
||||||
|
export_by_date=export_by_date,
|
||||||
|
sidecar=sidecar,
|
||||||
|
update=update,
|
||||||
|
export_as_hardlink=export_as_hardlink,
|
||||||
|
overwrite=overwrite,
|
||||||
|
export_edited=export_edited,
|
||||||
|
original_name=original_name,
|
||||||
|
export_live=export_live,
|
||||||
|
download_missing=download_missing,
|
||||||
|
exiftool=exiftool,
|
||||||
|
directory=directory,
|
||||||
|
filename_template=filename_template,
|
||||||
|
no_extended_attributes=no_extended_attributes,
|
||||||
|
export_raw=export_raw,
|
||||||
|
album_keyword=album_keyword,
|
||||||
|
person_keyword=person_keyword,
|
||||||
|
keyword_template=keyword_template,
|
||||||
|
export_db=export_db,
|
||||||
|
fileutil=fileutil,
|
||||||
|
dry_run=dry_run,
|
||||||
|
edited_suffix=edited_suffix,
|
||||||
|
)
|
||||||
|
results_exported.extend(results.exported)
|
||||||
|
results_new.extend(results.new)
|
||||||
|
results_updated.extend(results.updated)
|
||||||
|
results_skipped.extend(results.skipped)
|
||||||
|
results_exif_updated.extend(results.exif_updated)
|
||||||
|
|
||||||
|
else:
|
||||||
# show progress bar
|
# show progress bar
|
||||||
with click.progressbar(photos) as bar:
|
with click.progressbar(photos) as bar:
|
||||||
for p in bar:
|
for p in bar:
|
||||||
@@ -1335,47 +1366,9 @@ def export(
|
|||||||
results_updated.extend(results.updated)
|
results_updated.extend(results.updated)
|
||||||
results_skipped.extend(results.skipped)
|
results_skipped.extend(results.skipped)
|
||||||
results_exif_updated.extend(results.exif_updated)
|
results_exif_updated.extend(results.exif_updated)
|
||||||
else:
|
|
||||||
for p in photos:
|
|
||||||
results = export_photo(
|
|
||||||
photo=p,
|
|
||||||
dest=dest,
|
|
||||||
verbose_=verbose_,
|
|
||||||
export_by_date=export_by_date,
|
|
||||||
sidecar=sidecar,
|
|
||||||
update=update,
|
|
||||||
export_as_hardlink=export_as_hardlink,
|
|
||||||
overwrite=overwrite,
|
|
||||||
export_edited=export_edited,
|
|
||||||
original_name=original_name,
|
|
||||||
export_live=export_live,
|
|
||||||
download_missing=download_missing,
|
|
||||||
exiftool=exiftool,
|
|
||||||
directory=directory,
|
|
||||||
filename_template=filename_template,
|
|
||||||
no_extended_attributes=no_extended_attributes,
|
|
||||||
export_raw=export_raw,
|
|
||||||
album_keyword=album_keyword,
|
|
||||||
person_keyword=person_keyword,
|
|
||||||
keyword_template=keyword_template,
|
|
||||||
export_db=export_db,
|
|
||||||
fileutil=fileutil,
|
|
||||||
dry_run=dry_run,
|
|
||||||
edited_suffix=edited_suffix,
|
|
||||||
)
|
|
||||||
results_exported.extend(results.exported)
|
|
||||||
results_new.extend(results.new)
|
|
||||||
results_updated.extend(results.updated)
|
|
||||||
results_skipped.extend(results.skipped)
|
|
||||||
results_exif_updated.extend(results.exif_updated)
|
|
||||||
|
|
||||||
stop_time = time.perf_counter()
|
stop_time = time.perf_counter()
|
||||||
# print summary results
|
# print summary results
|
||||||
if not update:
|
if update:
|
||||||
photo_str = "photos" if len(results_exported) != 1 else "photo"
|
|
||||||
click.echo(f"Exported: {len(results_exported)} {photo_str}")
|
|
||||||
click.echo(f"Elapsed time: {stop_time-start_time} seconds")
|
|
||||||
else:
|
|
||||||
photo_str_new = "photos" if len(results_new) != 1 else "photo"
|
photo_str_new = "photos" if len(results_new) != 1 else "photo"
|
||||||
photo_str_updated = "photos" if len(results_new) != 1 else "photo"
|
photo_str_updated = "photos" if len(results_new) != 1 else "photo"
|
||||||
photo_str_skipped = "photos" if len(results_skipped) != 1 else "photo"
|
photo_str_skipped = "photos" if len(results_skipped) != 1 else "photo"
|
||||||
@@ -1388,8 +1381,10 @@ def export(
|
|||||||
+ f"skipped: {len(results_skipped)} {photo_str_skipped}, "
|
+ f"skipped: {len(results_skipped)} {photo_str_skipped}, "
|
||||||
+ f"updated EXIF data: {len(results_exif_updated)} {photo_str_exif_updated}"
|
+ f"updated EXIF data: {len(results_exif_updated)} {photo_str_exif_updated}"
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
photo_str = "photos" if len(results_exported) != 1 else "photo"
|
||||||
|
click.echo(f"Exported: {len(results_exported)} {photo_str}")
|
||||||
click.echo(f"Elapsed time: {stop_time-start_time} seconds")
|
click.echo(f"Elapsed time: {stop_time-start_time} seconds")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
click.echo("Did not find any photos to export")
|
click.echo("Did not find any photos to export")
|
||||||
|
|
||||||
@@ -1412,8 +1407,8 @@ def help(ctx, topic, **kw):
|
|||||||
|
|
||||||
|
|
||||||
def print_photo_info(photos, json=False):
|
def print_photo_info(photos, json=False):
|
||||||
if json:
|
|
||||||
dump = []
|
dump = []
|
||||||
|
if json:
|
||||||
for p in photos:
|
for p in photos:
|
||||||
dump.append(p.json())
|
dump.append(p.json())
|
||||||
click.echo(f"[{', '.join(dump)}]")
|
click.echo(f"[{', '.join(dump)}]")
|
||||||
@@ -1422,7 +1417,6 @@ def print_photo_info(photos, json=False):
|
|||||||
csv_writer = csv.writer(
|
csv_writer = csv.writer(
|
||||||
sys.stdout, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
|
sys.stdout, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
|
||||||
)
|
)
|
||||||
dump = []
|
|
||||||
# add headers
|
# add headers
|
||||||
dump.append(
|
dump.append(
|
||||||
[
|
[
|
||||||
@@ -1990,10 +1984,7 @@ def get_filenames_from_template(photo, filename_template, original_name):
|
|||||||
)
|
)
|
||||||
filenames = [f"{file_}{photo_ext}" for file_ in filenames]
|
filenames = [f"{file_}{photo_ext}" for file_ in filenames]
|
||||||
else:
|
else:
|
||||||
if original_name:
|
filenames = [photo.original_filename] if original_name else [photo.filename]
|
||||||
filenames = [photo.original_filename]
|
|
||||||
else:
|
|
||||||
filenames = [photo.filename]
|
|
||||||
return filenames
|
return filenames
|
||||||
|
|
||||||
|
|
||||||
@@ -2019,13 +2010,18 @@ def get_dirnames_from_template(photo, directory, export_by_date, dest, dry_run):
|
|||||||
dest_path = os.path.join(
|
dest_path = os.path.join(
|
||||||
dest, date_created.year, date_created.mm, date_created.dd
|
dest, date_created.year, date_created.mm, date_created.dd
|
||||||
)
|
)
|
||||||
if not dry_run and not os.path.isdir(dest_path):
|
if not (dry_run or os.path.isdir(dest_path)):
|
||||||
os.makedirs(dest_path)
|
os.makedirs(dest_path)
|
||||||
dest_paths = [dest_path]
|
dest_paths = [dest_path]
|
||||||
elif directory:
|
elif directory:
|
||||||
# got a directory template, render it and check results are valid
|
# got a directory template, render it and check results are valid
|
||||||
dirnames, unmatched = photo.render_template(directory)
|
dirnames, unmatched = photo.render_template(directory)
|
||||||
if not dirnames or unmatched:
|
if not dirnames:
|
||||||
|
raise click.BadOptionUsage(
|
||||||
|
"directory",
|
||||||
|
f"Invalid template '{directory}': results={dirnames} unmatched={unmatched}",
|
||||||
|
)
|
||||||
|
elif unmatched:
|
||||||
raise click.BadOptionUsage(
|
raise click.BadOptionUsage(
|
||||||
"directory",
|
"directory",
|
||||||
f"Invalid template '{directory}': results={dirnames} unmatched={unmatched}",
|
f"Invalid template '{directory}': results={dirnames} unmatched={unmatched}",
|
||||||
@@ -2036,7 +2032,7 @@ def get_dirnames_from_template(photo, directory, export_by_date, dest, dry_run):
|
|||||||
dest_path = os.path.join(dest, dirname)
|
dest_path = os.path.join(dest, dirname)
|
||||||
if not is_valid_filepath(dest_path, platform="auto"):
|
if not is_valid_filepath(dest_path, platform="auto"):
|
||||||
raise ValueError(f"Invalid file path: '{dest_path}'")
|
raise ValueError(f"Invalid file path: '{dest_path}'")
|
||||||
if not dry_run and not os.path.isdir(dest_path):
|
if not (dry_run or os.path.isdir(dest_path)):
|
||||||
os.makedirs(dest_path)
|
os.makedirs(dest_path)
|
||||||
dest_paths.append(dest_path)
|
dest_paths.append(dest_path)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -445,10 +445,7 @@ class ExportDB(ExportDB_ABC):
|
|||||||
dt = datetime.datetime.utcnow().isoformat()
|
dt = datetime.datetime.utcnow().isoformat()
|
||||||
python_path = sys.executable
|
python_path = sys.executable
|
||||||
cmd = sys.argv[0]
|
cmd = sys.argv[0]
|
||||||
if len(sys.argv) > 1:
|
args = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else ""
|
||||||
args = " ".join(sys.argv[1:])
|
|
||||||
else:
|
|
||||||
args = ""
|
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.29.9"
|
__version__ = "0.29.10"
|
||||||
|
|||||||
@@ -62,9 +62,6 @@ class AlbumInfo:
|
|||||||
try:
|
try:
|
||||||
return self._folder_names
|
return self._folder_names
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
|
||||||
self._folder_names = self._db._album_folder_hierarchy_list(self._uuid)
|
|
||||||
else:
|
|
||||||
self._folder_names = self._db._album_folder_hierarchy_list(self._uuid)
|
self._folder_names = self._db._album_folder_hierarchy_list(self._uuid)
|
||||||
return self._folder_names
|
return self._folder_names
|
||||||
|
|
||||||
|
|||||||
@@ -12,53 +12,44 @@ class DateTimeFormatter:
|
|||||||
@property
|
@property
|
||||||
def date(self):
|
def date(self):
|
||||||
""" ISO date in form 2020-03-22 """
|
""" ISO date in form 2020-03-22 """
|
||||||
date = self.dt.date().isoformat()
|
return self.dt.date().isoformat()
|
||||||
return date
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def year(self):
|
def year(self):
|
||||||
""" 4 digit year """
|
""" 4 digit year """
|
||||||
year = f"{self.dt.year}"
|
return f"{self.dt.year}"
|
||||||
return year
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def yy(self):
|
def yy(self):
|
||||||
""" 2 digit year """
|
""" 2 digit year """
|
||||||
yy = f"{self.dt.strftime('%y')}"
|
return f"{self.dt.strftime('%y')}"
|
||||||
return yy
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mm(self):
|
def mm(self):
|
||||||
""" 2 digit month """
|
""" 2 digit month """
|
||||||
mm = f"{self.dt.strftime('%m')}"
|
return f"{self.dt.strftime('%m')}"
|
||||||
return mm
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def month(self):
|
def month(self):
|
||||||
""" Month as locale's full name """
|
""" Month as locale's full name """
|
||||||
month = f"{self.dt.strftime('%B')}"
|
return f"{self.dt.strftime('%B')}"
|
||||||
return month
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mon(self):
|
def mon(self):
|
||||||
""" Month as locale's abbreviated name """
|
""" Month as locale's abbreviated name """
|
||||||
mon = f"{self.dt.strftime('%b')}"
|
return f"{self.dt.strftime('%b')}"
|
||||||
return mon
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dd(self):
|
def dd(self):
|
||||||
""" 2-digit day of the month """
|
""" 2-digit day of the month """
|
||||||
dd = f"{self.dt.strftime('%d')}"
|
return f"{self.dt.strftime('%d')}"
|
||||||
return dd
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dow(self):
|
def dow(self):
|
||||||
""" Day of week as locale's name """
|
""" Day of week as locale's name """
|
||||||
dow = f"{self.dt.strftime('%A')}"
|
return f"{self.dt.strftime('%A')}"
|
||||||
return dow
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def doy(self):
|
def doy(self):
|
||||||
""" Julian day of year starting from 001 """
|
""" Julian day of year starting from 001 """
|
||||||
doy = f"{self.dt.strftime('%j')}"
|
return f"{self.dt.strftime('%j')}"
|
||||||
return doy
|
|
||||||
|
|||||||
@@ -59,11 +59,7 @@ class _ExifToolProc:
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if exiftool:
|
self._exiftool = exiftool if exiftool else get_exiftool_path()
|
||||||
self._exiftool = exiftool
|
|
||||||
else:
|
|
||||||
self._exiftool = get_exiftool_path()
|
|
||||||
|
|
||||||
self._process_running = False
|
self._process_running = False
|
||||||
self._start_proc()
|
self._start_proc()
|
||||||
|
|
||||||
@@ -156,8 +152,7 @@ class ExifTool:
|
|||||||
|
|
||||||
if value is None:
|
if value is None:
|
||||||
value = ""
|
value = ""
|
||||||
command = []
|
command = [f"-{tag}={value}"]
|
||||||
command.append(f"-{tag}={value}")
|
|
||||||
if self.overwrite:
|
if self.overwrite:
|
||||||
command.append("-overwrite_original")
|
command.append("-overwrite_original")
|
||||||
self.run_commands(*command)
|
self.run_commands(*command)
|
||||||
@@ -193,7 +188,7 @@ class ExifTool:
|
|||||||
no_file: (bool) do not pass the filename to exiftool (default=False)
|
no_file: (bool) do not pass the filename to exiftool (default=False)
|
||||||
by default, all commands will be run against self.file
|
by default, all commands will be run against self.file
|
||||||
use no_file=True to run a command without passing the filename """
|
use no_file=True to run a command without passing the filename """
|
||||||
if not hasattr(self, "_process") or not self._process:
|
if not (hasattr(self, "_process") and self._process):
|
||||||
raise ValueError("exiftool process is not running")
|
raise ValueError("exiftool process is not running")
|
||||||
|
|
||||||
if not commands:
|
if not commands:
|
||||||
@@ -245,8 +240,7 @@ class ExifTool:
|
|||||||
|
|
||||||
def json(self):
|
def json(self):
|
||||||
""" returns JSON string containing all EXIF tags and values from exiftool """
|
""" returns JSON string containing all EXIF tags and values from exiftool """
|
||||||
json_str = self.run_commands("-json")
|
return self.run_commands("-json")
|
||||||
return json_str
|
|
||||||
|
|
||||||
def _read_exif(self):
|
def _read_exif(self):
|
||||||
""" read exif data from file """
|
""" read exif data from file """
|
||||||
@@ -254,5 +248,4 @@ class ExifTool:
|
|||||||
self.data = {k: v for k, v in data.items()}
|
self.data = {k: v for k, v in data.items()}
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
str_ = f"file: {self.file}\nexiftool: {self._exiftoolproc._exiftool}"
|
return f"file: {self.file}\nexiftool: {self._exiftoolproc._exiftool}"
|
||||||
return str_
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from abc import ABC, abstractmethod
|
|||||||
|
|
||||||
class FileUtilABC(ABC):
|
class FileUtilABC(ABC):
|
||||||
""" Abstract base class for FileUtil """
|
""" Abstract base class for FileUtil """
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def hardlink(cls, src, dest):
|
def hardlink(cls, src, dest):
|
||||||
@@ -39,6 +40,7 @@ class FileUtilABC(ABC):
|
|||||||
|
|
||||||
class FileUtilMacOS(FileUtilABC):
|
class FileUtilMacOS(FileUtilABC):
|
||||||
""" Various file utilities """
|
""" Various file utilities """
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def hardlink(cls, src, dest):
|
def hardlink(cls, src, dest):
|
||||||
""" Hardlinks a file from src path to dest path
|
""" Hardlinks a file from src path to dest path
|
||||||
@@ -119,9 +121,7 @@ class FileUtilMacOS(FileUtilABC):
|
|||||||
|
|
||||||
if s1[0] != stat.S_IFREG or s2[0] != stat.S_IFREG:
|
if s1[0] != stat.S_IFREG or s2[0] != stat.S_IFREG:
|
||||||
return False
|
return False
|
||||||
if s1 == s2:
|
return s1 == s2
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def file_sig(cls, f1):
|
def file_sig(cls, f1):
|
||||||
@@ -135,14 +135,17 @@ class FileUtilMacOS(FileUtilABC):
|
|||||||
|
|
||||||
class FileUtil(FileUtilMacOS):
|
class FileUtil(FileUtilMacOS):
|
||||||
""" Various file utilities """
|
""" Various file utilities """
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FileUtilNoOp(FileUtil):
|
class FileUtilNoOp(FileUtil):
|
||||||
""" No-Op implementation of FileUtil for testing / dry-run mode
|
""" No-Op implementation of FileUtil for testing / dry-run mode
|
||||||
all methods with exception of cmp_sig and file_cmp are no-op
|
all methods with exception of cmp_sig and file_cmp are no-op
|
||||||
cmp_sig functions as FileUtil.cmp_sig does
|
cmp_sig functions as FileUtil.cmp_sig does
|
||||||
file_cmp returns mock data
|
file_cmp returns mock data
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def noop(*args):
|
def noop(*args):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1153,7 +1153,7 @@ def _xmp_sidecar(
|
|||||||
def _write_sidecar(self, filename, sidecar_str):
|
def _write_sidecar(self, filename, sidecar_str):
|
||||||
""" write sidecar_str to filename
|
""" write sidecar_str to filename
|
||||||
used for exporting sidecar info """
|
used for exporting sidecar info """
|
||||||
if not filename and not sidecar_str:
|
if not (filename or sidecar_str):
|
||||||
raise (
|
raise (
|
||||||
ValueError(
|
ValueError(
|
||||||
f"filename {filename} and sidecar_str {sidecar_str} must not be None"
|
f"filename {filename} and sidecar_str {sidecar_str} must not be None"
|
||||||
|
|||||||
@@ -294,18 +294,17 @@ class PhotosDB:
|
|||||||
@property
|
@property
|
||||||
def keywords_as_dict(self):
|
def keywords_as_dict(self):
|
||||||
""" return keywords as dict of keyword, count in reverse sorted order (descending) """
|
""" return keywords as dict of keyword, count in reverse sorted order (descending) """
|
||||||
keywords = {}
|
keywords = {
|
||||||
for k in self._dbkeywords_keyword.keys():
|
k: len(self._dbkeywords_keyword[k]) for k in self._dbkeywords_keyword.keys()
|
||||||
keywords[k] = len(self._dbkeywords_keyword[k])
|
}
|
||||||
|
|
||||||
keywords = dict(sorted(keywords.items(), key=lambda kv: kv[1], reverse=True))
|
keywords = dict(sorted(keywords.items(), key=lambda kv: kv[1], reverse=True))
|
||||||
return keywords
|
return keywords
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def persons_as_dict(self):
|
def persons_as_dict(self):
|
||||||
""" return persons as dict of person, count in reverse sorted order (descending) """
|
""" return persons as dict of person, count in reverse sorted order (descending) """
|
||||||
persons = {}
|
persons = {k: len(self._dbfaces_person[k]) for k in self._dbfaces_person.keys()}
|
||||||
for k in self._dbfaces_person.keys():
|
|
||||||
persons[k] = len(self._dbfaces_person[k])
|
|
||||||
persons = dict(sorted(persons.items(), key=lambda kv: kv[1], reverse=True))
|
persons = dict(sorted(persons.items(), key=lambda kv: kv[1], reverse=True))
|
||||||
return persons
|
return persons
|
||||||
|
|
||||||
@@ -413,13 +412,12 @@ class PhotosDB:
|
|||||||
def album_info(self):
|
def album_info(self):
|
||||||
""" return list of AlbumInfo objects for each album in the photos database """
|
""" return list of AlbumInfo objects for each album in the photos database """
|
||||||
|
|
||||||
albums = [
|
return [
|
||||||
AlbumInfo(db=self, uuid=album)
|
AlbumInfo(db=self, uuid=album)
|
||||||
for album in self._dbalbums_album.keys()
|
for album in self._dbalbums_album.keys()
|
||||||
if self._dbalbum_details[album]["cloudownerhashedpersonid"] is None
|
if self._dbalbum_details[album]["cloudownerhashedpersonid"] is None
|
||||||
and not self._dbalbum_details[album]["intrash"]
|
and not self._dbalbum_details[album]["intrash"]
|
||||||
]
|
]
|
||||||
return albums
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def album_info_shared(self):
|
def album_info_shared(self):
|
||||||
@@ -433,13 +431,12 @@ class PhotosDB:
|
|||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
albums_shared = [
|
return [
|
||||||
AlbumInfo(db=self, uuid=album)
|
AlbumInfo(db=self, uuid=album)
|
||||||
for album in self._dbalbums_album.keys()
|
for album in self._dbalbums_album.keys()
|
||||||
if self._dbalbum_details[album]["cloudownerhashedpersonid"] is not None
|
if self._dbalbum_details[album]["cloudownerhashedpersonid"] is not None
|
||||||
and not self._dbalbum_details[album]["intrash"]
|
and not self._dbalbum_details[album]["intrash"]
|
||||||
]
|
]
|
||||||
return albums_shared
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def albums(self):
|
def albums(self):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
""" Custom template system for osxphotos (implemented in PhotoInfo.render_template) """
|
""" Custom template system for osxphotos (implemented in PhotoInfo.render_template) """
|
||||||
|
|
||||||
|
|
||||||
# Rolled my own template system because:
|
# Rolled my own template system because:
|
||||||
# 1. Needed to handle multiple values (e.g. album, keyword)
|
# 1. Needed to handle multiple values (e.g. album, keyword)
|
||||||
# 2. Needed to handle default values if template not found
|
# 2. Needed to handle default values if template not found
|
||||||
@@ -8,7 +9,6 @@
|
|||||||
# 4. Couldn't figure out how to do #1 and #2 with str.format()
|
# 4. Couldn't figure out how to do #1 and #2 with str.format()
|
||||||
#
|
#
|
||||||
# This code isn't elegant but it seems to work well. PRs gladly accepted.
|
# This code isn't elegant but it seems to work well. PRs gladly accepted.
|
||||||
|
|
||||||
import locale
|
import locale
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -71,7 +71,7 @@ TEMPLATE_SUBSTITUTIONS_MULTI_VALUED = {
|
|||||||
# Just the multi-valued substitution names without the braces
|
# Just the multi-valued substitution names without the braces
|
||||||
MULTI_VALUE_SUBSTITUTIONS = [
|
MULTI_VALUE_SUBSTITUTIONS = [
|
||||||
field.replace("{", "").replace("}", "")
|
field.replace("{", "").replace("}", "")
|
||||||
for field in TEMPLATE_SUBSTITUTIONS_MULTI_VALUED.keys()
|
for field in TEMPLATE_SUBSTITUTIONS_MULTI_VALUED
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -289,12 +289,16 @@ class PhotoTemplate:
|
|||||||
|
|
||||||
if field == "modified.yy":
|
if field == "modified.yy":
|
||||||
return (
|
return (
|
||||||
DateTimeFormatter(self.photo.date_modified).yy if self.photo.date_modified else None
|
DateTimeFormatter(self.photo.date_modified).yy
|
||||||
|
if self.photo.date_modified
|
||||||
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
if field == "modified.mm":
|
if field == "modified.mm":
|
||||||
return (
|
return (
|
||||||
DateTimeFormatter(self.photo.date_modified).mm if self.photo.date_modified else None
|
DateTimeFormatter(self.photo.date_modified).mm
|
||||||
|
if self.photo.date_modified
|
||||||
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
if field == "modified.month":
|
if field == "modified.month":
|
||||||
@@ -313,7 +317,9 @@ class PhotoTemplate:
|
|||||||
|
|
||||||
if field == "modified.dd":
|
if field == "modified.dd":
|
||||||
return (
|
return (
|
||||||
DateTimeFormatter(self.photo.date_modified).dd if self.photo.date_modified else None
|
DateTimeFormatter(self.photo.date_modified).dd
|
||||||
|
if self.photo.date_modified
|
||||||
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
if field == "modified.doy":
|
if field == "modified.doy":
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ class PLRevGeoLocationInfo:
|
|||||||
self.postalAddress = postalAddress
|
self.postalAddress = postalAddress
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
return all(
|
||||||
|
getattr(self, field) == getattr(other, field)
|
||||||
for field in [
|
for field in [
|
||||||
"addressString",
|
"addressString",
|
||||||
"countryCode",
|
"countryCode",
|
||||||
@@ -96,10 +98,8 @@ class PLRevGeoLocationInfo:
|
|||||||
"version",
|
"version",
|
||||||
"geoServiceProvider",
|
"geoServiceProvider",
|
||||||
"postalAddress",
|
"postalAddress",
|
||||||
]:
|
]
|
||||||
if getattr(self, field) != getattr(other, field):
|
)
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
@@ -151,21 +151,17 @@ class PLRevGeoMapItem:
|
|||||||
self.finalPlaceInfos = finalPlaceInfos
|
self.finalPlaceInfos = finalPlaceInfos
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
for field in ["sortedPlaceInfos", "finalPlaceInfos"]:
|
return all(
|
||||||
if getattr(self, field) != getattr(other, field):
|
getattr(self, field) == getattr(other, field)
|
||||||
return False
|
for field in ["sortedPlaceInfos", "finalPlaceInfos"]
|
||||||
return True
|
)
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
sortedPlaceInfos = []
|
sortedPlaceInfos = [str(place) for place in self.sortedPlaceInfos]
|
||||||
finalPlaceInfos = []
|
finalPlaceInfos = [str(place) for place in self.finalPlaceInfos]
|
||||||
for place in self.sortedPlaceInfos:
|
|
||||||
sortedPlaceInfos.append(str(place))
|
|
||||||
for place in self.finalPlaceInfos:
|
|
||||||
finalPlaceInfos.append(str(place))
|
|
||||||
return (
|
return (
|
||||||
f"finalPlaceInfos: {finalPlaceInfos}, sortedPlaceInfos: {sortedPlaceInfos}"
|
f"finalPlaceInfos: {finalPlaceInfos}, sortedPlaceInfos: {sortedPlaceInfos}"
|
||||||
)
|
)
|
||||||
@@ -192,10 +188,10 @@ class PLRevGeoMapItemAdditionalPlaceInfo:
|
|||||||
self.dominantOrderType = dominantOrderType
|
self.dominantOrderType = dominantOrderType
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
for field in ["area", "name", "placeType", "dominantOrderType"]:
|
return all(
|
||||||
if getattr(self, field) != getattr(other, field):
|
getattr(self, field) == getattr(other, field)
|
||||||
return False
|
for field in ["area", "name", "placeType", "dominantOrderType"]
|
||||||
return True
|
)
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
@@ -245,6 +241,8 @@ class CNPostalAddress:
|
|||||||
self._subLocality = _subLocality
|
self._subLocality = _subLocality
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
return all(
|
||||||
|
getattr(self, field) == getattr(other, field)
|
||||||
for field in [
|
for field in [
|
||||||
"_ISOCountryCode",
|
"_ISOCountryCode",
|
||||||
"_city",
|
"_city",
|
||||||
@@ -254,10 +252,8 @@ class CNPostalAddress:
|
|||||||
"_street",
|
"_street",
|
||||||
"_subAdministrativeArea",
|
"_subAdministrativeArea",
|
||||||
"_subLocality",
|
"_subLocality",
|
||||||
]:
|
]
|
||||||
if getattr(self, field) != getattr(other, field):
|
)
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
@@ -490,16 +486,14 @@ class PlaceInfo4(PlaceInfo):
|
|||||||
"names": self.names,
|
"names": self.names,
|
||||||
"country_code": self.country_code,
|
"country_code": self.country_code,
|
||||||
}
|
}
|
||||||
strval = "PlaceInfo(" + ", ".join([f"{k}='{v}'" for k, v in info.items()]) + ")"
|
return "PlaceInfo(" + ", ".join([f"{k}='{v}'" for k, v in info.items()]) + ")"
|
||||||
return strval
|
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
info = {
|
return {
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
"names": self.names._asdict(),
|
"names": self.names._asdict(),
|
||||||
"country_code": self.country_code,
|
"country_code": self.country_code,
|
||||||
}
|
}
|
||||||
return info
|
|
||||||
|
|
||||||
|
|
||||||
class PlaceInfo5(PlaceInfo):
|
class PlaceInfo5(PlaceInfo):
|
||||||
@@ -541,7 +535,7 @@ class PlaceInfo5(PlaceInfo):
|
|||||||
@property
|
@property
|
||||||
def address(self):
|
def address(self):
|
||||||
addr = self._plrevgeoloc.postalAddress
|
addr = self._plrevgeoloc.postalAddress
|
||||||
address = PostalAddress(
|
return PostalAddress(
|
||||||
street=addr._street,
|
street=addr._street,
|
||||||
sub_locality=addr._subLocality,
|
sub_locality=addr._subLocality,
|
||||||
city=addr._city,
|
city=addr._city,
|
||||||
@@ -551,7 +545,6 @@ class PlaceInfo5(PlaceInfo):
|
|||||||
country=addr._country,
|
country=addr._country,
|
||||||
iso_country_code=addr._ISOCountryCode,
|
iso_country_code=addr._ISOCountryCode,
|
||||||
)
|
)
|
||||||
return address
|
|
||||||
|
|
||||||
def _process_place_info(self):
|
def _process_place_info(self):
|
||||||
""" Process sortedPlaceInfos to set self._name and self._names """
|
""" Process sortedPlaceInfos to set self._name and self._names """
|
||||||
@@ -630,11 +623,10 @@ class PlaceInfo5(PlaceInfo):
|
|||||||
"address_str": self.address_str,
|
"address_str": self.address_str,
|
||||||
"address": str(self.address),
|
"address": str(self.address),
|
||||||
}
|
}
|
||||||
strval = "PlaceInfo(" + ", ".join([f"{k}='{v}'" for k, v in info.items()]) + ")"
|
return "PlaceInfo(" + ", ".join([f"{k}='{v}'" for k, v in info.items()]) + ")"
|
||||||
return strval
|
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
info = {
|
return {
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
"names": self.names._asdict(),
|
"names": self.names._asdict(),
|
||||||
"country_code": self.country_code,
|
"country_code": self.country_code,
|
||||||
@@ -642,4 +634,3 @@ class PlaceInfo5(PlaceInfo):
|
|||||||
"address_str": self.address_str,
|
"address_str": self.address_str,
|
||||||
"address": self.address._asdict(),
|
"address": self.address._asdict(),
|
||||||
}
|
}
|
||||||
return info
|
|
||||||
|
|||||||
@@ -261,10 +261,9 @@ def get_preferred_uti_extension(uti):
|
|||||||
|
|
||||||
# reference: https://developer.apple.com/documentation/coreservices/1442744-uttypecopypreferredtagwithclass?language=objc
|
# reference: https://developer.apple.com/documentation/coreservices/1442744-uttypecopypreferredtagwithclass?language=objc
|
||||||
|
|
||||||
ext = CoreServices.UTTypeCopyPreferredTagWithClass(
|
return CoreServices.UTTypeCopyPreferredTagWithClass(
|
||||||
uti, CoreServices.kUTTagClassFilenameExtension
|
uti, CoreServices.kUTTagClassFilenameExtension
|
||||||
)
|
)
|
||||||
return ext
|
|
||||||
|
|
||||||
|
|
||||||
def findfiles(pattern, path_):
|
def findfiles(pattern, path_):
|
||||||
|
|||||||
Reference in New Issue
Block a user