From 93fa820275a01fc481497d0093346edb8b0538e9 Mon Sep 17 00:00:00 2001 From: jenstandstad Date: Mon, 12 Jan 2026 15:45:19 +0100 Subject: [PATCH] Refactor CLI and add preprocessing pipeline - New CLI structure: -p project, -a action (required flags) - Add -i import, -f force, -v verbose, --dry-run, --no-cache options - Add preprocessor.py with chroma key filter (ProRes 4444 output) - Support background images from shared_assets folder - Support video metadata JSON files (talkinghead.json) - Add validation for preprocessed output before render - Update gnommo.sh with import command and new CLI interface - Fix Python 3.9 compatibility (Optional[] instead of | None) Co-Authored-By: Claude Opus 4.5 --- gnommo.sh | 73 +++--- gnommo/cli.py | 566 ++++++++++++++++++++++++----------------- gnommo/errors.py | 17 ++ gnommo/models.py | 37 ++- gnommo/parser.py | 75 +++++- gnommo/preprocessor.py | 195 ++++++++++++++ gnommo/renderer.py | 37 ++- gnommo/transformer.py | 26 +- gnommo/validator.py | 24 +- 9 files changed, 763 insertions(+), 287 deletions(-) create mode 100644 gnommo/preprocessor.py diff --git a/gnommo.sh b/gnommo.sh index fb647b3..831a8e4 100755 --- a/gnommo.sh +++ b/gnommo.sh @@ -4,7 +4,9 @@ # # Usage: # gnommo.sh -p Render project +# gnommo.sh -p import Generate slides.json from image files # gnommo.sh -p validate Validate only +# gnommo.sh -p preprocess Apply video preprocessing filters # gnommo.sh -p transcribe Transcribe video # gnommo.sh -p align Align markers to transcript # gnommo.sh -p all Full pipeline: transcribe → align → render @@ -26,13 +28,16 @@ fi PROJECT="" COMMAND="render" VERBOSE="" +FORCE="" usage() { echo "Usage: gnommo.sh -p [command] [options]" echo "" echo "Commands:" echo " render Render video (default)" + echo " import Generate slides.json from image files" echo " validate Validate project only" + echo " preprocess Apply video preprocessing filters (chroma key, etc.)" echo " transcribe Transcribe video audio" echo " align Align manuscript to transcript" echo " all Full pipeline: transcribe → align → render" @@ -40,10 +45,13 @@ usage() { echo "Options:" echo " -p Project directory (required)" echo " -v Verbose output" + echo " -f Force overwrite existing files" echo " -h Show this help" echo "" echo "Examples:" echo " gnommo.sh -p video1 # Render video1 project" + echo " gnommo.sh -p video1 import # Generate slides.json" + echo " gnommo.sh -p video1 import -f # Force overwrite slides.json" echo " gnommo.sh -p video1 validate # Validate only" echo " gnommo.sh -p video1 all # Full pipeline" exit 0 @@ -56,13 +64,17 @@ while [[ $# -gt 0 ]]; do shift 2 ;; -v|--verbose) - VERBOSE="--verbose" + VERBOSE="-v" + shift + ;; + -f|--force) + FORCE="-f" shift ;; -h|--help) usage ;; - validate|render|transcribe|align|all) + import|validate|render|preprocess|transcribe|align|all) COMMAND="$1" shift ;; @@ -90,64 +102,49 @@ if [[ ! -f "$PROJECT/project.json" ]]; then exit 1 fi -# Run commands +# Run commands using new CLI interface run_gnommo() { - "$VENV_PYTHON" -m gnommo "$@" + "$VENV_PYTHON" -m gnommo -p "$PROJECT" -a "$1" $VERBOSE +} + +run_gnommo_import() { + "$VENV_PYTHON" -m gnommo -p "$PROJECT" -a validate -i $FORCE $VERBOSE } case $COMMAND in + import) + echo "=== Importing assets for $PROJECT ===" + run_gnommo_import + ;; + validate) echo "=== Validating $PROJECT ===" - run_gnommo validate "$PROJECT" + run_gnommo validate ;; transcribe) echo "=== Transcribing $PROJECT ===" - VIDEO=$(find "$PROJECT/media" -name "*.mov" -o -name "*.mp4" | head -1) - if [[ -z "$VIDEO" ]]; then - echo "Error: No video file found in $PROJECT/media/" - exit 1 - fi - run_gnommo transcribe "$VIDEO" + run_gnommo transcribe ;; align) echo "=== Aligning $PROJECT ===" - run_gnommo align "$PROJECT" + run_gnommo align ;; render) echo "=== Rendering $PROJECT ===" - run_gnommo render "$PROJECT" $VERBOSE + run_gnommo render + ;; + + preprocess) + echo "=== Preprocessing $PROJECT ===" + run_gnommo preprocess ;; all) echo "=== Full Pipeline: $PROJECT ===" - echo "" - - # Step 1: Transcribe - echo ">>> Step 1/3: Transcribe" - VIDEO=$(find "$PROJECT/media" -name "*.mov" -o -name "*.mp4" | grep -v transcript | head -1) - if [[ -z "$VIDEO" ]]; then - echo "Error: No video file found in $PROJECT/media/" - exit 1 - fi - TRANSCRIPT="${VIDEO%.*}.transcript.json" - if [[ -f "$TRANSCRIPT" ]]; then - echo " Transcript exists, skipping: $TRANSCRIPT" - else - run_gnommo transcribe "$VIDEO" - fi - echo "" - - # Step 2: Align - echo ">>> Step 2/3: Align" - run_gnommo align "$PROJECT" - echo "" - - # Step 3: Render - echo ">>> Step 3/3: Render" - run_gnommo render "$PROJECT" $VERBOSE + run_gnommo all ;; *) diff --git a/gnommo/cli.py b/gnommo/cli.py index b54c6b1..0d0a86b 100644 --- a/gnommo/cli.py +++ b/gnommo/cli.py @@ -8,18 +8,11 @@ from pathlib import Path from . import __version__ from .errors import GnommoError, ParseError, ValidationError, RenderError -from .parser import ( - parse_manuscript, - parse_project_config, - parse_slides, - parse_transcript, - parse_videos, -) -from .validator import validate_project -from .transformer import build_render_plan -from .renderer import render, generate_ffmpeg_command_string -from .transcriber import transcribe_video, save_transcript, load_transcript -from .aligner import align_markers, save_aligned_transcript + + +class NotImplementedException(GnommoError): + """Feature not yet implemented.""" + pass def main() -> int: @@ -34,120 +27,79 @@ def main() -> int: version=f"%(prog)s {__version__}", ) - subparsers = parser.add_subparsers(dest="command", required=True) - - # validate command - validate_parser = subparsers.add_parser( - "validate", - help="Validate project without rendering", + # Required arguments + parser.add_argument( + "-p", "--project", + type=str, + required=True, + help="Project name (directory in current folder)", ) - validate_parser.add_argument( - "project", - type=Path, - help="Path to project directory", + parser.add_argument( + "-a", "--action", + type=str, + choices=["validate", "preprocess", "render", "all", "transcribe", "align"], + required=True, + help="Action to perform", ) - # render command - render_parser = subparsers.add_parser( - "render", - help="Render video from project", + # Optional arguments + parser.add_argument( + "-i", "--import", + dest="import_assets", + action="store_true", + help="Import assets and generate metadata JSON files", ) - render_parser.add_argument( - "project", - type=Path, - help="Path to project directory", - ) - render_parser.add_argument( - "-o", "--output", - type=Path, - help="Output file path (default: project/out/final.mp4)", - ) - render_parser.add_argument( + parser.add_argument( "-v", "--verbose", action="store_true", - help="Print FFmpeg command", + help="Verbose output", ) - render_parser.add_argument( + parser.add_argument( + "-f", "--force", + action="store_true", + help="Force destructive changes (overwrite existing files)", + ) + parser.add_argument( + "--no-cache", + action="store_true", + help="Force cache break (not implemented)", + ) + parser.add_argument( "--dry-run", action="store_true", - help="Print FFmpeg command without executing", - ) - - # generate-slides command - gen_slides_parser = subparsers.add_parser( - "generate-slides", - help="Generate slides.json from Keynote export folder", - ) - gen_slides_parser.add_argument( - "directory", - type=Path, - help="Path to slides directory (e.g., media/slides/Video1)", - ) - gen_slides_parser.add_argument( - "--type", - default="square", - help="Slide type for all slides (default: square)", - ) - - # transcribe command - transcribe_parser = subparsers.add_parser( - "transcribe", - help="Transcribe video audio using Whisper", - ) - transcribe_parser.add_argument( - "video", - type=Path, - help="Path to video file", - ) - transcribe_parser.add_argument( - "-o", "--output", - type=Path, - help="Output JSON file (default: