def __init__(self, filename, parent=None): super(MetadataWidget, self).__init__(parent) table = [] last = None with exiftool.ExifTool(exiftool_exe()) as et: metadata = et.get_metadata(filename) for tag, value in metadata.items(): ignore = [ 'SourceFile', 'ExifTool:ExifTool', 'File:FileName', 'File:Directory', 'File:FileSize', 'File:FileModifyDate', 'File:FileInodeChangeDate', 'File:FileAccessDate', 'File:FileType', 'File:FilePermissions', 'File:FileTypeExtension', 'File:MIMEType' ] if not value or any(t in tag for t in ignore): continue value = str(value).replace(', use -b option to extract', '') group, desc = tag.split(':') if last is None or group != last: table.append([group, desc, value]) last = group else: table.append([None, desc, value]) headers = [self.tr('Group'), self.tr('Description'), self.tr('Value')] table_widget = TableWidget(table, headers) main_layout = QVBoxLayout() main_layout.addWidget(table_widget) self.setLayout(main_layout) self.setMinimumSize(620, 500)
def __init__(self, filename, parent=None): super(LocationWidget, self).__init__(parent) self.temp_dir = QTemporaryDir() if self.temp_dir.isValid(): with exiftool.ExifTool(exiftool_exe()) as et: try: metadata = et.get_metadata(filename) lat = metadata["Composite:GPSLatitude"] lon = metadata["Composite:GPSLongitude"] except KeyError: label = QLabel(self.tr("Geolocation data not found!")) modify_font(label, bold=True) label.setStyleSheet("color: #FF0000") label.setAlignment(Qt.AlignCenter) layout = QVBoxLayout() layout.addWidget(label) self.setLayout(layout) return url = f"https://www.google.com/maps/place/{lat},{lon}/@{lat},{lon},17z/" \ f"data=!4m5!3m4!1s0x0:0x0!8m2!3d{lat}!4d{lon}" web_view = QWebEngineView() web_view.load(QUrl(url)) layout = QVBoxLayout() layout.addWidget(web_view) self.setLayout(layout)
def __init__(self, filename, parent=None): super(LocationWidget, self).__init__(parent) self.temp_dir = QTemporaryDir() if self.temp_dir.isValid(): with exiftool.ExifTool(exiftool_exe()) as et: temp_file = os.path.join(self.temp_dir.path(), "geo.html") metadata = et.get_metadata(filename) try: lat = metadata["Composite:GPSLatitude"] long = metadata["Composite:GPSLongitude"] except KeyError: label = QLabel(self.tr("Geolocation data not found!")) modify_font(label, bold=True) label.setStyleSheet("color: #FF0000") label.setAlignment(Qt.AlignCenter) layout = QVBoxLayout() layout.addWidget(label) self.setLayout(layout) return html = '<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2948.532014673314!2d{}!3d{}!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x0%3A0x0!2zNDLCsDIxJzA5LjAiTiA3McKwMDUnMjguMiJX!5e0!3m2!1sit!2sit!4v1590074026898!5m2!1sit!2sit" width="600" height="450" frameborder="0" style="border:0;" allowfullscreen="" aria-hidden="false" tabindex="0"></iframe>'.format( long, lat) with open(temp_file, "w") as file: file.write(html) web_view = QWebEngineView() web_view.load(QUrl("file://" + temp_file)) layout = QVBoxLayout() layout.addWidget(web_view) self.setLayout(layout)
def __init__(self, filename, parent=None): super(StructureWidget, self).__init__(parent) self.temp_dir = QTemporaryDir() if self.temp_dir.isValid(): temp_file = os.path.join(self.temp_dir.path(), 'structure.html') p = run([exiftool_exe(), '-htmldump0', filename], stdout=PIPE) with open(temp_file, 'w') as file: file.write(p.stdout.decode('utf-8')) web_view = QWebEngineView() web_view.load(QUrl('file://' + temp_file)) layout = QVBoxLayout() layout.addWidget(web_view) self.setLayout(layout)
def __init__(self, filename, parent=None): super(HeaderWidget, self).__init__(parent) self.temp_dir = QTemporaryDir() if self.temp_dir.isValid(): temp_file = os.path.join(self.temp_dir.path(), "structure.html") p = run([exiftool_exe(), "-htmldump0", filename], stdout=PIPE) with open(temp_file, "w") as file: file.write(p.stdout.decode("utf-8")) web_view = QWebEngineView() web_view.load(QUrl("file://" + temp_file)) layout = QVBoxLayout() layout.addWidget(web_view) self.setLayout(layout) self.setMinimumWidth(900)
def __init__(self, filename, image, parent=None): super(ThumbWidget, self).__init__(parent) temp_file = QTemporaryFile() if temp_file.open(): output = subprocess.check_output( [exiftool_exe(), '-b', '-ThumbnailImage', filename]) temp_name = temp_file.fileName() with open(temp_name, 'wb') as file: file.write(output) thumb = cv.imread(temp_name, cv.IMREAD_COLOR) if thumb is None: self.show_error(self.tr('Thumbnail image not found!')) return # resized = cv.resize(image, thumb.shape[:-1][::-1], interpolation=cv.INTER_AREA) resized = cv.resize(thumb, image.shape[:-1][::-1], interpolation=cv.INTER_LANCZOS4) diff = cv.absdiff(image, resized) # image_aspect = image.shape[1] / image.shape[0] # thumb_aspect = thumb.shape[1] / thumb.shape[0] # if thumb_aspect < image_aspect: # shape = (thumb.shape[1] // image_aspect, thumb.shape[1]) # elif thumb_aspect > image_aspect: # shape = (thumb.shape[0], thumb.shape[0] * image_aspect) # else: # shape = thumb.shape # resized = cv.resize(image, shape, None, 0, 0, interpolation=cv.INTER_AREA) # top = (thumb.shape[0] - resized.shape[0]) / 2 # bottom = top # left = (thumb.shape[1] - resized.shape[1]) / 2 # right = left # padded = cv.copyMakeBorder(resized, top, bottom, left, right, cv.BORDER_CONSTANT) # if padded.shape != thumb.shape: # padded = cv.resize(padded, thumb.shape, interpolation=cv.INTER_AREA) # diff = cv.cvtColor(cv.absdiff(thumb, padded), cv.COLOR_BGR2GRAY) viewer = ImageViewer(resized, diff) layout = QVBoxLayout() layout.addWidget(viewer) self.setLayout(layout)
def __init__(self, filename, parent=None): super(ExifWidget, self).__init__(parent) table = [] last = None with exiftool.ExifTool(exiftool_exe()) as et: metadata = et.get_metadata(filename) for tag, value in metadata.items(): ignore = [ "SourceFile", "ExifTool:ExifTool", "File:FileName", "File:Directory", "File:FileSize", "File:FileModifyDate", "File:FileInodeChangeDate", "File:FileAccessDate", "File:FileType", "File:FilePermissions", "File:FileTypeExtension", "File:MIMEType", ] if not value or any(t in tag for t in ignore): continue value = str(value).replace(", use -b option to extract", "") value = value.replace("Binary data ", "Binary data: ") group, desc = tag.split(":") if last is None or group != last: table.append([group, desc, value]) last = group else: table.append([None, desc, value]) headers = [self.tr("Group"), self.tr("Description"), self.tr("Value")] table_widget = TableWidget(table, headers) main_layout = QVBoxLayout() main_layout.addWidget(table_widget) self.setLayout(main_layout) self.setMinimumSize(740, 500)
def __init__(self, filename, image, parent=None): super(QualityWidget, self).__init__(parent) x = np.arange(1, 101) y = loss_curve(image) tail = 5 qm = np.argmin(y[:-tail]) + 1 if qm == 100 - tail: qm = 100 figure = Figure() canvas = FigureCanvas(figure) axes = canvas.figure.subplots() axes.plot(x, y * 100, label="compression loss") axes.fill_between(x, y * 100, alpha=0.2) axes.axvline(qm, linestyle=":", color="k", label=f"min error (q = {qm})") xt = axes.get_xticks() xt = np.append(xt, 1) axes.set_xticks(xt) axes.set_xlim([1, 100]) axes.set_ylim([0, 100]) axes.set_xlabel(self.tr("JPEG quality (%)")) axes.set_ylabel(self.tr("average error (%)")) axes.grid(True, which="both") axes.legend(loc="upper center") axes.figure.canvas.draw() figure.set_tight_layout(True) main_layout = QVBoxLayout() main_layout.addWidget(canvas) MRK = b"\xFF" SOI = b"\xD8" DQT = b"\xDB" # DHT = b'\xC4' MSK = b"\x0F" PAD = b"\x00" MAX_TABLES = 2 LEN_OFFSET = 2 LUMA_IDX = 0 CHROMA_IDX = 1 luma = np.zeros((DCT_SIZE, DCT_SIZE), dtype=int) chroma = np.zeros((DCT_SIZE, DCT_SIZE), dtype=int) temp_file = QTemporaryFile() try: if temp_file.open(): copyfile(filename, temp_file.fileName()) subprocess.run( [ exiftool_exe(), "-all=", "-overwrite_original", temp_file.fileName() ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) found = False with open(temp_file.fileName(), "rb") as file: first = file.read(1) if first not in [MRK, SOI]: raise ValueError(self.tr("File is not a JPEG image!")) while True: if not self.find_next(file, [MRK, DQT, PAD]): break length = file.read(1)[0] - LEN_OFFSET if length <= 0 or length % (TABLE_SIZE + 1) != 0: continue while length > 0: mode = file.read(1) if not mode: break index = mode[0] & MSK[0] if index >= MAX_TABLES: break length -= 1 for k in range(TABLE_SIZE): b = file.read(1)[0] if not b: break length -= 1 i, j = ZIG_ZAG[k] if index == LUMA_IDX: luma[i, j] = b elif index == CHROMA_IDX: chroma[i, j] = b else: found = True if not found: raise ValueError(self.tr("Unable to find JPEG tables!")) levels = [(1 - (np.mean(t.ravel()[1:]) - 1) / 254) * 100 for t in [luma, chroma]] distance = np.zeros(101) for qm in range(101): lu, ch = cv.split(get_tables(qm)) lu_diff = np.mean(cv.absdiff(luma, lu)) ch_diff = np.mean(cv.absdiff(chroma, ch)) distance[qm] = (lu_diff + 2 * ch_diff) / 3 closest = np.argmin(distance) deviation = distance[closest] if deviation == 0: quality = closest message = "(standard tables)" else: quality = int(np.round(closest - deviation)) message = f"(deviation from standard tables = {deviation:.4f})" if quality == 0: quality = 1 quality_label = QLabel( self.tr( f"[JPEG FORMAT] Last saved quality: {quality}% {message}")) modify_font(quality_label, bold=True) luma_label = QLabel( self. tr(f"Luminance Quantization Table (level = {levels[0]:.2f}%)\n" )) luma_label.setAlignment(Qt.AlignCenter) modify_font(luma_label, underline=True) luma_table = self.create_table(luma) luma_table.setFixedSize(420, 190) chroma_label = QLabel( self. tr(f"Chrominance Quantization Table (level = {levels[1]:.2f}%)\n" )) chroma_label.setAlignment(Qt.AlignCenter) modify_font(chroma_label, underline=True) chroma_table = self.create_table(chroma) chroma_table.setFixedSize(420, 190) table_layout = QGridLayout() table_layout.addWidget(luma_label, 0, 0) table_layout.addWidget(luma_table, 1, 0) table_layout.addWidget(chroma_label, 0, 1) table_layout.addWidget(chroma_table, 1, 1) table_layout.addWidget(quality_label, 2, 0, 1, 2) main_layout.addLayout(table_layout) except ValueError: modelfile = "models/jpeg_qf.mdl" try: model = load(modelfile) limit = model.best_ntree_limit if hasattr( model, "best_ntree_limit") else None # f = self.get_features(image) # p = model.predict_proba(f, ntree_limit=limit)[0, 0] qp = model.predict(np.reshape(y, (1, len(y))), ntree_limit=limit)[0] # if p > 0.5: # p = 2 * (p - 0.5) * 100 # output = self.tr('Uncompressed image (p = {:.2f}%)'.format(p)) # else: # p = (1 - 2 * p) * 100 # output = self.tr('Compressed image (p = {:.2f}%) ---> Estimated JPEG quality = {}%'.format(p, qm)) message = self.tr( f"[LOSSLESS FORMAT] Estimated last saved quality = {qp:.1f}%{'' if qp <= 99 else ' (uncompressed)'}" ) if qp == 100: message += " (uncompressed)" prob_label = QLabel(message) modify_font(prob_label, bold=True) main_layout.addWidget(prob_label) except FileNotFoundError: QMessageBox.critical( self, self.tr("Error"), self.tr(f'Model not found ("{modelfile}")!')) main_layout.addStretch() self.setLayout(main_layout)
def __init__(self, filename, parent=None): super(QualityWidget, self).__init__(parent) MRK = b'\xFF' SOI = b'\xD8' DQT = b'\xDB' # DHT = b'\xC4' MSK = b'\x0F' PAD = b'\x00' MAX_TABLES = 2 LEN_OFFSET = 2 LUMA_IDX = 0 CHROMA_IDX = 1 luma = np.zeros((DCT_SIZE, DCT_SIZE), dtype=int) chroma = np.zeros((DCT_SIZE, DCT_SIZE), dtype=int) temp_file = QTemporaryFile() if temp_file.open(): copyfile(filename, temp_file.fileName()) subprocess.run([ exiftool_exe(), '-all=', '-overwrite_original', temp_file.fileName() ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) found = False with open(temp_file.fileName(), 'rb') as file: first = file.read(1) if first not in [MRK, SOI]: self.show_error(self.tr('File is not a JPEG image!')) return while True: if not self.find_next(file, [MRK, DQT, PAD]): break length = file.read(1)[0] - LEN_OFFSET if length <= 0 or length % (TABLE_SIZE + 1) != 0: continue while length > 0: mode = file.read(1) if not mode: break index = mode[0] & MSK[0] if index >= MAX_TABLES: break length -= 1 for k in range(TABLE_SIZE): b = file.read(1)[0] if not b: break length -= 1 i, j = ZIG_ZAG[k] if index == LUMA_IDX: luma[i, j] = b elif index == CHROMA_IDX: chroma[i, j] = b else: found = True if not found: self.show_error(self.tr('Unable to find JPEG tables!')) return levels = [(1 - (np.mean(t.ravel()[1:]) - 1) / 254) * 100 for t in [luma, chroma]] distance = np.zeros(101) for q in range(101): lu, ch = cv.split(get_tables(q)) lu_diff = np.mean(cv.absdiff(luma, lu)) ch_diff = np.mean(cv.absdiff(chroma, ch)) distance[q] = (lu_diff + 2 * ch_diff) / 3 closest = np.argmin(distance) deviation = distance[closest] if deviation == 0: quality = closest message = '(standard tables)' else: quality = int(np.round(closest - deviation)) message = '(deviation from standard = {:.4f})'.format(deviation) quality_label = QLabel( self.tr('Last saved JPEG quality: {}% {}'.format(quality, message))) modify_font(quality_label, bold=True) luma_label = QLabel( self.tr('Luminance Quantization Table (level = {:.2f}%)\n'.format( levels[0]))) luma_label.setAlignment(Qt.AlignCenter) modify_font(luma_label, underline=True) luma_table = self.create_table(luma) luma_table.setMaximumHeight(200) chroma_label = QLabel( self.tr( 'Chrominance Quantization Table (level = {:.2f}%)\n'.format( levels[1]))) chroma_label.setAlignment(Qt.AlignCenter) modify_font(chroma_label, underline=True) chroma_table = self.create_table(chroma) chroma_table.setMaximumHeight(200) main_layout = QGridLayout() main_layout.addWidget(luma_label, 0, 0) main_layout.addWidget(luma_table, 1, 0) main_layout.addWidget(chroma_label, 0, 1) main_layout.addWidget(chroma_table, 1, 1) main_layout.addWidget(quality_label, 2, 0, 1, 2) self.setLayout(main_layout) self.setFixedSize(880, 270)
def __init__(self, filename, parent=None): super(QualityWidget, self).__init__(parent) MRK = b'\xFF' SOI = b'\xD8' DQT = b'\xDB' # DHT = b'\xC4' MSK = b'\x0F' PAD = b'\x00' MAX_TABLES = 2 LEN_OFFSET = 2 LUMA_IDX = 0 CHROMA_IDX = 1 luma = np.zeros((DCT_SIZE, DCT_SIZE), dtype=int) chroma = np.zeros((DCT_SIZE, DCT_SIZE), dtype=int) temp_file = QTemporaryFile() if temp_file.open(): copyfile(filename, temp_file.fileName()) subprocess.run([ exiftool_exe(), '-all=', '-overwrite_original', temp_file.fileName() ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) found = False with open(temp_file.fileName(), 'rb') as file: first = file.read(1) if first not in [MRK, SOI]: self.show_error(self.tr('File is not a JPEG image!')) return while True: if not self.find_next(file, [MRK, DQT, PAD]): break length = file.read(1)[0] - LEN_OFFSET if length <= 0 or length % (TABLE_SIZE + 1) != 0: continue while length > 0: mode = file.read(1) if not mode: break index = mode[0] & MSK[0] if index >= MAX_TABLES: break length -= 1 for k in range(TABLE_SIZE): b = file.read(1)[0] if not b: break length -= 1 i, j = ZIG_ZAG[k] if index == LUMA_IDX: luma[i, j] = b elif index == CHROMA_IDX: chroma[i, j] = b else: found = True if not found: self.show_error(self.tr('Unable to find JPEG tables!')) return tables = np.concatenate( (luma[:, :, np.newaxis], chroma[:, :, np.newaxis]), axis=2) levels = [0, 0] for i in range(2): table = tables[:, :, i] mean = (np.mean(table) * TABLE_SIZE - table[0, 0]) / (TABLE_SIZE - 1) levels[i] = (1 - mean / 255) * 100 if levels[i] > 99.6: levels[i] = 100 profile = [np.mean(np.abs(tables - get_tables(q))) for q in range(101)] quality = np.argmin(profile) luma_label = QLabel( self.tr('Luminance Quantization Table (level = {:.2f}%)\n'.format( levels[0]))) luma_label.setAlignment(Qt.AlignCenter) modify_font(luma_label, underline=True) luma_table = self.create_table(luma) chroma_label = QLabel( self.tr( 'Chrominance Quantization Table (level = {:.2f}%)\n'.format( levels[1]))) chroma_label.setAlignment(Qt.AlignCenter) modify_font(chroma_label, underline=True) chroma_table = self.create_table(chroma) quality_label = QLabel( self.tr( 'Estimated JPEG quality (last save) = {}%'.format(quality))) modify_font(quality_label, bold=True) deviation_label = QLabel( self.tr('(average deviation from standard tables = {:.2f})'.format( profile[quality]))) modify_font(deviation_label, italic=True) deviation_label.setAlignment(Qt.AlignRight) main_layout = QGridLayout() main_layout.addWidget(luma_label, 0, 0) main_layout.addWidget(luma_table, 1, 0) main_layout.addWidget(chroma_label, 0, 1) main_layout.addWidget(chroma_table, 1, 1) main_layout.addWidget(quality_label, 2, 0) main_layout.addWidget(deviation_label, 2, 1) self.setLayout(main_layout) self.setMinimumSize(890, 270)