@lalalic/markcut 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/AGENTS.md +1 -0
  2. package/package.json +1 -1
  3. package/{SKILL.md → skills/markcut/SKILL.md} +8 -2
  4. package/{docs → skills/markcut/docs}/system-prompt-edit.md +5 -2
  5. package/skills/markcut/docs/template.md +213 -0
  6. package/{templates → skills/markcut/docs/templates}/courseware/TEMPLATE.md +220 -10
  7. package/{templates → skills/markcut/docs/templates}/courseware/prompts/outline.md +5 -0
  8. package/{templates → skills/markcut/docs/templates}/courseware/prompts/scene.md +6 -0
  9. package/skills/markcut/docs/templates/deep-dive/TEMPLATE.md +355 -0
  10. package/skills/markcut/docs/templates/deep-dive/agents/researcher.md +69 -0
  11. package/skills/markcut/docs/templates/deep-dive/agents/reviewer.md +108 -0
  12. package/skills/markcut/docs/templates/deep-dive/prompts/fix.md +28 -0
  13. package/skills/markcut/docs/templates/deep-dive/prompts/outline.md +102 -0
  14. package/skills/markcut/docs/templates/deep-dive/prompts/script.md +64 -0
  15. package/skills/markcut/docs/templates/illustrated-book/TEMPLATE.md +360 -0
  16. package/skills/markcut/docs/templates/illustrated-book/agents/reviewer.md +115 -0
  17. package/skills/markcut/docs/templates/illustrated-book/prompts/fix.md +29 -0
  18. package/skills/markcut/docs/templates/illustrated-book/prompts/illustration.md +35 -0
  19. package/skills/markcut/docs/templates/illustrated-book/prompts/story.md +69 -0
  20. package/skills/markcut/docs/templates/short-film/TEMPLATE.md +387 -0
  21. package/skills/markcut/docs/templates/short-film/agents/reviewer.md +115 -0
  22. package/skills/markcut/docs/templates/short-film/prompts/fix.md +29 -0
  23. package/skills/markcut/docs/templates/short-film/prompts/screenplay.md +101 -0
  24. package/skills/markcut/docs/templates/short-film/prompts/storyboard.md +54 -0
  25. package/skills/markcut/docs/templates/short-video/TEMPLATE.md +302 -0
  26. package/skills/markcut/docs/templates/short-video/agents/reviewer.md +94 -0
  27. package/skills/markcut/docs/templates/short-video/prompts/fix.md +27 -0
  28. package/skills/markcut/docs/templates/short-video/prompts/script.md +65 -0
  29. package/skills/markcut/docs/templates/vlog/TEMPLATE.md +405 -0
  30. package/skills/markcut/docs/templates/vlog/agents/reviewer.md +89 -0
  31. package/skills/markcut/docs/templates/vlog/agents/story-writer.md +100 -0
  32. package/skills/markcut/docs/templates/vlog/prompts/bgm-select.md +38 -0
  33. package/skills/markcut/docs/templates/vlog/prompts/group-clips.md +93 -0
  34. package/skills/markcut/docs/templates/vlog/prompts/local-context.md +59 -0
  35. package/skills/markcut/docs/templates/vlog/prompts/outline.md +56 -0
  36. package/skills/markcut/docs/templates/vlog/prompts/review-fix.md +27 -0
  37. package/skills/markcut/docs/templates/vlog/prompts/storyboard.md +41 -0
  38. package/src/config.mjs +11 -3
  39. package/src/player/bundle/player.js +5 -24
  40. package/src/player/components/EditControls.tsx +1 -1
  41. package/src/player/components/SceneThumbnails.tsx +6 -33
  42. package/src/player/server.mjs +217 -129
  43. /package/{docs → skills/markcut/docs}/edit-mode.md +0 -0
  44. /package/{docs → skills/markcut/docs}/json-descriptive.md +0 -0
  45. /package/{docs → skills/markcut/docs}/label-mode.md +0 -0
  46. /package/{docs → skills/markcut/docs}/markdown-descriptive.md +0 -0
  47. /package/{templates → skills/markcut/docs/templates}/courseware/agents/reviewer.md +0 -0
  48. /package/{templates → skills/markcut/docs/templates}/courseware/prompts/fix.md +0 -0
@@ -0,0 +1,405 @@
1
+ ---
2
+ name: vlog
3
+ description: Turn raw photos and video clips into a narrated vlog with music, using markcut's vision pipeline for media understanding and story-based scene grouping.
4
+ when-to-use: travel diaries, event recaps, camping trips, day-in-life, any vlog from personal media
5
+ engine: "@lalalic/markcut — run via `npx @lalalic/markcut`"
6
+ ---
7
+
8
+ # Vlog Template
9
+
10
+ Follow this file top to bottom. Read the markcut skill (`SKILL.md` → `docs/markdown-descriptive.md`) first if you have not.
11
+
12
+ ```mermaid
13
+ flowchart LR
14
+ Profile[👤 user.md<br/>profile] --> Writer
15
+ History[📜 vlog_history.json<br/>past entries] --> Writer
16
+ Media[📁 Media folder] --> Vision[🧠 markcut vision --label]
17
+ Vision --> Labels[📝 metadata.json]
18
+ Labels --> Group[🔍 group-clips.md<br/>filter + group]
19
+ Group --> Groups[📋 grouped scenes]
20
+ Locale[🌍 local-context.md<br/>weather, events, news] --> Groups
21
+ Groups --> Writer[✍️ story-writer agent<br/>narration + title]
22
+ Writer --> Storyboard[📄 course.md]
23
+ BGM[🎵 bgm-select.md<br/>mandatory BGM] --> Assemble[📦 assemble + render]
24
+ Storyboard --> Assemble
25
+ Assemble --> MP4[📺 final.mp4]
26
+ MP4 --> Reviewer[🔍 reviewer agent]
27
+ Reviewer -->|FAIL| Fix[🔧 fix]
28
+ Fix --> Assemble
29
+ Reviewer -->|PASS| HistoryUpdate[📝 append to<br/>vlog_history.json]
30
+ ```
31
+
32
+ | Path | Runs in | Purpose |
33
+ |---|---|---|
34
+ | `TEMPLATE.md` | your context | everything |
35
+ | `prompts/*.md` | your context | fill-in prompts you execute |
36
+ | `agents/*.md` | separate session | subagent definitions |
37
+
38
+ ## 0. Prerequisites
39
+
40
+ - `npx @lalalic/markcut` runnable
41
+ - TTS CLI (default: `edge-tts`)
42
+ - `ffmpeg`/`ffprobe` on PATH
43
+ - For vision pipeline: ITT/VTT/STT CLIs configured (see `docs/markdown-descriptive.md` for env vars)
44
+ - For reviewer: image-understanding capability, STT CLI
45
+
46
+ ## 1. Inputs — collect before starting
47
+
48
+ | Input | Required | Default | Notes |
49
+ |---|---|---|---|
50
+ | Media folder | **yes** | — | absolute path to folder of photos/videos |
51
+ | Theme / event name | **yes** | — | e.g. "birthday camping", "Tokyo trip", "product unboxing" |
52
+ | Date range | no | from media EXIF | — |
53
+ | Language | no | en | narrator's language |
54
+ | Style | no | `daily` | `daily` (casual), `lyrical` (cinematic), `humorous` (funny) |
55
+ | Target duration | no | 60s | 15–120s for short vlogs |
56
+ | Profile | **parse from** | `~/.pi/agent/user.md` | user's personality, region, family members, voice ref, life context. Read this file at the start — it's the authoritative source for who the creator is. The creator's name, region, family, and regular activities feed into narration naturally ("my daughter", "drove up from Ottawa", "my third camping trip this year"). |
57
+ | Voice | no | en: `en-US-GuyNeural` | edge-tts voice. If profile has a voice reference URL, use TTS voice cloning instead |
58
+ | BGM style | no | `ambient` | mood/genre for background music. BGM is **mandatory** — see §2 for root-level audio node |
59
+ | Vlog history | **read from** | `vlog_history.json` | JSON array of past vlogs in the project. Used for story continuity ("last time I was here...", "my third camping trip this year"). If the file doesn't exist, start fresh. See §3 for history rules. |
60
+
61
+ **Rule:** The golden rule of vlogs — **every word in narration, title, or captions must be traceable to real data**: (1) user-stated facts, (2) clip VLM descriptions, (3) GPS/timestamp metadata, (4) user labels from interactive labeling, (5) local news/searched events for the date/location. Never invent people, dialogue, events, or place names. When unsure, paraphrase conservatively ("a path", "someone") rather than guessing.
62
+
63
+ ## 2. Scene grammar — mandatory structure
64
+
65
+ ### Overview
66
+
67
+ ```
68
+ # video ← root: width:1920 height:1080 fps:30 layout:series
69
+ │ tts:"<edge-tts CLI template>"
70
+ ├── ## Route ← (only if GPS data available)
71
+ │ layout:parallel
72
+ │ - map waypoints:[...]
73
+ │ - subtitle script:"Route narration..."
74
+
75
+ ├── ## <Chronological Scene> ×N ← each is a story beat
76
+ │ layout:parallel
77
+ │ - image/video src:... startFrom?... endAt?...
78
+ │ - image/video src:... duration:...
79
+ │ - subtitle script:"Narration line..." duration:N
80
+
81
+ └── ## <Outro Scene> ← closing moment
82
+ layout:parallel
83
+ - video/ image src:... duration:...
84
+ - subtitle script:"Closing line..." duration:N
85
+ ```
86
+
87
+ ### BGM — mandatory root-level audio
88
+
89
+ Add an `- audio isBackground:true` node at the root level. BGM is **non-optional**:
90
+
91
+ ```
92
+ # video
93
+ width:1920 height:1080 fps:30 layout:series
94
+ - audio isBackground:true src:bgm.mp3 volume:0.15 foreground:true
95
+
96
+ ## Hook ...
97
+ ```
98
+
99
+ - `foreground:true` ducks the music volume when TTS narration plays (auto-ducking).
100
+ - Volume: 0.10–0.20 for ambient BGM; 0.05–0.10 for lyrical music.
101
+ - Source BGM from royalty-free libraries (see `prompts/bgm-select.md`).
102
+
103
+ ### Core design: one scene per story beat, media + narration as parallel
104
+
105
+ Each scene is a **story beat** — a single moment or idea that stands on its own. Inside a scene, `layout:parallel` means all media and the subtitle narration play simultaneously.
106
+
107
+ ```
108
+ ## Into the Woods
109
+ layout:parallel
110
+ - image src:IMG_7100.JPG duration:2 start:0
111
+ - video src:IMG_7060.MOV startFrom:0 endAt:10 start:2
112
+ - subtitle script:"Drove into the park and a deer appeared on the trail. Good omen." duration:12
113
+ ```
114
+
115
+ **Rules:**
116
+
117
+ - **Scene count**: 5–12 scenes. Each 3–20 seconds. Total duration matches target ±15%.
118
+ - **Hook → Core → Vibe → Close arc**: The first scene hooks the viewer (best visual + intriguing line). Middle scenes are chronological story beats. The final scene provides emotional/humorous closure.
119
+ - **Transition between scenes**: The root `layout:series` plays scenes sequentially. For smooth transitions, add `transition:fade(0.5)` to the root config line.
120
+ - **Media per scene**: 1–4 clips. Group clips by visual continuity (same background/outfit/subject-state). Avoid jumping A→B→A.
121
+ - **Subtitle node**: Each scene needs a `subtitle script:"..."` node. This is the TTS narration. Its `duration` must match the intended scene length. The script text also becomes the VTT subtitle overlay.
122
+ - **Duration driven by subtitle**: Set `duration:` on the `subtitle` node. Media nodes use `start:` and `duration:` to align within the scene. Videos use `startFrom:`/`endAt:` for trimming.
123
+ - **BGM is mandatory**: Always add an `- audio isBackground:true foreground:true` at root level (see BGM section above).
124
+ - **No bullet-reveal** — vlogs are linear media, not slides. No `current`/`on:` events needed.
125
+
126
+ ### Scene media node style
127
+
128
+ For images:
129
+ ```markdown
130
+ - image src:<relative-path> duration:<s> start:<s>
131
+ ```
132
+
133
+ For video clips (trimmed):
134
+ ```markdown
135
+ - video src:<relative-path> startFrom:<s> endAt:<s> start:<s>
136
+ ```
137
+
138
+ The `src` path is relative to the media folder. During assembly, media is symlinked/copied into the markcut project so `npx markcut render` can serve it.
139
+
140
+ ## 2.b Production polish — route sync, overlays, effects
141
+
142
+ ### Route map synced with video
143
+
144
+ Beyond the opening route scene, show location context throughout the vlog:
145
+
146
+ **Per-scene map overlay** — Each scene that has GPS data shows a small map in the corner with the current location. The `map` node is placed inside the scene's `layout:parallel`:
147
+
148
+ ```
149
+ ## At the Lake
150
+ layout:parallel
151
+ - video src:lake.MOV startFrom:0 endAt:10 start:0
152
+ - map waypoints:[45.5997,-76.0053,"Lac Philippe"]
153
+ style:position:absolute;top:20px;right:20px;width:200px;height:150px;border-radius:12px;opacity:0.85
154
+ - subtitle script:"The lake was glass-still under the gray sky." duration:10
155
+ ```
156
+
157
+ The map is styled as picture-in-picture: top-right corner, rounded corners, semi-transparent. It shows a single waypoint marker for the scene's GPS location with a label.
158
+
159
+ **Map transition scene** — Between major location changes, insert a 3–5s map scene showing the driving route between two GPS points:
160
+
161
+ ```
162
+ ## Route-to-Camp
163
+ layout:parallel
164
+ - map duration:4 travelMode:DRIVING
165
+ waypoints:[45.5893,-75.9814,"Trailhead";45.6368,-76.0136,"Campground"]
166
+ - subtitle script:"Drove up the gravel road toward the campground." duration:4
167
+ ```
168
+
169
+ Use per-scene map overlays when GPS data is available for that specific clip. Use map transition scenes when the movement between locations is narratively significant (e.g., driving into the park, hiking between trails).
170
+
171
+ ### Overlays
172
+
173
+ - **Persistent logo**: `- image isBackground:true src:logo.png style:position:absolute;bottom:24px;right:24px;width:60px;opacity:0.7` at root level.
174
+ - **Narrator overlay**: `- image/video isBackground:true src:narrator.png` with circular crop + border.
175
+ - **Entrance effects**: `effect:zoomIn` on key images for reveal moments.
176
+ - **See courseware template §2.b** for detailed overlay patterns (transition-audio timing, TTS expression control, effects).
177
+
178
+ ## 3. Authoring rules — the professional bar
179
+
180
+ ### Filter rules (see `prompts/group-clips.md`)
181
+
182
+ - Keep all clips unless: aesthetic score < 0.4, near-duplicate of a better clip, or off-topic.
183
+ - Always keep clips with a positive user label from the interactive labeling step.
184
+ - Max ~20% drops. Never drop below 5 clips total.
185
+ - List every dropped clip with a reason.
186
+
187
+ ### Grouping rules (see `prompts/group-clips.md`)
188
+
189
+ - **Visual continuity rule**: clips sharing the same background/outfit/subject-state form one scene. Exhaust one scene before moving to the next. No jumping A→B→A.
190
+ - 1–4 clips per scene (a single long take >10s may be its own scene).
191
+ - Each scene 3–20s. Split if >20s.
192
+ - For video clips, suggest trim boundaries (`startFrom`/`endAt`) to keep only the narrative-relevant segment.
193
+
194
+ ### Narration script
195
+
196
+ - **Every word must be grounded** in clip captions, user labels, GPS data, creator profile, local news, or vlog history. Never invent people, dialogue, events, or place names.
197
+ - First person ("I"), conversational, matching the creator's persona from the profile.
198
+ - **One narration line per scene**. Length: 10–40 words (~3–15 seconds at 2.5 w/s).
199
+ - **No stale templates**: no "you won't believe this" openers, no "happiness is this simple" endings.
200
+ - **Forbidden punctuation**: no parentheses `()` and no ellipses `...`.
201
+ - **Emoji**: at most one strongly relevant emoji per scene.
202
+ - **Opening scene**: get into the topic fast and set the tone.
203
+ - **Closing scene**: emotional or humorous close that wraps the story.
204
+
205
+ ### Local context (news/events)
206
+
207
+ Before writing narration, search for local news, events, or weather for the vlog's date and location. Weave relevant findings into the story naturally:
208
+
209
+ - **Weather**: "It was unseasonably warm for October — 22 degrees and no clouds."
210
+ - **Local event**: "Turns out the Gatineau Park was running the fall colours festival that weekend."
211
+ - **Seasonal context**: "Full moon that night, so the trail was brighter than usual."
212
+
213
+ Rules:
214
+ - Only use facts confirmed by at least one search result. Never invent weather/events.
215
+ - Keep it to 1–2 local-context references per vlog. Don't overwhelm the narration.
216
+ - The context should **amplify** the story, not replace it. "Drove up during the fall colours festival so the park was packed" tells us more than just the festival fact.
217
+
218
+ See `prompts/local-context.md` for the search step.
219
+
220
+ ### Story continuity from vlog history
221
+
222
+ If `vlog_history.json` exists (see §5), read it before writing narration. Reference past vlogs naturally:
223
+
224
+ - **Return visits**: "Back at Lac Philippe — my third camping trip here this year. The deer were expecting us."
225
+ - **Seasonal contrast**: "Last time I was here it was all mud and rain. Today it's golden and still."
226
+ - **Personal milestones**: "One year ago I was just learning to pitch this tent. Now it takes ten minutes."
227
+
228
+ Rules:
229
+ - Use history only if the current vlog shares location, theme, or timing with a past entry.
230
+ - Keep references brief (one line per vlog max). The story should stand alone.
231
+ - Never fabricate a past event. Only what exists in the history file.
232
+
233
+ ### Creator profile
234
+
235
+ Read `~/.pi/agent/user.md` at the start and weave the creator's identity into narration naturally:
236
+
237
+ - **Personality**: introvert → quiet observational tone; extrovert → energetic, social.
238
+ - **Region**: name-drop the city/area ("drove up from Ottawa", "my favourite spot in the Outaouais").
239
+ - **Family**: use real names/roles ("my daughter", "my wife") as shown in the profile and visible in clips.
240
+ - **Regular activities**: if the user hikes regularly, frame this as one of many hikes ("another trail checked off the list").
241
+ - **Voice reference**: if the profile has a voice URL for TTS cloning, use that instead of default edge-tts voices.
242
+
243
+ ### Title
244
+
245
+ One title, 3–15 words, poetic/suspenseful/summarizing, social-media friendly. Must reflect real content and feel personal. If vlog history shows a series, maintain naming conventions (e.g. "Camping SZN: Episode 3 — Lac Philippe").
246
+
247
+ ### Multi-language (variants)
248
+
249
+ - Same pattern as courseware: `# <lang>` variant block at file end with per-language TTS voice.
250
+ - Every `script`/`subtitle` node gets a `<lang>:"..."` twin.
251
+
252
+ ## 4. Components & styles
253
+
254
+ Vlogs don't need custom JSX components — they use markcut's built-in `image`, `video`, `audio`, `subtitle`, and `map` nodes. However, you may add these optional enhancements:
255
+
256
+ ### Optional: NarratorBox component
257
+
258
+ If adding a narrator overlay with name label, include this in the `~~~js imports` block:
259
+
260
+ ```jsx
261
+ import { delayRender, continueRender } from 'remotion'
262
+
263
+ export function NarratorBox({ src, name = '', size = 100 }) {
264
+ return (
265
+ <div style={{
266
+ position: 'absolute', bottom: 40, left: 40,
267
+ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6,
268
+ }}>
269
+ <div style={{
270
+ width: size, height: size, borderRadius: '50%', overflow: 'hidden',
271
+ border: '3px solid rgba(255,255,255,.6)',
272
+ boxShadow: '0 0 20px rgba(0,0,0,.4)',
273
+ }}>
274
+ <img src={src} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
275
+ </div>
276
+ {name && (
277
+ <span style={{
278
+ fontSize: 14, color: '#fff', textShadow: '0 2px 8px rgba(0,0,0,.6)',
279
+ whiteSpace: 'nowrap', fontWeight: 500,
280
+ }}>{name}</span>
281
+ )}
282
+ </div>
283
+ )
284
+ }
285
+ ```
286
+
287
+ ### Stylesheet
288
+
289
+ No stylesheet needed for standard vlogs. The built-in markcut rendering handles video/image filling, subtitle positioning, and audio mixing. Add one only if you need custom overlays.
290
+
291
+ ## 5. Workflow
292
+
293
+ ### Phase 0: Load context
294
+
295
+ 1. **Load creator profile** — Read `~/.pi/agent/user.md`. Extract: the user's name, region, personality, family members, regular activities, and voice reference URL. This profile infuses every narration line.
296
+
297
+ 2. **Load vlog history** — If `vlog_history.json` exists in the project root, read it. Past vlog entries provide story continuity ("last time I was here..."). The file format is:
298
+ ```json
299
+ [
300
+ {
301
+ "date": "2026-06-18",
302
+ "title": "Deer, Fire, and a Birthday at Lac Philippe",
303
+ "location": "Gatineau Park, Quebec",
304
+ "theme": "birthday camping",
305
+ "people": ["Ray", "Ray's daughter"],
306
+ "key_events": ["deer sighting", "campfire", "lake morning"]
307
+ }
308
+ ]
309
+ ```
310
+ If the file doesn't exist, start fresh.
311
+
312
+ ### Phase 1: Prepare media
313
+
314
+ 3. **Understand media** — Run markcut's vision pipeline on the media folder:
315
+ ```
316
+ npx @lalalic/markcut vision <media-folder> --label
317
+ ```
318
+ This extracts metadata, opens a preview for interactive labeling (add descriptions to key clips), normalizes media, runs VLM captioning and STT on video clips, and segments long videos.
319
+
320
+ Interact with the label preview: browse clips, add descriptions to important ones. Close the browser when done. The output is `metadata.json` in the media folder with per-clip captions, aesthetic scores, GPS, timestamps, and user labels.
321
+
322
+ 4. **Prepare project context** — Collect `metadata.json` and build the inputs table (§1). Extract GPS waypoints for route sync. Note the location and date for local context search.
323
+
324
+ ### Phase 2: Group, Filter & Context
325
+
326
+ 5. **Search local news/events** — Fill `prompts/local-context.md` with the vlog's location, date range, and theme. This searches for relevant weather, events, news, or seasonal information. The result feeds into narration with 1–2 natural references.
327
+
328
+ 6. **Group and filter clips** — Fill `prompts/group-clips.md` with clip data from `metadata.json`. This produces:
329
+ - Dropped clips with reasons
330
+ - Visual-continuity scene groups
331
+ - Trim boundaries for video clips
332
+ - GPS waypoints for per-scene map overlays
333
+ - Route scene detection (if ≥2 GPS points)
334
+
335
+ Review output for narrative sense and drop reasonability.
336
+
337
+ ### Phase 3: Storyboard
338
+
339
+ 7. **Narrative arc + narration** — Combine grouped scenes + local context + vlog history + profile into a rich narrative:
340
+
341
+ **A. Orchestrator does it** (simpler): Fill `prompts/outline.md` → rough arc. Then fill `prompts/storyboard.md` per scene → markcut markdown with media refs + narration.
342
+
343
+ **B. Subagent does it** (recommended): Run `agents/story-writer.md` in a separate session. Pass it the grouped scene plan + local context + vlog history + profile. It returns `{title, scenes: [{name, narration, duration_sec}]}`. The orchestrator assembles this into markcut markdown.
344
+
345
+ **Every word grounded**: spot-check 3 scenes — narration must trace to clip captions, labels, GPS, local context, profile, or history. No fabrication.
346
+
347
+ ### Phase 4: Assemble
348
+
349
+ 8. **Select BGM** — Choose background music that matches the vlog's mood. Fill `prompts/bgm-select.md` or search using the `audio-sourcing` skill. Download the track and reference it as `src:bgm.mp3` in the root-level audio node.
350
+
351
+ 9. **Assemble course.md** — Write the markcut markdown file using §2 grammar. The root config line includes TTS voice. BGM is at root level (`- audio isBackground:true foreground:true src:bgm.mp3 volume:0.15`). For scenes with GPS data, add per-scene map overlays (see §2.b).
352
+
353
+ **Media staging**: Make media accessible to the renderer:
354
+ ```
355
+ mkdir -p .markcut/generated/media/
356
+ cp <media-folder>/clips_normalized/*.mp4 .markcut/generated/media/
357
+ cp <media-folder>/*.jpg .markcut/generated/media/
358
+ cp /path/to/bgm.mp3 .markcut/generated/media/bgm.mp3
359
+ ```
360
+ Reference as `src:.markcut/generated/media/<file>` in the markdown.
361
+
362
+ 10. **Render** — `npx @lalalic/markcut render course.md`. On engine errors: fix and re-render, max 3 attempts per error, then ask the user.
363
+
364
+ ### Phase 5: Review & History
365
+
366
+ 11. **Review (quality gate)** — Run `agents/reviewer.md` in a fresh separate session. Pass absolute paths to: `course.md`, the rendered MP4, this `TEMPLATE.md`, target duration, language, and the media folder. Returns `{verdict, findings[]}`.
367
+
368
+ 12. **Fix loop** — On FAIL: fill `prompts/review-fix.md` with findings, apply edits, re-render, re-review. Max 3 iterations, then escalate.
369
+
370
+ 13. **Append to vlog history** — On PASS, append the current vlog to `vlog_history.json`:
371
+ ```json
372
+ {
373
+ "date": "<date>",
374
+ "title": "<title from narration>",
375
+ "location": "<from GPS or inputs>",
376
+ "theme": "<theme>",
377
+ "people": ["<from profile, as referenced in narration>"],
378
+ "key_events": ["<from group-clips scene names>"]
379
+ }
380
+ ```
381
+ This feeds story continuity for the next vlog.
382
+
383
+ ## 6. Quality gate — exit criteria
384
+
385
+ Done only when ALL hold:
386
+
387
+ - [ ] reviewer verdict = `PASS` (zero blocker/major findings)
388
+ - [ ] total duration within ±15% of target
389
+ - [ ] structure matches §2 (hook → chronological scenes → close)
390
+ - [ ] no blank/black frames at scene boundaries; videos play correctly
391
+ - [ ] STT transcript of rendered audio matches script lines (≥90% content match)
392
+ - [ ] every narration line grounded in clip captions, labels, GPS, or user profile (spot-check 3 scenes)
393
+ - [ ] no fabricated people, dialogue, events, or place names
394
+ - [ ] BGM is **present** (ffprobe confirms audio stream or mixed track) and audible (not silent, not clipping)
395
+ - [ ] BGM ducked under voice — TTS audible above music in ≥90% of spoken segments
396
+ - [ ] local context references (if any) verified against search results — no invented weather/events
397
+ - [ ] history references (if any) match actual entries in `vlog_history.json` — no fabricated past events
398
+ - [ ] media `src` paths resolve correctly (no broken links in rendered output)
399
+ - [ ] profile used: narration tone matches creator personality, family names from profile appear correctly
400
+ - [ ] per-scene map overlays (if GPS available) positioned without obstructing main content
401
+
402
+ ## 7. Reference — worked examples
403
+
404
+ - Golden example: See `tests/fixtures/templates/courseware.md` for the template format.
405
+ - Reference vlog storyboard: The bullx vlog project at `free2/bullx/packages/vlog/storyboard.md` shows a real vlog (birthday camping at Lac Philippe) with 13 chronological scenes, route map, clip trimming, and first-person narration. The project's `.pi/agents/story-writer.md` and `.pi/prompts/vlog-storyboard.md` contain the source of truth for the story-writing methodology documented here.
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: vlog-reviewer
3
+ description: Read-only quality gate for vlog videos produced from the vlog template. Runs in a separate session with fresh context.
4
+ context: fresh
5
+ mode: read-only
6
+ tools: read file, bash (ffmpeg/ffprobe, STT CLI), image understanding
7
+ ---
8
+
9
+ # System prompt
10
+
11
+ You are a strict, evidence-based QA reviewer for vlog videos. You review; you **never edit** any file. Every finding must cite concrete evidence (a frame image, an STT excerpt, a line in the source, a measurement). If you cannot verify a check, report it as a finding with severity `minor` and say why.
12
+
13
+ ## Inputs (provided in the task)
14
+ - `vlog_md` — absolute path to the generated vlog markdown
15
+ - `mp4` — absolute path to the rendered video
16
+ - `template_md` — absolute path to the vlog `TEMPLATE.md`
17
+ - `duration_min` — target duration in seconds
18
+ - `language` — expected language
19
+ - `media_folder` — absolute path to the source media (to verify src paths)
20
+
21
+ ## Procedure
22
+
23
+ ### 1. Static review (source vs rulebook)
24
+ Read `template_md` §2/§3, then check `vlog_md`:
25
+ - Structure: hook scene → chronological scenes → closing scene. Route scene first if present.
26
+ - Every scene: `layout:parallel` with media + `subtitle script:"..."` node
27
+ - Scene count: 5-12 scenes. Each 3-20s duration.
28
+ - No `on:` events or `current=` props (these are courseware patterns, not for vlog)
29
+ - Narration rules: first-person, no parentheses, no ellipses, ≤1 emoji per scene
30
+ - No fabricated content: spot-check 3 scene narrations against clip captions (if available)
31
+
32
+ ### 2. Dynamic review (rendered artifact)
33
+ - `ffprobe` the MP4: duration, resolution, fps, audio stream present
34
+ - Extract 5-8 frames evenly spaced across the video. For each frame, verify with image understanding:
35
+ - not blank/black
36
+ - shows expected media (video frame or image) — no placeholder/broken image icons
37
+ - subtitles (if visible) match the expected narration
38
+ - Extract audio → transcribe with STT CLI. Compare against all `subtitle script:` lines in order.
39
+ - ≥90% content match required
40
+ - Verify correct language
41
+ - Check scene order matches source
42
+ - Media paths: spot-check 2-3 `src:` values exist in the source media folder
43
+
44
+ ### 3. Report
45
+ Severity: `blocker` (broken output: blank frames, missing audio, wrong structure) · `major` (quality bar: narration fabrication, duration >20% off, illegible text, broken media) · `minor` (polish: emoji usage, minor timing)
46
+
47
+ ## Output — exactly this JSON
48
+
49
+ ```json
50
+ {
51
+ "verdict": "PASS | FAIL",
52
+ "measured": {
53
+ "duration_s": 0,
54
+ "target_s": 0,
55
+ "resolution": "",
56
+ "stt_match_pct": 0,
57
+ "scene_count": 0,
58
+ "total_clips": 0
59
+ },
60
+ "findings": [
61
+ {
62
+ "id": "F1",
63
+ "severity": "blocker | major | minor",
64
+ "scene": "<scene name or 'global'>",
65
+ "check": "<which rule>",
66
+ "issue": "<what is wrong>",
67
+ "evidence": "<frame path / STT excerpt / source line>",
68
+ "fix_hint": "<one-line suggestion>"
69
+ }
70
+ ]
71
+ }
72
+ ```
73
+
74
+ `PASS` requires zero `blocker` and zero `major` findings.
75
+
76
+ # Task template
77
+
78
+ ```
79
+ Review a vlog video against its template and source media.
80
+
81
+ vlog_md: {vlog_md}
82
+ mp4: {mp4}
83
+ template_md: {template_md}
84
+ duration_min: {duration_min}
85
+ language: {language}
86
+ media_folder: {media_folder}
87
+
88
+ Follow your procedure. The python whisper CLI may be broken; use whisper-cli (whisper.cpp) as fallback. Save extracted frames under a reviewer directory. Output the verdict JSON only.
89
+ ```
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: vlog-story-writer
3
+ description: Given pre-grouped scenes with clips, write one first-person narration line per scene and a video title. Assumes the orchestrator has already filtered and grouped clips (prompts/group-clips.md).
4
+ context: fresh
5
+ mode: read-only
6
+ tools: none
7
+ ---
8
+
9
+ # System prompt
10
+
11
+ You are a seasoned short-form video storyteller with strong empathy. You step into the creator's shoes (first-person "I") and use a conversational tone to turn grouped scenes into a warm, emotionally rich story.
12
+
13
+ You receive **pre-grouped scenes** (filtering, near-duplicate dropping, and visual grouping already done). You produce ONE narration line per scene and a video title. You do two jobs: **script → title**.
14
+
15
+ ## RULE ZERO — NEVER FABRICATE (overrides everything)
16
+ Every word must be traceable to a clip caption, user label, GPS data, or the creator profile.
17
+ - Caption says "a forest path" → you may write "we walked through the woods"
18
+ - Creator profile says they live in Montreal → you may write "drove up from Montreal"
19
+ - User label "campfire" → you may write "we lit a campfire"
20
+ - NEVER invent people/animals the captions don't show. NEVER invent dialogue, events, or place names unless GPS or the user confirms them.
21
+ - When a caption is ambiguous, paraphrase conservatively ("someone", "a figure", "a path") rather than guessing.
22
+
23
+ ## Input structure
24
+
25
+ Each scene contains:
26
+ - `name` — short descriptive label (e.g. "Arrival", "Campfire Night")
27
+ - `clips` — array of {source, type, caption, aes_score, user_label?, startFrom, endAt}
28
+ - `estimated_duration` — total seconds
29
+ - If `route: true`, the scene is a route overview with waypoints; write a short route narration.
30
+
31
+ ## Step 1 — Script (one narration line per scene)
32
+
33
+ Write exactly one narration line per scene. Rules:
34
+ - First-person "I", conversational tone.
35
+ - **No stale templates**: no "you won't believe this" openers; no "turns out happiness is this simple" endings.
36
+ - Preserve proper nouns, brands, specific events visible in the clip captions.
37
+ - **Forbidden punctuation**: no parentheses `()` and no ellipses `...`. Natural conversational pauses only.
38
+ - **Emoji**: at most one strongly relevant emoji per scene.
39
+ - The line must describe what's on screen for that scene.
40
+ - Length: **10–40 words** (~3–15 seconds at 2.5 w/s). Fit within the scene's `estimated_duration`.
41
+ - **Opening scene**: get into the topic fast and set the tone.
42
+ - **Closing scene**: emotional or humorous close that wraps the story.
43
+ - **Route scene** (if present): brief route overview citing real waypoints only.
44
+
45
+ ## Step 2 — Title
46
+
47
+ One title, 3–15 words, poetic/suspenseful/summarizing, social-media friendly. Must reflect real content.
48
+
49
+ ## Output — JSON
50
+
51
+ ```json
52
+ {
53
+ "title": "string",
54
+ "scenes": [
55
+ {
56
+ "name": "string",
57
+ "narration": "string",
58
+ "duration_sec": 0
59
+ }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ # Task template (orchestrator fills and sends)
65
+
66
+ ```
67
+ Write narration for each of the following pre-grouped scenes.
68
+
69
+ Style: {style} (daily | lyrical | humorous)
70
+ Theme: {theme}
71
+ Language: {language}
72
+
73
+ --- Creator profile (from user.md) ---
74
+ {creator_profile}
75
+ --- End profile ---
76
+
77
+ --- Vlog history (past entries, for continuity) ---
78
+ {vlog_history_json}
79
+ --- End history ---
80
+
81
+ --- Local context (weather, events, news for this vlog's location/date) ---
82
+ {local_context}
83
+ --- End local context ---
84
+
85
+ Overall narrative summary: {narrative_summary}
86
+ Dominant mood: {dominant_mood}
87
+
88
+ Pre-grouped scenes (filtering and visual grouping already done):
89
+ {grouped_scenes_json}
90
+
91
+ Rules:
92
+ - First-person "I", matching the creator's personality from the profile. Use real family names/roles from profile.
93
+ - If vlog history shows a past visit to the same or nearby location, reference it naturally ("Back again — last time was all rain, today it's perfect").
94
+ - If local context provides notable weather/events, weave in 1–2 references naturally.
95
+ - Every word grounded in clip captions, profile, history, or local context. Never invent.
96
+ - Forbidden: parentheses (), ellipses ..., stale templates ("you won't believe this").
97
+ - At most 1 emoji per scene.
98
+
99
+ For each scene, write exactly one first-person narration line (10–40 words). Then write one video title (3–15 words). Output the JSON plan.
100
+ ```
@@ -0,0 +1,38 @@
1
+ # Prompt: Select Background Music (BGM)
2
+
3
+ > Fill every `{placeholder}`, then execute in your own context.
4
+ > BGM is **mandatory** for every vlog. Runs before assembly step.
5
+
6
+ ---
7
+
8
+ You are selecting background music for a {style} vlog: "{theme}".
9
+
10
+ Mood: {dominant_mood} (from clip understanding)
11
+ Location: {location}
12
+ Style: {style}
13
+
14
+ ## Guidelines
15
+
16
+ - BGM must match the dominant mood: upbeat for happy/travel vlogs, ambient for reflective/lyrical vlogs, playful for humorous vlogs.
17
+ - Genres that work well for vlogs: lo-fi, ambient electronic, acoustic guitar, soft piano, chillhop, cinematic pads.
18
+ - Duration: find a track that loops well or is at least as long as the target duration ({duration}s). Short tracks with seamless loop points are fine.
19
+ - License: royalty-free only. Recommended sources: Pixabay Music, Free Music Archive, Uppbeat (free tier).
20
+ - If this project has an `audio-sourcing` skill available, use it to search and download.
21
+
22
+ ## Output
23
+
24
+ If you find a suitable track, provide:
25
+
26
+ ```
27
+ BGM: <track title>
28
+ Source: <url>
29
+ Artist: <name>
30
+ License: <license type>
31
+ Duration: <seconds>
32
+ Loop: yes | no (if loop, describe loop point)
33
+
34
+ How to use in course.md:
35
+ - audio isBackground:true foreground:true src:bgm.mp3 volume:0.15
36
+ ```
37
+
38
+ If no suitable BGM is found (all options are wrong mood, too short, paid-only), report "None found" and suggest a fallback search query.