Refactored PhotosDB and CLI to require explicity passing the database to avoid non-deterministic behavior when last database can't be found. This may break existing code.

This commit is contained in:
Rhet Turnbull 2020-01-17 16:32:10 -08:00
parent ba1a2b32ad
commit ede56ffc31
6 changed files with 409 additions and 166 deletions

176
README.md
View File

@ -11,7 +11,7 @@
* [Example uses of the module](#example-uses-of-the-module)
* [Module Interface](#module-interface)
+ [PhotosDB](#photosdb)
- [Open the default Photos library](#open-the-default-photos-library)
- [Read a Photos library database](#read-a-photos-library-database)
- [Open System Photos library](#open-system-photos-library)
- [Open a specific Photos library](#open-a-specific-photos-library)
- [`keywords`](#keywords)
@ -68,6 +68,7 @@
* [Implementation Notes](#implementation-notes)
* [Dependencies](#dependencies)
* [Acknowledgements](#acknowledgements)
## What is osxphotos?
OSXPhotos provides the ability to interact with and query Apple's Photos.app library database on MacOS. Using this module you can query the Photos database for information about the photos stored in a Photos library on your Mac--for example, file name, file path, and metadata such as keywords/tags, persons/faces, albums, etc. You can also easily export both the original and edited photos.
@ -123,7 +124,7 @@ To get help on a specific command, use `osxphotos help <command_name>`
Example: `osxphotos help export`
```
Usage: osxphotos help [OPTIONS] DEST
Usage: osxphotos help [OPTIONS] [PHOTOS_LIBRARY]... DEST
Export photos from the Photos database. Export path DEST is required.
Optionally, query the Photos database using 1 or more search options; if
@ -132,81 +133,101 @@ Usage: osxphotos help [OPTIONS] DEST
photos will be exported.
Options:
--keyword TEXT Search for keyword(s).
--person TEXT Search for person(s).
--album TEXT Search for album(s).
--uuid TEXT Search for UUID(s).
--title TEXT Search for TEXT in title of photo.
--no-title Search for photos with no title.
--description TEXT Search for TEXT in description of photo.
--no-description Search for photos with no description.
--uti TEXT Search for photos whose uniform type identifier (UTI)
matches TEXT
-i, --ignore-case Case insensitive search for title or description. Does
not apply to keyword, person, or album.
--edited Search for photos that have been edited.
--external-edit Search for photos edited in external editor.
--favorite Search for photos marked favorite.
--not-favorite Search for photos not marked favorite.
--hidden Search for photos marked hidden.
--not-hidden Search for photos not marked hidden.
--burst Search for photos that were taken in a burst.
--not-burst Search for photos that are not part of a burst.
--live Search for Apple live photos
--not-live Search for photos that are not Apple live photos
--shared Search for photos in shared iCloud album (Photos 5
only).
--not-shared Search for photos not in shared iCloud album (Photos 5
only).
-V, --verbose Print verbose output.
--overwrite Overwrite existing files. Default behavior is to add
(1), (2), etc to filename if file already exists. Use
this with caution as it may create name collisions on
export. (e.g. if two files happen to have the same name)
--export-by-date Automatically create output folders to organize photos
by date created (e.g. DEST/2019/12/20/photoname.jpg).
--export-edited Also export edited version of photo if an edited version
exists. Edited photo will be named in form of
"photoname_edited.ext"
--export-bursts If a photo is a burst photo export all associated burst
images in the library.
--export-live If a photo is a live photo export the associated live
video component. Live video will have same name as
photo but with .mov extension.
--original-name Use photo's original filename instead of current
filename for export.
--sidecar Create json sidecar for each photo exported in format
useable by exiftool (https://exiftool.org/) The sidecar
file can be used to apply metadata to the file with
exiftool, for example: "exiftool -j=photoname.jpg.json
photoname.jpg" The sidecar file is named in format
photoname.ext.json where ext is extension of the photo
(e.g. jpg).
--only-movies Search only for movies (default searches both images and
movies).
--only-photos Search only for photos/images (default searches both
images and movies).
--download-missing Attempt to download missing photos from iCloud. The
current implementation uses Applescript to interact with
Photos to export the photo which will force Photos to
download from iCloud if the photo does not exist on
disk. This will be slow and will require internet
connection. This obviously only works if the Photos
library is synched to iCloud.
-h, --help Show this message and exit.
--db <Photos database path> Specify Photos database path.
--keyword TEXT Search for keyword(s).
--person TEXT Search for person(s).
--album TEXT Search for album(s).
--uuid TEXT Search for UUID(s).
--title TEXT Search for TEXT in title of photo.
--no-title Search for photos with no title.
--description TEXT Search for TEXT in description of photo.
--no-description Search for photos with no description.
--uti TEXT Search for photos whose uniform type identifier
(UTI) matches TEXT
-i, --ignore-case Case insensitive search for title or
description. Does not apply to keyword, person,
or album.
--edited Search for photos that have been edited.
--external-edit Search for photos edited in external editor.
--favorite Search for photos marked favorite.
--not-favorite Search for photos not marked favorite.
--hidden Search for photos marked hidden.
--not-hidden Search for photos not marked hidden.
--burst Search for photos that were taken in a burst.
--not-burst Search for photos that are not part of a burst.
--live Search for Apple live photos
--not-live Search for photos that are not Apple live
photos
--shared Search for photos in shared iCloud album
(Photos 5 only).
--not-shared Search for photos not in shared iCloud album
(Photos 5 only).
-V, --verbose Print verbose output.
--overwrite Overwrite existing files. Default behavior is
to add (1), (2), etc to filename if file
already exists. Use this with caution as it may
create name collisions on export. (e.g. if two
files happen to have the same name)
--export-by-date Automatically create output folders to organize
photos by date created (e.g.
DEST/2019/12/20/photoname.jpg).
--export-edited Also export edited version of photo if an
edited version exists. Edited photo will be
named in form of "photoname_edited.ext"
--export-bursts If a photo is a burst photo export all
associated burst images in the library.
--export-live If a photo is a live photo export the
associated live video component. Live video
will have same name as photo but with .mov
extension.
--original-name Use photo's original filename instead of
current filename for export.
--sidecar Create JSON sidecar for each photo exported in
format useable by exiftool
(https://exiftool.org/) The sidecar file can be
used to apply metadata to the file with
exiftool, for example: "exiftool
-j=photoname.jpg.json photoname.jpg" The
sidecar file is named in format
photoname.ext.json where ext is extension of
the photo (e.g. jpg). Note: this does not
create an XMP sidecar as used by Lightroom,
etc.
--only-movies Search only for movies (default searches both
images and movies).
--only-photos Search only for photos/images (default searches
both images and movies).
--download-missing Attempt to download missing photos from iCloud.
The current implementation uses Applescript to
interact with Photos to export the photo which
will force Photos to download from iCloud if
the photo does not exist on disk. This will be
slow and will require internet connection. This
obviously only works if the Photos library is
synched to iCloud.
-h, --help Show this message and exit.
```
Example: export all photos to ~/Desktop/export, including edited versions and live photo movies, group in folders by date created
`osxphotos export --export-edited --export-live --export-by-date ~/Pictures/Photos\ Library.photoslibrary ~/Desktop/export`
**Note**: Photos library/database path can also be specified using --db option:
`osxphotos export --export-edited --export-live --export-by-date --db ~/Pictures/Photos\ Library.photoslibrary ~/Desktop/export`
Example: find all photos with keyword "Kids" and output results to json file named results.json:
`osxphotos query --keyword Kids --json >results.json`
`osxphotos query --keyword Kids --json ~/Pictures/Photos\ Library.photoslibrary >results.json`
## Example uses of the module
```python
import os.path
import osxphotos
def main():
photosdb = osxphotos.PhotosDB()
db = os.path.expanduser("~/Pictures/Photos Library.photoslibrary")
photosdb = osxphotos.PhotosDB(db)
print(photosdb.keywords)
print(photosdb.persons)
print(photosdb.albums)
@ -245,12 +266,12 @@ if __name__ == "__main__":
otherwise, export the original version """
import os.path
import osxphotos
def main():
photosdb = osxphotos.PhotosDB()
db = os.path.expanduser("~/Pictures/Photos Library.photoslibrary")
photosdb = osxphotos.PhotosDB(db)
photos = photosdb.photos()
export_path = os.path.expanduser("~/Desktop/export")
@ -274,17 +295,16 @@ if __name__ == "__main__":
### PhotosDB
#### Open the default Photos library
#### Read a Photos library database
```python
osxphotos.PhotosDB()
osxphotos.PhotosDB(path)
osxphotos.PhotosDB(dbfile=path)
```
Opens the Photos library database and returns a PhotosDB object.
Reads the Photos library database and returns a PhotosDB object.
Optionally, pass the path to a specific database file or a Photos library (e.g. "/Users/smith/Pictures/Photos Library.photoslibrary" or "/Users/smith/Pictures/Photos Library.photoslibrary/database/photos.db"). Path to photos library may be passed **either** as first argument **or** as named argument `dbfile`. If path is not passed, PhotosDB will attempt to open the default Photos library (that is, the last library that was opened in Photos.app which may or may not also be the System Photos Library). **Note**: Users may specify a different library to open by holding down the *option* key while opening Photos.app.
Pass the path to a Photos library or to a specific database file (e.g. "/Users/smith/Pictures/Photos Library.photoslibrary" or "/Users/smith/Pictures/Photos Library.photoslibrary/database/photos.db"). Normally, it's recommended you pass the path the .photoslibrary folder, not the actual database path. The latter option is provided for debugging -- e.g. for reading a database file if you don't have the entire library. Path to photos library may be passed **either** as first argument **or** as named argument `dbfile`. **Note**: In Photos, users may specify a different library to open by holding down the *option* key while opening Photos.app. See also [get_last_library_path](#get_last_library_path) and [get_system_library_path](#get_system_library_path)
If an invalid path is passed, PhotosDB will raise `ValueError` exception.
@ -292,8 +312,7 @@ Open the default (last opened) Photos library. (E.g. this is the library that wo
```python
import osxphotos
photosdb = osxphotos.PhotosDB()
photosdb = osxphotos.PhotosDB(osxphotos.utils.get_last_library_path())
```
#### Open System Photos library
@ -516,7 +535,7 @@ For example, in my library, Photos says I have 19,386 photos and 474 movies. Ho
```python
>>> import osxphotos
>>> photosdb = osxphotos.PhotosDB()
>>> photosdb = osxphotos.PhotosDB("/Users/smith/Pictures/Photos Library.photoslibrary")
>>> photos = photosdb.photos()
>>> len(photos)
25002
@ -627,7 +646,7 @@ Example below gets list of all photos that are bursts, selects one of of them an
```python
>>> import osxphotos
>>> photosdb = osxphotos.PhotosDB()
>>> photosdb = osxphotos.PhotosDB("/Users/smith/Pictures/Photos Library.photoslibrary")
>>> bursts = [p for p in photosdb.photos() if p.burst]
>>> burst_photo = bursts[5]
>>> len(burst_photo.burst_photos)
@ -671,7 +690,7 @@ The json sidecar file can be used by exiftool to apply the metadata from the jso
```python
import osxphotos
photosdb = osxphotos.PhotosDB()
photosdb = osxphotos.PhotosDB("/Users/smith/Pictures/Photos Library.photoslibrary")
photos = photosdb.photos()
photos[0].export("/tmp","photo_name.jpg",sidecar=True)
```
@ -721,7 +740,8 @@ Checks to see if path exists, if it does, do nothing and return path. If path do
import osxphotos
def main():
photosdb = osxphotos.PhotosDB()
photosdb = osxphotos.PhotosDB("/Users/smith/Pictures/Photos Library.photoslibrary")
print(f"db file = {photosdb.db_path}")
print(f"db version = {photosdb.db_version}")

View File

@ -1,8 +1,14 @@
import osxphotos
import os.path
def main():
photosdb = osxphotos.PhotosDB()
db = osxphotos.utils.get_system_library_path()
if db is None:
# Note: get_system_library_path only works on MacOS 10.15+
db = os.path.expanduser("~/Pictures/Photos Library.photoslibrary")
photosdb = osxphotos.PhotosDB(db)
print(f"db file = {photosdb.db_path}")
print(f"db version = {photosdb.db_version}")

View File

@ -8,7 +8,8 @@ import osxphotos
def main():
photosdb = osxphotos.PhotosDB()
db = os.path.expanduser("~/Pictures/Photos Library.photoslibrary")
photosdb = osxphotos.PhotosDB(db)
photos = photosdb.photos()
export_path = os.path.expanduser("~/Desktop/export")

View File

@ -19,6 +19,42 @@ from .utils import create_path_by_date, _copy_file
# TODO: add search for filename
def get_photos_db(*db_options):
""" Return path to photos db, select first non-None arg
"""
if db_options:
for db in db_options:
if db is not None:
return db
# _list_libraries()
return None
# if get here, no valid database paths passed, so ask user
# _, major, _ = osxphotos.utils._get_os_version()
# last_lib = osxphotos.utils.get_last_library_path()
# if last_lib is not None:
# db = last_lib
# return db
# sys_lib = None
# if int(major) >= 15:
# sys_lib = osxphotos.utils.get_system_library_path()
# if sys_lib is not None:
# db = sys_lib
# return db
# db = os.path.expanduser("~/Pictures/Photos Library.photoslibrary")
# if os.path.isdir(db):
# return db
# else:
# return None ### TODO: put list here
# Click CLI object & context settings
class CLI_Obj:
def __init__(self, db=None, json=False, debug=False):
if debug:
@ -36,7 +72,8 @@ CTX_SETTINGS = dict(help_option_names=["-h", "--help"])
required=False,
metavar="<Photos database path>",
default=None,
help="Specify database file.",
help="Specify Photos database path.",
type=click.Path(exists=True),
)
@click.option(
"--json",
@ -53,49 +90,153 @@ def cli(ctx, db, json, debug):
@cli.command()
@click.option(
"--db",
required=False,
metavar="<Photos database path>",
default=None,
help="Specify Photos database path.",
type=click.Path(exists=True),
)
@click.option(
"--json",
"json_",
required=False,
is_flag=True,
default=False,
help="Print output in JSON format.",
)
@click.argument("photos_library", nargs=-1, type=click.Path(exists=True))
@click.pass_obj
def keywords(cli_obj):
@click.pass_context
def keywords(ctx, cli_obj, db, json_, photos_library):
""" Print out keywords found in the Photos library. """
photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None:
click.echo(cli.commands["keywords"].get_help(ctx))
click.echo("\n\nLocated the following Photos library databases: ")
_list_libraries()
return
photosdb = osxphotos.PhotosDB(dbfile=db)
keywords = {"keywords": photosdb.keywords_as_dict}
if cli_obj.json:
if json_ or cli_obj.json:
click.echo(json.dumps(keywords))
else:
click.echo(yaml.dump(keywords, sort_keys=False))
@cli.command()
@click.option(
"--db",
required=False,
metavar="<Photos database path>",
default=None,
help="Specify Photos database path.",
type=click.Path(exists=True),
)
@click.option(
"--json",
"json_",
required=False,
is_flag=True,
default=False,
help="Print output in JSON format.",
)
@click.argument("photos_library", nargs=-1, type=click.Path(exists=True))
@click.pass_obj
def albums(cli_obj):
@click.pass_context
def albums(ctx, cli_obj, db, json_, photos_library):
""" Print out albums found in the Photos library. """
photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None:
click.echo(cli.commands["albums"].get_help(ctx))
click.echo("\n\nLocated the following Photos library databases: ")
_list_libraries()
return
photosdb = osxphotos.PhotosDB(dbfile=db)
albums = {"albums": photosdb.albums_as_dict}
if photosdb.db_version >= _PHOTOS_5_VERSION:
albums["shared albums"] = photosdb.albums_shared_as_dict
if cli_obj.json:
if json_ or cli_obj.json:
click.echo(json.dumps(albums))
else:
click.echo(yaml.dump(albums, sort_keys=False))
@cli.command()
@click.option(
"--db",
required=False,
metavar="<Photos database path>",
default=None,
help="Specify Photos database path.",
type=click.Path(exists=True),
)
@click.option(
"--json",
"json_",
required=False,
is_flag=True,
default=False,
help="Print output in JSON format.",
)
@click.argument("photos_library", nargs=-1, type=click.Path(exists=True))
@click.pass_obj
def persons(cli_obj):
@click.pass_context
def persons(ctx, cli_obj, db, json_, photos_library):
""" Print out persons (faces) found in the Photos library. """
photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None:
click.echo(cli.commands["persons"].get_help(ctx))
click.echo("\n\nLocated the following Photos library databases: ")
_list_libraries()
return
photosdb = osxphotos.PhotosDB(dbfile=db)
persons = {"persons": photosdb.persons_as_dict}
if cli_obj.json:
if json_ or cli_obj.json:
click.echo(json.dumps(persons))
else:
click.echo(yaml.dump(persons, sort_keys=False))
@cli.command()
@click.option(
"--db",
required=False,
metavar="<Photos database path>",
default=None,
help="Specify Photos database path.",
type=click.Path(exists=True),
)
@click.option(
"--json",
"json_",
required=False,
is_flag=True,
default=False,
help="Print output in JSON format.",
)
@click.argument("photos_library", nargs=-1, type=click.Path(exists=True))
@click.pass_obj
def info(cli_obj):
@click.pass_context
def info(ctx, cli_obj, db, json_, photos_library):
""" Print out descriptive info of the Photos library database. """
pdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None:
click.echo(cli.commands["info"].get_help(ctx))
click.echo("\n\nLocated the following Photos library databases: ")
_list_libraries()
return
pdb = osxphotos.PhotosDB(dbfile=db)
info = {}
info["database_path"] = pdb.db_path
info["database_version"] = pdb.db_version
@ -146,61 +287,116 @@ def info(cli_obj):
info["persons_count"] = len(persons)
info["persons"] = persons
if cli_obj.json:
if cli_obj.json or json_:
click.echo(json.dumps(info))
else:
click.echo(yaml.dump(info, sort_keys=False))
@cli.command()
@click.option(
"--db",
required=False,
metavar="<Photos database path>",
default=None,
help="Specify Photos database path.",
type=click.Path(exists=True),
)
@click.option(
"--json",
"json_",
required=False,
is_flag=True,
default=False,
help="Print output in JSON format.",
)
@click.argument("photos_library", nargs=-1, type=click.Path(exists=True))
@click.pass_obj
@click.pass_context
def dump(ctx, cli_obj, db, json_, photos_library):
""" Print list of all photos & associated info from the Photos library. """
db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None:
click.echo(cli.commands["dump"].get_help(ctx))
click.echo("\n\nLocated the following Photos library databases: ")
_list_libraries()
return
pdb = osxphotos.PhotosDB(dbfile=db)
photos = pdb.photos(movies=True)
print_photo_info(photos, json_ or cli_obj.json)
@cli.command(name="list")
@click.option(
"--json",
"json_",
required=False,
is_flag=True,
default=False,
help="Print output in JSON format.",
)
@click.pass_obj
def dump(cli_obj, json):
""" Print list of all photos & associated info from the Photos library. """
pdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
photos = pdb.photos(movies=True)
print_photo_info(photos, cli_obj.json or json)
@cli.command(name="list")
@click.pass_obj
def list_libraries(cli_obj):
@click.pass_context
def list_libraries(ctx, cli_obj, json_):
""" Print list of Photos libraries found on the system. """
photo_libs = osxphotos.utils.list_photo_libraries()
sys_lib = None
_, major, _ = osxphotos.utils._get_os_version()
if int(major) >= 15:
sys_lib = osxphotos.utils.get_system_library_path()
_list_libraries(json_=json_ or cli_obj.json)
def _list_libraries(json_=False):
""" Print list of Photos libraries found on the system.
If json_ == True, print output as JSON (default = False) """
photo_libs = osxphotos.utils.list_photo_libraries()
sys_lib = osxphotos.utils.get_system_library_path()
last_lib = osxphotos.utils.get_last_library_path()
last_lib_flag = sys_lib_flag = False
if json_:
libs = {
"photo_libraries": photo_libs,
"system_library": sys_lib,
"last_library": last_lib,
}
click.echo(json.dumps(libs))
else:
last_lib_flag = sys_lib_flag = False
for lib in photo_libs:
if lib == sys_lib:
click.echo(f"(*)\t{lib}")
sys_lib_flag = True
elif lib == last_lib:
click.echo(f"(#)\t{lib}")
last_lib_flag = True
else:
click.echo(f"\t{lib}")
for lib in photo_libs:
if lib == sys_lib:
click.echo(f"(*)\t{lib}")
sys_lib_flag = True
elif lib == last_lib:
click.echo(f"(#)\t{lib}")
last_lib_flag = True
else:
click.echo(f"\t{lib}")
if sys_lib_flag or last_lib_flag:
click.echo("\n")
if sys_lib_flag:
click.echo("(*)\tSystem Photos Library")
if last_lib_flag:
click.echo("(#)\tLast opened Photos Library")
if sys_lib_flag or last_lib_flag:
click.echo("\n")
if sys_lib_flag:
click.echo("(*)\tSystem Photos Library")
if last_lib_flag:
click.echo("(#)\tLast opened Photos Library")
@cli.command()
@click.option(
"--db",
required=False,
metavar="<Photos database path>",
default=None,
help="Specify Photos database path.",
type=click.Path(exists=True),
)
@click.option(
"--json",
"json_",
required=False,
is_flag=True,
default=False,
help="Print output in JSON format.",
)
@click.option("--keyword", default=None, multiple=True, help="Search for keyword(s).")
@click.option("--person", default=None, multiple=True, help="Search for person(s).")
@click.option("--album", default=None, multiple=True, help="Search for album(s).")
@ -296,18 +492,14 @@ def list_libraries(cli_obj):
is_flag=True,
help="Search only for photos/images (default searches both images and movies).",
)
@click.option(
"--json",
required=False,
is_flag=True,
default=False,
help="Print output in JSON format",
)
@click.argument("photos_library", nargs=-1, type=click.Path(exists=True))
@click.pass_obj
@click.pass_context
def query(
ctx,
cli_obj,
db,
photos_library,
keyword,
person,
album,
@ -317,7 +509,7 @@ def query(
description,
no_description,
ignore_case,
json,
json_,
edited,
external_edit,
favorite,
@ -429,8 +621,15 @@ def query(
if only_photos:
ismovie = False
db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None:
click.echo(cli.commands["query"].get_help(ctx))
click.echo("\n\nLocated the following Photos library databases: ")
_list_libraries()
return
photos = _query(
cli_obj,
db,
keyword,
person,
album,
@ -440,7 +639,6 @@ def query(
description,
no_description,
ignore_case,
json,
edited,
external_edit,
favorite,
@ -463,10 +661,18 @@ def query(
incloud,
not_incloud,
)
print_photo_info(photos, cli_obj.json or json)
print_photo_info(photos, cli_obj.json or json_)
@cli.command()
@click.option(
"--db",
required=False,
metavar="<Photos database path>",
default=None,
help="Specify Photos database path.",
type=click.Path(exists=True),
)
@click.option("--keyword", default=None, multiple=True, help="Search for keyword(s).")
@click.option("--person", default=None, multiple=True, help="Search for person(s).")
@click.option("--album", default=None, multiple=True, help="Search for album(s).")
@ -591,12 +797,15 @@ def query(
"the photo does not exist on disk. This will be slow and will require internet connection. "
"This obviously only works if the Photos library is synched to iCloud.",
)
@click.argument("dest", nargs=1)
@click.argument("photos_library", nargs=-1, type=click.Path(exists=True))
@click.argument("dest", nargs=1, type=click.Path(exists=True))
@click.pass_obj
@click.pass_context
def export(
ctx,
cli_obj,
db,
photos_library,
keyword,
person,
album,
@ -679,8 +888,15 @@ def export(
if only_photos:
ismovie = False
db = get_photos_db(*photos_library, db, cli_obj.db)
if db is None:
click.echo(cli.commands["export"].get_help(ctx))
click.echo("\n\nLocated the following Photos library databases: ")
_list_libraries()
return
photos = _query(
cli_obj,
db,
keyword,
person,
album,
@ -690,7 +906,6 @@ def export(
description,
no_description,
ignore_case,
json,
edited,
external_edit,
favorite,
@ -708,10 +923,10 @@ def export(
not_burst,
live,
not_live,
False, # cloudasset
False, # not_cloudasset
False, # incloud
False # not_incloud
False, # cloudasset
False, # not_cloudasset
False, # incloud
False, # not_incloud
)
if photos:
@ -739,7 +954,7 @@ def export(
export_edited,
original_name,
export_live,
download_missing
download_missing,
)
else:
for p in photos:
@ -753,7 +968,7 @@ def export(
export_edited,
original_name,
export_live,
download_missing
download_missing,
)
if export_path:
click.echo(f"Exported {p.filename} to {export_path}")
@ -855,7 +1070,7 @@ def print_photo_info(photos, json=False):
def _query(
cli_obj,
db,
keyword,
person,
album,
@ -865,7 +1080,6 @@ def _query(
description,
no_description,
ignore_case,
json,
edited,
external_edit,
favorite,
@ -895,7 +1109,7 @@ def _query(
# TODO: this is getting too hairy -- need to change to named args
photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
photosdb = osxphotos.PhotosDB(dbfile=db)
photos = photosdb.photos(
keywords=keyword,
persons=person,
@ -1031,7 +1245,9 @@ def export_photo(
)
return None
elif photo.ismissing and not photo.iscloudasset or not photo.incloud:
click.echo(f"Skipping missing {photo.filename}: not iCloud asset or missing from cloud")
click.echo(
f"Skipping missing {photo.filename}: not iCloud asset or missing from cloud"
)
return None
filename = None
@ -1089,7 +1305,7 @@ def export_photo(
_copy_file(src_live, str(dest_live))
else:
click.echo(f"Skipping missing live movie for {filename}")
click.echo(f"Skipping missing live movie for {filename}")
return photo_path

View File

@ -1,3 +1,3 @@
""" version info """
__version__ = "0.21.6"
__version__ = "0.22.0"

View File

@ -39,11 +39,11 @@ from .utils import _check_file_exists, _get_os_version, get_last_library_path, _
class PhotosDB:
""" Processes a Photos.app library database to extract information about photos """
def __init__(self, *args, dbfile=None):
""" create a new PhotosDB object """
""" path to photos library or database may be specified EITHER as first argument or as named argument dbfile=path """
""" optional: specify full path to photos library or photos.db as first argument """
""" optional: specify path to photos library or photos.db using named argument dbfile=path """
def __init__(self, *dbfile_, dbfile=None):
""" create a new PhotosDB object
path to photos library or database may be specified EITHER as first argument or as named argument dbfile=path
specify full path to photos library or photos.db as first argument
specify path to photos library or photos.db using named argument dbfile=path """
# Check OS version
system = platform.system()
@ -89,28 +89,28 @@ class PhotosDB:
logging.debug(f"dbfile = {dbfile}")
# get the path to photos library database
if args:
if dbfile_:
# got a library path as argument
if dbfile:
# shouldn't pass via both *args and dbfile=
raise TypeError(
f"photos database path must be specified as argument or named parameter dbfile but not both: args: {args}, dbfile: {dbfile}",
args,
dbfile_,
dbfile,
)
elif len(args) == 1:
dbfile = args[0]
elif len(dbfile_) == 1:
dbfile = dbfile_[0]
else:
raise TypeError(
f"__init__ takes only a single argument (photos database path): {args}",
args,
f"__init__ takes only a single argument (photos database path): {dbfile_}",
dbfile_,
)
elif dbfile is None:
# no args and dbfile not passed, try to get last opened library
library_path = get_last_library_path()
if not library_path:
raise FileNotFoundError("could not get library path")
dbfile = os.path.join(library_path, "database/photos.db")
raise TypeError(
f"photos database path must be specified as argument or named parameter dbfile",
dbfile_,
dbfile,
)
if os.path.isdir(dbfile):
# passed a directory, assume it's a photoslibrary