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.
- package/README.md +74 -0
- package/assets/audio/silent300.mp3 +0 -0
- package/assets/audio/silent800.mp3 +0 -0
- package/assets/music/StarsBeyondEx.mp3 +0 -0
- package/assets/templates/business.json +89 -0
- package/assets/templates/children_book.json +135 -0
- package/assets/templates/podcast_standard.json +5 -0
- package/assets/templates/sensei_and_taro.json +123 -0
- package/lib/actions/audio.d.ts +3 -0
- package/lib/actions/audio.js +186 -0
- package/lib/actions/images.d.ts +2 -0
- package/lib/actions/images.js +211 -0
- package/lib/actions/movie.d.ts +2 -0
- package/lib/actions/movie.js +81 -0
- package/lib/actions/translate.d.ts +3 -0
- package/lib/actions/translate.js +236 -0
- package/lib/agents/add_bgm_agent.d.ts +3 -0
- package/lib/agents/add_bgm_agent.js +61 -0
- package/lib/agents/combine_audio_files_agent.d.ts +3 -0
- package/lib/agents/combine_audio_files_agent.js +57 -0
- package/lib/agents/image_google_agent.d.ts +15 -0
- package/lib/agents/image_google_agent.js +88 -0
- package/lib/agents/image_openai_agent.d.ts +15 -0
- package/lib/agents/image_openai_agent.js +59 -0
- package/lib/agents/index.d.ts +13 -0
- package/lib/agents/index.js +31 -0
- package/lib/agents/mulmo_prompts_agent.d.ts +7 -0
- package/lib/agents/mulmo_prompts_agent.js +41 -0
- package/lib/agents/prompts_data.d.ts +15 -0
- package/lib/agents/prompts_data.js +19 -0
- package/lib/agents/tts_nijivoice_agent.d.ts +4 -0
- package/lib/agents/tts_nijivoice_agent.js +68 -0
- package/lib/agents/tts_openai_agent.d.ts +4 -0
- package/lib/agents/tts_openai_agent.js +50 -0
- package/lib/agents/validate_mulmo_script_agent.d.ts +17 -0
- package/lib/agents/validate_mulmo_script_agent.js +38 -0
- package/lib/cli/args.d.ts +10 -0
- package/lib/cli/args.js +38 -0
- package/lib/cli/cli.d.ts +2 -0
- package/lib/cli/cli.js +78 -0
- package/lib/cli/common.d.ts +8 -0
- package/lib/cli/common.js +26 -0
- package/lib/cli/tool-args.d.ts +12 -0
- package/lib/cli/tool-args.js +53 -0
- package/lib/cli/tool-cli.d.ts +2 -0
- package/lib/cli/tool-cli.js +78 -0
- package/lib/methods/index.d.ts +3 -0
- package/lib/methods/index.js +19 -0
- package/lib/methods/mulmo_script.d.ts +11 -0
- package/lib/methods/mulmo_script.js +45 -0
- package/lib/methods/mulmo_script_template.d.ts +4 -0
- package/lib/methods/mulmo_script_template.js +22 -0
- package/lib/methods/mulmo_studio_context.d.ts +4 -0
- package/lib/methods/mulmo_studio_context.js +12 -0
- package/lib/tools/dump_prompt.d.ts +3 -0
- package/lib/tools/dump_prompt.js +9 -0
- package/lib/tools/prompt.d.ts +1 -0
- package/lib/tools/prompt.js +20 -0
- package/lib/tools/seed.d.ts +3 -0
- package/lib/tools/seed.js +201 -0
- package/lib/tools/seed_from_url.d.ts +3 -0
- package/lib/tools/seed_from_url.js +178 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +17 -0
- package/lib/types/schema.d.ts +5817 -0
- package/lib/types/schema.js +207 -0
- package/lib/types/type.d.ts +33 -0
- package/lib/types/type.js +2 -0
- package/lib/utils/const.d.ts +3 -0
- package/lib/utils/const.js +6 -0
- package/lib/utils/file.d.ts +28 -0
- package/lib/utils/file.js +112 -0
- package/lib/utils/filters.d.ts +3 -0
- package/lib/utils/filters.js +32 -0
- package/lib/utils/markdown.d.ts +1 -0
- package/lib/utils/markdown.js +27 -0
- package/lib/utils/preprocess.d.ts +247 -0
- package/lib/utils/preprocess.js +53 -0
- package/lib/utils/string.d.ts +9 -0
- package/lib/utils/string.js +60 -0
- package/lib/utils/text_hash.d.ts +1 -0
- package/lib/utils/text_hash.js +41 -0
- 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,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,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,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,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;
|