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.
@@ -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 { htmlImageFile: res.htmlImageFile, imagePath: res.imagePath };
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
- audios.forEach((audio) => {
35
- if (audio) {
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 = ["htmlImageFile", "imagePath", "movieFile", "lipSyncFile"];
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
- fs.writeFileSync(path.resolve(dir, viewJsonFileName), JSON.stringify(resultJson, null, 2));
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 (isMovie) {
32
- if (beat.soundEffectPrompt) {
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,
@@ -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
- if (beat.image.id && beatIndexMap[beat.image.id] !== undefined) {
358
- studioBeat.imageFile = studio.beats[beatIndexMap[beat.image.id]].imageFile;
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 if (index > 0) {
361
- studioBeat.imageFile = studio.beats[index - 1].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
  });
@@ -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("loadImage failed", error);
23
- const placeholderData = fs.readFileSync("assets/images/mulmocast_credit.png");
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 imagePaths = studio.beats.map((beat) => beat.imageFile);
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(imagePaths.map(loadImage));
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, runTranslateIfNeeded } from "../../helpers.js";
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 runTranslateIfNeeded(context, true);
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
  };
@@ -10,3 +10,4 @@ export declare const storyToScriptGenerateMode: {
10
10
  stepWise: string;
11
11
  oneStep: string;
12
12
  };
13
+ export declare const bundleTargetLang: string[];
@@ -10,3 +10,4 @@ export const storyToScriptGenerateMode = {
10
10
  stepWise: "step_wise",
11
11
  oneStep: "one_step",
12
12
  };
13
+ export const bundleTargetLang = ["ja", "en"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mulmocast",
3
- "version": "1.2.64",
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.0",
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.0",
96
+ "marked": "^16.4.1",
97
97
  "mulmocast-vision": "^1.0.4",
98
98
  "ora": "^9.0.0",
99
- "puppeteer": "^24.24.1",
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.37.0",
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