Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf8aed69cf | ||
|
|
800daf3658 | ||
|
|
d5a5bd41b3 | ||
|
|
911804317b | ||
|
|
aaba5cabf3 | ||
|
|
7fef67f852 | ||
|
|
62fedc7fbf | ||
|
|
1d006a4b50 | ||
|
|
2cedaedebb | ||
|
|
22d747ebab | ||
|
|
c1c7b0092d | ||
|
|
0220b0eaff | ||
|
|
d22affebd7 | ||
|
|
da320e4f56 | ||
|
|
591336673a | ||
|
|
906b6c0911 | ||
|
|
f21c7c8b08 | ||
|
|
1ea83e6c8e | ||
|
|
45da323840 | ||
|
|
def6f8cdfe | ||
|
|
6e45cf9591 | ||
|
|
7fb0bad6be | ||
|
|
811946018d | ||
|
|
2a0f27ca57 | ||
|
|
ff4066c49c | ||
|
|
1cf3e4b954 | ||
|
|
0219a9b4da | ||
|
|
b3c798033c | ||
|
|
9777e27e3a | ||
|
|
3a1ca343a6 | ||
|
|
42baa29c18 | ||
|
|
6a2be3e7d9 | ||
|
|
9c32d77b2c | ||
|
|
eb563ad297 | ||
|
|
d056b6f8c0 | ||
|
|
5a1176ed86 | ||
|
|
e77b8e8a0a |
210
README.md
@@ -5,15 +5,14 @@
|
||||
|
||||
## 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--for example, file name, file path, and metadata such as keywords/tags, persons/faces, albums, etc.
|
||||
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.
|
||||
|
||||
NOTE: OSXPhotos currently only supports image files -- e.g. it does not handle movies.
|
||||
**NOTE**: OSXPhotos currently only supports image files -- e.g. it does not handle movies.
|
||||
|
||||
## Supported operating systems
|
||||
|
||||
Only works on MacOS (aka Mac OS X). Tested on MacOS 10.12.6 / Photos 2.0, 10.13.6 / Photos 3.0 and MacOS 10.14.5, 10.14.6 / Photos 4.0. Requires python >= 3.6
|
||||
Only works on MacOS (aka Mac OS X). Tested on MacOS 10.12.6 / Photos 2.0, 10.13.6 / Photos 3.0, MacOS 10.14.5, 10.14.6 / Photos 4.0, MacOS 10.15.1 / Photos 5.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 module. 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 MacOS 10.14 on a machine running MacOS 10.12
|
||||
|
||||
@@ -37,18 +36,20 @@ After installing pipx:
|
||||
Usage: osxphotos [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
Options:
|
||||
--db <Photos database path> Specify database file
|
||||
--json Print output in JSON format
|
||||
--db <Photos database path> Specify database file.
|
||||
--json Print output in JSON format.
|
||||
-v, --version Show the version and exit.
|
||||
-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
|
||||
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.
|
||||
list Print list of Photos libraries found on the system.
|
||||
persons Print out persons (faces) found in the Photos library.
|
||||
query Query the Photos database using 1 or more search options; if...
|
||||
```
|
||||
|
||||
To get help on a specific command, use `osxphotos help <command_name>`
|
||||
@@ -58,28 +59,29 @@ 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)
|
||||
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)
|
||||
--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.
|
||||
--edited Search for photos that have been edited.
|
||||
--external-edit Search for photos edited in external editor.
|
||||
--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.
|
||||
```
|
||||
@@ -127,19 +129,68 @@ if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
```python
|
||||
""" Export all photos to ~/Desktop/export
|
||||
If file has been edited, export the edited version,
|
||||
otherwise, export the original version """
|
||||
|
||||
import os.path
|
||||
|
||||
import osxphotos
|
||||
|
||||
def main():
|
||||
photosdb = osxphotos.PhotosDB()
|
||||
photos = photosdb.photos()
|
||||
|
||||
export_path = os.path.expanduser("~/Desktop/export")
|
||||
|
||||
for p in photos:
|
||||
if not p.ismissing():
|
||||
if p.hasadjustments():
|
||||
exported = p.export(export_path, edited=True)
|
||||
else:
|
||||
exported = p.export(export_path)
|
||||
print(f"Exported {p.filename()} to {exported}")
|
||||
else:
|
||||
print(f"Skipping missing photo: {p.filename()}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
## Module Interface
|
||||
|
||||
### Utility Functions
|
||||
|
||||
#### ```get_system_library_path()```
|
||||
|
||||
**MacOS 10.15 Only** Returns path to System Photo Library as string. On MacOS version < 10.15, raises Exception.
|
||||
|
||||
#### ```get_last_library_path()```
|
||||
|
||||
Returns path to last opened Photo Library as string.
|
||||
|
||||
#### ```list_photo_libraries()```
|
||||
|
||||
Returns list of Photos libraries found on the system. **Note**: On MacOS 10.15, this appears to list all libraries. On older systems, it may not find some libraries if they are not located in ~/Pictures. Provided for convenience but do not rely on this to find all libraries on the system.
|
||||
|
||||
### PhotosDB
|
||||
|
||||
#### Open the default Photos library
|
||||
|
||||
```python
|
||||
osxphotos.PhotosDB([dbfile="path to database file"])
|
||||
osxphotos.PhotosDB()
|
||||
osxphotos.PhotosDB(path)
|
||||
osxphotos.PhotosDB(dbfile=path)
|
||||
```
|
||||
|
||||
Opens the Photos library database and returns a PhotosDB object. Optionally, pass the path to a specific database file. If `dbfile` is not included, will open the default (last opened) Photos database.
|
||||
Opens the Photos library database and returns a PhotosDB object.
|
||||
|
||||
Note: this will open the last library that was opened in Photos. This is not necessarily the System Photos Library. If you have more than one Photos library, you can select which to open by holding down Option key while opening Photos.
|
||||
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.
|
||||
|
||||
If an invalid path is passed, PhotosDB will raise `ValueError` exception.
|
||||
|
||||
Open the default (last opened) Photos library. (E.g. this is the library that would open if the user opened Photos.app)
|
||||
|
||||
```python
|
||||
import osxphotos
|
||||
@@ -147,7 +198,25 @@ import osxphotos
|
||||
photosdb = osxphotos.PhotosDB()
|
||||
```
|
||||
|
||||
Returns a PhotosDB object.
|
||||
#### Open System Photos library
|
||||
|
||||
In Photos 5 (Catalina / MacOS 10.15), you can use `get_system_library_path()` to get the path to the System photo library if you want to ensure PhotosDB opens the system library. This does not work on older versions of MacOS. E.g.
|
||||
|
||||
```python
|
||||
import osxphotos
|
||||
|
||||
path = osxphotos.get_system_library_path()
|
||||
photosdb = osxphotos.PhotosDB(path)
|
||||
```
|
||||
|
||||
also,
|
||||
|
||||
```python
|
||||
import osxphotos
|
||||
|
||||
path = osxphotos.get_system_library_path()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=path)
|
||||
```
|
||||
|
||||
#### Open a specific Photos library
|
||||
```python
|
||||
@@ -156,7 +225,17 @@ import osxphotos
|
||||
photosdb = osxphotos.PhotosDB(dbfile="/Users/smith/Pictures/Test.photoslibrary/database/photos.db")
|
||||
```
|
||||
|
||||
Pass the fully qualified path to the specific Photos database you want to open. The database is called photos.db and resides in the database folder in your Photos library
|
||||
or
|
||||
|
||||
```python
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB("/Users/smith/Pictures/Test.photoslibrary")
|
||||
```
|
||||
|
||||
Pass the fully qualified path to the Photos library or the actual database file inside the library. The database is called photos.db and resides in the database folder in your Photos library. If you pass only the path to the library, PhotosDB will add the database path automatically. The option to pass the actual database path is provided so database files can be queried even if separated from the actual .photoslibrary file.
|
||||
|
||||
Returns a PhotosDB object.
|
||||
|
||||
#### ```keywords```
|
||||
```python
|
||||
@@ -172,7 +251,9 @@ Returns a list of the keywords found in the Photos library
|
||||
albums = photosdb.albums()
|
||||
```
|
||||
|
||||
Returns a list of the albums found in the Photos library
|
||||
Returns a list of the albums found in the Photos library.
|
||||
|
||||
**Note**: In Photos 5.0 (MacOS 10.15/Catalina), It is possible to have more than one album with the same name in Photos. Albums with duplicate names are treated as a single album and the photos in each are combined. For example, if you have two albums named "Wedding" and each has 2 photos, osxphotos will treat this as a single album named "Wedding" with 4 photos in it.
|
||||
|
||||
#### ```persons```
|
||||
```python
|
||||
@@ -204,9 +285,11 @@ Returns a dictionary of persons (faces) found in the Photos library where key is
|
||||
albums_dict = photosdb.albums_as_dict()
|
||||
```
|
||||
|
||||
Returns a dictionary of albums found in the Photos library where key is the album name and value is the count of how many photos are in the album. Resulting dictionary is in reverse sorted order (e.g. album with the most photos is listed first)
|
||||
Returns a dictionary of albums found in the Photos library where key is the album name and value is the count of how many photos are in the album. Resulting dictionary is in reverse sorted order (e.g. album with the most photos is listed first).
|
||||
|
||||
#### ```get_photos_library_path```
|
||||
**Note**: In Photos 5.0 (MacOS 10.15/Catalina), It is possible to have more than one album with the same name in Photos. Albums with duplicate names are treated as a single album and the photos in each are combined. For example, if you have two albums named "Wedding" and each has 2 photos, osxphotos will treat this as a single album named "Wedding" with 4 photos in it.
|
||||
|
||||
#### ```get_library_path```
|
||||
```python
|
||||
# assumes photosdb is a PhotosDB object (see above)
|
||||
photosdb.get_photos_library_path()
|
||||
@@ -252,7 +335,7 @@ photos = photosdb.photos(
|
||||
```
|
||||
|
||||
- ```keywords```: list of one or more keywords. Returns only photos containing the keyword(s). If more than one keyword is provided finds photos matching any of the keywords (e.g. treated as "or")
|
||||
- ```uuid```: list of one or more uuids. Returns only photos whos UUID matches. Note: The UUID is the universally unique identifier that the Photos database uses to identify each photo. You shouldn't normally need to use this but it is a way to access a specific photo if you know the UUID. If more than more uuid is provided, returns photos that match any of the uuids (e.g. treated as "or")
|
||||
- ```uuid```: list of one or more uuids. Returns only photos whos UUID matches. **Note**: The UUID is the universally unique identifier that the Photos database uses to identify each photo. You shouldn't normally need to use this but it is a way to access a specific photo if you know the UUID. If more than more uuid is provided, returns photos that match any of the uuids (e.g. treated as "or")
|
||||
- ```persons```: list of one or more persons. Returns only photos containing the person(s). If more than one person provided, returns photos that match any of the persons (e.g. treated as "or")
|
||||
- ```albums```: list of one or more album names. Returns only photos contained in the album(s). If more than one album name is provided, returns photos contained in any of the albums (.e.g. treated as "or")
|
||||
|
||||
@@ -307,7 +390,7 @@ Returns the universally unique identifier (uuid) of the photo. This is how Phot
|
||||
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()`
|
||||
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
|
||||
@@ -328,16 +411,19 @@ 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 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()`)
|
||||
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()`)
|
||||
|
||||
#### `path_edited()`
|
||||
Returns the absolute path to the edited photo on disk as a string. If the photo has not been edited, returns `None`. See also `path()` and `hasadjustments()`.
|
||||
|
||||
#### `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.
|
||||
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.
|
||||
|
||||
#### `hasadjustments()`
|
||||
Returns `True` if the file has been edited in Photos, otherwise `False`
|
||||
Returns `True` if the picture has been edited, otherwise `False`
|
||||
|
||||
#### `external_edit()`
|
||||
Returns `True` if the picture was edited in an external editor (outside Photos.app), otherwise `False`
|
||||
|
||||
#### `favorite()`
|
||||
Returns `True` if the picture has been marked as a favorite, otherwise `False`
|
||||
@@ -351,11 +437,28 @@ Returns latitude and longitude as a tuple of floats (latitude, longitude). If l
|
||||
#### `to_json()`
|
||||
Returns a JSON representation of all photo info
|
||||
|
||||
Examples:
|
||||
#### `export(self, *args, edited=False, overwrite=False, increment=True)`
|
||||
Export photo from the Photos library to another destination on disk.
|
||||
- First argument of *args must be valid destination path (or exception raised).
|
||||
- Second argument of *args (optional): name of picture; if not provided, will use current filename
|
||||
- edited: boolean; if True (default=False), will export the edited version of the photo (or raise exception if no edited version)
|
||||
- overwrite: boolean; if True (default=False), will overwrite files if they alreay exist
|
||||
- increment: boolean; if True (default=True), will increment file name until a non-existant name is found
|
||||
|
||||
If overwrite=False and increment=False, export will fail if destination file already exists
|
||||
|
||||
Returns the full path to the exported file
|
||||
|
||||
**Implementation Note**: Because the usual python file copy methods don't preserve all the metadata available on MacOS, export uses /usr/bin/ditto to do the copy for export. ditto preserves most metadata such as extended attributes, permissions, ACLs, etc.
|
||||
|
||||
### Examples
|
||||
|
||||
```python
|
||||
# assumes photosdb is a PhotosDB object (see above)
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB()
|
||||
photos=photosdb.photos()
|
||||
|
||||
for p in photos:
|
||||
print(
|
||||
p.uuid(),
|
||||
@@ -377,6 +480,13 @@ for p in photos:
|
||||
|
||||
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.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributing is easy! If you find bugs or want to suggest additional features/changes, please open an [issue](https://github.com/RhetTbull/osxphotos/issues/).
|
||||
|
||||
I'll gladly consider pull requests for bug fixes or feature implementations.
|
||||
|
||||
If you have an interesting example that shows usage of this module, submit an issue or pull request and I'll include it or link to it.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
@@ -384,7 +494,7 @@ This module works by creating a copy of the sqlite3 database that Photos uses to
|
||||
|
||||
If Apple changes the database format this will likely break.
|
||||
|
||||
Apple does provide an framework ([PhotoKit](https://developer.apple.com/documentation/photokit?language=objc)) for querying the user's Photos library and I attempted to create the funcationality in this module using this framework but unfortunately PhotoKit does not provide access to much of the needed metadata (such as Faces/Persons). While copying the sqlite file is a bit kludgy, it allows osxphotos to provide access to all available metadata.
|
||||
Apple does provide a framework ([PhotoKit](https://developer.apple.com/documentation/photokit?language=objc)) for querying the user's Photos library and I attempted to create the funcationality in this module using this framework but unfortunately PhotoKit does not provide access to much of the needed metadata (such as Faces/Persons). While copying the sqlite file is a bit kludgy, it allows osxphotos to provide access to all available metadata.
|
||||
|
||||
## Dependencies
|
||||
- [PyObjC](https://pythonhosted.org/pyobjc/)
|
||||
@@ -392,7 +502,7 @@ Apple does provide an framework ([PhotoKit](https://developer.apple.com/document
|
||||
- [Click](https://pypi.org/project/click/)
|
||||
|
||||
## Acknowledgements
|
||||
This project 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
|
||||
This project was originally 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
|
||||
|
||||
To interact with the Photos app, I use [py-applescript]( https://github.com/rdhyee/py-applescript) by "Raymond Yee / rdhyee". Rather than import this module, I included the entire module
|
||||
(which is published as public domain code) in a private module to prevent ambiguity with
|
||||
|
||||
26
examples/export.py
Normal file
@@ -0,0 +1,26 @@
|
||||
""" Export all photos to ~/Desktop/export
|
||||
If file has been edited, export the edited version,
|
||||
otherwise, export the original version """
|
||||
|
||||
import os.path
|
||||
|
||||
import osxphotos
|
||||
|
||||
def main():
|
||||
photosdb = osxphotos.PhotosDB()
|
||||
photos = photosdb.photos()
|
||||
|
||||
export_path = os.path.expanduser("~/Desktop/export")
|
||||
|
||||
for p in photos:
|
||||
if not p.ismissing():
|
||||
if p.hasadjustments():
|
||||
exported = p.export(export_path, edited=True)
|
||||
else:
|
||||
exported = p.export(export_path)
|
||||
print(f"Exported {p.filename()} to {exported}")
|
||||
else:
|
||||
print(f"Skipping missing photo: {p.filename()}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
4
osxphotos/_version.py
Normal file
@@ -0,0 +1,4 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.15.0"
|
||||
|
||||
@@ -7,13 +7,16 @@ import yaml
|
||||
|
||||
import osxphotos
|
||||
|
||||
# TODO: add query for description, name (contains text)
|
||||
from ._version import __version__
|
||||
|
||||
# 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)
|
||||
def __init__(self, db=None, json=False, debug=False):
|
||||
if debug:
|
||||
osxphotos._debug(True)
|
||||
self.db = db
|
||||
self.json = json
|
||||
|
||||
|
||||
@@ -26,58 +29,63 @@ CTX_SETTINGS = dict(help_option_names=["-h", "--help"])
|
||||
required=False,
|
||||
metavar="<Photos database path>",
|
||||
default=None,
|
||||
help="Specify database file",
|
||||
help="Specify database file.",
|
||||
)
|
||||
@click.option(
|
||||
"--json",
|
||||
required=False,
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Print output in JSON format",
|
||||
help="Print output in JSON format.",
|
||||
)
|
||||
@click.option("--debug", required=False, is_flag=True, default=False, hidden=True)
|
||||
@click.version_option(__version__, "--version", "-v")
|
||||
@click.pass_context
|
||||
def cli(ctx, db, json):
|
||||
ctx.obj = CLI_Obj(db=db, json=json)
|
||||
def cli(ctx, db, json, debug):
|
||||
ctx.obj = CLI_Obj(db=db, json=json, debug=debug)
|
||||
|
||||
|
||||
@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()}
|
||||
""" Print out keywords found in the Photos library. """
|
||||
photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
|
||||
keywords = {"keywords": photosdb.keywords_as_dict()}
|
||||
if cli_obj.json:
|
||||
print(json.dumps(keywords))
|
||||
click.echo(json.dumps(keywords))
|
||||
else:
|
||||
print(yaml.dump(keywords, sort_keys=False))
|
||||
click.echo(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()}
|
||||
""" Print out albums found in the Photos library. """
|
||||
photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
|
||||
albums = {"albums": photosdb.albums_as_dict()}
|
||||
if cli_obj.json:
|
||||
print(json.dumps(albums))
|
||||
click.echo(json.dumps(albums))
|
||||
else:
|
||||
print(yaml.dump(albums, sort_keys=False))
|
||||
click.echo(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()}
|
||||
""" Print out persons (faces) found in the Photos library. """
|
||||
photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
|
||||
persons = {"persons": photosdb.persons_as_dict()}
|
||||
if cli_obj.json:
|
||||
print(json.dumps(persons))
|
||||
click.echo(json.dumps(persons))
|
||||
else:
|
||||
print(yaml.dump(persons, sort_keys=False))
|
||||
click.echo(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
|
||||
""" Print out descriptive info of the Photos library database. """
|
||||
pdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
|
||||
info = {}
|
||||
info["database_path"] = pdb.get_db_path()
|
||||
info["database_version"] = pdb.get_db_version()
|
||||
@@ -94,66 +102,105 @@ def info(cli_obj):
|
||||
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)
|
||||
# TODO: remove this
|
||||
# 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))
|
||||
click.echo(json.dumps(info))
|
||||
else:
|
||||
print(yaml.dump(info, sort_keys=False))
|
||||
click.echo(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
|
||||
""" Print list of all photos & associated info from the Photos library. """
|
||||
pdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
|
||||
photos = pdb.photos()
|
||||
print_photo_info(photos, cli_obj.json)
|
||||
|
||||
|
||||
@cli.command(name="list")
|
||||
@click.pass_obj
|
||||
def list_libraries(cli_obj):
|
||||
""" Print list of Photos libraries found on the system. """
|
||||
photo_libs = osxphotos.list_photo_libraries()
|
||||
sys_lib = None
|
||||
_, major, _ = osxphotos._get_os_version()
|
||||
if int(major) >= 15:
|
||||
sys_lib = osxphotos.get_system_library_path()
|
||||
|
||||
last_lib = osxphotos.get_last_library_path()
|
||||
|
||||
last_lib_flag = sys_lib_flag = False
|
||||
|
||||
for lib in photo_libs:
|
||||
if lib == sys_lib:
|
||||
click.echo(f"(*)\t{lib}")
|
||||
sys_lib_flag = True
|
||||
elif lib == last_lib:
|
||||
click.echo(f"(#)\t{lib}")
|
||||
last_lib_flag = True
|
||||
else:
|
||||
click.echo(f"\t{lib}")
|
||||
|
||||
if sys_lib_flag or last_lib_flag:
|
||||
click.echo("\n")
|
||||
if sys_lib_flag:
|
||||
click.echo("(*)\tSystem Photos Library")
|
||||
if last_lib_flag:
|
||||
click.echo("(#)\tLast opened Photos Library")
|
||||
|
||||
@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("--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"
|
||||
"--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("--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",
|
||||
help="Search for TEXT in description of photo.",
|
||||
)
|
||||
@click.option(
|
||||
"--no-description", is_flag=True, help="Search for photos with no description."
|
||||
)
|
||||
@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",
|
||||
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("--edited", is_flag=True, help="Search for photos that have been edited.")
|
||||
@click.option(
|
||||
"--not-favorite", is_flag=True, help="search for photos not marked favorite"
|
||||
"--external-edit", is_flag=True, help="Search for photos edited in external editor."
|
||||
)
|
||||
@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("--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)",
|
||||
help="Search for photos present on disk (e.g. not missing).",
|
||||
)
|
||||
@click.option(
|
||||
"--json",
|
||||
@@ -177,6 +224,8 @@ def query(
|
||||
no_description,
|
||||
ignore_case,
|
||||
json,
|
||||
edited,
|
||||
external_edit,
|
||||
favorite,
|
||||
not_favorite,
|
||||
hidden,
|
||||
@@ -184,52 +233,57 @@ def query(
|
||||
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)
|
||||
""" 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).
|
||||
"""
|
||||
|
||||
# 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
|
||||
if not any(
|
||||
[
|
||||
keyword,
|
||||
person,
|
||||
album,
|
||||
uuid,
|
||||
name,
|
||||
no_name,
|
||||
description,
|
||||
no_description,
|
||||
edited,
|
||||
external_edit,
|
||||
favorite,
|
||||
not_favorite,
|
||||
hidden,
|
||||
not_hidden,
|
||||
missing,
|
||||
not_missing,
|
||||
]
|
||||
):
|
||||
print(cli.commands["query"].get_help(ctx))
|
||||
click.echo(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))
|
||||
click.echo(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))
|
||||
click.echo(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))
|
||||
click.echo(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
|
||||
click.echo(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
|
||||
click.echo(cli.commands["query"].get_help(ctx))
|
||||
return
|
||||
else:
|
||||
photos = cli_obj.photosdb.photos(
|
||||
photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
|
||||
photos = photosdb.photos(
|
||||
keywords=keyword, persons=person, albums=album, uuid=uuid
|
||||
)
|
||||
|
||||
@@ -267,6 +321,12 @@ def query(
|
||||
elif no_description:
|
||||
photos = [p for p in photos if not p.description()]
|
||||
|
||||
if edited:
|
||||
photos = [p for p in photos if p.hasadjustments()]
|
||||
|
||||
if external_edit:
|
||||
photos = [p for p in photos if p.external_edit()]
|
||||
|
||||
if favorite:
|
||||
photos = [p for p in photos if p.favorite()]
|
||||
elif not_favorite:
|
||||
@@ -289,11 +349,11 @@ def query(
|
||||
@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> """
|
||||
""" Print help; for help on commands: help <command>. """
|
||||
if topic is None:
|
||||
print(ctx.parent.get_help())
|
||||
click.echo(ctx.parent.get_help())
|
||||
else:
|
||||
print(cli.commands[topic].get_help(ctx))
|
||||
click.echo(cli.commands[topic].get_help(ctx))
|
||||
|
||||
|
||||
def print_photo_info(photos, json=False):
|
||||
@@ -301,7 +361,7 @@ def print_photo_info(photos, json=False):
|
||||
dump = []
|
||||
for p in photos:
|
||||
dump.append(p.to_json())
|
||||
print(f"[{', '.join(dump)}]")
|
||||
click.echo(f"[{', '.join(dump)}]")
|
||||
else:
|
||||
# dump as CSV
|
||||
csv_writer = csv.writer(
|
||||
@@ -323,6 +383,7 @@ def print_photo_info(photos, json=False):
|
||||
"path",
|
||||
"ismissing",
|
||||
"hasadjustments",
|
||||
"external_edit",
|
||||
"favorite",
|
||||
"hidden",
|
||||
"latitude",
|
||||
@@ -345,6 +406,7 @@ def print_photo_info(photos, json=False):
|
||||
p.path(),
|
||||
p.ismissing(),
|
||||
p.hasadjustments(),
|
||||
p.external_edit(),
|
||||
p.favorite(),
|
||||
p.hidden(),
|
||||
p._latitude(),
|
||||
|
||||
19
setup.py
@@ -26,19 +26,22 @@
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# from distutils.core import setup
|
||||
from setuptools import setup, find_packages
|
||||
import os
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
# read the contents of README file
|
||||
from os import path
|
||||
|
||||
this_directory = path.abspath(path.dirname(__file__))
|
||||
with open(path.join(this_directory, "README.md"), encoding="utf-8") as f:
|
||||
this_directory = os.path.abspath(os.path.dirname(__file__))
|
||||
with open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f:
|
||||
long_description = f.read()
|
||||
|
||||
about = {}
|
||||
with open(
|
||||
os.path.join(this_directory, "osxphotos", "_version.py"), mode="r", encoding="utf-8"
|
||||
) as f:
|
||||
exec(f.read(), about)
|
||||
|
||||
setup(
|
||||
name="osxphotos",
|
||||
version="0.14.8",
|
||||
version=about["__version__"],
|
||||
description="Manipulate (read-only) Apple's Photos app library on Mac OS X",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||
<date>2019-08-24T02:51:33Z</date>
|
||||
<date>2019-12-07T16:40:40Z</date>
|
||||
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||
<date>2019-08-24T13:19:30Z</date>
|
||||
<date>2019-12-07T16:40:41Z</date>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
<key>PLLastRevGeoForcedProviderOutOfDateCheckVersionKey</key>
|
||||
<integer>1</integer>
|
||||
<key>PLLastRevGeoVerFileFetchDateKey</key>
|
||||
<date>2019-08-24T02:51:30Z</date>
|
||||
<date>2019-12-07T16:40:32Z</date>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<key>HistoricalMarker</key>
|
||||
<dict>
|
||||
<key>LastHistoryRowId</key>
|
||||
<integer>403</integer>
|
||||
<integer>414</integer>
|
||||
<key>LibraryBuildTag</key>
|
||||
<string>E3E46F2A-7168-4973-AB3E-5848F80BFC7D</string>
|
||||
<key>LibrarySchemaVersion</key>
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
<key>key</key>
|
||||
<integer>1</integer>
|
||||
<key>lastKnownDisplayName</key>
|
||||
<string>September 28, 2018</string>
|
||||
<string>Test Album (1)</string>
|
||||
<key>type</key>
|
||||
<string>album</string>
|
||||
<key>uuid</key>
|
||||
<string>DFFKmHt3Tk+AGzZLe2Xq+g</string>
|
||||
<string>Uq6qsKihRRSjMHTiD+0Azg</string>
|
||||
</dict>
|
||||
<key>lastKnownItemCounts</key>
|
||||
<dict>
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||
<date>2019-11-18T01:38:02Z</date>
|
||||
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||
<date>2019-11-27T05:30:14Z</date>
|
||||
<date>2019-12-07T22:47:22Z</date>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>ProcessedInQuiescentState</key>
|
||||
<true/>
|
||||
<key>SuggestedMeIdentifier</key>
|
||||
<string></string>
|
||||
<key>Version</key>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
@@ -11,6 +11,6 @@
|
||||
<key>PLLastRevGeoForcedProviderOutOfDateCheckVersionKey</key>
|
||||
<integer>1</integer>
|
||||
<key>PLLastRevGeoVerFileFetchDateKey</key>
|
||||
<date>2019-11-27T05:30:19Z</date>
|
||||
<date>2019-12-07T22:47:21Z</date>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LastHistoryRowId</key>
|
||||
<integer>522</integer>
|
||||
<integer>575</integer>
|
||||
<key>LibraryBuildTag</key>
|
||||
<string>D8C4AAA1-3AB6-4A65-BEBD-99CC3E5D433E</string>
|
||||
<key>LibrarySchemaVersion</key>
|
||||
|
||||
|
Before Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 485 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 367 KiB |
@@ -9,7 +9,7 @@
|
||||
<key>HistoricalMarker</key>
|
||||
<dict>
|
||||
<key>LastHistoryRowId</key>
|
||||
<integer>522</integer>
|
||||
<integer>575</integer>
|
||||
<key>LibraryBuildTag</key>
|
||||
<string>D8C4AAA1-3AB6-4A65-BEBD-99CC3E5D433E</string>
|
||||
<key>LibrarySchemaVersion</key>
|
||||
@@ -24,7 +24,7 @@
|
||||
<key>SnapshotCompletedDate</key>
|
||||
<date>2019-07-27T13:16:43Z</date>
|
||||
<key>SnapshotLastValidated</key>
|
||||
<date>2019-11-27T05:30:11Z</date>
|
||||
<date>2019-12-07T22:47:19Z</date>
|
||||
<key>SnapshotTables</key>
|
||||
<dict/>
|
||||
</dict>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<key>hostuuid</key>
|
||||
<string>9575E48B-8D5F-5654-ABAC-4431B1167324</string>
|
||||
<key>pid</key>
|
||||
<integer>1368</integer>
|
||||
<integer>423</integer>
|
||||
<key>processname</key>
|
||||
<string>photolibraryd</string>
|
||||
<key>uid</key>
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
<dict>
|
||||
<key>BlacklistedMeaningsByMeaning</key>
|
||||
<dict/>
|
||||
<key>MePersonUUID</key>
|
||||
<string>39488755-78C0-40B2-B378-EDA280E1823C</string>
|
||||
<key>SceneWhitelist</key>
|
||||
<array>
|
||||
<string>Graduation</string>
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
<dict>
|
||||
<key>CollapsedSidebarSectionIdentifiers</key>
|
||||
<array/>
|
||||
<key>ExpandedSidebarItemIdentifiers</key>
|
||||
<array>
|
||||
<string>92D68107-B6C7-453B-96D2-97B0F26D5B8B/L0/020</string>
|
||||
</array>
|
||||
<key>Photos</key>
|
||||
<dict>
|
||||
<key>CollapsedSidebarSectionIdentifiers</key>
|
||||
|
||||
@@ -3,24 +3,24 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BackgroundHighlightCollection</key>
|
||||
<date>2019-11-13T03:46:27Z</date>
|
||||
<date>2019-12-14T18:19:30Z</date>
|
||||
<key>BackgroundHighlightEnrichment</key>
|
||||
<date>2019-11-13T03:46:27Z</date>
|
||||
<date>2019-12-14T18:19:29Z</date>
|
||||
<key>BackgroundJobAssetRevGeocode</key>
|
||||
<date>2019-11-13T03:46:27Z</date>
|
||||
<date>2019-12-14T18:19:30Z</date>
|
||||
<key>BackgroundJobSearch</key>
|
||||
<date>2019-11-13T03:46:27Z</date>
|
||||
<date>2019-12-14T18:19:30Z</date>
|
||||
<key>BackgroundPeopleSuggestion</key>
|
||||
<date>2019-11-13T03:46:27Z</date>
|
||||
<date>2019-12-14T18:19:28Z</date>
|
||||
<key>BackgroundUserBehaviorProcessor</key>
|
||||
<date>2019-11-13T03:46:28Z</date>
|
||||
<date>0000-12-30T00:00:00Z</date>
|
||||
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
|
||||
<date>2019-11-13T03:46:28Z</date>
|
||||
<date>2019-12-14T18:19:28Z</date>
|
||||
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||
<date>2019-11-13T03:46:27Z</date>
|
||||
<date>2019-12-14T18:19:28Z</date>
|
||||
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||
<date>2019-11-13T03:46:28Z</date>
|
||||
<date>2019-12-10T06:45:58Z</date>
|
||||
<key>SiriPortraitDonation</key>
|
||||
<date>2019-11-13T03:46:28Z</date>
|
||||
<date>0000-12-30T00:00:00Z</date>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>FaceIDModelLastGenerationKey</key>
|
||||
<date>2019-11-13T03:46:28Z</date>
|
||||
<date>2019-12-10T06:45:58Z</date>
|
||||
<key>LastContactClassificationKey</key>
|
||||
<date>2019-11-13T03:46:29Z</date>
|
||||
<date>2019-12-10T06:46:00Z</date>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
|
Before Width: | Height: | Size: 485 KiB After Width: | Height: | Size: 383 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 104 KiB |
@@ -2,6 +2,10 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>coalesceDate</key>
|
||||
<date>2019-12-08T18:06:37Z</date>
|
||||
<key>coalescePayloadVersion</key>
|
||||
<integer>1</integer>
|
||||
<key>currentPayloadVersion</key>
|
||||
<integer>1</integer>
|
||||
<key>snapshotDate</key>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>coalesceDate</key>
|
||||
<date>2019-10-27T15:36:05Z</date>
|
||||
<date>2019-12-08T18:06:37Z</date>
|
||||
<key>coalescePayloadVersion</key>
|
||||
<integer>1</integer>
|
||||
<key>currentPayloadVersion</key>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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>adjustmentBaseVersion</key>
|
||||
<integer>0</integer>
|
||||
<key>adjustmentData</key>
|
||||
<data>
|
||||
shkyAAAAAAACAAAA
|
||||
</data>
|
||||
<key>adjustmentEditorBundleID</key>
|
||||
<string>com.apple.Photos</string>
|
||||
<key>adjustmentFormatIdentifier</key>
|
||||
<string>com.apple.Photos.externalEdit</string>
|
||||
<key>adjustmentFormatVersion</key>
|
||||
<string>1</string>
|
||||
<key>adjustmentTimestamp</key>
|
||||
<date>2019-12-01T15:27:48Z</date>
|
||||
</dict>
|
||||
</plist>
|
||||
|
After Width: | Height: | Size: 1.2 MiB |
@@ -43,7 +43,8 @@ def test_db_version():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
assert photosdb.get_db_version() in osxphotos._TESTED_DB_VERSIONS
|
||||
# assert photosdb.get_db_version() in osxphotos._TESTED_DB_VERSIONS
|
||||
assert photosdb.get_db_version() == "2622"
|
||||
|
||||
|
||||
def test_os_version():
|
||||
@@ -170,36 +171,3 @@ def test_keyword_not_in_album():
|
||||
assert len(photos3) == 1
|
||||
assert photos3[0].uuid() == "Pj99JmYjQkeezdY2OFuSaw"
|
||||
|
||||
|
||||
# def main():
|
||||
# photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
# print(photosdb.keywords())
|
||||
# print(photosdb.persons())
|
||||
# print(photosdb.albums())
|
||||
|
||||
# print(photosdb.keywords_as_dict())
|
||||
# print(photosdb.persons_as_dict())
|
||||
# print(photosdb.albums_as_dict())
|
||||
|
||||
# # # find all photos with Keyword = Foo and containing John Smith
|
||||
# # photos = photosdb.photos(keywords=["Foo"],persons=["John Smith"])
|
||||
# #
|
||||
# # # find all photos that include Alice Smith but do not contain the keyword Bar
|
||||
# # photos = [p for p in photosdb.photos(persons=["Alice Smith"])
|
||||
# # if p not in photosdb.photos(keywords=["Bar"]) ]
|
||||
# photos = photosdb.photos()
|
||||
# for p in photos:
|
||||
# print(
|
||||
# p.uuid(),
|
||||
# p.filename(),
|
||||
# p.date(),
|
||||
# p.description(),
|
||||
# p.name(),
|
||||
# p.keywords(),
|
||||
# p.albums(),
|
||||
# p.persons(),
|
||||
# p.path(),
|
||||
# )
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import pytest
|
||||
|
||||
from osxphotos import _UNKNOWN_PERSON
|
||||
|
||||
# TODO: put some of this code into a pre-function
|
||||
|
||||
PHOTOS_DB = "./tests/Test-10.15.1.photoslibrary/database/photos.db"
|
||||
PHOTOS_DB_PATH = "/Test-10.15.1.photoslibrary/database/Photos.sqlite"
|
||||
PHOTOS_LIBRARY_PATH = "/Test-10.15.1.photoslibrary"
|
||||
|
||||
KEYWORDS = [
|
||||
"Kids",
|
||||
"wedding",
|
||||
@@ -15,8 +20,11 @@ KEYWORDS = [
|
||||
"United Kingdom",
|
||||
]
|
||||
# Photos 5 includes blank person for detected face
|
||||
PERSONS = ["Katie", "Suzy", "Maria", ""]
|
||||
ALBUMS = ["Pumpkin Farm"]
|
||||
PERSONS = ["Katie", "Suzy", "Maria", _UNKNOWN_PERSON]
|
||||
ALBUMS = [
|
||||
"Pumpkin Farm",
|
||||
"Test Album",
|
||||
] # Note: there are 2 albums named "Test Album" for testing duplicate album names
|
||||
KEYWORDS_DICT = {
|
||||
"Kids": 4,
|
||||
"wedding": 2,
|
||||
@@ -28,22 +36,91 @@ KEYWORDS_DICT = {
|
||||
"UK": 1,
|
||||
"United Kingdom": 1,
|
||||
}
|
||||
PERSONS_DICT = {"Katie": 3, "Suzy": 2, "Maria": 1, "": 1}
|
||||
ALBUM_DICT = {"Pumpkin Farm": 3}
|
||||
PERSONS_DICT = {"Katie": 3, "Suzy": 2, "Maria": 1, _UNKNOWN_PERSON: 1}
|
||||
ALBUM_DICT = {
|
||||
"Pumpkin Farm": 3,
|
||||
"Test Album": 2,
|
||||
} # Note: there are 2 albums named "Test Album" for testing duplicate album names
|
||||
|
||||
UUID_DICT = {
|
||||
"missing": "A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C",
|
||||
"favorite": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
|
||||
"not_favorite": "A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C",
|
||||
"hidden": "A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C",
|
||||
"not_hidden": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
|
||||
"has_adjustments": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
|
||||
"no_adjustments": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
|
||||
"location": "DC99FBDD-7A52-4100-A5BB-344131646C30",
|
||||
"no_location": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
|
||||
"external_edit": "DC99FBDD-7A52-4100-A5BB-344131646C30",
|
||||
"no_external_edit": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
|
||||
"export": "D79B8D77-BFFC-460B-9312-034F2877D35B", # "Pumkins2.jpg"
|
||||
}
|
||||
|
||||
|
||||
def test_init():
|
||||
# test named argument
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
assert isinstance(photosdb, osxphotos.PhotosDB)
|
||||
|
||||
|
||||
def test_init2():
|
||||
# test positional argument
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB)
|
||||
assert isinstance(photosdb, osxphotos.PhotosDB)
|
||||
|
||||
|
||||
def test_init3():
|
||||
# test positional and named argument (raises exception)
|
||||
import osxphotos
|
||||
|
||||
with pytest.raises(Exception):
|
||||
assert osxphotos.PhotosDB(PHOTOS_DB, dbfile=PHOTOS_DB)
|
||||
|
||||
|
||||
def test_init4():
|
||||
# test invalid db
|
||||
import os
|
||||
import tempfile
|
||||
import osxphotos
|
||||
|
||||
bad_db = tempfile.mkstemp(suffix=".db", prefix="osxphotos-")
|
||||
|
||||
with pytest.raises(Exception):
|
||||
assert osxphotos.PhotosDB(bad_db)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
assert osxphotos.PhotosDB(dbfile=bad_db)
|
||||
|
||||
try:
|
||||
os.remove(bad_db)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def test_init5():
|
||||
# test failed get_last_library_path
|
||||
import osxphotos
|
||||
|
||||
def bad_library():
|
||||
return None
|
||||
|
||||
osxphotos.get_last_library_path = bad_library
|
||||
|
||||
with pytest.raises(Exception):
|
||||
assert osxphotos.PhotosDB()
|
||||
|
||||
|
||||
def test_db_version():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
assert photosdb.get_db_version() in osxphotos._TESTED_DB_VERSIONS
|
||||
# assert photosdb.get_db_version() in osxphotos._TESTED_DB_VERSIONS
|
||||
assert photosdb.get_db_version() == "6000"
|
||||
|
||||
|
||||
def test_os_version():
|
||||
@@ -123,7 +200,7 @@ def test_attributes():
|
||||
)
|
||||
assert p.description() == "Girl holding pumpkin"
|
||||
assert p.name() == "I found one!"
|
||||
assert p.albums() == ["Pumpkin Farm"]
|
||||
assert p.albums() == ["Pumpkin Farm", "Test Album"]
|
||||
assert p.persons() == ["Katie"]
|
||||
assert p.path().endswith(
|
||||
"tests/Test-10.15.1.photoslibrary/originals/D/D79B8D77-BFFC-460B-9312-034F2877D35B.jpeg"
|
||||
@@ -135,7 +212,7 @@ def test_missing():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=["A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C"])
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["missing"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
assert p.path() == None
|
||||
@@ -146,7 +223,7 @@ def test_favorite():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=["E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51"])
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["favorite"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
assert p.favorite() == True
|
||||
@@ -156,7 +233,7 @@ def test_not_favorite():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=["A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C"])
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["not_favorite"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
assert p.favorite() == False
|
||||
@@ -166,7 +243,7 @@ def test_hidden():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=["A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C"])
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["hidden"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
assert p.hidden() == True
|
||||
@@ -176,7 +253,7 @@ def test_not_hidden():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=["E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51"])
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["not_hidden"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
assert p.hidden() == False
|
||||
@@ -187,7 +264,7 @@ def test_location_1():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=["DC99FBDD-7A52-4100-A5BB-344131646C30"])
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["location"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
lat, lon = p.location()
|
||||
@@ -200,7 +277,7 @@ def test_location_2():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=["6191423D-8DB8-4D4C-92BE-9BBBA308AAC4"])
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_location"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
lat, lon = p.location()
|
||||
@@ -213,7 +290,7 @@ def test_hasadjustments1():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=["E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51"])
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
assert p.hasadjustments() == True
|
||||
@@ -224,12 +301,36 @@ def test_hasadjustments2():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=["6191423D-8DB8-4D4C-92BE-9BBBA308AAC4"])
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_adjustments"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
assert p.hasadjustments() == False
|
||||
|
||||
|
||||
def test_external_edit1():
|
||||
# test image has been edited in external editor
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["external_edit"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
|
||||
assert p.external_edit() == True
|
||||
|
||||
|
||||
def test_external_edit2():
|
||||
# test image has not been edited in external editor
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_external_edit"]])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
|
||||
assert p.external_edit() == False
|
||||
|
||||
|
||||
def test_path_edited1():
|
||||
# test a valid edited path
|
||||
import osxphotos
|
||||
@@ -284,3 +385,354 @@ def test_keyword_not_in_album():
|
||||
assert len(photos3) == 1
|
||||
assert photos3[0].uuid() == "A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C"
|
||||
|
||||
|
||||
def test_get_db_path():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
db_path = photosdb.get_db_path()
|
||||
assert db_path.endswith(PHOTOS_DB_PATH)
|
||||
|
||||
|
||||
def test_get_library_path():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
lib_path = photosdb.get_library_path()
|
||||
assert lib_path.endswith(PHOTOS_LIBRARY_PATH)
|
||||
|
||||
|
||||
def test_export_1():
|
||||
# test basic export
|
||||
# get an unedited image and export it using default filename
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
got_dest = photos[0].export(dest)
|
||||
|
||||
assert got_dest == expected_dest
|
||||
assert os.path.isfile(got_dest)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_2():
|
||||
# test export with user provided filename
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-2-test-{timestamp}.jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
got_dest = photos[0].export(dest, filename)
|
||||
|
||||
assert got_dest == expected_dest
|
||||
assert os.path.isfile(got_dest)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_3():
|
||||
# test file already exists and test increment=True (default)
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
filename2 = pathlib.Path(filename)
|
||||
filename2 = f"{filename2.stem} (1){filename2.suffix}"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
expected_dest_2 = os.path.join(dest, filename2)
|
||||
|
||||
got_dest = photos[0].export(dest)
|
||||
got_dest_2 = photos[0].export(dest)
|
||||
|
||||
assert got_dest_2 == expected_dest_2
|
||||
assert os.path.isfile(got_dest_2)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
os.remove(got_dest_2)
|
||||
|
||||
|
||||
def test_export_4():
|
||||
# test user supplied file already exists and test increment=True (default)
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-2-test-{timestamp}.jpg"
|
||||
filename2 = f"osxphotos-export-2-test-{timestamp} (1).jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
expected_dest_2 = os.path.join(dest, filename2)
|
||||
|
||||
got_dest = photos[0].export(dest, filename)
|
||||
got_dest_2 = photos[0].export(dest, filename)
|
||||
|
||||
assert got_dest_2 == expected_dest_2
|
||||
assert os.path.isfile(got_dest_2)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
os.remove(got_dest_2)
|
||||
|
||||
|
||||
def test_export_5():
|
||||
# test file already exists and test increment=True (default)
|
||||
# and overwrite = True
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest)
|
||||
got_dest_2 = photos[0].export(dest, overwrite=True)
|
||||
|
||||
assert got_dest_2 == got_dest
|
||||
assert got_dest_2 == expected_dest
|
||||
assert os.path.isfile(got_dest_2)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_6():
|
||||
# test user supplied file already exists and test increment=True (default)
|
||||
# and overwrite = True
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-test-{timestamp}.jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest, filename)
|
||||
got_dest_2 = photos[0].export(dest, filename, overwrite=True)
|
||||
|
||||
assert got_dest_2 == got_dest
|
||||
assert got_dest_2 == expected_dest
|
||||
assert os.path.isfile(got_dest_2)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_7():
|
||||
# test file already exists and test increment=False (not default), overwrite=False (default)
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest)
|
||||
with pytest.raises(Exception) as e:
|
||||
# try to export again with increment = False
|
||||
assert photos[0].export(dest, increment=False)
|
||||
assert e.type == type(FileExistsError())
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_8():
|
||||
# try to export missing file
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["missing"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
assert photos[0].export(dest)
|
||||
assert e.type == type(FileNotFoundError())
|
||||
|
||||
|
||||
def test_export_9():
|
||||
# try to export edited file that's not edited
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_adjustments"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
assert photos[0].export(dest, edited=True)
|
||||
assert e.type == type(FileNotFoundError())
|
||||
|
||||
|
||||
def test_export_10():
|
||||
# try to export edited file that's not edited and name provided
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_adjustments"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-test-{timestamp}.jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
assert photos[0].export(dest, filename, edited=True)
|
||||
assert e.type == type(FileNotFoundError())
|
||||
|
||||
|
||||
def test_export_11():
|
||||
# export edited file with name provided
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-test-{timestamp}.jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest, filename, edited=True)
|
||||
assert got_dest == expected_dest
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_12():
|
||||
# export edited file with default name
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
|
||||
|
||||
edited_name = pathlib.Path(photos[0].path_edited()).name
|
||||
edited_suffix = pathlib.Path(edited_name).suffix
|
||||
filename = pathlib.Path(photos[0].filename()).stem + "_edited" + edited_suffix
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest, edited=True)
|
||||
assert got_dest == expected_dest
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_13():
|
||||
# export to invalid destination
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
|
||||
# create a folder that doesn't exist
|
||||
i = 0
|
||||
while os.path.isdir(dest):
|
||||
dest = os.path.join(dest, str(i))
|
||||
i += 1
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
assert photos[0].export(dest)
|
||||
assert e.type == type(FileNotFoundError())
|
||||
|
||||
|
||||
392
tests/test_export_catalina_10_15_1.py
Normal file
@@ -0,0 +1,392 @@
|
||||
import pytest
|
||||
|
||||
from osxphotos import _UNKNOWN_PERSON
|
||||
|
||||
# TODO: put some of this code into a pre-function
|
||||
|
||||
PHOTOS_DB = "./tests/Test-10.15.1.photoslibrary/database/photos.db"
|
||||
PHOTOS_DB_PATH = "/Test-10.15.1.photoslibrary/database/Photos.sqlite"
|
||||
PHOTOS_LIBRARY_PATH = "/Test-10.15.1.photoslibrary"
|
||||
|
||||
KEYWORDS = [
|
||||
"Kids",
|
||||
"wedding",
|
||||
"flowers",
|
||||
"England",
|
||||
"London",
|
||||
"London 2018",
|
||||
"St. James's Park",
|
||||
"UK",
|
||||
"United Kingdom",
|
||||
]
|
||||
# Photos 5 includes blank person for detected face
|
||||
PERSONS = ["Katie", "Suzy", "Maria", _UNKNOWN_PERSON]
|
||||
ALBUMS = [
|
||||
"Pumpkin Farm",
|
||||
"Test Album",
|
||||
] # Note: there are 2 albums named "Test Album" for testing duplicate album names
|
||||
KEYWORDS_DICT = {
|
||||
"Kids": 4,
|
||||
"wedding": 2,
|
||||
"flowers": 1,
|
||||
"England": 1,
|
||||
"London": 1,
|
||||
"London 2018": 1,
|
||||
"St. James's Park": 1,
|
||||
"UK": 1,
|
||||
"United Kingdom": 1,
|
||||
}
|
||||
PERSONS_DICT = {"Katie": 3, "Suzy": 2, "Maria": 1, _UNKNOWN_PERSON: 1}
|
||||
ALBUM_DICT = {
|
||||
"Pumpkin Farm": 3,
|
||||
"Test Album": 2,
|
||||
} # Note: there are 2 albums named "Test Album" for testing duplicate album names
|
||||
|
||||
UUID_DICT = {
|
||||
"missing": "A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C",
|
||||
"favorite": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
|
||||
"not_favorite": "A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C",
|
||||
"hidden": "A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C",
|
||||
"not_hidden": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
|
||||
"has_adjustments": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
|
||||
"no_adjustments": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
|
||||
"location": "DC99FBDD-7A52-4100-A5BB-344131646C30",
|
||||
"no_location": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
|
||||
"external_edit": "DC99FBDD-7A52-4100-A5BB-344131646C30",
|
||||
"no_external_edit": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
|
||||
"export": "D79B8D77-BFFC-460B-9312-034F2877D35B", # "Pumkins2.jpg"
|
||||
}
|
||||
|
||||
def test_export_1():
|
||||
# test basic export
|
||||
# get an unedited image and export it using default filename
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
got_dest = photos[0].export(dest)
|
||||
|
||||
assert got_dest == expected_dest
|
||||
assert os.path.isfile(got_dest)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_2():
|
||||
# test export with user provided filename
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-2-test-{timestamp}.jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
got_dest = photos[0].export(dest, filename)
|
||||
|
||||
assert got_dest == expected_dest
|
||||
assert os.path.isfile(got_dest)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_3():
|
||||
# test file already exists and test increment=True (default)
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
filename2 = pathlib.Path(filename)
|
||||
filename2 = f"{filename2.stem} (1){filename2.suffix}"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
expected_dest_2 = os.path.join(dest, filename2)
|
||||
|
||||
got_dest = photos[0].export(dest)
|
||||
got_dest_2 = photos[0].export(dest)
|
||||
|
||||
assert got_dest_2 == expected_dest_2
|
||||
assert os.path.isfile(got_dest_2)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
os.remove(got_dest_2)
|
||||
|
||||
|
||||
def test_export_4():
|
||||
# test user supplied file already exists and test increment=True (default)
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-2-test-{timestamp}.jpg"
|
||||
filename2 = f"osxphotos-export-2-test-{timestamp} (1).jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
expected_dest_2 = os.path.join(dest, filename2)
|
||||
|
||||
got_dest = photos[0].export(dest, filename)
|
||||
got_dest_2 = photos[0].export(dest, filename)
|
||||
|
||||
assert got_dest_2 == expected_dest_2
|
||||
assert os.path.isfile(got_dest_2)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
os.remove(got_dest_2)
|
||||
|
||||
|
||||
def test_export_5():
|
||||
# test file already exists and test increment=True (default)
|
||||
# and overwrite = True
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest)
|
||||
got_dest_2 = photos[0].export(dest, overwrite=True)
|
||||
|
||||
assert got_dest_2 == got_dest
|
||||
assert got_dest_2 == expected_dest
|
||||
assert os.path.isfile(got_dest_2)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_6():
|
||||
# test user supplied file already exists and test increment=True (default)
|
||||
# and overwrite = True
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-test-{timestamp}.jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest, filename)
|
||||
got_dest_2 = photos[0].export(dest, filename, overwrite=True)
|
||||
|
||||
assert got_dest_2 == got_dest
|
||||
assert got_dest_2 == expected_dest
|
||||
assert os.path.isfile(got_dest_2)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_7():
|
||||
# test file already exists and test increment=False (not default), overwrite=False (default)
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest)
|
||||
with pytest.raises(Exception) as e:
|
||||
# try to export again with increment = False
|
||||
assert photos[0].export(dest, increment=False)
|
||||
assert e.type == type(FileExistsError())
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_8():
|
||||
# try to export missing file
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["missing"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
assert photos[0].export(dest)
|
||||
assert e.type == type(FileNotFoundError())
|
||||
|
||||
|
||||
def test_export_9():
|
||||
# try to export edited file that's not edited
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_adjustments"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
assert photos[0].export(dest, edited=True)
|
||||
assert e.type == type(FileNotFoundError())
|
||||
|
||||
|
||||
def test_export_10():
|
||||
# try to export edited file that's not edited and name provided
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_adjustments"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-test-{timestamp}.jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
assert photos[0].export(dest, filename, edited=True)
|
||||
assert e.type == type(FileNotFoundError())
|
||||
|
||||
|
||||
def test_export_11():
|
||||
# export edited file with name provided
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-test-{timestamp}.jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest, filename, edited=True)
|
||||
assert got_dest == expected_dest
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_12():
|
||||
# export edited file with default name
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
|
||||
|
||||
edited_name = pathlib.Path(photos[0].path_edited()).name
|
||||
edited_suffix = pathlib.Path(edited_name).suffix
|
||||
filename = pathlib.Path(photos[0].filename()).stem + "_edited" + edited_suffix
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest, edited=True)
|
||||
assert got_dest == expected_dest
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_13():
|
||||
# export to invalid destination
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
|
||||
# create a folder that doesn't exist
|
||||
i = 0
|
||||
while os.path.isdir(dest):
|
||||
dest = os.path.join(dest, str(i))
|
||||
i += 1
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
assert photos[0].export(dest)
|
||||
assert e.type == type(FileNotFoundError())
|
||||
378
tests/test_export_mojave_10_14_6.py
Normal file
@@ -0,0 +1,378 @@
|
||||
import pytest
|
||||
|
||||
from osxphotos import _UNKNOWN_PERSON
|
||||
|
||||
# TODO: put some of this code into a pre-function
|
||||
|
||||
|
||||
PHOTOS_DB = "./tests/Test-10.14.6.photoslibrary/database/photos.db"
|
||||
PHOTOS_DB_PATH = "/Test-10.14.6.photoslibrary/database/photos.db"
|
||||
PHOTOS_LIBRARY_PATH = "/Test-10.14.6.photoslibrary"
|
||||
|
||||
KEYWORDS = [
|
||||
"Kids",
|
||||
"wedding",
|
||||
"flowers",
|
||||
"England",
|
||||
"London",
|
||||
"London 2018",
|
||||
"St. James's Park",
|
||||
"UK",
|
||||
"United Kingdom",
|
||||
]
|
||||
PERSONS = ["Katie", "Suzy", "Maria"]
|
||||
ALBUMS = ["Pumpkin Farm", "Test Album", "Test Album (1)"]
|
||||
KEYWORDS_DICT = {
|
||||
"Kids": 4,
|
||||
"wedding": 2,
|
||||
"flowers": 1,
|
||||
"England": 1,
|
||||
"London": 1,
|
||||
"London 2018": 1,
|
||||
"St. James's Park": 1,
|
||||
"UK": 1,
|
||||
"United Kingdom": 1,
|
||||
}
|
||||
PERSONS_DICT = {"Katie": 3, "Suzy": 2, "Maria": 1}
|
||||
ALBUM_DICT = {"Pumpkin Farm": 3, "Test Album": 1, "Test Album (1)": 1}
|
||||
|
||||
UUID_DICT = {
|
||||
"missing": "od0fmC7NQx+ayVr+%i06XA",
|
||||
"has_adjustments": "6bxcNnzRQKGnK4uPrCJ9UQ",
|
||||
"no_adjustments": "15uNd7%8RguTEgNPKHfTWw",
|
||||
"export": "15uNd7%8RguTEgNPKHfTWw",
|
||||
}
|
||||
|
||||
def test_export_1():
|
||||
# test basic export
|
||||
# get an unedited image and export it using default filename
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
got_dest = photos[0].export(dest)
|
||||
|
||||
assert got_dest == expected_dest
|
||||
assert os.path.isfile(got_dest)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_2():
|
||||
# test export with user provided filename
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-2-test-{timestamp}.jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
got_dest = photos[0].export(dest, filename)
|
||||
|
||||
assert got_dest == expected_dest
|
||||
assert os.path.isfile(got_dest)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_3():
|
||||
# test file already exists and test increment=True (default)
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
filename2 = pathlib.Path(filename)
|
||||
filename2 = f"{filename2.stem} (1){filename2.suffix}"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
expected_dest_2 = os.path.join(dest, filename2)
|
||||
|
||||
got_dest = photos[0].export(dest)
|
||||
got_dest_2 = photos[0].export(dest)
|
||||
|
||||
assert got_dest_2 == expected_dest_2
|
||||
assert os.path.isfile(got_dest_2)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
os.remove(got_dest_2)
|
||||
|
||||
|
||||
def test_export_4():
|
||||
# test user supplied file already exists and test increment=True (default)
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-2-test-{timestamp}.jpg"
|
||||
filename2 = f"osxphotos-export-2-test-{timestamp} (1).jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
expected_dest_2 = os.path.join(dest, filename2)
|
||||
|
||||
got_dest = photos[0].export(dest, filename)
|
||||
got_dest_2 = photos[0].export(dest, filename)
|
||||
|
||||
assert got_dest_2 == expected_dest_2
|
||||
assert os.path.isfile(got_dest_2)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
os.remove(got_dest_2)
|
||||
|
||||
|
||||
def test_export_5():
|
||||
# test file already exists and test increment=True (default)
|
||||
# and overwrite = True
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest)
|
||||
got_dest_2 = photos[0].export(dest, overwrite=True)
|
||||
|
||||
assert got_dest_2 == got_dest
|
||||
assert got_dest_2 == expected_dest
|
||||
assert os.path.isfile(got_dest_2)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_6():
|
||||
# test user supplied file already exists and test increment=True (default)
|
||||
# and overwrite = True
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-test-{timestamp}.jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest, filename)
|
||||
got_dest_2 = photos[0].export(dest, filename, overwrite=True)
|
||||
|
||||
assert got_dest_2 == got_dest
|
||||
assert got_dest_2 == expected_dest
|
||||
assert os.path.isfile(got_dest_2)
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_7():
|
||||
# test file already exists and test increment=False (not default), overwrite=False (default)
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest)
|
||||
with pytest.raises(Exception) as e:
|
||||
# try to export again with increment = False
|
||||
assert photos[0].export(dest, increment=False)
|
||||
assert e.type == type(FileExistsError())
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_8():
|
||||
# try to export missing file
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["missing"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
assert photos[0].export(dest)
|
||||
assert e.type == type(FileNotFoundError())
|
||||
|
||||
|
||||
def test_export_9():
|
||||
# try to export edited file that's not edited
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_adjustments"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
assert photos[0].export(dest, edited=True)
|
||||
assert e.type == type(FileNotFoundError())
|
||||
|
||||
|
||||
def test_export_10():
|
||||
# try to export edited file that's not edited and name provided
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_adjustments"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-test-{timestamp}.jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
assert photos[0].export(dest, filename, edited=True)
|
||||
assert e.type == type(FileNotFoundError())
|
||||
|
||||
|
||||
def test_export_11():
|
||||
# export edited file with name provided
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
|
||||
|
||||
timestamp = time.time()
|
||||
filename = f"osxphotos-export-test-{timestamp}.jpg"
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest, filename, edited=True)
|
||||
assert got_dest == expected_dest
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_12():
|
||||
# export edited file with default name
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
|
||||
|
||||
edited_name = pathlib.Path(photos[0].path_edited()).name
|
||||
edited_suffix = pathlib.Path(edited_name).suffix
|
||||
filename = pathlib.Path(photos[0].filename()).stem + "_edited" + edited_suffix
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
got_dest = photos[0].export(dest, edited=True)
|
||||
assert got_dest == expected_dest
|
||||
|
||||
# remove the temporary file
|
||||
os.remove(got_dest)
|
||||
|
||||
|
||||
def test_export_13():
|
||||
# export to invalid destination
|
||||
# should raise exception
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import osxphotos
|
||||
|
||||
dest = tempfile.gettempdir()
|
||||
|
||||
# create a folder that doesn't exist
|
||||
i = 0
|
||||
while os.path.isdir(dest):
|
||||
dest = os.path.join(dest, str(i))
|
||||
i += 1
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["export"]])
|
||||
|
||||
filename = photos[0].filename()
|
||||
expected_dest = os.path.join(dest, filename)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
assert photos[0].export(dest)
|
||||
assert e.type == type(FileNotFoundError())
|
||||
@@ -42,7 +42,8 @@ def test_db_version():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
assert photosdb.get_db_version() in osxphotos._TESTED_DB_VERSIONS
|
||||
assert photosdb.get_db_version() == "3301"
|
||||
# assert photosdb.get_db_version() in osxphotos._TESTED_DB_VERSIONS
|
||||
|
||||
|
||||
def test_os_version():
|
||||
@@ -169,36 +170,3 @@ def test_keyword_not_in_album():
|
||||
assert len(photos3) == 1
|
||||
assert photos3[0].uuid() == "6iAZJP7ZQ5iXxapoJb3ytA"
|
||||
|
||||
|
||||
# def main():
|
||||
# photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
# print(photosdb.keywords())
|
||||
# print(photosdb.persons())
|
||||
# print(photosdb.albums())
|
||||
|
||||
# print(photosdb.keywords_as_dict())
|
||||
# print(photosdb.persons_as_dict())
|
||||
# print(photosdb.albums_as_dict())
|
||||
|
||||
# # # find all photos with Keyword = Foo and containing John Smith
|
||||
# # photos = photosdb.photos(keywords=["Foo"],persons=["John Smith"])
|
||||
# #
|
||||
# # # find all photos that include Alice Smith but do not contain the keyword Bar
|
||||
# # photos = [p for p in photosdb.photos(persons=["Alice Smith"])
|
||||
# # if p not in photosdb.photos(keywords=["Bar"]) ]
|
||||
# photos = photosdb.photos()
|
||||
# for p in photos:
|
||||
# print(
|
||||
# p.uuid(),
|
||||
# p.filename(),
|
||||
# p.date(),
|
||||
# p.description(),
|
||||
# p.name(),
|
||||
# p.keywords(),
|
||||
# p.albums(),
|
||||
# p.persons(),
|
||||
# p.path(),
|
||||
# )
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
|
||||
@@ -42,7 +42,8 @@ def test_db_version():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
assert photosdb.get_db_version() in osxphotos._TESTED_DB_VERSIONS
|
||||
# assert photosdb.get_db_version() in osxphotos._TESTED_DB_VERSIONS
|
||||
assert photosdb.get_db_version() == "4016"
|
||||
|
||||
|
||||
def test_os_version():
|
||||
@@ -169,36 +170,3 @@ def test_keyword_not_in_album():
|
||||
assert len(photos3) == 1
|
||||
assert photos3[0].uuid() == "od0fmC7NQx+ayVr+%i06XA"
|
||||
|
||||
|
||||
# def main():
|
||||
# photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
# print(photosdb.keywords())
|
||||
# print(photosdb.persons())
|
||||
# print(photosdb.albums())
|
||||
|
||||
# print(photosdb.keywords_as_dict())
|
||||
# print(photosdb.persons_as_dict())
|
||||
# print(photosdb.albums_as_dict())
|
||||
|
||||
# # # find all photos with Keyword = Foo and containing John Smith
|
||||
# # photos = photosdb.photos(keywords=["Foo"],persons=["John Smith"])
|
||||
# #
|
||||
# # # find all photos that include Alice Smith but do not contain the keyword Bar
|
||||
# # photos = [p for p in photosdb.photos(persons=["Alice Smith"])
|
||||
# # if p not in photosdb.photos(keywords=["Bar"]) ]
|
||||
# photos = photosdb.photos()
|
||||
# for p in photos:
|
||||
# print(
|
||||
# p.uuid(),
|
||||
# p.filename(),
|
||||
# p.date(),
|
||||
# p.description(),
|
||||
# p.name(),
|
||||
# p.keywords(),
|
||||
# p.albums(),
|
||||
# p.persons(),
|
||||
# p.path(),
|
||||
# )
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
|
||||
@@ -3,6 +3,9 @@ import pytest
|
||||
# TODO: put some of this code into a pre-function
|
||||
|
||||
PHOTOS_DB = "./tests/Test-10.14.6.photoslibrary/database/photos.db"
|
||||
PHOTOS_DB_PATH = "/Test-10.14.6.photoslibrary/database/photos.db"
|
||||
PHOTOS_LIBRARY_PATH = "/Test-10.14.6.photoslibrary"
|
||||
|
||||
KEYWORDS = [
|
||||
"Kids",
|
||||
"wedding",
|
||||
@@ -15,7 +18,7 @@ KEYWORDS = [
|
||||
"United Kingdom",
|
||||
]
|
||||
PERSONS = ["Katie", "Suzy", "Maria"]
|
||||
ALBUMS = ["Pumpkin Farm"]
|
||||
ALBUMS = ["Pumpkin Farm", "Test Album", "Test Album (1)"]
|
||||
KEYWORDS_DICT = {
|
||||
"Kids": 4,
|
||||
"wedding": 2,
|
||||
@@ -28,7 +31,7 @@ KEYWORDS_DICT = {
|
||||
"United Kingdom": 1,
|
||||
}
|
||||
PERSONS_DICT = {"Katie": 3, "Suzy": 2, "Maria": 1}
|
||||
ALBUM_DICT = {"Pumpkin Farm": 3}
|
||||
ALBUM_DICT = {"Pumpkin Farm": 3, "Test Album": 1, "Test Album (1)": 1}
|
||||
|
||||
|
||||
def test_init():
|
||||
@@ -43,6 +46,7 @@ def test_db_version():
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
assert photosdb.get_db_version() in osxphotos._TESTED_DB_VERSIONS
|
||||
assert photosdb.get_db_version() == "4025"
|
||||
|
||||
|
||||
def test_os_version():
|
||||
@@ -122,7 +126,7 @@ def test_attributes():
|
||||
)
|
||||
assert p.description() == "Girl holding pumpkin"
|
||||
assert p.name() == "I found one!"
|
||||
assert p.albums() == ["Pumpkin Farm"]
|
||||
assert p.albums() == ["Pumpkin Farm", "Test Album (1)"]
|
||||
assert p.persons() == ["Katie"]
|
||||
assert p.path().endswith(
|
||||
"/tests/Test-10.14.6.photoslibrary/Masters/2019/07/27/20190727-131650/Pumkins2.jpg"
|
||||
@@ -229,6 +233,30 @@ def test_hasadjustments2():
|
||||
assert p.hasadjustments() == False
|
||||
|
||||
|
||||
def test_external_edit1():
|
||||
# test image has been edited in external editor
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=["3Jn73XpSQQCluzRBMWRsMA"])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
|
||||
assert p.external_edit() == True
|
||||
|
||||
|
||||
def test_external_edit2():
|
||||
# test image has not been edited in external editor
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=["6bxcNnzRQKGnK4uPrCJ9UQ"])
|
||||
assert len(photos) == 1
|
||||
p = photos[0]
|
||||
|
||||
assert p.external_edit() == False
|
||||
|
||||
|
||||
def test_path_edited1():
|
||||
# test a valid edited path
|
||||
import osxphotos
|
||||
@@ -282,35 +310,18 @@ def test_keyword_not_in_album():
|
||||
assert photos3[0].uuid() == "od0fmC7NQx+ayVr+%i06XA"
|
||||
|
||||
|
||||
# def main():
|
||||
# photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
# print(photosdb.keywords())
|
||||
# print(photosdb.persons())
|
||||
# print(photosdb.albums())
|
||||
def test_get_db_path():
|
||||
import osxphotos
|
||||
|
||||
# print(photosdb.keywords_as_dict())
|
||||
# print(photosdb.persons_as_dict())
|
||||
# print(photosdb.albums_as_dict())
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
db_path = photosdb.get_db_path()
|
||||
assert db_path.endswith(PHOTOS_DB_PATH)
|
||||
|
||||
# # # find all photos with Keyword = Foo and containing John Smith
|
||||
# # photos = photosdb.photos(keywords=["Foo"],persons=["John Smith"])
|
||||
# #
|
||||
# # # find all photos that include Alice Smith but do not contain the keyword Bar
|
||||
# # photos = [p for p in photosdb.photos(persons=["Alice Smith"])
|
||||
# # if p not in photosdb.photos(keywords=["Bar"]) ]
|
||||
# photos = photosdb.photos()
|
||||
# for p in photos:
|
||||
# print(
|
||||
# p.uuid(),
|
||||
# p.filename(),
|
||||
# p.date(),
|
||||
# p.description(),
|
||||
# p.name(),
|
||||
# p.keywords(),
|
||||
# p.albums(),
|
||||
# p.persons(),
|
||||
# p.path(),
|
||||
# )
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
def test_get_library_path():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
lib_path = photosdb.get_library_path()
|
||||
assert lib_path.endswith(PHOTOS_LIBRARY_PATH)
|
||||
|
||||
|
||||