Adding fixes to the pipeline

This commit is contained in:
2026-03-14 21:29:59 +01:00
parent b6bc5a0463
commit 6949124fa7
6 changed files with 236 additions and 201 deletions
+44 -64
View File
@@ -34,7 +34,7 @@ Examples:
gnommo -p video1 validate Validate only
gnommo -p video1 import Generate slides.json from images
gnommo -p video1 pre Preprocess videos (chroma key, etc.)
gnommo -p video1 stitch --proxy -f Fast stitch with new begin/end values
gnommo -p video1 stitch --res tiny -f Fast stitch with new begin/end values
gnommo -p video1 all Full pipeline: transcribe → align → render
gnommo -p video1 render --dry-run Show FFmpeg command without running
gnommo -p video1 description Generate YouTube description file
@@ -113,9 +113,9 @@ Examples:
parser.add_argument(
"--res",
type=str,
choices=["low", "full"],
choices=["full", "low", "tiny"],
default="full",
help="Resolution: 'low' (490x270) for fast preview, 'full' for project resolution",
help="Resolution: 'full' (project res), 'low' (490x270), 'tiny' (320x180 ultrafast)",
)
parser.add_argument(
"-w",
@@ -124,11 +124,6 @@ Examples:
default=1,
help="Number of parallel workers for preprocessing (default: 1)",
)
parser.add_argument(
"--proxy",
action="store_true",
help="Use proxy workflow: downsample to 160x90 for fast iteration",
)
parser.add_argument(
"--final",
action="store_true",
@@ -184,14 +179,14 @@ Examples:
args.dry_run,
args.force,
args.workers,
args.proxy,
args.res,
)
elif action in ("stitch"):
return cmd_stitch(
project_path,
args.verbose,
args.force,
args.proxy,
args.res,
)
elif action == "render":
return cmd_render(
@@ -201,10 +196,9 @@ Examples:
args.slides,
args.res,
args.force,
args.proxy,
)
elif action == "transcribe":
return cmd_transcribe(project_path, args.verbose, args.proxy, args.final)
return cmd_transcribe(project_path, args.verbose, args.res, args.final)
elif action == "align":
return cmd_align(project_path, args.verbose)
elif action == "all":
@@ -739,17 +733,18 @@ def cmd_preprocess(
dry_run: bool,
force: bool = False,
workers: int = 1,
proxy: bool = False,
res: str = "full",
) -> int:
"""Run preprocessing pipeline on narration segments."""
from concurrent.futures import ThreadPoolExecutor, as_completed
from .parser import parse_project_config, parse_narration
from .preprocessor import (
preprocess_video,
create_proxies_for_videos,
create_downscaled_videos,
RES_CONFIGS,
)
mode_str = " (PROXY MODE)" if proxy else ""
mode_str = f" ({res.upper()})" if res != "full" else ""
print(f"Preprocessing narration: {project_path.name}{mode_str}")
config = parse_project_config(project_path)
@@ -760,12 +755,11 @@ def cmd_preprocess(
print(" Run 'gnommo -p <project> import' first to populate narration.json")
return 1
# Proxy mode: create low-res copies first, then work from proxy dir
if proxy:
proxy_dir = create_proxies_for_videos(narration_dir, narration, force, verbose)
# Switch to proxy directory for all subsequent operations
narration_dir = proxy_dir
print(f" Working from proxy dir: {proxy_dir}")
# Downscale source files first if a preview res was requested
if res != "full":
narration_dir = create_downscaled_videos(narration_dir, narration, res, force, verbose)
cfg = RES_CONFIGS[res]
print(f" Working from {res} dir ({cfg[0]}x{cfg[1]}): {narration_dir}")
# Resolve intermediate directory
gnommo_scratch = None
@@ -853,7 +847,7 @@ def cmd_stitch(
project_path: Path,
verbose: bool,
force: bool = False,
proxy: bool = False,
res: str = "full",
) -> int:
"""
Stitch narration segments from narration.json.
@@ -861,15 +855,11 @@ def cmd_stitch(
Reads segments from media/narration/narration.json, applies begin/end
trimming during concatenation, and writes output to media/videos/narration_combined.mov.
Also creates/updates an entry in videos.json with volume property.
This is useful for quickly iterating on begin/end trim points without
waiting for the full preprocessing pipeline. Works especially well
with --proxy for fast feedback.
"""
from .parser import parse_project_config, parse_narration, parse_videos
from .preprocessor import stitch_narration_segments, ensure_proxy_files_exist
from .preprocessor import stitch_narration_segments, ensure_downscaled_files_exist, RES_CONFIGS
mode_str = " (PROXY MODE)" if proxy else ""
mode_str = f" ({res.upper()})" if res != "full" else ""
print(f"Stitching narration: {project_path.name}{mode_str}")
config = parse_project_config(project_path)
@@ -887,15 +877,13 @@ def cmd_stitch(
else:
videos_dir = project_path / "media" / "videos"
# Proxy mode: use proxy directory for both input and output
# Create proxy files on-the-fly if they don't exist
if proxy:
proxy_narration_dir = ensure_proxy_files_exist(narration_dir, force=False, verbose=verbose)
proxy_videos_dir = videos_dir / "proxy"
proxy_videos_dir.mkdir(parents=True, exist_ok=True)
narration_dir = proxy_narration_dir
videos_dir = proxy_videos_dir
print(f" Using proxy dirs: {narration_dir}, {videos_dir}")
# Use downscaled dirs for non-full res
if res != "full":
cfg = RES_CONFIGS[res]
narration_dir = ensure_downscaled_files_exist(narration_dir, res, force=False, verbose=verbose)
videos_dir = videos_dir / cfg[2]
videos_dir.mkdir(parents=True, exist_ok=True)
print(f" Using {res} dirs: {narration_dir}, {videos_dir}")
# Get segment IDs in sorted order
segment_ids = sorted(narration.keys())
@@ -962,7 +950,7 @@ def cmd_stitch(
print("\n" + "=" * 60)
print("Auto-running transcribe to sync with new narration...")
print("=" * 60 + "\n")
return cmd_transcribe(project_path, verbose, proxy=proxy)
return cmd_transcribe(project_path, verbose, res=res)
# =============================================================================
@@ -1106,7 +1094,6 @@ def cmd_render(
slides_arg: str = None,
res: str = "full",
force: bool = False,
proxy: bool = False,
) -> int:
"""Render final video."""
from .parser import (
@@ -1121,7 +1108,7 @@ def cmd_render(
from .validator import validate_project
from .transformer import build_render_plan
from .renderer import render, generate_ffmpeg_command_string
from .preprocessor import PROXY_WIDTH, PROXY_HEIGHT, ensure_proxy_files_exist
from .preprocessor import RES_CONFIGS, ensure_downscaled_files_exist
# Parse slide range if provided
slide_range = None
@@ -1132,10 +1119,9 @@ def cmd_render(
print(f"Rendering: {project_path.name}")
# Show resolution mode
if proxy:
print(f" Resolution: PROXY ({PROXY_WIDTH}x{PROXY_HEIGHT}) - fast preview mode")
elif res == "low":
print(" Resolution: LOW (490x270) - fast preview mode")
if res != "full":
cfg = RES_CONFIGS[res]
print(f" Resolution: {res.upper()} ({cfg[0]}x{cfg[1]})")
# Show cache status
cache_info = get_cache_info()
@@ -1152,22 +1138,19 @@ def cmd_render(
save_citations(citations, citations_path)
config = parse_project_config(project_path)
# Override resolution for proxy or low-res preview mode
if proxy:
config.resolution = (PROXY_WIDTH, PROXY_HEIGHT)
elif res == "low":
config.resolution = (490, 270)
# Override resolution for preview modes
if res != "full":
cfg = RES_CONFIGS[res]
config.resolution = (cfg[0], cfg[1])
slides = parse_slides(project_path, config)
videos, videos_dir = parse_videos(project_path, config)
# Proxy mode: use videos from proxy directory
# Create proxy files on-the-fly if they don't exist
if proxy:
proxy_dir = ensure_proxy_files_exist(videos_dir, force=False, verbose=verbose)
videos_dir = proxy_dir
# Non-full res: use downscaled video directory, create on-the-fly if needed
if res != "full":
videos_dir = ensure_downscaled_files_exist(videos_dir, res, force=False, verbose=verbose)
if verbose:
print(f" Using proxy dir: {proxy_dir}")
print(f" Using {res} dir: {videos_dir}")
audio, audio_dir = parse_audio(project_path, config)
# Load whisper transcription JSON
@@ -1280,7 +1263,6 @@ def cmd_render(
audio,
audio_dir,
slide_range=slide_range,
proxy=proxy,
)
if plan.time_offset > 0:
print(f" Time offset: {plan.time_offset:.1f}s (partial render)")
@@ -1383,18 +1365,18 @@ def _find_narration_video(config, videos: dict) -> Optional[tuple[str, "VideoSou
def cmd_transcribe(
project_path: Path, verbose: bool, proxy: bool = False, final: bool = False
project_path: Path, verbose: bool, res: str = "full", final: bool = False
) -> int:
"""Transcribe video audio using Whisper."""
from .transcriber import transcribe_video, save_transcript, words_to_srt
from .parser import parse_project_config, parse_videos
from .preprocessor import ensure_proxy_files_exist
from .preprocessor import ensure_downscaled_files_exist
# Handle --final mode: transcribe the rendered output for YouTube captions
if final:
return _transcribe_final(project_path, verbose)
mode_str = " (PROXY)" if proxy else ""
mode_str = f" ({res.upper()})" if res != "full" else ""
print(f"Transcribing: {project_path.name}{mode_str}")
config = parse_project_config(project_path)
@@ -1403,11 +1385,9 @@ def cmd_transcribe(
print("Error: No videos defined in videos.json", file=sys.stderr)
return 1
# Proxy mode: use videos from proxy directory
# Create proxy files on-the-fly if they don't exist
if proxy:
proxy_dir = ensure_proxy_files_exist(videos_dir, force=False, verbose=verbose)
videos_dir = proxy_dir
# Non-full res: use downscaled video directory
if res != "full":
videos_dir = ensure_downscaled_files_exist(videos_dir, res, force=False, verbose=verbose)
# Check for multi-segment narration (concatenated file)
if isinstance(config.main_video, list) and len(config.main_video) > 1: