+++ /dev/null
-# -*- coding: utf-8 -*-
-#######################################################################################
-# Photo is a part of Plinn - http://plinn.org #
-# Copyright © 2004-2008 Benoît PIN <benoit.pin@ensmp.fr> #
-# #
-# 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 <?xml version="1.0" encoding="utf-8"?> 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)