mulmocast 2.1.27 → 2.1.29
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.
- package/assets/html/chart.html +7 -0
- package/assets/html/tailwind.html +3 -2
- package/lib/actions/captions.js +1 -1
- package/lib/actions/image_agents.js +1 -1
- package/lib/actions/pdf.js +1 -1
- package/lib/cli/commands/tool/index.js +11 -1
- package/lib/cli/commands/tool/info/builder.d.ts +6 -0
- package/lib/cli/commands/tool/info/builder.js +13 -0
- package/lib/cli/commands/tool/info/handler.d.ts +6 -0
- package/lib/cli/commands/tool/info/handler.js +219 -0
- package/lib/cli/commands/tool/info/index.d.ts +4 -0
- package/lib/cli/commands/tool/info/index.js +4 -0
- package/lib/data/index.d.ts +1 -0
- package/lib/data/index.js +1 -0
- package/lib/data/markdownStyles.d.ts +14 -0
- package/lib/data/markdownStyles.js +998 -0
- package/lib/types/schema.d.ts +5 -0
- package/lib/types/schema.js +1 -0
- package/lib/utils/context.d.ts +2 -0
- package/lib/utils/html_render.d.ts +3 -0
- package/lib/utils/html_render.js +50 -0
- package/lib/utils/image_plugins/chart.js +1 -1
- package/lib/utils/image_plugins/html_tailwind.js +1 -1
- package/lib/utils/image_plugins/markdown.js +7 -2
- package/lib/utils/image_plugins/mermaid.js +1 -1
- package/lib/utils/image_plugins/text_slide.js +1 -1
- package/lib/utils/markdown.js +35 -106
- package/package.json +1 -1
- package/scripts/test/test_all_markdown_styles.json +809 -0
- package/scripts/test/test_markdown_styles.json +53 -0
- package/scripts/test/test_render_stress.json +984 -0
package/lib/types/schema.d.ts
CHANGED
|
@@ -182,6 +182,7 @@ export declare const mediaSourceMermaidSchema: z.ZodDiscriminatedUnion<[z.ZodObj
|
|
|
182
182
|
export declare const mulmoMarkdownMediaSchema: z.ZodObject<{
|
|
183
183
|
type: z.ZodLiteral<"markdown">;
|
|
184
184
|
markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
|
|
185
|
+
style: z.ZodOptional<z.ZodString>;
|
|
185
186
|
}, z.core.$strict>;
|
|
186
187
|
export declare const mulmoImageMediaSchema: z.ZodObject<{
|
|
187
188
|
type: z.ZodLiteral<"image">;
|
|
@@ -271,6 +272,7 @@ export declare const mulmoVisionMediaSchema: z.ZodObject<{
|
|
|
271
272
|
export declare const mulmoImageAssetSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
272
273
|
type: z.ZodLiteral<"markdown">;
|
|
273
274
|
markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
|
|
275
|
+
style: z.ZodOptional<z.ZodString>;
|
|
274
276
|
}, z.core.$strict>, z.ZodObject<{
|
|
275
277
|
type: z.ZodLiteral<"web">;
|
|
276
278
|
url: z.ZodURL;
|
|
@@ -797,6 +799,7 @@ export declare const mulmoBeatSchema: z.ZodObject<{
|
|
|
797
799
|
image: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
798
800
|
type: z.ZodLiteral<"markdown">;
|
|
799
801
|
markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
|
|
802
|
+
style: z.ZodOptional<z.ZodString>;
|
|
800
803
|
}, z.core.$strict>, z.ZodObject<{
|
|
801
804
|
type: z.ZodLiteral<"web">;
|
|
802
805
|
url: z.ZodURL;
|
|
@@ -1981,6 +1984,7 @@ export declare const mulmoScriptSchema: z.ZodObject<{
|
|
|
1981
1984
|
image: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
1982
1985
|
type: z.ZodLiteral<"markdown">;
|
|
1983
1986
|
markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
|
|
1987
|
+
style: z.ZodOptional<z.ZodString>;
|
|
1984
1988
|
}, z.core.$strict>, z.ZodObject<{
|
|
1985
1989
|
type: z.ZodLiteral<"web">;
|
|
1986
1990
|
url: z.ZodURL;
|
|
@@ -2869,6 +2873,7 @@ export declare const mulmoStudioSchema: z.ZodObject<{
|
|
|
2869
2873
|
image: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
2870
2874
|
type: z.ZodLiteral<"markdown">;
|
|
2871
2875
|
markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
|
|
2876
|
+
style: z.ZodOptional<z.ZodString>;
|
|
2872
2877
|
}, z.core.$strict>, z.ZodObject<{
|
|
2873
2878
|
type: z.ZodLiteral<"web">;
|
|
2874
2879
|
url: z.ZodURL;
|
package/lib/types/schema.js
CHANGED
package/lib/utils/context.d.ts
CHANGED
|
@@ -275,6 +275,7 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
|
|
|
275
275
|
} | {
|
|
276
276
|
type: "markdown";
|
|
277
277
|
markdown: string | string[];
|
|
278
|
+
style?: string | undefined;
|
|
278
279
|
} | {
|
|
279
280
|
type: "web";
|
|
280
281
|
url: string;
|
|
@@ -931,6 +932,7 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
|
|
|
931
932
|
} | {
|
|
932
933
|
type: "markdown";
|
|
933
934
|
markdown: string | string[];
|
|
935
|
+
style?: string | undefined;
|
|
934
936
|
} | {
|
|
935
937
|
type: "web";
|
|
936
938
|
url: string;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const renderHTMLToImage: (html: string, outputPath: string, width: number, height: number, isMermaid?: boolean, omitBackground?: boolean) => Promise<void>;
|
|
2
|
+
export declare const renderMarkdownToImage: (markdown: string, style: string, outputPath: string, width: number, height: number) => Promise<void>;
|
|
3
|
+
export declare const interpolate: (template: string, data: Record<string, string>) => string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { marked } from "marked";
|
|
2
|
+
import puppeteer from "puppeteer";
|
|
3
|
+
const isCI = process.env.CI === "true";
|
|
4
|
+
export const renderHTMLToImage = async (html, outputPath, width, height, isMermaid = false, omitBackground = false) => {
|
|
5
|
+
// Use Puppeteer to render HTML to an image
|
|
6
|
+
const browser = await puppeteer.launch({
|
|
7
|
+
args: isCI ? ["--no-sandbox"] : [],
|
|
8
|
+
});
|
|
9
|
+
const page = await browser.newPage();
|
|
10
|
+
// Set the page content to the HTML generated from the Markdown
|
|
11
|
+
await page.setContent(html);
|
|
12
|
+
// Adjust page settings if needed (like width, height, etc.)
|
|
13
|
+
await page.setViewport({ width, height });
|
|
14
|
+
// height:100% ensures background fills viewport; only reset html, let body styles come from custom CSS
|
|
15
|
+
await page.addStyleTag({ content: "html{height:100%;margin:0;padding:0;overflow:hidden}" });
|
|
16
|
+
if (isMermaid) {
|
|
17
|
+
await page.waitForFunction(() => {
|
|
18
|
+
const element = document.querySelector(".mermaid");
|
|
19
|
+
return element && element.dataset.ready === "true";
|
|
20
|
+
}, { timeout: 20000 });
|
|
21
|
+
}
|
|
22
|
+
// Wait for Chart.js to finish rendering if this is a chart
|
|
23
|
+
if (html.includes("data-chart-ready")) {
|
|
24
|
+
await page.waitForFunction(() => {
|
|
25
|
+
const canvas = document.querySelector("canvas[data-chart-ready='true']");
|
|
26
|
+
return !!canvas;
|
|
27
|
+
}, { timeout: 20000 });
|
|
28
|
+
}
|
|
29
|
+
// Measure the size of the page and scale the page to the width and height
|
|
30
|
+
await page.evaluate(({ viewportWidth, viewportHeight }) => {
|
|
31
|
+
const docElement = document.documentElement;
|
|
32
|
+
const scrollWidth = Math.max(docElement.scrollWidth, document.body.scrollWidth || 0);
|
|
33
|
+
const scrollHeight = Math.max(docElement.scrollHeight, document.body.scrollHeight || 0);
|
|
34
|
+
const scale = Math.min(viewportWidth / (scrollWidth || viewportWidth), viewportHeight / (scrollHeight || viewportHeight), 1);
|
|
35
|
+
docElement.style.overflow = "hidden";
|
|
36
|
+
document.body.style.zoom = String(scale);
|
|
37
|
+
}, { viewportWidth: width, viewportHeight: height });
|
|
38
|
+
// Step 3: Capture screenshot of the page (which contains the Markdown-rendered HTML)
|
|
39
|
+
await page.screenshot({ path: outputPath, omitBackground });
|
|
40
|
+
await browser.close();
|
|
41
|
+
};
|
|
42
|
+
export const renderMarkdownToImage = async (markdown, style, outputPath, width, height) => {
|
|
43
|
+
const header = `<head><style>${style}</style></head>`;
|
|
44
|
+
const body = await marked(markdown);
|
|
45
|
+
const html = `<html>${header}<body>${body}</body></html>`;
|
|
46
|
+
await renderHTMLToImage(html, outputPath, width, height);
|
|
47
|
+
};
|
|
48
|
+
export const interpolate = (template, data) => {
|
|
49
|
+
return template.replace(/\$\{(.*?)\}/g, (_, key) => data[key.trim()] ?? "");
|
|
50
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getHTMLFile } from "../file.js";
|
|
2
|
-
import { renderHTMLToImage, interpolate } from "../
|
|
2
|
+
import { renderHTMLToImage, interpolate } from "../html_render.js";
|
|
3
3
|
import { parrotingImagePath } from "./utils.js";
|
|
4
4
|
import nodeProcess from "node:process";
|
|
5
5
|
export const imageType = "chart";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getHTMLFile } from "../file.js";
|
|
2
|
-
import { renderHTMLToImage, interpolate } from "../
|
|
2
|
+
import { renderHTMLToImage, interpolate } from "../html_render.js";
|
|
3
3
|
import { parrotingImagePath } from "./utils.js";
|
|
4
4
|
export const imageType = "html_tailwind";
|
|
5
5
|
const processHtmlTailwind = async (params) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { renderMarkdownToImage } from "../
|
|
1
|
+
import { renderMarkdownToImage } from "../html_render.js";
|
|
2
2
|
import { parrotingImagePath } from "./utils.js";
|
|
3
|
+
import { getMarkdownStyle } from "../../data/markdownStyles.js";
|
|
3
4
|
import { marked } from "marked";
|
|
4
5
|
export const imageType = "markdown";
|
|
5
6
|
const processMarkdown = async (params) => {
|
|
@@ -7,7 +8,11 @@ const processMarkdown = async (params) => {
|
|
|
7
8
|
if (!beat.image || beat.image.type !== imageType)
|
|
8
9
|
return;
|
|
9
10
|
const markdown = dumpMarkdown(params) ?? "";
|
|
10
|
-
|
|
11
|
+
// Use custom style if specified, otherwise use default textSlideStyle
|
|
12
|
+
const styleName = beat.image.style;
|
|
13
|
+
const customStyle = styleName ? getMarkdownStyle(styleName) : undefined;
|
|
14
|
+
const style = customStyle ? customStyle.css : textSlideStyle;
|
|
15
|
+
await renderMarkdownToImage(markdown, style, imagePath, canvasSize.width, canvasSize.height);
|
|
11
16
|
return imagePath;
|
|
12
17
|
};
|
|
13
18
|
const dumpMarkdown = (params) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MulmoMediaSourceMethods } from "../../methods/index.js";
|
|
2
2
|
import { getHTMLFile } from "../file.js";
|
|
3
|
-
import { renderHTMLToImage, interpolate } from "../
|
|
3
|
+
import { renderHTMLToImage, interpolate } from "../html_render.js";
|
|
4
4
|
import { parrotingImagePath } from "./utils.js";
|
|
5
5
|
import nodeProcess from "node:process";
|
|
6
6
|
export const imageType = "mermaid";
|
package/lib/utils/markdown.js
CHANGED
|
@@ -1,113 +1,42 @@
|
|
|
1
1
|
import { marked } from "marked";
|
|
2
2
|
import puppeteer from "puppeteer";
|
|
3
3
|
const isCI = process.env.CI === "true";
|
|
4
|
-
const reuseBrowser = process.env.MULMO_PUPPETEER_REUSE !== "0";
|
|
5
|
-
const browserLaunchArgs = isCI ? ["--no-sandbox"] : [];
|
|
6
|
-
// Shared browser to avoid spawning a new Chromium per render.
|
|
7
|
-
let sharedBrowserPromise = null;
|
|
8
|
-
let sharedBrowserRefs = 0;
|
|
9
|
-
let sharedBrowserCloseTimer = null;
|
|
10
|
-
// Acquire a browser instance; reuse a shared one when enabled.
|
|
11
|
-
const acquireBrowser = async () => {
|
|
12
|
-
if (!reuseBrowser) {
|
|
13
|
-
return await puppeteer.launch({ args: browserLaunchArgs });
|
|
14
|
-
}
|
|
15
|
-
sharedBrowserRefs += 1;
|
|
16
|
-
if (sharedBrowserCloseTimer) {
|
|
17
|
-
clearTimeout(sharedBrowserCloseTimer);
|
|
18
|
-
sharedBrowserCloseTimer = null;
|
|
19
|
-
}
|
|
20
|
-
if (!sharedBrowserPromise) {
|
|
21
|
-
sharedBrowserPromise = puppeteer.launch({ args: browserLaunchArgs });
|
|
22
|
-
}
|
|
23
|
-
const currentPromise = sharedBrowserPromise;
|
|
24
|
-
try {
|
|
25
|
-
return await currentPromise;
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
if (sharedBrowserPromise === currentPromise) {
|
|
29
|
-
sharedBrowserPromise = null;
|
|
30
|
-
}
|
|
31
|
-
sharedBrowserRefs = Math.max(0, sharedBrowserRefs - 1);
|
|
32
|
-
throw error;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
// Release the browser; close only after a short idle window.
|
|
36
|
-
const releaseBrowser = async (browser) => {
|
|
37
|
-
if (!reuseBrowser) {
|
|
38
|
-
await browser.close().catch(() => { });
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
sharedBrowserRefs = Math.max(0, sharedBrowserRefs - 1);
|
|
42
|
-
if (sharedBrowserRefs > 0 || !sharedBrowserPromise) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
// Delay close to allow back-to-back renders to reuse the browser.
|
|
46
|
-
sharedBrowserCloseTimer = setTimeout(async () => {
|
|
47
|
-
const current = sharedBrowserPromise;
|
|
48
|
-
sharedBrowserPromise = null;
|
|
49
|
-
sharedBrowserCloseTimer = null;
|
|
50
|
-
if (current) {
|
|
51
|
-
await (await current).close().catch(() => { });
|
|
52
|
-
}
|
|
53
|
-
}, 300);
|
|
54
|
-
};
|
|
55
|
-
// Wait for a single animation frame to let canvas paints settle.
|
|
56
|
-
const waitForNextFrame = async (page) => {
|
|
57
|
-
await page.evaluate(() => new Promise((resolve) => {
|
|
58
|
-
requestAnimationFrame(() => resolve());
|
|
59
|
-
}));
|
|
60
|
-
};
|
|
61
4
|
export const renderHTMLToImage = async (html, outputPath, width, height, isMermaid = false, omitBackground = false) => {
|
|
62
|
-
//
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// Step 3: Capture screenshot of the page (which contains the Markdown-rendered HTML)
|
|
98
|
-
await page.screenshot({ path: outputPath, omitBackground });
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
if (page) {
|
|
102
|
-
await page.close().catch(() => { });
|
|
103
|
-
}
|
|
104
|
-
if (useSharedBrowser) {
|
|
105
|
-
await releaseBrowser(browser);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
await browser.close().catch(() => { });
|
|
109
|
-
}
|
|
110
|
-
}
|
|
5
|
+
// Use Puppeteer to render HTML to an image
|
|
6
|
+
const browser = await puppeteer.launch({
|
|
7
|
+
args: isCI ? ["--no-sandbox"] : [],
|
|
8
|
+
});
|
|
9
|
+
const page = await browser.newPage();
|
|
10
|
+
// Set the page content to the HTML generated from the Markdown
|
|
11
|
+
await page.setContent(html);
|
|
12
|
+
// Adjust page settings if needed (like width, height, etc.)
|
|
13
|
+
await page.setViewport({ width, height });
|
|
14
|
+
await page.addStyleTag({ content: "html,body{margin:0;padding:0;overflow:hidden}" });
|
|
15
|
+
if (isMermaid) {
|
|
16
|
+
await page.waitForFunction(() => {
|
|
17
|
+
const el = document.querySelector(".mermaid");
|
|
18
|
+
return el && el.dataset.ready === "true";
|
|
19
|
+
}, { timeout: 20000 });
|
|
20
|
+
}
|
|
21
|
+
// Wait for Chart.js to finish rendering if this is a chart
|
|
22
|
+
if (html.includes("data-chart-ready")) {
|
|
23
|
+
await page.waitForFunction(() => {
|
|
24
|
+
const canvas = document.querySelector("canvas[data-chart-ready='true']");
|
|
25
|
+
return !!canvas;
|
|
26
|
+
}, { timeout: 20000 });
|
|
27
|
+
}
|
|
28
|
+
// Measure the size of the page and scale the page to the width and height
|
|
29
|
+
await page.evaluate(({ vw, vh }) => {
|
|
30
|
+
const de = document.documentElement;
|
|
31
|
+
const sw = Math.max(de.scrollWidth, document.body.scrollWidth || 0);
|
|
32
|
+
const sh = Math.max(de.scrollHeight, document.body.scrollHeight || 0);
|
|
33
|
+
const scale = Math.min(vw / (sw || vw), vh / (sh || vh), 1); // <=1 で縮小のみ
|
|
34
|
+
de.style.overflow = "hidden";
|
|
35
|
+
document.body.style.zoom = String(scale);
|
|
36
|
+
}, { vw: width, vh: height });
|
|
37
|
+
// Step 3: Capture screenshot of the page (which contains the Markdown-rendered HTML)
|
|
38
|
+
await page.screenshot({ path: outputPath, omitBackground });
|
|
39
|
+
await browser.close();
|
|
111
40
|
};
|
|
112
41
|
export const renderMarkdownToImage = async (markdown, style, outputPath, width, height) => {
|
|
113
42
|
const header = `<head><style>${style}</style></head>`;
|