First commit

This commit is contained in:
2024-06-28 15:48:09 +02:00
parent 3d3a3d3ac2
commit 8d319d8cc0
4 changed files with 99 additions and 0 deletions

2
.gitignore vendored
View File

@@ -160,3 +160,5 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
mapping.json
.vscode

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
Pillow
pytimeparse

81
sorter.py Normal file
View File

@@ -0,0 +1,81 @@
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)

14
start.py Normal file
View File

@@ -0,0 +1,14 @@
from argparse import ArgumentParser
from sorter import ImageSorter
if __name__ == '__main__':
parser = ArgumentParser(prog="image-sorter", description="A tool for renaming images to the date from the exif data")
parser.add_argument("-i", "--input_dir", required=True, type=str)
parser.add_argument("-o", "--output_dir", required=True, type=str)
parser.add_argument("-c", "--config_mapping_file", required=True, type=str)
parser.add_argument("-s", "--suffix", type=str)
parser.add_argument("-v", "--verbose", action="store_true")
args = parser.parse_args()
sorter = ImageSorter(args)
sorter.sort_files()