Skip to content

Instantly share code, notes, and snippets.

@outadoc
Last active November 7, 2020 15:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save outadoc/4e9538f40bcb607c83db525f1e0a67d1 to your computer and use it in GitHub Desktop.
Save outadoc/4e9538f40bcb607c83db525f1e0a67d1 to your computer and use it in GitHub Desktop.
Google Photos Takeout export EXIF fixer
# sort in date/month directories
exiftool -r "-Directory<DateTimeOriginal" -d "%Y/%m" -v .
# use CreationDate if DateTimeOriginal didn't work
exiftool "-Directory<CreationDate" -d "%Y/%m" -v .
#!/usr/bin/env python3
import argparse
import os
import re
from pathlib import Path
import json
from datetime import datetime, timezone
def main():
parser = argparse.ArgumentParser()
parser.add_argument('photos_directory')
parser.add_argument('metadata_directory')
args = parser.parse_args()
photos_directory = os.path.abspath(args.photos_directory)
metadata_directory = os.path.abspath(args.metadata_directory)
print("photos dir: \t" + photos_directory)
print("metadata dir: \t" + metadata_directory)
if not confirm():
exit(1)
jsons = list(Path(metadata_directory).glob("*"))
photos = list(Path(photos_directory).rglob("*"))
photos_timestamps = dict((x, y) for x, y in process_metadata_list(jsons))
process_photo_list(photos, photos_timestamps)
def process_metadata_list(jsons):
total = len(jsons)
i = 0
for file in jsons:
i = i + 1
print("processing metadata file " + str(i) + " out of " + str(total))
if file.is_file():
m = process_metadata(file)
#print(m)
if m:
yield m
def process_metadata(file):
with file.open() as f:
j = json.load(f)
if 'title' in j:
title = j['title'].replace('%', '_')
return (title, {
'taken': int(j['photoTakenTime']['timestamp']),
'created': int(j['creationTime']['timestamp']),
'modified': int(j['modificationTime']['timestamp']),
'latitude': j['geoData']['latitude'],
'longitude': j['geoData']['longitude'],
'altitude': j['geoData']['altitude']
})
else:
return None
def process_photo_list(photos, photos_timestamps):
total = len(photos)
i = 0
for file in photos:
i = i + 1
print("processing " + str(i) + " out of " + str(total))
if file.is_file():
basename = normalize_photo_name(os.path.basename(file.name))
if basename in photos_timestamps:
process_photo(file, photos_timestamps[basename])
else:
print('w: no metadata found for ' + basename)
def normalize_photo_name(basename):
#return re.sub(r'\([0-9]+\)\.', '.', basename)
return basename
def process_photo(file, photo_metadata):
print(file)
cmd = (
"exiftool -overwrite_original "
"-FileCreateDate=\"" +
timestamp_to_datetime(photo_metadata['created']) + "\" "
"-FileModifyDate=\"" +
timestamp_to_datetime(photo_metadata['modified']) + "\" "
"-DateTimeOriginal=\"" +
timestamp_to_datetime(photo_metadata['taken']) + "\" "
"-xmp:dateTimeOriginal=\"" +
timestamp_to_datetime_extended(photo_metadata['taken']) + "\" "
"-GPSLatitudeRef=" + str(photo_metadata['latitude']) + " "
"-GPSLongitudeRef=" + str(photo_metadata['longitude']) + " "
"-GPSAltitudeRef=" + str(photo_metadata['altitude']) + " "
"\"" + str(file) + "\""
)
#print(cmd)
os.system(cmd)
def timestamp_to_datetime(timestamp):
time = datetime.fromtimestamp(timestamp, tz=timezone.utc)
return time.strftime("%Y:%m:%d %H:%M:%S")
def timestamp_to_datetime_extended(timestamp):
time = datetime.fromtimestamp(timestamp, tz=timezone.utc)
return time.strftime("%Y:%m:%d %H:%M:%SZ")
def confirm():
answer = ""
while answer not in ["y", "n"]:
answer = input("is this ok? [Y/N] ").lower()
return answer == "y"
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment