@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,177 @@
1
+ /**
2
+ * 4 CLI wrappers for generation tasks: TTS, STT, TTI, TTV.
3
+ *
4
+ * Each function is a simple one-shot command wrapper — substitute {input}/{output}
5
+ * in the template, run the CLI, return the output path (or empty string on failure).
6
+ *
7
+ * This module exists so tests can mock each function individually instead of
8
+ * mocking low-level execSync/exec, which is brittle and slow.
9
+ *
10
+ * Default CLI templates are imported from src/config.mjs — the single source of truth.
11
+ * Override via MARKCUT_TTS_CLI / MARKCUT_STT_CLI / MARKCUT_TTI_CLI / MARKCUT_TTV_CLI.
12
+ */
13
+ import { execSync, exec } from "node:child_process";
14
+ import { existsSync, mkdirSync } from "node:fs";
15
+ import { dirname } from "node:path";
16
+ import {
17
+ DEFAULT_TTS_CLI,
18
+ DEFAULT_STT_CLI,
19
+ DEFAULT_TTI_CLI,
20
+ DEFAULT_TTV_CLI,
21
+ } from "../config.mjs";
22
+
23
+ export { DEFAULT_TTS_CLI, DEFAULT_STT_CLI, DEFAULT_TTI_CLI, DEFAULT_TTV_CLI };
24
+
25
+ // ── Helpers ───────────────────────────────────────────────────────────────
26
+
27
+ /**
28
+ * Smart placeholder substitution with quote-aware escaping.
29
+ *
30
+ * Detects whether {input}/{output} is wrapped in single or double quotes
31
+ * in the template and applies the correct escaping strategy:
32
+ *
33
+ * - Double-quoted: escape `"` → `\"` and keep `'` literal
34
+ * - Single-quoted: escape `'` → `'\''` (end quote, escaped quote, resume)
35
+ * - Unquoted: escape both `'` and `"`
36
+ *
37
+ * Examples:
38
+ * template: --text "{input}" input: it's "great"
39
+ * → --text "it's \"great\""
40
+ *
41
+ * template: --text '{input}' input: it's "great"
42
+ * → --text 'it'\''s "great"'
43
+ */
44
+ function substituteCli(template: string, input: string, output: string): string {
45
+ // Escape the output value (file paths rarely have quotes, but be safe)
46
+ const safeOutput = output;
47
+
48
+ // For input, detect quote context and escape accordingly
49
+ let safeInput: string;
50
+ // Look for {input} preceded by a quote character (ignoring whitespace)
51
+ const inputMatch = template.match(/(['"])\{input\}/);
52
+ if (inputMatch) {
53
+ const quote = inputMatch[1]!;
54
+ if (quote === '"') {
55
+ // Double-quoted: escape double quotes
56
+ safeInput = input.replace(/"/g, '\\"');
57
+ } else {
58
+ // Single-quoted: escape single quotes using '\'' sequence
59
+ safeInput = input.replace(/'/g, "'\\''");
60
+ }
61
+ } else {
62
+ // Unquoted: escape both
63
+ safeInput = input.replace(/"/g, '\\"').replace(/'/g, "'\\''");
64
+ }
65
+
66
+ return template
67
+ .replace(/\{input\}/g, safeInput)
68
+ .replace(/\{output\}/g, safeOutput);
69
+ }
70
+
71
+ // ── Exported functions ────────────────────────────────────────────────────
72
+
73
+ /**
74
+ * Generate TTS audio from text.
75
+ * @returns output path on success, empty string on failure.
76
+ */
77
+ export function generateTTS(text: string, outputPath: string, cli?: string): string {
78
+ mkdirSync(dirname(outputPath), { recursive: true });
79
+ const cmd = substituteCli(cli ?? DEFAULT_TTS_CLI, text, outputPath);
80
+ try {
81
+ execSync(cmd, { stdio: "pipe" });
82
+ } catch (e: any) {
83
+ console.warn(` ⚠ TTS failed: ${e.message}`);
84
+ return "";
85
+ }
86
+ return existsSync(outputPath) ? outputPath : "";
87
+ }
88
+
89
+ /**
90
+ * Run STT on an audio file to generate VTT subtitles.
91
+ * Completes when the CLI exits (VTT will be in outputDir).
92
+ */
93
+ export async function generateSTT(audioPath: string, outputDir: string, cli?: string): Promise<void> {
94
+ const cmd = substituteCli(cli ?? DEFAULT_STT_CLI, audioPath, outputDir);
95
+ try {
96
+ execSync(cmd, { stdio: "pipe" });
97
+ } catch (e: any) {
98
+ console.warn(` ⚠ STT failed: ${e.message}`);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Generate an image from a text prompt.
104
+ * @returns output path on success, empty string on failure.
105
+ */
106
+ export function generateTTI(prompt: string, outputPath: string, cli?: string): string {
107
+ mkdirSync(dirname(outputPath), { recursive: true });
108
+ const cmd = substituteCli(cli ?? DEFAULT_TTI_CLI, prompt, outputPath);
109
+ try {
110
+ execSync(cmd, {
111
+ encoding: "utf-8",
112
+ stdio: ["pipe", "pipe", "pipe"],
113
+ timeout: 300_000,
114
+ env: { ...process.env, TTI_CMD: cli ?? DEFAULT_TTI_CLI },
115
+ });
116
+ } catch (e: any) {
117
+ console.error(` ✗ TTI failed: ${e.message}`);
118
+ return "";
119
+ }
120
+ return existsSync(outputPath) ? outputPath : "";
121
+ }
122
+
123
+ /**
124
+ * Generate a video from a text prompt.
125
+ *
126
+ * When a custom `cli` template is provided, it's used as-is (template substitution
127
+ * + execSync). When no CLI is given (default), the function calls `generateTTI`
128
+ * to create an image from the prompt, then uses ffmpeg to produce a 3-second MP4.
129
+ *
130
+ * @param ttiCmd Optional TTI CLI template for the image step (defaults to DEFAULT_TTI_CLI).
131
+ * @returns output path on success, empty string on failure.
132
+ */
133
+ export function generateTTV(
134
+ prompt: string,
135
+ outputPath: string,
136
+ cli?: string,
137
+ ttiCmd?: string,
138
+ ): string {
139
+ mkdirSync(dirname(outputPath), { recursive: true });
140
+
141
+ if (!cli) {
142
+ // Default: use generateTTI to create an image, then ffmpeg to make a 3s MP4
143
+ const pngPath = outputPath.replace(/\.mp4$/, ".png");
144
+ const imageResult = generateTTI(prompt, pngPath, ttiCmd);
145
+ if (!imageResult || !existsSync(imageResult)) {
146
+ console.error(` ✗ TTV: TTI step produced no image for "${prompt.slice(0, 50)}..."`);
147
+ return "";
148
+ }
149
+ try {
150
+ execSync(
151
+ `ffmpeg -y -loop 1 -i "${pngPath}" -c:v libx264 -t 3 -pix_fmt yuv420p "${outputPath}"`,
152
+ { stdio: ["pipe", "pipe", "pipe"], timeout: 60_000 },
153
+ );
154
+ } catch (e: any) {
155
+ console.error(` ✗ TTV: ffmpeg failed: ${e.message}`);
156
+ return "";
157
+ }
158
+ // Clean up intermediate PNG
159
+ try { execSync(`rm "${pngPath}"`, { stdio: "pipe" }); } catch { /* best-effort */ }
160
+ return existsSync(outputPath) ? outputPath : "";
161
+ }
162
+
163
+ // Custom CLI mode: substitute template and run
164
+ const cmd = substituteCli(cli, prompt, outputPath);
165
+ try {
166
+ execSync(cmd, {
167
+ encoding: "utf-8",
168
+ stdio: ["pipe", "pipe", "pipe"],
169
+ timeout: 300_000,
170
+ env: { ...process.env, TTI_CMD: ttiCmd ?? DEFAULT_TTI_CLI },
171
+ });
172
+ } catch (e: any) {
173
+ console.error(` ✗ TTV failed: ${e.message}`);
174
+ return "";
175
+ }
176
+ return existsSync(outputPath) ? outputPath : "";
177
+ }