diff --git a/README.md b/README.md index fae389be..201720ea 100644 --- a/README.md +++ b/README.md @@ -428,6 +428,34 @@ Substitution Description {modified.hour} 2-digit hour of the file modification time {modified.min} 2-digit minute of the file modification time {modified.sec} 2-digit second of the file modification time +{today.date} Current date in iso format, e.g. + '2020-03-22' +{today.year} 4-digit year of current date +{today.yy} 2-digit year of current date +{today.mm} 2-digit month of the current date (zero + padded) +{today.month} Month name in user's locale of the current + date +{today.mon} Month abbreviation in the user's locale of + the current date +{today.dd} 2-digit day of the month (zero padded) of + current date +{today.dow} Day of week in user's locale of the current + date +{today.doy} 3-digit day of year (e.g Julian day) of + current date, starting from 1 (zero padded) +{today.hour} 2-digit hour of the current date +{today.min} 2-digit minute of the current date +{today.sec} 2-digit second of the current date +{today.strftime} Apply strftime template to current + date/time. Should be used in form + {today.strftime,TEMPLATE} where TEMPLATE is + a valid strftime template, e.g. + {today.strftime,%Y-%U} would result in year- + week number of year: '2020-23'. If used with + no template will return null value. See + https://strftime.org/ for help on strftime + templates. {place.name} Place name from the photo's reverse geolocation data, as displayed in Photos {place.country_code} The ISO country code from the photo's @@ -1461,7 +1489,6 @@ Example: find your "best" photo of food ### Template Substitutions The following substitutions are availabe for use with `PhotoInfo.render_template()` - | Substitution | Description | |--------------|-------------| |{name}|Current filename of the photo| @@ -1492,6 +1519,19 @@ The following substitutions are availabe for use with `PhotoInfo.render_template |{modified.hour}|2-digit hour of the file modification time| |{modified.min}|2-digit minute of the file modification time| |{modified.sec}|2-digit second of the file modification time| +|{today.date}|Current date in iso format, e.g. '2020-03-22'| +|{today.year}|4-digit year of current date| +|{today.yy}|2-digit year of current date| +|{today.mm}|2-digit month of the current date (zero padded)| +|{today.month}|Month name in user's locale of the current date| +|{today.mon}|Month abbreviation in the user's locale of the current date| +|{today.dd}|2-digit day of the month (zero padded) of current date| +|{today.dow}|Day of week in user's locale of the current date| +|{today.doy}|3-digit day of year (e.g Julian day) of current date, starting from 1 (zero padded)| +|{today.hour}|2-digit hour of the current date| +|{today.min}|2-digit minute of the current date| +|{today.sec}|2-digit second of the current date| +|{today.strftime}|Apply strftime template to current date/time. Should be used in form {today.strftime,TEMPLATE} where TEMPLATE is a valid strftime template, e.g. {today.strftime,%Y-%U} would result in year-week number of year: '2020-23'. If used with no template will return null value. See https://strftime.org/ for help on strftime templates.| |{place.name}|Place name from the photo's reverse geolocation data, as displayed in Photos| |{place.country_code}|The ISO country code from the photo's reverse geolocation data| |{place.name.country}|Country name from the photo's reverse geolocation data| diff --git a/osxphotos/__init__.py b/osxphotos/__init__.py index 9c0a6b49..a23aecfa 100644 --- a/osxphotos/__init__.py +++ b/osxphotos/__init__.py @@ -3,14 +3,9 @@ import logging from ._version import __version__ from .photoinfo import PhotoInfo from .photosdb import PhotosDB -from .utils import _set_debug, _debug, _get_logger +from .phototemplate import PhotoTemplate +from .utils import _debug, _get_logger, _set_debug -# TODO: find edited photos: see https://github.com/orangeturtle739/photos-export/blob/master/extract_photos.py # TODO: Add test for imageTimeZoneOffsetSeconds = None -# TODO: Fix command line so multiple --keyword, etc. are AND (instead of OR as they are in .photos()) -# Or fix the help text to match behavior # TODO: Add test for __str__ and to_json -# TODO: fix docstrings # TODO: Add special albums and magic albums -# TODO: cleanup os.path and pathlib code (import pathlib and also from pathlib import Path) - diff --git a/osxphotos/_version.py b/osxphotos/_version.py index 2beff064..9386875c 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,3 +1,3 @@ """ version info """ -__version__ = "0.29.27" +__version__ = "0.29.28" diff --git a/osxphotos/phototemplate.py b/osxphotos/phototemplate.py index 6d345c7a..ac4bc95f 100644 --- a/osxphotos/phototemplate.py +++ b/osxphotos/phototemplate.py @@ -9,6 +9,7 @@ # 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. +import datetime import locale import os import re @@ -59,6 +60,23 @@ TEMPLATE_SUBSTITUTIONS = { # + "{modified.strftime,%Y-%U} would result in year-week number of year: '2020-23'. " # + "If used with no template will return null value. " # + "See https://strftime.org/ for help on strftime templates.", + "{today.date}": "Current date in iso format, e.g. '2020-03-22'", + "{today.year}": "4-digit year of current date", + "{today.yy}": "2-digit year of current date", + "{today.mm}": "2-digit month of the current date (zero padded)", + "{today.month}": "Month name in user's locale of the current date", + "{today.mon}": "Month abbreviation in the user's locale of the current date", + "{today.dd}": "2-digit day of the month (zero padded) of current date", + "{today.dow}": "Day of week in user's locale of the current date", + "{today.doy}": "3-digit day of year (e.g Julian day) of current date, starting from 1 (zero padded)", + "{today.hour}": "2-digit hour of the current date", + "{today.min}": "2-digit minute of the current date", + "{today.sec}": "2-digit second of the current date", + "{today.strftime}": "Apply strftime template to current date/time. Should be used in form " + + "{today.strftime,TEMPLATE} where TEMPLATE is a valid strftime template, e.g. " + + "{today.strftime,%Y-%U} would result in year-week number of year: '2020-23'. " + + "If used with no template will return null value. " + + "See https://strftime.org/ for help on strftime templates.", "{place.name}": "Place name from the photo's reverse geolocation data, as displayed in Photos", "{place.country_code}": "The ISO country code from the photo's reverse geolocation data", "{place.name.country}": "Country name from the photo's reverse geolocation data", @@ -102,6 +120,10 @@ class PhotoTemplate: """ self.photo = photo + # holds value of current date/time for {today.x} fields + # gets initialized in get_template_value + self.today = None + def render(self, template, none_str="_", path_sep=None): """ Render a filename or directory template @@ -258,6 +280,10 @@ class PhotoTemplate: ValueError if no rule exists for field. """ + # initialize today with current date/time if needed + if self.today is None: + self.today = datetime.datetime.now() + # must be a valid keyword if field == "name": return pathlib.Path(self.photo.filename).stem @@ -404,6 +430,51 @@ class PhotoTemplate: # else: # return None + if field == "today.date": + return DateTimeFormatter(self.today).date + + if field == "today.year": + return DateTimeFormatter(self.today).year + + if field == "today.yy": + return DateTimeFormatter(self.today).yy + + if field == "today.mm": + return DateTimeFormatter(self.today).mm + + if field == "today.month": + return DateTimeFormatter(self.today).month + + if field == "today.mon": + return DateTimeFormatter(self.today).mon + + if field == "today.dd": + return DateTimeFormatter(self.today).dd + + if field == "today.dow": + return DateTimeFormatter(self.today).dow + + if field == "today.doy": + return DateTimeFormatter(self.today).doy + + if field == "today.hour": + return DateTimeFormatter(self.today).hour + + if field == "today.min": + return DateTimeFormatter(self.today).min + + if field == "today.sec": + return DateTimeFormatter(self.today).sec + + if field == "today.strftime": + if default: + try: + return self.today.strftime(default) + except: + raise ValueError(f"Invalid strftime template: '{default}'") + else: + return None + if field == "place.name": return self.photo.place.name if self.photo.place else None diff --git a/tests/test_template_today.py b/tests/test_template_today.py new file mode 100644 index 00000000..d0969ccc --- /dev/null +++ b/tests/test_template_today.py @@ -0,0 +1,70 @@ +import datetime + +import pytest + +PHOTOS_DB_PLACES = ( + "./tests/Test-Places-Catalina-10_15_1.photoslibrary/database/photos.db" +) + +DATETIME_TODAY = datetime.datetime(2020, 6, 21, 13, 0, 0) +""" Used to patch osxphotos.phototemplate.TODAY for testing """ + +UUID_DICT = { + "place_dc": "128FB4C6-0B16-4E7D-9108-FB2E90DA1546", + "1_1_2": "1EB2B765-0765-43BA-A90C-0D0580E6172C", + "2_1_1": "D79B8D77-BFFC-460B-9312-034F2877D35B", + "0_2_0": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4", + "folder_album_1": "3DD2C897-F19E-4CA6-8C22-B027D5A71907", + "folder_album_no_folder": "D79B8D77-BFFC-460B-9312-034F2877D35B", + "mojave_album_1": "15uNd7%8RguTEgNPKHfTWw", +} + +TODAY_VALUES = { + "{today.date}": "2020-06-21", + "{today.year}": "2020", + "{today.yy}": "20", + "{today.mm}": "06", + "{today.month}": "June", + "{today.mon}": "Jun", + "{today.dd}": "21", + "{today.dow}": "Sunday", + "{today.doy}": "173", + "{today.hour}": "13", + "{today.min}": "00", + "{today.sec}": "00", +} + + +def test_subst_today(): + """ Test that substitutions are correct for {today.x}""" + import locale + import osxphotos + + locale.setlocale(locale.LC_ALL, "en_US") + photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB_PLACES) + photo = photosdb.photos(uuid=[UUID_DICT["place_dc"]])[0] + + photo_template = osxphotos.PhotoTemplate(photo) + photo_template.today = DATETIME_TODAY + + for template in TODAY_VALUES: + rendered, _ = photo_template.render(template) + assert rendered[0] == TODAY_VALUES[template] + + +def test_subst_strftime_today(): + """ Test that strftime substitutions are correct for {today.strftime}""" + import locale + import osxphotos + + locale.setlocale(locale.LC_ALL, "en_US") + photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB_PLACES) + photo = photosdb.photos(uuid=[UUID_DICT["place_dc"]])[0] + + photo_template = osxphotos.PhotoTemplate(photo) + photo_template.today = DATETIME_TODAY + rendered, unmatched = photo_template.render("{today.strftime,%Y-%m-%d-%H%M%S}") + assert rendered[0] == "2020-06-21-130000" + + rendered, unmatched = photo.render_template("{today.strftime}") + assert rendered[0] == "_"