[m6] Flow video plates spec + flow_intake.py; M6 queued behind M5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a8273127ba
commit
86b76f6bdc
18
PLAN.md
18
PLAN.md
@ -212,6 +212,24 @@ presets, LLM strictly optional on top.
|
||||
ops validated server-side → client applies via the SAME preset functions.
|
||||
Buttons must never depend on the LLM lane.
|
||||
|
||||
**M6 — FLOW VIDEO PLATES (rear-projection era)**: Google-Flow-generated mp4s
|
||||
as first-class stage media. Feed: John's flowext rinse drops mp4s in
|
||||
`~/Downloads/flowrinse/<category>/`; `scripts/flow_intake.py` copies them to
|
||||
`assets/backdrops/video/<category>/` with poster jpgs.
|
||||
- Lane A: backdrop `params.mode` gains `"video"` (and existing modes accept
|
||||
.mp4 sources): `THREE.VideoTexture` on plane/corner/dome, muted, `loop`,
|
||||
`params.videoStart` offset. NEW entity kind `screen`: a video-textured
|
||||
plane with a slight emissive boost + optional CRT curve, for TVs/club
|
||||
screens/jumbotrons INSIDE the set (djsim_screens bank is made for these).
|
||||
Poster jpg shown until play.
|
||||
- Lane B: video backdrops/screens follow the master clock — on evaluate,
|
||||
`video.currentTime = (t - videoStart) % duration` only when drift >50ms
|
||||
(cheap scrub-follow); play/pause with the clock.
|
||||
- Lane C: render path must be deterministic — finalRender already steps
|
||||
frame-by-frame; stage exposes `await stage.syncVideos(t)` (seek + wait
|
||||
for 'seeked') that render.js awaits per frame before capture. Video audio
|
||||
is NEVER used (scene audio[] is the only audio source).
|
||||
|
||||
## 6. How John launches the lanes
|
||||
|
||||
Three Claude Code sessions (Opus 4.8), cwd `~/Documents/SCENEGOD`, one prompt
|
||||
|
||||
@ -202,3 +202,7 @@ badge check. Standing by.
|
||||
Acceptance: select man → CU low 35mm → camera frames his face from below in
|
||||
one click; golden_hour relights the scene in one click; every action lands
|
||||
as keys via B (scrub proves it) and is undo-able.
|
||||
|
||||
### 2026-07-19 — M6 FLOW VIDEO PLATES queued behind M5 (full spec PLAN §5 M6).
|
||||
Do M5 first; M6 is unlocked the moment your M5 acceptance is logged. Test
|
||||
media will be arriving in assets/backdrops/video/ via scripts/flow_intake.py.
|
||||
|
||||
@ -162,3 +162,7 @@ discover the panel scrolls — consider wheel = scroll, Cmd/Ctrl+wheel = zoom.
|
||||
`importRhubarb(characterId, json, audioStart)`; character picker if >1
|
||||
viseme-bearing char; toast the 503 install hint if absent.
|
||||
3. Session-5 backlog if time remains: audio trim, wheel-scroll fix (above).
|
||||
|
||||
### 2026-07-19 — M6 FLOW VIDEO PLATES queued behind M5 (full spec PLAN §5 M6).
|
||||
Do M5 first; M6 is unlocked the moment your M5 acceptance is logged. Test
|
||||
media will be arriving in assets/backdrops/video/ via scripts/flow_intake.py.
|
||||
|
||||
@ -157,3 +157,7 @@ not on PATH — my error). Standing by is fine; remaining work is A/B UI
|
||||
`/rhubarb?path=` endpoint (subprocess → JSON) IF rhubarb is installed —
|
||||
503-with-message otherwise, same pattern as ffmpeg. Log a REQUEST first if
|
||||
it needs more than that.
|
||||
|
||||
### 2026-07-19 — M6 FLOW VIDEO PLATES queued behind M5 (full spec PLAN §5 M6).
|
||||
Do M5 first; M6 is unlocked the moment your M5 acceptance is logged. Test
|
||||
media will be arriving in assets/backdrops/video/ via scripts/flow_intake.py.
|
||||
|
||||
45
scripts/flow_intake.py
Normal file
45
scripts/flow_intake.py
Normal file
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Sweep Flow Auto-Pilot downloads into SCENEGOD assets.
|
||||
|
||||
~/Downloads/flowrinse/<category>/*.mp4 -> assets/backdrops/video/<category>/
|
||||
Each mp4 gets a same-stem poster .jpg (frame at 1s) so the dock shows a thumb.
|
||||
Idempotent: skips files already present with the same size. Run any time:
|
||||
|
||||
python3 scripts/flow_intake.py [--src ~/Downloads/flowrinse] [--dry]
|
||||
"""
|
||||
import argparse, shutil, subprocess, sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
DEST = ROOT / "assets" / "backdrops" / "video"
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--src", default=str(Path.home() / "Downloads" / "flowrinse"))
|
||||
ap.add_argument("--dry", action="store_true")
|
||||
a = ap.parse_args()
|
||||
src = Path(a.src).expanduser()
|
||||
if not src.is_dir():
|
||||
sys.exit(f"no such dir: {src}")
|
||||
if not shutil.which("ffmpeg"):
|
||||
sys.exit("ffmpeg required for poster frames")
|
||||
copied = skipped = 0
|
||||
for mp4 in sorted(src.rglob("*.mp4")):
|
||||
cat = mp4.parent.name
|
||||
out = DEST / cat / mp4.name
|
||||
if out.exists() and out.stat().st_size == mp4.stat().st_size:
|
||||
skipped += 1
|
||||
continue
|
||||
print(("DRY " if a.dry else "") + f"{mp4} -> {out}")
|
||||
if a.dry:
|
||||
continue
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(mp4, out)
|
||||
jpg = out.with_suffix(".jpg")
|
||||
subprocess.run(["ffmpeg", "-v", "error", "-ss", "1", "-i", str(out),
|
||||
"-frames:v", "1", "-vf", "scale=320:-1", str(jpg), "-y"], check=False)
|
||||
copied += 1
|
||||
print(f"done: {copied} copied, {skipped} already present -> {DEST}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user