#!/usr/bin/env python3 # Eryn Wells import argparse import datetime import os import os.path as osp import shutil import subprocess from PIL import Image from PIL.ExifTags import TAGS from typing import Optional from erynwells_me.metadata import slugify from erynwells_me.paths import photos_path def date_argument(value: str) -> datetime.datetime: parsed_date = datetime.datetime.fromisoformat(value) if not parsed_date.tzinfo: local_timezone = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo parsed_date = datetime.datetime.combine(parsed_date.date(), parsed_date.time(), local_timezone) return parsed_date def parse_args(argv, *a, **kw): parser = argparse.ArgumentParser(*a, **kw) parser.add_argument('-e', '--edit', action='store_true') parser.add_argument('-n', '--dry-run', action='store_true') parser.add_argument('-t', '--title') parser.add_argument('-s', '--slug') parser.add_argument('-d', '--date', type=date_argument) parser.add_argument('--dump-exif', action='store_true') parser.add_argument('photos', nargs='+') args = parser.parse_args(argv) return args def main(argv): args = parse_args(argv[1:], prog=argv[0]) post_date: Optional[datetime.datetime] = args.date for index, photo in enumerate(args.photos): print(f'image\t\t{photo}') try: image = Image.open(photo) except IOError: continue raw_exif = image._getexif() friendly_exif = {} if raw_exif: friendly_exif = {TAGS[k]: v for k, v in raw_exif.items() if k in TAGS} photo_date = None if not args.date: try: date_string = f'{friendly_exif["DateTime"]} {friendly_exif["OffsetTime"]}' photo_date = datetime.datetime.strptime(date_string, '%Y:%m:%d %H:%M:%S %z') except KeyError: photo_date = datetime.datetime.strptime(friendly_exif["DateTime"], '%Y:%m:%d %H:%M:%S') else: photo_date = args.date print(f'capture-time\t{photo_date.isoformat()}') iso_rating = friendly_exif.get('ISOSpeedRatings') if iso_rating: print(f'iso\t\t{iso_rating}') focal_length_35mm = friendly_exif.get('FocalLengthIn35mmFilm') focal_length = friendly_exif.get('FocalLength') if focal_length or focal_length_35mm: print(f'focal-length\t{focal_length} {focal_length_35mm}') fstop = friendly_exif.get('FNumber') if fstop: print(f'f-stop\t\t{fstop}') exposure_time = friendly_exif.get('ExposureTime') if exposure_time: print(f'exposure-time\t{exposure_time}') if not post_date or photo_date < post_date: post_date = photo_date if index < len(args.photos) - 1: print() if not post_date: post_date = datetime.datetime.now() year = post_date.year month = post_date.month if args.slug: name = args.slug elif args.title: name = slugify(args.title) else: photo = args.photos[0] name = osp.splitext(osp.basename(photo))[0] post_path_year = os.path.join(photos_path(), f'{year:04}', name) post_path_year_month = os.path.join(photos_path(), f'{year:04}', f'{month:02}', name) if os.path.exists(post_path_year): if os.path.exists(post_path_year_month): print("Couldn't find path for photo post. Both of the following paths exist.", file=sys.stderr) print(f' {post_path_year}', file=sys.stderr) print(f' {post_path_year_month}', file=sys.stderr) return -1 else: post_path = post_path_year_month else: post_path = post_path_year try: hugo_command = ['hugo', 'new', '--clock', post_date.isoformat(), post_path] if not args.dry_run: result = subprocess.run(hugo_command) result.check_returncode() else: print(' '.join(hugo_command), file=sys.stderr) except subprocess.CalledProcessError: print(f'Failed to create new Hugo post', file=sys.stderr) return -1 index_file_path = os.path.join(post_path, 'index.md') if args.title and not args.dry_run: # The hugo command can't set a title for a post so I need to do it myself. with open(index_file_path) as index_file: index_file_contents = index_file.readlines() for i in range(len(index_file_contents)): line = index_file_contents[i] if not line.startswith('title:'): continue updated_line = f'title: "{args.title}"\n' index_file_contents[i] = updated_line with open(index_file_path, 'w') as index_file: index_file.writelines(index_file_contents) if not args.dry_run and args.edit: editor = os.environ.get('EDITOR', 'vi') subprocess.run([editor, index_file_path], shell=True) for photo in args.photos: print(f'Copy {photo} -> {post_path}') try: if not args.dry_run: shutil.copy(photo, post_path) except IOError: print(f'Failed to copy {photo}', file=sys.stderr) return -2 if __name__ == '__main__': import sys result = main(sys.argv) sys.exit(0 if not result else result)