mulmocast 2.4.7 → 2.4.8
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.
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { MulmoStudioContext, PublicAPIArgs } from "../types/index.js";
|
|
2
2
|
import type { GraphData } from "graphai";
|
|
3
|
+
export declare const stripHtmlTags: (text: string) => string;
|
|
4
|
+
export declare const calculateTimingRatios: (splitTexts: string[]) => number[];
|
|
3
5
|
export declare const caption_graph_data: GraphData;
|
|
4
6
|
export declare const captions: (context: MulmoStudioContext, args?: PublicAPIArgs) => Promise<MulmoStudioContext>;
|
package/lib/actions/captions.js
CHANGED
|
@@ -44,13 +44,21 @@ const getSplitTexts = (text, texts, textSplit) => {
|
|
|
44
44
|
}
|
|
45
45
|
return [text];
|
|
46
46
|
};
|
|
47
|
-
//
|
|
48
|
-
const
|
|
49
|
-
|
|
47
|
+
// HTML tags commonly used in caption texts
|
|
48
|
+
const CAPTION_HTML_TAGS = "span|br|b|i|em|strong|u|s|div|p|a|sub|sup|mark";
|
|
49
|
+
const captionTagRegex = new RegExp(`</?(?:${CAPTION_HTML_TAGS})(?:\\s[^>]*)?\\/?>`, "gi");
|
|
50
|
+
// Strip known HTML tags to get plain text length (for timing calculation, not sanitization)
|
|
51
|
+
export const stripHtmlTags = (text) => {
|
|
52
|
+
return text.replace(captionTagRegex, "");
|
|
53
|
+
};
|
|
54
|
+
// Calculate timing ratios based on text length (HTML tags excluded)
|
|
55
|
+
export const calculateTimingRatios = (splitTexts) => {
|
|
56
|
+
const plainTexts = splitTexts.map(stripHtmlTags);
|
|
57
|
+
const totalLength = plainTexts.reduce((sum, t) => sum + t.length, 0);
|
|
50
58
|
if (totalLength === 0) {
|
|
51
59
|
return splitTexts.map(() => 1 / splitTexts.length);
|
|
52
60
|
}
|
|
53
|
-
return
|
|
61
|
+
return plainTexts.map((t) => t.length / totalLength);
|
|
54
62
|
};
|
|
55
63
|
// Convert ratios to cumulative ratios: [0.3, 0.5, 0.2] -> [0, 0.3, 0.8, 1.0]
|
|
56
64
|
const calculateCumulativeRatios = (ratios) => {
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { ImageProcessorParams } from "../../types/index.js";
|
|
2
2
|
export declare const imageType = "html_tailwind";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve image:name references to file:// absolute paths using imageRefs.
|
|
5
|
+
* e.g., src="image:bg_office" → src="file:///abs/path/to/bg_office.png"
|
|
6
|
+
*/
|
|
7
|
+
export declare const resolveImageRefs: (html: string, imageRefs: Record<string, string>) => string;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve relative paths in src attributes to file:// absolute paths.
|
|
10
|
+
* Paths starting with http://, https://, file://, data:, image:, or / are left unchanged.
|
|
11
|
+
*/
|
|
12
|
+
export declare const resolveRelativeImagePaths: (html: string, baseDirPath: string) => string;
|
|
3
13
|
export declare const process: (params: ImageProcessorParams) => Promise<string | undefined>;
|
|
4
14
|
export declare const path: (params: ImageProcessorParams) => string;
|
|
5
15
|
export declare const html: (params: ImageProcessorParams) => Promise<string | undefined>;
|
|
@@ -6,12 +6,25 @@ import { renderHTMLToImage, interpolate, renderHTMLToFrames } from "../html_rend
|
|
|
6
6
|
import { framesToVideo } from "../ffmpeg_utils.js";
|
|
7
7
|
import { parrotingImagePath } from "./utils.js";
|
|
8
8
|
export const imageType = "html_tailwind";
|
|
9
|
+
/**
|
|
10
|
+
* Resolve image:name references to file:// absolute paths using imageRefs.
|
|
11
|
+
* e.g., src="image:bg_office" → src="file:///abs/path/to/bg_office.png"
|
|
12
|
+
*/
|
|
13
|
+
export const resolveImageRefs = (html, imageRefs) => {
|
|
14
|
+
return html.replace(/(\bsrc\s*=\s*)(["'])image:([^"']+)\2/gi, (match, prefix, quote, name) => {
|
|
15
|
+
const resolvedPath = imageRefs[name];
|
|
16
|
+
if (!resolvedPath) {
|
|
17
|
+
return match;
|
|
18
|
+
}
|
|
19
|
+
return `${prefix}${quote}file://${resolvedPath}${quote}`;
|
|
20
|
+
});
|
|
21
|
+
};
|
|
9
22
|
/**
|
|
10
23
|
* Resolve relative paths in src attributes to file:// absolute paths.
|
|
11
|
-
* Paths starting with http://, https://, file://, data:, or / are left unchanged.
|
|
24
|
+
* Paths starting with http://, https://, file://, data:, image:, or / are left unchanged.
|
|
12
25
|
*/
|
|
13
|
-
const resolveRelativeImagePaths = (html, baseDirPath) => {
|
|
14
|
-
return html.replace(/(\bsrc\s*=\s*)(["'])((?!https?:\/\/|file:\/\/|data:|\/)[^"']+)\2/gi, (_, prefix, quote, relativePath) => {
|
|
26
|
+
export const resolveRelativeImagePaths = (html, baseDirPath) => {
|
|
27
|
+
return html.replace(/(\bsrc\s*=\s*)(["'])((?!https?:\/\/|file:\/\/|data:|image:|\/)[^"']+)\2/gi, (_, prefix, quote, relativePath) => {
|
|
15
28
|
const absolutePath = nodePath.resolve(baseDirPath, relativePath);
|
|
16
29
|
return `${prefix}${quote}file://${absolutePath}${quote}`;
|
|
17
30
|
});
|
|
@@ -64,7 +77,8 @@ const processHtmlTailwindAnimated = async (params) => {
|
|
|
64
77
|
fps: String(fps),
|
|
65
78
|
custom_style: "",
|
|
66
79
|
});
|
|
67
|
-
const
|
|
80
|
+
const resolvedRefs = resolveImageRefs(rawHtmlData, params.imageRefs ?? {});
|
|
81
|
+
const htmlData = resolveRelativeImagePaths(resolvedRefs, context.fileDirs.mulmoFileDirPath);
|
|
68
82
|
// imagePath is set to the .mp4 path by imagePluginAgent for animated beats
|
|
69
83
|
const videoPath = imagePath;
|
|
70
84
|
// Create frames directory next to the video file
|
|
@@ -90,7 +104,8 @@ const processHtmlTailwindStatic = async (params) => {
|
|
|
90
104
|
html_body: html,
|
|
91
105
|
user_script: buildUserScript(script),
|
|
92
106
|
});
|
|
93
|
-
const
|
|
107
|
+
const resolvedRefs = resolveImageRefs(rawHtmlData, params.imageRefs ?? {});
|
|
108
|
+
const htmlData = resolveRelativeImagePaths(resolvedRefs, context.fileDirs.mulmoFileDirPath);
|
|
94
109
|
await renderHTMLToImage(htmlData, imagePath, canvasSize.width, canvasSize.height);
|
|
95
110
|
return imagePath;
|
|
96
111
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mulmocast",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.8",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.node.js",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"homepage": "https://github.com/receptron/mulmocast-cli#readme",
|
|
88
88
|
"dependencies": {
|
|
89
89
|
"@google-cloud/text-to-speech": "^6.4.0",
|
|
90
|
-
"@google/genai": "^1.
|
|
90
|
+
"@google/genai": "^1.44.0",
|
|
91
91
|
"@graphai/anthropic_agent": "^2.0.12",
|
|
92
92
|
"@graphai/browserless_agent": "^2.0.2",
|
|
93
93
|
"@graphai/gemini_agent": "^2.0.5",
|
|
@@ -108,10 +108,10 @@
|
|
|
108
108
|
"fluent-ffmpeg": "^2.1.3",
|
|
109
109
|
"graphai": "^2.0.16",
|
|
110
110
|
"jsdom": "^28.1.0",
|
|
111
|
-
"marked": "^17.0.
|
|
111
|
+
"marked": "^17.0.4",
|
|
112
112
|
"mulmocast-vision": "^1.0.8",
|
|
113
113
|
"ora": "^9.3.0",
|
|
114
|
-
"puppeteer": "^24.
|
|
114
|
+
"puppeteer": "^24.38.0",
|
|
115
115
|
"replicate": "^1.4.0",
|
|
116
116
|
"yaml": "^2.8.2",
|
|
117
117
|
"yargs": "^18.0.0",
|
|
@@ -128,8 +128,8 @@
|
|
|
128
128
|
"eslint": "^10.0.2",
|
|
129
129
|
"eslint-config-prettier": "^10.1.8",
|
|
130
130
|
"eslint-plugin-prettier": "^5.5.5",
|
|
131
|
-
"eslint-plugin-sonarjs": "^4.0.
|
|
132
|
-
"globals": "^17.
|
|
131
|
+
"eslint-plugin-sonarjs": "^4.0.1",
|
|
132
|
+
"globals": "^17.4.0",
|
|
133
133
|
"prettier": "^3.8.1",
|
|
134
134
|
"tsx": "^4.21.0",
|
|
135
135
|
"typescript": "6.0.0-beta",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$mulmocast": { "version": "1.1" },
|
|
3
|
+
"lang": "ja",
|
|
4
|
+
"canvasSize": { "width": 1080, "height": 1920 },
|
|
5
|
+
"title": "image: スキームテスト",
|
|
6
|
+
"speechParams": {
|
|
7
|
+
"speakers": {
|
|
8
|
+
"Presenter": { "voiceId": "shimmer" }
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"audioParams": {
|
|
12
|
+
"bgm": {
|
|
13
|
+
"kind": "url",
|
|
14
|
+
"url": "https://github.com/receptron/mulmocast-media/raw/refs/heads/main/bgms/theme001.mp3"
|
|
15
|
+
},
|
|
16
|
+
"bgmVolume": 0.12
|
|
17
|
+
},
|
|
18
|
+
"imageParams": {
|
|
19
|
+
"provider": "google",
|
|
20
|
+
"model": "gemini-3.1-flash-image-preview",
|
|
21
|
+
"images": {
|
|
22
|
+
"bg_office": {
|
|
23
|
+
"type": "imagePrompt",
|
|
24
|
+
"prompt": "Empty modern tech office with abandoned desks and computer screens still glowing, dramatic morning sunlight, photorealistic, vertical composition 9:16"
|
|
25
|
+
},
|
|
26
|
+
"bg_city": {
|
|
27
|
+
"type": "imagePrompt",
|
|
28
|
+
"prompt": "Futuristic city skyline at sunset with neon lights reflecting on glass buildings, vibrant colors, photorealistic, vertical composition 9:16"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"beats": [
|
|
33
|
+
{
|
|
34
|
+
"text": "image:スキームのテストです。背景画像がimageParamsで生成した画像に置き換わります。",
|
|
35
|
+
"speaker": "Presenter",
|
|
36
|
+
"image": {
|
|
37
|
+
"type": "html_tailwind",
|
|
38
|
+
"html": [
|
|
39
|
+
"<div class='h-full w-full overflow-hidden relative bg-black'>",
|
|
40
|
+
" <div id='wrap' style='position:absolute;inset:0;overflow:hidden'>",
|
|
41
|
+
" <img src='image:bg_office' style='width:100%;height:100%;object-fit:cover;filter:brightness(0.7)' />",
|
|
42
|
+
" </div>",
|
|
43
|
+
" <div style='position:absolute;top:50%;left:40px;right:40px;transform:translateY(-50%);text-align:center'>",
|
|
44
|
+
" <div style='display:inline-block;background:rgba(239,68,68,0.85);padding:12px 32px;border-radius:12px'>",
|
|
45
|
+
" <span style='color:white;font-size:80px;font-weight:900'>image: test</span>",
|
|
46
|
+
" </div>",
|
|
47
|
+
" <div style='color:white;font-size:48px;font-weight:900;margin-top:20px;text-shadow:0 4px 16px rgba(0,0,0,0.9)'>bg_office を参照</div>",
|
|
48
|
+
" </div>",
|
|
49
|
+
"</div>"
|
|
50
|
+
],
|
|
51
|
+
"script": [
|
|
52
|
+
"const animation = new MulmoAnimation();",
|
|
53
|
+
"animation.animate('#wrap', { scale: [1.0, 1.15] }, { start: 0, end: 'auto', easing: 'linear' });"
|
|
54
|
+
],
|
|
55
|
+
"animation": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"text": "二枚目の背景です。別のimageRefsキーを参照しています。",
|
|
60
|
+
"speaker": "Presenter",
|
|
61
|
+
"image": {
|
|
62
|
+
"type": "html_tailwind",
|
|
63
|
+
"html": [
|
|
64
|
+
"<div class='h-full w-full overflow-hidden relative bg-black'>",
|
|
65
|
+
" <div id='wrap' style='position:absolute;inset:0;overflow:hidden'>",
|
|
66
|
+
" <img src='image:bg_city' style='width:100%;height:100%;object-fit:cover;filter:brightness(0.6)' />",
|
|
67
|
+
" </div>",
|
|
68
|
+
" <div style='position:absolute;top:50%;left:40px;right:40px;transform:translateY(-50%);text-align:center'>",
|
|
69
|
+
" <div style='display:inline-block;background:rgba(59,130,246,0.85);padding:12px 32px;border-radius:12px'>",
|
|
70
|
+
" <span style='color:white;font-size:80px;font-weight:900'>image: test</span>",
|
|
71
|
+
" </div>",
|
|
72
|
+
" <div style='color:white;font-size:48px;font-weight:900;margin-top:20px;text-shadow:0 4px 16px rgba(0,0,0,0.9)'>bg_city を参照</div>",
|
|
73
|
+
" </div>",
|
|
74
|
+
"</div>"
|
|
75
|
+
],
|
|
76
|
+
"script": [
|
|
77
|
+
"const animation = new MulmoAnimation();",
|
|
78
|
+
"animation.animate('#wrap', { translateX: [0, -30] }, { start: 0, end: 'auto', easing: 'linear' });"
|
|
79
|
+
],
|
|
80
|
+
"animation": true
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|