Skip to content

Commit

Permalink
Working on slow import, #934
Browse files Browse the repository at this point in the history
  • Loading branch information
RhetTbull committed Jan 18, 2023
1 parent b6ab618 commit e88cfff
Showing 1 changed file with 69 additions and 29 deletions.
98 changes: 69 additions & 29 deletions osxphotos/cli/import_cli.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
"""import command for osxphotos CLI to import photos into Photos"""

# Note: the style in this module is a bit different than much of the other osxphotos code
# As an experiment, I've used mostly functions instead of classes (e.g. the report writer
# functions vs ReportWriter class used by export) and I've kept everything for import
# self-contained in this one file
from __future__ import annotations

import csv
import datetime
Expand Down Expand Up @@ -59,6 +56,12 @@
from .rich_progress import rich_progress
from .verbose import get_verbose_console, verbose_print

# Note: the style in this module is a bit different than much of the other osxphotos code
# As an experiment, I've used mostly functions instead of classes (e.g. the report writer
# functions vs ReportWriter class used by export) and I've kept everything for import
# self-contained in this one file


MetaData = namedtuple("MetaData", ["title", "description", "keywords", "location"])

OSXPHOTOS_ABOUT_STRING = f"Created by osxphotos version {__version__} (https://github.com/RhetTbull/osxphotos) on {datetime.datetime.now()}"
Expand Down Expand Up @@ -165,7 +168,8 @@ def import_photo(
Args:
filepath: path to the file to import
dup_check: enable or disable Photo's duplicate check on import
verbose: Callable"""
verbose: Callable
"""
if imported := PhotosLibrary().import_photos(
[filepath], skip_duplicate_check=not dup_check
):
Expand Down Expand Up @@ -207,7 +211,7 @@ def add_photo_to_albums(
split_folder: str,
exiftool_path: Path,
verbose: Callable[..., None],
):
) -> list[str]:
"""Add photo to one or more albums"""
albums = []
for a in album:
Expand All @@ -225,6 +229,7 @@ def add_photo_to_albums(
a, verbose=verbose, split_folder=split_folder, rich=True
)
photos_album.add(photo)
return albums


def clear_photo_metadata(photo: Photo, filepath: Path, verbose: Callable[..., None]):
Expand Down Expand Up @@ -348,7 +353,11 @@ def location_from_file(
return latitude, longitude


def set_photo_metadata(photo: Photo, metadata: MetaData, merge_keywords: bool):
def set_photo_metadata(
photo: Photo,
metadata: MetaData,
merge_keywords: bool,
) -> MetaData:
"""Set metadata (title, description, keywords) for a Photo object"""
photo.title = metadata.title
photo.description = metadata.description
Expand All @@ -358,6 +367,7 @@ def set_photo_metadata(photo: Photo, metadata: MetaData, merge_keywords: bool):
keywords.extend(old_keywords)
keywords = list(set(keywords))
photo.keywords = keywords
return MetaData(metadata.title, metadata.description, keywords, metadata.location)


def set_photo_metadata_from_exiftool(
Expand All @@ -366,12 +376,12 @@ def set_photo_metadata_from_exiftool(
exiftool_path: str,
merge_keywords: bool,
verbose: Callable[..., None],
):
) -> MetaData:
"""Set photo's metadata by reading metadata form file with exiftool"""
verbose(f"Setting metadata and location from EXIF for [filename]{filepath.name}[/]")
metadata = metadata_from_file(filepath, exiftool_path)
if any([metadata.title, metadata.description, metadata.keywords]):
set_photo_metadata(photo, metadata, merge_keywords)
metadata = set_photo_metadata(photo, metadata, merge_keywords)
verbose(f"Set metadata for [filename]{filepath.name}[/]:")
verbose(
f"title='{metadata.title}', description='{metadata.description}', keywords={metadata.keywords}"
Expand All @@ -387,6 +397,7 @@ def set_photo_metadata_from_exiftool(
)
else:
verbose(f"No location to set for [filename]{filepath.name}[/]")
return metadata


def set_photo_title(
Expand All @@ -396,7 +407,7 @@ def set_photo_title(
title_template: str,
exiftool_path: str,
verbose: Callable[..., None],
):
) -> str:
"""Set title of photo"""
title_text = render_photo_template(
filepath, relative_filepath, title_template, exiftool_path
Expand All @@ -412,6 +423,9 @@ def set_photo_title(
f"Setting title of photo [filename]{filepath.name}[/] to '{title_text[0]}'"
)
photo.title = title_text[0]
return title_text[0]
else:
return ""


def set_photo_description(
Expand All @@ -421,7 +435,7 @@ def set_photo_description(
description_template: str,
exiftool_path: str,
verbose: Callable[..., None],
):
) -> str:
"""Set description of photo"""
description_text = render_photo_template(
filepath, relative_filepath, description_template, exiftool_path
Expand All @@ -437,6 +451,9 @@ def set_photo_description(
f"Setting description of photo [filename]{filepath.name}[/] to '{description_text[0]}'"
)
photo.description = description_text[0]
return description_text[0]
else:
return ""


def set_photo_keywords(
Expand All @@ -447,7 +464,7 @@ def set_photo_keywords(
exiftool_path: str,
merge: bool,
verbose: Callable[..., None],
):
) -> list[str]:
"""Set keywords of photo"""
keywords = []
for keyword in keyword_template:
Expand All @@ -460,24 +477,26 @@ def set_photo_keywords(
keywords = list(set(keywords))
verbose(f"Setting keywords of photo [filename]{filepath.name}[/] to {keywords}")
photo.keywords = keywords
return keywords


def set_photo_location(
photo: Photo,
filepath: Path,
location: Tuple[float, float],
verbose: Callable[..., None],
):
) -> tuple[float, float]:
"""Set location of photo"""
verbose(
f"Setting location of photo [filename]{filepath.name}[/] to {location[0]}, {location[1]}"
)
photo.location = location
return location


def set_photo_date_from_filename(
photo: Photo, filepath: Path, parse_date: str, verbose: Callable[..., None]
):
) -> datetime.datetime | None:
"""Set date of photo from filename"""
# TODO: handle timezone (use code from timewarp), for now convert timezone to local timezone
try:
Expand All @@ -495,11 +514,12 @@ def set_photo_date_from_filename(
verbose(
f"[warning]Could not parse date from filename [filename]{filepath.name}[/][/]"
)
return
return None
verbose(
f"Setting date of photo [filename]{filepath.name}[/] to [time]{date.strftime('%Y-%m-%d %H:%M:%S')}[/]"
)
photo.date = date
return date


def get_relative_filepath(filepath: Path, relative_to: Optional[str]) -> Path:
Expand Down Expand Up @@ -619,6 +639,13 @@ def deserialize(cls, json_string: str) -> "ReportRecord":
)
return cls(**dict_data)

def update_from_metadata(self, metadata: MetaData):
"""Update a ReportRecord with data from a MetaData"""
self.title = metadata.title
self.description = metadata.description
self.keywords = metadata.keywords
self.location = metadata.location

def asdict(self):
return asdict(self)

Expand All @@ -632,14 +659,8 @@ def asjsondict(self):

def update_report_record(report_record: ReportRecord, photo: Photo, filepath: Path):
"""Update a ReportRecord with data from a Photo"""
report_record.albums = [a.title for a in photo.albums]
report_record.description = photo.description
report_record.filename = filepath.name
report_record.filepath = filepath
report_record.imported = True
report_record.keywords = photo.keywords
report_record.location = photo.location
report_record.title = photo.title
report_record.uuid = photo.uuid

return report_record
Expand Down Expand Up @@ -1482,26 +1503,41 @@ def import_cli(
error_count += 1
report_data[filepath].error = True
continue
report_data[filepath].imported = True
imported_count += 1

if clear_metadata:
clear_photo_metadata(photo, filepath, verbose)
report_data[filepath].title = ""
report_data[filepath].description = ""
report_data[filepath].keywords = []

if clear_location:
clear_photo_location(photo, filepath, verbose)
report_data[filepath].location = (None, None)

if exiftool:
set_photo_metadata_from_exiftool(
photo, filepath, exiftool_path, merge_keywords, verbose
metadata = set_photo_metadata_from_exiftool(
photo,
filepath,
exiftool_path,
merge_keywords,
verbose,
)
report_data[filepath].update_from_metadata(metadata)

if title:
set_photo_title(
photo, filepath, relative_filepath, title, exiftool_path, verbose
report_data.title = set_photo_title(
photo,
filepath,
relative_filepath,
title,
exiftool_path,
verbose,
)

if description:
set_photo_description(
report_data.description = set_photo_description(
photo,
filepath,
relative_filepath,
Expand All @@ -1511,7 +1547,7 @@ def import_cli(
)

if keyword:
set_photo_keywords(
report_data.keywords = set_photo_keywords(
photo,
filepath,
relative_filepath,
Expand All @@ -1522,13 +1558,16 @@ def import_cli(
)

if location:
set_photo_location(photo, filepath, location, verbose)
report_data.location = set_photo_location(
photo, filepath, location, verbose
)

if parse_date:
set_photo_date_from_filename(photo, filepath, parse_date, verbose)
# TODO: ReportRecord doesn't currently record date

if album:
add_photo_to_albums(
report_data[filepath].albums = add_photo_to_albums(
photo,
filepath,
relative_filepath,
Expand All @@ -1549,6 +1588,7 @@ def import_cli(
f"[error]Error running post-function [italic]{function[1]}[/italic]: {e}"
)

# update report data
update_report_record(report_data[filepath], photo, filepath)
import_db.set(str(filepath), report_data[filepath])

Expand Down

0 comments on commit e88cfff

Please sign in to comment.