First commit
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Pillow
|
||||
pytimeparse
|
||||
81
sorter.py
Normal file
81
sorter.py
Normal 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
14
start.py
Normal 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()
|
||||
Reference in New Issue
Block a user