Compare commits

..

8 Commits

Author SHA1 Message Date
Rhet Turnbull
d6fee89fd9 version bump 2020-06-23 12:07:07 -07:00
Rhet Turnbull
b8618cf272 Bug fix for issue #178 2020-06-23 12:01:20 -07:00
Rhet Turnbull
6b7c5d07fd Updated CHANGELOG.md 2020-06-22 07:22:19 -07:00
Rhet Turnbull
bd5ba702aa Closes #174 2020-06-22 07:14:10 -07:00
Rhet Turnbull
c8d76a89e4 Added today to template system, closes #167 2020-06-21 21:58:18 -07:00
Rhet Turnbull
a8e996e660 Minor refactoring in photoinfo.py 2020-06-21 12:06:25 -07:00
Rhet Turnbull
c68a5ab39f Updated CHANGELOG.md 2020-06-21 09:01:15 -07:00
Rhet Turnbull
1ebf995833 Bug fix for issue #172 2020-06-21 08:42:19 -07:00
9 changed files with 228 additions and 28 deletions

View File

@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v0.29.28](https://github.com/RhetTbull/osxphotos/compare/v0.29.26...v0.29.28)
> 22 June 2020
- Closes #174 [`#174`](https://github.com/RhetTbull/osxphotos/issues/174)
- Added today to template system, closes #167 [`#167`](https://github.com/RhetTbull/osxphotos/issues/167)
- Minor refactoring in photoinfo.py [`a8e996e`](https://github.com/RhetTbull/osxphotos/commit/a8e996e66072e94de93fd4ea78a456bc61831f52)
#### [v0.29.26](https://github.com/RhetTbull/osxphotos/compare/v0.29.25...v0.29.26)
> 21 June 2020
- Bug fix for issue #172 [`1ebf995`](https://github.com/RhetTbull/osxphotos/commit/1ebf99583397617f0d3a234c898beae1c14f5a63)
#### [v0.29.25](https://github.com/RhetTbull/osxphotos/compare/v0.29.24...v0.29.25)
> 21 June 2020
- More PhotoInfo.albums refactoring, closes #169 [`#169`](https://github.com/RhetTbull/osxphotos/issues/169)
#### [v0.29.24](https://github.com/RhetTbull/osxphotos/compare/v0.29.23...v0.29.24)
> 21 June 2020

View File

@@ -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|

View File

@@ -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)

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.29.25"
__version__ = "0.29.29"

View File

@@ -69,6 +69,7 @@ class PhotoInfo:
@property
def filename(self):
""" filename of the picture """
# sourcery off
if self.has_raw and self.raw_original:
# return name of the RAW file
# TODO: not yet implemented
@@ -89,8 +90,7 @@ class PhotoInfo:
seconds = self._info["imageTimeZoneOffsetSeconds"] or 0
delta = timedelta(seconds=seconds)
tz = timezone(delta)
imagedate_utc = imagedate.astimezone(tz=tz)
return imagedate_utc
return imagedate.astimezone(tz=tz)
@property
def date_modified(self):
@@ -101,8 +101,7 @@ class PhotoInfo:
seconds = self._info["imageTimeZoneOffsetSeconds"] or 0
delta = timedelta(seconds=seconds)
tz = timezone(delta)
imagedate_utc = imagedate.astimezone(tz=tz)
return imagedate_utc
return imagedate.astimezone(tz=tz)
else:
return None
@@ -494,12 +493,11 @@ class PhotoInfo:
self is not included in the returned list """
if self._info["burst"]:
burst_uuid = self._info["burstUUID"]
burst_photos = [
return [
PhotoInfo(db=self._db, uuid=u, info=self._db._dbphotos[u])
for u in self._db._dbphotos_burst[burst_uuid]
if u != self._uuid
]
return burst_photos
else:
return []

View File

@@ -798,7 +798,7 @@ class PhotosDB:
try:
self._dbphotos[uuid]["imageDate"] = datetime.fromtimestamp(row[5] + td)
except ValueError:
self._dbphotos[uuid]["imageDate"] = datetime.date(1970, 1, 1)
self._dbphotos[uuid]["imageDate"] = datetime(1970, 1, 1)
self._dbphotos[uuid]["mainRating"] = row[6]
self._dbphotos[uuid]["hasAdjustments"] = row[7]

View File

@@ -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

View File

@@ -503,7 +503,6 @@ class PlaceInfo5(PlaceInfo):
""" revgeoloc_bplist: a binary plist blob containing
a serialized PLRevGeoLocationInfo object """
self._bplist = revgeoloc_bplist
# todo: check for None?
self._plrevgeoloc = archiver.unarchive(revgeoloc_bplist)
self._process_place_info()
@@ -535,16 +534,23 @@ class PlaceInfo5(PlaceInfo):
@property
def address(self):
addr = self._plrevgeoloc.postalAddress
return PostalAddress(
street=addr._street,
sub_locality=addr._subLocality,
city=addr._city,
sub_administrative_area=addr._subAdministrativeArea,
state_province=addr._state,
postal_code=addr._postalCode,
country=addr._country,
iso_country_code=addr._ISOCountryCode,
)
if addr is not None:
postal_address = PostalAddress(
street=addr._street,
sub_locality=addr._subLocality,
city=addr._city,
sub_administrative_area=addr._subAdministrativeArea,
state_province=addr._state,
postal_code=addr._postalCode,
country=addr._country,
iso_country_code=addr._ISOCountryCode,
)
else:
postal_address = PostalAddress(
None, None, None, None, None, None, None, None
)
return postal_address
def _process_place_info(self):
""" Process sortedPlaceInfos to set self._name and self._names """
@@ -632,5 +638,5 @@ class PlaceInfo5(PlaceInfo):
"country_code": self.country_code,
"ishome": self.ishome,
"address_str": self.address_str,
"address": self.address._asdict(),
"address": self.address._asdict() if self.address is not None else None,
}

View File

@@ -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] == "_"