mulmocast 0.0.16 → 0.0.17
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/audio.d.ts +1 -1
- package/lib/actions/audio.js +8 -5
- package/lib/actions/captions.d.ts +1 -1
- package/lib/actions/captions.js +16 -13
- package/lib/actions/images.d.ts +2 -3
- package/lib/actions/images.js +25 -19
- package/lib/actions/pdf.js +1 -2
- package/lib/actions/translate.js +10 -10
- package/lib/agents/combine_audio_files_agent.js +88 -42
- package/lib/cli/commands/movie/handler.js +1 -6
- package/lib/cli/helpers.d.ts +3 -1
- package/lib/cli/helpers.js +14 -3
- package/lib/methods/mulmo_studio_context.d.ts +3 -0
- package/lib/methods/mulmo_studio_context.js +10 -0
- package/lib/types/index.d.ts +0 -1
- package/lib/types/index.js +0 -1
- package/lib/types/schema.d.ts +70 -55
- package/lib/types/schema.js +2 -2
- package/lib/types/type.d.ts +3 -1
- package/lib/utils/file.d.ts +6 -1
- package/lib/utils/file.js +20 -1
- package/lib/utils/preprocess.d.ts +0 -9
- package/lib/utils/preprocess.js +3 -10
- package/package.json +8 -8
package/lib/actions/audio.d.ts
CHANGED
|
@@ -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<
|
|
7
|
+
export declare const audio: (context: MulmoStudioContext, callbacks?: CallbackFunction[]) => Promise<MulmoStudioContext>;
|
package/lib/actions/audio.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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<
|
|
3
|
+
export declare const captions: (context: MulmoStudioContext, callbacks?: CallbackFunction[]) => Promise<MulmoStudioContext>;
|
package/lib/actions/captions.js
CHANGED
|
@@ -30,7 +30,7 @@ const graph_data = {
|
|
|
30
30
|
const imagePath = `${imageDirPath}/${context.studio.filename}/${index}_caption.png`;
|
|
31
31
|
const template = getHTMLFile("caption");
|
|
32
32
|
const text = (() => {
|
|
33
|
-
const multiLingual = context.
|
|
33
|
+
const multiLingual = context.multiLingual;
|
|
34
34
|
if (caption && multiLingual) {
|
|
35
35
|
return multiLingual[index].multiLingualTexts[caption].text;
|
|
36
36
|
}
|
|
@@ -63,18 +63,21 @@ const graph_data = {
|
|
|
63
63
|
},
|
|
64
64
|
};
|
|
65
65
|
export const captions = async (context, callbacks) => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
callbacks
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
if (context.caption) {
|
|
67
|
+
try {
|
|
68
|
+
MulmoStudioContextMethods.setSessionState(context, "caption", true);
|
|
69
|
+
const graph = new GraphAI(graph_data, { ...vanillaAgents });
|
|
70
|
+
graph.injectValue("context", context);
|
|
71
|
+
if (callbacks) {
|
|
72
|
+
callbacks.forEach((callback) => {
|
|
73
|
+
graph.registerCallback(callback);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
await graph.run();
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
MulmoStudioContextMethods.setSessionState(context, "caption", false);
|
|
74
80
|
}
|
|
75
|
-
await graph.run();
|
|
76
|
-
}
|
|
77
|
-
finally {
|
|
78
|
-
MulmoStudioContextMethods.setSessionState(context, "caption", false);
|
|
79
81
|
}
|
|
82
|
+
return context;
|
|
80
83
|
};
|
package/lib/actions/images.d.ts
CHANGED
|
@@ -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
|
|
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>;
|
package/lib/actions/images.js
CHANGED
|
@@ -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,
|
|
25
|
+
const { context, beat, index, imageAgentInfo, imageRefs } = namedInputs;
|
|
26
26
|
const imageParams = { ...imageAgentInfo.imageParams, ...beat.imageParams };
|
|
27
|
-
const imagePath =
|
|
27
|
+
const imagePath = getBeatPngImagePath(context, index);
|
|
28
28
|
const returnValue = {
|
|
29
29
|
imageParams,
|
|
30
|
-
movieFile: beat.moviePrompt ?
|
|
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 {
|
|
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
|
-
|
|
266
|
-
|
|
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 =
|
|
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
|
-
|
|
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) => {
|
package/lib/actions/pdf.js
CHANGED
|
@@ -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);
|
package/lib/actions/translate.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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: [
|
|
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.
|
|
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: ":
|
|
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
|
|
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("
|
|
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(
|
|
231
|
+
writingMessage(outputMultilingualFilePath);
|
|
232
232
|
if (results.mergeStudioResult) {
|
|
233
|
-
context.
|
|
233
|
+
context.multiLingual = results.mergeStudioResult.multiLingual;
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
finally {
|
|
@@ -1,6 +1,35 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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}
|
|
46
|
-
|
|
66
|
+
ffmpegContext.filterComplex.push(`${silentId}atrim=start=0:end=${totalPadding}${paddingId}`);
|
|
67
|
+
inputIds.push(audioId, paddingId);
|
|
47
68
|
}
|
|
48
69
|
else {
|
|
49
|
-
|
|
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
|
-
|
|
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=${
|
|
57
|
-
|
|
90
|
+
ffmpegContext.filterComplex.push(`${silentId}atrim=start=0:end=${beatDuration - spillover}${paddingId}`);
|
|
91
|
+
inputIds.push(paddingId);
|
|
58
92
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
};
|
package/lib/cli/helpers.d.ts
CHANGED
|
@@ -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;
|
package/lib/cli/helpers.js
CHANGED
|
@@ -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
|
});
|
|
@@ -106,6 +115,7 @@ export const initializeContext = async (argv) => {
|
|
|
106
115
|
return null;
|
|
107
116
|
}
|
|
108
117
|
const presentationStyle = getPresentationStyle(presentationStylePath);
|
|
118
|
+
const multiLingual = getMultiLingual(outputMultilingualFilePath, mulmoScript.beats.length);
|
|
109
119
|
// Create or update MulmoStudio file with MulmoScript
|
|
110
120
|
const currentStudio = readMulmoScriptFile(outputStudioFilePath);
|
|
111
121
|
try {
|
|
@@ -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,9 @@ 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;
|
|
20
23
|
setSessionState(context: MulmoStudioContext, sessionType: SessionType, value: boolean): void;
|
|
21
24
|
setBeatSessionState(context: MulmoStudioContext, sessionType: BeatSessionType, index: number, value: boolean): void;
|
|
22
25
|
};
|
|
@@ -30,6 +30,16 @@ 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
|
+
},
|
|
33
43
|
setSessionState(context, sessionType, value) {
|
|
34
44
|
context.sessionState.inSession[sessionType] = value;
|
|
35
45
|
notifyStateChange(context, sessionType);
|
package/lib/types/index.d.ts
CHANGED
package/lib/types/index.js
CHANGED
package/lib/types/schema.d.ts
CHANGED
|
@@ -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";
|
package/lib/types/schema.js
CHANGED
|
@@ -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:
|
|
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
|
package/lib/types/type.d.ts
CHANGED
|
@@ -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[];
|
package/lib/utils/file.d.ts
CHANGED
|
@@ -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/
|
|
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;
|
package/lib/utils/preprocess.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { GraphAILogger } from "graphai";
|
|
2
|
-
import { mulmoScriptSchema,
|
|
2
|
+
import { mulmoScriptSchema, mulmoStudioSchema } from "../types/index.js";
|
|
3
3
|
const rebuildStudio = (currentStudio, mulmoScript, fileName) => {
|
|
4
|
-
const
|
|
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.
|
|
3
|
+
"version": "0.0.17",
|
|
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.
|
|
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",
|
|
@@ -68,10 +68,10 @@
|
|
|
68
68
|
"inquirer": "^12.6.1",
|
|
69
69
|
"marked": "^15.0.12",
|
|
70
70
|
"ora": "^8.2.0",
|
|
71
|
-
"puppeteer": "^24.10.
|
|
71
|
+
"puppeteer": "^24.10.2",
|
|
72
72
|
"yaml": "^2.8.0",
|
|
73
73
|
"yargs": "^17.7.2",
|
|
74
|
-
"zod": "^3.25.
|
|
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.
|
|
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.
|
|
90
|
+
"typescript-eslint": "^8.34.1"
|
|
91
91
|
},
|
|
92
92
|
"engines": {
|
|
93
|
-
"node": ">=
|
|
93
|
+
"node": ">=18.0.0"
|
|
94
94
|
}
|
|
95
95
|
}
|