# -*- coding: utf-8 -*-
#######################################################################################
#   Photo is a part of Plinn - http://plinn.org                                       #
#   Copyright © 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.   #
#######################################################################################
# 
# 

from types import StringTypes
from logging import getLogger
import re
console = getLogger('Photo.xmp')

class XMP(object) :
	XMP_HEADER = u'<?xpacket begin="\ufeff" id="W5M0MpCehiHzreSzNTczkc9d"?>'
	XMP_HEADER_PATTERN = u'''<\?xpacket begin=['"]\ufeff['"] id=['"]W5M0MpCehiHzreSzNTczkc9d['"][^\?]*\?>'''
	XMP_PADDING_LINE = u'\u0020' * 63 + u'\n'
	XMP_TRAILER = u'<?xpacket end="w"?>'

	_readers = {}
	_writers = {}

	
	
	def __init__(self, file, content_type='image/jpeg', encoding='utf-8') :
		try :
			self.reader = self._readers[content_type]
		except KeyError:
			raise NotImplementedError, "%r content type not supported by XMP" % content_type
		
		try :
			self.writer = self._writers[content_type]
		except KeyError :
			self.writer = None
			console.info('XMP file opened on read-only mode.')
		
		self.file = file
		self.encoding = encoding
		self.xmp = None
		self._open()
	

	def __del__(self) :
		try :
			self.file.close()
		except :
			pass
	

	def _open(self) :

		if type(self.file) in StringTypes :
			self.file = file(self.file)

		packet = self.reader(self.file)
		
		if packet is not None :
			# tests / unwrap
			reEncodedHeader = re.compile(self.XMP_HEADER_PATTERN.encode(self.encoding))
			m = reEncodedHeader.match(packet)
			if m is None :
				console.warn('XMP packet wrapper not found')
				self.xmp = packet
				return

			xmp = packet[m.end():]
			trailer = self.XMP_TRAILER[:-6].encode(self.encoding)  # TODO handle read-only mode
			trailerPos = xmp.find(trailer)
			assert trailerPos != -1, "No xmp trailer found"
		
			xmp = xmp[:trailerPos]
			xmp = xmp.strip()
			self.xmp = xmp
		else :
			self.xmp = None
	
	def save(self, f=None):
		original = self.file
		if f :
			if type(f) in StringTypes :
				new = file(f, 'w')
			else :
				new = f
		elif f is None :
			new = self.file
		
		self.writer(original, new, self.xmp)
				
	
	def getXMP(self) :
		return self.xmp
	
	
	def setXMP(self, xmp) :
		self.xmp = xmp

	#
	# xmp utils
	#
	
	@staticmethod
	def getXmpPadding(size) :
		# size of trailer in kB
		return (XMP.XMP_PADDING_LINE * 32 * size)
	
	
	@staticmethod
	def genXMPPacket(uXmpData, paddingSize):
		packet = u''

		packet += XMP.XMP_HEADER
		packet += uXmpData
		packet += XMP.getXmpPadding(paddingSize)
		packet += XMP.XMP_TRAILER

		return packet
	

	
	#
	# content type registry stuff
	#
	
		
	@classmethod
	def registerReader(cls, content_type, reader) :
		cls._readers[content_type] = reader

	@classmethod
	def registerWriter(cls, content_type, writer) :
		cls._writers[content_type] = writer
	
	@classmethod
	def registerWrapper(cls, content_type, wrapper) :
		""" Registers specific wrapper to prepare data
			for embedding xmp into specific content_type file.
		"""
		pass



def test() :
	from xml.dom.minidom import parse
	data = parse('new.xmp').documentElement.toxml()

	def test1() :
		original = 'original.jpg'
		modified = 'modified.jpg'
	
		x = XMP(original)
		x.setXMP(data)
		x.save(modified)

	def test2() :
		from cStringIO import StringIO
		sio = StringIO()
		sio.write(file('modified.jpg').read())
		sio.seek(0)

		x = XMP(sio)
		x.setXMP(data)
		x.save()
		
		f2 = open('modified2.jpg', 'w')
		f2.write(sio.read())
		f2.close()
	
	
	test1()
	test2()
		

		
if __name__ == '__main__' :
	test()
