Skip to content

Instantly share code, notes, and snippets.

@mohit0749
Last active July 21, 2024 16:43
Show Gist options
  • Save mohit0749/734537a0a186590c18c0e070d23d576a to your computer and use it in GitHub Desktop.
Save mohit0749/734537a0a186590c18c0e070d23d576a to your computer and use it in GitHub Desktop.
GooglePhotosTakeoutHelper

Google Photos Takeout Helper

This script simplifies the management of your Google Photos takeout archive. Designed to work with the google photos takeout files, it merges metadata with corresponding photos on creation time, geographical information, and other metadata attributes.

Features

  • Merges JSON metadata with corresponding photo files.
  • Utilizes ExifTool to modify photo metadata directly.
  • Organizes photos into a specified output directory.
  • Moves processed files to a bin directory for backup.

Usage

  1. Ensure Python and ExifTool are installed on your system.
  2. Adjust the directory, output_directory, and bin_directory paths in the script to match your local setup.
  3. Run the script with python google_photos_takeout_helper.py to start processing your Google Photos takeout archive.

Requirements

  • Python 3.x
  • ExifTool (Ensure it is added to your system's PATH)
directory = '/path/to/photos'
output_directory = '/path/to/output_directory'
bin_directory = '/path/to/trash_directory'
import json
import os
import shutil
from datetime import datetime
# Helper function to convert timestamp to datetime
def convert_timestamp(timestamp):
return datetime.utcfromtimestamp(int(timestamp))
# Create the output directory and bin directory if they don't exist
os.makedirs(output_directory, exist_ok=True)
os.makedirs(bin_directory, exist_ok=True)
for file_name in os.listdir(directory):
if file_name.endswith('.json'):
with open(os.path.join(directory, file_name), 'r') as json_file:
json_data = json.load(json_file)
photo_name = file_name.strip(".json")
photo_path = os.path.join(directory, photo_name)
# Check if the photo file exists
if os.path.exists(photo_path):
# Extract relevant metadata
creation_time = json_data.get('creationTime', {}).get('timestamp')
geo_data = json_data.get('geoData', {})
description = json_data.get('description', '')
image_views = json_data.get('imageViews', 0)
photo_taken_time = json_data.get('photoTakenTime', {}).get('timestamp')
url = json_data.get('url', '')
# Convert timestamp to datetime
creation_datetime = convert_timestamp(creation_time) if creation_time else None
photo_taken_datetime = convert_timestamp(photo_taken_time) if photo_taken_time else None
# Modify photo metadata with ExifTool
command = f'exiftool -overwrite_original ' \
f'-MakerNotes:all= ' \
f'-gps:all= ' \
f'-gpslatitude={geo_data.get("latitude", 0)} ' \
f'-gpslongitude={geo_data.get("longitude", 0)} ' \
f'-gpsaltitude={geo_data.get("altitude", 0)} ' \
f'-gpslatituderef={"N" if geo_data.get("latitude", 0) >= 0 else "S"} ' \
f'-gpslongituderef={"E" if geo_data.get("longitude", 0) >= 0 else "W"} ' \
f'-gpsdatetime="{photo_taken_datetime.strftime("%Y:%m:%d %H:%M:%S") if photo_taken_datetime else ""}" ' \
f'-description="{description}" ' \
f'-imagewidth={image_views} ' \
f'-EXIF:DateTimeOriginal="{photo_taken_datetime.strftime("%Y:%m:%d %H:%M:%S") if photo_taken_datetime else ""}" ' \
f'-url="{url}" ' \
f'"{photo_path}"'
# Execute ExifTool command
print("Executing command:", command) # Print the command for debugging
try:
os.system(command)
print(f"Processed photo: {photo_name}")
# Make a copy of the photo to the output directory
output_photo_path = os.path.join(output_directory, photo_name)
shutil.copy(photo_path, output_photo_path)
# Move the original photo to the bin directory
bin_photo_path = os.path.join(bin_directory, photo_name)
shutil.move(photo_path, bin_photo_path)
print(f"Moved original photo to bin: {photo_name}")
# Move the JSON file to the bin directory
bin_json_path = os.path.join(bin_directory, file_name)
shutil.move(os.path.join(directory, file_name), bin_json_path)
print(f"Moved JSON file to bin: {file_name}")
# Set the last modified time of the copied photo
os.utime(output_photo_path, (os.stat(output_photo_path).st_atime, int(photo_taken_time)))
except Exception as e:
print(f"Error processing photo {photo_name}: {str(e)}")
else:
print(f"Photo not found: {photo_name}")
else:
print(f"Ignoring non-JSON file: {file_name}")
print("Processing completed. Photos have been copied to the output directory with the creation time as the last modified time.")
@iddan
Copy link

iddan commented Jun 29, 2024

This is great!
Made a small change to work on nested directories:

# Source: https://gist.github.com/mohit0749/734537a0a186590c18c0e070d23d576a

directory = '/path/to/Takeout/Google Photos'
output_directory = '/path/to/Takeout/Google Photos 2'
bin_directory = '/Users/example/.Trash'

import json
import os
import shutil
from datetime import datetime
from pathlib import Path


# Helper function to convert timestamp to datetime
def convert_timestamp(timestamp):
    return datetime.utcfromtimestamp(int(timestamp))

# Create the output directory and bin directory if they don't exist
os.makedirs(output_directory, exist_ok=True)
os.makedirs(bin_directory, exist_ok=True)
 
for json_file_path in Path(directory).glob('**/*.json'):
    with json_file_path.open('r') as json_file:
        json_data = json.load(json_file)
    file_name = json_file_path.name
    photo_name = file_name.strip(".json")
    photo_path = json_file_path.parent / photo_name

    # Check if the photo file exists
    if photo_path.exists():
        # Extract relevant metadata
        creation_time = json_data.get('creationTime', {}).get('timestamp')
        geo_data = json_data.get('geoData', {})
        description = json_data.get('description', '')
        image_views = json_data.get('imageViews', 0)
        photo_taken_time = json_data.get('photoTakenTime', {}).get('timestamp')
        url = json_data.get('url', '')

        # Convert timestamp to datetime
        creation_datetime = convert_timestamp(creation_time) if creation_time else None
        photo_taken_datetime = convert_timestamp(photo_taken_time) if photo_taken_time else None

        # Modify photo metadata with ExifTool
        command = f'exiftool -overwrite_original ' \
                f'-MakerNotes:all= ' \
                f'-gps:all= ' \
                f'-gpslatitude={geo_data.get("latitude", 0)} ' \
                f'-gpslongitude={geo_data.get("longitude", 0)} ' \
                f'-gpsaltitude={geo_data.get("altitude", 0)} ' \
                f'-gpslatituderef={"N" if geo_data.get("latitude", 0) >= 0 else "S"} ' \
                f'-gpslongituderef={"E" if geo_data.get("longitude", 0) >= 0 else "W"} ' \
                f'-gpsdatetime="{photo_taken_datetime.strftime("%Y:%m:%d %H:%M:%S") if photo_taken_datetime else ""}" ' \
                f'-description="{description}" ' \
                f'-imagewidth={image_views} ' \
                f'-EXIF:DateTimeOriginal="{photo_taken_datetime.strftime("%Y:%m:%d %H:%M:%S") if photo_taken_datetime else ""}" ' \
                f'-url="{url}" ' \
                f'"{photo_path}"'

        # Execute ExifTool command
        print("Executing command:", command)  # Print the command for debugging
        try:
            os.system(command)
            print(f"Processed photo: {photo_name}")
            # Make a copy of the photo to the output directory
            output_photo_path = os.path.join(output_directory, photo_name)
            shutil.copy(photo_path, output_photo_path)
            
            # Move the original photo to the bin directory
            bin_photo_path = os.path.join(bin_directory, photo_name)
            shutil.move(photo_path, bin_photo_path)
            print(f"Moved original photo to bin: {photo_name}")
            
            # Move the JSON file to the bin directory
            bin_json_path = os.path.join(bin_directory, file_name)
            shutil.move(json_file_path, bin_json_path)
            print(f"Moved JSON file to bin: {file_name}")

            # Set the last modified time of the copied photo
            os.utime(output_photo_path, (os.stat(output_photo_path).st_atime, int(photo_taken_time)))

        except Exception as e:
            print(f"Error processing photo {photo_name}: {str(e)}")
    else:
        print(f"Photo not found: {photo_name}")

print("Processing completed. Photos have been copied to the output directory with the creation time as the last modified time.")
@christian-dale
Copy link

Great stuff!

I found a bug which occurred some times with this line (The first character of an image name would be removed for some reason):
photo_name = file_name.strip(".json")

Changing it to this seems to have resolved the problem:
photo_name = file_name.removesuffix(".json")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment