mulmocast 1.2.28 → 1.2.30

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 (42) hide show
  1. package/lib/actions/html.d.ts +3 -0
  2. package/lib/actions/html.js +62 -0
  3. package/lib/actions/image_agents.d.ts +4 -1
  4. package/lib/actions/image_agents.js +10 -1
  5. package/lib/actions/images.d.ts +6 -1
  6. package/lib/actions/images.js +2 -0
  7. package/lib/actions/index.d.ts +2 -0
  8. package/lib/actions/index.js +2 -0
  9. package/lib/actions/markdown.d.ts +3 -0
  10. package/lib/actions/markdown.js +61 -0
  11. package/lib/agents/image_replicate_agent.js +8 -33
  12. package/lib/agents/movie_replicate_agent.js +9 -7
  13. package/lib/cli/bin.js +4 -0
  14. package/lib/cli/commands/html/builder.d.ts +16 -0
  15. package/lib/cli/commands/html/builder.js +5 -0
  16. package/lib/cli/commands/html/handler.d.ts +4 -0
  17. package/lib/cli/commands/html/handler.js +11 -0
  18. package/lib/cli/commands/html/index.d.ts +4 -0
  19. package/lib/cli/commands/html/index.js +4 -0
  20. package/lib/cli/commands/markdown/builder.d.ts +16 -0
  21. package/lib/cli/commands/markdown/builder.js +5 -0
  22. package/lib/cli/commands/markdown/handler.d.ts +4 -0
  23. package/lib/cli/commands/markdown/handler.js +11 -0
  24. package/lib/cli/commands/markdown/index.d.ts +4 -0
  25. package/lib/cli/commands/markdown/index.js +4 -0
  26. package/lib/methods/mulmo_beat.d.ts +6 -1
  27. package/lib/types/schema.d.ts +26 -0
  28. package/lib/types/schema.js +4 -0
  29. package/lib/types/type.d.ts +1 -1
  30. package/lib/utils/context.d.ts +6 -0
  31. package/lib/utils/context.js +2 -0
  32. package/lib/utils/image_plugins/index.d.ts +7 -11
  33. package/lib/utils/image_plugins/index.js +1 -12
  34. package/lib/utils/image_plugins/markdown.d.ts +1 -0
  35. package/lib/utils/image_plugins/markdown.js +8 -1
  36. package/lib/utils/image_plugins/mermaid.d.ts +1 -0
  37. package/lib/utils/image_plugins/mermaid.js +9 -0
  38. package/lib/utils/image_plugins/text_slide.d.ts +1 -0
  39. package/lib/utils/image_plugins/text_slide.js +12 -1
  40. package/package.json +11 -9
  41. package/scripts/test/test_image_refs.json +10 -0
  42. package/scripts/test/test_markdown.json +60 -0
@@ -0,0 +1,3 @@
1
+ import { MulmoStudioContext } from "../types/index.js";
2
+ export declare const htmlFilePath: (context: MulmoStudioContext) => string;
3
+ export declare const html: (context: MulmoStudioContext, imageWidth?: string) => Promise<void>;
@@ -0,0 +1,62 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { isNull } from "graphai";
4
+ import { localizedText } from "../utils/utils.js";
5
+ import { writingMessage } from "../utils/file.js";
6
+ import { MulmoStudioContextMethods } from "../methods/mulmo_studio_context.js";
7
+ const generateHtmlContent = (context, imageWidth) => {
8
+ const { studio, multiLingual, lang = "en" } = context;
9
+ const title = studio.script.title || "MulmoCast Content";
10
+ const description = studio.script.description || "";
11
+ let html = `<h1>${title}</h1>\n\n`;
12
+ if (description) {
13
+ html += `${description}\n\n`;
14
+ }
15
+ studio.script.beats.forEach((beat, index) => {
16
+ const text = localizedText(beat, multiLingual?.[index], lang);
17
+ const studioBeat = studio.beats[index];
18
+ if (text.trim() || studioBeat?.html || studioBeat?.imageFile) {
19
+ if (studioBeat?.html) {
20
+ html += `${studioBeat.html}\n\n`;
21
+ }
22
+ else if (studioBeat?.imageFile && isNull(studioBeat.html)) {
23
+ const imagePath = path.relative(context.fileDirs.outDirPath, studioBeat.imageFile);
24
+ const altText = `Beat ${index + 1}`;
25
+ if (imageWidth) {
26
+ // Use HTML img tag for width control
27
+ html += `<img src="${imagePath}" alt="${altText}" width="${imageWidth}" />\n\n`;
28
+ }
29
+ else {
30
+ // Use standard html image syntax
31
+ html += `<img src="${imagePath}" alt="${altText}" />\n\n`;
32
+ }
33
+ }
34
+ if (text.trim()) {
35
+ html += `${text}\n\n`;
36
+ }
37
+ }
38
+ });
39
+ return html;
40
+ };
41
+ export const htmlFilePath = (context) => {
42
+ const { studio, fileDirs, lang = "en" } = context;
43
+ // Add language suffix only when target language is different from script's original language
44
+ const langSuffix = studio.script.lang !== lang ? `_${lang}` : "";
45
+ const filename = `${studio.filename}${langSuffix}.html`;
46
+ return path.join(fileDirs.outDirPath, filename);
47
+ };
48
+ const generateHtml = async (context, imageWidth) => {
49
+ const outputHtmlPath = htmlFilePath(context);
50
+ const htmlContent = generateHtmlContent(context, imageWidth);
51
+ fs.writeFileSync(outputHtmlPath, htmlContent, "utf8");
52
+ writingMessage(outputHtmlPath);
53
+ };
54
+ export const html = async (context, imageWidth) => {
55
+ try {
56
+ MulmoStudioContextMethods.setSessionState(context, "html", true);
57
+ await generateHtml(context, imageWidth);
58
+ }
59
+ finally {
60
+ MulmoStudioContextMethods.setSessionState(context, "html", false);
61
+ }
62
+ };
@@ -12,9 +12,9 @@ export declare const imagePreprocessAgent: (namedInputs: {
12
12
  htmlImageSystemPrompt: string;
13
13
  } | {
14
14
  imagePath: string | undefined;
15
+ movieFile: string | undefined;
15
16
  referenceImageForMovie: string | undefined;
16
17
  imageParams: MulmoImageParams;
17
- movieFile: string | undefined;
18
18
  soundEffectFile?: string;
19
19
  soundEffectPrompt?: string;
20
20
  soundEffectModel?: string;
@@ -35,6 +35,7 @@ export declare const imagePreprocessAgent: (namedInputs: {
35
35
  agent: string;
36
36
  movieParams: MulmoMovieParams;
37
37
  };
38
+ markdown?: string;
38
39
  htmlPrompt?: undefined;
39
40
  htmlImageFile?: undefined;
40
41
  htmlPath?: undefined;
@@ -64,6 +65,7 @@ export declare const imagePreprocessAgent: (namedInputs: {
64
65
  agent: string;
65
66
  movieParams: MulmoMovieParams;
66
67
  };
68
+ markdown?: string;
67
69
  htmlPrompt?: undefined;
68
70
  htmlImageFile?: undefined;
69
71
  htmlPath?: undefined;
@@ -96,6 +98,7 @@ export declare const imagePreprocessAgent: (namedInputs: {
96
98
  agent: string;
97
99
  movieParams: MulmoMovieParams;
98
100
  };
101
+ markdown?: string;
99
102
  htmlPrompt?: undefined;
100
103
  htmlImageFile?: undefined;
101
104
  htmlPath?: undefined;
@@ -60,8 +60,17 @@ export const imagePreprocessAgent = async (namedInputs) => {
60
60
  if (beat.image) {
61
61
  const plugin = MulmoBeatMethods.getPlugin(beat);
62
62
  const pluginPath = plugin.path({ beat, context, imagePath, ...htmlStyle(context, beat) });
63
+ if (plugin.markdown) {
64
+ returnValue.markdown = plugin.markdown({ beat, context, imagePath, ...htmlStyle(context, beat) });
65
+ }
66
+ const isTypeMovie = beat.image.type === "movie";
63
67
  // undefined prompt indicates that image generation is not needed
64
- return { ...returnValue, imagePath: pluginPath, referenceImageForMovie: pluginPath };
68
+ return {
69
+ ...returnValue,
70
+ imagePath: isTypeMovie ? undefined : pluginPath,
71
+ movieFile: isTypeMovie ? pluginPath : undefined,
72
+ referenceImageForMovie: pluginPath,
73
+ };
65
74
  }
66
75
  if (beat.moviePrompt && !beat.imagePrompt) {
67
76
  return { ...returnValue, imagePath, imageFromMovie: true }; // no image prompt, only movie prompt
@@ -35,9 +35,9 @@ export declare const beat_graph_data: {
35
35
  htmlImageSystemPrompt: string;
36
36
  } | {
37
37
  imagePath: string | undefined;
38
+ movieFile: string | undefined;
38
39
  referenceImageForMovie: string | undefined;
39
40
  imageParams: MulmoImageParams;
40
- movieFile: string | undefined;
41
41
  soundEffectFile?: string;
42
42
  soundEffectPrompt?: string;
43
43
  soundEffectModel?: string;
@@ -58,6 +58,7 @@ export declare const beat_graph_data: {
58
58
  agent: string;
59
59
  movieParams: import("../types/type.js").MulmoMovieParams;
60
60
  };
61
+ markdown?: string;
61
62
  htmlPrompt?: undefined;
62
63
  htmlImageFile?: undefined;
63
64
  htmlPath?: undefined;
@@ -87,6 +88,7 @@ export declare const beat_graph_data: {
87
88
  agent: string;
88
89
  movieParams: import("../types/type.js").MulmoMovieParams;
89
90
  };
91
+ markdown?: string;
90
92
  htmlPrompt?: undefined;
91
93
  htmlImageFile?: undefined;
92
94
  htmlPath?: undefined;
@@ -119,6 +121,7 @@ export declare const beat_graph_data: {
119
121
  agent: string;
120
122
  movieParams: import("../types/type.js").MulmoMovieParams;
121
123
  };
124
+ markdown?: string;
122
125
  htmlPrompt?: undefined;
123
126
  htmlImageFile?: undefined;
124
127
  htmlPath?: undefined;
@@ -359,6 +362,7 @@ export declare const beat_graph_data: {
359
362
  lipSyncFile: string;
360
363
  hasMovieAudio: string;
361
364
  htmlImageFile: string;
365
+ markdown: string;
362
366
  };
363
367
  output: {
364
368
  imageFile: string;
@@ -367,6 +371,7 @@ export declare const beat_graph_data: {
367
371
  lipSyncFile: string;
368
372
  hasMovieAudio: string;
369
373
  htmlImageFile: string;
374
+ markdown: string;
370
375
  };
371
376
  isResult: boolean;
372
377
  };
@@ -285,6 +285,7 @@ export const beat_graph_data = {
285
285
  lipSyncFile: ":preprocessor.lipSyncFile",
286
286
  hasMovieAudio: ":audioChecker.hasMovieAudio",
287
287
  htmlImageFile: ":preprocessor.htmlImageFile",
288
+ markdown: ":preprocessor.markdown",
288
289
  },
289
290
  output: {
290
291
  imageFile: ".imageFile",
@@ -293,6 +294,7 @@ export const beat_graph_data = {
293
294
  lipSyncFile: ".lipSyncFile",
294
295
  hasMovieAudio: ".hasMovieAudio",
295
296
  htmlImageFile: ".htmlImageFile",
297
+ markdown: ".markdown",
296
298
  },
297
299
  isResult: true,
298
300
  },
@@ -6,3 +6,5 @@ export * from "./image_agents.js";
6
6
  export * from "./movie.js";
7
7
  export * from "./pdf.js";
8
8
  export * from "./translate.js";
9
+ export * from "./markdown.js";
10
+ export * from "./html.js";
@@ -6,3 +6,5 @@ export * from "./image_agents.js";
6
6
  export * from "./movie.js";
7
7
  export * from "./pdf.js";
8
8
  export * from "./translate.js";
9
+ export * from "./markdown.js";
10
+ export * from "./html.js";
@@ -0,0 +1,3 @@
1
+ import { MulmoStudioContext } from "../types/index.js";
2
+ export declare const markdownFilePath: (context: MulmoStudioContext) => string;
3
+ export declare const markdown: (context: MulmoStudioContext, imageWidth?: string) => Promise<void>;
@@ -0,0 +1,61 @@
1
+ import fs from "fs";
2
+ import { localizedText } from "../utils/utils.js";
3
+ import { writingMessage } from "../utils/file.js";
4
+ import { MulmoStudioContextMethods } from "../methods/mulmo_studio_context.js";
5
+ import path from "path";
6
+ const generateMarkdownContent = (context, imageWidth) => {
7
+ const { studio, multiLingual, lang = "en" } = context;
8
+ const title = studio.script.title || "MulmoCast Content";
9
+ const description = studio.script.description || "";
10
+ let markdown = `# ${title}\n\n`;
11
+ if (description) {
12
+ markdown += `${description}\n\n`;
13
+ }
14
+ studio.script.beats.forEach((beat, index) => {
15
+ const text = localizedText(beat, multiLingual?.[index], lang);
16
+ const studioBeat = studio.beats[index];
17
+ if (text.trim() || studioBeat?.markdown || studioBeat?.imageFile) {
18
+ if (studioBeat?.markdown) {
19
+ markdown += `${studioBeat.markdown}\n\n`;
20
+ }
21
+ else if (studioBeat?.imageFile) {
22
+ const imagePath = path.relative(context.fileDirs.outDirPath, studioBeat.imageFile);
23
+ if (imageWidth) {
24
+ // Use HTML img tag for width control
25
+ const altText = `Beat ${index + 1}`;
26
+ markdown += `<img src="${imagePath}" alt="${altText}" width="${imageWidth}" />\n\n`;
27
+ }
28
+ else {
29
+ // Use standard markdown image syntax
30
+ markdown += `![Beat ${index + 1}](${imagePath})\n\n`;
31
+ }
32
+ }
33
+ if (text.trim()) {
34
+ markdown += `${text}\n\n`;
35
+ }
36
+ }
37
+ });
38
+ return markdown;
39
+ };
40
+ export const markdownFilePath = (context) => {
41
+ const { studio, fileDirs, lang = "en" } = context;
42
+ // Add language suffix only when target language is different from script's original language
43
+ const langSuffix = studio.script.lang !== lang ? `_${lang}` : "";
44
+ const filename = `${studio.filename}${langSuffix}.md`;
45
+ return path.join(fileDirs.outDirPath, filename);
46
+ };
47
+ const generateMarkdown = async (context, imageWidth) => {
48
+ const outputMarkdownPath = markdownFilePath(context);
49
+ const markdownContent = generateMarkdownContent(context, imageWidth);
50
+ fs.writeFileSync(outputMarkdownPath, markdownContent, "utf8");
51
+ writingMessage(outputMarkdownPath);
52
+ };
53
+ export const markdown = async (context, imageWidth) => {
54
+ try {
55
+ MulmoStudioContextMethods.setSessionState(context, "markdown", true);
56
+ await generateMarkdown(context, imageWidth);
57
+ }
58
+ finally {
59
+ MulmoStudioContextMethods.setSessionState(context, "markdown", false);
60
+ }
61
+ };
@@ -1,9 +1,10 @@
1
+ import { readFileSync } from "fs";
1
2
  import { GraphAILogger } from "graphai";
2
3
  import Replicate from "replicate";
3
4
  import { getAspectRatio } from "./movie_replicate_agent.js";
4
5
  import { provider2ImageAgent } from "../utils/provider2agent.js";
5
6
  export const imageReplicateAgent = async ({ namedInputs, params, config, }) => {
6
- const { prompt } = namedInputs;
7
+ const { prompt, referenceImages } = namedInputs;
7
8
  const { canvasSize } = params;
8
9
  const model = params.model ?? provider2ImageAgent.replicate.defaultModel;
9
10
  const apiKey = config?.apiKey;
@@ -15,40 +16,14 @@ export const imageReplicateAgent = async ({ namedInputs, params, config, }) => {
15
16
  });
16
17
  const input = {
17
18
  prompt,
18
- width: canvasSize.width,
19
- height: canvasSize.height,
19
+ aspect_ratio: getAspectRatio(canvasSize),
20
20
  };
21
- if (model === "bytedance/seedream-4") {
22
- input.size = "custom";
23
- if (input.width < 1024) {
24
- const ratio = 1024 / input.width;
25
- input.width = 1024;
26
- input.height = Math.round(input.height * ratio);
27
- }
28
- if (input.height < 1024) {
29
- const ratio = 1024 / input.height;
30
- input.width = Math.round(input.width * ratio);
31
- input.height = 1024;
32
- }
33
- }
34
- else if (model === "qwen/qwen-image") {
35
- input.aspect_ratio = getAspectRatio(canvasSize);
36
- }
37
- // Add image if provided (for image-to-image generation)
38
- /*
39
- if (imagePath) {
40
- const buffer = readFileSync(imagePath);
41
- const base64Image = `data:image/png;base64,${buffer.toString("base64")}`;
42
- const start_image = provider2MovieAgent.replicate.modelParams[model]?.start_image;
43
- if (start_image === "first_frame_image" || start_image === "image" || start_image === "start_image") {
44
- input[start_image] = base64Image;
45
- } else if (start_image === undefined) {
46
- throw new Error(`Model ${model} does not support image-to-video generation`);
47
- } else {
48
- input.image = base64Image;
49
- }
21
+ if (referenceImages && referenceImages.length > 0) {
22
+ input.image_input = referenceImages.map((image) => {
23
+ const buffer = readFileSync(image);
24
+ return `data:image/png;base64,${buffer.toString("base64")}`;
25
+ });
50
26
  }
51
- */
52
27
  try {
53
28
  const output = await replicate.run(model, { input });
54
29
  // Download the generated video
@@ -54,15 +54,17 @@ async function generateMovie(model, apiKey, prompt, imagePath, aspectRatio, dura
54
54
  }
55
55
  }
56
56
  export const getAspectRatio = (canvasSize) => {
57
- if (canvasSize.width > canvasSize.height) {
57
+ const ratio = canvasSize.width / canvasSize.height;
58
+ const tolerance = 0.1;
59
+ if (ratio > 4 / 3 + tolerance)
58
60
  return "16:9";
59
- }
60
- else if (canvasSize.width < canvasSize.height) {
61
- return "9:16";
62
- }
63
- else {
61
+ if (ratio > 4 / 3 - tolerance)
62
+ return "4:3";
63
+ if (ratio > 3 / 4 + tolerance)
64
64
  return "1:1";
65
- }
65
+ if (ratio > 3 / 4 - tolerance)
66
+ return "3:4";
67
+ return "9:16";
66
68
  };
67
69
  export const movieReplicateAgent = async ({ namedInputs, params, config, }) => {
68
70
  const { prompt, imagePath } = namedInputs;
package/lib/cli/bin.js CHANGED
@@ -10,6 +10,8 @@ import * as audioCmd from "./commands/audio/index.js";
10
10
  import * as imagesCmd from "./commands/image/index.js";
11
11
  import * as movieCmd from "./commands/movie/index.js";
12
12
  import * as pdfCmd from "./commands/pdf/index.js";
13
+ import * as markdownCmd from "./commands/markdown/index.js";
14
+ import * as htmlCmd from "./commands/html/index.js";
13
15
  import * as toolCmd from "./commands/tool/index.js";
14
16
  import { GraphAILogger } from "graphai";
15
17
  const __filename = fileURLToPath(import.meta.url);
@@ -32,6 +34,8 @@ export const main = async () => {
32
34
  .command(imagesCmd)
33
35
  .command(movieCmd)
34
36
  .command(pdfCmd)
37
+ .command(markdownCmd)
38
+ .command(htmlCmd)
35
39
  .command(toolCmd)
36
40
  .demandCommand()
37
41
  .strict()
@@ -0,0 +1,16 @@
1
+ import type { Argv } from "yargs";
2
+ export declare const builder: (yargs: Argv) => Argv<{
3
+ o: string | undefined;
4
+ } & {
5
+ b: string | undefined;
6
+ } & {
7
+ l: string | undefined;
8
+ } & {
9
+ f: boolean;
10
+ } & {
11
+ p: string | undefined;
12
+ } & {
13
+ file: string | undefined;
14
+ } & {
15
+ image_width: string | undefined;
16
+ }>;
@@ -0,0 +1,5 @@
1
+ import { commonOptions } from "../../common.js";
2
+ export const builder = (yargs) => commonOptions(yargs).option("image_width", {
3
+ describe: "Image width (e.g., 400px, 50%, auto)",
4
+ type: "string",
5
+ });
@@ -0,0 +1,4 @@
1
+ import { CliArgs } from "../../../types/cli_types.js";
2
+ export declare const handler: (argv: CliArgs<{
3
+ image_width?: string;
4
+ }>) => Promise<void>;
@@ -0,0 +1,11 @@
1
+ import { images, html } from "../../../actions/index.js";
2
+ import { initializeContext, runTranslateIfNeeded } from "../../helpers.js";
3
+ export const handler = async (argv) => {
4
+ const context = await initializeContext(argv);
5
+ if (!context) {
6
+ process.exit(1);
7
+ }
8
+ await runTranslateIfNeeded(context);
9
+ await images(context);
10
+ await html(context, argv.image_width);
11
+ };
@@ -0,0 +1,4 @@
1
+ export declare const command = "html <file>";
2
+ export declare const desc = "Generate html files";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -0,0 +1,4 @@
1
+ export const command = "html <file>";
2
+ export const desc = "Generate html files";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -0,0 +1,16 @@
1
+ import type { Argv } from "yargs";
2
+ export declare const builder: (yargs: Argv) => Argv<{
3
+ o: string | undefined;
4
+ } & {
5
+ b: string | undefined;
6
+ } & {
7
+ l: string | undefined;
8
+ } & {
9
+ f: boolean;
10
+ } & {
11
+ p: string | undefined;
12
+ } & {
13
+ file: string | undefined;
14
+ } & {
15
+ image_width: string | undefined;
16
+ }>;
@@ -0,0 +1,5 @@
1
+ import { commonOptions } from "../../common.js";
2
+ export const builder = (yargs) => commonOptions(yargs).option("image_width", {
3
+ describe: "Image width (e.g., 400px, 50%, auto)",
4
+ type: "string",
5
+ });
@@ -0,0 +1,4 @@
1
+ import { CliArgs } from "../../../types/cli_types.js";
2
+ export declare const handler: (argv: CliArgs<{
3
+ image_width?: string;
4
+ }>) => Promise<void>;
@@ -0,0 +1,11 @@
1
+ import { images, markdown } from "../../../actions/index.js";
2
+ import { initializeContext, runTranslateIfNeeded } from "../../helpers.js";
3
+ export const handler = async (argv) => {
4
+ const context = await initializeContext(argv);
5
+ if (!context) {
6
+ process.exit(1);
7
+ }
8
+ await runTranslateIfNeeded(context);
9
+ await images(context);
10
+ await markdown(context, argv.image_width);
11
+ };
@@ -0,0 +1,4 @@
1
+ export declare const command = "markdown <file>";
2
+ export declare const desc = "Generate markdown files";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -0,0 +1,4 @@
1
+ export const command = "markdown <file>";
2
+ export const desc = "Generate markdown files";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -1,6 +1,11 @@
1
1
  import { MulmoBeat } from "../types/index.js";
2
2
  export declare const MulmoBeatMethods: {
3
3
  getHtmlPrompt(beat: MulmoBeat): string | undefined;
4
- getPlugin(beat: MulmoBeat): typeof import("../utils/image_plugins/text_slide.js") | typeof import("../utils/image_plugins/markdown.js") | typeof import("../utils/image_plugins/chart.js") | typeof import("../utils/image_plugins/mermaid.js") | typeof import("../utils/image_plugins/html_tailwind.js") | typeof import("../utils/image_plugins/image.js") | typeof import("../utils/image_plugins/movie.js") | typeof import("../utils/image_plugins/beat.js") | typeof import("../utils/image_plugins/voice_over.js") | typeof import("../utils/image_plugins/vision.js");
4
+ getPlugin(beat: MulmoBeat): {
5
+ imageType: string;
6
+ process: (params: import("../types/type.js").ImageProcessorParams) => Promise<string | undefined> | void;
7
+ path: (params: import("../types/type.js").ImageProcessorParams) => string | undefined;
8
+ markdown?: (params: import("../types/type.js").ImageProcessorParams) => string | undefined;
9
+ };
5
10
  getImageReferenceForImageGenerator(beat: MulmoBeat, imageRefs: Record<string, string>): string[];
6
11
  };
@@ -5777,8 +5777,12 @@ export declare const mulmoStudioBeatSchema: z.ZodObject<{
5777
5777
  lipSyncFile: z.ZodOptional<z.ZodString>;
5778
5778
  captionFile: z.ZodOptional<z.ZodString>;
5779
5779
  htmlImageFile: z.ZodOptional<z.ZodString>;
5780
+ markdown: z.ZodOptional<z.ZodString>;
5781
+ html: z.ZodOptional<z.ZodString>;
5780
5782
  }, "strict", z.ZodTypeAny, {
5781
5783
  duration?: number | undefined;
5784
+ markdown?: string | undefined;
5785
+ html?: string | undefined;
5782
5786
  id?: string | undefined;
5783
5787
  startAt?: number | undefined;
5784
5788
  hash?: string | undefined;
@@ -5795,6 +5799,8 @@ export declare const mulmoStudioBeatSchema: z.ZodObject<{
5795
5799
  htmlImageFile?: string | undefined;
5796
5800
  }, {
5797
5801
  duration?: number | undefined;
5802
+ markdown?: string | undefined;
5803
+ html?: string | undefined;
5798
5804
  id?: string | undefined;
5799
5805
  startAt?: number | undefined;
5800
5806
  hash?: string | undefined;
@@ -6027,18 +6033,24 @@ export declare const mulmoSessionStateSchema: z.ZodObject<{
6027
6033
  multiLingual: z.ZodBoolean;
6028
6034
  caption: z.ZodBoolean;
6029
6035
  pdf: z.ZodBoolean;
6036
+ markdown: z.ZodBoolean;
6037
+ html: z.ZodBoolean;
6030
6038
  }, "strip", z.ZodTypeAny, {
6031
6039
  image: boolean;
6032
6040
  video: boolean;
6033
6041
  audio: boolean;
6042
+ markdown: boolean;
6034
6043
  pdf: boolean;
6044
+ html: boolean;
6035
6045
  multiLingual: boolean;
6036
6046
  caption: boolean;
6037
6047
  }, {
6038
6048
  image: boolean;
6039
6049
  video: boolean;
6040
6050
  audio: boolean;
6051
+ markdown: boolean;
6041
6052
  pdf: boolean;
6053
+ html: boolean;
6042
6054
  multiLingual: boolean;
6043
6055
  caption: boolean;
6044
6056
  }>;
@@ -6078,7 +6090,9 @@ export declare const mulmoSessionStateSchema: z.ZodObject<{
6078
6090
  image: boolean;
6079
6091
  video: boolean;
6080
6092
  audio: boolean;
6093
+ markdown: boolean;
6081
6094
  pdf: boolean;
6095
+ html: boolean;
6082
6096
  multiLingual: boolean;
6083
6097
  caption: boolean;
6084
6098
  };
@@ -6098,7 +6112,9 @@ export declare const mulmoSessionStateSchema: z.ZodObject<{
6098
6112
  image: boolean;
6099
6113
  video: boolean;
6100
6114
  audio: boolean;
6115
+ markdown: boolean;
6101
6116
  pdf: boolean;
6117
+ html: boolean;
6102
6118
  multiLingual: boolean;
6103
6119
  caption: boolean;
6104
6120
  };
@@ -8479,8 +8495,12 @@ export declare const mulmoStudioSchema: z.ZodObject<{
8479
8495
  lipSyncFile: z.ZodOptional<z.ZodString>;
8480
8496
  captionFile: z.ZodOptional<z.ZodString>;
8481
8497
  htmlImageFile: z.ZodOptional<z.ZodString>;
8498
+ markdown: z.ZodOptional<z.ZodString>;
8499
+ html: z.ZodOptional<z.ZodString>;
8482
8500
  }, "strict", z.ZodTypeAny, {
8483
8501
  duration?: number | undefined;
8502
+ markdown?: string | undefined;
8503
+ html?: string | undefined;
8484
8504
  id?: string | undefined;
8485
8505
  startAt?: number | undefined;
8486
8506
  hash?: string | undefined;
@@ -8497,6 +8517,8 @@ export declare const mulmoStudioSchema: z.ZodObject<{
8497
8517
  htmlImageFile?: string | undefined;
8498
8518
  }, {
8499
8519
  duration?: number | undefined;
8520
+ markdown?: string | undefined;
8521
+ html?: string | undefined;
8500
8522
  id?: string | undefined;
8501
8523
  startAt?: number | undefined;
8502
8524
  hash?: string | undefined;
@@ -8515,6 +8537,8 @@ export declare const mulmoStudioSchema: z.ZodObject<{
8515
8537
  }, "strict", z.ZodTypeAny, {
8516
8538
  beats: {
8517
8539
  duration?: number | undefined;
8540
+ markdown?: string | undefined;
8541
+ html?: string | undefined;
8518
8542
  id?: string | undefined;
8519
8543
  startAt?: number | undefined;
8520
8544
  hash?: string | undefined;
@@ -8862,6 +8886,8 @@ export declare const mulmoStudioSchema: z.ZodObject<{
8862
8886
  }, {
8863
8887
  beats: {
8864
8888
  duration?: number | undefined;
8889
+ markdown?: string | undefined;
8890
+ html?: string | undefined;
8865
8891
  id?: string | undefined;
8866
8892
  startAt?: number | undefined;
8867
8893
  hash?: string | undefined;
@@ -404,6 +404,8 @@ export const mulmoStudioBeatSchema = z
404
404
  lipSyncFile: z.string().optional(), // path to the lip sync file
405
405
  captionFile: z.string().optional(), // path to the caption image
406
406
  htmlImageFile: z.string().optional(), // path to the html image
407
+ markdown: z.string().optional(), // markdown string (alternative to image)
408
+ html: z.string().optional(), // html string (alternative to image)
407
409
  })
408
410
  .strict();
409
411
  export const mulmoStudioMultiLingualDataSchema = z.object({
@@ -424,6 +426,8 @@ export const mulmoSessionStateSchema = z.object({
424
426
  multiLingual: z.boolean(),
425
427
  caption: z.boolean(),
426
428
  pdf: z.boolean(),
429
+ markdown: z.boolean(),
430
+ html: z.boolean(),
427
431
  }),
428
432
  inBeatSession: z.object({
429
433
  audio: z.record(z.string(), z.boolean()),
@@ -85,7 +85,7 @@ export type Text2HtmlAgentInfo = {
85
85
  };
86
86
  export type BeatMediaType = "movie" | "image";
87
87
  export type StoryToScriptGenerateMode = (typeof storyToScriptGenerateMode)[keyof typeof storyToScriptGenerateMode];
88
- export type SessionType = "audio" | "image" | "video" | "multiLingual" | "caption" | "pdf";
88
+ export type SessionType = "audio" | "image" | "video" | "multiLingual" | "caption" | "pdf" | "markdown" | "html";
89
89
  export type BeatSessionType = "audio" | "image" | "multiLingual" | "caption" | "movie" | "html" | "imageReference" | "soundEffect" | "lipSync";
90
90
  export type SessionProgressEvent = {
91
91
  kind: "session";
@@ -2,6 +2,8 @@ import type { MulmoStudioBeat, MulmoScript, MulmoPresentationStyle, MulmoStudioM
2
2
  export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: string, videoCaptionLang?: string, presentationStyle?: MulmoPresentationStyle | null) => {
3
3
  beats: {
4
4
  duration?: number | undefined;
5
+ markdown?: string | undefined;
6
+ html?: string | undefined;
5
7
  id?: string | undefined;
6
8
  startAt?: number | undefined;
7
9
  hash?: string | undefined;
@@ -354,6 +356,8 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
354
356
  studio: {
355
357
  beats: {
356
358
  duration?: number | undefined;
359
+ markdown?: string | undefined;
360
+ html?: string | undefined;
357
361
  id?: string | undefined;
358
362
  startAt?: number | undefined;
359
363
  hash?: string | undefined;
@@ -831,6 +835,8 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
831
835
  multiLingual: boolean;
832
836
  caption: boolean;
833
837
  pdf: boolean;
838
+ markdown: boolean;
839
+ html: boolean;
834
840
  };
835
841
  inBeatSession: {
836
842
  audio: {};
@@ -34,6 +34,8 @@ const initSessionState = () => {
34
34
  multiLingual: false,
35
35
  caption: false,
36
36
  pdf: false,
37
+ markdown: false,
38
+ html: false,
37
39
  },
38
40
  inBeatSession: {
39
41
  audio: {},
@@ -1,11 +1,7 @@
1
- import * as pluginTextSlide from "./text_slide.js";
2
- import * as pluginMarkdown from "./markdown.js";
3
- import * as pluginChart from "./chart.js";
4
- import * as pluginMermaid from "./mermaid.js";
5
- import * as pluginHtmlTailwind from "./html_tailwind.js";
6
- import * as pluginImage from "./image.js";
7
- import * as pluginMovie from "./movie.js";
8
- import * as pluginBeat from "./beat.js";
9
- import * as pluginVoiceOver from "./voice_over.js";
10
- import * as pluginVision from "./vision.js";
11
- export declare const findImagePlugin: (imageType?: string) => typeof pluginTextSlide | typeof pluginMarkdown | typeof pluginChart | typeof pluginMermaid | typeof pluginHtmlTailwind | typeof pluginImage | typeof pluginMovie | typeof pluginBeat | typeof pluginVoiceOver | typeof pluginVision | undefined;
1
+ import { ImageProcessorParams } from "../../types/index.js";
2
+ export declare const findImagePlugin: (imageType?: string) => {
3
+ imageType: string;
4
+ process: (params: ImageProcessorParams) => Promise<string | undefined> | void;
5
+ path: (params: ImageProcessorParams) => string | undefined;
6
+ markdown?: (params: ImageProcessorParams) => string | undefined;
7
+ } | undefined;
@@ -8,18 +8,7 @@ import * as pluginMovie from "./movie.js";
8
8
  import * as pluginBeat from "./beat.js";
9
9
  import * as pluginVoiceOver from "./voice_over.js";
10
10
  import * as pluginVision from "./vision.js";
11
- const imagePlugins = [
12
- pluginTextSlide,
13
- pluginMarkdown,
14
- pluginImage,
15
- pluginChart,
16
- pluginMermaid,
17
- pluginMovie,
18
- pluginHtmlTailwind,
19
- pluginBeat,
20
- pluginVoiceOver,
21
- pluginVision,
22
- ];
11
+ const imagePlugins = [pluginTextSlide, pluginMarkdown, pluginImage, pluginChart, pluginMermaid, pluginMovie, pluginHtmlTailwind, pluginBeat, pluginVoiceOver, pluginVision];
23
12
  export const findImagePlugin = (imageType) => {
24
13
  return imagePlugins.find((plugin) => plugin.imageType === imageType);
25
14
  };
@@ -2,3 +2,4 @@ import { ImageProcessorParams } from "../../types/index.js";
2
2
  export declare const imageType = "markdown";
3
3
  export declare const process: (params: ImageProcessorParams) => Promise<string | undefined>;
4
4
  export declare const path: (params: ImageProcessorParams) => string;
5
+ export declare const markdown: (params: ImageProcessorParams) => string | undefined;
@@ -5,9 +5,16 @@ const processMarkdown = async (params) => {
5
5
  const { beat, imagePath, textSlideStyle, canvasSize } = params;
6
6
  if (!beat.image || beat.image.type !== imageType)
7
7
  return;
8
- const markdown = Array.isArray(beat.image.markdown) ? beat.image.markdown.join("\n") : beat.image.markdown;
8
+ const markdown = dumpMarkdown(params) ?? "";
9
9
  await renderMarkdownToImage(markdown, textSlideStyle, imagePath, canvasSize.width, canvasSize.height);
10
10
  return imagePath;
11
11
  };
12
+ const dumpMarkdown = (params) => {
13
+ const { beat } = params;
14
+ if (!beat.image || beat.image.type !== imageType)
15
+ return;
16
+ return Array.isArray(beat.image.markdown) ? beat.image.markdown.join("\n") : beat.image.markdown;
17
+ };
12
18
  export const process = processMarkdown;
13
19
  export const path = parrotingImagePath;
20
+ export const markdown = dumpMarkdown;
@@ -2,3 +2,4 @@ import { ImageProcessorParams } from "../../types/index.js";
2
2
  export declare const imageType = "mermaid";
3
3
  export declare const process: (params: ImageProcessorParams) => Promise<string | undefined>;
4
4
  export declare const path: (params: ImageProcessorParams) => string;
5
+ export declare const markdown: (params: ImageProcessorParams) => string | undefined;
@@ -19,5 +19,14 @@ const processMermaid = async (params) => {
19
19
  }
20
20
  return imagePath;
21
21
  };
22
+ const dumpMarkdown = (params) => {
23
+ const { beat } = params;
24
+ if (!beat.image || beat.image.type !== imageType)
25
+ return;
26
+ if (beat.image.code.kind !== "text")
27
+ return; // support only text for now
28
+ return `\`\`\`mermaid\n${beat.image.code.text}\n\`\`\``;
29
+ };
22
30
  export const process = processMermaid;
23
31
  export const path = parrotingImagePath;
32
+ export const markdown = dumpMarkdown;
@@ -2,3 +2,4 @@ import { ImageProcessorParams } from "../../types/index.js";
2
2
  export declare const imageType = "textSlide";
3
3
  export declare const process: (params: ImageProcessorParams) => Promise<string | undefined>;
4
4
  export declare const path: (params: ImageProcessorParams) => string;
5
+ export declare const markdown: (params: ImageProcessorParams) => string | undefined;
@@ -6,7 +6,7 @@ const processTextSlide = async (params) => {
6
6
  if (!beat.image || beat.image.type !== imageType)
7
7
  return;
8
8
  const slide = beat.image.slide;
9
- const markdown = `# ${slide.title}\n` + (slide.subtitle ? `## ${slide.subtitle}\n` : "") + (slide.bullets ?? []).map((text) => `- ${text}`).join("\n");
9
+ const markdown = dumpMarkdown(params) ?? "";
10
10
  const topMargin = (() => {
11
11
  if (slide.bullets?.length && slide.bullets.length > 0) {
12
12
  return "";
@@ -17,5 +17,16 @@ const processTextSlide = async (params) => {
17
17
  await renderMarkdownToImage(markdown, textSlideStyle + topMargin, imagePath, canvasSize.width, canvasSize.height);
18
18
  return imagePath;
19
19
  };
20
+ const dumpMarkdown = (params) => {
21
+ const { beat } = params;
22
+ if (!beat.image || beat.image.type !== imageType)
23
+ return;
24
+ const slide = beat.image.slide;
25
+ const titleString = slide.title ? `# ${slide.title}\n` : "";
26
+ const subtitleString = slide.subtitle ? `## ${slide.subtitle}\n` : "";
27
+ const bulletsString = (slide.bullets ?? []).map((text) => `- ${text}`).join("\n");
28
+ return `${titleString}${subtitleString}${bulletsString}`;
29
+ };
20
30
  export const process = processTextSlide;
21
31
  export const path = parrotingImagePath;
32
+ export const markdown = dumpMarkdown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mulmocast",
3
- "version": "1.2.28",
3
+ "version": "1.2.30",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "lib/index.node.js",
@@ -49,6 +49,8 @@
49
49
  "scripting": "npx tsx ./src/cli/bin.ts tool scripting",
50
50
  "prompt": "npx tsx ./src/cli/bin.ts tool prompt",
51
51
  "schema": "npx tsx ./src/cli/bin.ts tool schema",
52
+ "markdown": "npx tsx ./src/cli/bin.ts markdown",
53
+ "html": "npx tsx ./src/cli/bin.ts html",
52
54
  "story_to_script": "npx tsx ./src/cli/bin.ts tool story_to_script",
53
55
  "whisper": "npx tsx ./src/cli/bin.ts tool whisper",
54
56
  "latest": "yarn upgrade-interactive --latest",
@@ -67,7 +69,7 @@
67
69
  "homepage": "https://github.com/receptron/mulmocast-cli#readme",
68
70
  "dependencies": {
69
71
  "@google-cloud/text-to-speech": "^6.3.0",
70
- "@google/genai": "^1.17.0",
72
+ "@google/genai": "^1.19.0",
71
73
  "@graphai/anthropic_agent": "^2.0.11",
72
74
  "@graphai/browserless_agent": "^2.0.1",
73
75
  "@graphai/gemini_agent": "^2.0.1",
@@ -77,20 +79,20 @@
77
79
  "@graphai/stream_agent_filter": "^2.0.2",
78
80
  "@graphai/vanilla": "^2.0.12",
79
81
  "@graphai/vanilla_node_agents": "^2.0.4",
80
- "@inquirer/input": "^4.2.2",
81
- "@inquirer/select": "^4.3.2",
82
- "@modelcontextprotocol/sdk": "^1.17.5",
82
+ "@inquirer/input": "^4.2.4",
83
+ "@inquirer/select": "^4.3.4",
84
+ "@modelcontextprotocol/sdk": "^1.18.0",
83
85
  "@mozilla/readability": "^0.6.0",
84
86
  "@tavily/core": "^0.5.11",
85
87
  "clipboardy": "^4.0.0",
86
88
  "dotenv": "^17.2.2",
87
89
  "fluent-ffmpeg": "^2.1.3",
88
90
  "graphai": "^2.0.14",
89
- "jsdom": "^26.1.0",
90
- "marked": "^16.2.1",
91
- "mulmocast-vision": "^0.1.1",
91
+ "jsdom": "^27.0.0",
92
+ "marked": "^16.3.0",
93
+ "mulmocast-vision": "^1.0.2",
92
94
  "ora": "^8.2.0",
93
- "puppeteer": "^24.19.0",
95
+ "puppeteer": "^24.20.0",
94
96
  "replicate": "^1.1.0",
95
97
  "yaml": "^2.8.1",
96
98
  "yargs": "^18.0.0",
@@ -55,6 +55,16 @@
55
55
  "provider": "google",
56
56
  "model": "gemini-2.5-flash-image-preview"
57
57
  }
58
+ },
59
+ {
60
+ "id": "seedream-4",
61
+ "text": "Hello World with a witch and a broom with Seadream-4",
62
+ "imagePrompt": "Saying hello to the world",
63
+ "imageNames": ["witch", "broom"],
64
+ "imageParams": {
65
+ "provider": "replicate",
66
+ "model": "bytedance/seedream-4"
67
+ }
58
68
  }
59
69
  ]
60
70
  }
@@ -0,0 +1,60 @@
1
+ {
2
+ "$mulmocast": {
3
+ "version": "1.1"
4
+ },
5
+ "lang": "en",
6
+ "title": "Test Markdown",
7
+ "description": "This is a test markdown file.",
8
+ "beats": [
9
+ {
10
+ "text": "Hello World",
11
+ "image": {
12
+ "type": "markdown",
13
+ "markdown": []
14
+ }
15
+ },
16
+ {
17
+ "text": "Hello World",
18
+ "image": {
19
+ "type": "markdown",
20
+ "markdown": ["## Chapter 1"]
21
+ }
22
+ },
23
+ {
24
+ "image": {
25
+ "type": "markdown",
26
+ "markdown": ["## Chapter 2", "- Hello", "- World"]
27
+ }
28
+ },
29
+ {
30
+ "image": {
31
+ "type": "textSlide",
32
+ "slide": {
33
+ "title": "Chapter 3",
34
+ "subtitle": "Subtitle",
35
+ "bullets": ["Hello", "World"]
36
+ }
37
+ }
38
+ },
39
+ {
40
+ "image": {
41
+ "type": "textSlide",
42
+ "slide": {
43
+ "title": "",
44
+ "subtitle": "Chapter 4",
45
+ "bullets": ["Hello", "World"]
46
+ }
47
+ }
48
+ },
49
+ {
50
+ "image": {
51
+ "type": "mermaid",
52
+ "title": "Business Process Flow",
53
+ "code": {
54
+ "kind": "text",
55
+ "text": "graph LR\n A[Market Research] --> B[Product Planning]\n B --> C[Development]\n C --> D[Testing]\n D --> E[Manufacturing]\n E --> F[Marketing]\n F --> G[Sales]\n G --> H[Customer Support]\n H --> A"
56
+ }
57
+ }
58
+ }
59
+ ]
60
+ }