#!/usr/bin/env -S uv run # /// script # dependencies = ["piexif"] # /// import argparse from concurrent.futures import ThreadPoolExecutor import logging import re import subprocess from datetime import datetime from pathlib import Path import piexif logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Set creation time to date in filename and mod time to now for image files.", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( "paths", nargs="+", type=Path, help="Paths to image files or folders containing images.", ) return parser.parse_args() def update_image_times(path: Path) -> None: match = re.search(r"\D(\d{4}-\d{2}-\d{2})\D?", path.name) if not match: logging.debug(f"No date found in {path.name}") return date_str = match.group(1) try: dt = datetime.fromisoformat(date_str) formatted_date = dt.strftime("%m/%d/%Y %H:%M:%S") today = datetime.now().strftime("%m/%d/%Y %H:%M:%S") subprocess.run(["SetFile", "-d", formatted_date, str(path)], check=True) subprocess.run(["SetFile", "-m", today, str(path)], check=True) # Update EXIF DateTimeOriginal exif_dict = piexif.load(str(path)) exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal] = dt.strftime("%Y:%m:%d %H:%M:%S").encode() exif_bytes = piexif.dump(exif_dict) piexif.insert(exif_bytes, str(path)) logging.info(f"Updated times and EXIF for {path}") except ValueError as e: logging.error(f"Invalid date {date_str} in {path.name}: {e}") except subprocess.CalledProcessError as e: logging.error(f"Failed to update {path}: {e}") except Exception as e: logging.error(f"Failed to update EXIF for {path}: {e}") def main() -> None: args = parse_args() files = [] for it in args.paths: if it.is_dir(): files.extend(it.glob("*.jpg")) files.extend(it.glob("*.jpeg")) elif it.is_file() and it.suffix.lower() in {".jpg", ".jpeg"}: files.append(it) else: logging.warning(f"Path {it} is neither an image nor a directory, skipping.") pool = ThreadPoolExecutor(max_workers=5) for path in files: pool.submit(update_image_times, path) pool.shutdown(wait=True) if __name__ == "__main__": main()