Exif & IPTC Write Access
Allow to write data to any exif/iptc tag.
Parameters are:
- tag name
- tag value
Blueprint information
Related branches
Related bugs
Sprints
Whiteboard
=== modified file 'phatch/
--- phatch/
+++ phatch/
@@ -32,6 +32,7 @@
tags = [_t('file')]
__doc__ = _t('Copy the image file')
valid_last = True
+ flush_metadata_
def interface(
=== modified file 'phatch/
--- phatch/
+++ phatch/
@@ -46,17 +46,16 @@
path = info['path']
#get time_shift construct timedict if necessary
if not ('timedict' in cache):
- cache['
- gpx_file = self.get_field('GPS log (gpx)',info)
+ cache['
+ gpx_file = self.get_field('GPS Log (gpx)',info)
#add geodata
- gps.add_
+ photo.metadata.
+ info['Exif.
+ cache['
+ cache['
return photo
- def apply_pil(
- """Just pass the image unaltered."""
- return image
-
def is_done(
"""Method used for resuming when a batch was interrupted.
For metadata there is no way to know if this image has been done
=== modified file 'phatch/
--- phatch/
+++ phatch/
@@ -32,6 +32,7 @@
tags = [_t('file')]
__doc__ = _t('Rename the image file')
valid_last = True
+ flush_metadata_
def interface(
=== modified file 'phatch/
--- phatch/
+++ phatch/
@@ -46,6 +46,7 @@
tags = [_t('default')]
__doc__ = _t('Save an image')
valid_last = True
+ flush_metadata_
def interface(
=== added file 'phatch/
--- phatch/
+++ phatch/
@@ -0,0 +1,109 @@
+# Phatch - Photo Batch Processor
+# Copyright (C) 2007-2008 www.stani.be
+#
+# 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 3 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, see http://
+#
+# Phatch recommends SPE (http://
+
+# Embedded icon is designed by Igor Kekeljevic (http://
+
+import os
+from core import ct, models
+from core.translation import _t
+#from core.lib import gps
+
+class Action(
+ """Defined variables: <filename> <type> <folder> <width> <height>"""
+
+ label = _t('Write Tag')
+ author = 'Stani'
+ email = '<email address hidden>'
+ version = '0.1'
+ tags = [_t('metadata')]
+ __doc__ = _t('Write new value to a tag')
+ cache = True
+ valid_last = True
+
+ def interface(
+ fields[_t('Tag (Exif, Iptc)')]= self.ExifItpcFi
+ fields[_t('Value')] = self.CharField(
+
+ def apply(self,
+ info = photo.get_info()
+ tag = self.get_field('Tag (Exif, Iptc)',info)
+ value = self.get_
+ if value.strip() == '':
+ value = None
+ photo.metadata[tag] = value
+ return photo
+
+ def is_done(
+ """Method used for resuming when a batch was interrupted.
+ For metadata there is no way to know if this image has been done
+ already, so return False by default."""
+ return False
+
+ def is_overwrite_
+ """Always force overwrite as we want to store the tags
+ in existing images."""
+ return True
+
+ #FIXME: replace this icon with another one (Nadia?)
+ icon = \
+'x\xda\
+\x00\x000\
+\x08|\
+\xba\xban\
+\xab/U\
+\x83KI\
+\xee\xdc\
+\xc8vB\
+\r\xc0\
+\xdbb`
+\xf2L&
+\xc0\x99z\
+0\xd9\
+\xff\n\
+\xcd\xce\
+\xb5z\
+c\x02\
+\x8c\xb9\
+\x97\xa3\
+\nL \x03\x8e\
+\x03\xe9t\
+\xe8h`
+\xdf\xbc\
+\x06\xa6\
+\xd1EM\
+qw0\xf3\
+\x1eI$
+\x03\xb0\
+\xd6\x1c\
+3f\x17\
+\x13\r\
+\xfd\x9e\
+\x9b\xb9\
+\xb0\xb7\
+\x1f+\
+\r\xc0\
+\xbf*\
+\xa8Kb\
+\x00\xc7q\
+\xc3%\
+\x94\xfb\
+v\x07\
+#\x84\
+\x00(t\
+u\xbd\
+\x0f\xb7\
=== modified file 'phatch/
--- phatch/core/api.py 2009-06-03 15:52:30 +0000
+++ phatch/core/api.py 2009-06-04 22:15:16 +0000
@@ -274,15 +274,18 @@
result[
return photo, result
+def flush_log(photo):
+ if photo.log:
+ log_error(
+ photo.log = ''
+
def apply_action(
try:
photo = action.
#log non fatal errors/warnings
- if photo.log:
- log_error(
- photo.log = ''
+ flush_log(photo)
return photo, result
except Exception, details:
folder, image = os.path.
@@ -377,6 +380,9 @@
#do the actions
for action_index, action in enumerate(actions):
+ if action.
+ photo.flush_
+ flush_log(photo)
@@ -390,6 +396,8 @@
if result['abort']: return
elif result['skip']:
+ photo.flush_
+ flush_log(photo)
del photo, progress_result, action_index, action
send.
=== modified file 'phatch/
--- phatch/
+++ phatch/
@@ -80,6 +80,19 @@
target.
return '\n'.join(warnings)
+def flush(path,
+ image = pyexiv2.Image(path)
+ image.readMetad
+ warnings = []
+ for tag, value in metadata.items():
+ try:
+ image[tag] = value
+ except Exception, message:
+ warnings.
+ #save metadata (this might rise an exception)
+ image.writeMeta
+ return '\n'.join(warnings)
+
def test():
import Image
IMAGE = 'IMGA3230.JPG'
=== modified file 'phatch/
--- phatch/
+++ phatch/
@@ -28,9 +28,12 @@
#gui independent (core.lib)
from odict import odict as Fields
+from unicoding import ensure_unicode
NO_FIELDS = Fields()
_t = unicode
+USE_INSPECTOR = _('Use the Image Inspector to list all the variables.')
+
#---image
ALIGN_HORIZONTAL = [_t('left'
ALIGN_VERTICAL = [_t('top'
@@ -231,6 +234,18 @@
FILENAME,
]
+ EXIF_IPTC = ['Exif.
+ 'Exif.Image.
+ 'Exif.Image.
+ 'Exif.Photo.
+ 'Iptc.Applicati
+ 'Iptc.Applicati
+ 'Iptc.Applicati
+ 'Iptc.Applicati
+ 'Iptc.Applicati
+ 'Iptc.Applicati
+ 'Iptc.Applicati
+
def __init_
"""For the possible options see the source code."""
@@ -356,6 +371,12 @@
def __str__(self):
return self._message
+
+ def __unicode__(self):
+ if self.details:
+ return '%s\n%s'
+ else:
+ return self._message
#---field mixins
class PilConstantMixin:
@@ -430,7 +451,7 @@
- _('Use the Image Inspector to list all the variables.'))
+ USE_INSPECTOR)
def to_python(
return x
@@ -438,6 +459,10 @@
def to_string(self,x):
return unicode(x)
+ def fix_string(self,x):
+ """For the ui (see 'write tag' action)"""
+ return x
+
def get_as_
"""For GUI: Translation, but no interpolation here"""
return self.value_
@@ -669,11 +694,11 @@
def __init_
- def set_as_
+ def fix_string(self,x):
#ignore translation
if x and x[0]=='.':
x = x[1:]
- super(ImageType
+ return super(ImageType
class ImageReadTypeFi
def __init_
@@ -790,6 +815,21 @@
+class ExifItpcField(
+ allow_empty = 'False'
+
+ def fix_string(self,x):
+ #ignore translation
+ if x and x[0]=='<' and x[-1]=='>':
+ x = x[1:-1]
+ return super(ExifItpcF
+
+ def to_python(
+ if not(x[:5] in ('Exif.','Iptc.')):
+ raise ValidationError
+ _('Tag should start with "Exif." or "Iptc."'),
+ USE_INSPECTOR)
+ return super(ExifItpcF
class ColorField(Field):
pass
=== modified file 'phatch/
--- phatch/
+++ phatch/
@@ -30,12 +30,9 @@
def init():
pass
-def pil(image):
- return image
-
class Action(Form):
all_layers = False
- pil = staticmethod(pil)
+ pil = None
author = 'Stani'
cache = False
dpi = new('dpi')
@@ -45,6 +42,7 @@
tags = []
update_size = False
valid_last = False
+ flush_metadata_
__doc__ = 'Action base class.'
def values(
@@ -62,7 +60,9 @@
return photo.update_size()
return photo
- def apply_pil(
+ def apply_pil(
+ if pil is None:
+ return image
return self.pil(
def rename(
=== modified file 'phatch/
--- phatch/core/pil.py 2009-06-04 19:57:32 +0000
+++ phatch/core/pil.py 2009-06-05 00:08:16 +0000
@@ -38,6 +38,7 @@
except:
pyexiv2 = None
exif = False
+WWW_PYEXIV2 = 'http://
try:
from unicoding import ensure_unicode
@@ -342,6 +343,20 @@
self.log = ''
+ self.metadata = {}
+
+ def flush_metadata(
+ #is there something to be flushed?
+ if not self.metadata:
+ return
+ #throw an error if pyexiv2 is not installed
+ if not exif:
+ raise ImportError(
+ +' (%s)'%WWW_PYEXIV2)
+ self.log += exif.flush(
+ #as metadata has changed, use new source
+ self._exif_source = self.info['path']
+ self.metadata = {}
def get_filename(
=== modified file 'phatch/
--- phatch/
+++ phatch/
@@ -34,6 +34,7 @@
SELECT = _('Select')
ALL = _('All')
TAGS = [SELECT, ALL,'Pil']
+
if pyexiv2:
TAGS.
TAGS.extend(
=== modified file 'phatch/
--- phatch/
+++ phatch/
@@ -211,10 +211,11 @@
def set_form_
- label, old = self.GetPyData(
+ label, old = self.GetPyData(
+ form = self.get_
+ field = form._get_
+ value_as_string = field.fix_
if value_as_
- form = self.get_
- field = form._get_
try:
if isinstance(
Work Items
Dependency tree
* Blueprints in grey have been implemented.