def getDngProfileDict(filename): """ Read profile related tags from a dng or dcp file. Return a dictionary of (str) decoded {tagname : tagvalue} pairs. @param filename: @type filename: str @return: dictionary @rtype: dict """ with exiftool.ExifTool() as e: profileDict = e.readBinaryDataAsDict(filename, taglist=['LinearizationTable', 'ProfileLookTableData', 'ProfileLookTableDims', 'ProfileLookTableEncoding', 'ProfileToneCurve', 'CalibrationIlluminant1', 'CalibrationIlluminant2', 'ColorMatrix1', 'ColorMatrix2', 'CameraCalibration1', 'CameraCalibration2', 'ForwardMatrix1', 'ForwardMatrix2', 'AnalogBalance' ]) return profileDict
def setRating(self, action): # rating : the tag is written into the .mie file; the file is # created if needed. listWdg = self.listWdg sel = listWdg.selectedItems() if action.text() in ['0', '1', '2', '3', '4', '5']: with exiftool.ExifTool() as e: value = int(action.text()) for item in sel: filename = item.data(Qt.UserRole)[0] e.writeXMPTag(filename, 'XMP:rating', value) item.setText(basename(filename) + '\n' + ''.join(['*'] * value))
def contextMenuHandler(action): global isSuspended if action.text() == 'Pause': isSuspended = True # quit full screen mode newWin.showMaximized() elif action.text() == 'Full Screen': if action.isChecked(): newWin.showFullScreen() else: newWin.showMaximized() elif action.text() == 'Resume': newWin.close() isSuspended = False playDiaporama(diaporamaGenerator, parent=window) # rating : the tag is written into the .mie file; the file is # created if needed. elif action.text() in ['0', '1', '2', '3', '4', '5']: with exiftool.ExifTool() as e: e.writeXMPTag(name, 'XMP:rating', int(action.text()))
def playDiaporama(diaporamaGenerator, parent=None): """ Open a new window and play a slide show. @param diaporamaGenerator: generator for file names @type diaporamaGenerator: iterator object @param parent: @type parent: """ global isSuspended isSuspended = False # init diaporama window newWin = QMainWindow(parent) newWin.setAttribute(Qt.WA_DeleteOnClose) newWin.setContextMenuPolicy(Qt.CustomContextMenu) newWin.setWindowTitle(parent.tr('Slide show')) label = imageLabel(mainForm=parent) label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) label.img = None newWin.setCentralWidget(label) newWin.showFullScreen() # Pause key shortcut actionEsc = QAction('Pause', None) actionEsc.setShortcut(QKeySequence(Qt.Key_Escape)) newWin.addAction(actionEsc) # context menu event handler def contextMenuHandler(action): global isSuspended if action.text() == 'Pause': isSuspended = True # quit full screen mode newWin.showMaximized() elif action.text() == 'Full Screen': if action.isChecked(): newWin.showFullScreen() else: newWin.showMaximized() elif action.text() == 'Resume': newWin.close() isSuspended = False playDiaporama(diaporamaGenerator, parent=window) # rating : the tag is written into the .mie file; the file is # created if needed. elif action.text() in ['0', '1', '2', '3', '4', '5']: with exiftool.ExifTool() as e: e.writeXMPTag(name, 'XMP:rating', int(action.text())) # connect shortkey action actionEsc.triggered.connect( lambda checked=False, name=actionEsc: contextMenuHandler( name)) # named arg checked is sent # context menu def contextMenu(position): menu = QMenu() actionEsc.setEnabled(not isSuspended) action2 = QAction('Full Screen', None) action2.setCheckable(True) action2.setChecked(newWin.windowState() & Qt.WindowFullScreen) action3 = QAction('Resume', None) action3.setEnabled(isSuspended) for action in [actionEsc, action2, action3]: menu.addAction(action) action.triggered.connect( lambda checked=False, name=action: contextMenuHandler( name)) # named arg checked is sent subMenuRating = menu.addMenu('Rating') for i in range(6): action = QAction(str(i), None) subMenuRating.addAction(action) action.triggered.connect( lambda checked=False, name=action: contextMenuHandler( name)) # named arg checked is sent menu.exec_(position) # connect contextMenuRequested newWin.customContextMenuRequested.connect(contextMenu) newWin.setToolTip("Esc to exit full screen mode") newWin.setWhatsThis(""" <b>Slide Show</b><br> The slide show cycles through the starting directory and its subfolders to display images. Photos rated 0 or 1 star are not shown (by default, all photos are rated 5 stars).<br> Hit the Esc key to <b>exit full screen mode and pause.</b><br> Use the Context Menu for <b>rating and resuming.</b> The rating is saved in the .mie sidecar and the image file is not modified. """) # end of setWhatsThis # play diaporama window.modeDiaporama = True while True: if isSuspended: newWin.setWindowTitle(newWin.windowTitle() + ' Paused') break try: if not newWin.isVisible(): break name = next(diaporamaGenerator) # search rating in metadata rating = 5 # default with exiftool.ExifTool() as e: try: rt = e.readXMPTag( name, 'XMP:rating') # raise ValueError if sidecar not found r = search("\d", rt) if r is not None: rating = int(r.group(0)) except ValueError: rating = 5 # don't display image with low rating if rating < 2: app.processEvents() imImg = imImage.loadImageFromFile(name, createsidecar=False, cmsConfigure=True, window=window) # zoom might be modified by the mouse wheel : remember if label.img is not None: imImg.Zoom_coeff = label.img.Zoom_coeff coeff = imImg.resize_coeff(label) imImg.yOffset -= (imImg.height() * coeff - label.height()) / 2.0 imImg.xOffset -= (imImg.width() * coeff - label.width()) / 2.0 app.processEvents() if isSuspended: newWin.setWindowTitle(newWin.windowTitle() + ' Paused') break newWin.setWindowTitle( parent.tr('Slide show') + ' ' + name + ' ' + ' '.join(['*'] * imImg.meta.rating)) gc.collect() label.img = imImg label.repaint() app.processEvents() gc.collect() sleep(2) app.processEvents() except StopIteration: newWin.close() window.diaporamaGenerator = None break except ValueError: continue except RuntimeError: window.diaporamaGenerator = None break except: window.diaporamaGenerator = None window.modeDiaporama = False raise app.processEvents() window.modeDiaporama = False
def run(self): # next() raises a StopIteration exception when the generator ends. # If this exception is unhandled by run(), it causes thread termination. # If wdg internal C++ object was destroyed by main thread (form closing) # a RuntimeError exception is raised and causes thread termination too. # Thus, no further synchronization is needed. from bLUeTop import exiftool with exiftool.ExifTool() as e: while True: try: filename = next(self.fileListGen) # get orientation try: # read metadata from sidecar (.mie) if it exists, otherwise from image file. profile, metadata = e.get_metadata( filename, tags=("colorspace", "profileDescription", "orientation", "model", "rating", "FileCreateDate"), createsidecar=False) except ValueError: metadata = {} # get image info tmp = [ value for key, value in metadata.items() if 'orientation' in key.lower() ] orientation = tmp[ 0] if tmp else 1 # metadata.get("EXIF:Orientation", 1) # EXIF:DateTimeOriginal seems to be missing in many files tmp = [ value for key, value in metadata.items() if 'date' in key.lower() ] date = tmp[ 0] if tmp else '' # metadata.get("EXIF:ModifyDate", '') tmp = [ value for key, value in metadata.items() if 'rating' in key.lower() ] rating = tmp[ 0] if tmp else 0 # metadata.get("XMP:Rating", 5) rating = ''.join(['*'] * int(rating)) transformation = exiftool.decodeExifOrientation( orientation) # get thumbnail img = e.get_thumbNail(filename, thumbname='thumbnailimage') # no thumbnail found : try preview if img.isNull(): img = e.get_thumbNail( filename, thumbname='PreviewImage' ) # the order is important : for jpeg PreviewImage is full sized ! # all failed : open image if img.isNull(): img = QImage(filename) # remove possible black borders, except for .NEF if filename[-3:] not in ['nef', 'NEF']: bBorder = 7 img = img.copy( QRect(0, bBorder, img.width(), img.height() - 2 * bBorder)) pxm = QPixmap.fromImage(img) if not transformation.isIdentity(): pxm = pxm.transformed(transformation) # set item caption and tooltip item = QListWidgetItem( QIcon(pxm), basename(filename)) # + '\n' + rating) item.setToolTip( basename(filename) + ' ' + date + ' ' + rating) # set item mimeData to get filename=item.data(Qt.UserRole)[0] transformation=item.data(Qt.UserRole)[1] item.setData(Qt.UserRole, (filename, transformation)) self.wdg.addItem(item) # for clean exiting we catch all exceptions and force break except OSError: continue except: break