def __init__(self, path, image_list, thumb_size=80, *arg, **kw): super(Image, self).__init__(*arg, **kw) self.path = path self.image_list = image_list self.name, ext = os.path.splitext(os.path.basename(self.path)) self.selected = False self.thumb_size = thumb_size # read metadata self.metadata = Metadata(self.path) self.metadata.unsaved.connect(self.show_status) self.file_times = (os.path.getatime(self.path), os.path.getmtime(self.path)) # set file type self.file_type = self.metadata.get_mime_type() if not self.file_type: self.file_type = mimetypes.guess_type(self.path)[0] if not self.file_type: self.file_type = imghdr.what(self.path) if self.file_type: self.file_type = 'image/' + self.file_type # anything not recognised is assumed to be 'raw' if not self.file_type: self.file_type = 'image/raw' # sub widgets layout = QtWidgets.QGridLayout() layout.setSpacing(0) layout.setContentsMargins(3, 3, 3, 3) self.setLayout(layout) self.setToolTip(self.path) # label to display image self.image = QtWidgets.QLabel() self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) layout.addWidget(self.image, 0, 0, 1, 2) # label to display file name self.label = QtWidgets.QLabel() self.label.setAlignment(Qt.AlignRight) scale_font(self.label, 80) layout.addWidget(self.label, 1, 1) # label to display status self.status = QtWidgets.QLabel() self.status.setAlignment(Qt.AlignLeft) set_symbol_font(self.status) scale_font(self.status, 80) layout.addWidget(self.status, 1, 0) self.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Plain) self.setObjectName("thumbnail") self.set_selected(False) self.show_status(False) self._set_thumb_size(self.thumb_size)
def get_file_data(self): if not os.path.isdir(self.root): return None file_list = [] for root, dirs, files in os.walk(self.root): for name in files: base, ext = os.path.splitext(name) if ext.lower() in self.image_types: file_list.append(os.path.join(root, name)) file_data = {} for path in file_list: metadata = Metadata(path) timestamp = metadata.date_taken if not timestamp: timestamp = metadata.date_digitised if not timestamp: timestamp = metadata.date_modified if not timestamp: # use file date as last resort timestamp = datetime.fromtimestamp(os.path.getmtime(path)) else: timestamp = timestamp.datetime name = os.path.basename(path) file_data[name] = { 'camera' : metadata.camera_model, 'path' : path, 'name' : name, 'timestamp' : timestamp, } return file_data
def reload_metadata(self): self.metadata = Metadata(self.path, notify=self.show_status, utf_safe=self.app.options.utf_safe) self.show_status(False) self.load_thumbnail() self.image_list.emit_selection()
def open_file(self, path): path = Metadata.abspath(path) if not os.path.isfile(path): return if self.get_image(path): # already opened this path return image = Image(path, self, thumb_size=self.thumb_size) self.images.append(image) self.show_thumbnail(image)
def get_file_data(self): if not os.path.isdir(self.root): return None file_list = [] for root, dirs, files in os.walk(self.root): # ignore special directories such as .thumbs dirs[:] = [x for x in dirs if x[0] != '.'] for name in files: base, ext = os.path.splitext(name) if ext.lower() in self.image_types: file_list.append(os.path.join(root, name)) file_data = {} for path in file_list: metadata = Metadata(path) timestamp = metadata.date_taken if not timestamp: timestamp = metadata.date_digitised if not timestamp: timestamp = metadata.date_modified if not timestamp: # use file date as last resort timestamp = datetime.fromtimestamp(os.path.getmtime(path)) else: timestamp = timestamp['datetime'] sc_path = metadata.find_sidecar() name = os.path.basename(path) camera = metadata.camera_model if camera: camera = camera['model'] file_data[name] = { 'camera': camera, 'path': path, 'sc_path': sc_path, 'name': name, 'timestamp': timestamp, } return file_data
def copy_files(self, info_list, move): for info in info_list: dest_path = info['dest_path'] dest_dir = os.path.dirname(dest_path) if not os.path.isdir(dest_dir): os.makedirs(dest_dir) sc_file = Metadata.find_side_car(info['path']) if move: shutil.move(info['path'], dest_path) if sc_file: shutil.move(sc_file, dest_path + '.xmp') else: shutil.copy2(info['path'], dest_path) if sc_file: shutil.copy2(sc_file, dest_path + '.xmp') yield info
def get_file_info(self, path): metadata = Metadata(path, None) timestamp = metadata.date_taken if not timestamp: timestamp = metadata.date_digitised if not timestamp: timestamp = metadata.date_modified if not timestamp: # use file date as last resort timestamp = datetime.fromtimestamp(os.path.getmtime(path)) else: timestamp = timestamp.datetime folder, name = os.path.split(path) return { 'camera': six.text_type(metadata.camera_model), 'path': path, 'name': name, 'timestamp': timestamp, }
def __init__(self, path, image_list, thumb_size=80, *arg, **kw): super(Image, self).__init__(*arg, **kw) self.app = QtWidgets.QApplication.instance() self.path = path self.image_list = image_list self.name, ext = os.path.splitext(os.path.basename(self.path)) self.selected = False self.thumb_size = thumb_size # read metadata self.metadata = Metadata(self.path, notify=self.show_status, utf_safe=self.app.options.utf_safe) self.file_times = (os.path.getatime(self.path), os.path.getmtime(self.path)) # set file type self.file_type = self.metadata.mime_type # sub widgets layout = QtWidgets.QGridLayout() layout.setSpacing(0) layout.setContentsMargins(3, 3, 3, 3) self.setLayout(layout) self.setToolTip(self.path) # label to display image self.image = QtWidgets.QLabel() self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) layout.addWidget(self.image, 0, 0, 1, 2) # label to display file name self.label = QtWidgets.QLabel() self.label.setAlignment(Qt.AlignRight) scale_font(self.label, 80) layout.addWidget(self.label, 1, 1) # label to display status self.status = QtWidgets.QLabel() self.status.setAlignment(Qt.AlignLeft) set_symbol_font(self.status) scale_font(self.status, 80) layout.addWidget(self.status, 1, 0) self.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Plain) self.setObjectName("thumbnail") self.set_selected(False) self.show_status(False) self._set_thumb_size(self.thumb_size)
def __init__(self, path, image_list, thumb_size=80, parent=None): QtGui.QFrame.__init__(self, parent) self.path = path self.image_list = image_list self.name = os.path.splitext(os.path.basename(self.path))[0] self.selected = False self.pixmap = None self.thumb_size = thumb_size self.metadata = Metadata(self.path) self.metadata.new_status.connect(self.show_status) layout = QtGui.QGridLayout() layout.setSpacing(0) layout.setContentsMargins(3, 3, 3, 3) self.setLayout(layout) # label to display image self.image = QtGui.QLabel() self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) layout.addWidget(self.image, 0, 0, 1, 2) # label to display file name self.label = QtGui.QLabel(self.name) self.label.setAlignment(Qt.AlignRight) self.label.setStyleSheet("QLabel { font-size: 12px }") layout.addWidget(self.label, 1, 1) # label to display status self.status = QtGui.QLabel() self.status.setAlignment(Qt.AlignLeft) self.status.setSizePolicy( QtGui.QSizePolicy.Fixed, self.status.sizePolicy().verticalPolicy()) self.status.setStyleSheet("QLabel { font-size: 12px }") self.status.setFont(QtGui.QFont("Dejavu Sans")) layout.addWidget(self.status, 1, 0) self.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Plain) self.setObjectName("thumbnail") self.set_selected(False) self.show_status(False) self._set_thumb_size(self.thumb_size)
def copy_metadata(self, image, path): # copy metadata, forcing IPTC creation md = Metadata(path, None) md.copy(image.metadata) md.save(True, 'none', True)
def copy_metadata(self, image, path): # copy metadata md = Metadata.clone(path, image.metadata) # save metedata, forcing IPTC creation md.dirty = True md.save(if_mode=True, sc_mode='none', force_iptc=True)
class Image(QtWidgets.QFrame): def __init__(self, path, image_list, thumb_size=80, *arg, **kw): super(Image, self).__init__(*arg, **kw) self.path = path self.image_list = image_list self.name, ext = os.path.splitext(os.path.basename(self.path)) self.selected = False self.thumb_size = thumb_size # read image with open(self.path, 'rb') as pf: image_data = pf.read() # read metadata self.metadata = Metadata( self.path, image_data, new_status=self.show_status) # set file type ext = ext.lower() self.file_type = imghdr.what(self.path) or 'raw' if self.file_type == 'tiff' and ext not in ('.tif', '.tiff'): self.file_type = 'raw' # make 'master' thumbnail self.pixmap = QtGui.QPixmap() self.pixmap.loadFromData(image_data) unrotate = self.file_type == 'raw' if self.pixmap.isNull(): # image read failed so attempt to use exif thumbnail thumb = self.metadata.get_exif_thumbnail() if thumb: self.pixmap.loadFromData(bytearray(thumb)) unrotate = False if not self.pixmap.isNull(): if max(self.pixmap.width(), self.pixmap.height()) > 450: # store a scaled down version of image to save memory self.pixmap = self.pixmap.scaled( 300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation) if unrotate: # loading preview which is already re-oriented orientation = self.metadata.orientation if orientation and orientation.value > 1: # need to unrotate and or unreflect image transform = QtGui.QTransform() if orientation.value in (3, 4): transform = transform.rotate(180.0) elif orientation.value in (5, 6): transform = transform.rotate(-90.0) elif orientation.value in (7, 8): transform = transform.rotate(90.0) if orientation.value in (2, 4, 5, 7): transform = transform.scale(-1.0, 1.0) self.pixmap = self.pixmap.transformed(transform) # sub widgets layout = QtWidgets.QGridLayout() layout.setSpacing(0) layout.setContentsMargins(3, 3, 3, 3) self.setLayout(layout) self.setToolTip(self.path) # label to display image self.image = QtWidgets.QLabel() self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) layout.addWidget(self.image, 0, 0, 1, 2) # label to display file name self.label = QtWidgets.QLabel() self.label.setAlignment(Qt.AlignRight) self.label.setStyleSheet("QLabel { font-size: 12px }") layout.addWidget(self.label, 1, 1) # label to display status self.status = QtWidgets.QLabel() self.status.setAlignment(Qt.AlignLeft) self.status.setStyleSheet("QLabel { font-size: 12px }") self.status.setFont(QtGui.QFont("Dejavu Sans")) if not self.status.fontInfo().exactMatch(): # probably on Windows, try a different font self.status.setFont(QtGui.QFont("Segoe UI Symbol")) layout.addWidget(self.status, 1, 0) self.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Plain) self.setObjectName("thumbnail") self.set_selected(False) self.show_status(False) self._set_thumb_size(self.thumb_size) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.drag_start_pos = event.pos() if event.modifiers() == Qt.ControlModifier: self.image_list.select_image(self, multiple_selection=True) elif event.modifiers() == Qt.ShiftModifier: self.image_list.select_image(self, extend_selection=True) elif not self.get_selected(): # don't clear selection in case we're about to drag self.image_list.select_image(self) def mouseReleaseEvent(self, event): if event.modifiers() not in (Qt.ControlModifier, Qt.ShiftModifier): # clear any multiple selection self.image_list.select_image(self) def mouseMoveEvent(self, event): if not self.image_list.drag_icon: return if ((event.pos() - self.drag_start_pos).manhattanLength() < QtWidgets.QApplication.startDragDistance()): return paths = [] for image in self.image_list.get_selected_images(): paths.append(image.path) if not paths: return drag = QtGui.QDrag(self) # construct icon count = min(len(paths), 8) src_icon = self.image_list.drag_icon src_w = src_icon.width() src_h = src_icon.height() margin = (count - 1) * 4 if count == 1: icon = src_icon else: icon = QtGui.QPixmap(src_w + margin, src_h + margin) icon.fill(Qt.transparent) with QtGui.QPainter(icon) as paint: for i in range(count): paint.drawPixmap( QtCore.QPoint(margin - (i * 4), i * 4), src_icon) drag.setPixmap(icon) if src_h == src_w: # round marker used in Bing maps version 8 drag.setHotSpot(QtCore.QPoint(src_w // 2, (src_h // 2) + margin)) else: drag.setHotSpot(QtCore.QPoint(src_w // 2, src_h + margin)) mimeData = QtCore.QMimeData() mimeData.setData(DRAG_MIMETYPE, repr(paths).encode('utf-8')) drag.setMimeData(mimeData) dropAction = drag.exec_(Qt.CopyAction) def mouseDoubleClickEvent(self, event): if sys.platform.startswith('linux'): subprocess.call(['xdg-open', self.path]) elif sys.platform.startswith('darwin'): subprocess.call(['open', self.path]) elif sys.platform.startswith('win'): subprocess.call(['start', self.path], shell=True) def show_status(self, changed): status = '' # set 'geotagged' status if self.metadata.latlong: status += six.unichr(0x2690) # set 'unsaved' status if changed: status += six.unichr(0x26A1) self.status.setText(status) self._elide_name() if changed: self.image_list.new_metadata.emit(True) def _elide_name(self): self.status.adjustSize() elided_name = self.label.fontMetrics().elidedText( self.name, Qt.ElideLeft, self.thumb_size - self.status.width()) self.label.setText(elided_name) def _set_thumb_size(self, thumb_size): self.thumb_size = thumb_size self.image.setFixedSize(self.thumb_size, self.thumb_size) self._elide_name() def set_thumb_size(self, thumb_size): self._set_thumb_size(thumb_size) self.load_thumbnail() def load_thumbnail(self): if self.pixmap.isNull(): self.image.setText(self.tr('Can not\nload\nimage')) else: pixmap = self.pixmap orientation = self.metadata.orientation if orientation and orientation.value > 1: # need to rotate and or reflect image transform = QtGui.QTransform() if orientation.value in (3, 4): transform = transform.rotate(180.0) elif orientation.value in (5, 6): transform = transform.rotate(90.0) elif orientation.value in (7, 8): transform = transform.rotate(-90.0) if orientation.value in (2, 4, 5, 7): transform = transform.scale(-1.0, 1.0) pixmap = pixmap.transformed(transform) self.image.setPixmap(pixmap.scaled( self.thumb_size, self.thumb_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)) def set_selected(self, value): self.selected = value if self.selected: self.setStyleSheet("#thumbnail {border: 2px solid red}") else: self.setStyleSheet("#thumbnail {border: 2px solid grey}") def get_selected(self): return self.selected
class Image(QtGui.QFrame): def __init__(self, path, image_list, thumb_size=80, parent=None): QtGui.QFrame.__init__(self, parent) self.path = path self.image_list = image_list self.name = os.path.splitext(os.path.basename(self.path))[0] self.selected = False self.pixmap = None self.thumb_size = thumb_size self.metadata = Metadata(self.path) self.metadata.new_status.connect(self.show_status) layout = QtGui.QGridLayout() layout.setSpacing(0) layout.setContentsMargins(3, 3, 3, 3) self.setLayout(layout) # label to display image self.image = QtGui.QLabel() self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) layout.addWidget(self.image, 0, 0, 1, 2) # label to display file name self.label = QtGui.QLabel(self.name) self.label.setAlignment(Qt.AlignRight) self.label.setStyleSheet("QLabel { font-size: 12px }") layout.addWidget(self.label, 1, 1) # label to display status self.status = QtGui.QLabel() self.status.setAlignment(Qt.AlignLeft) self.status.setSizePolicy( QtGui.QSizePolicy.Fixed, self.status.sizePolicy().verticalPolicy()) self.status.setStyleSheet("QLabel { font-size: 12px }") self.status.setFont(QtGui.QFont("Dejavu Sans")) layout.addWidget(self.status, 1, 0) self.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Plain) self.setObjectName("thumbnail") self.set_selected(False) self.show_status(False) self._set_thumb_size(self.thumb_size) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.drag_start_pos = event.pos() self.image_list.thumb_mouse_press(self.path, event) def mouseMoveEvent(self, event): if ((event.pos() - self.drag_start_pos).manhattanLength() < QtGui.QApplication.startDragDistance()): return drag = QtGui.QDrag(self) mimeData = QtCore.QMimeData() paths = list() for image in self.image_list.get_selected_images(): paths.append(image.path) mimeData.setText(str(paths)) drag.setMimeData(mimeData) dropAction = drag.exec_(Qt.LinkAction) def mouseDoubleClickEvent(self, event): if sys.platform.startswith('linux'): subprocess.call(['xdg-open', self.path]) elif sys.platform.startswith('darwin'): subprocess.call(['open', self.path]) elif sys.platform.startswith('win'): subprocess.call(['start', self.path], shell=True) @QtCore.pyqtSlot(bool) def show_status(self, changed): status = u'' # set 'geotagged' status if self.metadata.has_GPS(): status += unichr(0x2690) # set 'unsaved' status if changed: status += unichr(0x26A1) self.status.setText(status) if changed: self.image_list.new_metadata.emit(True) def _set_thumb_size(self, thumb_size): self.thumb_size = thumb_size self.image.setFixedSize(self.thumb_size, self.thumb_size) margins = self.layout().contentsMargins() self.setFixedWidth(self.thumb_size + margins.left() + margins.right() + (self.frameWidth() * 2)) def set_thumb_size(self, thumb_size): self._set_thumb_size(thumb_size) self.load_thumbnail() def load_thumbnail(self): result = False if not self.pixmap: result = True self.pixmap = QtGui.QPixmap(self.path) if max(self.pixmap.width(), self.pixmap.height()) > 400: # store a scaled down version of image to save memory self.pixmap = self.pixmap.scaled( 400, 400, Qt.KeepAspectRatio, Qt.SmoothTransformation) orientation = self.metadata.get_item('orientation') if orientation is not None and orientation != 1: # need to rotate and or reflect image transform = QtGui.QTransform() if orientation in (3, 4): transform = transform.rotate(180.0) elif orientation in (5, 6): transform = transform.rotate(90.0) elif orientation in (7, 8): transform = transform.rotate(-90.0) if orientation in (2, 4, 5, 7): transform = transform.scale(-1.0, 1.0) self.pixmap = self.pixmap.transformed(transform) self.image.setPixmap(self.pixmap.scaled( self.thumb_size, self.thumb_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)) return result def set_selected(self, value): self.selected = value if self.selected: self.setStyleSheet("#thumbnail {border: 2px solid red}") else: self.setStyleSheet("#thumbnail {border: 2px solid grey}") def get_selected(self): return self.selected
def __init__(self, path, image_list, thumb_size=80, *arg, **kw): super(Image, self).__init__(*arg, **kw) self.path = path self.image_list = image_list self.name, ext = os.path.splitext(os.path.basename(self.path)) self.selected = False self.thumb_size = thumb_size # read image with open(self.path, 'rb') as pf: image_data = pf.read() # read metadata self.metadata = Metadata( self.path, image_data, new_status=self.show_status) # set file type ext = ext.lower() self.file_type = mimetypes.guess_type(self.path)[0] if not self.file_type: self.file_type = imghdr.what(self.path, image_data) if self.file_type: self.file_type = 'image/' + self.file_type # anything not recognised is assumed to be 'raw' if not self.file_type: self.file_type = 'image/raw' # make 'master' thumbnail self.pixmap = QtGui.QPixmap() self.pixmap.loadFromData(image_data) unrotate = self.file_type == 'image/x-dcraw' if self.pixmap.isNull(): # image read failed so attempt to use exif thumbnail thumb = self.metadata.get_exif_thumbnail() if thumb: self.pixmap.loadFromData(bytearray(thumb)) unrotate = False if not self.pixmap.isNull(): if max(self.pixmap.width(), self.pixmap.height()) > 450: # store a scaled down version of image to save memory self.pixmap = self.pixmap.scaled( 300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation) if unrotate: # loading preview which is already re-oriented orientation = self.metadata.orientation if orientation and orientation.value > 1: # need to unrotate and or unreflect image transform = QtGui.QTransform() if orientation.value in (3, 4): transform = transform.rotate(180.0) elif orientation.value in (5, 6): transform = transform.rotate(-90.0) elif orientation.value in (7, 8): transform = transform.rotate(90.0) if orientation.value in (2, 4, 5, 7): transform = transform.scale(-1.0, 1.0) self.pixmap = self.pixmap.transformed(transform) # sub widgets layout = QtWidgets.QGridLayout() layout.setSpacing(0) layout.setContentsMargins(3, 3, 3, 3) self.setLayout(layout) self.setToolTip(self.path) # label to display image self.image = QtWidgets.QLabel() self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) layout.addWidget(self.image, 0, 0, 1, 2) # label to display file name self.label = QtWidgets.QLabel() self.label.setAlignment(Qt.AlignRight) self.label.setStyleSheet("QLabel { font-size: 12px }") layout.addWidget(self.label, 1, 1) # label to display status self.status = QtWidgets.QLabel() self.status.setAlignment(Qt.AlignLeft) self.status.setStyleSheet("QLabel { font-size: 12px }") self.status.setFont(QtGui.QFont("Dejavu Sans")) if not self.status.fontInfo().exactMatch(): # probably on Windows, try a different font self.status.setFont(QtGui.QFont("Segoe UI Symbol")) layout.addWidget(self.status, 1, 0) self.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Plain) self.setObjectName("thumbnail") self.set_selected(False) self.show_status(False) self._set_thumb_size(self.thumb_size)
class Image(QtWidgets.QFrame): def __init__(self, path, image_list, thumb_size=80, *arg, **kw): super(Image, self).__init__(*arg, **kw) self.path = path self.image_list = image_list self.name, ext = os.path.splitext(os.path.basename(self.path)) self.selected = False self.thumb_size = thumb_size # read image with open(self.path, 'rb') as pf: image_data = pf.read() # read metadata self.metadata = Metadata( self.path, image_data, new_status=self.show_status) # set file type ext = ext.lower() self.file_type = mimetypes.guess_type(self.path)[0] if not self.file_type: self.file_type = imghdr.what(self.path, image_data) if self.file_type: self.file_type = 'image/' + self.file_type # anything not recognised is assumed to be 'raw' if not self.file_type: self.file_type = 'image/raw' # make 'master' thumbnail self.pixmap = QtGui.QPixmap() self.pixmap.loadFromData(image_data) unrotate = self.file_type == 'image/x-dcraw' if self.pixmap.isNull(): # image read failed so attempt to use exif thumbnail thumb = self.metadata.get_exif_thumbnail() if thumb: self.pixmap.loadFromData(bytearray(thumb)) unrotate = False if not self.pixmap.isNull(): if max(self.pixmap.width(), self.pixmap.height()) > 450: # store a scaled down version of image to save memory self.pixmap = self.pixmap.scaled( 300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation) if unrotate: # loading preview which is already re-oriented orientation = self.metadata.orientation if orientation and orientation.value > 1: # need to unrotate and or unreflect image transform = QtGui.QTransform() if orientation.value in (3, 4): transform = transform.rotate(180.0) elif orientation.value in (5, 6): transform = transform.rotate(-90.0) elif orientation.value in (7, 8): transform = transform.rotate(90.0) if orientation.value in (2, 4, 5, 7): transform = transform.scale(-1.0, 1.0) self.pixmap = self.pixmap.transformed(transform) # sub widgets layout = QtWidgets.QGridLayout() layout.setSpacing(0) layout.setContentsMargins(3, 3, 3, 3) self.setLayout(layout) self.setToolTip(self.path) # label to display image self.image = QtWidgets.QLabel() self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) layout.addWidget(self.image, 0, 0, 1, 2) # label to display file name self.label = QtWidgets.QLabel() self.label.setAlignment(Qt.AlignRight) self.label.setStyleSheet("QLabel { font-size: 12px }") layout.addWidget(self.label, 1, 1) # label to display status self.status = QtWidgets.QLabel() self.status.setAlignment(Qt.AlignLeft) self.status.setStyleSheet("QLabel { font-size: 12px }") self.status.setFont(QtGui.QFont("Dejavu Sans")) if not self.status.fontInfo().exactMatch(): # probably on Windows, try a different font self.status.setFont(QtGui.QFont("Segoe UI Symbol")) layout.addWidget(self.status, 1, 0) self.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Plain) self.setObjectName("thumbnail") self.set_selected(False) self.show_status(False) self._set_thumb_size(self.thumb_size) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.drag_start_pos = event.pos() if event.modifiers() == Qt.ControlModifier: self.image_list.select_image(self, multiple_selection=True) elif event.modifiers() == Qt.ShiftModifier: self.image_list.select_image(self, extend_selection=True) elif not self.get_selected(): # don't clear selection in case we're about to drag self.image_list.select_image(self) def mouseReleaseEvent(self, event): if event.modifiers() not in (Qt.ControlModifier, Qt.ShiftModifier): # clear any multiple selection self.image_list.select_image(self) def mouseMoveEvent(self, event): if not self.image_list.drag_icon: return if ((event.pos() - self.drag_start_pos).manhattanLength() < QtWidgets.QApplication.startDragDistance()): return paths = [] for image in self.image_list.get_selected_images(): paths.append(image.path) if not paths: return drag = QtGui.QDrag(self) # construct icon count = min(len(paths), 8) src_icon = self.image_list.drag_icon src_w = src_icon.width() src_h = src_icon.height() margin = (count - 1) * 4 if count == 1: icon = src_icon else: icon = QtGui.QPixmap(src_w + margin, src_h + margin) icon.fill(Qt.transparent) with QtGui.QPainter(icon) as paint: for i in range(count): paint.drawPixmap( QtCore.QPoint(margin - (i * 4), i * 4), src_icon) drag.setPixmap(icon) if src_h == src_w: # round marker used in Bing maps version 8 drag.setHotSpot(QtCore.QPoint(src_w // 2, (src_h // 2) + margin)) else: drag.setHotSpot(QtCore.QPoint(src_w // 2, src_h + margin)) mimeData = QtCore.QMimeData() mimeData.setData(DRAG_MIMETYPE, repr(paths).encode('utf-8')) drag.setMimeData(mimeData) dropAction = drag.exec_(Qt.CopyAction) def mouseDoubleClickEvent(self, event): webbrowser.open(self.path) def show_status(self, changed): status = '' # set 'geotagged' status if self.metadata.latlong: status += six.unichr(0x2690) # set 'unsaved' status if changed: status += six.unichr(0x26A1) self.status.setText(status) self._elide_name() if changed: self.image_list.new_metadata.emit(True) def _elide_name(self): self.status.adjustSize() elided_name = self.label.fontMetrics().elidedText( self.name, Qt.ElideLeft, self.thumb_size - self.status.width()) self.label.setText(elided_name) def _set_thumb_size(self, thumb_size): self.thumb_size = thumb_size self.image.setFixedSize(self.thumb_size, self.thumb_size) self._elide_name() def set_thumb_size(self, thumb_size): self._set_thumb_size(thumb_size) self.load_thumbnail() def load_thumbnail(self): if self.pixmap.isNull(): self.image.setText(self.tr('Can not\ncreate\nthumbnail')) else: pixmap = self.pixmap orientation = self.metadata.orientation if orientation and orientation.value > 1: # need to rotate and or reflect image transform = QtGui.QTransform() if orientation.value in (3, 4): transform = transform.rotate(180.0) elif orientation.value in (5, 6): transform = transform.rotate(90.0) elif orientation.value in (7, 8): transform = transform.rotate(-90.0) if orientation.value in (2, 4, 5, 7): transform = transform.scale(-1.0, 1.0) pixmap = pixmap.transformed(transform) self.image.setPixmap(pixmap.scaled( self.thumb_size, self.thumb_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)) def set_selected(self, value): self.selected = value if self.selected: self.setStyleSheet("#thumbnail {border: 2px solid red}") else: self.setStyleSheet("#thumbnail {border: 2px solid grey}") def get_selected(self): return self.selected
def diff_metadata(self): dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle(translate('ImageList', 'Metadata differences')) dialog.setLayout(QtWidgets.QVBoxLayout()) table = TableWidget() table.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) table.setColumnCount(3) table.setHorizontalHeaderLabels([ translate('ImageList', 'new value'), translate('ImageList', 'undo'), translate('ImageList', 'old value') ]) labels = [] row = 0 undo = {} new_md = self.metadata old_md = Metadata(self.path) for key in ('title', 'description', 'keywords', 'rating', 'copyright', 'creator', 'date_taken', 'date_digitised', 'date_modified', 'orientation', 'lens_model', 'lens_make', 'lens_serial', 'lens_spec', 'focal_length', 'focal_length_35', 'aperture', 'latlong', 'altitude', 'location_taken', 'location_shown', 'thumbnail'): values = getattr(new_md, key), getattr(old_md, key) if values[0] == values[1]: continue table.setRowCount(row + 1) for n, value in enumerate(values): if not value: value = '' elif isinstance(value, MultiString): value = '\n'.join(value) else: value = six.text_type(value) item = QtWidgets.QTableWidgetItem(value) table.setItem(row, n * 2, item) undo[key] = QtWidgets.QTableWidgetItem() undo[key].setFlags(undo[key].flags() | Qt.ItemIsUserCheckable) undo[key].setCheckState(False) table.setItem(row, 1, undo[key]) labels.append(key) row += 1 table.setVerticalHeaderLabels(labels) table.resizeColumnsToContents() table.resizeRowsToContents() dialog.layout().addWidget(table) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addWidget(button_box) if dialog.exec_() != QtWidgets.QDialog.Accepted: return changed = False dirty = False for key, widget in undo.items(): if widget.checkState() == Qt.Checked: setattr(new_md, key, getattr(old_md, key)) changed = True else: dirty = True if not dirty: self.reload_metadata() elif changed: self.image_list.emit_selection()
def reload_metadata(self): self.metadata = Metadata(self.path) self.metadata.unsaved.connect(self.show_status) self.show_status(False) self.load_thumbnail() self.image_list.emit_selection()
def diff_selected_metadata(self): dialog = QtWidgets.QDialog(parent=self) dialog.setLayout(QtWidgets.QVBoxLayout()) dialog.setFixedSize(min(800, self.window().width()), min(400, self.window().height())) table = QtWidgets.QTableWidget() table.setColumnCount(3) table.setHorizontalHeaderLabels([ translate('ImageList', 'new value'), translate('ImageList', 'undo'), translate('ImageList', 'old value') ]) table.horizontalHeader().setSectionResizeMode( 0, QtWidgets.QHeaderView.Stretch) table.horizontalHeader().setSectionResizeMode( 2, QtWidgets.QHeaderView.Stretch) dialog.layout().addWidget(table) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addWidget(button_box) changed = False position = None for image in self.get_selected_images(): if not image.metadata.changed(): continue dialog.setWindowTitle( translate('ImageList', 'Metadata differences: {}').format(image.name)) labels = [] row = 0 undo = {} table.clearContents() new_md = image.metadata old_md = Metadata(image.path) for key in ('title', 'description', 'keywords', 'rating', 'copyright', 'creator', 'date_taken', 'date_digitised', 'date_modified', 'orientation', 'camera_model', 'lens_model', 'lens_spec', 'focal_length', 'focal_length_35', 'aperture', 'latlong', 'altitude', 'location_taken', 'location_shown', 'thumbnail'): values = getattr(new_md, key), getattr(old_md, key) if values[0] == values[1]: continue values = [str(x or '') for x in values] table.setRowCount(row + 1) for n, value in enumerate(values): item = QtWidgets.QTableWidgetItem(value) table.setItem(row, n * 2, item) undo[key] = QtWidgets.QTableWidgetItem() undo[key].setFlags(undo[key].flags() | Qt.ItemIsUserCheckable) undo[key].setCheckState(Qt.Unchecked) table.setItem(row, 1, undo[key]) labels.append(key) row += 1 if not row: continue table.setVerticalHeaderLabels(labels) table.resizeColumnsToContents() table.resizeRowsToContents() if position: dialog.move(position) if dialog.exec_() != QtWidgets.QDialog.Accepted: return position = dialog.pos() undo_all = True for key, widget in undo.items(): if widget.checkState() == Qt.Checked: setattr(new_md, key, getattr(old_md, key)) changed = True else: undo_all = False if undo_all: image.reload_metadata() if changed: self.emit_selection()
def __init__(self, path, image_list, thumb_size=80, *arg, **kw): super(Image, self).__init__(*arg, **kw) self.path = path self.image_list = image_list self.name, ext = os.path.splitext(os.path.basename(self.path)) self.selected = False self.thumb_size = thumb_size # read image with open(self.path, 'rb') as pf: image_data = pf.read() # read metadata self.metadata = Metadata( self.path, image_data, new_status=self.show_status) # set file type ext = ext.lower() self.file_type = imghdr.what(self.path) or 'raw' if self.file_type == 'tiff' and ext not in ('.tif', '.tiff'): self.file_type = 'raw' # make 'master' thumbnail self.pixmap = QtGui.QPixmap() self.pixmap.loadFromData(image_data) unrotate = self.file_type == 'raw' if self.pixmap.isNull(): # image read failed so attempt to use exif thumbnail thumb = self.metadata.get_exif_thumbnail() if thumb: self.pixmap.loadFromData(bytearray(thumb)) unrotate = False if not self.pixmap.isNull(): if max(self.pixmap.width(), self.pixmap.height()) > 450: # store a scaled down version of image to save memory self.pixmap = self.pixmap.scaled( 300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation) if unrotate: # loading preview which is already re-oriented orientation = self.metadata.orientation if orientation and orientation.value > 1: # need to unrotate and or unreflect image transform = QtGui.QTransform() if orientation.value in (3, 4): transform = transform.rotate(180.0) elif orientation.value in (5, 6): transform = transform.rotate(-90.0) elif orientation.value in (7, 8): transform = transform.rotate(90.0) if orientation.value in (2, 4, 5, 7): transform = transform.scale(-1.0, 1.0) self.pixmap = self.pixmap.transformed(transform) # sub widgets layout = QtWidgets.QGridLayout() layout.setSpacing(0) layout.setContentsMargins(3, 3, 3, 3) self.setLayout(layout) self.setToolTip(self.path) # label to display image self.image = QtWidgets.QLabel() self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) layout.addWidget(self.image, 0, 0, 1, 2) # label to display file name self.label = QtWidgets.QLabel() self.label.setAlignment(Qt.AlignRight) self.label.setStyleSheet("QLabel { font-size: 12px }") layout.addWidget(self.label, 1, 1) # label to display status self.status = QtWidgets.QLabel() self.status.setAlignment(Qt.AlignLeft) self.status.setStyleSheet("QLabel { font-size: 12px }") self.status.setFont(QtGui.QFont("Dejavu Sans")) if not self.status.fontInfo().exactMatch(): # probably on Windows, try a different font self.status.setFont(QtGui.QFont("Segoe UI Symbol")) layout.addWidget(self.status, 1, 0) self.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Plain) self.setObjectName("thumbnail") self.set_selected(False) self.show_status(False) self._set_thumb_size(self.thumb_size)
class Image(QtWidgets.QFrame): def __init__(self, path, image_list, thumb_size=80, *arg, **kw): super(Image, self).__init__(*arg, **kw) self.path = path self.image_list = image_list self.name, ext = os.path.splitext(os.path.basename(self.path)) self.selected = False self.thumb_size = thumb_size # read metadata self.metadata = Metadata(self.path) self.metadata.unsaved.connect(self.show_status) self.file_times = (os.path.getatime(self.path), os.path.getmtime(self.path)) # set file type self.file_type = self.metadata.get_mime_type() if not self.file_type: self.file_type = mimetypes.guess_type(self.path)[0] if not self.file_type: self.file_type = imghdr.what(self.path) if self.file_type: self.file_type = 'image/' + self.file_type # anything not recognised is assumed to be 'raw' if not self.file_type: self.file_type = 'image/raw' # sub widgets layout = QtWidgets.QGridLayout() layout.setSpacing(0) layout.setContentsMargins(3, 3, 3, 3) self.setLayout(layout) self.setToolTip(self.path) # label to display image self.image = QtWidgets.QLabel() self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) layout.addWidget(self.image, 0, 0, 1, 2) # label to display file name self.label = QtWidgets.QLabel() self.label.setAlignment(Qt.AlignRight) scale_font(self.label, 80) layout.addWidget(self.label, 1, 1) # label to display status self.status = QtWidgets.QLabel() self.status.setAlignment(Qt.AlignLeft) set_symbol_font(self.status) scale_font(self.status, 80) layout.addWidget(self.status, 1, 0) self.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Plain) self.setObjectName("thumbnail") self.set_selected(False) self.show_status(False) self._set_thumb_size(self.thumb_size) @QtCore.pyqtSlot() @catch_all def reload_metadata(self): self.metadata = Metadata(self.path) self.metadata.unsaved.connect(self.show_status) self.show_status(False) self.load_thumbnail() self.image_list.emit_selection() @QtCore.pyqtSlot() @catch_all def save_metadata(self): self.image_list._save_files(images=[self]) @QtCore.pyqtSlot() @catch_all def diff_metadata(self): dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle(self.tr('Metadata differences')) dialog.setLayout(QtWidgets.QVBoxLayout()) table = TableWidget() table.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) table.setColumnCount(3) table.setHorizontalHeaderLabels( [self.tr('new value'), self.tr('undo'), self.tr('old value')]) labels = [] row = 0 undo = {} new_md = self.metadata old_md = Metadata(self.path) for key in ('title', 'description', 'keywords', 'rating', 'copyright', 'creator', 'date_taken', 'date_digitised', 'date_modified', 'orientation', 'lens_model', 'lens_make', 'lens_serial', 'lens_spec', 'focal_length', 'focal_length_35', 'aperture', 'latlong', 'location_taken', 'location_shown', 'thumbnail'): values = getattr(new_md, key), getattr(old_md, key) if values[0] == values[1]: continue table.setRowCount(row + 1) for n, value in enumerate(values): if not value: value = '' elif isinstance(value, MultiString): value = '\n'.join(value) else: value = six.text_type(value) item = QtWidgets.QTableWidgetItem(value) table.setItem(row, n * 2, item) undo[key] = QtWidgets.QTableWidgetItem() undo[key].setFlags(undo[key].flags() | Qt.ItemIsUserCheckable) undo[key].setCheckState(False) table.setItem(row, 1, undo[key]) labels.append(key) row += 1 table.setVerticalHeaderLabels(labels) table.resizeColumnsToContents() table.resizeRowsToContents() dialog.layout().addWidget(table) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addWidget(button_box) if dialog.exec_() != QtWidgets.QDialog.Accepted: return changed = False dirty = False for key, widget in undo.items(): if widget.checkState() == Qt.Checked: setattr(new_md, key, getattr(old_md, key)) changed = True else: dirty = True if not dirty: self.reload_metadata() elif changed: self.image_list.emit_selection() def get_video_frame(self): if not cv2: return video = cv2.VideoCapture(self.path) if not video.isOpened(): return OK, cv_image = video.read() if not OK: return height, width, channel = cv_image.shape fmt = QtGui.QImage.Format_RGB888 # need to pad to 4 pixel multiple new_width = width - (width % -4) if channel == 4: # assume BGRA fmt = QtGui.QImage.Format_ARGB32 np_image = np.empty((height, new_width, channel), dtype=np.uint8) np_image[:, :width, 0] = cv_image[:, :, 3] np_image[:, :width, 1] = cv_image[:, :, 2] np_image[:, :width, 2] = cv_image[:, :, 1] np_image[:, :width, 3] = cv_image[:, :, 0] elif channel == 3: # assume BGR np_image = np.empty((height, new_width, channel), dtype=np.uint8) np_image[:, :width, 0] = cv_image[:, :, 2] np_image[:, :width, 1] = cv_image[:, :, 1] np_image[:, :width, 2] = cv_image[:, :, 0] elif channel == 1: # assume Y channel = 3 np_image = np.empty((height, new_width, channel), dtype=np.uint8) np_image[:, :width, 0] = cv_image[:, :, 0] np_image[:, :width, 1] = cv_image[:, :, 0] np_image[:, :width, 2] = cv_image[:, :, 0] else: return bpl = new_width * channel qt_im = QtGui.QImage(np_image.data, width, height, bpl, fmt) # attach np_image so it isn't deleted until qt_im is qt_im._data = np_image return qt_im def transform(self, pixmap, orientation, inverse=False): orientation = (orientation or 1) - 1 if not orientation: return pixmap # need to rotate and or reflect image transform = QtGui.QTransform() if orientation & 0b001: # reflect left-right transform = transform.scale(-1.0, 1.0) if orientation & 0b010: transform = transform.rotate(180.0) if orientation & 0b100: # transpose horizontal & vertical transform = QtGui.QTransform(0, 1, 1, 0, 1, 1) * transform if inverse: transform = transform.transposed() return pixmap.transformed(transform) @QtCore.pyqtSlot() @catch_all def regenerate_thumbnail(self): with Busy(): # get Qt image first qt_im = QtGui.QImage(self.path) if self.file_type.startswith('video') and qt_im.isNull(): # use OpenCV to read first frame qt_im = self.get_video_frame() if not qt_im or qt_im.isNull(): logger.error('Cannot read %s image data from %s', self.file_type, self.path) return # reorient if required if self.file_type in ('image/x-canon-cr2', 'image/x-nikon-nef'): qt_im = self.transform(qt_im, self.metadata.orientation, inverse=True) w = qt_im.width() h = qt_im.height() # use Qt's scaling (not high quality) to pre-shrink very # large images, to avoid PIL "DecompressionBombWarning" if max(w, h) >= 6000: qt_im = qt_im.scaled(6000, 6000, Qt.KeepAspectRatio, Qt.SmoothTransformation) w = qt_im.width() h = qt_im.height() # DCF spec says thumbnail must be 160 x 120 so pad picture # to 4:3 aspect ratio if w >= h: new_h = int(0.5 + (float(w * 3) / 4.0)) new_w = int(0.5 + (float(h * 4) / 3.0)) if new_h > h: pad = (new_h - h) // 2 qt_im = qt_im.copy(0, -pad, w, new_h) elif new_w > w: pad = (new_w - w) // 2 qt_im = qt_im.copy(-pad, 0, new_w, h) w, h = 160, 120 else: new_h = int(0.5 + (float(w * 4) / 3.0)) new_w = int(0.5 + (float(h * 3) / 4.0)) if new_w > w: pad = (new_w - w) // 2 qt_im = qt_im.copy(-pad, 0, new_w, h) elif new_h > h: pad = (new_h - h) // 2 qt_im = qt_im.copy(0, -pad, w, new_h) w, h = 120, 160 fmt = 'JPEG' if PIL: # convert Qt image to PIL image buf = QtCore.QBuffer() buf.open(QtCore.QIODevice.WriteOnly) qt_im.save(buf, 'PPM') data = BytesIO(buf.data().data()) pil_im = PIL.open(data) # scale PIL image pil_im = pil_im.resize((w, h), PIL.ANTIALIAS) # save image to memory data = BytesIO() pil_im.save(data, fmt) data = data.getvalue() else: # scale Qt image - not as good quality as PIL qt_im = qt_im.scaled(w, h, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) # save image to memory buf = QtCore.QBuffer() buf.open(QtCore.QIODevice.WriteOnly) qt_im.save(buf, fmt) data = buf.data().data() # set thumbnail self.metadata.thumbnail = data, fmt, w, h # reload thumbnail self.load_thumbnail() @catch_all def contextMenuEvent(self, event): menu = QtWidgets.QMenu(self) menu.addAction(self.tr('Reload metadata'), self.reload_metadata) menu.addAction(self.tr('Save metadata'), self.save_metadata) menu.addAction(self.tr('View changes'), self.diff_metadata) menu.addAction(self.tr('Regenerate thumbnail'), self.regenerate_thumbnail) action = menu.exec_(event.globalPos()) @catch_all def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.drag_start_pos = event.pos() if event.modifiers() == Qt.ControlModifier: self.image_list.select_image(self, multiple_selection=True) elif event.modifiers() == Qt.ShiftModifier: self.image_list.select_image(self, extend_selection=True) elif not self.get_selected(): # don't clear selection in case we're about to drag self.image_list.select_image(self) @catch_all def mouseReleaseEvent(self, event): if event.modifiers() not in (Qt.ControlModifier, Qt.ShiftModifier): # clear any multiple selection self.image_list.select_image(self) @catch_all def mouseMoveEvent(self, event): if not self.image_list.drag_icon: return if ((event.pos() - self.drag_start_pos).manhattanLength() < QtWidgets.QApplication.startDragDistance()): return paths = [] for image in self.image_list.get_selected_images(): paths.append(image.path) if not paths: return drag = QtGui.QDrag(self) # construct icon count = min(len(paths), 8) src_icon = self.image_list.drag_icon src_w = src_icon.width() src_h = src_icon.height() margin = (count - 1) * 4 if count == 1: icon = src_icon else: icon = QtGui.QPixmap(src_w + margin, src_h + margin) icon.fill(Qt.transparent) with QtGui.QPainter(icon) as paint: for i in range(count): paint.drawPixmap(QtCore.QPoint(margin - (i * 4), i * 4), src_icon) drag.setPixmap(icon) if self.image_list.drag_hotspot: x, y = self.image_list.drag_hotspot else: x, y = src_w // 2, src_h drag.setHotSpot(QtCore.QPoint(x, y + margin)) mimeData = QtCore.QMimeData() mimeData.setData(DRAG_MIMETYPE, repr(paths).encode('utf-8')) drag.setMimeData(mimeData) dropAction = drag.exec_(Qt.CopyAction) @catch_all def mouseDoubleClickEvent(self, event): webbrowser.open(self.path) @QtCore.pyqtSlot(bool) @catch_all def show_status(self, changed): status = '' # set 'geotagged' status if self.metadata.latlong: status += six.unichr(0x2690) # set 'unsaved' status if changed: status += six.unichr(0x26A1) self.status.setText(status) self._elide_name() if changed: self.image_list.new_metadata.emit(True) def _elide_name(self): self.status.adjustSize() elided_name = self.label.fontMetrics().elidedText( self.name, Qt.ElideLeft, self.thumb_size - self.status.width()) self.label.setText(elided_name) def _set_thumb_size(self, thumb_size): self.thumb_size = thumb_size self.image.setFixedSize(self.thumb_size, self.thumb_size) self._elide_name() def set_thumb_size(self, thumb_size): self._set_thumb_size(thumb_size) self.load_thumbnail() def load_thumbnail(self): pixmap = QtGui.QPixmap() thumb = self.metadata.thumbnail if thumb: pixmap.loadFromData(thumb.data) if pixmap.isNull(): self.image.setText(self.tr('No\nthumbnail\nin file')) return pixmap = self.transform(pixmap, self.metadata.orientation) self.image.setPixmap( pixmap.scaled(self.thumb_size, self.thumb_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)) def set_selected(self, value): self.selected = value if self.selected: self.setStyleSheet("#thumbnail {border: 2px solid red}") else: self.setStyleSheet("#thumbnail {border: 2px solid grey}") def get_selected(self): return self.selected