236 lines
8.7 KiB
Python
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 |