Added --shared/--not-shared to CLI

This commit is contained in:
Rhet Turnbull
2019-12-26 22:46:32 -08:00
parent 6d20e9e361
commit 0271b8ad9d
2 changed files with 48 additions and 6 deletions

View File

@@ -59,7 +59,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.
@@ -285,6 +285,8 @@ Pass the fully qualified path to the Photos library or the actual database file
Returns a PhotosDB object. Returns a PhotosDB object.
**Note**: If you have a large library (e.g. many thousdands of photos), creating the PhotosDB object can take a long time (10s of seconds). See [Implementation Notes](#implementation-notes) for additional details.
#### `keywords` #### `keywords`
```python ```python
# assumes photosdb is a PhotosDB object (see above) # assumes photosdb is a PhotosDB object (see above)
@@ -631,7 +633,7 @@ If you have an interesting example that shows usage of this module, submit an is
## Implementation Notes ## Implementation Notes
This module works by creating a copy of the sqlite3 database that photos uses to store data about the photos library. the class photosdb then queries this database to extract information about the photos such as persons (faces identified in the photos), albums, keywords, etc. This module works by creating a copy of the sqlite3 database that photos uses to store data about the photos library. the class photosdb then queries this database to extract information about the photos such as persons (faces identified in the photos), albums, keywords, etc. If your library is large, the database can be hundreds of MB in size and the copy then read can take many 10s of seconds to complete. Once copied, the entire database is processed and an in-memory data structure is created meaning all subsequent accesses of the PhotosDB object occur much more quickly.
If apple changes the database format this will likely break. If apple changes the database format this will likely break.

View File

@@ -11,7 +11,7 @@ import yaml
import osxphotos import osxphotos
from ._constants import _EXIF_TOOL_URL from ._constants import _EXIF_TOOL_URL, _PHOTOS_5_VERSION
from ._version import __version__ from ._version import __version__
from .utils import create_path_by_date from .utils import create_path_by_date
@@ -69,12 +69,16 @@ def albums(cli_obj):
""" Print out albums found in the Photos library. """ """ Print out albums found in the Photos library. """
photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db) photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
albums = {"albums": photosdb.albums_as_dict} 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 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.pass_obj @click.pass_obj
def persons(cli_obj): def persons(cli_obj):
@@ -107,6 +111,11 @@ def info(cli_obj):
info["albums_count"] = len(albums) info["albums_count"] = len(albums)
info["albums"] = albums info["albums"] = albums
if pdb.db_version >= _PHOTOS_5_VERSION:
albums_shared = pdb.albums_shared_as_dict
info["shared_albums_count"] = len(albums_shared)
info["shared_albums"] = albums_shared
persons = pdb.persons_as_dict persons = pdb.persons_as_dict
# handle empty person names (added by Photos 5.0+ when face detected but not identified) # handle empty person names (added by Photos 5.0+ when face detected but not identified)
@@ -216,6 +225,12 @@ def list_libraries(cli_obj):
is_flag=True, is_flag=True,
help="Search for photos present on disk (e.g. not missing).", help="Search for photos present on disk (e.g. not missing).",
) )
@click.option(
"--shared", is_flag=True, help="Search for photos in shared iCloud album (Photos 5 only)."
)
@click.option(
"--not-shared", is_flag=True, help="Search for photos not in shared iCloud album (Photos 5 only)."
)
@click.option( @click.option(
"--json", "--json",
required=False, required=False,
@@ -246,6 +261,8 @@ def query(
not_hidden, not_hidden,
missing, missing,
not_missing, not_missing,
shared,
not_shared,
): ):
""" Query the Photos database using 1 or more search options; """ Query the Photos database using 1 or more search options;
if more than one option is provided, they are treated as "AND" if more than one option is provided, they are treated as "AND"
@@ -271,6 +288,8 @@ def query(
not_hidden, not_hidden,
missing, missing,
not_missing, not_missing,
shared,
not_shared,
] ]
): ):
click.echo(cli.commands["query"].get_help(ctx)) click.echo(cli.commands["query"].get_help(ctx))
@@ -316,6 +335,8 @@ def query(
not_hidden, not_hidden,
missing, missing,
not_missing, not_missing,
shared,
not_shared,
) )
print_photo_info(photos, cli_obj.json or json) print_photo_info(photos, cli_obj.json or json)
@@ -354,6 +375,12 @@ def query(
) )
@click.option("--hidden", is_flag=True, help="Search for photos marked hidden.") @click.option("--hidden", is_flag=True, help="Search for photos marked hidden.")
@click.option("--not-hidden", is_flag=True, help="Search for photos not marked hidden.") @click.option("--not-hidden", is_flag=True, help="Search for photos not marked hidden.")
@click.option(
"--shared", is_flag=True, help="Search for photos in shared iCloud album (Photos 5 only)."
)
@click.option(
"--not-shared", is_flag=True, help="Search for photos not in shared iCloud album (Photos 5 only)."
)
@click.option("--verbose", is_flag=True, help="Print verbose output.") @click.option("--verbose", is_flag=True, help="Print verbose output.")
@click.option( @click.option(
"--overwrite", "--overwrite",
@@ -378,7 +405,7 @@ def query(
@click.option( @click.option(
"--original-name", "--original-name",
is_flag=True, is_flag=True,
help="Use photo's original filename instead of current filename for export", help="Use photo's original filename instead of current filename for export.",
) )
@click.option( @click.option(
"--sidecar", "--sidecar",
@@ -387,7 +414,7 @@ def query(
f"in format useable by exiftool ({_EXIF_TOOL_URL}) " f"in format useable by exiftool ({_EXIF_TOOL_URL}) "
"The sidecar file can be used to apply metadata to the file with exiftool, for example: " "The sidecar file can be used to apply metadata to the file with exiftool, for example: "
'"exiftool -j=photoname.jpg.json photoname.jpg" ' '"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)", "The sidecar file is named in format photoname.ext.json where ext is extension of the photo (e.g. jpg).",
) )
@click.argument("dest", nargs=1) @click.argument("dest", nargs=1)
@click.pass_obj @click.pass_obj
@@ -410,6 +437,8 @@ def export(
not_favorite, not_favorite,
hidden, hidden,
not_hidden, not_hidden,
shared,
not_shared,
verbose, verbose,
overwrite, overwrite,
export_by_date, export_by_date,
@@ -452,6 +481,8 @@ def export(
not_hidden, not_hidden,
None, # missing -- won't export these but will warn user None, # missing -- won't export these but will warn user
None, # not-missing None, # not-missing
shared,
not_shared,
) )
if photos: if photos:
@@ -533,6 +564,7 @@ def print_photo_info(photos, json=False):
"external_edit", "external_edit",
"favorite", "favorite",
"hidden", "hidden",
"shared",
"latitude", "latitude",
"longitude", "longitude",
"path_edited", "path_edited",
@@ -556,6 +588,7 @@ def print_photo_info(photos, json=False):
p.external_edit, p.external_edit,
p.favorite, p.favorite,
p.hidden, p.hidden,
p.shared,
p._latitude, p._latitude,
p._longitude, p._longitude,
p.path_edited, p.path_edited,
@@ -585,6 +618,8 @@ def _query(
not_hidden, not_hidden,
missing, missing,
not_missing, not_missing,
shared,
not_shared,
): ):
""" run a query against PhotosDB to extract the photos based on user supply criteria """ """ run a query against PhotosDB to extract the photos based on user supply criteria """
""" used by query and export commands """ """ used by query and export commands """
@@ -645,6 +680,11 @@ def _query(
elif not_missing: elif not_missing:
photos = [p for p in photos if not p.ismissing] photos = [p for p in photos if not p.ismissing]
if shared:
photos = [p for p in photos if p.shared]
elif not_shared:
photos = [p for p in photos if not p.shared]
return photos return photos
@@ -671,7 +711,7 @@ def export_photo(
if photo.ismissing: if photo.ismissing:
space = " " if not verbose else "" space = " " if not verbose else ""
click.echo(f"{space}Skipping missing photos {photo.filename}") click.echo(f"{space}Skipping missing photo {photo.filename}")
return None return None
elif not os.path.exists(photo.path): elif not os.path.exists(photo.path):
space = " " if not verbose else "" space = " " if not verbose else ""