diff --git a/GlitchTrailer/videos.json b/GlitchTrailer/videos.json index 487a564..fa0aecf 100644 --- a/GlitchTrailer/videos.json +++ b/GlitchTrailer/videos.json @@ -1503,9 +1503,12 @@ }, "Logo": { "source_file": "Logo.mov", - "duration": 14.0, + "duration": 18.0, "has_audio": true, - "is_shared": true + "is_shared": true, + "cutout": "fullscreen", + "layer": "above", + "pause_narration": 14.0 }, "MontageZoom": { "source_file": "MontageZoom.mp4", diff --git a/INT5000/media/videos/videos.json b/INT5000/media/videos/videos.json index 487a564..dce89a1 100644 --- a/INT5000/media/videos/videos.json +++ b/INT5000/media/videos/videos.json @@ -1501,12 +1501,13 @@ "has_audio": false, "is_shared": true }, - "Logo": { - "source_file": "Logo.mov", - "duration": 14.0, + "source_file": "Logo.mov", + "duration": 18.0, "has_audio": true, - "is_shared": true - }, + "is_shared": true, + "cutout": "fullscreen", + "layer": "above", + "pause_narration": 14.0 "MontageZoom": { "source_file": "MontageZoom.mp4", "duration": 17.0, diff --git a/gnommo/cli.py b/gnommo/cli.py index 3104b3f..d2c89be 100644 --- a/gnommo/cli.py +++ b/gnommo/cli.py @@ -3744,6 +3744,7 @@ def _print_render_plan_details(plan, marker_timings, slides: dict, events=None) so markers the raw alignment marks unaligned still show their real position. """ from .models import CAMERA_PRESETS + from .transformer import _SHORTHAND_PREFIXES events_by_id = {e["id"]: e for e in (events or [])} @@ -3817,44 +3818,13 @@ def _print_render_plan_details(plan, marker_timings, slides: dict, events=None) else: aligned_count += 1 print(f' {marker_id:6} {_st}{conf_str} "{context}"') - elif any( - marker_id.startswith(p) - for p in ( - "video:", - "vft:", - "vfb:", - "vf2t:", - "vf2b:", - "vst:", - "vsb:", - "vftp:", - "vfbp:", - "vf2tp:", - "vf2bp:", - "vstp:", - "vsbp:", + elif ( + _vpfx := next( + (p for p in _SHORTHAND_PREFIXES if marker_id.startswith(p)), None ) - ): + ) or marker_id.startswith("video:"): aligned_count += 1 - pfx_len = next( - len(p) - for p in ( - "video:", - "vft:", - "vfb:", - "vf2t:", - "vf2b:", - "vst:", - "vsb:", - "vftp:", - "vfbp:", - "vf2tp:", - "vf2bp:", - "vstp:", - "vsbp:", - ) - if marker_id.startswith(p) - ) + pfx_len = len(_vpfx) if _vpfx else len("video:") # Handles are stored lowercased in videos.json (and the plan's video # events), so lowercase before the lookup — otherwise a camel-cased # marker like vst:KnightRotating misses and shows '?'. @@ -3863,33 +3833,42 @@ def _print_render_plan_details(plan, marker_timings, slides: dict, events=None) event = video_events_by_id.get(video_id) if event: cutout_name = event.cutout_name - end_on = event.video_source.end_on or "next_slide" + # The resolved per-occurrence policy (incl. inline overrides), not + # just the videos.json default. + end_on = event.end_on or event.video_source.end_on or "next_slide" layer_tag = f" [{event.layer}]" + # FINAL (output-time) window from the plan — pause-shifted, same + # base as the slides above. Showing start→end makes end_on visible: + # the clip should stop at the next slide/video's time. (Previously + # this printed the RAW pre-pause marker time, which looked like an + # 18s drift against the pause-shifted slides.) + t_str = ( + f"{_format_time(event.start_time)}→{_format_time(event.end_time)}" + ) else: # No resolved event — but the shorthand prefix itself fixes the # cutout and layer (vst: = square/above), so never show '?'. - from .transformer import _SHORTHAND_PREFIXES - - _pfx = next( - (p for p in _SHORTHAND_PREFIXES if marker_id.startswith(p)), None - ) - if _pfx: - cutout_name, _layer = _SHORTHAND_PREFIXES[_pfx] + if _vpfx: + cutout_name, _layer = _SHORTHAND_PREFIXES[_vpfx] layer_tag = f" [{_layer}]" else: cutout_name = "?" layer_tag = "" end_on = "next_slide" + t_str = _format_time(timing.timestamp) # no event → raw fallback cache_ind = " 📁" if video_id in plan.cached_files else "" print( - f" {marker_id:20} {time_str} in '{cutout_name}' [{end_on}]{layer_tag}{cache_ind}" + f" {marker_id:28} {t_str} in '{cutout_name}' [{end_on}]{layer_tag}{cache_ind}" ) elif marker_id.startswith("narration:"): aligned_count += 1 - video_id = marker_id[10:] + video_id = marker_id[10:].lower() cache_ind = " 📁" if video_id in plan.cached_files else "" - print(f" {marker_id:20} {time_str} (continuous){cache_ind}") + # Final (output-time) start from the plan, consistent with slides/videos. + _nev = video_events_by_id.get(video_id) + _nt = _format_time(_nev.start_time) if _nev else time_str + print(f" {marker_id:20} {_nt} (continuous){cache_ind}") elif marker_id in CAMERA_PRESETS: aligned_count += 1 print(f" {time_str} [{marker_id}]") diff --git a/gnommo/models.py b/gnommo/models.py index d838042..a61ba13 100644 --- a/gnommo/models.py +++ b/gnommo/models.py @@ -450,6 +450,10 @@ class VideoEvent: cutout: "CutoutDefinition" cutout_name: str = "" # resolved cutout name (e.g. "fullscreen"), for display layer: str = "above" # "above" = on top of slides; "below" = behind slides + # Resolved per-occurrence end policy (next_slide/next_video/end/loop/…). Kept so + # the render-plan listing can report the ACTUAL end rule (incl. inline overrides), + # not just the videos.json default. Purely informational; end_time is authoritative. + end_on: str = "" # Resolved per-occurrence CSS-like cutout placement (see VideoSource). object_fit: str = "cover" object_position: str = "center" diff --git a/gnommo/transformer.py b/gnommo/transformer.py index b1dad9c..e20f093 100644 --- a/gnommo/transformer.py +++ b/gnommo/transformer.py @@ -1512,6 +1512,7 @@ def _extract_video_events( cutout=cutout, cutout_name=cutout_name, layer=layer, + end_on=end_on or "", volume=volume, object_fit=object_fit, object_position=object_position,