X-Git-Url: https://scm.cri.minesparis.psl.eu/git/Photo.git/blobdiff_plain/b0a7e10b4f32cf74864bb53268ca4d3080f23bc0..6c41809185e322ce2d30e98234f71144f78f06c0:/metadata.py?ds=sidebyside diff --git a/metadata.py b/metadata.py deleted file mode 100755 index 310392b..0000000 --- a/metadata.py +++ /dev/null @@ -1,339 +0,0 @@ -# -*- coding: utf-8 -*- -####################################################################################### -# Photo is a part of Plinn - http://plinn.org # -# Copyright © 2004-2008 Benoît PIN # -# # -# This program is free software; you can redistribute it and/or # -# modify it under the terms of the GNU General Public License # -# as published by the Free Software Foundation; either version 2 # -# of the License, or (at your option) any later version. # -# # -# This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program; if not, write to the Free Software # -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # -####################################################################################### -""" Photo metadata read / write module - - - -""" - -from AccessControl import ClassSecurityInfo -from Acquisition import aq_base -from Globals import InitializeClass -from AccessControl.Permissions import view -from ZODB.interfaces import BlobError -from ZODB.utils import cp -from OFS.Image import File -from xmp import XMP -from logging import getLogger -from cache import memoizedmethod -from libxml2 import parseDoc -from standards.xmp import accessors as xmpAccessors -import xmputils -from types import TupleType -from subprocess import Popen, PIPE -from Products.PortalTransforms.libtransforms.utils import bin_search, \ - MissingBinary - -XPATH_EMPTY_TAGS = "//node()[name()!='' and not(node()) and not(@*)]" -console = getLogger('Photo.metadata') - -try : - XMPDUMP = 'xmpdump' - XMPLOAD = 'xmpload' - bin_search(XMPDUMP) - bin_search(XMPLOAD) - xmpIO_OK = True -except MissingBinary : - xmpIO_OK = False - console.warn("xmpdump or xmpload not available.") - -class Metadata : - """ Photo metadata read / write mixin """ - - security = ClassSecurityInfo() - - - # - # reading api - # - - security.declarePrivate('getXMP') - if xmpIO_OK : - @memoizedmethod() - def getXMP(self): - """returns xmp metadata packet with xmpdump call - """ - if self.size : - blob_file_path = self.bdata._p_blob_uncommitted or self.bdata._p_blob_committed - dumpcmd = '%s %s' % (XMPDUMP, blob_file_path) - p = Popen(dumpcmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, shell=True) - xmp, err = p.communicate() - if err : - raise SystemError, err - return xmp - - else : - @memoizedmethod() - def getXMP(self): - """returns xmp metadata packet with XMP object - """ - xmp = None - if self.size : - try : - bf = self.open('r') - x = XMP(bf, content_type=self.content_type) - xmp = x.getXMP() - except NotImplementedError : - pass - - return xmp - - security.declareProtected(view, 'getXmpFile') - def getXmpFile(self, REQUEST): - """returns the xmp packet over http. - """ - xmp = self.getXMP() - if xmp is not None : - return File('xmp', 'xmp', xmp, content_type='text/xml').index_html(REQUEST, REQUEST.RESPONSE) - else : - return None - - security.declarePrivate('getXmpBag') - def getXmpBag(self, name, root, index=None) : - index = self.getXmpPathIndex() - if index : - path = '/'.join(filter(None, ['rdf:RDF/rdf:Description', root, name])) - node = index.get(path) - - if node : - values = xmputils.getBagValues(node.element) - return values - return tuple() - - security.declarePrivate('getXmpSeq') - def getXmpSeq(self, name, root) : - index = self.getXmpPathIndex() - if index : - path = '/'.join(filter(None, ['rdf:RDF/rdf:Description', root, name])) - node = index.get(path) - - if node : - values = xmputils.getSeqValues(node.element) - return values - return tuple() - - security.declarePrivate('getXmpAlt') - def getXmpAlt(self, name, root) : - index = self.getXmpPathIndex() - if index : - path = '/'.join(filter(None, ['rdf:RDF/rdf:Description', root, name])) - node = index.get(path) - - if node : - firstLi = node.get('rdf:Alt/rdf:li') - if firstLi : - assert firstLi.unique, "More than one rdf:Alt (localisation not yet supported)" - return firstLi.element.content - return '' - - security.declarePrivate('getXmpProp') - def getXmpProp(self, name, root): - index = self.getXmpPathIndex() - if index : - path = '/'.join(filter(None, ['rdf:RDF/rdf:Description', root, name])) - node = index.get(path) - if node : - return node.element.content - return '' - - - security.declarePrivate('getXmpPathIndex') - @memoizedmethod(volatile=True) - def getXmpPathIndex(self): - xmp = self.getXMP() - if xmp : - d = parseDoc(xmp) - index = xmputils.getPathIndex(d) - return index - - security.declarePrivate('getXmpValue') - def getXmpValue(self, name): - """ returns pythonic version of xmp property """ - info = xmpAccessors[name] - root = info['root'] - rdfType = info['rdfType'].capitalize() - methName = 'getXmp%s' % rdfType - meth = getattr(aq_base(self), methName) - return meth(name, root) - - - security.declareProtected(view, 'getXmpField') - def getXmpField(self, name): - """ returns data formated for a html form field """ - editableValue = self.getXmpValue(name) - if type(editableValue) == TupleType : - editableValue = ', '.join(editableValue) - return {'id' : name.replace(':', '_'), - 'value' : editableValue} - - - # - # writing api - # - - security.declarePrivate('setXMP') - if xmpIO_OK : - def setXMP(self, xmp): - """setXMP with xmpload call - """ - if self.size : - blob = self.bdata - if blob.readers : - raise BlobError("Already opened for reading.") - - if blob._p_blob_uncommitted is None: - filename = blob._create_uncommitted_file() - uncommitted = file(filename, 'w') - cp(file(blob._p_blob_committed, 'rb'), uncommitted) - uncommitted.close() - else : - filename = blob._p_blob_uncommitted - - loadcmd = '%s %s' % (XMPLOAD, filename) - p = Popen(loadcmd, stdin=PIPE, stderr=PIPE, shell=True) - p.stdin.write(xmp) - p.stdin.close() - p.wait() - err = p.stderr.read() - if err : - raise SystemError, err - - f = file(filename) - f.seek(0,2) - self.updateSize(size=f.tell()) - f.close() - self.bdata._p_changed = True - - - # purge caches - try : del self._methodResultsCache['getXMP'] - except KeyError : pass - - for name in ('getXmpPathIndex',) : - try : - del self._v__methodResultsCache[name] - except (AttributeError, KeyError): - continue - - self.ZCacheable_invalidate() - self.ZCacheable_set(None) - self.http__refreshEtag() - - else : - def setXMP(self, xmp): - """setXMP with XMP object - """ - if self.size : - bf = self.open('r+') - x = XMP(bf, content_type=self.content_type) - x.setXMP(xmp) - x.save() - self.updateSize(size=bf.tell()) - - # don't call update_data - self.ZCacheable_invalidate() - self.ZCacheable_set(None) - self.http__refreshEtag() - - # purge caches - try : del self._methodResultsCache['getXMP'] - except KeyError : pass - for name in ('getXmpPathIndex', ) : - try : - del self._v__methodResultsCache[name] - except (AttributeError, KeyError): - continue - - - - security.declarePrivate('setXmpField') - def setXmpFields(self, **kw): - xmp = self.getXMP() - if xmp : - doc = parseDoc(xmp) - else : - doc = xmputils.createEmptyXmpDoc() - - index = xmputils.getPathIndex(doc) - - pathPrefix = 'rdf:RDF/rdf:Description' - preferedNsDeclaration = 'rdf:RDF/rdf:Description' - - for id, value in kw.items() : - name = id.replace('_', ':') - info = xmpAccessors.get(name) - if not info : continue - root = info['root'] - rdfType = info['rdfType'] - path = '/'.join([p for p in [pathPrefix, root, name] if p]) - - Metadata._setXmpField(index - , path - , rdfType - , name - , value - , preferedNsDeclaration) - - # clean empty tags without attributes - context = doc.xpathNewContext() - nodeset = context.xpathEval(XPATH_EMPTY_TAGS) - while nodeset : - for n in nodeset : - n.unlinkNode() - n.freeNode() - nodeset = context.xpathEval(XPATH_EMPTY_TAGS) - - - - xmp = doc.serialize('utf-8') - # remove header - xmp = xmp.split('?>', 1)[1].lstrip('\n') - self.setXMP(xmp) - - @staticmethod - def _setXmpField(index, path, rdfType, name, value, preferedNsDeclaration) : - if rdfType in ('Bag', 'Seq') : - value = value.replace(';', ',') - value = value.split(',') - value = [item.strip() for item in value] - value = filter(None, value) - - if value : - # edit - xmpPropIndex = index.getOrCreate(path - , rdfType - , preferedNsDeclaration) - if rdfType == 'prop' : - xmpPropIndex.element.setContent(value) - else : - #rdfPrefix = index.getDocumentNs()['http://www.w3.org/1999/02/22-rdf-syntax-ns#'] - func = getattr(xmputils, 'createRDF%s' % rdfType) - newNode = func(name, value, index) - oldNode = xmpPropIndex.element - oldNode.replaceNode(newNode) - else : - # delete - xmpPropIndex = index.get(path) - if xmpPropIndex is not None : - xmpPropIndex.element.unlinkNode() - xmpPropIndex.element.freeNode() - - -InitializeClass(Metadata)