@makeitvisible/cli 0.1.0 → 0.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.
@@ -0,0 +1,367 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
2
+ import { dirname, resolve, join } from 'path';
3
+ import { fileURLToPath, pathToFileURL } from 'url';
4
+
5
+ // src/video/renderer.ts
6
+
7
+ // src/video/types.ts
8
+ var RESOLUTIONS = {
9
+ "1080p": { width: 1920, height: 1080 },
10
+ "720p": { width: 1280, height: 720 },
11
+ "480p": { width: 854, height: 480 },
12
+ square: { width: 1080, height: 1080 },
13
+ vertical: { width: 1080, height: 1920 }
14
+ };
15
+ var DEFAULT_THEME = {
16
+ primaryColor: "#10B981",
17
+ // Green accent
18
+ backgroundColor: "#0A0A0B",
19
+ // Dark background
20
+ foregroundColor: "#FAFAFA",
21
+ // Light text
22
+ accentColor: "#10B981",
23
+ // Green accent
24
+ mutedColor: "#A1A1A6",
25
+ // Secondary text
26
+ cardColor: "#141415",
27
+ // Card background
28
+ borderColor: "#1C1C1E",
29
+ // Border
30
+ fontFamily: "Inter, system-ui, sans-serif"
31
+ };
32
+
33
+ // src/video/storyboard.ts
34
+ var FPS = 30;
35
+ var DURATIONS = {
36
+ title: 4,
37
+ overview: 6,
38
+ walkthrough: 8,
39
+ code: 6,
40
+ outro: 4
41
+ };
42
+ function generateStoryboard(artifact, theme = {}, codeSnippets, uiScenes) {
43
+ const mergedTheme = { ...DEFAULT_THEME, ...theme };
44
+ const scenes = [];
45
+ const snippets = codeSnippets || artifact.codeSnippets || [];
46
+ scenes.push(createTitleScene(artifact));
47
+ scenes.push(createOverviewScene(artifact));
48
+ const walkthroughScenes = createWalkthroughScenes(artifact);
49
+ scenes.push(...walkthroughScenes);
50
+ if (snippets.length > 0) {
51
+ const codeHighlightScenes = createCodeHighlightScenes(snippets);
52
+ scenes.push(...codeHighlightScenes);
53
+ }
54
+ if (artifact.context.affectedComponents.length > 0) {
55
+ scenes.push(createCodeScene(artifact));
56
+ }
57
+ scenes.push(createOutroScene(artifact));
58
+ const totalDurationInFrames = scenes.reduce((sum, s) => sum + s.durationInFrames, 0);
59
+ const enhancedArtifact = {
60
+ ...artifact,
61
+ codeSnippets: snippets
62
+ };
63
+ return {
64
+ title: artifact.title,
65
+ totalDurationInFrames,
66
+ scenes,
67
+ artifact: enhancedArtifact,
68
+ theme: mergedTheme
69
+ };
70
+ }
71
+ function createTitleScene(artifact) {
72
+ return {
73
+ id: "title",
74
+ type: "title",
75
+ title: artifact.title,
76
+ content: artifact.description,
77
+ durationInFrames: DURATIONS.title * FPS,
78
+ props: {
79
+ source: artifact.source,
80
+ keywords: artifact.context.keywords.slice(0, 5)
81
+ }
82
+ };
83
+ }
84
+ function createOverviewScene(artifact) {
85
+ return {
86
+ id: "overview",
87
+ type: "overview",
88
+ title: "What Changed",
89
+ content: artifact.context.summary,
90
+ durationInFrames: DURATIONS.overview * FPS,
91
+ props: {
92
+ breakingChanges: artifact.context.breakingChanges,
93
+ componentCount: artifact.context.affectedComponents.length
94
+ }
95
+ };
96
+ }
97
+ function createWalkthroughScenes(artifact) {
98
+ const scenes = [];
99
+ const details = artifact.context.technicalDetails;
100
+ const chunks = chunkArray(details, 3);
101
+ chunks.forEach((chunk, index) => {
102
+ scenes.push({
103
+ id: `walkthrough-${index + 1}`,
104
+ type: "walkthrough",
105
+ title: index === 0 ? "How It Works" : `Details (${index + 1})`,
106
+ content: chunk.join("\n\n"),
107
+ durationInFrames: DURATIONS.walkthrough * FPS,
108
+ props: {
109
+ steps: chunk,
110
+ stepIndex: index
111
+ }
112
+ });
113
+ });
114
+ if (scenes.length === 0) {
115
+ scenes.push({
116
+ id: "walkthrough-1",
117
+ type: "walkthrough",
118
+ title: "How It Works",
119
+ content: artifact.context.summary,
120
+ durationInFrames: DURATIONS.walkthrough * FPS,
121
+ props: {
122
+ steps: [artifact.context.summary],
123
+ stepIndex: 0
124
+ }
125
+ });
126
+ }
127
+ return scenes;
128
+ }
129
+ function createCodeHighlightScenes(snippets) {
130
+ const scenes = [];
131
+ const displaySnippets = snippets.slice(0, 6);
132
+ const chunkedSnippets = chunkArray(displaySnippets, 2);
133
+ chunkedSnippets.forEach((chunk, index) => {
134
+ scenes.push({
135
+ id: `code-highlight-${index + 1}`,
136
+ type: "code-highlight",
137
+ title: index === 0 ? "Code Changes" : `More Changes (${index + 1})`,
138
+ content: chunk.map((s) => s.description || s.file).join(", "),
139
+ durationInFrames: DURATIONS.code * FPS,
140
+ props: {
141
+ snippets: chunk.map((s) => ({
142
+ file: s.file,
143
+ code: s.code,
144
+ language: s.language || detectLanguage(s.file),
145
+ description: s.description,
146
+ changeType: s.changeType,
147
+ startLine: s.startLine
148
+ }))
149
+ }
150
+ });
151
+ });
152
+ return scenes;
153
+ }
154
+ function detectLanguage(filename) {
155
+ const ext = filename.split(".").pop()?.toLowerCase() || "";
156
+ const langMap = {
157
+ ts: "typescript",
158
+ tsx: "typescript",
159
+ js: "javascript",
160
+ jsx: "javascript",
161
+ vue: "vue",
162
+ py: "python",
163
+ rb: "ruby",
164
+ go: "go",
165
+ rs: "rust",
166
+ java: "java",
167
+ css: "css",
168
+ scss: "scss",
169
+ html: "html",
170
+ json: "json",
171
+ md: "markdown",
172
+ yaml: "yaml",
173
+ yml: "yaml"
174
+ };
175
+ return langMap[ext] || "text";
176
+ }
177
+ function createCodeScene(artifact) {
178
+ const components = artifact.context.affectedComponents;
179
+ const displayComponents = components.slice(0, 8);
180
+ return {
181
+ id: "code",
182
+ type: "code",
183
+ title: "Files Changed",
184
+ content: `${components.length} file${components.length !== 1 ? "s" : ""} affected`,
185
+ durationInFrames: DURATIONS.code * FPS,
186
+ props: {
187
+ files: displayComponents,
188
+ totalFiles: components.length,
189
+ hasMore: components.length > 8
190
+ }
191
+ };
192
+ }
193
+ function createOutroScene(artifact) {
194
+ const cta = artifact.guidelines[0] || "Learn more about this feature";
195
+ return {
196
+ id: "outro",
197
+ type: "outro",
198
+ title: "Summary",
199
+ content: cta,
200
+ durationInFrames: DURATIONS.outro * FPS,
201
+ props: {
202
+ guidelines: artifact.guidelines.slice(0, 3),
203
+ source: artifact.source
204
+ }
205
+ };
206
+ }
207
+ function chunkArray(array, size) {
208
+ const chunks = [];
209
+ for (let i = 0; i < array.length; i += size) {
210
+ chunks.push(array.slice(i, i + size));
211
+ }
212
+ return chunks;
213
+ }
214
+ function getVideoDurationSeconds(storyboard) {
215
+ return storyboard.totalDurationInFrames / FPS;
216
+ }
217
+ function formatDuration(seconds) {
218
+ const mins = Math.floor(seconds / 60);
219
+ const secs = Math.floor(seconds % 60);
220
+ return `${mins}:${secs.toString().padStart(2, "0")}`;
221
+ }
222
+
223
+ // src/video/renderer.ts
224
+ var __dirname$1 = dirname(fileURLToPath(import.meta.url));
225
+ async function generateVideo(input) {
226
+ const startTime = Date.now();
227
+ try {
228
+ const { artifact, codeSnippets, config, theme = {}, verbose } = input;
229
+ const mergedTheme = { ...DEFAULT_THEME, ...theme };
230
+ const storyboard = generateStoryboard(artifact, mergedTheme, codeSnippets);
231
+ if (verbose) {
232
+ console.log(`
233
+ Storyboard generated:`);
234
+ console.log(` - Scenes: ${storyboard.scenes.length}`);
235
+ console.log(` - Duration: ${formatDuration(getVideoDurationSeconds(storyboard))}`);
236
+ }
237
+ const outputDir = resolve(config.outputDir);
238
+ if (!existsSync(outputDir)) {
239
+ mkdirSync(outputDir, { recursive: true });
240
+ }
241
+ const storyboardPath = join(outputDir, "storyboard.json");
242
+ writeFileSync(storyboardPath, JSON.stringify(storyboard, null, 2));
243
+ if (verbose) {
244
+ console.log(` - Storyboard saved: ${storyboardPath}`);
245
+ }
246
+ const propsPath = join(outputDir, "video-props.json");
247
+ const videoProps = {
248
+ storyboard,
249
+ resolution: config.resolution || RESOLUTIONS["1080p"],
250
+ fps: config.fps || 30
251
+ };
252
+ writeFileSync(propsPath, JSON.stringify(videoProps, null, 2));
253
+ if (verbose) {
254
+ console.log(` - Props saved: ${propsPath}`);
255
+ }
256
+ const filename = config.filename || "video";
257
+ const format = config.format || "mp4";
258
+ const videoPath = join(outputDir, `${filename}.${format}`);
259
+ const renderResult = await tryRenderWithRemotion({
260
+ storyboard,
261
+ outputPath: videoPath,
262
+ config,
263
+ verbose
264
+ });
265
+ if (renderResult.success) {
266
+ return {
267
+ success: true,
268
+ videoPath: renderResult.videoPath,
269
+ posterPath: renderResult.posterPath,
270
+ durationMs: Date.now() - startTime
271
+ };
272
+ }
273
+ const durationMs = Date.now() - startTime;
274
+ return {
275
+ success: true,
276
+ videoPath: propsPath,
277
+ durationMs
278
+ };
279
+ } catch (error) {
280
+ return {
281
+ success: false,
282
+ error: error instanceof Error ? error.message : String(error),
283
+ durationMs: Date.now() - startTime
284
+ };
285
+ }
286
+ }
287
+ async function tryRenderWithRemotion(options) {
288
+ const { storyboard, outputPath, config, verbose } = options;
289
+ const remotionAvailable = await isRemotionAvailable();
290
+ if (!remotionAvailable) {
291
+ if (verbose) {
292
+ console.log("\n \u26A0\uFE0F Remotion dependencies not installed.");
293
+ console.log(" To enable automatic video rendering, install Remotion in the CLI:");
294
+ console.log("");
295
+ console.log(" cd $(npm root -g)/@makeitvisible/cli && npm install");
296
+ console.log("");
297
+ console.log(" Or install locally:");
298
+ console.log(" npm install @remotion/bundler @remotion/renderer @remotion/cli remotion react react-dom");
299
+ console.log("");
300
+ console.log(" For now, you can render manually with the generated video-props.json");
301
+ }
302
+ return { success: false, error: "Remotion not available" };
303
+ }
304
+ try {
305
+ const renderPath = pathToFileURL(join(__dirname$1, "remotion", "render.js")).href;
306
+ const { renderVideo } = await import(
307
+ /* @vite-ignore */
308
+ renderPath
309
+ );
310
+ const resolution = config.resolution || RESOLUTIONS["1080p"];
311
+ let resolutionKey = "1080p";
312
+ if (resolution.width === RESOLUTIONS["720p"].width) {
313
+ resolutionKey = "720p";
314
+ } else if (resolution.width === RESOLUTIONS.square.width && resolution.height === RESOLUTIONS.square.height) {
315
+ resolutionKey = "square";
316
+ } else if (resolution.width === RESOLUTIONS.vertical.width && resolution.height === RESOLUTIONS.vertical.height) {
317
+ resolutionKey = "vertical";
318
+ }
319
+ if (verbose) {
320
+ console.log(`
321
+ Rendering video with Remotion...`);
322
+ console.log(` Resolution: ${resolution.width}x${resolution.height}`);
323
+ }
324
+ const result = await renderVideo({
325
+ storyboard,
326
+ outputPath,
327
+ resolution: resolutionKey,
328
+ codec: "h264",
329
+ verbose,
330
+ onProgress: (progress) => {
331
+ if (verbose) {
332
+ process.stdout.write(`\r Rendering: ${Math.round(progress * 100)}%`);
333
+ }
334
+ }
335
+ });
336
+ if (verbose && result.success) {
337
+ console.log("\n");
338
+ }
339
+ return result;
340
+ } catch (error) {
341
+ if (verbose) {
342
+ console.log(`
343
+ Remotion render failed: ${error instanceof Error ? error.message : error}`);
344
+ }
345
+ return {
346
+ success: false,
347
+ error: error instanceof Error ? error.message : String(error)
348
+ };
349
+ }
350
+ }
351
+ async function isRemotionAvailable() {
352
+ try {
353
+ await import('@remotion/bundler');
354
+ await import('@remotion/renderer');
355
+ await import('remotion');
356
+ return true;
357
+ } catch (error) {
358
+ if (process.env.DEBUG_REMOTION) {
359
+ console.error("Remotion availability check failed:", error);
360
+ }
361
+ return false;
362
+ }
363
+ }
364
+
365
+ export { generateVideo, isRemotionAvailable };
366
+ //# sourceMappingURL=renderer.js.map
367
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/video/types.ts","../../src/video/storyboard.ts","../../src/video/renderer.ts"],"names":["__dirname"],"mappings":"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","file":"renderer.js","sourcesContent":["/**\n * Video Generation Types\n *\n * Types for video generation from artifacts.\n */\n\nimport type { ArtifactPayload } from \"../api/client.js\";\n\n/**\n * Code snippet with metadata for display in videos\n */\nexport interface CodeSnippet {\n /** File path */\n file: string;\n /** Code content */\n code: string;\n /** Starting line number (optional) */\n startLine?: number;\n /** Language for syntax highlighting */\n language?: string;\n /** Description of what this code shows */\n description?: string;\n /** Whether this is an addition (green), deletion (red), or neutral */\n changeType?: \"added\" | \"deleted\" | \"modified\" | \"context\";\n}\n\n/**\n * Enhanced artifact payload with code content for video generation\n */\nexport interface VideoArtifactPayload extends ArtifactPayload {\n /** Relevant code snippets extracted from the analysis */\n codeSnippets?: CodeSnippet[];\n /** Full diff content (for diff-based analysis) */\n diffContent?: string;\n /** UI components identified (for potential screenshot/mock generation) */\n uiComponents?: Array<{\n name: string;\n file: string;\n props?: Record<string, unknown>;\n }>;\n}\n\n/**\n * Configuration for video generation\n */\nexport interface VideoConfig {\n /** Output directory for the video */\n outputDir: string;\n /** Output filename (without extension) */\n filename?: string;\n /** Video format */\n format?: \"mp4\" | \"webm\";\n /** Video resolution */\n resolution?: VideoResolution;\n /** Frames per second */\n fps?: number;\n /** Whether to generate a poster image */\n generatePoster?: boolean;\n}\n\n/**\n * Video resolution preset\n */\nexport interface VideoResolution {\n width: number;\n height: number;\n}\n\n/**\n * Common resolution presets\n */\nexport const RESOLUTIONS = {\n \"1080p\": { width: 1920, height: 1080 },\n \"720p\": { width: 1280, height: 720 },\n \"480p\": { width: 854, height: 480 },\n square: { width: 1080, height: 1080 },\n vertical: { width: 1080, height: 1920 },\n} as const;\n\n/**\n * Scene types for the video\n */\nexport type SceneType = \"title\" | \"overview\" | \"walkthrough\" | \"code\" | \"code-highlight\" | \"diff\" | \"ui\" | \"outro\";\n\n/**\n * A single scene in the video storyboard\n */\nexport interface VideoScene {\n /** Unique scene identifier */\n id: string;\n /** Type of scene */\n type: SceneType;\n /** Scene title/heading */\n title: string;\n /** Main content/description */\n content: string;\n /** Duration in frames (at 30fps, 30 frames = 1 second) */\n durationInFrames: number;\n /** Additional props for the scene */\n props?: Record<string, unknown>;\n}\n\n/**\n * Complete storyboard for video generation\n */\nexport interface VideoStoryboard {\n /** Video title */\n title: string;\n /** Total duration in frames */\n totalDurationInFrames: number;\n /** List of scenes */\n scenes: VideoScene[];\n /** Source artifact data */\n artifact: ArtifactPayload;\n /** Design tokens for styling */\n theme: VideoTheme;\n}\n\n/**\n * Theme/design tokens for video styling\n */\nexport interface VideoTheme {\n /** Primary brand color */\n primaryColor: string;\n /** Background color */\n backgroundColor: string;\n /** Foreground/text color */\n foregroundColor: string;\n /** Accent color */\n accentColor: string;\n /** Muted text color */\n mutedColor: string;\n /** Card background color */\n cardColor: string;\n /** Border color */\n borderColor: string;\n /** Font family */\n fontFamily: string;\n}\n\n/**\n * Default dark theme matching the dashboard design\n */\nexport const DEFAULT_THEME: VideoTheme = {\n primaryColor: \"#10B981\", // Green accent\n backgroundColor: \"#0A0A0B\", // Dark background\n foregroundColor: \"#FAFAFA\", // Light text\n accentColor: \"#10B981\", // Green accent\n mutedColor: \"#A1A1A6\", // Secondary text\n cardColor: \"#141415\", // Card background\n borderColor: \"#1C1C1E\", // Border\n fontFamily: \"Inter, system-ui, sans-serif\",\n};\n\n/**\n * Result of video generation\n */\nexport interface VideoGenerationResult {\n /** Whether generation succeeded */\n success: boolean;\n /** Path to the generated video */\n videoPath?: string;\n /** Path to the poster image (if generated) */\n posterPath?: string;\n /** Error message if failed */\n error?: string;\n /** Generation duration in ms */\n durationMs?: number;\n}\n\n/**\n * Input for generating a video from an artifact\n */\nexport interface GenerateVideoInput {\n /** The artifact payload (can be enhanced with code content) */\n artifact: ArtifactPayload | VideoArtifactPayload;\n /** List of affected files (from analysis) */\n files?: string[];\n /** Code snippets to display in the video */\n codeSnippets?: CodeSnippet[];\n /** Video configuration */\n config: VideoConfig;\n /** Optional custom theme */\n theme?: Partial<VideoTheme>;\n /** Verbose logging */\n verbose?: boolean;\n}\n","/**\n * Storyboard Generator\n *\n * Converts artifact data into a video storyboard with scenes.\n */\n\nimport type { ArtifactPayload } from \"../api/client.js\";\nimport type { VideoScene, VideoStoryboard, VideoTheme, CodeSnippet, VideoArtifactPayload } from \"./types.js\";\nimport { DEFAULT_THEME } from \"./types.js\";\n\n/**\n * Generated UI scene data (from ui-generator)\n */\nexport interface UISceneData {\n id: string;\n componentName: string;\n description: string;\n jsx: string;\n mockData: Record<string, unknown>;\n pattern: string;\n durationInFrames: number;\n}\n\n/**\n * Input for storyboard generation\n */\nexport interface StoryboardInput {\n artifact: ArtifactPayload | VideoArtifactPayload;\n codeSnippets?: CodeSnippet[];\n uiScenes?: UISceneData[];\n theme?: Partial<VideoTheme>;\n}\n\n/** Frames per second (standard) */\nconst FPS = 30;\n\n/** Default scene durations in seconds */\nconst DURATIONS = {\n title: 4,\n overview: 6,\n walkthrough: 8,\n code: 6,\n outro: 4,\n};\n\n/**\n * Generate a storyboard from an artifact\n */\nexport function generateStoryboard(\n artifact: ArtifactPayload | VideoArtifactPayload,\n theme: Partial<VideoTheme> = {},\n codeSnippets?: CodeSnippet[],\n uiScenes?: UISceneData[]\n): VideoStoryboard {\n const mergedTheme = { ...DEFAULT_THEME, ...theme };\n const scenes: VideoScene[] = [];\n\n // Get code snippets from input or from enhanced artifact\n const snippets = codeSnippets || (artifact as VideoArtifactPayload).codeSnippets || [];\n\n // Scene 1: Title card\n scenes.push(createTitleScene(artifact));\n\n // Scene 2: Overview/Summary\n scenes.push(createOverviewScene(artifact));\n\n // Scene 3-N: UI mockup scenes (if we have generated UI)\n if (uiScenes && uiScenes.length > 0) {\n const uiVideoScenes = createUIScenes(uiScenes);\n scenes.push(...uiVideoScenes);\n }\n\n // Scene N+1: Walkthrough scenes based on technical details\n const walkthroughScenes = createWalkthroughScenes(artifact);\n scenes.push(...walkthroughScenes);\n\n // Scene N+2: Code highlight scenes (if we have actual code snippets)\n if (snippets.length > 0) {\n const codeHighlightScenes = createCodeHighlightScenes(snippets);\n scenes.push(...codeHighlightScenes);\n }\n\n // Scene N+3: File list (if affected components exist)\n if (artifact.context.affectedComponents.length > 0) {\n scenes.push(createCodeScene(artifact));\n }\n\n // Final scene: Outro/CTA\n scenes.push(createOutroScene(artifact));\n\n const totalDurationInFrames = scenes.reduce((sum, s) => sum + s.durationInFrames, 0);\n\n // Store snippets in the artifact for the video renderer\n const enhancedArtifact: VideoArtifactPayload = {\n ...artifact,\n codeSnippets: snippets,\n };\n\n return {\n title: artifact.title,\n totalDurationInFrames,\n scenes,\n artifact: enhancedArtifact,\n theme: mergedTheme,\n };\n}\n\n/**\n * Create UI mockup scenes from generated UI data\n */\nfunction createUIScenes(uiScenes: UISceneData[]): VideoScene[] {\n return uiScenes.map((ui, index) => ({\n id: ui.id || `ui-${index}`,\n type: \"ui\" as const,\n title: ui.componentName,\n content: ui.description,\n durationInFrames: ui.durationInFrames || 6 * FPS,\n props: {\n componentName: ui.componentName,\n jsx: ui.jsx,\n mockData: ui.mockData,\n pattern: ui.pattern,\n },\n }));\n}\n\n/**\n * Create the title scene\n */\nfunction createTitleScene(artifact: ArtifactPayload): VideoScene {\n return {\n id: \"title\",\n type: \"title\",\n title: artifact.title,\n content: artifact.description,\n durationInFrames: DURATIONS.title * FPS,\n props: {\n source: artifact.source,\n keywords: artifact.context.keywords.slice(0, 5),\n },\n };\n}\n\n/**\n * Create the overview scene\n */\nfunction createOverviewScene(artifact: ArtifactPayload): VideoScene {\n return {\n id: \"overview\",\n type: \"overview\",\n title: \"What Changed\",\n content: artifact.context.summary,\n durationInFrames: DURATIONS.overview * FPS,\n props: {\n breakingChanges: artifact.context.breakingChanges,\n componentCount: artifact.context.affectedComponents.length,\n },\n };\n}\n\n/**\n * Create walkthrough scenes from technical details\n */\nfunction createWalkthroughScenes(artifact: ArtifactPayload): VideoScene[] {\n const scenes: VideoScene[] = [];\n const details = artifact.context.technicalDetails;\n\n // Group details into scenes (max 3 details per scene)\n const chunks = chunkArray(details, 3);\n\n chunks.forEach((chunk, index) => {\n scenes.push({\n id: `walkthrough-${index + 1}`,\n type: \"walkthrough\",\n title: index === 0 ? \"How It Works\" : `Details (${index + 1})`,\n content: chunk.join(\"\\n\\n\"),\n durationInFrames: DURATIONS.walkthrough * FPS,\n props: {\n steps: chunk,\n stepIndex: index,\n },\n });\n });\n\n // Ensure at least one walkthrough scene\n if (scenes.length === 0) {\n scenes.push({\n id: \"walkthrough-1\",\n type: \"walkthrough\",\n title: \"How It Works\",\n content: artifact.context.summary,\n durationInFrames: DURATIONS.walkthrough * FPS,\n props: {\n steps: [artifact.context.summary],\n stepIndex: 0,\n },\n });\n }\n\n return scenes;\n}\n\n/**\n * Create code highlight scenes from code snippets\n */\nfunction createCodeHighlightScenes(snippets: CodeSnippet[]): VideoScene[] {\n const scenes: VideoScene[] = [];\n \n // Group snippets by file or show individually (max 3 scenes)\n const displaySnippets = snippets.slice(0, 6);\n const chunkedSnippets = chunkArray(displaySnippets, 2);\n\n chunkedSnippets.forEach((chunk, index) => {\n scenes.push({\n id: `code-highlight-${index + 1}`,\n type: \"code-highlight\",\n title: index === 0 ? \"Code Changes\" : `More Changes (${index + 1})`,\n content: chunk.map(s => s.description || s.file).join(\", \"),\n durationInFrames: DURATIONS.code * FPS,\n props: {\n snippets: chunk.map(s => ({\n file: s.file,\n code: s.code,\n language: s.language || detectLanguage(s.file),\n description: s.description,\n changeType: s.changeType,\n startLine: s.startLine,\n })),\n },\n });\n });\n\n return scenes;\n}\n\n/**\n * Detect language from file extension\n */\nfunction detectLanguage(filename: string): string {\n const ext = filename.split(\".\").pop()?.toLowerCase() || \"\";\n const langMap: Record<string, string> = {\n ts: \"typescript\",\n tsx: \"typescript\",\n js: \"javascript\",\n jsx: \"javascript\",\n vue: \"vue\",\n py: \"python\",\n rb: \"ruby\",\n go: \"go\",\n rs: \"rust\",\n java: \"java\",\n css: \"css\",\n scss: \"scss\",\n html: \"html\",\n json: \"json\",\n md: \"markdown\",\n yaml: \"yaml\",\n yml: \"yaml\",\n };\n return langMap[ext] || \"text\";\n}\n\n/**\n * Create a code/files scene\n */\nfunction createCodeScene(artifact: ArtifactPayload | VideoArtifactPayload): VideoScene {\n const components = artifact.context.affectedComponents;\n const displayComponents = components.slice(0, 8);\n\n return {\n id: \"code\",\n type: \"code\",\n title: \"Files Changed\",\n content: `${components.length} file${components.length !== 1 ? \"s\" : \"\"} affected`,\n durationInFrames: DURATIONS.code * FPS,\n props: {\n files: displayComponents,\n totalFiles: components.length,\n hasMore: components.length > 8,\n },\n };\n}\n\n/**\n * Create the outro scene\n */\nfunction createOutroScene(artifact: ArtifactPayload): VideoScene {\n // Pick a guideline for the CTA if available\n const cta = artifact.guidelines[0] || \"Learn more about this feature\";\n\n return {\n id: \"outro\",\n type: \"outro\",\n title: \"Summary\",\n content: cta,\n durationInFrames: DURATIONS.outro * FPS,\n props: {\n guidelines: artifact.guidelines.slice(0, 3),\n source: artifact.source,\n },\n };\n}\n\n/**\n * Chunk an array into smaller arrays\n */\nfunction chunkArray<T>(array: T[], size: number): T[][] {\n const chunks: T[][] = [];\n for (let i = 0; i < array.length; i += size) {\n chunks.push(array.slice(i, i + size));\n }\n return chunks;\n}\n\n/**\n * Calculate total video duration in seconds\n */\nexport function getVideoDurationSeconds(storyboard: VideoStoryboard): number {\n return storyboard.totalDurationInFrames / FPS;\n}\n\n/**\n * Format duration as MM:SS\n */\nexport function formatDuration(seconds: number): string {\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, \"0\")}`;\n}\n","/**\n * Video Renderer\n *\n * Renders video using Remotion. Everything is self-contained in the CLI.\n */\n\nimport { writeFileSync, mkdirSync, existsSync } from \"fs\";\nimport { join, resolve, dirname } from \"path\";\nimport { fileURLToPath, pathToFileURL } from \"url\";\nimport type {\n VideoConfig,\n VideoGenerationResult,\n GenerateVideoInput,\n} from \"./types.js\";\nimport { DEFAULT_THEME, RESOLUTIONS } from \"./types.js\";\nimport { generateStoryboard, getVideoDurationSeconds, formatDuration } from \"./storyboard.js\";\n\n// Get directory of current module for resolving relative imports\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Generate a video from an artifact\n *\n * This function:\n * 1. Creates a storyboard from the artifact\n * 2. Writes the storyboard to a JSON file\n * 3. Attempts to render with Remotion if available\n * 4. Falls back to outputting props for manual rendering\n */\nexport async function generateVideo(input: GenerateVideoInput): Promise<VideoGenerationResult> {\n const startTime = Date.now();\n\n try {\n const { artifact, codeSnippets, config, theme = {}, verbose } = input;\n\n // Merge theme with defaults\n const mergedTheme = { ...DEFAULT_THEME, ...theme };\n\n // Generate storyboard with code snippets\n const storyboard = generateStoryboard(artifact, mergedTheme, codeSnippets);\n\n if (verbose) {\n console.log(`\\nStoryboard generated:`);\n console.log(` - Scenes: ${storyboard.scenes.length}`);\n console.log(` - Duration: ${formatDuration(getVideoDurationSeconds(storyboard))}`);\n }\n\n // Ensure output directory exists\n const outputDir = resolve(config.outputDir);\n if (!existsSync(outputDir)) {\n mkdirSync(outputDir, { recursive: true });\n }\n\n // Write storyboard JSON\n const storyboardPath = join(outputDir, \"storyboard.json\");\n writeFileSync(storyboardPath, JSON.stringify(storyboard, null, 2));\n\n if (verbose) {\n console.log(` - Storyboard saved: ${storyboardPath}`);\n }\n\n // Write video props for Remotion composition\n const propsPath = join(outputDir, \"video-props.json\");\n const videoProps = {\n storyboard,\n resolution: config.resolution || RESOLUTIONS[\"1080p\"],\n fps: config.fps || 30,\n };\n writeFileSync(propsPath, JSON.stringify(videoProps, null, 2));\n\n if (verbose) {\n console.log(` - Props saved: ${propsPath}`);\n }\n\n // Determine output path\n const filename = config.filename || \"video\";\n const format = config.format || \"mp4\";\n const videoPath = join(outputDir, `${filename}.${format}`);\n\n // Try to render with bundled Remotion\n const renderResult = await tryRenderWithRemotion({\n storyboard,\n outputPath: videoPath,\n config,\n verbose,\n });\n\n if (renderResult.success) {\n return {\n success: true,\n videoPath: renderResult.videoPath,\n posterPath: renderResult.posterPath,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Remotion not available - return props path for manual rendering\n const durationMs = Date.now() - startTime;\n\n return {\n success: true,\n videoPath: propsPath,\n durationMs,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n durationMs: Date.now() - startTime,\n };\n }\n}\n\ninterface RenderOptions {\n storyboard: ReturnType<typeof generateStoryboard>;\n outputPath: string;\n config: VideoConfig;\n verbose?: boolean;\n}\n\ninterface RenderResult {\n success: boolean;\n videoPath?: string;\n posterPath?: string;\n error?: string;\n}\n\n/**\n * Attempt to render with bundled Remotion\n */\nasync function tryRenderWithRemotion(options: RenderOptions): Promise<RenderResult> {\n const { storyboard, outputPath, config, verbose } = options;\n\n // Check if Remotion is available\n const remotionAvailable = await isRemotionAvailable();\n if (!remotionAvailable) {\n if (verbose) {\n console.log(\"\\n ⚠️ Remotion dependencies not installed.\");\n console.log(\" To enable automatic video rendering, install Remotion in the CLI:\");\n console.log(\"\");\n console.log(\" cd $(npm root -g)/@makeitvisible/cli && npm install\");\n console.log(\"\");\n console.log(\" Or install locally:\");\n console.log(\" npm install @remotion/bundler @remotion/renderer @remotion/cli remotion react react-dom\");\n console.log(\"\");\n console.log(\" For now, you can render manually with the generated video-props.json\");\n }\n return { success: false, error: \"Remotion not available\" };\n }\n\n try {\n // Dynamic import to avoid errors when Remotion isn't installed\n // Use absolute path based on this module's location\n const renderPath = pathToFileURL(join(__dirname, \"remotion\", \"render.js\")).href;\n const { renderVideo } = await import(/* @vite-ignore */ renderPath);\n\n // Map resolution to preset name\n const resolution = config.resolution || RESOLUTIONS[\"1080p\"];\n let resolutionKey: \"1080p\" | \"720p\" | \"480p\" | \"square\" | \"vertical\" = \"1080p\";\n\n if (resolution.width === RESOLUTIONS[\"720p\"].width) {\n resolutionKey = \"720p\";\n } else if (resolution.width === RESOLUTIONS.square.width && resolution.height === RESOLUTIONS.square.height) {\n resolutionKey = \"square\";\n } else if (resolution.width === RESOLUTIONS.vertical.width && resolution.height === RESOLUTIONS.vertical.height) {\n resolutionKey = \"vertical\";\n }\n\n if (verbose) {\n console.log(`\\n Rendering video with Remotion...`);\n console.log(` Resolution: ${resolution.width}x${resolution.height}`);\n }\n\n const result = await renderVideo({\n storyboard,\n outputPath,\n resolution: resolutionKey,\n codec: \"h264\",\n verbose,\n onProgress: (progress: number) => {\n if (verbose) {\n process.stdout.write(`\\r Rendering: ${Math.round(progress * 100)}%`);\n }\n },\n });\n\n if (verbose && result.success) {\n console.log(\"\\n\");\n }\n\n return result;\n } catch (error) {\n if (verbose) {\n console.log(`\\n Remotion render failed: ${error instanceof Error ? error.message : error}`);\n }\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Check if Remotion is available for rendering\n */\nexport async function isRemotionAvailable(): Promise<boolean> {\n try {\n // Use dynamic imports for ESM compatibility\n await import(\"@remotion/bundler\");\n await import(\"@remotion/renderer\");\n await import(\"remotion\");\n return true;\n } catch (error) {\n // Log error for debugging\n if (process.env.DEBUG_REMOTION) {\n console.error(\"Remotion availability check failed:\", error);\n }\n return false;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makeitvisible/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "CLI for code analysis and artifact generation - Transform technical changes into audience-friendly materials",
5
5
  "author": "Visible Team",
6
6
  "license": "MIT",
@@ -48,26 +48,37 @@
48
48
  "ai",
49
49
  "openai",
50
50
  "git",
51
- "changelog"
51
+ "changelog",
52
+ "video",
53
+ "remotion"
52
54
  ],
53
55
  "scripts": {
54
56
  "dev": "tsup --watch",
55
57
  "build": "tsup",
58
+ "video:preview": "remotion studio src/video/remotion/index.tsx",
59
+ "video:render": "remotion render src/video/remotion/index.tsx Main",
56
60
  "typecheck": "tsc --noEmit",
57
61
  "lint": "eslint src/",
58
62
  "clean": "rm -rf dist",
59
63
  "prepublishOnly": "npm run build"
60
64
  },
61
65
  "dependencies": {
66
+ "@remotion/bundler": "^4.0.409",
67
+ "@remotion/cli": "^4.0.409",
68
+ "@remotion/renderer": "^4.0.409",
62
69
  "chalk": "^5.3.0",
63
70
  "commander": "^12.0.0",
64
71
  "glob": "^11.0.0",
65
72
  "openai": "^4.73.0",
66
- "ora": "^8.0.0"
73
+ "ora": "^8.0.0",
74
+ "react": "^18.3.1",
75
+ "react-dom": "^18.3.1",
76
+ "remotion": "^4.0.409"
67
77
  },
68
78
  "devDependencies": {
69
79
  "@types/node": "^20.12.0",
80
+ "@types/react": "^18.2.0",
70
81
  "tsup": "^8.0.0",
71
82
  "typescript": "^5.4.0"
72
83
  }
73
- }
84
+ }