From e7dc402d9e6013d3e21e263e63bdf9dcb856a0c3 Mon Sep 17 00:00:00 2001 From: jenstandstad Date: Sat, 25 Jul 2026 21:33:51 +0200 Subject: [PATCH] Adding fixes to build --- example/media/videos/videos.json | 2 +- gnommo/cli.py | 8 ++++---- gnommo/scaffold.py | 30 +++++++++++++++++++++++++++--- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/example/media/videos/videos.json b/example/media/videos/videos.json index 44f3270..a4a634d 100644 --- a/example/media/videos/videos.json +++ b/example/media/videos/videos.json @@ -25,7 +25,7 @@ "source_file": "Logo.mov", "is_shared": true, "cutout": "fullscreen", - "pause_narration": 17, + "pause_narration": 14, "take": 25, "skip": 0 }, diff --git a/gnommo/cli.py b/gnommo/cli.py index 35264a6..c8e15c7 100644 --- a/gnommo/cli.py +++ b/gnommo/cli.py @@ -4924,10 +4924,10 @@ def _cmd_render_impl( # Override path: the marker_timings the plan used were EFFECTIVE # narration (narration_time + adjustment). narration_time itself is # owned by events.json — restore it so the effective value doesn't leak - # into the stored narration_time. - _by_id = {e["id"]: e for e in _existing_events} - for e in _events: - _old = _by_id.get(e["id"]) + # into the stored narration_time. Pair by ordinal (not id) so a marker + # reused several times keeps each occurrence's own narration_time + # instead of collapsing onto the last occurrence's value. + for e, _old in _scaffold.pair_events_by_ordinal(_events, _existing_events): if _old is not None and _old.get("narration_time") is not None: e["narration_time"] = _old["narration_time"] else: diff --git a/gnommo/scaffold.py b/gnommo/scaffold.py index 811e934..2db1fe3 100644 --- a/gnommo/scaffold.py +++ b/gnommo/scaffold.py @@ -236,17 +236,41 @@ def reinterpolate_events(events: list[dict]) -> None: # ── merge: human tweaks survive re-align ────────────────────────────────────── +def pair_events_by_ordinal(fresh: list[dict], existing: Optional[list[dict]]): + """Yield (fresh_event, matching_existing_event_or_None) pairs. + + Both lists are in deterministic manuscript order, so the Nth occurrence of a + given id in `fresh` corresponds to the Nth occurrence in `existing`. Keying by + id alone would collapse legitimately-repeated markers (e.g. the same video + reused at two points in the script) onto a single old entry and cross- + contaminate their timings/tweaks. + """ + from collections import defaultdict + + buckets: dict = defaultdict(list) + for e in existing or []: + buckets[e.get("id")].append(e) + counters: dict = defaultdict(int) + for e in fresh: + mid = e.get("id") + idx = counters[mid] + counters[mid] += 1 + bucket = buckets.get(mid, []) + yield e, (bucket[idx] if idx < len(bucket) else None) + + def merge_events(fresh: list[dict], existing: Optional[list[dict]]) -> list[dict]: """Carry each id's `adjustment` (the human tweak) from the existing events.json onto the freshly-derived events. narration_time and mapping come from `fresh` (aligner-owned); the tweak is relative so it stays meaningful even when the alignment shifts. New ids are added; ids gone from the manuscript are dropped. + + Matching is by ordinal (Nth occurrence to Nth occurrence), not by id, so a + marker reused several times keeps each occurrence's own tweak. """ if not existing: return fresh - by_id = {e.get("id"): e for e in existing} - for e in fresh: - old = by_id.get(e["id"]) + for e, old in pair_events_by_ordinal(fresh, existing): if old is not None and old.get("adjustment"): e["adjustment"] = old["adjustment"] return fresh