@lalalic/markcut 2.4.1 → 2.4.3
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.
package/package.json
CHANGED
package/skills/markcut/SKILL.md
CHANGED
|
@@ -88,3 +88,4 @@ some common issues (photo or video can't be displayed, audio missing), take belo
|
|
|
88
88
|
- markcut resolver will automatically calculate the duration based on the audio script length
|
|
89
89
|
- **don't** rm `.markcut` directory, which served as cache for all generated content. cache will auto update according to the content change. rm `.markcut` will cause all content to be regenerated, which is time consuming and wasteful.
|
|
90
90
|
- put all manual assets in `assets` folder, such as bgm, logo, watermark, etc. don't put them in `.markcut` folder, which is auto generated and will be deleted when `markcut clean` command is run.
|
|
91
|
+
- `npx @lalalic/markcut preview` stuck until user close the preview window.
|
|
@@ -1,60 +1,265 @@
|
|
|
1
|
-
# Markdown Descriptive
|
|
1
|
+
# markcut Markdown Descriptive Spec
|
|
2
|
+
markcut Markdown Descriptive is a markdown-based authoring format for markcut. It is designed to be human-readable and writable, while also being machine-parsable. It is the primary authoring format for markcut, and is used for both interactive and non-interactive workflows.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
## DSL — Complete Grammar Reference
|
|
6
|
+
|
|
7
|
+
This section defines the markcut Markdown Descriptive grammar. An agent should read this to understand the exact syntax rules for constructing or parsing a `.md` video document.
|
|
8
|
+
|
|
9
|
+
### 1. Document Structure (top to bottom)
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─ YAML Frontmatter (optional)
|
|
13
|
+
├─ ───...───
|
|
14
|
+
│ hook: My Video
|
|
15
|
+
│ emotion: ...
|
|
16
|
+
│ ───...───
|
|
17
|
+
├─ # video ← Root node (REQUIRED, heading level 1)
|
|
18
|
+
│ width:1080 height:1920 fps:30 layout:series
|
|
19
|
+
│ subtitle:{src:"captions.vtt",type:"Typewriter"}
|
|
20
|
+
│ ~~~css stylesheet ← root.stylesheet
|
|
21
|
+
│ .bg { background: #000; }
|
|
22
|
+
│ ~~~
|
|
23
|
+
├─ ~~~js imports ← Imports block
|
|
24
|
+
│ export { PieChart } from "recharts"
|
|
25
|
+
│ ~~~
|
|
26
|
+
├─ ## scene1 ← Scene nodes (heading level 2+)
|
|
27
|
+
│ layout:parallel
|
|
28
|
+
│ - image src:bg.jpg duration:3
|
|
29
|
+
│ - script "Hello world"
|
|
30
|
+
│ ### sub-scene ← Nested scene (heading level 3)
|
|
31
|
+
│ - component duration:2 jsx:"<Title />"
|
|
32
|
+
├─ ## scene2
|
|
33
|
+
│ - video src:clip.mp4 effects:[fadeIn]
|
|
34
|
+
├─ # zh ← Variant sections (h1 headings OTHER than `video`)
|
|
35
|
+
│ tts:"edge-tts --voice zh-CN-YunxiNeural..." ← override root.tts for zh variant
|
|
36
|
+
├─ # youtube ← Variant sections
|
|
37
|
+
width:1920 height:1080
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Heading Levels & Their Roles
|
|
41
|
+
|
|
42
|
+
| Level | Pattern | Role | Rules |
|
|
43
|
+
|---|---|---|---|
|
|
44
|
+
| H1 | `# video` | **Root** — video canvas definition | Exactly one `# video` required. Everything else is nested inside this section. |
|
|
45
|
+
| H1 | `# zh`, `# youtube`, `# portrait` etc. | **Variant** — override section for alternate versions | Any `# <name>` other than `video`. Only contains override configs + variant-prefixed overrides on base nodes. |
|
|
46
|
+
| H2 | `## <name>` | **Scene** — top-level scene container | `name` = single token (no spaces). Optional `title` via ` - ` separator: `## Chapter1 - The Beginning` → name=`Chapter1`, title=`The Beginning`. |
|
|
47
|
+
| H3 | `### <name>` | **Nested scene** — sub-scene inside parent | Same rules as H2. Creates nesting: parent scene's children array includes this as a sub-container. |
|
|
48
|
+
| H4+ | `#### <name>` | **Deeply nested** — further nesting | Works identically; depth reflects nesting level. |
|
|
2
49
|
|
|
3
|
-
|
|
50
|
+
### 3. Leaf Nodes — Bullet Syntax
|
|
4
51
|
|
|
5
|
-
|
|
52
|
+
Every leaf (media, component, rhythm, map, effect, include) is a **markdown bullet** (`- `) containing space-separated tokens:
|
|
6
53
|
|
|
7
|
-
|
|
54
|
+
```
|
|
55
|
+
- <typeToken> [key:value ...] [bareFlag ...]
|
|
56
|
+
```
|
|
8
57
|
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- Leaf nodes via `- typeToken ...` bullets
|
|
14
|
-
- Component registrations via `` ~~~js imports `` code fence (or inline JSX definitions)
|
|
15
|
-
- Properties via indented code fences (`~~~<lang> <propName>`); `~~~script` only valid on audio nodes
|
|
58
|
+
Where:
|
|
59
|
+
- **`typeToken`** — the node type (see table below). Required as first token.
|
|
60
|
+
- **`key:value`** — property assignments. Space-separated. Values with spaces must be quoted: `title:"Long Title"`, `instruction:"make it cinematic"`.
|
|
61
|
+
- **`bareFlag`** — boolean property that defaults to `true` when present (no `:value` needed). Examples: `isBackground`, `foreground`, `visible`.
|
|
16
62
|
|
|
17
|
-
|
|
63
|
+
#### Type Token Reference
|
|
18
64
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
65
|
+
| Token | Stream Type | When | Required Keys |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| `image` | `image` | Still photos, title cards | `src`, `duration` |
|
|
68
|
+
| `video` | `video` | Video clips | `src` + (`duration` or `endAt`) |
|
|
69
|
+
| `audio` | `audio` | Voiceover, BGM, SFX | `src` + (`duration` or `endAt`) |
|
|
70
|
+
| `script "..."` | `audio` | Narration/TTS (shorthand for audio with script) | `script` text (the `"..."` is the primary content) |
|
|
71
|
+
| `component` | `component` | JSX React component | `jsx` (inline or code fence) + `duration` (unless `isBackground`) |
|
|
72
|
+
| `rhythm` | `rhythm` | Beat-synced audio with timed children | `src`, `spots`, `children` |
|
|
73
|
+
| `map` | `map` | Animated route visualization | `duration`, `waypoints` |
|
|
74
|
+
| `include` | `include` | Embed external `.md` sub-video | `src` (path to another `.md` file) |
|
|
75
|
+
| `parallel` | *container* | Parallel layout container | `children` (indented bullets below) |
|
|
76
|
+
| `series` | *container* | Sequential layout container | `children` (indented bullets below) |
|
|
77
|
+
| `transitionSeries` | *container* | Sequential with transitions | `children`, `transition` (e.g. `transition:fade`) |
|
|
78
|
+
|
|
79
|
+
### 4. Property Value Formats
|
|
80
|
+
|
|
81
|
+
| Format | Example | Description |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `key:number` | `duration:3`, `width:1080`, `volume:0.8` | Numeric values (int or float). Seconds for time values. |
|
|
84
|
+
| `key:string` | `src:cover.jpg`, `fit:cover`, `animation:fadeIn` | Unquoted single-word string. |
|
|
85
|
+
| `key:"quoted string"` | `title:"Long Title"`, `jsx:"<StatCounter value={42} />"` | Quoted string for values containing spaces or special chars. Supports double `"` and single `'` quotes. |
|
|
86
|
+
| `bareFlag` | `isBackground`, `foreground` | Boolean flag — sets the key to `true` when present. |
|
|
87
|
+
| `key:JSON` | `spots:[0.5,1.2,1.9]`, `on:(start, slide.current=0)`, `effects:[fadeIn, bounceIn]` | Structured values using brackets/parens. See subsections below. |
|
|
88
|
+
| `key:<mode>` | `layout:series` | Enum-like mode selection. |
|
|
89
|
+
| `~~~<lang> [propName=lang]` | `~~~jsx jsx`, `~~~script script` | Indented code fence for long-form property values (JSX, script, prompt, etc.). See section 6. |
|
|
23
90
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
91
|
+
### 5. Structured Property Patterns
|
|
92
|
+
|
|
93
|
+
#### Effects — `effects:[name, name(params...)]`
|
|
94
|
+
|
|
95
|
+
Apply animations on any leaf or container. No wrapper node needed.
|
|
96
|
+
Built-in `animation` values:
|
|
97
|
+
`fadeIn fadeOut fadeInDown fadeInUp fadeInLeft fadeInRight fadeOutDown fadeOutUp fadeOutLeft fadeOutRight slideInDown slideInUp slideInLeft slideInRight slideOutDown slideOutUp slideOutLeft slideOutRight zoomIn zoomOut zoomInDown zoomInUp zoomInLeft zoomInRight bounce bounceIn rotateIn rotateOut rotateInDownLeft rotateInDownRight rotateInUpLeft rotateInUpRight flipInX flipInY pulse flash heartBeat rubberBand shakeX shakeY swing tada wobble jello rollIn rollOut jackInTheBox lightSpeedIn lightSpeedOut`
|
|
98
|
+
|
|
99
|
+
`- effect animation:fadeIn duration:2` then indented children.
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
- image src:hero.jpg duration:3 effects:[fadeIn(1.5, ease-out, 2)]
|
|
103
|
+
- image src:hero.jpg duration:3 effects:[fadeIn, bounceIn(1, ease-out)]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Params: `(duration=1s, timingFunction=linear, iterationCount=1)` — all optional, comma-separated.
|
|
107
|
+
|
|
108
|
+
#### Events — `on:(when, state)`
|
|
109
|
+
|
|
110
|
+
Fire JS expression at a specific frame to mutate component state.
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
- script "Narration" on:(start, slide.current=0)
|
|
114
|
+
- script "Beat" on:(50%, slide.current++)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`when`: `start`/`end`/`50%` (percent) / `2.5s` (seconds value). `state`: any JS expression.
|
|
118
|
+
|
|
119
|
+
#### Lists — `spots:[n,n,n]`, `start:n`
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
- rhythm src:beat.mp3 spots:[0.5,1.2,1.9]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### Waypoints — `waypoints:[lat,lng,"label"; lat,lng,"label"]`
|
|
126
|
+
|
|
127
|
+
Semicolons separate waypoints, commas separate fields within one waypoint.
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
- map duration:3 waypoints:[37.77,-122.41,"SF";34.05,-118.24,"LA"]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### Duration shorthand
|
|
134
|
+
|
|
135
|
+
`duration:n` is syntactic sugar for `end:start+duration` (both `start` and `end` default to 0 when absent). If `end` is set explicitly, `duration` is ignored.
|
|
136
|
+
|
|
137
|
+
### 6. Long-Form Properties — Indented Code Fences
|
|
138
|
+
|
|
139
|
+
When a property value is too long for a single line (JSX, prompts, scripts, markdown source), use an **indented code fence** under the bullet:
|
|
140
|
+
|
|
141
|
+
```md
|
|
142
|
+
- component duration:4
|
|
143
|
+
~~~jsx jsx
|
|
144
|
+
<StatCounter value={42} />
|
|
145
|
+
~~~
|
|
146
|
+
- video start:5 volume:0
|
|
147
|
+
~~~prompt prompt
|
|
148
|
+
animation of a robot learning to walk, cinematic lighting
|
|
149
|
+
~~~
|
|
150
|
+
- audio src:bg.mp3 duration:10
|
|
151
|
+
~~~script
|
|
152
|
+
This is a longer narration that spans multiple lines.
|
|
153
|
+
~~~
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Syntax**: `~~~<lang> [propertyName=lang]`. If `propertyName` is omitted, it defaults to `lang`.
|
|
157
|
+
|
|
158
|
+
| Fence | Sets property | Use case |
|
|
159
|
+
|---|---|---|
|
|
160
|
+
| `~~~jsx jsx` or `~~~jsx` | `jsx` | Component JSX expression |
|
|
161
|
+
| `~~~prompt prompt` | `prompt` | TTI/TTV generation prompt |
|
|
162
|
+
| `~~~script script` or `~~~script` | `script` | Narration text on audio nodes |
|
|
163
|
+
| `~~~css stylesheet` | `stylesheet` | Global CSS (only valid at root level) |
|
|
164
|
+
| `~~~md <key>` | arbitrary | Markdown content for a specific key (e.g., `source` for `react-markdown`) |
|
|
165
|
+
|
|
166
|
+
### 7. Scene Metadata Block
|
|
167
|
+
|
|
168
|
+
Scene properties go on the **line(s) immediately below the heading**, before any child bullets:
|
|
169
|
+
|
|
170
|
+
```md
|
|
171
|
+
## MyScene
|
|
172
|
+
layout:parallel transition:fade transitionTime:1.2
|
|
173
|
+
title:"My Scene Title" instruction:"Cinematic intro"
|
|
174
|
+
- image src:bg.jpg duration:3
|
|
175
|
+
- script "Narration here"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Multiple metadata lines are allowed. They're parsed as space-separated key:value pairs per line.
|
|
179
|
+
|
|
180
|
+
### 8. Imports Block — Component Registry
|
|
181
|
+
|
|
182
|
+
A `` ~~~js imports `` code fence (typically at the end of the document) registers external React components for use in `jsx:` expressions:
|
|
183
|
+
|
|
184
|
+
```md
|
|
185
|
+
~~~js imports
|
|
186
|
+
export { PieChart } from "recharts"
|
|
187
|
+
export { StatCounter as Counter } from "stat-counter"
|
|
188
|
+
export function Hello({ name }) {
|
|
189
|
+
return <div style={{color: '#fff'}}>Hello {name}</div>;
|
|
190
|
+
}
|
|
191
|
+
~~~
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Patterns: `export { Name } from "spec"`, `export { Name as Alias } from "spec"`, `export function Name(...) { ... }`, `import { Name } from "spec"` (also works).
|
|
195
|
+
|
|
196
|
+
### 9. Variant Overrides Syntax
|
|
197
|
+
|
|
198
|
+
Variant sections (`# zh`, `# portrait`, etc.) provide root-level overrides. Leaf nodes use **variant-prefixed keys** or **bare variant keys** to override content per variant:
|
|
199
|
+
|
|
200
|
+
```md
|
|
201
|
+
# video
|
|
202
|
+
- script "English text" ← base value
|
|
203
|
+
zh:"中文文本" ← bare variant key: replaces "script" (primary key for audio)
|
|
204
|
+
- component jsx:"<Slide>{source}</Slide>"
|
|
205
|
+
~~~md source ← base value for key "source"
|
|
206
|
+
## English title
|
|
207
|
+
~~~
|
|
208
|
+
~~~md zh-source ← variant-prefixed: replaces "source" when variant=zh
|
|
209
|
+
## 中文标题
|
|
210
|
+
~~~
|
|
29
211
|
```
|
|
30
212
|
|
|
31
|
-
|
|
213
|
+
| Override type | Syntax | What it replaces |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| **Bare variant key** | `zh:"value"` | Node's primary content key (`script` for audio, `jsx` for component, `src` for image/video) |
|
|
216
|
+
| **Variant-prefixed key** | `zh-src:path` | Specific key matching the suffix (`zh-src` → replaces `src`) |
|
|
217
|
+
| **Variant-prefixed code fence** | `~~~md zh-source` | Same as above, for code-fence-backed properties |
|
|
218
|
+
|
|
219
|
+
### 10. Root Config Line
|
|
32
220
|
|
|
33
|
-
The line after `# video`
|
|
34
|
-
`key:value` pairs:
|
|
221
|
+
The line after `# video` holds canvas-level config as space-separated `key:value` pairs:
|
|
35
222
|
|
|
36
223
|
```markdown
|
|
37
224
|
# video
|
|
38
|
-
width:1920 height:1080 fps:30 layout:series
|
|
39
|
-
|
|
40
|
-
.bg { background: #000; }
|
|
41
|
-
~~~
|
|
42
|
-
subtitle:captions.vtt
|
|
225
|
+
width:1920 height:1080 fps:30 layout:series transition:fade transitionTime:1.2
|
|
226
|
+
subtitle:{src:"captions.vtt",type:"Typewriter",fontSize:48}
|
|
43
227
|
```
|
|
44
228
|
|
|
45
|
-
Supported keys: `width`, `height`, `fps`, `layout`, `tts`, `stt`, `tti`, `ttv`,
|
|
46
|
-
`transition`, `transitionTime`, `instruction`, `metadata`, `stylesheet`, `subtitle`.
|
|
229
|
+
Supported root keys: `width`, `height`, `fps`, `layout`, `tts`, `stt`, `tti`, `ttv`, `transition`, `transitionTime`, `instruction`, `metadata`, `stylesheet`, `subtitle`, `voices`.
|
|
47
230
|
|
|
48
|
-
Values containing spaces must be quoted with double or single quotes.
|
|
231
|
+
Values containing spaces (like JSON for `subtitle:` or `voices:`) must be quoted with double or single quotes, or wrapped in `{...}`/`[...]` brackets which the parser handles natively.
|
|
49
232
|
|
|
50
|
-
###
|
|
233
|
+
### 11. Frontmatter (Metadata Only)
|
|
234
|
+
|
|
235
|
+
Optional YAML block at the very top, delimited by `---`. **Does NOT affect video config** — only for metadata:
|
|
236
|
+
|
|
237
|
+
```yaml
|
|
238
|
+
---
|
|
239
|
+
title: My Campaign
|
|
240
|
+
description: Q4 product launch
|
|
241
|
+
hook: ...
|
|
242
|
+
conflict: ...
|
|
243
|
+
emotion: ...
|
|
244
|
+
ending: ...
|
|
245
|
+
---
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
All video configuration comes from the root config line (section 10), NOT from frontmatter.
|
|
249
|
+
|
|
250
|
+
### 12. Minimal Valid Document
|
|
51
251
|
|
|
52
252
|
```markdown
|
|
53
253
|
# video
|
|
54
|
-
width:
|
|
254
|
+
width:1080 height:1920 fps:30 layout:series
|
|
255
|
+
|
|
256
|
+
## Scene1
|
|
257
|
+
- image src:bg.jpg duration:3
|
|
258
|
+
- script "Hello world"
|
|
55
259
|
```
|
|
56
260
|
|
|
57
|
-
|
|
261
|
+
### 13. verify
|
|
262
|
+
`npx @lalalic/markcut verify book.md` to verify the markdown document is valid and can be rendered. It will check for missing required fields, invalid values, and other common issues.
|
|
58
263
|
|
|
59
264
|
## Template Variables
|
|
60
265
|
|
|
@@ -73,16 +278,18 @@ width:1920 height:1080
|
|
|
73
278
|
|
|
74
279
|
> Template variables are NOT resolved in root config keys, jsx, script, style,
|
|
75
280
|
> or other string fields — only in `src`, `prompt`, and `stylesheet`.
|
|
76
|
-
|
|
77
|
-
|
|
281
|
+
|
|
282
|
+
### Subtitle
|
|
283
|
+
|
|
284
|
+
Subtitles are configured at the root level as a VTT overlay. Set via `subtitle:` on the root config line, or `root.subtitle` in JSON.
|
|
78
285
|
|
|
79
286
|
| Field | Required | Type | Notes |
|
|
80
287
|
|---|---|---|---|
|
|
81
288
|
| `src` | yes | string | VTT path/URL, inline VTT body, or plain text |
|
|
82
|
-
| `type` | opt | string | caption animation: `Bounce`, `Fade`, `Typewriter`, `Colorful`,
|
|
83
|
-
| `fontSize` | opt | number | default 56 |
|
|
289
|
+
| `type` | opt | string | caption animation: `Bounce`, `Fade`, `Typewriter`, `Colorful`, `Glowing`, `Neon`, `Zoom`. Default: plain static caption |
|
|
290
|
+
| `fontSize` | opt | number \| string | default 56 (accepts CSS value like `"2em"` or number) |
|
|
84
291
|
| `fontFamily` | opt | string | font family |
|
|
85
|
-
| `fontStyle` | opt | string | `normal`, `italic`, `bold`,
|
|
292
|
+
| `fontStyle` | opt | string | `normal`, `italic`, `bold`, `bold italic` |
|
|
86
293
|
| `style` | opt | string | inline CSS for the overlay container |
|
|
87
294
|
|
|
88
295
|
> **HTML in cue text**: Cue text supports HTML tags with inline CSS, so you can style individual words:
|
|
@@ -94,53 +301,6 @@ width:1920 height:1080
|
|
|
94
301
|
|
|
95
302
|
If `src` is plain text (no `-->` markers), it renders as a single caption for the full video duration. The `type` field maps to a `remotion-subtitle` animation component — omit for a plain static caption.
|
|
96
303
|
|
|
97
|
-
|
|
98
|
-
### Imports block (recommended)
|
|
99
|
-
|
|
100
|
-
Use a `` ~~~js imports `` code fence at end of the document (or anywhere in the body). The block acts as a **component registry** — it re-exports components from external packages or defines them inline, making them available to JSX expressions throughout the video.
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
~~~js imports
|
|
104
|
-
export { PieChart } from "recharts"
|
|
105
|
-
export { BarChart, LineChart } from "recharts"
|
|
106
|
-
export { StatCounter as Counter } from "stat-counter"
|
|
107
|
-
|
|
108
|
-
export function Hello({ name }) {
|
|
109
|
-
return <div style={{color: '#fff'}}>Hello {name}</div>;
|
|
110
|
-
}
|
|
111
|
-
~~~
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Think of this block as the **index file** for the video's component scope. `export { Name } from "spec"` re-exports an external component (conceptually correct — the block is the public API of available components). `export function Name()` defines an inline component directly.
|
|
115
|
-
|
|
116
|
-
The imports block is the **primary** way to register components. The legacy YAML `imports:` array in frontmatter is still supported as a fallback, but the code block is preferred.
|
|
117
|
-
|
|
118
|
-
> **How it works**: The server extracts the imports block from the raw source, parses it with `parseImportsBlock`, then runs `bundleFromEntries` which creates a temp npm project, installs packages, and bundles everything into a single ESM file with esbuild. The resulting bundle URL is set on `root.imports` (e.g. `/.component-cache/be710e5c.js`). At render time, `MarkCut.useComponentRegistry` dynamically imports this URL and feeds the named exports to react-jsx-parser.
|
|
119
|
-
|
|
120
|
-
Supported patterns inside the block:
|
|
121
|
-
|
|
122
|
-
| Pattern | Effect |
|
|
123
|
-
|---|---|
|
|
124
|
-
| `export { Name } from "spec"` | Re-exports `Name` from the resolved source (recommended) |
|
|
125
|
-
| `export { Name as Alias } from "spec"` | Re-exports under `Alias` instead |
|
|
126
|
-
| `export { N1, N2 } from "spec"` | Re-exports multiple from the same source |
|
|
127
|
-
| `export default Name from "spec"` | Re-exports default export |
|
|
128
|
-
| `export function Name(...) { ... }` | Inline component definition |
|
|
129
|
-
| `export default function Name(...) { ... }` | Inline component definition (default) |
|
|
130
|
-
|
|
131
|
-
For compatibility, `import { Name } from "spec"` also works and produces the same result — both syntaxes register the name identically.
|
|
132
|
-
|
|
133
|
-
`from:` spec forms:
|
|
134
|
-
|
|
135
|
-
| Prefix | Resolved by bundler as |
|
|
136
|
-
|---|---|
|
|
137
|
-
| `pkg` or `npm:pkg` | npm package — `npm install pkg`, then `esbuild` re-exports it |
|
|
138
|
-
| `pkg@1.2.3` or `npm:pkg@1.2.3` | npm package with pinned version |
|
|
139
|
-
| `@scope/pkg` or `npm:@scope/pkg` | npm scoped package |
|
|
140
|
-
| `git:user/repo/path` | Raw specifier passed to esbuild; requires resolvable module |
|
|
141
|
-
| `github:user/repo/path` | Same as `git:` |
|
|
142
|
-
| `https://...`, `http://...`, path | Used as-is by esbuild |
|
|
143
|
-
|
|
144
304
|
## Key Reference (use these names)
|
|
145
305
|
|
|
146
306
|
| Key | Means | Applies to | Note
|
|
@@ -191,42 +351,6 @@ For compatibility, `import { Name } from "spec"` also works and produces the sam
|
|
|
191
351
|
|
|
192
352
|
## Type Catalog
|
|
193
353
|
|
|
194
|
-
Each type below shows: when to use, required keys, markdown syntax.
|
|
195
|
-
|
|
196
|
-
### `scene` (heading) — organizer (preferred)
|
|
197
|
-
|
|
198
|
-
When: always. `scene` is a container — scenes can nest inside other scenes via deeper headings (`##` → `###` → `####`).
|
|
199
|
-
|
|
200
|
-
Syntax:
|
|
201
|
-
|
|
202
|
-
```md
|
|
203
|
-
## <title>
|
|
204
|
-
layout:<x> [transition:<t> transitionTime:<n>] [instruction:..]
|
|
205
|
-
- <children>
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
Scene metadata (layout, instruction, transition) goes on the line(s) immediately below the heading, before any child bullets. This keeps the heading clean. `name` comes from the heading text (must be a single token — no spaces). For multi-word titles, use key-value `title:"Long Title"` on the metadata line. `title` optionally follows ` - ` in the heading (e.g. `## Chapter1 - The Beginning` splits to name=`Chapter1`, title=`The Beginning`).
|
|
209
|
-
|
|
210
|
-
For narration, use the `- script "..."` audio alias as a child of the scene (see [Narration / TTS](#narration--tts) below).
|
|
211
|
-
|
|
212
|
-
### `image`
|
|
213
|
-
|
|
214
|
-
When: photos, stills, title cards. Required: `src`, `duration`.
|
|
215
|
-
|
|
216
|
-
`- image src:cover.jpg duration:2 fit:cover`
|
|
217
|
-
|
|
218
|
-
### `video`
|
|
219
|
-
|
|
220
|
-
When: moving footage. Required: `src` + (`duration` or `endAt`).
|
|
221
|
-
|
|
222
|
-
`- video src:clip.mp4 startFrom:1 endAt:4 volume:0.8`
|
|
223
|
-
|
|
224
|
-
### `audio`
|
|
225
|
-
|
|
226
|
-
When: voiceover, BGM, SFX, TTS narration. Required: `src` + (`duration` or `endAt`).
|
|
227
|
-
|
|
228
|
-
`- audio src:bgm.mp3 duration:6 volume:0.4 isBackground`
|
|
229
|
-
|
|
230
354
|
### Narration / TTS
|
|
231
355
|
|
|
232
356
|
Narration text is set via the `script` field on audio nodes. There are three ways to provide it:
|
|
@@ -243,7 +367,7 @@ This is a shorthand that creates an audio node with the `script` field. Supports
|
|
|
243
367
|
|
|
244
368
|
```md
|
|
245
369
|
- script "Welcome to the course"
|
|
246
|
-
- script "Voiceover" volume:0.8 start:2
|
|
370
|
+
- script "Voiceover" volume:0.8 start:2 foreground:true
|
|
247
371
|
```
|
|
248
372
|
|
|
249
373
|
**3. Code fence `~~~script` on an audio node**
|
|
@@ -265,9 +389,12 @@ All three patterns produce an `audio` node with a `script` field. The pipeline's
|
|
|
265
389
|
When a `script` contains multiple lines matching `SpeakerName: text` format, the pipeline automatically expands it into a multi-turn dialogue where each line becomes a separate audio node with its own TTS generation. This allows different speakers to have different voices.
|
|
266
390
|
|
|
267
391
|
```md
|
|
268
|
-
- script
|
|
269
|
-
|
|
270
|
-
Ray:
|
|
392
|
+
- script
|
|
393
|
+
~~~script
|
|
394
|
+
Ray: Hello everyone and welcome
|
|
395
|
+
Alice: Good day to you all
|
|
396
|
+
Ray: Let's get started
|
|
397
|
+
~~~
|
|
271
398
|
```
|
|
272
399
|
|
|
273
400
|
The dialogue lines play sequentially. Each line is transcribed separately, and subtitles include the speaker prefix (e.g., `Ray: Hello everyone and welcome`).
|
|
@@ -291,112 +418,6 @@ voices:{"Ray":"--voice en-US-GuyNeural --rate +20%","Clone":"--voice clone-xxx -
|
|
|
291
418
|
|
|
292
419
|
The final TTS command for a speaker: `<root tts cli> <speaker voice flags>`.
|
|
293
420
|
|
|
294
|
-
### `subtitle` (root-level overlay)
|
|
295
|
-
|
|
296
|
-
Subtitles are configured at the root level as a VTT overlay, not as tree nodes. Set via YAML frontmatter `subtitle:` or `root.subtitle` in JSON.
|
|
297
|
-
|
|
298
|
-
The `type` field selects an animated caption component from `remotion-subtitle`:
|
|
299
|
-
|
|
300
|
-
| Value | Component |
|
|
301
|
-
|---|---|
|
|
302
|
-
| *(omit)* | `Caption` — plain static text |
|
|
303
|
-
| `Bounce` | `BounceCaption` — bouncing entrance |
|
|
304
|
-
| `Fade` | `FadeCaption` — fade in |
|
|
305
|
-
| `Typewriter` | `TypewriterCaption` — typewriter reveal |
|
|
306
|
-
| `Colorful` | `ColorfulCaption` — rainbow text |
|
|
307
|
-
| `Glowing` | `GlowingCaption` — glow effect |
|
|
308
|
-
| `Neon` | `NeonCaption` — neon sign |
|
|
309
|
-
| `Zoom` | `ZoomCaption` — zoom in |
|
|
310
|
-
|
|
311
|
-
> **HTML in cue text**: Each cue's text is rendered via `dangerouslySetInnerHTML`, so you can use HTML tags with inline CSS to style individual words:
|
|
312
|
-
> ```vtt
|
|
313
|
-
> 00:00:01.000 --> 00:00:03.000
|
|
314
|
-
> The <span style="color:#ff6b6b;font-weight:bold">quick</span> brown <span style="font-style:italic">fox</span> jumps over the lazy dog
|
|
315
|
-
> ```
|
|
316
|
-
> The `Typewriter` caption animation correctly respects HTML tag boundaries (character reveal skips over tags, only animates visible text).
|
|
317
|
-
|
|
318
|
-
Each cue is rendered as a separate `<Sequence>` for optimal performance — inactive cues consume zero CPU.
|
|
319
|
-
|
|
320
|
-
See [JSON Descriptive](json-descriptive.md#subtitle-root-level-overlay) for the full field reference.
|
|
321
|
-
|
|
322
|
-
## Effects on Any Node
|
|
323
|
-
|
|
324
|
-
Apply animations directly via the `effects` key on **any** node (leaf or container). No wrapper node needed.
|
|
325
|
-
|
|
326
|
-
```md
|
|
327
|
-
- image src:hero.jpg duration:3 effects:[fadeIn]
|
|
328
|
-
- component duration:2 jsx:"<Title />" effects:[bounceIn]
|
|
329
|
-
- video src:clip.mp4 duration:4 effects:[fadeIn, slideInLeft]
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
### Parameterized syntax
|
|
333
|
-
|
|
334
|
-
Add positional parameters in parentheses — comma-separated.
|
|
335
|
-
|
|
336
|
-
```md
|
|
337
|
-
- image src:card.jpg duration:3 effects:[fadeIn(1.5)]
|
|
338
|
-
- image src:card.jpg duration:3 effects:[fadeIn(1.5, ease-out)]
|
|
339
|
-
- image src:card.jpg duration:3 effects:[fadeIn(1.5, ease-out, 2)]
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
Order: `(duration, timingFunction, iterationCount)`
|
|
343
|
-
|
|
344
|
-
| Position | Parameter | Example |
|
|
345
|
-
|---|---|---|
|
|
346
|
-
| 1st | `duration` (seconds) | `fadeIn(1.5)` |
|
|
347
|
-
| 2nd | `timingFunction` | `fadeIn(1.5, ease-out)` |
|
|
348
|
-
| 3rd | `iterationCount` | `fadeIn(1.5, ease-out, 2)` |
|
|
349
|
-
|
|
350
|
-
All parameters are optional — omit trailing ones.
|
|
351
|
-
|
|
352
|
-
### Multiple effects
|
|
353
|
-
|
|
354
|
-
Effects are applied outermost-first (first in the array is the outermost wrapper):
|
|
355
|
-
|
|
356
|
-
```md
|
|
357
|
-
- image src:hero.jpg duration:3 effects:[fadeIn, bounceIn]
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
### `component`
|
|
361
|
-
|
|
362
|
-
When: JSX expression rendered at runtime with registered imports in scope. Required for non-background components: `duration` . The `jsx` value can come from an inline attribute or an indented code fence.
|
|
363
|
-
|
|
364
|
-
Components must be registered via a `` ~~~js imports `` code block. Usage is via `jsx:"<TagName ... />"` on the component node.
|
|
365
|
-
|
|
366
|
-
```md
|
|
367
|
-
~~~js imports
|
|
368
|
-
import { StatCounter } from "stat-counter"
|
|
369
|
-
import { Logo } from "github:myorg/design#Logo"
|
|
370
|
-
~~~
|
|
371
|
-
|
|
372
|
-
# JSX usage (references registered components as tags)
|
|
373
|
-
- component duration:1 jsx:"<StatCounter value={42} />"
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
### Code fence properties
|
|
377
|
-
|
|
378
|
-
Properties that are too long for a single line can be provided via an indented code fence under the bullet item. The fence language (`~~~<lang> <propName>`) specifies which property to set:
|
|
379
|
-
|
|
380
|
-
```md
|
|
381
|
-
- component duration:4 isBackground:true
|
|
382
|
-
~~~jsx jsx
|
|
383
|
-
<div style={{color:'#fff'}}>Hello</div>
|
|
384
|
-
~~~
|
|
385
|
-
|
|
386
|
-
- video start:5 volume:0
|
|
387
|
-
~~~prompt prompt
|
|
388
|
-
animation of a robot learning to walk, cinematic lighting
|
|
389
|
-
~~~
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
The fence syntax is `~~~<lang> <propName>`. If `propName` is omitted, it defaults to `lang`. Common patterns:
|
|
393
|
-
|
|
394
|
-
| Fence | Sets property | Use case |
|
|
395
|
-
|---|---|---|
|
|
396
|
-
| `~~~jsx jsx` or `~~~jsx` | `jsx` | Component JSX expression |
|
|
397
|
-
| `~~~prompt prompt` | `prompt` | TTI/TTV generation prompt |
|
|
398
|
-
| `~~~script script` or `~~~script` | `script` | Narration text on audio nodes |
|
|
399
|
-
|
|
400
421
|
### `rhythm`
|
|
401
422
|
|
|
402
423
|
When: beat-synced audio (music drops, music-reactive reveals). Required: `src`, `spots`, `children`.
|
|
@@ -410,15 +431,6 @@ Each child is assigned to a beat slot: child[i] starts at `spots[i]`, ends at `s
|
|
|
410
431
|
- image src:flash3.jpg
|
|
411
432
|
```
|
|
412
433
|
|
|
413
|
-
### `effect`
|
|
414
|
-
|
|
415
|
-
When: CSS keyframe animation wrapper. Required: `children`. `duration` falls back to children max.
|
|
416
|
-
|
|
417
|
-
`- effect animation:fadeIn duration:2` then indented children.
|
|
418
|
-
|
|
419
|
-
Built-in `animation` values:
|
|
420
|
-
`fadeIn fadeOut fadeInDown fadeInUp fadeInLeft fadeInRight fadeOutDown fadeOutUp fadeOutLeft fadeOutRight slideInDown slideInUp slideInLeft slideInRight slideOutDown slideOutUp slideOutLeft slideOutRight zoomIn zoomOut zoomInDown zoomInUp zoomInLeft zoomInRight bounce bounceIn rotateIn rotateOut rotateInDownLeft rotateInDownRight rotateInUpLeft rotateInUpRight flipInX flipInY pulse flash heartBeat rubberBand shakeX shakeY swing tada wobble jello rollIn rollOut jackInTheBox lightSpeedIn lightSpeedOut`
|
|
421
|
-
|
|
422
434
|
### `map`
|
|
423
435
|
|
|
424
436
|
When: animated route. Required: `duration`, `waypoints`.
|
|
@@ -491,19 +503,6 @@ Any valid JavaScript expression — assign values, increment counters, toggle bo
|
|
|
491
503
|
|
|
492
504
|
The expression is evaluated with all registered component IDs as scope variables. Each component variable is a Proxy whose property setter triggers a React re-render.
|
|
493
505
|
|
|
494
|
-
### JSON form
|
|
495
|
-
|
|
496
|
-
In the compiled stream tree, events are represented as an `on` field on any node:
|
|
497
|
-
|
|
498
|
-
```json
|
|
499
|
-
{
|
|
500
|
-
"type": "audio",
|
|
501
|
-
"src": "narration.mp3",
|
|
502
|
-
"duration": 3,
|
|
503
|
-
"on": { "when": "start", "state": "slide1.current=0" }
|
|
504
|
-
}
|
|
505
|
-
```
|
|
506
|
-
|
|
507
506
|
## Variants (Language / Platform / Any Override)
|
|
508
507
|
|
|
509
508
|
Produce different versions of the same video from a single markdown file
|
|
@@ -617,12 +616,10 @@ npx markcut render courseware.md --variant zh --output zh-video.mp4
|
|
|
617
616
|
|
|
618
617
|
### Combined variants
|
|
619
618
|
|
|
620
|
-
|
|
621
|
-
can override values set by a previous one. The directory is named with
|
|
622
|
-
all variants joined by `-`:
|
|
619
|
+
`--variant` value support multiple variants joined by `-` to merge multiple override sections. For example, `--variant zh-tiktok` merges the `# zh` and `# tiktok` sections into the base config.
|
|
623
620
|
|
|
624
621
|
```bash
|
|
625
|
-
npx markcut preview courseware.md --variant zh
|
|
622
|
+
npx markcut preview courseware.md --variant zh-tiktok
|
|
626
623
|
# Uses .markcut/courseware/zh-tiktok/ for variant artifacts
|
|
627
624
|
```
|
|
628
625
|
|
|
@@ -632,37 +629,6 @@ When a variant section provides a root-level key (e.g., `tts`), it merges
|
|
|
632
629
|
into the base config. Scene-level `tts` overrides root-level `tts`,
|
|
633
630
|
and the `ttsCli` option (if provided) overrides both.
|
|
634
631
|
|
|
635
|
-
### How overrides are resolved
|
|
636
|
-
|
|
637
|
-
1. The parser extracts `# video` as the base root and each `# <name>` as a
|
|
638
|
-
variant config map.
|
|
639
|
-
2. When `--variant zh` is used, the `# zh` section's root keys are merged
|
|
640
|
-
into the base root (e.g., `tts` is replaced).
|
|
641
|
-
3. `resolveVariantOverrides` walks the entire tree: for each node,
|
|
642
|
-
variant-prefixed keys (like `zh-src`) replace their unprefixed
|
|
643
|
-
counterparts. Bare variant keys (like `zh`) replace the node's
|
|
644
|
-
primary content key (see table above).
|
|
645
|
-
4. All `<variant>-*` keys and bare variant keys are stripped from the
|
|
646
|
-
output — the compiled tree contains only the resolved values.
|
|
647
|
-
5. The pipeline then runs TTS/STT/media generation as usual with the
|
|
648
|
-
overridden config and content.
|
|
649
|
-
|
|
650
|
-
### Directory layout for variant artifacts
|
|
651
|
-
|
|
652
|
-
```
|
|
653
|
-
.markcut/
|
|
654
|
-
├── generated/ ← shared, content-addressed
|
|
655
|
-
│ ├── tts/ ← TTS audio (same script → same file)
|
|
656
|
-
│ ├── media/ ← TTI/TTV images/videos
|
|
657
|
-
│ └── includes/ ← compiled sub-video JSON
|
|
658
|
-
├── courseware/ ← per-source-file
|
|
659
|
-
│ ├── components/ ← component bundles (shared)
|
|
660
|
-
│ ├── compiled.json ← default (en) compiled output
|
|
661
|
-
│ └── zh/
|
|
662
|
-
│ ├── compiled.json ← zh variant compiled output
|
|
663
|
-
│ └── subtitles.vtt ← zh variant subtitles
|
|
664
|
-
```
|
|
665
|
-
|
|
666
632
|
TTS audio files are content-addressed (hash of `script + CLI`), so
|
|
667
633
|
identical scripts across variants produce the same file. The merged
|
|
668
634
|
`subtitles.vtt` is per-variant because script content and timing offsets
|
|
@@ -676,16 +642,6 @@ differ.
|
|
|
676
642
|
4. `transitionTime` subtracted between `transitionSeries` items.
|
|
677
643
|
5. Containers derive duration from children (parallel=max, series=sum, ts=sum−overlap).
|
|
678
644
|
|
|
679
|
-
## Generation Workflow
|
|
680
|
-
|
|
681
|
-
1. Root: `# video` + `width:<n> height:<n> fps:<n> layout:<mode>` on the next line.
|
|
682
|
-
2. Frontmatter (optional): `---` block for root attrs + tts/stt pipeline config + `stylesheet`.
|
|
683
|
-
3. Component registrations: `` ~~~js imports `` block for external components.
|
|
684
|
-
4. Scenes via `##` with `layout:` metadata on the line below.
|
|
685
|
-
5. Leaves as `- type key:value ...` bullets indented under scenes.
|
|
686
|
-
6. Long values (JSX, prompts, scripts) use indented `~~~<lang> <propName>` code fences.
|
|
687
|
-
7. Verify each leaf has resolvable `duration`.
|
|
688
|
-
|
|
689
645
|
## Tween Animation
|
|
690
646
|
|
|
691
647
|
Animate numeric props over time by using `tween(from?, to, easing?)` expressions inside JSX usage expressions. Tweens resolve at render time using Remotion's `interpolate()`.
|
|
@@ -721,30 +677,7 @@ tween(#000, #FFF) — color interpolation
|
|
|
721
677
|
- `tween()` uses Remotion's `interpolate()` with the component's action duration.
|
|
722
678
|
- At frame 0, `tween(from, to)` returns `from`.
|
|
723
679
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
- [ ] Root has `width`, `height`, `fps`, `layout`.
|
|
727
|
-
- [ ] Every scene has ≥1 child or `script`.
|
|
728
|
-
- [ ] All values use explicit `key:value` syntax (no bare tokens).
|
|
729
|
-
- [ ] `start` only used inside `parallel` containers.
|
|
730
|
-
- [ ] No `src` on component nodes (use `jsx:` instead).
|
|
731
|
-
- [ ] Component registrations use `` ~~~js imports `` block — the ONLY supported method.
|
|
732
|
-
- [ ] Every component `jsx:` references a name registered in `` ~~~js imports ``.
|
|
733
|
-
- [ ] Every `jsx:` on a component node is a usage expression (JSX tag), not a definition.
|
|
734
|
-
- [ ] Inline component definitions go inside `` ~~~js imports `` as `export function Name(...) { ... }`.
|
|
735
|
-
- [ ] Scene names are single tokens (no spaces) — use `title:"..."` for multi-word titles.
|
|
736
|
-
- [ ] Code fence properties are indented under their parent bullet.
|
|
737
|
-
- [ ] Event targets (`id:name` on component) match the `id` used in `on:(when, id.prop=value)` expressions.
|
|
738
|
-
|
|
739
|
-
## Validation with CLI
|
|
740
|
-
|
|
741
|
-
Use `markcut verify` to parse and validate a descriptive markdown file without rendering:
|
|
742
|
-
|
|
743
|
-
```bash
|
|
744
|
-
markcut verify courseware.md
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
# common used components
|
|
680
|
+
# Common used components
|
|
748
681
|
- `react-markdown` — render markdown content, use plugins to extend functionality
|
|
749
682
|
- `remark-gfm` — support GitHub Flavored Markdown (tables, strikethrough, task lists)
|
|
750
683
|
- `remark-toc` — generate table of contents
|
|
@@ -756,7 +689,13 @@ markcut verify courseware.md
|
|
|
756
689
|
- `@remotion/shapes` — render shapes like arrows, circles, rectangles, etc
|
|
757
690
|
- `@remotion/starburst` — render starburst animations
|
|
758
691
|
|
|
759
|
-
#
|
|
692
|
+
# Best practices
|
|
693
|
+
- Markcut engine will automatically determine the duration of background streams, video, script audio. Don't set duration for them if no special requirements (e.g., speed up/down on purpose).
|
|
694
|
+
- global streams (BGM, Logo, Lip-sync video,...) should be set on root level, and set `isBackground:true` to let it loop to fill the whole video duration.
|
|
695
|
+
- Set `isBackground:true` for static vision content, such as image, when audio is playing
|
|
696
|
+
- adjust audio start time with `start` property to avoid audio cut when transition effect is applied on vision scene
|
|
697
|
+
|
|
698
|
+
|
|
760
699
|
~~~ example - avoid audio cut
|
|
761
700
|
- parallel
|
|
762
701
|
- image|video isBackground:true
|