@lalalic/markcut 1.0.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 (98) hide show
  1. package/.env.example +27 -0
  2. package/.github/user-steer.md +9 -0
  3. package/.vscode/settings.json +3 -0
  4. package/AGENTS.md +271 -0
  5. package/README.md +219 -0
  6. package/SKILL.md +209 -0
  7. package/docs/dynamic-components.md +191 -0
  8. package/docs/edit-mode.md +220 -0
  9. package/docs/json-descriptive.md +539 -0
  10. package/docs/label-mode.md +110 -0
  11. package/docs/markdown-descriptive.md +751 -0
  12. package/docs/templates.md +52 -0
  13. package/package.json +64 -0
  14. package/remotion.config.ts +5 -0
  15. package/scripts/artlist-dl.mjs +190 -0
  16. package/scripts/build-pipeline.sh +19 -0
  17. package/scripts/build-player.sh +20 -0
  18. package/src/Root.tsx +55 -0
  19. package/src/config.mjs +88 -0
  20. package/src/context/EventContext.tsx +168 -0
  21. package/src/context/index.tsx +42 -0
  22. package/src/descriptive/compiler.test.ts +1135 -0
  23. package/src/descriptive/compiler.ts +1230 -0
  24. package/src/descriptive/dsl.ts +455 -0
  25. package/src/descriptive/markdown.test.ts +866 -0
  26. package/src/descriptive/markdown.ts +674 -0
  27. package/src/descriptive/resolve.test.ts +951 -0
  28. package/src/descriptive/resolve.ts +891 -0
  29. package/src/entry.tsx +163 -0
  30. package/src/index.ts +4 -0
  31. package/src/player/browser.tsx +356 -0
  32. package/src/player/bundle/player.js +60259 -0
  33. package/src/player/bundler.mjs +269 -0
  34. package/src/player/label-server.mjs +599 -0
  35. package/src/player/pipeline.mjs +11123 -0
  36. package/src/player/pipeline.ts +117 -0
  37. package/src/player/server-shared.mjs +144 -0
  38. package/src/player/server.mjs +1006 -0
  39. package/src/render/cli-tools.ts +177 -0
  40. package/src/render/cli.mjs +628 -0
  41. package/src/schema/index.ts +259 -0
  42. package/src/types/Audio.tsx +56 -0
  43. package/src/types/Component.tsx +135 -0
  44. package/src/types/Effect.tsx +88 -0
  45. package/src/types/Folder.tsx +180 -0
  46. package/src/types/FrameSyncStyle.tsx +51 -0
  47. package/src/types/Image.tsx +51 -0
  48. package/src/types/Include.tsx +394 -0
  49. package/src/types/Map.tsx +252 -0
  50. package/src/types/Rhythm.tsx +58 -0
  51. package/src/types/Scene.tsx +42 -0
  52. package/src/types/Subtitle.tsx +218 -0
  53. package/src/types/Video.tsx +70 -0
  54. package/src/types/keyframes.ts +454 -0
  55. package/src/utils/__tests__/vtt.test.ts +129 -0
  56. package/src/utils/index.ts +168 -0
  57. package/src/utils/tween.ts +118 -0
  58. package/src/vision/cli.mjs +1187 -0
  59. package/src/vision/vision_prompts.md +67 -0
  60. package/tests/dsl.test.ts +317 -0
  61. package/tests/fixtures/audio.json +25 -0
  62. package/tests/fixtures/basic.json +27 -0
  63. package/tests/fixtures/component-all.json +38 -0
  64. package/tests/fixtures/components.json +38 -0
  65. package/tests/fixtures/effects.json +64 -0
  66. package/tests/fixtures/full.json +51 -0
  67. package/tests/fixtures/map.json +23 -0
  68. package/tests/fixtures/md/all-nodes.md +28 -0
  69. package/tests/fixtures/md/basic.md +6 -0
  70. package/tests/fixtures/md/component-imports.md +20 -0
  71. package/tests/fixtures/md/edge-cases.md +33 -0
  72. package/tests/fixtures/md/effects.md +17 -0
  73. package/tests/fixtures/md/frontmatter.md +20 -0
  74. package/tests/fixtures/md/full-feature.md +58 -0
  75. package/tests/fixtures/md/imports-block.md +19 -0
  76. package/tests/fixtures/md/include-main.md +11 -0
  77. package/tests/fixtures/md/include-sub.md +25 -0
  78. package/tests/fixtures/md/jsx-code-fence.md +21 -0
  79. package/tests/fixtures/md/map.md +11 -0
  80. package/tests/fixtures/md/nested-scenes.md +25 -0
  81. package/tests/fixtures/md/rhythm.md +17 -0
  82. package/tests/fixtures/md/scenes.md +16 -0
  83. package/tests/fixtures/md/tween.md +11 -0
  84. package/tests/fixtures/md/vars-test.md +6 -0
  85. package/tests/fixtures/scenes.json +40 -0
  86. package/tests/fixtures/subtitle.json +59 -0
  87. package/tests/fixtures/subvideo.json +59 -0
  88. package/tests/fixtures/templates/courseware.md +351 -0
  89. package/tests/fixtures/tween-visual.json +28 -0
  90. package/tests/fixtures/video-series.json +54 -0
  91. package/tests/md-descriptive.test.ts +742 -0
  92. package/tests/render.test.ts +985 -0
  93. package/tests/schema.test.ts +68 -0
  94. package/tests/server.test.ts +308 -0
  95. package/tests/utils.ts +391 -0
  96. package/tests/vitest.config.ts +18 -0
  97. package/tests/vitest.integration.config.ts +16 -0
  98. package/tsconfig.json +20 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Standalone pipeline bundle entry point.
3
+ *
4
+ * Bundled with esbuild into `src/player/pipeline.mjs` so plain-JS contexts
5
+ * (like the player server) can run the full descriptive resolve + compile
6
+ * pipeline without a TypeScript loader.
7
+ *
8
+ * Exports:
9
+ * - isDescriptiveRoot(data): boolean — heuristic format detection
10
+ * - resolveAndCompile(data, options): Promise<Root> — full pipeline
11
+ * - compileDescriptiveRoot(root): Root — pure compile (no I/O)
12
+ * - resolveAll(root, options): Promise<DescriptiveRoot>
13
+ *
14
+ * Build:
15
+ * bash scripts/build-pipeline.sh
16
+ */
17
+ import { compileDescriptiveRoot, resolveVariantOverrides, parseImportsBlock, extractDependencySpecs } from "../descriptive/compiler";
18
+ import { resolveAll, resolveIncludes } from "../descriptive/resolve";
19
+ import { parseMarkdownDescriptive, parseMarkdownVariants } from "../descriptive/markdown";
20
+ import type { DescriptiveRoot } from "../descriptive/compiler";
21
+ import type { Root } from "../schema/index";
22
+
23
+ export interface ResolveAndCompileOptions {
24
+ /** Base directory for resolving relative media src paths */
25
+ baseDir?: string;
26
+ /** Output directory for generated TTS audio / STT VTT files */
27
+ scriptOutputDir?: string;
28
+ /** Output directory for generated TTI/TTV media files */
29
+ mediaOutputDir?: string;
30
+ /** Output directory for pre-compiled include JSON files */
31
+ includeOutputDir?: string;
32
+ /** TTS CLI template override (default: edge-tts). Overrides root.tts. */
33
+ ttsCli?: string;
34
+ /** STT CLI template override (default: whisper). Overrides root.stt. */
35
+ sttCli?: string;
36
+ /** Separate output dir for the merged subtitles.vtt (e.g., per-variant).
37
+ * Per-clip VTTs stay in scriptOutputDir; defaults to scriptOutputDir. */
38
+ subtitleOutputDir?: string;
39
+ /** Variant chain to apply to include nodes (e.g. ["zh", "tiktok"]).
40
+ * When set, included .md files are parsed with variant awareness. */
41
+ variants?: string[];
42
+ }
43
+
44
+ /**
45
+ * Heuristic: detect whether a parsed JSON object is a descriptive root
46
+ * (vs. a pre-compiled stream tree Root).
47
+ *
48
+ * Signals:
49
+ * - has `layout` field (descriptive-only, stripped by compiler)
50
+ * - has `tts` or `stt` config (descriptive-only)
51
+ * - any direct child has `type: "scene"` with `layout`
52
+ * - any direct child has `type` of `series`/`parallel`/`transitionSeries`
53
+ */
54
+ export function isDescriptiveRoot(data: any): boolean {
55
+ if (!data || typeof data !== "object") return false;
56
+ if (data.layout || data.tts || data.stt) return true;
57
+
58
+ const children = data.children ?? [];
59
+ if (!Array.isArray(children)) return false;
60
+
61
+ // Descriptive containers as direct children
62
+ if (children.some((c: any) =>
63
+ c?.type === "series" || c?.type === "parallel" || c?.type === "transitionSeries"
64
+ )) return true;
65
+
66
+ // Scenes with descriptive layout
67
+ if (children.some((c: any) =>
68
+ c?.type === "scene" && (c.layout || c.tts)
69
+ )) return true;
70
+
71
+ return false;
72
+ }
73
+
74
+ /**
75
+ * Run the full pipeline: resolve (TTS/STT/durations) + compile → Root.
76
+ *
77
+ * Pure data in, compiled Root out. Side effects: writes TTS audio files
78
+ * and VTT files to `options.scriptOutputDir` when scripts/scripts exist.
79
+ */
80
+ export async function resolveAndCompile(
81
+ data: DescriptiveRoot,
82
+ options: ResolveAndCompileOptions = {},
83
+ ): Promise<Root> {
84
+ // 1. Async resolve: durations, TTS, STT, includes
85
+ const resolved = await resolveAll(data, {
86
+ baseDir: options.baseDir,
87
+ scriptOutputDir: options.scriptOutputDir,
88
+ mediaOutputDir: options.mediaOutputDir,
89
+ includeOutputDir: options.includeOutputDir,
90
+ ttsCli: options.ttsCli,
91
+ sttCli: options.sttCli,
92
+ subtitleOutputDir: options.subtitleOutputDir,
93
+ variants: options.variants,
94
+ });
95
+
96
+ // 2. Sync compile: descriptive → stream tree
97
+ const compiled = compileDescriptiveRoot(resolved);
98
+
99
+ return compiled;
100
+ }
101
+
102
+ /**
103
+ * Parse a descriptive markdown document into a DescriptiveRoot,
104
+ * then run the full resolve + compile pipeline.
105
+ *
106
+ * Convenience wrapper for markdown inputs.
107
+ */
108
+ export async function resolveAndCompileMarkdown(
109
+ markdown: string,
110
+ options: ResolveAndCompileOptions = {},
111
+ ): Promise<Root> {
112
+ const descriptive = parseMarkdownDescriptive(markdown);
113
+ return resolveAndCompile(descriptive, options);
114
+ }
115
+
116
+ export { compileDescriptiveRoot, resolveVariantOverrides, resolveAll, resolveIncludes, parseMarkdownDescriptive, parseMarkdownVariants, parseImportsBlock, extractDependencySpecs };
117
+ export type { DescriptiveRoot, Root, VariantParseResult } from "../descriptive/markdown";
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Shared utilities for markcut player servers.
4
+ *
5
+ * Extracts common code between server.mjs (edit/preview) and
6
+ * label-server.mjs (label annotation) into one place.
7
+ */
8
+
9
+ import { statSync, createReadStream, readFileSync, existsSync } from "node:fs";
10
+ import { extname } from "node:path";
11
+
12
+ // ─── MIME types ──────────────────────────────────────────────────────────
13
+ export const MIME = {
14
+ ".html": "text/html; charset=utf-8",
15
+ ".js": "text/javascript",
16
+ ".css": "text/css",
17
+ ".json": "application/json",
18
+ ".png": "image/png",
19
+ ".jpg": "image/jpeg",
20
+ ".jpeg": "image/jpeg",
21
+ ".webp": "image/webp",
22
+ ".gif": "image/gif",
23
+ ".mp4": "video/mp4",
24
+ ".webm": "video/webm",
25
+ ".mov": "video/quicktime",
26
+ ".mp3": "audio/mpeg",
27
+ ".wav": "audio/wav",
28
+ ".vtt": "text/vtt",
29
+ ".wasm": "application/wasm",
30
+ };
31
+
32
+ // ─── Extract scene info from compiled root ───────────────────────────────
33
+ export function extractScenes(root) {
34
+ const scenes = [];
35
+ let totalDuration = 0;
36
+
37
+ // Format 1: scenes as direct children of root
38
+ if (root.children?.length && !root.children.find(c => c.name === "scenes" || c.id === "scenes")) {
39
+ let offset = 0;
40
+ for (const s of root.children) {
41
+ if (!(s.type === "folder" || s.type === "scene" || s.children?.length)) continue;
42
+ if (s.isBackground) continue;
43
+ const leaf = (s.children || []).find(c => c.src && (c.type === "image" || c.type === "video"));
44
+ const action = leaf?.actions?.[0] || s.actions?.[0] || {};
45
+ const dur = (action.end || 5) - (action.start || 0);
46
+ scenes.push({
47
+ name: s.name || s.id || "scene",
48
+ start: offset,
49
+ end: offset + dur,
50
+ duration: dur,
51
+ src: leaf?.src || "",
52
+ mediaType: leaf?.type || "unknown",
53
+ });
54
+ offset += dur;
55
+ }
56
+ totalDuration = offset;
57
+ }
58
+
59
+ // Format 2: scenes wrapped in a "scenes" folder
60
+ const scenesFolder = root.children?.find(c => c.name === "scenes" || c.id === "scenes");
61
+ if (scenesFolder?.children && scenes.length === 0) {
62
+ let offset = 0;
63
+ for (const s of scenesFolder.children) {
64
+ const child = s.children?.[0] || {};
65
+ const action = child?.actions?.[0];
66
+ const dur = action ? (action.end - action.start) : 5;
67
+ scenes.push({
68
+ name: s.name,
69
+ start: offset,
70
+ end: offset + dur,
71
+ duration: dur,
72
+ src: child.src || "",
73
+ mediaType: child.type || "unknown",
74
+ });
75
+ offset += dur;
76
+ }
77
+ totalDuration = offset;
78
+ }
79
+
80
+ // Format 3: flat scenes array (labels.json format)
81
+ if (scenes.length === 0 && Array.isArray(root.scenes)) {
82
+ let offset = 0;
83
+ for (const s of root.scenes) {
84
+ const dur = (s.end ?? (s.start ?? 0) + 5) - (s.start ?? 0);
85
+ scenes.push({
86
+ name: s.name || "scene",
87
+ start: offset,
88
+ end: offset + dur,
89
+ duration: dur,
90
+ src: s.src || "",
91
+ mediaType: s.mediaType || "unknown",
92
+ });
93
+ offset += dur;
94
+ }
95
+ totalDuration = offset;
96
+ }
97
+
98
+ return { scenes, totalDuration };
99
+ }
100
+
101
+ // ─── Static file serving with range request support ──────────────────────
102
+ export function serveFile(req, res, filePath) {
103
+ if (!existsSync(filePath) || !statSync(filePath).isFile()) return false;
104
+
105
+ const ext = extname(filePath).toLowerCase();
106
+ const mime = MIME[ext] || "application/octet-stream";
107
+ const fileSize = statSync(filePath).size;
108
+ const cacheControl = (ext === ".js" || ext === ".html") ? "no-cache" : "public, max-age=3600";
109
+ const range = req.headers.range;
110
+
111
+ if (range) {
112
+ const parts = range.replace(/bytes=/, "").split("-");
113
+ const start = parseInt(parts[0], 10);
114
+ const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
115
+ const chunkSize = end - start + 1;
116
+ const stream = createReadStream(filePath, { start, end });
117
+ res.writeHead(206, {
118
+ "Content-Range": `bytes ${start}-${end}/${fileSize}`,
119
+ "Accept-Ranges": "bytes",
120
+ "Content-Length": chunkSize,
121
+ "Content-Type": mime,
122
+ "Cache-Control": cacheControl,
123
+ });
124
+ stream.pipe(res);
125
+ } else {
126
+ const data = readFileSync(filePath);
127
+ res.writeHead(200, {
128
+ "Content-Type": mime,
129
+ "Accept-Ranges": "bytes",
130
+ "Content-Length": fileSize,
131
+ "Cache-Control": cacheControl,
132
+ });
133
+ res.end(data);
134
+ }
135
+ return true;
136
+ }
137
+
138
+ // ─── Shutdown handler ────────────────────────────────────────────────────
139
+ export function handleShutdown(req, res, message = "Shutdown requested") {
140
+ res.writeHead(200, { "Content-Type": "application/json" });
141
+ res.end(JSON.stringify({ shutting_down: true }));
142
+ console.log(`\n 🚪 ${message}\n`);
143
+ process.exit(0);
144
+ }