Add render -n command

This commit is contained in:
2026-07-30 16:09:05 +02:00
parent cbdc22cc16
commit 4fbb6425df
+73 -7
View File
@@ -186,6 +186,17 @@ Examples:
dest="chunk_slides",
help="Split render into chunks of N slides each and concatenate (overrides render_chunk_slides in .gnommo.conf)",
)
parser.add_argument(
"-n",
"--chunk-debug",
type=int,
default=0,
dest="chunk_debug_slides",
metavar="N",
help="Debug: render the video in chunks of N slides each as SEPARATE files "
"on disk (suffixed with slide range, e.g. video1_S1_S4.mp4), without "
"concatenating — isolates where long-range renders OOM/fail.",
)
parser.add_argument(
"--res",
type=str,
@@ -394,6 +405,7 @@ Examples:
args.res,
args.force,
chunk_slides=args.chunk_slides,
chunk_debug_slides=args.chunk_debug_slides,
realign=args.realign,
)
elif action == "grade":
@@ -4182,16 +4194,22 @@ def _chunked_render(
out_dir: Path,
final_output: Path,
plan=None,
concat: bool = True,
) -> int:
"""Render in slide-based chunks then concatenate — avoids filter graph OOM."""
import math
"""Render in slide-based chunks — avoids filter graph OOM on long videos.
concat=True (default) concatenates the chunks into `final_output` and deletes
the parts. concat=False (debug, -n) keeps each chunk as its own inspectable
file in `out_dir`, named `{stem}_{Sfirst}_{Slast}{suffix}`, and does not
concatenate — for isolating which slide range a long render fails on.
"""
# Split slide IDs into groups of chunk_size
groups = [
slide_ids[i : i + chunk_size] for i in range(0, len(slide_ids), chunk_size)
]
_mode = "concatenate" if concat else "separate files (debug, no concat)"
print(
f"\n Auto-chunking: {len(slide_ids)} slides → {len(groups)} chunks of ≤{chunk_size}"
f"\n Chunking: {len(slide_ids)} slides → {len(groups)} chunks of ≤{chunk_size} [{_mode}]"
)
# Report clips that span a chunk boundary. v2 seeks into them so they continue
@@ -4212,15 +4230,22 @@ def _chunked_render(
print(" If a seam looks off, verify frame alignment (concat uses -c copy).",
file=sys.stderr)
chunks_dir = out_dir / "chunks"
chunks_dir.mkdir(parents=True, exist_ok=True)
if concat:
chunks_dir = out_dir / "chunks"
chunks_dir.mkdir(parents=True, exist_ok=True)
else:
out_dir.mkdir(parents=True, exist_ok=True)
chunk_paths: list[Path] = []
for i, group in enumerate(groups):
start = group[0]
end = groups[i + 1][0] if i + 1 < len(groups) else None
slides_arg = f"{start}:{end}" if end else f"{start}:"
chunk_path = chunks_dir / f"chunk_{i+1:03d}_{start}-{end or 'end'}.mp4"
if concat:
chunk_path = chunks_dir / f"chunk_{i+1:03d}_{start}-{end or 'end'}.mp4"
else:
# Debug: land next to the final output, named by this chunk's own range.
chunk_path = out_dir / f"{final_output.stem}_{start}_{group[-1]}{final_output.suffix}"
print(f"\n {'='*56}")
print(f" Chunk {i+1}/{len(groups)}: {slides_arg}{chunk_path.name}")
@@ -4237,9 +4262,25 @@ def _chunked_render(
)
if result != 0:
print(f"\n Chunk {i+1} failed — aborting.", file=sys.stderr)
if not concat:
print(
f" → the render fails somewhere in {slides_arg} "
f"({len(group)} slides). Narrow with a smaller -n.",
file=sys.stderr,
)
return result
chunk_paths.append(chunk_path)
# Debug mode: leave the per-chunk files on disk for inspection, no concat.
if not concat:
if dry_run:
print(f"\n [dry-run] Would write {len(groups)} separate chunk file(s) to {out_dir}")
return 0
print(f"\n Wrote {len(chunk_paths)} chunk file(s) to {out_dir}:")
for p in chunk_paths:
print(f" {p.name}")
return 0
if dry_run:
print(
f"\n [dry-run] Would concatenate {len(chunk_paths)} chunks → {final_output}"
@@ -4656,6 +4697,7 @@ def cmd_render(
res: str = "full",
force: bool = False,
chunk_slides: int = 0,
chunk_debug_slides: int = 0,
_output_path_override: Path = None,
plan_only: bool = False,
realign: bool = False,
@@ -4672,6 +4714,7 @@ def cmd_render(
res=res,
force=force,
chunk_slides=chunk_slides,
chunk_debug_slides=chunk_debug_slides,
_output_path_override=_output_path_override,
plan_only=plan_only,
realign=realign,
@@ -4730,6 +4773,7 @@ def _cmd_render_impl(
res: str = "full",
force: bool = False,
chunk_slides: int = 0,
chunk_debug_slides: int = 0,
_output_path_override: Path = None,
plan_only: bool = False,
realign: bool = False,
@@ -5153,8 +5197,30 @@ def _cmd_render_impl(
# Check if chunked rendering is needed (avoids filter graph OOM on long videos)
from .cache import get_render_chunk_size
_chunk_size = chunk_slides or get_render_chunk_size() or 0
_slide_ids = [e.slide_id for e in plan.slide_events]
# Debug mode (-n N): render each N-slide chunk to its own file on disk and
# stop — no concatenation. Lets you see exactly which slide range a long
# render dies on, and what chunking does to the memory profile.
if chunk_debug_slides > 0 and _output_path_override is None:
if slide_range:
print(" -n/--chunk-debug cannot be combined with --slides.", file=sys.stderr)
return 1
return _chunked_render(
project_path,
verbose,
dry_run,
res,
force,
chunk_debug_slides,
_slide_ids,
out_dir,
output_path,
plan=plan,
concat=False,
)
_chunk_size = chunk_slides or get_render_chunk_size() or 0
if _chunk_size > 0 and not slide_range and len(_slide_ids) > _chunk_size:
return _chunked_render(
project_path,