def __init__(self, path, parent=None): QtCore.QObject.__init__(self, parent) # create metadata handlers for image file and sidecar (if present) self._path = path self._if = MetadataHandler(path) self._sc = None for base in (os.path.splitext(path)[0], path): for ext in ('.xmp', '.XMP'): if not self._sc: self.sc_path = base + ext if os.path.exists(self.sc_path): self._sc = MetadataHandler(self.sc_path) self._unsaved = False
def create_side_car(self): self.sc_path = self._path + '.xmp' with open(self.sc_path, 'w') as of: of.write('<x:xmpmeta x:xmptk="XMP Core 4.4.0-Exiv2" ') of.write('xmlns:x="adobe:ns:meta/">\n') of.write('</x:xmpmeta>') self._sc = MetadataHandler(self.sc_path) self._sc.copy(self._if, comment=False)
class Metadata(QtCore.QObject): _keys = { 'date_digitised' : (('Exif.Photo.DateTimeDigitized', True),), 'date_modified' : (('Exif.Image.DateTime', True),), 'date_taken' : (('Exif.Photo.DateTimeOriginal', True), ('Exif.Image.DateTimeOriginal', True),), 'title' : (('Xmp.dc.title', True), ('Iptc.Application2.ObjectName', True), ('Exif.Image.ImageDescription', True),), 'creator' : (('Xmp.dc.creator', True), ('Xmp.tiff.Artist', False), ('Iptc.Application2.Byline', True), ('Exif.Image.Artist', True),), 'description' : (('Xmp.dc.description', True), ('Iptc.Application2.Caption', True),), 'keywords' : (('Xmp.dc.subject', True), ('Iptc.Application2.Keywords', True),), 'copyright' : (('Xmp.dc.rights', True), ('Xmp.tiff.Copyright', False), ('Iptc.Application2.Copyright', True), ('Exif.Image.Copyright', True),), 'latitude' : (('Exif.GPSInfo.GPSLatitude', True), ('Xmp.exif.GPSLatitude', True),), 'longitude' : (('Exif.GPSInfo.GPSLongitude', True), ('Xmp.exif.GPSLongitude', True),), 'orientation' : (('Exif.Image.Orientation', True),), 'soft_full' : (('Exif.Image.ProcessingSoftware', True),), 'soft_name' : (('Iptc.Application2.Program', True),), 'soft_vsn' : (('Iptc.Application2.ProgramVersion', True),), } _list_items = ('keywords',) def __init__(self, path, parent=None): QtCore.QObject.__init__(self, parent) # create metadata handlers for image file and sidecar (if present) self._path = path self._if = MetadataHandler(path) self._sc = None for base in (os.path.splitext(path)[0], path): for ext in ('.xmp', '.XMP'): if not self._sc: self.sc_path = base + ext if os.path.exists(self.sc_path): self._sc = MetadataHandler(self.sc_path) self._unsaved = False def create_side_car(self): self.sc_path = self._path + '.xmp' with open(self.sc_path, 'w') as of: of.write('<x:xmpmeta x:xmptk="XMP Core 4.4.0-Exiv2" ') of.write('xmlns:x="adobe:ns:meta/">\n') of.write('</x:xmpmeta>') self._sc = MetadataHandler(self.sc_path) self._sc.copy(self._if, comment=False) def save(self, if_mode, sc_mode): if not self._unsaved: return self.set_item('soft_full', 'Photini editor v%s' % (version)) self.set_item('soft_name', 'Photini editor') self.set_item('soft_vsn', '%s' % (version)) if sc_mode == 'delete' and self._sc: self._if.copy(self._sc, comment=False) OK = False if if_mode: OK = self._if.save() if sc_mode == 'delete' and self._sc and OK: os.unlink(self.sc_path) self._sc = None if sc_mode == 'auto' and not self._sc and not OK: self.create_side_car() if sc_mode == 'always' and not self._sc: self.create_side_car() if self._sc: OK = self._sc.save() self._set_unsaved(not OK) # tag lists: merge tags from sidecar and image file def get_exif_tags(self): result = self._if.get_exif_tags() if self._sc: for tag in self._sc.get_exif_tags(): if tag not in result: result.append(tag) return result def get_iptc_tags(self): result = self._if.get_iptc_tags() if self._sc: for tag in self._sc.get_iptc_tags(): if tag not in result: result.append(tag) return result def get_xmp_tags(self): result = self._if.get_xmp_tags() if self._sc: for tag in self._sc.get_xmp_tags(): if tag not in result: result.append(tag) return result # getters: use sidecar if tag is present, otherwise use image file def get_exif_tag_string(self, tag): if self._sc and tag in self._sc.get_exif_tags(): return self._sc.get_exif_tag_string(tag) return self._if.get_exif_tag_string(tag) def get_iptc_tag_multiple(self, tag): if self._sc and tag in self._sc.get_iptc_tags(): return self._sc.get_iptc_tag_multiple(tag) return self._if.get_iptc_tag_multiple(tag) def get_xmp_tag_string(self, tag): if self._sc and tag in self._sc.get_xmp_tags(): return self._sc.get_xmp_tag_string(tag) return self._if.get_xmp_tag_string(tag) def get_xmp_tag_multiple(self, tag): if self._sc and tag in self._sc.get_xmp_tags(): return self._sc.get_xmp_tag_multiple(tag) return self._if.get_xmp_tag_multiple(tag) # setters: set in both sidecar and image file def set_exif_tag_string(self, tag, value): if self._sc: self._sc.set_exif_tag_string(tag, value) self._if.set_exif_tag_string(tag, value) def set_exif_tag_long(self, tag, value): if self._sc: self._sc.set_exif_tag_long(tag, value) self._if.set_exif_tag_long(tag, value) def set_iptc_tag_multiple(self, tag, value): if self._sc: self._sc.set_iptc_tag_multiple(tag, value) self._if.set_iptc_tag_multiple(tag, value) def set_xmp_tag_string(self, tag, value): if self._sc: self._sc.set_xmp_tag_string(tag, value) self._if.set_xmp_tag_string(tag, value) def set_xmp_tag_multiple(self, tag, value): if self._sc: self._sc.set_xmp_tag_multiple(tag, value) self._if.set_xmp_tag_multiple(tag, value) def clear_tag(self, tag): if self._sc: self._sc.clear_tag(tag) self._if.clear_tag(tag) def get_tags(self): return self.get_exif_tags() + self.get_iptc_tags() + self.get_xmp_tags() def has_GPS(self): return (('Xmp.exif.GPSLatitude' in self.get_xmp_tags()) or ('Exif.GPSInfo.GPSLatitude' in self.get_exif_tags())) def get_item(self, name): for key, required in self._keys[name]: family, group, tag = key.split('.') if key in self.get_xmp_tags(): if tag.startswith('GPS'): return GPSvalue().from_xmp_string( self.get_xmp_tag_string(key)) return u'; '.join(self.get_xmp_tag_multiple(key)) if key in self.get_iptc_tags(): return u'; '.join(self.get_iptc_tag_multiple(key)) if key in self.get_exif_tags(): if tag.startswith('DateTime'): return datetime.datetime.strptime( self.get_exif_tag_string(key), '%Y:%m:%d %H:%M:%S') if tag == 'Orientation': return int(self.get_exif_tag_string(key)) if group == 'GPSInfo': return GPSvalue().from_exif_string( self.get_exif_tag_string(key), self.get_exif_tag_string('%sRef' % key)) return unicode(self.get_exif_tag_string(key), 'iso8859_1') return None def set_item(self, name, value): if value == self.get_item(name): return if name in self._list_items: value = map(lambda x: x.strip(), value.split(';')) for i in reversed(range(len(value))): if not value[i]: del value[i] elif isinstance(value, (str, unicode)): value = [value.strip()] if not value: self.del_item(name) return for key, required in self._keys[name]: if required or key in self.get_tags(): family, group, tag = key.split('.') if family == 'Xmp': if isinstance(value, GPSvalue): self.set_xmp_tag_string(key, value.to_xmp_string()) else: self.set_xmp_tag_multiple(key, value) elif family == 'Iptc': self.set_iptc_tag_multiple(key, value) elif family == 'Exif': if isinstance(value, GPSvalue): string, ref = value.to_exif_string() self.set_exif_tag_string(key, string) self.set_exif_tag_string('%sRef' % key, ref) elif isinstance(value, datetime.datetime): self.set_exif_tag_string( key, value.strftime('%Y:%m:%d %H:%M:%S')) elif isinstance(value, int): self.set_exif_tag_long(key, value) else: self.set_exif_tag_string(key, value[0]) self._set_unsaved(True) def del_item(self, name): changed = False for key, required in self._keys[name]: if key in self.get_tags(): self.clear_tag(key) changed = True if changed: self._set_unsaved(True) new_status = QtCore.pyqtSignal(bool) def _set_unsaved(self, status): self._unsaved = status self.new_status.emit(self._unsaved) def changed(self): return self._unsaved