mulmocast 1.2.64 → 1.2.66
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/lib/actions/bundle.js +31 -18
- package/lib/actions/image_agents.js +9 -3
- package/lib/actions/images.js +26 -4
- package/lib/actions/pdf.js +6 -6
- package/lib/cli/commands/bundle/handler.js +7 -3
- package/lib/utils/const.d.ts +1 -0
- package/lib/utils/const.js +1 -0
- package/package.json +5 -5
- package/scripts/test/test_media.json +21 -1
- package/scripts/test/test_reference.json +0 -0
package/lib/actions/bundle.js
CHANGED
|
@@ -5,15 +5,16 @@ import { listLocalizedAudioPaths } from "./audio.js";
|
|
|
5
5
|
import { imagePreprocessAgent } from "./image_agents.js";
|
|
6
6
|
import { mkdir } from "../utils/file.js";
|
|
7
7
|
import { ZipBuilder } from "../utils/zip.js";
|
|
8
|
+
import { bundleTargetLang } from "../utils/const.js";
|
|
8
9
|
const beatImage = (context) => {
|
|
9
10
|
return async (beat, index) => {
|
|
10
11
|
try {
|
|
11
12
|
const res = await imagePreprocessAgent({ context, beat, index, imageRefs: {} });
|
|
12
13
|
if ("htmlPrompt" in res) {
|
|
13
|
-
return {
|
|
14
|
+
return { htmlImageSource: res.htmlImageFile, imageSource: res.imagePath };
|
|
14
15
|
}
|
|
15
16
|
const { imagePath, movieFile, lipSyncFile } = res;
|
|
16
|
-
return { imagePath, movieFile, lipSyncFile };
|
|
17
|
+
return { imageSource: imagePath, videoSource: movieFile, videoWithAudioSource: lipSyncFile };
|
|
17
18
|
}
|
|
18
19
|
catch (e) {
|
|
19
20
|
GraphAILogger.log(e);
|
|
@@ -21,32 +22,37 @@ const beatImage = (context) => {
|
|
|
21
22
|
}
|
|
22
23
|
};
|
|
23
24
|
};
|
|
25
|
+
// TODO reference
|
|
24
26
|
const viewJsonFileName = "mulmo_view.json";
|
|
25
27
|
const zipFileName = "mulmo.zip";
|
|
26
28
|
export const mulmoViewerBundle = async (context) => {
|
|
27
29
|
const isZip = true;
|
|
28
|
-
const audios = listLocalizedAudioPaths(context);
|
|
29
|
-
const images = await Promise.all(context.studio.script.beats.map(beatImage(context)));
|
|
30
30
|
const dir = path.resolve(context.fileDirs.fileName);
|
|
31
31
|
mkdir(dir);
|
|
32
32
|
const zipper = new ZipBuilder(path.resolve(dir, zipFileName));
|
|
33
33
|
const resultJson = [];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const fileName = path.basename(audio ?? "");
|
|
37
|
-
resultJson.push({ audio: fileName });
|
|
38
|
-
if (fs.existsSync(audio)) {
|
|
39
|
-
fs.copyFileSync(audio, path.resolve(dir, fileName));
|
|
40
|
-
zipper.addFile(audio, fileName);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
resultJson.push({});
|
|
45
|
-
}
|
|
34
|
+
context.studio.script.beats.forEach((beat) => {
|
|
35
|
+
resultJson.push({ text: beat.text, duration: beat.duration, audioSources: {}, multiLinguals: {} });
|
|
46
36
|
});
|
|
37
|
+
for (const lang of bundleTargetLang) {
|
|
38
|
+
const audios = listLocalizedAudioPaths({ ...context, lang });
|
|
39
|
+
audios.forEach((audio, index) => {
|
|
40
|
+
if (audio) {
|
|
41
|
+
const fileName = path.basename(audio ?? "");
|
|
42
|
+
if (resultJson[index] && resultJson[index].audioSources) {
|
|
43
|
+
resultJson[index].audioSources[lang] = fileName;
|
|
44
|
+
}
|
|
45
|
+
if (fs.existsSync(audio)) {
|
|
46
|
+
fs.copyFileSync(audio, path.resolve(dir, fileName));
|
|
47
|
+
zipper.addFile(audio, fileName);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const images = await Promise.all(context.studio.script.beats.map(beatImage(context)));
|
|
47
53
|
images.forEach((image, index) => {
|
|
48
54
|
const data = resultJson[index];
|
|
49
|
-
const keys = ["
|
|
55
|
+
const keys = ["htmlImageSource", "imageSource", "videoSource", "videoWithAudioSource"];
|
|
50
56
|
keys.forEach((key) => {
|
|
51
57
|
const value = image[key];
|
|
52
58
|
if (value) {
|
|
@@ -58,7 +64,14 @@ export const mulmoViewerBundle = async (context) => {
|
|
|
58
64
|
}
|
|
59
65
|
});
|
|
60
66
|
});
|
|
61
|
-
|
|
67
|
+
context.multiLingual.forEach((beat, index) => {
|
|
68
|
+
bundleTargetLang.forEach((lang) => {
|
|
69
|
+
if (resultJson[index] && resultJson[index].multiLinguals) {
|
|
70
|
+
resultJson[index].multiLinguals[lang] = beat.multiLingualTexts[lang].text;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
fs.writeFileSync(path.resolve(dir, viewJsonFileName), JSON.stringify({ beats: resultJson, bgmSource: context.studio?.script.audioParams?.bgm }, null, 2));
|
|
62
75
|
zipper.addFile(path.resolve(dir, viewJsonFileName));
|
|
63
76
|
if (isZip) {
|
|
64
77
|
await zipper.finalize();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GraphAILogger } from "graphai";
|
|
1
2
|
import { MulmoPresentationStyleMethods, MulmoStudioContextMethods, MulmoBeatMethods, MulmoMediaSourceMethods } from "../methods/index.js";
|
|
2
3
|
import { getBeatPngImagePath, getBeatMoviePaths, getAudioFilePath } from "../utils/file.js";
|
|
3
4
|
import { imagePrompt, htmlImageSystemPrompt } from "../utils/prompt.js";
|
|
@@ -28,14 +29,17 @@ export const imagePreprocessAgent = async (namedInputs) => {
|
|
|
28
29
|
beatDuration: beat.duration ?? studioBeat?.duration,
|
|
29
30
|
};
|
|
30
31
|
const isMovie = Boolean(beat.moviePrompt || beat?.image?.type === "movie");
|
|
31
|
-
if (
|
|
32
|
-
if (
|
|
32
|
+
if (beat.soundEffectPrompt) {
|
|
33
|
+
if (isMovie) {
|
|
33
34
|
returnValue.soundEffectAgentInfo = MulmoPresentationStyleMethods.getSoundEffectAgentInfo(context.presentationStyle, beat);
|
|
34
35
|
returnValue.soundEffectModel =
|
|
35
36
|
beat.soundEffectParams?.model ?? context.presentationStyle.soundEffectParams?.model ?? returnValue.soundEffectAgentInfo.defaultModel;
|
|
36
37
|
returnValue.soundEffectFile = moviePaths.soundEffectFile;
|
|
37
38
|
returnValue.soundEffectPrompt = beat.soundEffectPrompt;
|
|
38
39
|
}
|
|
40
|
+
else {
|
|
41
|
+
GraphAILogger.warn(`soundEffectPrompt is set, but there is no video. beat: ${index}`);
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
44
|
if (beat.enableLipSync) {
|
|
41
45
|
const lipSyncAgentInfo = MulmoPresentationStyleMethods.getLipSyncAgentInfo(context.presentationStyle, beat);
|
|
@@ -70,8 +74,10 @@ export const imagePreprocessAgent = async (namedInputs) => {
|
|
|
70
74
|
// ImagePluginPreprocessAgentResponse
|
|
71
75
|
return {
|
|
72
76
|
...returnValue,
|
|
73
|
-
imagePath: isTypeMovie ? undefined : pluginPath,
|
|
77
|
+
// imagePath: isTypeMovie ? undefined : pluginPath,
|
|
78
|
+
imagePath: isTypeMovie ? imagePath : pluginPath,
|
|
74
79
|
movieFile: isTypeMovie ? pluginPath : undefined,
|
|
80
|
+
imageFromMovie: isTypeMovie,
|
|
75
81
|
referenceImageForMovie: pluginPath,
|
|
76
82
|
markdown,
|
|
77
83
|
html,
|
package/lib/actions/images.js
CHANGED
|
@@ -354,11 +354,33 @@ export const images_graph_data = {
|
|
|
354
354
|
studio.beats.forEach((studioBeat, index) => {
|
|
355
355
|
const beat = studio.script.beats[index];
|
|
356
356
|
if (beat.image?.type === "beat") {
|
|
357
|
-
|
|
358
|
-
|
|
357
|
+
// reference Beat by plugin
|
|
358
|
+
const referenceBeat = (() => {
|
|
359
|
+
if (beat.image.id) {
|
|
360
|
+
if (beatIndexMap[beat.image.id] !== undefined) {
|
|
361
|
+
return studio.beats[beatIndexMap[beat.image.id]];
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
GraphAILogger.info(`reference beat not exist: id=${beat.image.id}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else if (index > 0) {
|
|
368
|
+
return studio.beats[index - 1];
|
|
369
|
+
}
|
|
370
|
+
})();
|
|
371
|
+
if (referenceBeat === undefined) {
|
|
372
|
+
// error?
|
|
373
|
+
GraphAILogger.info(`reference beat not exist: index=${index}`);
|
|
359
374
|
}
|
|
360
|
-
else
|
|
361
|
-
studioBeat.imageFile =
|
|
375
|
+
else {
|
|
376
|
+
studioBeat.imageFile = referenceBeat.imageFile;
|
|
377
|
+
studioBeat.movieFile = referenceBeat.movieFile;
|
|
378
|
+
studioBeat.soundEffectFile = referenceBeat.soundEffectFile;
|
|
379
|
+
studioBeat.lipSyncFile = referenceBeat.lipSyncFile;
|
|
380
|
+
studioBeat.hasMovieAudio = referenceBeat.hasMovieAudio;
|
|
381
|
+
studioBeat.htmlImageFile = referenceBeat.htmlImageFile;
|
|
382
|
+
studioBeat.markdown = referenceBeat.markdown;
|
|
383
|
+
studioBeat.html = referenceBeat.html;
|
|
362
384
|
}
|
|
363
385
|
}
|
|
364
386
|
});
|
package/lib/actions/pdf.js
CHANGED
|
@@ -4,14 +4,14 @@ import puppeteer from "puppeteer";
|
|
|
4
4
|
import { GraphAILogger, sleep } from "graphai";
|
|
5
5
|
import { MulmoPresentationStyleMethods } from "../methods/index.js";
|
|
6
6
|
import { localizedText, isHttp } from "../utils/utils.js";
|
|
7
|
-
import { getOutputPdfFilePath, writingMessage, getHTMLFile } from "../utils/file.js";
|
|
7
|
+
import { getOutputPdfFilePath, writingMessage, getHTMLFile, mulmoCreditPath } from "../utils/file.js";
|
|
8
8
|
import { interpolate } from "../utils/markdown.js";
|
|
9
9
|
import { MulmoStudioContextMethods } from "../methods/mulmo_studio_context.js";
|
|
10
10
|
const isCI = process.env.CI === "true";
|
|
11
11
|
const getPdfSize = (pdfSize) => {
|
|
12
12
|
return pdfSize === "a4" ? "A4" : "Letter";
|
|
13
13
|
};
|
|
14
|
-
const loadImage = async (imagePath) => {
|
|
14
|
+
const loadImage = async (imagePath, index) => {
|
|
15
15
|
try {
|
|
16
16
|
const imageData = isHttp(imagePath) ? Buffer.from(await (await fetch(imagePath)).arrayBuffer()) : fs.readFileSync(imagePath);
|
|
17
17
|
const ext = path.extname(imagePath).toLowerCase().replace(".", "");
|
|
@@ -19,8 +19,8 @@ const loadImage = async (imagePath) => {
|
|
|
19
19
|
return `data:image/${mimeType};base64,${imageData.toString("base64")}`;
|
|
20
20
|
}
|
|
21
21
|
catch (error) {
|
|
22
|
-
GraphAILogger.info(
|
|
23
|
-
const placeholderData = fs.readFileSync(
|
|
22
|
+
GraphAILogger.info(`loadImage failed: file: ${imagePath} index: ${index}`, error);
|
|
23
|
+
const placeholderData = fs.readFileSync(mulmoCreditPath());
|
|
24
24
|
return `data:image/png;base64,${placeholderData.toString("base64")}`;
|
|
25
25
|
}
|
|
26
26
|
};
|
|
@@ -100,9 +100,9 @@ const generatePDFHTML = async (context, pdfMode, pdfSize) => {
|
|
|
100
100
|
const { studio, multiLingual, lang = "en" } = context;
|
|
101
101
|
const { width: imageWidth, height: imageHeight } = MulmoPresentationStyleMethods.getCanvasSize(context.presentationStyle);
|
|
102
102
|
const isLandscapeImage = imageWidth > imageHeight;
|
|
103
|
-
const
|
|
103
|
+
const imageFiles = studio.beats.map((beat) => beat.htmlImageFile ?? beat.imageFile);
|
|
104
104
|
const texts = studio.script.beats.map((beat, index) => localizedText(beat, multiLingual?.[index], lang));
|
|
105
|
-
const imageDataUrls = await Promise.all(
|
|
105
|
+
const imageDataUrls = await Promise.all(imageFiles.map(loadImage));
|
|
106
106
|
const defaultPageSize = `${getPdfSize(pdfSize)} ${isLandscapeImage ? "landscape" : "portrait"}`;
|
|
107
107
|
const pageSize = pdfMode === "handout" ? `${getPdfSize(pdfSize)} portrait` : defaultPageSize;
|
|
108
108
|
const pagesHTML = generatePagesHTML(pdfMode, imageDataUrls, texts);
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { mulmoViewerBundle, audio, images } from "../../../actions/index.js";
|
|
2
|
-
import { initializeContext
|
|
1
|
+
import { mulmoViewerBundle, audio, images, translate } from "../../../actions/index.js";
|
|
2
|
+
import { initializeContext } from "../../helpers.js";
|
|
3
|
+
import { bundleTargetLang } from "../../../utils/const.js";
|
|
3
4
|
export const handler = async (argv) => {
|
|
4
5
|
const context = await initializeContext(argv);
|
|
5
6
|
if (!context) {
|
|
6
7
|
process.exit(1);
|
|
7
8
|
}
|
|
8
|
-
await
|
|
9
|
+
await translate(context, { targetLangs: bundleTargetLang });
|
|
10
|
+
for (const lang of bundleTargetLang.filter((_lang) => _lang !== context.lang)) {
|
|
11
|
+
await audio({ ...context, lang });
|
|
12
|
+
}
|
|
9
13
|
await audio(context).then(images).then(mulmoViewerBundle);
|
|
10
14
|
};
|
package/lib/utils/const.d.ts
CHANGED
package/lib/utils/const.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mulmocast",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.66",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.node.js",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"@graphai/vanilla_node_agents": "^2.0.4",
|
|
85
85
|
"@inquirer/input": "^4.2.5",
|
|
86
86
|
"@inquirer/select": "^4.4.0",
|
|
87
|
-
"@modelcontextprotocol/sdk": "^1.20.
|
|
87
|
+
"@modelcontextprotocol/sdk": "^1.20.1",
|
|
88
88
|
"@mozilla/readability": "^0.6.0",
|
|
89
89
|
"@tavily/core": "^0.5.11",
|
|
90
90
|
"archiver": "^7.0.1",
|
|
@@ -93,10 +93,10 @@
|
|
|
93
93
|
"fluent-ffmpeg": "^2.1.3",
|
|
94
94
|
"graphai": "^2.0.16",
|
|
95
95
|
"jsdom": "^27.0.0",
|
|
96
|
-
"marked": "^16.4.
|
|
96
|
+
"marked": "^16.4.1",
|
|
97
97
|
"mulmocast-vision": "^1.0.4",
|
|
98
98
|
"ora": "^9.0.0",
|
|
99
|
-
"puppeteer": "^24.
|
|
99
|
+
"puppeteer": "^24.25.0",
|
|
100
100
|
"replicate": "^1.3.0",
|
|
101
101
|
"yaml": "^2.8.1",
|
|
102
102
|
"yargs": "^18.0.0",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"@types/fluent-ffmpeg": "^2.1.26",
|
|
110
110
|
"@types/jsdom": "^27.0.0",
|
|
111
111
|
"@types/yargs": "^17.0.33",
|
|
112
|
-
"eslint": "^9.
|
|
112
|
+
"eslint": "^9.38.0",
|
|
113
113
|
"eslint-config-prettier": "^10.1.8",
|
|
114
114
|
"eslint-plugin-prettier": "^5.5.4",
|
|
115
115
|
"eslint-plugin-sonarjs": "^3.0.5",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
"speaker": "Presenter",
|
|
52
|
-
"text": "This is a reference beat.",
|
|
52
|
+
"text": "This is a image reference beat.",
|
|
53
53
|
"duration": 0.5,
|
|
54
54
|
"image": {
|
|
55
55
|
"type": "beat",
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
|
+
"id": "textSlide",
|
|
84
85
|
"speaker": "Presenter",
|
|
85
86
|
"text": "",
|
|
86
87
|
"duration": 2,
|
|
@@ -94,6 +95,7 @@
|
|
|
94
95
|
},
|
|
95
96
|
{
|
|
96
97
|
"speaker": "Presenter",
|
|
98
|
+
"id": "pingpongmov",
|
|
97
99
|
"text": "This is a local movie with audio.",
|
|
98
100
|
"image": {
|
|
99
101
|
"type": "movie",
|
|
@@ -253,6 +255,24 @@
|
|
|
253
255
|
"</footer>"
|
|
254
256
|
]
|
|
255
257
|
}
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"speaker": "Presenter",
|
|
261
|
+
"text": "This is a text slide reference beat.",
|
|
262
|
+
"duration": 0.5,
|
|
263
|
+
"image": {
|
|
264
|
+
"type": "beat",
|
|
265
|
+
"id": "textSlide"
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"speaker": "Presenter",
|
|
270
|
+
"text": "This is a movie reference beat.",
|
|
271
|
+
"duration": 0.5,
|
|
272
|
+
"image": {
|
|
273
|
+
"type": "beat",
|
|
274
|
+
"id": "pingpongmov"
|
|
275
|
+
}
|
|
256
276
|
}
|
|
257
277
|
]
|
|
258
278
|
}
|
|
File without changes
|