Added get_db_path and get_library_path to PhotosDB
This commit is contained in:
@@ -133,7 +133,11 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
#### ```get_system_library_path()```
|
#### ```get_system_library_path()```
|
||||||
|
|
||||||
**MacOS 10.15 Only** Return path to System Photo Library as string. On MacOS version < 10.15, raises Exception.
|
**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.
|
||||||
|
|
||||||
### PhotosDB
|
### PhotosDB
|
||||||
|
|
||||||
@@ -216,7 +220,7 @@ Returns a dictionary of albums found in the Photos library where key is the albu
|
|||||||
|
|
||||||
**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.
|
**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_photos_library_path```
|
#### ```get_library_path```
|
||||||
```python
|
```python
|
||||||
# assumes photosdb is a PhotosDB object (see above)
|
# assumes photosdb is a PhotosDB object (see above)
|
||||||
photosdb.get_photos_library_path()
|
photosdb.get_photos_library_path()
|
||||||
|
|||||||
@@ -109,13 +109,16 @@ def _get_resource_loc(model_id):
|
|||||||
|
|
||||||
return folder_id, file_id
|
return folder_id, file_id
|
||||||
|
|
||||||
|
|
||||||
def get_system_library_path():
|
def get_system_library_path():
|
||||||
""" return the path to the system Photos library as string """
|
""" return the path to the system Photos library as string """
|
||||||
""" only works on MacOS 10.15+ """
|
""" only works on MacOS 10.15+ """
|
||||||
""" on earlier versions, will raise exception """
|
""" on earlier versions, will raise exception """
|
||||||
ver, major, minor = _get_os_version()
|
_, major, _ = _get_os_version()
|
||||||
if int(major) < 15:
|
if int(major) < 15:
|
||||||
raise Exception("get_system_library_path not implemented for MacOS < 10.15",major)
|
raise Exception(
|
||||||
|
"get_system_library_path not implemented for MacOS < 10.15", major
|
||||||
|
)
|
||||||
|
|
||||||
plist_file = Path(
|
plist_file = Path(
|
||||||
str(Path.home())
|
str(Path.home())
|
||||||
@@ -135,7 +138,57 @@ def get_system_library_path():
|
|||||||
else:
|
else:
|
||||||
logging.warning("Could not get path to Photos database")
|
logging.warning("Could not get path to Photos database")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_last_library_path():
|
||||||
|
""" return the path to the last opened Photos library """
|
||||||
|
# TODO: Need a module level method for this and another PhotosDB method to get current library path
|
||||||
|
plist_file = Path(
|
||||||
|
str(Path.home())
|
||||||
|
+ "/Library/Containers/com.apple.Photos/Data/Library/Preferences/com.apple.Photos.plist"
|
||||||
|
)
|
||||||
|
if plist_file.is_file():
|
||||||
|
with open(plist_file, "rb") as fp:
|
||||||
|
pl = plistload(fp)
|
||||||
|
else:
|
||||||
|
logging.warning(f"could not find plist file: {str(plist_file)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# get the IPXDefaultLibraryURLBookmark from com.apple.Photos.plist
|
||||||
|
# this is a serialized CFData object
|
||||||
|
photosurlref = pl["IPXDefaultLibraryURLBookmark"]
|
||||||
|
|
||||||
|
if photosurlref is not None:
|
||||||
|
# use CFURLCreateByResolvingBookmarkData to de-serialize bookmark data into a CFURLRef
|
||||||
|
photosurl = CoreFoundation.CFURLCreateByResolvingBookmarkData(
|
||||||
|
kCFAllocatorDefault, photosurlref, 0, None, None, None, None
|
||||||
|
)
|
||||||
|
|
||||||
|
# the CFURLRef we got is a sruct that python treats as an array
|
||||||
|
# I'd like to pass this to CFURLGetFileSystemRepresentation to get the path but
|
||||||
|
# CFURLGetFileSystemRepresentation barfs when it gets an array from python instead of expected struct
|
||||||
|
# first element is the path string in form:
|
||||||
|
# file:///Users/username/Pictures/Photos%20Library.photoslibrary/
|
||||||
|
photosurlstr = photosurl[0].absoluteString() if photosurl[0] else None
|
||||||
|
|
||||||
|
# now coerce the file URI back into an OS path
|
||||||
|
# surely there must be a better way
|
||||||
|
if photosurlstr is not None:
|
||||||
|
photospath = os.path.normpath(
|
||||||
|
urllib.parse.unquote(urllib.parse.urlparse(photosurlstr).path)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logging.warning(
|
||||||
|
"Could not extract photos URL String from IPXDefaultLibraryURLBookmark"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return photospath
|
||||||
|
else:
|
||||||
|
logging.warning("Could not get path to Photos database")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class PhotosDB:
|
class PhotosDB:
|
||||||
def __init__(self, dbfile=None):
|
def __init__(self, dbfile=None):
|
||||||
""" create a new PhotosDB object """
|
""" create a new PhotosDB object """
|
||||||
@@ -181,7 +234,7 @@ class PhotosDB:
|
|||||||
|
|
||||||
logging.debug(f"dbfile = {dbfile}")
|
logging.debug(f"dbfile = {dbfile}")
|
||||||
if dbfile is None:
|
if dbfile is None:
|
||||||
library_path = self.get_photos_library_path()
|
library_path = get_last_library_path()
|
||||||
# TODO: verify library path not None
|
# TODO: verify library path not None
|
||||||
dbfile = os.path.join(library_path, "database/photos.db")
|
dbfile = os.path.join(library_path, "database/photos.db")
|
||||||
|
|
||||||
@@ -283,7 +336,7 @@ class PhotosDB:
|
|||||||
""" return list of albums found in photos database """
|
""" return list of albums found in photos database """
|
||||||
# Could be more than one album with same name
|
# Could be more than one album with same name
|
||||||
# Right now, they are treated as same album and photos are combined from albums with same name
|
# Right now, they are treated as same album and photos are combined from albums with same name
|
||||||
albums = set()
|
albums = set()
|
||||||
for album in self._dbalbums_album.keys():
|
for album in self._dbalbums_album.keys():
|
||||||
albums.add(self._dbalbum_details[album]["title"])
|
albums.add(self._dbalbum_details[album]["title"])
|
||||||
return list(albums)
|
return list(albums)
|
||||||
@@ -333,59 +386,12 @@ class PhotosDB:
|
|||||||
return self._db_version
|
return self._db_version
|
||||||
|
|
||||||
def get_db_path(self):
|
def get_db_path(self):
|
||||||
""" return path to the Photos library database PhotosDB was initialized with """
|
""" returns path to the Photos library database PhotosDB was initialized with """
|
||||||
return os.path.abspath(self._dbfile)
|
return os.path.abspath(self._dbfile)
|
||||||
|
|
||||||
def get_photos_library_path(self):
|
def get_library_path(self):
|
||||||
""" return the path to the last opened Photos library """
|
""" returns path to the Photos library PhotosDB was initialized with """
|
||||||
# TODO: this is only for last opened library
|
return self._library_path
|
||||||
# TODO: Need a module level method for this and another PhotosDB method to get current library path
|
|
||||||
# TODO: Also need a way to get path of system library
|
|
||||||
plist_file = Path(
|
|
||||||
str(Path.home())
|
|
||||||
+ "/Library/Containers/com.apple.Photos/Data/Library/Preferences/com.apple.Photos.plist"
|
|
||||||
)
|
|
||||||
if plist_file.is_file():
|
|
||||||
with open(plist_file, "rb") as fp:
|
|
||||||
pl = plistload(fp)
|
|
||||||
else:
|
|
||||||
print("could not find plist file: " + str(plist_file), file=sys.stderr)
|
|
||||||
return None
|
|
||||||
|
|
||||||
# get the IPXDefaultLibraryURLBookmark from com.apple.Photos.plist
|
|
||||||
# this is a serialized CFData object
|
|
||||||
photosurlref = pl["IPXDefaultLibraryURLBookmark"]
|
|
||||||
|
|
||||||
if photosurlref is not None:
|
|
||||||
# use CFURLCreateByResolvingBookmarkData to de-serialize bookmark data into a CFURLRef
|
|
||||||
photosurl = CoreFoundation.CFURLCreateByResolvingBookmarkData(
|
|
||||||
kCFAllocatorDefault, photosurlref, 0, None, None, None, None
|
|
||||||
)
|
|
||||||
|
|
||||||
# the CFURLRef we got is a sruct that python treats as an array
|
|
||||||
# I'd like to pass this to CFURLGetFileSystemRepresentation to get the path but
|
|
||||||
# CFURLGetFileSystemRepresentation barfs when it gets an array from python instead of expected struct
|
|
||||||
# first element is the path string in form:
|
|
||||||
# file:///Users/username/Pictures/Photos%20Library.photoslibrary/
|
|
||||||
photosurlstr = photosurl[0].absoluteString() if photosurl[0] else None
|
|
||||||
|
|
||||||
# now coerce the file URI back into an OS path
|
|
||||||
# surely there must be a better way
|
|
||||||
if photosurlstr is not None:
|
|
||||||
photospath = os.path.normpath(
|
|
||||||
urllib.parse.unquote(urllib.parse.urlparse(photosurlstr).path)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
"Could not extract photos URL String from IPXDefaultLibraryURLBookmark",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
return photospath
|
|
||||||
else:
|
|
||||||
print("Could not get path to Photos database", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _copy_db_file(self, fname):
|
def _copy_db_file(self, fname):
|
||||||
""" copies the sqlite database file to a temp file """
|
""" copies the sqlite database file to a temp file """
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ from osxphotos import _UNKNOWN_PERSON
|
|||||||
# TODO: put some of this code into a pre-function
|
# TODO: put some of this code into a pre-function
|
||||||
|
|
||||||
PHOTOS_DB = "./tests/Test-10.15.1.photoslibrary/database/photos.db"
|
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 = [
|
KEYWORDS = [
|
||||||
"Kids",
|
"Kids",
|
||||||
"wedding",
|
"wedding",
|
||||||
@@ -18,7 +21,10 @@ KEYWORDS = [
|
|||||||
]
|
]
|
||||||
# Photos 5 includes blank person for detected face
|
# Photos 5 includes blank person for detected face
|
||||||
PERSONS = ["Katie", "Suzy", "Maria", _UNKNOWN_PERSON]
|
PERSONS = ["Katie", "Suzy", "Maria", _UNKNOWN_PERSON]
|
||||||
ALBUMS = ["Pumpkin Farm", "Test Album"] # Note: there are 2 albums named "Test Album" for testing duplicate album names
|
ALBUMS = [
|
||||||
|
"Pumpkin Farm",
|
||||||
|
"Test Album",
|
||||||
|
] # Note: there are 2 albums named "Test Album" for testing duplicate album names
|
||||||
KEYWORDS_DICT = {
|
KEYWORDS_DICT = {
|
||||||
"Kids": 4,
|
"Kids": 4,
|
||||||
"wedding": 2,
|
"wedding": 2,
|
||||||
@@ -31,8 +37,10 @@ KEYWORDS_DICT = {
|
|||||||
"United Kingdom": 1,
|
"United Kingdom": 1,
|
||||||
}
|
}
|
||||||
PERSONS_DICT = {"Katie": 3, "Suzy": 2, "Maria": 1, _UNKNOWN_PERSON: 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
|
ALBUM_DICT = {
|
||||||
|
"Pumpkin Farm": 3,
|
||||||
|
"Test Album": 2,
|
||||||
|
} # Note: there are 2 albums named "Test Album" for testing duplicate album names
|
||||||
|
|
||||||
|
|
||||||
def test_init():
|
def test_init():
|
||||||
@@ -312,3 +320,19 @@ def test_keyword_not_in_album():
|
|||||||
assert len(photos3) == 1
|
assert len(photos3) == 1
|
||||||
assert photos3[0].uuid() == "A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C"
|
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)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import pytest
|
|||||||
# TODO: put some of this code into a pre-function
|
# TODO: put some of this code into a pre-function
|
||||||
|
|
||||||
PHOTOS_DB = "./tests/Test-10.14.6.photoslibrary/database/photos.db"
|
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 = [
|
KEYWORDS = [
|
||||||
"Kids",
|
"Kids",
|
||||||
"wedding",
|
"wedding",
|
||||||
@@ -307,35 +310,18 @@ def test_keyword_not_in_album():
|
|||||||
assert photos3[0].uuid() == "od0fmC7NQx+ayVr+%i06XA"
|
assert photos3[0].uuid() == "od0fmC7NQx+ayVr+%i06XA"
|
||||||
|
|
||||||
|
|
||||||
# def main():
|
def test_get_db_path():
|
||||||
# photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
import osxphotos
|
||||||
# print(photosdb.keywords())
|
|
||||||
# print(photosdb.persons())
|
|
||||||
# print(photosdb.albums())
|
|
||||||
|
|
||||||
# print(photosdb.keywords_as_dict())
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
# print(photosdb.persons_as_dict())
|
db_path = photosdb.get_db_path()
|
||||||
# print(photosdb.albums_as_dict())
|
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__":
|
def test_get_library_path():
|
||||||
# main()
|
import osxphotos
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
lib_path = photosdb.get_library_path()
|
||||||
|
assert lib_path.endswith(PHOTOS_LIBRARY_PATH)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user