def resize_image(source, output, height_width_max, out_format='jpeg', enlarge=False): """Converts an image to a new format and resizes it. Args: source: path to inputimage file. output: path to output image file. height_width_max: resize image so height and width aren't greater than this value. out_format: output file format (like "jpeg") enlarge: if set, enlarge images that are smaller than height_width_max. Returns: Output from running "sips" command if it failed, None on success. """ # To use ImageMagick: #result = su.execandcombine( # [imageutils.CONVERT_TOOL, source, '-delete', '1--1', '-quality', '90%', # '-resize', "%dx%d^" % (height_width_max, height_width_max),output]) out_height_width_max = 0 if enlarge: out_height_width_max = height_width_max else: (width, height) = get_image_width_height(source) if height > height_width_max or width > height_width_max: out_height_width_max = height_width_max args = [_SIPS_TOOL, '-s', 'format', out_format] if out_height_width_max: args.extend(['--resampleHeightWidthMax', '%d' % (out_height_width_max)]) # TODO(tilmansp): This has problems with non-ASCII output folders. args.extend([source, '--out', output]) result = su.fsdec(su.execandcombine(args)) if result.find('Error') != -1 or result.find('Warning') != -1: return result return None
def check_convert(): """Tests if ImageMagick convert tool is available. Prints error message to sys.stderr if there is a problem.""" found_it = False try: output = su.execandcombine([CONVERT_TOOL, "-version" ]) if output.find("ImageMagick") >= 0: found_it = True except StandardError: pass if not found_it: print >> sys.stderr, """Cannot execute "%s". Make sure you have ImageMagick installed. You can download a copy from http://www.imagemagick.org/script/index.php """ % (CONVERT_TOOL) return False return True
def check_convert(): """Tests if ImageMagick convert tool is available. Prints error message to sys.stderr if there is a problem.""" found_it = False try: output = su.execandcombine([CONVERT_TOOL, "-version"]) if output.find("ImageMagick") >= 0: found_it = True except StandardError: pass if not found_it: print >> sys.stderr, """Cannot execute "%s". Make sure you have ImageMagick installed. You can download a copy from http://www.imagemagick.org/script/index.php """ % (CONVERT_TOOL) return False return True
def check_exif_tool(msgstream=sys.stderr): """Tests if a compatible version of exiftool is available.""" try: output = su.execandcombine((EXIFTOOL, "-ver")) version = float(output) if version < 8.61: print >> msgstream, "You have version %f of exiftool." % version print >> msgstream, """ Please upgrade to version 8.61 or newer of exiftool. You can download a copy from http://www.sno.phy.queensu.ca/~phil/exiftool/. Phosare wants to use the new -X option to read IPTC data in XML format.""" return False return True except StandardError: print >> msgstream, """Cannot execute "%s". Make sure you have exiftool installed as /usr/bin/exiftool. You can download a copy from http://www.sno.phy.queensu.ca/~phil/exiftool/. """ % (EXIFTOOL) return False
def check_exif_tool(msgstream=sys.stderr): """Tests if a compatible version of exiftool is available.""" try: output = su.execandcombine((EXIFTOOL, "-ver")) version = float(output) if version < 7.47: print >> msgstream, "You have version %f of exiftool." % version print >> msgstream, """ Please upgrade to version 7.47 or newer of exiftool. You can download a copy from http://www.sno.phy.queensu.ca/~phil/exiftool/. Phosare wants to use the new -X option to read IPTC data in XML format.""" return False return True except StandardError: print >> msgstream, """Cannot execute "%s". Make sure you have exiftool installed as /usr/bin/exiftool. You can download a copy from http://www.sno.phy.queensu.ca/~phil/exiftool/. """ % (EXIFTOOL) return False
def resize_image(source, output, height_width_max, out_format='jpeg', enlarge=False): """Converts an image to a new format and resizes it. Args: source: path to inputimage file. output: path to output image file. height_width_max: resize image so height and width aren't greater than this value. out_format: output file format (like "jpeg") enlarge: if set, enlarge images that are smaller than height_width_max. Returns: Output from running "sips" command if it failed, None on success. """ # To use ImageMagick: #result = su.execandcombine( # [imageutils.CONVERT_TOOL, source, '-delete', '1--1', '-quality', '90%', # '-resize', "%dx%d^" % (height_width_max, height_width_max),output]) out_height_width_max = 0 if enlarge: out_height_width_max = height_width_max else: (width, height) = get_image_width_height(source) if height > height_width_max or width > height_width_max: out_height_width_max = height_width_max args = [_SIPS_TOOL, '-s', 'format', out_format] if out_height_width_max: args.extend( ['--resampleHeightWidthMax', '%d' % (out_height_width_max)]) # TODO(tilmansp): This has problems with non-ASCII output folders. args.extend([source, '--out', output]) result = su.fsdec(su.execandcombine(args)) if result.find('Error') != -1 or result.find( 'Warning') != -1 or result.find('Trace') != -1: return result return None
def _get_iptc_data_exiftool(image_file): """Returns IptcData for an image file using exiftool.""" args = [ EXIFTOOL, "-X", "-m", "-q", "-q", '-c', '%.6f', "-Keywords", "-Caption-Abstract", "-ImageDescription", "-DateTimeOriginal", "-Rating", "-GPSLatitude", "-Subject", "-GPSLongitude", "-RegionName", "-RegionType", "-RegionAreaX", "-RegionAreaY", "-RegionAreaW", "-RegionAreaH", "-ImageWidth", "-ImageHeight", "-HierarchicalSubject", "-AlreadyApplied", image_file ] output = su.execandcombine(args) if not output: return None iptc_data = None try: xml_data = minidom.parseString(output) for xml_desc in xml_data.getElementsByTagName('rdf:Description'): iptc_data = IptcData() iptc_data.rating = 0 iptc_data.image_file = xml_desc.getAttribute("rdf:about") iptc_data.keywords = [] _get_xml_nodevalues(xml_desc, 'IPTC:Keywords', iptc_data.keywords) _get_xml_nodevalues(xml_desc, 'XMP-lr:HierarchicalSubject', iptc_data.hierarchical_subject) # Keywords can also be stored as Subject in the XMP directory _get_xml_nodevalues(xml_desc, 'XMP:Subject', iptc_data.keywords) for xml_caption in xml_desc.getElementsByTagName( 'IPTC:Caption-Abstract'): if xml_caption.firstChild: iptc_data.caption = xml_caption.firstChild.nodeValue if not iptc_data.caption: for xml_caption in xml_desc.getElementsByTagName( 'IFD0:ImageDescription'): if xml_caption.firstChild: iptc_data.caption = xml_caption.firstChild.nodeValue _parse_datetime_original(xml_desc, iptc_data, image_file) for xml_element in xml_desc.getElementsByTagName('XMP-xmp:Rating'): if xml_element.firstChild: iptc_data.rating = int(xml_element.firstChild.nodeValue) _parse_gps(xml_desc, iptc_data) for xml_element in xml_desc.getElementsByTagName( 'File:ImageWidth'): if xml_element.firstChild: iptc_data.image_width = int( xml_element.firstChild.nodeValue) for xml_element in xml_desc.getElementsByTagName( 'File:ImageHeight'): if xml_element.firstChild: iptc_data.image_height = int( xml_element.firstChild.nodeValue) for xml_element in xml_desc.getElementsByTagName( 'XMP-crs:AlreadyApplied'): if xml_element.firstChild: iptc_data.already_applied = xml_element.firstChild.nodeValue #string_rectangles = [] #_get_xml_nodevalues(xml_desc, 'XMP-MP:RegionRectangle', string_rectangles) #for string_rectangle in string_rectangles: # rectangle = [] # for c in string_rectangle.split(','): # rectangle.append(float(c)) # iptc_data.region_rectangles.append(rectangle) #_get_xml_nodevalues(xml_desc, 'XMP-MP:RegionPersonDisplayName', # iptc_data.region_names) # Handle Region tags _parse_regions(xml_desc, iptc_data) break xml_data.unlink() except parsers.expat.ExpatError, ex: su.perr('Could not parse exiftool output %s: %s' % (output, ex))
command.append(u'-RegionType=Face') elif new_persons != None: command.append('-RegionName=') if new_rectangles: for rectangle in new_rectangles: command.append('-RegionAreaX=%s' % (str(rectangle[0]))) command.append('-RegionAreaY=%s' % (str(rectangle[1]))) command.append('-RegionAreaW=%s' % (str(rectangle[2]))) command.append('-RegionAreaH=%s' % (str(rectangle[3]))) command.append('-RegionAreaUnit=normalized') elif new_rectangles != None: command.append('-RegionAreaX=') command.append("-iptc:CodedCharacterSet=ESC % G") command.append(filepath) result = su.fsdec(su.execandcombine(command)) if tmp: os.remove(tmp) if result.find("1 image files updated") != -1: if result != "1 image files updated": su.pout(result) # wipe out the back file created by exiftool backup_file = filepath + "_original" if os.path.exists(backup_file): os.remove(backup_file) return True else: su.perr("Failed to update IPTC data in image %s: %s" % (filepath, result)) return False
def get_iptc_data(image_file): """get caption, keywords, datetime, rating, and GPS info all in one operation.""" output = su.execandcombine( (EXIFTOOL, "-X", "-m", "-q", "-q", '-c', '%.6f', "-Keywords", "-Caption-Abstract", "-DateTimeOriginal", "-Rating", "-GPSLatitude", "-Subject", "-GPSLongitude", "-RegionRectangle", "-RegionPersonDisplayName", image_file)) keywords = [] caption = None date_time_original = None rating = 0 gps = None region_names = [] region_rectangles = [] if output: try: gps_latitude = None gps_longitude = None xml_data = minidom.parseString(output) for xml_desc in xml_data.getElementsByTagName('rdf:Description'): _get_xml_nodevalues(xml_desc, 'IPTC:Keywords', keywords) # Keywords can also be stored as Subject in the XMP directory _get_xml_nodevalues(xml_desc, 'XMP:Subject', keywords) for xml_caption in xml_data.getElementsByTagName( 'IPTC:Caption-Abstract'): caption = xml_caption.firstChild.nodeValue for xml_element in xml_data.getElementsByTagName( 'ExifIFD:DateTimeOriginal'): if not xml_element.firstChild: continue try: date_time_original = time.strptime( xml_element.firstChild.nodeValue, '%Y:%m:%d %H:%M:%S') date_time_original = datetime.datetime( date_time_original.tm_year, date_time_original.tm_mon, date_time_original.tm_mday, date_time_original.tm_hour, date_time_original.tm_min, date_time_original.tm_sec) except ValueError, _ve: su.perr('Exiftool returned an invalid ' 'date %s for %s - ignoring.' % ( xml_element.firstChild.nodeValue, image_file)) for xml_element in xml_data.getElementsByTagName( 'XMP-xmp:Rating'): rating = int(xml_element.firstChild.nodeValue) for xml_element in xml_data.getElementsByTagName( 'Composite:GPSLatitude'): gps_latitude = xml_element.firstChild.nodeValue for xml_element in xml_data.getElementsByTagName( 'Composite:GPSLongitude'): gps_longitude = xml_element.firstChild.nodeValue string_rectangles = [] _get_xml_nodevalues(xml_desc, 'XMP-MP:RegionRectangle', string_rectangles) for string_rectangle in string_rectangles: rectangle = [] for c in string_rectangle.split(','): rectangle.append(float(c)) region_rectangles.append(rectangle) _get_xml_nodevalues(xml_desc, 'XMP-MP:RegionPersonDisplayName', region_names) xml_data.unlink() if gps_latitude and gps_longitude: gps = imageutils.GpsLocation().from_composite(gps_latitude, gps_longitude) except parsers.expat.ExpatError, ex: su.perr('Could not parse exiftool output %s: %s' % ( output, ex))
def update_iptcdata(filepath, new_caption, new_keywords, new_datetime, new_rating, new_gps, new_rectangles, new_persons): """Updates the caption and keywords of an image file.""" # Some cameras write into ImageDescription, so we wipe it out to not cause # conflicts with Caption-Abstract. We also wipe out the XMP Subject and # Description tags (we use Keywords and Caption-Abstract). command = [EXIFTOOL, '-F', '-m', '-P', '-ImageDescription=', '-Subject=', '-Description='] tmp = None if not new_caption is None: if not new_caption: command.append('-Caption-Abstract=') else: tmpfd, tmp = tempfile.mkstemp(dir="/var/tmp") os.close(tmpfd) file1 = open(tmp, "w") print >> file1, new_caption.encode("utf-8") file1.close() command.append('-Caption-Abstract<=%s' % (tmp)) if new_datetime: command.append('-DateTimeOriginal="%s"' % ( new_datetime.strftime("%Y:%m:%d %H:%M:%S"))) if new_keywords: for keyword in new_keywords: command.append(u'-keywords=%s' % (keyword)) elif new_keywords != None: command.append('-keywords=') if new_rating >= 0: command.append('-Rating=%d' % (new_rating)) if new_gps: command.append('-c') command.append('%.6f') command.append('-GPSLatitude="%f"' % (abs(new_gps.latitude))) command.append('-GPSLatitudeRef=' + new_gps.latitude_ref()) command.append('-GPSLongitude="%f"' % (abs(new_gps.longitude))) command.append('-GPSLongitudeRef=' + new_gps.longitude_ref()) if new_persons: for person in new_persons: command.append(u'-RegionPersonDisplayName=%s' % (person)) elif new_persons != None: command.append('-RegionPersonDisplayName=') if new_rectangles: for rectangle in new_rectangles: command.append('-RegionRectangle=%s' % ( ','.join(str(c) for c in rectangle))) elif new_rectangles != None: command.append('-RegionRectangle=') command.append("-iptc:CodedCharacterSet=ESC % G") command.append(filepath) result = su.fsdec(su.execandcombine(command)) if tmp: os.remove(tmp) if result.find("1 image files updated") != -1: if result != "1 image files updated": su.pout(result) # wipe out the back file created by exiftool backup_file = filepath + "_original" if os.path.exists(backup_file): os.remove(backup_file) return True else: su.perr("Failed to update IPTC data in image %s: %s" % ( filepath, result)) return False
def get_iptc_data(image_file): """get caption, keywords, datetime, rating, and GPS info all in one operation.""" output = su.execandcombine( (EXIFTOOL, "-X", "-m", "-q", "-q", '-c', '%.6f', "-Keywords", "-Caption-Abstract", "-DateTimeOriginal", "-Rating", "-GPSLatitude", "-Subject", "-GPSLongitude", "-RegionRectangle", "-RegionPersonDisplayName", image_file)) keywords = [] caption = None date_time_original = None rating = 0 gps = None region_names = [] region_rectangles = [] if output: try: gps_latitude = None gps_longitude = None xml_data = minidom.parseString(output) for xml_desc in xml_data.getElementsByTagName('rdf:Description'): _get_xml_nodevalues(xml_desc, 'IPTC:Keywords', keywords) # Keywords can also be stored as Subject in the XMP directory _get_xml_nodevalues(xml_desc, 'XMP:Subject', keywords) for xml_caption in xml_data.getElementsByTagName( 'IPTC:Caption-Abstract'): caption = xml_caption.firstChild.nodeValue for xml_element in xml_data.getElementsByTagName( 'ExifIFD:DateTimeOriginal'): if not xml_element.firstChild: continue try: date_time_original = time.strptime( xml_element.firstChild.nodeValue, '%Y:%m:%d %H:%M:%S') date_time_original = datetime.datetime( date_time_original.tm_year, date_time_original.tm_mon, date_time_original.tm_mday, date_time_original.tm_hour, date_time_original.tm_min, date_time_original.tm_sec) except ValueError, _ve: su.perr('Exiftool returned an invalid ' 'date %s for %s - ignoring.' % (xml_element.firstChild.nodeValue, image_file)) for xml_element in xml_data.getElementsByTagName( 'XMP-xmp:Rating'): rating = int(xml_element.firstChild.nodeValue) for xml_element in xml_data.getElementsByTagName( 'Composite:GPSLatitude'): gps_latitude = xml_element.firstChild.nodeValue for xml_element in xml_data.getElementsByTagName( 'Composite:GPSLongitude'): gps_longitude = xml_element.firstChild.nodeValue string_rectangles = [] _get_xml_nodevalues(xml_desc, 'XMP-MP:RegionRectangle', string_rectangles) for string_rectangle in string_rectangles: rectangle = [] for c in string_rectangle.split(','): rectangle.append(float(c)) region_rectangles.append(rectangle) _get_xml_nodevalues(xml_desc, 'XMP-MP:RegionPersonDisplayName', region_names) xml_data.unlink() if gps_latitude and gps_longitude: gps = imageutils.GpsLocation().from_composite( gps_latitude, gps_longitude) except parsers.expat.ExpatError, ex: su.perr('Could not parse exiftool output %s: %s' % (output, ex))
def update_iptcdata(filepath, new_caption, new_keywords, new_datetime, new_rating, new_gps, new_rectangles, new_persons): """Updates the caption and keywords of an image file.""" # Some cameras write into ImageDescription, so we wipe it out to not cause # conflicts with Caption-Abstract. We also wipe out the XMP Subject and # Description tags (we use Keywords and Caption-Abstract). command = [ EXIFTOOL, '-F', '-m', '-P', '-ImageDescription=', '-Subject=', '-Description=' ] tmp = None if not new_caption is None: if not new_caption: command.append('-Caption-Abstract=') else: tmpfd, tmp = tempfile.mkstemp(dir="/var/tmp") os.close(tmpfd) file1 = open(tmp, "w") print >> file1, new_caption.encode("utf-8") file1.close() command.append('-Caption-Abstract<=%s' % (tmp)) if new_datetime: command.append('-DateTimeOriginal="%s"' % (new_datetime.strftime("%Y:%m:%d %H:%M:%S"))) if new_keywords: for keyword in new_keywords: command.append(u'-keywords=%s' % (keyword)) elif new_keywords != None: command.append('-keywords=') if new_rating >= 0: command.append('-Rating=%d' % (new_rating)) if new_gps: command.append('-c') command.append('%.6f') command.append('-GPSLatitude="%f"' % (abs(new_gps.latitude))) command.append('-GPSLatitudeRef=' + new_gps.latitude_ref()) command.append('-GPSLongitude="%f"' % (abs(new_gps.longitude))) command.append('-GPSLongitudeRef=' + new_gps.longitude_ref()) if new_persons: for person in new_persons: command.append(u'-RegionPersonDisplayName=%s' % (person)) elif new_persons != None: command.append('-RegionPersonDisplayName=') if new_rectangles: for rectangle in new_rectangles: command.append('-RegionRectangle=%s' % (','.join(str(c) for c in rectangle))) elif new_rectangles != None: command.append('-RegionRectangle=') command.append("-iptc:CodedCharacterSet=ESC % G") command.append(filepath) result = su.fsdec(su.execandcombine(command)) if tmp: os.remove(tmp) if result.find("1 image files updated") != -1: if result != "1 image files updated": su.pout(result) # wipe out the back file created by exiftool backup_file = filepath + "_original" if os.path.exists(backup_file): os.remove(backup_file) return True else: su.perr("Failed to update IPTC data in image %s: %s" % (filepath, result)) return False
def _get_iptc_data_exiftool(image_file): """Returns IptcData for an image file using exiftool.""" args = [EXIFTOOL, "-X", "-m", "-q", "-q", '-c', '%.6f', "-Keywords", "-Caption-Abstract", "-ImageDescription", "-DateTimeOriginal", "-Rating", "-GPSLatitude", "-Subject", "-GPSLongitude", "-RegionName", "-RegionType", "-RegionAreaX", "-RegionAreaY", "-RegionAreaW", "-RegionAreaH", "-ImageWidth", "-ImageHeight", "-HierarchicalSubject", "-AlreadyApplied", image_file ] output = su.execandcombine(args) if not output: return None iptc_data = None try: xml_data = minidom.parseString(output) for xml_desc in xml_data.getElementsByTagName('rdf:Description'): iptc_data = IptcData() iptc_data.rating = 0 iptc_data.image_file = xml_desc.getAttribute("rdf:about") iptc_data.keywords = [] _get_xml_nodevalues(xml_desc, 'IPTC:Keywords', iptc_data.keywords) _get_xml_nodevalues(xml_desc, 'XMP-lr:HierarchicalSubject', iptc_data.hierarchical_subject) # Keywords can also be stored as Subject in the XMP directory _get_xml_nodevalues(xml_desc, 'XMP:Subject', iptc_data.keywords) for xml_caption in xml_desc.getElementsByTagName('IPTC:Caption-Abstract'): if xml_caption.firstChild: iptc_data.caption = xml_caption.firstChild.nodeValue if not iptc_data.caption: for xml_caption in xml_desc.getElementsByTagName('IFD0:ImageDescription'): if xml_caption.firstChild: iptc_data.caption = xml_caption.firstChild.nodeValue _parse_datetime_original(xml_desc, iptc_data, image_file) for xml_element in xml_desc.getElementsByTagName('XMP-xmp:Rating'): if xml_element.firstChild: iptc_data.rating = int(xml_element.firstChild.nodeValue) _parse_gps(xml_desc, iptc_data) for xml_element in xml_desc.getElementsByTagName('File:ImageWidth'): if xml_element.firstChild: iptc_data.image_width = int(xml_element.firstChild.nodeValue) for xml_element in xml_desc.getElementsByTagName('File:ImageHeight'): if xml_element.firstChild: iptc_data.image_height = int(xml_element.firstChild.nodeValue) for xml_element in xml_desc.getElementsByTagName('XMP-crs:AlreadyApplied'): if xml_element.firstChild: iptc_data.already_applied = xml_element.firstChild.nodeValue #string_rectangles = [] #_get_xml_nodevalues(xml_desc, 'XMP-MP:RegionRectangle', string_rectangles) #for string_rectangle in string_rectangles: # rectangle = [] # for c in string_rectangle.split(','): # rectangle.append(float(c)) # iptc_data.region_rectangles.append(rectangle) #_get_xml_nodevalues(xml_desc, 'XMP-MP:RegionPersonDisplayName', # iptc_data.region_names) # Handle Region tags _parse_regions(xml_desc, iptc_data) break xml_data.unlink() except parsers.expat.ExpatError, ex: su.perr('Could not parse exiftool output %s: %s' % (output, ex))
command.append(u'-RegionType=Face') elif new_persons != None: command.append('-RegionName=') if new_rectangles: for rectangle in new_rectangles: command.append('-RegionAreaX=%s' % (str(rectangle[0]))) command.append('-RegionAreaY=%s' % (str(rectangle[1]))) command.append('-RegionAreaW=%s' % (str(rectangle[2]))) command.append('-RegionAreaH=%s' % (str(rectangle[3]))) command.append('-RegionAreaUnit=normalized') elif new_rectangles != None: command.append('-RegionAreaX=') command.append("-iptc:CodedCharacterSet=ESC % G") command.append(filepath) result = su.fsdec(su.execandcombine(command)) if tmp: os.remove(tmp) if result.find("1 image files updated") != -1: if result != "1 image files updated": su.pout(result) # wipe out the back file created by exiftool backup_file = filepath + "_original" if os.path.exists(backup_file): os.remove(backup_file) return True else: su.perr("Failed to update IPTC data in image %s: %s" % ( filepath, result)) return False