From 73e6318640c220aa54c3274d702bfa81760e5050 Mon Sep 17 00:00:00 2001 From: Alexander Wainwright Date: Sun, 20 Apr 2025 23:53:04 +1000 Subject: [PATCH] Update config and settings etc --- src/emulsion/main.py | 117 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 12 deletions(-) diff --git a/src/emulsion/main.py b/src/emulsion/main.py index 52e92d9..66735de 100644 --- a/src/emulsion/main.py +++ b/src/emulsion/main.py @@ -6,7 +6,6 @@ import datetime import toml from alive_progress import alive_bar - CONFIG_PATH = os.path.expanduser("~/.config/emulsion/config.toml") @@ -27,8 +26,15 @@ def parse_args(config): nargs='*', help='Image files to process (e.g. *.jpg *.tif).' ) + + # Configurable fields parser.add_argument('--author', default=None, help='Name of the photographer.') - parser.add_argument('--lab', default=None, help='Name of the lab to store in XMP:DevelopedBy.') + parser.add_argument('--lab', default=None, help='Name of the lab who developed the film.') + parser.add_argument('--make', default=None, help='Camera make (stored in EXIF:Make).') + parser.add_argument('--model', default=None, help='Camera model (stored in EXIF:Model).') + parser.add_argument('--film', default=None, help='Film stock (stored in EXIF:UserComment and XMP:Description).') + + # Time settings parser.add_argument('--base-date', default=None, help='Base date or date/time (e.g. 2023-04-10 or 2023-04-10 12:00:00).') parser.add_argument('--time-increment', type=int, default=None, help='Time increment in seconds between images.') parser.add_argument('--dry-run', action='store_true', help='Show what would be changed without modifying files.') @@ -36,10 +42,22 @@ def parse_args(config): args = parser.parse_args() + # Merge from config if args.author is None and 'author' in config: args.author = config['author'] + if args.lab is None and 'lab' in config: args.lab = config['lab'] + + if args.make is None and 'make' in config: + args.make = config['make'] + + if args.model is None and 'model' in config: + args.model = config['model'] + + if args.film is None and 'film' in config: + args.film = config['film'] + if args.time_increment is None and 'time_increment' in config: args.time_increment = config['time_increment'] @@ -47,6 +65,10 @@ def parse_args(config): def prompt_for_config(args): + """ + Prompt for config-only fields before creating a config file. + (Base date is ephemeral, not stored in config.) + """ try: if not args.author: args.author = input("Photographer's name (Author)? ").strip() @@ -55,6 +77,18 @@ def prompt_for_config(args): resp = input("Lab name (optional, enter to skip)? ").strip() args.lab = resp if resp else "" + if args.make is None: + resp = input("Camera make (optional, enter to skip)? ").strip() + args.make = resp if resp else "" + + if args.model is None: + resp = input("Camera model (optional, enter to skip)? ").strip() + args.model = resp if resp else "" + + if args.film is None: + resp = input("Film stock (optional, enter to skip)? ").strip() + args.film = resp if resp else "" + if not args.time_increment: dflt = "60" resp = input(f"Time increment in seconds [{dflt}]: ").strip() @@ -66,6 +100,10 @@ def prompt_for_config(args): def prompt_if_missing(args): + """ + Prompt for ephemeral fields like base_date if missing, + and also fill in other fields if user didn't supply them. + """ try: if not args.author: args.author = input("Photographer's name (Author)? ").strip() @@ -74,8 +112,20 @@ def prompt_if_missing(args): resp = input("Lab name (optional, enter to skip)? ").strip() args.lab = resp if resp else "" + if args.make is None: + resp = input("Camera make (optional, enter to skip)? ").strip() + args.make = resp if resp else "" + + if args.model is None: + resp = input("Camera model (optional, enter to skip)? ").strip() + args.model = resp if resp else "" + + if args.film is None: + resp = input("Film stock (optional, enter to skip)? ").strip() + args.film = resp if resp else "" + if not args.base_date: - dflt = "2023-01-01" + dflt = datetime.datetime.now().strftime("%Y-%m-%d") resp = input(f"Base date/time for first image [{dflt}]: ").strip() args.base_date = resp if resp else dflt @@ -97,28 +147,56 @@ def parse_user_date(dt_str): return datetime.datetime.strptime(dt_str, "%Y-%m-%d") -def build_exiftool_cmd(file_path, author, lab, timestamp, dry_run=False): +def build_exiftool_cmd(file_path, author, lab, make, model, film, timestamp, dry_run=False): + """ + Use standard EXIF fields: + - EXIF:Make (args.make) + - EXIF:Model (args.model) + - EXIF:UserComment (args.film) + Also store film in XMP:Description for better compatibility. + """ + current_year = datetime.datetime.now().year cmd = [ "exiftool", "-overwrite_original", + + # Photographer info f"-Artist={author}", f"-Creator={author}", f"-By-line={author}", f"-Credit={author}", - f"-CopyrightNotice={author}", - f"-Copyright={author}", + f"-CopyrightNotice=© {current_year} {author}", + f"-Copyright=© {current_year} {author}", + + # Timestamps + f"-DateTimeOriginal={timestamp}", + + # Clear out some lab fields "-WebStatement=", - "-CreatorWorkURL=", - f"-DateTimeOriginal={timestamp}" + "-CreatorWorkURL=" ] + + # Lab in XMP:DevelopedBy if lab: cmd.append(f"-XMP:DevelopedBy={lab}") + + # If user gave a make, store it in EXIF:Make + if make: + cmd.append(f"-Make={make}") + + # If user gave a model, store it in EXIF:Model + if model: + cmd.append(f"-Model={model}") + + # If user gave a film stock, store it in EXIF:UserComment AND XMP:Description + if film: + cmd.append(f"-UserComment={film}") + cmd.append(f"-XMP:Description={film}") + cmd.append(file_path) if dry_run: - # Return a string for printing only return " ".join(cmd) - return cmd @@ -130,9 +208,21 @@ def create_config_file(args): defaults = { "author": args.author or "Your Name", "lab": args.lab or "", + "make": args.make or "", + "model": args.model or "", + "film": args.film or "", "time_increment": args.time_increment if args.time_increment else 60 } + # Remove empty values so user is prompted next time if they left something blank + keys_to_remove = [] + for k, v in defaults.items(): + if isinstance(v, str) and not v.strip(): + keys_to_remove.append(k) + + for k in keys_to_remove: + del defaults[k] + os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) with open(CONFIG_PATH, "w", encoding="utf-8") as f: @@ -183,6 +273,9 @@ def main(): file_path=f, author=args.author, lab=args.lab, + make=args.make, + model=args.model, + film=args.film, timestamp=timestamp_str, dry_run=args.dry_run ) @@ -194,8 +287,8 @@ def main(): subprocess.run( cmd, check=True, - stdout=subprocess.DEVNULL, # Hide exiftool normal output - stderr=subprocess.DEVNULL # Hide exiftool error output + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL ) bar.text(f"Updated {f} => {timestamp_str}") except subprocess.CalledProcessError as e: