Skip to content

Instantly share code, notes, and snippets.

@zsarge
Created July 1, 2025 15:13
Show Gist options
  • Save zsarge/e0c49cab7f287f30f8e1628d2dc0c6cb to your computer and use it in GitHub Desktop.
Save zsarge/e0c49cab7f287f30f8e1628d2dc0c6cb to your computer and use it in GitHub Desktop.
Update the times on photos based on their names.
"""
When you download photos from Google Photos, the files do not keep the appropriate Modified/Created times.
This script takes a folder path, and loops through the file names of all images from Google Photos.
If the images are images, the EXIF data is updated; Otherwise, we just update the file times.
This script was based on https://github.com/valentin-stamate/photo-date/tree/master
with input from Deepseek V3.
"""
import os
from datetime import datetime
import piexif
import re
def parse_filename_to_datetime(filename: str) -> datetime | None:
"""Parse datetime from filename using multiple patterns"""
# Pattern 1: PXL_YYYYMMDD_HHMMSS... (Google Pixel)
if filename.startswith('PXL_'):
parts = filename.split('_')
if len(parts) >= 3:
date_str = parts[1]
time_str = parts[2]
if len(date_str) == 8 and date_str.isdigit():
# Extract leading digits from time portion
num_part = ''.join(char for char in time_str if char.isdigit())
if len(num_part) >= 6:
time_part = num_part[:6] # Use first 6 digits (HHMMSS)
try:
return datetime.strptime(date_str + time_part, "%Y%m%d%H%M%S")
except ValueError:
pass
# Pattern 2: YYYYMMDDHHMMSS... (timestamp prefix)
if len(filename) >= 14 and filename[:14].isdigit():
try:
return datetime.strptime(filename[:14], "%Y%m%d%H%M%S")
except ValueError:
pass
# Pattern 3: Standard camera names (IMG_xxxx, DSC_xxxx, etc.)
std_cam_pattern = r'^(IMG|DSC|PXL|MOV)_(\d{4,})'
match = re.match(std_cam_pattern, filename)
if match:
num_part = match.group(2)
if len(num_part) >= 8:
try:
# Try to parse as YYYYMMDD if we have at least 8 digits
return datetime.strptime(num_part[:8], "%Y%m%d")
except ValueError:
pass
return None
def update_image_metadata(path, date_time):
"""Update EXIF metadata and file timestamps for images"""
file_name = os.path.basename(path)
date_time_str = date_time.strftime("%Y:%m:%d %H:%M:%S")
try:
exif_dict = piexif.load(path)
# Update EXIF datetime fields
exif_dict['0th'][piexif.ImageIFD.DateTime] = date_time_str
exif_dict['Exif'][piexif.ExifIFD.DateTimeOriginal] = date_time_str
exif_dict['Exif'][piexif.ExifIFD.DateTimeDigitized] = date_time_str
exif_bytes = piexif.dump(exif_dict)
piexif.insert(exif_bytes, path)
# Update file system timestamps
update_file_timestamps(path, date_time)
print(f'Image: %-70s modified | New datetime: %s' % (file_name, date_time_str))
except Exception as e:
print(f'Image: %-70s ERROR: %s' % (file_name, str(e)))
def update_file_timestamps(path, date_time):
"""Update file system timestamps only"""
timestamp = datetime.timestamp(date_time)
os.utime(path, (timestamp, timestamp))
def visit(file_path: str):
"""Process individual file"""
name = os.path.basename(file_path)
ext = os.path.splitext(file_path)[1].lower()
# Extract datetime from filename
new_date_time = parse_filename_to_datetime(name)
if not new_date_time:
print(f"File: %-70s skipped (no date in filename)" % name)
return
# Handle images
if ext in ['.jpg', '.jpeg', '.tif', '.tiff']:
update_image_metadata(file_path, new_date_time)
# Handle videos
elif ext in ['.mp4', '.mov', '.avi', '.mkv']:
update_file_timestamps(file_path, new_date_time)
print(f'Video: %-70s timestamps updated | New datetime: %s' % (name, new_date_time))
# Skip other file types
else:
print(f"File: %-70s skipped (unsupported format)" % name)
def change_recursive(root: str):
"""Process directory recursively"""
for entry in os.listdir(root):
path = os.path.join(root, entry)
if os.path.isdir(path):
change_recursive(path)
else:
visit(path)
def main():
directory = input("Insert the path to your photos and videos: ")
change_recursive(directory)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment