mulmocast 2.9.0 → 2.9.2

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 (127) hide show
  1. package/lib/actions/movie.d.ts +2 -9
  2. package/lib/actions/movie.js +149 -147
  3. package/lib/agents/image_google_agent.d.ts +5 -0
  4. package/lib/agents/image_google_agent.js +86 -0
  5. package/lib/agents/movie_google_agent.d.ts +9 -0
  6. package/lib/agents/movie_google_agent.js +122 -0
  7. package/lib/agents/mulmo_prompts_agent.d.ts +7 -0
  8. package/lib/agents/mulmo_prompts_agent.js +37 -0
  9. package/lib/agents/prompts_data.d.ts +15 -0
  10. package/lib/agents/prompts_data.js +16 -0
  11. package/lib/agents/tts_nijivoice_agent.d.ts +5 -0
  12. package/lib/agents/tts_nijivoice_agent.js +69 -0
  13. package/lib/agents/validate_mulmo_script_agent.d.ts +17 -0
  14. package/lib/agents/validate_mulmo_script_agent.js +34 -0
  15. package/lib/cli/args.d.ts +15 -0
  16. package/lib/cli/args.js +62 -0
  17. package/lib/cli/bin.js +0 -0
  18. package/lib/cli/cli.d.ts +17 -0
  19. package/lib/cli/cli.js +117 -0
  20. package/lib/cli/run.d.ts +2 -0
  21. package/lib/cli/run.js +3 -0
  22. package/lib/cli/tool-args.d.ts +16 -0
  23. package/lib/cli/tool-args.js +64 -0
  24. package/lib/cli/tool-cli.d.ts +2 -0
  25. package/lib/cli/tool-cli.js +32 -0
  26. package/lib/cli/tool_commands/prompt/builder.d.ts +4 -0
  27. package/lib/cli/tool_commands/prompt/builder.js +11 -0
  28. package/lib/cli/tool_commands/prompt/handler.d.ts +4 -0
  29. package/lib/cli/tool_commands/prompt/handler.js +14 -0
  30. package/lib/cli/tool_commands/prompt/index.d.ts +4 -0
  31. package/lib/cli/tool_commands/prompt/index.js +4 -0
  32. package/lib/cli/tool_commands/schema/builder.d.ts +2 -0
  33. package/lib/cli/tool_commands/schema/builder.js +3 -0
  34. package/lib/cli/tool_commands/schema/handler.d.ts +2 -0
  35. package/lib/cli/tool_commands/schema/handler.js +12 -0
  36. package/lib/cli/tool_commands/schema/index.d.ts +4 -0
  37. package/lib/cli/tool_commands/schema/index.js +4 -0
  38. package/lib/cli/tool_commands/scripting/builder.d.ts +20 -0
  39. package/lib/cli/tool_commands/scripting/builder.js +63 -0
  40. package/lib/cli/tool_commands/scripting/handler.d.ts +12 -0
  41. package/lib/cli/tool_commands/scripting/handler.js +36 -0
  42. package/lib/cli/tool_commands/scripting/index.d.ts +4 -0
  43. package/lib/cli/tool_commands/scripting/index.js +4 -0
  44. package/lib/index.d.ts +8 -0
  45. package/lib/index.js +8 -0
  46. package/lib/methods/mulmo_beat.d.ts +3 -0
  47. package/lib/methods/mulmo_beat.js +17 -0
  48. package/lib/methods/mulmo_beat_node.d.ts +3 -0
  49. package/lib/methods/mulmo_presentation_style.d.ts +5 -1
  50. package/lib/methods/mulmo_presentation_style.js +43 -1
  51. package/lib/methods/mulmo_script_template.d.ts +4 -0
  52. package/lib/methods/mulmo_script_template.js +6 -0
  53. package/lib/methods/mulmo_studio.d.ts +8 -0
  54. package/lib/methods/mulmo_studio.js +25 -0
  55. package/lib/methods/mulmo_studio_context.d.ts +6 -1
  56. package/lib/methods/mulmo_studio_context.js +36 -1
  57. package/lib/slide/blocks.d.ts +13 -0
  58. package/lib/slide/blocks.js +251 -0
  59. package/lib/slide/index.d.ts +6 -0
  60. package/lib/slide/index.js +7 -0
  61. package/lib/slide/layouts/big_quote.d.ts +2 -0
  62. package/lib/slide/layouts/big_quote.js +19 -0
  63. package/lib/slide/layouts/columns.d.ts +2 -0
  64. package/lib/slide/layouts/columns.js +53 -0
  65. package/lib/slide/layouts/comparison.d.ts +2 -0
  66. package/lib/slide/layouts/comparison.js +25 -0
  67. package/lib/slide/layouts/funnel.d.ts +2 -0
  68. package/lib/slide/layouts/funnel.js +25 -0
  69. package/lib/slide/layouts/grid.d.ts +2 -0
  70. package/lib/slide/layouts/grid.js +38 -0
  71. package/lib/slide/layouts/index.d.ts +3 -0
  72. package/lib/slide/layouts/index.js +46 -0
  73. package/lib/slide/layouts/matrix.d.ts +2 -0
  74. package/lib/slide/layouts/matrix.js +53 -0
  75. package/lib/slide/layouts/split.d.ts +2 -0
  76. package/lib/slide/layouts/split.js +51 -0
  77. package/lib/slide/layouts/stats.d.ts +2 -0
  78. package/lib/slide/layouts/stats.js +23 -0
  79. package/lib/slide/layouts/table.d.ts +2 -0
  80. package/lib/slide/layouts/table.js +10 -0
  81. package/lib/slide/layouts/timeline.d.ts +2 -0
  82. package/lib/slide/layouts/timeline.js +27 -0
  83. package/lib/slide/layouts/title.d.ts +2 -0
  84. package/lib/slide/layouts/title.js +19 -0
  85. package/lib/slide/layouts/waterfall.d.ts +2 -0
  86. package/lib/slide/layouts/waterfall.js +63 -0
  87. package/lib/slide/render.d.ts +17 -0
  88. package/lib/slide/render.js +101 -0
  89. package/lib/slide/schema.d.ts +9309 -0
  90. package/lib/slide/schema.js +434 -0
  91. package/lib/slide/utils.d.ts +76 -0
  92. package/lib/slide/utils.js +243 -0
  93. package/lib/tools/prompt.d.ts +1 -0
  94. package/lib/tools/prompt.js +18 -0
  95. package/lib/tools/seed.d.ts +3 -0
  96. package/lib/tools/seed.js +211 -0
  97. package/lib/tools/seed_from_url.d.ts +3 -0
  98. package/lib/tools/seed_from_url.js +188 -0
  99. package/lib/types/slide.d.ts +9309 -0
  100. package/lib/types/slide.js +434 -0
  101. package/lib/utils/assets.d.ts +18 -0
  102. package/lib/utils/assets.js +101 -0
  103. package/lib/utils/cli.d.ts +1 -0
  104. package/lib/utils/cli.js +15 -0
  105. package/lib/utils/const.d.ts +12 -0
  106. package/lib/utils/const.js +12 -0
  107. package/lib/utils/filters.js +11 -2
  108. package/lib/utils/image_preprocess.d.ts +14 -0
  109. package/lib/utils/image_preprocess.js +52 -0
  110. package/lib/utils/markdown.d.ts +3 -0
  111. package/lib/utils/markdown.js +42 -0
  112. package/lib/utils/pdf.d.ts +9 -0
  113. package/lib/utils/pdf.js +77 -0
  114. package/lib/utils/preprocess.d.ts +343 -0
  115. package/lib/utils/preprocess.js +58 -0
  116. package/lib/utils/provider2agent.d.ts +141 -0
  117. package/lib/utils/provider2agent.js +267 -0
  118. package/lib/utils/text_hash.d.ts +1 -0
  119. package/lib/utils/text_hash.js +4 -0
  120. package/package.json +8 -8
  121. package/scripts/test/test_voice_over_transition_no_audio.json +71 -0
  122. package/lib/utils/sdk_timeout.d.ts +0 -3
  123. package/lib/utils/sdk_timeout.js +0 -7
  124. package/scripts/test/test_captions_2.json~ +0 -278
  125. package/scripts/test/test_en_portrait.json~ +0 -31
  126. package/scripts/test/test_slide_image_ref_gemini_en.json~ +0 -289
  127. package/scripts/test/test_vertexai.json~ +0 -21
@@ -0,0 +1,64 @@
1
+ import yargs from "yargs";
2
+ import { hideBin } from "yargs/helpers";
3
+ import { commonOptions } from "./common.js";
4
+ import { getAvailableTemplates } from "../utils/file.js";
5
+ import { llmAgents } from "../utils/utils.js";
6
+ const availableTemplateNames = getAvailableTemplates().map((template) => template.filename);
7
+ export const args = commonOptions(yargs(hideBin(process.argv)))
8
+ .scriptName("mulmo-tool")
9
+ .option("u", {
10
+ alias: "url",
11
+ description: "URLs to reference (required when not in interactive mode)",
12
+ demandOption: false,
13
+ default: [],
14
+ type: "array",
15
+ string: true,
16
+ })
17
+ .option("i", {
18
+ alias: "interactive",
19
+ description: "Generate script in interactive mode with user prompts",
20
+ demandOption: false,
21
+ type: "boolean",
22
+ })
23
+ .option("t", {
24
+ alias: "template",
25
+ description: "Template name to use",
26
+ demandOption: false,
27
+ choices: availableTemplateNames,
28
+ type: "string",
29
+ })
30
+ .option("c", {
31
+ alias: "cache",
32
+ description: "cache dir",
33
+ demandOption: false,
34
+ type: "string",
35
+ })
36
+ .option("s", {
37
+ alias: "script",
38
+ description: "script filename",
39
+ demandOption: false,
40
+ default: "script",
41
+ type: "string",
42
+ })
43
+ .option("llm_agent", {
44
+ description: "llm agent",
45
+ demandOption: false,
46
+ choices: llmAgents,
47
+ type: "string",
48
+ })
49
+ .option("llm_model", {
50
+ description: "llm model",
51
+ demandOption: false,
52
+ type: "string",
53
+ })
54
+ .command("$0 <action>", "Run mulmocast tool", (yargs) => {
55
+ return yargs.positional("action", {
56
+ describe: "action to perform",
57
+ choices: ["scripting", "prompt", "schema"],
58
+ type: "string",
59
+ });
60
+ })
61
+ .strict()
62
+ .help()
63
+ .alias("help", "h")
64
+ .parseSync();
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import yargs from "yargs/yargs";
4
+ import { hideBin } from "yargs/helpers";
5
+ import { GraphAILogger } from "graphai";
6
+ import * as scriptingCmd from "./tool_commands/scripting/index.js";
7
+ import * as promptCmd from "./tool_commands/prompt/index.js";
8
+ import * as schemaCmd from "./tool_commands/schema/index.js";
9
+ const cli = yargs(hideBin(process.argv))
10
+ .scriptName("mulmo-tool")
11
+ .usage("$0 <command> [args]")
12
+ .option("v", {
13
+ alias: "verbose",
14
+ describe: "verbose log",
15
+ demandOption: true,
16
+ default: false,
17
+ type: "boolean",
18
+ })
19
+ .command(scriptingCmd)
20
+ .command(promptCmd)
21
+ .command(schemaCmd)
22
+ .demandCommand()
23
+ .strict()
24
+ .help()
25
+ .alias("help", "h");
26
+ const main = async () => {
27
+ await cli.parseAsync();
28
+ };
29
+ main().catch((error) => {
30
+ GraphAILogger.info("An unexpected error occurred:", error);
31
+ process.exit(1);
32
+ });
@@ -0,0 +1,4 @@
1
+ import { Argv } from "yargs";
2
+ export declare const builder: (yargs: Argv) => Argv<{
3
+ t: string | undefined;
4
+ }>;
@@ -0,0 +1,11 @@
1
+ import { getAvailableTemplates } from "@/src/utils/file.js";
2
+ const availableTemplateNames = getAvailableTemplates().map((template) => template.filename);
3
+ export const builder = (yargs) => {
4
+ return yargs.option("t", {
5
+ alias: "template",
6
+ describe: "Template name to use",
7
+ demandOption: false,
8
+ choices: availableTemplateNames,
9
+ type: "string",
10
+ });
11
+ };
@@ -0,0 +1,4 @@
1
+ import { ToolCliArgs } from "@/src/types/cli_types.js";
2
+ export declare const handler: (argv: ToolCliArgs<{
3
+ template?: string;
4
+ }>) => Promise<void>;
@@ -0,0 +1,14 @@
1
+ import { selectTemplate } from "@/src/utils/inquirer.js";
2
+ import { dumpPromptFromTemplate } from "../../../tools/dump_prompt.js";
3
+ import { setGraphAILogger } from "@/src/utils/cli.js";
4
+ export const handler = async (argv) => {
5
+ let { template } = argv;
6
+ const { v: verbose } = argv;
7
+ if (!template) {
8
+ template = await selectTemplate();
9
+ }
10
+ setGraphAILogger(verbose, {
11
+ template,
12
+ });
13
+ await dumpPromptFromTemplate({ templateName: template });
14
+ };
@@ -0,0 +1,4 @@
1
+ export declare const command = "prompt";
2
+ export declare const desc = "Dump prompt from template";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -0,0 +1,4 @@
1
+ export const command = "prompt";
2
+ export const desc = "Dump prompt from template";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -0,0 +1,2 @@
1
+ import { Argv } from "yargs";
2
+ export declare const builder: (yargs: Argv) => Argv<{}>;
@@ -0,0 +1,3 @@
1
+ export const builder = (yargs) => {
2
+ return yargs;
3
+ };
@@ -0,0 +1,2 @@
1
+ import { ToolCliArgs } from "@/src/types/cli_types.js";
2
+ export declare const handler: (argv: ToolCliArgs) => Promise<void>;
@@ -0,0 +1,12 @@
1
+ import { mulmoScriptSchema } from "@/src/types/schema.js";
2
+ import { zodToJsonSchema } from "zod-to-json-schema";
3
+ import { GraphAILogger } from "graphai";
4
+ import { setGraphAILogger } from "@/src/utils/cli.js";
5
+ export const handler = async (argv) => {
6
+ const { v: verbose } = argv;
7
+ setGraphAILogger(verbose);
8
+ const defaultSchema = zodToJsonSchema(mulmoScriptSchema, {
9
+ strictUnions: true,
10
+ });
11
+ GraphAILogger.info(JSON.stringify(defaultSchema, null, 2));
12
+ };
@@ -0,0 +1,4 @@
1
+ export declare const command = "schema";
2
+ export declare const desc = "Dump mulmocast schema";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -0,0 +1,4 @@
1
+ export const command = "schema";
2
+ export const desc = "Dump mulmocast schema";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -0,0 +1,20 @@
1
+ import { Argv } from "yargs";
2
+ export declare const builder: (yargs: Argv) => Argv<{
3
+ o: string | undefined;
4
+ } & {
5
+ b: string | undefined;
6
+ } & {
7
+ u: string[] | never[];
8
+ } & {
9
+ i: boolean | undefined;
10
+ } & {
11
+ t: string | undefined;
12
+ } & {
13
+ c: string | undefined;
14
+ } & {
15
+ s: string;
16
+ } & {
17
+ llm_agent: string | undefined;
18
+ } & {
19
+ llm_model: string | undefined;
20
+ }>;
@@ -0,0 +1,63 @@
1
+ import { llmAgents } from "../../../utils/utils.js";
2
+ import { getAvailableTemplates } from "../../../utils/file.js";
3
+ const availableTemplateNames = getAvailableTemplates().map((template) => template.filename);
4
+ export const builder = (yargs) => {
5
+ return yargs
6
+ .option("o", {
7
+ alias: "outdir",
8
+ description: "output dir",
9
+ demandOption: false,
10
+ type: "string",
11
+ })
12
+ .option("b", {
13
+ alias: "basedir",
14
+ description: "base dir",
15
+ demandOption: false,
16
+ type: "string",
17
+ })
18
+ .option("u", {
19
+ alias: "url",
20
+ description: "URLs to reference (required when not in interactive mode)",
21
+ demandOption: false,
22
+ default: [],
23
+ type: "array",
24
+ string: true,
25
+ })
26
+ .option("i", {
27
+ alias: "interactive",
28
+ description: "Generate script in interactive mode with user prompts",
29
+ demandOption: false,
30
+ type: "boolean",
31
+ })
32
+ .option("t", {
33
+ alias: "template",
34
+ description: "Template name to use",
35
+ demandOption: false,
36
+ choices: availableTemplateNames,
37
+ type: "string",
38
+ })
39
+ .option("c", {
40
+ alias: "cache",
41
+ description: "cache dir",
42
+ demandOption: false,
43
+ type: "string",
44
+ })
45
+ .option("s", {
46
+ alias: "script",
47
+ description: "script filename",
48
+ demandOption: false,
49
+ default: "script",
50
+ type: "string",
51
+ })
52
+ .option("llm_agent", {
53
+ description: "llm agent",
54
+ demandOption: false,
55
+ choices: llmAgents,
56
+ type: "string",
57
+ })
58
+ .option("llm_model", {
59
+ description: "llm model",
60
+ demandOption: false,
61
+ type: "string",
62
+ });
63
+ };
@@ -0,0 +1,12 @@
1
+ import { ToolCliArgs } from "@/src/types/cli_types.js";
2
+ export declare const handler: (argv: ToolCliArgs<{
3
+ o?: string;
4
+ b?: string;
5
+ u?: string[];
6
+ i?: boolean;
7
+ t?: string;
8
+ c?: string;
9
+ s?: string;
10
+ llm_agent?: string;
11
+ llm_model?: string;
12
+ }>) => Promise<void>;
@@ -0,0 +1,36 @@
1
+ import { getBaseDirPath, getFullPath } from "@/src/utils/file.js";
2
+ import { outDirName, cacheDirName } from "../../../utils/const.js";
3
+ import { getUrlsIfNeeded, selectTemplate } from "@/src/utils/inquirer.js";
4
+ import { createMulmoScriptFromUrl } from "../../../tools/create_mulmo_script_from_url.js";
5
+ import { createMulmoScriptInteractively } from "../../../tools/create_mulmo_script_interactively.js";
6
+ import { setGraphAILogger } from "@/src/utils/cli.js";
7
+ export const handler = async (argv) => {
8
+ const { o: outdir, b: basedir, v: verbose, i: interactive, c: cache, s: filename, llm_agent, llm_model } = argv;
9
+ let { t: template } = argv;
10
+ const urls = argv.u || [];
11
+ const baseDirPath = getBaseDirPath(basedir);
12
+ const outDirPath = getFullPath(baseDirPath, outdir ?? outDirName);
13
+ const cacheDirPath = getFullPath(outDirPath, cache ?? cacheDirName);
14
+ if (!template) {
15
+ template = await selectTemplate();
16
+ }
17
+ setGraphAILogger(verbose, {
18
+ baseDirPath,
19
+ outDirPath,
20
+ cacheDirPath,
21
+ template,
22
+ urls,
23
+ interactive,
24
+ filename,
25
+ llm_agent,
26
+ llm_model,
27
+ });
28
+ const context = { outDirPath, templateName: template, urls, filename: filename, cacheDirPath, llm_model, llm_agent };
29
+ if (interactive) {
30
+ await createMulmoScriptInteractively(context);
31
+ }
32
+ else {
33
+ context.urls = await getUrlsIfNeeded(urls);
34
+ await createMulmoScriptFromUrl(context);
35
+ }
36
+ };
@@ -0,0 +1,4 @@
1
+ export declare const command = "scripting";
2
+ export declare const desc = "Generate mulmocast script";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -0,0 +1,4 @@
1
+ export const command = "scripting";
2
+ export const desc = "Generate mulmocast script";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
package/lib/index.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./actions/index.js";
2
+ export * from "./cli/helpers.js";
3
+ export * from "./utils/file.js";
4
+ export * from "./utils/ffmpeg_utils.js";
5
+ export * from "./utils/provider2agent.js";
6
+ export * from "./methods/index.js";
7
+ export * from "./agents/index.js";
8
+ export * from "./types/index.js";
package/lib/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./actions/index.js";
2
+ export * from "./cli/helpers.js";
3
+ export * from "./utils/file.js";
4
+ export * from "./utils/ffmpeg_utils.js";
5
+ export * from "./utils/provider2agent.js";
6
+ export * from "./methods/index.js";
7
+ export * from "./agents/index.js";
8
+ export * from "./types/index.js";
@@ -8,6 +8,9 @@ export declare const MulmoBeatMethods: {
8
8
  isAnimationObject: (animation: unknown) => animation is AnimationConfig;
9
9
  isAnimatedHtmlTailwind: (beat: MulmoBeat) => boolean;
10
10
  isMovieMode: (animation: unknown) => boolean;
11
+ isVoiceOver: (beat?: MulmoBeat) => boolean;
12
+ getPrevRenderedBeatIndex(beats: MulmoBeat[], index: number): number;
13
+ getNextRenderedBeatIndex(beats: MulmoBeat[], index: number): number;
11
14
  getHtmlPrompt(beat: MulmoBeat): string | undefined;
12
15
  getImageReferenceForImageGenerator(beat: MulmoBeat, imageRefs: Record<string, string>): string[];
13
16
  };
@@ -1,3 +1,4 @@
1
+ import { ImageMediaType } from "../types/index.js";
1
2
  /** Type guard: checks if animation value is an object config like { fps: 30 } */
2
3
  const isAnimationObject = (animation) => {
3
4
  return typeof animation === "object" && animation !== null && !Array.isArray(animation);
@@ -17,11 +18,27 @@ const isAnimatedHtmlTailwind = (beat) => {
17
18
  const animation = beat.image.animation;
18
19
  return isAnimationEnabled(animation);
19
20
  };
21
+ // voice_over beats share the preceding beat's shot, and contribute no video segment of their own.
22
+ const isVoiceOver = (beat) => beat?.image?.type === ImageMediaType.VoiceOver;
20
23
  export const MulmoBeatMethods = {
21
24
  isAnimationEnabled,
22
25
  isAnimationObject,
23
26
  isAnimatedHtmlTailwind,
24
27
  isMovieMode,
28
+ isVoiceOver,
29
+ // Index of the closest preceding beat which is actually rendered as a video segment (-1 if none).
30
+ getPrevRenderedBeatIndex(beats, index) {
31
+ const offset = beats
32
+ .slice(0, Math.max(index, 0))
33
+ .reverse()
34
+ .findIndex((beat) => !isVoiceOver(beat));
35
+ return offset < 0 ? -1 : index - 1 - offset;
36
+ },
37
+ // Index of the closest following beat which is actually rendered as a video segment (-1 if none).
38
+ getNextRenderedBeatIndex(beats, index) {
39
+ const offset = beats.slice(index + 1).findIndex((beat) => !isVoiceOver(beat));
40
+ return offset < 0 ? -1 : index + 1 + offset;
41
+ },
25
42
  getHtmlPrompt(beat) {
26
43
  if (beat?.htmlPrompt?.data) {
27
44
  return beat.htmlPrompt.prompt + "\n\n[data]\n" + JSON.stringify(beat.htmlPrompt.data, null, 2);
@@ -17,6 +17,9 @@ export declare const MulmoBeatMethods: {
17
17
  };
18
18
  isAnimatedHtmlTailwind: (beat: MulmoBeat) => boolean;
19
19
  isMovieMode: (animation: unknown) => boolean;
20
+ isVoiceOver: (beat?: MulmoBeat) => boolean;
21
+ getPrevRenderedBeatIndex(beats: MulmoBeat[], index: number): number;
22
+ getNextRenderedBeatIndex(beats: MulmoBeat[], index: number): number;
20
23
  getHtmlPrompt(beat: MulmoBeat): string | undefined;
21
24
  getImageReferenceForImageGenerator(beat: MulmoBeat, imageRefs: Record<string, string>): string[];
22
25
  };
@@ -4,7 +4,7 @@
4
4
  * Works in both Node.js and modern browsers.
5
5
  */
6
6
  import type { SlideTheme } from "@mulmocast/deck";
7
- import { MulmoCanvasDimension, MulmoBeat, Text2SpeechProvider, Text2ImageAgentInfo, Text2HtmlAgentInfo, BeatMediaType, MulmoPresentationStyle, SpeakerData, Text2ImageProvider, MulmoStudioContext, MulmoTransition } from "../types/index.js";
7
+ import { MulmoCanvasDimension, MulmoBeat, Text2SpeechProvider, Text2ImageAgentInfo, Text2HtmlAgentInfo, BeatMediaType, MulmoPresentationStyle, SpeakerData, Text2ImageProvider, MulmoStudioContext, MulmoTransition, MulmoFillOption } from "../types/index.js";
8
8
  export declare const MulmoPresentationStyleMethods: {
9
9
  getCanvasSize(presentationStyle: MulmoPresentationStyle): MulmoCanvasDimension;
10
10
  getAllSpeechProviders(presentationStyle: MulmoPresentationStyle): Set<Text2SpeechProvider>;
@@ -30,6 +30,10 @@ export declare const MulmoPresentationStyleMethods: {
30
30
  getSpeakerData(presentationStyle: MulmoPresentationStyle, beat: MulmoBeat, lang: string | undefined): SpeakerData;
31
31
  getSpeaker(context: MulmoStudioContext, beat: MulmoBeat, targetLang: string | undefined): SpeakerData;
32
32
  getMovieTransition(context: MulmoStudioContext, beat: MulmoBeat): MulmoTransition | null;
33
+ getNeedFirstFrame(context: MulmoStudioContext): boolean[];
34
+ getNeedLastFrame(context: MulmoStudioContext): boolean[];
35
+ getFillOption(context: MulmoStudioContext, beat: MulmoBeat): MulmoFillOption;
36
+ getMovieVolume(context: MulmoStudioContext, beat: MulmoBeat): number;
33
37
  getText2ImageProvider(provider: Text2ImageProvider | undefined): Text2ImageProvider;
34
38
  getImageAgentInfo(presentationStyle: MulmoPresentationStyle, beat?: MulmoBeat): Text2ImageAgentInfo;
35
39
  getMovieAgentInfo(presentationStyle: MulmoPresentationStyle, beat?: MulmoBeat): {
@@ -6,7 +6,8 @@
6
6
  import { isNull } from "graphai";
7
7
  import { userAssert } from "../utils/utils.js";
8
8
  import { slideThemes } from "../data/slideThemes.js";
9
- import { text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoCanvasDimensionSchema, mulmoTransitionSchema, } from "../types/schema.js";
9
+ import { text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoCanvasDimensionSchema, mulmoTransitionSchema, mulmoFillOptionSchema, } from "../types/schema.js";
10
+ import { MulmoBeatMethods } from "./mulmo_beat.js";
10
11
  import { provider2ImageAgent, provider2MovieAgent, provider2LLMAgent, provider2TTSAgent, provider2SoundEffectAgent, provider2LipSyncAgent, defaultProviders, getModelAudio, AUDIO_MODE_ALWAYS, } from "../types/provider2agent.js";
11
12
  const defaultTextSlideStyles = [
12
13
  '*,*::before,*::after{box-sizing:border-box}body,h1,h2,h3,h4,p,figure,blockquote,dl,dd{margin:0}ul[role="list"],ol[role="list"]{list-style:none}html:focus-within{scroll-behavior:smooth}body{min-height:100vh;text-rendering:optimizeSpeed;line-height:1.5}a:not([class]){text-decoration-skip-ink:auto}img,picture{max-width:100%;display:block}input,button,textarea,select{font:inherit}@media(prefers-reduced-motion:reduce){html:focus-within{scroll-behavior:auto}*,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important;scroll-behavior:auto !important}}',
@@ -22,6 +23,13 @@ const defaultTextSlideStyles = [
22
23
  "td, th { padding: 8px }",
23
24
  "tr:nth-child(even) { background-color: #eee }",
24
25
  ];
26
+ const DEFAULT_DUCKING_RATIO = 0.3;
27
+ // Both frame requirements answer the same question for each *rendered* beat (voice_over beats have
28
+ // no video segment, so they never supply a frame): does a transition need a still from this beat?
29
+ const mapRenderedBeats = (context, needsFrame) => {
30
+ const beats = context.studio.script.beats;
31
+ return beats.map((beat, index) => (MulmoBeatMethods.isVoiceOver(beat) ? false : needsFrame(beats, index)));
32
+ };
25
33
  export const MulmoPresentationStyleMethods = {
26
34
  getCanvasSize(presentationStyle) {
27
35
  return mulmoCanvasDimensionSchema.parse(presentationStyle.canvasSize);
@@ -95,6 +103,40 @@ export const MulmoPresentationStyleMethods = {
95
103
  return null;
96
104
  return mulmoTransitionSchema.parse(transitionData);
97
105
  },
106
+ getNeedFirstFrame(context) {
107
+ // This beat slides/wipes in over the previous shot, so it supplies its own first frame.
108
+ return mapRenderedBeats(context, (beats, index) => {
109
+ if (MulmoBeatMethods.getPrevRenderedBeatIndex(beats, index) < 0)
110
+ return false; // No previous shot: the transition is skipped
111
+ const transition = MulmoPresentationStyleMethods.getMovieTransition(context, beats[index]);
112
+ return (transition?.type.startsWith("slidein_") || transition?.type.startsWith("wipe")) ?? false;
113
+ });
114
+ },
115
+ getNeedLastFrame(context) {
116
+ // Any transition consumes the last frame of the previous rendered beat, skipping voice_over beats.
117
+ return mapRenderedBeats(context, (beats, index) => {
118
+ const nextIndex = MulmoBeatMethods.getNextRenderedBeatIndex(beats, index);
119
+ if (nextIndex < 0)
120
+ return false; // Last rendered beat doesn't need _last
121
+ return MulmoPresentationStyleMethods.getMovieTransition(context, beats[nextIndex]) !== null;
122
+ });
123
+ },
124
+ getFillOption(context, beat) {
125
+ const globalFillOption = context.presentationStyle.movieParams?.fillOption;
126
+ const beatFillOption = beat.movieParams?.fillOption;
127
+ const defaultFillOption = mulmoFillOptionSchema.parse({}); // let the schema infer the default value
128
+ return { ...defaultFillOption, ...globalFillOption, ...beatFillOption };
129
+ },
130
+ getMovieVolume(context, beat) {
131
+ const baseMovieVolume = beat.audioParams?.movieVolume ?? context.presentationStyle.audioParams.movieVolume ?? 1.0;
132
+ const ducking = context.presentationStyle.audioParams.ducking;
133
+ const hasSpeech = !!beat.text && !context.presentationStyle.audioParams.suppressSpeech;
134
+ if (ducking && hasSpeech) {
135
+ const ratio = ducking.ratio ?? DEFAULT_DUCKING_RATIO;
136
+ return baseMovieVolume * ratio;
137
+ }
138
+ return baseMovieVolume;
139
+ },
98
140
  /* NOTE: This method is not used.
99
141
  getTTSModel(context: MulmoStudioContext, beat: MulmoBeat): string | undefined {
100
142
  const speaker = MulmoPresentationStyleMethods.getSpeaker(context, beat);
@@ -0,0 +1,4 @@
1
+ import { MulmoScript, MulmoScriptTemplate } from "../types/index.js";
2
+ export declare const MulmoScriptTemplateMethods: {
3
+ getSystemPrompt(template: MulmoScriptTemplate, script?: MulmoScript): string;
4
+ };
@@ -0,0 +1,6 @@
1
+ import { getMulmoScriptTemplateSystemPrompt } from "../utils/prompt.js";
2
+ export const MulmoScriptTemplateMethods = {
3
+ getSystemPrompt(template, script) {
4
+ return getMulmoScriptTemplateSystemPrompt(template, script);
5
+ },
6
+ };
@@ -0,0 +1,8 @@
1
+ import { MulmoStudio } from "../types/index.js";
2
+ type SessionType = "audio" | "image" | "video" | "multiLingual" | "caption" | "pdf";
3
+ type BeatSessionType = "audio" | "image" | "multiLingual" | "caption" | "movie";
4
+ export declare const MulmoStudioMethods: {
5
+ setSessionState(studio: MulmoStudio, sessionType: SessionType, value: boolean): void;
6
+ setBeatSessionState(studio: MulmoStudio, sessionType: BeatSessionType, index: number, value: boolean): void;
7
+ };
8
+ export {};
@@ -0,0 +1,25 @@
1
+ import { GraphAILogger } from "graphai";
2
+ const notifyStateChange = (studio, sessionType) => {
3
+ const prefix = studio.state.inSession[sessionType] ? "<" : " >";
4
+ GraphAILogger.info(`${prefix} ${sessionType}`);
5
+ };
6
+ const notifyBeatStateChange = (studio, sessionType, index) => {
7
+ const prefix = studio.state.inBeatSession[sessionType][index] ? "{" : " }";
8
+ GraphAILogger.info(`${prefix} ${sessionType} ${index}`);
9
+ };
10
+ export const MulmoStudioMethods = {
11
+ setSessionState(studio, sessionType, value) {
12
+ studio.state.inSession[sessionType] = value;
13
+ notifyStateChange(studio, sessionType);
14
+ },
15
+ setBeatSessionState(studio, sessionType, index, value) {
16
+ if (value) {
17
+ studio.state.inBeatSession[sessionType][index] = true;
18
+ }
19
+ else {
20
+ // NOTE: Setting to false causes the parse error in rebuildStudio in preprocess.ts
21
+ delete studio.state.inBeatSession[sessionType][index];
22
+ }
23
+ notifyBeatStateChange(studio, sessionType, index);
24
+ },
25
+ };
@@ -3,7 +3,7 @@
3
3
  * (No Node.js built-ins like fs, path, dotenv, etc.)
4
4
  * Works in both Node.js and modern browsers.
5
5
  */
6
- import { BeatSessionType, MulmoStudioContext, SessionProgressCallback, SessionType, MulmoBeat, SpeechOptions } from "../types/index.js";
6
+ import { BeatSessionType, MulmoStudioContext, SessionProgressCallback, SessionType, MulmoBeat, MulmoStudioBeat, SpeechOptions } from "../types/index.js";
7
7
  import { provider2TTSAgent } from "../types/provider2agent.js";
8
8
  export declare const addSessionProgressCallback: (cb: SessionProgressCallback) => void;
9
9
  export declare const removeSessionProgressCallback: (cb: SessionProgressCallback) => void;
@@ -18,6 +18,11 @@ export declare const MulmoStudioContextMethods: {
18
18
  setBeatSessionState(context: MulmoStudioContext, sessionType: BeatSessionType | undefined, index: number, id: string | undefined, value: boolean): void;
19
19
  needTranslate(context: MulmoStudioContext, includeCaption?: boolean): boolean | "" | undefined;
20
20
  getIntroPadding(context: MulmoStudioContext): number;
21
+ getExtraPadding(context: MulmoStudioContext, index: number): number;
22
+ getBeatDuration(context: MulmoStudioContext, index: number): number;
23
+ getVoiceOverGroupDuration(context: MulmoStudioContext, index: number): number;
24
+ isMovieBeat(context: MulmoStudioContext, studioBeat: MulmoStudioBeat, beat: MulmoBeat): boolean;
25
+ getSegmentDuration(context: MulmoStudioContext, index: number): number;
21
26
  getAudioParam(context: MulmoStudioContext, beat: MulmoBeat, lang?: string): {
22
27
  provider: keyof typeof provider2TTSAgent;
23
28
  voiceId: string;
@@ -3,10 +3,11 @@
3
3
  * (No Node.js built-ins like fs, path, dotenv, etc.)
4
4
  * Works in both Node.js and modern browsers.
5
5
  */
6
- import { text2SpeechProviderSchema, } from "../types/index.js";
6
+ import { ImageMediaType, text2SpeechProviderSchema, } from "../types/index.js";
7
7
  import { beatId } from "../utils/utils.js";
8
8
  import { GraphAILogger } from "graphai";
9
9
  import { MulmoPresentationStyleMethods } from "./mulmo_presentation_style.js";
10
+ import { MulmoBeatMethods } from "./mulmo_beat.js";
10
11
  const sessionProgressCallbacks = new Set();
11
12
  export const addSessionProgressCallback = (cb) => {
12
13
  sessionProgressCallbacks.add(cb);
@@ -92,6 +93,40 @@ export const MulmoStudioContextMethods = {
92
93
  }
93
94
  return context.presentationStyle.audioParams.introPadding;
94
95
  },
96
+ // Intro/outro padding is the only padding not already folded into studio.beats[].duration.
97
+ getExtraPadding(context, index) {
98
+ if (index === 0) {
99
+ return MulmoStudioContextMethods.getIntroPadding(context);
100
+ }
101
+ else if (index === context.studio.beats.length - 1) {
102
+ return context.presentationStyle.audioParams.outroPadding;
103
+ }
104
+ return 0;
105
+ },
106
+ // The duration this beat occupies on the audio timeline.
107
+ getBeatDuration(context, index) {
108
+ return (context.studio.beats[index]?.duration ?? 0) + MulmoStudioContextMethods.getExtraPadding(context, index);
109
+ },
110
+ // The total duration of the voice_over beats which follow (and share the shot of) this beat.
111
+ getVoiceOverGroupDuration(context, index) {
112
+ const trailingBeats = context.studio.script.beats.slice(index + 1);
113
+ const groupEnd = trailingBeats.findIndex((beat) => !MulmoBeatMethods.isVoiceOver(beat));
114
+ const groupSize = groupEnd < 0 ? trailingBeats.length : groupEnd;
115
+ return trailingBeats.slice(0, groupSize).reduce((total, _, offset) => total + MulmoStudioContextMethods.getBeatDuration(context, index + 1 + offset), 0);
116
+ },
117
+ // Whether this beat's source is a movie (as opposed to a still which has to be looped).
118
+ isMovieBeat(context, studioBeat, beat) {
119
+ return !!(studioBeat.lipSyncFile ||
120
+ studioBeat.movieFile ||
121
+ MulmoPresentationStyleMethods.getImageType(context.presentationStyle, beat) === ImageMediaType.Movie);
122
+ },
123
+ // How long this beat's video segment is: its own duration plus the trailing voice_over beats which
124
+ // share its shot (they have no video segment of their own), never shorter than its movie. This is
125
+ // the single rule for a segment's length -- createVideo builds from it and transitions clamp against it.
126
+ getSegmentDuration(context, index) {
127
+ const ownDuration = MulmoStudioContextMethods.getBeatDuration(context, index) + MulmoStudioContextMethods.getVoiceOverGroupDuration(context, index);
128
+ return Math.max(ownDuration, context.studio.beats[index]?.movieDuration ?? 0);
129
+ },
95
130
  getAudioParam(context, beat, lang) {
96
131
  const speaker = MulmoPresentationStyleMethods.getSpeaker(context, beat, lang);
97
132
  const speechOptions = { ...speaker.speechOptions, ...beat.speechOptions };
@@ -0,0 +1,13 @@
1
+ import type { ContentBlock, TableCellValue } from "./schema.js";
2
+ export declare const resolveCellColor: (cellObj: {
3
+ color?: string;
4
+ }, isRowHeader: boolean) => string;
5
+ export declare const renderBadge: (text: string, color: string) => string;
6
+ export declare const renderCellValue: (cell: TableCellValue, isRowHeader: boolean) => string;
7
+ export declare const renderTableCore: (headers: string[] | undefined, rows: TableCellValue[][], rowHeaders?: boolean, striped?: boolean) => string;
8
+ /** Render a single content block to HTML */
9
+ export declare const renderContentBlock: (block: ContentBlock) => string;
10
+ /** Render an array of content blocks to HTML */
11
+ export declare const renderContentBlocks: (blocks: ContentBlock[]) => string;
12
+ /** Render content blocks with fixed aspect-ratio container for image blocks (used in card layouts) */
13
+ export declare const renderCardContentBlocks: (blocks: ContentBlock[]) => string;