Relatives asked me to help with their photo backups. Their iPhone photos were in .HEIC format, which they couldn’t view on their laptop. Exporting to JPG however changed the “created” date, which they used to sort their album. After trying everything, they decided to ask the “computer guy” for help (me).

Luckily, photos have metadata, which we can manipulate in Python. The following script converts all HEIC files in the input directory to JPG files in the output directory, and overwrites the os ‘Date Created’ and ‘Modified’ with the original ‘ContentCreated’ or ‘DateTimeOriginal’ tag.

It requires some python packages that can be installed using

pip install pillow pillow-heif piexif

The input and output directory should be specified in

# Input and output directories
from_directory = "from"
to_directory = "to"

Complete code (also on GitHub)

import os
from datetime import datetime
from PIL import Image
import pillow_heif
import piexif
from PIL import ExifTags

pillow_heif.register_heif_opener()

# Input and output directories
from_directory = "from"
to_directory = "to"

# Ensure they exists
os.makedirs(to_directory, exist_ok=True)

# Function extract EXIF DateTime or ContentCreated
def extract_datetime_or_created(exif_data):
    if exif_data:
        for tag_id, value in exif_data.items():
            tag_name = ExifTags.TAGS.get(tag_id, tag_id)
            if tag_name == 'DateTime':
                return value
            if tag_name == 'ContentCreated':
                return value
    return None

# Process all HEIC files in "from""
for filename in os.listdir(from_directory):
    if filename.lower().endswith(".heic"):
        heic_file_path = os.path.join(from_directory, filename)
        
        try:
            img = Image.open(heic_file_path)
            exif_data = img.getexif() # Extract EXIF, use DateTime/ContentCreated
            content_created_str = extract_datetime_or_created(exif_data)
            jpeg_file_path = os.path.join(to_directory, f"{os.path.splitext(filename)[0]}.jpg") # Convert to JPEG
            img = img.convert("RGB")

            if content_created_str:
                try:
                    content_created_datetime = datetime.strptime(content_created_str, "%Y:%m:%d %H:%M:%S")
                    exif_timestamp = content_created_datetime.strftime("%Y:%m:%d %H:%M:%S")
                except ValueError:
                    exif_timestamp = content_created_str
            else:
                exif_timestamp = None

            # If valid ContentCreated/DateTime, overwrite EXIF
            exif_dict = piexif.load(img.info.get("exif", b""))
            if exif_timestamp:
                # Set DateTime (EXIF tag 0x9003 (DateTimeOriginal))
                exif_dict['0th'][piexif.ImageIFD.DateTime] = exif_timestamp
                exif_dict['Exif'][piexif.ExifIFD.DateTimeOriginal] = exif_timestamp  # Set DateTimeOriginal tag

            # Convert EXIF dict to bytes
            exif_bytes = piexif.dump(exif_dict)

            # Save as JPEG
            img.save(jpeg_file_path, "JPEG", exif=exif_bytes, quality=100, optimize=True)

            print(f"Converted: {filename} to {jpeg_file_path} (ContentCreated: {exif_timestamp})")

            
            if content_created_str:
                file_timestamp = content_created_datetime.timestamp()

                # Edit os creation and modification times
                os.utime(jpeg_file_path, (file_timestamp, file_timestamp))
                print(f"Updated timestamps for {jpeg_file_path} (Created/Modified: {exif_timestamp})")

        except Exception as e:
            print(f"Failed to process {filename}: {e}")
# Some code to inspect the metadata of HEIC files
from PIL import Image
import pillow_heif
from PIL import ExifTags

# Register HEIC support in Pillow
pillow_heif.register_heif_opener()

# Open the HEIC image file
heic_file_path = "path/file.HEIC"
img = Image.open(heic_file_path)

# Show the image using the default viewer (Preview on macOS)
img.show()

# Extract EXIF data
exif_data = img.getexif()

if exif_data:
    print("\nEXIF Data:")
    for tag_id, value in exif_data.items():
        tag_name = ExifTags.TAGS.get(tag_id, tag_id)  # Get the tag name from the ExifTags dictionary
        print(f"{tag_name}: {value}")
else:
    print("No EXIF data found.")

And there you have it! A simple script to convert HEIC files to JPG while preserving the original creation date.