Added tests
@@ -4,14 +4,16 @@ from pathlib import Path
|
|||||||
from plistlib import load as plistload
|
from plistlib import load as plistload
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import tempfile
|
import tempfile
|
||||||
import objc
|
|
||||||
import CoreFoundation
|
|
||||||
from Foundation import *
|
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import sys
|
import sys
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
import pprint
|
import pprint
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
|
import objc
|
||||||
|
import CoreFoundation
|
||||||
|
from Foundation import *
|
||||||
|
|
||||||
from . import _applescript
|
from . import _applescript
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -72,7 +74,7 @@ class PhotosDB:
|
|||||||
dbfile = os.path.join(library_path, "database/photos.db")
|
dbfile = os.path.join(library_path, "database/photos.db")
|
||||||
logger.debug(dbfile)
|
logger.debug(dbfile)
|
||||||
|
|
||||||
logger.debug("filename = %s" % dbfile)
|
logger.debug(f"filename = {dbfile}")
|
||||||
|
|
||||||
# TODO: replace os.path with pathlib
|
# TODO: replace os.path with pathlib
|
||||||
# TODO: clean this up -- we'll already know library_path
|
# TODO: clean this up -- we'll already know library_path
|
||||||
@@ -80,12 +82,12 @@ class PhotosDB:
|
|||||||
(library_path, tmp) = os.path.split(library_path)
|
(library_path, tmp) = os.path.split(library_path)
|
||||||
masters_path = os.path.join(library_path, "Masters")
|
masters_path = os.path.join(library_path, "Masters")
|
||||||
self._masters_path = masters_path
|
self._masters_path = masters_path
|
||||||
logger.debug("library = %s, masters = %s" % (library_path, masters_path))
|
logger.debug(f"library = {library_path}, masters = {masters_path}")
|
||||||
|
|
||||||
if not _check_file_exists(dbfile):
|
if not _check_file_exists(dbfile):
|
||||||
sys.exit("_dbfile %s does not exist" % (dbfile))
|
sys.exit(f"_dbfile {dbfile} does not exist")
|
||||||
|
|
||||||
logger.info("database filename = %s" % dbfile)
|
logger.info(f"database filename = {dbfile}")
|
||||||
|
|
||||||
self._dbfile = dbfile
|
self._dbfile = dbfile
|
||||||
self._setup_applescript()
|
self._setup_applescript()
|
||||||
@@ -234,12 +236,12 @@ class PhotosDB:
|
|||||||
|
|
||||||
def _open_sql_file(self, file):
|
def _open_sql_file(self, file):
|
||||||
fname = file
|
fname = file
|
||||||
logger.debug("Trying to open database %s" % (fname))
|
logger.debug(f"Trying to open database {fname}")
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect("%s" % (fname))
|
conn = sqlite3.connect(f"{fname}")
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
print("An error occurred: %s %s" % (e.args[0], fname))
|
print(f"An error occurred: {e.args[0]} {fname}")
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
logger.debug("SQLite database is open")
|
logger.debug("SQLite database is open")
|
||||||
return (conn, c)
|
return (conn, c)
|
||||||
@@ -277,7 +279,7 @@ class PhotosDB:
|
|||||||
)
|
)
|
||||||
for person in c:
|
for person in c:
|
||||||
if person[0] == None:
|
if person[0] == None:
|
||||||
logger.debug("skipping person = None %s" % person[1])
|
logger.debug(f"skipping person = None {person[1]}")
|
||||||
continue
|
continue
|
||||||
if not person[1] in self._dbfaces_uuid:
|
if not person[1] in self._dbfaces_uuid:
|
||||||
self._dbfaces_uuid[person[1]] = []
|
self._dbfaces_uuid[person[1]] = []
|
||||||
@@ -314,7 +316,7 @@ class PhotosDB:
|
|||||||
self._dbalbums_album[album[0]] = []
|
self._dbalbums_album[album[0]] = []
|
||||||
self._dbalbums_uuid[album[1]].append(album[0])
|
self._dbalbums_uuid[album[1]].append(album[0])
|
||||||
self._dbalbums_album[album[0]].append(album[1])
|
self._dbalbums_album[album[0]].append(album[1])
|
||||||
logger.debug("%s %s" % (album[1], album[0]))
|
logger.debug(f"{album[1]} {album[0]}")
|
||||||
# set_pbar_status(i)
|
# set_pbar_status(i)
|
||||||
i = i + 1
|
i = i + 1
|
||||||
logger.debug("Finished walking through albums")
|
logger.debug("Finished walking through albums")
|
||||||
@@ -344,7 +346,7 @@ class PhotosDB:
|
|||||||
self._dbkeywords_keyword[keyword[0]] = []
|
self._dbkeywords_keyword[keyword[0]] = []
|
||||||
self._dbkeywords_uuid[keyword[1]].append(keyword[0])
|
self._dbkeywords_uuid[keyword[1]].append(keyword[0])
|
||||||
self._dbkeywords_keyword[keyword[0]].append(keyword[1])
|
self._dbkeywords_keyword[keyword[0]].append(keyword[1])
|
||||||
logger.debug("%s %s" % (keyword[1], keyword[0]))
|
logger.debug(f"{keyword[1]} {keyword[0]}")
|
||||||
# set_pbar_status(i)
|
# set_pbar_status(i)
|
||||||
i = i + 1
|
i = i + 1
|
||||||
logger.debug("Finished walking through keywords")
|
logger.debug("Finished walking through keywords")
|
||||||
@@ -357,7 +359,7 @@ class PhotosDB:
|
|||||||
i = 0
|
i = 0
|
||||||
for vol in c:
|
for vol in c:
|
||||||
self._dbvolumes[vol[0]] = vol[1]
|
self._dbvolumes[vol[0]] = vol[1]
|
||||||
logger.debug("%s %s" % (vol[0], vol[1]))
|
logger.debug(f"{vol[0]} {vol[1]}")
|
||||||
# set_pbar_status(i)
|
# set_pbar_status(i)
|
||||||
i = i + 1
|
i = i + 1
|
||||||
logger.debug("Finished walking through volumes")
|
logger.debug("Finished walking through volumes")
|
||||||
@@ -385,7 +387,7 @@ class PhotosDB:
|
|||||||
i = i + 1
|
i = i + 1
|
||||||
uuid = row[0]
|
uuid = row[0]
|
||||||
if _debug:
|
if _debug:
|
||||||
print("i = %d, uuid = '%s, master = '%s" % (i, uuid, row[2]))
|
print(f"i = {i:d}, uuid = '{uuid}, master = '{row[2]}")
|
||||||
self._dbphotos[uuid] = {}
|
self._dbphotos[uuid] = {}
|
||||||
self._dbphotos[uuid]["modelID"] = row[1]
|
self._dbphotos[uuid]["modelID"] = row[1]
|
||||||
self._dbphotos[uuid]["masterUuid"] = row[2]
|
self._dbphotos[uuid]["masterUuid"] = row[2]
|
||||||
@@ -490,58 +492,52 @@ class PhotosDB:
|
|||||||
If called with no args, returns the entire database of photos
|
If called with no args, returns the entire database of photos
|
||||||
If called with args, returns photos matching the args (e.g. keywords, persons, etc.)
|
If called with args, returns photos matching the args (e.g. keywords, persons, etc.)
|
||||||
If more than one arg, returns photos matching all the criteria (e.g. keywords AND persons)
|
If more than one arg, returns photos matching all the criteria (e.g. keywords AND persons)
|
||||||
TODO: Still need to fix AND
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def photos(self, keywords=[], uuid=[], persons=[], albums=[]):
|
def photos(self, keywords=[], uuid=[], persons=[], albums=[]):
|
||||||
#TODO: combine photos and photos_sets, I think only one needed
|
photos_sets = [] # list of photo sets to perform intersection of
|
||||||
# photos = [] # list of photos (PhotoInfo objects) that will be returned
|
|
||||||
photos_sets = [] # list of sets to perform intersection of
|
|
||||||
if not keywords and not uuid and not persons and not albums:
|
if not keywords and not uuid and not persons and not albums:
|
||||||
# return all the photos
|
# return all the photos
|
||||||
|
# append keys of all photos as a single set to photos_sets
|
||||||
logger.debug("return all photos")
|
logger.debug("return all photos")
|
||||||
photos_sets.append(set(self._dbphotos.keys()))
|
photos_sets.append(set(self._dbphotos.keys()))
|
||||||
else:
|
else:
|
||||||
if albums:
|
if albums:
|
||||||
for album in albums:
|
for album in albums:
|
||||||
logger.info("album=%s" % album)
|
logger.info(f"album={album}")
|
||||||
if album in self._dbalbums_album:
|
if album in self._dbalbums_album:
|
||||||
logger.info("processing album %s:" % album)
|
logger.info(f"processing album {album}:")
|
||||||
photos_sets.append(set(self._dbalbums_album[album]))
|
photos_sets.append(set(self._dbalbums_album[album]))
|
||||||
else:
|
else:
|
||||||
logger.debug("Could not find album '%s' in database" % (album))
|
logger.debug(f"Could not find album '{album}' in database")
|
||||||
|
|
||||||
if uuid:
|
if uuid:
|
||||||
for u in uuid:
|
for u in uuid:
|
||||||
logger.info("uuid=%s" % u)
|
logger.info(f"uuid={u}")
|
||||||
if u in self._dbphotos:
|
if u in self._dbphotos:
|
||||||
logger.info("processing uuid %s:" % u)
|
logger.info(f"processing uuid {u}:")
|
||||||
photos_sets.append(set([u]))
|
photos_sets.append(set([u]))
|
||||||
else:
|
else:
|
||||||
logger.debug("Could not find uuid '%s' in database" % (u))
|
logger.debug(f"Could not find uuid '{u}' in database")
|
||||||
|
|
||||||
if keywords:
|
if keywords:
|
||||||
for keyword in keywords:
|
for keyword in keywords:
|
||||||
logger.info("keyword=%s" % keyword)
|
logger.info(f"keyword={keyword}")
|
||||||
if keyword in self._dbkeywords_keyword:
|
if keyword in self._dbkeywords_keyword:
|
||||||
logger.info("processing keyword %s:" % keyword)
|
logger.info(f"processing keyword {keyword}:")
|
||||||
photos_sets.append(set(self._dbkeywords_keyword[keyword]))
|
photos_sets.append(set(self._dbkeywords_keyword[keyword]))
|
||||||
logger.debug(f"photos_sets {photos_sets}")
|
logger.debug(f"photos_sets {photos_sets}")
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
logger.debug(f"Could not find keyword '{keyword}' in database")
|
||||||
"Could not find keyword '%s' in database" % (keyword)
|
|
||||||
)
|
|
||||||
|
|
||||||
if persons:
|
if persons:
|
||||||
for person in persons:
|
for person in persons:
|
||||||
logger.info("person=%s" % person)
|
logger.info(f"person={person}")
|
||||||
if person in self._dbfaces_person:
|
if person in self._dbfaces_person:
|
||||||
logger.info("processing person %s:" % person)
|
logger.info(f"processing person {person}:")
|
||||||
photos_sets.append(set(self._dbfaces_person[person]))
|
photos_sets.append(set(self._dbfaces_person[person]))
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
logger.debug(f"Could not find person '{person}' in database")
|
||||||
"Could not find person '%s' in database" % (person)
|
|
||||||
)
|
|
||||||
|
|
||||||
photoinfo = []
|
photoinfo = []
|
||||||
if photos_sets: # found some photos
|
if photos_sets: # found some photos
|
||||||
@@ -622,6 +618,9 @@ class PhotoInfo:
|
|||||||
def uuid(self):
|
def uuid(self):
|
||||||
return self.__uuid
|
return self.__uuid
|
||||||
|
|
||||||
|
def ismissing(self):
|
||||||
|
return True if self.__info["isMissing"] == 1 else False
|
||||||
|
|
||||||
# compare two PhotoInfo objects for equality
|
# compare two PhotoInfo objects for equality
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if isinstance(other, self.__class__):
|
if isinstance(other, self.__class__):
|
||||||
|
|||||||
6
setup.py
@@ -27,7 +27,7 @@
|
|||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
# from distutils.core import setup
|
# from distutils.core import setup
|
||||||
from setuptools import setup
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
# read the contents of README file
|
# read the contents of README file
|
||||||
from os import path
|
from os import path
|
||||||
@@ -47,7 +47,7 @@ setup(
|
|||||||
url="https://github.com/RhetTbull/",
|
url="https://github.com/RhetTbull/",
|
||||||
project_urls={"GitHub": "https://github.com/RhetTbull/osxphotos"},
|
project_urls={"GitHub": "https://github.com/RhetTbull/osxphotos"},
|
||||||
download_url="https://github.com/RhetTbull/osxphotos",
|
download_url="https://github.com/RhetTbull/osxphotos",
|
||||||
packages=["osxphotos"],
|
packages=find_packages(exclude=["tests","examples"]),
|
||||||
license="License :: OSI Approved :: MIT License",
|
license="License :: OSI Approved :: MIT License",
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
@@ -55,7 +55,7 @@ setup(
|
|||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
"Operating System :: MacOS :: MacOS X",
|
"Operating System :: MacOS :: MacOS X",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python :: 3.6",
|
||||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
],
|
],
|
||||||
install_requires=["pyobjc","loguru"],
|
install_requires=["pyobjc","loguru"],
|
||||||
|
|||||||
21
tests/README.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Tests for osxphotos #
|
||||||
|
|
||||||
|
## Running Tests ##
|
||||||
|
Tests require pytest:
|
||||||
|
`pip install pytest`
|
||||||
|
|
||||||
|
To run the tests, do the following from the main source folder:
|
||||||
|
`python -m pytest tests/`
|
||||||
|
|
||||||
|
Running the tests this way allows the library to be tested without installing it.
|
||||||
|
|
||||||
|
## Attribution ##
|
||||||
|
These tests utilize a test Photos library. The test library is populated with photos from [flickr](https://www.flickr.com). All images used are licensed under Creative Commons 2.0 Attribution [license](https://creativecommons.org/licenses/by/2.0/).
|
||||||
|
|
||||||
|
Images used from:
|
||||||
|
- [Jeff Hitchcock](https://www.flickr.com/photos/arbron/48353451872/)
|
||||||
|
- [Carlos Montesdeoca](https://www.flickr.com/photos/carlosmontesdeocastudio)
|
||||||
|
- [Rydale Clothing](https://www.flickr.com/photos/rydaleclothing)
|
||||||
|
- [Marco Verch](https://www.flickr.com/photos/30478819@N08/48228222317/)
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 541 KiB |
|
After Width: | Height: | Size: 528 KiB |
|
After Width: | Height: | Size: 574 KiB |
|
After Width: | Height: | Size: 500 KiB |
|
After Width: | Height: | Size: 450 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>DatabaseMinorVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>DatabaseVersion</key>
|
||||||
|
<integer>112</integer>
|
||||||
|
<key>LastOpenMode</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
<key>LibrarySchemaVersion</key>
|
||||||
|
<integer>3301</integer>
|
||||||
|
<key>MetaSchemaVersion</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
<key>createDate</key>
|
||||||
|
<date>2019-07-26T20:15:17Z</date>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
tests/Test-10.13.6.photoslibrary/database/RKAlbum_name.skindex
Normal file
BIN
tests/Test-10.13.6.photoslibrary/database/RKMemory_title.skindex
Normal file
BIN
tests/Test-10.13.6.photoslibrary/database/metaSchema.db
Normal file
BIN
tests/Test-10.13.6.photoslibrary/database/photos.db
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?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>Photos</key>
|
||||||
|
<dict>
|
||||||
|
<key>CollapsedSidebarSectionIdentifiers</key>
|
||||||
|
<array/>
|
||||||
|
<key>ExpandedSidebarItemIdentifiers</key>
|
||||||
|
<array>
|
||||||
|
<string>TopLevelAlbums</string>
|
||||||
|
<string>TopLevelSlideshows</string>
|
||||||
|
</array>
|
||||||
|
<key>IPXWorkspaceControllerZoomLevelsKey</key>
|
||||||
|
<dict>
|
||||||
|
<key>kZoomLevelIdentifierAlbums</key>
|
||||||
|
<integer>7</integer>
|
||||||
|
<key>kZoomLevelIdentifierVersions</key>
|
||||||
|
<integer>7</integer>
|
||||||
|
</dict>
|
||||||
|
<key>lastAddToDestination</key>
|
||||||
|
<dict>
|
||||||
|
<key>key</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>lastKnownDisplayName</key>
|
||||||
|
<string>September 28, 2018</string>
|
||||||
|
<key>type</key>
|
||||||
|
<string>album</string>
|
||||||
|
<key>uuid</key>
|
||||||
|
<string>+Ep8CrNRRhea9eVA618FMg</string>
|
||||||
|
</dict>
|
||||||
|
<key>lastKnownItemCounts</key>
|
||||||
|
<dict>
|
||||||
|
<key>other</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>photos</key>
|
||||||
|
<integer>7</integer>
|
||||||
|
<key>videos</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||||
|
<date>2019-07-26T20:15:18Z</date>
|
||||||
|
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||||
|
<date>2019-07-26T20:15:18Z</date>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?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/>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PVClustererBringUpState</key>
|
||||||
|
<integer>50</integer>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IncrementalPersonProcessingStage</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PersonBuilderLastMinimumFaceGroupSizeForCreatingMergeCandidates</key>
|
||||||
|
<integer>15</integer>
|
||||||
|
<key>PersonBuilderMergeCandidatesEnabled</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 490 KiB |
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PLLanguageAndLocaleKey</key>
|
||||||
|
<string>en-US:en_US</string>
|
||||||
|
<key>PLLastGeoProviderIdKey</key>
|
||||||
|
<string>7618</string>
|
||||||
|
<key>PLLastLocationInfoFormatVer</key>
|
||||||
|
<integer>12</integer>
|
||||||
|
<key>PLLastRevGeoForcedProviderOutOfDateCheckVersionKey</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>PLLastRevGeoVerFileFetchDateKey</key>
|
||||||
|
<date>2019-07-26T20:15:18Z</date>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>LastHistoryRowId</key>
|
||||||
|
<integer>545</integer>
|
||||||
|
<key>LibraryBuildTag</key>
|
||||||
|
<string>BEA5F0E8-BA6B-4462-8F73-3E53BBE4C943</string>
|
||||||
|
<key>LibrarySchemaVersion</key>
|
||||||
|
<integer>3301</integer>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>FileVersion</key>
|
||||||
|
<integer>11</integer>
|
||||||
|
<key>Source</key>
|
||||||
|
<dict>
|
||||||
|
<key>35230</key>
|
||||||
|
<dict>
|
||||||
|
<key>CountryMinVersions</key>
|
||||||
|
<dict>
|
||||||
|
<key>OTHER</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>CurrentVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>NoResultErrorIsSuccess</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>57879</key>
|
||||||
|
<dict>
|
||||||
|
<key>CountryMinVersions</key>
|
||||||
|
<dict>
|
||||||
|
<key>OTHER</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>CurrentVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>NoResultErrorIsSuccess</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>7618</key>
|
||||||
|
<dict>
|
||||||
|
<key>AddCountyIfNeeded</key>
|
||||||
|
<true/>
|
||||||
|
<key>CountryMinVersions</key>
|
||||||
|
<dict>
|
||||||
|
<key>OTHER</key>
|
||||||
|
<integer>10</integer>
|
||||||
|
</dict>
|
||||||
|
<key>CurrentVersion</key>
|
||||||
|
<integer>10</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 288 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 272 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 225 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 485 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 329 KiB |
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>DatabaseMinorVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>DatabaseVersion</key>
|
||||||
|
<integer>112</integer>
|
||||||
|
<key>HistoricalMarker</key>
|
||||||
|
<dict>
|
||||||
|
<key>LastHistoryRowId</key>
|
||||||
|
<integer>551</integer>
|
||||||
|
<key>LibraryBuildTag</key>
|
||||||
|
<string>BEA5F0E8-BA6B-4462-8F73-3E53BBE4C943</string>
|
||||||
|
<key>LibrarySchemaVersion</key>
|
||||||
|
<integer>3301</integer>
|
||||||
|
</dict>
|
||||||
|
<key>LibrarySchemaVersion</key>
|
||||||
|
<integer>3301</integer>
|
||||||
|
<key>MetaSchemaVersion</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
<key>SnapshotComplete</key>
|
||||||
|
<true/>
|
||||||
|
<key>SnapshotCompletedDate</key>
|
||||||
|
<date>2019-07-26T20:15:17Z</date>
|
||||||
|
<key>SnapshotLastValidated</key>
|
||||||
|
<date>2019-07-26T20:15:17Z</date>
|
||||||
|
<key>SnapshotTables</key>
|
||||||
|
<dict/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
188
tests/test_1.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
# TODO: put some of this code into a pre-function
|
||||||
|
|
||||||
|
PHOTOS_DB = "./tests/Test-10.13.6.photoslibrary/database/photos.db"
|
||||||
|
KEYWORDS = [
|
||||||
|
"Kids",
|
||||||
|
"wedding",
|
||||||
|
"flowers",
|
||||||
|
"England",
|
||||||
|
"London",
|
||||||
|
"London 2018",
|
||||||
|
"St. James's Park",
|
||||||
|
"UK",
|
||||||
|
"United Kingdom",
|
||||||
|
]
|
||||||
|
PERSONS = ["Katie", "Suzy", "Maria"]
|
||||||
|
ALBUMS = ["Pumpkin Farm"]
|
||||||
|
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}
|
||||||
|
|
||||||
|
|
||||||
|
def test_init():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
assert isinstance(photosdb, osxphotos.PhotosDB)
|
||||||
|
|
||||||
|
|
||||||
|
def test_persons():
|
||||||
|
import osxphotos
|
||||||
|
import collections
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
assert "Katie" in photosdb.persons()
|
||||||
|
assert collections.Counter(PERSONS) == collections.Counter(photosdb.persons())
|
||||||
|
|
||||||
|
|
||||||
|
def test_keywords():
|
||||||
|
import osxphotos
|
||||||
|
import collections
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
assert "wedding" in photosdb.keywords()
|
||||||
|
assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords())
|
||||||
|
|
||||||
|
|
||||||
|
def test_albums():
|
||||||
|
import osxphotos
|
||||||
|
import collections
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
assert "Pumpkin Farm" in photosdb.albums()
|
||||||
|
assert collections.Counter(ALBUMS) == collections.Counter(photosdb.albums())
|
||||||
|
|
||||||
|
|
||||||
|
def test_keywords_dict():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
keywords = photosdb.keywords_as_dict()
|
||||||
|
assert keywords["wedding"] == 2
|
||||||
|
assert keywords == KEYWORDS_DICT
|
||||||
|
|
||||||
|
|
||||||
|
def test_persons_as_dict():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
persons = photosdb.persons_as_dict()
|
||||||
|
assert persons["Maria"] == 1
|
||||||
|
assert persons == PERSONS_DICT
|
||||||
|
|
||||||
|
|
||||||
|
def test_albums_as_dict():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
albums = photosdb.albums_as_dict()
|
||||||
|
assert albums["Pumpkin Farm"] == 3
|
||||||
|
assert albums == ALBUM_DICT
|
||||||
|
|
||||||
|
|
||||||
|
def test_attributes():
|
||||||
|
import datetime
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
photos = photosdb.photos(uuid=["RWmFYiDjSyKjeK8Pfna0Eg"])
|
||||||
|
assert len(photos) == 1
|
||||||
|
p = photos[0]
|
||||||
|
assert p.keywords() == ["Kids"]
|
||||||
|
assert p.filename() == "Pumkins2.jpg"
|
||||||
|
assert p.date() == datetime.datetime(2018, 9, 28, 16, 7, 7)
|
||||||
|
assert p.description() == "Girl holding pumpkin"
|
||||||
|
assert p.name() == "I found one!"
|
||||||
|
assert p.albums() == ["Pumpkin Farm"]
|
||||||
|
assert p.persons() == ["Katie"]
|
||||||
|
assert (
|
||||||
|
p.path()
|
||||||
|
== "./tests/Test-10.13.6.photoslibrary/Masters/2019/07/26/20190726-203227/Pumkins2.jpg"
|
||||||
|
)
|
||||||
|
assert p.ismissing() == False
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
photos = photosdb.photos(uuid=["6iAZJP7ZQ5iXxapoJb3ytA"])
|
||||||
|
assert len(photos) == 1
|
||||||
|
p = photos[0]
|
||||||
|
assert p.path() == None
|
||||||
|
assert p.ismissing() == True
|
||||||
|
|
||||||
|
|
||||||
|
def test_count():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
photos = photosdb.photos()
|
||||||
|
assert len(photos) == 7
|
||||||
|
|
||||||
|
|
||||||
|
def test_keyword_2():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
photos = photosdb.photos(keywords=["wedding"])
|
||||||
|
assert len(photos) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_keyword_not_in_album():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
|
||||||
|
# find all photos with keyword "Kids" not in the album "Pumpkin Farm"
|
||||||
|
photos1 = photosdb.photos(albums=["Pumpkin Farm"])
|
||||||
|
photos2 = photosdb.photos(keywords=["Kids"])
|
||||||
|
photos3 = [p for p in photos2 if p not in photos1]
|
||||||
|
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()
|
||||||