← Commit history
Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "adom-aiflow-video"-version = "0.3.0"+version = "0.3.1" dependencies = [  "clap",  "serde",
Cargo.toml+1−1
@@ -2,7 +2,7 @@ # Copied from adom/adom-aiflow template/flow-app. This copy is yours: change anything, never depend on aiflow. [package] name = "adom-aiflow-video"-version = "0.3.0"+version = "0.3.1" edition = "2021" license = "MIT" 
README.md+1−1
@@ -58,4 +58,4 @@ Your film is yours: it carries no Adom logo. The Adom logo sting is only for off  ## Status -0.3.0. It grew out of the two Ghost Hand films ([john/ghost-hand](https://wiki.adom.inc/john/ghost-hand)). Working today: the flow and its hints, clip recording, the disk estimate, script feedback, the text privacy scan, file tracking and cleanup, the walkthrough director, and the narration page for recording your own voice. Still done by the AI by hand, with the skill as the guide: the cut itself, the frame-by-frame privacy look, the wiki publish and the YouTube upload. AI Flow does not suggest this app at intake yet.+0.3.1. It grew out of the two Ghost Hand films ([john/ghost-hand](https://wiki.adom.inc/john/ghost-hand)). Working today: the flow and its hints, clip recording, the disk estimate, script feedback, the text privacy scan, file tracking and cleanup, the walkthrough director, and the narration page for recording your own voice. Still done by the AI by hand, with the skill as the guide: the cut itself, the frame-by-frame privacy look, the wiki publish and the YouTube upload. AI Flow does not suggest this app at intake yet.
bin/adom-aiflow-video
⋯ 1 unchanged line ⋯
package.json+1−1
@@ -1,7 +1,7 @@ {   "slug": "adom-aiflow-video",   "type": "app",-  "version": "0.3.0",+  "version": "0.3.1",   "title": "AI Flow: Video",   "description": "Make a narrated walkthrough video of a board built in Adom Hydrogen, put it on the project's wiki page and help the human upload it to YouTube. A dynamic skill: a small Rust binary that suggests each step, records clips, ships a ready-made 3D walkthrough director and gives gentle feedback; the AI does the work. Open source (MIT): the full Rust source ships on this page; build it yourself with ./build.sh.",   "category": "Developer Tools",
page.json+1−1
@@ -1,7 +1,7 @@ {   "slug": "adom-aiflow-video",   "type": "app",-  "version": "0.3.0",+  "version": "0.3.1",   "title": "AI Flow: Video",   "description": "Make a narrated walkthrough video of a board built in Adom Hydrogen, put it on the project's wiki page and help the human upload it to YouTube. A dynamic skill: a small Rust binary that suggests each step, records clips, ships a ready-made 3D walkthrough director and gives gentle feedback; the AI does the work. Open source (MIT): the full Rust source ships on this page; build it yourself with ./build.sh.",   "category": "Developer Tools",
skills/adom-aiflow-video/SKILL.md+4
@@ -111,3 +111,7 @@ The film plays silent while they record, so they never hear the AI voiceover. If ## The film must play perfectly on their computer  A narration is only as good as the film it is spoken to: if the film pauses, the voice drifts out of step. So the page loads the whole film into memory, plays a few seconds of it out of sight and measures it, before every first take. Some computers stall on one kind of video (we met a laptop that stalled on 1080p H.264 and played VP9 perfectly), so `narrate` also makes a VP9 copy beside the film (`<name>.smooth.webm`) and the page switches to it when the film does not keep time. `narrate status` shows `film playback on their computer` with what was measured and which copy is in use. If neither keeps time, the page tells the human and you: have them close heavy apps, or re-encode the film smaller and serve that. Every take's json also records `stalls` and a `sync` list (recording time against film time), so a take with a pause can still be lined up piece by piece instead of being thrown away.++## Keep the human posted on the page++After they press Use this take they are looking at the narration page, not at your chat. The page says straight away that you have the take; from then on it is yours to keep current. Run `narrate note "Step 2 of 4: lining your recorder's sound up with the take"` as you start each stage (fetching their recorder's clip, lining it up, putting voice, picture and drawings on the film, writing captions) and again when the film is ready to watch, with how to watch it. A note that still says "Ready to record" while you are muxing reads as if nothing is happening.
src/narrate.rs+6
@@ -218,6 +218,12 @@ fn handle(mut s: TcpStream, video: PathBuf, out: PathBuf, sh: Arc<Mutex<Shared>>             let file = req["file"].as_str().unwrap_or("").to_string();             if file.is_empty() || file.contains('/') || !out.join(&file).is_file() { return respond(&mut s, "404 Not Found", "application/json", "", b"{\"error\":\"no such take\"}"); }             sh.lock().unwrap().state["chosen"] = file.clone().into();+            // never leave the human looking at a stale note: the page says at once that the AI has the take+            let ext = std::fs::read_to_string(out.join(Path::new(&file).with_extension("json"))).ok().and_then(|t| serde_json::from_str::<Value>(&t).ok()).map(|m| m["audioPlan"] == "external").unwrap_or(false);+            let auto = if ext { "Your AI has your take. Next: it fetches the sound from your recorder (plug it in if you have not), lines it up, puts your voice on the film and writes new captions. It will update this box at each step." } else { "Your AI has your take. Next: it puts your voice on the film and writes new captions from what you said. It will update this box at each step." };+            sh.lock().unwrap().state["note"] = auto.into();+            broadcast(&sh, "note", &json!({"text": auto}));+            println!("Hint: the page now tells the human you have the take. Keep that box current: run `narrate note \"Step 2 of 4: ...\"` as you start each stage (fetching their recorder's sound, lining it up, muxing voice, picture and drawings, writing captions) and when the film is ready to watch. They are looking at that page, not at your chat.");             println!("OK: the human chose {}", out.join(&file).display());             println!("Hint: put their voice on the film (the take's json says how far into the recording the film started), write new captions from what they said, render any drawings (take-N.draw.json), and tell them on the page what you are doing: `narrate note \"...\"`. Show them the result before anything is published.");             respond(&mut s, "200 OK", "application/json", "", json!({"ok": true, "file": file}).to_string().as_bytes())