mulmocast 2.12.0 → 2.13.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 (38) hide show
  1. package/assets/schemas/mulmo_script.json +434 -166
  2. package/lib/actions/avatar.d.ts +37 -0
  3. package/lib/actions/avatar.js +176 -0
  4. package/lib/actions/image_agents.js +9 -9
  5. package/lib/actions/index.d.ts +1 -0
  6. package/lib/actions/index.js +1 -0
  7. package/lib/actions/movie.d.ts +1 -0
  8. package/lib/actions/movie.js +40 -2
  9. package/lib/agents/combine_audio_files_agent.js +2 -2
  10. package/lib/cli/commands/movie/handler.js +2 -2
  11. package/lib/mcp/server.js +3 -3
  12. package/lib/methods/mulmo_beat.d.ts +1 -0
  13. package/lib/methods/mulmo_beat.js +3 -0
  14. package/lib/methods/mulmo_beat_node.d.ts +1 -0
  15. package/lib/methods/mulmo_presentation_style.d.ts +5 -1
  16. package/lib/methods/mulmo_presentation_style.js +8 -0
  17. package/lib/types/schema.d.ts +405 -0
  18. package/lib/types/schema.js +61 -0
  19. package/lib/types/type.d.ts +6 -1
  20. package/lib/utils/context.d.ts +158 -0
  21. package/lib/utils/image_plugins/index.js +2 -0
  22. package/lib/utils/image_plugins/remotion.d.ts +16 -0
  23. package/lib/utils/image_plugins/remotion.js +164 -0
  24. package/lib/utils/remotion/claude_prompt.d.ts +33 -0
  25. package/lib/utils/remotion/claude_prompt.js +111 -0
  26. package/lib/utils/remotion/claude_runner.d.ts +4 -0
  27. package/lib/utils/remotion/claude_runner.js +47 -0
  28. package/lib/utils/remotion/commit_files.d.ts +11 -0
  29. package/lib/utils/remotion/commit_files.js +37 -0
  30. package/lib/utils/remotion/packages.d.ts +5 -0
  31. package/lib/utils/remotion/packages.js +20 -0
  32. package/lib/utils/remotion/render.d.ts +19 -0
  33. package/lib/utils/remotion/render.js +63 -0
  34. package/lib/utils/remotion/system_prompt.d.ts +3 -0
  35. package/lib/utils/remotion/system_prompt.js +62 -0
  36. package/package.json +111 -23
  37. package/scripts/test/test_avatar.json +46 -0
  38. package/scripts/test/test_remotion.json +38 -0
@@ -34,6 +34,37 @@ export const speechOptionsSchema = z
34
34
  const speakerIdSchema = z.string();
35
35
  export const defaultSpeaker = "Presenter";
36
36
  export const text2SpeechProviderSchema = z.enum(Object.keys(provider2TTSAgent)).default(defaultProviders.tts);
37
+ // Talking avatars (AvatarScript): a speaker can speak with an avatar shown over the beat visuals.
38
+ const avatarPercentSchema = z.string().regex(/^-?\d+(\.\d+)?%$/, 'a percentage such as "84%"');
39
+ export const avatarPositionSchema = z
40
+ .object({
41
+ x: avatarPercentSchema.optional().describe("Horizontal centre of the avatar, % of the canvas width. Default 84%"),
42
+ y: avatarPercentSchema.optional().describe("Bottom edge of the avatar, % of the canvas height. Default 100%"),
43
+ scale: avatarPercentSchema.optional().describe("Height of the avatar, % of the canvas height. Default 62%"),
44
+ })
45
+ .strict();
46
+ export const avatarEmotionSchema = z.enum(["neutral", "happy", "sad", "angry", "surprised", "relaxed"]);
47
+ export const speakerAvatarSchema = z
48
+ .object({
49
+ source: z.string().describe("AvatarScript avatar package (a folder with avatar.json, or a mesh-avatar-studio project), relative to the script"),
50
+ position: avatarPositionSchema.optional(),
51
+ })
52
+ .strict();
53
+ export const mulmoAvatarParamsSchema = z
54
+ .object({
55
+ position: avatarPositionSchema.optional(),
56
+ emotion: avatarEmotionSchema.optional().describe("The avatar's expression during this beat"),
57
+ motions: z
58
+ .array(z
59
+ .object({
60
+ motion: z.string().describe("Motion name, e.g. nod, tilt, think, giggle, surprise"),
61
+ at: z.string().optional().describe("Words of the beat's text where the motion starts. Default: the start of the beat"),
62
+ })
63
+ .strict())
64
+ .optional(),
65
+ hidden: z.boolean().optional().describe("Hide the speaker's avatar during this beat"),
66
+ })
67
+ .strict();
37
68
  export const speakerDataSchema = z
38
69
  .object({
39
70
  displayName: z.record(langSchema, z.string()).optional(),
@@ -44,6 +75,7 @@ export const speakerDataSchema = z
44
75
  model: z.string().optional().describe("TTS model to use for this speaker"),
45
76
  baseURL: z.string().optional(), // Azure/custom endpoint URL
46
77
  apiVersion: z.string().optional(), // Azure API version (e.g., "2025-04-01-preview")
78
+ avatar: speakerAvatarSchema.optional().describe("Avatar shown over the beat visuals, speaking this speaker's lines"),
47
79
  })
48
80
  .strict();
49
81
  export const speakerSchema = speakerDataSchema.extend({
@@ -139,6 +171,7 @@ export const ImageMediaType = {
139
171
  MoviePrompt: "moviePrompt",
140
172
  Beat: "beat",
141
173
  VoiceOver: "voice_over",
174
+ Remotion: "remotion",
142
175
  };
143
176
  // Beat image types whose still derives from the movie or another beat — they cannot supply
144
177
  // their own still for the $beatImage frame sentinel.
@@ -326,6 +359,18 @@ export const mulmoVisionMediaSchema = z
326
359
  data: z.record(z.string(), z.any()),
327
360
  })
328
361
  .strict();
362
+ export const mulmoRemotionMediaSchema = z
363
+ .object({
364
+ 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
+ fps: z.number().min(1).max(60).optional().describe("Frame rate of the rendered video. Default: 30."),
367
+ })
368
+ .strict();
369
+ export const mulmoRemotionParamsSchema = z
370
+ .object({
371
+ brief: z.string().optional().describe("Whole-video art direction shared by every remotion beat (palette, type, mood), so all scenes look like one film."),
372
+ })
373
+ .strict();
329
374
  export const mulmoImageAssetSchema = z.union([
330
375
  mulmoMarkdownMediaSchema,
331
376
  mulmoWebMediaSchema,
@@ -341,6 +386,7 @@ export const mulmoImageAssetSchema = z.union([
341
386
  mulmoVoiceOverMediaSchema,
342
387
  mulmoVisionMediaSchema,
343
388
  mulmoSlideMediaSchema,
389
+ mulmoRemotionMediaSchema,
344
390
  ]);
345
391
  const mulmoAudioMediaSchema = z
346
392
  .object({
@@ -567,6 +613,7 @@ export const mulmoBeatSchema = z
567
613
  .optional(),
568
614
  soundEffectParams: mulmoSoundEffectParamsSchema.optional(),
569
615
  lipSyncParams: mulmoLipSyncParamsSchema.optional(),
616
+ avatarParams: mulmoAvatarParamsSchema.optional(),
570
617
  htmlImageParams: mulmoHtmlImageParamsSchema.optional(),
571
618
  speechOptions: speechOptionsSchema.optional(),
572
619
  textSlideParams: textSlideParamsSchema.optional(),
@@ -626,6 +673,7 @@ export const mulmoPresentationStyleSchema = z.object({
626
673
  provider: defaultProviders.soundEffect,
627
674
  }),
628
675
  lipSyncParams: mulmoLipSyncParamsSchema.optional(),
676
+ avatarParams: mulmoAvatarParamsSchema.optional(),
629
677
  htmlImageParams: mulmoHtmlImageParamsSchema
630
678
  .extend({
631
679
  provider: text2HtmlImageProviderSchema,
@@ -635,6 +683,8 @@ export const mulmoPresentationStyleSchema = z.object({
635
683
  textSlideParams: textSlideParamsSchema.optional(),
636
684
  // for slide plugin
637
685
  slideParams: mulmoSlideParamsSchema.optional(),
686
+ // for remotion plugin
687
+ remotionParams: mulmoRemotionParamsSchema.optional(),
638
688
  captionParams: mulmoCaptionParamsSchema.optional(),
639
689
  audioParams: audioParamsSchema.default({
640
690
  introPadding: 1.0,
@@ -730,11 +780,22 @@ export const mulmoSessionStateSchema = z.object({
730
780
  lipSync: z.record(z.string(), z.boolean()),
731
781
  }),
732
782
  });
783
+ export const mulmoAvatarTrackSchema = z
784
+ .object({
785
+ speaker: z.string(),
786
+ file: z.string(), // see-through, avatar-sized video (WebM VP9 with alpha)
787
+ width: z.number(), // the size it was rendered at, pixels
788
+ height: z.number(),
789
+ // where it is shown: absolute [start, end) seconds, and its place and size on the canvas in pixels
790
+ placements: z.array(z.object({ start: z.number(), end: z.number(), x: z.number(), y: z.number(), width: z.number(), height: z.number() }).strict()),
791
+ })
792
+ .strict();
733
793
  export const mulmoStudioSchema = z
734
794
  .object({
735
795
  script: mulmoScriptSchema,
736
796
  filename: z.string(),
737
797
  beats: z.array(mulmoStudioBeatSchema).min(1),
798
+ avatarTracks: z.array(mulmoAvatarTrackSchema).optional(),
738
799
  })
739
800
  .strict();
740
801
  export const mulmoPromptTemplateSchema = z
@@ -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, mulmoTextSlideMediaSchema, mulmoMarkdownMediaSchema, mulmoImageMediaSchema, mulmoChartMediaSchema, mediaSourceSchema, mediaSourceMermaidSchema, backgroundImageSchema, backgroundImageSourceSchema, mulmoSessionStateSchema, mulmoOpenAIImageModelSchema, mulmoGoogleImageModelSchema, mulmoGoogleMovieModelSchema, mulmoReplicateMovieModelSchema, mulmoImagePromptMediaSchema, mulmoImageReferenceSchema, mulmoMovieMediaSchema, mulmoMoviePromptMediaSchema, markdownLayoutSchema, row2Schema, grid2x2Schema } 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, 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";
@@ -44,6 +44,10 @@ export type MultiLingualTexts = z.infer<typeof multiLingualTextsSchema>;
44
44
  export type MulmoMovieParams = z.infer<typeof mulmoMovieParamsSchema>;
45
45
  export type MulmoSoundEffectParams = z.infer<typeof mulmoSoundEffectParamsSchema>;
46
46
  export type MulmoLipSyncParams = z.infer<typeof mulmoLipSyncParamsSchema>;
47
+ export type MulmoAvatarParams = z.infer<typeof mulmoAvatarParamsSchema>;
48
+ export type MulmoAvatarPosition = z.infer<typeof avatarPositionSchema>;
49
+ export type MulmoSpeakerAvatar = z.infer<typeof speakerAvatarSchema>;
50
+ export type MulmoAvatarTrack = z.infer<typeof mulmoAvatarTrackSchema>;
47
51
  export type MulmoOpenAIImageModel = z.infer<typeof mulmoOpenAIImageModelSchema>;
48
52
  export type MulmoGoogleImageModel = z.infer<typeof mulmoGoogleImageModelSchema>;
49
53
  export type MulmoGoogleMovieModel = z.infer<typeof mulmoGoogleMovieModelSchema>;
@@ -61,6 +65,7 @@ export type MulmoImageMedia = z.infer<typeof mulmoImageMediaSchema>;
61
65
  export type MulmoMovieMedia = z.infer<typeof mulmoMovieMediaSchema>;
62
66
  export type MulmoChartMedia = z.infer<typeof mulmoChartMediaSchema>;
63
67
  export type MulmoMermaidMedia = z.infer<typeof mulmoMermaidMediaSchema>;
68
+ export type MulmoRemotionMedia = z.infer<typeof mulmoRemotionMediaSchema>;
64
69
  export type MulmoSessionState = z.infer<typeof mulmoSessionStateSchema>;
65
70
  export type MulmoStudioContext = {
66
71
  fileDirs: FileObject;
@@ -53,6 +53,14 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
53
53
  model?: string | undefined;
54
54
  baseURL?: string | undefined;
55
55
  apiVersion?: string | undefined;
56
+ avatar?: {
57
+ source: string;
58
+ position?: {
59
+ x?: string | undefined;
60
+ y?: string | undefined;
61
+ scale?: string | undefined;
62
+ } | undefined;
63
+ } | undefined;
56
64
  lang?: Record<string, {
57
65
  voiceId: string;
58
66
  displayName?: Record<string, string> | undefined;
@@ -68,6 +76,14 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
68
76
  model?: string | undefined;
69
77
  baseURL?: string | undefined;
70
78
  apiVersion?: string | undefined;
79
+ avatar?: {
80
+ source: string;
81
+ position?: {
82
+ x?: string | undefined;
83
+ y?: string | undefined;
84
+ scale?: string | undefined;
85
+ } | undefined;
86
+ } | undefined;
71
87
  }> | undefined;
72
88
  }>;
73
89
  };
@@ -532,6 +548,10 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
532
548
  type: "vision";
533
549
  style: string;
534
550
  data: Record<string, any>;
551
+ } | {
552
+ type: "remotion";
553
+ prompt: string;
554
+ fps?: number | undefined;
535
555
  } | {
536
556
  type: "slide";
537
557
  slide: {
@@ -2371,6 +2391,19 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
2371
2391
  provider?: string | undefined;
2372
2392
  model?: string | undefined;
2373
2393
  } | undefined;
2394
+ avatarParams?: {
2395
+ position?: {
2396
+ x?: string | undefined;
2397
+ y?: string | undefined;
2398
+ scale?: string | undefined;
2399
+ } | undefined;
2400
+ emotion?: "neutral" | "happy" | "sad" | "angry" | "surprised" | "relaxed" | undefined;
2401
+ motions?: {
2402
+ motion: string;
2403
+ at?: string | undefined;
2404
+ }[] | undefined;
2405
+ hidden?: boolean | undefined;
2406
+ } | undefined;
2374
2407
  htmlImageParams?: {
2375
2408
  model?: string | undefined;
2376
2409
  } | undefined;
@@ -2474,6 +2507,19 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
2474
2507
  provider?: string | undefined;
2475
2508
  model?: string | undefined;
2476
2509
  } | undefined;
2510
+ avatarParams?: {
2511
+ position?: {
2512
+ x?: string | undefined;
2513
+ y?: string | undefined;
2514
+ scale?: string | undefined;
2515
+ } | undefined;
2516
+ emotion?: "neutral" | "happy" | "sad" | "angry" | "surprised" | "relaxed" | undefined;
2517
+ motions?: {
2518
+ motion: string;
2519
+ at?: string | undefined;
2520
+ }[] | undefined;
2521
+ hidden?: boolean | undefined;
2522
+ } | undefined;
2477
2523
  htmlImageParams?: {
2478
2524
  provider: string;
2479
2525
  model?: string | undefined;
@@ -2540,6 +2586,9 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
2540
2586
  } | undefined;
2541
2587
  } | undefined;
2542
2588
  } | undefined;
2589
+ remotionParams?: {
2590
+ brief?: string | undefined;
2591
+ } | undefined;
2543
2592
  captionParams?: {
2544
2593
  lang?: string | undefined;
2545
2594
  styles?: string[] | undefined;
@@ -2588,6 +2637,20 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
2588
2637
  markdown?: string | undefined;
2589
2638
  html?: string | undefined;
2590
2639
  }[];
2640
+ avatarTracks?: {
2641
+ speaker: string;
2642
+ file: string;
2643
+ width: number;
2644
+ height: number;
2645
+ placements: {
2646
+ start: number;
2647
+ end: number;
2648
+ x: number;
2649
+ y: number;
2650
+ width: number;
2651
+ height: number;
2652
+ }[];
2653
+ }[] | undefined;
2591
2654
  };
2592
2655
  export declare const fetchScript: (isHttpPath: boolean, mulmoFilePath: string, fileOrUrl: string) => Promise<MulmoScript | null>;
2593
2656
  export declare const getMultiLingual: (multilingualFilePath: string, beats: MulmoStudioBeat[]) => MulmoStudioMultiLingual;
@@ -2619,6 +2682,14 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
2619
2682
  model?: string | undefined;
2620
2683
  baseURL?: string | undefined;
2621
2684
  apiVersion?: string | undefined;
2685
+ avatar?: {
2686
+ source: string;
2687
+ position?: {
2688
+ x?: string | undefined;
2689
+ y?: string | undefined;
2690
+ scale?: string | undefined;
2691
+ } | undefined;
2692
+ } | undefined;
2622
2693
  lang?: Record<string, {
2623
2694
  voiceId: string;
2624
2695
  displayName?: Record<string, string> | undefined;
@@ -2634,6 +2705,14 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
2634
2705
  model?: string | undefined;
2635
2706
  baseURL?: string | undefined;
2636
2707
  apiVersion?: string | undefined;
2708
+ avatar?: {
2709
+ source: string;
2710
+ position?: {
2711
+ x?: string | undefined;
2712
+ y?: string | undefined;
2713
+ scale?: string | undefined;
2714
+ } | undefined;
2715
+ } | undefined;
2637
2716
  }> | undefined;
2638
2717
  }>;
2639
2718
  };
@@ -3098,6 +3177,10 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
3098
3177
  type: "vision";
3099
3178
  style: string;
3100
3179
  data: Record<string, any>;
3180
+ } | {
3181
+ type: "remotion";
3182
+ prompt: string;
3183
+ fps?: number | undefined;
3101
3184
  } | {
3102
3185
  type: "slide";
3103
3186
  slide: {
@@ -4937,6 +5020,19 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
4937
5020
  provider?: string | undefined;
4938
5021
  model?: string | undefined;
4939
5022
  } | undefined;
5023
+ avatarParams?: {
5024
+ position?: {
5025
+ x?: string | undefined;
5026
+ y?: string | undefined;
5027
+ scale?: string | undefined;
5028
+ } | undefined;
5029
+ emotion?: "neutral" | "happy" | "sad" | "angry" | "surprised" | "relaxed" | undefined;
5030
+ motions?: {
5031
+ motion: string;
5032
+ at?: string | undefined;
5033
+ }[] | undefined;
5034
+ hidden?: boolean | undefined;
5035
+ } | undefined;
4940
5036
  htmlImageParams?: {
4941
5037
  model?: string | undefined;
4942
5038
  } | undefined;
@@ -5040,6 +5136,19 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
5040
5136
  provider?: string | undefined;
5041
5137
  model?: string | undefined;
5042
5138
  } | undefined;
5139
+ avatarParams?: {
5140
+ position?: {
5141
+ x?: string | undefined;
5142
+ y?: string | undefined;
5143
+ scale?: string | undefined;
5144
+ } | undefined;
5145
+ emotion?: "neutral" | "happy" | "sad" | "angry" | "surprised" | "relaxed" | undefined;
5146
+ motions?: {
5147
+ motion: string;
5148
+ at?: string | undefined;
5149
+ }[] | undefined;
5150
+ hidden?: boolean | undefined;
5151
+ } | undefined;
5043
5152
  htmlImageParams?: {
5044
5153
  provider: string;
5045
5154
  model?: string | undefined;
@@ -5106,6 +5215,9 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
5106
5215
  } | undefined;
5107
5216
  } | undefined;
5108
5217
  } | undefined;
5218
+ remotionParams?: {
5219
+ brief?: string | undefined;
5220
+ } | undefined;
5109
5221
  captionParams?: {
5110
5222
  lang?: string | undefined;
5111
5223
  styles?: string[] | undefined;
@@ -5154,6 +5266,20 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
5154
5266
  markdown?: string | undefined;
5155
5267
  html?: string | undefined;
5156
5268
  }[];
5269
+ avatarTracks?: {
5270
+ speaker: string;
5271
+ file: string;
5272
+ width: number;
5273
+ height: number;
5274
+ placements: {
5275
+ start: number;
5276
+ end: number;
5277
+ x: number;
5278
+ y: number;
5279
+ width: number;
5280
+ height: number;
5281
+ }[];
5282
+ }[] | undefined;
5157
5283
  };
5158
5284
  multiLingual: {
5159
5285
  multiLingualTexts: Record<string, {
@@ -5192,6 +5318,14 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
5192
5318
  model?: string | undefined;
5193
5319
  baseURL?: string | undefined;
5194
5320
  apiVersion?: string | undefined;
5321
+ avatar?: {
5322
+ source: string;
5323
+ position?: {
5324
+ x?: string | undefined;
5325
+ y?: string | undefined;
5326
+ scale?: string | undefined;
5327
+ } | undefined;
5328
+ } | undefined;
5195
5329
  lang?: Record<string, {
5196
5330
  voiceId: string;
5197
5331
  displayName?: Record<string, string> | undefined;
@@ -5207,6 +5341,14 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
5207
5341
  model?: string | undefined;
5208
5342
  baseURL?: string | undefined;
5209
5343
  apiVersion?: string | undefined;
5344
+ avatar?: {
5345
+ source: string;
5346
+ position?: {
5347
+ x?: string | undefined;
5348
+ y?: string | undefined;
5349
+ scale?: string | undefined;
5350
+ } | undefined;
5351
+ } | undefined;
5210
5352
  }> | undefined;
5211
5353
  }>;
5212
5354
  };
@@ -5498,6 +5640,19 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
5498
5640
  provider?: string | undefined;
5499
5641
  model?: string | undefined;
5500
5642
  } | undefined;
5643
+ avatarParams?: {
5644
+ position?: {
5645
+ x?: string | undefined;
5646
+ y?: string | undefined;
5647
+ scale?: string | undefined;
5648
+ } | undefined;
5649
+ emotion?: "neutral" | "happy" | "sad" | "angry" | "surprised" | "relaxed" | undefined;
5650
+ motions?: {
5651
+ motion: string;
5652
+ at?: string | undefined;
5653
+ }[] | undefined;
5654
+ hidden?: boolean | undefined;
5655
+ } | undefined;
5501
5656
  htmlImageParams?: {
5502
5657
  provider: string;
5503
5658
  model?: string | undefined;
@@ -5564,6 +5719,9 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
5564
5719
  } | undefined;
5565
5720
  } | undefined;
5566
5721
  } | undefined;
5722
+ remotionParams?: {
5723
+ brief?: string | undefined;
5724
+ } | undefined;
5567
5725
  captionParams?: {
5568
5726
  lang?: string | undefined;
5569
5727
  styles?: string[] | undefined;
@@ -9,6 +9,7 @@ import * as pluginBeat from "./beat.js";
9
9
  import * as pluginVoiceOver from "./voice_over.js";
10
10
  import * as pluginVision from "./vision.js";
11
11
  import * as pluginSlide from "./slide.js";
12
+ import * as pluginRemotion from "./remotion.js";
12
13
  const imagePlugins = [
13
14
  pluginTextSlide,
14
15
  pluginMarkdown,
@@ -21,6 +22,7 @@ const imagePlugins = [
21
22
  pluginVoiceOver,
22
23
  pluginVision,
23
24
  pluginSlide,
25
+ pluginRemotion,
24
26
  ];
25
27
  export const findImagePlugin = (imageType) => {
26
28
  return imagePlugins.find((plugin) => plugin.imageType === imageType);
@@ -0,0 +1,16 @@
1
+ import { ImageProcessorParams } from "../../types/index.js";
2
+ import { ComponentReviewer, ComponentWriter } from "../remotion/claude_runner.js";
3
+ import { SceneRenderer } from "../remotion/render.js";
4
+ export declare const imageType: "remotion";
5
+ export type RemotionDeps = {
6
+ ensurePackages: () => void;
7
+ writeComponent: ComponentWriter;
8
+ reviewComponent: ComponentReviewer;
9
+ renderScene: SceneRenderer;
10
+ };
11
+ export declare const remotionWorkDir: (imagePath: string) => string;
12
+ export declare const remotionStillPath: (imagePath: string) => string;
13
+ export declare const reviewCandidatePath: (filePath: string) => string;
14
+ export declare const createRemotionProcess: (deps: RemotionDeps) => (params: ImageProcessorParams) => Promise<string | undefined>;
15
+ export declare const process: (params: ImageProcessorParams) => Promise<string | undefined>;
16
+ export declare const path: (params: import("../../types/type.js").BeatPathParams) => string;
@@ -0,0 +1,164 @@
1
+ import fs from "node:fs";
2
+ import nodePath from "node:path";
3
+ import { GraphAILogger } from "graphai";
4
+ import { ImageMediaType } from "../../types/index.js";
5
+ import { localizedText } from "../utils.js";
6
+ import { REMOTION_COMPONENT_FILE, buildRepairPrompt, buildReviewPrompt, buildScenePrompt, remotionCacheKey, } from "../remotion/claude_prompt.js";
7
+ import { reviewComponentWithClaude, writeComponentWithClaude } from "../remotion/claude_runner.js";
8
+ import { ensureRemotionPackages, renderRemotionScene } from "../remotion/render.js";
9
+ import { commitFiles } from "../remotion/commit_files.js";
10
+ import { parrotingImagePath } from "./utils.js";
11
+ export const imageType = ImageMediaType.Remotion;
12
+ const DEFAULT_FPS = 30;
13
+ const MAX_REPAIR_ATTEMPTS = 2;
14
+ // Without audio the length is unknown; any length gives the same final frame, since the system
15
+ // prompt makes every animation finish before the end.
16
+ const STILL_ONLY_DURATION_SEC = 10;
17
+ const REVIEW_FRACTIONS = [0.3, 0.6, 0.95];
18
+ export const remotionWorkDir = (imagePath) => imagePath.replace(/(_animated)?\.(mp4|png)$/, "_remotion");
19
+ export const remotionStillPath = (imagePath) => imagePath.replace(/_animated\.mp4$/, ".png");
20
+ const loadOrWriteComponent = async (job, force, deps) => {
21
+ if (!force && fs.existsSync(job.codePath)) {
22
+ return { code: fs.readFileSync(job.codePath, "utf8"), isNew: false };
23
+ }
24
+ const code = await deps.writeComponent(buildScenePrompt(job.spec), job.workDir);
25
+ fs.writeFileSync(job.codePath, code);
26
+ return { code, isNew: true };
27
+ };
28
+ // Resolves to the code that rendered, which is a repaired version when the given one failed.
29
+ const renderWithRepair = async (job, code, attemptsLeft, deps, reviewStills = []) => {
30
+ fs.writeFileSync(nodePath.join(job.workDir, REMOTION_COMPONENT_FILE), code);
31
+ try {
32
+ const extraStills = reviewStills.map(({ frame, file }) => ({ frame, path: file }));
33
+ await deps.renderScene({ workDir: job.workDir, props: job.props, videoPath: job.videoPath, stillPath: job.stillPath, extraStills });
34
+ return code;
35
+ }
36
+ catch (error) {
37
+ if (attemptsLeft <= 0) {
38
+ throw new Error(`remotion beat failed to render after repairs (code: ${job.codePath})`, { cause: error });
39
+ }
40
+ const message = error instanceof Error ? error.message : String(error);
41
+ GraphAILogger.info(`remotion: render failed, asking claude -p to repair (${attemptsLeft} left): ${message.slice(0, 200)}`);
42
+ const repaired = await deps.writeComponent(buildRepairPrompt(job.spec, code, message), job.workDir);
43
+ fs.writeFileSync(job.codePath, repaired);
44
+ return await renderWithRepair(job, repaired, attemptsLeft - 1, deps, reviewStills);
45
+ }
46
+ };
47
+ const reviewStillsFor = (job) => REVIEW_FRACTIONS.map((fraction, index) => ({
48
+ fraction,
49
+ frame: Math.min(job.props.durationInFrames - 1, Math.floor(job.props.durationInFrames * fraction)),
50
+ file: nodePath.join(job.workDir, `review_${index}.png`),
51
+ }));
52
+ const errorMessage = (error) => (error instanceof Error ? error.message : String(error));
53
+ // null when the review itself failed; undefined when the reviewer approved the scene as it is.
54
+ const askForReview = async (job, renderedCode, reviewStills, deps) => {
55
+ try {
56
+ return await deps.reviewComponent(buildReviewPrompt(job.spec, renderedCode, reviewStills), job.workDir);
57
+ }
58
+ catch (error) {
59
+ GraphAILogger.info(`remotion: visual review skipped: ${errorMessage(error)}`);
60
+ return null;
61
+ }
62
+ };
63
+ const nodeFileOps = {
64
+ exists: (file) => fs.existsSync(file),
65
+ rename: (from, to) => fs.renameSync(from, to),
66
+ remove: (file) => fs.rmSync(file, { force: true }),
67
+ };
68
+ export const reviewCandidatePath = (filePath) => filePath.replace(/(\.[^./]+)$/, ".review$1");
69
+ // The reviewed version renders to side files that replace the first version's output only once it
70
+ // succeeded, so a failure leaves the scene that already rendered — files and cached code — untouched.
71
+ const renderReviewedVersion = async (job, improved, deps) => {
72
+ const candidate = {
73
+ ...job,
74
+ codePath: reviewCandidatePath(job.codePath),
75
+ videoPath: job.videoPath && reviewCandidatePath(job.videoPath),
76
+ stillPath: reviewCandidatePath(job.stillPath),
77
+ };
78
+ try {
79
+ const renderedImprovement = await renderWithRepair(candidate, improved, MAX_REPAIR_ATTEMPTS, deps);
80
+ fs.writeFileSync(candidate.codePath, renderedImprovement);
81
+ const moves = [
82
+ ...(job.videoPath && candidate.videoPath ? [{ from: candidate.videoPath, to: job.videoPath }] : []),
83
+ { from: candidate.stillPath, to: job.stillPath },
84
+ { from: candidate.codePath, to: job.codePath },
85
+ ];
86
+ commitFiles(moves, nodeFileOps);
87
+ }
88
+ catch (error) {
89
+ GraphAILogger.info(`remotion: the reviewed version was not applied, keeping the first one: ${errorMessage(error)}`);
90
+ }
91
+ finally {
92
+ [candidate.codePath, candidate.videoPath, candidate.stillPath].forEach((file) => file && fs.rmSync(file, { force: true }));
93
+ }
94
+ };
95
+ const reviewAndImprove = async (job, renderedCode, reviewStills, deps) => {
96
+ const improved = await askForReview(job, renderedCode, reviewStills, deps);
97
+ if (improved === null)
98
+ return;
99
+ if (improved === undefined) {
100
+ GraphAILogger.info(`remotion: visual review approved ${job.codePath}`);
101
+ return;
102
+ }
103
+ GraphAILogger.info(`remotion: visual review improved ${job.codePath}`);
104
+ await renderReviewedVersion(job, improved, deps);
105
+ };
106
+ const toFrameCount = (durationSec, fps) => {
107
+ const frames = Math.floor(durationSec * fps);
108
+ if (frames <= 0) {
109
+ throw new Error(`remotion: frame count is ${frames} (duration=${durationSec}, fps=${fps}). Increase duration or fps.`);
110
+ }
111
+ return frames;
112
+ };
113
+ // Lets a scene number itself and pace its place in the video; unknown when the beat is not in the script.
114
+ const scenePosition = (beats, index) => (index < 0 ? undefined : { index, count: beats.length });
115
+ // The words actually spoken over the scene: the translation when the video is rendered in another language.
116
+ 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;
119
+ const duration = params.beatDuration ?? beat.duration;
120
+ const index = context.studio.script.beats.indexOf(beat);
121
+ const spec = {
122
+ prompt,
123
+ narration: spokenNarration(context, beat, index),
124
+ brief: context.presentationStyle.remotionParams?.brief || undefined,
125
+ position: scenePosition(context.studio.script.beats, index),
126
+ fps,
127
+ width: canvasSize.width,
128
+ height: canvasSize.height,
129
+ };
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
+ };
139
+ };
140
+ export const createRemotionProcess = (deps) => async (params) => {
141
+ const { beat, context } = params;
142
+ if (!beat.image || beat.image.type !== imageType)
143
+ return;
144
+ const job = buildJob(params, beat.image.prompt, beat.image.fps ?? DEFAULT_FPS);
145
+ deps.ensurePackages();
146
+ fs.mkdirSync(job.workDir, { recursive: true });
147
+ const { code, isNew } = await loadOrWriteComponent(job, context.force, deps);
148
+ if (isNew) {
149
+ const reviewStills = reviewStillsFor(job);
150
+ const renderedCode = await renderWithRepair(job, code, MAX_REPAIR_ATTEMPTS, deps, reviewStills);
151
+ await reviewAndImprove(job, renderedCode, reviewStills, deps);
152
+ }
153
+ else {
154
+ await renderWithRepair(job, code, MAX_REPAIR_ATTEMPTS, deps);
155
+ }
156
+ return job.videoPath ?? job.stillPath;
157
+ };
158
+ export const process = createRemotionProcess({
159
+ ensurePackages: ensureRemotionPackages,
160
+ writeComponent: writeComponentWithClaude,
161
+ reviewComponent: reviewComponentWithClaude,
162
+ renderScene: renderRemotionScene,
163
+ });
164
+ export const path = parrotingImagePath;
@@ -0,0 +1,33 @@
1
+ export declare const REMOTION_COMPONENT_FILE = "Generated.tsx";
2
+ export declare const REMOTION_ENTRY_FILE = "index.tsx";
3
+ export declare const REMOTION_COMPOSITION_ID = "Beat";
4
+ export type RemotionSceneSpec = {
5
+ prompt: string;
6
+ narration?: string;
7
+ brief?: string;
8
+ position?: {
9
+ index: number;
10
+ count: number;
11
+ };
12
+ fps: number;
13
+ width: number;
14
+ height: number;
15
+ };
16
+ export declare const buildClaudeArgs: (userPrompt: string, systemPrompt?: string, tools?: string) => string[];
17
+ export declare const buildScenePrompt: (spec: RemotionSceneSpec) => string;
18
+ export declare const buildRepairPrompt: (spec: RemotionSceneSpec, code: string, error: string) => string;
19
+ export type ReviewFrame = {
20
+ fraction: number;
21
+ file: string;
22
+ };
23
+ export declare const buildReviewPrompt: (spec: RemotionSceneSpec, code: string, frames: ReviewFrame[]) => string;
24
+ export declare const parseReviewReply: (replyText: string) => string | undefined;
25
+ export declare const remotionCacheKey: (spec: RemotionSceneSpec, systemPrompt?: string) => string;
26
+ export declare const extractTsx: (replyText: string) => string | undefined;
27
+ type ClaudeJsonReply = {
28
+ result: string;
29
+ is_error?: boolean;
30
+ };
31
+ export declare const parseClaudeReply: (stdout: string) => ClaudeJsonReply;
32
+ export declare const buildEntrySource: () => string;
33
+ export {};