예제 #1
0
        def testAutomaticObserving(self):
            with objc.autorelease_pool():
                observer = PyObjCTestObserver.alloc().init()
                o = PyObjCTestObserved2.alloc().init()
                with objc.autorelease_pool():
                    self.assertEqual(o.foo, None)
                    self.assertEqual(o.bar, None)

                    o.foo = "foo"
                    self.assertEqual(o.foo, "foo")

                    o.bar = "bar"
                    self.assertEqual(o.bar, "bar")

                    o.addObserver_forKeyPath_options_context_(
                        observer,
                        "bar",
                        (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld),
                        0,
                    )
                    o.addObserver_forKeyPath_options_context_(
                        observer,
                        "foo",
                        (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld),
                        0,
                    )
                    try:
                        o.bar = "world"
                        self.assertEqual(o.bar, "world")

                        o.foo = "xxx"
                        self.assertEqual(o.foo, "xxx")
                    finally:
                        o.removeObserver_forKeyPath_(observer, "bar")
                        o.removeObserver_forKeyPath_(observer, "foo")
                    self.assertEqual(len(observer.observed), 2)

                    self.assertEqual(
                        observer.observed[0],
                        ("bar", o, {"kind": 1, "new": "world", "old": "bar"}, 0),
                    )
                    self.assertEqual(
                        observer.observed[1],
                        ("foo", o, {"kind": 1, "new": "xxx", "old": "foo"}, 0),
                    )

                    del observer

                before = DEALLOCS
                del o

            self.assertEqual(DEALLOCS, before + 1, "Leaking an observed object")
예제 #2
0
        def testAutomaticObserving(self):
            with objc.autorelease_pool():
                observer = PyObjCTestObserver.alloc().init()
                o = PyObjCTestObserved2.alloc().init()
                with objc.autorelease_pool():
                    self.assertEqual(o.foo, None)
                    self.assertEqual(o.bar, None)

                    o.foo = 'foo'
                    self.assertEqual(o.foo, 'foo')

                    o.bar = 'bar'
                    self.assertEqual(o.bar, 'bar')

                    o.addObserver_forKeyPath_options_context_(
                        observer, 'bar', (NSKeyValueObservingOptionNew
                                          | NSKeyValueObservingOptionOld), 0)
                    o.addObserver_forKeyPath_options_context_(
                        observer, 'foo', (NSKeyValueObservingOptionNew
                                          | NSKeyValueObservingOptionOld), 0)
                    try:
                        o.bar = "world"
                        self.assertEqual(o.bar, "world")

                        o.foo = "xxx"
                        self.assertEqual(o.foo, "xxx")
                    finally:
                        o.removeObserver_forKeyPath_(observer, "bar")
                        o.removeObserver_forKeyPath_(observer, "foo")
                    self.assertEqual(len(observer.observed), 2)

                    self.assertEqual(observer.observed[0], ('bar', o, {
                        'kind': 1,
                        'new': 'world',
                        'old': 'bar'
                    }, 0))
                    self.assertEqual(observer.observed[1], ('foo', o, {
                        'kind': 1,
                        'new': 'xxx',
                        'old': 'foo'
                    }, 0))

                    del observer

                before = DEALLOCS
                del o

            self.assertEqual(DEALLOCS, before + 1,
                             "Leaking an observed object")
예제 #3
0
    def fetch_uuid_list(self, uuid_list):
        """ fetch PHAssets with uuids in uuid_list

        Args:
            uuid_list: list of str (UUID of image assets to fetch)

        Returns:
            list of PhotoAsset objects

        Raises:
            PhotoKitFetchFailed if fetch failed
        """

        # pylint: disable=no-member
        with objc.autorelease_pool():
            fetch_options = Photos.PHFetchOptions.alloc().init()
            fetch_result = Photos.PHAsset.fetchAssetsWithLocalIdentifiers_options_(
                uuid_list, fetch_options
            )
            if fetch_result and fetch_result.count() >= 1:
                return [
                    self._asset_factory(fetch_result.objectAtIndex_(idx))
                    for idx in range(fetch_result.count())
                ]
            else:
                raise PhotoKitFetchFailed(
                    f"Fetch did not return result for uuid_list {uuid_list}"
                )
예제 #4
0
 def del_value_cache_free(self, name):
     with objc.autorelease_pool():
         try:
             self._user_defaults.removeObjectForKey_(self._copy_str(name))
             self._user_defaults.synchronize()
         except:
             self.LOG.exception(
                 "Unable to delete '%s' from the user defaults:", name)
    def get_value(self, name):
        with objc.autorelease_pool():
            try:
                v = self._user_defaults.stringForKey_(self._copy_str(name))
                return str(v) if v is not None else None
            except:
                pass

        return None
    def get_array_value(self, name):
        with objc.autorelease_pool():
            try:
                v = self._user_defaults.arrayForKey_(self._copy_str(name))
                return [str(i) for i in v] if v is not None else None
            except:
                pass

        return None
    def get_dict_value(self, name):
        with objc.autorelease_pool():
            try:
                v = self._user_defaults.dictionaryForKey_(self._copy_str(name))
                return {str(k): str(i) for k, i in v.items()} if v is not None else None
            except:
                pass

        return None
 def set_dict_value(self, name, value):
     with objc.autorelease_pool():
         try:
             if value is not None:
                 self._user_defaults.setObject_forKey_(self._copy_dict(value), self._copy_str(name))
             else:
                 self.del_value(self._copy_str(name))
         except:
             self.LOG.exception("Unable to set dict '%s' in the user defaults:", name)
예제 #9
0
    def get_value_cache_free(self, name):
        with objc.autorelease_pool():
            try:
                v = self._user_defaults.stringForKey_(self._copy_str(name))
                return str(v) if v is not None else None
            except:
                pass

        return None
예제 #10
0
    def get_array_value_cache_free(self, name, allow_cache=False):
        with objc.autorelease_pool():
            try:
                v = self._user_defaults.arrayForKey_(self._copy_str(name))
                return [str(i) for i in v] if v is not None else None
            except:
                pass

        return None
예제 #11
0
파일: utils.py 프로젝트: lgxz/osxphotos
def get_preferred_uti_extension(uti):
    """ get preferred extension for a UTI type
        uti: UTI str, e.g. 'public.jpeg'
        returns: preferred extension as str """

    # reference: https://developer.apple.com/documentation/coreservices/1442744-uttypecopypreferredtagwithclass?language=objc
    with objc.autorelease_pool():
        return CoreServices.UTTypeCopyPreferredTagWithClass(
            uti, CoreServices.kUTTagClassFilenameExtension)
예제 #12
0
    def get_dict_value_cache_free(self, name, allow_cache=False):
        with objc.autorelease_pool():
            try:
                v = self._user_defaults.dictionaryForKey_(self._copy_str(name))
                return {str(k): str(i)
                        for k, i in v.items()} if v is not None else None
            except:
                pass

        return None
예제 #13
0
    def export(
        self, dest, filename=None, version=PHOTOS_VERSION_CURRENT, overwrite=False
    ):
        """ Export video to path

        Args:
            dest: str, path to destination directory
            filename: str, optional name of exported file; if not provided, defaults to asset's original filename
            version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
            overwrite: bool, if True, overwrites destination file if it already exists; default is False

        Returns:
            List of path to exported image(s)

        Raises:
            ValueError if dest is not a valid directory
        """

        with objc.autorelease_pool():
            if self.slow_mo and version == PHOTOS_VERSION_CURRENT:
                return [
                    self._export_slow_mo(
                        dest, filename=filename, version=version, overwrite=overwrite
                    )
                ]

            filename = (
                pathlib.Path(filename)
                if filename
                else pathlib.Path(self.original_filename)
            )

            dest = pathlib.Path(dest)
            if not dest.is_dir():
                raise ValueError("dest must be a valid directory: {dest}")

            output_file = None
            videodata = self._request_video_data(version=version)
            if videodata.asset is None:
                raise PhotoKitExportError("Could not get video for asset")

            url = videodata.asset.URL()
            path = pathlib.Path(NSURL_to_path(url))
            del videodata
            if not path.is_file():
                raise FileNotFoundError("Could not get path to video file")
            ext = path.suffix
            output_file = dest / f"{filename.stem}{ext}"

            if not overwrite:
                output_file = pathlib.Path(increment_filename(output_file))

            FileUtil.copy(path, output_file)

            return [str(output_file)]
예제 #14
0
 def set_array_value_cache_free(self, name, value):
     with objc.autorelease_pool():
         try:
             if value is not None:
                 self._user_defaults.setObject_forKey_(
                     self._copy_array(value), self._copy_str(name))
                 self._user_defaults.synchronize()
             else:
                 self.del_value_cache_free(self._copy_str(name))
         except:
             self.LOG.exception(
                 "Unable to set array '%s' in the user defaults:", name)
예제 #15
0
        def testAutomaticObserving(self):
            with objc.autorelease_pool():
                observer = PyObjCTestObserver.alloc().init()
                o = PyObjCTestObserved2.alloc().init()
                with objc.autorelease_pool():
                    self.assertEqual(o.foo, None)
                    self.assertEqual(o.bar, None)

                    o.foo = "foo"
                    self.assertEqual(o.foo, "foo")

                    o.bar = "bar"
                    self.assertEqual(o.bar, "bar")

                    o.addObserver_forKeyPath_options_context_(
                        observer, "bar", (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld), 0
                    )
                    o.addObserver_forKeyPath_options_context_(
                        observer, "foo", (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld), 0
                    )
                    try:
                        o.bar = "world"
                        self.assertEqual(o.bar, "world")

                        o.foo = "xxx"
                        self.assertEqual(o.foo, "xxx")
                    finally:
                        o.removeObserver_forKeyPath_(observer, "bar")
                        o.removeObserver_forKeyPath_(observer, "foo")
                    self.assertEqual(len(observer.observed), 2)

                    self.assertEqual(observer.observed[0], ("bar", o, {"kind": 1, "new": "world", "old": "bar"}, 0))
                    self.assertEqual(observer.observed[1], ("foo", o, {"kind": 1, "new": "xxx", "old": "foo"}, 0))

                    del observer

                before = DEALLOCS
                del o

            self.assertEqual(DEALLOCS, before + 1, "Leaking an observed object")
예제 #16
0
    def requestLivePhotoResources(self, version=PHOTOS_VERSION_CURRENT):
        """ return the photos and video components of a live video as [PHAssetResource] """

        with objc.autorelease_pool():
            options = Photos.PHLivePhotoRequestOptions.alloc().init()
            options.setNetworkAccessAllowed_(True)
            options.setVersion_(version)
            options.setDeliveryMode_(
                Photos.PHVideoRequestOptionsDeliveryModeHighQualityFormat
            )
            delegate = PhotoKitNotificationDelegate.alloc().init()

            self.nc.addObserver_selector_name_object_(
                delegate, "liveNotification:", None, None
            )

            self.live_photo = None

            def handler(result, info):
                """ result handler for requestLivePhotoForAsset:targetSize:contentMode:options:resultHandler: """
                if not info["PHImageResultIsDegradedKey"]:
                    self.live_photo = result
                    self.info = info
                    self.nc.postNotificationName_object_(
                        PHOTOKIT_NOTIFICATION_FINISHED_REQUEST, self
                    )

            try:
                self.manager.requestLivePhotoForAsset_targetSize_contentMode_options_resultHandler_(
                    self.asset,
                    Photos.PHImageManagerMaximumSize,
                    Photos.PHImageContentModeDefault,
                    options,
                    handler,
                )
                AppHelper.runConsoleEventLoop(installInterrupt=True)
            except KeyboardInterrupt:
                AppHelper.stopEventLoop()
            finally:
                pass

            asset_resources = Photos.PHAssetResource.assetResourcesForLivePhoto_(
                self.live_photo
            )

            # not sure why this is needed -- some weird ref count thing maybe
            # if I don't do this, memory leaks
            data = copy.copy(asset_resources)
            del asset_resources
            return data
예제 #17
0
    def _request_video_data(self, version=PHOTOS_VERSION_ORIGINAL):
        """ Request video data for self._phasset 
            
        Args:
            version: which version to request
                     PHOTOS_VERSION_ORIGINAL (default), request original highest fidelity version 
                     PHOTOS_VERSION_CURRENT, request current version with all edits
                     PHOTOS_VERSION_UNADJUSTED, request highest quality unadjusted version
        
        Raises:
            ValueError if passed invalid value for version
        """
        with objc.autorelease_pool():
            if version not in [
                PHOTOS_VERSION_CURRENT,
                PHOTOS_VERSION_ORIGINAL,
                PHOTOS_VERSION_UNADJUSTED,
            ]:
                raise ValueError("Invalid value for version")

            options_request = Photos.PHVideoRequestOptions.alloc().init()
            options_request.setNetworkAccessAllowed_(True)
            options_request.setVersion_(version)
            options_request.setDeliveryMode_(
                Photos.PHVideoRequestOptionsDeliveryModeHighQualityFormat
            )
            requestdata = AVAssetData()
            event = threading.Event()

            def handler(asset, audiomix, info):
                """ result handler for requestAVAssetForVideo:asset options:options resultHandler """
                nonlocal requestdata

                requestdata.asset = asset
                requestdata.audiomix = audiomix
                requestdata.info = info

                event.set()

            self._manager.requestAVAssetForVideo_options_resultHandler_(
                self.phasset, options_request, handler
            )
            event.wait()

            # not sure why this is needed -- some weird ref count thing maybe
            # if I don't do this, memory leaks
            data = copy.copy(requestdata)
            del requestdata
            return data
예제 #18
0
    def _export_slow_mo(
        self, dest, filename=None, version=PHOTOS_VERSION_CURRENT, overwrite=False
    ):
        """ Export slow-motion video to path

        Args:
            dest: str, path to destination directory
            filename: str, optional name of exported file; if not provided, defaults to asset's original filename
            version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
            overwrite: bool, if True, overwrites destination file if it already exists; default is False

        Returns:
            Path to exported image

        Raises:
            ValueError if dest is not a valid directory
        """
        with objc.autorelease_pool():
            if not self.slow_mo:
                raise PhotoKitMediaTypeError("Not a slow-mo video")

            videodata = self._request_video_data(version=version)
            if (
                not isinstance(videodata.asset, AVFoundation.AVComposition)
                or len(videodata.asset.tracks()) != 2
            ):
                raise PhotoKitMediaTypeError("Does not appear to be slow-mo video")

            filename = (
                pathlib.Path(filename)
                if filename
                else pathlib.Path(self.original_filename)
            )

            dest = pathlib.Path(dest)
            if not dest.is_dir():
                raise ValueError("dest must be a valid directory: {dest}")

            output_file = dest / f"{filename.stem}.mov"

            if not overwrite:
                output_file = pathlib.Path(increment_filename(output_file))

            exporter = SlowMoVideoExporter.alloc().initWithAVAsset_path_(
                videodata.asset, output_file
            )
            video = exporter.exportSlowMoVideo()
            # exporter.dealloc()
            return video
예제 #19
0
    def _runloop_thread(self):
        try:
            with objc.autorelease_pool():
                queue_ptr = objc.objc_object(c_void_p=self._dispatch_queue)

                self._manager = CoreBluetooth.CBCentralManager.alloc()
                self._manager.initWithDelegate_queue_options_(
                    self, queue_ptr, None)

                #self._peripheral_manager = CoreBluetooth.CBPeripheralManager.alloc()
                #self._peripheral_manager.initWithDelegate_queue_options_(self, queue_ptr, None)
                self._runloop_started_lock.set()
                AppHelper.runConsoleEventLoop(installInterrupt=True)
        except Exception as e:
            log.exception(e)
        log.info("Exiting runloop")
예제 #20
0
def detect_text(img_path: str, orientation: Optional[int] = None) -> List:
    """process image at img_path with VNRecognizeTextRequest and return list of results

    Args:
        img_path: path to the image file
        orientation: optional EXIF orientation (if known, passing orientation may improve quality of results)
    """
    if not vision:
        logging.warning(
            f"detect_text not implemented for this version of macOS")
        return []

    with objc.autorelease_pool():
        input_url = NSURL.fileURLWithPath_(img_path)

        with pipes() as (out, err):
            # capture stdout and stderr from system calls
            # otherwise, Quartz.CIImage.imageWithContentsOfURL_
            # prints to stderr something like:
            # 2020-09-20 20:55:25.538 python[73042:5650492] Creating client/daemon connection: B8FE995E-3F27-47F4-9FA8-559C615FD774
            # 2020-09-20 20:55:25.652 python[73042:5650492] Got the query meta data reply for: com.apple.MobileAsset.RawCamera.Camera, response: 0
            input_image = Quartz.CIImage.imageWithContentsOfURL_(input_url)

        vision_options = NSDictionary.dictionaryWithDictionary_({})
        if orientation is not None:
            if not 1 <= orientation <= 8:
                raise ValueError("orientation must be between 1 and 8")
            vision_handler = Vision.VNImageRequestHandler.alloc(
            ).initWithCIImage_orientation_options_(input_image, orientation,
                                                   vision_options)
        else:
            vision_handler = (
                Vision.VNImageRequestHandler.alloc().initWithCIImage_options_(
                    input_image, vision_options))
        results = []
        handler = make_request_handler(results)
        vision_request = (Vision.VNRecognizeTextRequest.alloc().
                          initWithCompletionHandler_(handler))
        error = vision_handler.performRequests_error_([vision_request], None)
        vision_request.dealloc()
        vision_handler.dealloc()

        for result in results:
            result[0] = str(result[0])

        return results
예제 #21
0
    def _request_resource_data(self, resource):
        """ Request asset resource data (either photo or video component)
            
        Args:
            resource: PHAssetResource to request
        
        Raises:
        """

        with objc.autorelease_pool():
            resource_manager = Photos.PHAssetResourceManager.defaultManager()
            options = Photos.PHAssetResourceRequestOptions.alloc().init()
            options.setNetworkAccessAllowed_(True)

            requestdata = PHAssetResourceData()
            event = threading.Event()

            def handler(data):
                """ result handler for requestImageDataAndOrientationForAsset_options_resultHandler_ 
                    all returned by the request is set as properties of nonlocal data (Fetchdata object) """

                nonlocal requestdata

                requestdata.data += data

            def completion_handler(error):
                if error:
                    raise PhotoKitExportError(
                        "Error requesting data for asset resource"
                    )
                event.set()

            resource_manager.requestDataForAssetResource_options_dataReceivedHandler_completionHandler_(
                resource, options, handler, completion_handler
            )

            event.wait()

            # not sure why this is needed -- some weird ref count thing maybe
            # if I don't do this, memory leaks
            data = copy.copy(requestdata.data)
            del requestdata
            return data
예제 #22
0
    def exportSlowMoVideo(self):
        """export slow-mo video with AVAssetExportSession

        Returns:
            path to exported file
        """

        with objc.autorelease_pool():
            exporter = (
                AVFoundation.AVAssetExportSession.alloc().initWithAsset_presetName_(
                    self.avasset, AVFoundation.AVAssetExportPresetHighestQuality
                )
            )
            exporter.setOutputURL_(self.url)
            exporter.setOutputFileType_(AVFoundation.AVFileTypeQuickTimeMovie)
            exporter.setShouldOptimizeForNetworkUse_(True)

            self.done = False

            def handler():
                """result handler for exportAsynchronouslyWithCompletionHandler"""
                self.done = True

            exporter.exportAsynchronouslyWithCompletionHandler_(handler)
            # wait for export to complete
            # would be more elegant to use a dispatch queue, notification, or thread event to wait
            # but I can't figure out how to make that work and this does work
            while True:
                status = exporter.status()
                if status == AVFoundation.AVAssetExportSessionStatusCompleted:
                    break
                elif status not in (
                    AVFoundation.AVAssetExportSessionStatusWaiting,
                    AVFoundation.AVAssetExportSessionStatusExporting,
                ):
                    raise PhotoKitExportError(
                        f"Error encountered during exportAsynchronouslyWithCompletionHandler: status = {status}"
                    )
                time.sleep(MIN_SLEEP)

            exported_path = NSURL_to_path(exporter.outputURL())
            # exporter.dealloc()
            return exported_path
예제 #23
0
파일: uti.py 프로젝트: oPromessa/osxphotos
def get_preferred_uti_extension(uti):
    """get preferred extension for a UTI type
    uti: UTI str, e.g. 'public.jpeg'
    returns: preferred extension as str or None if cannot be determined"""

    if (OS_VER, OS_MAJOR) <= (10, 16):
        # reference: https://developer.apple.com/documentation/coreservices/1442744-uttypecopypreferredtagwithclass?language=objc
        # deprecated in Catalina+, likely won't work at all on macOS 12
        with objc.autorelease_pool():
            extension = CoreServices.UTTypeCopyPreferredTagWithClass(
                uti, CoreServices.kUTTagClassFilenameExtension
            )
            if extension:
                return extension

            # on MacOS 10.12, HEIC files are not supported and UTTypeCopyPreferredTagWithClass will return None for HEIC
            if uti == "public.heic":
                return "HEIC"

            return None

    return _get_ext_from_uti_dict(uti)
예제 #24
0
파일: uti.py 프로젝트: oPromessa/osxphotos
def get_uti_for_extension(extension):
    """get UTI for a given file extension"""

    if not extension:
        return None

    # accepts extension with or without leading 0
    if extension[0] == ".":
        extension = extension[1:]

    if (OS_VER, OS_MAJOR) <= (10, 16):
        # https://developer.apple.com/documentation/coreservices/1448939-uttypecreatepreferredidentifierf
        with objc.autorelease_pool():
            uti = CoreServices.UTTypeCreatePreferredIdentifierForTag(
                CoreServices.kUTTagClassFilenameExtension, extension, None
            )
            if uti:
                return uti

            # on MacOS 10.12, HEIC files are not supported and UTTypeCopyPreferredTagWithClass will return None for HEIC
            if extension.lower() == "heic":
                return "public.heic"

            return None

    uti = _get_uti_from_ext_dict(extension)
    if uti:
        return uti

    uti = _get_uti_from_mdls(extension)
    if uti:
        # cache the UTI
        EXT_UTI_DICT[extension.lower()] = uti
        UTI_EXT_DICT[uti] = extension.lower()
        return uti

    return None
예제 #25
0
    def export(
        self,
        dest,
        filename=None,
        version=PHOTOS_VERSION_CURRENT,
        overwrite=False,
        photo=True,
        video=True,
        **kwargs,
    ):
        """Export image to path

        Args:
            dest: str, path to destination directory
            filename: str, optional name of exported file; if not provided, defaults to asset's original filename
            version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
            overwrite: bool, if True, overwrites destination file if it already exists; default is False
            photo: bool, if True, export photo component of live photo
            video: bool, if True, export live video component of live photo
            **kwargs: used only to avoid issues with each asset type having slightly different export arguments

        Returns:
            list of [path to exported image and/or video]

        Raises:
            ValueError if dest is not a valid directory
            PhotoKitExportError if error during export
        """

        with objc.autorelease_pool():
            with pipes() as (out, err):
                filename = (
                    pathlib.Path(filename)
                    if filename
                    else pathlib.Path(self.original_filename)
                )

                dest = pathlib.Path(dest)
                if not dest.is_dir():
                    raise ValueError("dest must be a valid directory: {dest}")

                request = LivePhotoRequest.alloc().initWithManager_Asset_(
                    self._manager, self.phasset
                )
                resources = request.requestLivePhotoResources(version=version)

                video_resource = None
                photo_resource = None
                for resource in resources:
                    if resource.type() == Photos.PHAssetResourceTypePairedVideo:
                        video_resource = resource
                    elif resource.type() == Photos.PHAssetMediaTypeImage:
                        photo_resource = resource

                if not video_resource or not photo_resource:
                    raise PhotoKitExportError(
                        "Did not find photo/video resources for live photo"
                    )

                photo_ext = get_preferred_uti_extension(
                    photo_resource.uniformTypeIdentifier()
                )
                photo_output_file = dest / f"{filename.stem}.{photo_ext}"
                video_ext = get_preferred_uti_extension(
                    video_resource.uniformTypeIdentifier()
                )
                video_output_file = dest / f"{filename.stem}.{video_ext}"

                if not overwrite:
                    photo_output_file = pathlib.Path(
                        increment_filename(photo_output_file)
                    )
                    video_output_file = pathlib.Path(
                        increment_filename(video_output_file)
                    )

                exported = []
                if photo:
                    data = self._request_resource_data(photo_resource)
                    # image_data = self.request_image_data(version=version)
                    with open(photo_output_file, "wb") as fd:
                        fd.write(data)
                    exported.append(str(photo_output_file))
                    del data
                if video:
                    data = self._request_resource_data(video_resource)
                    with open(video_output_file, "wb") as fd:
                        fd.write(data)
                    exported.append(str(video_output_file))
                    del data

                request.dealloc()
                return exported
예제 #26
0
    def export(
        self,
        dest,
        filename=None,
        version=PHOTOS_VERSION_CURRENT,
        overwrite=False,
        raw=False,
        **kwargs,
    ):
        """Export image to path

        Args:
            dest: str, path to destination directory
            filename: str, optional name of exported file; if not provided, defaults to asset's original filename
            version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
            overwrite: bool, if True, overwrites destination file if it already exists; default is False
            raw: bool, if True, export RAW component of RAW+JPEG pair, default is False
            **kwargs: used only to avoid issues with each asset type having slightly different export arguments

        Returns:
            List of path to exported image(s)

        Raises:
            ValueError if dest is not a valid directory
        """

        with objc.autorelease_pool():
            with pipes() as (out, err):
                filename = (
                    pathlib.Path(filename)
                    if filename
                    else pathlib.Path(self.original_filename)
                )

                dest = pathlib.Path(dest)
                if not dest.is_dir():
                    raise ValueError("dest must be a valid directory: {dest}")

                output_file = None
                if self.isphoto:
                    # will hold exported image data and needs to be cleaned up at end
                    imagedata = None
                    if raw:
                        # export the raw component
                        resources = self._resources()
                        for resource in resources:
                            if (
                                resource.type()
                                == Photos.PHAssetResourceTypeAlternatePhoto
                            ):
                                data = self._request_resource_data(resource)
                                suffix = pathlib.Path(self.raw_filename).suffix
                                ext = suffix[1:] if suffix else ""
                                break
                        else:
                            raise PhotoKitExportError(
                                "Could not get image data for RAW photo"
                            )
                    else:
                        # TODO: if user has selected use RAW as original, this returns the RAW
                        # can get the jpeg with resource.type() == Photos.PHAssetResourceTypePhoto
                        imagedata = self._request_image_data(version=version)
                        if not imagedata.image_data:
                            raise PhotoKitExportError("Could not get image data")
                        ext = get_preferred_uti_extension(imagedata.uti)
                        data = imagedata.image_data

                    output_file = dest / f"{filename.stem}.{ext}"

                    if not overwrite:
                        output_file = pathlib.Path(increment_filename(output_file))

                    with open(output_file, "wb") as fd:
                        fd.write(data)

                    if imagedata:
                        del imagedata
                elif self.ismovie:
                    videodata = self._request_video_data(version=version)
                    if videodata.asset is None:
                        raise PhotoKitExportError("Could not get video for asset")

                    url = videodata.asset.URL()
                    path = pathlib.Path(NSURL_to_path(url))
                    if not path.is_file():
                        raise FileNotFoundError("Could not get path to video file")
                    ext = path.suffix
                    output_file = dest / f"{filename.stem}{ext}"

                    if not overwrite:
                        output_file = pathlib.Path(increment_filename(output_file))

                    FileUtil.copy(path, output_file)

                return [str(output_file)]
예제 #27
0
def theme():
    with objc.autorelease_pool():
        user_defaults = Foundation.NSUserDefaults.standardUserDefaults()
        system_theme = user_defaults.stringForKey_("AppleInterfaceStyle")
        return "dark" if system_theme == "Dark" else "light"
예제 #28
0
    def _request_image_data(self, version=PHOTOS_VERSION_ORIGINAL):
        """ Request image data and metadata for self._phasset 
            
        Args:
            version: which version to request
                     PHOTOS_VERSION_ORIGINAL (default), request original highest fidelity version 
                     PHOTOS_VERSION_CURRENT, request current version with all edits
                     PHOTOS_VERSION_UNADJUSTED, request highest quality unadjusted version
        
        Returns:
            ImageData instance

        Raises:
            ValueError if passed invalid value for version
        """

        # reference: https://developer.apple.com/documentation/photokit/phimagemanager/3237282-requestimagedataandorientationfo?language=objc

        with objc.autorelease_pool():
            if version not in [
                PHOTOS_VERSION_CURRENT,
                PHOTOS_VERSION_ORIGINAL,
                PHOTOS_VERSION_UNADJUSTED,
            ]:
                raise ValueError("Invalid value for version")

            # pylint: disable=no-member
            options_request = Photos.PHImageRequestOptions.alloc().init()
            options_request.setNetworkAccessAllowed_(True)
            options_request.setSynchronous_(True)
            options_request.setVersion_(version)
            options_request.setDeliveryMode_(
                Photos.PHImageRequestOptionsDeliveryModeHighQualityFormat
            )
            requestdata = ImageData()
            event = threading.Event()

            def handler(imageData, dataUTI, orientation, info):
                """ result handler for requestImageDataAndOrientationForAsset_options_resultHandler_ 
                    all returned by the request is set as properties of nonlocal data (Fetchdata object) """

                nonlocal requestdata

                options = {}
                # pylint: disable=no-member
                options[Quartz.kCGImageSourceShouldCache] = Foundation.kCFBooleanFalse
                imgSrc = Quartz.CGImageSourceCreateWithData(imageData, options)
                requestdata.metadata = Quartz.CGImageSourceCopyPropertiesAtIndex(
                    imgSrc, 0, options
                )
                requestdata.uti = dataUTI
                requestdata.orientation = orientation
                requestdata.info = info
                requestdata.image_data = imageData

                event.set()

            self._manager.requestImageDataAndOrientationForAsset_options_resultHandler_(
                self.phasset, options_request, handler
            )
            event.wait()
            # options_request.dealloc()

            # not sure why this is needed -- some weird ref count thing maybe
            # if I don't do this, memory leaks
            data = copy.copy(requestdata)
            del requestdata
            return data
예제 #29
0
    def export(
        self,
        dest,
        filename=None,
        version=PHOTOS_VERSION_CURRENT,
        overwrite=False,
        photo=True,
        video=True,
    ):
        """ Export image to path

        Args:
            dest: str, path to destination directory
            filename: str, optional name of exported file; if not provided, defaults to asset's original filename
            version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
            overwrite: bool, if True, overwrites destination file if it already exists; default is False
            photo: bool, if True, export photo component of live photo
            video: bool, if True, export live video component of live photo

        Returns:
            list of [path to exported image and/or video]

        Raises:
            ValueError if dest is not a valid directory
            PhotoKitExportError if error during export
        """

        with objc.autorelease_pool():
            filename = (
                pathlib.Path(filename)
                if filename
                else pathlib.Path(self.original_filename)
            )

            dest = pathlib.Path(dest)
            if not dest.is_dir():
                raise ValueError("dest must be a valid directory: {dest}")

            request = LivePhotoRequest.alloc().initWithManager_Asset_(
                self._manager, self.phasset
            )
            resources = request.requestLivePhotoResources(version=version)

            video_resource = None
            photo_resource = None
            for resource in resources:
                if resource.type() == Photos.PHAssetResourceTypePairedVideo:
                    video_resource = resource
                elif resource.type() == Photos.PHAssetMediaTypeImage:
                    photo_resource = resource

            if not video_resource or not photo_resource:
                raise PhotoKitExportError(
                    "Did not find photo/video resources for live photo"
                )

            photo_ext = get_preferred_uti_extension(
                photo_resource.uniformTypeIdentifier()
            )
            photo_output_file = dest / f"{filename.stem}.{photo_ext}"
            video_ext = get_preferred_uti_extension(
                video_resource.uniformTypeIdentifier()
            )
            video_output_file = dest / f"{filename.stem}.{video_ext}"

            if not overwrite:
                photo_output_file = pathlib.Path(increment_filename(photo_output_file))
                video_output_file = pathlib.Path(increment_filename(video_output_file))

            # def handler(error):
            #     if error:
            #         raise PhotoKitExportError(f"writeDataForAssetResource error: {error}")

            # resource_manager = Photos.PHAssetResourceManager.defaultManager()
            # options = Photos.PHAssetResourceRequestOptions.alloc().init()
            # options.setNetworkAccessAllowed_(True)
            # exported = []
            # Note: Tried writeDataForAssetResource_toFile_options_completionHandler_ which works
            # but sets quarantine flag and for reasons I can't determine (maybe quarantine flag)
            # causes pathlib.Path().is_file() to fail in tests

            # if photo:
            #     photo_output_url = path_to_NSURL(photo_output_file)
            #     resource_manager.writeDataForAssetResource_toFile_options_completionHandler_(
            #         photo_resource, photo_output_url, options, handler
            #     )
            #     exported.append(str(photo_output_file))

            # if video:
            #     video_output_url = path_to_NSURL(video_output_file)
            #     resource_manager.writeDataForAssetResource_toFile_options_completionHandler_(
            #         video_resource, video_output_url, options, handler
            #     )
            #     exported.append(str(video_output_file))

            # def completion_handler(error):
            #     if error:
            #         raise PhotoKitExportError(f"writeDataForAssetResource error: {error}")

            # would be nice to be able to usewriteDataForAssetResource_toFile_options_completionHandler_
            # but it sets quarantine flags that cause issues so instead, request the data and write the files directly

            exported = []
            if photo:
                data = self._request_resource_data(photo_resource)
                # image_data = self.request_image_data(version=version)
                with open(photo_output_file, "wb") as fd:
                    fd.write(data)
                exported.append(str(photo_output_file))
                del data
            if video:
                data = self._request_resource_data(video_resource)
                with open(video_output_file, "wb") as fd:
                    fd.write(data)
                exported.append(str(video_output_file))
                del data

            request.dealloc()
            return exported
 def del_value(self, name):
     with objc.autorelease_pool():
         try:
             self._user_defaults.removeObjectForKey_(self._copy_str(name))
         except:
             self.LOG.exception("Unable to delete '%s' from the user defaults:", name)
예제 #31
0
    def export(
        self, dest, filename=None, version=PHOTOS_VERSION_CURRENT, overwrite=False
    ):
        """ Export image to path

        Args:
            dest: str, path to destination directory
            filename: str, optional name of exported file; if not provided, defaults to asset's original filename
            version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
            overwrite: bool, if True, overwrites destination file if it already exists; default is False

        Returns:
            List of path to exported image(s)

        Raises:
            ValueError if dest is not a valid directory
        """

        # if self.live:
        #     raise NotImplementedError("Live photos not implemented yet")

        with objc.autorelease_pool():
            filename = (
                pathlib.Path(filename)
                if filename
                else pathlib.Path(self.original_filename)
            )

            dest = pathlib.Path(dest)
            if not dest.is_dir():
                raise ValueError("dest must be a valid directory: {dest}")

            output_file = None
            if self.isphoto:
                imagedata = self._request_image_data(version=version)
                if not imagedata.image_data:
                    raise PhotoKitExportError("Could not get image data")

                ext = get_preferred_uti_extension(imagedata.uti)

                output_file = dest / f"{filename.stem}.{ext}"

                if not overwrite:
                    output_file = pathlib.Path(increment_filename(output_file))

                with open(output_file, "wb") as fd:
                    fd.write(imagedata.image_data)
                    del imagedata
            elif self.ismovie:
                videodata = self._request_video_data(version=version)
                if videodata.asset is None:
                    raise PhotoKitExportError("Could not get video for asset")

                url = videodata.asset.URL()
                path = pathlib.Path(NSURL_to_path(url))
                if not path.is_file():
                    raise FileNotFoundError("Could not get path to video file")
                ext = path.suffix
                output_file = dest / f"{filename.stem}{ext}"

                if not overwrite:
                    output_file = pathlib.Path(increment_filename(output_file))

                FileUtil.copy(path, output_file)

            return [str(output_file)]
예제 #32
0
    def write_jpeg(self, input_path, output_path, compression_quality=1.0):
        """convert image to jpeg and write image to output_path

        Args:
            input_path: path to input image (e.g. '/path/to/import/file.CR2') as str or pathlib.Path
            output_path: path to exported jpeg (e.g. '/path/to/export/file.jpeg') as str or pathlib.Path
            compression_quality: JPEG compression quality, float in range 0.0 to 1.0; default is 1.0 (best quality)

        Return:
            True if conversion successful, else False

        Raises:
            ValueError if compression quality not in range 0.0 to 1.0
            FileNotFoundError if input_path doesn't exist
            ImageConversionError if error during conversion
        """

        # Set up a dedicated objc autorelease pool for this function call.
        # This is to ensure that all the NSObjects are cleaned up after each
        # call to prevent memory leaks.
        # https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html
        # https://pyobjc.readthedocs.io/en/latest/api/module-objc.html#memory-management
        with objc.autorelease_pool():
            # accept input_path or output_path as pathlib.Path
            if not isinstance(input_path, str):
                input_path = str(input_path)

            if not isinstance(output_path, str):
                output_path = str(output_path)

            if not pathlib.Path(input_path).is_file():
                raise FileNotFoundError(f"could not find {input_path}")

            if not (0.0 <= compression_quality <= 1.0):
                raise ValueError(
                    "illegal value for compression_quality: {compression_quality}"
                )

            input_url = NSURL.fileURLWithPath_(input_path)
            output_url = NSURL.fileURLWithPath_(output_path)

            with pipes() as (out, err):
                # capture stdout and stderr from system calls
                # otherwise, Quartz.CIImage.imageWithContentsOfURL_
                # prints to stderr something like:
                # 2020-09-20 20:55:25.538 python[73042:5650492] Creating client/daemon connection: B8FE995E-3F27-47F4-9FA8-559C615FD774
                # 2020-09-20 20:55:25.652 python[73042:5650492] Got the query meta data reply for: com.apple.MobileAsset.RawCamera.Camera, response: 0
                input_image = Quartz.CIImage.imageWithContentsOfURL_(input_url)

            if input_image is None:
                raise ImageConversionError(
                    f"Could not create CIImage for {input_path}")

            output_colorspace = (input_image.colorSpace()
                                 or Quartz.CGColorSpaceCreateWithName(
                                     Quartz.CoreGraphics.kCGColorSpaceSRGB))

            output_options = NSDictionary.dictionaryWithDictionary_({
                "kCGImageDestinationLossyCompressionQuality":
                compression_quality
            })
            (
                _,
                error,
            ) = self.context.writeJPEGRepresentationOfImage_toURL_colorSpace_options_error_(
                input_image, output_url, output_colorspace, output_options,
                None)
            if not error:
                return True
            else:
                raise ImageConversionError(
                    f"Error converting file {input_path} to jpeg at {output_path}: {error}"
                )