Files
playground/date_images.py
2026-07-24 15:58:31 +02:00

80 lines
2.5 KiB
Python
Executable File

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