v0.9.24
This commit is contained in:
@@ -14,7 +14,7 @@ Classes:
|
|||||||
MainWindow: The main application window containing the thumbnail grid and docks.
|
MainWindow: The main application window containing the thumbnail grid and docks.
|
||||||
"""
|
"""
|
||||||
__appname__ = "BagheeraView"
|
__appname__ = "BagheeraView"
|
||||||
__version__ = "0.9.23"
|
__version__ = "0.9.24"
|
||||||
__author__ = "Ignacio Serantes"
|
__author__ = "Ignacio Serantes"
|
||||||
__email__ = "kde@aynoa.net"
|
__email__ = "kde@aynoa.net"
|
||||||
__license__ = "LGPL"
|
__license__ = "LGPL"
|
||||||
|
|||||||
31
constants.py
31
constants.py
@@ -29,7 +29,7 @@ if FORCE_X11:
|
|||||||
# --- CONFIGURATION ---
|
# --- CONFIGURATION ---
|
||||||
PROG_NAME = "Bagheera Image Viewer"
|
PROG_NAME = "Bagheera Image Viewer"
|
||||||
PROG_ID = "bagheeraview"
|
PROG_ID = "bagheeraview"
|
||||||
PROG_VERSION = "0.9.23"
|
PROG_VERSION = "0.9.24"
|
||||||
PROG_AUTHOR = "Ignacio Serantes"
|
PROG_AUTHOR = "Ignacio Serantes"
|
||||||
|
|
||||||
# --- CACHE SETTINGS ---
|
# --- CACHE SETTINGS ---
|
||||||
@@ -57,17 +57,21 @@ DISK_CACHE_MAX_BYTES = 10 * 1024 * 1024 * 1024
|
|||||||
|
|
||||||
# --- PATHS ---
|
# --- PATHS ---
|
||||||
CONFIG_FILE = f"{PROG_ID}rc"
|
CONFIG_FILE = f"{PROG_ID}rc"
|
||||||
CONFIG_LOCATION = '.config/iserantes'
|
CONFIG_LOCATION = os.environ.get('XDG_CONFIG_HOME')
|
||||||
CONFIG_DIR = os.path.join(os.path.expanduser("~"), CONFIG_LOCATION, PROG_ID)
|
CONFIG_DIR = os.path.join(CONFIG_LOCATION, 'iserantes', PROG_ID)
|
||||||
CONFIG_PATH = os.path.join(CONFIG_DIR, CONFIG_FILE)
|
CONFIG_PATH = os.path.join(CONFIG_DIR, CONFIG_FILE)
|
||||||
CACHE_PATH = os.path.join(CONFIG_DIR, "thumbnails")
|
|
||||||
|
APP_DATA_LOCATION = os.path.expanduser('~/.local/share')
|
||||||
|
APP_DATA_DIR = os.path.join(APP_DATA_LOCATION, 'iserantes', PROG_ID)
|
||||||
|
|
||||||
|
CACHE_PATH = os.path.join(APP_DATA_DIR, "thumbnails")
|
||||||
|
|
||||||
HISTORY_FILE = "history.json"
|
HISTORY_FILE = "history.json"
|
||||||
HISTORY_PATH = os.path.join(CONFIG_DIR, HISTORY_FILE)
|
HISTORY_PATH = os.path.join(APP_DATA_DIR, HISTORY_FILE)
|
||||||
LAYOUTS_DIR = os.path.join(CONFIG_DIR, "layouts") # Layouts saving directory
|
LAYOUTS_DIR = os.path.join(APP_DATA_DIR, "layouts") # Layouts saving directory
|
||||||
FAVORITES_FILE = "favorites.json"
|
FAVORITES_FILE = "favorites.json"
|
||||||
FAVORITES_PATH = os.path.join(CONFIG_DIR, FAVORITES_FILE)
|
FAVORITES_PATH = os.path.join(APP_DATA_DIR, FAVORITES_FILE)
|
||||||
DUPLICATE_CACHE_PATH = os.path.join(CONFIG_DIR, "duplicates")
|
DUPLICATE_CACHE_PATH = os.path.join(APP_DATA_DIR, "duplicates")
|
||||||
DUPLICATE_HASH_DB_NAME = b"hashes"
|
DUPLICATE_HASH_DB_NAME = b"hashes"
|
||||||
DUPLICATE_EXCEPTIONS_DB_NAME = b"exceptions"
|
DUPLICATE_EXCEPTIONS_DB_NAME = b"exceptions"
|
||||||
DUPLICATE_PENDING_DB_NAME = b"pending"
|
DUPLICATE_PENDING_DB_NAME = b"pending"
|
||||||
@@ -204,15 +208,15 @@ if importlib.util.find_spec("mediapipe") is not None:
|
|||||||
pass
|
pass
|
||||||
HAVE_FACE_RECOGNITION = importlib.util.find_spec("face_recognition") is not None
|
HAVE_FACE_RECOGNITION = importlib.util.find_spec("face_recognition") is not None
|
||||||
|
|
||||||
MEDIAPIPE_FACE_MODEL_PATH = os.path.join(CONFIG_DIR,
|
MEDIAPIPE_FACE_MODEL_PATH = os.path.join(
|
||||||
"blaze_face_short_range.tflite")
|
APP_DATA_DIR, "blaze_face_short_range.tflite")
|
||||||
MEDIAPIPE_FACE_MODEL_URL = (
|
MEDIAPIPE_FACE_MODEL_URL = (
|
||||||
"https://storage.googleapis.com/mediapipe-models/face_detector/"
|
"https://storage.googleapis.com/mediapipe-models/face_detector/"
|
||||||
"blaze_face_short_range/float16/1/blaze_face_short_range.tflite"
|
"blaze_face_short_range/float16/1/blaze_face_short_range.tflite"
|
||||||
)
|
)
|
||||||
|
|
||||||
MEDIAPIPE_OBJECT_MODEL_PATH = os.path.join(CONFIG_DIR,
|
MEDIAPIPE_OBJECT_MODEL_PATH = os.path.join(
|
||||||
"efficientdet_lite0.tflite")
|
APP_DATA_DIR, "efficientdet_lite0.tflite")
|
||||||
MEDIAPIPE_OBJECT_MODEL_URL = (
|
MEDIAPIPE_OBJECT_MODEL_URL = (
|
||||||
"https://storage.googleapis.com/mediapipe-models/object_detector/"
|
"https://storage.googleapis.com/mediapipe-models/object_detector/"
|
||||||
"efficientdet_lite0/float16/1/efficientdet_lite0.tflite"
|
"efficientdet_lite0/float16/1/efficientdet_lite0.tflite"
|
||||||
@@ -782,6 +786,7 @@ _UI_TEXTS = {
|
|||||||
"RENAME_ERROR_EXISTS": "File '{}' already exists.",
|
"RENAME_ERROR_EXISTS": "File '{}' already exists.",
|
||||||
"FILE_RENAMED": "File renamed to {}",
|
"FILE_RENAMED": "File renamed to {}",
|
||||||
"ERROR_RENAME": "Could not rename file: {}",
|
"ERROR_RENAME": "Could not rename file: {}",
|
||||||
|
"ERROR_JPEG_METADATA_LIMIT": "Metadata size limit exceeded for '{}'. This JPEG file has too much existing metadata (XMP) to save more.",
|
||||||
"MAIN_DOCK_TITLE": "",
|
"MAIN_DOCK_TITLE": "",
|
||||||
"LAYOUTS_TAB": "Layouts",
|
"LAYOUTS_TAB": "Layouts",
|
||||||
"LAYOUTS_TABLE_HEADER": ["Name", "Last Modified"],
|
"LAYOUTS_TABLE_HEADER": ["Name", "Last Modified"],
|
||||||
@@ -1333,6 +1338,7 @@ _UI_TEXTS = {
|
|||||||
"RENAME_ERROR_EXISTS": "El archivo '{}' ya existe.",
|
"RENAME_ERROR_EXISTS": "El archivo '{}' ya existe.",
|
||||||
"FILE_RENAMED": "Archivo renombrado a {}",
|
"FILE_RENAMED": "Archivo renombrado a {}",
|
||||||
"ERROR_RENAME": "No se pudo renombrar el archivo: {}",
|
"ERROR_RENAME": "No se pudo renombrar el archivo: {}",
|
||||||
|
"ERROR_JPEG_METADATA_LIMIT": "Límite de metadatos excedido para '{}'. Este archivo JPEG ya tiene demasiados metadatos (XMP) para guardar más.",
|
||||||
"MAIN_DOCK_TITLE": "Panel principal",
|
"MAIN_DOCK_TITLE": "Panel principal",
|
||||||
"LAYOUTS_TAB": "Diseños",
|
"LAYOUTS_TAB": "Diseños",
|
||||||
"LAYOUTS_TABLE_HEADER": ["Nombre", "Última Modificación"],
|
"LAYOUTS_TABLE_HEADER": ["Nombre", "Última Modificación"],
|
||||||
@@ -1885,6 +1891,7 @@ _UI_TEXTS = {
|
|||||||
"RENAME_ERROR_EXISTS": "O ficheiro '{}' xa existe.",
|
"RENAME_ERROR_EXISTS": "O ficheiro '{}' xa existe.",
|
||||||
"FILE_RENAMED": "Ficheiro renomeado a {}",
|
"FILE_RENAMED": "Ficheiro renomeado a {}",
|
||||||
"ERROR_RENAME": "Non se puido renomear o ficheiro: {}",
|
"ERROR_RENAME": "Non se puido renomear o ficheiro: {}",
|
||||||
|
"ERROR_JPEG_METADATA_LIMIT": "Límite de metadatos excedido para '{}'. Este ficheiro JPEG xa ten demasiados metadatos (XMP) para gardar máis.",
|
||||||
"MAIN_DOCK_TITLE": "Panel principal",
|
"MAIN_DOCK_TITLE": "Panel principal",
|
||||||
"LAYOUTS_TAB": "Deseños",
|
"LAYOUTS_TAB": "Deseños",
|
||||||
"LAYOUTS_TABLE_HEADER": ["Nome", "Última Modificación"],
|
"LAYOUTS_TABLE_HEADER": ["Nome", "Última Modificación"],
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ class ImageController(QObject):
|
|||||||
|
|
||||||
faces_to_save.append(face_copy)
|
faces_to_save.append(face_copy)
|
||||||
|
|
||||||
XmpManager.save_faces(path, faces_to_save)
|
return XmpManager.save_faces(path, faces_to_save)
|
||||||
|
|
||||||
def add_face(self, name, x, y, w, h, region_type="Face"):
|
def add_face(self, name, x, y, w, h, region_type="Face"):
|
||||||
"""Adds a new face. The full tag path should be passed as 'name'."""
|
"""Adds a new face. The full tag path should be passed as 'name'."""
|
||||||
@@ -390,8 +390,8 @@ class ImageController(QObject):
|
|||||||
self.metadata_changed.emit(current_path,
|
self.metadata_changed.emit(current_path,
|
||||||
{'tags': new_tags_list,
|
{'tags': new_tags_list,
|
||||||
'rating': self._current_rating})
|
'rating': self._current_rating})
|
||||||
except IOError as e:
|
except Exception:
|
||||||
print(f"Error setting tags for {current_path}: {e}")
|
raise
|
||||||
|
|
||||||
def set_rating(self, new_rating):
|
def set_rating(self, new_rating):
|
||||||
current_path = self.get_current_path()
|
current_path = self.get_current_path()
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ from PySide6.QtGui import QImage, QImageReader, QImageIOHandler, QIcon
|
|||||||
from PySide6.QtWidgets import QApplication
|
from PySide6.QtWidgets import QApplication
|
||||||
|
|
||||||
from constants import (
|
from constants import (
|
||||||
APP_CONFIG, CACHE_PATH, CACHE_MAX_SIZE, CACHE_MAX_RAM_BYTES, CONFIG_DIR,
|
APP_CONFIG, CACHE_PATH, CACHE_MAX_SIZE, CACHE_MAX_RAM_BYTES,
|
||||||
MIN_FREE_RAM_PERCENT, DISK_CACHE_MAX_BYTES, HAVE_BAGHEERASEARCH_LIB,
|
APP_DATA_DIR, MIN_FREE_RAM_PERCENT, DISK_CACHE_MAX_BYTES,
|
||||||
IMAGE_EXTENSIONS,
|
HAVE_BAGHEERASEARCH_LIB, IMAGE_EXTENSIONS,
|
||||||
SCANNER_SETTINGS_DEFAULTS, SEARCH_CMD, THUMBNAIL_SIZES,
|
SCANNER_SETTINGS_DEFAULTS, SEARCH_CMD, THUMBNAIL_SIZES,
|
||||||
UITexts
|
UITexts
|
||||||
)
|
)
|
||||||
@@ -543,7 +543,7 @@ class ThumbnailCache(QObject):
|
|||||||
|
|
||||||
def lmdb_open(self):
|
def lmdb_open(self):
|
||||||
# Initialize LMDB environment
|
# Initialize LMDB environment
|
||||||
cache_dir = Path(CONFIG_DIR)
|
cache_dir = Path(APP_DATA_DIR)
|
||||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -239,7 +239,10 @@ class FastTagManager:
|
|||||||
current_path = controller.get_current_path() if controller else None
|
current_path = controller.get_current_path() if controller else None
|
||||||
if not current_path:
|
if not current_path:
|
||||||
return
|
return
|
||||||
|
try:
|
||||||
controller.toggle_tag(tag_name, is_checked)
|
controller.toggle_tag(tag_name, is_checked)
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self.viewer, UITexts.ERROR, str(e))
|
||||||
self.viewer.update_status_bar()
|
self.viewer.update_status_bar()
|
||||||
if self.main_win:
|
if self.main_win:
|
||||||
if is_checked:
|
if is_checked:
|
||||||
@@ -2836,8 +2839,11 @@ class ImageViewer(QWidget):
|
|||||||
self.main_win.face_names_history = updated_history
|
self.main_win.face_names_history = updated_history
|
||||||
|
|
||||||
# Save changes and add new tag
|
# Save changes and add new tag
|
||||||
|
try:
|
||||||
self.controller.save_faces()
|
self.controller.save_faces()
|
||||||
self.controller.toggle_tag(new_full_tag, True)
|
self.controller.toggle_tag(new_full_tag, True)
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, UITexts.ERROR, str(e))
|
||||||
if self.canvas:
|
if self.canvas:
|
||||||
self.canvas.update()
|
self.canvas.update()
|
||||||
|
|
||||||
@@ -3137,7 +3143,10 @@ class ImageViewer(QWidget):
|
|||||||
self.canvas.update()
|
self.canvas.update()
|
||||||
|
|
||||||
if added_count > 0:
|
if added_count > 0:
|
||||||
|
try:
|
||||||
self.controller.save_faces()
|
self.controller.save_faces()
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, UITexts.ERROR, str(e))
|
||||||
|
|
||||||
def run_pet_detection(self):
|
def run_pet_detection(self):
|
||||||
"""Runs pet detection on the current image."""
|
"""Runs pet detection on the current image."""
|
||||||
@@ -3196,7 +3205,10 @@ class ImageViewer(QWidget):
|
|||||||
self.canvas.update()
|
self.canvas.update()
|
||||||
|
|
||||||
if added_count > 0:
|
if added_count > 0:
|
||||||
|
try:
|
||||||
self.controller.save_faces()
|
self.controller.save_faces()
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, UITexts.ERROR, str(e))
|
||||||
|
|
||||||
def run_body_detection(self):
|
def run_body_detection(self):
|
||||||
"""Runs body detection on the current image."""
|
"""Runs body detection on the current image."""
|
||||||
@@ -3257,7 +3269,10 @@ class ImageViewer(QWidget):
|
|||||||
self.canvas.update()
|
self.canvas.update()
|
||||||
|
|
||||||
if added_count > 0:
|
if added_count > 0:
|
||||||
|
try:
|
||||||
self.controller.save_faces()
|
self.controller.save_faces()
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, UITexts.ERROR, str(e))
|
||||||
|
|
||||||
def toggle_filmstrip(self):
|
def toggle_filmstrip(self):
|
||||||
"""Shows or hides the filmstrip widget."""
|
"""Shows or hides the filmstrip widget."""
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ Classes:
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import collections
|
import collections
|
||||||
|
import logging
|
||||||
from PySide6.QtDBus import QDBusConnection, QDBusMessage, QDBus
|
from PySide6.QtDBus import QDBusConnection, QDBusMessage, QDBus
|
||||||
try:
|
try:
|
||||||
import exiv2
|
import exiv2
|
||||||
@@ -21,6 +22,8 @@ except ImportError:
|
|||||||
from utils import preserve_mtime
|
from utils import preserve_mtime
|
||||||
from constants import RATING_XATTR_NAME, XATTR_NAME
|
from constants import RATING_XATTR_NAME, XATTR_NAME
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_app_modified_callback = None
|
_app_modified_callback = None
|
||||||
|
|
||||||
MetadataResult = collections.namedtuple('MetadataResult', ['tags', 'rating'])
|
MetadataResult = collections.namedtuple('MetadataResult', ['tags', 'rating'])
|
||||||
@@ -141,6 +144,31 @@ class MetadataManager:
|
|||||||
iptc = image.iptcData()
|
iptc = image.iptcData()
|
||||||
xmp = image.xmpData()
|
xmp = image.xmpData()
|
||||||
|
|
||||||
|
# Remove keys that are no longer in the dictionary
|
||||||
|
containers = [
|
||||||
|
(exif, exiv2.ExifKey, "Exif."),
|
||||||
|
(iptc, exiv2.IptcKey, "Iptc."),
|
||||||
|
(xmp, exiv2.XmpKey, "Xmp.")
|
||||||
|
]
|
||||||
|
|
||||||
|
for container, key_class, prefix in containers:
|
||||||
|
keys_to_remove = []
|
||||||
|
for datum in container:
|
||||||
|
k = datum.key()
|
||||||
|
# Only consider keys belonging to this specific container
|
||||||
|
if k.startswith(prefix) and k not in metadata_dict:
|
||||||
|
keys_to_remove.append(k)
|
||||||
|
|
||||||
|
for key in keys_to_remove:
|
||||||
|
try:
|
||||||
|
x_key = key_class(key)
|
||||||
|
it = container.findKey(x_key)
|
||||||
|
if it != container.end():
|
||||||
|
container.erase(it)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error removing metadata key {key}: {e}")
|
||||||
|
|
||||||
|
# Set or update values from the dictionary
|
||||||
for key, value in metadata_dict.items():
|
for key, value in metadata_dict.items():
|
||||||
try:
|
try:
|
||||||
if key.startswith("Exif."):
|
if key.startswith("Exif."):
|
||||||
@@ -156,7 +184,13 @@ class MetadataManager:
|
|||||||
notify_baloo(path)
|
notify_baloo(path)
|
||||||
mark_app_modified(path)
|
mark_app_modified(path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error writing metadata for {path}: {e}")
|
error_msg = str(e)
|
||||||
|
if "kerTooLargeJpegSegment" in error_msg or "38" in error_msg:
|
||||||
|
msg = UITexts.ERROR_JPEG_METADATA_LIMIT.format(os.path.basename(path))
|
||||||
|
logger.error(msg)
|
||||||
|
raise IOError(msg) from e
|
||||||
|
logger.error(f"Error writing metadata for {path}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
class XattrManager:
|
class XattrManager:
|
||||||
|
|||||||
@@ -139,7 +139,8 @@ class PropertiesDialog(QDialog):
|
|||||||
meta_layout = QVBoxLayout(meta_widget)
|
meta_layout = QVBoxLayout(meta_widget)
|
||||||
|
|
||||||
self.meta_toolbar = QToolBar()
|
self.meta_toolbar = QToolBar()
|
||||||
self._setup_table_toolbar(self.meta_toolbar, self.on_add_meta, self.on_delete_meta,
|
self._setup_table_toolbar(
|
||||||
|
self.meta_toolbar, self.on_add_meta, self.on_delete_meta,
|
||||||
self.on_delete_all_meta, self.on_save_meta, self.on_cancel_meta)
|
self.on_delete_all_meta, self.on_save_meta, self.on_cancel_meta)
|
||||||
meta_layout.addWidget(self.meta_toolbar)
|
meta_layout.addWidget(self.meta_toolbar)
|
||||||
|
|
||||||
@@ -172,7 +173,8 @@ class PropertiesDialog(QDialog):
|
|||||||
exif_layout = QVBoxLayout(exif_widget)
|
exif_layout = QVBoxLayout(exif_widget)
|
||||||
|
|
||||||
self.exif_toolbar = QToolBar()
|
self.exif_toolbar = QToolBar()
|
||||||
self._setup_table_toolbar(self.exif_toolbar, self.on_add_exif, self.on_delete_exif,
|
self._setup_table_toolbar(
|
||||||
|
self.exif_toolbar, self.on_add_exif, self.on_delete_exif,
|
||||||
self.on_delete_all_exif, self.on_save_exif, self.on_cancel_exif)
|
self.on_delete_all_exif, self.on_save_exif, self.on_cancel_exif)
|
||||||
exif_layout.addWidget(self.exif_toolbar)
|
exif_layout.addWidget(self.exif_toolbar)
|
||||||
|
|
||||||
@@ -219,17 +221,21 @@ class PropertiesDialog(QDialog):
|
|||||||
# Start background loading
|
# Start background loading
|
||||||
self.reload_metadata()
|
self.reload_metadata()
|
||||||
|
|
||||||
def _setup_table_toolbar(self, toolbar, add_slot, del_slot, del_all_slot, save_slot, cancel_slot):
|
def _setup_table_toolbar(self, toolbar, add_slot, del_slot, del_all_slot, save_slot,
|
||||||
|
cancel_slot):
|
||||||
"""Helper to populate toolbars with buttons."""
|
"""Helper to populate toolbars with buttons."""
|
||||||
toolbar.addAction(QIcon.fromTheme("list-add"), UITexts.CREATE, add_slot)
|
toolbar.addAction(QIcon.fromTheme("list-add"), UITexts.CREATE, add_slot)
|
||||||
toolbar.addAction(QIcon.fromTheme("list-remove"), UITexts.DELETE, del_slot)
|
toolbar.addAction(QIcon.fromTheme("list-remove"), UITexts.DELETE, del_slot)
|
||||||
toolbar.addAction(QIcon.fromTheme("edit-clear-all"), UITexts.PROPERTIES_DELETE_ALL, del_all_slot)
|
toolbar.addAction(
|
||||||
|
QIcon.fromTheme("edit-clear-all"), UITexts.PROPERTIES_DELETE_ALL,
|
||||||
|
del_all_slot)
|
||||||
toolbar.addSeparator()
|
toolbar.addSeparator()
|
||||||
toolbar.addAction(QIcon.fromTheme("document-save"), UITexts.SAVE, save_slot)
|
toolbar.addAction(QIcon.fromTheme("document-save"), UITexts.SAVE, save_slot)
|
||||||
toolbar.addAction(QIcon.fromTheme("edit-undo"), UITexts.CANCEL, cancel_slot)
|
toolbar.addAction(QIcon.fromTheme("edit-undo"), UITexts.CANCEL, cancel_slot)
|
||||||
|
|
||||||
def on_add_meta(self):
|
def on_add_meta(self):
|
||||||
key, ok = QInputDialog.getText(self, UITexts.PROPERTIES_ADD_ATTR, UITexts.PROPERTIES_ADD_ATTR_NAME)
|
key, ok = QInputDialog.getText(
|
||||||
|
self, UITexts.PROPERTIES_ADD_ATTR, UITexts.PROPERTIES_ADD_ATTR_NAME)
|
||||||
if ok and key:
|
if ok and key:
|
||||||
row = self.table.rowCount()
|
row = self.table.rowCount()
|
||||||
self.table.insertRow(row)
|
self.table.insertRow(row)
|
||||||
@@ -240,7 +246,8 @@ class PropertiesDialog(QDialog):
|
|||||||
self.table.editItem(v_item)
|
self.table.editItem(v_item)
|
||||||
|
|
||||||
def on_delete_meta(self):
|
def on_delete_meta(self):
|
||||||
rows = sorted(set(index.row() for index in self.table.selectedIndexes()), reverse=True)
|
rows = sorted(set(index.row() for index in self.table.selectedIndexes()),
|
||||||
|
reverse=True)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
self.table.removeRow(row)
|
self.table.removeRow(row)
|
||||||
|
|
||||||
@@ -261,13 +268,14 @@ class PropertiesDialog(QDialog):
|
|||||||
XattrManager.set_attribute(self.path, k, v)
|
XattrManager.set_attribute(self.path, k, v)
|
||||||
self.reload_metadata()
|
self.reload_metadata()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.warning(self, UITexts.ERROR, f"Error: {e}")
|
QMessageBox.critical(self, UITexts.ERROR, str(e))
|
||||||
|
|
||||||
def on_cancel_meta(self):
|
def on_cancel_meta(self):
|
||||||
self.update_metadata_table(self.original_xattrs)
|
self.update_metadata_table(self.original_xattrs)
|
||||||
|
|
||||||
def on_add_exif(self):
|
def on_add_exif(self):
|
||||||
key, ok = QInputDialog.getText(self, UITexts.PROPERTIES_ADD_ATTR, UITexts.PROPERTIES_ADD_ATTR_NAME)
|
key, ok = QInputDialog.getText(
|
||||||
|
self, UITexts.PROPERTIES_ADD_ATTR, UITexts.PROPERTIES_ADD_ATTR_NAME)
|
||||||
if ok and key:
|
if ok and key:
|
||||||
row = self.exif_table.rowCount()
|
row = self.exif_table.rowCount()
|
||||||
self.exif_table.insertRow(row)
|
self.exif_table.insertRow(row)
|
||||||
@@ -278,7 +286,9 @@ class PropertiesDialog(QDialog):
|
|||||||
self.exif_table.editItem(v_item)
|
self.exif_table.editItem(v_item)
|
||||||
|
|
||||||
def on_delete_exif(self):
|
def on_delete_exif(self):
|
||||||
rows = sorted(set(index.row() for index in self.exif_table.selectedIndexes()), reverse=True)
|
rows = sorted(
|
||||||
|
set(index.row() for index in self.exif_table.selectedIndexes()),
|
||||||
|
reverse=True)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
self.exif_table.removeRow(row)
|
self.exif_table.removeRow(row)
|
||||||
|
|
||||||
@@ -295,7 +305,7 @@ class PropertiesDialog(QDialog):
|
|||||||
MetadataManager.write_metadata(self.path, new_exif)
|
MetadataManager.write_metadata(self.path, new_exif)
|
||||||
self.reload_metadata()
|
self.reload_metadata()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.warning(self, UITexts.ERROR, f"Error: {e}")
|
QMessageBox.critical(self, UITexts.ERROR, str(e))
|
||||||
|
|
||||||
def on_cancel_exif(self):
|
def on_cancel_exif(self):
|
||||||
self.update_exif_table(self.original_exif)
|
self.update_exif_table(self.original_exif)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "bagheeraview"
|
name = "bagheeraview"
|
||||||
version = "0.9.23"
|
version = "0.9.24"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Ignacio Serantes" }
|
{ name = "Ignacio Serantes" }
|
||||||
]
|
]
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="bagheeraview",
|
name="bagheeraview",
|
||||||
version="0.9.23",
|
version="0.9.24",
|
||||||
author="Ignacio Serantes",
|
author="Ignacio Serantes",
|
||||||
description="Bagheera Image Viewer - An image viewer for KDE with Baloo in mind",
|
description="Bagheera Image Viewer - An image viewer for KDE with Baloo in mind",
|
||||||
long_description="A fast image viewer built with PySide6, featuring search and "
|
long_description="A fast image viewer built with PySide6, featuring search and "
|
||||||
|
|||||||
@@ -17,13 +17,17 @@ Dependencies:
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
from utils import preserve_mtime
|
from utils import preserve_mtime
|
||||||
from metadatamanager import notify_baloo, mark_app_modified
|
from metadatamanager import notify_baloo, mark_app_modified
|
||||||
|
from constants import UITexts
|
||||||
try:
|
try:
|
||||||
import exiv2
|
import exiv2
|
||||||
except ImportError:
|
except ImportError:
|
||||||
exiv2 = None
|
exiv2 = None
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class XmpManager:
|
class XmpManager:
|
||||||
"""
|
"""
|
||||||
@@ -167,5 +171,10 @@ class XmpManager:
|
|||||||
mark_app_modified(path)
|
mark_app_modified(path)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error saving faces to XMP: {e}")
|
error_msg = str(e)
|
||||||
return False
|
if "kerTooLargeJpegSegment" in error_msg or "38" in error_msg:
|
||||||
|
msg = UITexts.ERROR_JPEG_METADATA_LIMIT.format(os.path.basename(path))
|
||||||
|
logger.error(msg)
|
||||||
|
raise IOError(msg) from e
|
||||||
|
logger.error(f"Error saving faces to XMP: {e}")
|
||||||
|
raise
|
||||||
|
|||||||
Reference in New Issue
Block a user