def test_pickle_exif_tag(self): tags = [] tags.append(ExifTag('Exif.Image.DateTime', datetime.datetime(2010, 12, 22, 19, 21, 0))) tags.append(ExifTag('Exif.GPSInfo.GPSDateStamp', datetime.date.today())) tags.append(ExifTag('Exif.Image.Copyright', '(C) 2010 Santa Claus')) tags.append(ExifTag('Exif.GPSInfo.GPSVersionID', '0')) tags.append(ExifTag('Exif.Pentax.Temperature', '14')) tags.append(ExifTag('Exif.Photo.UserComment', 'foo bar baz')) tags.append(ExifTag('Exif.Image.BitsPerSample', 8)) tags.append(ExifTag('Exif.Image.TimeZoneOffset', 7)) tags.append(ExifTag('Exif.Image.ImageWidth', 7492)) tags.append(ExifTag('Exif.OlympusCs.ManometerReading', 29)) tags.append(ExifTag('Exif.Image.XResolution', make_fraction(7, 3))) tags.append(ExifTag('Exif.Image.BaselineExposure', make_fraction(-7, 3))) tags.append(ExifTag('Exif.Photo.ExifVersion', '0100')) for tag in tags: s = pickle.dumps(tag) t = pickle.loads(s) self.assert_(isinstance(t, ExifTag)) self.assertEqual(t.key, tag.key) self.assertEqual(t.type, tag.type) self.assertEqual(t.name, tag.name) self.assertEqual(t.label, tag.label) self.assertEqual(t.description, tag.description) self.assertEqual(t.section_name, tag.section_name) self.assertEqual(t.section_description, tag.section_description) self.assertEqual(t.raw_value, tag.raw_value) self.assertEqual(t.value, tag.value) self.assertEqual(t.human_value, tag.human_value)
def test_convert_to_string_rational(self): # Valid values tag = XmpTag('Xmp.xmpDM.videoPixelAspectRatio') self.assertEqual(tag.type, 'Rational') self.assertEqual(tag._convert_to_string(make_fraction(5, 3), 'Rational'), '5/3') self.assertEqual(tag._convert_to_string(make_fraction(-5, 3), 'Rational'), '-5/3') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_string, 'invalid', 'Rational')
def test_convert_to_string_rational(self): # Valid values tag = ExifTag("Exif.Image.XResolution") self.assertEqual(tag.type, "Rational") self.assertEqual(tag._convert_to_string(make_fraction(5, 3)), "5/3") # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, "invalid") self.failUnlessRaises(ExifValueError, tag._convert_to_string, make_fraction(-5, 3))
def test_convert_to_string_srational(self): # Valid values tag = ExifTag('Exif.Image.BaselineExposure') self.assertEqual(tag.type, 'SRational') self.assertEqual(tag._convert_to_string(make_fraction(5, 3)), '5/3') self.assertEqual(tag._convert_to_string(make_fraction(-5, 3)), '-5/3') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, 'invalid')
def test_convert_to_string_srational(self): # Valid values tag = ExifTag("Exif.Image.BaselineExposure") self.assertEqual(tag.type, "SRational") self.assertEqual(tag._convert_to_string(make_fraction(5, 3)), "5/3") self.assertEqual(tag._convert_to_string(make_fraction(-5, 3)), "-5/3") # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, "invalid")
def test_convert_to_string_rational(self): # Valid values tag = ExifTag('Exif.Image.XResolution') self.assertEqual(tag.type, 'Rational') self.assertEqual(tag._convert_to_string(make_fraction(5, 3)), '5/3') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, 'invalid') self.failUnlessRaises(ExifValueError, tag._convert_to_string, make_fraction(-5, 3))
def test_convert_to_python_srational(self): # Valid values tag = ExifTag('Exif.Image.BaselineExposure') self.assertEqual(tag.type, 'SRational') self.assertEqual(tag._convert_to_python('5/3'), make_fraction(5, 3)) self.assertEqual(tag._convert_to_python('-5/3'), make_fraction(-5, 3)) # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_python, 'invalid') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '5 / 3') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '5/-3')
def test_convert_to_string_rational(self): # Valid values tag = XmpTag('Xmp.xmpDM.videoPixelAspectRatio') self.assertEqual(tag.type, 'Rational') self.assertEqual( tag._convert_to_string(make_fraction(5, 3), 'Rational'), '5/3') self.assertEqual( tag._convert_to_string(make_fraction(-5, 3), 'Rational'), '-5/3') # Invalid values self.assertRaises(XmpValueError, tag._convert_to_string, 'invalid', 'Rational')
def testReadMetadata(self): """ Perform various tests on reading the metadata contained in a file. """ # Check that the reference file is not corrupted filename = os.path.join('data', 'smiley1.jpg') filepath = testutils.get_absolute_file_path(filename) md5sum = 'c066958457c685853293058f9bf129c1' self.assertCorrectFile(filepath, md5sum) # Read the image metadata image = pyexiv2.ImageMetadata(filepath) image.read() # Exhaustive tests on the values of EXIF metadata exifTags = [('Exif.Image.ImageDescription', str, 'Well it is a smiley that happens to be green'), ('Exif.Image.XResolution', FRACTION, make_fraction(72, 1)), ('Exif.Image.YResolution', FRACTION, make_fraction(72, 1)), ('Exif.Image.ResolutionUnit', int, 2), ('Exif.Image.Software', str, 'ImageReady'), ('Exif.Image.DateTime', datetime.datetime, datetime.datetime(2004, 7, 13, 21, 23, 44)), ('Exif.Image.Artist', str, 'No one'), ('Exif.Image.Copyright', str, ''), ('Exif.Image.ExifTag', long, 226L), ('Exif.Photo.Flash', int, 80), ('Exif.Photo.PixelXDimension', long, 167L), ('Exif.Photo.PixelYDimension', long, 140L)] self.assertEqual(image.exif_keys, [tag[0] for tag in exifTags]) for key, ktype, value in exifTags: self.check_type_and_value(image[key], ktype, value) # Exhaustive tests on the values of IPTC metadata iptcTags = [('Iptc.Application2.Caption', str, ['yelimS green faced dude (iptc caption)']), ('Iptc.Application2.Writer', str, ['Nobody']), ('Iptc.Application2.Byline', str, ['Its me']), ('Iptc.Application2.ObjectName', str, ['GreeenDude']), ('Iptc.Application2.DateCreated', datetime.date, [datetime.date(2004, 7, 13)]), ('Iptc.Application2.City', str, ['Seattle']), ('Iptc.Application2.ProvinceState', str, ['WA']), ('Iptc.Application2.CountryName', str, ['USA']), ('Iptc.Application2.Category', str, ['Things']), ('Iptc.Application2.Keywords', str, ['Green', 'Smiley', 'Dude']), ('Iptc.Application2.Copyright', str, ['\xa9 2004 Nobody'])] self.assertEqual(image.iptc_keys, [tag[0] for tag in iptcTags]) for key, ktype, values in iptcTags: self.check_type_and_values(image[key], ktype, values)
def add_exif_using_timestamp(filename, time, points, offset_time=0, offset_angle=0): ''' Find lat, lon and bearing of filename and write to EXIF. ''' metadata = pyexiv2.ImageMetadata(filename) metadata.read() # subtract offset in s beween gpx time and exif time t = time - datetime.timedelta(seconds=offset_time) try: lat, lon, bearing, elevation, speed = interpolate_lat_lon(points, t) lat_deg = decimal_to_dms(lat, ["S", "N"]) lon_deg = decimal_to_dms(lon, ["W", "E"]) # convert decimal coordinates into degrees, minutes and seconds as fractions for EXIF exiv_lat = (make_fraction(lat_deg[0], 1), make_fraction(int(lat_deg[1]), 1), make_fraction(int(lat_deg[2] * 1000000), 1000000)) exiv_lon = (make_fraction(lon_deg[0], 1), make_fraction(int(lon_deg[1]), 1), make_fraction(int(lon_deg[2] * 1000000), 1000000)) # convert direction into fraction bearing = offset_bearing(bearing, offset_angle) exiv_bearing = make_fraction(int(bearing * 100), 100) # add to exif metadata["Exif.GPSInfo.GPSLatitude"] = exiv_lat metadata["Exif.GPSInfo.GPSLatitudeRef"] = lat_deg[3] metadata["Exif.GPSInfo.GPSLongitude"] = exiv_lon metadata["Exif.GPSInfo.GPSLongitudeRef"] = lon_deg[3] metadata["Exif.Image.GPSTag"] = 654 metadata["Exif.GPSInfo.GPSMapDatum"] = "WGS-84" metadata["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0' metadata["Exif.GPSInfo.GPSImgDirection"] = exiv_bearing metadata["Exif.GPSInfo.GPSImgDirectionRef"] = "T" if elevation is not None: exiv_elevation = make_fraction(abs(int(elevation * 10)), 10) metadata["Exif.GPSInfo.GPSAltitude"] = exiv_elevation metadata[ "Exif.GPSInfo.GPSAltitudeRef"] = '0' if elevation >= 0 else '1' metadata.write() print("Added geodata to {}: {}, {:.6f}, {:.6f}, {:.1f}".format( os.path.basename(filename), time, lat, lon, bearing)) if (speed is not None and speed < 0.6): move_into_dir(filename, os.path.dirname(filename) + "\S0") except ValueError, e: print("Skipping {0}: {1}".format(filename, e))
def test_setitem(self): self.metadata.read() # Set new tags key = 'Exif.Photo.ExposureBiasValue' tag = ExifTag(key, make_fraction(0, 3)) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['exif']) self.failUnlessEqual(self.metadata._tags['exif'][key], tag) key = 'Iptc.Application2.City' tag = IptcTag(key, ['Barcelona']) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['iptc']) self.failUnlessEqual(self.metadata._tags['iptc'][key], tag) key = 'Xmp.dc.description' tag = XmpTag(key, {'x-default': 'Sunset picture.'}) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['xmp']) self.failUnlessEqual(self.metadata._tags['xmp'][key], tag) # Replace existing tags key = 'Exif.Photo.ExifVersion' tag = ExifTag(key, '0220') self.metadata[key] = tag self.failUnless(key in self.metadata._tags['exif']) self.failUnlessEqual(self.metadata._tags['exif'][key], tag) key = 'Iptc.Application2.Caption' tag = IptcTag(key, ['Sunset on Barcelona.']) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['iptc']) self.failUnlessEqual(self.metadata._tags['iptc'][key], tag) key = 'Xmp.dc.subject' tag = XmpTag(key, ['sunset', 'Barcelona', 'beautiful', 'beach']) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['xmp']) self.failUnlessEqual(self.metadata._tags['xmp'][key], tag)
def test_setitem(metadata_ro): m = metadata_ro.metadata m.read() # Set new tags key = 'Exif.Photo.ExposureBiasValue' tag = ExifTag(key, make_fraction(0, 3)) m[key] = tag assert key in m._tags['exif'] assert m._tags['exif'][key] == tag key = 'Iptc.Application2.City' tag = IptcTag(key, ['Barcelona']) m[key] = tag assert key in m._tags['iptc'] assert m._tags['iptc'][key] == tag key = 'Xmp.dc.description' tag = XmpTag(key, {'x-default': 'Sunset picture.'}) m[key] = tag assert key in m._tags['xmp'] assert m._tags['xmp'][key] == tag # Replace existing tags key = 'Exif.Photo.ExifVersion' tag = ExifTag(key, '0220') m[key] = tag assert key in m._tags['exif'] assert m._tags['exif'][key] == tag key = 'Iptc.Application2.Caption' tag = IptcTag(key, ['Sunset on Barcelona.']) m[key] = tag assert key in m._tags['iptc'] assert m._tags['iptc'][key] == tag key = 'Xmp.dc.subject' tag = XmpTag(key, ['sunset', 'Barcelona', 'beautiful', 'beach']) m[key] = tag assert key in m._tags['xmp'] assert m._tags['xmp'][key] == tag
def test_convert_to_python_rational(self): # Valid values tag = XmpTag('Xmp.xmpDM.videoPixelAspectRatio') self.assertEqual(tag.type, 'Rational') self.assertEqual(tag._convert_to_python('5/3', 'Rational'), make_fraction(5, 3)) self.assertEqual(tag._convert_to_python('-5/3', 'Rational'), make_fraction(-5, 3)) # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_python, 'invalid', 'Rational') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '5 / 3', 'Rational') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '5/-3', 'Rational')
def add_gps_to_exif( filename, lat, lon, bearing, elevation, updated_filename=None, remove_image_description=True, ): """ Given lat, lon, bearing, elevation, write to EXIF """ # TODO: use this within add_exif_using_timestamp if updated_filename is not None: shutil.copy2(filename, updated_filename) filename = updated_filename metadata = pyexiv2.ImageMetadata(filename) metadata.read() lat_deg = to_deg(lat, ["S", "N"]) lon_deg = to_deg(lon, ["W", "E"]) # convert decimal coordinates into degrees, minutes and seconds as fractions for EXIF exiv_lat = ( make_fraction(lat_deg[0], 1), make_fraction(int(lat_deg[1]), 1), make_fraction(int(lat_deg[2] * 1000000), 1000000), ) exiv_lon = ( make_fraction(lon_deg[0], 1), make_fraction(int(lon_deg[1]), 1), make_fraction(int(lon_deg[2] * 1000000), 1000000), ) # convert direction into fraction exiv_bearing = make_fraction(int(bearing * 100), 100) # add to exif metadata["Exif.GPSInfo.GPSLatitude"] = exiv_lat metadata["Exif.GPSInfo.GPSLatitudeRef"] = lat_deg[3] metadata["Exif.GPSInfo.GPSLongitude"] = exiv_lon metadata["Exif.GPSInfo.GPSLongitudeRef"] = lon_deg[3] metadata["Exif.Image.GPSTag"] = 654 metadata["Exif.GPSInfo.GPSMapDatum"] = "WGS-84" metadata["Exif.GPSInfo.GPSVersionID"] = "2 0 0 0" metadata["Exif.GPSInfo.GPSImgDirection"] = exiv_bearing metadata["Exif.GPSInfo.GPSImgDirectionRef"] = "T" if remove_image_description: metadata["Exif.Image.ImageDescription"] = [] if elevation is not None: exiv_elevation = make_fraction(int(abs(elevation) * 100), 100) metadata["Exif.GPSInfo.GPSAltitude"] = exiv_elevation metadata[ "Exif.GPSInfo.GPSAltitudeRef"] = "0" if elevation >= 0 else "1" metadata.write()
def add_exif_using_timestamp(filename, points, offset_time=0): ''' Find lat, lon and bearing of filename and write to EXIF. ''' metadata = pyexiv2.ImageMetadata(filename) metadata.read() t = metadata['Exif.Photo.DateTimeOriginal'].value try: t_subsec = metadata['Exif.Photo.SubSecTimeOriginal'].value t_subsec_delta = datetime.timedelta(seconds=float("0."+t_subsec)) t += t_subsec_delta except KeyError: pass # subtract offset in s beween gpx time and exif time t = t - datetime.timedelta(seconds=offset_time) try: lat, lon, bearing, elevation = interpolate_lat_lon(points, t) lat_deg = to_deg(lat, ["S", "N"]) lon_deg = to_deg(lon, ["W", "E"]) # convert decimal coordinates into degrees, minutes and seconds as fractions for EXIF exiv_lat = (make_fraction(lat_deg[0],1), make_fraction(int(lat_deg[1]),1), make_fraction(int(lat_deg[2]*1000000),1000000)) exiv_lon = (make_fraction(lon_deg[0],1), make_fraction(int(lon_deg[1]),1), make_fraction(int(lon_deg[2]*1000000),1000000)) # convert direction into fraction exiv_bearing = make_fraction(int(bearing*100),100) # add to exif metadata["Exif.GPSInfo.GPSLatitude"] = exiv_lat metadata["Exif.GPSInfo.GPSLatitudeRef"] = lat_deg[3] metadata["Exif.GPSInfo.GPSLongitude"] = exiv_lon metadata["Exif.GPSInfo.GPSLongitudeRef"] = lon_deg[3] metadata["Exif.Image.GPSTag"] = 654 metadata["Exif.GPSInfo.GPSMapDatum"] = "WGS-84" metadata["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0' metadata["Exif.GPSInfo.GPSImgDirection"] = exiv_bearing metadata["Exif.GPSInfo.GPSImgDirectionRef"] = "T" if elevation is not None: exiv_elevation = make_fraction(abs(int(elevation*10)),10) metadata["Exif.GPSInfo.GPSAltitude"] = exiv_elevation metadata["Exif.GPSInfo.GPSAltitudeRef"] = '0' if elevation >= 0 else '1' try: metadata.write() print("Added geodata to: {0} ({1}, {2}, {3}), altitude {4}".format(filename, lat, lon, bearing, elevation)) except IOError: print("Failed writing to: {0}".format(filename)) except ValueError, e: print("Skipping {0}: {1}".format(filename, e))
def test_make_fraction(self): if Fraction is not None: self.assertEqual(make_fraction(3, 5), Fraction(3, 5)) self.assertEqual(make_fraction(-3, 5), Fraction(-3, 5)) self.assertEqual(make_fraction('3/2'), Fraction(3, 2)) self.assertEqual(make_fraction('-3/4'), Fraction(-3, 4)) self.assertEqual(make_fraction('0/0'), Fraction(0, 1)) else: self.assertEqual(make_fraction(3, 5), Rational(3, 5)) self.assertEqual(make_fraction(-3, 5), Rational(-3, 5)) self.assertEqual(make_fraction('3/2'), Rational(3, 2)) self.assertEqual(make_fraction('-3/4'), Rational(-3, 4)) self.assertEqual(make_fraction('0/0'), Rational(0, 1)) self.assertRaises(ZeroDivisionError, make_fraction, 3, 0) self.assertRaises(ZeroDivisionError, make_fraction, '3/0') self.assertRaises(TypeError, make_fraction, 5, 3, 2) self.assertRaises(TypeError, make_fraction, None)
def add_exif_using_timestamp(filename, points, offset_time=0, timestamp=None, orientation=1, image_description=None): ''' Find lat, lon and bearing of filename and write to EXIF. ''' metadata = pyexiv2.ImageMetadata(filename) metadata.read() if timestamp: metadata['Exif.Photo.DateTimeOriginal'] = timestamp t = metadata['Exif.Photo.DateTimeOriginal'].value # subtract offset in s beween gpx time and exif time t = t - datetime.timedelta(seconds=offset_time) try: lat, lon, bearing, elevation = interpolate_lat_lon(points, t) lat_deg = to_deg(lat, ["S", "N"]) lon_deg = to_deg(lon, ["W", "E"]) # convert decimal coordinates into degrees, minutes and seconds as fractions for EXIF exiv_lat = (make_fraction(lat_deg[0],1), make_fraction(int(lat_deg[1]),1), make_fraction(int(lat_deg[2]*1000000),1000000)) exiv_lon = (make_fraction(lon_deg[0],1), make_fraction(int(lon_deg[1]),1), make_fraction(int(lon_deg[2]*1000000),1000000)) # convert direction into fraction exiv_bearing = make_fraction(int(bearing*1000),1000) # add to exif metadata["Exif.GPSInfo.GPSLatitude"] = exiv_lat metadata["Exif.GPSInfo.GPSLatitudeRef"] = lat_deg[3] metadata["Exif.GPSInfo.GPSLongitude"] = exiv_lon metadata["Exif.GPSInfo.GPSLongitudeRef"] = lon_deg[3] metadata["Exif.Image.GPSTag"] = 654 metadata["Exif.GPSInfo.GPSMapDatum"] = "WGS-84" metadata["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0' metadata["Exif.GPSInfo.GPSImgDirection"] = exiv_bearing metadata["Exif.GPSInfo.GPSImgDirectionRef"] = "T" metadata["Exif.Image.Orientation"] = orientation if image_description is not None: metadata["Exif.Image.ImageDescription"] = image_description if elevation is not None: exiv_elevation = make_fraction(int(abs(elevation)*100),100) metadata["Exif.GPSInfo.GPSAltitude"] = exiv_elevation metadata["Exif.GPSInfo.GPSAltitudeRef"] = '0' if elevation >= 0 else '1' metadata.write() print("Added geodata to: {0} ({1}, {2}, {3}), altitude {4}".format(filename, lat, lon, bearing, elevation)) except ValueError as e: print("Skipping {0}: {1}".format(filename, e))
def test_convert_to_python_rational(self): # Valid values tag = ExifTag("Exif.Image.XResolution") self.assertEqual(tag.type, "Rational") self.assertEqual(tag._convert_to_python("5/3"), make_fraction(5, 3)) # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_python, "invalid") self.failUnlessRaises(ExifValueError, tag._convert_to_python, "-5/3") self.failUnlessRaises(ExifValueError, tag._convert_to_python, "5 / 3") self.failUnlessRaises(ExifValueError, tag._convert_to_python, "5/-3")
def test_convert_to_python_rational(self): # Valid values tag = ExifTag('Exif.Image.XResolution') self.assertEqual(tag.type, 'Rational') self.assertEqual(tag._convert_to_python('5/3'), make_fraction(5, 3)) # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_python, 'invalid') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '-5/3') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '5 / 3') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '5/-3')
def add_exif_using_timestamp(filename, time, points, offset_time=0, offset_bearing=0): ''' Find lat, lon and bearing of filename and write to EXIF. ''' metadata = pyexiv2.ImageMetadata(filename) metadata.read() print(time) print(datetime.timedelta(seconds=offset_time)) #print(points) # subtract offset in s beween gpx time and exif time t = time - datetime.timedelta(seconds=offset_time) try: lat, lon, bearing, elevation = interpolate_lat_lon(points, t) lat_deg = decimal_to_dms(lat, ["S", "N"]) lon_deg = decimal_to_dms(lon, ["W", "E"]) # convert decimal coordinates into degrees, minutes and seconds as fractions for EXIF exiv_lat = (make_fraction(lat_deg[0],1), make_fraction(int(lat_deg[1]),1), make_fraction(int(lat_deg[2]*1000000),1000000)) exiv_lon = (make_fraction(lon_deg[0],1), make_fraction(int(lon_deg[1]),1), make_fraction(int(lon_deg[2]*1000000),1000000)) corrected_bearing = (bearing + offset_bearing) % 360 # convert direction into fraction exiv_bearing = make_fraction(int(corrected_bearing*100),100) # add to exif metadata["Exif.GPSInfo.GPSLatitude"] = exiv_lat metadata["Exif.GPSInfo.GPSLatitudeRef"] = lat_deg[3] metadata["Exif.GPSInfo.GPSLongitude"] = exiv_lon metadata["Exif.GPSInfo.GPSLongitudeRef"] = lon_deg[3] metadata["Exif.Image.GPSTag"] = 654 metadata["Exif.GPSInfo.GPSMapDatum"] = "WGS-84" metadata["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0' metadata["Exif.GPSInfo.GPSImgDirection"] = exiv_bearing metadata["Exif.GPSInfo.GPSImgDirectionRef"] = "T" if elevation is not None: exiv_elevation = make_fraction(abs(int(elevation*10)),10) metadata["Exif.GPSInfo.GPSAltitude"] = exiv_elevation metadata["Exif.GPSInfo.GPSAltitudeRef"] = '0' if elevation >= 0 else '1' metadata.write() print("Added geodata to: {} time {} lat {} lon {} alt {} bearing {}".format(filename, time, lat, lon, elevation, exiv_bearing)) except ValueError, e: print("Skipping {0}: {1}".format(filename, e))
def add_lat_lon(self, lat, lon, precision=1000000): ''' Add lat, lon to gps (lat, lon in float) @source: originally from geotag_from_gpx.py ''' # convert decimal coordinates into degrees, minutes and seconds lat_deg = decimal_to_dms(lat, ["S", "N"]) lon_deg = decimal_to_dms(lon, ["W", "E"]) # convert degrees, minutes and seconds as fractions for EXIF exiv_lat = (make_fraction(lat_deg[0], 1), make_fraction(int(lat_deg[1]), 1), make_fraction(int(lat_deg[2] * precision), precision)) exiv_lon = (make_fraction(lon_deg[0], 1), make_fraction(int(lon_deg[1]), 1), make_fraction(int(lon_deg[2] * precision), precision)) # add to exif self.metadata["Exif.GPSInfo.GPSLatitude"] = exiv_lat self.metadata["Exif.GPSInfo.GPSLatitudeRef"] = lat_deg[3] self.metadata["Exif.GPSInfo.GPSLongitude"] = exiv_lon self.metadata["Exif.GPSInfo.GPSLongitudeRef"] = lon_deg[3] self.metadata["Exif.Image.GPSTag"] = 654 self.metadata["Exif.GPSInfo.GPSMapDatum"] = "WGS-84" self.metadata["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0'
def testReadMetadata(self): """ Perform various tests on reading the metadata contained in a file. """ # Check that the reference file is not corrupted filename = os.path.join('data', 'DSCF_0273.JPG') filepath = testutils.get_absolute_file_path(filename) md5sum = '64d4b7eab1e78f1f6bfb3c966e99eef2' #self.assertCorrectFile(filepath, md5sum) # Read the image metadata image = pyexiv2.ImageMetadata(filepath) image.read() # Exhaustive tests on the values of EXIF metadata exifTags = [('Exif.Image.Software', str, 'Digital Camera FinePix S4800 Ver1.00'), ('Exif.Image.XResolution', FRACTION, make_fraction(72, 1)), ('Exif.Image.YResolution', FRACTION, make_fraction(72, 1)), ('Exif.Image.ResolutionUnit', int, 2), ('Exif.Image.Software', str, 'Digital Camera FinePix S4800 Ver1.00'), ('Exif.Image.DateTime', datetime.datetime, datetime.datetime(2015, 1, 17, 13, 53, 3)), ('Exif.Image.Artist', str, 'Vincent Vande Vyvre'), ('Exif.Photo.ApertureValue', FRACTION, make_fraction(6, 1)), ('Exif.Photo.FNumber', FRACTION, make_fraction(8, 1)), ('Exif.Photo.PixelXDimension', int, 250), ('Exif.Photo.PixelYDimension', int, 140)] for key, ktype, value in exifTags: self.check_type_and_value(image[key], ktype, value) # Exhaustive tests on the values of IPTC metadata iptcTags = [('Iptc.Application2.Caption', str, ['S']), ('Iptc.Application2.Byline', str, ['Vincent Vande Vyvre']), ('Iptc.Application2.0x00d7', str, ['www.oqapy.eu']), ('Iptc.Application2.Keywords', str, ['bruxelles botanique']), ('Iptc.Application2.Copyright', str, ['2013 Vincent Vande Vyvre'])] for key, ktype, values in iptcTags: self.check_type_and_values(image[key], ktype, values)
def write_direction_to_image(filename, direction): ''' Write the direction to the exif tag of the photograph. @param filename: photograph filename @param direction: direction of view in degrees ''' metadata = pyexiv2.ImageMetadata(filename) metadata.read() exiv_direction = make_fraction(int(direction * 10), 10) try: metadata["Exif.GPSInfo.GPSImgDirection"] = exiv_direction metadata["Exif.GPSInfo.GPSImgDirectionRef"] = "T" metadata.write() print("Added direction to: {0} ({1} degrees)".format(filename, float(exiv_direction))) except ValueError, e: print("Skipping {0}: {1}".format(filename, e))
def set_gps_exif(self, file_name, lat, lon, alt): """Adds GPS position as EXIF metadata file_name -- image file lat -- latitude (1e7 deg, as int) lon -- longitude (1e7 deg, as int) alt -- altitude MSL (in mm, as int) """ def get_loc(value, loc): if value < 0: return loc[0] elif value > 0: return loc[1] else: return "" try: exiv_lat = (make_fraction(abs(lat), 10000000), make_fraction(0, 1), make_fraction(0, 1)) exiv_lng = (make_fraction(abs(lon), 10000000), make_fraction(0, 1), make_fraction(0, 1)) if alt > 0.: exiv_alt = make_fraction(alt, 1000) else: exiv_alt = make_fraction(0, 1) exiv_image = pyexiv2.ImageMetadata(file_name) exiv_image.read() exiv_image["Exif.GPSInfo.GPSLatitude"] = exiv_lat exiv_image["Exif.GPSInfo.GPSLatitudeRef"] = get_loc( lat, ["S", "N"]) exiv_image["Exif.GPSInfo.GPSLongitude"] = exiv_lng exiv_image["Exif.GPSInfo.GPSLongitudeRef"] = get_loc( lon, ["W", "E"]) exiv_image["Exif.GPSInfo.GPSAltitude"] = exiv_alt exiv_image["Exif.GPSInfo.GPSAltitudeRef"] = '0' exiv_image["Exif.Image.GPSTag"] = 654 exiv_image["Exif.GPSInfo.GPSMapDatum"] = "WGS-84" exiv_image["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0' exiv_image.write() self.verbose_print("writing exif done") except: self.verbose_print("writing exif failed") pass
def write_newpos_to_image(filename, coordinate): ''' Write the new position to the exif tag of the picture. @param filename: picture filename @param coordinate: coordinate (details) ''' metadata = pyexiv2.ImageMetadata(filename) metadata.read() exiv_direction = make_fraction(int(direction * 10), 10) try: metadata["Exif.GPSInfo.GPSImgDirection"] = exiv_direction metadata["Exif.GPSInfo.GPSImgDirectionRef"] = "T" metadata.write() print("Added direction to: {0} ({1} degrees)".format( filename, float(exiv_direction))) except ValueError, e: print("Skipping {0}: {1}".format(filename, e))
def write_direction_to_image(filename, direction): ''' Write the direction to the exif tag of the photograph. @param filename: photograph filename @param direction: direction of view in degrees ''' metadata = pyexiv2.ImageMetadata(filename) metadata.read() exiv_direction = make_fraction(int(direction * 10), 10) try: metadata["Exif.GPSInfo.GPSImgDirection"] = exiv_direction metadata["Exif.GPSInfo.GPSImgDirectionRef"] = "T" metadata.write() print("Added direction to: {0} ({1} degrees)".format( filename, float(exiv_direction))) except ValueError, e: print("Skipping {0}: {1}".format(filename, e))
def add_coord_into_exif(filename, lat, lon,bearing): ''' Find lat, lon and bearing of filename and write to EXIF. ''' metadata = pyexiv2.ImageMetadata(filename) metadata.read() #print(metadata) try: #t = metadata['Exif.Photo.DateTimeOriginal'].value bearing=round(float(bearing)) lat_deg = to_deg(float(lat), ["S", "N"]) lon_deg = to_deg(float(lon), ["W", "E"]) # convert decimal coordinates into degrees, minutes and seconds as fractions for EXIF exiv_lat = (make_fraction(lat_deg[0],1), make_fraction(int(lat_deg[1]),1), make_fraction(int(lat_deg[2]*1000000),1000000)) exiv_lon = (make_fraction(lon_deg[0],1), make_fraction(int(lon_deg[1]),1), make_fraction(int(lon_deg[2]*1000000),1000000)) # convert direction into fraction exiv_bearing = make_fraction(int(bearing*100),100) # add to exif metadata["Exif.GPSInfo.GPSLatitude"] = exiv_lat metadata["Exif.GPSInfo.GPSLatitudeRef"] = lat_deg[3] metadata["Exif.GPSInfo.GPSLongitude"] = exiv_lon metadata["Exif.GPSInfo.GPSLongitudeRef"] = lon_deg[3] metadata["Exif.Image.GPSTag"] = 654 metadata["Exif.GPSInfo.GPSMapDatum"] = "WGS-84" metadata["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0' metadata["Exif.GPSInfo.GPSImgDirection"] = exiv_bearing metadata["Exif.GPSInfo.GPSImgDirectionRef"] = "T" metadata.write() #print("Added geodata to: {0} ({1}, {2}, {3})".format(filename, lat, lon, bearing)) except ValueError, e: print("Skipping {0}: {1}".format(filename, e))
def add_gps_to_exif(filename, lat, lon, bearing, elevation, updated_filename=None, remove_image_description=True): ''' Given lat, lon, bearing, elevation, write to EXIF ''' # TODO: use this within add_exif_using_timestamp if updated_filename is not None: shutil.copy2(filename, updated_filename) filename = updated_filename metadata = pyexiv2.ImageMetadata(filename) metadata.read() lat_deg = to_deg(lat, ["S", "N"]) lon_deg = to_deg(lon, ["W", "E"]) # convert decimal coordinates into degrees, minutes and seconds as fractions for EXIF exiv_lat = (make_fraction(lat_deg[0],1), make_fraction(int(lat_deg[1]),1), make_fraction(int(lat_deg[2]*1000000),1000000)) exiv_lon = (make_fraction(lon_deg[0],1), make_fraction(int(lon_deg[1]),1), make_fraction(int(lon_deg[2]*1000000),1000000)) # convert direction into fraction exiv_bearing = make_fraction(int(bearing*100),100) # add to exif metadata["Exif.GPSInfo.GPSLatitude"] = exiv_lat metadata["Exif.GPSInfo.GPSLatitudeRef"] = lat_deg[3] metadata["Exif.GPSInfo.GPSLongitude"] = exiv_lon metadata["Exif.GPSInfo.GPSLongitudeRef"] = lon_deg[3] metadata["Exif.Image.GPSTag"] = 654 metadata["Exif.GPSInfo.GPSMapDatum"] = "WGS-84" metadata["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0' metadata["Exif.GPSInfo.GPSImgDirection"] = exiv_bearing metadata["Exif.GPSInfo.GPSImgDirectionRef"] = "T" if remove_image_description: metadata["Exif.Image.ImageDescription"] = [] if elevation is not None: exiv_elevation = make_fraction(int(abs(elevation)*100),100) metadata["Exif.GPSInfo.GPSAltitude"] = exiv_elevation metadata["Exif.GPSInfo.GPSAltitudeRef"] = '0' if elevation >= 0 else '1' metadata.write()
def test_pickle_xmp_tag(self): tags = [] tags.append(XmpTag('Xmp.dc.subject', ['foo', 'bar', 'baz'])) tags.append(XmpTag('Xmp.xmpRights.Marked', True)) tags.append(XmpTag('Xmp.xmp.CreateDate', datetime.date.today())) tags.append(XmpTag('Xmp.xmpMM.SaveID', 34)) tags.append(XmpTag('Xmp.dc.format', ('image', 'jpeg'))) tags.append(XmpTag('Xmp.photoshop.CaptionWriter', 'John Doe')) tags.append(XmpTag('Xmp.dc.source', 'bleh')) tags.append(XmpTag('Xmp.xmpMM.DocumentID', 'http://example.com')) tags.append(XmpTag('Xmp.xmp.BaseURL', 'http://example.com')) tags.append(XmpTag('Xmp.xmpDM.videoPixelAspectRatio', make_fraction(5, 3))) for tag in tags: s = pickle.dumps(tag) t = pickle.loads(s) self.assert_(isinstance(t, XmpTag)) self.assertEqual(t.key, tag.key) self.assertEqual(t.type, tag.type) self.assertEqual(t.name, tag.name) self.assertEqual(t.title, tag.title) self.assertEqual(t.description, tag.description) self.assertEqual(t.raw_value, tag.raw_value) self.assertEqual(t.value, tag.value)
def write_exif(filename, coordinates): ''' Write Lat Lon Direction ''' metadata = pyexiv2.ImageMetadata(filename) metadata.read() try: # convert decimal coordinates into degrees, minutes and seconds as fractions for EXIF #exiv_lat = (make_fraction(48,1), make_fraction(58,1), make_fraction(int(52.69876547*1000000),1000000)) exiv_lat = (make_fraction(int(coordinates[0]), 1), make_fraction(int(coordinates[1]), 1), make_fraction(int(float(coordinates[2]) * 1000000), 1000000)) exiv_lon = (make_fraction(int(coordinates[4]), 1), make_fraction(int(coordinates[5]), 1), make_fraction(int(float(coordinates[6]) * 1000000), 1000000)) # convert direction into fraction exiv_bearing = make_fraction(int(coordinates[8] * 10), 10) # add to exif metadata["Exif.GPSInfo.GPSLatitude"] = exiv_lat metadata["Exif.GPSInfo.GPSLatitudeRef"] = coordinates[3] metadata["Exif.GPSInfo.GPSLongitude"] = exiv_lon metadata["Exif.GPSInfo.GPSLongitudeRef"] = coordinates[7] metadata["Exif.GPSInfo.GPSMapDatum"] = "WGS-84" metadata["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0' metadata["Exif.GPSInfo.GPSImgDirection"] = exiv_bearing metadata["Exif.GPSInfo.GPSImgDirectionRef"] = "T" metadata.write() print("Added geodata to: {0}".format(filename)) except ValueError, e: print("Skipping {0}: {1}".format(filename, e))
def add_lat_lon(self, lat, lon, precision=1000000): ''' Add lat, lon to gps (lat, lon in float) @source: originally from geotag_from_gpx.py ''' # convert decimal coordinates into degrees, minutes and seconds lat_deg = decimal_to_dms(lat, ["S", "N"]) lon_deg = decimal_to_dms(lon, ["W", "E"]) # convert degrees, minutes and seconds as fractions for EXIF exiv_lat = (make_fraction(lat_deg[0],1), make_fraction(int(lat_deg[1]),1), make_fraction(int(lat_deg[2]*precision),precision)) exiv_lon = (make_fraction(lon_deg[0],1), make_fraction(int(lon_deg[1]),1), make_fraction(int(lon_deg[2]*precision),precision)) # add to exif self.metadata["Exif.GPSInfo.GPSLatitude"] = exiv_lat self.metadata["Exif.GPSInfo.GPSLatitudeRef"] = lat_deg[3] self.metadata["Exif.GPSInfo.GPSLongitude"] = exiv_lon self.metadata["Exif.GPSInfo.GPSLongitudeRef"] = lon_deg[3] self.metadata["Exif.Image.GPSTag"] = 654 self.metadata["Exif.GPSInfo.GPSMapDatum"] = "WGS-84" self.metadata["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0'
def _convert_to_python(self, value, type): """ Convert a raw value to its corresponding python type. :param value: the raw value to be converted :type value: string :param type: the simple type of the raw value :type type: string :return: the value converted to its corresponding python type :raise XmpValueError: if the conversion fails """ if type == 'Boolean': if value == 'True': return True elif value == 'False': return False else: raise XmpValueError(value, type) elif type == 'Colorant': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type) elif type == 'Date': match = self._date_re.match(value) if match is None: raise XmpValueError(value, type) gd = match.groupdict() if gd['month'] is not None: month = int(gd['month']) else: month = 1 if gd['day'] is not None: day = int(gd['day']) else: day = 1 if gd['time'] is None: try: return datetime.date(int(gd['year']), month, day) except ValueError: raise XmpValueError(value, type) else: if gd['minutes'] is None: # Malformed time raise XmpValueError(value, type) if gd['seconds'] is not None: seconds = int(gd['seconds']) else: seconds = 0 if gd['decimal'] is not None: microseconds = int(float('0.%s' % gd['decimal']) * 1E6) else: microseconds = 0 if gd['tzd'] == 'Z': tzinfo = FixedOffset() else: tzinfo = FixedOffset(gd['sign'], int(gd['ohours']), int(gd['ominutes'])) try: return datetime.datetime(int(gd['year']), month, day, int(gd['hours']), int(gd['minutes']), seconds, microseconds, tzinfo) except ValueError: raise XmpValueError(value, type) elif type == 'Dimensions': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type) elif type == 'Font': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type) elif type == 'GPSCoordinate': try: return GPSCoordinate.from_string(value) except ValueError: raise XmpValueError(value, type) elif type == 'Integer': try: return int(value) except ValueError: raise XmpValueError(value, type) elif type == 'Locale': # TODO # See RFC 3066 raise NotImplementedError('XMP conversion for type [%s]' % type) elif type == 'MIMEType': if value.count('/') != 1: raise XmpValueError(value, type) try: return tuple(value.split('/', 1)) except ValueError: raise XmpValueError(value, type) elif type == 'Rational': try: return make_fraction(value) except (ValueError, ZeroDivisionError): raise XmpValueError(value, type) elif type == 'Real': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type) elif type in ('AgentName', 'ProperName', 'Text'): try: return unicode(value, 'utf-8') except TypeError: raise XmpValueError(value, type) elif type == 'Thumbnail': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type) elif type in ('URI', 'URL'): return value elif type == 'XPath': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type) raise NotImplementedError('XMP conversion for type [%s]' % type)
def test_fraction_to_string(self): self.assertEqual(fraction_to_string(make_fraction(3, 5)), '3/5') self.assertEqual(fraction_to_string(make_fraction(-3, 5)), '-3/5') self.assertEqual(fraction_to_string(make_fraction(0, 1)), '0/1') self.assertRaises(TypeError, fraction_to_string, None) self.assertRaises(TypeError, fraction_to_string, 'invalid')
def _convert_to_python(self, value, type_): """Convert a raw value to its corresponding python type. Args: value -- the raw value to be converted type_ -- the simple type of the raw value Return: the value converted to its corresponding python type Raise XmpValueError: if the conversion fails """ if type_ == 'Boolean': if value == 'True': return True elif value == 'False': return False else: raise XmpValueError(value, type_) elif type_ == 'Colorant': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type_) elif type_ == 'Date': match = self._date_re.match(value) if match is None: raise XmpValueError(value, type_) gd = match.groupdict() if gd['month'] is not None: month = int(gd['month']) else: month = 1 if gd['day'] is not None: day = int(gd['day']) else: day = 1 if gd['time'] is None: try: return datetime.date(int(gd['year']), month, day) except ValueError: raise XmpValueError(value, type_) else: if gd['minutes'] is None: # Malformed time raise XmpValueError(value, type_) if gd['seconds'] is not None: seconds = int(gd['seconds']) else: seconds = 0 if gd['decimal'] is not None: microseconds = int(float('0.%s' % gd['decimal']) * 1E6) else: microseconds = 0 if gd['tzd'] is not None and gd[ 'tzd'] == 'Z' and gd['sign'] is None and gd[ 'ohours'] is None and gd['ominutes'] is None: tzinfo = FixedOffset() elif gd['sign'] is not None and gd[ 'ohours'] is not None and gd['ominutes'] is not None: tzinfo = FixedOffset(gd['sign'], int(gd['ohours']), int(gd['ominutes'])) else: tzinfo = None # TODO: Decide if following is way to hande # ====================================================================== # ERROR: test_convert_to_python_date (xmp.TestXmpTag) # ---------------------------------------------------------------------- # Traceback (most recent call last): # File "c:\Users\BSFau\Cloudstation\BSFsoftDev\axternal\py3exiv2\py3exiv2\test\xmp.py", line 91, in test_convert_to_python_date # datetime.datetime(1999, 10, 13, 5, 3, tzinfo=FixedOffset()), # TypeError: can't subtract offset-naive and offset-aware datetimes # ---------------------------------------------------------------------- # u_tm = datetime.datetime.utcfromtimestamp(0) # l_tm = datetime.datetime.fromtimestamp(0) # tzinfo = datetime.timezone(l_tm - u_tm) try: if tzinfo is not None: return datetime.datetime(int(gd['year']), month, day, int(gd['hours']), int(gd['minutes']), seconds, microseconds, tzinfo) else: return datetime.datetime(int(gd['year']), month, day, int(gd['hours']), int(gd['minutes']), seconds, microseconds) except ValueError: raise XmpValueError(value, type_) elif type_ == 'Dimensions': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type_) elif type_ == 'Font': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type_) elif type_ == 'GPSCoordinate': try: return GPSCoordinate.from_string(value) except ValueError: raise XmpValueError(value, type_) elif type_ == 'Integer': try: return int(value) except ValueError: raise XmpValueError(value, type_) elif type_ == 'Locale': # TODO # See RFC 3066 raise NotImplementedError('XMP conversion for type [%s]' % type_) elif type_ == 'MIMEType': if value.count('/') != 1: raise XmpValueError(value, type_) try: return tuple(value.split('/', 1)) except ValueError: raise XmpValueError(value, type_) elif type_ == 'Rational': try: return make_fraction(value) except (ValueError, ZeroDivisionError): raise XmpValueError(value, type_) elif type_ == 'Real': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type_) elif type_ in ('AgentName', 'ProperName', 'Text'): if isinstance(value, bytes): try: value = str(value, 'utf-8') except TypeError: raise XmpValueError(value, type_) return value elif type_ == 'Thumbnail': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type_) elif type_ in ('URI', 'URL'): if isinstance(value, bytes): try: value = value.decode('utf-8') except UnicodeDecodeError: # Unknow encoding, return the raw value pass return value elif type_ == 'XPath': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type_) raise NotImplementedError('XMP conversion for type [%s]' % type_)
def make_fraction(self, v, precision=1000): ''' Make fraction with the specified precision ''' fv = make_fraction(int(v * precision), precision) return fv
def _convert_to_python(self, value): """ Convert one raw value to its corresponding python type. :param value: the raw value to be converted :type value: string :return: the value converted to its corresponding python type :raise ExifValueError: if the conversion fails """ if self.type == 'Ascii': # The value may contain a Datetime for format in self._datetime_formats: try: t = time.strptime(value, format) except ValueError: continue else: return datetime.datetime(*t[:6]) # Or a Date (e.g. Exif.GPSInfo.GPSDateStamp) for format in self._date_formats: try: t = time.strptime(value, format) except ValueError: continue else: return datetime.date(*t[:3]) # Default to string. # There is currently no charset conversion. # TODO: guess the encoding and decode accordingly into unicode # where relevant. return value elif self.type in ('Byte', 'SByte'): if isinstance(value, bytes): return value.decode('utf-8') return value elif self.type == 'Comment': if isinstance(value, str): if value.startswith('charset='): charset, val = value.split(' ', 1) return val return value if value.startswith(b'charset='): charset = charset.split('=')[1].strip('"') encoding = self._match_encoding(charset) return val.decode(encoding, 'replace') else: # No encoding defined. try: return value.decode('utf-8') except UnicodeError: pass return value elif self.type in ('Short', 'SShort'): try: return int(value) except ValueError: raise ExifValueError(value, self.type) elif self.type in ('Long', 'SLong'): try: return int(value) except ValueError: raise ExifValueError(value, self.type) elif self.type in ('Rational', 'SRational'): try: r = make_fraction(value) except (ValueError, ZeroDivisionError): raise ExifValueError(value, self.type) else: if self.type == 'Rational' and r.numerator < 0: raise ExifValueError(value, self.type) return r elif self.type == 'Undefined': # There is currently no charset conversion. # TODO: guess the encoding and decode accordingly into unicode # where relevant. return undefined_to_string(value) raise ExifValueError(value, self.type)
def test_make_fraction_valid(args, frac): assert make_fraction(*args) == Fraction(*frac)
def test_make_fraction_invalid(exc, args): with pytest.raises(exc): make_fraction(*args)
def testReadMetadataXMP(self): filename = os.path.join('data', 'exiv2-bug540.jpg') filepath = testutils.get_absolute_file_path(filename) md5sum = '64d4b7eab1e78f1f6bfb3c966e99eef2' self.assertCorrectFile(filepath, md5sum) # Read the image metadata image = pyexiv2.ImageMetadata(filepath) image.read() xmpTags = [('Xmp.dc.creator', list, [u'Ian Britton']), ('Xmp.dc.description', dict, {u'x-default': u'Communications'}), ('Xmp.dc.rights', dict, {u'x-default': u'ian Britton - FreeFoto.com'}), ('Xmp.dc.source', unicode, u'FreeFoto.com'), ('Xmp.dc.subject', list, [u'Communications']), ('Xmp.dc.title', dict, {u'x-default': u'Communications'}), ('Xmp.exif.ApertureValue', FRACTION, make_fraction(8, 1)), ('Xmp.exif.BrightnessValue', FRACTION, make_fraction(333, 1280)), ('Xmp.exif.ColorSpace', int, 1), ('Xmp.exif.DateTimeOriginal', datetime.datetime, datetime.datetime(2002, 7, 13, 15, 58, 28, tzinfo=pyexiv2.utils.FixedOffset())), ('Xmp.exif.ExifVersion', unicode, u'0200'), ('Xmp.exif.ExposureBiasValue', FRACTION, make_fraction(-13, 20)), ('Xmp.exif.ExposureProgram', int, 4), ('Xmp.exif.FNumber', FRACTION, make_fraction(3, 5)), ('Xmp.exif.FileSource', int, 0), ('Xmp.exif.FlashpixVersion', unicode, u'0100'), ('Xmp.exif.FocalLength', FRACTION, make_fraction(0, 1)), ('Xmp.exif.FocalPlaneResolutionUnit', int, 2), ('Xmp.exif.FocalPlaneXResolution', FRACTION, make_fraction(3085, 256)), ('Xmp.exif.FocalPlaneYResolution', FRACTION, make_fraction(3085, 256)), ('Xmp.exif.GPSLatitude', pyexiv2.utils.GPSCoordinate, pyexiv2.utils.GPSCoordinate.from_string('54,59.380000N')), ('Xmp.exif.GPSLongitude', pyexiv2.utils.GPSCoordinate, pyexiv2.utils.GPSCoordinate.from_string('1,54.850000W')), ('Xmp.exif.GPSMapDatum', unicode, u'WGS84'), ('Xmp.exif.GPSTimeStamp', datetime.datetime, datetime.datetime(2002, 7, 13, 14, 58, 24, tzinfo=pyexiv2.utils.FixedOffset())), ('Xmp.exif.GPSVersionID', unicode, u'2.0.0.0'), ('Xmp.exif.ISOSpeedRatings', list, [0]), ('Xmp.exif.MeteringMode', int, 5), ('Xmp.exif.PixelXDimension', int, 2400), ('Xmp.exif.PixelYDimension', int, 1600), ('Xmp.exif.SceneType', int, 0), ('Xmp.exif.SensingMethod', int, 2), ('Xmp.exif.ShutterSpeedValue', FRACTION, make_fraction(30827, 3245)), ('Xmp.pdf.Keywords', unicode, u'Communications'), ('Xmp.photoshop.AuthorsPosition', unicode, u'Photographer'), ('Xmp.photoshop.CaptionWriter', unicode, u'Ian Britton'), ('Xmp.photoshop.Category', unicode, u'BUS'), ('Xmp.photoshop.City', unicode, u' '), ('Xmp.photoshop.Country', unicode, u'Ubited Kingdom'), ('Xmp.photoshop.Credit', unicode, u'Ian Britton'), ('Xmp.photoshop.DateCreated', datetime.date, datetime.date(2002, 6, 20)), ('Xmp.photoshop.Headline', unicode, u'Communications'), ('Xmp.photoshop.State', unicode, u' '), ('Xmp.photoshop.SupplementalCategories', list, [u'Communications']), ('Xmp.photoshop.Urgency', int, 5), ('Xmp.tiff.Artist', unicode, u'Ian Britton'), ('Xmp.tiff.BitsPerSample', list, [8]), ('Xmp.tiff.Compression', int, 6), ('Xmp.tiff.Copyright', dict, {u'x-default': u'ian Britton - FreeFoto.com'}), ('Xmp.tiff.ImageDescription', dict, {u'x-default': u'Communications'}), ('Xmp.tiff.ImageLength', int, 400), ('Xmp.tiff.ImageWidth', int, 600), ('Xmp.tiff.Make', unicode, u'FUJIFILM'), ('Xmp.tiff.Model', unicode, u'FinePixS1Pro'), ('Xmp.tiff.Orientation', int, 1), ('Xmp.tiff.ResolutionUnit', int, 2), ('Xmp.tiff.Software', unicode, u'Adobe Photoshop 7.0'), ('Xmp.tiff.XResolution', FRACTION, make_fraction(300, 1)), ('Xmp.tiff.YCbCrPositioning', int, 2), ('Xmp.tiff.YResolution', FRACTION, make_fraction(300, 1)), ('Xmp.xmp.CreateDate', datetime.datetime, datetime.datetime(2002, 7, 13, 15, 58, 28, tzinfo=pyexiv2.utils.FixedOffset())), ('Xmp.xmp.ModifyDate', datetime.datetime, datetime.datetime(2002, 7, 19, 13, 28, 10, tzinfo=pyexiv2.utils.FixedOffset())), ('Xmp.xmpBJ.JobRef', list, []), ('Xmp.xmpBJ.JobRef[1]', str, ''), ('Xmp.xmpBJ.JobRef[1]/stJob:name', str, 'Photographer'), ('Xmp.xmpMM.DocumentID', str, 'adobe:docid:photoshop:84d4dba8-9b11-11d6-895d-c4d063a70fb0'), ('Xmp.xmpRights.Marked', bool, True), ('Xmp.xmpRights.WebStatement', str, 'www.freefoto.com')] self.assertEqual(image.xmp_keys, [tag[0] for tag in xmpTags]) for key, ktype, value in xmpTags: self.check_type_and_value(image[key], ktype, value)
def _convert_to_python(self, value): """ Convert one raw value to its corresponding python type. :param value: the raw value to be converted :type value: string :return: the value converted to its corresponding python type :raise ExifValueError: if the conversion fails """ if self.type == 'Ascii': # The value may contain a Datetime for format in self._datetime_formats: try: t = time.strptime(value, format) except ValueError: continue else: return datetime.datetime(*t[:6]) # Or a Date (e.g. Exif.GPSInfo.GPSDateStamp) for format in self._date_formats: try: t = time.strptime(value, format) except ValueError: continue else: return datetime.date(*t[:3]) # Default to string. # There is currently no charset conversion. # TODO: guess the encoding and decode accordingly into unicode # where relevant. return value elif self.type in ('Byte', 'SByte'): return value elif self.type == 'Comment': if value.startswith('charset='): charset, val = value.split(' ', 1) charset = charset.split('=')[1].strip('"') encoding = self._match_encoding(charset) return val.decode(encoding, 'replace') else: # No encoding defined. try: return value.decode('utf-8') except UnicodeError: return value elif self.type in ('Short', 'SShort'): try: return int(value) except ValueError: raise ExifValueError(value, self.type) elif self.type in ('Long', 'SLong'): try: return long(value) except ValueError: raise ExifValueError(value, self.type) elif self.type in ('Rational', 'SRational'): try: r = make_fraction(value) except (ValueError, ZeroDivisionError): raise ExifValueError(value, self.type) else: if self.type == 'Rational' and r.numerator < 0: raise ExifValueError(value, self.type) return r elif self.type == 'Undefined': # There is currently no charset conversion. # TODO: guess the encoding and decode accordingly into unicode # where relevant. return undefined_to_string(value) raise ExifValueError(value, self.type)
def _convert_to_python(self, value, type_): """Convert a raw value to its corresponding python type. Args: value -- the raw value to be converted type_ -- the simple type of the raw value Return: the value converted to its corresponding python type Raise XmpValueError: if the conversion fails """ if type_ == 'Boolean': if value == 'True': return True elif value == 'False': return False else: raise XmpValueError(value, type_) elif type_ == 'Colorant': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type_) elif type_ == 'Date': match = self._date_re.match(value) if match is None: raise XmpValueError(value, type_) gd = match.groupdict() if gd['month'] is not None: month = int(gd['month']) else: month = 1 if gd['day'] is not None: day = int(gd['day']) else: day = 1 if gd['time'] is None: try: return datetime.date(int(gd['year']), month, day) except ValueError: raise XmpValueError(value, type_) else: if gd['minutes'] is None: # Malformed time raise XmpValueError(value, type_) if gd['seconds'] is not None: seconds = int(gd['seconds']) else: seconds = 0 if gd['decimal'] is not None: microseconds = int(float('0.%s' % gd['decimal']) * 1E6) else: microseconds = 0 if gd['tzd'] == 'Z': tzinfo = FixedOffset() else: tzinfo = FixedOffset(gd['sign'], int(gd['ohours']), int(gd['ominutes'])) try: return datetime.datetime(int(gd['year']), month, day, int(gd['hours']), int(gd['minutes']), seconds, microseconds, tzinfo) except ValueError: raise XmpValueError(value, type_) elif type_ == 'Dimensions': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type_) elif type_ == 'Font': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type_) elif type_ == 'GPSCoordinate': try: return GPSCoordinate.from_string(value) except ValueError: raise XmpValueError(value, type_) elif type_ == 'Integer': try: return int(value) except ValueError: raise XmpValueError(value, type_) elif type_ == 'Locale': # TODO # See RFC 3066 raise NotImplementedError('XMP conversion for type [%s]' % type_) elif type_ == 'MIMEType': if value.count('/') != 1: raise XmpValueError(value, type_) try: return tuple(value.split('/', 1)) except ValueError: raise XmpValueError(value, type_) elif type_ == 'Rational': try: return make_fraction(value) except (ValueError, ZeroDivisionError): raise XmpValueError(value, type_) elif type_ == 'Real': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type_) elif type_ in ('AgentName', 'ProperName', 'Text'): if isinstance(value, bytes): try: value = str(value, 'utf-8') except TypeError: raise XmpValueError(value, type_) return value elif type_ == 'Thumbnail': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type_) elif type_ in ('URI', 'URL'): if isinstance(value, bytes): try: value = value.decode('utf-8') except UnicodeDecodeError: # Unknow encoding, return the raw value pass return value elif type_ == 'XPath': # TODO raise NotImplementedError('XMP conversion for type [%s]' % type_) raise NotImplementedError('XMP conversion for type [%s]' % type_)
def downloadPhoto(path, photoId, photoNum=None): """Downloads an image and sets exif data. Keyword arguments: path -- a string containing the name of the directory to write to photoId -- a string containing the ID number of the image to download """ # create the directory to store the images setPath = path.replace("/", "-") if not os.path.exists(setPath): os.makedirs(setPath) # download the photo metadata photoInfo = flickr.photos.getInfo(photo_id=photoId).find('photo') # work out a filename for the photo once downloaded filename = path + os.sep + generateFilename(photoInfo, photoNum) # try downloading the file try: sizes = flickr.photos.getSizes(photo_id=photoId).find('sizes') if filename.endswith('.mp4'): url = sizes.find('.//size[@label="Video Original"]').get('source') else: url = sizes.find('.//size[@label="Original"]').get('source') urllib.request.urlretrieve(url, filename) print(filename) except: return # a place to build up the exif info to write photoExif = {} if not filename.endswith('.mp4'): # work out the exif and XMP vales photoExif['Exif.Image.Copyright'] = getCopyright(photoInfo) photoExif['Exif.Image.Artist'] = getOwner(photoInfo) photoExif['Xmp.dc.rights'] = photoExif['Exif.Image.Copyright'] photoExif['Xmp.dc.creator'] = [getOwner(photoInfo)] photoExif['Xmp.xmpRights.Owner'] = photoExif['Xmp.dc.creator'] photoExif['Xmp.xmpRights.Marked'] = True photoExif['Xmp.xmpRights.UsageTerms'] = getLicense(photoInfo) photoExif['Xmp.dc.title'] = photoInfo.find('title').text or 'Unknown' photoExif['Exif.Image.ImageDescription'] = photoExif['Xmp.dc.title'] # only set the description if there's one to set (or default to setting the same as the title) description = photoInfo.find('description').text if description is None: description = photoExif['Xmp.dc.title'] photoExif['Xmp.dc.description'] = description photoExif['Exif.Photo.UserComment'] = description # set the subject using the image tags (if there are any) tags = [] for tag in photoInfo.find('tags').getiterator('tag'): tags.append(tag.text) if len(tags) > 0: photoExif['Xmp.dc.subject'] = tags # set the date taken taken = photoInfo.find('dates').get('taken') takenFormatted = datetime.datetime.strptime(taken, '%Y-%m-%d %H:%M:%S') photoExif['Xmp.dc.date'] = [takenFormatted] photoExif['Xmp.xmp.CreateDate'] = takenFormatted # add GPS coordinates if available location = photoInfo.find('location') if location is not None: lat = gpsDecimalLatToDMS(float(location.get('latitude'))) lon = gpsDecimalLonToDMS(float(location.get('longitude'))) latDMS = (make_fraction(lat[0], 1), make_fraction(int(lat[1]), 1), make_fraction(int(lat[2] * 1000000), 1000000)) lonDMS = (make_fraction(lon[0], 1), make_fraction(int(lon[1]), 1), make_fraction(int(lon[2] * 1000000), 1000000)) photoExif["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0' photoExif["Exif.GPSInfo.GPSLatitude"] = latDMS photoExif["Exif.GPSInfo.GPSLatitudeRef"] = lat[3] photoExif["Exif.GPSInfo.GPSLongitude"] = lonDMS photoExif["Exif.GPSInfo.GPSLongitudeRef"] = lon[3] photoExif[ "Exif.GPSInfo.GPSDateStamp"] = datetime.datetime.strptime( taken, '%Y-%m-%d %H:%M:%S').strftime('%Y:%m:%d') setExif(filename, photoExif)
def testReadMetadataXMP(self): filename = os.path.join('data', 'exiv2-bug540.jpg') filepath = testutils.get_absolute_file_path(filename) md5sum = '64d4b7eab1e78f1f6bfb3c966e99eef2' self.assertCorrectFile(filepath, md5sum) # Read the image metadata image = pyexiv2.ImageMetadata(filepath) image.read() xmpTags = [ ('Xmp.dc.creator', list, [u'Ian Britton']), ('Xmp.dc.description', dict, { u'x-default': u'Communications' }), ('Xmp.dc.rights', dict, { u'x-default': u'ian Britton - FreeFoto.com' }), ('Xmp.dc.source', unicode, u'FreeFoto.com'), ('Xmp.dc.subject', list, [u'Communications']), ('Xmp.dc.title', dict, { u'x-default': u'Communications' }), ('Xmp.exif.ApertureValue', FRACTION, make_fraction(8, 1)), ('Xmp.exif.BrightnessValue', FRACTION, make_fraction(333, 1280)), ('Xmp.exif.ColorSpace', int, 1), ('Xmp.exif.DateTimeOriginal', datetime.datetime, datetime.datetime(2002, 7, 13, 15, 58, 28, tzinfo=pyexiv2.utils.FixedOffset())), ('Xmp.exif.ExifVersion', unicode, u'0200'), ('Xmp.exif.ExposureBiasValue', FRACTION, make_fraction(-13, 20)), ('Xmp.exif.ExposureProgram', int, 4), ('Xmp.exif.FNumber', FRACTION, make_fraction(3, 5)), ('Xmp.exif.FileSource', int, 0), ('Xmp.exif.FlashpixVersion', unicode, u'0100'), ('Xmp.exif.FocalLength', FRACTION, make_fraction(0, 1)), ('Xmp.exif.FocalPlaneResolutionUnit', int, 2), ('Xmp.exif.FocalPlaneXResolution', FRACTION, make_fraction(3085, 256)), ('Xmp.exif.FocalPlaneYResolution', FRACTION, make_fraction(3085, 256)), ('Xmp.exif.GPSLatitude', pyexiv2.utils.GPSCoordinate, pyexiv2.utils.GPSCoordinate.from_string('54,59.380000N')), ('Xmp.exif.GPSLongitude', pyexiv2.utils.GPSCoordinate, pyexiv2.utils.GPSCoordinate.from_string('1,54.850000W')), ('Xmp.exif.GPSMapDatum', unicode, u'WGS84'), ('Xmp.exif.GPSTimeStamp', datetime.datetime, datetime.datetime(2002, 7, 13, 14, 58, 24, tzinfo=pyexiv2.utils.FixedOffset())), ('Xmp.exif.GPSVersionID', unicode, u'2.0.0.0'), ('Xmp.exif.ISOSpeedRatings', list, [0]), ('Xmp.exif.MeteringMode', int, 5), ('Xmp.exif.PixelXDimension', int, 2400), ('Xmp.exif.PixelYDimension', int, 1600), ('Xmp.exif.SceneType', int, 0), ('Xmp.exif.SensingMethod', int, 2), ('Xmp.exif.ShutterSpeedValue', FRACTION, make_fraction(30827, 3245)), ('Xmp.pdf.Keywords', unicode, u'Communications'), ('Xmp.photoshop.AuthorsPosition', unicode, u'Photographer'), ('Xmp.photoshop.CaptionWriter', unicode, u'Ian Britton'), ('Xmp.photoshop.Category', unicode, u'BUS'), ('Xmp.photoshop.City', unicode, u' '), ('Xmp.photoshop.Country', unicode, u'Ubited Kingdom'), ('Xmp.photoshop.Credit', unicode, u'Ian Britton'), ('Xmp.photoshop.DateCreated', datetime.date, datetime.date(2002, 6, 20)), ('Xmp.photoshop.Headline', unicode, u'Communications'), ('Xmp.photoshop.State', unicode, u' '), ('Xmp.photoshop.SupplementalCategories', list, [u'Communications' ]), ('Xmp.photoshop.Urgency', int, 5), ('Xmp.tiff.Artist', unicode, u'Ian Britton'), ('Xmp.tiff.BitsPerSample', list, [8]), ('Xmp.tiff.Compression', int, 6), ('Xmp.tiff.Copyright', dict, { u'x-default': u'ian Britton - FreeFoto.com' }), ('Xmp.tiff.ImageDescription', dict, { u'x-default': u'Communications' }), ('Xmp.tiff.ImageLength', int, 400), ('Xmp.tiff.ImageWidth', int, 600), ('Xmp.tiff.Make', unicode, u'FUJIFILM'), ('Xmp.tiff.Model', unicode, u'FinePixS1Pro'), ('Xmp.tiff.Orientation', int, 1), ('Xmp.tiff.ResolutionUnit', int, 2), ('Xmp.tiff.Software', unicode, u'Adobe Photoshop 7.0'), ('Xmp.tiff.XResolution', FRACTION, make_fraction(300, 1)), ('Xmp.tiff.YCbCrPositioning', int, 2), ('Xmp.tiff.YResolution', FRACTION, make_fraction(300, 1)), ('Xmp.xmp.CreateDate', datetime.datetime, datetime.datetime(2002, 7, 13, 15, 58, 28, tzinfo=pyexiv2.utils.FixedOffset())), ('Xmp.xmp.ModifyDate', datetime.datetime, datetime.datetime(2002, 7, 19, 13, 28, 10, tzinfo=pyexiv2.utils.FixedOffset())), ('Xmp.xmpBJ.JobRef', list, []), ('Xmp.xmpBJ.JobRef[1]', str, ''), ('Xmp.xmpBJ.JobRef[1]/stJob:name', str, 'Photographer'), ('Xmp.xmpMM.DocumentID', str, 'adobe:docid:photoshop:84d4dba8-9b11-11d6-895d-c4d063a70fb0'), ('Xmp.xmpRights.Marked', bool, True), ('Xmp.xmpRights.WebStatement', str, 'www.freefoto.com') ] self.assertEqual(image.xmp_keys, [tag[0] for tag in xmpTags]) for key, ktype, value in xmpTags: self.check_type_and_value(image[key], ktype, value)