mulmocast 0.0.5 → 0.0.6

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.
Files changed (132) hide show
  1. package/README.md +257 -39
  2. package/assets/audio/silent60sec.mp3 +0 -0
  3. package/assets/html/caption.html +45 -0
  4. package/assets/html/chart.html +1 -1
  5. package/assets/html/mermaid.html +6 -2
  6. package/assets/html/tailwind.html +13 -0
  7. package/assets/templates/business.json +57 -4
  8. package/assets/templates/comic_strips.json +35 -0
  9. package/assets/templates/ghibli_strips.json +35 -0
  10. package/lib/actions/audio.js +24 -11
  11. package/lib/actions/captions.d.ts +2 -0
  12. package/lib/actions/captions.js +62 -0
  13. package/lib/actions/images.js +3 -2
  14. package/lib/actions/index.d.ts +1 -0
  15. package/lib/actions/index.js +1 -0
  16. package/lib/actions/movie.js +78 -86
  17. package/lib/actions/pdf.js +15 -5
  18. package/lib/actions/translate.js +32 -26
  19. package/lib/agents/add_bgm_agent.js +15 -39
  20. package/lib/agents/combine_audio_files_agent.js +43 -36
  21. package/lib/agents/index.d.ts +2 -3
  22. package/lib/agents/index.js +2 -3
  23. package/lib/agents/tts_google_agent.d.ts +4 -0
  24. package/lib/agents/tts_google_agent.js +51 -0
  25. package/lib/agents/validate_schema_agent.d.ts +19 -0
  26. package/lib/agents/validate_schema_agent.js +36 -0
  27. package/lib/cli/args.d.ts +2 -0
  28. package/lib/cli/args.js +9 -2
  29. package/lib/cli/bin.d.ts +3 -0
  30. package/lib/cli/bin.js +38 -0
  31. package/lib/cli/cli.js +34 -7
  32. package/lib/cli/commands/audio/builder.d.ts +14 -0
  33. package/lib/cli/commands/audio/builder.js +6 -0
  34. package/lib/cli/commands/audio/handler.d.ts +4 -0
  35. package/lib/cli/commands/audio/handler.js +7 -0
  36. package/lib/cli/commands/audio/index.d.ts +4 -0
  37. package/lib/cli/commands/audio/index.js +4 -0
  38. package/lib/cli/commands/image/builder.d.ts +14 -0
  39. package/lib/cli/commands/image/builder.js +6 -0
  40. package/lib/cli/commands/image/handler.d.ts +4 -0
  41. package/lib/cli/commands/image/handler.js +7 -0
  42. package/lib/cli/commands/image/index.d.ts +4 -0
  43. package/lib/cli/commands/image/index.js +4 -0
  44. package/lib/cli/commands/movie/builder.d.ts +18 -0
  45. package/lib/cli/commands/movie/builder.js +19 -0
  46. package/lib/cli/commands/movie/handler.d.ts +6 -0
  47. package/lib/cli/commands/movie/handler.js +12 -0
  48. package/lib/cli/commands/movie/index.d.ts +4 -0
  49. package/lib/cli/commands/movie/index.js +4 -0
  50. package/lib/cli/commands/pdf/builder.d.ts +18 -0
  51. package/lib/cli/commands/pdf/builder.js +19 -0
  52. package/lib/cli/commands/pdf/handler.d.ts +6 -0
  53. package/lib/cli/commands/pdf/handler.js +8 -0
  54. package/lib/cli/commands/pdf/index.d.ts +4 -0
  55. package/lib/cli/commands/pdf/index.js +4 -0
  56. package/lib/cli/commands/tool/index.d.ts +6 -0
  57. package/lib/cli/commands/tool/index.js +8 -0
  58. package/lib/cli/commands/tool/prompt/builder.d.ts +4 -0
  59. package/lib/cli/commands/tool/prompt/builder.js +11 -0
  60. package/lib/cli/commands/tool/prompt/handler.d.ts +4 -0
  61. package/lib/cli/commands/tool/prompt/handler.js +14 -0
  62. package/lib/cli/commands/tool/prompt/index.d.ts +4 -0
  63. package/lib/cli/commands/tool/prompt/index.js +4 -0
  64. package/lib/cli/commands/tool/schema/builder.d.ts +2 -0
  65. package/lib/cli/commands/tool/schema/builder.js +3 -0
  66. package/lib/cli/commands/tool/schema/handler.d.ts +2 -0
  67. package/lib/cli/commands/tool/schema/handler.js +12 -0
  68. package/lib/cli/commands/tool/schema/index.d.ts +4 -0
  69. package/lib/cli/commands/tool/schema/index.js +4 -0
  70. package/lib/cli/commands/tool/scripting/builder.d.ts +20 -0
  71. package/lib/cli/commands/tool/scripting/builder.js +63 -0
  72. package/lib/cli/commands/tool/scripting/handler.d.ts +12 -0
  73. package/lib/cli/commands/tool/scripting/handler.js +36 -0
  74. package/lib/cli/commands/tool/scripting/index.d.ts +4 -0
  75. package/lib/cli/commands/tool/scripting/index.js +4 -0
  76. package/lib/cli/commands/tool/story_to_script/builder.d.ts +18 -0
  77. package/lib/cli/commands/tool/story_to_script/builder.js +53 -0
  78. package/lib/cli/commands/tool/story_to_script/handler.d.ts +11 -0
  79. package/lib/cli/commands/tool/story_to_script/handler.js +35 -0
  80. package/lib/cli/commands/tool/story_to_script/index.d.ts +4 -0
  81. package/lib/cli/commands/tool/story_to_script/index.js +4 -0
  82. package/lib/cli/commands/translate/builder.d.ts +14 -0
  83. package/lib/cli/commands/translate/builder.js +5 -0
  84. package/lib/cli/commands/translate/handler.d.ts +4 -0
  85. package/lib/cli/commands/translate/handler.js +6 -0
  86. package/lib/cli/commands/translate/index.d.ts +4 -0
  87. package/lib/cli/commands/translate/index.js +4 -0
  88. package/lib/cli/common.d.ts +6 -2
  89. package/lib/cli/common.js +18 -7
  90. package/lib/cli/helpers.d.ts +38 -0
  91. package/lib/cli/helpers.js +115 -0
  92. package/lib/cli/tool-args.d.ts +1 -0
  93. package/lib/cli/tool-args.js +1 -1
  94. package/lib/cli/tool-cli.js +8 -0
  95. package/lib/methods/mulmo_script.d.ts +0 -1
  96. package/lib/methods/mulmo_script.js +4 -7
  97. package/lib/methods/mulmo_script_template.js +2 -12
  98. package/lib/tools/create_mulmo_script_from_url.d.ts +1 -1
  99. package/lib/tools/create_mulmo_script_from_url.js +43 -14
  100. package/lib/tools/create_mulmo_script_interactively.js +14 -13
  101. package/lib/tools/dump_prompt.js +2 -0
  102. package/lib/tools/story_to_script.d.ts +10 -0
  103. package/lib/tools/story_to_script.js +201 -0
  104. package/lib/types/cli_types.d.ts +14 -0
  105. package/lib/types/cli_types.js +1 -0
  106. package/lib/types/schema.d.ts +493 -176
  107. package/lib/types/schema.js +37 -7
  108. package/lib/types/type.d.ts +6 -1
  109. package/lib/utils/const.d.ts +1 -0
  110. package/lib/utils/const.js +1 -0
  111. package/lib/utils/ffmpeg_utils.d.ts +12 -0
  112. package/lib/utils/ffmpeg_utils.js +63 -0
  113. package/lib/utils/file.d.ts +7 -3
  114. package/lib/utils/file.js +24 -5
  115. package/lib/utils/image_plugins/chart.js +6 -1
  116. package/lib/utils/image_plugins/html_tailwind.d.ts +3 -0
  117. package/lib/utils/image_plugins/html_tailwind.js +18 -0
  118. package/lib/utils/image_plugins/index.d.ts +2 -1
  119. package/lib/utils/image_plugins/index.js +2 -1
  120. package/lib/utils/image_plugins/mermaid.js +1 -1
  121. package/lib/utils/image_plugins/tailwind.d.ts +3 -0
  122. package/lib/utils/image_plugins/tailwind.js +18 -0
  123. package/lib/utils/image_plugins/text_slide.js +9 -2
  124. package/lib/utils/markdown.d.ts +1 -1
  125. package/lib/utils/markdown.js +8 -2
  126. package/lib/utils/preprocess.d.ts +23 -12
  127. package/lib/utils/preprocess.js +4 -0
  128. package/lib/utils/prompt.d.ts +15 -0
  129. package/lib/utils/prompt.js +57 -0
  130. package/lib/utils/utils.d.ts +2 -0
  131. package/lib/utils/utils.js +10 -0
  132. package/package.json +27 -23
@@ -1,46 +1,22 @@
1
1
  import { GraphAILogger } from "graphai";
2
- import ffmpeg from "fluent-ffmpeg";
3
- import { MulmoScriptMethods } from "../methods/index.js";
2
+ import { FfmpegContextAddInput, FfmpegContextInit, FfmpegContextGenerateOutput, ffmpegGetMediaDuration } from "../utils/ffmpeg_utils.js";
4
3
  const addBGMAgent = async ({ namedInputs, params, }) => {
5
4
  const { voiceFile, outputFile, script } = namedInputs;
6
5
  const { musicFile } = params;
7
- const promise = new Promise((resolve, reject) => {
8
- ffmpeg.ffprobe(voiceFile, (err, metadata) => {
9
- if (err) {
10
- GraphAILogger.info("Error getting metadata: " + err.message);
11
- reject(err);
12
- }
13
- const speechDuration = metadata.format.duration;
14
- const padding = MulmoScriptMethods.getPadding(script);
15
- const totalDuration = (padding * 2) / 1000 + Math.round(speechDuration ?? 0);
16
- GraphAILogger.log("totalDucation:", speechDuration, totalDuration);
17
- const command = ffmpeg();
18
- command
19
- .input(musicFile)
20
- .input(voiceFile)
21
- .complexFilter([
22
- // Add a 2-second delay to the speech
23
- `[1:a]adelay=${padding}|${padding}, volume=4[a1]`, // 4000ms delay for both left and right channels
24
- // Set the background music volume to 0.2
25
- `[0:a]volume=0.2[a0]`,
26
- // Mix the delayed speech and the background music
27
- `[a0][a1]amix=inputs=2:duration=longest:dropout_transition=3[amixed]`,
28
- // Trim the output to the length of speech + 8 seconds
29
- `[amixed]atrim=start=0:end=${totalDuration}[trimmed]`,
30
- // Add fade out effect for the last 4 seconds
31
- `[trimmed]afade=t=out:st=${totalDuration - padding / 1000}:d=${padding}`,
32
- ])
33
- .on("error", (err) => {
34
- GraphAILogger.info("Error: " + err.message);
35
- reject(err);
36
- })
37
- .on("end", () => {
38
- resolve(0);
39
- })
40
- .save(outputFile);
41
- });
42
- });
43
- await promise;
6
+ const speechDuration = await ffmpegGetMediaDuration(voiceFile);
7
+ const introPadding = script.audioParams.introPadding;
8
+ const outroPadding = script.audioParams.outroPadding;
9
+ const totalDuration = speechDuration + introPadding + outroPadding;
10
+ GraphAILogger.log("totalDucation:", speechDuration, totalDuration);
11
+ const ffmpegContext = FfmpegContextInit();
12
+ const musicInputIndex = FfmpegContextAddInput(ffmpegContext, musicFile);
13
+ const voiceInputIndex = FfmpegContextAddInput(ffmpegContext, voiceFile);
14
+ ffmpegContext.filterComplex.push(`[${musicInputIndex}:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo, volume=0.2[music]`);
15
+ ffmpegContext.filterComplex.push(`[${voiceInputIndex}:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo, volume=2, adelay=${introPadding * 1000}|${introPadding * 1000}[voice]`);
16
+ ffmpegContext.filterComplex.push(`[music][voice]amix=inputs=2:duration=longest[mixed]`);
17
+ ffmpegContext.filterComplex.push(`[mixed]atrim=start=0:end=${totalDuration}[trimmed]`);
18
+ ffmpegContext.filterComplex.push(`[trimmed]afade=t=out:st=${totalDuration - outroPadding}:d=${outroPadding}[faded]`);
19
+ await FfmpegContextGenerateOutput(ffmpegContext, outputFile, ["-map", "[faded]"]);
44
20
  return outputFile;
45
21
  };
46
22
  const addBGMAgentInfo = {
@@ -1,45 +1,52 @@
1
1
  import { GraphAILogger } from "graphai";
2
- import ffmpeg from "fluent-ffmpeg";
3
- import { silentPath, silentLastPath } from "../utils/file.js";
4
- const combineAudioFilesAgent = async ({ namedInputs }) => {
5
- const { context, combinedFileName, audioDirPath } = namedInputs;
6
- const command = ffmpeg();
7
- const getDuration = (filePath, isLastGap) => {
8
- return new Promise((resolve, reject) => {
9
- ffmpeg.ffprobe(filePath, (err, metadata) => {
10
- if (err) {
11
- GraphAILogger.info("Error while getting metadata:", err);
12
- reject(err);
13
- }
14
- else {
15
- // TODO: Remove hard-coded 0.8 and 0.3
16
- resolve(metadata.format.duration + (isLastGap ? 0.8 : 0.3));
17
- }
18
- });
19
- });
20
- };
21
- await Promise.all(context.studio.beats.map(async (studioBeat, index) => {
22
- const isLastGap = index === context.studio.beats.length - 2;
2
+ import { silent60secPath } from "../utils/file.js";
3
+ import { FfmpegContextInit, FfmpegContextGenerateOutput, FfmpegContextInputFormattedAudio, ffmpegGetMediaDuration } from "../utils/ffmpeg_utils.js";
4
+ const combineAudioFilesAgent = async ({ namedInputs, }) => {
5
+ const { context, combinedFileName } = namedInputs;
6
+ const ffmpegContext = FfmpegContextInit();
7
+ const longSilentId = FfmpegContextInputFormattedAudio(ffmpegContext, silent60secPath);
8
+ // We cannot reuse longSilentId. We need to explicitly split it for each beat.
9
+ const silentIds = context.studio.beats.map((_, index) => `[ls_${index}]`);
10
+ ffmpegContext.filterComplex.push(`${longSilentId}asplit=${silentIds.length}${silentIds.join("")}`);
11
+ const inputIds = (await Promise.all(context.studio.beats.map(async (studioBeat, index) => {
12
+ const isClosingGap = index === context.studio.beats.length - 2;
23
13
  if (studioBeat.audioFile) {
24
- command.input(studioBeat.audioFile);
25
- command.input(isLastGap ? silentLastPath : silentPath);
26
- studioBeat.duration = await getDuration(studioBeat.audioFile, isLastGap);
14
+ const audioId = FfmpegContextInputFormattedAudio(ffmpegContext, studioBeat.audioFile);
15
+ const padding = (() => {
16
+ if (index === context.studio.beats.length - 1) {
17
+ return 0;
18
+ }
19
+ return isClosingGap ? context.studio.script.audioParams.closingPadding : context.studio.script.audioParams.padding;
20
+ })();
21
+ studioBeat.duration = (await ffmpegGetMediaDuration(studioBeat.audioFile)) + padding;
22
+ if (padding > 0) {
23
+ const silentId = silentIds.pop();
24
+ ffmpegContext.filterComplex.push(`${silentId}atrim=start=0:end=${padding}[padding_${index}]`);
25
+ return [audioId, `[padding_${index}]`];
26
+ }
27
+ else {
28
+ return [audioId];
29
+ }
27
30
  }
28
31
  else {
29
- GraphAILogger.error("Missing studioBeat.audioFile:", index);
32
+ // NOTE: We come here when the text is empty and no audio property is specified.
33
+ studioBeat.duration = context.studio.script.beats[index].duration ?? 1.0;
34
+ const silentId = silentIds.pop();
35
+ ffmpegContext.filterComplex.push(`${silentId}atrim=start=0:end=${studioBeat.duration}[silent_${index}]`);
36
+ return [`[silent_${index}]`];
30
37
  }
31
- }));
32
- await new Promise((resolve, reject) => {
33
- command
34
- .on("end", () => {
35
- resolve(0);
36
- })
37
- .on("error", (err) => {
38
- GraphAILogger.info("Error while combining MP3 files:", err);
39
- reject(err);
40
- })
41
- .mergeToFile(combinedFileName, audioDirPath);
38
+ }))).flat();
39
+ // HACK: Because the last beat may not use an silent audio, we need to consume it to make ffmpeg happy.
40
+ if (silentIds.length > 1) {
41
+ throw new Error("UNEXPECTED: silentIds.length > 1");
42
+ }
43
+ silentIds.forEach((silentId) => {
44
+ GraphAILogger.log(`Using extra silentId: ${silentId}`);
45
+ ffmpegContext.filterComplex.push(`${silentId}atrim=start=0:end=${0.01}[silent_extra]`);
46
+ inputIds.push("[silent_extra]");
42
47
  });
48
+ ffmpegContext.filterComplex.push(`${inputIds.join("")}concat=n=${inputIds.length}:v=0:a=1[aout]`);
49
+ await FfmpegContextGenerateOutput(ffmpegContext, combinedFileName, ["-map", "[aout]"]);
43
50
  return {
44
51
  studio: context.studio,
45
52
  };
@@ -2,12 +2,11 @@ import addBGMAgent from "./add_bgm_agent.js";
2
2
  import combineAudioFilesAgent from "./combine_audio_files_agent.js";
3
3
  import imageGoogleAgent from "./image_google_agent.js";
4
4
  import imageOpenaiAgent from "./image_openai_agent.js";
5
- import mulmoPromptsAgent from "./mulmo_prompts_agent.js";
6
5
  import ttsNijivoiceAgent from "./tts_nijivoice_agent.js";
7
6
  import ttsOpenaiAgent from "./tts_openai_agent.js";
8
- import validateMulmoScriptAgent from "./validate_mulmo_script_agent.js";
7
+ import validateSchemaAgent from "./validate_schema_agent.js";
9
8
  import { browserlessAgent } from "@graphai/browserless_agent";
10
9
  import { textInputAgent } from "@graphai/input_agents";
11
10
  import { openAIAgent } from "@graphai/openai_agent";
12
11
  import { fileWriteAgent } from "@graphai/vanilla_node_agents";
13
- export { openAIAgent, fileWriteAgent, browserlessAgent, textInputAgent, addBGMAgent, combineAudioFilesAgent, imageGoogleAgent, imageOpenaiAgent, mulmoPromptsAgent, ttsNijivoiceAgent, ttsOpenaiAgent, validateMulmoScriptAgent, };
12
+ export { openAIAgent, fileWriteAgent, browserlessAgent, textInputAgent, addBGMAgent, combineAudioFilesAgent, imageGoogleAgent, imageOpenaiAgent, ttsNijivoiceAgent, ttsOpenaiAgent, validateSchemaAgent, };
@@ -2,13 +2,12 @@ import addBGMAgent from "./add_bgm_agent.js";
2
2
  import combineAudioFilesAgent from "./combine_audio_files_agent.js";
3
3
  import imageGoogleAgent from "./image_google_agent.js";
4
4
  import imageOpenaiAgent from "./image_openai_agent.js";
5
- import mulmoPromptsAgent from "./mulmo_prompts_agent.js";
6
5
  import ttsNijivoiceAgent from "./tts_nijivoice_agent.js";
7
6
  import ttsOpenaiAgent from "./tts_openai_agent.js";
8
- import validateMulmoScriptAgent from "./validate_mulmo_script_agent.js";
7
+ import validateSchemaAgent from "./validate_schema_agent.js";
9
8
  import { browserlessAgent } from "@graphai/browserless_agent";
10
9
  import { textInputAgent } from "@graphai/input_agents";
11
10
  import { openAIAgent } from "@graphai/openai_agent";
12
11
  // import * as vanilla from "@graphai/vanilla";
13
12
  import { fileWriteAgent } from "@graphai/vanilla_node_agents";
14
- export { openAIAgent, fileWriteAgent, browserlessAgent, textInputAgent, addBGMAgent, combineAudioFilesAgent, imageGoogleAgent, imageOpenaiAgent, mulmoPromptsAgent, ttsNijivoiceAgent, ttsOpenaiAgent, validateMulmoScriptAgent, };
13
+ export { openAIAgent, fileWriteAgent, browserlessAgent, textInputAgent, addBGMAgent, combineAudioFilesAgent, imageGoogleAgent, imageOpenaiAgent, ttsNijivoiceAgent, ttsOpenaiAgent, validateSchemaAgent, };
@@ -0,0 +1,4 @@
1
+ import type { AgentFunction, AgentFunctionInfo } from "graphai";
2
+ export declare const ttsGoogleAgent: AgentFunction;
3
+ declare const ttsGoogleAgentInfo: AgentFunctionInfo;
4
+ export default ttsGoogleAgentInfo;
@@ -0,0 +1,51 @@
1
+ import { GraphAILogger } from "graphai";
2
+ import * as textToSpeech from "@google-cloud/text-to-speech";
3
+ const client = new textToSpeech.TextToSpeechClient();
4
+ export const ttsGoogleAgent = async ({ namedInputs, params }) => {
5
+ const { text } = namedInputs;
6
+ const { voice, suppressError, speed } = params;
7
+ // Construct the voice request
8
+ const voiceParams = {
9
+ languageCode: "en-US", // TODO: Make this configurable
10
+ ssmlGender: "FEMALE", // TODO: Make this configurable
11
+ };
12
+ if (voice) {
13
+ voiceParams.name = voice;
14
+ }
15
+ // Construct the request
16
+ const request = {
17
+ input: { text: text },
18
+ voice: voiceParams,
19
+ audioConfig: {
20
+ audioEncoding: "MP3",
21
+ speakingRate: speed || 1.0,
22
+ },
23
+ };
24
+ try {
25
+ // Call the Text-to-Speech API
26
+ const [response] = await client.synthesizeSpeech(request);
27
+ return { buffer: response.audioContent };
28
+ }
29
+ catch (e) {
30
+ if (suppressError) {
31
+ return {
32
+ error: e,
33
+ };
34
+ }
35
+ GraphAILogger.info(e);
36
+ throw new Error("TTS Google Error");
37
+ }
38
+ };
39
+ const ttsGoogleAgentInfo = {
40
+ name: "ttsGoogleAgent",
41
+ agent: ttsGoogleAgent,
42
+ mock: ttsGoogleAgent,
43
+ samples: [],
44
+ description: "Google TTS agent",
45
+ category: ["tts"],
46
+ author: "Receptron Team",
47
+ repository: "https://github.com/receptron/graphai-agents/tree/main/tts/tts-openai-agent",
48
+ license: "MIT",
49
+ environmentVariables: ["OPENAI_API_KEY"],
50
+ };
51
+ export default ttsGoogleAgentInfo;
@@ -0,0 +1,19 @@
1
+ import type { AgentFunction, AgentFunctionInfo, DefaultConfigData } from "graphai";
2
+ import { MulmoScript } from "../types/index.js";
3
+ import { ZodSchema } from "zod";
4
+ interface ValidateMulmoScriptInputs {
5
+ text: string;
6
+ schema: ZodSchema;
7
+ }
8
+ interface ValidateMulmoScriptResponse {
9
+ isValid: boolean;
10
+ data?: MulmoScript;
11
+ error?: string;
12
+ }
13
+ /**
14
+ * Zod schema validation agent
15
+ * Validates if a JSON string conforms to the Zod schema
16
+ */
17
+ export declare const validateSchemaAgent: AgentFunction<object, ValidateMulmoScriptResponse, ValidateMulmoScriptInputs, DefaultConfigData>;
18
+ declare const validateMulmoScriptAgentInfo: AgentFunctionInfo;
19
+ export default validateMulmoScriptAgentInfo;
@@ -0,0 +1,36 @@
1
+ import assert from "node:assert";
2
+ /**
3
+ * Zod schema validation agent
4
+ * Validates if a JSON string conforms to the Zod schema
5
+ */
6
+ export const validateSchemaAgent = async ({ namedInputs, }) => {
7
+ const { text, schema } = namedInputs;
8
+ try {
9
+ assert(schema, "schema is required");
10
+ assert(text, "text is required");
11
+ const jsonData = JSON.parse(text);
12
+ const parsed = schema.parse(jsonData);
13
+ return {
14
+ isValid: true,
15
+ data: parsed,
16
+ };
17
+ }
18
+ catch (error) {
19
+ return {
20
+ isValid: false,
21
+ error: error instanceof Error ? error.message : String(error),
22
+ };
23
+ }
24
+ };
25
+ const validateMulmoScriptAgentInfo = {
26
+ name: "validateSchemaAgent",
27
+ agent: validateSchemaAgent,
28
+ mock: validateSchemaAgent,
29
+ samples: [],
30
+ description: "Validates if a JSON string conforms to the Zod schema",
31
+ category: ["validation"],
32
+ author: "Receptron Team",
33
+ repository: "https://github.com/receptron/mulmocast-cli/tree/main/src/agents/validate_schema_agent.ts",
34
+ license: "MIT",
35
+ };
36
+ export default validateMulmoScriptAgentInfo;
package/lib/cli/args.d.ts CHANGED
@@ -3,9 +3,11 @@ export declare const getArgs: () => {
3
3
  v: boolean;
4
4
  o: string | undefined;
5
5
  b: string | undefined;
6
+ l: string | undefined;
6
7
  a: string | undefined;
7
8
  i: string | undefined;
8
9
  f: boolean;
10
+ c: string | undefined;
9
11
  pdf_mode: string;
10
12
  pdf_size: string;
11
13
  _: (string | number)[];
package/lib/cli/args.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import yargs from "yargs";
2
2
  import { hideBin } from "yargs/helpers";
3
3
  import { commonOptions } from "./common.js";
4
- import { pdf_modes, pdf_sizes } from "../utils/const.js";
4
+ import { pdf_modes, pdf_sizes, languages } from "../utils/const.js";
5
5
  export const getArgs = () => {
6
6
  return commonOptions(yargs(hideBin(process.argv)))
7
7
  .scriptName("mulmo")
@@ -23,6 +23,13 @@ export const getArgs = () => {
23
23
  demandOption: false,
24
24
  default: false,
25
25
  type: "boolean",
26
+ })
27
+ .option("c", {
28
+ alias: "caption",
29
+ description: "Video captions",
30
+ choices: languages,
31
+ demandOption: false,
32
+ type: "string",
26
33
  })
27
34
  .option("pdf_mode", {
28
35
  description: "pdf mode",
@@ -40,7 +47,7 @@ export const getArgs = () => {
40
47
  return yargs
41
48
  .positional("action", {
42
49
  describe: "action to perform",
43
- choices: ["translate", "audio", "images", "movie", "pdf", "preprocess"],
50
+ choices: ["translate", "audio", "images", "movie", "pdf"],
44
51
  type: "string",
45
52
  })
46
53
  .positional("file", {
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ export declare const main: () => Promise<void>;
package/lib/cli/bin.js ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import yargs from "yargs/yargs";
4
+ import { hideBin } from "yargs/helpers";
5
+ import * as translateCmd from "./commands/translate/index.js";
6
+ import * as audioCmd from "./commands/audio/index.js";
7
+ import * as imagesCmd from "./commands/image/index.js";
8
+ import * as movieCmd from "./commands/movie/index.js";
9
+ import * as pdfCmd from "./commands/pdf/index.js";
10
+ import * as toolCmd from "./commands/tool/index.js";
11
+ import { GraphAILogger } from "graphai";
12
+ export const main = async () => {
13
+ const cli = yargs(hideBin(process.argv))
14
+ .scriptName("mulmo")
15
+ .usage("$0 <command> [options]")
16
+ .option("v", {
17
+ alias: "verbose",
18
+ describe: "verbose log",
19
+ demandOption: true,
20
+ default: false,
21
+ type: "boolean",
22
+ })
23
+ .command(translateCmd)
24
+ .command(audioCmd)
25
+ .command(imagesCmd)
26
+ .command(movieCmd)
27
+ .command(pdfCmd)
28
+ .command(toolCmd)
29
+ .demandCommand()
30
+ .strict()
31
+ .help()
32
+ .alias("help", "h");
33
+ await cli.parseAsync();
34
+ };
35
+ main().catch((error) => {
36
+ GraphAILogger.info("An unexpected error occurred:", error);
37
+ process.exit(1);
38
+ });
package/lib/cli/cli.js CHANGED
@@ -6,18 +6,32 @@ import { GraphAILogger } from "graphai";
6
6
  import { getArgs } from "./args.js";
7
7
  import { createOrUpdateStudioData } from "../utils/preprocess.js";
8
8
  import { outDirName, imageDirName, audioDirName } from "../utils/const.js";
9
- import { translate, audio, images, movie, pdf } from "../actions/index.js";
10
- import { getBaseDirPath, getFullPath, readMulmoScriptFile, fetchMulmoScriptFile, getOutputStudioFilePath } from "../utils/file.js";
9
+ import { translate, audio, images, movie, pdf, captions } from "../actions/index.js";
10
+ import { getBaseDirPath, getFullPath, readMulmoScriptFile, fetchMulmoScriptFile, getOutputStudioFilePath, resolveDirPath } from "../utils/file.js";
11
11
  import { isHttp } from "../utils/utils.js";
12
+ import clipboardy from "clipboardy";
12
13
  export const getFileObject = (_args) => {
13
14
  const { basedir, file, outdir, imagedir, audiodir } = _args;
14
15
  const baseDirPath = getBaseDirPath(basedir);
15
- const fileOrUrl = file ?? "";
16
- const fileName = path.parse(fileOrUrl).name;
16
+ const outDirPath = getFullPath(baseDirPath, outdir ?? outDirName);
17
+ const { fileOrUrl, fileName } = (() => {
18
+ if (file === "__clipboard") {
19
+ // We generate a new unique script file from clipboard text in the output directory
20
+ const now = new Date();
21
+ const pad = (n) => n.toString().padStart(2, "0");
22
+ const fileName = `script_${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
23
+ const clipboardText = clipboardy.readSync();
24
+ const fileOrUrl = resolveDirPath(outDirPath, `${fileName}.json`);
25
+ fs.writeFileSync(fileOrUrl, clipboardText, "utf8");
26
+ return { fileOrUrl, fileName };
27
+ }
28
+ const fileOrUrl = file ?? "";
29
+ const fileName = path.parse(fileOrUrl).name;
30
+ return { fileOrUrl, fileName };
31
+ })();
17
32
  const isHttpPath = isHttp(fileOrUrl);
18
33
  const mulmoFilePath = isHttpPath ? "" : getFullPath(baseDirPath, fileOrUrl);
19
34
  const mulmoFileDirPath = path.dirname(isHttpPath ? baseDirPath : mulmoFilePath);
20
- const outDirPath = getFullPath(baseDirPath, outdir ?? outDirName);
21
35
  const imageDirPath = getFullPath(outDirPath, imagedir ?? imageDirName);
22
36
  const audioDirPath = getFullPath(outDirPath, audiodir ?? audioDirName);
23
37
  const outputStudioFilePath = getOutputStudioFilePath(outDirPath, fileName);
@@ -50,7 +64,7 @@ export const main = async () => {
50
64
  GraphAILogger.setLevelEnabled("log", false);
51
65
  GraphAILogger.setLevelEnabled("warn", false);
52
66
  }
53
- const { action, force, pdf_mode, pdf_size } = args;
67
+ const { action, f: force, pdf_mode, pdf_size, l: lang, c: caption } = args;
54
68
  const mulmoScript = await fetchScript(isHttpPath, mulmoFilePath, fileOrUrl);
55
69
  // Create or update MulmoStudio file with MulmoScript
56
70
  const currentStudio = readMulmoScriptFile(outputStudioFilePath);
@@ -68,8 +82,11 @@ export const main = async () => {
68
82
  studio,
69
83
  fileDirs: files,
70
84
  force: Boolean(force),
85
+ lang: lang,
86
+ caption: caption,
71
87
  };
72
- if (action === "translate") {
88
+ if (action === "translate" || lang || caption) {
89
+ GraphAILogger.log("run translate");
73
90
  await translate(context);
74
91
  }
75
92
  if (action === "audio") {
@@ -79,6 +96,9 @@ export const main = async () => {
79
96
  await images(context);
80
97
  }
81
98
  if (action === "movie") {
99
+ if (caption) {
100
+ await captions(context);
101
+ }
82
102
  await audio(context);
83
103
  await images(context);
84
104
  await movie(context);
@@ -87,4 +107,11 @@ export const main = async () => {
87
107
  await images(context);
88
108
  await pdf(context, pdf_mode, pdf_size);
89
109
  }
110
+ if (context.studio.script.title) {
111
+ GraphAILogger.info(context.studio.script.title);
112
+ }
113
+ if (context.studio.script.references) {
114
+ const textOutput = context.studio.script.references.map((reference) => `${reference.title}\n${reference.url}`).join("\n");
115
+ GraphAILogger.info(textOutput);
116
+ }
90
117
  };
@@ -0,0 +1,14 @@
1
+ import type { Argv } from "yargs";
2
+ export declare const builder: (yargs: Argv) => Argv<{
3
+ o: string | undefined;
4
+ } & {
5
+ b: string | undefined;
6
+ } & {
7
+ l: string | undefined;
8
+ } & {
9
+ f: boolean;
10
+ } & {
11
+ file: string | undefined;
12
+ } & {
13
+ a: string | undefined;
14
+ }>;
@@ -0,0 +1,6 @@
1
+ import { commonOptions } from "../../common.js";
2
+ export const builder = (yargs) => commonOptions(yargs).option("a", {
3
+ alias: "audiodir",
4
+ describe: "Audio output directory",
5
+ type: "string",
6
+ });
@@ -0,0 +1,4 @@
1
+ import { CliArgs } from "../../../types/cli_types.js";
2
+ export declare const handler: (argv: CliArgs<{
3
+ a?: string;
4
+ }>) => Promise<void>;
@@ -0,0 +1,7 @@
1
+ import { audio } from "../../../actions/index.js";
2
+ import { initializeContext, runTranslateIfNeeded } from "../../../cli/helpers.js";
3
+ export const handler = async (argv) => {
4
+ const context = await initializeContext(argv);
5
+ await runTranslateIfNeeded(context, argv);
6
+ await audio(context);
7
+ };
@@ -0,0 +1,4 @@
1
+ export declare const command = "audio <file>";
2
+ export declare const desc = "Generate audio files";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -0,0 +1,4 @@
1
+ export const command = "audio <file>";
2
+ export const desc = "Generate audio files";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -0,0 +1,14 @@
1
+ import type { Argv } from "yargs";
2
+ export declare const builder: (yargs: Argv) => Argv<{
3
+ o: string | undefined;
4
+ } & {
5
+ b: string | undefined;
6
+ } & {
7
+ l: string | undefined;
8
+ } & {
9
+ f: boolean;
10
+ } & {
11
+ file: string | undefined;
12
+ } & {
13
+ i: string | undefined;
14
+ }>;
@@ -0,0 +1,6 @@
1
+ import { commonOptions } from "../../common.js";
2
+ export const builder = (yargs) => commonOptions(yargs).option("i", {
3
+ alias: "imagedir",
4
+ describe: "Image output directory",
5
+ type: "string",
6
+ });
@@ -0,0 +1,4 @@
1
+ import { CliArgs } from "../../../types/cli_types.js";
2
+ export declare const handler: (argv: CliArgs<{
3
+ i?: string;
4
+ }>) => Promise<void>;
@@ -0,0 +1,7 @@
1
+ import { images } from "../../../actions/index.js";
2
+ import { initializeContext, runTranslateIfNeeded } from "../../../cli/helpers.js";
3
+ export const handler = async (argv) => {
4
+ const context = await initializeContext(argv);
5
+ await runTranslateIfNeeded(context, argv);
6
+ await images(context);
7
+ };
@@ -0,0 +1,4 @@
1
+ export declare const command = "images <file>";
2
+ export declare const desc = "Generate image files";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -0,0 +1,4 @@
1
+ export const command = "images <file>";
2
+ export const desc = "Generate image files";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -0,0 +1,18 @@
1
+ import type { Argv } from "yargs";
2
+ export declare const builder: (yargs: Argv) => Argv<{
3
+ o: string | undefined;
4
+ } & {
5
+ b: string | undefined;
6
+ } & {
7
+ l: string | undefined;
8
+ } & {
9
+ f: boolean;
10
+ } & {
11
+ file: string | undefined;
12
+ } & {
13
+ a: string | undefined;
14
+ } & {
15
+ i: string | undefined;
16
+ } & {
17
+ c: string | undefined;
18
+ }>;
@@ -0,0 +1,19 @@
1
+ import { commonOptions } from "../../common.js";
2
+ import { languages } from "../../../utils/const.js";
3
+ export const builder = (yargs) => commonOptions(yargs)
4
+ .option("a", {
5
+ alias: "audiodir",
6
+ describe: "Audio output directory",
7
+ type: "string",
8
+ })
9
+ .option("i", {
10
+ alias: "imagedir",
11
+ describe: "Image output directory",
12
+ type: "string",
13
+ })
14
+ .option("c", {
15
+ alias: "caption",
16
+ describe: "Video captions",
17
+ choices: languages,
18
+ type: "string",
19
+ });
@@ -0,0 +1,6 @@
1
+ import { CliArgs } from "../../../types/cli_types.js";
2
+ export declare const handler: (argv: CliArgs<{
3
+ a?: string;
4
+ i?: string;
5
+ c?: string;
6
+ }>) => Promise<void>;
@@ -0,0 +1,12 @@
1
+ import { audio, images, movie, captions } from "../../../actions/index.js";
2
+ import { initializeContext, runTranslateIfNeeded } from "../../../cli/helpers.js";
3
+ export const handler = async (argv) => {
4
+ const context = await initializeContext(argv);
5
+ await runTranslateIfNeeded(context, argv);
6
+ await audio(context);
7
+ await images(context);
8
+ if (context.caption) {
9
+ await captions(context);
10
+ }
11
+ await movie(context);
12
+ };
@@ -0,0 +1,4 @@
1
+ export declare const command = "movie <file>";
2
+ export declare const desc = "Generate movie file";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";