def __init__(self) -> None:
        self.thumbnailSizeNeeded = QSize(ThumbnailSize.width, ThumbnailSize.height)
        self.thumbnail_cache = ThumbnailCacheSql(create_table_if_not_exists=False)
        self.fdo_cache_large = FdoCacheLarge()
        self.fdo_cache_normal = FdoCacheNormal()

        super().__init__('Thumbnail Extractor')
    def do_work(self):

        backup_arguments = pickle.loads(self.content)
        self.path = backup_arguments.path
        self.device_name = backup_arguments.device_name
        self.uri = get_uri(path=self.path)
        self.fdo_cache_normal = FdoCacheNormal()
        self.fdo_cache_large = FdoCacheLarge()

        while True:
            worker_id, directive, content = self.receiver.recv_multipart()
            self.device_id = int(worker_id)

            self.check_for_command(directive, content)

            data = pickle.loads(content)  # type: BackupFileData
            if data.message == BackupStatus.backup_started:
                self.reset_problems()
            elif data.message == BackupStatus.backup_completed:
                self.send_problems()
            else:
                self.amount_downloaded = 0
                self.init_copy_progress()

                self.do_backup(data=data)
    def run(self):
        """
        Set up process and then process thumbnail requests one by one
        """

        # Always set use_thumbnail_cache to True, because this is a daemon
        # process that runs for the lifetime of the program. User can
        # change the program preferences.
        # Whether to actually use it will be determined at the time the
        # thumbnail is sought, using the user's preference at that moment.
        thumbnail_caches = GetThumbnailFromCache(use_thumbnail_cache=True)

        self.frontend = self.context.socket(zmq.PUSH)

        directive, content = self.receiver.recv_multipart()

        self.check_for_command(directive, content)

        data = pickle.loads(content)  # type: ThumbnailDaemonData
        assert data.frontend_port is not None
        self.frontend.connect("tcp://localhost:{}".format(data.frontend_port))

        # handle freedesktop.org cache files directly
        fdo_cache_large = FdoCacheLarge()
        fdo_cache_normal = FdoCacheNormal()

        while True:
            directive, content = self.receiver.recv_multipart()

            self.check_for_command(directive, content)

            data = pickle.loads(content)  # type: ThumbnailDaemonData
            rpd_file = data.rpd_file
            if data.backup_full_file_names is not None:
                # File has been backed up, and an extractor has already generated a FDO
                # thumbnail for it. Copy and modify the existing FDO thumbnail

                # MD5 name of the existing FDO thumbnail
                md5_name = data.fdo_name
                assert md5_name

                for backup_full_file_name in data.backup_full_file_names:

                    # Check to see if existing thumbnail in FDO cache can be
                    # modified and renamed to reflect new URI
                    try:
                        mtime = os.path.getmtime(backup_full_file_name)
                    except OSError:
                        logging.debug("Backup file does not exist: %s",
                                      backup_full_file_name)
                    else:
                        logging.debug(
                            "Copying and modifying existing FDO 128 thumbnail for %s",
                            backup_full_file_name,
                        )
                        fdo_cache_normal.modify_existing_thumbnail_and_save_copy(
                            existing_cache_thumbnail=md5_name,
                            full_file_name=backup_full_file_name,
                            size=rpd_file.size,
                            modification_time=mtime,
                            error_on_missing_thumbnail=True,
                        )

                        logging.debug(
                            "Copying and modifying existing FDO 256 thumbnail for %s",
                            backup_full_file_name,
                        )
                        fdo_cache_large.modify_existing_thumbnail_and_save_copy(
                            existing_cache_thumbnail=md5_name,
                            full_file_name=backup_full_file_name,
                            size=rpd_file.size,
                            modification_time=mtime,
                            error_on_missing_thumbnail=False,
                        )
            else:
                # file has just been downloaded and renamed
                rpd_file.modified_via_daemon_process = True
                try:

                    # Check the download source to see if it's in the caches, not the
                    # file we've just downloaded

                    use_thumbnail_cache = data.use_thumbnail_cache and not (
                        data.write_fdo_thumbnail
                        and rpd_file.should_write_fdo())
                    cache_search = thumbnail_caches.get_from_cache(
                        rpd_file=rpd_file,
                        use_thumbnail_cache=use_thumbnail_cache)
                    (
                        task,
                        thumbnail_bytes,
                        full_file_name_to_work_on,
                        origin,
                    ) = cache_search
                    processing = set()  # type: Set[ExtractionProcessing]

                    if task == ExtractionTask.undetermined:
                        # Thumbnail was not found in any cache: extract it

                        task = preprocess_thumbnail_from_disk(
                            rpd_file=rpd_file, processing=processing)
                        if task != ExtractionTask.bypass:
                            if rpd_file.thm_full_name is not None:
                                full_file_name_to_work_on = (
                                    rpd_file.download_thm_full_name)
                            else:
                                full_file_name_to_work_on = (
                                    rpd_file.download_full_file_name)

                    if task == ExtractionTask.bypass:
                        self.content = pickle.dumps(
                            GenerateThumbnailsResults(
                                rpd_file=rpd_file,
                                thumbnail_bytes=thumbnail_bytes),
                            pickle.HIGHEST_PROTOCOL,
                        )
                        self.send_message_to_sink()

                    elif task != ExtractionTask.undetermined:
                        # Send data to load balancer, which will send to one of its
                        # workers

                        self.content = pickle.dumps(
                            ThumbnailExtractorArgument(
                                rpd_file=rpd_file,
                                task=task,
                                processing=processing,
                                full_file_name_to_work_on=
                                full_file_name_to_work_on,
                                secondary_full_file_name="",
                                exif_buffer=None,
                                thumbnail_bytes=thumbnail_bytes,
                                use_thumbnail_cache=data.use_thumbnail_cache,
                                file_to_work_on_is_temporary=False,
                                write_fdo_thumbnail=data.write_fdo_thumbnail,
                                send_thumb_to_main=True,
                                force_exiftool=data.force_exiftool,
                            ),
                            pickle.HIGHEST_PROTOCOL,
                        )
                        self.frontend.send_multipart([b"data", self.content])
                except SystemExit as e:
                    sys.exit(e)
                except:
                    logging.error("Exception working on file %s",
                                  rpd_file.full_file_name)
                    logging.exception("Traceback:")
class ThumbnailExtractor(LoadBalancerWorker):

    # Exif rotation constants
    rotate_0 = '1'
    rotate_90 = '6'
    rotate_180 = '3'
    rotate_270 = '8'

    maxStandardSize = QSize(
        max(ThumbnailSize.width, ThumbnailSize.height),
        max(ThumbnailSize.width, ThumbnailSize.height)
    )

    def __init__(self) -> None:
        self.thumbnailSizeNeeded = QSize(ThumbnailSize.width, ThumbnailSize.height)
        self.thumbnail_cache = ThumbnailCacheSql(create_table_if_not_exists=False)
        self.fdo_cache_large = FdoCacheLarge()
        self.fdo_cache_normal = FdoCacheNormal()

        super().__init__('Thumbnail Extractor')

    def rotate_thumb(self, thumbnail: QImage, orientation: str) -> QImage:
        """
        If required return a rotated copy the thumbnail
        :param thumbnail: thumbnail to rotate
        :param orientation: EXIF orientation tag
        :return: possibly rotated thumbnail
        """

        if orientation == self.rotate_90:
            thumbnail = thumbnail.transformed(QTransform().rotate(90))
        elif orientation == self.rotate_270:
            thumbnail = thumbnail.transformed(QTransform().rotate(270))
        elif orientation == self.rotate_180:
            thumbnail = thumbnail.transformed(QTransform().rotate(180))
        return thumbnail

    def image_large_enough(self, size: QSize) -> bool:
        """Check if image is equal or bigger than thumbnail size."""

        return (
            size.width() >= self.thumbnailSizeNeeded.width() or
            size.height() >= self.thumbnailSizeNeeded.height()
        )

    def _extract_256_thumb(self, rpd_file: RPDFile,
                          processing: Set[ExtractionProcessing],
                          orientation: Optional[str]) -> PhotoDetails:

        thumbnail = None
        data = rpd_file.metadata.get_preview_256()
        if isinstance(data, bytes):
            thumbnail = QImage.fromData(data)
            if thumbnail.isNull():
                thumbnail = None
            else:
                if thumbnail.width() > 160 or thumbnail.height() > 120:
                    processing.add(ExtractionProcessing.resize)

        return PhotoDetails(thumbnail, orientation)

    def _extract_metadata(self, rpd_file: RPDFile,
                          processing: Set[ExtractionProcessing]) -> PhotoDetails:

        thumbnail = orientation = None
        try:
            orientation = rpd_file.metadata.orientation()
        except Exception:
            pass

        rpd_file.mdatatime = rpd_file.metadata.timestamp(missing=0.0)

        # Not all files have an exif preview, but some do
        # (typically CR2, ARW, PEF, RW2).
        # If they exist, they are (almost!) always 160x120

        # TODO how about thumbnail_cache_status?
        if self.write_fdo_thumbnail and rpd_file.fdo_thumbnail_256 is None:
            photo_details = self._extract_256_thumb(
                rpd_file=rpd_file, processing=processing, orientation=orientation
            )
            if photo_details.thumbnail is not None:
                return photo_details
            # if no valid preview found, fall back to the code below and make do with the best
            # we can get

        preview = rpd_file.metadata.get_small_thumbnail_or_first_indexed_preview()
        if preview:
            thumbnail = QImage.fromData(preview)
            if thumbnail.isNull():
                thumbnail = None
            else:
                # logging.critical("%s, %sx%s", orientation, thumbnail.width(), thumbnail.height())
                if thumbnail.width() < thumbnail.height() and \
                        orientation in (self.rotate_270, self.rotate_90):
                    # The orientation has already been applied to the thumbnail
                    logging.debug("Already rotated: %s", rpd_file.get_current_full_file_name())
                    orientation = self.rotate_0

                if max(thumbnail.width(), thumbnail.height()) > 160:
                    logging.debug("Resizing: %s", rpd_file.get_current_full_file_name())
                    processing.add(ExtractionProcessing.resize)
                elif not rpd_file.is_jpeg():
                    processing.add(ExtractionProcessing.strip_bars_photo)

        return PhotoDetails(thumbnail, orientation)

    def get_disk_photo_thumb(self, rpd_file: Photo,
                             full_file_name: str,
                             processing: Set[ExtractionProcessing],
                             force_exiftool: bool) -> PhotoDetails:
        """
        Get the photo's thumbnail from a file that is on disk.

        Sets rpd_file's mdatatime.

        :param rpd_file: file details
        :param full_file_name: full name of the file from which to get the metadata
        :param processing: processing extraction tasks to complete,
        :param force_exiftool: whether to force the use of ExifTool to load the metadata
        :return: thumbnail and its orientation
        """

        orientation = None
        thumbnail = None
        photo_details = PhotoDetails(thumbnail, orientation)
        if rpd_file.load_metadata(full_file_name=full_file_name, et_process=self.exiftool_process,
                                  force_exiftool=force_exiftool):

            photo_details = self._extract_metadata(rpd_file, processing)
            thumbnail = photo_details.thumbnail

        if thumbnail is not None:
            return photo_details
        elif rpd_file.is_raw() and have_functioning_rawkit:
            try:
                with rawkit.raw.Raw(filename=full_file_name) as raw:
                    raw.options.white_balance = rawkit.options.WhiteBalance(camera=True, auto=False)
                    if rpd_file.cache_full_file_name and not rpd_file.download_full_file_name:
                        temp_file = '{}.tiff'.format(os.path.splitext(full_file_name)[0])
                        cache_dir = os.path.dirname(rpd_file.cache_full_file_name)
                        if os.path.isdir(cache_dir):
                            temp_file = os.path.join(cache_dir, temp_file)
                            temp_dir = None
                        else:
                            temp_dir = tempfile.mkdtemp(prefix="rpd-tmp-")
                            temp_file = os.path.join(temp_dir, temp_file)
                    else:
                        temp_dir = tempfile.mkdtemp(prefix="rpd-tmp-")
                        name = os.path.basename(full_file_name)
                        temp_file = '{}.tiff'.format(os.path.splitext(name)[0])
                        temp_file = os.path.join(temp_dir, temp_file)
                    try:
                        logging.debug("Saving temporary rawkit render to %s", temp_file)
                        raw.save(filename=temp_file)
                    except Exception:
                        logging.exception(
                            "Rendering %s failed. Exception:", rpd_file.full_file_name
                        )
                    else:
                        thumbnail = QImage(temp_file)
                        os.remove(temp_file)
                        if thumbnail.isNull():
                            logging.debug("Qt failed to load rendered %s", rpd_file.full_file_name)
                            thumbnail = None
                        else:
                            logging.debug("Rendered %s using libraw", rpd_file.full_file_name)
                            processing.add(ExtractionProcessing.resize)

                            # libraw already correctly oriented the thumbnail
                            processing.remove(ExtractionProcessing.orient)
                            orientation = '1'
                if temp_dir:
                    os.rmdir(temp_dir)
            except ImportError as e:
                logging.warning(
                    'Cannot use rawkit to render thumbnail for %s', rpd_file.full_file_name
                )
            except Exception as e:
                logging.exception(
                    "Rendering thumbnail for %s not supported. Exception:", rpd_file.full_file_name
                )

        if thumbnail is None and rpd_file.is_loadable():
            thumbnail = QImage(full_file_name)
            processing.add(ExtractionProcessing.resize)
            if not rpd_file.from_camera:
                processing.remove(ExtractionProcessing.orient)
            if thumbnail.isNull():
                thumbnail = None
                logging.warning(
                    "Unable to create a thumbnail out of the file: {}".format(full_file_name)
                )

        return PhotoDetails(thumbnail, orientation)

    def get_from_buffer(self, rpd_file: Photo,
                        raw_bytes: bytearray,
                        processing: Set[ExtractionProcessing]) -> PhotoDetails:
        if not rpd_file.load_metadata(raw_bytes=raw_bytes, et_process=self.exiftool_process):
            # logging.warning("Extractor failed to load metadata from extract of %s", rpd_file.name)
            return PhotoDetails(None, None)
        else:
            return self._extract_metadata(rpd_file, processing)

    def get_photo_orientation(self, rpd_file: Photo,
                              force_exiftool: bool,
                              full_file_name: Optional[str]=None,
                              raw_bytes: Optional[bytearray]=None) -> Optional[str]:

        if rpd_file.metadata is None:
            self.load_photo_metadata(
                rpd_file=rpd_file, full_file_name=full_file_name, raw_bytes=raw_bytes,
                force_exiftool=force_exiftool
            )

        if rpd_file.metadata is not None:
            try:
                return rpd_file.metadata.orientation()
            except Exception:
                pass
        return None

    def assign_mdatatime(self, rpd_file: Union[Photo, Video],
                         force_exiftool: bool,
                         full_file_name: Optional[str]=None,
                         raw_bytes: Optional[bytearray]=None) -> None:
        """
        Load the file's metadata and assign the metadata time to the rpd file
        """

        if rpd_file.file_type == FileType.photo:
            self.assign_photo_mdatatime(
                rpd_file=rpd_file, full_file_name=full_file_name, raw_bytes=raw_bytes,
                force_exiftool=force_exiftool
            )
        else:
            self.assign_video_mdatatime(rpd_file=rpd_file, full_file_name=full_file_name)

    def assign_photo_mdatatime(self, rpd_file: Photo, force_exiftool: bool,
                               full_file_name: Optional[str]=None,
                               raw_bytes: Optional[bytearray]=None) -> None:
        """
        Load the photo's metadata and assign the metadata time to the rpd file
        """

        self.load_photo_metadata(
            rpd_file=rpd_file, full_file_name=full_file_name, raw_bytes=raw_bytes,
            force_exiftool=force_exiftool
        )
        if rpd_file.metadata is not None and rpd_file.date_time() is None:
            rpd_file.mdatatime = 0.0

    def load_photo_metadata(self, rpd_file: Photo, force_exiftool: bool,
                        full_file_name: Optional[str]=None,
                        raw_bytes: Optional[bytearray]=None) -> None:
        """
        Load the photo's metadata into the rpd file
        """

        if raw_bytes is not None:
            if rpd_file.is_jpeg_type():
                rpd_file.load_metadata(app1_segment=raw_bytes, et_process=self.exiftool_process)
            else:
                rpd_file.load_metadata(raw_bytes=raw_bytes, et_process=self.exiftool_process)
        else:
            rpd_file.load_metadata(
                full_file_name=full_file_name, et_process=self.exiftool_process,
                force_exiftool=force_exiftool
            )

    def assign_video_mdatatime(self, rpd_file: Video, full_file_name: str) -> None:
        """
        Load the video's metadata and assign the metadata time to the rpd file
        """

        if rpd_file.metadata is None:
            rpd_file.load_metadata(full_file_name=full_file_name, et_process=self.exiftool_process)
        if rpd_file.date_time() is None:
            rpd_file.mdatatime = 0.0

    def get_video_rotation(self, rpd_file: Video, full_file_name: str) -> Optional[str]:
        """
        Some videos have a rotation tag. If this video does, return it.
        """

        if rpd_file.metadata is None:
            rpd_file.load_metadata(full_file_name=full_file_name, et_process=self.exiftool_process)
        orientation = rpd_file.metadata.rotation(missing=None)
        if orientation == 180:
            return self.rotate_180
        elif orientation == 90:
            return self.rotate_90
        elif orientation == 270:
            return self.rotate_270
        return None

    def check_for_stop(self, directive: bytes, content: bytes):
        if directive == b'cmd':
            assert content == b'STOP'
            return True
        return False

    def extract_thumbnail(self, task: ExtractionTask,
                          rpd_file: Union[Photo, Video],
                          processing: Set[ExtractionProcessing],
                          data: ThumbnailExtractorArgument
                          ) -> Tuple[Optional[QImage], Optional[str]]:
        """
        Extract the thumbnail using one of a variety of methods,
        depending on the file

        :param task: extraction task to perform
        :param rpd_file: rpd_file to work on
        :param processing: processing tasks
        :param data: some other processing arguments passed to this process
        :return: thumbnail and its orientation, if found
        """

        orientation = None

        if task == ExtractionTask.load_from_exif:
            thumbnail_details = self.get_disk_photo_thumb(
                rpd_file, data.full_file_name_to_work_on, processing, data.force_exiftool
            )
            thumbnail = thumbnail_details.thumbnail
            if thumbnail is not None:
                orientation = thumbnail_details.orientation

        elif task in (ExtractionTask.load_file_directly,
                      ExtractionTask.load_file_and_exif_directly,
                      ExtractionTask.load_file_directly_metadata_from_secondary):
            thumbnail = QImage(data.full_file_name_to_work_on)

            if task == ExtractionTask.load_file_and_exif_directly:
                self.assign_photo_mdatatime(
                    rpd_file=rpd_file, full_file_name=data.full_file_name_to_work_on,
                    force_exiftool=data.force_exiftool
                )
            elif task == ExtractionTask.load_file_directly_metadata_from_secondary:
                self.assign_mdatatime(
                    rpd_file=rpd_file, full_file_name=data.secondary_full_file_name,
                    force_exiftool=data.force_exiftool
                )

            if ExtractionProcessing.orient in processing:
                orientation = self.get_photo_orientation(
                    rpd_file=rpd_file, full_file_name=data.full_file_name_to_work_on,
                    force_exiftool=data.force_exiftool
                )

        elif task in (ExtractionTask.load_from_bytes,
                      ExtractionTask.load_from_bytes_metadata_from_temp_extract):
            try:
                assert data.thumbnail_bytes is not None
            except AssertionError:
                logging.error(
                    "Thumbnail bytes not extracted for %s (value is None)",
                    rpd_file.get_current_full_file_name()
                )
            thumbnail = QImage.fromData(data.thumbnail_bytes)
            if thumbnail.width() > self.thumbnailSizeNeeded.width() or thumbnail.height()\
                    > self.thumbnailSizeNeeded.height():
                processing.add(ExtractionProcessing.resize)
                processing.remove(ExtractionProcessing.strip_bars_photo)
            if data.exif_buffer and ExtractionProcessing.orient in processing:
                orientation = self.get_photo_orientation(
                    rpd_file=rpd_file, raw_bytes=data.exif_buffer,
                    force_exiftool=data.force_exiftool
                )
            if task == ExtractionTask.load_from_bytes_metadata_from_temp_extract:
                self.assign_mdatatime(
                    rpd_file=rpd_file, full_file_name=data.secondary_full_file_name,
                    force_exiftool=data.force_exiftool
                )
                orientation = rpd_file.metadata.orientation()
                os.remove(data.secondary_full_file_name)
                rpd_file.temp_cache_full_file_chunk = ''

        elif task == ExtractionTask.load_from_exif_buffer:
            thumbnail_details = self.get_from_buffer(rpd_file, data.exif_buffer, processing)
            thumbnail = thumbnail_details.thumbnail
            if thumbnail is not None:
                orientation = thumbnail_details.orientation

        elif task in (ExtractionTask.load_heif_directly,
                      ExtractionTask.load_heif_and_exif_directly):
            assert have_heif_module
            thumbnail = load_heif(
                data.full_file_name_to_work_on, process_name=self.identity.decode()
            )

            if task == ExtractionTask.load_heif_and_exif_directly:
                self.assign_photo_mdatatime(
                    rpd_file=rpd_file, full_file_name=data.full_file_name_to_work_on,
                    force_exiftool=data.force_exiftool
                )
            if ExtractionProcessing.orient in processing:
                orientation = self.get_photo_orientation(
                    rpd_file=rpd_file, full_file_name=data.full_file_name_to_work_on,
                    force_exiftool=data.force_exiftool
                )

        else:
            assert task in (
                ExtractionTask.extract_from_file, ExtractionTask.extract_from_file_and_load_metadata
            )
            if rpd_file.file_type == FileType.photo:
                self.assign_photo_mdatatime(
                    rpd_file=rpd_file, full_file_name=data.full_file_name_to_work_on,
                    force_exiftool=data.force_exiftool
                )
                thumbnail_bytes = rpd_file.metadata.get_small_thumbnail_or_first_indexed_preview()
                if thumbnail_bytes:
                    thumbnail = QImage.fromData(thumbnail_bytes)
                    orientation = rpd_file.metadata.orientation()
            else:
                assert rpd_file.file_type == FileType.video

                if ExtractionTask.extract_from_file_and_load_metadata:
                    self.assign_video_mdatatime(
                        rpd_file=rpd_file, full_file_name=data.full_file_name_to_work_on
                    )
                if not have_gst:
                    thumbnail = None
                else:
                    png = get_video_frame(data.full_file_name_to_work_on, 1.0)
                    if not png:
                        thumbnail = None
                        logging.warning(
                            "Could not extract video thumbnail from %s",
                            data.rpd_file.get_display_full_name()
                        )
                    else:
                        thumbnail = QImage.fromData(png)
                        if thumbnail.isNull():
                            thumbnail = None
                        else:
                            processing.add(ExtractionProcessing.add_film_strip)
                            orientation = self.get_video_rotation(
                                rpd_file, data.full_file_name_to_work_on
                            )
                            if orientation is not None:
                                processing.add(ExtractionProcessing.orient)
                            processing.add(ExtractionProcessing.resize)

        return thumbnail, orientation

    def process_files(self):
        """
        Loop continuously processing photo and video thumbnails
        """

        logging.debug("{} worker started".format(self.requester.identity.decode()))

        while True:
            directive, content = self.requester.recv_multipart()
            if self.check_for_stop(directive, content):
                break

            data = pickle.loads(content)  # type: ThumbnailExtractorArgument

            thumbnail_256 = png_data = None
            task = data.task
            processing = data.processing
            rpd_file = data.rpd_file

            logging.debug(
                "Working on task %s for %s", task.name, rpd_file.download_name or rpd_file.name
            )

            self.write_fdo_thumbnail = data.write_fdo_thumbnail

            try:
                if rpd_file.fdo_thumbnail_256 is not None and data.write_fdo_thumbnail:
                    if rpd_file.thumbnail_status != ThumbnailCacheStatus.fdo_256_ready:
                        logging.error(
                            "Unexpected thumbnail cache status for %s: %s",
                            rpd_file.full_file_name, rpd_file.thumbnail_status.name
                        )
                    thumbnail = thumbnail_256 = QImage.fromData(rpd_file.fdo_thumbnail_256)
                    orientation_unknown = False
                else:
                    thumbnail, orientation = self.extract_thumbnail(
                        task, rpd_file, processing, data
                    )
                    if data.file_to_work_on_is_temporary:
                        os.remove(data.full_file_name_to_work_on)
                        rpd_file.temp_cache_full_file_chunk = ''

                    if thumbnail is not None:
                        if ExtractionProcessing.strip_bars_photo in processing:
                            thumbnail = crop_160x120_thumbnail(thumbnail)
                        elif ExtractionProcessing.strip_bars_video in processing:
                            thumbnail = crop_160x120_thumbnail(thumbnail, 15)
                        if ExtractionProcessing.resize in processing:
                            # Resize the thumbnail before rotating
                            if ((orientation == '1' or orientation is None) and
                                    thumbnail.height() > thumbnail.width()):

                                # Special case: pictures from some cellphones have already
                                # been rotated
                                thumbnail = thumbnail.scaled(
                                    self.maxStandardSize,
                                    Qt.KeepAspectRatio,
                                    Qt.SmoothTransformation
                                )
                            else:
                                if rpd_file.should_write_fdo() and \
                                        image_large_enough_fdo(thumbnail.size()) \
                                        and max(thumbnail.height(), thumbnail.width()) > 256:
                                    thumbnail_256 = thumbnail.scaled(
                                        QSize(256, 256),
                                        Qt.KeepAspectRatio,
                                        Qt.SmoothTransformation
                                    )
                                    thumbnail = thumbnail_256
                                if data.send_thumb_to_main:
                                    # thumbnail = self.rotate_thumb(thumbnail, orientation)
                                    # orientation = None
                                    thumbnail = thumbnail.scaled(
                                        self.thumbnailSizeNeeded,
                                        Qt.KeepAspectRatio,
                                        Qt.SmoothTransformation
                                    )
                                else:
                                    thumbnail = None

                            if not thumbnail is None and thumbnail.isNull():
                                thumbnail = None

                    if orientation is not None:
                        if thumbnail is not None:
                            thumbnail = self.rotate_thumb(thumbnail, orientation)
                        if thumbnail_256 is not None:
                            thumbnail_256 = self.rotate_thumb(thumbnail_256, orientation)

                    if ExtractionProcessing.add_film_strip in processing:
                        if thumbnail is not None:
                            thumbnail = add_filmstrip(thumbnail)
                        if thumbnail_256 is not None:
                            thumbnail = add_filmstrip(thumbnail_256)

                    if thumbnail is not None:
                        buffer = qimage_to_png_buffer(thumbnail)
                        png_data = buffer.data()

                    orientation_unknown = (
                        ExtractionProcessing.orient in processing and orientation is None
                    )

                    if data.send_thumb_to_main and data.use_thumbnail_cache and \
                            rpd_file.thumbnail_cache_status == ThumbnailCacheDiskStatus.not_found:
                        self.thumbnail_cache.save_thumbnail(
                            full_file_name=rpd_file.full_file_name,
                            size=rpd_file.size,
                            mtime=rpd_file.modification_time,
                            mdatatime=rpd_file.mdatatime,
                            generation_failed=thumbnail is None,
                            orientation_unknown=orientation_unknown,
                            thumbnail=thumbnail,
                            camera_model=rpd_file.camera_model
                        )

                if (thumbnail is not None or thumbnail_256 is not None) and \
                        rpd_file.should_write_fdo():
                    if self.write_fdo_thumbnail:
                        # The modification time of the file may have changed when the file was saved
                        # Ideally it shouldn't, but it does sometimes, e.g. on NTFS!
                        # So need to get the modification time from the saved file.
                        mtime = os.path.getmtime(rpd_file.download_full_file_name)

                        if thumbnail_256 is not None:
                            rpd_file.fdo_thumbnail_256_name = self.fdo_cache_large.save_thumbnail(
                                full_file_name=rpd_file.download_full_file_name,
                                size=rpd_file.size,
                                modification_time=mtime,
                                generation_failed=False,
                                thumbnail=thumbnail_256,
                                free_desktop_org=False
                            )
                            thumbnail_128 = thumbnail_256.scaled(
                                    QSize(128, 128),
                                    Qt.KeepAspectRatio,
                                    Qt.SmoothTransformation
                            )
                        else:
                            thumbnail_128 = thumbnail.scaled(
                                QSize(128, 128),
                                Qt.KeepAspectRatio,
                                Qt.SmoothTransformation
                            )
                        rpd_file.fdo_thumbnail_128_name = self.fdo_cache_normal.save_thumbnail(
                            full_file_name=rpd_file.download_full_file_name,
                            size=rpd_file.size,
                            modification_time=mtime,
                            generation_failed=False,
                            thumbnail=thumbnail_128,
                            free_desktop_org=False
                        )
                    elif thumbnail_256 is not None and rpd_file.fdo_thumbnail_256 is None:
                        rpd_file.fdo_thumbnail_256 = qimage_to_png_buffer(thumbnail).data()

                if thumbnail is not None:
                    if orientation_unknown:
                        rpd_file.thumbnail_status = ThumbnailCacheStatus.orientation_unknown
                    elif rpd_file.fdo_thumbnail_256 is not None:
                        rpd_file.thumbnail_status = ThumbnailCacheStatus.fdo_256_ready
                    else:
                        rpd_file.thumbnail_status = ThumbnailCacheStatus.ready

            except SystemExit as e:
                self.exiftool_process.terminate()
                sys.exit(e)
            except:
                logging.error("Exception working on file %s", rpd_file.full_file_name)
                logging.error("Task: %s", task)
                logging.error("Processing tasks: %s", processing)
                logging.exception("Traceback:")

            # Purge metadata, as it cannot be pickled
            if not data.send_thumb_to_main:
                png_data = None
            rpd_file.metadata = None
            self.sender.send_multipart(
                [
                    b'0', b'data',
                    pickle.dumps(
                        GenerateThumbnailsResults(rpd_file=rpd_file, thumbnail_bytes=png_data),
                        pickle.HIGHEST_PROTOCOL
                    )
                ]
            )
            self.requester.send_multipart([b'', b'', b'OK'])

    def do_work(self):
        if False:
            # exiv2 pumps out a LOT to stderr - use cautiously!
            context = show_errors()
            self.error_stream = sys.stderr
        else:
            # Redirect stderr, hiding error output from exiv2
            context = stdchannel_redirected(sys.stderr, os.devnull)
            self.error_stream = sys.stdout
        with context:
            # In some situations, using a context manager for exiftool can
            # result in exiftool processes not being terminated. So let's
            # handle starting and terminating it manually.
            self.exiftool_process = exiftool.ExifTool()
            self.exiftool_process.start()
            self.process_files()
            self.exit()

    def cleanup_pre_stop(self) -> None:
        logging.debug(
            "Terminating thumbnail extractor ExifTool process for %s", self.identity.decode()
        )
        self.exiftool_process.terminate()