From 7ce356d299b80c42856a8e0b733121d7cf7a4995 Mon Sep 17 00:00:00 2001 From: ajp_anton Date: Thu, 4 Jun 2026 06:09:49 +0000 Subject: [PATCH] Normalize timestamp group folder names --- grouping.py | 16 ++++++++++++++-- photo_metadata.py | 34 +++++++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/grouping.py b/grouping.py index 0c0fa1c..030a033 100644 --- a/grouping.py +++ b/grouping.py @@ -12,11 +12,23 @@ Files are sorted by their current path and moved into one folder named from __future__ import annotations import os +import re import shutil import sys from pathlib import Path +ORDERED_TIMESTAMP_STEM_RE = re.compile(r"^(?P\d{8}_\d{6})-[1-9]\d*$") + + +def group_stem(path: Path) -> str: + stem = path.stem + match = ORDERED_TIMESTAMP_STEM_RE.match(stem) + if match: + return match.group("timestamp") + return stem + + def unique_path(path: Path) -> Path: if not path.exists(): return path @@ -87,8 +99,8 @@ def group_items(items: list[Path]) -> Path | None: return None sorted_items = sorted(items, key=lambda item: item.name.lower()) - first = sorted_items[0].stem - last = sorted_items[-1].stem + first = group_stem(sorted_items[0]) + last = group_stem(sorted_items[-1]) target_dir = unique_path(working_dir_for_items(sorted_items) / f"{first}-{last}") target_dir.mkdir(parents=True) diff --git a/photo_metadata.py b/photo_metadata.py index df69859..974c5ba 100644 --- a/photo_metadata.py +++ b/photo_metadata.py @@ -84,6 +84,7 @@ TIME_ONLY_RE = re.compile( TZ_RE = re.compile(r"^(?P[+-])(?P\d{1,2})(?::?(?P\d{2}))?$") OFFSET_RE = re.compile(r"^([+-])(?:(\d+):)?(\d{1,2})(?::(\d{2}))?$") TOKEN_SHIFT_RE = re.compile(r"(\d+|[a-zA-Z]+)") +ORDERED_TIMESTAMP_STEM_RE = re.compile(r"^(?P\d{8}_\d{6})-[1-9]\d*$") @dataclass @@ -917,6 +918,14 @@ def normalized_extension(path: Path) -> str: return path.suffix.lower() +def group_stem_from_name(name: str) -> str: + stem = Path(name).stem + match = ORDERED_TIMESTAMP_STEM_RE.match(stem) + if match: + return match.group("timestamp") + return stem + + def plan_names(records: list[MediaRecord], choices: UserChoices) -> None: if not choices.rename_files: for record in records: @@ -981,12 +990,27 @@ def assign_groups(records: list[MediaRecord], groups: list[list[MediaRecord]]) - record.group_id = index -def assign_group_names(groups: list[list[MediaRecord]]) -> None: +def unique_group_name(base_name: str, working_dir: Path, used_names: set[str]) -> str: + if base_name not in used_names and not (working_dir / base_name).exists(): + used_names.add(base_name) + return base_name + + for index in range(1, 10000): + candidate = f"{base_name}-{index}" + if candidate not in used_names and not (working_dir / candidate).exists(): + used_names.add(candidate) + return candidate + + raise RuntimeError(f"Could not find an available group directory name for {base_name}") + + +def assign_group_names(groups: list[list[MediaRecord]], working_dir: Path) -> None: + used_names: set[str] = set() for group in groups: ordered = sorted(group, key=photo_sort_key) - first = Path(ordered[0].final_name or ordered[0].path.name).stem - last = Path(ordered[-1].final_name or ordered[-1].path.name).stem - group_name = f"{first}-{last}" + first = group_stem_from_name(ordered[0].final_name or ordered[0].path.name) + last = group_stem_from_name(ordered[-1].final_name or ordered[-1].path.name) + group_name = unique_group_name(f"{first}-{last}", working_dir, used_names) for record in ordered: record.group_name = group_name @@ -1165,7 +1189,7 @@ def prepare_plan(records: list[MediaRecord], choices: UserChoices) -> list[list[ plan_names(records, choices) if groups: - assign_group_names(groups) + assign_group_names(groups, choices.working_dir) plan_targets(records, choices) return groups