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()
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):
            dlg = wx.MessageDialog(None, 'Sorry!','The file ' + file_copy \
                + ' already exists.', wx.OK | wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()
            return
        else:
            try:
                print self.folder + file_copy
                print self.path
                self.info.saveAs(self.folder + file_copy)
            except:
                err = wx.MessageDialog(None, 'There was an error!')
                err.ShowModal()
                err.Destroy()
                print 'error'
Example #3
0
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
Example #4
0
def captioner(person, yesterday):
    # >> the captioning 
    filename = person[0]
    firstname = person[1]
    lastname = person[2]
    offense = person[3]
    
    fn = "mugs/%s" % (filename)
    print "FILENAME:"
    print filename
    print firstname
    print lastname
    print offense

    info = IPTCInfo(fn, force=True)

    # call the sql for the mug

    # note there needs to be a lot more caption stuff here 
    info.data['caption/abstract'] = "%s %s was booked into the Greene County jail on %s. Offenses as listed by the jail upon booking, starting with warrant number, level of offense, the offense and the bond amount: %s" % (firstname, lastname, yesterday, offense)
    info.data['by-line'] = "Greene County Jail"

    # create the directory to save the file
    saveAsDir = "./dailymugs/%s" % (yesterday)

    if not os.path.exists(saveAsDir):
        os.makedirs(saveAsDir)

    # save the file 
    saveasName = "%s/%s" % (saveAsDir, filename)

    info.saveAs(saveasName)
 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 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]
Example #7
0
 def test_walk_files_and_tag_only_in_subdirectory(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')
     os.makedirs('_testdir/2015/10')
     self._create_testfile('_testdir/2015/10/test3.jpg')
     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,
                      ['cat', 'mammal', 'vertebrate', 'whiskers', 'animal'])
     # file in 2015 has no tag set
     self.assertRaisesRegexp(Exception, 'No IPTC data found', IPTCInfo, '_testdir/2015/10/test3.jpg')
Example #8
0
    def print_iptcinfo(self,filename):
        """
        Prints the IPC Info for the specified filename
        :param filename: .jpg filename
        :return: none
        """
        info = IPTCInfo(filename)
        if len(info.data) < 4: raise Exception(info.error)

        # Print list of keywords, supplemental categories, contacts
        print("Keywords:{}".format(info.keywords))
        print("SupplementalCatagories:{}".format(info.supplementalCategories))
        print("Contacts:{}".format(info.contacts))
        print("Data:{}".format(info.data))

        tags = ['date created', 'digital creation date', 'reference number', 'custom8', 'custom9', 'sub-location',
                'object cycle', 'custom4', 'custom5', 'custom6', 'custom7', 'custom1', 'custom2', 'reference date',
                'by-line title', 'local caption', 'keywords', 'province/state', 'category', 'custom17', 'custom14',
                'digital creation time', 'custom12', 'custom13', 'custom10', 'custom11', 'headline', 'custom18',
                'custom19', 'source', 'contact', 'by-line', 'object name', 'content location code',
                'language identifier', 'release date', 'expiration date', 'reference service', 'custom16',
                'original transmission reference', 'originating program', 'subject reference', 'city',
                'supplemental category', 'content location name', 'country/primary location code', 'editorial update',
                'custom15', 'fixture identifier', 'custom3', 'country/primary location name', 'action advised',
                'custom20', 'copyright notice', 'program version', 'image orientation', 'edit status',
                'expiration time', 'release time', 'credit', 'time created', 'special instructions', 'writer/editor',
                'caption/abstract', 'urgency', 'image type']

        for i in tags:
            desc=info.data[i]
            print("  {0}:{1}".format(i,desc))
def _process_new_files(current_folder):
    items = os.listdir(NEW_ITEM_PATH + current_folder)
    config_path = _get_config_file_path(current_folder)
    dom = minidom.parse(config_path)
    config_images = _get_config_images(dom)

    for item in items:
        filepath = NEW_ITEM_PATH + current_folder + item
        __debug('processing %s' % current_folder + '/' + item)

        if os.path.isfile(filepath) and _get_extension(item) == '.JPG':
            os.rename(filepath,
                      _get_extension(item) + JPEG_EXT)

        if os.path.isdir(filepath):
            if not os.path.isdir(CONFIG_PATH + current_folder + item) and\
            current_folder != HIDDEN:
                #                _append_folder_to_config(config_path, item)
                _append_folder_to_dom(item, dom)
                __debug('   added %s to %s config' % (item, config_path))
            _setup_configs(current_folder, item)
            _setup_image_folders(current_folder + item)
            _process_new_files(current_folder + item + '/')
            __debug('DONE folder')
        elif os.path.isfile(filepath) and _get_extension(item) == JPEG_EXT:
            iptc = IPTCInfo(filepath)
            exif = get_exif(filepath)
            #            if _create_sized_images(item, current_folder):
            #               _append_image_to_config(item, current_folder, metadata)
            _create_sized_images(item, current_folder)
            _append_image_to_dom(item, exif, iptc, dom, config_images,
                                 current_folder)
            __debug('DONE image')

    _write_dom_to_xml(dom, config_path)
Example #10
0
def get_imageinfo(filepath):
    """
    Return EXIF and IPTC information found from image file in a dictionary.
    """
    info = {}
    info['exif'] = exif = exifparser.read_exif(filepath)
    info.update(exifparser.parse_datetime(exif, 'EXIF DateTimeOriginal'))
    info['gps'] = gps = exifparser.parse_gps(exif)
    if 'lat' in gps:  # Backwards compatibility
        info['lat'], info['lon'] = gps['lat'], gps['lon']
    info['iptc'] = iptc = IPTCInfo(filepath, force=True)
    if iptc:  # TODO: this to own function
        if iptc.data['caption/abstract']:
            info['caption'] = iptc.data['caption/abstract']
        if iptc.data['object name']:
            info['title'] = iptc.data['object name']
        if iptc.data['keywords']:
            kw_str = ','.join(iptc.data['keywords'])
            info['keywords'] = kw_str
            info['tags'] = iptc.data['keywords']
        for key in info:  # Convert all str values to unicode
            if isinstance(info[key], str):
                info[key] = unicode(info[key], guess_encoding(info[key]))
    with open(str(filepath), 'rb') as f:
        im = ImagePIL.open(f)
        info['width'], info['height'] = im.size
        del im
    return info
Example #11
0
def get_photo_title_and_description(path):
    """
    Extract JPEG metadata and parse title and description out of it.
    Images *must* be edited with Picasa.

    Use the first sentence of the description as a title.

    Handle wacky cases like usage of ... and ""

    Looks like Picasa does not use standard EXIF
    metadata http://productforums.google.com/forum/#!topic/picasa/fiNTD6432as
    but IPTC instead.


    """
    info = IPTCInfo(path)

    title = os.path.basename(path)
    desc = info.data['caption/abstract']

    if not desc:
        # No metadata
        title = os.path.basename(path)
        description = ""
        return title, description

    # Picasa guys screwed it up
    # https://groups.google.com/forum/?fromgroups=#!topic/picasawebalbums/CjeRCs402WA

    desc = desc.decode("utf-8")

    return title, desc
Example #12
0
def get_imagegroups():
    """ Returns a tuple of tuples representing 
    groups of 10 image paths (each group is a page)"""
    path_to_optimized_images = os.path.join(THIS_DIR, "static", "images",
                                            "opt")
    try:
        os.chdir(path_to_optimized_images)
    except OSError:
        raise OSError(
            "Problem getting optimized images at {path}. Run the process-images.py script."
            .format(path=path_to_optimized_images))
    sorted_image_paths = sorted(filter(os.path.isfile, os.listdir('.')),
                                reverse=True)
    os.chdir(THIS_DIR)
    groups = tuple(grouper(10, sorted_image_paths))
    f = []
    for group in groups:
        l = []
        for name in group:
            if name is not None:
                path = "static/images/opt/%s" % name
                d = {}
                info = IPTCInfo(path, force=True)
                caption = info.data['caption/abstract'] or ""
                date = datetime.datetime.fromtimestamp(
                    int(name.replace(".jpg",
                                     ""))).strftime('%B %d, %Y at %H:%M')
                d[path] = {"date": date, "caption": caption}
                l.append(d)
        f.append(l)
    return f
Example #13
0
def getTitle(filepath, filename):
    # try to get the ITPC title (from Lightroom), fall back to image filename
    filename = filename[0:filename.lower().find(".jpg")]
    try:
        info = IPTCInfo(filepath)
        return info.data.get(5, filename)
    except:
        return filename
Example #14
0
 def _get_raw_metadata(self, path):
     data = super(Image, self)._get_raw_metadata(path)
     if HAS_IPTC:
         try:
             data.update(IPTCInfo(path).__dict__['_data'])
         except:
             pass
     return data
Example #15
0
def read_iptc(abspath, charset='utf-8', new=False):
    '''Parses IPTC metadata from a photo with iptcinfo.py'''

    info = IPTCInfo(abspath, True, charset)
    if len(info.data) < 4:
        print('IPTC is empty for %s' % abspath)
        return None

    return 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)
Example #17
0
 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'])
Example #18
0
 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'])
Example #19
0
 def read_list_from_jpg(self, filename):
     """
     Reads a list of data from the IPTC special_instructions field of a .JPG file
     :param filename: .jpg filename
     :return: list of items
     """
     info = IPTCInfo(filename)
     if len(info.data) < 4: raise Exception(info.error)
     s = info.data['special instructions']
     items = eval(s)  # TBD - use another method to convert a string to a list for tighter security
     return items
Example #20
0
def captioner(person, yesterday):
    # >> the captioning
    filename = person[0]
    firstname = person[1]
    lastname = person[2]
    offense = person[3]

    fn = "mugs/%s" % (filename)
    print "FILENAME:"
    print filename
    print firstname
    print lastname
    print offense

    info = IPTCInfo(fn, force=True)

    # call the sql for the mug

    # note there needs to be a lot more caption stuff here
    info.data[
        'caption/abstract'] = "%s %s was booked into the Greene County jail on %s. Offenses as listed by the jail upon booking, starting with warrant number, level of offense, the offense and the bond amount: %s" % (
            firstname, lastname, yesterday, offense)
    info.data['by-line'] = "Greene County Jail"

    # create the directory to save the file
    saveAsDir = "./dailymugs/%s" % (yesterday)

    if not os.path.exists(saveAsDir):
        os.makedirs(saveAsDir)

    # save the file
    saveasName = "%s/%s" % (saveAsDir, filename)

    info.saveAs(saveasName)
Example #21
0
 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'])
Example #22
0
    def build_photo_sets(self, path, extensions):
        # Build local photo sets
        photo_sets = {}  # Dictionary
        skips_root = []  # List
        keywords = set(self.cmd_args.keyword) if self.cmd_args.keyword else ()

        for r, dirs, files in os.walk(path, followlinks=True):

            if self.cmd_args.starts_with and not r.startswith('{}{}'.format(
                    self.cmd_args.sync_path, self.cmd_args.starts_with)):
                continue

            files = [f for f in files if not f.startswith('.')]
            dirs[:] = [d for d in dirs if not d.startswith('.')]

            for file in files:
                ext = file.lower().split('.').pop()
                if ext in extensions:
                    if r == self.cmd_args.sync_path:
                        skips_root.append(file)
                    else:
                        # If filtering by keyword...
                        if keywords:
                            file_path = os.path.join(r, file)

                            # Create object for file that may or may not (force=TRUE) have IPTC metadata.
                            info = IPTCInfo(file_path, force=True)

                            # intersection(*others): Return a new set with elements common to the set and all others.
                            matches = keywords.intersection(info.keywords)

                            if not matches:
                                # No matching keyword(s) found, skip file
                                logger.info(
                                    'Skipped file [%s] because it does not match any keywords [%s].'
                                    % (file, list(keywords)))
                                continue

                        photo_sets.setdefault(r, [])
                        file_path = os.path.join(r, file)
                        file_stat = os.stat(file_path)
                        logger.info('appending %s to photo sets %s' %
                                    (file, r))
                        photo_sets[r].append((file, file_stat))

        if skips_root:
            logger.warning(
                'Root photos are not synced to avoid disorganized flickr sets. Sync at the topmost level of your photos directory to avoid this warning.'
            )
            logger.warning('Skipped files: %s.' % skips_root)
        return photo_sets
 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))
Example #24
0
 def read_encrypted_list_from_jpg(self, filename, password):
     """
     Reads a list of data encrypted in the IPTC special_instructions field of a .JPG file
     :param filename: .jpg filename
     :param  password: the password used for encryption
     :return: list of items
     """
     self._password=password
     info = IPTCInfo(filename)
     if len(info.data) < 4: raise Exception(info.error)
     token = info.data['special instructions']
     s = self._fermet_decrypt(token,password)
     items = eval(s) # # TBD - use another method to convert a string to a list for tighter security
     return items
Example #25
0
    def test_skip_special_directories(self):
        file_walker = FileWalker(FileLabeler(), LabelServiceExecutor(TestServiceConnector()))
        os.makedirs('_testdir/2016/10')
        self._create_testfile('_testdir/2016/10/test1.jpg')
        os.makedirs('_testdir/2016/10/@eaDir')
        self._create_testfile('_testdir/2016/10/@eaDir/test1.jpg')
        os.makedirs('_testdir/2016/10/2006-07-21 12.45.16.jpg/')
        self._create_testfile('_testdir/2016/10/2006-07-21 12.45.16.jpg/SYNOPHOTO_THUMB_XL.jpg')
        file_walker.walk_and_tag('_testdir/2016')

        self.assertEqual(IPTCInfo('_testdir/2016/10/test1.jpg').keywords,
                         ['cat', 'mammal', 'vertebrate', 'whiskers', 'animal'])
        self.assertRaisesRegexp(Exception, 'No IPTC data found.', IPTCInfo, '_testdir/2016/10/@eaDir/test1.jpg')
        self.assertRaisesRegexp(Exception, 'No IPTC data found.', IPTCInfo,
                                '_testdir/2016/10/2006-07-21 12.45.16.jpg/SYNOPHOTO_THUMB_XL.jpg')
Example #26
0
    def handle_record(self, image):
        """Handle a record.

        Read metadata from IPTC fields, decodes latin-1 encoding.
        Return a new MHNTRecord

        """
        info = IPTCInfo(image)
        image_metadata = {}
        for key, value in info.getData().items():
            key = IPTCData.keyAsStr(key)
            try:
                image_metadata[key] = self.decode_strip_encode(value)
            except AttributeError:
                image_metadata[key] = [self.decode_strip_encode(x) for x in value]
        image_metadata['original_name'] = search_original_name(image_metadata['caption/abstract'])
        if 'headline' in image_metadata:
            image_metadata['title'] = image_metadata['headline']
        elif image_metadata['original_name']:
            image_metadata['title'] = image_metadata['original_name']
        else:
            image_metadata['title'] = "NONE"
        image_metadata['nonstandard_231'] = "REDACTED"
        return metadata.MetadataRecord(image, image_metadata)
    def build_local_photo_sets(self, path,
                               valid_extensions):  # build local photo sets
        local_photo_sets = {}
        keywords = set(
            self.parser_args.keywords) if self.parser_args.keywords else ()

        for root_dir, dirs, files in os.walk(path, followlinks=True):

            if self.parser_args.starts_with and not root_dir.startswith(
                    '{}{}'.format(self.parser_args.sync_path,
                                  self.parser_args.starts_with)):
                logger.debug(
                    'skipping local directory "%s" (--starts-with="%s" not satisfied)',
                    root_dir, self.parser_args.starts_with)
                continue

            files = [f for f in files if not f.startswith('.')]

            for file in files:
                file_path = os.path.join(root_dir, file)
                file_stat = os.stat(file_path)

                file_extension = file.lower().split('.').pop()

                if file_extension not in valid_extensions:
                    #logger.debug('skipping local file "%s" (unrecognized filename extension; valid extensions are: %s)', file_path, list(valid_extensions)) #TODO too many files
                    continue

                if root_dir == self.parser_args.sync_path:
                    logger.info(
                        'skipping local file "%s" (files in --sync-path root are not synced to avoid disorganized flickr sets)',
                        file_path)
                    continue

                if keywords:
                    file_info = IPTCInfo(
                        file_path, force=True
                    )  # use "force=True" if file may not have IPTC metadata

                    if not keywords.intersection(file_info.keywords):
                        logger.debug(
                            'skipping local file "%s" (--keywords=%s not satisfied)',
                            file_path, list(keywords))
                        continue

                local_photo_sets.setdefault(root_dir, [])
                local_photo_sets[root_dir].append((file, file_stat))
        return local_photo_sets
Example #28
0
    def build_photo_sets(self, path, extensions):
        # Build your local photo sets
        photo_sets = {}
        skips_root = []
        keywords = set(self.cmd_args.keyword) if self.cmd_args.keyword else ()

        for r, dirs, files in os.walk(path, followlinks=True):

            if self.cmd_args.starts_with and not r.startswith('{}{}'.format(
                    self.cmd_args.sync_path, self.cmd_args.starts_with)):
                continue

            files = [f for f in files if not f.startswith('.')]
            dirs[:] = [d for d in dirs if not d.startswith('.')]

            for file in files:
                if not file.startswith('.'):
                    ext = file.lower().split('.').pop()
                    if ext in extensions:
                        if r == self.cmd_args.sync_path:
                            skips_root.append(file)
                        else:
                            # filter by keywords
                            if keywords:
                                file_path = os.path.join(r, file)
                                info = IPTCInfo(file_path, force=True)
                                matches = keywords.intersection(info.keywords)
                                if not matches:
                                    # no matching keyword(s) found, skip file
                                    logger.info(
                                        'Skipped [%s] does not match any keyword %s'
                                        % (file, list(keywords)))
                                    continue

                            photo_sets.setdefault(r, [])
                            file_path = os.path.join(r, file)
                            file_stat = os.stat(file_path)
                            photo_sets[r].append((file, file_stat))

        if skips_root:
            logger.warn(
                'To avoid disorganization on flickr sets root photos are not synced, skipped these photos: %s'
                % skips_root)
            logger.warn(
                'Try to sync at top most level of your photos directory')
        return photo_sets
Example #29
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()
Example #30
0
    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
Example #31
0
    def photo_init(self, media):
        'Initialize photo metadata.'

        # Define default values.
        self.filename = media.filename
        self.filepath = os.path.relpath(media.filepath, 'site_media')
        self.timestamp = media.timestamp
        self.title = u''
        self.tags = u''
        self.author = u''
        self.city = u''
        self.sublocation = u''
        self.state = u''
        self.country = u''
        self.taxon = u''
        self.rights = u''
        self.caption = u''
        self.size = u''
        self.source = u''
        self.references = u''
        self.notes = u''

        # Create metadata object.
        info = IPTCInfo(media.filepath, True, 'utf-8')
        # Check if file has IPTC data.
        if len(info.data) < 4:
            print(u'%s has no IPTC data!' % media.filename)

        # Fill values with IPTC data.
        self.title = info.data['object name']  #5
        self.tags = info.data['keywords']  #25
        self.author = info.data['by-line']  #80
        self.city = info.data['city']  #90
        self.sublocation = info.data['sub-location']  #92
        self.state = info.data['province/state']  #95
        self.country = info.data['country/primary location name']  #101
        self.taxon = info.data['headline']  #105
        self.rights = info.data['copyright notice']  #116
        self.caption = info.data['caption/abstract']  #120
        self.size = info.data['special instructions']  #40
        self.source = info.data['source']  #115
        self.references = info.data['credit']  #110
        self.notes = u''
Example #32
0
 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()
Example #33
0
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))
Example #34
0
and use math to parse the whole desc thing. 

Please remember that we need to add in the rest of the (headers?) (the stuff that 
is like location and whatever and source and things that you haven't included; 
see allthedata.txt)
'''

for f in os.listdir('.'):
    try:
        for pic in os.listdir(f):
            if 'jpg' in str(pic).lower() or 'jpeg' in str(pic).lower() or 'gif' in str(pic).lower() or 'bmp' in str(pic).lower():
                i += 1
                #print f + '/' + pic
                iterPhoto = open(f + '/' + pic)
                try:
                    info = IPTCInfo(iterPhoto)
                except:
                    print "IPTC Error (not present)"
                if info:
                    try:
                        d = info.getData()['caption/abstract']
                    except:
                        print "IPTC, but no caption"
                        d = False
                    if d:
                        dStripped = ''.join(d.splitlines())
                        #print dStripped
                        i += 1
                        p = re.compile('<b>Car:</b><br>(.*?)<p>')
                        car = False
                        try:
Example #35
0
    def create_meta(self, charset='utf-8', new=False):
        '''Define as variáveis extraídas dos metadados da imagem.

        Usa a biblioteca do arquivo iptcinfo.py para padrão IPTC e pyexiv2 para EXIF.
        '''
        logger.info('Lendo metadados de %s e criando objetos.', self.filename)

        # Criar objeto com metadados.
        info = IPTCInfo(self.source_filepath, True, charset)
        # Checando se o arquivo tem dados IPTC.
        if len(info.data) < 4:
            logger.warning('%s não tem dados IPTC!', self.filename)

        # Limpa metadados pra não misturar com o anterior.
        self.meta = {}
        self.meta = {
            'source_filepath': os.path.abspath(self.source_filepath),
            'title': info.data['object name'],  #5
            'tags': info.data['keywords'],  #25
            'author': info.data['by-line'],  #80
            'city': info.data['city'],  #90
            'sublocation': info.data['sub-location'],  #92
            'state': info.data['province/state'],  #95
            'country': info.data['country/primary location name'],  #101
            'taxon': info.data['headline'],  #105
            'rights': info.data['copyright notice'],  #116
            'caption': info.data['caption/abstract'],  #120
            'size': info.data['special instructions'],  #40
            'source': info.data['source'],  #115
            'references': info.data['credit'],  #110
            'timestamp': self.timestamp,
            'notes': u'',
        }

        if new:
            # Adiciona o antigo caminho aos metadados.
            self.meta['old_filepath'] = os.path.abspath(self.source_filepath)
            new_filename = rename_file(self.filename, self.meta['author'])
            # Atualiza media object.
            self.source_filepath = os.path.join(
                os.path.dirname(self.source_filepath), new_filename)
            self.filename = new_filename
            # Atualiza os metadados.
            self.meta['source_filepath'] = os.path.abspath(
                self.source_filepath)
            os.rename(self.meta['old_filepath'], self.meta['source_filepath'])
        else:
            self.meta['source_filepath'] = os.path.abspath(
                self.source_filepath)

        # Prepara alguns campos para banco de dados.
        self.meta = prepare_meta(self.meta)

        # Extraindo metadados do EXIF.
        exif = get_exif(self.source_filepath)
        # Extraindo data.
        self.meta['date'] = get_date(exif)
        # Extraindo a geolocalização.
        gps = get_gps(exif)
        self.meta.update(gps)

        # Processar imagem.
        web_filepath, thumb_filepath = self.process_photo()
        # Caso arquivo esteja corrompido, interromper.
        if not web_filepath:
            return None
        self.meta['web_filepath'] = web_filepath.strip('site_media/')
        self.meta['thumb_filepath'] = thumb_filepath.strip('site_media/')

        print
        print u'\tVariável\tMetadado'
        print u'\t' + 40 * '-'
        print u'\t' + self.meta['web_filepath']
        print u'\t' + self.meta['thumb_filepath']
        print u'\t' + 40 * '-'
        print u'\tTítulo:\t\t%s' % self.meta['title']
        print u'\tDescrição:\t%s' % self.meta['caption']
        print u'\tTáxon:\t\t%s' % ', '.join(self.meta['taxon'])
        print u'\tTags:\t\t%s' % '\n\t\t\t'.join(self.meta['tags'])
        print u'\tTamanho:\t%s' % self.meta['size']
        print u'\tEspecialista:\t%s' % ', '.join(self.meta['source'])
        print u'\tAutor:\t\t%s' % ', '.join(self.meta['author'])
        print u'\tSublocal:\t%s' % self.meta['sublocation']
        print u'\tCidade:\t\t%s' % self.meta['city']
        print u'\tEstado:\t\t%s' % self.meta['state']
        print u'\tPaís:\t\t%s' % self.meta['country']
        print u'\tDireitos:\t%s' % self.meta['rights']
        print u'\tData:\t\t%s' % self.meta['date']
        print
        print u'\tGeolocalização:\t%s' % self.meta['geolocation'].decode(
            "utf8")
        print u'\tDecimal:\t%s, %s' % (self.meta['latitude'],
                                       self.meta['longitude'])
        print

        return self.meta
Example #36
0
#!/usr/bin/env python
# -*- coding: utf-8 -*-

try:
    from iptcinfo import IPTCInfo
except ImportError:
    import sys, os
    sys.path.insert(0, os.path.join(os.pardir, os.pardir))
    from iptcinfo import IPTCInfo

if __name__ == '__main__':
    iptc = IPTCInfo(sys.argv[1], force=True)
    caption = iptc.data["caption/abstract"] or u'árvíztűrő Dag 1 tükörfúrógép'
    newcaption = caption.replace("Dag 1", "Dag 2")
    iptc.data["caption/abstract"] = newcaption
    iptc.saveAs(sys.argv[1].rsplit('.', 1)[0] + '-t.jpg') 
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)
Example #38
0
from iptcinfo import IPTCInfo
import sys

fn = (len(sys.argv) > 1 and [sys.argv[1]] or ['test.jpg'])[0]
fn2 = (len(sys.argv) > 2 and [sys.argv[2]] or ['test_out.jpg'])[0]

# Create new info object
info = IPTCInfo(fn)

# Check if file had IPTC data
if len(info.data) < 4: raise Exception(info.error)

# Print list of keywords, supplemental categories, contacts
print "KEYWORDS"
print info.keywords
print "SUPPLEMENTAL"
print info.supplementalCategories
print "CONTACTS"
print info.contacts
print "DATA"
print info.data

# Get specific attributes...
caption = info.data['caption/abstract']

# Create object for file that may does have IPTC data.
# info = IPTCInfo(fn)
# for files without IPTC data, use
info = IPTCInfo(fn, force=True)

# Add/change an attribute
Example #39
0
#!/usr/bin/python

from iptcinfo import IPTCInfo
import sys


# html crap
print "Content-Type: text/html\n\n"
print "Hello, World!\n"

# change
fn = (len(sys.argv) > 1 and [sys.argv[1]] or ['test.jpg'])[0]
fn2 = (len(sys.argv) > 2 and [sys.argv[2]] or ['test_out.jpg'])[0]

# Create new info object
info = IPTCInfo(fn)

# Check if file had IPTC data
if len(info.data) < 4: raise Exception(info.error)

# Print list of keywords, supplemental categories, or contacts
print "Keywords:"
print info.keywords

# Add/change an attribute
info.keywords.append('chang')

# Save new info to file
##### See disclaimer in 'SAVING FILES' section #####
print "\nSaving..."
info.saveAs(fn2)
Example #40
0
#!/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()
Example #41
0
#!/usr/bin/python

from iptcinfo import IPTCInfo
import sys

# change
fn = (len(sys.argv) > 1 and [sys.argv[1]] or ['test.jpg'])[0]
fn2 = (len(sys.argv) > 2 and [sys.argv[2]] or ['test_out.jpg'])[0]

# Create new info object
info = IPTCInfo(fn)

# Check if file had IPTC data
if len(info.data) < 4: raise Exception(info.error)

# Print list of keywords, supplemental categories, or contacts
print "Keywords:"
print info.keywords

# Add/change an attribute
info.keywords.append('chang')

# Save new info to file
##### See disclaimer in 'SAVING FILES' section #####
print "\nSaving..."
foo = info.saveAs(fn2)
print "\nDone"
Example #42
0
 def test_create_empty_keywords(self):
     info = IPTCInfo(self.jpg_file, force=True)
     self.assertEqual(info.keywords, [])
Example #43
0
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()
Example #44
0
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# :mode=python:encoding=UTF-8:
from iptcinfo import IPTCInfo
import logging
logging.basicConfig(level=logging.DEBUG)
import sys

fn = (len(sys.argv) > 1 and [sys.argv[1]] or ['test.jpg'])[0]
fn2 = (len(sys.argv) > 2 and [sys.argv[2]] or ['test_out.jpg'])[0]

# Create new info object
info = IPTCInfo(fn, force=True)

# 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']
Example #45
0
 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'])
Example #46
0
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))
Example #47
0
 def test_label_image(self):
     labeler = FileLabeler()
     labeler.label(self.jpg_file, (u'cat', u'mammal'))
     info = IPTCInfo(self.jpg_file)
     self.assertEqual(info.keywords, ['cat', 'mammal'])