@lalalic/markcut 2.4.3 → 2.4.5

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/AGENTS.md CHANGED
@@ -242,9 +242,9 @@ npx vitest run tests/render.test.ts -t "Test Name" # single test
242
242
  1. **TransitionSeries + isBackground**: `TransitionSeries` from `@remotion/transitions` rejects non-Sequence/Transition children. Don't mix `isBackground:true` children with `isSeries:true, transition:"..."` at the same level.
243
243
  2. **Video endAt formula**: Must be `startFrom_frames + ((endAt_source - startFrom_source) * fps / playbackRate)` — changing to `endAt_source * fps` breaks slow-motion playback.
244
244
  3. **`signalstats` ffmpeg filter**: Doesn't output YAVG on all systems; use file size as proxy for visual content.
245
- 4. **Asset paths**: Local files are resolved via `staticFile()` files go in `public/` and are referenced without the `public/` prefix.
245
+ 4. **Asset paths**: All `src` paths in markdown are resolved relative to the `.md` file's directory. Preview mode serves files via a multi-directory HTTP server; render mode uses absolute paths directly (bypassing `staticFile()`). Never use a `public/` prefix in markdown `src`.
246
246
  5. **Map rendering**: Depends on external tile availability; may produce blank frames offline.
247
- 6. **CLI md render needs three things preview gets for free**: (a) `parseMarkdownVariants(raw)` (returns `{base, variants}`) — `parseMarkdownDescriptive` returns the root directly; (b) bundling the ` ```js imports ``` ` block via `bundleFromEntries` and setting `root.imports` (otherwise jsx components are unregistered → slides render as unstyled black frames); (c) staging `.markcut/` assets into `public/.render-assets/` and rewriting srcs to relative paths (remotion render only serves its publicDir). All three live in `src/render/cli.mjs`; the import-map shim for shared react/remotion instances is `src/utils/component-import-map.ts` (must stay in sync with `player/browser.tsx` + `bundler.mjs getSharedExternals`).
247
+ 6. **CLI md render needs two things preview gets for free**: (a) `parseMarkdownVariants(raw)` (returns `{base, variants}`) — `parseMarkdownDescriptive` returns the root directly; (b) bundling the ` ```js imports ``` ` block via `bundleFromEntries` and setting `root.imports` (otherwise jsx components are unregistered → slides render as unstyled black frames). Asset staging (`stageLocalAssets`) was removed render now keeps absolute paths from `resolve.ts`, bypassing `staticFile()` entirely. All live in `src/render/cli.mjs`; the import-map shim for shared react/remotion instances is `src/utils/component-import-map.ts` (must stay in sync with `player/browser.tsx` + `bundler.mjs getSharedExternals`).
248
248
  7. **Publishing**: verify `npx @lalalic/markcut render <md>` from a clean temp dir before/after publish — v1.1.1 shipped with cli.mjs/pipeline.mjs out of sync and md rendering was completely broken. Also beware stale npx caches (`~/.npm/_npx`) silently running old code.
249
249
  8. **Clean break from `actions[]`**: leaf timing is now flat base fields (`start`/`end`/`duration`/`startFrom`/`endAt`). Old compiled JSON carrying `actions:[{...}]` no longer parses into a duration — re-compile from markdown/descriptive JSON, or migrate `actions[0]` → flat fields (drop `id`/`volume`/`style`/`effectId`; keep `start`/`end`/`startFrom`/`endAt`/`loop`).
250
250
 
package/README.md CHANGED
@@ -91,10 +91,7 @@ Inline components can be defined entirely in the imports block using `export fun
91
91
 
92
92
  | Document | What it covers |
93
93
  |---|---|
94
- | [docs/json-descriptive.md](docs/json-descriptive.md) | Full JSON descriptive schema reference |
95
94
  | [docs/markdown-strict-descriptive.md](docs/markdown-strict-descriptive.md) | Markdown descriptive syntax reference |
96
- | [docs/label-mode.md](docs/label-mode.md) | Label mode player and workflow |
97
- | [docs/edit-mode.md](docs/edit-mode.md) | Live edit mode with SSE reload |
98
95
 
99
96
 
100
97
  ## Variants
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lalalic/markcut",
3
- "version": "2.4.3",
3
+ "version": "2.4.5",
4
4
  "description": "Markdown-to-video engine. Describe scenes in markdown, get a rendered video.",
5
5
  "bin": {
6
6
  "markcut": "bin/markcut"
@@ -43,7 +43,7 @@ This section defines the markcut Markdown Descriptive grammar. An agent should r
43
43
  |---|---|---|---|
44
44
  | H1 | `# video` | **Root** — video canvas definition | Exactly one `# video` required. Everything else is nested inside this section. |
45
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`. |
46
+ | H2 | `## <name>` | **Scene** — top-level scene container | `name` = single word (no spaces).|
47
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
48
  | H4+ | `#### <name>` | **Deeply nested** — further nesting | Works identically; depth reflects nesting level. |
49
49
 
@@ -258,8 +258,14 @@ width:1080 height:1920 fps:30 layout:series
258
258
  - script "Hello world"
259
259
  ```
260
260
 
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.
261
+ ### 13. `src` Path Resolution — Context Root
262
+ all src paths are resolved relative to the markdown file's location. For example, if your markdown file is at `./videos/course.md`, then `src:./assets/bg.jpg` resolves to `./videos/assets/bg.jpg`. Absolute paths (starting with `/`) are resolved relative to the project root.
263
+
264
+
265
+ ### 14. verify
266
+ - all assets path are resolved relative to the markdown file's location.
267
+ - `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.
268
+
263
269
 
264
270
  ## Template Variables
265
271
 
@@ -7,9 +7,8 @@
7
7
  * (TTS/STT/durations)
8
8
  */
9
9
  import { execSync, spawn } from "node:child_process";
10
- import { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync } from "node:fs";
11
- import { createHash } from "node:crypto";
12
- import { resolve, dirname, join, extname } from "node:path";
10
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
11
+ import { resolve, dirname, join } from "node:path";
13
12
  import { fileURLToPath } from "node:url";
14
13
  import {
15
14
  DEFAULT_TTS_CLI, DEFAULT_STT_CLI, DEFAULT_TTI_CLI, DEFAULT_TTV_CLI,
@@ -96,62 +95,10 @@ Commands:
96
95
  * Use --verbose to see every frame line (original behavior).
97
96
  */
98
97
 
99
- /**
100
- * Resolve a potentially relative source path against baseDir, skipping
101
- * absolute paths, URLs, and data URIs.
102
- */
103
- function resolveAssetPath(src, baseDir) {
104
- if (!src || typeof src !== "string") return null;
105
- // Already absolute on disk
106
- if (src.startsWith("/")) return src;
107
- // URL or data URI — can't stage locally
108
- if (/^(https?:|data:|blob:)/.test(src)) return null;
109
- // Relative path: resolve against the input file's directory
110
- const abs = resolve(baseDir, src);
111
- if (existsSync(abs)) return abs;
112
- return null;
113
- }
114
-
115
- /**
116
- * Stage all local src files (absolute and relative) into
117
- * ROOT/public/.render-assets/ so `npx remotion render` (publicDir = ROOT/public)
118
- * can serve them. Relative paths are resolved against `baseDir` (the input
119
- * file's directory). The preview server handles these paths via multi-root
120
- * serving; the render CLI needs them staged into the one public dir Remotion
121
- * knows about.
122
- */
123
- function stageLocalAssets(tree, baseDir) {
124
- const assetsDir = join(ROOT, "public", ".render-assets");
125
- const seen = new Map();
126
- const walk = (node) => {
127
- if (!node || typeof node !== "object") return;
128
- if (typeof node.src === "string") {
129
- const absPath = resolveAssetPath(node.src, baseDir);
130
- if (absPath) {
131
- let staged = seen.get(absPath);
132
- if (!staged) {
133
- const name = createHash("md5").update(absPath).digest("hex").slice(0, 12) + extname(absPath);
134
- mkdirSync(assetsDir, { recursive: true });
135
- copyFileSync(absPath, join(assetsDir, name));
136
- staged = `.render-assets/${name}`;
137
- seen.set(absPath, staged);
138
- }
139
- node.src = staged;
140
- }
141
- }
142
- for (const value of Object.values(node)) {
143
- if (value && typeof value === "object") walk(value);
144
- }
145
- };
146
- walk(tree);
147
- return tree;
148
- }
149
-
150
- function renderOne(streamTree, outputPath, verbose, baseDir) {
151
- const adapted = stageLocalAssets(JSON.parse(JSON.stringify(streamTree)), baseDir);
98
+ function renderOne(streamTree, outputPath, verbose) {
152
99
  const tmpProps = join(ROOT, ".tmp", "render-stream.json");
153
100
  mkdirSync(dirname(tmpProps), { recursive: true });
154
- writeFileSync(tmpProps, JSON.stringify({ root: adapted }));
101
+ writeFileSync(tmpProps, JSON.stringify({ root: streamTree }));
155
102
 
156
103
  mkdirSync(dirname(outputPath), { recursive: true });
157
104
 
@@ -409,19 +356,18 @@ edit=${DEFAULT_EDIT_CLI}`);
409
356
  const { bundleFromEntries } = await import("../player/bundler.mjs");
410
357
  const entries = parseImportsBlock(rawSource);
411
358
  const extraSpecs = extractDependencySpecs(rawSource);
412
- const bundleDir = join(ROOT, "public", ".render-assets");
359
+ const bundleDir = join(ROOT, ".tmp", "component-bundle");
413
360
  mkdirSync(bundleDir, { recursive: true });
414
361
  const bundle = await bundleFromEntries(entries, extraSpecs, rawSource, bundleDir);
415
362
  if (bundle.url) {
416
- streamTree.imports = ".render-assets/" + bundle.url.split("/").pop();
363
+ streamTree.imports = join(bundleDir, bundle.url.split("/").pop());
417
364
  console.log(` \u2705 components \u2192 ${bundle.exports.join(", ")}`);
418
365
  }
419
366
  }
420
367
  }
421
368
 
422
369
  const output = args.output ? resolve(args.output) : join(ROOT, "out", "video.mp4");
423
- const fileDir = dirname(resolve(args.file));
424
- await renderOne(streamTree, output, args.verbose, fileDir);
370
+ await renderOne(streamTree, output, args.verbose);
425
371
 
426
372
  console.log("\n✅ Render complete.");
427
373
  process.exit(0);