From 715a36cf6e0e0d4ac476755e134b849275413758 Mon Sep 17 00:00:00 2001 From: jenstandstad Date: Thu, 23 Jul 2026 14:45:28 +0200 Subject: [PATCH] Improvements --- gnommo/cli.py | 57 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/gnommo/cli.py b/gnommo/cli.py index 4e46f49..1189a03 100644 --- a/gnommo/cli.py +++ b/gnommo/cli.py @@ -1259,6 +1259,27 @@ def _generate_slides_json(directory: Path, verbose: bool) -> None: print(f" [{slide_id}]") +# Every user-tweakable video parameter with its default value. Written into +# each new videos.json entry (and backfilled onto existing ones) so nothing is +# an invisible optional. Auto-managed fields (output_file, duration, has_audio, +# is_shared, attribution) are intentionally omitted — they're set by the tool. +# end_on defaults to null = "use the marker-type default" (video → next_slide, +# narration → play to end); set it to "next_slide", "end", or "take" explicitly. +_VIDEO_DEFAULTS = { + "cutout": "square", # named zone from project.json cutouts + "layer": "above", # above | mid | below (relative to slides/narrator) + "filter": [], # preprocessing filter chain (empty = none) + "take": None, # max seconds to play (null = to next slide/end) + "skip": 0.0, # seconds to skip at the start + "zoom": 1.0, # scale within the cutout + "volume": 1.0, # audio volume multiplier + "use_audio_channels": "both", # both | left | right + "always_visible": False, # always on screen (like the talking head) + "pause_narration": 0.0, # seconds to freeze narration for a cutscene + "end_on": None, # null | next_slide | end | take +} + + def _import_videos(videos_dir: Path, config, verbose: bool) -> None: """Import video files into videos.json. @@ -1336,25 +1357,41 @@ def _import_videos(videos_dir: Path, config, verbose: bool) -> None: print(f" Skipping {video_id} (already exists)") continue - # Build the video entry - video_entry = { - "source_file": video_file.name, - "output_file": video_file.name, - "cutout": "square", - "filter": [], - } + # Build the video entry with every tweakable parameter spelled out at + # its default, so nothing is an invisible optional (that's what makes + # end_on/layer/take/… easy to miss). Auto-managed fields (duration, + # has_audio) are filled in later by the metadata probe. + video_entry = {"source_file": video_file.name, **_VIDEO_DEFAULTS} if verbose: print(f" Added: {video_id}") existing_videos[video_id] = video_entry added_count += 1 - if added_count > 0 or removed_count > 0: + # Backfill missing defaults onto existing (non-shared) entries so every knob + # is visible there too. setdefault only adds absent keys — user-set values + # and the ETL-projected cutout/layer are never overwritten. + backfilled = 0 + for vid_id, entry in existing_videos.items(): + if not isinstance(entry, dict) or entry.get("is_shared"): + continue + before = len(entry) + for k, v in _VIDEO_DEFAULTS.items(): + entry.setdefault(k, list(v) if isinstance(v, list) else v) + if len(entry) != before: + backfilled += 1 + + if added_count > 0 or removed_count > 0 or backfilled > 0: # Write updated videos.json with open(videos_json_path, "w", encoding="utf-8") as f: json.dump(existing_videos, f, indent=2) - if added_count > 0: - print(f" Updated {videos_json_path.name} (+{added_count} videos)") + bits = [] + if added_count: + bits.append(f"+{added_count} new") + if backfilled: + bits.append(f"{backfilled} backfilled with defaults") + if bits: + print(f" Updated {videos_json_path.name} ({', '.join(bits)})") else: print(f" No new videos to add")