Files
EricRollei-AAA_Metadata_System/eric_metadata/utils/namespace.py
T

236 lines
8.7 KiB
Python

"""
namespace.py
Description: Handles XML namespaces for metadata operations
This module is responsible for managing the registration of XML namespaces
Author: Eric Hiss (GitHub: EricRollei)
Contact: [eric@historic.camera, eric@rollei.us]
Version: 1.0.0
Date: [March 2025]
License: Dual License (Non-Commercial and Commercial Use)
Copyright (c) 2025 Eric Hiss. All rights reserved.
Dual License:
1. Non-Commercial Use: This software is licensed under the terms of the
Creative Commons Attribution-NonCommercial 4.0 International License.
To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/
2. Commercial Use: For commercial use, a separate license is required.
Please contact Eric Hiss at [eric@historic.camera, eric@rollei.us] for licensing options.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT.
Dependencies:
This code depends on several third-party libraries, each with its own license:
"""
# Metadata_system/src/eric_metadata/utils/namespace.py
import os
import sys
import importlib
from typing import Dict, Optional, Tuple, List
class NamespaceManager:
"""Manages XML namespaces for metadata handling"""
# Core namespaces required for metadata operations
NAMESPACES = {
'x': 'adobe:ns:meta/',
'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
'dc': 'http://purl.org/dc/elements/1.1/',
'xmp': 'http://ns.adobe.com/xap/1.0/',
'photoshop': 'http://ns.adobe.com/photoshop/1.0/',
'mwg-rs': 'http://www.metadataworkinggroup.com/schemas/regions/',
'stArea': 'http://ns.adobe.com/xmp/sType/Area#',
'Iptc4xmpCore': 'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/',
'eiqa': 'http://ericproject.org/schemas/eiqa/1.0/',
'ai': 'http://ericproject.org/schemas/ai/1.0/',
'xml': 'http://www.w3.org/XML/1998/namespace',
'mwg-kw': 'http://www.metadataworkinggroup.com/schemas/keywords/'
}
@classmethod
def register_with_pyexiv2(cls, debug: bool = False) -> bool:
"""
Register all namespaces with PyExiv2
Args:
debug: Whether to enable debug output
Returns:
bool: True if at least one namespace was registered
"""
success_count = 0
# Get PyExiv2 module
pyexiv2 = cls.get_pyexiv2_module()
if not pyexiv2:
if debug:
print("PyExiv2 module not found")
return False
# Method 0: Top-level registerNs function (for 2.15.x)
if hasattr(pyexiv2, 'registerNs'):
try:
for prefix, uri in cls.NAMESPACES.items():
try:
pyexiv2.registerNs(uri, prefix)
success_count += 1
if debug:
print(f"Registered namespace {prefix} with registerNs")
except Exception as e:
if debug:
print(f"registerNs failed for {prefix}: {e}")
except Exception as e:
if debug:
print(f"registerNs global error: {e}")
# Continue with other methods if top-level method failed
if success_count == 0:
# Try the other methods as fallbacks...
# Method 1: Direct module function (old API)
if hasattr(pyexiv2, 'registerNamespace'):
try:
for prefix, uri in cls.NAMESPACES.items():
try:
pyexiv2.registerNamespace(uri, prefix)
success_count += 1
if debug:
print(f"Registered namespace {prefix} with method 1")
except Exception as e:
if debug:
print(f"Method 1 failed for {prefix}: {e}")
except Exception as e:
if debug:
print(f"Method 1 global error: {e}")
# Method 2: xmp module function (newer API)
if hasattr(pyexiv2, 'xmp'):
try:
for prefix, uri in cls.NAMESPACES.items():
try:
if hasattr(pyexiv2.xmp, 'register_namespace'):
pyexiv2.xmp.register_namespace(uri, prefix)
success_count += 1
if debug:
print(f"Registered namespace {prefix} with method 2")
except Exception as e:
if debug:
print(f"Method 2 failed for {prefix}: {e}")
except Exception as e:
if debug:
print(f"Method 2 global error: {e}")
# Method 3: exiv2api module function
if hasattr(pyexiv2, 'exiv2api'):
try:
for prefix, uri in cls.NAMESPACES.items():
try:
if hasattr(pyexiv2.exiv2api, 'registerNamespace'):
pyexiv2.exiv2api.registerNamespace(uri, prefix)
success_count += 1
if debug:
print(f"Registered namespace {prefix} with method 3")
except Exception as e:
if debug:
print(f"Method 3 failed for {prefix}: {e}")
except Exception as e:
if debug:
print(f"Method 3 global error: {e}")
return success_count > 0
@classmethod
def get_pyexiv2_module(cls) -> Optional[object]:
"""
Get the PyExiv2 module using various methods
Returns:
object or None: The PyExiv2 module if found
"""
# First try direct import
try:
import pyexiv2
return pyexiv2
except ImportError:
pass
# Try py3exiv2 which is sometimes imported as pyexiv2
try:
import py3exiv2
return py3exiv2
except ImportError:
pass
# Try dynamic import
try:
pyexiv2 = importlib.import_module('pyexiv2')
return pyexiv2
except ImportError:
try:
py3exiv2 = importlib.import_module('py3exiv2')
return py3exiv2
except ImportError:
pass
# Look for pyexiv2 in any loaded modules
for name, module in sys.modules.items():
if 'pyexiv2' in name or 'exiv2' in name:
return module
# Not found
return None
@classmethod
def create_exiftool_config(cls, output_path: Optional[str] = None) -> str:
"""
Create ExifTool configuration file with custom namespaces
Args:
output_path: Optional path to write the config file
If None, writes to the current directory
Returns:
str: Path to the created config file
"""
# Generate namespace configuration
ns_config = []
for prefix, uri in cls.NAMESPACES.items():
ns_config.append(f' NAMESPACE => {{ "{prefix}" => "{uri}" }}')
# Create config file content
config = f'''
%Image::ExifTool::UserDefined = (
'Image::ExifTool::XMP::eiqa' => {{
GROUPS => {{ 0 => 'XMP', 1 => 'XMP-eiqa', 2 => 'Image' }},
NAMESPACE => {{ 'eiqa' => '{cls.NAMESPACES["eiqa"]}' }},
WRITABLE => 'string',
noise_analysis => {{ }},
blur_analysis => {{ }},
quality_sort => {{ }},
technical => {{ }}
}},
'Image::ExifTool::XMP::ai' => {{
GROUPS => {{ 0 => 'XMP', 1 => 'XMP-ai', 2 => 'Image' }},
NAMESPACE => {{ 'ai' => '{cls.NAMESPACES["ai"]}' }},
WRITABLE => 'string',
aesthetic => {{ }},
semantic => {{ }},
clip_iqa => {{ }},
nima => {{ }}
}}
);
1;
'''
# Determine output path
if output_path is None:
output_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'exiftool_config.conf')
# Write config file
with open(output_path, 'w', encoding='utf-8') as f:
f.write(config)
return output_path