Add sidecar functionality

This commit is contained in:
Alexander Wainwright
2025-12-20 23:44:05 +10:00
parent 0c7f244a99
commit c91a151a2b

View File

@@ -94,10 +94,16 @@ def prompt_if_missing(args):
# Same prompts as config, plus base_date
prompt_for_config(args)
try:
if not args.base_date:
while not args.base_date:
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
# Validate immediately so we don't crash later
try:
parse_user_date(args.base_date)
except ValueError:
print("Invalid format. Please use 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'.")
args.base_date = None
except KeyboardInterrupt:
print("\nInterrupted by user. Exiting.")
sys.exit(1)
@@ -111,10 +117,10 @@ def parse_user_date(dt_str):
return datetime.datetime.strptime(dt_str, "%Y-%m-%d")
def build_exiftool_cmd(file_path, author, lab, make, model, film, timestamp):
def build_exiftool_cmd(file_path, author, lab, make, model, film, timestamp, sidecar_source=None):
"""
Builds the command list. Does NOT handle dry_run formatting.
Always returns a list for subprocess safety.
Builds the command list.
sidecar_source: If set, we are creating/updating a sidecar FROM this source image.
"""
current_year = datetime.datetime.now().year
cmd = [
@@ -142,7 +148,15 @@ def build_exiftool_cmd(file_path, author, lab, make, model, film, timestamp):
cmd.append(f"-UserComment={film}")
cmd.append(f"-XMP:Description={film}")
cmd.append(file_path)
if sidecar_source:
# Advanced ExifTool usage: read source, write to specific sidecar file
# This ensures it works even if the sidecar doesn't exist yet
cmd.append(f"-srcfile")
cmd.append(file_path)
cmd.append(sidecar_source)
else:
cmd.append(file_path)
return cmd
@@ -166,6 +180,8 @@ def run_exiftool(cmd, dry_run=False):
return True, "Updated"
except subprocess.CalledProcessError as e:
return False, f"Error: {e}"
except FileNotFoundError:
return False, "Error: 'exiftool' not found. Please install it."
def create_config_file(args):
@@ -201,9 +217,6 @@ def main():
if args.workers is None:
args.workers = os.cpu_count() or 1
# LOGIC SIMPLIFICATION 1:
# Instead of writing a separate sequential loop for dry-runs,
# we just force workers=1 here. The executor handles the rest.
if args.dry_run:
print("Dry run detected: Forcing sequential processing.")
args.workers = 1
@@ -243,8 +256,12 @@ def main():
ts_dt = base_dt + datetime.timedelta(seconds=i * time_increment)
timestamp_str = ts_dt.strftime("%Y:%m:%d %H:%M:%S")
sidecar_source = None
if args.sidecar:
target_file_path = f"{f}.xmp"
# If sidecar doesn't exist, we must tell ExifTool to create it from the source image
if not os.path.exists(target_file_path):
sidecar_source = f
else:
target_file_path = f
@@ -255,14 +272,14 @@ def main():
make=args.make,
model=args.model,
film=args.film,
timestamp=timestamp_str
timestamp=timestamp_str,
sidecar_source=sidecar_source
)
tasks.append((cmd, f, timestamp_str))
with ThreadPoolExecutor(max_workers=args.workers) as executor:
# Submit all tasks
# Note: We pass args.dry_run into the function here
futures = {
executor.submit(run_exiftool, cmd, args.dry_run): (f, ts)
for cmd, f, ts in tasks
@@ -274,8 +291,6 @@ def main():
success, msg = future.result()
if args.dry_run:
# For dry run, we PRINT the command so the user can copy it
# bar.text() is transient, print() persists in terminal
print(msg)
elif not success:
bar.text(f"Failed {original_file}: {msg}")