adesse

Signed testimonials for certifications.
Log | Files | Refs

commit 616c746b100620cd96f2456f694e156fced617fe
parent 11aedc15e2dede14df11d4a1a370cdd3fc9bfe53
Author: lash <dev@holbrook.no>
Date:   Sun,  6 Sep 2026 14:41:45 -0600

Implement title and description import from exiftool xmp export

Diffstat:
Aadesse/util/xmp.py | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/test_xmp.py | 29+++++++++++++++++++++++++++++
2 files changed, 186 insertions(+), 0 deletions(-)

diff --git a/adesse/util/xmp.py b/adesse/util/xmp.py @@ -0,0 +1,157 @@ +import os +import logging +import hashlib + +import lxml.etree +from rdflib import Graph, Namespace, RDF, URIRef, DC, Literal +from adesse import Attachment + +logg = logging.getLogger('util.xmp') + +EXIFTOOL_XMP = Namespace('http://ns.exiftool.org/XMP/XMP-xmp/1.0/') +EXIFTOOL_XMPPDF = Namespace('http://ns.exiftool.org/XMP/XMP-pdf/1.0/') +EXIFTOOL_XMPDC = Namespace('http://ns.exiftool.org/XMP/XMP-dc/1.0/') +EXIFTOOL_SYSTEM = Namespace('http://ns.exiftool.org/File/System/1.0/') + + + +def _from_xml(item, o, exiftool=False): + parsens = DC + if exiftool: + parsens = EXIFTOOL_XMPDC + title = o.value(item, parsens.term('title')) + if title == None: + title = o.value(item, parsens.term('Title')) + logg.debug('have title {} {} {}'.format(item, parsens, title)) + desc = o.value(item, parsens.term('subject')) + if desc == None: + desc = o.value(item, parsens.term('Subject')) + logg.debug('have desc {} {} {}'.format(item, parsens, desc)) +# o = Attachment(title) + + +def check_file(fp, strict=False): + logg.debug('checi file {}'.format(fp)) + os.stat(fp) + if not strict: + return fp + return fp + + + +def _parse_xmp(xmp_file, resource_file, search_dir=None): + o = lxml.etree.parse(xmp_file) + r = o.getroot() + r = r.find('rdf:RDF', namespaces={'rdf': str(RDF)}) + + f = None + for d in search_dir: + fp = os.path.join(d, resource_file) + try: + f = open(fp, 'rb') + break + except FileNotFoundError: + pass + + if f == None: + raise FileNotFoundError() + + h = hashlib.sha256() + v = f.read() + h.update(v) + f.close() + z = h.digest() + uu = 'URN:sha256:' + z.hex() + + for v in r.findall('rdf:Description', namespaces={'rdf': str(RDF)}): + v.set('{{{}}}about'.format(str(RDF)), uu) + + s = lxml.etree.tostring(r) + return (uu, s,) + + + +def rdfn(v): + s = str(RDF) + '_' + str(int(v)) + return URIRef(s) + + +def _get_alt_lang(sbj, prd, o): + v = o.value(sbj, prd) + typ = o.value(v, RDF.type) + r = None + if typ == RDF.Alt: + i = 1 + while i: + v = o.value(v, rdfn(i)) + if isinstance(v, Literal): + if v.language == 'x-default': + r = v.value + i = 0 + else: + i += 1 + else: + i += 1 + return r + + +def parse_title(sbj, o): + return _get_alt_lang(sbj, DC.title, o) + + +def parse_description(sbj, o): + return _get_alt_lang(sbj, DC.description, o) + + +def attachment_from_xmp(xml_file, resource_file=None, search_dir=None): + if search_dir == None: + search_dir = ['.'] + r = _parse_xmp(xml_file, resource_file=resource_file, search_dir=search_dir) + o = Graph() + sbj = URIRef(r[0]) + o.parse(r[1], format='xml') + title = parse_title(sbj, o) + desc = parse_description(sbj, o) + logg.debug('have title {} desc {}'.format(title, desc)) + + +#def attachment_from_xmp(filepath, search_dir=None): +# if search_dir == None: +# search_dir = ['.'] +# state = { +# 'exif': False, +# 'xmp': False, +# } +# items = [] +# o = Graph() +# r = o.parse(filepath, format='xml') +# #o.bind('XMP-dc', DC, replace=True) +# for v in o.namespace_manager.namespaces(): +# logg.debug('have namespace {}'.format(v)) +# k = v[0] +# if 'ExifTool' in k: +# state['exif'] = True +# if 'xmp' in k: +# state['xmp'] = True +# if not state['xmp']: +# raise AttributeError('not an xmp doument') +# for v in o.subjects(): +# fp = None +# for d in search_dir: +# fp = os.path.join(d, v) +# try: +# fp = check_file(fp) +# break +# except FileNotFoundError: +# fp = None +# if fp == None: +# continue +# +# if v not in items: +# items.append(v) +# logg.debug('added subject {} {}'.format(v, fp)) +# +# r = [] +# for v in items: +# r.append(_from_xml(v, o, exiftool=state['exif'])) +# return r diff --git a/test/test_xmp.py b/test/test_xmp.py @@ -0,0 +1,29 @@ +import unittest +import datetime +import hashlib +import os +import logging + +from rdflib.term import URIRef + +from adesse.ns import XMP +from adesse.util import xmp + +logging.basicConfig(level=logging.DEBUG) + +thisdir = os.path.realpath(os.path.dirname(__file__)) + + +class TestXmpLoad(unittest.TestCase): + + def setUp(self): + pass + + + def test_parse(self): + fp = os.path.join(thisdir, 'import.xmp') + xmp.attachment_from_xmp(fp, 'import.pdf', search_dir=[thisdir]) + + +if __name__ == '__main__': + unittest.main()