Adding support for handoff

This commit is contained in:
2026-07-15 10:25:02 +02:00
parent c6efbaf2ed
commit c30c0f1c5e
3 changed files with 136 additions and 299 deletions
+52 -18
View File
@@ -65,6 +65,16 @@ Examples:
gnommo -p video1 up Push manifest files to rendering server
gnommo -p video1 up --dry-run Preview which files would be pushed
gnommo -p video1 down Pull files from rendering server to local
gnommo -p video1 push Push project metadata to the local gnommoweb server
gnommo -p video1 push --prod Push project metadata to production gnommoweb (glitch.university)
gnommo -p video1 push --force Force push, overwriting the server copy
gnommo -p video1 pull Pull (fetch) project metadata from the local server
gnommo -p video1 pull --prod Pull project metadata from production
gnommo -p video1 pull --force Force pull, overwriting the local copy
gnommo -p video1 handoff --prod Upload the rendered video for online review (glitch.university/review/<id>)
gnommo -p video1 handoff Upload the rendered video to the local server
gnommo -p video1 handoff --file X Upload a specific video file instead of out/<output_video>
Note: 'push' sends metadata (script/slides/etc); 'handoff' uploads the actual video file.
gnommo -p video1 extract-audio --combined Extract audio from narration_combined.mov
gnommo -p video1 extract-audio --combined --channel left Extract left channel only
gnommo -p video1 extract-audio --segment seg01 Extract from a specific segment
@@ -4131,21 +4141,34 @@ def cmd_render(
audio, audio_dir = parse_audio(project_path, config)
# Load whisper transcription JSON
# Check for narration_combined in videos.json (new workflow) or multi-segment in config (legacy)
# Resolve the combined narration skeleton. The .mov file — not the videos.json
# entry — is the source of truth: stitch run without the external drive leaves
# the file in media/videos/ even when the videos.json entry is absent (e.g. a
# metadata pull overwrote it). Legacy multi-segment projects are handled below.
combined_path = videos_dir / "narration_combined.mov"
resolved_combined = _resolve_narration_combined(project_path, videos_dir, config)
if resolved_combined and resolved_combined != combined_path:
# File lives on external disk — point the VideoSource at the absolute path so
# the renderer doesn't re-resolve it via the local (missing) videos_dir.
if "narration_combined" in videos:
narration_json = project_path / "media" / "narration" / "narration.json"
_narr_segments = _read_json(narration_json) if narration_json.exists() else {}
if resolved_combined and resolved_combined.exists():
# File is available (locally or via process cache). Ensure a videos.json
# entry exists — synthesizing one when stitch's entry was lost — then use it.
if "narration_combined" not in videos:
from .models import VideoSource
_first_seg = next(iter(_narr_segments.values()), {})
_seg_cutout = (
_first_seg.get("cutout") if isinstance(_first_seg, dict) else None
)
videos["narration_combined"] = VideoSource(
source_file="narration_combined.mov",
cutout=_seg_cutout or "talkinghead",
always_visible=True,
volume=1.0,
)
if resolved_combined != combined_path:
# File lives on external disk — point the VideoSource at the absolute
# path so the renderer doesn't re-resolve it via the local videos_dir.
videos["narration_combined"].source_file = str(resolved_combined)
if (
"narration_combined" in videos
and resolved_combined
and resolved_combined.exists()
):
# New workflow: narration_combined was created by 'gnommo concat' and is in videos.json
# This entry has the correct volume setting from videos.json
transcript_path = resolved_combined.with_suffix(".transcript.json")
config.main_video = "narration_combined"
if verbose:
@@ -4188,10 +4211,21 @@ def cmd_render(
if verbose:
print(f" Using combined narration: {combined_path.name}")
else:
# Check if narration.json exists with segments (new workflow) - if so, require narration_combined
narration_json = project_path / "media" / "narration" / "narration.json"
if narration_json.exists() and _read_json(narration_json):
elif _narr_segments:
# narration.json has segments, but the combined .mov could not be found.
# Distinguish "stitched, file unreachable" from "never stitched" so the
# hint is actionable instead of always blaming videos.json.
if "narration_combined" in videos:
print(
f"Error: narration_combined.mov could not be found.", file=sys.stderr
)
print(
f"videos.json references narration_combined, but the file is not on disk "
f"(checked local media/videos and the process cache).",
file=sys.stderr,
)
print(_narration_combined_hint(project_path, config), file=sys.stderr)
else:
print(
f"Error: narration_combined not found in videos.json", file=sys.stderr
)
@@ -4203,8 +4237,8 @@ def cmd_render(
f"Run 'gnommo -p {project_path.name} stitch' first.",
file=sys.stderr,
)
return 1
return 1
else:
# Single video - look for .transcript.json next to the narration video
result = _find_narration_video(config, videos)
if result: