from datetime import datetime, timedelta from os import listdir from os.path import exists, isfile, join, splitext from PIL import Image, ExifTags from pytimeparse.timeparse import timeparse from shutil import copy import json class ImageSorter: def __init__(self, args): self.args = args self.mapping = self.read_mapping_config() if args.config_mapping_file else {} self.verbose = self.args.verbose def sort_files(self): self.print_log(f"Reading files from {self.args.input_dir}") files = [f for f in listdir(self.args.input_dir) if isfile(join(self.args.input_dir, f))] for file in files: filepath = join(self.args.input_dir, file) self.print_log(f"Start handling file {filepath}") if splitext(file)[1].lower() != ".jpg": continue date_taken = self.get_date_taken(filepath) if date_taken: self.write_file(filepath, date_taken) def get_date_taken(self, file): try: with Image.open(file) as img: exif = {ExifTags.TAGS[k]: v for k, v in img._getexif().items() if k in ExifTags.TAGS} camera_model = exif.get("Model") self.print_log(f"Camera model is {camera_model}") date_str = exif.get("DateTime") if not date_str: self.print_log("No DateTime EXIF data found") return None date_taken = datetime.strptime(date_str, "%Y:%m:%d %H:%M:%S") self.print_log(f"Original date taken is {date_taken.strftime('%Y-%m-%d %H:%M:%S')}") return date_taken + self.get_camera_model_time_delta(camera_model) except Exception as e: self.print_log(f"Error reading EXIF data from {file}: {e}") return None def read_mapping_config(self): try: with open(self.args.config_mapping_file, "r") as f: return json.load(f) except Exception as e: self.print_log(f"Error reading config mapping file: {e}") return {} def get_camera_model_time_delta(self, camera_model): time_str = self.mapping.get(camera_model, "0s") seconds = timeparse(time_str) self.print_log(f"Found timedelta {seconds} seconds for camera model {camera_model}") return timedelta(seconds=seconds) def write_file(self, filepath, date_taken): base_name = f"Picture_{date_taken.strftime('%Y%m%d_%H%M%S')}" new_file_name = f"{base_name}_{self.args.suffix}" if self.args.suffix else base_name count = 1 final_file_name = new_file_name while exists(join(self.args.output_dir, f"{final_file_name}.jpg")): final_file_name = f"{new_file_name} ({count})" count += 1 self.print_log(f"File already exists, trying {final_file_name}.jpg instead") output_file = join(self.args.output_dir, f"{final_file_name}.jpg") copy(filepath, output_file) self.print_log(f"Copied {filepath} to {output_file}") self.print_log() def print_log(self, msg=""): if self.verbose: print(msg)