def __init__(self, photo, container, export_directory, base_name, options): """Creates a new ExportFile object.""" self.photo = photo self.container = container # We cannot resize movie files. if options.size and not imageutils.is_movie_file(photo.image_path): self.size = options.size extension = 'jpg' else: self.size = None extension = su.getfileextension(photo.image_path) self.export_file = os.path.join( export_directory, base_name + '.' + extension) # Location of "Original" file, if any. originals_folder = u"Originals" if options.picasa: if (os.path.exists(os.path.join(export_directory, u".picasaoriginals")) or not os.path.exists(os.path.join(export_directory, u"Originals"))): originals_folder = u".picasaoriginals" if photo.originalpath: self.original_export_file = os.path.join( export_directory, originals_folder, base_name + "." + su.getfileextension(photo.originalpath)) else: self.original_export_file = None
def __init__(self, photo, album_name, base_name, options): """Creates a new PicasaFile object.""" self.photo = photo self.title = base_name if options.size: extension = "jpg" else: extension = su.getfileextension(photo.image_path) self.export_file = os.path.join(album_name, base_name + "." + extension) self.picasa_photo = None
def __init__(self, photo, album_name, base_name, options): """Creates a new PicasaFile object.""" self.photo = photo self.title = base_name if options.size: extension = "jpg" else: extension = su.getfileextension(photo.image_path) self.export_file = os.path.join(album_name, base_name + '.' + extension) self.picasa_photo = None
def __init__(self, photo, export_directory, base_name, options): """Creates a new ExportFile object.""" self.photo = photo if options.size: extension = "jpg" else: extension = su.getfileextension(photo.image_path) self.export_file = os.path.join( export_directory, base_name + '.' + extension) # Location of "Original" file, if any. originals_folder = u"Originals" if options.picasa: if (os.path.exists(os.path.join(export_directory, u".picasaoriginals")) or not os.path.exists(os.path.join(export_directory, u"Originals"))): originals_folder = u".picasaoriginals" if photo.originalpath: self.original_export_file = os.path.join( export_directory, originals_folder, base_name + "." + su.getfileextension(photo.originalpath)) else: self.original_export_file = None
def __init__(self, photo, export_directory, base_name, options): """Creates a new ExportFile object.""" self.photo = photo if options.size: extension = "jpg" else: extension = su.getfileextension(photo.image_path) self.export_file = os.path.join(export_directory, base_name + '.' + extension) # Location of "Original" file, if any. originals_folder = u"Originals" if options.picasa: if (os.path.exists( os.path.join(export_directory, u".picasaoriginals")) or not os.path.exists( os.path.join(export_directory, u"Originals"))): originals_folder = u".picasaoriginals" if photo.originalpath: self.original_export_file = os.path.join( export_directory, originals_folder, base_name + "." + su.getfileextension(photo.originalpath)) else: self.original_export_file = None
def get_content_type(image_path): """Returns the appropriate content type for a media file, based on the extension. Args: image_path: path to the media file. Returns: Content type string, like "image/jpeg". """ extension = su.getfileextension(image_path) if extension == "jpg": return "image/jpeg" if extension == "mov": return "video/quicktime" if extension == "m4v": return "video/mp4" if extension == "avi": return "video/avi" print >> sys.stderr, ('Uploading of media files with extension %s' ' not supported. Defaulting to image/jpeg: %s') % ( extension, image_path) return "image/jpeg"
def get_content_type(image_path): """Returns the appropriate content type for a media file, based on the extension. Args: image_path: path to the media file. Returns: Content type string, like "image/jpeg". """ extension = su.getfileextension(image_path) if extension == "jpg": return "image/jpeg" if extension == "mov": return "video/quicktime" if extension == "m4v": return "video/mp4" if extension == "avi": return "video/avi" print >> sys.stderr, ( "Uploading of media files with extension %s" " not supported. Defaulting to image/jpeg: %s" ) % (extension, image_path) return "image/jpeg"
def __init__(self, key, data, keyword_map, face_map, aperture_data): self.id = key self.data = data self._caption = su.nn_string(data.get("Caption")).strip() self.comment = su.nn_string(data.get("Comment")).strip() version = None if aperture_data: version = aperture_data.versions.get(key) if data.has_key("DateAsTimerInterval"): self.date = applexml.getappletime(data.get("DateAsTimerInterval")) elif version: self.date = version.image_date else: # Try to get the date from a the caption in "YYYYMMDD ..." format m = re.match(_CAPTION_PATTERN, self._caption) if m: year = int(m.group(1)) month = int(m.group(2)) if not month: month = 1 date = int(m.group(3)) if not date: date = 1 self.date = datetime.datetime(year, month, date) else: self.date = None self.mod_date = applexml.getappletime( data.get("ModDateAsTimerInterval")) self.image_path = data.get("ImagePath") if data.has_key("Rating"): self.rating = int(data.get("Rating")) elif version: self.rating = version.mainRating else: self.rating = None if data.get("longitude"): latitude = float(data.get("latitude")) longitude = float(data.get("longitude")) self.gps = imageutils.GpsLocation(latitude, longitude) elif version: self.gps = version.location else: self.gps = None self.keywords = [] keyword_list = data.get("Keywords") if keyword_list is not None: for i in keyword_list: self.keywords.append(keyword_map.get(i)) elif version: self.keywords = version.keywords if version: self.originalpath = None # This is just a placeholder... # Use the preview if there are adjustments. if (version.rotation or version.hasAdjustments or not su.getfileextension(version.master_image_path) in _JPG_EXTENSIONS): #if version.rotation: # su.pout(u"Rotated: %s (%d)" % (self._caption, version.rotation)) #if version.hasAdjustments: # su.pout(u"Adjustments: %s" % (self._caption)) #if not su.getfileextension(version.master_image_path) in _JPG_EXTENSIONS: # su.pout(u"Not JPEG: %s" % (self._caption)) self.originalpath = version.master_image_path if not version.imageProxy.fullSizePreviewPath: su.pout(u"No preview path for %s." % (self.caption)) else: self.image_path = version.imageProxy.fullSizePreviewPath else: self.image_path = version.master_image_path self.originalpath = None if not version.imageProxy.fullSizePreviewUpToDate: su.pout(u"%s: full size preview not up to date." % (self.caption)) else: self.originalpath = data.get("OriginalPath") self.roll = data.get("Roll") self.albums = [] # list of albums that this image belongs to self.faces = [] self.face_rectangles = [] self.event_name = '' # name of event (roll) that this image belongs to self.event_index = '' # index within event self.event_index0 = '' # index with event, left padded with 0 face_list = data.get("Faces") if face_list: for face_entry in face_list: face_key = face_entry.get("face key") face_name = face_map.get(face_key) if face_name: self.faces.append(face_name) # Rectangle is '{{x, y}, {width, height}}' as ratios, # referencing the lower left corner of the face rectangle, # with lower left corner of image as (0,0) rectangle = parse_face_rectangle(face_entry.get("rectangle")) # Convert to using center of area, relative to upper left corner of image rectangle[0] += rectangle[2] / 2.0 rectangle[1] = max(0.0, 1.0 - rectangle[1] - rectangle[3] / 2.0) self.face_rectangles.append(rectangle) # Other keys in face_entry: face index # Now sort the faces left to right. sorted_names = {} sorted_rectangles = {} for i in xrange(len(self.faces)): x = self.face_rectangles[i][0] while sorted_names.has_key(x): x += 0.00001 sorted_names[x] = self.faces[i] sorted_rectangles[x] = self.face_rectangles[i] self.faces = [sorted_names[x] for x in sorted(sorted_names.keys())] self.face_rectangles = [ sorted_rectangles[x] for x in sorted(sorted_rectangles.keys())]
def check_iptc_data(self, export_file, options, is_original=False): """Tests if a file has the proper keywords and caption in the meta data.""" if not su.getfileextension(export_file) in ("jpg", "tif", "tiff", "png", "nef", "cr2"): return False (file_keywords, file_caption, date_time_original, rating, gps, region_rectangles, region_names) = exiftool.get_iptc_data( export_file) if options.aperture: # Aperture maintains all these metadata in the preview files, and # does not even save all the information into the .xml file. new_caption = None new_keywords = None new_date = None new_rating = -1 new_gps = None else: new_caption = imageutils.get_photo_caption(self.photo, options.captiontemplate) if not su.equalscontent(file_caption, new_caption): su.pout('Updating IPTC for %s because it has Caption "%s" ' 'instead of "%s".' % (export_file, file_caption, new_caption)) else: new_caption = None new_keywords = self.get_export_keywords(options.face_keywords) if not imageutils.compare_keywords(new_keywords, file_keywords): su.pout("Updating IPTC for %s because of keywords (%s instead " "of %s)" % (export_file, ",".join(file_keywords), ",".join(new_keywords))) else: new_keywords = None new_date = None if self.photo.date and date_time_original != self.photo.date: su.pout("Updating IPTC for %s because of date (%s instead of " "%s)" % (export_file, date_time_original, self.photo.date)) new_date = self.photo.date new_rating = -1 if self.photo.rating != None and rating != self.photo.rating: su.pout("Updating IPTC for %s because of rating (%d instead of " "%d)" % (export_file, rating, self.photo.rating)) new_rating = self.photo.rating new_gps = None if options.gps and self.photo.gps: if (not gps or not self.photo.gps.is_same(gps)): if gps: old_gps = gps else: old_gps = imageutils.GpsLocation() su.pout("Updating IPTC for %s because of GPS %s vs %s" % (export_file, old_gps.to_string(), self.photo.gps.to_string())) new_gps = self.photo.gps # Don't export the faces into the original file (could have been # cropped). do_faces = options.faces and not is_original (new_rectangles, new_persons) = self._check_person_iptc_data( export_file, region_rectangles, region_names, do_faces) if (new_caption != None or new_keywords != None or new_date or new_gps or new_rating != -1 or new_rectangles or new_persons): if not options.dryrun: exiftool.update_iptcdata(export_file, new_caption, new_keywords, new_date, new_rating, new_gps, new_rectangles, new_persons) return True return False
def is_movie_file(file_name): """Tests if the file (name or full path) is a movie file.""" return su.getfileextension(file_name) in ("mov", "avi", "m4v", "mpg")
def is_image_file(file_name): """Tests if the file (name or full path) is an image file.""" return su.getfileextension(file_name) in ("jpg", "jpeg", "tif", "png", "nef")
def is_sharable_image_file(file_name): """Tests if the file (name or full path) is an image file in a format suitable for sharing.""" return su.getfileextension(file_name) in ("jpg", "jpeg", "tif", "png")
def check_iptc_data(self, export_file, options, is_original=False, file_updated=False): """Tests if a file has the proper keywords and caption in the meta data.""" if not su.getfileextension(export_file) in _EXIF_EXTENSIONS: return False messages = [] iptc_data = exiftool.get_iptc_data(export_file) new_caption = imageutils.get_photo_caption(self.photo, self.container, options.captiontemplate) if not su.equalscontent(iptc_data.caption, new_caption): messages.append(u' File caption: %s' % (su.nn_string(iptc_data.caption).strip())) messages.append(u' iPhoto caption: %s' % (new_caption)) else: new_caption = None new_keywords = None new_date = None new_rating = -1 new_keywords = self.get_export_keywords(options.face_keywords) if not imageutils.compare_keywords(new_keywords, iptc_data.keywords): messages.append(u' File keywords: %s' % (u','.join(iptc_data.keywords))) if new_keywords == None: messages.append(u' iPhoto keywords: <None>') else: messages.append(u' iPhoto keywords: %s' % (u','.join(new_keywords))) else: new_keywords = None if not options.aperture: #if self.photo.date and date_time_original != self.photo.date: # messages.append(u' File date: %s' % (date_time_original)) # messages.append(u' iPhoto date: %s' % (self.photo.date)) # new_date = self.photo.date if self.photo.rating != None and iptc_data.rating != self.photo.rating: messages.append(u' File rating: %d' % (iptc_data.rating)) messages.append(u' iPhoto rating: %d' % (self.photo.rating)) new_rating = self.photo.rating else: if options.face_keywords: merged_keywords = iptc_data.keywords[:] for keyword in self.photo.getfaces(): if not keyword in merged_keywords: merged_keywords.append(keyword) new_keywords = merged_keywords if iptc_data.hierarchical_subject and not options.reverse: messages.append(u' File subjects: %s' % (u','.join(iptc_data.hierarchical_subject))) new_gps = None if options.gps and self.photo.gps: if (not iptc_data.gps or not self.photo.gps.is_same(iptc_data.gps)): if iptc_data.gps: old_gps = iptc_data.gps else: old_gps = imageutils.GpsLocation() messages.append(u' File GPS: %s' % (old_gps.to_string())) messages.append(u' iPhoto GPS: %s' % (self.photo.gps.to_string())) new_gps = self.photo.gps # Don't export the faces into the original file (could have been # cropped). do_faces = options.faces and not is_original (new_rectangles, new_persons) = self._check_person_iptc_data( export_file, iptc_data.region_rectangles, iptc_data.region_names, do_faces, messages) if (new_caption != None or new_keywords != None or new_date or (not options.reverse and iptc_data.hierarchical_subject) or new_gps or new_rating != -1 or new_rectangles != None or new_persons != None): su.pout(u'Updating IPTC for %s because of\n%s' % (export_file, u'\n'.join(messages))) if (file_updated or imageutils.should_update(options)) and not options.dryrun: exiftool.update_iptcdata(export_file, new_caption, new_keywords, new_date, new_rating, new_gps, new_rectangles, new_persons, iptc_data.image_width, iptc_data.image_height, hierarchical_subject=[]) return True return False
def _check_need_to_export(self, source_file, options): """Returns true if the image file needs to be exported. Args: source_file: path to image file, with aliases resolved. options: processing options. """ if not os.path.exists(self.export_file): return True # In link mode, check the inode. if options.link: export_stat = os.stat(self.export_file) source_stat = os.stat(source_file) if export_stat.st_ino != source_stat.st_ino: su.pout('Changed: %s: inodes don\'t match: %d vs. %d' % (self.export_file, export_stat.st_ino, source_stat.st_ino)) return True if (not options.reverse and os.path.getmtime(self.export_file) + _MTIME_FUDGE < os.path.getmtime(source_file)): su.pout('Changed: %s: newer version is available: %s vs. %s' % (self.export_file, time.ctime(os.path.getmtime(self.export_file)), time.ctime(os.path.getmtime(source_file)))) return True if (options.reverse and os.path.getmtime(source_file) + _MTIME_FUDGE < os.path.getmtime(self.export_file)): su.pout('Changed: %s: newer version is available: %s vs. %s' % (self.export_file, time.ctime(os.path.getmtime(source_file)), time.ctime(os.path.getmtime(self.export_file)))) return True if not self.size and not options.reverse: # With creative renaming in iPhoto it is possible to get # stale files if titles get swapped between images. Double # check the size, allowing for some difference for meta data # changes made in the exported copy source_size = os.path.getsize(source_file) export_size = os.path.getsize(self.export_file) diff = abs(source_size - export_size) if diff > _MAX_FILE_DIFF or (diff > 32 and options.link): su.pout('Changed: %s: file size: %d vs. %d' % (self.export_file, export_size, source_size)) return True # In reverse mode, we don't check the file size (might have changed because # of Preview regeneration), so we look at the image dimensions instead to catch # some out-of-sync images. if options.reverse and su.getfileextension(self.export_file) in _EXIF_EXTENSIONS: (source_width, source_height) = imageutils.get_image_width_height(source_file) (export_width, export_height) = imageutils.get_image_width_height(self.export_file) if ((source_width and export_width and source_width != export_width) or (source_height and export_height and source_height != export_height)): su.pout('Changed: %s: dimensions: %dx%d vs. %dx%d' % ( self.export_file, source_width, source_height, export_width, export_height)) return True # In link mode, we don't need to check the modification date in the # database because we catch the changes by the size check above. #if (not options.link and # datetime.datetime.fromtimestamp(os.path.getmtime( # self.export_file)) < self.photo.mod_date): # su.pout('Changed: %s: modified in iPhoto: %s vs. %s ' % ( # self.export_file, # time.ctime(os.path.getmtime(self.export_file)), # self.photo.mod_date)) # return True return False
def __init__(self, key, data, keyword_map, face_map, aperture_data): self.id = key self.data = data self._caption = su.nn_string(data.get("Caption")).strip() self.comment = su.nn_string(data.get("Comment")).strip() version = None if aperture_data: version = aperture_data.versions.get(key) if data.has_key("DateAsTimerInterval"): self.date = applexml.getappletime(data.get("DateAsTimerInterval")) elif version: self.date = version.image_date else: # Try to get the date from a the caption in "YYYYMMDD ..." format m = re.match(_CAPTION_PATTERN, self._caption) if m: year = int(m.group(1)) month = int(m.group(2)) if not month: month = 1 date = int(m.group(3)) if not date: date = 1 self.date = datetime.datetime(year, month, date) else: self.date = None self.mod_date = applexml.getappletime( data.get("ModDateAsTimerInterval")) self.image_path = data.get("ImagePath") if data.has_key("Rating"): self.rating = int(data.get("Rating")) elif version: self.rating = version.mainRating else: self.rating = None if data.get("longitude"): latitude = float(data.get("latitude")) longitude = float(data.get("longitude")) self.gps = imageutils.GpsLocation(latitude, longitude) elif version: self.gps = version.location else: self.gps = None self.keywords = [] keyword_list = data.get("Keywords") if keyword_list is not None: for i in keyword_list: self.keywords.append(keyword_map.get(i)) elif version: self.keywords = version.keywords if version: self.originalpath = None # This is just a placeholder... # Use the preview if there are adjustments. if (version.rotation or version.hasAdjustments or not su.getfileextension( version.master_image_path) in _JPG_EXTENSIONS): #if version.rotation: # su.pout(u"Rotated: %s (%d)" % (self._caption, version.rotation)) #if version.hasAdjustments: # su.pout(u"Adjustments: %s" % (self._caption)) #if not su.getfileextension(version.master_image_path) in _JPG_EXTENSIONS: # su.pout(u"Not JPEG: %s" % (self._caption)) self.originalpath = version.master_image_path if not version.imageProxy.fullSizePreviewPath: su.pout(u"No preview path for %s." % (self.caption)) else: self.image_path = version.imageProxy.fullSizePreviewPath else: self.image_path = version.master_image_path self.originalpath = None if not version.imageProxy.fullSizePreviewUpToDate: su.pout(u"%s: full size preview not up to date." % (self.caption)) else: self.originalpath = data.get("OriginalPath") self.roll = data.get("Roll") self.albums = [] # list of albums that this image belongs to self.faces = [] self.face_rectangles = [] self.event_name = '' # name of event (roll) that this image belongs to self.event_index = '' # index within event self.event_index0 = '' # index with event, left padded with 0 face_list = data.get("Faces") if face_list: for face_entry in face_list: face_key = face_entry.get("face key") face_name = face_map.get(face_key) if face_name: self.faces.append(face_name) # Rectangle is '{{x, y}, {width, height}}' as ratios, # referencing the lower left corner of the face rectangle, # with lower left corner of image as (0,0) rectangle = parse_face_rectangle( face_entry.get("rectangle")) # Convert to using center of area, relative to upper left corner of image rectangle[0] += rectangle[2] / 2.0 rectangle[1] = max(0.0, 1.0 - rectangle[1] - rectangle[3] / 2.0) self.face_rectangles.append(rectangle) # Other keys in face_entry: face index # Now sort the faces left to right. sorted_names = {} sorted_rectangles = {} for i in xrange(len(self.faces)): x = self.face_rectangles[i][0] while sorted_names.has_key(x): x += 0.00001 sorted_names[x] = self.faces[i] sorted_rectangles[x] = self.face_rectangles[i] self.faces = [ sorted_names[x] for x in sorted(sorted_names.keys()) ] self.face_rectangles = [ sorted_rectangles[x] for x in sorted(sorted_rectangles.keys()) ]
def check_iptc_data(self, export_file, options, is_original=False): """Tests if a file has the proper keywords and caption in the meta data.""" if not su.getfileextension(export_file) in ("jpg", "tif", "tiff", "png", "nef", "cr2"): return False (file_keywords, file_caption, date_time_original, rating, gps, region_rectangles, region_names) = exiftool.get_iptc_data(export_file) if options.aperture: # Aperture maintains all these metadata in the preview files, and # does not even save all the information into the .xml file. new_caption = None new_keywords = None new_date = None new_rating = -1 new_gps = None else: new_caption = imageutils.get_photo_caption(self.photo, options.captiontemplate) if not su.equalscontent(file_caption, new_caption): su.pout('Updating IPTC for %s because it has Caption "%s" ' 'instead of "%s".' % (export_file, file_caption, new_caption)) else: new_caption = None new_keywords = self.get_export_keywords(options.face_keywords) if not imageutils.compare_keywords(new_keywords, file_keywords): su.pout("Updating IPTC for %s because of keywords (%s instead " "of %s)" % (export_file, ",".join(file_keywords), ",".join(new_keywords))) else: new_keywords = None new_date = None if self.photo.date and date_time_original != self.photo.date: su.pout("Updating IPTC for %s because of date (%s instead of " "%s)" % (export_file, date_time_original, self.photo.date)) new_date = self.photo.date new_rating = -1 if self.photo.rating != None and rating != self.photo.rating: su.pout( "Updating IPTC for %s because of rating (%d instead of " "%d)" % (export_file, rating, self.photo.rating)) new_rating = self.photo.rating new_gps = None if options.gps and self.photo.gps: if (not gps or not self.photo.gps.is_same(gps)): if gps: old_gps = gps else: old_gps = imageutils.GpsLocation() su.pout("Updating IPTC for %s because of GPS %s vs %s" % (export_file, old_gps.to_string(), self.photo.gps.to_string())) new_gps = self.photo.gps # Don't export the faces into the original file (could have been # cropped). do_faces = options.faces and not is_original (new_rectangles, new_persons) = self._check_person_iptc_data(export_file, region_rectangles, region_names, do_faces) if (new_caption != None or new_keywords != None or new_date or new_gps or new_rating != -1 or new_rectangles or new_persons): if not options.dryrun: exiftool.update_iptcdata(export_file, new_caption, new_keywords, new_date, new_rating, new_gps, new_rectangles, new_persons) return True return False