mulmocast 2.2.6 → 2.3.0
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/README.md +45 -0
- package/lib/cli/commands/tool/info/builder.d.ts +2 -0
- package/lib/cli/commands/tool/info/builder.js +6 -1
- package/lib/cli/commands/tool/info/handler.d.ts +1 -0
- package/lib/cli/commands/tool/info/handler.js +84 -3
- package/lib/cli/commands/tool/info/index.d.ts +1 -1
- package/lib/cli/commands/tool/info/index.js +1 -1
- package/lib/tools/complete_script.d.ts +2 -1
- package/lib/tools/complete_script.js +11 -5
- package/lib/utils/context.js +6 -2
- package/lib/utils/image_plugins/slide.js +2 -4
- package/lib/utils/mulmo_config.d.ts +28 -0
- package/lib/utils/mulmo_config.js +90 -0
- package/package.json +10 -10
- package/scripts/test/test_media.json +17 -0
package/README.md
CHANGED
|
@@ -207,6 +207,51 @@ MulmoScript configuration (same as OpenAI):
|
|
|
207
207
|
|
|
208
208
|
For detailed setup and region availability, see [Azure OpenAI Usage Guide](./docs/azure_openai_usage.md).
|
|
209
209
|
|
|
210
|
+
### mulmo.config.json
|
|
211
|
+
|
|
212
|
+
Create a `mulmo.config.json` file to set project-wide defaults. The CLI searches for it in **CWD** first, then **home directory (`~/`)**.
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"speechParams": {
|
|
217
|
+
"provider": "elevenlabs"
|
|
218
|
+
},
|
|
219
|
+
"imageParams": {
|
|
220
|
+
"provider": "google"
|
|
221
|
+
},
|
|
222
|
+
"audioParams": {
|
|
223
|
+
"bgm": { "kind": "path", "path": "assets/bgm.mp3" },
|
|
224
|
+
"bgmVolume": 0.15
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Top-level keys are applied as **defaults** (script values take precedence). Use the `override` key to **force** values over scripts — useful for enterprise branding or TTS provider enforcement:
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"speechParams": {
|
|
234
|
+
"provider": "elevenlabs"
|
|
235
|
+
},
|
|
236
|
+
"override": {
|
|
237
|
+
"speechParams": {
|
|
238
|
+
"provider": "elevenlabs",
|
|
239
|
+
"model": "eleven_multilingual_v2",
|
|
240
|
+
"speakers": {
|
|
241
|
+
"Presenter": { "provider": "elevenlabs", "voiceId": "Rachel" }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Priority chain: `config (defaults)` < `template/style` < `script` < `config.override` < `presentationStyle (-p)`
|
|
249
|
+
|
|
250
|
+
Verify the merged result with:
|
|
251
|
+
```bash
|
|
252
|
+
mulmo tool info merged --script <script.json>
|
|
253
|
+
```
|
|
254
|
+
|
|
210
255
|
## Workflow
|
|
211
256
|
|
|
212
257
|
1. Create a MulmoScript JSON file with `mulmo tool scripting`
|
|
@@ -2,7 +2,7 @@ export const builder = (yargs) => yargs
|
|
|
2
2
|
.positional("category", {
|
|
3
3
|
describe: "Category to show info for",
|
|
4
4
|
type: "string",
|
|
5
|
-
choices: ["styles", "bgm", "templates", "voices", "images", "movies", "llm", "themes"],
|
|
5
|
+
choices: ["styles", "bgm", "templates", "voices", "images", "movies", "llm", "themes", "config", "merged"],
|
|
6
6
|
})
|
|
7
7
|
.option("format", {
|
|
8
8
|
alias: "F",
|
|
@@ -10,4 +10,9 @@ export const builder = (yargs) => yargs
|
|
|
10
10
|
type: "string",
|
|
11
11
|
choices: ["text", "json", "yaml"],
|
|
12
12
|
default: "text",
|
|
13
|
+
})
|
|
14
|
+
.option("script", {
|
|
15
|
+
alias: "S",
|
|
16
|
+
describe: "Script file path (required for 'merged' category)",
|
|
17
|
+
type: "string",
|
|
13
18
|
});
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
+
import path from "path";
|
|
2
3
|
import { getMarkdownStyleNames, getMarkdownCategories, getMarkdownStylesByCategory } from "../../../../data/markdownStyles.js";
|
|
3
4
|
import { bgmAssets } from "../../../../data/bgmAssets.js";
|
|
4
5
|
import { templateDataSet } from "../../../../data/templateDataSet.js";
|
|
5
6
|
import { slideThemes } from "../../../../data/slideThemes.js";
|
|
6
7
|
import { provider2TTSAgent, provider2ImageAgent, provider2MovieAgent, provider2LLMAgent } from "../../../../types/provider2agent.js";
|
|
8
|
+
import { findConfigFile, loadMulmoConfig, mergeConfigWithScript } from "../../../../utils/mulmo_config.js";
|
|
9
|
+
import { readMulmoScriptFile } from "../../../../utils/file.js";
|
|
7
10
|
import YAML from "yaml";
|
|
8
11
|
const formatOutput = (data, format) => {
|
|
9
12
|
if (format === "json") {
|
|
@@ -170,6 +173,78 @@ const printThemesText = () => {
|
|
|
170
173
|
}
|
|
171
174
|
console.log("");
|
|
172
175
|
};
|
|
176
|
+
const getConfigInfo = () => {
|
|
177
|
+
const baseDirPath = process.cwd();
|
|
178
|
+
const configPath = findConfigFile(baseDirPath);
|
|
179
|
+
if (!configPath) {
|
|
180
|
+
return { configFile: null, config: null };
|
|
181
|
+
}
|
|
182
|
+
const config = loadMulmoConfig(baseDirPath);
|
|
183
|
+
return { configFile: configPath, config };
|
|
184
|
+
};
|
|
185
|
+
const printConfigText = () => {
|
|
186
|
+
const baseDirPath = process.cwd();
|
|
187
|
+
const configPath = findConfigFile(baseDirPath);
|
|
188
|
+
console.log("\n📄 mulmo.config.json\n");
|
|
189
|
+
if (!configPath) {
|
|
190
|
+
console.log(" No mulmo.config.json found.");
|
|
191
|
+
console.log(" Searched: CWD → ~\n");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
console.log(` Active config: ${configPath}\n`);
|
|
195
|
+
const config = loadMulmoConfig(baseDirPath);
|
|
196
|
+
if (config) {
|
|
197
|
+
console.log(JSON.stringify(config, null, 2));
|
|
198
|
+
}
|
|
199
|
+
console.log("");
|
|
200
|
+
};
|
|
201
|
+
const readScriptFile = (scriptPath) => {
|
|
202
|
+
const result = readMulmoScriptFile(scriptPath, `Error: File not found: ${scriptPath}`);
|
|
203
|
+
if (!result) {
|
|
204
|
+
console.error(`Error: Could not read script file: ${scriptPath}`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
return result.mulmoData;
|
|
208
|
+
};
|
|
209
|
+
const getMergedInfo = (scriptPath) => {
|
|
210
|
+
if (!scriptPath) {
|
|
211
|
+
console.error("Error: --script <file> is required for 'merged' category");
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
const baseDirPath = process.cwd();
|
|
215
|
+
const configResult = loadMulmoConfig(baseDirPath);
|
|
216
|
+
const script = readScriptFile(scriptPath);
|
|
217
|
+
if (!configResult) {
|
|
218
|
+
return { configFile: null, merged: script };
|
|
219
|
+
}
|
|
220
|
+
const configPath = findConfigFile(baseDirPath);
|
|
221
|
+
const merged = mergeConfigWithScript(configResult, script);
|
|
222
|
+
return { configFile: configPath, defaults: configResult.defaults, override: configResult.override, merged };
|
|
223
|
+
};
|
|
224
|
+
const printMergedText = (scriptPath) => {
|
|
225
|
+
if (!scriptPath) {
|
|
226
|
+
console.error("Error: --script <file> is required for 'merged' category");
|
|
227
|
+
console.error("Usage: mulmo tool info merged --script <script.json>");
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
const baseDirPath = process.cwd();
|
|
231
|
+
const configResult = loadMulmoConfig(baseDirPath);
|
|
232
|
+
const script = readScriptFile(scriptPath);
|
|
233
|
+
console.log("\n📋 Merged Script Result\n");
|
|
234
|
+
console.log(` Script: ${path.resolve(scriptPath)}`);
|
|
235
|
+
if (!configResult) {
|
|
236
|
+
console.log(" Config: (none)\n");
|
|
237
|
+
console.log(JSON.stringify(script, null, 2));
|
|
238
|
+
console.log("");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const configPath = findConfigFile(baseDirPath);
|
|
242
|
+
console.log(` Config: ${configPath}`);
|
|
243
|
+
console.log(` Override: ${configResult.override ? "yes" : "no"}\n`);
|
|
244
|
+
const merged = mergeConfigWithScript(configResult, script);
|
|
245
|
+
console.log(JSON.stringify(merged, null, 2));
|
|
246
|
+
console.log("");
|
|
247
|
+
};
|
|
173
248
|
const printAllCategories = () => {
|
|
174
249
|
console.log("\n📚 Available Info Categories\n");
|
|
175
250
|
console.log(" Usage: mulmo tool info <category> [--format json|yaml]\n");
|
|
@@ -181,14 +256,16 @@ const printAllCategories = () => {
|
|
|
181
256
|
console.log(" images - Image generation providers and models");
|
|
182
257
|
console.log(" movies - Movie generation providers and models");
|
|
183
258
|
console.log(" llm - LLM providers and models");
|
|
184
|
-
console.log(" themes - Slide themes and color palettes
|
|
259
|
+
console.log(" themes - Slide themes and color palettes");
|
|
260
|
+
console.log(" config - Active mulmo.config.json location and contents");
|
|
261
|
+
console.log(" merged - Show script merged with mulmo.config.json (--script <file>)\n");
|
|
185
262
|
};
|
|
186
|
-
const validCategories = ["styles", "bgm", "templates", "voices", "images", "movies", "llm", "themes"];
|
|
263
|
+
const validCategories = ["styles", "bgm", "templates", "voices", "images", "movies", "llm", "themes", "config", "merged"];
|
|
187
264
|
const isValidCategory = (category) => {
|
|
188
265
|
return validCategories.includes(category);
|
|
189
266
|
};
|
|
190
267
|
export const handler = (argv) => {
|
|
191
|
-
const { category, format = "text" } = argv;
|
|
268
|
+
const { category, format = "text", script: scriptPath } = argv;
|
|
192
269
|
if (!category) {
|
|
193
270
|
if (format === "text") {
|
|
194
271
|
printAllCategories();
|
|
@@ -216,6 +293,8 @@ export const handler = (argv) => {
|
|
|
216
293
|
movies: getMoviesInfo,
|
|
217
294
|
llm: getLlmInfo,
|
|
218
295
|
themes: getThemesInfo,
|
|
296
|
+
config: getConfigInfo,
|
|
297
|
+
merged: () => getMergedInfo(scriptPath),
|
|
219
298
|
};
|
|
220
299
|
const textPrinters = {
|
|
221
300
|
styles: printStylesText,
|
|
@@ -226,6 +305,8 @@ export const handler = (argv) => {
|
|
|
226
305
|
movies: printMoviesText,
|
|
227
306
|
llm: printLlmText,
|
|
228
307
|
themes: printThemesText,
|
|
308
|
+
config: printConfigText,
|
|
309
|
+
merged: () => printMergedText(scriptPath),
|
|
229
310
|
};
|
|
230
311
|
if (format === "text") {
|
|
231
312
|
textPrinters[category]();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const command = "info [category]";
|
|
2
|
-
export declare const desc = "Show available options (styles, bgm, templates, voices, images, movies, llm, themes)";
|
|
2
|
+
export declare const desc = "Show available options (styles, bgm, templates, voices, images, movies, llm, themes, config, merged)";
|
|
3
3
|
export { builder } from "./builder.js";
|
|
4
4
|
export { handler } from "./handler.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export const command = "info [category]";
|
|
2
|
-
export const desc = "Show available options (styles, bgm, templates, voices, images, movies, llm, themes)";
|
|
2
|
+
export const desc = "Show available options (styles, bgm, templates, voices, images, movies, llm, themes, config, merged)";
|
|
3
3
|
export { builder } from "./builder.js";
|
|
4
4
|
export { handler } from "./handler.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ZodSafeParseResult } from "zod";
|
|
2
2
|
import type { MulmoScript } from "../types/type.js";
|
|
3
|
-
type PartialMulmoScript = Record<string, unknown>;
|
|
3
|
+
export type PartialMulmoScript = Record<string, unknown>;
|
|
4
4
|
/**
|
|
5
5
|
* Add $mulmocast version if not present
|
|
6
6
|
*/
|
|
@@ -17,6 +17,7 @@ export type CompleteScriptResult = ZodSafeParseResult<MulmoScript>;
|
|
|
17
17
|
type CompleteScriptOptions = {
|
|
18
18
|
templateName?: string;
|
|
19
19
|
styleName?: string;
|
|
20
|
+
baseDirPath?: string;
|
|
20
21
|
};
|
|
21
22
|
/**
|
|
22
23
|
* Complete a partial MulmoScript with schema defaults, optional style or template
|
|
@@ -4,6 +4,7 @@ import { mulmoScriptSchema } from "../types/schema.js";
|
|
|
4
4
|
import { getScriptFromPromptTemplate } from "../utils/file.js";
|
|
5
5
|
import { currentMulmoScriptVersion } from "../types/const.js";
|
|
6
6
|
import { promptTemplates } from "../data/index.js";
|
|
7
|
+
import { loadMulmoConfig } from "../utils/mulmo_config.js";
|
|
7
8
|
/**
|
|
8
9
|
* Add $mulmocast version if not present
|
|
9
10
|
*/
|
|
@@ -16,7 +17,7 @@ export const addMulmocastVersion = (data) => {
|
|
|
16
17
|
$mulmocast: { version: currentMulmoScriptVersion },
|
|
17
18
|
};
|
|
18
19
|
};
|
|
19
|
-
const deepMergeKeys = ["speechParams", "imageParams", "movieParams", "audioParams"];
|
|
20
|
+
const deepMergeKeys = ["speechParams", "imageParams", "movieParams", "audioParams", "slideParams"];
|
|
20
21
|
/**
|
|
21
22
|
* Merge base with override (override takes precedence)
|
|
22
23
|
*/
|
|
@@ -82,11 +83,13 @@ export const getStyle = (style) => {
|
|
|
82
83
|
* completeScript(data, { styleName: "./my-style.json" })
|
|
83
84
|
*/
|
|
84
85
|
export const completeScript = (data, options = {}) => {
|
|
85
|
-
const { templateName, styleName } = options;
|
|
86
|
+
const { templateName, styleName, baseDirPath } = options;
|
|
86
87
|
// template and style are mutually exclusive
|
|
87
88
|
if (templateName && styleName) {
|
|
88
89
|
throw new Error("Cannot specify both templateName and styleName. They are mutually exclusive.");
|
|
89
90
|
}
|
|
91
|
+
// Load mulmo.config.json
|
|
92
|
+
const configResult = baseDirPath ? loadMulmoConfig(baseDirPath) : null;
|
|
90
93
|
// Get base config from template or style
|
|
91
94
|
const getBase = () => {
|
|
92
95
|
if (templateName) {
|
|
@@ -97,9 +100,12 @@ export const completeScript = (data, options = {}) => {
|
|
|
97
100
|
}
|
|
98
101
|
return undefined;
|
|
99
102
|
};
|
|
100
|
-
const
|
|
101
|
-
// Merge
|
|
102
|
-
const
|
|
103
|
+
const templateOrStyle = getBase();
|
|
104
|
+
// Merge chain: config.defaults < template/style < input data < config.override
|
|
105
|
+
const defaults = configResult?.defaults;
|
|
106
|
+
const withDefaults = defaults && templateOrStyle ? mergeScripts(defaults, templateOrStyle) : (templateOrStyle ?? defaults);
|
|
107
|
+
const withData = withDefaults ? mergeScripts(withDefaults, data) : data;
|
|
108
|
+
const merged = configResult?.override ? mergeScripts(withData, configResult.override) : withData;
|
|
103
109
|
// Add version if not present
|
|
104
110
|
const withVersion = addMulmocastVersion(merged);
|
|
105
111
|
return mulmoScriptSchema.safeParse(withVersion);
|
package/lib/utils/context.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readMulmoScriptFile, fetchMulmoScriptFile, isFile } from "./file.js";
|
|
|
3
3
|
import { beatId, multiLingualObjectToArray } from "./utils.js";
|
|
4
4
|
import { mulmoStudioSchema, mulmoCaptionParamsSchema, mulmoPresentationStyleSchema } from "../types/schema.js";
|
|
5
5
|
import { MulmoPresentationStyleMethods, MulmoScriptMethods, MulmoStudioMultiLingualMethod } from "../methods/index.js";
|
|
6
|
+
import { loadMulmoConfig, mergeConfigWithScript } from "./mulmo_config.js";
|
|
6
7
|
export const silentMp3 = "https://github.com/receptron/mulmocast-cli/raw/refs/heads/main/assets/audio/silent300.mp3";
|
|
7
8
|
const mulmoCredit = (speaker, isPortrait) => {
|
|
8
9
|
return {
|
|
@@ -117,10 +118,13 @@ export const getPresentationStyle = (presentationStylePath) => {
|
|
|
117
118
|
};
|
|
118
119
|
export const initializeContextFromFiles = async (files, raiseError, force, withBackup, captionLang, targetLang, index) => {
|
|
119
120
|
const { fileName, isHttpPath, fileOrUrl, mulmoFilePath, presentationStylePath, outputMultilingualFilePath } = files;
|
|
120
|
-
const
|
|
121
|
-
if (!
|
|
121
|
+
const rawScript = await fetchScript(isHttpPath, mulmoFilePath, fileOrUrl);
|
|
122
|
+
if (!rawScript) {
|
|
122
123
|
return null;
|
|
123
124
|
}
|
|
125
|
+
// Load and merge mulmo.config.json (defaults < script < override)
|
|
126
|
+
const config = loadMulmoConfig(files.baseDirPath);
|
|
127
|
+
const mulmoScript = config ? mergeConfigWithScript(config, rawScript) : rawScript;
|
|
124
128
|
// The index param is used when you want to process only a specific beat in an app, etc. This is to avoid parser errors.
|
|
125
129
|
if (!isNull(index) && mulmoScript.beats[index]) {
|
|
126
130
|
mulmoScript.beats = [mulmoScript.beats[index]];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import nodePath from "node:path";
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
3
|
import { generateSlideHTML } from "../../slide/index.js";
|
|
4
|
+
import { slideThemes } from "../../data/slideThemes.js";
|
|
4
5
|
import { renderHTMLToImage } from "../html_render.js";
|
|
5
6
|
import { parrotingImagePath } from "./utils.js";
|
|
6
7
|
import { pathToDataUrl } from "../../methods/mulmo_media_source.js";
|
|
@@ -95,10 +96,7 @@ const resolveTheme = (params) => {
|
|
|
95
96
|
throw new Error("resolveTheme called on non-slide beat");
|
|
96
97
|
}
|
|
97
98
|
const defaultTheme = context.presentationStyle.slideParams?.theme;
|
|
98
|
-
const theme = beat.image.theme ?? defaultTheme;
|
|
99
|
-
if (!theme) {
|
|
100
|
-
throw new Error("Slide theme is required: set slideParams.theme or beat.image.theme");
|
|
101
|
-
}
|
|
99
|
+
const theme = beat.image.theme ?? defaultTheme ?? slideThemes.corporate;
|
|
102
100
|
return theme;
|
|
103
101
|
};
|
|
104
102
|
const resolveSlide = (params, converter = pathToDataUrl) => {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type PartialMulmoScript } from "../tools/complete_script.js";
|
|
2
|
+
/**
|
|
3
|
+
* Search for mulmo.config.json in CWD → ~ order.
|
|
4
|
+
* Returns the first found path, or null if not found.
|
|
5
|
+
*/
|
|
6
|
+
export declare const findConfigFile: (baseDirPath: string) => string | null;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve all kind:"path" references in config relative to the config file directory.
|
|
9
|
+
*/
|
|
10
|
+
export declare const resolveConfigPaths: (config: PartialMulmoScript, configDirPath: string) => PartialMulmoScript;
|
|
11
|
+
export type MulmoConfigResult = {
|
|
12
|
+
defaults: PartialMulmoScript;
|
|
13
|
+
override: PartialMulmoScript | null;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Load mulmo.config.json from baseDirPath or home directory.
|
|
17
|
+
* Resolves kind:"path" entries relative to the config file location.
|
|
18
|
+
* Returns { defaults, override } or null if no config file is found.
|
|
19
|
+
*
|
|
20
|
+
* - defaults: applied as low-priority base (script wins)
|
|
21
|
+
* - override: applied after script merge (wins over script)
|
|
22
|
+
*/
|
|
23
|
+
export declare const loadMulmoConfig: (baseDirPath: string) => MulmoConfigResult | null;
|
|
24
|
+
/**
|
|
25
|
+
* Merge mulmo.config.json with a MulmoScript.
|
|
26
|
+
* defaults < script < override
|
|
27
|
+
*/
|
|
28
|
+
export declare const mergeConfigWithScript: (configResult: MulmoConfigResult, script: PartialMulmoScript) => PartialMulmoScript;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { GraphAILogger } from "graphai";
|
|
5
|
+
import { mergeScripts } from "../tools/complete_script.js";
|
|
6
|
+
import { getFullPath } from "./file.js";
|
|
7
|
+
const CONFIG_FILE_NAME = "mulmo.config.json";
|
|
8
|
+
/**
|
|
9
|
+
* Search for mulmo.config.json in CWD → ~ order.
|
|
10
|
+
* Returns the first found path, or null if not found.
|
|
11
|
+
*/
|
|
12
|
+
export const findConfigFile = (baseDirPath) => {
|
|
13
|
+
const candidates = [path.resolve(baseDirPath, CONFIG_FILE_NAME), path.resolve(os.homedir(), CONFIG_FILE_NAME)];
|
|
14
|
+
for (const candidate of candidates) {
|
|
15
|
+
if (existsSync(candidate)) {
|
|
16
|
+
return candidate;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Resolve kind:"path" entries in config to absolute paths relative to config file location.
|
|
23
|
+
*/
|
|
24
|
+
const resolveMediaSourcePath = (source, configDirPath) => {
|
|
25
|
+
if (source.kind === "path" && typeof source.path === "string") {
|
|
26
|
+
return { ...source, path: getFullPath(configDirPath, source.path) };
|
|
27
|
+
}
|
|
28
|
+
return source;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Immutably resolve a nested kind:"path" source at the given key path.
|
|
32
|
+
* e.g. ["audioParams", "bgm"] resolves config.audioParams.bgm
|
|
33
|
+
*/
|
|
34
|
+
const resolveNestedPath = (obj, keys, configDirPath) => {
|
|
35
|
+
const [head, ...tail] = keys;
|
|
36
|
+
const child = obj[head];
|
|
37
|
+
if (!child || typeof child !== "object") {
|
|
38
|
+
return obj;
|
|
39
|
+
}
|
|
40
|
+
const childObj = child;
|
|
41
|
+
const resolved = tail.length === 0 ? resolveMediaSourcePath(childObj, configDirPath) : resolveNestedPath(childObj, tail, configDirPath);
|
|
42
|
+
return resolved === child ? obj : { ...obj, [head]: resolved };
|
|
43
|
+
};
|
|
44
|
+
/** Key paths to kind:"path" sources that need resolution */
|
|
45
|
+
const MEDIA_SOURCE_PATHS = [
|
|
46
|
+
["audioParams", "bgm"],
|
|
47
|
+
["slideParams", "branding", "logo", "source"],
|
|
48
|
+
["slideParams", "branding", "backgroundImage", "source"],
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* Resolve all kind:"path" references in config relative to the config file directory.
|
|
52
|
+
*/
|
|
53
|
+
export const resolveConfigPaths = (config, configDirPath) => {
|
|
54
|
+
return MEDIA_SOURCE_PATHS.reduce((acc, keys) => resolveNestedPath(acc, keys, configDirPath), config);
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Load mulmo.config.json from baseDirPath or home directory.
|
|
58
|
+
* Resolves kind:"path" entries relative to the config file location.
|
|
59
|
+
* Returns { defaults, override } or null if no config file is found.
|
|
60
|
+
*
|
|
61
|
+
* - defaults: applied as low-priority base (script wins)
|
|
62
|
+
* - override: applied after script merge (wins over script)
|
|
63
|
+
*/
|
|
64
|
+
export const loadMulmoConfig = (baseDirPath) => {
|
|
65
|
+
const configPath = findConfigFile(baseDirPath);
|
|
66
|
+
if (!configPath) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const content = readFileSync(configPath, "utf-8");
|
|
71
|
+
const raw = JSON.parse(content);
|
|
72
|
+
const configDirPath = path.dirname(configPath);
|
|
73
|
+
const { override: rawOverride, ...rest } = raw;
|
|
74
|
+
const defaults = resolveConfigPaths(rest, configDirPath);
|
|
75
|
+
const override = rawOverride ? resolveConfigPaths(rawOverride, configDirPath) : null;
|
|
76
|
+
return { defaults, override };
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
GraphAILogger.error(`Error loading ${configPath}: ${error.message}`);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Merge mulmo.config.json with a MulmoScript.
|
|
85
|
+
* defaults < script < override
|
|
86
|
+
*/
|
|
87
|
+
export const mergeConfigWithScript = (configResult, script) => {
|
|
88
|
+
const withDefaults = mergeScripts(configResult.defaults, script);
|
|
89
|
+
return configResult.override ? mergeScripts(withDefaults, configResult.override) : withDefaults;
|
|
90
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mulmocast",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.node.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"resolutions": {
|
|
27
|
-
"minimatch": "^10.2.
|
|
27
|
+
"minimatch": "^10.2.3"
|
|
28
28
|
},
|
|
29
29
|
"bin": {
|
|
30
30
|
"mulmo": "lib/cli/bin.js",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"homepage": "https://github.com/receptron/mulmocast-cli#readme",
|
|
88
88
|
"dependencies": {
|
|
89
89
|
"@google-cloud/text-to-speech": "^6.4.0",
|
|
90
|
-
"@google/genai": "^1.
|
|
90
|
+
"@google/genai": "^1.42.0",
|
|
91
91
|
"@graphai/anthropic_agent": "^2.0.12",
|
|
92
92
|
"@graphai/browserless_agent": "^2.0.2",
|
|
93
93
|
"@graphai/gemini_agent": "^2.0.5",
|
|
@@ -97,13 +97,13 @@
|
|
|
97
97
|
"@graphai/stream_agent_filter": "^2.0.3",
|
|
98
98
|
"@graphai/vanilla": "^2.0.12",
|
|
99
99
|
"@graphai/vanilla_node_agents": "^2.0.4",
|
|
100
|
-
"@inquirer/input": "^5.0.
|
|
101
|
-
"@inquirer/select": "^5.0
|
|
102
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
100
|
+
"@inquirer/input": "^5.0.8",
|
|
101
|
+
"@inquirer/select": "^5.1.0",
|
|
102
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
103
103
|
"@mozilla/readability": "^0.6.0",
|
|
104
104
|
"@tavily/core": "^0.5.11",
|
|
105
105
|
"archiver": "^7.0.1",
|
|
106
|
-
"clipboardy": "^5.3.
|
|
106
|
+
"clipboardy": "^5.3.1",
|
|
107
107
|
"dotenv": "^17.3.1",
|
|
108
108
|
"fluent-ffmpeg": "^2.1.3",
|
|
109
109
|
"graphai": "^2.0.16",
|
|
@@ -122,10 +122,10 @@
|
|
|
122
122
|
"@receptron/test_utils": "^2.0.3",
|
|
123
123
|
"@types/archiver": "^7.0.0",
|
|
124
124
|
"@types/fluent-ffmpeg": "^2.1.28",
|
|
125
|
-
"@types/jsdom": "^
|
|
125
|
+
"@types/jsdom": "^28.0.0",
|
|
126
126
|
"@types/yargs": "^17.0.35",
|
|
127
127
|
"cross-env": "^10.1.0",
|
|
128
|
-
"eslint": "^10.0.
|
|
128
|
+
"eslint": "^10.0.2",
|
|
129
129
|
"eslint-config-prettier": "^10.1.8",
|
|
130
130
|
"eslint-plugin-prettier": "^5.5.5",
|
|
131
131
|
"eslint-plugin-sonarjs": "^4.0.0",
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"prettier": "^3.8.1",
|
|
134
134
|
"tsx": "^4.21.0",
|
|
135
135
|
"typescript": "^5.9.3",
|
|
136
|
-
"typescript-eslint": "^8.56.
|
|
136
|
+
"typescript-eslint": "^8.56.1"
|
|
137
137
|
},
|
|
138
138
|
"engines": {
|
|
139
139
|
"node": ">=22.0.0"
|
|
@@ -273,6 +273,23 @@
|
|
|
273
273
|
"type": "beat",
|
|
274
274
|
"id": "pingpongmov"
|
|
275
275
|
}
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"speaker": "Presenter",
|
|
279
|
+
"text": "This is a slide with table layout.",
|
|
280
|
+
"image": {
|
|
281
|
+
"type": "slide",
|
|
282
|
+
"slide": {
|
|
283
|
+
"layout": "table",
|
|
284
|
+
"title": "Feature Comparison",
|
|
285
|
+
"headers": ["Feature", "Free", "Pro"],
|
|
286
|
+
"rows": [
|
|
287
|
+
["Audio", "OpenAI", "ElevenLabs"],
|
|
288
|
+
["Images", "DALL-E", "Imagen 3"],
|
|
289
|
+
["Video", "-", "Veo"]
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
}
|
|
276
293
|
}
|
|
277
294
|
]
|
|
278
295
|
}
|