Python script to rename files based on EXIF 'Created on'

This script will rename files in a specified directory based on the EXIF “Created on” date.

How to use

# To just print what would be done, without actually renaming the files:
python exifrename.py <directory> --dry-run
# To rename the files:
python exifrename.py <directory>

Example:

Renaming: IMG_5198.JPG -> campchyoca/14/2021-07-03_07-47-04-IMG_5198.JPG

Full source code

#!/usr/bin/env python3
import os
import argparse
from pathlib import Path
from PIL import Image, ExifTags
from datetime import datetime

def get_exif_creation_date(file_path):
    """Retrieve the EXIF creation date from an image file."""
    try:
        with Image.open(file_path) as img:
            exif = img._getexif()
            if exif is not None:
                for tag, value in exif.items():
                    decoded_tag = ExifTags.TAGS.get(tag, tag)
                    if decoded_tag == "DateTimeOriginal":
                        return value
    except Exception as e:
        print(f"Error reading EXIF data from {file_path}: {e}")
    return None

def format_filename(date_string, original_name):
    """Format the filename with the date and original name."""
    try:
        date_object = datetime.strptime(date_string, "%Y:%m:%d %H:%M:%S")
        formatted_date = date_object.strftime("%Y-%m-%d_%H-%M-%S")
        return f"{formatted_date}-{original_name}"
    except ValueError:
        print(f"Invalid date format for {original_name}: {date_string}")
        return None

def ensure_unique_filename(directory, filename):
    """Ensure the filename is unique within the directory by appending a counter if necessary."""
    base, ext = os.path.splitext(filename)
    counter = 1
    while (directory / filename).exists():
        filename = f"{base}.{counter}{ext}"
        counter += 1
    return filename

def exif_rename_files(directory, dry_run, recursive=False):
    """Rename files in the specified directory based on their EXIF creation date."""
    # Process files in current directory
    for file in directory.iterdir():
        if file.is_file():
            exif_date = get_exif_creation_date(file)
            if exif_date:
                new_name = format_filename(exif_date, file.name)
                if new_name:
                    # Check if the formatted date is already in the filename
                    date_object = datetime.strptime(exif_date, "%Y:%m:%d %H:%M:%S")
                    formatted_date = date_object.strftime("%Y-%m-%d_%H-%M-%S")
                    if formatted_date in file.name:
                        print(f"Skipping: {file} (already contains EXIF date)")
                        continue
                    
                    unique_name = ensure_unique_filename(directory, new_name)
                    new_path = directory / unique_name
                    if dry_run:
                        print(f"Would rename: {file} -> {new_path}")
                    else:
                        print(f"Renaming: {file} -> {new_path}")
                        file.rename(new_path)
    
    # Process subdirectories if recursive option is enabled
    if recursive:
        for subdir in directory.iterdir():
            if subdir.is_dir():
                print(f"Processing subdirectory: {subdir}")
                exif_rename_files(subdir, dry_run, recursive)

def main():
    parser = argparse.ArgumentParser(description="Rename files based on EXIF creation date.")
    parser.add_argument("directory", type=Path, help="Directory containing files to rename.")
    parser.add_argument("--dry-run", action="store_true", help="Show what would be done without making changes.")
    parser.add_argument("-r", "--recursive", action="store_true", help="Process subdirectories recursively.")
    args = parser.parse_args()

    if not args.directory.is_dir():
        print(f"Error: {args.directory} is not a valid directory.")
        return

    exif_rename_files(args.directory, args.dry_run, args.recursive)

if __name__ == "__main__":
    main()