From f79a0cb0825e86c3cf0f1f724d76af590f199f05 Mon Sep 17 00:00:00 2001 From: jenstandstad Date: Sat, 25 Jul 2026 21:43:21 +0200 Subject: [PATCH] Adding symmetric up down --- gnommo/transfer.py | 51 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/gnommo/transfer.py b/gnommo/transfer.py index 9d69e02..b92e084 100644 --- a/gnommo/transfer.py +++ b/gnommo/transfer.py @@ -7,16 +7,17 @@ Workflow: Design: - commit appends a timestamped entry to commits.log - - up checks server commits.log for newer entry (aborts if found), - then rsyncs a manifest of only the *inputs* needed to render - (manuscript, slides, narration, audio, videos, keynote). For - narration it pushes the raw raw_mov/ recordings, NOT the processed - segments — the rig runs preprocess to produce the large - processed/*_processed.mov files itself. It also does NOT push the - rendered output (out/*.mp4/.srt) — that is produced on the rig, so - pushing a stale local copy would clobber it. - - down rsyncs everything the server has back to local, including the - freshly rendered out/*.mp4. Rendered output flows one way: rig → local. + - up checks server commits.log for newer entry (aborts if found), then + rsyncs the WHOLE project tree, excluding only the large/derived files + in _SYNC_EXCLUDES. Everything else — manuscript, slides, narration + raw_mov/, per-segment transcripts, events.json, audio, videos, keynote + — is carried automatically, so a new kind of input file can never be + silently left behind. + - down rsyncs the WHOLE project tree back, with the same _SYNC_EXCLUDES. + +Sync model: move everything, exclude a small denylist. The exclusions are large +derived artifacts each side regenerates or ships on its own (rendered output, +preprocessed segments, downscales, chunk scratch), so they never travel over SSH. """ import json @@ -27,16 +28,27 @@ from typing import Optional _COMMITS_LOG = "commits.log" -# Dirs/patterns excluded on down (mirrors what up never pushes). Note: events.json -# and scaffold.json are NOT excluded — they must reach the rig so `down` + `render` -# is all that's needed there. The render never rewrites them, so they stay identical -# on both ends (no clobbering). -_DOWN_EXCLUDES = [ +# Files/dirs NEVER transferred in either direction. Everything else syncs, so new +# artifacts (transcripts, manuscript_transcribed.txt, events.json, scaffold.json, …) +# are carried automatically without touching this list. These are large, derived, +# or machine-local outputs that each side regenerates or ships itself: +# - out/ rendered video (produced on the rig; `handoff` ships it for review) +# - processed/ heavy preprocessed *_processed.mov segments (rig regenerates) +# - intermediate/ ffmpeg work files +# - low/ proxy/ derived downscales +# - **/chunks/ per-chunk preprocess scratch +# Note: events.json / scaffold.json are deliberately NOT excluded — they must reach +# the rig so `down` + `render` is all that's needed there, and render never rewrites +# them, so they stay identical on both ends. +_SYNC_EXCLUDES = [ + "out/", "media/narration/processed/", "media/narration/intermediate/", "media/videos/intermediate/", "media/narration/low/", "media/videos/low/", + "media/narration/proxy/", + "media/videos/proxy/", "**/chunks/", "*.tmp", ".*", # rsync in-progress temp files (.filename.XXXXXX) and .DS_Store @@ -125,6 +137,15 @@ def _build_manifest(project_path: Path) -> list[str]: for f in sorted(raw_mov_dir.glob("*.mov")): if f.is_file() and not f.name.startswith("."): files.add(str(f.relative_to(project_path))) + # Per-segment Whisper transcripts. These are the ONLY non-deterministic input + # to marker alignment (Whisper is not bit-reproducible across platforms), so + # syncing them makes `build` produce identical events.json on any machine — the + # rig can no longer diverge by re-transcribing locally. + transcripts_dir = narration_dir / "transcripts" + if transcripts_dir.is_dir(): + for f in sorted(transcripts_dir.glob("*.json")): + if f.is_file() and not f.name.startswith("."): + files.add(str(f.relative_to(project_path))) # Audio — standard location, non-shared entries only audio_json = project_path / "media" / "audio" / "audio.json"