dotfiles/config/nix/scripts/picture_name_date

115 lines
3.7 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env nix-shell
#! nix-shell -i python3 --pure
#! nix-shell -p python3 python3Packages.coloredlogs python3Packages.exifread
2019-08-04 19:05:57 +02:00
2022-06-09 18:42:02 +02:00
import argparse
import datetime
import logging
2019-08-04 19:05:57 +02:00
import os
import re
import typing
2022-06-09 18:42:02 +02:00
import coloredlogs
import exifread
2019-08-04 19:05:57 +02:00
2022-06-09 18:42:02 +02:00
log = logging.getLogger(__name__)
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s", logger=log)
EXTENSION_PATTERN = re.compile(r"\.(JPE?G|DNG)", re.I)
2021-06-13 11:49:21 +02:00
COMMON_PATTERN = re.compile(r"(IMG|DSC[NF]?|100|P10|f|t)_?\d+", re.I)
2022-06-09 18:42:02 +02:00
EXIF_TAG_ID = 0x9003 # DateTimeOriginal
2021-06-13 11:49:21 +02:00
EXIF_DATE_FORMAT = "%Y:%m:%d %H:%M:%S"
2019-08-04 19:05:57 +02:00
2021-06-13 11:49:21 +02:00
def get_pictures(directory: str = ".", skip_renamed: bool = True) -> typing.Generator:
2019-08-04 19:05:57 +02:00
for root, _, files in os.walk(directory):
for filename in files:
filename_trunk, extension = os.path.splitext(filename)
if not re.match(EXTENSION_PATTERN, extension):
continue
if skip_renamed:
if not re.match(COMMON_PATTERN, filename_trunk):
continue
full_path = os.path.join(root, filename)
yield full_path
2022-06-09 18:42:02 +02:00
def main(args: argparse.Namespace) -> None:
log.warning("Counting files...")
kwargs = {"directory": args.dir, "skip_renamed": args.skip_renamed}
log.warning("Processing files...")
for full_path in get_pictures(**kwargs):
2022-06-09 18:42:02 +02:00
# Find date
2023-11-23 22:59:09 +01:00
with open(full_path, "rb") as fd:
2022-06-09 18:42:02 +02:00
exif_data = exifread.process_file(fd)
if not exif_data:
log.warning(f"{full_path} does not have EXIF data")
for ifd_tag in exif_data.values():
if ifd_tag.tag == EXIF_TAG_ID:
date_raw = ifd_tag.values
break
else:
log.warning(f"{full_path} does not have required EXIF tag")
continue
date = datetime.datetime.strptime(date_raw, EXIF_DATE_FORMAT)
# Determine new filename
ext = os.path.splitext(full_path)[1].lower()
2023-11-23 22:59:09 +01:00
if ext == ".jpeg":
ext = ".jpg"
new_name = date.isoformat().replace(":", "-").replace("T", "_") + args.suffix
2022-06-09 18:42:02 +02:00
# First substitution is to allow images being sent to a NTFS filesystem
# Second substitution is for esthetics
new_path = os.path.join(args.dir, f"{new_name}{ext}")
# TODO Allow keeping image in same folder
i = 0
while os.path.exists(new_path):
if full_path == new_path:
break
log.debug(f"{new_path} already exists, incrementing")
2022-06-09 18:42:02 +02:00
i += 1
new_path = os.path.join(args.dir, f"{new_name}_{i}{ext}")
# Rename file
if full_path == new_path:
log.debug(f"{full_path} already at required filename")
continue
log.info(f"{full_path} →\t{new_path}")
if os.path.exists(new_path):
raise FileExistsError(f"Won't overwrite {new_path}")
if not args.dry:
os.rename(full_path, new_path)
2019-08-04 19:05:57 +02:00
if __name__ == "__main__":
2022-06-09 18:42:02 +02:00
parser = argparse.ArgumentParser(description="Rename images based on their dates")
parser.add_argument(
"dir",
metavar="DIRECTORY",
type=str,
default=".",
nargs="?",
help="Directory containing the pictures",
)
parser.add_argument(
"-d",
"--dry",
action="store_true",
help="Do not actually rename, just show old and new path",
)
parser.add_argument(
"-r",
2022-06-09 18:42:02 +02:00
"--skip-renamed",
action="store_true",
help="Skip images whose filename doesn't match usual camera output filenames.",
)
parser.add_argument(
"-s",
"--suffix",
default="",
help="Text to add before the extension",
2022-06-09 18:42:02 +02:00
)
args = parser.parse_args()
main(args)