Compare commits

...

77 Commits

Author SHA1 Message Date
Rhet Turnbull
1b17608a02 Added TODOs 2019-11-24 21:24:02 -08:00
Rhet Turnbull
d4353d48bd Fixed bug in date() if imageTimeZoneOffsetSeconds was None 2019-11-24 21:12:26 -08:00
Rhet Turnbull
5af2b3e039 Added name and description to cmd_line 2019-11-24 21:08:01 -08:00
Rhet Turnbull
b36b7e7eb2 Added hidden/favorite/missing to cmd_line 2019-11-24 20:14:59 -08:00
Rhet Turnbull
99be93d88f version bump 2019-11-24 09:46:51 -08:00
Rhet Turnbull
d840c11ddb Added favorite and hidden to cmd_line 2019-11-24 09:45:36 -08:00
Rhet Turnbull
b1fd019120 Added hidden and favorite and test for 10.15 2019-11-24 09:39:36 -08:00
Rhet Turnbull
8b4a386c13 Fixed original_filename for older database versions 2019-11-24 08:46:02 -08:00
Rhet Turnbull
6abd10eddf removed old progress bar code 2019-11-24 08:35:30 -08:00
Rhet Turnbull
aa73c2f055 removed loguru code 2019-11-24 08:33:00 -08:00
Rhet Turnbull
966954340b version bump for release to pypi 2019-11-23 19:40:29 -08:00
Rhet Turnbull
7732708cb2 fixed command line to better handle detected but unidentified faces in Photos 5 2019-11-23 19:24:20 -08:00
Rhet Turnbull
a7d9f72372 Updated doc string 2019-11-23 19:06:52 -08:00
Rhet Turnbull
eabda3ea93 Turned off debug 2019-11-23 19:05:25 -08:00
Rhet Turnbull
a995a8d610 README update 2019-11-23 14:53:17 -08:00
Rhet Turnbull
ecd75c775d Added link to issues 2019-11-23 14:51:32 -08:00
Rhet Turnbull
ae7fc69b33 version bump 2019-11-23 14:49:13 -08:00
Rhet Turnbull
83186a655d Changed path() to return absolute path and fixed tests 2019-11-23 14:48:38 -08:00
Rhet Turnbull
243492df88 added test for 10.15/Catalina 2019-11-23 13:56:26 -08:00
Rhet Turnbull
986a092130 fixed == / != None 2019-11-23 13:27:54 -08:00
Rhet Turnbull
1c323338c6 version bump 2019-11-18 21:04:30 -08:00
Rhet Turnbull
b005f70133 fixed bug on 3.6 due to logging 2019-11-18 21:03:55 -08:00
Rhet Turnbull
9023a69073 Initial release for MacOS 10.15 / Photos 5 2019-11-17 18:01:03 -08:00
Rhet Turnbull
e7958c94e8 alpha version of filenames/paths 2019-11-17 17:19:33 -08:00
Rhet Turnbull
17bc04a24a replaced debug print statements with logging.debug 2019-11-17 16:46:34 -08:00
Rhet Turnbull
e0b1113870 replaced debug print statements with logging.debug 2019-11-17 16:45:46 -08:00
Rhet Turnbull
10f0cf1092 query will now run on Photos 5 2019-11-17 08:53:24 -08:00
Rhet Turnbull
a4b5f2a501 basic Photos 5 info now being read 2019-11-17 08:43:34 -08:00
Rhet Turnbull
02fcfbca2c More updates to _process_photos5 2019-11-16 11:01:04 -08:00
Rhet Turnbull
7eff015439 moved process_photos to process_photos4 and process_photos5 2019-11-16 08:18:22 -08:00
Rhet Turnbull
1c7e81b578 Fixed cleanup code 2019-11-16 07:59:59 -08:00
Rhet Turnbull
8649cda11f Fixed cleanup code 2019-11-16 07:59:04 -08:00
Rhet Turnbull
0c152c8c91 Updated some doc strings 2019-11-16 07:28:45 -08:00
Rhet Turnbull
b0a9e87d00 added _cleanup_tmp_files 2019-11-13 21:48:50 -08:00
Rhet Turnbull
fc6d7b1cf5 added comments/TODOs 2019-11-11 21:39:51 -08:00
Rhet Turnbull
5234567974 Moved db version check to function in prep for move to Photos 5 code 2019-11-11 21:30:51 -08:00
Rhet Turnbull
f62a9d3d4e fixed version check for Catalina 2019-10-14 09:44:11 -07:00
Rhet Turnbull
d311431c91 README update, added todo 2019-09-30 09:57:55 -07:00
Rhet Turnbull
572e32b71b version bump 2019-08-31 13:01:25 -07:00
Rhet Turnbull
3744a596b5 Merge branch 'master' of https://github.com/RhetTbull/osxphotos 2019-08-31 13:00:29 -07:00
Rhet Turnbull
221df0029e Added pythonpackage.yml for CI workflow 2019-08-31 12:52:21 -07:00
Rhet Turnbull
a1038314e2 added todo 2019-08-24 08:36:57 -07:00
Rhet Turnbull
39ef8ddf3f fixed typo in README 2019-08-24 08:31:34 -07:00
Rhet Turnbull
292f3e5ea4 release 0.12.2 for pypi 2019-08-24 08:24:51 -07:00
Rhet Turnbull
e9a4de447c Updated help text 2019-08-24 08:01:53 -07:00
Rhet Turnbull
42bc929142 Updated README for 10.12 2019-08-24 07:42:38 -07:00
Rhet Turnbull
58f52833d6 Added support and tests for 10.12 2019-08-24 07:25:48 -07:00
Rhet Turnbull
7cf8dcf22a sorted imports 2019-08-23 17:04:55 -07:00
Rhet Turnbull
e139fbb2fb import sort 2019-08-23 06:58:38 -07:00
Rhet Turnbull
332cbe3e37 black 2019-08-19 21:29:27 -07:00
Rhet Turnbull
e35876a1ba version bump 2019-08-19 21:28:16 -07:00
Rhet Turnbull
693d27c049 version bump 2019-08-19 21:27:32 -07:00
Rhet Turnbull
8742967885 updated requirements.txt 2019-08-19 21:24:46 -07:00
Rhet Turnbull
a27cf45f95 fixed PhotoInfo.date() to account for timezone, updated tests 2019-08-18 22:29:53 -07:00
Rhet Turnbull
93cc305fa7 added help to query 2019-08-18 19:13:35 -07:00
Rhet Turnbull
0d51798384 Updated README 2019-08-18 16:59:46 -07:00
Rhet Turnbull
22e466cd0e removed loguru 2019-08-18 16:56:05 -07:00
Rhet Turnbull
33ef56fd94 added click in to install_requires 2019-08-18 16:54:19 -07:00
Rhet Turnbull
6b24706851 fixed --json for query 2019-08-18 16:02:06 -07:00
Rhet Turnbull
be3d6dee8d added to_json() to PhotoInfo 2019-08-18 15:21:28 -07:00
Rhet Turnbull
07c9c31a09 Added query to cmd_line and added __repr__ and __str__ to osxphotos classes 2019-08-18 14:56:04 -07:00
Rhet Turnbull
9db32a51ca Added description to cmd_line commands 2019-08-18 09:59:16 -07:00
Rhet Turnbull
5200cd69ce Updated json output 2019-08-18 09:48:58 -07:00
Rhet Turnbull
0e65ab5452 Added osxphotos command line tool 2019-08-18 09:39:55 -07:00
Rhet Turnbull
cbdc193b83 version bump 2019-08-17 10:56:51 -04:00
Rhet Turnbull
fb2c12d981 Added tests for 10.14.6 2019-08-17 10:42:27 -04:00
Rhet Turnbull
81be373505 update requirements.txt 2019-07-27 21:55:19 -04:00
Rhet Turnbull
5f1bd5f13d More README updates 2019-07-27 21:52:30 -04:00
Rhet Turnbull
7ef472fcb2 README update 2019-07-27 21:48:10 -04:00
Rhet Turnbull
a58ac149f3 Updated README, added os & db version tests, updated test library for 10.13 2019-07-27 21:45:37 -04:00
Rhet Turnbull
32bd4cfb9d updated README 2019-07-27 21:17:02 -04:00
Rhet Turnbull
9b44adff34 Standardized warning for OS/DB version check 2019-07-27 21:06:48 -04:00
Rhet Turnbull
75c25c8bf5 Added checks for database version and OS version 2019-07-27 20:57:37 -04:00
Rhet Turnbull
a79472ba0c renamed test files 2019-07-27 20:24:31 -04:00
Rhet Turnbull
cafb88173f removed extra ismissing definition, fixed some linting issues 2019-07-27 20:16:17 -04:00
Rhet Turnbull
af122e9392 Added test for 10.14 mojave 2019-07-27 17:48:27 -04:00
Rhet Turnbull
470585bf8c Removed stray logging statement, fixed version check 2019-07-26 23:06:53 -04:00
443 changed files with 3442 additions and 227 deletions

34
.github/workflows/pythonpackage.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Python package
on: [push]
jobs:
build:
runs-on: macOS-10.14
strategy:
max-parallel: 4
matrix:
python-version: [3.6, 3.7]
steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Lint with flake8
run: |
# pip install flake8
# stop the build if there are Python syntax errors or undefined names
# flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
# flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pip install pytest
python -m pytest tests/

120
README.md
View File

@@ -1,12 +1,20 @@
# [OSXPhotos](https://github.com/RhetTbull/osxphotos)
# OSXPhotos
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
## What is osxphotos?
OSXPhotos provides the ability to interact with and query Apple's Photos app library database on Mac OS X.
OSXPhotos provides the ability to interact with and query Apple's Photos app library database on Mac OS X. Using this module you can query the Photos database for information about the photos stored in a Photos library--for example, file name, file path, and metadata such as keywords/tags, persons/faces, albums, etc.
## Supported operating systems
Only works on Mac OS X. Only tested on Mac OS 10.13 and Photos 3.0. Requires python >= 3.6
Only works on Mac OS X. Tested on Mac OS 10.12.6 / Photos 2.0, 10.13.6 / Photos 3.0 and Mac OS 10.14.5, 10.14.6 / Photos 4.0. Requires python >= 3.6
NOTE: Alpha support for Mac OS 10.15.0 / Photos 5.0. Photos 5.0 uses a new database format which required rewrite of much of the code for this package. If you find bugs, please open an [issue](https://github.com/RhetTbull/osxphotos/issues/).
This module will read Photos databases for any supported version on any supported OS version. E.g. you can read a database created with Photos 4.0 on Mac OS 10.14 on a machine running Mac OS 10.12
## Installation instructions
@@ -16,7 +24,67 @@ osxmetadata uses setuptools, thus simply run:
## Command Line Usage
This project started as a command line utility, `photosmeta`, available at [photosmeta](https://github.com/RhetTbull/photosmeta) This module converts the photosmeta functionality into a module. Eventually, I plan to rewrite photosmeta using this module and include it as a command line script in this module.
This module will install a command line utility called `osxphotos` that allows you to query the Photos database.
If you only care about the command line tool, I recommend installing with [pipx](https://github.com/pipxproject/pipx)
After installing pipx:
`pipx install osxphotos`
```
Usage: osxphotos [OPTIONS] COMMAND [ARGS]...
Options:
--db <Photos database path> Specify database file
--json Print output in JSON format
-h, --help Show this message and exit.
Commands:
albums print out albums found in the Photos library
dump print list of all photos & associated info from the Photos...
help print help; for help on commands: help <command>
info print out descriptive info of the Photos library database
keywords print out keywords found in the Photos library
persons print out persons (faces) found in the Photos library
query query the Photos database using 1 or more search options
```
To get help on a specific command, use `osxphotos help <command_name>`
Example: `osxphotos help query`
```
Usage: osxphotos help [OPTIONS]
Query the Photos database using 1 or more search options
If more than one option is provided, they are treated as "AND" (e.g.
search for photos matching all options)
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)
--name TEXT search for TEXT in name of photo
--no-name search for photos with no name
--description TEXT search for TEXT in description of photo
--no-description search for photos with no description
-i, --ignore-case case insensitive search for name or description. Does
not apply to keyword, person, or album
--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
--missing search for photos missing from disk
--not-missing search for photos present on disk (e.g. not missing)
--json Print output in JSON format
-h, --help Show this message and exit.
```
Example: find all photos with keyword "Kids" and output results to json file named results.json:
`osxphotos query --keyword Kids --json >results.json`
## Example uses of the module
@@ -43,6 +111,7 @@ def main():
print(
p.uuid,
p.filename(),
p.original_filename(),
p.date(),
p.description(),
p.name(),
@@ -143,6 +212,23 @@ photosdb.get_photos_library_path()
Returns the path to the Photos library as a string
#### ```get_db_path```
```python
# assumes photosdb is a PhotosDB object (see above)
photosdb.get_db_path()
```
Returns the path to the Photos database PhotosDB was initialized with
#### ```get_db_version```
```python
# assumes photosdb is a PhotosDB object (see above)
photosdb.get_db_version()
```
Returns the version number for Photos library database. You likely won't need this but it's provided in case needed for debugging. PhotosDB will print a warning to `sys.stderr` if you open a database version that has not been tested.
#### ```photos```
```python
# assumes photosdb is a PhotosDB object (see above)
@@ -216,7 +302,10 @@ PhotosDB.photos() returns a list of PhotoInfo objects. Each PhotoInfo object re
Returns the universally unique identifier (uuid) of the photo. This is how Photos keeps track of individual photos within the database.
#### `filename()`
Returns the filename of the photo
Returns the current filename of the photo on disk. See also `original_filename()`
#### `original_filename()`
Returns the original filename of the photo when it was imported to Photos. Note: Photos 5.0+ renames the photo when it adds the file to the library using UUID. See also `filename()`
#### `date()`
Returns the date of the photo as a datetime.datetime object
@@ -237,7 +326,7 @@ Returns a list of albums the photo is contained in
Returns a list of the names of the persons in the photo
#### `path()`
Returns the path to the photo on disk as a string. Note: this returns the path to the *original* unedited file (see `hasadjustments()`). If the file is missing on disk, path=`None` (see `ismissing()`)
Returns the absolute path to the photo on disk as a string. Note: this returns the path to the *original* unedited file (see `hasadjustments()`). If the file is missing on disk, path=`None` (see `ismissing()`)
#### `ismissing()`
Returns `True` if the original image file is missing on disk, otherwise `False`. This can occur if the file has been uploaded to iCloud but not yet downloaded to the local library or if the file was deleted or imported from a disk that has been unmounted. Note: this status is set by Photos and osxphotos does not verify that the file path returned by `path()` actually exists. It merely reports what Photos has stored in the library database.
@@ -245,6 +334,15 @@ Returns `True` if the original image file is missing on disk, otherwise `False`.
#### `hasadjustments()`
Returns `True` if the file has been edited in Photos, otherwise `False`
#### `favorite()`
Returns `True` if the picture has been marked as a favorite, otherwise `False`
#### `hidden()`
Returns `True` if the picture has been marked as hidden, otherwise `False`
#### `to_json()`
Returns a JSON representation of all photo info
Examples:
```python
@@ -254,6 +352,7 @@ for p in photos:
print(
p.uuid(),
p.filename(),
p.original_filename(),
p.date(),
p.description(),
p.name(),
@@ -266,6 +365,11 @@ for p in photos:
)
```
## History
This project started as a command line utility, `photosmeta`, available at [photosmeta](https://github.com/RhetTbull/photosmeta) This module converts the photosmeta Photos library query functionality into a module.
## Implementation Notes
This module is very kludgy. It 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.
@@ -275,7 +379,9 @@ If Apple changes the database format this will likely break.
The sqlite3 database used by Photos uses write ahead logging that is updated asynchronously in the background by a Photos helper service. Sometimes the update takes a long time meaning the latest changes made in Photos (e.g. add a keyword) will not show up in the database for sometime. I know of no way around this.
## Dependencies
[PyObjC](https://pythonhosted.org/pyobjc/)
- [PyObjC](https://pythonhosted.org/pyobjc/)
- [PyYAML](https://pypi.org/project/PyYAML/)
- [Click](https://pypi.org/project/click/)
## Acknowledgements
This code was inspired by photo-export by Patrick Fältström see: (https://github.com/patrikhson/photo-export) Copyright (c) 2015 Patrik Fältström paf@frobbit.se

View File

@@ -3,6 +3,9 @@ import osxphotos
def main():
photosdb = osxphotos.PhotosDB()
print(f"db file = {photosdb.get_db_path()}")
print(f"db version = {photosdb.get_db_version()}")
print(photosdb.keywords())
print(photosdb.persons())
print(photosdb.albums())

File diff suppressed because it is too large Load Diff

354
osxphotos/cmd_line.py Normal file
View File

@@ -0,0 +1,354 @@
import csv
import json
import sys
import click
import yaml
import osxphotos
# TODO: add query for description, name (contains text)
# TODO: add "--any" to search any field (e.g. keyword, description, name contains "wedding") (add case insensitive option)
class CLI_Obj:
def __init__(self, db=None, json=False):
self.photosdb = osxphotos.PhotosDB(dbfile=db)
self.json = json
CTX_SETTINGS = dict(help_option_names=["-h", "--help"])
@click.group(context_settings=CTX_SETTINGS)
@click.option(
"--db",
required=False,
metavar="<Photos database path>",
default=None,
help="Specify database file",
)
@click.option(
"--json",
required=False,
is_flag=True,
default=False,
help="Print output in JSON format",
)
@click.pass_context
def cli(ctx, db, json):
ctx.obj = CLI_Obj(db=db, json=json)
@cli.command()
@click.pass_obj
def keywords(cli_obj):
""" print out keywords found in the Photos library"""
keywords = {"keywords": cli_obj.photosdb.keywords_as_dict()}
if cli_obj.json:
print(json.dumps(keywords))
else:
print(yaml.dump(keywords, sort_keys=False))
@cli.command()
@click.pass_obj
def albums(cli_obj):
""" print out albums found in the Photos library """
albums = {"albums": cli_obj.photosdb.albums_as_dict()}
if cli_obj.json:
print(json.dumps(albums))
else:
print(yaml.dump(albums, sort_keys=False))
@cli.command()
@click.pass_obj
def persons(cli_obj):
""" print out persons (faces) found in the Photos library """
persons = {"persons": cli_obj.photosdb.persons_as_dict()}
if cli_obj.json:
print(json.dumps(persons))
else:
print(yaml.dump(persons, sort_keys=False))
@cli.command()
@click.pass_obj
def info(cli_obj):
""" print out descriptive info of the Photos library database """
pdb = cli_obj.photosdb
info = {}
info["database_path"] = pdb.get_db_path()
info["database_version"] = pdb.get_db_version()
photos = pdb.photos()
info["photo_count"] = len(photos)
keywords = pdb.keywords_as_dict()
info["keywords_count"] = len(keywords)
info["keywords"] = keywords
albums = pdb.albums_as_dict()
info["albums_count"] = len(albums)
info["albums"] = albums
persons = pdb.persons_as_dict()
# handle empty person names (added by Photos 5.0+ when face detected but not identified)
noperson = "UNKNOWN"
if "" in persons:
if noperson in persons:
persons[noperson].append(persons[""])
else:
persons[noperson] = persons[""]
persons.pop("", None)
info["persons_count"] = len(persons)
info["persons"] = persons
if cli_obj.json:
print(json.dumps(info))
else:
print(yaml.dump(info, sort_keys=False))
@cli.command()
@click.pass_obj
def dump(cli_obj):
""" print list of all photos & associated info from the Photos library """
pdb = cli_obj.photosdb
photos = pdb.photos()
print_photo_info(photos, cli_obj.json)
@cli.command()
@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)")
@click.option("--uuid", default=None, multiple=True, help="search for UUID(s)")
@click.option(
"--name", default=None, multiple=True, help="search for TEXT in name of photo"
)
@click.option("--no-name", is_flag=True, help="search for photos with no name")
@click.option(
"--description",
default=None,
multiple=True,
help="search for TEXT in description of photo",
)
@click.option("--no-description", is_flag=True, help="search for photos with no description")
@click.option(
"-i",
"--ignore-case",
is_flag=True,
help="case insensitive search for name or description. Does not apply to keyword, person, or album",
)
@click.option("--favorite", is_flag=True, help="search for photos marked favorite")
@click.option(
"--not-favorite", is_flag=True, help="search for photos not marked favorite"
)
@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("--missing", is_flag=True, help="search for photos missing from disk")
@click.option(
"--not-missing",
is_flag=True,
help="search for photos present on disk (e.g. not missing)",
)
@click.option(
"--json",
required=False,
is_flag=True,
default=False,
help="Print output in JSON format",
)
@click.pass_obj
@click.pass_context
def query(
ctx,
cli_obj,
keyword,
person,
album,
uuid,
name,
no_name,
description,
no_description,
ignore_case,
json,
favorite,
not_favorite,
hidden,
not_hidden,
missing,
not_missing,
):
""" Query the Photos database using 1 or more search options\n
If more than one option is provided, they are treated as "AND"
(e.g. search for photos matching all options)
"""
# if no query terms, show help and return
if (
not keyword
and not person
and not album
and not uuid
and not name
and not no_name
and not description
and not no_description
and not favorite
and not not_favorite
and not hidden
and not not_hidden
and not missing
and not not_missing
):
print(cli.commands["query"].get_help(ctx))
return
elif favorite and not_favorite:
# can't search for both favorite and notfavorite
print(cli.commands["query"].get_help(ctx))
return
elif hidden and not_hidden:
# can't search for both hidden and nothidden
print(cli.commands["query"].get_help(ctx))
return
elif missing and not_missing:
# can't search for both missing and notmissing
print(cli.commands["query"].get_help(ctx))
return
elif name and no_name:
# can't search for both name and no_name
print(cli.commands["query"].get_help(ctx))
return
elif description and no_description:
# can't search for both description and no_description
print(cli.commands["query"].get_help(ctx))
return
else:
photos = cli_obj.photosdb.photos(
keywords=keyword, persons=person, albums=album, uuid=uuid
)
if name:
# search name field for text
# if more than one, find photos with all name values in in name
if ignore_case:
# case-insensitive
for n in name:
n = n.lower()
photos = [p for p in photos if p.name() and n in p.name().lower()]
else:
for n in name:
photos = [p for p in photos if p.name() and n in p.name()]
elif no_name:
photos = [p for p in photos if not p.name()]
if description:
# search description field for text
# if more than one, find photos with all name values in in description
if ignore_case:
# case-insensitive
for d in description:
d = d.lower()
photos = [
p
for p in photos
if p.description() and d in p.description().lower()
]
else:
for d in description:
photos = [
p for p in photos if p.description() and d in p.description()
]
elif no_description:
photos = [p for p in photos if not p.description()]
if favorite:
photos = [p for p in photos if p.favorite()]
elif not_favorite:
photos = [p for p in photos if not p.favorite()]
if hidden:
photos = [p for p in photos if p.hidden()]
elif not_hidden:
photos = [p for p in photos if not p.hidden()]
if missing:
photos = [p for p in photos if p.ismissing()]
elif not_missing:
photos = [p for p in photos if not p.ismissing()]
print_photo_info(photos, cli_obj.json or json)
@cli.command()
@click.argument("topic", default=None, required=False, nargs=1)
@click.pass_context
def help(ctx, topic, **kw):
""" print help; for help on commands: help <command> """
if topic is None:
print(ctx.parent.get_help())
else:
print(cli.commands[topic].get_help(ctx))
def print_photo_info(photos, json=False):
if json:
dump = []
for p in photos:
dump.append(p.to_json())
print(f"[{', '.join(dump)}]")
else:
# dump as CSV
csv_writer = csv.writer(
sys.stdout, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
)
dump = []
# add headers
dump.append(
[
"uuid",
"filename",
"original_filename",
"date",
"description",
"name",
"keywords",
"albums",
"persons",
"path",
"ismissing",
"hasadjustments",
"favorite",
"hidden",
]
)
for p in photos:
dump.append(
[
p.uuid(),
p.filename(),
p.original_filename(),
str(p.date()),
p.description(),
p.name(),
", ".join(p.keywords()),
", ".join(p.albums()),
", ".join(p.persons()),
p.path(),
p.ismissing(),
p.hasadjustments(),
p.favorite(),
p.hidden(),
]
)
for row in dump:
csv_writer.writerow(row)
if __name__ == "__main__":
cli()

View File

@@ -1,4 +1,122 @@
# pip install -r requirements.txt
pyobjc-core
pyobjc
ansimarkup==1.4.0
astroid==2.2.5
atomicwrites==1.3.0
attrs==19.1.0
better-exceptions-fork==0.2.1.post6
certifi==2019.3.9
Click==7.0
colorama==0.4.1
importlib-metadata==0.18
isort==4.3.20
lazy-object-proxy==1.4.1
mccabe==0.6.1
more-itertools==7.2.0
packaging==19.0
pluggy==0.12.0
py==1.8.0
Pygments==2.4.2
pylint==2.3.1
pyobjc==5.2
pyobjc-core==5.2
pyobjc-framework-Accounts==5.2
pyobjc-framework-AddressBook==5.2
pyobjc-framework-AdSupport==5.2
pyobjc-framework-AppleScriptKit==5.2
pyobjc-framework-AppleScriptObjC==5.2
pyobjc-framework-ApplicationServices==5.2
pyobjc-framework-Automator==5.2
pyobjc-framework-AVFoundation==5.2
pyobjc-framework-AVKit==5.2
pyobjc-framework-BusinessChat==5.2
pyobjc-framework-CalendarStore==5.2
pyobjc-framework-CFNetwork==5.2
pyobjc-framework-CloudKit==5.2
pyobjc-framework-Cocoa==5.2
pyobjc-framework-Collaboration==5.2
pyobjc-framework-ColorSync==5.2
pyobjc-framework-Contacts==5.2
pyobjc-framework-ContactsUI==5.2
pyobjc-framework-CoreAudio==5.2
pyobjc-framework-CoreAudioKit==5.2
pyobjc-framework-CoreBluetooth==5.2
pyobjc-framework-CoreData==5.2
pyobjc-framework-CoreLocation==5.2
pyobjc-framework-CoreMedia==5.2
pyobjc-framework-CoreMediaIO==5.2
pyobjc-framework-CoreML==5.2
pyobjc-framework-CoreServices==5.2
pyobjc-framework-CoreSpotlight==5.2
pyobjc-framework-CoreText==5.2
pyobjc-framework-CoreWLAN==5.2
pyobjc-framework-CryptoTokenKit==5.2
pyobjc-framework-DictionaryServices==5.2
pyobjc-framework-DiscRecording==5.2
pyobjc-framework-DiscRecordingUI==5.2
pyobjc-framework-DiskArbitration==5.2
pyobjc-framework-DVDPlayback==5.2
pyobjc-framework-EventKit==5.2
pyobjc-framework-ExceptionHandling==5.2
pyobjc-framework-ExternalAccessory==5.2
pyobjc-framework-FinderSync==5.2
pyobjc-framework-FSEvents==5.2
pyobjc-framework-GameCenter==5.2
pyobjc-framework-GameController==5.2
pyobjc-framework-GameKit==5.2
pyobjc-framework-GameplayKit==5.2
pyobjc-framework-ImageCaptureCore==5.2
pyobjc-framework-IMServicePlugIn==5.2
pyobjc-framework-InputMethodKit==5.2
pyobjc-framework-InstallerPlugins==5.2
pyobjc-framework-InstantMessage==5.2
pyobjc-framework-Intents==5.2
pyobjc-framework-IOSurface==5.2
pyobjc-framework-iTunesLibrary==5.2
pyobjc-framework-LatentSemanticMapping==5.2
pyobjc-framework-LaunchServices==5.2
pyobjc-framework-libdispatch==5.2
pyobjc-framework-LocalAuthentication==5.2
pyobjc-framework-MapKit==5.2
pyobjc-framework-MediaAccessibility==5.2
pyobjc-framework-MediaLibrary==5.2
pyobjc-framework-MediaPlayer==5.2
pyobjc-framework-MediaToolbox==5.2
pyobjc-framework-ModelIO==5.2
pyobjc-framework-MultipeerConnectivity==5.2
pyobjc-framework-NaturalLanguage==5.2
pyobjc-framework-NetFS==5.2
pyobjc-framework-Network==5.2
pyobjc-framework-NetworkExtension==5.2
pyobjc-framework-NotificationCenter==5.2
pyobjc-framework-OpenDirectory==5.2
pyobjc-framework-OSAKit==5.2
pyobjc-framework-Photos==5.2
pyobjc-framework-PhotosUI==5.2
pyobjc-framework-PreferencePanes==5.2
pyobjc-framework-PubSub==5.2
pyobjc-framework-QTKit==5.2
pyobjc-framework-Quartz==5.2
pyobjc-framework-SafariServices==5.2
pyobjc-framework-SceneKit==5.2
pyobjc-framework-ScreenSaver==5.2
pyobjc-framework-ScriptingBridge==5.2
pyobjc-framework-SearchKit==5.2
pyobjc-framework-Security==5.2
pyobjc-framework-SecurityFoundation==5.2
pyobjc-framework-SecurityInterface==5.2
pyobjc-framework-ServiceManagement==5.2
pyobjc-framework-Social==5.2
pyobjc-framework-SpriteKit==5.2
pyobjc-framework-StoreKit==5.2
pyobjc-framework-SyncServices==5.2
pyobjc-framework-SystemConfiguration==5.2
pyobjc-framework-UserNotifications==5.2
pyobjc-framework-VideoSubscriberAccount==5.2
pyobjc-framework-VideoToolbox==5.2
pyobjc-framework-Vision==5.2
pyobjc-framework-WebKit==5.2
pyparsing==2.4.1.1
PyYAML==5.1.2
six==1.12.0
wcwidth==0.1.7
wrapt==1.11.1
zipp==0.5.2

View File

@@ -38,7 +38,7 @@ with open(path.join(this_directory, "README.md"), encoding="utf-8") as f:
setup(
name="osxphotos",
version="0.10.1",
version="0.14.4",
description="Manipulate (read-only) Apple's Photos app library on Mac OS X",
long_description=long_description,
long_description_content_type="text/markdown",
@@ -58,8 +58,8 @@ setup(
"Programming Language :: Python :: 3.6",
"Topic :: Software Development :: Libraries :: Python Modules",
],
install_requires=["pyobjc",],
# entry_points = {
# 'console_scripts' : ['osxmetadata=osxmetadata.cmd_line:main'],
# }
install_requires=["pyobjc","Click","pyyaml",],
entry_points = {
'console_scripts' : ['osxphotos=osxphotos.cmd_line:cli'],
}
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>DatabaseMinorVersion</key>
<integer>1</integer>
<key>DatabaseVersion</key>
<integer>112</integer>
<key>LastOpenMode</key>
<integer>2</integer>
<key>LibrarySchemaVersion</key>
<integer>2622</integer>
<key>MetaSchemaVersion</key>
<integer>2</integer>
<key>createDate</key>
<date>2019-08-24T02:50:48Z</date>
</dict>
</plist>

Binary file not shown.

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LithiumMessageTracer</key>
<dict>
<key>LastReportedDate</key>
<date>2019-08-24T02:50:48Z</date>
</dict>
<key>PXPeopleScreenUnlocked</key>
<true/>
<key>Photos</key>
<dict>
<key>IPXWorkspaceControllerPhotosHasContentKey</key>
<true/>
<key>IPXWorkspaceControllerZoomLevelsKey</key>
<dict>
<key>kZoomLevelIdentifierAlbums</key>
<integer>7</integer>
<key>kZoomLevelIdentifierVersions</key>
<integer>7</integer>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
<date>2019-08-24T02:51:33Z</date>
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
<date>2019-08-24T13:19:30Z</date>
</dict>
</plist>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PLLanguageAndLocaleKey</key>
<string>en-US:en_US</string>
<key>PLLastGeoProviderIdKey</key>
<string>7618</string>
<key>PLLastLocationInfoFormatVer</key>
<integer>12</integer>
<key>PLLastRevGeoForcedProviderOutOfDateCheckVersionKey</key>
<integer>1</integer>
<key>PLLastRevGeoVerFileFetchDateKey</key>
<date>2019-08-24T02:51:30Z</date>
</dict>
</plist>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LastHistoryRowId</key>
<integer>414</integer>
<key>LibraryBuildTag</key>
<string>E3E46F2A-7168-4973-AB3E-5848F80BFC7D</string>
<key>LibrarySchemaVersion</key>
<integer>2622</integer>
</dict>
</plist>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>FileVersion</key>
<integer>11</integer>
<key>Source</key>
<dict>
<key>35230</key>
<dict>
<key>CountryMinVersions</key>
<dict>
<key>OTHER</key>
<integer>1</integer>
</dict>
<key>CurrentVersion</key>
<integer>1</integer>
<key>NoResultErrorIsSuccess</key>
<true/>
</dict>
<key>57879</key>
<dict>
<key>CountryMinVersions</key>
<dict>
<key>OTHER</key>
<integer>1</integer>
</dict>
<key>CurrentVersion</key>
<integer>1</integer>
<key>NoResultErrorIsSuccess</key>
<true/>
</dict>
<key>7618</key>
<dict>
<key>AddCountyIfNeeded</key>
<true/>
<key>CountryMinVersions</key>
<dict>
<key>OTHER</key>
<integer>10</integer>
</dict>
<key>CurrentVersion</key>
<integer>10</integer>
</dict>
</dict>
</dict>
</plist>

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>DatabaseMinorVersion</key>
<integer>1</integer>
<key>DatabaseVersion</key>
<integer>112</integer>
<key>HistoricalMarker</key>
<dict>
<key>LastHistoryRowId</key>
<integer>403</integer>
<key>LibraryBuildTag</key>
<string>E3E46F2A-7168-4973-AB3E-5848F80BFC7D</string>
<key>LibrarySchemaVersion</key>
<integer>2622</integer>
</dict>
<key>LibrarySchemaVersion</key>
<integer>2622</integer>
<key>MetaSchemaVersion</key>
<integer>2</integer>
<key>SnapshotComplete</key>
<true/>
<key>SnapshotCompletedDate</key>
<date>2019-08-24T02:50:48Z</date>
<key>SnapshotTables</key>
<dict/>
</dict>
</plist>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -3,8 +3,8 @@
<plist version="1.0">
<dict>
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
<date>2019-07-26T20:15:18Z</date>
<date>2019-07-28T01:23:52Z</date>
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
<date>2019-07-26T20:15:18Z</date>
<date>2019-07-28T01:23:52Z</date>
</dict>
</plist>

View File

@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
<dict>
<key>SuggestedMeIdentifier</key>
<string></string>
</dict>
</plist>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LithiumMessageTracer</key>
<dict>
<key>LastReportedDate</key>
<date>2019-07-27T12:01:15Z</date>
</dict>
</dict>
</plist>

View File

@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>LastHistoryRowId</key>
<integer>545</integer>
<integer>615</integer>
<key>LibraryBuildTag</key>
<string>BEA5F0E8-BA6B-4462-8F73-3E53BBE4C943</string>
<key>LibrarySchemaVersion</key>

Some files were not shown because too many files have changed in this diff Show More