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:
176
README.md
176
README.md
@@ -11,7 +11,7 @@
|
|||||||
* [Example uses of the module](#example-uses-of-the-module)
|
* [Example uses of the module](#example-uses-of-the-module)
|
||||||
* [Module Interface](#module-interface)
|
* [Module Interface](#module-interface)
|
||||||
+ [PhotosDB](#photosdb)
|
+ [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 System Photos library](#open-system-photos-library)
|
||||||
- [Open a specific Photos library](#open-a-specific-photos-library)
|
- [Open a specific Photos library](#open-a-specific-photos-library)
|
||||||
- [`keywords`](#keywords)
|
- [`keywords`](#keywords)
|
||||||
@@ -68,6 +68,7 @@
|
|||||||
* [Implementation Notes](#implementation-notes)
|
* [Implementation Notes](#implementation-notes)
|
||||||
* [Dependencies](#dependencies)
|
* [Dependencies](#dependencies)
|
||||||
* [Acknowledgements](#acknowledgements)
|
* [Acknowledgements](#acknowledgements)
|
||||||
|
|
||||||
## What is osxphotos?
|
## 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.
|
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`
|
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.
|
Export photos from the Photos database. Export path DEST is required.
|
||||||
Optionally, query the Photos database using 1 or more search options; if
|
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.
|
photos will be exported.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--keyword TEXT Search for keyword(s).
|
--db <Photos database path> Specify Photos database path.
|
||||||
--person TEXT Search for person(s).
|
--keyword TEXT Search for keyword(s).
|
||||||
--album TEXT Search for album(s).
|
--person TEXT Search for person(s).
|
||||||
--uuid TEXT Search for UUID(s).
|
--album TEXT Search for album(s).
|
||||||
--title TEXT Search for TEXT in title of photo.
|
--uuid TEXT Search for UUID(s).
|
||||||
--no-title Search for photos with no title.
|
--title TEXT Search for TEXT in title of photo.
|
||||||
--description TEXT Search for TEXT in description of photo.
|
--no-title Search for photos with no title.
|
||||||
--no-description Search for photos with no description.
|
--description TEXT Search for TEXT in description of photo.
|
||||||
--uti TEXT Search for photos whose uniform type identifier (UTI)
|
--no-description Search for photos with no description.
|
||||||
matches TEXT
|
--uti TEXT Search for photos whose uniform type identifier
|
||||||
-i, --ignore-case Case insensitive search for title or description. Does
|
(UTI) matches TEXT
|
||||||
not apply to keyword, person, or album.
|
-i, --ignore-case Case insensitive search for title or
|
||||||
--edited Search for photos that have been edited.
|
description. Does not apply to keyword, person,
|
||||||
--external-edit Search for photos edited in external editor.
|
or album.
|
||||||
--favorite Search for photos marked favorite.
|
--edited Search for photos that have been edited.
|
||||||
--not-favorite Search for photos not marked favorite.
|
--external-edit Search for photos edited in external editor.
|
||||||
--hidden Search for photos marked hidden.
|
--favorite Search for photos marked favorite.
|
||||||
--not-hidden Search for photos not marked hidden.
|
--not-favorite Search for photos not marked favorite.
|
||||||
--burst Search for photos that were taken in a burst.
|
--hidden Search for photos marked hidden.
|
||||||
--not-burst Search for photos that are not part of a burst.
|
--not-hidden Search for photos not marked hidden.
|
||||||
--live Search for Apple live photos
|
--burst Search for photos that were taken in a burst.
|
||||||
--not-live Search for photos that are not Apple live photos
|
--not-burst Search for photos that are not part of a burst.
|
||||||
--shared Search for photos in shared iCloud album (Photos 5
|
--live Search for Apple live photos
|
||||||
only).
|
--not-live Search for photos that are not Apple live
|
||||||
--not-shared Search for photos not in shared iCloud album (Photos 5
|
photos
|
||||||
only).
|
--shared Search for photos in shared iCloud album
|
||||||
-V, --verbose Print verbose output.
|
(Photos 5 only).
|
||||||
--overwrite Overwrite existing files. Default behavior is to add
|
--not-shared Search for photos not in shared iCloud album
|
||||||
(1), (2), etc to filename if file already exists. Use
|
(Photos 5 only).
|
||||||
this with caution as it may create name collisions on
|
-V, --verbose Print verbose output.
|
||||||
export. (e.g. if two files happen to have the same name)
|
--overwrite Overwrite existing files. Default behavior is
|
||||||
--export-by-date Automatically create output folders to organize photos
|
to add (1), (2), etc to filename if file
|
||||||
by date created (e.g. DEST/2019/12/20/photoname.jpg).
|
already exists. Use this with caution as it may
|
||||||
--export-edited Also export edited version of photo if an edited version
|
create name collisions on export. (e.g. if two
|
||||||
exists. Edited photo will be named in form of
|
files happen to have the same name)
|
||||||
"photoname_edited.ext"
|
--export-by-date Automatically create output folders to organize
|
||||||
--export-bursts If a photo is a burst photo export all associated burst
|
photos by date created (e.g.
|
||||||
images in the library.
|
DEST/2019/12/20/photoname.jpg).
|
||||||
--export-live If a photo is a live photo export the associated live
|
--export-edited Also export edited version of photo if an
|
||||||
video component. Live video will have same name as
|
edited version exists. Edited photo will be
|
||||||
photo but with .mov extension.
|
named in form of "photoname_edited.ext"
|
||||||
--original-name Use photo's original filename instead of current
|
--export-bursts If a photo is a burst photo export all
|
||||||
filename for export.
|
associated burst images in the library.
|
||||||
--sidecar Create json sidecar for each photo exported in format
|
--export-live If a photo is a live photo export the
|
||||||
useable by exiftool (https://exiftool.org/) The sidecar
|
associated live video component. Live video
|
||||||
file can be used to apply metadata to the file with
|
will have same name as photo but with .mov
|
||||||
exiftool, for example: "exiftool -j=photoname.jpg.json
|
extension.
|
||||||
photoname.jpg" The sidecar file is named in format
|
--original-name Use photo's original filename instead of
|
||||||
photoname.ext.json where ext is extension of the photo
|
current filename for export.
|
||||||
(e.g. jpg).
|
--sidecar Create JSON sidecar for each photo exported in
|
||||||
--only-movies Search only for movies (default searches both images and
|
format useable by exiftool
|
||||||
movies).
|
(https://exiftool.org/) The sidecar file can be
|
||||||
--only-photos Search only for photos/images (default searches both
|
used to apply metadata to the file with
|
||||||
images and movies).
|
exiftool, for example: "exiftool
|
||||||
--download-missing Attempt to download missing photos from iCloud. The
|
-j=photoname.jpg.json photoname.jpg" The
|
||||||
current implementation uses Applescript to interact with
|
sidecar file is named in format
|
||||||
Photos to export the photo which will force Photos to
|
photoname.ext.json where ext is extension of
|
||||||
download from iCloud if the photo does not exist on
|
the photo (e.g. jpg). Note: this does not
|
||||||
disk. This will be slow and will require internet
|
create an XMP sidecar as used by Lightroom,
|
||||||
connection. This obviously only works if the Photos
|
etc.
|
||||||
library is synched to iCloud.
|
--only-movies Search only for movies (default searches both
|
||||||
-h, --help Show this message and exit.
|
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:
|
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
|
## Example uses of the module
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
import os.path
|
||||||
|
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
photosdb = osxphotos.PhotosDB()
|
db = os.path.expanduser("~/Pictures/Photos Library.photoslibrary")
|
||||||
|
photosdb = osxphotos.PhotosDB(db)
|
||||||
print(photosdb.keywords)
|
print(photosdb.keywords)
|
||||||
print(photosdb.persons)
|
print(photosdb.persons)
|
||||||
print(photosdb.albums)
|
print(photosdb.albums)
|
||||||
@@ -245,12 +266,12 @@ if __name__ == "__main__":
|
|||||||
otherwise, export the original version """
|
otherwise, export the original version """
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
photosdb = osxphotos.PhotosDB()
|
db = os.path.expanduser("~/Pictures/Photos Library.photoslibrary")
|
||||||
|
photosdb = osxphotos.PhotosDB(db)
|
||||||
photos = photosdb.photos()
|
photos = photosdb.photos()
|
||||||
|
|
||||||
export_path = os.path.expanduser("~/Desktop/export")
|
export_path = os.path.expanduser("~/Desktop/export")
|
||||||
@@ -274,17 +295,16 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
### PhotosDB
|
### PhotosDB
|
||||||
|
|
||||||
#### Open the default Photos library
|
#### Read a Photos library database
|
||||||
|
|
||||||
```python
|
```python
|
||||||
osxphotos.PhotosDB()
|
|
||||||
osxphotos.PhotosDB(path)
|
osxphotos.PhotosDB(path)
|
||||||
osxphotos.PhotosDB(dbfile=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.
|
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
|
```python
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
photosdb = osxphotos.PhotosDB(osxphotos.utils.get_last_library_path())
|
||||||
photosdb = osxphotos.PhotosDB()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Open System Photos library
|
#### 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
|
```python
|
||||||
>>> import osxphotos
|
>>> import osxphotos
|
||||||
>>> photosdb = osxphotos.PhotosDB()
|
>>> photosdb = osxphotos.PhotosDB("/Users/smith/Pictures/Photos Library.photoslibrary")
|
||||||
>>> photos = photosdb.photos()
|
>>> photos = photosdb.photos()
|
||||||
>>> len(photos)
|
>>> len(photos)
|
||||||
25002
|
25002
|
||||||
@@ -627,7 +646,7 @@ Example below gets list of all photos that are bursts, selects one of of them an
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
>>> import osxphotos
|
>>> import osxphotos
|
||||||
>>> photosdb = osxphotos.PhotosDB()
|
>>> photosdb = osxphotos.PhotosDB("/Users/smith/Pictures/Photos Library.photoslibrary")
|
||||||
>>> bursts = [p for p in photosdb.photos() if p.burst]
|
>>> bursts = [p for p in photosdb.photos() if p.burst]
|
||||||
>>> burst_photo = bursts[5]
|
>>> burst_photo = bursts[5]
|
||||||
>>> len(burst_photo.burst_photos)
|
>>> 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
|
```python
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
|
||||||
photosdb = osxphotos.PhotosDB()
|
photosdb = osxphotos.PhotosDB("/Users/smith/Pictures/Photos Library.photoslibrary")
|
||||||
photos = photosdb.photos()
|
photos = photosdb.photos()
|
||||||
photos[0].export("/tmp","photo_name.jpg",sidecar=True)
|
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
|
import osxphotos
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
photosdb = osxphotos.PhotosDB()
|
|
||||||
|
photosdb = osxphotos.PhotosDB("/Users/smith/Pictures/Photos Library.photoslibrary")
|
||||||
print(f"db file = {photosdb.db_path}")
|
print(f"db file = {photosdb.db_path}")
|
||||||
print(f"db version = {photosdb.db_version}")
|
print(f"db version = {photosdb.db_version}")
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import osxphotos
|
import osxphotos
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
|
||||||
def main():
|
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 file = {photosdb.db_path}")
|
||||||
print(f"db version = {photosdb.db_version}")
|
print(f"db version = {photosdb.db_version}")
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import osxphotos
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
photosdb = osxphotos.PhotosDB()
|
db = os.path.expanduser("~/Pictures/Photos Library.photoslibrary")
|
||||||
|
photosdb = osxphotos.PhotosDB(db)
|
||||||
photos = photosdb.photos()
|
photos = photosdb.photos()
|
||||||
|
|
||||||
export_path = os.path.expanduser("~/Desktop/export")
|
export_path = os.path.expanduser("~/Desktop/export")
|
||||||
|
|||||||
@@ -19,6 +19,42 @@ from .utils import create_path_by_date, _copy_file
|
|||||||
# TODO: add search for filename
|
# 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:
|
class CLI_Obj:
|
||||||
def __init__(self, db=None, json=False, debug=False):
|
def __init__(self, db=None, json=False, debug=False):
|
||||||
if debug:
|
if debug:
|
||||||
@@ -36,7 +72,8 @@ CTX_SETTINGS = dict(help_option_names=["-h", "--help"])
|
|||||||
required=False,
|
required=False,
|
||||||
metavar="<Photos database path>",
|
metavar="<Photos database path>",
|
||||||
default=None,
|
default=None,
|
||||||
help="Specify database file.",
|
help="Specify Photos database path.",
|
||||||
|
type=click.Path(exists=True),
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--json",
|
"--json",
|
||||||
@@ -53,49 +90,153 @@ def cli(ctx, db, json, debug):
|
|||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@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_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. """
|
""" 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}
|
keywords = {"keywords": photosdb.keywords_as_dict}
|
||||||
if cli_obj.json:
|
if json_ or cli_obj.json:
|
||||||
click.echo(json.dumps(keywords))
|
click.echo(json.dumps(keywords))
|
||||||
else:
|
else:
|
||||||
click.echo(yaml.dump(keywords, sort_keys=False))
|
click.echo(yaml.dump(keywords, sort_keys=False))
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@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_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. """
|
""" 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}
|
albums = {"albums": photosdb.albums_as_dict}
|
||||||
if photosdb.db_version >= _PHOTOS_5_VERSION:
|
if photosdb.db_version >= _PHOTOS_5_VERSION:
|
||||||
albums["shared albums"] = photosdb.albums_shared_as_dict
|
albums["shared albums"] = photosdb.albums_shared_as_dict
|
||||||
|
|
||||||
if cli_obj.json:
|
if json_ or cli_obj.json:
|
||||||
click.echo(json.dumps(albums))
|
click.echo(json.dumps(albums))
|
||||||
else:
|
else:
|
||||||
click.echo(yaml.dump(albums, sort_keys=False))
|
click.echo(yaml.dump(albums, sort_keys=False))
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@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_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. """
|
""" 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}
|
persons = {"persons": photosdb.persons_as_dict}
|
||||||
if cli_obj.json:
|
if json_ or cli_obj.json:
|
||||||
click.echo(json.dumps(persons))
|
click.echo(json.dumps(persons))
|
||||||
else:
|
else:
|
||||||
click.echo(yaml.dump(persons, sort_keys=False))
|
click.echo(yaml.dump(persons, sort_keys=False))
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@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_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. """
|
""" 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 = {}
|
||||||
info["database_path"] = pdb.db_path
|
info["database_path"] = pdb.db_path
|
||||||
info["database_version"] = pdb.db_version
|
info["database_version"] = pdb.db_version
|
||||||
@@ -146,61 +287,116 @@ def info(cli_obj):
|
|||||||
info["persons_count"] = len(persons)
|
info["persons_count"] = len(persons)
|
||||||
info["persons"] = persons
|
info["persons"] = persons
|
||||||
|
|
||||||
if cli_obj.json:
|
if cli_obj.json or json_:
|
||||||
click.echo(json.dumps(info))
|
click.echo(json.dumps(info))
|
||||||
else:
|
else:
|
||||||
click.echo(yaml.dump(info, sort_keys=False))
|
click.echo(yaml.dump(info, sort_keys=False))
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@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(
|
@click.option(
|
||||||
"--json",
|
"--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,
|
required=False,
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
default=False,
|
default=False,
|
||||||
help="Print output in JSON format.",
|
help="Print output in JSON format.",
|
||||||
)
|
)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def dump(cli_obj, json):
|
@click.pass_context
|
||||||
""" Print list of all photos & associated info from the Photos library. """
|
def list_libraries(ctx, cli_obj, json_):
|
||||||
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):
|
|
||||||
""" Print list of Photos libraries found on the system. """
|
""" Print list of Photos libraries found on the system. """
|
||||||
photo_libs = osxphotos.utils.list_photo_libraries()
|
_list_libraries(json_=json_ or cli_obj.json)
|
||||||
sys_lib = None
|
|
||||||
_, major, _ = osxphotos.utils._get_os_version()
|
|
||||||
if int(major) >= 15:
|
|
||||||
sys_lib = osxphotos.utils.get_system_library_path()
|
|
||||||
|
|
||||||
|
|
||||||
|
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 = 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:
|
for lib in photo_libs:
|
||||||
if lib == sys_lib:
|
if lib == sys_lib:
|
||||||
click.echo(f"(*)\t{lib}")
|
click.echo(f"(*)\t{lib}")
|
||||||
sys_lib_flag = True
|
sys_lib_flag = True
|
||||||
elif lib == last_lib:
|
elif lib == last_lib:
|
||||||
click.echo(f"(#)\t{lib}")
|
click.echo(f"(#)\t{lib}")
|
||||||
last_lib_flag = True
|
last_lib_flag = True
|
||||||
else:
|
else:
|
||||||
click.echo(f"\t{lib}")
|
click.echo(f"\t{lib}")
|
||||||
|
|
||||||
if sys_lib_flag or last_lib_flag:
|
if sys_lib_flag or last_lib_flag:
|
||||||
click.echo("\n")
|
click.echo("\n")
|
||||||
if sys_lib_flag:
|
if sys_lib_flag:
|
||||||
click.echo("(*)\tSystem Photos Library")
|
click.echo("(*)\tSystem Photos Library")
|
||||||
if last_lib_flag:
|
if last_lib_flag:
|
||||||
click.echo("(#)\tLast opened Photos Library")
|
click.echo("(#)\tLast opened Photos Library")
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@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("--keyword", default=None, multiple=True, help="Search for keyword(s).")
|
||||||
@click.option("--person", default=None, multiple=True, help="Search for person(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).")
|
@click.option("--album", default=None, multiple=True, help="Search for album(s).")
|
||||||
@@ -296,18 +492,14 @@ def list_libraries(cli_obj):
|
|||||||
is_flag=True,
|
is_flag=True,
|
||||||
help="Search only for photos/images (default searches both images and movies).",
|
help="Search only for photos/images (default searches both images and movies).",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.argument("photos_library", nargs=-1, type=click.Path(exists=True))
|
||||||
"--json",
|
|
||||||
required=False,
|
|
||||||
is_flag=True,
|
|
||||||
default=False,
|
|
||||||
help="Print output in JSON format",
|
|
||||||
)
|
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def query(
|
def query(
|
||||||
ctx,
|
ctx,
|
||||||
cli_obj,
|
cli_obj,
|
||||||
|
db,
|
||||||
|
photos_library,
|
||||||
keyword,
|
keyword,
|
||||||
person,
|
person,
|
||||||
album,
|
album,
|
||||||
@@ -317,7 +509,7 @@ def query(
|
|||||||
description,
|
description,
|
||||||
no_description,
|
no_description,
|
||||||
ignore_case,
|
ignore_case,
|
||||||
json,
|
json_,
|
||||||
edited,
|
edited,
|
||||||
external_edit,
|
external_edit,
|
||||||
favorite,
|
favorite,
|
||||||
@@ -429,8 +621,15 @@ def query(
|
|||||||
if only_photos:
|
if only_photos:
|
||||||
ismovie = False
|
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(
|
photos = _query(
|
||||||
cli_obj,
|
db,
|
||||||
keyword,
|
keyword,
|
||||||
person,
|
person,
|
||||||
album,
|
album,
|
||||||
@@ -440,7 +639,6 @@ def query(
|
|||||||
description,
|
description,
|
||||||
no_description,
|
no_description,
|
||||||
ignore_case,
|
ignore_case,
|
||||||
json,
|
|
||||||
edited,
|
edited,
|
||||||
external_edit,
|
external_edit,
|
||||||
favorite,
|
favorite,
|
||||||
@@ -463,10 +661,18 @@ def query(
|
|||||||
incloud,
|
incloud,
|
||||||
not_incloud,
|
not_incloud,
|
||||||
)
|
)
|
||||||
print_photo_info(photos, cli_obj.json or json)
|
print_photo_info(photos, cli_obj.json or json_)
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@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("--keyword", default=None, multiple=True, help="Search for keyword(s).")
|
||||||
@click.option("--person", default=None, multiple=True, help="Search for person(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).")
|
@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. "
|
"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.",
|
"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_obj
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def export(
|
def export(
|
||||||
ctx,
|
ctx,
|
||||||
cli_obj,
|
cli_obj,
|
||||||
|
db,
|
||||||
|
photos_library,
|
||||||
keyword,
|
keyword,
|
||||||
person,
|
person,
|
||||||
album,
|
album,
|
||||||
@@ -679,8 +888,15 @@ def export(
|
|||||||
if only_photos:
|
if only_photos:
|
||||||
ismovie = False
|
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(
|
photos = _query(
|
||||||
cli_obj,
|
db,
|
||||||
keyword,
|
keyword,
|
||||||
person,
|
person,
|
||||||
album,
|
album,
|
||||||
@@ -690,7 +906,6 @@ def export(
|
|||||||
description,
|
description,
|
||||||
no_description,
|
no_description,
|
||||||
ignore_case,
|
ignore_case,
|
||||||
json,
|
|
||||||
edited,
|
edited,
|
||||||
external_edit,
|
external_edit,
|
||||||
favorite,
|
favorite,
|
||||||
@@ -708,10 +923,10 @@ def export(
|
|||||||
not_burst,
|
not_burst,
|
||||||
live,
|
live,
|
||||||
not_live,
|
not_live,
|
||||||
False, # cloudasset
|
False, # cloudasset
|
||||||
False, # not_cloudasset
|
False, # not_cloudasset
|
||||||
False, # incloud
|
False, # incloud
|
||||||
False # not_incloud
|
False, # not_incloud
|
||||||
)
|
)
|
||||||
|
|
||||||
if photos:
|
if photos:
|
||||||
@@ -739,7 +954,7 @@ def export(
|
|||||||
export_edited,
|
export_edited,
|
||||||
original_name,
|
original_name,
|
||||||
export_live,
|
export_live,
|
||||||
download_missing
|
download_missing,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
for p in photos:
|
for p in photos:
|
||||||
@@ -753,7 +968,7 @@ def export(
|
|||||||
export_edited,
|
export_edited,
|
||||||
original_name,
|
original_name,
|
||||||
export_live,
|
export_live,
|
||||||
download_missing
|
download_missing,
|
||||||
)
|
)
|
||||||
if export_path:
|
if export_path:
|
||||||
click.echo(f"Exported {p.filename} to {export_path}")
|
click.echo(f"Exported {p.filename} to {export_path}")
|
||||||
@@ -855,7 +1070,7 @@ def print_photo_info(photos, json=False):
|
|||||||
|
|
||||||
|
|
||||||
def _query(
|
def _query(
|
||||||
cli_obj,
|
db,
|
||||||
keyword,
|
keyword,
|
||||||
person,
|
person,
|
||||||
album,
|
album,
|
||||||
@@ -865,7 +1080,6 @@ def _query(
|
|||||||
description,
|
description,
|
||||||
no_description,
|
no_description,
|
||||||
ignore_case,
|
ignore_case,
|
||||||
json,
|
|
||||||
edited,
|
edited,
|
||||||
external_edit,
|
external_edit,
|
||||||
favorite,
|
favorite,
|
||||||
@@ -895,7 +1109,7 @@ def _query(
|
|||||||
|
|
||||||
# TODO: this is getting too hairy -- need to change to named args
|
# 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(
|
photos = photosdb.photos(
|
||||||
keywords=keyword,
|
keywords=keyword,
|
||||||
persons=person,
|
persons=person,
|
||||||
@@ -1031,7 +1245,9 @@ def export_photo(
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
elif photo.ismissing and not photo.iscloudasset or not photo.incloud:
|
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
|
return None
|
||||||
|
|
||||||
filename = None
|
filename = None
|
||||||
@@ -1089,7 +1305,7 @@ def export_photo(
|
|||||||
|
|
||||||
_copy_file(src_live, str(dest_live))
|
_copy_file(src_live, str(dest_live))
|
||||||
else:
|
else:
|
||||||
click.echo(f"Skipping missing live movie for {filename}")
|
click.echo(f"Skipping missing live movie for {filename}")
|
||||||
|
|
||||||
return photo_path
|
return photo_path
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.21.6"
|
__version__ = "0.22.0"
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ from .utils import _check_file_exists, _get_os_version, get_last_library_path, _
|
|||||||
class PhotosDB:
|
class PhotosDB:
|
||||||
""" Processes a Photos.app library database to extract information about photos """
|
""" Processes a Photos.app library database to extract information about photos """
|
||||||
|
|
||||||
def __init__(self, *args, dbfile=None):
|
def __init__(self, *dbfile_, dbfile=None):
|
||||||
""" create a new PhotosDB object """
|
""" create a new PhotosDB object
|
||||||
""" path to photos library or database may be specified EITHER as first argument or as named argument dbfile=path """
|
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 """
|
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 """
|
specify path to photos library or photos.db using named argument dbfile=path """
|
||||||
|
|
||||||
# Check OS version
|
# Check OS version
|
||||||
system = platform.system()
|
system = platform.system()
|
||||||
@@ -89,28 +89,28 @@ class PhotosDB:
|
|||||||
logging.debug(f"dbfile = {dbfile}")
|
logging.debug(f"dbfile = {dbfile}")
|
||||||
|
|
||||||
# get the path to photos library database
|
# get the path to photos library database
|
||||||
if args:
|
if dbfile_:
|
||||||
# got a library path as argument
|
# got a library path as argument
|
||||||
if dbfile:
|
if dbfile:
|
||||||
# shouldn't pass via both *args and dbfile=
|
# shouldn't pass via both *args and dbfile=
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"photos database path must be specified as argument or named parameter dbfile but not both: args: {args}, dbfile: {dbfile}",
|
f"photos database path must be specified as argument or named parameter dbfile but not both: args: {args}, dbfile: {dbfile}",
|
||||||
args,
|
dbfile_,
|
||||||
dbfile,
|
dbfile,
|
||||||
)
|
)
|
||||||
elif len(args) == 1:
|
elif len(dbfile_) == 1:
|
||||||
dbfile = args[0]
|
dbfile = dbfile_[0]
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"__init__ takes only a single argument (photos database path): {args}",
|
f"__init__ takes only a single argument (photos database path): {dbfile_}",
|
||||||
args,
|
dbfile_,
|
||||||
)
|
)
|
||||||
elif dbfile is None:
|
elif dbfile is None:
|
||||||
# no args and dbfile not passed, try to get last opened library
|
raise TypeError(
|
||||||
library_path = get_last_library_path()
|
f"photos database path must be specified as argument or named parameter dbfile",
|
||||||
if not library_path:
|
dbfile_,
|
||||||
raise FileNotFoundError("could not get library path")
|
dbfile,
|
||||||
dbfile = os.path.join(library_path, "database/photos.db")
|
)
|
||||||
|
|
||||||
if os.path.isdir(dbfile):
|
if os.path.isdir(dbfile):
|
||||||
# passed a directory, assume it's a photoslibrary
|
# passed a directory, assume it's a photoslibrary
|
||||||
|
|||||||
Reference in New Issue
Block a user