mulmocast 0.0.16 → 0.0.18

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.
@@ -4,4 +4,4 @@ import { MulmoStudioContext, MulmoBeat } from "../types/index.js";
4
4
  export declare const getBeatAudioPath: (text: string, context: MulmoStudioContext, beat: MulmoBeat, lang?: string) => string | undefined;
5
5
  export declare const audioFilePath: (context: MulmoStudioContext) => string;
6
6
  export declare const generateBeatAudio: (index: number, context: MulmoStudioContext, callbacks?: CallbackFunction[]) => Promise<void>;
7
- export declare const audio: (context: MulmoStudioContext, callbacks?: CallbackFunction[]) => Promise<void>;
7
+ export declare const audio: (context: MulmoStudioContext, callbacks?: CallbackFunction[]) => Promise<MulmoStudioContext>;
@@ -59,7 +59,7 @@ const preprocessor = (namedInputs) => {
59
59
  const text = localizedText(beat, multiLingual, lang);
60
60
  const { voiceId, provider, speechOptions } = getAudioParam(presentationStyle, beat);
61
61
  const audioPath = getBeatAudioPath(text, context, beat, lang);
62
- studioBeat.audioFile = audioPath;
62
+ studioBeat.audioFile = audioPath; // TODO
63
63
  const needsTTS = !beat.audio && audioPath !== undefined;
64
64
  return {
65
65
  ttsAgent: provider_to_agent[provider],
@@ -120,7 +120,7 @@ const graph_data = {
120
120
  inputs: {
121
121
  rows: ":context.studio.script.beats",
122
122
  studioBeat: ":context.studio.beats",
123
- multiLingual: ":context.studio.multiLingual",
123
+ multiLingual: ":context.multiLingual",
124
124
  context: ":context",
125
125
  },
126
126
  params: {
@@ -213,7 +213,7 @@ export const generateBeatAudio = async (index, context, callbacks) => {
213
213
  graph.injectValue("__mapIndex", index);
214
214
  graph.injectValue("beat", context.studio.script.beats[index]);
215
215
  graph.injectValue("studioBeat", context.studio.beats[index]);
216
- graph.injectValue("multiLingual", context.studio.multiLingual);
216
+ graph.injectValue("multiLingual", context.multiLingual);
217
217
  graph.injectValue("context", context);
218
218
  if (callbacks) {
219
219
  callbacks.forEach((callback) => {
@@ -249,10 +249,13 @@ export const audio = async (context, callbacks) => {
249
249
  graph.registerCallback(callback);
250
250
  });
251
251
  }
252
- await graph.run();
252
+ const result = await graph.run();
253
253
  writingMessage(audioCombinedFilePath);
254
+ MulmoStudioContextMethods.setSessionState(context, "audio", false);
255
+ return result.combineFiles;
254
256
  }
255
- finally {
257
+ catch (__error) {
256
258
  MulmoStudioContextMethods.setSessionState(context, "audio", false);
259
+ throw __error;
257
260
  }
258
261
  };
@@ -1,3 +1,3 @@
1
1
  import { MulmoStudioContext } from "../types/index.js";
2
2
  import type { CallbackFunction } from "graphai";
3
- export declare const captions: (context: MulmoStudioContext, callbacks?: CallbackFunction[]) => Promise<void>;
3
+ export declare const captions: (context: MulmoStudioContext, callbacks?: CallbackFunction[]) => Promise<MulmoStudioContext>;
@@ -2,7 +2,7 @@ import { GraphAI, GraphAILogger } from "graphai";
2
2
  import * as agents from "@graphai/vanilla";
3
3
  import { getHTMLFile } from "../utils/file.js";
4
4
  import { renderHTMLToImage, interpolate } from "../utils/markdown.js";
5
- import { MulmoStudioContextMethods } from "../methods/mulmo_studio_context.js";
5
+ import { MulmoStudioContextMethods, MulmoPresentationStyleMethods } from "../methods/index.js";
6
6
  const vanillaAgents = agents.default ?? agents;
7
7
  const graph_data = {
8
8
  version: 0.5,
@@ -23,14 +23,13 @@ const graph_data = {
23
23
  const { beat, context, index } = namedInputs;
24
24
  try {
25
25
  MulmoStudioContextMethods.setBeatSessionState(context, "caption", index, true);
26
- const { fileDirs } = namedInputs.context;
27
- const { caption } = context;
28
- const { imageDirPath } = fileDirs;
29
- const { canvasSize } = context.presentationStyle;
26
+ const imageDirPath = MulmoStudioContextMethods.getImageDirPath(context);
27
+ const caption = MulmoStudioContextMethods.getCaption(context);
28
+ const canvasSize = MulmoPresentationStyleMethods.getCanvasSize(context.presentationStyle);
30
29
  const imagePath = `${imageDirPath}/${context.studio.filename}/${index}_caption.png`;
31
30
  const template = getHTMLFile("caption");
32
31
  const text = (() => {
33
- const multiLingual = context.studio.multiLingual;
32
+ const multiLingual = context.multiLingual;
34
33
  if (caption && multiLingual) {
35
34
  return multiLingual[index].multiLingualTexts[caption].text;
36
35
  }
@@ -63,18 +62,21 @@ const graph_data = {
63
62
  },
64
63
  };
65
64
  export const captions = async (context, callbacks) => {
66
- try {
67
- MulmoStudioContextMethods.setSessionState(context, "caption", true);
68
- const graph = new GraphAI(graph_data, { ...vanillaAgents });
69
- graph.injectValue("context", context);
70
- if (callbacks) {
71
- callbacks.forEach((callback) => {
72
- graph.registerCallback(callback);
73
- });
65
+ if (context.caption) {
66
+ try {
67
+ MulmoStudioContextMethods.setSessionState(context, "caption", true);
68
+ const graph = new GraphAI(graph_data, { ...vanillaAgents });
69
+ graph.injectValue("context", context);
70
+ if (callbacks) {
71
+ callbacks.forEach((callback) => {
72
+ graph.registerCallback(callback);
73
+ });
74
+ }
75
+ await graph.run();
76
+ }
77
+ finally {
78
+ MulmoStudioContextMethods.setSessionState(context, "caption", false);
74
79
  }
75
- await graph.run();
76
- }
77
- finally {
78
- MulmoStudioContextMethods.setSessionState(context, "caption", false);
79
80
  }
81
+ return context;
80
82
  };
@@ -4,8 +4,6 @@ export declare const imagePreprocessAgent: (namedInputs: {
4
4
  context: MulmoStudioContext;
5
5
  beat: MulmoBeat;
6
6
  index: number;
7
- suffix: string;
8
- imageDirPath: string;
9
7
  imageAgentInfo: Text2ImageAgentInfo;
10
8
  imageRefs: Record<string, string>;
11
9
  }) => Promise<{
@@ -87,5 +85,6 @@ export declare const imagePreprocessAgent: (namedInputs: {
87
85
  referenceImage: string;
88
86
  prompt: string;
89
87
  }>;
90
- export declare const images: (context: MulmoStudioContext, callbacks?: CallbackFunction[]) => Promise<void>;
88
+ export declare const getImageRefs: (context: MulmoStudioContext) => Promise<Record<string, string>>;
89
+ export declare const images: (context: MulmoStudioContext, callbacks?: CallbackFunction[]) => Promise<MulmoStudioContext>;
91
90
  export declare const generateBeatImage: (index: number, context: MulmoStudioContext, callbacks?: CallbackFunction[]) => Promise<void>;
@@ -4,7 +4,7 @@ import { GraphAI, GraphAILogger } from "graphai";
4
4
  import { TaskManager } from "graphai/lib/task_manager.js";
5
5
  import * as agents from "@graphai/vanilla";
6
6
  import { fileWriteAgent } from "@graphai/vanilla_node_agents";
7
- import { getOutputStudioFilePath, mkdir } from "../utils/file.js";
7
+ import { getOutputStudioFilePath, getBeatPngImagePath, getBeatMoviePath, getReferenceImagePath, mkdir } from "../utils/file.js";
8
8
  import { fileCacheAgentFilter } from "../utils/filters.js";
9
9
  import { imageGoogleAgent, imageOpenaiAgent, movieGoogleAgent, mediaMockAgent } from "../agents/index.js";
10
10
  import { MulmoPresentationStyleMethods, MulmoStudioContextMethods } from "../methods/index.js";
@@ -22,12 +22,12 @@ const htmlStyle = (context, beat) => {
22
22
  };
23
23
  };
24
24
  export const imagePreprocessAgent = async (namedInputs) => {
25
- const { context, beat, index, suffix, imageDirPath, imageAgentInfo, imageRefs } = namedInputs;
25
+ const { context, beat, index, imageAgentInfo, imageRefs } = namedInputs;
26
26
  const imageParams = { ...imageAgentInfo.imageParams, ...beat.imageParams };
27
- const imagePath = `${imageDirPath}/${context.studio.filename}/${index}${suffix}.png`;
27
+ const imagePath = getBeatPngImagePath(context, index);
28
28
  const returnValue = {
29
29
  imageParams,
30
- movieFile: beat.moviePrompt ? `${imageDirPath}/${context.studio.filename}/${index}.mov` : undefined,
30
+ movieFile: beat.moviePrompt ? getBeatMoviePath(context, index) : undefined,
31
31
  };
32
32
  if (beat.image) {
33
33
  const plugin = imagePlugins.find((plugin) => plugin.imageType === beat?.image?.type);
@@ -61,7 +61,6 @@ const beat_graph_data = {
61
61
  concurrency: 4,
62
62
  nodes: {
63
63
  context: {},
64
- imageDirPath: {},
65
64
  imageAgentInfo: {},
66
65
  movieAgentInfo: {},
67
66
  imageRefs: {},
@@ -73,8 +72,6 @@ const beat_graph_data = {
73
72
  context: ":context",
74
73
  beat: ":beat",
75
74
  index: ":__mapIndex",
76
- suffix: "p",
77
- imageDirPath: ":imageDirPath",
78
75
  imageAgentInfo: ":imageAgentInfo",
79
76
  imageRefs: ":imageRefs",
80
77
  },
@@ -153,7 +150,6 @@ const graph_data = {
153
150
  concurrency: 4,
154
151
  nodes: {
155
152
  context: {},
156
- imageDirPath: {},
157
153
  imageAgentInfo: {},
158
154
  movieAgentInfo: {},
159
155
  outputStudioFilePath: {},
@@ -165,7 +161,6 @@ const graph_data = {
165
161
  context: ":context",
166
162
  imageAgentInfo: ":imageAgentInfo",
167
163
  movieAgentInfo: ":movieAgentInfo",
168
- imageDirPath: ":imageDirPath",
169
164
  imageRefs: ":imageRefs",
170
165
  },
171
166
  isResult: true,
@@ -200,7 +195,10 @@ const graph_data = {
200
195
  }
201
196
  }
202
197
  });
203
- return { studio };
198
+ return {
199
+ ...context,
200
+ studio,
201
+ };
204
202
  },
205
203
  inputs: {
206
204
  array: ":map.output",
@@ -262,11 +260,8 @@ const graphOption = async (context) => {
262
260
  }
263
261
  return options;
264
262
  };
265
- const prepareGenerateImages = async (context) => {
266
- const { studio, fileDirs } = context;
267
- const { outDirPath, imageDirPath } = fileDirs;
268
- mkdir(`${imageDirPath}/${studio.filename}`);
269
- const imageAgentInfo = MulmoPresentationStyleMethods.getImageAgentInfo(context.presentationStyle, context.dryRun);
263
+ // TODO: unit test
264
+ export const getImageRefs = async (context) => {
270
265
  const imageRefs = {};
271
266
  const images = context.presentationStyle.imageParams?.images;
272
267
  if (images) {
@@ -299,12 +294,21 @@ const prepareGenerateImages = async (context) => {
299
294
  return "png"; // default
300
295
  }
301
296
  })();
302
- const imagePath = `${imageDirPath}/${context.studio.filename}/${key}.${extension}`;
297
+ const imagePath = getReferenceImagePath(context, key, extension);
303
298
  await fs.promises.writeFile(imagePath, buffer);
304
299
  imageRefs[key] = imagePath;
305
300
  }
306
301
  }));
307
302
  }
303
+ return imageRefs;
304
+ };
305
+ const prepareGenerateImages = async (context) => {
306
+ const { studio } = context;
307
+ const imageProjectDirPath = MulmoStudioContextMethods.getImageProjectDirPath(context);
308
+ const outDirPath = MulmoStudioContextMethods.getOutDirPath(context);
309
+ mkdir(imageProjectDirPath);
310
+ const imageAgentInfo = MulmoPresentationStyleMethods.getImageAgentInfo(context.presentationStyle, context.dryRun);
311
+ const imageRefs = await getImageRefs(context);
308
312
  GraphAILogger.info(`text2image: provider=${imageAgentInfo.provider} model=${imageAgentInfo.imageParams.model}`);
309
313
  const injections = {
310
314
  context,
@@ -313,7 +317,6 @@ const prepareGenerateImages = async (context) => {
313
317
  agent: context.dryRun ? "mediaMockAgent" : "movieGoogleAgent",
314
318
  },
315
319
  outputStudioFilePath: getOutputStudioFilePath(outDirPath, studio.filename),
316
- imageDirPath,
317
320
  imageRefs,
318
321
  };
319
322
  return injections;
@@ -346,10 +349,13 @@ const generateImages = async (context, callbacks) => {
346
349
  export const images = async (context, callbacks) => {
347
350
  try {
348
351
  MulmoStudioContextMethods.setSessionState(context, "image", true);
349
- await generateImages(context, callbacks);
352
+ const newContext = await generateImages(context, callbacks);
353
+ MulmoStudioContextMethods.setSessionState(context, "image", false);
354
+ return newContext;
350
355
  }
351
- finally {
356
+ catch (error) {
352
357
  MulmoStudioContextMethods.setSessionState(context, "image", false);
358
+ throw error;
353
359
  }
354
360
  };
355
361
  export const generateBeatImage = async (index, context, callbacks) => {
@@ -171,15 +171,18 @@ const createVideo = async (audioArtifactFilePath, outputVideoPath, context, capt
171
171
  return true;
172
172
  };
173
173
  export const movieFilePath = (context) => {
174
- const { studio, fileDirs, caption } = context;
175
- return getOutputVideoFilePath(fileDirs.outDirPath, studio.filename, context.lang, caption);
174
+ const outDirPath = MulmoStudioContextMethods.getOutDirPath(context);
175
+ const fileName = MulmoStudioContextMethods.getFileName(context);
176
+ const caption = MulmoStudioContextMethods.getCaption(context);
177
+ return getOutputVideoFilePath(outDirPath, fileName, context.lang, caption);
176
178
  };
177
179
  export const movie = async (context) => {
178
180
  MulmoStudioContextMethods.setSessionState(context, "video", true);
179
181
  try {
180
- const { studio, fileDirs, caption } = context;
181
- const { outDirPath } = fileDirs;
182
- const audioArtifactFilePath = getAudioArtifactFilePath(outDirPath, studio.filename);
182
+ const caption = MulmoStudioContextMethods.getCaption(context);
183
+ const fileName = MulmoStudioContextMethods.getFileName(context);
184
+ const outDirPath = MulmoStudioContextMethods.getOutDirPath(context);
185
+ const audioArtifactFilePath = getAudioArtifactFilePath(outDirPath, fileName);
183
186
  const outputVideoPath = movieFilePath(context);
184
187
  if (await createVideo(audioArtifactFilePath, outputVideoPath, context, caption)) {
185
188
  writingMessage(outputVideoPath);
@@ -95,8 +95,7 @@ const getHandoutTemplateData = (isLandscapeImage) => ({
95
95
  item_flex: isLandscapeImage ? "flex: 1;" : "",
96
96
  });
97
97
  const generatePDFHTML = async (context, pdfMode, pdfSize) => {
98
- const { studio, lang = "en" } = context;
99
- const { multiLingual } = studio;
98
+ const { studio, multiLingual, lang = "en" } = context;
100
99
  const { width: imageWidth, height: imageHeight } = MulmoPresentationStyleMethods.getCanvasSize(context.presentationStyle);
101
100
  const isLandscapeImage = imageWidth > imageHeight;
102
101
  const imagePaths = studio.beats.map((beat) => beat.imageFile);
@@ -4,7 +4,7 @@ import * as agents from "@graphai/vanilla";
4
4
  import { openAIAgent } from "@graphai/openai_agent";
5
5
  import { fileWriteAgent } from "@graphai/vanilla_node_agents";
6
6
  import { recursiveSplitJa, replacementsJa, replacePairsJa } from "../utils/string.js";
7
- import { getOutputStudioFilePath, mkdir, writingMessage } from "../utils/file.js";
7
+ import { getOutputMultilingualFilePath, mkdir, writingMessage } from "../utils/file.js";
8
8
  import { translateSystemPrompt, translatePrompts } from "../utils/prompt.js";
9
9
  import { MulmoStudioContextMethods } from "../methods/mulmo_studio_context.js";
10
10
  const vanillaAgents = agents.default ?? agents;
@@ -14,7 +14,7 @@ const translateGraph = {
14
14
  context: {},
15
15
  defaultLang: {},
16
16
  outDirPath: {},
17
- outputStudioFilePath: {},
17
+ outputMultilingualFilePath: {},
18
18
  lang: {
19
19
  agent: "stringUpdateTextAgent",
20
20
  inputs: {
@@ -27,7 +27,7 @@ const translateGraph = {
27
27
  isResult: true,
28
28
  agent: "mergeObjectAgent",
29
29
  inputs: {
30
- items: [":context.studio", { multiLingual: ":beatsMap.mergeMultiLingualData" }],
30
+ items: [{ multiLingual: ":beatsMap.mergeMultiLingualData" }],
31
31
  },
32
32
  },
33
33
  beatsMap: {
@@ -52,7 +52,7 @@ const translateGraph = {
52
52
  },
53
53
  inputs: {
54
54
  index: ":__mapIndex",
55
- rows: ":context.studio.multiLingual",
55
+ rows: ":context.multiLingual",
56
56
  },
57
57
  },
58
58
  preprocessMultiLingual: {
@@ -167,8 +167,8 @@ const translateGraph = {
167
167
  // console: { before: true },
168
168
  agent: "fileWriteAgent",
169
169
  inputs: {
170
- file: ":outputStudioFilePath",
171
- text: ":mergeStudioResult.toJSON()",
170
+ file: ":outputMultilingualFilePath",
171
+ text: ":mergeStudioResult.multiLingual.toJSON()",
172
172
  },
173
173
  },
174
174
  },
@@ -213,7 +213,7 @@ export const translate = async (context, callbacks) => {
213
213
  MulmoStudioContextMethods.setSessionState(context, "multiLingual", true);
214
214
  const { studio, fileDirs } = context;
215
215
  const { outDirPath } = fileDirs;
216
- const outputStudioFilePath = getOutputStudioFilePath(outDirPath, studio.filename);
216
+ const outputMultilingualFilePath = getOutputMultilingualFilePath(outDirPath, studio.filename);
217
217
  mkdir(outDirPath);
218
218
  assert(!!process.env.OPENAI_API_KEY, "The OPENAI_API_KEY environment variable is missing or empty");
219
219
  const graph = new GraphAI(translateGraph, { ...vanillaAgents, fileWriteAgent, openAIAgent }, { agentFilters });
@@ -221,16 +221,16 @@ export const translate = async (context, callbacks) => {
221
221
  graph.injectValue("defaultLang", defaultLang);
222
222
  graph.injectValue("targetLangs", targetLangs);
223
223
  graph.injectValue("outDirPath", outDirPath);
224
- graph.injectValue("outputStudioFilePath", outputStudioFilePath);
224
+ graph.injectValue("outputMultilingualFilePath", outputMultilingualFilePath);
225
225
  if (callbacks) {
226
226
  callbacks.forEach((callback) => {
227
227
  graph.registerCallback(callback);
228
228
  });
229
229
  }
230
230
  const results = await graph.run();
231
- writingMessage(outputStudioFilePath);
231
+ writingMessage(outputMultilingualFilePath);
232
232
  if (results.mergeStudioResult) {
233
- context.studio = results.mergeStudioResult;
233
+ context.multiLingual = results.mergeStudioResult.multiLingual;
234
234
  }
235
235
  }
236
236
  finally {
@@ -1,6 +1,35 @@
1
- import { GraphAILogger } from "graphai";
1
+ import { assert } from "graphai";
2
2
  import { silent60secPath } from "../utils/file.js";
3
3
  import { FfmpegContextInit, FfmpegContextGenerateOutput, FfmpegContextInputFormattedAudio, ffmpegGetMediaDuration } from "../utils/ffmpeg_utils.js";
4
+ const getMovieDulation = async (beat) => {
5
+ if (beat.image?.type === "movie" && (beat.image.source.kind === "url" || beat.image.source.kind === "path")) {
6
+ const pathOrUrl = beat.image.source.kind === "url" ? beat.image.source.url : beat.image.source.path;
7
+ return await ffmpegGetMediaDuration(pathOrUrl);
8
+ }
9
+ return 0;
10
+ };
11
+ const getPadding = (context, beat, index) => {
12
+ if (beat.audioParams?.padding !== undefined) {
13
+ return beat.audioParams.padding;
14
+ }
15
+ if (index === context.studio.beats.length - 1) {
16
+ return 0;
17
+ }
18
+ const isClosingGap = index === context.studio.beats.length - 2;
19
+ return isClosingGap ? context.presentationStyle.audioParams.closingPadding : context.presentationStyle.audioParams.padding;
20
+ };
21
+ const getTotalPadding = (padding, movieDuration, audioDuration, duration, canSpillover = false) => {
22
+ if (movieDuration > 0) {
23
+ return padding + (movieDuration - audioDuration);
24
+ }
25
+ else if (duration && duration > audioDuration) {
26
+ return padding + (duration - audioDuration);
27
+ }
28
+ else if (canSpillover && duration && audioDuration > duration) {
29
+ return duration - audioDuration; // negative value to indicate that there is a spill over.
30
+ }
31
+ return padding;
32
+ };
4
33
  const combineAudioFilesAgent = async ({ namedInputs, }) => {
5
34
  const { context, combinedFileName } = namedInputs;
6
35
  const ffmpegContext = FfmpegContextInit();
@@ -8,64 +37,81 @@ const combineAudioFilesAgent = async ({ namedInputs, }) => {
8
37
  // We cannot reuse longSilentId. We need to explicitly split it for each beat.
9
38
  const silentIds = context.studio.beats.map((_, index) => `[ls_${index}]`);
10
39
  ffmpegContext.filterComplex.push(`${longSilentId}asplit=${silentIds.length}${silentIds.join("")}`);
11
- const inputIds = (await Promise.all(context.studio.beats.map(async (studioBeat, index) => {
40
+ // First, get the audio durations of all beats, taking advantage of multi-threading capability of ffmpeg.
41
+ const mediaDurations = await Promise.all(context.studio.beats.map(async (studioBeat, index) => {
12
42
  const beat = context.studio.script.beats[index];
13
- const isClosingGap = index === context.studio.beats.length - 2;
14
- const movieDuration = await (() => {
15
- if (beat.image?.type === "movie" && (beat.image.source.kind === "url" || beat.image.source.kind === "path")) {
16
- const pathOrUrl = beat.image.source.kind === "url" ? beat.image.source.url : beat.image.source.path;
17
- return ffmpegGetMediaDuration(pathOrUrl);
18
- }
19
- return 0;
20
- })();
43
+ const movieDuration = await getMovieDulation(beat);
44
+ const audioDuration = studioBeat.audioFile ? await ffmpegGetMediaDuration(studioBeat.audioFile) : 0;
45
+ return {
46
+ movieDuration,
47
+ audioDuration,
48
+ };
49
+ }));
50
+ const inputIds = [];
51
+ const beatDurations = [];
52
+ context.studio.beats.reduce((spillover, studioBeat, index) => {
53
+ const beat = context.studio.script.beats[index];
54
+ const { audioDuration, movieDuration } = mediaDurations[index];
55
+ const paddingId = `[padding_${index}]`;
56
+ const canSpillover = index < context.studio.beats.length - 1 && mediaDurations[index + 1].movieDuration + mediaDurations[index + 1].audioDuration === 0;
21
57
  if (studioBeat.audioFile) {
22
58
  const audioId = FfmpegContextInputFormattedAudio(ffmpegContext, studioBeat.audioFile);
23
- const padding = (() => {
24
- if (beat.audioParams?.padding !== undefined) {
25
- return beat.audioParams.padding;
26
- }
27
- if (index === context.studio.beats.length - 1) {
28
- return 0;
29
- }
30
- return isClosingGap ? context.presentationStyle.audioParams.closingPadding : context.presentationStyle.audioParams.padding;
31
- })();
32
- const audioDuration = await ffmpegGetMediaDuration(studioBeat.audioFile);
33
- const totalPadding = await (async () => {
34
- if (movieDuration > 0) {
35
- return padding + (movieDuration - audioDuration);
36
- }
37
- else if (beat.duration && beat.duration > audioDuration) {
38
- return padding + (beat.duration - audioDuration);
39
- }
40
- return padding;
41
- })();
42
- studioBeat.duration = audioDuration + totalPadding;
59
+ // padding is the amount of audio padding specified in the script.
60
+ const padding = getPadding(context, beat, index);
61
+ // totalPadding is the amount of audio padding to be added to the audio file.
62
+ const totalPadding = getTotalPadding(padding, movieDuration, audioDuration, beat.duration, canSpillover);
63
+ beatDurations.push(audioDuration + totalPadding);
43
64
  if (totalPadding > 0) {
44
65
  const silentId = silentIds.pop();
45
- ffmpegContext.filterComplex.push(`${silentId}atrim=start=0:end=${totalPadding}[padding_${index}]`);
46
- return [audioId, `[padding_${index}]`];
66
+ ffmpegContext.filterComplex.push(`${silentId}atrim=start=0:end=${totalPadding}${paddingId}`);
67
+ inputIds.push(audioId, paddingId);
47
68
  }
48
69
  else {
49
- return [audioId];
70
+ inputIds.push(audioId);
71
+ if (totalPadding < 0) {
72
+ return -totalPadding;
73
+ }
50
74
  }
51
75
  }
52
76
  else {
53
77
  // NOTE: We come here when the text is empty and no audio property is specified.
54
- studioBeat.duration = beat.duration ?? (movieDuration > 0 ? movieDuration : 1.0);
78
+ const beatDuration = (() => {
79
+ const duration = beat.duration ?? (movieDuration > 0 ? movieDuration : 1.0);
80
+ if (!canSpillover && duration < spillover) {
81
+ return spillover; // We need to consume the spillover here.
82
+ }
83
+ return duration;
84
+ })();
85
+ beatDurations.push(beatDuration);
86
+ if (beatDuration <= spillover) {
87
+ return spillover - beatDuration;
88
+ }
55
89
  const silentId = silentIds.pop();
56
- ffmpegContext.filterComplex.push(`${silentId}atrim=start=0:end=${studioBeat.duration}[silent_${index}]`);
57
- return [`[silent_${index}]`];
90
+ ffmpegContext.filterComplex.push(`${silentId}atrim=start=0:end=${beatDuration - spillover}${paddingId}`);
91
+ inputIds.push(paddingId);
58
92
  }
59
- }))).flat();
60
- silentIds.forEach((silentId) => {
61
- GraphAILogger.log(`Using extra silentId: ${silentId}`);
62
- ffmpegContext.filterComplex.push(`${silentId}atrim=start=0:end=${0.01}[silent_extra]`);
63
- inputIds.push("[silent_extra]");
93
+ return 0;
94
+ }, 0);
95
+ assert(beatDurations.length === context.studio.beats.length, "beatDurations.length !== studio.beats.length");
96
+ // We need to "consume" extra silentIds.
97
+ silentIds.forEach((silentId, index) => {
98
+ const extraId = `[silent_extra_${index}]`;
99
+ ffmpegContext.filterComplex.push(`${silentId}atrim=start=0:end=${0.01}${extraId}`);
100
+ inputIds.push(extraId);
64
101
  });
102
+ // Finally, combine all audio files.
65
103
  ffmpegContext.filterComplex.push(`${inputIds.join("")}concat=n=${inputIds.length}:v=0:a=1[aout]`);
66
104
  await FfmpegContextGenerateOutput(ffmpegContext, combinedFileName, ["-map", "[aout]"]);
105
+ const result = {
106
+ studio: {
107
+ ...context.studio,
108
+ beats: context.studio.beats.map((studioBeat, index) => ({ ...studioBeat, duration: beatDurations[index] })),
109
+ },
110
+ };
111
+ // context.studio = result.studio; // TODO: removing this breaks test/test_movie.ts
67
112
  return {
68
- studio: context.studio,
113
+ ...context,
114
+ ...result,
69
115
  };
70
116
  };
71
117
  const combineAudioFilesAgentInfo = {
@@ -6,10 +6,5 @@ export const handler = async (argv) => {
6
6
  process.exit(1);
7
7
  }
8
8
  await runTranslateIfNeeded(context, argv);
9
- await audio(context);
10
- await images(context);
11
- if (context.caption) {
12
- await captions(context);
13
- }
14
- await movie(context);
9
+ await audio(context).then(images).then(captions).then(movie);
15
10
  };
@@ -1,4 +1,4 @@
1
- import type { MulmoScript, MulmoStudioContext, MulmoPresentationStyle } from "../types/type.js";
1
+ import type { MulmoScript, MulmoStudioContext, MulmoPresentationStyle, MulmoStudioMultiLingual } from "../types/type.js";
2
2
  import type { CliArgs } from "../types/cli_types.js";
3
3
  export declare const setGraphAILogger: (verbose: boolean | undefined, logValues?: Record<string, unknown>) => void;
4
4
  export interface FileObject {
@@ -11,6 +11,7 @@ export interface FileObject {
11
11
  isHttpPath: boolean;
12
12
  fileOrUrl: string;
13
13
  outputStudioFilePath: string;
14
+ outputMultilingualFilePath: string;
14
15
  presentationStylePath: string | undefined;
15
16
  fileName: string;
16
17
  }
@@ -23,6 +24,7 @@ export declare const getFileObject: (args: {
23
24
  file: string;
24
25
  }) => FileObject;
25
26
  export declare const fetchScript: (isHttpPath: boolean, mulmoFilePath: string, fileOrUrl: string) => Promise<MulmoScript | null>;
27
+ export declare const getMultiLingual: (multilingualFilePath: string, beatsLength: number) => MulmoStudioMultiLingual;
26
28
  export declare const getPresentationStyle: (presentationStylePath: string | undefined) => MulmoPresentationStyle | null;
27
29
  type InitOptions = {
28
30
  b?: string;
@@ -2,12 +2,12 @@ import { GraphAILogger } from "graphai";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
4
  import clipboardy from "clipboardy";
5
- import { getBaseDirPath, getFullPath, readMulmoScriptFile, fetchMulmoScriptFile, getOutputStudioFilePath, resolveDirPath, mkdir } from "../utils/file.js";
5
+ import { getBaseDirPath, getFullPath, readMulmoScriptFile, fetchMulmoScriptFile, getOutputStudioFilePath, resolveDirPath, mkdir, getOutputMultilingualFilePath, } from "../utils/file.js";
6
6
  import { isHttp } from "../utils/utils.js";
7
7
  import { createOrUpdateStudioData } from "../utils/preprocess.js";
8
8
  import { outDirName, imageDirName, audioDirName } from "../utils/const.js";
9
9
  import { translate } from "../actions/translate.js";
10
- import { mulmoPresentationStyleSchema } from "../types/schema.js";
10
+ import { mulmoPresentationStyleSchema, mulmoStudioMultiLingualSchema } from "../types/schema.js";
11
11
  export const setGraphAILogger = (verbose, logValues) => {
12
12
  if (verbose) {
13
13
  if (logValues) {
@@ -48,6 +48,7 @@ export const getFileObject = (args) => {
48
48
  const imageDirPath = getFullPath(outDirPath, imagedir ?? imageDirName);
49
49
  const audioDirPath = getFullPath(outDirPath, audiodir ?? audioDirName);
50
50
  const outputStudioFilePath = getOutputStudioFilePath(outDirPath, fileName);
51
+ const outputMultilingualFilePath = getOutputMultilingualFilePath(outDirPath, fileName);
51
52
  const presentationStylePath = presentationStyle ? getFullPath(baseDirPath, presentationStyle) : undefined;
52
53
  return {
53
54
  baseDirPath,
@@ -59,6 +60,7 @@ export const getFileObject = (args) => {
59
60
  isHttpPath,
60
61
  fileOrUrl,
61
62
  outputStudioFilePath,
63
+ outputMultilingualFilePath,
62
64
  presentationStylePath,
63
65
  fileName,
64
66
  };
@@ -78,6 +80,13 @@ export const fetchScript = async (isHttpPath, mulmoFilePath, fileOrUrl) => {
78
80
  }
79
81
  return readMulmoScriptFile(mulmoFilePath, "ERROR: File does not exist " + mulmoFilePath)?.mulmoData ?? null;
80
82
  };
83
+ export const getMultiLingual = (multilingualFilePath, beatsLength) => {
84
+ if (fs.existsSync(multilingualFilePath)) {
85
+ const jsonData = readMulmoScriptFile(multilingualFilePath, "ERROR: File does not exist " + multilingualFilePath)?.mulmoData ?? null;
86
+ return mulmoStudioMultiLingualSchema.parse(jsonData);
87
+ }
88
+ return [...Array(beatsLength)].map(() => ({ multiLingualTexts: {} }));
89
+ };
81
90
  export const getPresentationStyle = (presentationStylePath) => {
82
91
  if (presentationStylePath) {
83
92
  if (!fs.existsSync(presentationStylePath)) {
@@ -97,7 +106,7 @@ export const initializeContext = async (argv) => {
97
106
  presentationStyle: argv.p,
98
107
  file: argv.file ?? "",
99
108
  });
100
- const { fileName, isHttpPath, fileOrUrl, mulmoFilePath, outputStudioFilePath, presentationStylePath } = files;
109
+ const { fileName, isHttpPath, fileOrUrl, mulmoFilePath, outputStudioFilePath, presentationStylePath, outputMultilingualFilePath } = files;
101
110
  setGraphAILogger(argv.v, {
102
111
  files,
103
112
  });
@@ -111,6 +120,7 @@ export const initializeContext = async (argv) => {
111
120
  try {
112
121
  // validate mulmoStudioSchema. skip if __test_invalid__ is true
113
122
  const studio = createOrUpdateStudioData(mulmoScript, currentStudio?.mulmoData, fileName);
123
+ const multiLingual = getMultiLingual(outputMultilingualFilePath, studio.beats.length);
114
124
  return {
115
125
  studio,
116
126
  fileDirs: files,
@@ -136,6 +146,7 @@ export const initializeContext = async (argv) => {
136
146
  },
137
147
  },
138
148
  presentationStyle: presentationStyle ?? studio.script,
149
+ multiLingual,
139
150
  };
140
151
  }
141
152
  catch (error) {
@@ -17,6 +17,11 @@ export declare const removeSessionProgressCallback: (cb: SessionProgressCallback
17
17
  export declare const MulmoStudioContextMethods: {
18
18
  resolveAssetPath(context: MulmoStudioContext, relativePath: string): string;
19
19
  getAudioDirPath(context: MulmoStudioContext): string;
20
+ getImageDirPath(context: MulmoStudioContext): string;
21
+ getImageProjectDirPath(context: MulmoStudioContext): string;
22
+ getOutDirPath(context: MulmoStudioContext): string;
23
+ getFileName(context: MulmoStudioContext): string;
24
+ getCaption(context: MulmoStudioContext): string | undefined;
20
25
  setSessionState(context: MulmoStudioContext, sessionType: SessionType, value: boolean): void;
21
26
  setBeatSessionState(context: MulmoStudioContext, sessionType: BeatSessionType, index: number, value: boolean): void;
22
27
  };
@@ -30,6 +30,22 @@ export const MulmoStudioContextMethods = {
30
30
  getAudioDirPath(context) {
31
31
  return context.fileDirs.audioDirPath;
32
32
  },
33
+ getImageDirPath(context) {
34
+ return context.fileDirs.imageDirPath;
35
+ },
36
+ getImageProjectDirPath(context) {
37
+ const imageDirPath = MulmoStudioContextMethods.getImageDirPath(context);
38
+ return `${imageDirPath}/${context.studio.filename}`;
39
+ },
40
+ getOutDirPath(context) {
41
+ return context.fileDirs.outDirPath;
42
+ },
43
+ getFileName(context) {
44
+ return context.studio.filename;
45
+ },
46
+ getCaption(context) {
47
+ return context.caption;
48
+ },
33
49
  setSessionState(context, sessionType, value) {
34
50
  context.sessionState.inSession[sessionType] = value;
35
51
  notifyStateChange(context, sessionType);
@@ -1,3 +1,2 @@
1
1
  export * from "./type.js";
2
2
  export * from "./schema.js";
3
- export * from "./cli_types.js";
@@ -1,3 +1,2 @@
1
1
  export * from "./type.js";
2
2
  export * from "./schema.js";
3
- export * from "./cli_types.js";
@@ -879,6 +879,76 @@ export declare const mulmoAudioAssetSchema: z.ZodUnion<[z.ZodObject<{
879
879
  type: "midi";
880
880
  source: string;
881
881
  }>]>;
882
+ export declare const mulmoImageParamsImagesSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
883
+ type: z.ZodLiteral<"image">;
884
+ source: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
885
+ kind: z.ZodLiteral<"url">;
886
+ url: z.ZodString;
887
+ }, "strict", z.ZodTypeAny, {
888
+ url: string;
889
+ kind: "url";
890
+ }, {
891
+ url: string;
892
+ kind: "url";
893
+ }>, z.ZodObject<{
894
+ kind: z.ZodLiteral<"base64">;
895
+ data: z.ZodString;
896
+ }, "strict", z.ZodTypeAny, {
897
+ kind: "base64";
898
+ data: string;
899
+ }, {
900
+ kind: "base64";
901
+ data: string;
902
+ }>, z.ZodObject<{
903
+ kind: z.ZodLiteral<"text">;
904
+ text: z.ZodString;
905
+ }, "strict", z.ZodTypeAny, {
906
+ text: string;
907
+ kind: "text";
908
+ }, {
909
+ text: string;
910
+ kind: "text";
911
+ }>, z.ZodObject<{
912
+ kind: z.ZodLiteral<"path">;
913
+ path: z.ZodString;
914
+ }, "strict", z.ZodTypeAny, {
915
+ path: string;
916
+ kind: "path";
917
+ }, {
918
+ path: string;
919
+ kind: "path";
920
+ }>]>;
921
+ }, "strict", z.ZodTypeAny, {
922
+ type: "image";
923
+ source: {
924
+ url: string;
925
+ kind: "url";
926
+ } | {
927
+ kind: "base64";
928
+ data: string;
929
+ } | {
930
+ text: string;
931
+ kind: "text";
932
+ } | {
933
+ path: string;
934
+ kind: "path";
935
+ };
936
+ }, {
937
+ type: "image";
938
+ source: {
939
+ url: string;
940
+ kind: "url";
941
+ } | {
942
+ kind: "base64";
943
+ data: string;
944
+ } | {
945
+ text: string;
946
+ kind: "text";
947
+ } | {
948
+ path: string;
949
+ kind: "path";
950
+ };
951
+ }>>;
882
952
  export declare const mulmoImageParamsSchema: z.ZodObject<{
883
953
  model: z.ZodOptional<z.ZodString>;
884
954
  style: z.ZodOptional<z.ZodString>;
@@ -6470,43 +6540,6 @@ export declare const mulmoStudioSchema: z.ZodObject<{
6470
6540
  movieFile?: string | undefined;
6471
6541
  captionFile?: string | undefined;
6472
6542
  }>, "many">;
6473
- multiLingual: z.ZodArray<z.ZodObject<{
6474
- multiLingualTexts: z.ZodRecord<z.ZodString, z.ZodObject<{
6475
- text: z.ZodString;
6476
- lang: z.ZodString;
6477
- texts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
6478
- ttsTexts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
6479
- duration: z.ZodOptional<z.ZodNumber>;
6480
- }, "strict", z.ZodTypeAny, {
6481
- text: string;
6482
- lang: string;
6483
- texts?: string[] | undefined;
6484
- ttsTexts?: string[] | undefined;
6485
- duration?: number | undefined;
6486
- }, {
6487
- text: string;
6488
- lang: string;
6489
- texts?: string[] | undefined;
6490
- ttsTexts?: string[] | undefined;
6491
- duration?: number | undefined;
6492
- }>>;
6493
- }, "strip", z.ZodTypeAny, {
6494
- multiLingualTexts: Record<string, {
6495
- text: string;
6496
- lang: string;
6497
- texts?: string[] | undefined;
6498
- ttsTexts?: string[] | undefined;
6499
- duration?: number | undefined;
6500
- }>;
6501
- }, {
6502
- multiLingualTexts: Record<string, {
6503
- text: string;
6504
- lang: string;
6505
- texts?: string[] | undefined;
6506
- ttsTexts?: string[] | undefined;
6507
- duration?: number | undefined;
6508
- }>;
6509
- }>, "many">;
6510
6543
  }, "strict", z.ZodTypeAny, {
6511
6544
  beats: {
6512
6545
  duration?: number | undefined;
@@ -6516,15 +6549,6 @@ export declare const mulmoStudioSchema: z.ZodObject<{
6516
6549
  movieFile?: string | undefined;
6517
6550
  captionFile?: string | undefined;
6518
6551
  }[];
6519
- multiLingual: {
6520
- multiLingualTexts: Record<string, {
6521
- text: string;
6522
- lang: string;
6523
- texts?: string[] | undefined;
6524
- ttsTexts?: string[] | undefined;
6525
- duration?: number | undefined;
6526
- }>;
6527
- }[];
6528
6552
  script: {
6529
6553
  audioParams: {
6530
6554
  padding: number;
@@ -6782,15 +6806,6 @@ export declare const mulmoStudioSchema: z.ZodObject<{
6782
6806
  movieFile?: string | undefined;
6783
6807
  captionFile?: string | undefined;
6784
6808
  }[];
6785
- multiLingual: {
6786
- multiLingualTexts: Record<string, {
6787
- text: string;
6788
- lang: string;
6789
- texts?: string[] | undefined;
6790
- ttsTexts?: string[] | undefined;
6791
- duration?: number | undefined;
6792
- }>;
6793
- }[];
6794
6809
  script: {
6795
6810
  $mulmocast: {
6796
6811
  version: "1.0";
@@ -140,12 +140,13 @@ const mulmoMidiMediaSchema = z
140
140
  .strict();
141
141
  export const mulmoAudioAssetSchema = z.union([mulmoAudioMediaSchema, mulmoMidiMediaSchema]);
142
142
  const imageIdSchema = z.string();
143
+ export const mulmoImageParamsImagesSchema = z.record(imageIdSchema, mulmoImageMediaSchema);
143
144
  export const mulmoImageParamsSchema = z
144
145
  .object({
145
146
  model: z.string().optional(), // default: provider specific
146
147
  style: z.string().optional(), // optional image style
147
148
  moderation: z.string().optional(), // optional image style
148
- images: z.record(imageIdSchema, mulmoImageMediaSchema).optional(),
149
+ images: mulmoImageParamsImagesSchema.optional(),
149
150
  })
150
151
  .strict();
151
152
  export const textSlideParamsSchema = z
@@ -301,7 +302,6 @@ export const mulmoStudioSchema = z
301
302
  script: mulmoScriptSchema,
302
303
  filename: z.string(),
303
304
  beats: z.array(mulmoStudioBeatSchema).min(1),
304
- multiLingual: mulmoStudioMultiLingualSchema,
305
305
  })
306
306
  .strict();
307
307
  export const mulmoScriptTemplateSchema = z
@@ -1,4 +1,4 @@
1
- import { langSchema, localizedTextSchema, mulmoBeatSchema, mulmoScriptSchema, mulmoStudioSchema, mulmoStudioBeatSchema, mulmoStoryboardSchema, mulmoStoryboardSceneSchema, mulmoStudioMultiLingualSchema, mulmoStudioMultiLingualDataSchema, speakerDictionarySchema, mulmoImageParamsSchema, mulmoMovieParamsSchema, mulmoSpeechParamsSchema, textSlideParamsSchema, speechOptionsSchema, speakerDataSchema, mulmoCanvasDimensionSchema, mulmoScriptTemplateSchema, mulmoScriptTemplateFileSchema, text2ImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoPresentationStyleSchema, multiLingualTextsSchema, mulmoMermaidMediaSchema, mulmoTextSlideMediaSchema, mulmoMarkdownMediaSchema, mulmoImageMediaSchema, mulmoChartMediaSchema, mediaSourceSchema, mulmoSessionStateSchema } from "./schema.js";
1
+ import { langSchema, localizedTextSchema, mulmoBeatSchema, mulmoScriptSchema, mulmoStudioSchema, mulmoStudioBeatSchema, mulmoStoryboardSchema, mulmoStoryboardSceneSchema, mulmoStudioMultiLingualSchema, mulmoStudioMultiLingualDataSchema, speakerDictionarySchema, mulmoImageParamsSchema, mulmoImageParamsImagesSchema, mulmoMovieParamsSchema, mulmoSpeechParamsSchema, textSlideParamsSchema, speechOptionsSchema, speakerDataSchema, mulmoCanvasDimensionSchema, mulmoScriptTemplateSchema, mulmoScriptTemplateFileSchema, text2ImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoPresentationStyleSchema, multiLingualTextsSchema, mulmoMermaidMediaSchema, mulmoTextSlideMediaSchema, mulmoMarkdownMediaSchema, mulmoImageMediaSchema, mulmoChartMediaSchema, mediaSourceSchema, mulmoSessionStateSchema } from "./schema.js";
2
2
  import { pdf_modes, pdf_sizes, storyToScriptGenerateMode } from "../utils/const.js";
3
3
  import { LLM } from "../utils/utils.js";
4
4
  import { z } from "zod";
@@ -9,6 +9,7 @@ export type MulmoSpeechParams = z.infer<typeof mulmoSpeechParamsSchema>;
9
9
  export type SpeechOptions = z.infer<typeof speechOptionsSchema>;
10
10
  export type SpeakerData = z.infer<typeof speakerDataSchema>;
11
11
  export type MulmoImageParams = z.infer<typeof mulmoImageParamsSchema>;
12
+ export type MulmoImageParamsImages = z.infer<typeof mulmoImageParamsImagesSchema>;
12
13
  export type TextSlideParams = z.infer<typeof textSlideParamsSchema>;
13
14
  export type Text2ImageProvider = z.infer<typeof text2ImageProviderSchema>;
14
15
  export type Text2MovieProvider = z.infer<typeof text2MovieProviderSchema>;
@@ -51,6 +52,7 @@ export type MulmoStudioContext = {
51
52
  caption?: string;
52
53
  sessionState: MulmoSessionState;
53
54
  presentationStyle: MulmoPresentationStyle;
55
+ multiLingual: MulmoStudioMultiLingual;
54
56
  };
55
57
  export type ScriptingParams = {
56
58
  urls: string[];
@@ -1,4 +1,4 @@
1
- import { MulmoScript, MulmoScriptTemplateFile } from "../types/index.js";
1
+ import type { MulmoScript, MulmoScriptTemplateFile, MulmoStudioContext } from "../types/index.js";
2
2
  import { PDFMode } from "../types/index.js";
3
3
  import { ZodSchema } from "zod";
4
4
  export declare const updateNpmRoot: (_npmRoot: string) => void;
@@ -18,10 +18,15 @@ export declare const fetchMulmoScriptFile: (url: string) => Promise<{
18
18
  status: string | number;
19
19
  }>;
20
20
  export declare const getOutputStudioFilePath: (outDirPath: string, fileName: string) => string;
21
+ export declare const getOutputMultilingualFilePath: (outDirPath: string, fileName: string) => string;
21
22
  export declare const resolveDirPath: (dirPath: string, studioFileName: string) => string;
22
23
  export declare const getAudioFilePath: (audioDirPath: string, dirName: string, fileName: string, lang?: string) => string;
23
24
  export declare const getAudioArtifactFilePath: (outDirPath: string, fileName: string) => string;
24
25
  export declare const getOutputVideoFilePath: (outDirPath: string, fileName: string, lang?: string, caption?: string) => string;
26
+ export declare const imageSuffix = "p";
27
+ export declare const getBeatPngImagePath: (context: MulmoStudioContext, index: number) => string;
28
+ export declare const getBeatMoviePath: (context: MulmoStudioContext, index: number) => string;
29
+ export declare const getReferenceImagePath: (context: MulmoStudioContext, key: string, extension: string) => string;
25
30
  export declare const getOutputPdfFilePath: (outDirPath: string, fileName: string, pdfMode: PDFMode, lang?: string) => string;
26
31
  export declare const getTemplateFilePath: (templateName: string) => string;
27
32
  export declare const mkdir: (dirPath: string) => void;
package/lib/utils/file.js CHANGED
@@ -3,7 +3,7 @@ import path from "path";
3
3
  import { parse as yamlParse } from "yaml";
4
4
  import { fileURLToPath } from "url";
5
5
  import { GraphAILogger } from "graphai";
6
- import { MulmoScriptTemplateMethods } from "../methods/mulmo_script_template.js";
6
+ import { MulmoScriptTemplateMethods, MulmoStudioContextMethods } from "../methods/index.js";
7
7
  import { mulmoScriptTemplateSchema } from "../types/schema.js";
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = path.dirname(__filename);
@@ -56,9 +56,13 @@ export const fetchMulmoScriptFile = async (url) => {
56
56
  export const getOutputStudioFilePath = (outDirPath, fileName) => {
57
57
  return path.resolve(outDirPath, fileName + "_studio.json");
58
58
  };
59
+ export const getOutputMultilingualFilePath = (outDirPath, fileName) => {
60
+ return path.resolve(outDirPath, fileName + "_lang.json");
61
+ };
59
62
  export const resolveDirPath = (dirPath, studioFileName) => {
60
63
  return path.resolve(dirPath, studioFileName);
61
64
  };
65
+ // audio
62
66
  export const getAudioFilePath = (audioDirPath, dirName, fileName, lang) => {
63
67
  if (lang) {
64
68
  return path.resolve(audioDirPath, dirName, `${fileName}_${lang}.mp3`);
@@ -73,6 +77,21 @@ export const getOutputVideoFilePath = (outDirPath, fileName, lang, caption) => {
73
77
  const suffix2 = caption ? `__${caption}` : "";
74
78
  return path.resolve(outDirPath, `${fileName}${suffix}${suffix2}.mp4`);
75
79
  };
80
+ // image
81
+ export const imageSuffix = "p";
82
+ export const getBeatPngImagePath = (context, index) => {
83
+ const imageProjectDirPath = MulmoStudioContextMethods.getImageProjectDirPath(context);
84
+ return `${imageProjectDirPath}/${index}${imageSuffix}.png`;
85
+ };
86
+ export const getBeatMoviePath = (context, index) => {
87
+ const imageProjectDirPath = MulmoStudioContextMethods.getImageProjectDirPath(context);
88
+ return `${imageProjectDirPath}/${index}.mov`;
89
+ };
90
+ export const getReferenceImagePath = (context, key, extension) => {
91
+ const imageProjectDirPath = MulmoStudioContextMethods.getImageProjectDirPath(context);
92
+ return `${imageProjectDirPath}/${key}.${extension}`;
93
+ };
94
+ // pdf
76
95
  export const getOutputPdfFilePath = (outDirPath, fileName, pdfMode, lang) => {
77
96
  if (lang) {
78
97
  return path.resolve(outDirPath, `${fileName}_${pdfMode}_${lang}.pdf`);
@@ -8,15 +8,6 @@ export declare const createOrUpdateStudioData: (_mulmoScript: MulmoScript, curre
8
8
  movieFile?: string | undefined;
9
9
  captionFile?: string | undefined;
10
10
  }[];
11
- multiLingual: {
12
- multiLingualTexts: Record<string, {
13
- text: string;
14
- lang: string;
15
- texts?: string[] | undefined;
16
- ttsTexts?: string[] | undefined;
17
- duration?: number | undefined;
18
- }>;
19
- }[];
20
11
  script: {
21
12
  audioParams: {
22
13
  padding: number;
@@ -1,7 +1,8 @@
1
1
  import { GraphAILogger } from "graphai";
2
- import { mulmoScriptSchema, mulmoBeatSchema, mulmoStudioSchema } from "../types/index.js";
2
+ import { mulmoScriptSchema, mulmoStudioSchema } from "../types/index.js";
3
3
  const rebuildStudio = (currentStudio, mulmoScript, fileName) => {
4
- const parsed = mulmoStudioSchema.safeParse(currentStudio);
4
+ const isTest = process.env.NODE_ENV === "test";
5
+ const parsed = isTest && currentStudio ? { data: mulmoStudioSchema.parse(currentStudio), success: true, error: null } : mulmoStudioSchema.safeParse(currentStudio);
5
6
  if (parsed.success) {
6
7
  return parsed.data;
7
8
  }
@@ -13,7 +14,6 @@ const rebuildStudio = (currentStudio, mulmoScript, fileName) => {
13
14
  script: mulmoScript,
14
15
  filename: fileName,
15
16
  beats: [...Array(mulmoScript.beats.length)].map(() => ({})),
16
- multiLingual: [...Array(mulmoScript.beats.length)].map(() => ({ multiLingualTexts: {} })),
17
17
  });
18
18
  };
19
19
  const mulmoCredit = (speaker) => {
@@ -46,12 +46,5 @@ export const createOrUpdateStudioData = (_mulmoScript, currentStudio, fileName)
46
46
  }
47
47
  studio.script = mulmoScriptSchema.parse(mulmoScript); // update the script
48
48
  studio.beats = studio.script.beats.map((_, index) => studio.beats[index] ?? {});
49
- mulmoScript.beats.forEach((beat, index) => {
50
- // Filling the default values
51
- studio.script.beats[index] = mulmoBeatSchema.parse(beat);
52
- if (!studio.multiLingual[index]) {
53
- studio.multiLingual[index] = { multiLingualTexts: {} };
54
- }
55
- });
56
49
  return studio;
57
50
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mulmocast",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -27,7 +27,7 @@
27
27
  "preprocess": "npx tsx ./src/cli/bin.ts preprocess",
28
28
  "pdf": "npx tsx ./src/cli/bin.ts pdf",
29
29
  "test": "rm -f scratchpad/test*.* && npx tsx ./src/audio.ts scripts/test/test.json && npx tsx ./src/images.ts scripts/test/test.json && npx tsx ./src/movie.ts scripts/test/test.json",
30
- "ci_test": "tsx --test ./test/*/test_*.ts",
30
+ "ci_test": "NODE_ENV=test tsx --test ./test/*/test_*.ts",
31
31
  "lint": "eslint src test",
32
32
  "build": "tsc",
33
33
  "build_test": "tsc && git checkout -- lib/*",
@@ -50,7 +50,7 @@
50
50
  "dependencies": {
51
51
  "@google-cloud/text-to-speech": "^6.1.0",
52
52
  "@graphai/anthropic_agent": "^2.0.2",
53
- "@graphai/browserless_agent": "^2.0.0",
53
+ "@graphai/browserless_agent": "^2.0.1",
54
54
  "@graphai/gemini_agent": "^2.0.0",
55
55
  "@graphai/groq_agent": "^2.0.0",
56
56
  "@graphai/input_agents": "^1.0.1",
@@ -64,14 +64,14 @@
64
64
  "dotenv": "^16.4.7",
65
65
  "fluent-ffmpeg": "^2.1.3",
66
66
  "google-auth-library": "^9.15.1",
67
- "graphai": "^2.0.6",
67
+ "graphai": "^2.0.8",
68
68
  "inquirer": "^12.6.1",
69
69
  "marked": "^15.0.12",
70
70
  "ora": "^8.2.0",
71
- "puppeteer": "^24.10.0",
71
+ "puppeteer": "^24.10.2",
72
72
  "yaml": "^2.8.0",
73
73
  "yargs": "^17.7.2",
74
- "zod": "^3.25.51",
74
+ "zod": "^3.25.67",
75
75
  "zod-to-json-schema": "^3.24.5"
76
76
  },
77
77
  "devDependencies": {
@@ -82,14 +82,14 @@
82
82
  "@types/yargs": "^17.0.33",
83
83
  "eslint": "^9.29.0",
84
84
  "eslint-config-prettier": "^10.1.5",
85
- "eslint-plugin-prettier": "^5.4.1",
85
+ "eslint-plugin-prettier": "^5.5.0",
86
86
  "prettier": "^3.3.3",
87
87
  "ts-node": "^10.9.2",
88
88
  "tsx": "^4.20.3",
89
89
  "typescript": "^5.7.3",
90
- "typescript-eslint": "^8.34.0"
90
+ "typescript-eslint": "^8.34.1"
91
91
  },
92
92
  "engines": {
93
- "node": ">=16.0.0"
93
+ "node": ">=18.0.0"
94
94
  }
95
95
  }