Files
BagheeraView/metadatamanager.py
Ignacio Serantes 291f2f9e47 A bunch of changes
2026-03-23 22:50:02 +01:00

188 lines
5.6 KiB
Python

"""
Metadata Manager Module for Bagheera.
This module provides a dedicated class for handling various metadata formats
like EXIF, IPTC, and XMP, using the exiv2 library.
Classes:
MetadataManager: A class with static methods to read metadata from files.
"""
import os
from PySide6.QtDBus import QDBusConnection, QDBusMessage, QDBus
try:
import exiv2
HAVE_EXIV2 = True
except ImportError:
exiv2 = None
HAVE_EXIV2 = False
from utils import preserve_mtime
from constants import RATING_XATTR_NAME, XATTR_NAME
def notify_baloo(path):
"""
Notifies the Baloo file indexer about a file change using DBus.
This is an asynchronous, non-blocking call. It's more efficient than
calling `balooctl` via subprocess.
Args:
path (str): The absolute path of the file that was modified.
"""
if not path:
return
# Use QDBusMessage directly for robust calling
msg = QDBusMessage.createMethodCall(
"org.kde.baloo.file", "/org/kde/baloo/file",
"org.kde.baloo.file.indexer", "indexFile"
)
msg.setArguments([path])
QDBusConnection.sessionBus().call(msg, QDBus.NoBlock)
def load_common_metadata(path):
"""
Loads tag and rating data for a path using extended attributes.
"""
tags = []
raw_tags = XattrManager.get_attribute(path, XATTR_NAME)
if raw_tags:
tags = sorted(list(set(t.strip()
for t in raw_tags.split(',') if t.strip())))
raw_rating = XattrManager.get_attribute(path, RATING_XATTR_NAME, "0")
try:
rating = int(raw_rating)
except ValueError:
rating = 0
return tags, rating
class MetadataManager:
"""Manages reading EXIF, IPTC, and XMP metadata."""
@staticmethod
def read_all_metadata(path):
"""
Reads all available EXIF, IPTC, and XMP metadata from a file.
Args:
path (str): The path to the image file.
Returns:
dict: A dictionary containing all found metadata key-value pairs.
Returns an empty dictionary if exiv2 is not available or on error.
"""
if not HAVE_EXIV2:
return {}
all_metadata = {}
try:
image = exiv2.ImageFactory.open(path)
image.readMetadata()
# EXIF
for datum in image.exifData():
if datum.toString():
all_metadata[datum.key()] = datum.toString()
# IPTC
for datum in image.iptcData():
if datum.toString():
all_metadata[datum.key()] = datum.toString()
# XMP
for datum in image.xmpData():
if datum.toString():
all_metadata[datum.key()] = datum.toString()
except Exception as e:
print(f"Error reading metadata for {path}: {e}")
return all_metadata
class XattrManager:
"""A manager class to handle reading and writing extended attributes (xattrs)."""
@staticmethod
def get_attribute(path_or_fd, attr_name, default_value=""):
"""
Gets a string value from a file's extended attribute. This is a disk read.
Args:
path_or_fd (str or int): The path to the file or a file descriptor.
attr_name (str): The name of the extended attribute.
default_value (any): The value to return if the attribute is not found.
Returns:
str: The attribute value or the default value.
"""
if path_or_fd is None or path_or_fd == "":
return default_value
try:
return os.getxattr(path_or_fd, attr_name).decode('utf-8')
except (OSError, AttributeError):
return default_value
@staticmethod
def set_attribute(file_path, attr_name, value):
"""
Sets a string value for a file's extended attribute.
If the value is None or an empty string, the attribute is removed.
Args:
file_path (str): The path to the file.
attr_name (str): The name of the extended attribute.
value (str or None): The value to set.
Raises:
IOError: If the attribute could not be saved.
"""
if not file_path:
return
try:
with preserve_mtime(file_path):
if value:
os.setxattr(file_path, attr_name, str(value).encode('utf-8'))
else:
try:
os.removexattr(file_path, attr_name)
except OSError:
pass
notify_baloo(file_path)
except Exception as e:
raise IOError(f"Could not save xattr '{attr_name}' "
"for {file_path}: {e}") from e
@staticmethod
def get_all_attributes(path):
"""
Gets all extended attributes for a file as a dictionary.
Args:
path (str): The path to the file.
Returns:
dict: A dictionary mapping attribute names to values.
"""
attributes = {}
if not path:
return attributes
try:
keys = os.listxattr(path)
for key in keys:
try:
val = os.getxattr(path, key)
try:
val_str = val.decode('utf-8')
except UnicodeDecodeError:
val_str = str(val)
attributes[key] = val_str
except (OSError, AttributeError):
pass
except (OSError, AttributeError):
pass
return attributes