Added media type specials, closes #60
This commit is contained in:
parent
1f8fd6e929
commit
15d7ad538d
19
README.md
19
README.md
@ -657,6 +657,25 @@ Returns the path to the live video component of a [live photo](#live_photo). If
|
||||
|
||||
**Note**: will also return None if the live video component is missing on disk. It's possible that the original photo may be on disk ([ismissing](#ismissing)==False) but the video component is missing, likely because it has not been downloaded from iCloud.
|
||||
|
||||
#### `portrait`
|
||||
Returns True if photo was taken in iPhone portrait mode, otherwise False.
|
||||
|
||||
#### `hdr`
|
||||
Returns True if photo was taken in High Dynamic Range (HDR) mode, otherwise False.
|
||||
|
||||
#### `selfie`
|
||||
Returns True if photo is a selfie (taken with front-facing camera), otherwise False.
|
||||
|
||||
**Note**: Only implemented for Photos version 3.0+. On Photos version < 3.0, returns None.
|
||||
|
||||
#### `time_lapse`
|
||||
Returns True if photo is a time lapse video, otherwise False.
|
||||
|
||||
#### `panorama`
|
||||
Returns True if photo is a panorama, otherwise False.
|
||||
|
||||
**Note**: The result of `PhotoInfo.panorama` will differ from the "Panoramas" Media Types smart album in that it will also identify panorama photos from older phones that Photos does not recognize as panoramas.
|
||||
|
||||
#### `json()`
|
||||
Returns a JSON representation of all photo info
|
||||
|
||||
|
||||
@ -13,6 +13,9 @@ import os.path
|
||||
# TODO: Should this also use compatibleBackToVersion from LiGlobals?
|
||||
_TESTED_DB_VERSIONS = ["6000", "4025", "4016", "3301", "2622"]
|
||||
|
||||
# only version 3 - 4 have RKVersion.selfPortrait
|
||||
_PHOTOS_3_VERSION = "3301"
|
||||
|
||||
# versions later than this have a different database structure
|
||||
_PHOTOS_5_VERSION = "6000"
|
||||
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.22.12"
|
||||
__version__ = "0.22.13"
|
||||
|
||||
@ -454,6 +454,41 @@ class PhotoInfo:
|
||||
|
||||
return photopath
|
||||
|
||||
@property
|
||||
def panorama(self):
|
||||
""" Returns True if photo is a panorama, otherwise False """
|
||||
return self._info["panorama"]
|
||||
|
||||
@property
|
||||
def slow_mo(self):
|
||||
""" Returns True if photo is a slow motion video, otherwise False """
|
||||
return self._info["slow_mo"]
|
||||
|
||||
@property
|
||||
def time_lapse(self):
|
||||
""" Returns True if photo is a time lapse video, otherwise False """
|
||||
return self._info["time_lapse"]
|
||||
|
||||
@property
|
||||
def hdr(self):
|
||||
""" Returns True if photo is an HDR photo, otherwise False """
|
||||
return self._info["hdr"]
|
||||
|
||||
@property
|
||||
def screenshot(self):
|
||||
""" Returns True if photo is an HDR photo, otherwise False """
|
||||
return self._info["screenshot"]
|
||||
|
||||
@property
|
||||
def portrait(self):
|
||||
""" Returns True if photo is a portrait, otherwise False """
|
||||
return self._info["portrait"]
|
||||
|
||||
@property
|
||||
def selfie(self):
|
||||
""" Returns True if photo is a selfie (front facing camera), otherwise False """
|
||||
return self._info["selfie"]
|
||||
|
||||
def export(
|
||||
self,
|
||||
dest,
|
||||
|
||||
@ -18,6 +18,7 @@ from shutil import copyfile
|
||||
from ._constants import (
|
||||
_MOVIE_TYPE,
|
||||
_PHOTO_TYPE,
|
||||
_PHOTOS_3_VERSION,
|
||||
_PHOTOS_5_VERSION,
|
||||
_TESTED_DB_VERSIONS,
|
||||
_TESTED_OS_VERSIONS,
|
||||
@ -522,21 +523,36 @@ class PhotosDB:
|
||||
self._dbvolumes[vol[0]] = vol[1]
|
||||
|
||||
# Get photo details
|
||||
c.execute(
|
||||
""" SELECT RKVersion.uuid, RKVersion.modelId, RKVersion.masterUuid, RKVersion.filename,
|
||||
RKVersion.lastmodifieddate, RKVersion.imageDate, RKVersion.mainRating,
|
||||
RKVersion.hasAdjustments, RKVersion.hasKeywords, RKVersion.imageTimeZoneOffsetSeconds,
|
||||
RKMaster.volumeId, RKMaster.imagePath, RKVersion.extendedDescription, RKVersion.name,
|
||||
RKMaster.isMissing, RKMaster.originalFileName, RKVersion.isFavorite, RKVersion.isHidden,
|
||||
RKVersion.latitude, RKVersion.longitude,
|
||||
RKVersion.adjustmentUuid, RKVersion.type, RKMaster.UTI,
|
||||
RKVersion.burstUuid, RKVersion.burstPickType,
|
||||
RKVersion.specialType, RKMaster.modelID
|
||||
FROM RKVersion, RKMaster WHERE RKVersion.isInTrash = 0 AND
|
||||
RKVersion.masterUuid = RKMaster.uuid AND RKVersion.filename NOT LIKE '%.pdf' """
|
||||
)
|
||||
|
||||
# TODO: RKVersion.selfPortrait -- only in Photos 3 and up
|
||||
if self._db_version < _PHOTOS_3_VERSION:
|
||||
# Photos < 3.0 doesn't have RKVersion.selfPortrait (selfie)
|
||||
c.execute(
|
||||
""" SELECT RKVersion.uuid, RKVersion.modelId, RKVersion.masterUuid, RKVersion.filename,
|
||||
RKVersion.lastmodifieddate, RKVersion.imageDate, RKVersion.mainRating,
|
||||
RKVersion.hasAdjustments, RKVersion.hasKeywords, RKVersion.imageTimeZoneOffsetSeconds,
|
||||
RKMaster.volumeId, RKMaster.imagePath, RKVersion.extendedDescription, RKVersion.name,
|
||||
RKMaster.isMissing, RKMaster.originalFileName, RKVersion.isFavorite, RKVersion.isHidden,
|
||||
RKVersion.latitude, RKVersion.longitude,
|
||||
RKVersion.adjustmentUuid, RKVersion.type, RKMaster.UTI,
|
||||
RKVersion.burstUuid, RKVersion.burstPickType,
|
||||
RKVersion.specialType, RKMaster.modelID
|
||||
FROM RKVersion, RKMaster WHERE RKVersion.isInTrash = 0 AND
|
||||
RKVersion.masterUuid = RKMaster.uuid AND RKVersion.filename NOT LIKE '%.pdf' """
|
||||
)
|
||||
else:
|
||||
c.execute(
|
||||
""" SELECT RKVersion.uuid, RKVersion.modelId, RKVersion.masterUuid, RKVersion.filename,
|
||||
RKVersion.lastmodifieddate, RKVersion.imageDate, RKVersion.mainRating,
|
||||
RKVersion.hasAdjustments, RKVersion.hasKeywords, RKVersion.imageTimeZoneOffsetSeconds,
|
||||
RKMaster.volumeId, RKMaster.imagePath, RKVersion.extendedDescription, RKVersion.name,
|
||||
RKMaster.isMissing, RKMaster.originalFileName, RKVersion.isFavorite, RKVersion.isHidden,
|
||||
RKVersion.latitude, RKVersion.longitude,
|
||||
RKVersion.adjustmentUuid, RKVersion.type, RKMaster.UTI,
|
||||
RKVersion.burstUuid, RKVersion.burstPickType,
|
||||
RKVersion.specialType, RKMaster.modelID,
|
||||
RKVersion.selfPortrait
|
||||
FROM RKVersion, RKMaster WHERE RKVersion.isInTrash = 0 AND
|
||||
RKVersion.masterUuid = RKMaster.uuid AND RKVersion.filename NOT LIKE '%.pdf' """
|
||||
)
|
||||
|
||||
# order of results
|
||||
# 0 RKVersion.uuid
|
||||
@ -566,8 +582,7 @@ class PhotosDB:
|
||||
# 24 RKVersion.burstPickType
|
||||
# 25 RKVersion.specialType
|
||||
# 26 RKMaster.modelID
|
||||
|
||||
# 27 RKVersion.selfPortrait -- 1 if selfie (not yet implemented)
|
||||
# 27 RKVersion.selfPortrait -- 1 if selfie, Photos >= 3, not present for Photos < 3
|
||||
|
||||
for row in c:
|
||||
uuid = row[0]
|
||||
@ -671,9 +686,11 @@ class PhotosDB:
|
||||
self._dbphotos[uuid]["screenshot"] = True if row[25] == 6 else False
|
||||
self._dbphotos[uuid]["portrait"] = True if row[25] == 9 else False
|
||||
|
||||
# TODO: Handle selfies (front facing camera, RKVersion.selfPortrait == 1)
|
||||
# self._dbphotos[uuid]["selfie"] = True if row[27] == 1 else False
|
||||
self._dbphotos[uuid]["selfie"] = None
|
||||
# selfies (front facing camera, RKVersion.selfPortrait == 1)
|
||||
if self._db_version >= _PHOTOS_3_VERSION:
|
||||
self._dbphotos[uuid]["selfie"] = True if row[27] == 1 else False
|
||||
else:
|
||||
self._dbphotos[uuid]["selfie"] = None
|
||||
|
||||
# Init cloud details that will be filled in later if cloud asset
|
||||
self._dbphotos[uuid]["cloudAssetGUID"] = None # Photos 5
|
||||
|
||||
94
tests/test_specials_catalina_10_15_1.py
Normal file
94
tests/test_specials_catalina_10_15_1.py
Normal file
@ -0,0 +1,94 @@
|
||||
# Test cloud photos
|
||||
|
||||
import pytest
|
||||
|
||||
PHOTOS_DB_CLOUD = "./tests/Test-Cloud-10.15.1.photoslibrary/database/photos.db"
|
||||
|
||||
UUID_DICT = {
|
||||
"portrait": "7CDA5F84-AA16-4D28-9AA6-A49E1DF8A332",
|
||||
"hdr": "D11D25FF-5F31-47D2-ABA9-58418878DC15",
|
||||
"selfie": "080525C4-1F05-48E5-A3F4-0C53127BB39C",
|
||||
"time_lapse": "4614086E-C797-4876-B3B9-3057E8D757C9",
|
||||
"panorama": "1C1C8F1F-826B-4A24-B1CB-56628946A834",
|
||||
"no_specials": "C2BBC7A4-5333-46EE-BAF0-093E72111B39",
|
||||
}
|
||||
|
||||
|
||||
def test_portrait():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["portrait"]])
|
||||
|
||||
assert photos[0].portrait
|
||||
assert not photos[0].hdr
|
||||
assert not photos[0].selfie
|
||||
assert not photos[0].time_lapse
|
||||
assert not photos[0].panorama
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].portrait
|
||||
|
||||
|
||||
def test_hdr():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["hdr"]])
|
||||
|
||||
assert photos[0].hdr
|
||||
assert not photos[0].portrait
|
||||
assert not photos[0].selfie
|
||||
assert not photos[0].time_lapse
|
||||
assert not photos[0].panorama
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].hdr
|
||||
|
||||
|
||||
def test_selfie():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["selfie"]])
|
||||
|
||||
assert photos[0].selfie
|
||||
assert not photos[0].portrait
|
||||
assert not photos[0].hdr
|
||||
assert not photos[0].time_lapse
|
||||
assert not photos[0].panorama
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].selfie
|
||||
|
||||
|
||||
def test_time_lapse():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["time_lapse"]], movies=True)
|
||||
|
||||
assert photos[0].time_lapse
|
||||
assert not photos[0].portrait
|
||||
assert not photos[0].hdr
|
||||
assert not photos[0].selfie
|
||||
assert not photos[0].panorama
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].time_lapse
|
||||
|
||||
|
||||
def test_panorama():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["panorama"]])
|
||||
|
||||
assert photos[0].panorama
|
||||
assert not photos[0].portrait
|
||||
assert not photos[0].selfie
|
||||
assert not photos[0].time_lapse
|
||||
assert not photos[0].hdr
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].panorama
|
||||
96
tests/test_specials_mojave_10_14_6.py
Normal file
96
tests/test_specials_mojave_10_14_6.py
Normal file
@ -0,0 +1,96 @@
|
||||
# Test cloud photos
|
||||
|
||||
import pytest
|
||||
|
||||
PHOTOS_DB_CLOUD = "./tests/Test-Cloud-10.14.6.photoslibrary/database/photos.db"
|
||||
|
||||
UUID_DICT = {
|
||||
# "portrait": "7CDA5F84-AA16-4D28-9AA6-A49E1DF8A332",
|
||||
"hdr": "UIgouj2cQqyKJnB2bCHrSg",
|
||||
"selfie": "NsO5Yg8qSPGBGiVxsCd5Kw",
|
||||
"time_lapse": "pKAWFwtlQYuR962KEaonPA",
|
||||
# "panorama": "1C1C8F1F-826B-4A24-B1CB-56628946A834",
|
||||
"no_specials": "%PgMNP%xRTWTJF+oOyZbXQ",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="don't have portrait photo in the 10.14.6yy database")
|
||||
def test_portrait():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["portrait"]])
|
||||
|
||||
assert photos[0].portrait
|
||||
assert not photos[0].hdr
|
||||
assert not photos[0].selfie
|
||||
assert not photos[0].time_lapse
|
||||
assert not photos[0].panorama
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].portrait
|
||||
|
||||
|
||||
def test_hdr():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["hdr"]])
|
||||
|
||||
assert photos[0].hdr
|
||||
assert not photos[0].portrait
|
||||
assert not photos[0].selfie
|
||||
assert not photos[0].time_lapse
|
||||
assert not photos[0].panorama
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].hdr
|
||||
|
||||
|
||||
def test_selfie():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["selfie"]])
|
||||
|
||||
assert photos[0].selfie
|
||||
assert not photos[0].portrait
|
||||
assert not photos[0].hdr
|
||||
assert not photos[0].time_lapse
|
||||
assert not photos[0].panorama
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].selfie
|
||||
|
||||
|
||||
def test_time_lapse():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["time_lapse"]], movies=True)
|
||||
|
||||
assert photos[0].time_lapse
|
||||
assert not photos[0].portrait
|
||||
assert not photos[0].hdr
|
||||
assert not photos[0].selfie
|
||||
assert not photos[0].panorama
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].time_lapse
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="no panorama in 10.14.6 database")
|
||||
def test_panorama():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["panorama"]])
|
||||
|
||||
assert photos[0].panorama
|
||||
assert not photos[0].portrait
|
||||
assert not photos[0].selfie
|
||||
assert not photos[0].time_lapse
|
||||
assert not photos[0].hdr
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].panorama
|
||||
87
tests/test_specials_sierra_10_12.py
Normal file
87
tests/test_specials_sierra_10_12.py
Normal file
@ -0,0 +1,87 @@
|
||||
# Test cloud photos
|
||||
|
||||
import pytest
|
||||
|
||||
PHOTOS_DB = "./tests/Test-10.12.6.photoslibrary/database/photos.db"
|
||||
|
||||
UUID_DICT = {"no_specials": "Pj99JmYjQkeezdY2OFuSaw"}
|
||||
|
||||
|
||||
def test_portrait():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB)
|
||||
# photos = photosdb.photos(uuid=[UUID_DICT["portrait"]])
|
||||
|
||||
# assert photos[0].portrait
|
||||
# assert not photos[0].hdr
|
||||
# assert not photos[0].selfie
|
||||
# assert not photos[0].time_lapse
|
||||
# assert not photos[0].panorama
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].portrait
|
||||
|
||||
|
||||
def test_hdr():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB)
|
||||
# photos = photosdb.photos(uuid=[UUID_DICT["hdr"]])
|
||||
|
||||
# assert photos[0].hdr
|
||||
# assert not photos[0].portrait
|
||||
# assert not photos[0].selfie
|
||||
# assert not photos[0].time_lapse
|
||||
# assert not photos[0].panorama
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].hdr
|
||||
|
||||
|
||||
def test_selfie():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB)
|
||||
# photos = photosdb.photos(uuid=[UUID_DICT["selfie"]])
|
||||
|
||||
# assert photos[0].selfie
|
||||
# assert not photos[0].portrait
|
||||
# assert not photos[0].hdr
|
||||
# assert not photos[0].time_lapse
|
||||
# assert not photos[0].panorama
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert photos[0].selfie is None
|
||||
|
||||
|
||||
def test_time_lapse():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB)
|
||||
# photos = photosdb.photos(uuid=[UUID_DICT["time_lapse"]], movies=True)
|
||||
|
||||
# assert photos[0].time_lapse
|
||||
# assert not photos[0].portrait
|
||||
# assert not photos[0].hdr
|
||||
# assert not photos[0].selfie
|
||||
# assert not photos[0].panorama
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].time_lapse
|
||||
|
||||
|
||||
def test_panorama():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(PHOTOS_DB)
|
||||
# photos = photosdb.photos(uuid=[UUID_DICT["panorama"]])
|
||||
|
||||
# assert photos[0].panorama
|
||||
# assert not photos[0].portrait
|
||||
# assert not photos[0].selfie
|
||||
# assert not photos[0].time_lapse
|
||||
# assert not photos[0].hdr
|
||||
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["no_specials"]])
|
||||
assert not photos[0].panorama
|
||||
Loading…
x
Reference in New Issue
Block a user