Added support for filtering only movies or photos to CLI; added search for UTI to CLI
This commit is contained in:
@@ -18,6 +18,7 @@ from .utils import create_path_by_date
|
|||||||
# TODO: add "--any" to search any field (e.g. keyword, description, title contains "wedding") (add case insensitive option)
|
# TODO: add "--any" to search any field (e.g. keyword, description, title contains "wedding") (add case insensitive option)
|
||||||
# TODO: add search for filename
|
# TODO: add search for filename
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
@@ -78,7 +79,6 @@ def albums(cli_obj):
|
|||||||
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):
|
||||||
@@ -113,7 +113,6 @@ def info(cli_obj):
|
|||||||
shared_movies = [p for p in movies if p.shared]
|
shared_movies = [p for p in movies if p.shared]
|
||||||
info["shared_movie_count"] = len(shared_movies)
|
info["shared_movie_count"] = len(shared_movies)
|
||||||
|
|
||||||
|
|
||||||
keywords = pdb.keywords_as_dict
|
keywords = pdb.keywords_as_dict
|
||||||
info["keywords_count"] = len(keywords)
|
info["keywords_count"] = len(keywords)
|
||||||
info["keywords"] = keywords
|
info["keywords"] = keywords
|
||||||
@@ -214,6 +213,12 @@ def list_libraries(cli_obj):
|
|||||||
@click.option(
|
@click.option(
|
||||||
"--no-description", is_flag=True, help="Search for photos with no description."
|
"--no-description", is_flag=True, help="Search for photos with no description."
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
"--uti",
|
||||||
|
default=None,
|
||||||
|
multiple=False,
|
||||||
|
help="Search for photos whose uniform type identifier (UTI) matches TEXT",
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-i",
|
"-i",
|
||||||
"--ignore-case",
|
"--ignore-case",
|
||||||
@@ -237,10 +242,24 @@ def list_libraries(cli_obj):
|
|||||||
help="Search for photos present on disk (e.g. not missing).",
|
help="Search for photos present on disk (e.g. not missing).",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--shared", is_flag=True, help="Search for photos in shared iCloud album (Photos 5 only)."
|
"--shared",
|
||||||
|
is_flag=True,
|
||||||
|
help="Search for photos in shared iCloud album (Photos 5 only).",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--not-shared", is_flag=True, help="Search for photos not in shared iCloud album (Photos 5 only)."
|
"--not-shared",
|
||||||
|
is_flag=True,
|
||||||
|
help="Search for photos not in shared iCloud album (Photos 5 only).",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--only-movies",
|
||||||
|
is_flag=True,
|
||||||
|
help="Search only for movies (default searches both images and movies)",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--only-photos",
|
||||||
|
is_flag=True,
|
||||||
|
help="Search only for photos/images (default searches both images and movies)",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--json",
|
"--json",
|
||||||
@@ -274,6 +293,9 @@ def query(
|
|||||||
not_missing,
|
not_missing,
|
||||||
shared,
|
shared,
|
||||||
not_shared,
|
not_shared,
|
||||||
|
only_movies,
|
||||||
|
only_photos,
|
||||||
|
uti,
|
||||||
):
|
):
|
||||||
""" 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"
|
||||||
@@ -301,6 +323,9 @@ def query(
|
|||||||
not_missing,
|
not_missing,
|
||||||
shared,
|
shared,
|
||||||
not_shared,
|
not_shared,
|
||||||
|
only_movies,
|
||||||
|
only_photos,
|
||||||
|
uti,
|
||||||
]
|
]
|
||||||
):
|
):
|
||||||
click.echo(cli.commands["query"].get_help(ctx))
|
click.echo(cli.commands["query"].get_help(ctx))
|
||||||
@@ -325,7 +350,18 @@ def query(
|
|||||||
# can't search for both description and no_description
|
# can't search for both description and no_description
|
||||||
click.echo(cli.commands["query"].get_help(ctx))
|
click.echo(cli.commands["query"].get_help(ctx))
|
||||||
return
|
return
|
||||||
else:
|
elif only_photos and only_movies:
|
||||||
|
# can't have only photos and only movies
|
||||||
|
click.echo(cli.commands["query"].get_help(ctx))
|
||||||
|
return
|
||||||
|
|
||||||
|
# actually have something to query
|
||||||
|
isphoto = ismovie = True # default searches for everything
|
||||||
|
if only_movies:
|
||||||
|
isphoto = False
|
||||||
|
if only_photos:
|
||||||
|
ismovie = False
|
||||||
|
|
||||||
photos = _query(
|
photos = _query(
|
||||||
cli_obj,
|
cli_obj,
|
||||||
keyword,
|
keyword,
|
||||||
@@ -348,6 +384,9 @@ def query(
|
|||||||
not_missing,
|
not_missing,
|
||||||
shared,
|
shared,
|
||||||
not_shared,
|
not_shared,
|
||||||
|
isphoto,
|
||||||
|
ismovie,
|
||||||
|
uti,
|
||||||
)
|
)
|
||||||
print_photo_info(photos, cli_obj.json or json)
|
print_photo_info(photos, cli_obj.json or json)
|
||||||
|
|
||||||
@@ -370,6 +409,12 @@ def query(
|
|||||||
@click.option(
|
@click.option(
|
||||||
"--no-description", is_flag=True, help="Search for photos with no description."
|
"--no-description", is_flag=True, help="Search for photos with no description."
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
"--uti",
|
||||||
|
default=None,
|
||||||
|
multiple=False,
|
||||||
|
help="Search for photos whose uniform type identifier (UTI) matches TEXT",
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-i",
|
"-i",
|
||||||
"--ignore-case",
|
"--ignore-case",
|
||||||
@@ -387,10 +432,14 @@ 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(
|
@click.option(
|
||||||
"--shared", is_flag=True, help="Search for photos in shared iCloud album (Photos 5 only)."
|
"--shared",
|
||||||
|
is_flag=True,
|
||||||
|
help="Search for photos in shared iCloud album (Photos 5 only).",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--not-shared", is_flag=True, help="Search for photos not in shared iCloud album (Photos 5 only)."
|
"--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(
|
||||||
@@ -427,6 +476,16 @@ def query(
|
|||||||
'"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.option(
|
||||||
|
"--only-movies",
|
||||||
|
is_flag=True,
|
||||||
|
help="Search only for movies (default searches both images and movies)",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--only-photos",
|
||||||
|
is_flag=True,
|
||||||
|
help="Search only for photos/images (default searches both images and movies)",
|
||||||
|
)
|
||||||
@click.argument("dest", nargs=1)
|
@click.argument("dest", nargs=1)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
@@ -441,6 +500,7 @@ def export(
|
|||||||
no_title,
|
no_title,
|
||||||
description,
|
description,
|
||||||
no_description,
|
no_description,
|
||||||
|
uti,
|
||||||
ignore_case,
|
ignore_case,
|
||||||
edited,
|
edited,
|
||||||
external_edit,
|
external_edit,
|
||||||
@@ -456,6 +516,8 @@ def export(
|
|||||||
export_edited,
|
export_edited,
|
||||||
original_name,
|
original_name,
|
||||||
sidecar,
|
sidecar,
|
||||||
|
only_photos,
|
||||||
|
only_movies,
|
||||||
dest,
|
dest,
|
||||||
):
|
):
|
||||||
""" Export photos from the Photos database.
|
""" Export photos from the Photos database.
|
||||||
@@ -471,7 +533,34 @@ def export(
|
|||||||
if not os.path.isdir(dest):
|
if not os.path.isdir(dest):
|
||||||
sys.exit("DEST must be valid path")
|
sys.exit("DEST must be valid path")
|
||||||
|
|
||||||
# if no query terms, show help and return
|
# sanity check input args
|
||||||
|
if favorite and not_favorite:
|
||||||
|
# can't search for both favorite and notfavorite
|
||||||
|
click.echo(cli.commands["export"].get_help(ctx))
|
||||||
|
return
|
||||||
|
elif hidden and not_hidden:
|
||||||
|
# can't search for both hidden and nothidden
|
||||||
|
click.echo(cli.commands["export"].get_help(ctx))
|
||||||
|
return
|
||||||
|
elif title and no_title:
|
||||||
|
# can't search for both title and no_title
|
||||||
|
click.echo(cli.commands["export"].get_help(ctx))
|
||||||
|
return
|
||||||
|
elif description and no_description:
|
||||||
|
# can't search for both description and no_description
|
||||||
|
click.echo(cli.commands["export"].get_help(ctx))
|
||||||
|
return
|
||||||
|
elif only_photos and only_movies:
|
||||||
|
# can't have only photos and only movies
|
||||||
|
click.echo(cli.commands["export"].get_help(ctx))
|
||||||
|
return
|
||||||
|
|
||||||
|
isphoto = ismovie = True # default searches for everything
|
||||||
|
if only_movies:
|
||||||
|
isphoto = False
|
||||||
|
if only_photos:
|
||||||
|
ismovie = False
|
||||||
|
|
||||||
photos = _query(
|
photos = _query(
|
||||||
cli_obj,
|
cli_obj,
|
||||||
keyword,
|
keyword,
|
||||||
@@ -494,6 +583,9 @@ def export(
|
|||||||
None, # not-missing
|
None, # not-missing
|
||||||
shared,
|
shared,
|
||||||
not_shared,
|
not_shared,
|
||||||
|
isphoto,
|
||||||
|
ismovie,
|
||||||
|
uti,
|
||||||
)
|
)
|
||||||
|
|
||||||
if photos:
|
if photos:
|
||||||
@@ -579,6 +671,9 @@ def print_photo_info(photos, json=False):
|
|||||||
"latitude",
|
"latitude",
|
||||||
"longitude",
|
"longitude",
|
||||||
"path_edited",
|
"path_edited",
|
||||||
|
"isphoto",
|
||||||
|
"ismovie",
|
||||||
|
"uti",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
for p in photos:
|
for p in photos:
|
||||||
@@ -587,7 +682,7 @@ def print_photo_info(photos, json=False):
|
|||||||
p.uuid,
|
p.uuid,
|
||||||
p.filename,
|
p.filename,
|
||||||
p.original_filename,
|
p.original_filename,
|
||||||
str(p.date),
|
p.date.isoformat(),
|
||||||
p.description,
|
p.description,
|
||||||
p.title,
|
p.title,
|
||||||
", ".join(p.keywords),
|
", ".join(p.keywords),
|
||||||
@@ -603,6 +698,9 @@ def print_photo_info(photos, json=False):
|
|||||||
p._latitude,
|
p._latitude,
|
||||||
p._longitude,
|
p._longitude,
|
||||||
p.path_edited,
|
p.path_edited,
|
||||||
|
p.isphoto,
|
||||||
|
p.ismovie,
|
||||||
|
p.uti,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
for row in dump:
|
for row in dump:
|
||||||
@@ -631,6 +729,9 @@ def _query(
|
|||||||
not_missing,
|
not_missing,
|
||||||
shared,
|
shared,
|
||||||
not_shared,
|
not_shared,
|
||||||
|
isphoto,
|
||||||
|
ismovie,
|
||||||
|
uti,
|
||||||
):
|
):
|
||||||
""" 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 """
|
||||||
@@ -638,7 +739,14 @@ def _query(
|
|||||||
""" if either is modified, need to ensure all three functions are updated """
|
""" if either is modified, need to ensure all three functions are updated """
|
||||||
|
|
||||||
photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
|
photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
|
||||||
photos = photosdb.photos(keywords=keyword, persons=person, albums=album, uuid=uuid, movies=True)
|
photos = photosdb.photos(
|
||||||
|
keywords=keyword,
|
||||||
|
persons=person,
|
||||||
|
albums=album,
|
||||||
|
uuid=uuid,
|
||||||
|
images=isphoto,
|
||||||
|
movies=ismovie,
|
||||||
|
)
|
||||||
|
|
||||||
if title:
|
if title:
|
||||||
# search title field for text
|
# search title field for text
|
||||||
@@ -656,7 +764,7 @@ def _query(
|
|||||||
|
|
||||||
if description:
|
if description:
|
||||||
# search description field for text
|
# search description field for text
|
||||||
# if more than one, find photos with all name values in in description
|
# if more than one, find photos with all name values in description
|
||||||
if ignore_case:
|
if ignore_case:
|
||||||
# case-insensitive
|
# case-insensitive
|
||||||
for d in description:
|
for d in description:
|
||||||
@@ -696,6 +804,14 @@ def _query(
|
|||||||
elif not_shared:
|
elif not_shared:
|
||||||
photos = [p for p in photos if not p.shared]
|
photos = [p for p in photos if not p.shared]
|
||||||
|
|
||||||
|
if shared:
|
||||||
|
photos = [p for p in photos if p.shared]
|
||||||
|
elif not_shared:
|
||||||
|
photos = [p for p in photos if not p.shared]
|
||||||
|
|
||||||
|
if uti:
|
||||||
|
photos = [p for p in photos if uti in p.uti]
|
||||||
|
|
||||||
return photos
|
return photos
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.19.00"
|
__version__ = "0.19.01"
|
||||||
|
|||||||
@@ -536,6 +536,9 @@ class PhotoInfo:
|
|||||||
"longitude": self._longitude,
|
"longitude": self._longitude,
|
||||||
"path_edited": self.path_edited,
|
"path_edited": self.path_edited,
|
||||||
"shared": self.shared,
|
"shared": self.shared,
|
||||||
|
"isphoto": self.isphoto,
|
||||||
|
"ismovie": self.ismovie,
|
||||||
|
"uti": self.uti,
|
||||||
}
|
}
|
||||||
return yaml.dump(info, sort_keys=False)
|
return yaml.dump(info, sort_keys=False)
|
||||||
|
|
||||||
@@ -561,6 +564,9 @@ class PhotoInfo:
|
|||||||
"longitude": self._longitude,
|
"longitude": self._longitude,
|
||||||
"path_edited": self.path_edited,
|
"path_edited": self.path_edited,
|
||||||
"shared": self.shared,
|
"shared": self.shared,
|
||||||
|
"isphoto": self.isphoto,
|
||||||
|
"ismovie": self.ismovie,
|
||||||
|
"uti": self.uti,
|
||||||
}
|
}
|
||||||
return json.dumps(pic)
|
return json.dumps(pic)
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,23 +3,23 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>BackgroundHighlightCollection</key>
|
<key>BackgroundHighlightCollection</key>
|
||||||
<date>2019-12-29T06:18:40Z</date>
|
<date>2019-12-29T09:24:37Z</date>
|
||||||
<key>BackgroundHighlightEnrichment</key>
|
<key>BackgroundHighlightEnrichment</key>
|
||||||
<date>2019-12-29T06:18:40Z</date>
|
<date>2019-12-29T09:24:37Z</date>
|
||||||
<key>BackgroundJobAssetRevGeocode</key>
|
<key>BackgroundJobAssetRevGeocode</key>
|
||||||
<date>2019-12-29T06:18:40Z</date>
|
<date>2019-12-29T10:47:44Z</date>
|
||||||
<key>BackgroundJobSearch</key>
|
<key>BackgroundJobSearch</key>
|
||||||
<date>2019-12-29T06:18:40Z</date>
|
<date>2019-12-29T09:24:37Z</date>
|
||||||
<key>BackgroundPeopleSuggestion</key>
|
<key>BackgroundPeopleSuggestion</key>
|
||||||
<date>2019-12-29T06:18:40Z</date>
|
<date>2019-12-29T09:24:36Z</date>
|
||||||
<key>BackgroundUserBehaviorProcessor</key>
|
<key>BackgroundUserBehaviorProcessor</key>
|
||||||
<date>2019-12-28T23:29:58Z</date>
|
<date>2019-12-28T23:29:58Z</date>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
|
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
|
||||||
<date>2019-12-29T06:18:45Z</date>
|
<date>2019-12-29T10:47:45Z</date>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||||
<date>2019-12-28T23:29:57Z</date>
|
<date>2019-12-28T23:29:57Z</date>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||||
<date>2019-12-29T06:18:40Z</date>
|
<date>2019-12-29T09:24:37Z</date>
|
||||||
<key>SiriPortraitDonation</key>
|
<key>SiriPortraitDonation</key>
|
||||||
<date>2019-12-28T23:29:58Z</date>
|
<date>2019-12-28T23:29:58Z</date>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
Reference in New Issue
Block a user