mulmocast 0.0.25 → 0.0.27

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 CHANGED
@@ -173,7 +173,7 @@ DEFAULT_OPENAI_IMAGE_MODEL=gpt-image-1 # required for high-quality Ghibli-style
173
173
 
174
174
  ### Step 2: Generate a Ghibli-style MulmoScript
175
175
  ```bash
176
- mulmo tool scripting -i -t ghibli_strips -o ./ -s story
176
+ mulmo tool scripting -i -t ghibli_comic -o ./ -s story
177
177
  ```
178
178
  This will initiate an interactive conversation with the AI to create your Ghibli-inspired story. Once completed, a JSON file (e.g., `story-1747834931950.json`) will be generated.
179
179
 
@@ -32,7 +32,7 @@ const getAudioPath = (context, beat, audioFile) => {
32
32
  }
33
33
  throw new Error("Invalid audio source");
34
34
  }
35
- if (beat.text === undefined || beat.text === "") {
35
+ if (beat.text === undefined || beat.text === "" || context.studio.script.audioParams.suppressSpeech) {
36
36
  return undefined; // It indicates that the audio is not needed.
37
37
  }
38
38
  return audioFile;
@@ -372,13 +372,11 @@ const prepareGenerateImages = async (context) => {
372
372
  const imageProjectDirPath = MulmoStudioContextMethods.getImageProjectDirPath(context);
373
373
  const outDirPath = MulmoStudioContextMethods.getOutDirPath(context);
374
374
  mkdir(imageProjectDirPath);
375
- const imageAgentInfo = MulmoPresentationStyleMethods.getImageAgentInfo(context.presentationStyle, context.dryRun);
375
+ const imageAgentInfo = MulmoPresentationStyleMethods.getImageAgentInfo(context.presentationStyle);
376
376
  const htmlImageAgentInfo = MulmoPresentationStyleMethods.getHtmlImageAgentInfo(context.presentationStyle);
377
377
  const imageRefs = await getImageRefs(context);
378
378
  // Determine movie agent based on provider
379
379
  const getMovieAgent = () => {
380
- if (context.dryRun)
381
- return "mediaMockAgent";
382
380
  const provider = context.presentationStyle.movieParams?.provider ?? "google";
383
381
  switch (provider) {
384
382
  case "replicate":
@@ -7,8 +7,6 @@ export declare const builder: (yargs: Argv) => Argv<{
7
7
  l: string | undefined;
8
8
  } & {
9
9
  f: boolean;
10
- } & {
11
- dryRun: boolean;
12
10
  } & {
13
11
  p: string | undefined;
14
12
  } & {
@@ -7,8 +7,6 @@ export declare const builder: (yargs: Argv) => Argv<{
7
7
  l: string | undefined;
8
8
  } & {
9
9
  f: boolean;
10
- } & {
11
- dryRun: boolean;
12
10
  } & {
13
11
  p: string | undefined;
14
12
  } & {
@@ -7,8 +7,6 @@ export declare const builder: (yargs: Argv) => Argv<{
7
7
  l: string | undefined;
8
8
  } & {
9
9
  f: boolean;
10
- } & {
11
- dryRun: boolean;
12
10
  } & {
13
11
  p: string | undefined;
14
12
  } & {
@@ -7,8 +7,6 @@ export declare const builder: (yargs: Argv) => Argv<{
7
7
  l: string | undefined;
8
8
  } & {
9
9
  f: boolean;
10
- } & {
11
- dryRun: boolean;
12
10
  } & {
13
11
  p: string | undefined;
14
12
  } & {
@@ -7,8 +7,6 @@ export declare const builder: (yargs: Argv) => Argv<import("yargs").Omit<{
7
7
  l: string | undefined;
8
8
  } & {
9
9
  f: boolean;
10
- } & {
11
- dryRun: boolean;
12
10
  } & {
13
11
  p: string | undefined;
14
12
  } & {
@@ -7,8 +7,6 @@ export declare const commonOptions: (yargs: Argv) => Argv<{
7
7
  l: string | undefined;
8
8
  } & {
9
9
  f: boolean;
10
- } & {
11
- dryRun: boolean;
12
10
  } & {
13
11
  p: string | undefined;
14
12
  } & {
package/lib/cli/common.js CHANGED
@@ -25,11 +25,6 @@ export const commonOptions = (yargs) => {
25
25
  describe: "Force regenerate",
26
26
  type: "boolean",
27
27
  default: false,
28
- })
29
- .option("dryRun", {
30
- describe: "Dry run",
31
- type: "boolean",
32
- default: false,
33
28
  })
34
29
  .option("p", {
35
30
  alias: "presentationStyle",
@@ -1,20 +1,10 @@
1
- import type { MulmoScript, MulmoStudioContext, MulmoPresentationStyle, MulmoStudioMultiLingual } from "../types/type.js";
2
1
  import type { CliArgs } from "../types/cli_types.js";
2
+ import { FileObject, InitOptions, MulmoStudioContext } from "../types/index.js";
3
+ export declare const runTranslateIfNeeded: (context: MulmoStudioContext, argv: {
4
+ l?: string;
5
+ c?: string;
6
+ }) => Promise<void>;
3
7
  export declare const setGraphAILogger: (verbose: boolean | undefined, logValues?: Record<string, unknown>) => void;
4
- export interface FileObject {
5
- baseDirPath: string;
6
- mulmoFilePath: string;
7
- mulmoFileDirPath: string;
8
- outDirPath: string;
9
- imageDirPath: string;
10
- audioDirPath: string;
11
- isHttpPath: boolean;
12
- fileOrUrl: string;
13
- outputStudioFilePath: string;
14
- outputMultilingualFilePath: string;
15
- presentationStylePath: string | undefined;
16
- fileName: string;
17
- }
18
8
  export declare const getFileObject: (args: {
19
9
  basedir?: string;
20
10
  outdir?: string;
@@ -23,22 +13,4 @@ export declare const getFileObject: (args: {
23
13
  presentationStyle?: string;
24
14
  file: string;
25
15
  }) => FileObject;
26
- export declare const fetchScript: (isHttpPath: boolean, mulmoFilePath: string, fileOrUrl: string) => Promise<MulmoScript | null>;
27
- export declare const getMultiLingual: (multilingualFilePath: string, beatsLength: number) => MulmoStudioMultiLingual;
28
- export declare const getPresentationStyle: (presentationStylePath: string | undefined) => MulmoPresentationStyle | null;
29
- type InitOptions = {
30
- b?: string;
31
- o?: string;
32
- i?: string;
33
- a?: string;
34
- file?: string;
35
- l?: string;
36
- c?: string;
37
- p?: string;
38
- };
39
- export declare const initializeContext: (argv: CliArgs<InitOptions>) => Promise<MulmoStudioContext | null>;
40
- export declare const runTranslateIfNeeded: (context: MulmoStudioContext, argv: {
41
- l?: string;
42
- c?: string;
43
- }) => Promise<void>;
44
- export {};
16
+ export declare const initializeContext: (argv: CliArgs<InitOptions>, raiseError?: boolean) => Promise<MulmoStudioContext | null>;
@@ -2,12 +2,17 @@ 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, getOutputMultilingualFilePath, } from "../utils/file.js";
5
+ import { getBaseDirPath, getFullPath, getOutputStudioFilePath, resolveDirPath, mkdir, getOutputMultilingualFilePath, generateTimestampedFileName, } from "../utils/file.js";
6
6
  import { isHttp } from "../utils/utils.js";
7
- import { createOrUpdateStudioData } from "../utils/preprocess.js";
8
7
  import { outDirName, imageDirName, audioDirName } from "../utils/const.js";
9
8
  import { translate } from "../actions/translate.js";
10
- import { mulmoCaptionParamsSchema, mulmoPresentationStyleSchema, mulmoStudioMultiLingualSchema } from "../types/schema.js";
9
+ import { initializeContextFromFiles } from "../utils/context.js";
10
+ export const runTranslateIfNeeded = async (context, argv) => {
11
+ if (argv.l || context.studio.script.captionParams?.lang) {
12
+ GraphAILogger.log("run translate");
13
+ await translate(context);
14
+ }
15
+ };
11
16
  export const setGraphAILogger = (verbose, logValues) => {
12
17
  if (verbose) {
13
18
  if (logValues) {
@@ -29,9 +34,7 @@ export const getFileObject = (args) => {
29
34
  const { fileOrUrl, fileName } = (() => {
30
35
  if (file === "__clipboard") {
31
36
  // We generate a new unique script file from clipboard text in the output directory
32
- const now = new Date();
33
- const pad = (n) => n.toString().padStart(2, "0");
34
- const fileName = `script_${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
37
+ const fileName = generateTimestampedFileName("script");
35
38
  const clipboardText = clipboardy.readSync();
36
39
  const fileOrUrl = resolveDirPath(outDirPath, `${fileName}.json`);
37
40
  mkdir(outDirPath);
@@ -65,44 +68,7 @@ export const getFileObject = (args) => {
65
68
  fileName,
66
69
  };
67
70
  };
68
- export const fetchScript = async (isHttpPath, mulmoFilePath, fileOrUrl) => {
69
- if (isHttpPath) {
70
- const res = await fetchMulmoScriptFile(fileOrUrl);
71
- if (!res.result || !res.script) {
72
- GraphAILogger.info(`ERROR: HTTP error! ${res.status} ${fileOrUrl}`);
73
- return null;
74
- }
75
- return res.script;
76
- }
77
- if (!fs.existsSync(mulmoFilePath)) {
78
- GraphAILogger.info(`ERROR: File not exists ${mulmoFilePath}`);
79
- return null;
80
- }
81
- return readMulmoScriptFile(mulmoFilePath, "ERROR: File does not exist " + mulmoFilePath)?.mulmoData ?? null;
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
- const dataSet = mulmoStudioMultiLingualSchema.parse(jsonData);
87
- while (dataSet.length < beatsLength) {
88
- dataSet.push({ multiLingualTexts: {} });
89
- }
90
- dataSet.length = beatsLength;
91
- return dataSet;
92
- }
93
- return [...Array(beatsLength)].map(() => ({ multiLingualTexts: {} }));
94
- };
95
- export const getPresentationStyle = (presentationStylePath) => {
96
- if (presentationStylePath) {
97
- if (!fs.existsSync(presentationStylePath)) {
98
- throw new Error(`ERROR: File not exists ${presentationStylePath}`);
99
- }
100
- const jsonData = readMulmoScriptFile(presentationStylePath, "ERROR: File does not exist " + presentationStylePath)?.mulmoData ?? null;
101
- return mulmoPresentationStyleSchema.parse(jsonData);
102
- }
103
- return null;
104
- };
105
- export const initializeContext = async (argv) => {
71
+ export const initializeContext = async (argv, raiseError = false) => {
106
72
  const files = getFileObject({
107
73
  basedir: argv.b,
108
74
  outdir: argv.o,
@@ -111,62 +77,6 @@ export const initializeContext = async (argv) => {
111
77
  presentationStyle: argv.p,
112
78
  file: argv.file ?? "",
113
79
  });
114
- const { fileName, isHttpPath, fileOrUrl, mulmoFilePath, outputStudioFilePath, presentationStylePath, outputMultilingualFilePath } = files;
115
- setGraphAILogger(argv.v, {
116
- files,
117
- });
118
- const mulmoScript = await fetchScript(isHttpPath, mulmoFilePath, fileOrUrl);
119
- if (!mulmoScript) {
120
- return null;
121
- }
122
- const presentationStyle = getPresentationStyle(presentationStylePath);
123
- // Create or update MulmoStudio file with MulmoScript
124
- const currentStudio = readMulmoScriptFile(outputStudioFilePath);
125
- try {
126
- // validate mulmoStudioSchema. skip if __test_invalid__ is true
127
- const studio = createOrUpdateStudioData(mulmoScript, currentStudio?.mulmoData, fileName);
128
- const multiLingual = getMultiLingual(outputMultilingualFilePath, studio.beats.length);
129
- if (argv.c) {
130
- studio.script.captionParams = mulmoCaptionParamsSchema.parse({
131
- ...(studio.script.captionParams ?? {}),
132
- lang: argv.c,
133
- });
134
- }
135
- return {
136
- studio,
137
- fileDirs: files,
138
- force: Boolean(argv.f),
139
- dryRun: Boolean(argv.dryRun),
140
- lang: argv.l,
141
- sessionState: {
142
- inSession: {
143
- audio: false,
144
- image: false,
145
- video: false,
146
- multiLingual: false,
147
- caption: false,
148
- pdf: false,
149
- },
150
- inBeatSession: {
151
- audio: {},
152
- image: {},
153
- movie: {},
154
- multiLingual: {},
155
- caption: {},
156
- },
157
- },
158
- presentationStyle: presentationStyle ?? studio.script,
159
- multiLingual,
160
- };
161
- }
162
- catch (error) {
163
- GraphAILogger.info(`Error: invalid MulmoScript Schema: ${isHttpPath ? fileOrUrl : mulmoFilePath} \n ${error}`);
164
- return null;
165
- }
166
- };
167
- export const runTranslateIfNeeded = async (context, argv) => {
168
- if (argv.l || context.studio.script.captionParams?.lang) {
169
- GraphAILogger.log("run translate");
170
- await translate(context);
171
- }
80
+ setGraphAILogger(Boolean(argv.v), { files });
81
+ return await initializeContextFromFiles(files, raiseError, Boolean(argv.f), argv.c, argv.l);
172
82
  };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
6
+ import fs from "fs";
7
+ import path from "path";
8
+ import { fileURLToPath } from "url";
9
+ import { GraphAILogger } from "graphai";
10
+ import { audio, images, movie, captions, pdf } from "../actions/index.js";
11
+ import { initializeContext, runTranslateIfNeeded } from "../cli/helpers.js";
12
+ import { outDirName } from "../utils/const.js";
13
+ import { resolveDirPath, mkdir, generateTimestampedFileName } from "../utils/file.js";
14
+ import { mulmoScriptSchema } from "../types/schema.js";
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
17
+ // Load MulmoScript JSON Schema from file
18
+ const MULMO_SCRIPT_JSON_SCHEMA = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../../assets/schemas/html_prompt.json"), "utf8"));
19
+ const server = new Server({
20
+ name: "mulmocast-mcp",
21
+ version: "0.1.0",
22
+ }, {
23
+ capabilities: {
24
+ tools: {},
25
+ },
26
+ });
27
+ // Helper function to save MulmoScript content to output directory
28
+ const saveMulmoScriptToOutput = async (mulmoScript) => {
29
+ const baseDirPath = process.cwd();
30
+ const outputDirPath = path.resolve(baseDirPath, outDirName);
31
+ // Create timestamp-based filename similar to __clipboard handling
32
+ const fileName = generateTimestampedFileName("mcp_script");
33
+ // Ensure output directory exists
34
+ mkdir(outputDirPath);
35
+ // Save MulmoScript to file
36
+ const filePath = resolveDirPath(outputDirPath, `${fileName}.json`);
37
+ fs.writeFileSync(filePath, JSON.stringify(mulmoScript, null, 2), "utf8");
38
+ return filePath;
39
+ };
40
+ // List available tools
41
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
42
+ return {
43
+ tools: [
44
+ {
45
+ name: "generate",
46
+ description: "Generate movie or PDF from MulmoScript content",
47
+ inputSchema: {
48
+ type: "object",
49
+ properties: {
50
+ cmd: {
51
+ type: "string",
52
+ enum: ["movie", "pdf"],
53
+ description: "Command to execute: 'movie' to generate video, 'pdf' to generate PDF",
54
+ },
55
+ mulmoScript: MULMO_SCRIPT_JSON_SCHEMA,
56
+ options: {
57
+ type: "object",
58
+ description: "Optional generation parameters",
59
+ properties: {
60
+ pdfMode: { type: "string", enum: ["slide", "talk", "handout"], description: "PDF generation mode (for PDF only)" },
61
+ pdfSize: { type: "string", enum: ["A4", "Letter", "Legal"], description: "PDF page size (for PDF only)" },
62
+ lang: { type: "string", description: "Language for translation" },
63
+ caption: { type: "string", description: "Caption language" },
64
+ force: { type: "boolean", description: "Force regeneration" },
65
+ verbose: { type: "boolean", description: "Enable verbose logging" },
66
+ },
67
+ additionalProperties: false,
68
+ },
69
+ },
70
+ required: ["cmd", "mulmoScript"],
71
+ additionalProperties: false,
72
+ },
73
+ },
74
+ ],
75
+ };
76
+ });
77
+ // Handle tool calls
78
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
79
+ const { name, arguments: args } = request.params;
80
+ try {
81
+ if (name !== "generate") {
82
+ throw new Error(`Unknown tool: ${name}`);
83
+ }
84
+ const { cmd, mulmoScript, options = {}, } = args;
85
+ // Validate MulmoScript schema
86
+ const validatedScript = mulmoScriptSchema.parse(mulmoScript);
87
+ // Save MulmoScript to output directory
88
+ const filePath = await saveMulmoScriptToOutput(validatedScript);
89
+ // Create argv-like object for CLI compatibility
90
+ const argv = {
91
+ file: filePath,
92
+ l: options.lang,
93
+ c: options.caption,
94
+ f: options.force || false,
95
+ v: options.verbose || false,
96
+ pdf_mode: options.pdfMode || "handout",
97
+ pdf_size: options.pdfSize || "Letter",
98
+ _: [],
99
+ $0: "mcp-server",
100
+ };
101
+ // Initialize context using the saved file
102
+ const context = await initializeContext(argv);
103
+ if (!context) {
104
+ throw new Error("Failed to initialize context from MulmoScript");
105
+ }
106
+ // Run translation if needed
107
+ await runTranslateIfNeeded(context, argv);
108
+ // Execute the requested command
109
+ switch (cmd) {
110
+ case "movie":
111
+ // Generate movie (audio + images + captions + movie)
112
+ await audio(context).then(images).then(captions).then(movie);
113
+ return {
114
+ content: [
115
+ {
116
+ type: "text",
117
+ text: `Movie generated successfully from MulmoScript. Output saved to: ${context.fileDirs.outDirPath}`,
118
+ },
119
+ ],
120
+ };
121
+ case "pdf":
122
+ // Generate images first, then PDF
123
+ await images(context);
124
+ await pdf(context, options.pdfMode || "handout", options.pdfSize || "Letter");
125
+ return {
126
+ content: [
127
+ {
128
+ type: "text",
129
+ text: `PDF generated successfully from MulmoScript. Output saved to: ${context.fileDirs.outDirPath}`,
130
+ },
131
+ ],
132
+ };
133
+ default:
134
+ throw new Error(`Unknown command: ${cmd}. Supported commands: movie, pdf`);
135
+ }
136
+ }
137
+ catch (error) {
138
+ const errorMessage = error instanceof Error ? error.message : String(error);
139
+ return {
140
+ content: [
141
+ {
142
+ type: "text",
143
+ text: `Error: ${errorMessage}`,
144
+ },
145
+ ],
146
+ isError: true,
147
+ };
148
+ }
149
+ });
150
+ // Start the server
151
+ async function main() {
152
+ const transport = new StdioServerTransport();
153
+ await server.connect(transport);
154
+ GraphAILogger.error("MulmoCast MCP Server running on stdio");
155
+ }
156
+ main().catch((error) => {
157
+ GraphAILogger.error("Failed to start MCP server:", error);
158
+ process.exit(1);
159
+ });
@@ -9,7 +9,7 @@ export declare const MulmoPresentationStyleMethods: {
9
9
  getSpeaker(presentationStyle: MulmoPresentationStyle, beat: MulmoBeat): SpeakerData;
10
10
  getProvider(presentationStyle: MulmoPresentationStyle, beat: MulmoBeat): Text2SpeechProvider;
11
11
  getVoiceId(presentationStyle: MulmoPresentationStyle, beat: MulmoBeat): string;
12
- getImageAgentInfo(presentationStyle: MulmoPresentationStyle, dryRun?: boolean): Text2ImageAgentInfo;
12
+ getImageAgentInfo(presentationStyle: MulmoPresentationStyle): Text2ImageAgentInfo;
13
13
  getHtmlImageAgentInfo(presentationStyle: MulmoPresentationStyle): Text2HtmlAgentInfo;
14
14
  getImageType(_: MulmoPresentationStyle, beat: MulmoBeat): BeatMediaType;
15
15
  };
@@ -57,7 +57,7 @@ export const MulmoPresentationStyleMethods = {
57
57
  const speaker = MulmoPresentationStyleMethods.getSpeaker(presentationStyle, beat);
58
58
  return speaker.voiceId;
59
59
  },
60
- getImageAgentInfo(presentationStyle, dryRun = false) {
60
+ getImageAgentInfo(presentationStyle) {
61
61
  // Notice that we copy imageParams from presentationStyle and update
62
62
  // provider and model appropriately.
63
63
  const provider = text2ImageProviderSchema.parse(presentationStyle.imageParams?.provider);
@@ -66,7 +66,7 @@ export const MulmoPresentationStyleMethods = {
66
66
  };
67
67
  return {
68
68
  provider,
69
- agent: dryRun ? "mediaMockAgent" : provider === "google" ? "imageGoogleAgent" : "imageOpenaiAgent",
69
+ agent: provider === "google" ? "imageGoogleAgent" : "imageOpenaiAgent",
70
70
  imageParams: { ...defaultImageParams, ...presentationStyle.imageParams },
71
71
  };
72
72
  },