mulmocast 2.13.0 → 2.14.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.
@@ -13517,29 +13517,95 @@
13517
13517
  "additionalProperties": false
13518
13518
  },
13519
13519
  {
13520
- "type": "object",
13521
- "properties": {
13522
- "type": {
13523
- "type": "string",
13524
- "const": "remotion"
13525
- },
13526
- "prompt": {
13527
- "type": "string",
13528
- "minLength": 1,
13529
- "description": "What the scene shows. Claude Code (claude -p) writes a Remotion component from it."
13520
+ "anyOf": [
13521
+ {
13522
+ "type": "object",
13523
+ "properties": {
13524
+ "type": {
13525
+ "type": "string",
13526
+ "const": "remotion"
13527
+ },
13528
+ "fps": {
13529
+ "description": "Frame rate of the rendered video. Default: 30.",
13530
+ "type": "number",
13531
+ "minimum": 1,
13532
+ "maximum": 60
13533
+ },
13534
+ "prompt": {
13535
+ "type": "string",
13536
+ "minLength": 1,
13537
+ "description": "What the scene shows. Claude Code (claude -p) writes a Remotion component from it."
13538
+ }
13539
+ },
13540
+ "required": [
13541
+ "type",
13542
+ "prompt"
13543
+ ],
13544
+ "additionalProperties": false
13530
13545
  },
13531
- "fps": {
13532
- "description": "Frame rate of the rendered video. Default: 30.",
13533
- "type": "number",
13534
- "minimum": 1,
13535
- "maximum": 60
13546
+ {
13547
+ "type": "object",
13548
+ "properties": {
13549
+ "type": {
13550
+ "type": "string",
13551
+ "const": "remotion"
13552
+ },
13553
+ "fps": {
13554
+ "description": "Frame rate of the rendered video. Default: 30.",
13555
+ "type": "number",
13556
+ "minimum": 1,
13557
+ "maximum": 60
13558
+ },
13559
+ "code": {
13560
+ "oneOf": [
13561
+ {
13562
+ "type": "object",
13563
+ "properties": {
13564
+ "kind": {
13565
+ "type": "string",
13566
+ "const": "text"
13567
+ },
13568
+ "text": {
13569
+ "type": "string",
13570
+ "minLength": 1
13571
+ }
13572
+ },
13573
+ "required": [
13574
+ "kind",
13575
+ "text"
13576
+ ],
13577
+ "additionalProperties": false
13578
+ },
13579
+ {
13580
+ "type": "object",
13581
+ "properties": {
13582
+ "kind": {
13583
+ "type": "string",
13584
+ "const": "path"
13585
+ },
13586
+ "path": {
13587
+ "type": "string",
13588
+ "minLength": 1,
13589
+ "description": "Relative to the script"
13590
+ }
13591
+ },
13592
+ "required": [
13593
+ "kind",
13594
+ "path"
13595
+ ],
13596
+ "additionalProperties": false
13597
+ }
13598
+ ],
13599
+ "description": "A finished Remotion component (TSX that default-exports the scene). It is rendered as is, without claude -p."
13600
+ }
13601
+ },
13602
+ "required": [
13603
+ "type",
13604
+ "code"
13605
+ ],
13606
+ "additionalProperties": false
13536
13607
  }
13537
- },
13538
- "required": [
13539
- "type",
13540
- "prompt"
13541
- ],
13542
- "additionalProperties": false
13608
+ ]
13543
13609
  }
13544
13610
  ]
13545
13611
  },
@@ -0,0 +1,3 @@
1
+ export { REMOTION_COMPONENT_GUIDE } from "./utils/remotion/system_prompt.js";
2
+ export { REMOTION_PACKAGES, REMOTION_RENDER_PACKAGES, REMOTION_SCENE_PACKAGES, remotionInstallCommand } from "./utils/remotion/packages.js";
3
+ export { ensureRemotionPackages, missingInstalledRemotionPackages } from "./utils/remotion/render.js";
@@ -0,0 +1,4 @@
1
+ // Entry point for hosts whose own agent writes the remotion beat's component and passes it as `code`.
2
+ export { REMOTION_COMPONENT_GUIDE } from "./utils/remotion/system_prompt.js";
3
+ export { REMOTION_PACKAGES, REMOTION_RENDER_PACKAGES, REMOTION_SCENE_PACKAGES, remotionInstallCommand } from "./utils/remotion/packages.js";
4
+ export { ensureRemotionPackages, missingInstalledRemotionPackages } from "./utils/remotion/render.js";
@@ -545,11 +545,28 @@ export declare const mulmoVisionMediaSchema: z.ZodObject<{
545
545
  style: z.ZodString;
546
546
  data: z.ZodRecord<z.ZodString, z.ZodAny>;
547
547
  }, z.core.$strict>;
548
- export declare const mulmoRemotionMediaSchema: z.ZodObject<{
549
- type: z.ZodLiteral<"remotion">;
548
+ export declare const remotionCodeSourceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
549
+ kind: z.ZodLiteral<"text">;
550
+ text: z.ZodString;
551
+ }, z.core.$strict>, z.ZodObject<{
552
+ kind: z.ZodLiteral<"path">;
553
+ path: z.ZodString;
554
+ }, z.core.$strict>], "kind">;
555
+ export declare const mulmoRemotionMediaSchema: z.ZodUnion<readonly [z.ZodObject<{
550
556
  prompt: z.ZodString;
557
+ type: z.ZodLiteral<"remotion">;
551
558
  fps: z.ZodOptional<z.ZodNumber>;
552
- }, z.core.$strict>;
559
+ }, z.core.$strict>, z.ZodObject<{
560
+ code: z.ZodDiscriminatedUnion<[z.ZodObject<{
561
+ kind: z.ZodLiteral<"text">;
562
+ text: z.ZodString;
563
+ }, z.core.$strict>, z.ZodObject<{
564
+ kind: z.ZodLiteral<"path">;
565
+ path: z.ZodString;
566
+ }, z.core.$strict>], "kind">;
567
+ type: z.ZodLiteral<"remotion">;
568
+ fps: z.ZodOptional<z.ZodNumber>;
569
+ }, z.core.$strict>]>;
553
570
  export declare const mulmoRemotionParamsSchema: z.ZodObject<{
554
571
  brief: z.ZodOptional<z.ZodString>;
555
572
  }, z.core.$strict>;
@@ -4233,11 +4250,21 @@ export declare const mulmoImageAssetSchema: z.ZodUnion<readonly [z.ZodObject<{
4233
4250
  bgOpacity: z.ZodOptional<z.ZodNumber>;
4234
4251
  }, z.core.$strip>>;
4235
4252
  }, z.core.$strict>>>;
4253
+ }, z.core.$strict>, z.ZodUnion<readonly [z.ZodObject<{
4254
+ prompt: z.ZodString;
4255
+ type: z.ZodLiteral<"remotion">;
4256
+ fps: z.ZodOptional<z.ZodNumber>;
4236
4257
  }, z.core.$strict>, z.ZodObject<{
4258
+ code: z.ZodDiscriminatedUnion<[z.ZodObject<{
4259
+ kind: z.ZodLiteral<"text">;
4260
+ text: z.ZodString;
4261
+ }, z.core.$strict>, z.ZodObject<{
4262
+ kind: z.ZodLiteral<"path">;
4263
+ path: z.ZodString;
4264
+ }, z.core.$strict>], "kind">;
4237
4265
  type: z.ZodLiteral<"remotion">;
4238
- prompt: z.ZodString;
4239
4266
  fps: z.ZodOptional<z.ZodNumber>;
4240
- }, z.core.$strict>]>;
4267
+ }, z.core.$strict>]>]>;
4241
4268
  export declare const mulmoAudioAssetSchema: z.ZodUnion<readonly [z.ZodObject<{
4242
4269
  type: z.ZodLiteral<"audio">;
4243
4270
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -8644,11 +8671,21 @@ export declare const mulmoBeatSchema: z.ZodObject<{
8644
8671
  bgOpacity: z.ZodOptional<z.ZodNumber>;
8645
8672
  }, z.core.$strip>>;
8646
8673
  }, z.core.$strict>>>;
8674
+ }, z.core.$strict>, z.ZodUnion<readonly [z.ZodObject<{
8675
+ prompt: z.ZodString;
8676
+ type: z.ZodLiteral<"remotion">;
8677
+ fps: z.ZodOptional<z.ZodNumber>;
8647
8678
  }, z.core.$strict>, z.ZodObject<{
8679
+ code: z.ZodDiscriminatedUnion<[z.ZodObject<{
8680
+ kind: z.ZodLiteral<"text">;
8681
+ text: z.ZodString;
8682
+ }, z.core.$strict>, z.ZodObject<{
8683
+ kind: z.ZodLiteral<"path">;
8684
+ path: z.ZodString;
8685
+ }, z.core.$strict>], "kind">;
8648
8686
  type: z.ZodLiteral<"remotion">;
8649
- prompt: z.ZodString;
8650
8687
  fps: z.ZodOptional<z.ZodNumber>;
8651
- }, z.core.$strict>]>>;
8688
+ }, z.core.$strict>]>]>>;
8652
8689
  audio: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
8653
8690
  type: z.ZodLiteral<"audio">;
8654
8691
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -13901,11 +13938,21 @@ export declare const mulmoScriptSchema: z.ZodObject<{
13901
13938
  bgOpacity: z.ZodOptional<z.ZodNumber>;
13902
13939
  }, z.core.$strip>>;
13903
13940
  }, z.core.$strict>>>;
13941
+ }, z.core.$strict>, z.ZodUnion<readonly [z.ZodObject<{
13942
+ prompt: z.ZodString;
13943
+ type: z.ZodLiteral<"remotion">;
13944
+ fps: z.ZodOptional<z.ZodNumber>;
13904
13945
  }, z.core.$strict>, z.ZodObject<{
13946
+ code: z.ZodDiscriminatedUnion<[z.ZodObject<{
13947
+ kind: z.ZodLiteral<"text">;
13948
+ text: z.ZodString;
13949
+ }, z.core.$strict>, z.ZodObject<{
13950
+ kind: z.ZodLiteral<"path">;
13951
+ path: z.ZodString;
13952
+ }, z.core.$strict>], "kind">;
13905
13953
  type: z.ZodLiteral<"remotion">;
13906
- prompt: z.ZodString;
13907
13954
  fps: z.ZodOptional<z.ZodNumber>;
13908
- }, z.core.$strict>]>>;
13955
+ }, z.core.$strict>]>]>>;
13909
13956
  audio: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
13910
13957
  type: z.ZodLiteral<"audio">;
13911
13958
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -18682,11 +18729,21 @@ export declare const mulmoStudioSchema: z.ZodObject<{
18682
18729
  bgOpacity: z.ZodOptional<z.ZodNumber>;
18683
18730
  }, z.core.$strip>>;
18684
18731
  }, z.core.$strict>>>;
18732
+ }, z.core.$strict>, z.ZodUnion<readonly [z.ZodObject<{
18733
+ prompt: z.ZodString;
18734
+ type: z.ZodLiteral<"remotion">;
18735
+ fps: z.ZodOptional<z.ZodNumber>;
18685
18736
  }, z.core.$strict>, z.ZodObject<{
18737
+ code: z.ZodDiscriminatedUnion<[z.ZodObject<{
18738
+ kind: z.ZodLiteral<"text">;
18739
+ text: z.ZodString;
18740
+ }, z.core.$strict>, z.ZodObject<{
18741
+ kind: z.ZodLiteral<"path">;
18742
+ path: z.ZodString;
18743
+ }, z.core.$strict>], "kind">;
18686
18744
  type: z.ZodLiteral<"remotion">;
18687
- prompt: z.ZodString;
18688
18745
  fps: z.ZodOptional<z.ZodNumber>;
18689
- }, z.core.$strict>]>>;
18746
+ }, z.core.$strict>]>]>>;
18690
18747
  audio: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
18691
18748
  type: z.ZodLiteral<"audio">;
18692
18749
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -359,13 +359,29 @@ export const mulmoVisionMediaSchema = z
359
359
  data: z.record(z.string(), z.any()),
360
360
  })
361
361
  .strict();
362
- export const mulmoRemotionMediaSchema = z
363
- .object({
362
+ export const remotionCodeSourceSchema = z.discriminatedUnion("kind", [
363
+ z.object({ kind: z.literal("text"), text: z.string().min(1) }).strict(),
364
+ z.object({ kind: z.literal("path"), path: z.string().min(1).describe("Relative to the script") }).strict(),
365
+ ]);
366
+ const remotionMediaShape = {
364
367
  type: z.literal(ImageMediaType.Remotion),
365
- prompt: z.string().min(1).describe("What the scene shows. Claude Code (claude -p) writes a Remotion component from it."),
366
368
  fps: z.number().min(1).max(60).optional().describe("Frame rate of the rendered video. Default: 30."),
367
- })
368
- .strict();
369
+ };
370
+ // Two strict shapes rather than one object with a refine, so the bundled JSON schema states "prompt or code, not both" too.
371
+ export const mulmoRemotionMediaSchema = z.union([
372
+ z
373
+ .object({
374
+ ...remotionMediaShape,
375
+ prompt: z.string().min(1).describe("What the scene shows. Claude Code (claude -p) writes a Remotion component from it."),
376
+ })
377
+ .strict(),
378
+ z
379
+ .object({
380
+ ...remotionMediaShape,
381
+ code: remotionCodeSourceSchema.describe("A finished Remotion component (TSX that default-exports the scene). It is rendered as is, without claude -p."),
382
+ })
383
+ .strict(),
384
+ ]);
369
385
  export const mulmoRemotionParamsSchema = z
370
386
  .object({
371
387
  brief: z.string().optional().describe("Whole-video art direction shared by every remotion beat (palette, type, mood), so all scenes look like one film."),
@@ -1,6 +1,6 @@
1
1
  import { type CallbackFunction } from "graphai";
2
2
  import type { UsageCollectorAPI } from "./usage.js";
3
- import { langSchema, localizedTextSchema, mulmoBeatSchema, mulmoScriptSchema, mulmoStudioSchema, mulmoStudioBeatSchema, mulmoStoryboardSchema, mulmoStoryboardSceneSchema, mulmoStudioMultiLingualSchema, mulmoStudioMultiLingualArraySchema, mulmoStudioMultiLingualDataSchema, mulmoStudioMultiLingualFileSchema, speakerDictionarySchema, speakerSchema, mulmoSpeechParamsSchema, mulmoImageParamsSchema, mulmoImageParamsImagesValueSchema, mulmoImageParamsImagesSchema, mulmoFillOptionSchema, mulmoTransitionSchema, mulmoVideoFilterSchema, mulmoMovieParamsSchema, mulmoSoundEffectParamsSchema, mulmoLipSyncParamsSchema, textSlideParamsSchema, speechOptionsSchema, speakerDataSchema, mulmoCanvasDimensionSchema, mulmoPromptTemplateSchema, mulmoPromptTemplateFileSchema, text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoPresentationStyleSchema, multiLingualTextsSchema, mulmoImageAssetSchema, mulmoMermaidMediaSchema, mulmoRemotionMediaSchema, mulmoTextSlideMediaSchema, mulmoMarkdownMediaSchema, mulmoImageMediaSchema, mulmoChartMediaSchema, mediaSourceSchema, mediaSourceMermaidSchema, backgroundImageSchema, backgroundImageSourceSchema, mulmoSessionStateSchema, mulmoOpenAIImageModelSchema, mulmoGoogleImageModelSchema, mulmoGoogleMovieModelSchema, mulmoReplicateMovieModelSchema, mulmoImagePromptMediaSchema, mulmoImageReferenceSchema, mulmoMovieMediaSchema, mulmoMoviePromptMediaSchema, markdownLayoutSchema, row2Schema, grid2x2Schema, mulmoAvatarParamsSchema, avatarPositionSchema, speakerAvatarSchema, mulmoAvatarTrackSchema } from "./schema.js";
3
+ import { langSchema, localizedTextSchema, mulmoBeatSchema, mulmoScriptSchema, mulmoStudioSchema, mulmoStudioBeatSchema, mulmoStoryboardSchema, mulmoStoryboardSceneSchema, mulmoStudioMultiLingualSchema, mulmoStudioMultiLingualArraySchema, mulmoStudioMultiLingualDataSchema, mulmoStudioMultiLingualFileSchema, speakerDictionarySchema, speakerSchema, mulmoSpeechParamsSchema, mulmoImageParamsSchema, mulmoImageParamsImagesValueSchema, mulmoImageParamsImagesSchema, mulmoFillOptionSchema, mulmoTransitionSchema, mulmoVideoFilterSchema, mulmoMovieParamsSchema, mulmoSoundEffectParamsSchema, mulmoLipSyncParamsSchema, textSlideParamsSchema, speechOptionsSchema, speakerDataSchema, mulmoCanvasDimensionSchema, mulmoPromptTemplateSchema, mulmoPromptTemplateFileSchema, text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoPresentationStyleSchema, multiLingualTextsSchema, mulmoImageAssetSchema, mulmoMermaidMediaSchema, mulmoRemotionMediaSchema, remotionCodeSourceSchema, mulmoTextSlideMediaSchema, mulmoMarkdownMediaSchema, mulmoImageMediaSchema, mulmoChartMediaSchema, mediaSourceSchema, mediaSourceMermaidSchema, backgroundImageSchema, backgroundImageSourceSchema, mulmoSessionStateSchema, mulmoOpenAIImageModelSchema, mulmoGoogleImageModelSchema, mulmoGoogleMovieModelSchema, mulmoReplicateMovieModelSchema, mulmoImagePromptMediaSchema, mulmoImageReferenceSchema, mulmoMovieMediaSchema, mulmoMoviePromptMediaSchema, markdownLayoutSchema, row2Schema, grid2x2Schema, mulmoAvatarParamsSchema, avatarPositionSchema, speakerAvatarSchema, mulmoAvatarTrackSchema } from "./schema.js";
4
4
  import { pdf_modes, pdf_sizes, storyToScriptGenerateMode } from "./const.js";
5
5
  import type { LLM } from "./provider2agent.js";
6
6
  import { z } from "zod";
@@ -66,6 +66,7 @@ export type MulmoMovieMedia = z.infer<typeof mulmoMovieMediaSchema>;
66
66
  export type MulmoChartMedia = z.infer<typeof mulmoChartMediaSchema>;
67
67
  export type MulmoMermaidMedia = z.infer<typeof mulmoMermaidMediaSchema>;
68
68
  export type MulmoRemotionMedia = z.infer<typeof mulmoRemotionMediaSchema>;
69
+ export type MulmoRemotionCodeSource = z.infer<typeof remotionCodeSourceSchema>;
69
70
  export type MulmoSessionState = z.infer<typeof mulmoSessionStateSchema>;
70
71
  export type MulmoStudioContext = {
71
72
  fileDirs: FileObject;
@@ -549,8 +549,18 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
549
549
  style: string;
550
550
  data: Record<string, any>;
551
551
  } | {
552
- type: "remotion";
553
552
  prompt: string;
553
+ type: "remotion";
554
+ fps?: number | undefined;
555
+ } | {
556
+ code: {
557
+ kind: "text";
558
+ text: string;
559
+ } | {
560
+ kind: "path";
561
+ path: string;
562
+ };
563
+ type: "remotion";
554
564
  fps?: number | undefined;
555
565
  } | {
556
566
  type: "slide";
@@ -3178,8 +3188,18 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
3178
3188
  style: string;
3179
3189
  data: Record<string, any>;
3180
3190
  } | {
3181
- type: "remotion";
3182
3191
  prompt: string;
3192
+ type: "remotion";
3193
+ fps?: number | undefined;
3194
+ } | {
3195
+ code: {
3196
+ kind: "text";
3197
+ text: string;
3198
+ } | {
3199
+ kind: "path";
3200
+ path: string;
3201
+ };
3202
+ type: "remotion";
3183
3203
  fps?: number | undefined;
3184
3204
  } | {
3185
3205
  type: "slide";
@@ -1,4 +1,4 @@
1
- import { ImageProcessorParams } from "../../types/index.js";
1
+ import { ImageProcessorParams, MulmoRemotionCodeSource, MulmoStudioContext } from "../../types/index.js";
2
2
  import { ComponentReviewer, ComponentWriter } from "../remotion/claude_runner.js";
3
3
  import { SceneRenderer } from "../remotion/render.js";
4
4
  export declare const imageType: "remotion";
@@ -11,6 +11,7 @@ export type RemotionDeps = {
11
11
  export declare const remotionWorkDir: (imagePath: string) => string;
12
12
  export declare const remotionStillPath: (imagePath: string) => string;
13
13
  export declare const reviewCandidatePath: (filePath: string) => string;
14
+ export declare const remotionCodeLocation: (code: MulmoRemotionCodeSource, context: MulmoStudioContext, beatIndex: number) => string;
14
15
  export declare const createRemotionProcess: (deps: RemotionDeps) => (params: ImageProcessorParams) => Promise<string | undefined>;
15
16
  export declare const process: (params: ImageProcessorParams) => Promise<string | undefined>;
16
17
  export declare const path: (params: import("../../types/type.js").BeatPathParams) => string;
@@ -3,6 +3,7 @@ import nodePath from "node:path";
3
3
  import { GraphAILogger } from "graphai";
4
4
  import { ImageMediaType } from "../../types/index.js";
5
5
  import { localizedText } from "../utils.js";
6
+ import { getFullPath } from "../file.js";
6
7
  import { REMOTION_COMPONENT_FILE, buildRepairPrompt, buildReviewPrompt, buildScenePrompt, remotionCacheKey, } from "../remotion/claude_prompt.js";
7
8
  import { reviewComponentWithClaude, writeComponentWithClaude } from "../remotion/claude_runner.js";
8
9
  import { ensureRemotionPackages, renderRemotionScene } from "../remotion/render.js";
@@ -114,9 +115,18 @@ const toFrameCount = (durationSec, fps) => {
114
115
  const scenePosition = (beats, index) => (index < 0 ? undefined : { index, count: beats.length });
115
116
  // The words actually spoken over the scene: the translation when the video is rendered in another language.
116
117
  const spokenNarration = (context, beat, index) => (index < 0 ? beat.text : localizedText(beat, context.multiLingual?.[index], context.lang, context.studio.script.lang)) || undefined;
117
- const buildJob = (params, prompt, fps) => {
118
- const { beat, context, imagePath, canvasSize } = params;
118
+ const buildSceneOutput = (params, fps) => {
119
+ const { beat, imagePath, canvasSize } = params;
119
120
  const duration = params.beatDuration ?? beat.duration;
121
+ return {
122
+ workDir: remotionWorkDir(imagePath),
123
+ props: { durationInFrames: toFrameCount(duration ?? STILL_ONLY_DURATION_SEC, fps), fps, width: canvasSize.width, height: canvasSize.height },
124
+ videoPath: duration !== undefined ? imagePath : undefined,
125
+ stillPath: remotionStillPath(imagePath),
126
+ };
127
+ };
128
+ const buildJob = (params, prompt, fps) => {
129
+ const { beat, context, canvasSize } = params;
120
130
  const index = context.studio.script.beats.indexOf(beat);
121
131
  const spec = {
122
132
  prompt,
@@ -127,21 +137,49 @@ const buildJob = (params, prompt, fps) => {
127
137
  width: canvasSize.width,
128
138
  height: canvasSize.height,
129
139
  };
130
- const workDir = remotionWorkDir(imagePath);
131
- return {
132
- spec,
133
- workDir,
134
- codePath: nodePath.join(workDir, `${remotionCacheKey(spec)}.tsx`),
135
- props: { durationInFrames: toFrameCount(duration ?? STILL_ONLY_DURATION_SEC, fps), fps, width: spec.width, height: spec.height },
136
- videoPath: duration !== undefined ? imagePath : undefined,
137
- stillPath: remotionStillPath(imagePath),
138
- };
140
+ const output = buildSceneOutput(params, fps);
141
+ return { ...output, spec, codePath: nodePath.join(output.workDir, `${remotionCacheKey(spec)}.tsx`) };
142
+ };
143
+ // Named in errors, so whoever wrote the component (usually the host's agent) knows what to fix.
144
+ export const remotionCodeLocation = (code, context, beatIndex) => {
145
+ if (code.kind === "path")
146
+ return getFullPath(context.fileDirs.mulmoFileDirPath, code.path);
147
+ return beatIndex < 0 ? "inline code" : `inline code of beat ${beatIndex + 1}`;
148
+ };
149
+ const readGivenComponent = (code, location) => {
150
+ if (code.kind === "text")
151
+ return code.text;
152
+ try {
153
+ return fs.readFileSync(location, "utf8");
154
+ }
155
+ catch (error) {
156
+ throw new Error(`remotion: cannot read the component file ${location}`, { cause: error });
157
+ }
158
+ };
159
+ // A given component is the caller's to fix, so a failed render stops here instead of asking claude -p to repair it.
160
+ const renderGivenComponent = async (params, code, fps, deps) => {
161
+ const output = buildSceneOutput(params, fps);
162
+ const location = remotionCodeLocation(code, params.context, params.context.studio.script.beats.indexOf(params.beat));
163
+ deps.ensurePackages();
164
+ const source = readGivenComponent(code, location);
165
+ fs.mkdirSync(output.workDir, { recursive: true });
166
+ fs.writeFileSync(nodePath.join(output.workDir, REMOTION_COMPONENT_FILE), source);
167
+ try {
168
+ await deps.renderScene({ workDir: output.workDir, props: output.props, videoPath: output.videoPath, stillPath: output.stillPath });
169
+ }
170
+ catch (error) {
171
+ throw new Error(`remotion beat failed to render the given component (code: ${location}): ${errorMessage(error)}`, { cause: error });
172
+ }
173
+ return output.videoPath ?? output.stillPath;
139
174
  };
140
175
  export const createRemotionProcess = (deps) => async (params) => {
141
176
  const { beat, context } = params;
142
177
  if (!beat.image || beat.image.type !== imageType)
143
178
  return;
144
- const job = buildJob(params, beat.image.prompt, beat.image.fps ?? DEFAULT_FPS);
179
+ const fps = beat.image.fps ?? DEFAULT_FPS;
180
+ if ("code" in beat.image)
181
+ return await renderGivenComponent(params, beat.image.code, fps, deps);
182
+ const job = buildJob(params, beat.image.prompt, fps);
145
183
  deps.ensurePackages();
146
184
  fs.mkdirSync(job.workDir, { recursive: true });
147
185
  const { code, isNew } = await loadOrWriteComponent(job, context.force, deps);
@@ -15,5 +15,6 @@ export type RemotionRenderRequest = {
15
15
  }[];
16
16
  };
17
17
  export type SceneRenderer = (request: RemotionRenderRequest) => Promise<void>;
18
+ export declare const missingInstalledRemotionPackages: () => string[];
18
19
  export declare const ensureRemotionPackages: () => void;
19
20
  export declare const renderRemotionScene: SceneRenderer;
@@ -24,10 +24,12 @@ const isPackageInstalled = (name) => {
24
24
  return !(error instanceof Error && "code" in error && error.code === "MODULE_NOT_FOUND");
25
25
  }
26
26
  };
27
+ // Resolved from mulmocast's own location, which is where the renderer looks for them.
28
+ export const missingInstalledRemotionPackages = () => missingRemotionPackages(isPackageInstalled);
27
29
  // Checked before asking claude -p for a component: a generated import of a missing optional peer
28
30
  // would otherwise surface as an opaque bundling error after the generation was already paid for.
29
31
  export const ensureRemotionPackages = () => {
30
- const missing = missingRemotionPackages(isPackageInstalled);
32
+ const missing = missingInstalledRemotionPackages();
31
33
  if (missing.length > 0) {
32
34
  throw new Error(`The remotion beat needs packages that are not installed (${missing.join(", ")}). Install them with: ${remotionInstallCommand()}`);
33
35
  }
@@ -1,3 +1,4 @@
1
1
  export declare const REMOTION_SYSTEM_PROMPT: string;
2
+ export declare const REMOTION_COMPONENT_GUIDE: string;
2
3
  export declare const REMOTION_REVIEW_MARKER = "LGTM";
3
4
  export declare const REMOTION_REVIEW_SYSTEM_PROMPT: string;
@@ -2,13 +2,13 @@ import { REMOTION_SCENE_PACKAGES } from "./packages.js";
2
2
  const ALLOWED_IMPORTS = ["react", "remotion", ...REMOTION_SCENE_PACKAGES].map((name) => `"${name}"`).join(", ");
3
3
  // The contract and the house style for the component `claude -p` writes for one remotion beat.
4
4
  // Every snippet in the toolkit section was rendered with this repository's renderer settings.
5
- const OUTPUT_CONTRACT = `You are a senior motion designer and Remotion (v4.0.533) engineer. You write ONE React component in TypeScript (TSX) that renders one scene of a commercial-quality video.
6
-
7
- # Output contract
8
- - Reply with ONLY the TSX source in one \`\`\`tsx code block. No explanation before or after.
9
- - \`export default\` the scene component. It receives props \`{ durationInFrames: number; fps: number; width: number; height: number }\`; read them with \`useVideoConfig()\`.
5
+ const ROLE = `You are a senior motion designer and Remotion (v4.0.533) engineer. You write ONE React component in TypeScript (TSX) that renders one scene of a commercial-quality video.`;
6
+ const REPLY_FORMAT = `- Reply with ONLY the TSX source in one \`\`\`tsx code block. No explanation before or after.`;
7
+ const COMPONENT_RULES = `- One self-contained file: no relative imports, since it is rendered on its own.`;
8
+ const CONTRACT_BODY = `- \`export default\` the scene component. It receives props \`{ durationInFrames: number; fps: number; width: number; height: number }\`; read them with \`useVideoConfig()\`.
10
9
  - Allowed imports, nothing else: ${ALLOWED_IMPORTS}. Subpaths of these packages are allowed too, e.g. "@remotion/transitions/fade" (also "/slide", "/wipe", "/iris", "/cross-zoom", "/film-burn", "/dissolve", "/clock-wipe") and "@remotion/effects/<name>".
11
10
  - No network, no external URLs, no image/video/audio files, no web fonts. Draw everything with DOM, SVG, canvas or three.js.`;
11
+ const OUTPUT_CONTRACT = [ROLE, "", "# Output contract", REPLY_FORMAT, CONTRACT_BODY].join("\n");
12
12
  const TIMING_RULES = `# Timing (hard rules — breaking them causes flicker or broken renders)
13
13
  - Everything is a pure function of \`useCurrentFrame()\`. NEVER use CSS animations/transitions, \`setTimeout\`, \`requestAnimationFrame\`, \`Date\`, or three.js \`useFrame\`.
14
14
  - NEVER use \`Math.random()\`; use \`random("seed-" + i)\` from "remotion" or \`noise2D/noise3D\` from "@remotion/noise".
@@ -51,6 +51,10 @@ const SELF_CHECK = `# Before you answer, check silently
51
51
  - At 30%, 60% and 100% of the duration the frame is fully composed, readable, balanced, and nothing overflows the safe margins or overlaps.
52
52
  - The scene matches the brief and the narration, and looks like a frame from a premium brand film.`;
53
53
  export const REMOTION_SYSTEM_PROMPT = [OUTPUT_CONTRACT, TIMING_RULES, DESIGN_SYSTEM, MOTION_LANGUAGE, TOOLKIT, SELF_CHECK].join("\n\n");
54
+ // The same contract for a host's agent that writes the component file itself and passes it as \`code\`.
55
+ // The system prompt stays byte-identical, since it is part of every cached component's key.
56
+ const COMPONENT_CONTRACT = ["# Component contract", COMPONENT_RULES, CONTRACT_BODY].join("\n");
57
+ export const REMOTION_COMPONENT_GUIDE = [COMPONENT_CONTRACT, TIMING_RULES, DESIGN_SYSTEM, MOTION_LANGUAGE, TOOLKIT, SELF_CHECK].join("\n\n");
54
58
  export const REMOTION_REVIEW_MARKER = "LGTM";
55
59
  export const REMOTION_REVIEW_SYSTEM_PROMPT = [
56
60
  REMOTION_SYSTEM_PROMPT,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mulmocast",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "lib/index.node.js",
@@ -16,6 +16,10 @@
16
16
  "./data": {
17
17
  "default": "./lib/data/index.js"
18
18
  },
19
+ "./remotion": {
20
+ "types": "./lib/index.remotion.d.ts",
21
+ "default": "./lib/index.remotion.js"
22
+ },
19
23
  "./assets/*": "./assets/*",
20
24
  "./scripts/*": "./scripts/*",
21
25
  "./tools/complete_script": {