def build_calibration_matrices(i, prev_sensor, K_matrices, filename1, filename2): '''Extract exif metadata from image files, and use them to build the 2 calibration matrices.''' def get_sensor_sizes(i, prev_sensor, metadata1, metadata2): '''Looks up sensor width from the database based on the camera model.''' #focal length in pixels = (image width in pixels) * (focal length in mm) / (CCD width in mm) if i == 0: sensor_1 = cam_db.get_sensor_size( metadata1['Exif.Image.Model'].strip().upper()) sensor_2 = cam_db.get_sensor_size( metadata2['Exif.Image.Model'].strip().upper()) elif i >= 1: sensor_1 = prev_sensor sensor_2 = cam_db.get_sensor_size(metadata2['Exif.Image.Model']) metadata1 = GExiv2.Metadata(filename1) metadata2 = GExiv2.Metadata(filename2) # if False and metadata1.get_supports_exif() and metadata2.get_supports_exif(): # sensor_1, sensor_2 = get_sensor_sizes(i, prev_sensor, metadata1, metadata2) # else: # if metadata1.get_supports_exif() == False: # print "Exif data not available for ", filename1 # if metadata2.get_supports_exif() == False: # print "Exif data not available for ", filename2 # sys.exit("Please try again.") # CDANCETTE : Fix sensor size at 30mm sensor_1, sensor_2 = 6.35, 6.35 focal = 50 #3.8 # 50mm # CDANCETTE # Calibration matrix for camera 1 (K1) # f1_mm = metadata1.get_focal_length() f1_mm = focal w1 = metadata1.get_pixel_width() h1 = metadata1.get_pixel_height() f1_px = (w1 * f1_mm) / sensor_1 K1 = np.array([[f1_px, 0, w1 / 2], [0, f1_px, h1 / 2], [0, 0, 1]]) # Calibration matrix for camera 2 (K2) # f2_mm = metadata2.get_focal_length() f2_mm = focal w2 = metadata2.get_pixel_width() h2 = metadata2.get_pixel_height() f2_px = (w2 * f2_mm) / sensor_2 K2 = np.array([[f2_px, 0, w2 / 2], [0, f2_px, h2 / 2], [0, 0, 1]]) if i == 0: K_matrices.append(K1) K_matrices.append(K2) elif i >= 1: K_matrices.append(K2) return sensor_2, K_matrices
def resize(self, src, dst): src_meta = GExiv2.Metadata(src) src_p = QPixmap(src) dst_p = src_p.scaled(4500, 3000, Qt.KeepAspectRatio, Qt.SmoothTransformation) dst_p.save(src) shutil.move(src, dst) # copy all the metadata dst_meta = GExiv2.Metadata(dst) for tag in src_meta.get_tags(): dst_meta[tag] = src_meta[tag] dst_meta.save_file()
def get_metadata(path): """ get a gexiv2 metadata object or None for path :param path: path to an image that is supported by gexiv2 :returns :py:class:`GExiv2.Metadata` or None """ GExiv2.initialize() metadata = GExiv2.Metadata.new() try: metadata.open_path(path) except GLib.Error as e: print('{}: can not get metadata obj: {}'.format(path, e)) return None else: return metadata
def comparePicturesByExifDate(self, curOrigFullFileName): try: src_exif = GExiv2.Metadata(self.absCopiesOrigDateiName) src_ExifDate = src_exif.get_date_time().strftime('%Y%m%d%H%M%S') #logging.DEBUG("src-exifTimeStamp for " + self.fileBaseName + " is " + src_ExifDate) tgt_exif = GExiv2.Metadata(curOrigFullFileName) tgt_ExifDate = tgt_exif.get_date_time().strftime('%Y%m%d%H%M%S') #logging.DEBUG("tgt-exifTimeStamp for " + os.path.basename(curOrigFullFileName) + " is " +tgt_ExifDate) ) if src_ExifDate == tgt_ExifDate: return True else: return False except: logging.debug("Image does not have exiv date time") return False
def merge_sc(self, other): # merge sidecar data into image file data, ignoring thumbnails raw_sc = GExiv2.Metadata() raw_sc.open_path(other._path) # allow exiv2 to infer Exif tags from XMP for tag in raw_sc.get_exif_tags(): if tag.startswith('Exif.Thumbnail'): continue # ignore inferred datetime values the exiv2 gets wrong # (I think it's adding the local timezone offset) if tag in ('Exif.Image.DateTime', 'Exif.Photo.DateTimeOriginal', 'Exif.Photo.DateTimeDigitized'): self.clear_tag(tag) else: value = raw_sc.get_tag_string(tag) if value: self.set_tag_string(tag, value) # copy all XMP tags except inferred Exif tags for tag in raw_sc.get_xmp_tags(): if tag.startswith('Xmp.xmp.Thumbnails'): continue ns = tag.split('.')[1] if ns in ('exif', 'exifEX', 'tiff', 'aux'): # exiv2 will already have supplied the equivalent Exif tag pass elif self.get_tag_type(tag) == 'XmpText': value = raw_sc.get_tag_string(tag) if value: self.set_tag_string(tag, value) else: value = raw_sc.get_tag_multiple(tag) if value: self.set_tag_multiple(tag, value)
def UpdateFileMetadata(filename, datetime): exif = GExiv2.Metadata(filename) if exif is not None: fileDate = GetDateFromExif(exif) if fileDate is not None: fileDate = datetime.strptime(fileDate, DATE_FORMAT) # date within acceptable limit. don't update if abs((fileDate - datetime).days) <= FILE_METADATA_DATE_TOLERANCE: return log('Updating exif: %s to date: %s', \ filename, datetime.strftime(DATE_FORMAT)) if DRY_RUN == False: if exif is not None: exif['Exif.Photo.DateTimeOriginal'] = datetime.strftime( DATE_FORMAT) exif['Exif.Photo.DateTimeDigitized'] = datetime.strftime( DATE_FORMAT) exif['Exif.Image.DateTime'] = datetime.strftime(DATE_FORMAT) exif['Exif.Image.DateTimeOriginal'] = datetime.strftime( DATE_FORMAT) exif.save_file() xmpfile = XMPFiles(file_path=filename, open_forupdate=True) xmp = xmpfile.get_xmp() if xmp is not None: for xmpConst in XMP_CONSTANTS: for dateConst in DATE_CONSTANTS: if xmp.does_property_exist(xmpConst, dateConst): xmp.set_property( \ xmpConst, dateConst, datetime.strftime(DATE_FORMAT_XMP)) if (xmpfile.can_put_xmp(xmp)): xmpfile.put_xmp(xmp) xmpfile.close_file()
def remove_rotation(image_path): ''' Fix a picture taken from an Apple device :param image: Image path ''' # Get Metadata exif = GExiv2.Metadata(image_path) # Get current orientation orientation = exif.get_orientation() degrees = None if orientation == GExiv2.Orientation.ROT_90: degrees = 90 elif orientation == GExiv2.Orientation.ROT_180: degrees = 180 elif orientation == GExiv2.Orientation.ROT_270: degrees = 270 if degrees is not None: # Remove orientation tag exif.set_orientation(GExiv2.Orientation.NORMAL) exif.save_file() # Rotate image image = Image.open(image_path) rotated_image = image.rotate(360 - degrees) rotated_image.save()
def resize_image_to_1024(parent_file_path, file_path): """Resizes the image to 1024 if it is larger in width. Parameters ========== parent_file_path : string The path to the original image file with all the metadata. file_path : string The path to the temporary image file that should be resized. Notes ===== Do this before rotating the image because it is only based on width. """ img = Image.open(file_path) width, height = img.size aspect_ratio = float(width) / float(height) max_width = 1024 if width > max_width: reduced_size = max_width, int(max_width / aspect_ratio) img.thumbnail(reduced_size, Image.ANTIALIAS) img.save(file_path, img.format) # Copy all metadata from parent file to the resized file os.system('jhead -q -te {} {}'.format(parent_file_path, file_path)) metadata = GExiv2.Metadata(file_path) if (metadata.get_pixel_width() != reduced_size[0] or metadata.get_pixel_height() != reduced_size[1]): metadata.set_pixel_width(reduced_size[0]) metadata.set_pixel_height(reduced_size[1]) metadata.save_file()
def add_meta_data(self, img_path, comment="", rotation="landscape"): # Comment tags used by various programs: http://redmine.yorba.org/projects/shotwell/wiki/PhotoTags # All the supported tags: http://www.exiv2.org/metadata.html metadata = GExiv2.Metadata(img_path) metadata['Iptc.Application2.DateCreated'] = self.developed.date( ).strftime('%Y-%m-%d') metadata[ 'Iptc.Application2.DigitizationDate'] = self.last_digitized.date( ).strftime('%Y-%m-%d') metadata[ 'Iptc.Application2.DigitizationTime'] = self.last_digitized.time( ).strftime('%H:%M:%S%z') metadata['Exif.Photo.DateTimeOriginal'] = self.developed.date( ).strftime('%Y-%m-%d') metadata[ 'Exif.Photo.DateTimeDigitized'] = self.last_digitized.strftime( '%Y-%m-%d %H:%M:%S') metadata['Iptc.Application2.Caption'] = comment metadata['Exif.Image.ImageDescription'] = comment # We already rotated correctly, so specify that there are no further # rotations. #See: http://sylvana.net/jpegcrop/exif_orientation.html metadata['Exif.Image.Orientation'] = '1' metadata.save_file()
def write_metadata(self, filepath, info): metadata = GExiv2.Metadata() metadata.open_path(filepath) compressor_name = "compressor v[{}]".format(info["version"]) # Exif.Image.ProcessingSoftware is overwritten by Lightroom when the final export is done key = "Exif.Image.ProcessingSoftware"; metadata.set_tag_string(key, compressor_name) key = "Exif.Image.Software"; metadata.set_tag_string(key, compressor_name) try: key = "Exif.Image.ExposureTime"; metadata.set_exif_tag_rational(key, info["exposure_time"], 1) except Exception as e: key = "Exif.Image.ExposureTime"; metadata.set_exif_tag_rational(key, info["exposure_time"]) key = "Exif.Image.ImageNumber"; metadata.set_tag_long(key, info["exposure_count"]) key = "Exif.Image.DateTimeOriginal"; metadata.set_tag_string(key, info["capture_date"].strftime(self.EXIF_DATE_FORMAT)) key = "Exif.Image.DateTime"; metadata.set_tag_string(key, info["compressing_date"].strftime(self.EXIF_DATE_FORMAT)) if info["focal_length"] is not None: try: key = "Exif.Image.FocalLength"; metadata.set_exif_tag_rational(key, info["focal_length"]) except Exception as e: key = "Exif.Image.FocalLength"; metadata.set_exif_tag_rational(key, info["focal_length"], 1) # TODO GPS Location metadata.save_file(filepath) self.log.debug("metadata written to {}".format(filepath))
def get_data(self, filename): tag_camera = "" tag_focal = "" tag_city = "" tag_country = "" # get EXIF metadata self.tags = GExiv2.Metadata(filename) # read tags from metadata if 'Exif.Image.Model' in self.tags: tag_camera = self.tags['Exif.Image.Model'] if 'Exif.Photo.FocalLengthIn35mmFilm' in self.tags: tag_focal = self.tags['Exif.Photo.FocalLengthIn35mmFilm'] if 'Xmp.photoshop.City' in self.tags: tag_city = self.tags['Xmp.photoshop.City'] if 'Xmp.photoshop.Country' in self.tags: tag_country = self.tags['Xmp.photoshop.Country'] # check if GPS info available posGPS = self.tags.get_gps_info() if posGPS[0] <> 0 or posGPS[1] <> 0: tag_gps = "yes" else: tag_gps = "no" return tag_camera, tag_focal, tag_gps, tag_city, tag_country
def read_photo_metadata(filepath): '''Parses image metadata using GExiv2.''' metadata = GExiv2.Metadata(filepath) if not metadata.get_tags(): return None else: return metadata
def save_pixbuf(pixbuf, filename, update_orientation_tag=False): """Save the image with all the exif keys that exist if we have exif support. NOTE: This is used to override edited images, not to save images to new paths. The filename must exist as it is used to retrieve the image format and exif data. Args: pixbuf: GdkPixbuf.Pixbuf image to act on. filename: Name of the image to save. update_orientation_tag: If True, set orientation tag to NORMAL. """ if not os.path.isfile(filename): raise FileNotFoundError( "Original file to retrieve data from not found") # Get needed information info = GdkPixbuf.Pixbuf.get_file_info(filename)[0] extension = info.get_extensions()[0] if _has_exif: exif = GExiv2.Metadata(filename) # Save pixbuf.savev(filename, extension, [], []) if _has_exif and exif.get_supports_exif(): if update_orientation_tag: exif.set_orientation(GExiv2.Orientation.NORMAL) exif.save_file()
def fetch_thumbnail(filename, size=Gst.get_int('thumbnail-size'), orient=1): """Load a photo's thumbnail from disk >>> fetch_thumbnail('gg/widgets.py') Traceback (most recent call last): OSError: gg/widgets.py: No thumbnail found. >>> type(fetch_thumbnail('demo/IMG_2411.JPG')) <class 'gi.repository.GdkPixbuf.Pixbuf'> """ try: exif = GExiv2.Metadata(filename) except GObject.GError: raise OSError('{}: No thumbnail found.'.format(filename)) with ignored(KeyError, ValueError): orient = int(exif['Exif.Image.Orientation']) try: thumb = GdkPixbuf.Pixbuf.new_from_file_at_size(filename, size, size) except GObject.GError: try: preview = exif.get_preview_properties() data = exif.get_preview_image(preview[0]).get_data() except (IndexError, GObject.GError): raise OSError('{}: No thumbnail found.'.format(filename)) return GdkPixbuf.Pixbuf.new_from_stream_at_scale( Gio.MemoryInputStream.new_from_data(data, None), size, size, True, None) return ROTATIONS.get(orient, lambda x: x)(thumb)
def run(self, task): # Save task id for logging. self.task_id = task.id # Read metadata from a temp file. try: self.metadata = GExiv2.Metadata() self.metadata.open_buf(bytes(task.get_file_data)) except Exception as e: logger.warning( "[Task {0}]: Unable to read image metadata: {1}".format( task.id, e)) self.metadata = None # Run all analysis. if self.metadata: self._get_comment() self._get_dimensions() self._get_exif() self._get_iptc() self._get_xmp() self._get_previews() self._get_gps_data() return self.results
def read(self): if self.pixmap is None: self.pixmap = QPixmap(self.path) self.metadata = GExiv2.Metadata(self.path) # the view will need three parameters: # rotation # size # zoom # the first is needed to properly orient the view over the scene # the other two are needed for zoom, mostly # but the rotation defines the images size, so they're linked self.size = self.pixmap.size() try: # try directly to get the tag, because sometimes get_tags() returns # tags that don't actually are in the file # this implicitly loads the metadata rot = self.metadata['Exif.Image.Orientation'] except KeyError: # guess :-/ logger.info("rotation 'guessed'") rot = '1' self.rotation = self.rotation_to_degrees[rot] if self.rotation in (90, 270): self.size = QSize(self.size.height(), self.size.width()) self.zoom
def __init__(self, file_path): try: self.metadata = GExiv2.Metadata(file_path) except Exception as e: logger.warning("Unable to read image metadata: {0}".format(e)) self.metadata = None self.results = AutoVivification()
def gexiv2_version() -> str: """ :return: version number of GExiv2 """ # GExiv2.get_version() returns an integer XXYYZZ, where XX is the # major version, YY is the minor version, and ZZ is the micro version v = "{0:06d}".format(GExiv2.get_version()) return "{}.{}.{}".format(v[0:2], v[2:4], v[4:6]).replace("00", "0")
def update_images_info(self, folder, output_folder): """ 1) Retrieves all the images available in the input folder 2) For each image, retrieves the timestamp 3) From the list of available locations, searches for the closest location in time. 4) EXIF is modified and image is stored in the output folder. :param folder: Name of the input folder :param output_folder: Name of the output folder. :return: None """ extensions = ["jpg"] if not os.path.exists(output_folder): mkpath(output_folder) files = [] for extension in extensions: files += glob.glob('%s/*.%s' % (folder, extension.lower())) files += glob.glob('%s/*.%s' % (folder, extension.upper())) timestamps = sorted(self.locations.keys()) take_closest = lambda num, collection: min(collection, key=lambda x: abs(x - num)) # http://coreygoldberg.blogspot.com.es/2014/01/python-fixing-my-photo-library-dates.html # https://git.gnome.org/browse/gexiv2/tree/GExiv2.py for filename in files: output_file = os.path.join(output_folder, os.path.basename(filename)) print filename, "-->", output_file copyfile(filename, output_file) exif = GExiv2.Metadata(filename) curr_timestamp = datetime.strptime(exif['Exif.Image.DateTime'], "%Y:%m:%d %H:%M:%S") curr_timestamp = (curr_timestamp - datetime(1970, 1, 1)).total_seconds() closest_timestamp = take_closest(curr_timestamp, timestamps) gps_coords = self.locations[closest_timestamp] exif.set_gps_info(gps_coords[1], gps_coords[0], 0.0) if gps_coords[0] >= 0.0: exif.set_tag_string("Exif.GPSInfo.GPSLatitudeRef", "N") else: exif.set_tag_string("Exif.GPSInfo.GPSLatitudeRef", "S") if gps_coords[1] >= 0.0: exif.set_tag_string("Exif.GPSInfo.GPSLongitudeRef", "E") else: exif.set_tag_string("Exif.GPSInfo.GPSLongitudeRef", "W") exif.save_file(output_file)
def main(camera, date, film, iso, files): """Tag scanned images with film-specific EXIF metadata.""" if date: exif_datetime = date.strftime('%Y:%m:%d %H:%M:%S') click.echo('Set dates to: {}'.format(date)) if camera: click.echo('Set camera to: {}'.format(camera)) if film: click.echo('Set film to: {}'.format(film)) if iso: click.echo('Set ISO to: {}'.format(iso)) click.confirm('Does this look OK?', abort=True) workqueue = [] for f in files: p = Path(f) if p.exists() and p.is_dir(): workqueue.extend([x for x in p.glob("*.[jJ][pP][gG]")]) elif p.exists() and p.is_file(): workqueue.append(p) with click.progressbar(workqueue, label='Tagging images...', show_pos=True) as bar: for image in bar: # Write new metadata to image. m = GExiv2.Metadata() m.register_xmp_namespace("http://analogexif.sourceforge.net/ns", "AnalogExif") m.open_path(str(image)) if date: m.set_tag_string('Exif.Image.DateTime', exif_datetime) m.set_tag_string('Exif.Photo.DateTimeOriginal', exif_datetime) m.set_tag_string('Exif.Photo.DateTimeDigitized', exif_datetime) if camera: if not camera in m.get_tag_multiple('Xmp.dc.subject'): m.set_tag_string('Xmp.dc.subject', camera) # set a keyword! for k, v in cameras[camera].items(): if isinstance(v, int): m.set_tag_long(k, v) else: m.set_tag_string(k, v) if film: if not film in m.get_tag_multiple('Xmp.dc.subject'): m.set_tag_string('Xmp.dc.subject', film) # set a keyword! m.set_tag_string('Xmp.AnalogExif.Film', film) for k, v in films[film].items(): if isinstance(v, int): m.set_tag_long(k, v) else: m.set_tag_string(k, v) if iso: m.set_tag_long("Exif.Photo.ISOSpeedRatings", iso) m.save_file(str(image)) click.echo("Done.")
def test_rotate_image(self): tmp_image_path = 'tmp_rotation_test_image.jpg' shutil.copyfile('rotation_test_image.jpg', tmp_image_path) metadata_before = GExiv2.Metadata(tmp_image_path) self.uploader.rotate_image(tmp_image_path) metadata_after = GExiv2.Metadata(tmp_image_path) assert metadata_after['Exif.Image.Orientation'] == '1' assert metadata_after.get_pixel_height() == \ metadata_before.get_pixel_width() assert metadata_after.get_pixel_width() == \ metadata_before.get_pixel_height() os.remove('tmp_rotation_test_image.jpg')
def do_activate(self): if not self.window: self.window = psp_mw.MainWindow(self) GExiv2.initialize() self.window.present()
except ValueError: pass from gi.repository import GObject, GExiv2 import six from photini import __version__ logger = logging.getLogger(__name__) gexiv2_version = '{} {}, GExiv2 {}, GObject {}'.format( ('PyGI', 'pgi')[using_pgi], gi.__version__, GExiv2._version, GObject._version) # pydoc gi.repository.GExiv2.Metadata is useful to see methods available GExiv2.log_set_level(GExiv2.LogLevel.MUTE) def safe_fraction(value): # Avoid ZeroDivisionError when '0/0' used for zero values in Exif if isinstance(value, six.string_types): numerator, sep, denominator = value.partition('/') if denominator and int(denominator) == 0: return Fraction(0.0) return Fraction(value).limit_denominator(1000000) class MetadataValue(object): # base for classes that store a metadata value, e.g. a string, int # or float def __init__(self, value): assert(value is not None)
# didjvu is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. '''XMP support (GExiv2 backend)''' import re import gi try: gi.require_version('GExiv2', '0.10') except ValueError as exc: # no coverage raise ImportError(exc) from gi.repository import GExiv2 if GExiv2.get_version() < 1003: raise ImportError('GExiv2 >= 0.10.3 is required') # no coverage from .. import temporary from .. import timestamp from . import namespaces as ns GExiv2.Metadata.register_xmp_namespace(ns.didjvu, 'didjvu') class XmpError(RuntimeError): pass class MetadataBase(object): _empty_xmp = (
## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program. If not, see ## <http://www.gnu.org/licenses/>. from gi.repository import GObject, GExiv2 GExiv2.initialize() class MetadataHandler(object): def __init__(self, path): self._path = path self._md = GExiv2.Metadata() self._md.open_path(path) def save(self): try: return self._md.save_file(self._path) except GObject.GError as ex: print str(ex) return False def copy(self, other, exif=True, iptc=True, xmp=True, comment=True):