#!/usr/bin/env python3 from subprocess import Popen, PIPE from datetime import datetime import json from os.path import isfile, isdir from os import walk TYPES = [ "JPEG", "JPG", "EXV", "CR2", "CRW", "MRW", "TIFF", "TIF", "WEBP", "DNG", "NEF", "FEF", "ARW", "RW2", "SR2", "SRW", "ORF", "PNG", "PGF", "RAF", "PSD", "JP2" ] def extractor(input_files, start, end, recursive): if not isinstance(input_files, type(list())): raise ValueError("Input files must be a list.") exif_dict_list = [] files = [] usefull_exif = [ "Image.Model", "Photo.LensModel", "Photo.FocalLength", "Photo.ApertureValue", "Photo.ExposureTime", "Photo.ISOSpeedRatings", "Photo.PixelXDimension", "Photo.PixelYDimension", "Image.ImageWidth", "Image.ImageLength", "Photo.DateTimeOriginal" ] for item in input_files: if isdir(item): for (dirpath, dirnames, filenames) in walk(item): for filename in filenames: if filename.split(".")[-1].upper() in TYPES: files.append("{}/{}".format(dirpath, filename)) if not recursive: break else: files.append(item) input_files = files for input_file in input_files: exif_dict = {} if not isfile(input_file): raise ValueError("{} doesn't exist here.".format(input_file)) exif_dict.update({'file': input_file}) for extracted in usefull_exif: cmd = ["exiv2", "-g", extracted, "-Pt", input_file] with Popen(cmd, stdout=PIPE) as extracted_data: extracted_data = extracted_data.stdout.read().decode() if extracted_data: extracted_data = extracted_data.splitlines()[0] exif_dict.update({extracted.split(".")[1]: extracted_data}) else: exif_dict.update({extracted.split(".")[1]: None}) if exif_dict['FocalLength']: exif_dict['FocalLength'] = float( exif_dict['FocalLength'].replace(" mm", "")) if exif_dict['ApertureValue']: exif_dict['ApertureValue'] = float( exif_dict['ApertureValue'].replace("F", "")) if exif_dict['PixelXDimension'] and exif_dict['PixelYDimension']: exif_dict['PixelXDimension'] = int(exif_dict['PixelXDimension']) exif_dict['PixelYDimension'] = int(exif_dict['PixelYDimension']) pixels = exif_dict['PixelXDimension'] * \ exif_dict['PixelYDimension'] elif exif_dict['ImageLength'] and exif_dict['ImageWidth']: exif_dict['PixelXDimension'] = int(exif_dict['ImageLength']) exif_dict['PixelYDimension'] = int(exif_dict['ImageWidth']) pixels = exif_dict['PixelXDimension'] * \ exif_dict['PixelYDimension'] else: pixels = None if exif_dict['DateTimeOriginal']: exif_dict['DateTimeOriginal'] = datetime.strptime( exif_dict['DateTimeOriginal'], "%Y:%m:%d %H:%M:%S") if exif_dict['ExposureTime']: try: exif_dict['ExposureTime'] = float( exif_dict['ExposureTime'].split(" ")[0]) except ValueError: exposure = exif_dict['ExposureTime'].split(" ")[0] exif_dict['ExposureTime'] = float( 1/(float(exposure.split("/")[1]))) if pixels: exif_dict.update({'Dimension': round((pixels/10**6), 1)}) else: exif_dict.update({'Dimension': None}) exif_dict_list.append(exif_dict) cameras, lenses, focals, apertures, exposures = {}, {}, {}, {}, {} isos, dimensions, cameras_lenses, dates = {}, {}, {}, {} cameras_list, lenses_list, focals_list, apertures_list, exposures_list = [], [], [], [], [] isos_list, dimensions_list, cameras_lenses_list, dates_list = [], [], [], [] for data in exif_dict_list: if start <= data['DateTimeOriginal'] <= end: cameras_list.append(data['Model']) lenses_list.append(data['LensModel']) focals_list.append(data['FocalLength']) apertures_list.append(data['ApertureValue']) exposures_list.append(data['ExposureTime']) isos_list.append(data['ISOSpeedRatings']) dimensions_list.append(data['Dimension']) if data['LensModel']: cameras_lenses_list.append( "{} + {}".format(data['Model'], data['LensModel'])) dates_list.append(data['DateTimeOriginal'].strftime("%Y%m%d")) for camera in list(set(cameras_list)): if camera: cameras.update({camera: cameras_list.count(camera)}) for lens in list(set(lenses_list)): if lens: lenses.update({lens: lenses_list.count(lens)}) for camera_lens in list(set(cameras_lenses_list)): if camera_lens: cameras_lenses.update( {camera_lens: cameras_lenses_list.count(camera_lens)}) for focal in list(set(focals_list)): if focal: focals.update({focal: focals_list.count(focal)}) for aperture in list(set(apertures_list)): if aperture: apertures.update({aperture: apertures_list.count(aperture)}) for exposure in list(set(exposures_list)): if exposure: exposures.update( {float(exposure*1000): exposures_list.count(exposure)}) for iso in list(set(isos_list)): if iso: isos.update({iso: isos_list.count(iso)}) for dimension in list(set(dimensions_list)): if dimension: dimensions.update({dimension: dimensions_list.count(dimension)}) for date in list(set(dates_list)): if date: dates.update({date: dates_list.count(date)}) return { "total": len(dimensions_list), "date": dates, "cameras": cameras, "lenses": lenses, "cameras+lenses": cameras_lenses, "focals": focals, "apertures": apertures, "shutter_speeds": exposures, "isos": isos, "dimensions": dimensions } if __name__ == '__main__': import argparse recursive = False parser = argparse.ArgumentParser() parser.add_argument( "infile", help="input file (EXV, JPG, CR2, ...). Can be more than one.", nargs='+') parser.add_argument("-s", "--start-date", help="start date as YYYYMMDD. If omitted, begin of the collection.", const=None) parser.add_argument("-e", "--end-date", help="end date as YYYYMMDD. If omitted, today", const=None) parser.add_argument("-R", "--recursive", help="Walk throught directory to find all files", action='store_true') args = parser.parse_args() if not args.start_date: args.start_date = "18250101" # Ok, that's the year of the 1st photography by Nicéphore Niépce. Shoul'd be enough for a start date. if not args.end_date: args.end_date = datetime.strftime(datetime.now(), "%Y%m%d") try: datetime.strptime(args.start_date, "%Y%m%d") datetime.strptime(args.end_date, "%Y%m%d") except ValueError: print("Date must be YYYYMMDD.") exit() recursive = args.recursive print(json.dumps(extractor(args.infile, datetime.strptime(args.start_date, "%Y%m%d"), datetime.strptime(args.end_date, "%Y%m%d"), recursive), indent=4, sort_keys=True))