Skip to content

Instantly share code, notes, and snippets.

@ruofeidu
Created December 30, 2025 00:05
Show Gist options
  • Select an option

  • Save ruofeidu/0dfa7ee0da9720055dc7af284ae8ec25 to your computer and use it in GitHub Desktop.

Select an option

Save ruofeidu/0dfa7ee0da9720055dc7af284ae8ec25 to your computer and use it in GitHub Desktop.
rename photos by EXIF / file creation date
import os
import re
from datetime import datetime
from pathlib import Path
from PIL import Image
# Tag IDs for EXIF data
# 36867: DateTimeOriginal (Shutter click)
# 306: DateTime (Last metadata change)
EXIF_DATE_TAGS = (36867, 306)
def get_best_date(entry: os.DirEntry) -> str:
"""Retrieves the most accurate date available for a file.
Checks EXIF metadata first, then falls back to the file system creation time.
Returns a string in YYYYMMDD format.
"""
# Attempts to read EXIF data
try:
with Image.open(entry.path) as img:
exif = img.getexif()
if exif:
for tag in EXIF_DATE_TAGS:
val = exif.get(tag)
if val:
# Cleans EXIF format 'YYYY:MM:DD HH:MM:SS'
date_str = str(val)[:10].replace(':', '')
if len(date_str) == 8 and date_str.isdigit():
return date_str
except Exception:
# Fail silently to allow fallback to file system stats
pass
# Falls back to file system creation time (st_ctime)
# Uses the DirEntry stat to avoid an extra syscall
timestamp = entry.stat().st_ctime
return datetime.fromtimestamp(timestamp).strftime('%Y%m%d')
def rename_files():
"""Scans the current directory and renames images with a date prefix."""
valid_extensions = {'.jpg', '.jpeg', '.png'}
# Matches 8 digits at the start of a string
date_prefix_regex = re.compile(r"^\d{8}_")
cwd = Path('.')
rename_count = 0
# Uses scandir for better performance on large directories
with os.scandir(cwd) as entries:
for entry in entries:
if not entry.is_file():
continue
path = Path(entry.name)
if path.suffix.lower() not in valid_extensions:
continue
# Skips files that already have the YYYYMMDD_ format
if date_prefix_regex.match(entry.name):
continue
date_prefix = get_best_date(entry)
new_name = f"{date_prefix}_{entry.name}"
target_path = cwd / new_name
# Handles naming collisions (e.g., 20251229_photo.jpg already exists)
counter = 1
while target_path.exists():
stem = Path(new_name).stem
ext = Path(new_name).suffix
target_path = cwd / f"{stem}_{counter}{ext}"
counter += 1
try:
os.rename(entry.path, target_path)
print(f"Renamed: {entry.name} -> {target_path.name}")
rename_count += 1
except OSError as e:
print(f"Failed to rename {entry.name}: {e}")
print(f"\nProcessing complete. {rename_count} files updated.")
if __name__ == "__main__":
rename_files()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment