First commit
This commit is contained in:
138
metadatamanager.py
Normal file
138
metadatamanager.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user