diff --git a/GlitchTheme.kth b/GlitchTheme.kth new file mode 100644 index 0000000..9755883 Binary files /dev/null and b/GlitchTheme.kth differ diff --git a/backup.json b/backup.json index 7d633a3..c92d952 100644 --- a/backup.json +++ b/backup.json @@ -4,18 +4,18 @@ "mount_path": "/Volumes/LaCie Jens", "backups": { "small": { - "last_attempt": "2026-02-26T10:09:08Z", + "last_attempt": "2026-03-26T09:48:05Z", "last_status": "success", - "last_completed": "2026-02-26T10:09:14Z" + "last_completed": "2026-03-26T09:48:13Z" }, "big": { "last_attempt": "2026-02-27T12:17:30Z", "last_status": "failed" }, "all": { - "last_attempt": "2026-02-27T12:46:26Z", + "last_attempt": "2026-03-26T10:32:56Z", "last_status": "success", - "last_completed": "2026-02-27T13:41:50Z" + "last_completed": "2026-03-26T10:36:24Z" } } } diff --git a/gnommo/cli.py b/gnommo/cli.py index 99cfd15..0eb4d08 100644 --- a/gnommo/cli.py +++ b/gnommo/cli.py @@ -2066,7 +2066,18 @@ def _print_render_plan_details(plan, marker_timings, slides: dict) -> None: print(f' {marker_id:6} {time_str} "{context}"') else: unaligned_count += 1 - print(f' {marker_id:6} ??:??.?? NOT ALIGNED - "{context}"') + # Check if this is a slide that was interpolated into the plan + if marker_id in slides: + interp_event = next( + (e for e in plan.slide_events if e.slide_id == marker_id), None + ) + if interp_event: + interp_str = _format_time(interp_event.start_time) + print(f' {marker_id:6} ~{interp_str} INTERPOLATED - "{context}"') + else: + print(f' {marker_id:6} ??:??.?? NOT ALIGNED - "{context}"') + else: + print(f' {marker_id:6} ??:??.?? NOT ALIGNED - "{context}"') print(" " + "-" * 76) @@ -2302,9 +2313,9 @@ def cmd_render( if verbose: print(f" Using combined narration: {combined_path.name}") else: - # Check if narration.json exists (new workflow) - if so, require narration_combined + # Check if narration.json exists with segments (new workflow) - if so, require narration_combined narration_json = project_path / "media" / "narration" / "narration.json" - if narration_json.exists(): + if narration_json.exists() and _read_json(narration_json): print( f"Error: narration_combined not found in videos.json", file=sys.stderr ) diff --git a/gnommo/transformer.py b/gnommo/transformer.py index d831d74..218a7d9 100644 --- a/gnommo/transformer.py +++ b/gnommo/transformer.py @@ -760,27 +760,57 @@ def _extract_slide_events( Each slide starts at its own marker timestamp and ends when the next slide's marker appears. Before the first slide, no slide is shown. + + Slides that could not be aligned (timestamp < 0) have their position + interpolated evenly between the surrounding aligned slides rather than + being excluded. """ range_start, range_end = time_range if time_range else (0.0, float("inf")) - # Get slide markers in manuscript order (not sorted by timestamp!) - # The order in marker_timings reflects manuscript order - slide_markers: list[tuple[float, str]] = [] + # Get ALL slide markers in manuscript order (aligned and unaligned) + all_slide_markers: list[tuple[float, str]] = [] for timing in marker_timings: - if timing.marker_id in slides and timing.timestamp >= 0: - slide_markers.append((timing.timestamp, timing.marker_id)) + if timing.marker_id in slides: + all_slide_markers.append((timing.timestamp, timing.marker_id)) - if not slide_markers: + if not all_slide_markers: return [] + # Interpolate timestamps for unaligned slides (timestamp < 0). + # For each run of consecutive unaligned slides, spread them evenly between + # the nearest aligned slides before and after in manuscript order. + n = len(all_slide_markers) + resolved: list[tuple[float, str]] = list(all_slide_markers) + + i = 0 + while i < n: + if resolved[i][0] < 0: + run_start = i + while i < n and resolved[i][0] < 0: + i += 1 + run_end = i # exclusive + + prev_time = resolved[run_start - 1][0] if run_start > 0 else 0.0 + next_time = resolved[run_end][0] if run_end < n else total_duration + + count = run_end - run_start + for j, idx in enumerate(range(run_start, run_end)): + frac = (j + 1) / (count + 1) + resolved[idx] = ( + prev_time + (next_time - prev_time) * frac, + resolved[idx][1], + ) + else: + i += 1 + events: list[SlideEvent] = [] - for i, (marker_time, marker_id) in enumerate(slide_markers): + for i, (marker_time, marker_id) in enumerate(resolved): # Each slide starts at its own marker time start_time = marker_time # End time is when the NEXT slide's marker appears, or end of video - if i + 1 < len(slide_markers): - end_time = slide_markers[i + 1][0] + if i + 1 < len(resolved): + end_time = resolved[i + 1][0] else: end_time = total_duration