Skip to content

Instantly share code, notes, and snippets.

@Ryan-M-Smith
Created August 10, 2022 00:00
Show Gist options
  • Save Ryan-M-Smith/108f489ee110ba985cabe54d9b6877c8 to your computer and use it in GitHub Desktop.
Save Ryan-M-Smith/108f489ee110ba985cabe54d9b6877c8 to your computer and use it in GitHub Desktop.
A Python automation script that sorts photos into certain directories based on the date they were taken.

Photo Sorter

A Python automation script that sorts photos into certain directories based on the date they were taken.

How does it work?

The script uses image metadata, specifically the date the photo was taken, to determine what directories to create and where to move the photos.

All new directories are created using os.makedirs() with exist_ok=True, so if you already have a directory named 2022 in the target directory (the place where your unsorted photos are stored), nothing will be deleted or overwritten. However, directories may be added within that directory depending on the sorting method you choose.

Sorting options

When you run the script, it will prompt you to choose one of three sorting options:

How do you want to sort?
1: By year
2: By month and year
3: By day, month, and year
>>>

Here's what each option does:

  • By year: divide the photos up into directories for each year (2021, 2022, etc.)
  • By month and year: divide the photos up into directories for each year, and then into subdirectories for each month (2021/February, 2022/June, etc.)
  • By day, month, and year: divide the photos up into directories for each year, subdirectories for each month, and subdirectories within those subdiectories for each day (2021/February/18, 2022/June/5, etc.)

How to run

To run, just download the script and run it using Python. Note that the script does require exif to get file metadata. This can be installed through the requirements.txt file or directly using pip.

#
# Photo Sorter
# A Python automation script that sorts photos into certain directories based on the date they were taken.
# Copyright (c) 2022 Ryan Smith <[email protected]>
#
from genericpath import isfile
import exif, os, datetime, shutil
from pathlib import Path
def parse_datetime(to_parse: str) -> datetime.date:
"Parse an EXIF date string into a `datetime.date` object."
date = to_parse.split()[0]
date_result = datetime.date.fromisoformat(date.replace(":", "-"))
return date_result
def get_years(data: dict) -> set:
""" Get the years represented in the photo metadata. """
return {date.year for date in data.values()}
def get_months(data: dict, year: int):
""" Get the months represented in the photo metadata for a given year. """
return {date.month for date in data.values() if date.year == year}
def get_days(data: dict, month: int):
""" Get the days represented in the photo metadata for a given month and year. """
return {date.day for date in data.values() if date.month == month}
def create_sort_dirs(sort_type: int, images_path: Path, data: dict) -> list:
""" Create directories to sort photos into, and return the new directories created. """
if sort_type == 1:
new_dirs = list(map(lambda year: os.path.join(images_path, str(year)), get_years(data)))
elif sort_type == 2:
years = get_years(data)
new_dirs = list()
for year in years:
for month in get_months(data, year):
month_name = datetime.datetime(year=year, month=month, day=1).strftime("%B")
new_dirs.append(os.path.join(images_path, str(year), month_name))
elif sort_type == 3:
years = get_years(data)
new_dirs = list()
for year in years:
for month in get_months(data, year):
for day in get_days(data, month):
month_name = datetime.datetime(year=year, month=month, day=day).strftime("%B")
new_dirs.append(os.path.join(images_path, str(year), month_name, str(day)))
for directory in new_dirs:
os.makedirs(directory, exist_ok=True)
return new_dirs
def sort_photos(sort_type: int, images_path: Path, data: dict) -> None:
""" Sort photos based on their metadata. """
sort_dirs = create_sort_dirs(sort_type, images_path, data)
# Move the photos to the correct directory based on their metadata
if sort_type == 1:
for image, date in data.items():
new_dir = list(filter(lambda path: str(date.year) in (p := path.split(str(images_path)))[1], sort_dirs))[0]
shutil.move(os.path.join(images_path, image), os.path.join(images_path, new_dir))
elif sort_type == 2:
for image, date in data.items():
month_name = datetime.datetime(year=date.year, month=date.month, day=date.day).strftime("%B")
new_dir = list(filter(lambda path: all(d in (p := path.split(str(images_path)))[1] for d in (str(date.year), month_name)), sort_dirs))[0]
shutil.move(os.path.join(images_path, image), os.path.join(images_path, new_dir))
elif sort_type == 3:
for image, date in data.items():
month_name = datetime.datetime(year=date.year, month=date.month, day=date.day).strftime("%B")
new_dir = list(filter(lambda path: all(d in (p := path.split(str(images_path)))[1] for d in (str(date.year), month_name, str(date.day))), sort_dirs))[0]
shutil.move(os.path.join(images_path, image), os.path.join(images_path, new_dir))
def main() -> None:
""" The main function. """
# Get the photos folder from the user
images_path = Path(input("Enter the path to the folder you'd like to sort: ").strip())
# Determine how to sort
sort_type = int(input("How do you want to sort?\n1: By year\n2: By month and year\n3: By day, month, and year\n>>> "))
# Get all the images in the directory the user provided
images = [image for image in os.listdir(images_path) if isfile(os.path.join(images_path, image))]
dir_structure = dict()
# Read the image metadata
for image in images:
with open(os.path.join(images_path, image), "rb") as img_file:
img = exif.Image(img_file)
dir_structure[image] = parse_datetime(img.get("datetime_original"))
sort_photos(sort_type, images_path, dir_structure) # Sort the photos
if __name__ == "__main__":
import sys
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment