def get_exif_extract_from_jpeg(self, folder: str,
                                   file_name: str) -> bytearray:
        """
        Extract strictly the app1 (exif) section of a jpeg.

        Uses libgphoto2 to extract the exif header.

        Assumes jpeg on camera is straight from the camera, i.e. not
        modified by an exif altering program off the camera.

        :param folder: directory on the camera where the jpeg is stored
        :param file_name: name of the jpeg
        :return: first section of jpeg such that it can be read by
         exiv2 or similar

        """

        camera_file = self._get_file(folder, file_name, None,
                                     gp.GP_FILE_TYPE_EXIF)

        try:
            exif_data = gp.check_result(
                gp.gp_file_get_data_and_size(camera_file))
        except gp.GPhoto2Error as ex:
            logging.error(
                "Error getting exif info for %s from camera %s: %s",
                os.path.join(folder, file_name),
                self.display_name,
                gphoto2_named_error(ex.code),
            )
            raise CameraProblemEx(code=CameraErrorCode.read, gp_exception=ex)
        return bytearray(exif_data)
    def get_exif_extract(self,
                         folder: str,
                         file_name: str,
                         size_in_bytes: int = 200) -> bytearray:
        """ "
        Attempt to read only the exif portion of the file.

        Assumes exif is located at the beginning of the file.
        Use the result like this:
        metadata = GExiv2.Metadata()
        metadata.open_buf(buf)

        :param folder: directory on the camera the file is stored
        :param file_name: the photo's file name
        :param size_in_bytes: how much of the photo to read, starting
         from the front of the file
        """

        buffer = bytearray(size_in_bytes)
        try:
            self.camera.file_read(folder, file_name, gp.GP_FILE_TYPE_NORMAL, 0,
                                  buffer, self.context)
        except gp.GPhoto2Error as e:
            logging.error(
                "Unable to extract portion of file from camera %s: %s",
                self.display_name,
                gphoto2_named_error(e.code),
            )
            raise CameraProblemEx(code=CameraErrorCode.read, gp_exception=e)
        else:
            return buffer
    def _get_file(
        self,
        dir_name: str,
        file_name: str,
        dest_full_filename: Optional[str] = None,
        file_type: int = gp.GP_FILE_TYPE_NORMAL,
    ) -> gp.CameraFile:

        try:
            camera_file = gp.check_result(
                gp.gp_camera_file_get(self.camera, dir_name, file_name,
                                      file_type, self.context))
        except gp.GPhoto2Error as ex:
            logging.error(
                "Error reading %s from camera %s: %s",
                os.path.join(dir_name, file_name),
                self.display_name,
                gphoto2_named_error(ex.code),
            )
            raise CameraProblemEx(code=CameraErrorCode.read, gp_exception=ex)

        if dest_full_filename is not None:
            try:
                gp.check_result(
                    gp.gp_file_save(camera_file, dest_full_filename))
            except gp.GPhoto2Error as ex:
                logging.error(
                    "Error saving %s from camera %s: %s",
                    os.path.join(dir_name, file_name),
                    self.display_name,
                    gphoto2_named_error(ex.code),
                )
                raise CameraProblemEx(code=CameraErrorCode.write,
                                      gp_exception=ex)

        return camera_file
    def save_file_chunk(
        self,
        dir_name: str,
        file_name: str,
        chunk_size_in_bytes: int,
        dest_full_filename: str,
        mtime: int = None,
    ) -> None:
        """
        Save the file from the camera to a local destination.

        :param dir_name: directory on the camera
        :param file_name: the photo or video
        :param chunk_size_in_bytes: how much of the file to read, starting
         from the front of the file
        :param dest_full_filename: full path including filename where
        the file will be saved.
        :param mtime: if specified, set the file modification time to this value
        """

        # get_exif_extract() can raise CameraProblemEx(code=CameraErrorCode.read):
        buffer = self.get_exif_extract(dir_name, file_name,
                                       chunk_size_in_bytes)

        view = memoryview(buffer)
        dest_file = None
        try:
            dest_file = io.open(dest_full_filename, "wb")
            src_bytes = view.tobytes()
            dest_file.write(src_bytes)
            dest_file.close()
            if mtime is not None:
                os.utime(dest_full_filename, times=(mtime, mtime))
        except (OSError, PermissionError) as ex:
            logging.error(
                "Error saving file %s from camera %s: %s",
                os.path.join(dir_name, file_name),
                self.display_name,
                gphoto2_named_error(ex.errno),
            )
            if dest_file is not None:
                dest_file.close()
            raise CameraProblemEx(code=CameraErrorCode.write, py_exception=ex)
    def get_thumbnail(
        self,
        dir_name: str,
        file_name: str,
        ignore_embedded_thumbnail=False,
        cache_full_filename: Optional[str] = None,
    ) -> Optional[bytes]:
        """
        :param dir_name: directory on the camera
        :param file_name: the photo or video
        :param ignore_embedded_thumbnail: if True, do not retrieve the
        embedded thumbnail
        :param cache_full_filename: full path including filename where the
        thumbnail will be saved. If none, will not save it.
        :return: thumbnail in bytes format, which will be full
        resolution if the embedded thumbnail is not selected
        """

        if self.can_fetch_thumbnails and not ignore_embedded_thumbnail:
            get_file_type = gp.GP_FILE_TYPE_PREVIEW
        else:
            get_file_type = gp.GP_FILE_TYPE_NORMAL

        camera_file = self._get_file(dir_name, file_name, cache_full_filename,
                                     get_file_type)

        try:
            thumbnail_data = gp.check_result(
                gp.gp_file_get_data_and_size(camera_file))
        except gp.GPhoto2Error as ex:
            logging.error(
                "Error getting image %s from camera %s: %s",
                os.path.join(dir_name, file_name),
                self.display_name,
                gphoto2_named_error(ex.code),
            )
            raise CameraProblemEx(code=CameraErrorCode.read, gp_exception=ex)

        if thumbnail_data:
            data = memoryview(thumbnail_data)
            return data.tobytes()
    def get_THM_file(self, full_THM_name: str) -> Optional[bytes]:
        """
        Get THM thumbnail from camera

        :param full_THM_name: path and file name of the THM file
        :return: THM in raw bytes
        """
        dir_name, file_name = os.path.split(full_THM_name)
        camera_file = self._get_file(dir_name, file_name)
        try:
            thumbnail_data = gp.check_result(
                gp.gp_file_get_data_and_size(camera_file))
        except gp.GPhoto2Error as ex:
            logging.error(
                "Error getting THM file %s from camera %s: %s",
                os.path.join(dir_name, file_name),
                self.display_name,
                gphoto2_named_error(ex.code),
            )
            raise CameraProblemEx(code=CameraErrorCode.read, gp_exception=ex)

        if thumbnail_data:
            data = memoryview(thumbnail_data)
            return data.tobytes()
    def save_file_by_chunks(
        self,
        dir_name: str,
        file_name: str,
        size: int,
        dest_full_filename: str,
        progress_callback,
        check_for_command,
        return_file_bytes=False,
        chunk_size=1048576,
    ) -> Optional[bytes]:
        """
        :param dir_name: directory on the camera
        :param file_name: the photo or video
        :param size: the size of the file in bytes
        :param dest_full_filename: full path including filename where
         the file will be saved
        :param progress_callback: a function with which to update
         copy progress
        :param check_for_command: a function with which to check to see
         if the execution should pause, resume or stop
        :param return_file_bytes: if True, return a copy of the file's
         bytes, else make that part of the return value None
        :param chunk_size: the size of the chunks to copy. The default
         is 1MB.
        :return: True if the file was successfully saved, else False,
         and the bytes that were copied
        """

        src_bytes = None
        view = memoryview(bytearray(size))
        amount_downloaded = 0
        for offset in range(0, size, chunk_size):
            check_for_command()
            stop = min(offset + chunk_size, size)
            try:
                bytes_read = gp.check_result(
                    self.camera.file_read(
                        dir_name,
                        file_name,
                        gp.GP_FILE_TYPE_NORMAL,
                        offset,
                        view[offset:stop],
                        self.context,
                    ))
                amount_downloaded += bytes_read
                if progress_callback is not None:
                    progress_callback(amount_downloaded, size)
            except gp.GPhoto2Error as ex:
                logging.error(
                    "Error copying file %s from camera %s: %s",
                    os.path.join(dir_name, file_name),
                    self.display_name,
                    gphoto2_named_error(ex.code),
                )
                if progress_callback is not None:
                    progress_callback(size, size)
                raise CameraProblemEx(code=CameraErrorCode.read,
                                      gp_exception=ex)

        dest_file = None
        try:
            dest_file = io.open(dest_full_filename, "wb")
            src_bytes = view.tobytes()
            dest_file.write(src_bytes)
            dest_file.close()
        except (OSError, PermissionError) as ex:
            logging.error(
                "Error saving file %s from camera %s. Error %s: %s",
                os.path.join(dir_name, file_name),
                self.display_name,
                ex.errno,
                ex.strerror,
            )
            if dest_file is not None:
                dest_file.close()
            raise CameraProblemEx(code=CameraErrorCode.write, py_exception=ex)

        if return_file_bytes:
            return src_bytes
    def __init__(
        self,
        model: str,
        port: str,
        is_mtp_device: bool,
        get_folders: bool = True,
        raise_errors: bool = False,
        context: gp.Context = None,
        specific_folders: Optional[List[str]] = None,
    ) -> None:
        """
        Initialize a camera via libgphoto2.

        :param model: camera model, as returned by camera_autodetect() or
         gp_camera_autodetect()
        :param port: camera port, as returned by camera_autodetect()
        :param get_folders: whether to detect the DCIM folders on the
         camera
        :param raise_errors: if True, if necessary free camera,
         and raise error that occurs during initialization
        :param specific_folders: folders such as DCIM,  PRIVATE,
         and MP_ROOT that are searched for if get_folders is True.
         If None, the root level folders are returned -- one for each
         storage slot.
        """

        self.model = model
        self.port = port
        self.is_mtp_device = is_mtp_device
        # class method _concise_model_name discusses why a display name is
        # needed
        self.display_name = model
        self.camera_config = None

        if context is None:
            self.context = gp.Context()
        else:
            self.context = context

        self._select_camera(model, port)

        self.specific_folders = None  # type: Optional[List[str]]
        self.specific_folder_located = False
        self._dual_slots_active = False

        self.storage_info = []

        self.camera_initialized = False
        try:
            self.camera.init(self.context)
            self.camera_initialized = True
        except gp.GPhoto2Error as e:
            if e.code == gp.GP_ERROR_IO_USB_CLAIM:
                error_code = CameraErrorCode.inaccessible
                logging.error("{} is already mounted".format(model))
            elif e.code == gp.GP_ERROR:
                logging.error(
                    "An error occurred initializing the camera using libgphoto2"
                )
                error_code = CameraErrorCode.inaccessible
            else:
                logging.error("Unable to access camera: %s",
                              gphoto2_named_error(e.code))
                error_code = CameraErrorCode.locked
            if raise_errors:
                raise CameraProblemEx(error_code, gp_exception=e)
            return

        concise_model_name = self._concise_model_name()
        if concise_model_name:
            self.display_name = concise_model_name

        if get_folders:
            try:
                self.specific_folders = self._locate_specific_folders(
                    path="/", specific_folders=specific_folders)
                self.specific_folder_located = len(self.specific_folders) > 0

                logging.debug(
                    "Folders located on %s: %s",
                    self.display_name,
                    ", ".join(", ".join(map(str, sl))
                              for sl in self.specific_folders),
                )
            except gp.GPhoto2Error as e:
                logging.error(
                    "Unable to access camera %s: %s. Is it locked?",
                    self.display_name,
                    gphoto2_named_error(e.code),
                )
                if raise_errors:
                    self.free_camera()
                    raise CameraProblemEx(CameraErrorCode.locked,
                                          gp_exception=e)

        self.folders_and_files = []
        self.audio_files = {}
        self.video_thumbnails = []
        abilities = self.camera.get_abilities()
        self.can_fetch_thumbnails = (
            abilities.file_operations & gp.GP_FILE_OPERATION_PREVIEW != 0)