mulmocast 0.0.1

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 (83) hide show
  1. package/README.md +74 -0
  2. package/assets/audio/silent300.mp3 +0 -0
  3. package/assets/audio/silent800.mp3 +0 -0
  4. package/assets/music/StarsBeyondEx.mp3 +0 -0
  5. package/assets/templates/business.json +89 -0
  6. package/assets/templates/children_book.json +135 -0
  7. package/assets/templates/podcast_standard.json +5 -0
  8. package/assets/templates/sensei_and_taro.json +123 -0
  9. package/lib/actions/audio.d.ts +3 -0
  10. package/lib/actions/audio.js +186 -0
  11. package/lib/actions/images.d.ts +2 -0
  12. package/lib/actions/images.js +211 -0
  13. package/lib/actions/movie.d.ts +2 -0
  14. package/lib/actions/movie.js +81 -0
  15. package/lib/actions/translate.d.ts +3 -0
  16. package/lib/actions/translate.js +236 -0
  17. package/lib/agents/add_bgm_agent.d.ts +3 -0
  18. package/lib/agents/add_bgm_agent.js +61 -0
  19. package/lib/agents/combine_audio_files_agent.d.ts +3 -0
  20. package/lib/agents/combine_audio_files_agent.js +57 -0
  21. package/lib/agents/image_google_agent.d.ts +15 -0
  22. package/lib/agents/image_google_agent.js +88 -0
  23. package/lib/agents/image_openai_agent.d.ts +15 -0
  24. package/lib/agents/image_openai_agent.js +59 -0
  25. package/lib/agents/index.d.ts +13 -0
  26. package/lib/agents/index.js +31 -0
  27. package/lib/agents/mulmo_prompts_agent.d.ts +7 -0
  28. package/lib/agents/mulmo_prompts_agent.js +41 -0
  29. package/lib/agents/prompts_data.d.ts +15 -0
  30. package/lib/agents/prompts_data.js +19 -0
  31. package/lib/agents/tts_nijivoice_agent.d.ts +4 -0
  32. package/lib/agents/tts_nijivoice_agent.js +68 -0
  33. package/lib/agents/tts_openai_agent.d.ts +4 -0
  34. package/lib/agents/tts_openai_agent.js +50 -0
  35. package/lib/agents/validate_mulmo_script_agent.d.ts +17 -0
  36. package/lib/agents/validate_mulmo_script_agent.js +38 -0
  37. package/lib/cli/args.d.ts +10 -0
  38. package/lib/cli/args.js +38 -0
  39. package/lib/cli/cli.d.ts +2 -0
  40. package/lib/cli/cli.js +78 -0
  41. package/lib/cli/common.d.ts +8 -0
  42. package/lib/cli/common.js +26 -0
  43. package/lib/cli/tool-args.d.ts +12 -0
  44. package/lib/cli/tool-args.js +53 -0
  45. package/lib/cli/tool-cli.d.ts +2 -0
  46. package/lib/cli/tool-cli.js +78 -0
  47. package/lib/methods/index.d.ts +3 -0
  48. package/lib/methods/index.js +19 -0
  49. package/lib/methods/mulmo_script.d.ts +11 -0
  50. package/lib/methods/mulmo_script.js +45 -0
  51. package/lib/methods/mulmo_script_template.d.ts +4 -0
  52. package/lib/methods/mulmo_script_template.js +22 -0
  53. package/lib/methods/mulmo_studio_context.d.ts +4 -0
  54. package/lib/methods/mulmo_studio_context.js +12 -0
  55. package/lib/tools/dump_prompt.d.ts +3 -0
  56. package/lib/tools/dump_prompt.js +9 -0
  57. package/lib/tools/prompt.d.ts +1 -0
  58. package/lib/tools/prompt.js +20 -0
  59. package/lib/tools/seed.d.ts +3 -0
  60. package/lib/tools/seed.js +201 -0
  61. package/lib/tools/seed_from_url.d.ts +3 -0
  62. package/lib/tools/seed_from_url.js +178 -0
  63. package/lib/types/index.d.ts +1 -0
  64. package/lib/types/index.js +17 -0
  65. package/lib/types/schema.d.ts +5817 -0
  66. package/lib/types/schema.js +207 -0
  67. package/lib/types/type.d.ts +33 -0
  68. package/lib/types/type.js +2 -0
  69. package/lib/utils/const.d.ts +3 -0
  70. package/lib/utils/const.js +6 -0
  71. package/lib/utils/file.d.ts +28 -0
  72. package/lib/utils/file.js +112 -0
  73. package/lib/utils/filters.d.ts +3 -0
  74. package/lib/utils/filters.js +32 -0
  75. package/lib/utils/markdown.d.ts +1 -0
  76. package/lib/utils/markdown.js +27 -0
  77. package/lib/utils/preprocess.d.ts +247 -0
  78. package/lib/utils/preprocess.js +53 -0
  79. package/lib/utils/string.d.ts +9 -0
  80. package/lib/utils/string.js +60 -0
  81. package/lib/utils/text_hash.d.ts +1 -0
  82. package/lib/utils/text_hash.js +41 -0
  83. package/package.json +77 -0
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.images = void 0;
40
+ const dotenv_1 = __importDefault(require("dotenv"));
41
+ const graphai_1 = require("graphai");
42
+ const agents = __importStar(require("@graphai/vanilla"));
43
+ const vanilla_node_agents_1 = require("@graphai/vanilla_node_agents");
44
+ const file_1 = require("../utils/file");
45
+ const filters_1 = require("../utils/filters");
46
+ const markdown_1 = require("../utils/markdown");
47
+ const image_google_agent_1 = __importDefault(require("../agents/image_google_agent"));
48
+ const image_openai_agent_1 = __importDefault(require("../agents/image_openai_agent"));
49
+ const methods_1 = require("../methods");
50
+ dotenv_1.default.config();
51
+ // const openai = new OpenAI();
52
+ const google_auth_library_1 = require("google-auth-library");
53
+ const preprocess_agent = async (namedInputs) => {
54
+ const { context, beat, index, suffix, imageDirPath } = namedInputs;
55
+ const imageParams = { ...context.studio.script.imageParams, ...beat.imageParams };
56
+ const prompt = (beat.imagePrompt || beat.text) + "\n" + (imageParams.style || "");
57
+ const imagePath = `${imageDirPath}/${context.studio.filename}/${index}${suffix}.png`;
58
+ const aspectRatio = methods_1.MulmoScriptMethods.getAspectRatio(context.studio.script);
59
+ if (beat.image) {
60
+ if (beat.image.type === "textSlide") {
61
+ const slide = beat.image.slide;
62
+ const markdown = `# ${slide.title}` + slide.bullets.map((text) => `- ${text}`).join("\n");
63
+ await (0, markdown_1.convertMarkdownToImage)(markdown, methods_1.MulmoScriptMethods.getTextSlideStyle(context.studio.script, beat), imagePath);
64
+ }
65
+ else if (beat.image.type === "markdown") {
66
+ const markdown = Array.isArray(beat.image.markdown) ? beat.image.markdown.join("\n") : beat.image.markdown;
67
+ await (0, markdown_1.convertMarkdownToImage)(markdown, methods_1.MulmoScriptMethods.getTextSlideStyle(context.studio.script, beat), imagePath);
68
+ }
69
+ else if (beat.image.type === "image") {
70
+ if (beat.image.source.kind === "url") {
71
+ // undefined prompt indicates "no need to generate image"
72
+ return { path: beat.image.source.url, prompt: undefined, imageParams, aspectRatio };
73
+ }
74
+ else if (beat.image.source.kind === "path") {
75
+ const path = methods_1.MulmoStudioContextMethods.resolveAssetPath(context, beat.image.source.path);
76
+ return { path, prompt: undefined, imageParams, aspectRatio };
77
+ }
78
+ }
79
+ }
80
+ return { path: imagePath, prompt, imageParams, aspectRatio };
81
+ };
82
+ const graph_data = {
83
+ version: 0.5,
84
+ concurrency: 2,
85
+ nodes: {
86
+ context: {},
87
+ imageDirPath: {},
88
+ text2imageAgent: {},
89
+ outputStudioFilePath: {},
90
+ map: {
91
+ agent: "mapAgent",
92
+ inputs: { rows: ":context.studio.beats", context: ":context", text2imageAgent: ":text2imageAgent", imageDirPath: ":imageDirPath" },
93
+ isResult: true,
94
+ params: {
95
+ rowKey: "beat",
96
+ compositeResult: true,
97
+ },
98
+ graph: {
99
+ nodes: {
100
+ preprocessor: {
101
+ agent: preprocess_agent,
102
+ inputs: {
103
+ context: ":context",
104
+ beat: ":beat",
105
+ index: ":__mapIndex",
106
+ suffix: "p",
107
+ imageDirPath: ":imageDirPath",
108
+ },
109
+ },
110
+ imageGenerator: {
111
+ if: ":preprocessor.prompt",
112
+ agent: ":text2imageAgent",
113
+ params: {
114
+ model: ":preprocessor.imageParams.model",
115
+ size: ":preprocessor.imageParams.size",
116
+ moderation: ":preprocessor.imageParams.moderation",
117
+ aspectRatio: ":preprocessor.aspectRatio",
118
+ },
119
+ inputs: {
120
+ prompt: ":preprocessor.prompt",
121
+ file: ":preprocessor.path", // only for fileCacheAgentFilter
122
+ text: ":preprocessor.prompt", // only for fileCacheAgentFilter
123
+ },
124
+ defaultValue: {},
125
+ },
126
+ output: {
127
+ agent: "copyAgent",
128
+ inputs: {
129
+ result: ":imageGenerator",
130
+ image: ":preprocessor.path",
131
+ },
132
+ output: {
133
+ imageFile: ".image",
134
+ },
135
+ isResult: true,
136
+ },
137
+ },
138
+ },
139
+ },
140
+ mergeResult: {
141
+ agent: (namedInputs) => {
142
+ const { array, context } = namedInputs;
143
+ const { studio } = context;
144
+ array.forEach((update, index) => {
145
+ const beat = studio.beats[index];
146
+ studio.beats[index] = { ...beat, ...update };
147
+ });
148
+ // console.log(namedInputs);
149
+ return { studio };
150
+ },
151
+ inputs: {
152
+ array: ":map.output",
153
+ context: ":context",
154
+ },
155
+ },
156
+ writeOutout: {
157
+ // console: { before: true },
158
+ agent: "fileWriteAgent",
159
+ inputs: {
160
+ file: ":outputStudioFilePath",
161
+ text: ":mergeResult.studio.toJSON()",
162
+ },
163
+ },
164
+ },
165
+ };
166
+ const googleAuth = async () => {
167
+ const auth = new google_auth_library_1.GoogleAuth({
168
+ scopes: ["https://www.googleapis.com/auth/cloud-platform"],
169
+ });
170
+ const client = await auth.getClient();
171
+ const accessToken = await client.getAccessToken();
172
+ return accessToken.token;
173
+ };
174
+ const images = async (context) => {
175
+ const { studio, fileDirs } = context;
176
+ const { outDirPath, imageDirPath } = fileDirs;
177
+ (0, file_1.mkdir)(`${imageDirPath}/${studio.filename}`);
178
+ const agentFilters = [
179
+ {
180
+ name: "fileCacheAgentFilter",
181
+ agent: filters_1.fileCacheAgentFilter,
182
+ nodeIds: ["imageGenerator"],
183
+ },
184
+ ];
185
+ const options = {
186
+ agentFilters,
187
+ };
188
+ // We need to get google's auth token only if the google is the text2image provider.
189
+ if (methods_1.MulmoScriptMethods.getImageProvider(studio.script) === "google") {
190
+ console.log("google was specified as text2image engine");
191
+ const token = await googleAuth();
192
+ options.config = {
193
+ imageGoogleAgent: {
194
+ projectId: process.env.GOOGLE_PROJECT_ID,
195
+ token,
196
+ },
197
+ };
198
+ }
199
+ const injections = {
200
+ context,
201
+ text2imageAgent: methods_1.MulmoScriptMethods.getText2imageAgent(studio.script),
202
+ outputStudioFilePath: (0, file_1.getOutputStudioFilePath)(outDirPath, studio.filename),
203
+ imageDirPath,
204
+ };
205
+ const graph = new graphai_1.GraphAI(graph_data, { ...agents, imageGoogleAgent: image_google_agent_1.default, imageOpenaiAgent: image_openai_agent_1.default, fileWriteAgent: vanilla_node_agents_1.fileWriteAgent }, options);
206
+ Object.keys(injections).forEach((key) => {
207
+ graph.injectValue(key, injections[key]);
208
+ });
209
+ await graph.run();
210
+ };
211
+ exports.images = images;
@@ -0,0 +1,2 @@
1
+ import { MulmoStudioContext } from "../types";
2
+ export declare const movie: (context: MulmoStudioContext) => Promise<void>;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.movie = void 0;
7
+ const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
8
+ const methods_1 = require("../methods");
9
+ const file_1 = require("../utils/file");
10
+ const createVideo = (audioPath, outputVideoPath, studio) => {
11
+ const start = performance.now();
12
+ let command = (0, fluent_ffmpeg_1.default)();
13
+ if (studio.beats.some((beat) => !beat.imageFile)) {
14
+ console.error("beat.imageFile is not set. Please run `yarn run images ${file}` ");
15
+ return;
16
+ }
17
+ // Add each image input
18
+ studio.beats.forEach((beat) => {
19
+ command = command.input(beat.imageFile); // HACK
20
+ });
21
+ const imageCount = studio.beats.length;
22
+ const canvasInfo = methods_1.MulmoScriptMethods.getCanvasSize(studio.script);
23
+ const filterComplexParts = [];
24
+ studio.beats.forEach((beat, index) => {
25
+ // Resize background image to match canvas dimensions
26
+ const duration = beat.duration + (index === 0 ? methods_1.MulmoScriptMethods.getPadding(studio.script) / 1000 : 0);
27
+ const parts = `[${index}:v]loop=loop=-1:size=1:start=0,` +
28
+ `trim=duration=${duration},` +
29
+ `fps=30,` +
30
+ `setpts=PTS-STARTPTS,` +
31
+ `scale=${canvasInfo.width}:${canvasInfo.height},` +
32
+ `setsar=1,format=yuv420p` +
33
+ `[v${index}]`;
34
+ // console.log(parts);
35
+ filterComplexParts.push(parts);
36
+ });
37
+ // Concatenate the trimmed images
38
+ const concatInput = studio.beats.map((_, index) => `[v${index}]`).join("");
39
+ filterComplexParts.push(`${concatInput}concat=n=${imageCount}:v=1:a=0[v]`);
40
+ // Apply the filter complex for concatenation and map audio input
41
+ command
42
+ .complexFilter(filterComplexParts)
43
+ .input(audioPath) // Add audio input
44
+ .outputOptions([
45
+ "-preset veryfast", // Faster encoding
46
+ "-map [v]", // Map the video stream
47
+ `-map ${imageCount /* + captionCount*/}:a`, // Map the audio stream (audio is the next input after all images)
48
+ "-c:v h264_videotoolbox", // Set video codec
49
+ "-threads 8",
50
+ "-filter_threads 8",
51
+ "-b:v 5M", // bitrate (only for videotoolbox)
52
+ "-bufsize",
53
+ "10M", // Add buffer size for better quality
54
+ "-maxrate",
55
+ "7M", // Maximum bitrate
56
+ "-r 30", // Set frame rate
57
+ "-pix_fmt yuv420p", // Set pixel format for better compatibility
58
+ ])
59
+ .on("start", (__cmdLine) => {
60
+ console.log("Started FFmpeg ..."); // with command:', cmdLine);
61
+ })
62
+ .on("error", (err, stdout, stderr) => {
63
+ console.error("Error occurred:", err);
64
+ console.error("FFmpeg stdout:", stdout);
65
+ console.error("FFmpeg stderr:", stderr);
66
+ })
67
+ .on("end", () => {
68
+ const end = performance.now();
69
+ console.log(`Video created successfully! ${Math.round(end - start) / 1000} sec`);
70
+ })
71
+ .output(outputVideoPath)
72
+ .run();
73
+ };
74
+ const movie = async (context) => {
75
+ const { studio, fileDirs } = context;
76
+ const { outDirPath } = fileDirs;
77
+ const audioPath = (0, file_1.getOutputBGMFilePath)(outDirPath, studio.filename);
78
+ const outputVideoPath = (0, file_1.getOutputVideoFilePath)(outDirPath, studio.filename);
79
+ createVideo(audioPath, outputVideoPath, studio);
80
+ };
81
+ exports.movie = movie;
@@ -0,0 +1,3 @@
1
+ import "dotenv/config";
2
+ import { MulmoStudioContext } from "../types";
3
+ export declare const translate: (context: MulmoStudioContext) => Promise<void>;
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.translate = void 0;
37
+ require("dotenv/config");
38
+ const graphai_1 = require("graphai");
39
+ const agents = __importStar(require("@graphai/vanilla"));
40
+ const openai_agent_1 = require("@graphai/openai_agent");
41
+ const vanilla_node_agents_1 = require("@graphai/vanilla_node_agents");
42
+ const string_1 = require("../utils/string");
43
+ const file_1 = require("../utils/file");
44
+ const translateGraph = {
45
+ version: 0.5,
46
+ nodes: {
47
+ studio: {},
48
+ defaultLang: {},
49
+ outDirPath: {},
50
+ outputStudioFilePath: {},
51
+ lang: {
52
+ agent: "stringUpdateTextAgent",
53
+ inputs: {
54
+ newText: ":studio.script.lang",
55
+ oldText: ":defaultLang",
56
+ },
57
+ },
58
+ targetLangs: {}, // TODO
59
+ mergeStudioResult: {
60
+ isResult: true,
61
+ agent: "mergeObjectAgent",
62
+ inputs: {
63
+ items: [":studio", { beats: ":beatsMap.mergeBeatData" }],
64
+ },
65
+ },
66
+ beatsMap: {
67
+ agent: "mapAgent",
68
+ inputs: {
69
+ targetLangs: ":targetLangs",
70
+ rows: ":studio.beats",
71
+ lang: ":lang",
72
+ },
73
+ params: {
74
+ rowKey: "beat",
75
+ compositeResult: true,
76
+ },
77
+ graph: {
78
+ version: 0.5,
79
+ nodes: {
80
+ preprocessBeats: {
81
+ agent: "mapAgent",
82
+ inputs: {
83
+ beat: ":beat",
84
+ rows: ":targetLangs",
85
+ lang: ":lang.text",
86
+ },
87
+ params: {
88
+ compositeResult: true,
89
+ rowKey: "targetLang",
90
+ },
91
+ graph: {
92
+ version: 0.5,
93
+ nodes: {
94
+ localizedTexts: {
95
+ inputs: {
96
+ targetLang: ":targetLang",
97
+ beat: ":beat",
98
+ lang: ":lang",
99
+ system: "Please translate the given text into the language specified in language (in locale format, like en, ja, fr, ch).",
100
+ prompt: ["## Original Language", ":lang", "", "## Language", ":targetLang", "", "## Target", ":beat.text"],
101
+ },
102
+ passThrough: {
103
+ lang: ":targetLang",
104
+ },
105
+ output: {
106
+ text: ".text",
107
+ },
108
+ // return { lang, text } <- localizedText
109
+ agent: "openAIAgent",
110
+ },
111
+ splitText: {
112
+ agent: (namedInputs) => {
113
+ const { localizedText, targetLang } = namedInputs;
114
+ // Cache
115
+ if (localizedText.texts) {
116
+ return localizedText;
117
+ }
118
+ if (targetLang === "ja") {
119
+ return {
120
+ ...localizedText,
121
+ texts: (0, string_1.recursiveSplitJa)(localizedText.text),
122
+ };
123
+ }
124
+ // not split
125
+ return {
126
+ ...localizedText,
127
+ texts: [localizedText.text],
128
+ };
129
+ // return { lang, text, texts }
130
+ },
131
+ inputs: {
132
+ targetLang: ":targetLang",
133
+ localizedText: ":localizedTexts",
134
+ },
135
+ },
136
+ ttsTexts: {
137
+ agent: (namedInputs) => {
138
+ const { localizedText, targetLang } = namedInputs;
139
+ // cache
140
+ if (localizedText.ttsTexts) {
141
+ return localizedText;
142
+ }
143
+ if (targetLang === "ja") {
144
+ return {
145
+ ...localizedText,
146
+ ttsTexts: localizedText?.texts?.map((text) => (0, string_1.replacePairsJa)(text, string_1.replacementsJa)),
147
+ };
148
+ }
149
+ return {
150
+ ...localizedText,
151
+ ttsTexts: localizedText.texts,
152
+ };
153
+ },
154
+ inputs: {
155
+ targetLang: ":targetLang",
156
+ localizedText: ":splitText",
157
+ },
158
+ isResult: true,
159
+ },
160
+ },
161
+ },
162
+ },
163
+ mergeLocalizedText: {
164
+ agent: "arrayToObjectAgent",
165
+ inputs: {
166
+ items: ":preprocessBeats.ttsTexts",
167
+ },
168
+ params: {
169
+ key: "lang",
170
+ },
171
+ },
172
+ mergeBeatData: {
173
+ isResult: true,
174
+ agent: "mergeObjectAgent",
175
+ inputs: {
176
+ items: [":beat", { multiLingualTexts: ":mergeLocalizedText" }],
177
+ },
178
+ },
179
+ },
180
+ },
181
+ },
182
+ writeOutout: {
183
+ // console: { before: true },
184
+ agent: "fileWriteAgent",
185
+ inputs: {
186
+ file: ":outputStudioFilePath",
187
+ text: ":mergeStudioResult.toJSON()",
188
+ },
189
+ },
190
+ },
191
+ };
192
+ const localizedTextCacheAgentFilter = async (context, next) => {
193
+ const { namedInputs } = context;
194
+ const { targetLang, beat, lang } = namedInputs;
195
+ // The original text is unchanged and the target language text is present
196
+ if (beat.multiLingualTexts &&
197
+ beat.multiLingualTexts[lang] &&
198
+ beat.multiLingualTexts[lang].text === beat.text &&
199
+ beat.multiLingualTexts[targetLang] &&
200
+ beat.multiLingualTexts[targetLang].text) {
201
+ return { text: beat.multiLingualTexts[targetLang].text };
202
+ }
203
+ // same language
204
+ if (targetLang === lang) {
205
+ return { text: beat.text };
206
+ }
207
+ return await next(context);
208
+ };
209
+ const agentFilters = [
210
+ {
211
+ name: "localizedTextCacheAgentFilter",
212
+ agent: localizedTextCacheAgentFilter,
213
+ nodeIds: ["localizedTexts"],
214
+ },
215
+ ];
216
+ const defaultLang = "en";
217
+ const targetLangs = ["ja", "en"];
218
+ const translate = async (context) => {
219
+ const { studio, fileDirs } = context;
220
+ const { outDirPath } = fileDirs;
221
+ const outputStudioFilePath = (0, file_1.getOutputStudioFilePath)(outDirPath, studio.filename);
222
+ (0, file_1.mkdir)(outDirPath);
223
+ (0, graphai_1.assert)(!!process.env.OPENAI_API_KEY, "The OPENAI_API_KEY environment variable is missing or empty");
224
+ const graph = new graphai_1.GraphAI(translateGraph, { ...agents, fileWriteAgent: vanilla_node_agents_1.fileWriteAgent, openAIAgent: openai_agent_1.openAIAgent }, { agentFilters });
225
+ graph.injectValue("studio", studio);
226
+ graph.injectValue("defaultLang", defaultLang);
227
+ graph.injectValue("targetLangs", targetLangs);
228
+ graph.injectValue("outDirPath", outDirPath);
229
+ graph.injectValue("outputStudioFilePath", outputStudioFilePath);
230
+ await graph.run();
231
+ (0, file_1.writingMessage)(outputStudioFilePath);
232
+ // const results = await graph.run();
233
+ // const mulmoDataResult = results.mergeResult;
234
+ // console.log(JSON.stringify(mulmoDataResult, null, 2));
235
+ };
236
+ exports.translate = translate;
@@ -0,0 +1,3 @@
1
+ import { AgentFunctionInfo } from "graphai";
2
+ declare const addBGMAgentInfo: AgentFunctionInfo;
3
+ export default addBGMAgentInfo;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
7
+ const methods_1 = require("../methods");
8
+ const addBGMAgent = async ({ namedInputs, params, }) => {
9
+ const { voiceFile, outputFile, script } = namedInputs;
10
+ const { musicFile } = params;
11
+ const promise = new Promise((resolve, reject) => {
12
+ fluent_ffmpeg_1.default.ffprobe(voiceFile, (err, metadata) => {
13
+ if (err) {
14
+ console.error("Error getting metadata: " + err.message);
15
+ reject(err);
16
+ }
17
+ const speechDuration = metadata.format.duration;
18
+ const padding = methods_1.MulmoScriptMethods.getPadding(script);
19
+ const totalDuration = (padding * 2) / 1000 + Math.round(speechDuration ?? 0);
20
+ console.log("totalDucation:", speechDuration, totalDuration);
21
+ const command = (0, fluent_ffmpeg_1.default)();
22
+ command
23
+ .input(musicFile)
24
+ .input(voiceFile)
25
+ .complexFilter([
26
+ // Add a 2-second delay to the speech
27
+ `[1:a]adelay=${padding}|${padding}, volume=4[a1]`, // 4000ms delay for both left and right channels
28
+ // Set the background music volume to 0.2
29
+ `[0:a]volume=0.2[a0]`,
30
+ // Mix the delayed speech and the background music
31
+ `[a0][a1]amix=inputs=2:duration=longest:dropout_transition=3[amixed]`,
32
+ // Trim the output to the length of speech + 8 seconds
33
+ `[amixed]atrim=start=0:end=${totalDuration}[trimmed]`,
34
+ // Add fade out effect for the last 4 seconds
35
+ `[trimmed]afade=t=out:st=${totalDuration - padding / 1000}:d=${padding}`,
36
+ ])
37
+ .on("error", (err) => {
38
+ console.error("Error: " + err.message);
39
+ reject(err);
40
+ })
41
+ .on("end", () => {
42
+ resolve(0);
43
+ })
44
+ .save(outputFile);
45
+ });
46
+ });
47
+ await promise;
48
+ return outputFile;
49
+ };
50
+ const addBGMAgentInfo = {
51
+ name: "addBGMAgent",
52
+ agent: addBGMAgent,
53
+ mock: addBGMAgent,
54
+ samples: [],
55
+ description: "addBGMAgent",
56
+ category: ["ffmpeg"],
57
+ author: "satoshi nakajima",
58
+ repository: "https://github.com/snakajima/ai-podcaster",
59
+ license: "MIT",
60
+ };
61
+ exports.default = addBGMAgentInfo;
@@ -0,0 +1,3 @@
1
+ import { AgentFunctionInfo } from "graphai";
2
+ declare const combineAudioFilesAgentInfo: AgentFunctionInfo;
3
+ export default combineAudioFilesAgentInfo;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
7
+ const file_1 = require("../utils/file");
8
+ const methods_1 = require("../methods");
9
+ const combineAudioFilesAgent = async ({ namedInputs }) => {
10
+ const { context, combinedFileName, scratchpadDirPath } = namedInputs;
11
+ const command = (0, fluent_ffmpeg_1.default)();
12
+ context.studio.beats.forEach((mulmoBeat, index) => {
13
+ const audioPath = mulmoBeat.audio?.type === "audio" &&
14
+ ((mulmoBeat.audio?.source.kind === "path" && methods_1.MulmoStudioContextMethods.resolveAssetPath(context, mulmoBeat.audio.source.path)) ||
15
+ (mulmoBeat.audio?.source.kind === "url" && mulmoBeat.audio.source.url));
16
+ const filePath = audioPath || (0, file_1.getScratchpadFilePath)(scratchpadDirPath, mulmoBeat.audioFile ?? "");
17
+ const isLast = index === context.studio.beats.length - 2;
18
+ command.input(filePath);
19
+ command.input(isLast ? file_1.silentLastPath : file_1.silentPath);
20
+ // Measure and log the timestamp of each section
21
+ fluent_ffmpeg_1.default.ffprobe(filePath, (err, metadata) => {
22
+ if (err) {
23
+ console.error("Error while getting metadata:", err);
24
+ }
25
+ else {
26
+ context.studio.beats[index]["duration"] = metadata.format.duration + (isLast ? 0.8 : 0.3);
27
+ }
28
+ });
29
+ });
30
+ const promise = new Promise((resolve, reject) => {
31
+ command
32
+ .on("end", () => {
33
+ resolve(0);
34
+ })
35
+ .on("error", (err) => {
36
+ console.error("Error while combining MP3 files:", err);
37
+ reject(err);
38
+ })
39
+ .mergeToFile(combinedFileName, scratchpadDirPath);
40
+ });
41
+ await promise;
42
+ return {
43
+ studio: context.studio,
44
+ };
45
+ };
46
+ const combineAudioFilesAgentInfo = {
47
+ name: "combineAudioFilesAgent",
48
+ agent: combineAudioFilesAgent,
49
+ mock: combineAudioFilesAgent,
50
+ samples: [],
51
+ description: "combineAudioFilesAgent",
52
+ category: ["ffmpeg"],
53
+ author: "satoshi nakajima",
54
+ repository: "https://github.com/snakajima/ai-podcaster",
55
+ license: "MIT",
56
+ };
57
+ exports.default = combineAudioFilesAgentInfo;