From fd5e748dca759ea1c3a7329d447f363afe8418b7 Mon Sep 17 00:00:00 2001 From: Rhet Turnbull Date: Sun, 29 Mar 2020 23:03:12 -0700 Subject: [PATCH] Added places command to CLI --- README.md | 10 +++++++- osxphotos/__main__.py | 52 ++++++++++++++++++++++++++++++++++++++++- osxphotos/_constants.py | 3 +++ osxphotos/_version.py | 2 +- tests/test_cli.py | 20 ++++++++++++++++ 5 files changed, 84 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 81a5651a..d93787d5 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,14 @@ Then you should be able to run `osxphotos` on the command line: Usage: osxphotos [OPTIONS] COMMAND [ARGS]... Options: - --db Specify database file. + --db Specify Photos database path. Path to Photos + library/database can be specified using either + --db or directly as PHOTOS_LIBRARY positional + argument. If neither --db or PHOTOS_LIBRARY + provided, will attempt to find the library to + use in the following order: 1. last opened + library, 2. system library, 3. + ~/Pictures/Photos Library.photoslibrary --json Print output in JSON format. -v, --version Show the version and exit. -h, --help Show this message and exit. @@ -71,6 +78,7 @@ Commands: keywords Print out keywords found in the Photos library. list Print list of Photos libraries found on the system. persons Print out persons (faces) found in the Photos library. + places Print out places found in the Photos library. query Query the Photos database using 1 or more search options; if... ``` diff --git a/osxphotos/__main__.py b/osxphotos/__main__.py index 92ba9caf..9c16e069 100644 --- a/osxphotos/__main__.py +++ b/osxphotos/__main__.py @@ -18,7 +18,7 @@ from pathvalidate import ( import osxphotos -from ._constants import _EXIF_TOOL_URL, _PHOTOS_5_VERSION +from ._constants import _EXIF_TOOL_URL, _PHOTOS_5_VERSION, _UNKNOWN_PLACE from ._version import __version__ from .exiftool import get_exiftool_path from .template import render_filepath_template, TEMPLATE_SUBSTITUTIONS @@ -479,6 +479,56 @@ def info(ctx, cli_obj, db, json_, photos_library): click.echo(yaml.dump(info, sort_keys=False)) +@cli.command() +@DB_OPTION +@JSON_OPTION +@DB_ARGUMENT +@click.pass_obj +@click.pass_context +def places(ctx, cli_obj, db, json_, photos_library): + """ Print out places found in the Photos library. """ + + # below needed for to make CliRunner work for testing + cli_db = cli_obj.db if cli_obj is not None else None + db = get_photos_db(*photos_library, db, cli_db) + if db is None: + click.echo(cli.commands["places"].get_help(ctx), err=True) + click.echo("\n\nLocated the following Photos library databases: ", err=True) + _list_libraries() + return + + photosdb = osxphotos.PhotosDB(dbfile=db) + place_names = {} + for photo in photosdb.photos(movies=True): + if photo.place: + try: + place_names[photo.place.name] += 1 + except: + place_names[photo.place.name] = 1 + else: + try: + place_names[_UNKNOWN_PLACE] += 1 + except: + place_names[_UNKNOWN_PLACE] = 1 + + # sort by place count + places = { + "places": { + name: place_names[name] + for name in sorted( + place_names.keys(), key=lambda key: place_names[key], reverse=True + ) + } + } + + # below needed for to make CliRunner work for testing + cli_json = cli_obj.json if cli_obj is not None else None + if json_ or cli_json: + click.echo(json.dumps(places)) + else: + click.echo(yaml.dump(places, sort_keys=False)) + + @cli.command() @DB_OPTION @JSON_OPTION diff --git a/osxphotos/_constants.py b/osxphotos/_constants.py index 09855526..35c75373 100644 --- a/osxphotos/_constants.py +++ b/osxphotos/_constants.py @@ -25,6 +25,9 @@ _TESTED_OS_VERSIONS = ["12", "13", "14", "15"] # Photos 5 has persons who are empty string if unidentified face _UNKNOWN_PERSON = "_UNKNOWN_" +# photos with no reverse geolocation info (place) +_UNKNOWN_PLACE = "_UNKNOWN_" + _EXIF_TOOL_URL = "https://exiftool.org/" # Where are shared iCloud photos located? diff --git a/osxphotos/_version.py b/osxphotos/_version.py index b0a5cb74..0cbbfb41 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,3 +1,3 @@ """ version info """ -__version__ = "0.24.2" +__version__ = "0.24.3" diff --git a/tests/test_cli.py b/tests/test_cli.py index 2ea18fdb..557e118d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -4,6 +4,7 @@ from click.testing import CliRunner CLI_PHOTOS_DB = "tests/Test-10.15.1.photoslibrary" LIVE_PHOTOS_DB = "tests/Test-Cloud-10.15.1.photoslibrary/database/photos.db" RAW_PHOTOS_DB = "tests/Test-RAW-10.15.1.photoslibrary" +PLACES_PHOTOS_DB = "tests/Test-Places-Catalina-10_15_1.photoslibrary" CLI_OUTPUT_NO_SUBCOMMAND = [ "Options:", @@ -85,6 +86,8 @@ CLI_EXPORT_RAW_EDITED = [ ] CLI_EXPORT_RAW_EDITED_ORIGINAL = ["IMG_0476_2.CR2", "IMG_0476_2_edited.jpeg"] +CLI_PLACES_JSON = """{"places": {"_UNKNOWN_": 1, "Maui, Wailea, Hawai'i, United States": 1, "Washington, District of Columbia, United States": 1}}""" + def test_osxphotos(): import osxphotos @@ -408,3 +411,20 @@ def test_export_directory_template_3(): workdir = os.getcwd() for filepath in CLI_EXPORTED_DIRECTORY_TEMPLATE_FILENAMES3: assert os.path.isfile(os.path.join(workdir, filepath)) + + +def test_places(): + import json + import os + import os.path + import osxphotos + from osxphotos.__main__ import places + + runner = CliRunner() + cwd = os.getcwd() + # pylint: disable=not-context-manager + with runner.isolated_filesystem(): + result = runner.invoke(places, [os.path.join(cwd, PLACES_PHOTOS_DB), "--json"]) + assert result.exit_code == 0 + json_got = json.loads(result.output) + assert json_got == json.loads(CLI_PLACES_JSON)