|
# |
|
# 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()) |