Created
August 29, 2021 19:29
-
-
Save rruntsch/60871695678551dafd3adaf5c69dadad to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Name: c_photo_exif.py | |
Author: Randy Runtsch | |
Date: March 20, 2021 | |
Update Date: August 28, 2021 | |
Description: The c_photo_exif class opens all image files in the specified | |
folder, reads the Exif data from each file. It then gets the sunrise | |
and sunset times of the day the photo was taken. It then writes a subset | |
of the Exif data, and the sunrise and sunset times, as a row in a CSV file. | |
GPS Tags: https://www.opanda.com/en/pe/help/gps.html#GPSLongitude | |
""" | |
import csv | |
import exifread | |
import os | |
import datetime | |
#from pytz import timezone | |
class c_photo_exif: | |
def __init__(self, input_photo_folder, output_csv_file_nm): | |
# Create the output CSV file with headers and process all of the | |
# photo files. | |
self._rows_processed = 0 | |
csv_writer = self.init_csv_file(output_csv_file_nm) | |
self.process_photos(input_photo_folder, csv_writer) | |
def process_photos(self, input_photo_folder, csv_writer): | |
# Process all of the image files contained in the input folder. | |
rows = 0 | |
for subdirs, dirs, files in os.walk(input_photo_folder): | |
for photo_file_nm in files: | |
# Process only files with a .jpg extension. | |
if photo_file_nm[-4:] == '.jpg': | |
self._rows_processed += 1 | |
photo_file_nm_full = input_photo_folder + '/' + photo_file_nm | |
self.process_photo(photo_file_nm, photo_file_nm_full, csv_writer) | |
def process_photo(self, photo_file_nm, photo_file_nm_full, csv_writer): | |
# Get a subset of EXIF values from the photo file. Call function write_csv_file_row() | |
# to write the values as a row to a CSV file. | |
photo_file = open(photo_file_nm_full, 'rb') | |
tags = exifread.process_file(photo_file, strict = False) | |
camera_make = tags['Image Make'].printable | |
camera_model = tags['Image Model'].printable | |
photo_date_time = tags['EXIF DateTimeOriginal'].printable | |
focal_length_raw = tags['EXIF FocalLength'].printable | |
shutter_speed = tags['EXIF ExposureTime'].printable | |
f_stop_raw = tags['EXIF FNumber'].printable | |
exposure_bias = tags['EXIF ExposureBiasValue'].printable | |
white_balance = tags['EXIF WhiteBalance'].printable | |
iso = tags['EXIF ISOSpeedRatings'].printable | |
# Handle errors for GPS tags, since they may not exist in all records. | |
try: | |
gps_latitude = tags['GPS GPSLatitude'] | |
except: | |
gps_latitude = "0" | |
try: | |
gps_longitude = tags['GPS GPSLongitude'] | |
except: | |
gps_longitude = "0" | |
# The GPS GPSLongitudeRef tag stores "W" for west and "E" for east. | |
# Every coordinate west of the Prime Meridian is negative longitude, | |
# while every coordinate to its right is positive. | |
try: | |
gps_longitude_ref = tags['GPS GPSLongitudeRef'].printable | |
except: | |
gps_longitude_ref = "" | |
latitude = self.calculate_coordinate(gps_latitude, "") | |
longitude = self.calculate_coordinate(gps_longitude, gps_longitude_ref) | |
# Convert some iPhone values from fraction string to decimal. | |
f_stop = self.calculate_fraction(f_stop_raw) | |
focal_length = self.calculate_fraction(focal_length_raw) | |
# Exif date-time values contain colons in the date part (for example, 2021:01:20). Replace | |
# the first two colons with hyphens (-) so that they will be treated as date-time values | |
# in other programs, such as Tableau. This will leave the colons in place in the time portion | |
# of the value. | |
photo_date_time = photo_date_time.replace(':', '-', 2) | |
self.write_csv_file_row(csv_writer, photo_file_nm, photo_date_time, \ | |
camera_make, camera_model, focal_length, shutter_speed, f_stop, exposure_bias, iso, white_balance, \ | |
latitude, longitude) | |
def calculate_fraction(self, fraction_string): | |
# iPhones store f stop values as a fraction. For example, 8/5. | |
# If the value contains a slash, divide the numerator by the denominator | |
# and return the result. Else, simply return the original value. | |
return_val = fraction_string | |
if fraction_string.find("/") != -1: | |
fraction_list = fraction_string.split("/") | |
return_val = int(fraction_list[0]) / int(fraction_list[1]) | |
return return_val | |
def calculate_coordinate(self, gps_string, gps_longitude_ref): | |
# Convert a GPS latitude or longitude coordinate string to a numeric decimal value. | |
# A GPS coordinate is composed of these comma-separated strings: | |
# - Degrees | |
# - Minutes (optional) | |
# - Seconds (optional) | |
if gps_string == "0": | |
return 0 | |
degrees = gps_string.values[0] | |
minutes_raw = gps_string.values[1] | |
seconds_raw = gps_string.values[2] | |
minutes = (minutes_raw.numerator / minutes_raw.denominator) / 60 | |
seconds = (seconds_raw.numerator / seconds_raw.denominator) / 3600 | |
coordinate = degrees + minutes + seconds | |
# Change longitude in the Western Hempispher to negative values. | |
if gps_longitude_ref == "W": | |
coordinate = -abs(coordinate) | |
return coordinate | |
def init_csv_file(self, output_csv_file_nm): | |
# Open the CSV output file for write (create a new file and overwrite | |
# the existing file), write its header, and return its handle. | |
headers = ['File Name', 'Photo Date Time', 'Camera Make', 'Camera Model', \ | |
'Focal Length', 'Shutter Speed', 'F Stop', 'Exposure Bias', 'ISO', 'White Balance', 'Latitude', 'Longitude'] | |
csv_file = open(output_csv_file_nm, 'w', encoding='utf-8', newline='') | |
csv_writer = csv.DictWriter(csv_file, headers) | |
csv_writer.writeheader() | |
return csv_writer | |
def write_csv_file_row(self, csv_writer, file_name, photo_date_time, \ | |
camera_make, camera_model, focal_length, shutter_speed, f_stop, exposure_bias, iso, white_balance, \ | |
latitude, longitude): | |
# Assemble the record of Exif values by column and write it to the CSV file. | |
row = {'File Name' : file_name, 'Photo Date Time' : photo_date_time, \ | |
'Camera Make' : camera_make, 'Camera Model' : camera_model, \ | |
'Focal Length' : focal_length, 'Shutter Speed' : shutter_speed, \ | |
'F Stop' : f_stop, 'Exposure Bias': exposure_bias, 'ISO' : iso, 'White Balance' : white_balance, \ | |
'Latitude' : latitude, 'Longitude' : longitude} | |
csv_writer.writerow(row) | |
def get_rows_processed(self): | |
return self._rows_processed |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment