Add render -n command
This commit is contained in:
+73
-7
@@ -186,6 +186,17 @@ Examples:
|
|||||||
dest="chunk_slides",
|
dest="chunk_slides",
|
||||||
help="Split render into chunks of N slides each and concatenate (overrides render_chunk_slides in .gnommo.conf)",
|
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(
|
parser.add_argument(
|
||||||
"--res",
|
"--res",
|
||||||
type=str,
|
type=str,
|
||||||
@@ -394,6 +405,7 @@ Examples:
|
|||||||
args.res,
|
args.res,
|
||||||
args.force,
|
args.force,
|
||||||
chunk_slides=args.chunk_slides,
|
chunk_slides=args.chunk_slides,
|
||||||
|
chunk_debug_slides=args.chunk_debug_slides,
|
||||||
realign=args.realign,
|
realign=args.realign,
|
||||||
)
|
)
|
||||||
elif action == "grade":
|
elif action == "grade":
|
||||||
@@ -4182,16 +4194,22 @@ def _chunked_render(
|
|||||||
out_dir: Path,
|
out_dir: Path,
|
||||||
final_output: Path,
|
final_output: Path,
|
||||||
plan=None,
|
plan=None,
|
||||||
|
concat: bool = True,
|
||||||
) -> int:
|
) -> int:
|
||||||
"""Render in slide-based chunks then concatenate — avoids filter graph OOM."""
|
"""Render in slide-based chunks — avoids filter graph OOM on long videos.
|
||||||
import math
|
|
||||||
|
|
||||||
|
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
|
# Split slide IDs into groups of chunk_size
|
||||||
groups = [
|
groups = [
|
||||||
slide_ids[i : i + chunk_size] for i in range(0, len(slide_ids), chunk_size)
|
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(
|
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
|
# 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).",
|
print(" If a seam looks off, verify frame alignment (concat uses -c copy).",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
|
|
||||||
chunks_dir = out_dir / "chunks"
|
if concat:
|
||||||
chunks_dir.mkdir(parents=True, exist_ok=True)
|
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] = []
|
chunk_paths: list[Path] = []
|
||||||
for i, group in enumerate(groups):
|
for i, group in enumerate(groups):
|
||||||
start = group[0]
|
start = group[0]
|
||||||
end = groups[i + 1][0] if i + 1 < len(groups) else None
|
end = groups[i + 1][0] if i + 1 < len(groups) else None
|
||||||
slides_arg = f"{start}:{end}" if end else f"{start}:"
|
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"\n {'='*56}")
|
||||||
print(f" Chunk {i+1}/{len(groups)}: {slides_arg} → {chunk_path.name}")
|
print(f" Chunk {i+1}/{len(groups)}: {slides_arg} → {chunk_path.name}")
|
||||||
@@ -4237,9 +4262,25 @@ def _chunked_render(
|
|||||||
)
|
)
|
||||||
if result != 0:
|
if result != 0:
|
||||||
print(f"\n Chunk {i+1} failed — aborting.", file=sys.stderr)
|
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
|
return result
|
||||||
chunk_paths.append(chunk_path)
|
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:
|
if dry_run:
|
||||||
print(
|
print(
|
||||||
f"\n [dry-run] Would concatenate {len(chunk_paths)} chunks → {final_output}"
|
f"\n [dry-run] Would concatenate {len(chunk_paths)} chunks → {final_output}"
|
||||||
@@ -4656,6 +4697,7 @@ def cmd_render(
|
|||||||
res: str = "full",
|
res: str = "full",
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
chunk_slides: int = 0,
|
chunk_slides: int = 0,
|
||||||
|
chunk_debug_slides: int = 0,
|
||||||
_output_path_override: Path = None,
|
_output_path_override: Path = None,
|
||||||
plan_only: bool = False,
|
plan_only: bool = False,
|
||||||
realign: bool = False,
|
realign: bool = False,
|
||||||
@@ -4672,6 +4714,7 @@ def cmd_render(
|
|||||||
res=res,
|
res=res,
|
||||||
force=force,
|
force=force,
|
||||||
chunk_slides=chunk_slides,
|
chunk_slides=chunk_slides,
|
||||||
|
chunk_debug_slides=chunk_debug_slides,
|
||||||
_output_path_override=_output_path_override,
|
_output_path_override=_output_path_override,
|
||||||
plan_only=plan_only,
|
plan_only=plan_only,
|
||||||
realign=realign,
|
realign=realign,
|
||||||
@@ -4730,6 +4773,7 @@ def _cmd_render_impl(
|
|||||||
res: str = "full",
|
res: str = "full",
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
chunk_slides: int = 0,
|
chunk_slides: int = 0,
|
||||||
|
chunk_debug_slides: int = 0,
|
||||||
_output_path_override: Path = None,
|
_output_path_override: Path = None,
|
||||||
plan_only: bool = False,
|
plan_only: bool = False,
|
||||||
realign: 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)
|
# Check if chunked rendering is needed (avoids filter graph OOM on long videos)
|
||||||
from .cache import get_render_chunk_size
|
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]
|
_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:
|
if _chunk_size > 0 and not slide_range and len(_slide_ids) > _chunk_size:
|
||||||
return _chunked_render(
|
return _chunked_render(
|
||||||
project_path,
|
project_path,
|
||||||
|
|||||||
Reference in New Issue
Block a user