class imageFile(object): """get name of file out of it's path and provide function \ to save with new copyright info""" def __init__(self, path, cr_text): self.path = path self.split_image_path = path.split('/') self.image_name= self.split_image_path[-1] self.split_image_name = self.image_name.split('.') self.orig_name = self.split_image_name[0] self.folder = (string.join(self.split_image_path[0:-1], "/") + "/") self.info= IPTCInfo(str(path), force= True) self.copyright = self.info.data['copyright notice'] self.info.data['copyright notice']= (year + " " + cr_text) def save_new(self): file_copy = (str(self.orig_name) + '_cr' + '.' + str(self.split_image_name[-1])) if os.path.exists(self.folder + '/' + file_copy): print "Sorry! The file " + file_copy + " already exists!" return else: try: self.info.saveAs(self.folder + file_copy) #os.remove(self.path) shutil.move(self.path, "/Users/" + user + "/.Trash") except: print "Unexpected error:", sys.exc_info()[0] raise def save_over(self): self.info.save()
def process_files(srcdir): global dryrun from iptcinfo import IPTCInfo files = os.listdir(srcdir) for name in files: if not os.path.splitext(name)[1] in fileTypesAllowed: logger.info('Ignoring %s' % name) continue else: filepath = os.path.join(srcdir, name) logger.info('Processing %s' % name) try: info = IPTCInfo(filepath, force=True) caption = info.data['caption/abstract'] if not caption: logger.info('Caption not found.') info.data['caption/abstract'] = os.path.splitext(name)[0] logger.info('New caption -> %s' % info.data['caption/abstract']) if not dryrun: logger.info('Overwriting %s' % name) info.save() else: logger.info('Found existing caption. Nothing to do') except: continue
def write_info(self): """Write picasa album names and star rating to photo's IPTC keywords.""" for photo, info in self.photos.items(): photo = IPTCInfo(photo, force=True) if "albums" in info: photo.keywords = list(set(photo.keywords + info["albums"])) print "Write: {}".format(photo.keywords) photo.save()
def test_only_write_tags_once(self): labeler = FileLabeler() info = IPTCInfo(self.jpg_file, force=True) info.keywords = ('cat', 'mammal') info.save() os.remove('%s~' % self.jpg_file) labeler.label(self.jpg_file, (u'cat', u'mammal')) info = IPTCInfo(self.jpg_file) self.assertEqual(info.keywords, ['cat', 'mammal'])
def test_preserve_existing_labels(self): labeler = FileLabeler() info = IPTCInfo(self.jpg_file, force=True) info.keywords = ('cat', 'mammal') info.save() os.remove('%s~' % self.jpg_file) labeler.label(self.jpg_file, (u'dog', u'mammal')) info = IPTCInfo(self.jpg_file) self.assertEqual(info.keywords, ['cat', 'mammal', 'dog'])
def write_info(self): """Write picasa album names and star rating to photo's IPTC keywords.""" for filename, info in self.photos.items(): photo = IPTCInfo(filename, force=True) if "albums" in info: photo.keywords = list(set(photo.keywords + info["albums"])) print "Writing {}: {}".format(filename, photo.keywords) try: photo.save() except: self.errors[filename] = sys.exc_info()[0]
def write_list_to_jpg(self, filename, items): """ Writes a list of data to the IPTC special_instructions field of a .JPG file :param filename: .jpg filename :param items: list of items to be written (specified using Python list syntax) :return: none """ info = IPTCInfo(filename) if len(info.data) < 4: raise Exception(info.error) info.data['caption/abstract']='Contains Special Instructions' info.data['special instructions']=str(items) info.save()
def test_skip_already_tagged_files(self): file_walker = FileWalker(FileLabeler(), LabelServiceExecutor(TestServiceConnector())) os.makedirs('_testdir/2016/10') self._create_testfile('_testdir/2016/10/test1.jpg') os.makedirs('_testdir/2016/11') self._create_testfile('_testdir/2016/11/test2.jpg') info = IPTCInfo('_testdir/2016/11/test2.jpg', force=True) info.keywords = ('already', 'tagged') info.data[TAGGED_PHOTO_KEY] = TAGGED_PHOTO_LABEL info.save() file_walker.walk_and_tag('_testdir/2016') self.assertEqual(IPTCInfo('_testdir/2016/10/test1.jpg').keywords, ['cat', 'mammal', 'vertebrate', 'whiskers', 'animal']) self.assertEqual(IPTCInfo('_testdir/2016/11/test2.jpg').keywords, ['already', 'tagged'])
def write_encrypted_list_to_jpg(self, filename, password, items): """ Writes an encrypted list of data to the IPTC special_instructions field of a .JPG file :param filename: .jpg filename :param password: the password used for encryption :param items: list of items to be written (specified using Python list syntax) :return: none """ info = IPTCInfo(filename) if len(info.data) < 4: raise Exception(info.error) token = self._fernet_encrypt(str(items),password) info.data['caption/abstract']='Contains Special Instructions' info.data['special instructions']=token info.save()
def updateIPTC(obj, event): """ On edit store the updated image metadata inside the image file itself in IPTC format """ if WATERMARK: state = getToolByName(obj,'portal_workflow').getInfoFor(obj,'review_state') else: state = None if WATERMARK and state in ['published', 'featured']: img = ImageExtender(obj).fields[0].get(obj) else: img = obj.getImage() fd, filename = tempfile.mkstemp('_'+obj.getId()) os.close(fd) fout = open(filename, 'wb') fout.write(img.data) fout.close() info = IPTCInfo(filename, force=True) info.data['object name'] = obj.Title() info.data['caption/abstract'] = obj.Description() info.data['by-line'] = obj.Creator() info.data['copyright notice'] = obj.Rights() info.data['keywords'] = [i for i in obj.Subject()] info.keyword = info.data['keywords'] info.data['sub-location'] = obj.getLocation().strip() info.data['city'] = obj.getLocation().strip() info.data['province/state'] = obj.getLocation().strip() info.data['country/primary location name'] = obj.getLocation().strip() info.data['country/primary location code'] = obj.getLocation().strip() info.save() if WATERMARK: # Set the original image field to have the updated IPTC fin = open(filename) ImageExtender(obj).fields[0].set(obj, fin.read()) fin.close() if state in ['published', 'featured']: applyWatermark(obj) else: obj.setImage(ImageExtender(obj).fields[0].get(obj)) else: fin = open(filename) obj.setImage(fin.read()) fin.close()
def iptcCaptionSet(fn, caption): logging.disable(logging.CRITICAL) if (caption==""): caption = " " now = datetime.now() now = now.strftime("%Y%m%d") info = None try: n = 1 info = IPTCInfo(fn, force=True) n = 2 info.data['caption/abstract'] = caption info.data['date created'] = now info.data['writer/editor'] = "picman" info.data['copyright notice'] = "" #info.data['keywords'] = "" n = 3 info.save() os.remove(fn + "~") except Exception, e: info = None print "[%s]" % (caption) print "iptcCaptionSet() failed to process %s - %d %s" % (fn, n, str(e))
class Image(object): """ Models an image filename with tags. """ def __init__(self, filename): logging.info('Creating new Image from “{}”.'.format(filename)) self.basename = "" self.date = "" self.dirname = "" self.event = "" self.number = "" self.origname = "" self.prefix = "" self.suffix = "" self.iptc = None self.tags = set() self.origname = filename self.dirname = os.path.dirname(filename) self.basename = os.path.basename(filename) self._parse_folder_name() self._parse_filename() self._load_iptc() def add_tag(self, tag): """ Adds the given tag. :param tag: Tag to add. :type tag: Tag :raise TypeError: Raised if not a :py:class:`Tag` given. """ if not isinstance(tag, Tag): raise TypeError("Image::add_tag(hashtags.Tag)") self.tags.add(tag) def remove_tag(self, tag): """ Removes the tag, if it is there. If the given tag is not in the set, no error is printed. :param tag: Tag to remove. :type tag: Tag """ if not isinstance(tag, Tag): raise TypeError("Image::remove_tag(hashtags.Tag)") self.tags.discard(tag) def __repr__(self): return "Image('{}')".format(self.current_path()) def rename(self): """ Performs the actual renaming. If the file exists, it will try to increase the picture number. If the attribute :py:attr:`tempname` is set, this name will be used instead of :py:attr:`origname`. """ newname = self.current_path() if os.path.isfile(newname): print 'File “{}” already exists.'.format(newname) answer = raw_input('Do you want to increase the number? [Y/n] ') if answer != "n": while os.path.isfile(newname): self.number = str(int(self.number) +1) newname = self.current_path() print 'Now using “{}”.'.format(newname) assert not os.path.isfile(newname) oldname = self.origname try: oldname = self.tempname except AttributeError: pass logging.info('Renaming “{}” to “{}”.'.format(self.origname, newname)) os.rename(oldname, newname) def _tagstring(self): tagstring = "" if len(self.tags) > 0: tagstring = "#" + "#".join(sorted([tag.escape() for tag in self.tags])) return tagstring def current_path(self): """ Gives the current path of this image. :raises FilenameTooLongError: Raised if generated filename is longer than the filesystem probably supports. :return: Current path. :rtype: str """ filename = "{}-{}-{}{}.{}".format( self.date, self.event, self.number, self._tagstring(), self.suffix ) pathname = os.path.join(self.dirname, filename) if len(pathname) > 256: raise FilenameTooLongError("Filename “{}” is longer than 256 chars.".format(pathname)) return pathname def _parse_filename(self): """ Parses the filename to find the hashtags. :raises FilenameParseError: Raised if name could not be parsed. """ m = re.match(r"([^#]+)(#.*)*\.(\w+)", self.basename) if m is None: raise FilenameParseError('Could not parse “{}”.'.format(self.basename)) if not m.group(2) is None: taglist = m.group(2).split("#") for tag in taglist: if len(tag) > 0: self.add_tag(Tag.from_escaped(tag)) self.prefix = m.group(1) self.suffix = m.group(3) self._parse_prefix() def _parse_prefix(self): """ Parses the first part of the filename. If date or event are already set from the folder name, they are not overwritten. """ prefixparts = self.prefix.split('-') if len(prefixparts) >= 3: if self.date == "": self.date = prefixparts[0] if self.event == "": self.event = '-'.join(prefixparts[1:-1]) self.number = prefixparts[-1] # The number could not be parsed yet, try to find a number. At this # point, any number is fine. if self.number == "": numbers = re.findall(r"\d+", self.prefix) if len(numbers) > 0: self.number = numbers[-1] if self.number == "": global next_id self.number = str(next_id) next_id += 1 # In case anything is missing, this could not be parsed. if self.date == "": raise DateParseError('Could not parse “{}”.'.format(self.prefix)) if self.event == "": raise EventParseError('Could not parse “{}”.'.format(self.prefix)) if self.number == "": raise NumberParseError('Could not parse “{}”.'.format(self.prefix)) def _parse_folder_name(self): """ Parses date and event name from a folder name. Sets :py:attr:`date` and :py:attr:`event`. """ if len(self.dirname) == 0: return pattern = re.compile(r"([012]\d{3}[01]\d[0123]\d)-([^/]+)/?") album_dir = os.path.basename(self.dirname) m = pattern.match(album_dir) if m is None: raise FolderParseError('Could not parse “{}”.'.format(album_dir)) self.date = m.group(1) self.event = m.group(2) def get_tags(self): """ Gives the list with all tags. :return: A list with all tags. :rtype: list """ return list(self.tags) def _load_iptc(self): """ Loads the IPTC data from the original file and saves them with :py:meth:`add_tag`. """ try: self.iptc = IPTCInfo(self.origname, force=True) except IOError as e: pass else: logging.info('Found Tags “{}” in “{}”.'.format( ', '.join(sorted(self.iptc.keywords)), self.origname)) for keyword in self.iptc.keywords: self.add_tag(Tag(keyword)) def write_iptc(self): """ Writes the IPTC data. """ self.iptc.data['keywords'] = list(sorted(self.get_tags())) logging.info('Saving IPTC keywords to “{}”.'.format(self.origname)) self.iptc.save() def name_changed(self): """ Checks whether the name that :py:meth:`current_path` gives is the same than :py:attr:`origname`. :return: Whether the filename was changed. :rtype: bool """ return self.origname != self.current_path() def iptc_changed(self): """ Check whether the tags match the IPTC tags. :return: Whether the IPTC tags need to be rewritten. :rtype: bool """ return sorted(map(Tag, self.iptc.keywords)) != sorted(self.get_tags()) def save(self): """ Renames the file and updates the IPTC fields. """ if self.iptc_changed(): self.write_iptc() if self.name_changed(): self.rename() def rename_to_temp(self): """ Renames the image to a temporary name. It generates a random UUID 4 and renames the picture to it. The temporary name is stored in the :py:attr:`tempname` attribute. """ self.tempname = str(uuid.uuid4()) os.rename(self.origname, self.tempname)
#!/usr/bin/env python # :mode=python:encoding=utf-8 # -*- coding: utf-8 -*- import sys sys.path.insert(0, '.') from iptcinfo import IPTCInfo, LOG, LOGDBG if __name__ == '__main__': import logging logging.basicConfig(level=logging.DEBUG) LOGDBG.setLevel(logging.DEBUG) if len(sys.argv) > 1: info = IPTCInfo(sys.argv[1], True) info.keywords = ['test'] info.supplementalCategories = [] info.contacts = [] print("info = %s\n%s" % (info, "=" * 30), file=sys.stderr) info.save()
def test_write_keywords(self): info = IPTCInfo(self.jpg_file, force=True) info.keywords = ('A', 'B') info.save() info = IPTCInfo(self.jpg_file) self.assertEqual(info.keywords, ['A', 'B'])
class Image: def __init__(self, filename): self.filename = filename self.info = None self.exif = None try: self.info = IPTCInfo(self.filename, force=True) except: print "cannot initialize IPTCInfo!" pass try: self.exif = MinimalExifReader(self.filename) except: print "cannot initialize exif reader" pass # if we can't create either reader, give up if self.exif == None and self.info == None: print "cannot read file metadata" raise # at a minimum, we set a datestamp if one doesn't exist # set to current date & time exifDateStamp = None if self.exif != None: exifDateStamp = self.exif.dateTimeOriginal("%Y%m%d-%H%M%S") if exifDateStamp == "": exifDateStamp = None iptcDate = self.getData("dateCreated") iptcTime = self.getData("timeCreated") if exifDateStamp != None and \ iptcDate == None and \ iptcTime == None: datestamp = exifDateStamp.split('-') self.info.data[iptcMap['dateCreated']] = datestamp[0] self.info.data[iptcMap['timeCreated']] = datestamp[1] elif exifDateStamp == None and \ iptcDate == None and \ iptcTime == None: date, time = nowStamp() self.info.data[iptcMap['dateCreated']] = date self.info.data[iptcMap['timeCreated']] = time def availableFields(self): return iptcMap.keys() def setData(self, field, data): if field in iptcMap: self.info.data[iptcMap[field]] = data def getData(self, field): if self.info != None: if field in iptcMap: return self.info.data[iptcMap[field]] return None def writeFile(self): self.info.save() def dump(self): for f in self.availableFields(): print f + " : " + str(self.getData(f))
# Check if file had IPTC data # if len(info.data) < 4: raise Exception(info.error) # Get list of keywords, supplemental categories, or contacts keywords = info.keywords suppCats = info.supplementalCategories contacts = info.contacts # Get specific attributes... caption = info.data['caption/abstract'] # Create object for file that may or may not have IPTC data. info = IPTCInfo(fn, force=True) # Add/change an attribute info.data['caption/abstract'] = 'árvíztűrő tükörfúrógép' info.data['supplemental category'] = ['portrait'] info.data[123] = '123' info.data['nonstandard_123'] = 'n123' print info.data # Save new info to file ##### See disclaimer in 'SAVING FILES' section ##### info.save() info.saveAs(fn2) #re-read IPTC info print IPTCInfo(fn2)