mulmocast 1.1.6 → 1.1.7

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.
@@ -1,7 +1,56 @@
1
1
  import "dotenv/config";
2
2
  import type { CallbackFunction } from "graphai";
3
- import { MulmoStudioContext } from "../types/index.js";
3
+ import { LANG, LocalizedText, MulmoStudioContext } from "../types/index.js";
4
+ export declare const translateTextGraph: {
5
+ version: number;
6
+ nodes: {
7
+ localizedText: {
8
+ inputs: {
9
+ targetLang: string;
10
+ beat: string;
11
+ multiLingual: string;
12
+ lang: string;
13
+ beatIndex: string;
14
+ mulmoContext: string;
15
+ system: string;
16
+ prompt: string[];
17
+ };
18
+ passThrough: {
19
+ lang: string;
20
+ };
21
+ output: {
22
+ text: string;
23
+ };
24
+ agent: string;
25
+ };
26
+ splitText: {
27
+ agent: (namedInputs: {
28
+ localizedText: LocalizedText;
29
+ targetLang: LANG;
30
+ }) => string[];
31
+ inputs: {
32
+ targetLang: string;
33
+ localizedText: string;
34
+ };
35
+ };
36
+ textTranslateResult: {
37
+ isResult: boolean;
38
+ agent: string;
39
+ inputs: {
40
+ lang: string;
41
+ text: string;
42
+ texts: string;
43
+ ttsTexts: string;
44
+ cacheKey: string;
45
+ };
46
+ };
47
+ };
48
+ };
49
+ export declare const translateBeat: (index: number, context: MulmoStudioContext, targetLangs: string[], args?: {
50
+ settings?: Record<string, string>;
51
+ callbacks?: CallbackFunction[];
52
+ }) => Promise<void>;
4
53
  export declare const translate: (context: MulmoStudioContext, args?: {
5
54
  callbacks?: CallbackFunction[];
6
55
  settings?: Record<string, string>;
7
- }) => Promise<void>;
56
+ }) => Promise<MulmoStudioContext>;
@@ -1,14 +1,152 @@
1
1
  import "dotenv/config";
2
- import { GraphAI, assert, isNull } from "graphai";
2
+ import { createHash } from "crypto";
3
+ import fs from "fs";
4
+ import { GraphAI, assert, isNull, GraphAILogger } from "graphai";
3
5
  import * as agents from "@graphai/vanilla";
4
6
  import { openAIAgent } from "@graphai/openai_agent";
5
7
  import { fileWriteAgent } from "@graphai/vanilla_node_agents";
6
8
  import { recursiveSplitJa } from "../utils/string.js";
7
9
  import { settings2GraphAIConfig } from "../utils/utils.js";
10
+ import { getMultiLingual } from "../utils/context.js";
11
+ import { currentMulmoScriptVersion } from "../utils/const.js";
8
12
  import { getOutputMultilingualFilePath, mkdir, writingMessage } from "../utils/file.js";
9
13
  import { translateSystemPrompt, translatePrompts } from "../utils/prompt.js";
10
14
  import { MulmoStudioContextMethods } from "../methods/mulmo_studio_context.js";
11
15
  const vanillaAgents = agents.default ?? agents;
16
+ const hashSHA256 = (text) => {
17
+ return createHash("sha256").update(text, "utf8").digest("hex");
18
+ };
19
+ // 1. translateGraph / map each beats.
20
+ // 2. beatGraph / map each target lang.
21
+ // 3. translateTextGraph / translate text.
22
+ export const translateTextGraph = {
23
+ version: 0.5,
24
+ nodes: {
25
+ localizedText: {
26
+ inputs: {
27
+ targetLang: ":targetLang", // for cache
28
+ beat: ":beat", // for cache
29
+ multiLingual: ":multiLingual", // for cache
30
+ lang: ":lang", // for cache
31
+ beatIndex: ":beatIndex", // for cache (state)
32
+ mulmoContext: ":context", // for cache (state)
33
+ system: translateSystemPrompt,
34
+ prompt: translatePrompts,
35
+ },
36
+ passThrough: {
37
+ lang: ":targetLang",
38
+ },
39
+ output: {
40
+ text: ".text",
41
+ },
42
+ // return { lang, text } <- localizedText
43
+ agent: "openAIAgent",
44
+ },
45
+ splitText: {
46
+ agent: (namedInputs) => {
47
+ const { localizedText, targetLang } = namedInputs;
48
+ // Cache
49
+ if (localizedText.texts) {
50
+ return localizedText.texts;
51
+ }
52
+ if (targetLang === "ja") {
53
+ return recursiveSplitJa(localizedText.text);
54
+ }
55
+ // not split
56
+ return [localizedText.text];
57
+ },
58
+ inputs: {
59
+ targetLang: ":targetLang",
60
+ localizedText: ":localizedText",
61
+ },
62
+ },
63
+ textTranslateResult: {
64
+ isResult: true,
65
+ agent: "copyAgent",
66
+ inputs: {
67
+ lang: ":targetLang",
68
+ text: ":localizedText.text",
69
+ texts: ":splitText",
70
+ ttsTexts: ":splitText",
71
+ cacheKey: ":multiLingual.cacheKey",
72
+ },
73
+ },
74
+ },
75
+ };
76
+ const beatGraph = {
77
+ version: 0.5,
78
+ nodes: {
79
+ targetLangs: {},
80
+ context: {},
81
+ beat: {},
82
+ __mapIndex: {},
83
+ // for cache
84
+ multiLingual: {
85
+ agent: (namedInputs) => {
86
+ const { multiLinguals, beatIndex, text } = namedInputs;
87
+ const cacheKey = hashSHA256(text ?? "");
88
+ const multiLingual = multiLinguals?.[beatIndex];
89
+ if (!multiLingual) {
90
+ return { cacheKey, multiLingualTexts: {} };
91
+ }
92
+ return {
93
+ multiLingualTexts: Object.keys(multiLingual.multiLingualTexts).reduce((tmp, lang) => {
94
+ if (multiLingual.multiLingualTexts[lang].cacheKey === cacheKey) {
95
+ tmp[lang] = multiLingual.multiLingualTexts[lang];
96
+ }
97
+ return tmp;
98
+ }, {}),
99
+ cacheKey,
100
+ };
101
+ },
102
+ inputs: {
103
+ text: ":beat.text",
104
+ beatIndex: ":__mapIndex",
105
+ multiLinguals: ":context.multiLingual",
106
+ },
107
+ },
108
+ preprocessMultiLingual: {
109
+ agent: "mapAgent",
110
+ inputs: {
111
+ beat: ":beat",
112
+ multiLingual: ":multiLingual",
113
+ rows: ":targetLangs",
114
+ lang: ":context.studio.script.lang",
115
+ context: ":context",
116
+ beatIndex: ":__mapIndex",
117
+ },
118
+ params: {
119
+ compositeResult: true,
120
+ rowKey: "targetLang",
121
+ },
122
+ graph: translateTextGraph,
123
+ },
124
+ mergeLocalizedText: {
125
+ // console: { after: true},
126
+ agent: "arrayToObjectAgent",
127
+ inputs: {
128
+ items: ":preprocessMultiLingual.textTranslateResult",
129
+ },
130
+ params: {
131
+ key: "lang",
132
+ },
133
+ },
134
+ multiLingualTexts: {
135
+ agent: "mergeObjectAgent",
136
+ inputs: {
137
+ items: [":multiLingual.multiLingualTexts", ":mergeLocalizedText"],
138
+ },
139
+ },
140
+ mergeMultiLingualData: {
141
+ isResult: true,
142
+ // console: { after: true},
143
+ agent: "mergeObjectAgent",
144
+ inputs: {
145
+ items: [":multiLingual", { multiLingualTexts: ":multiLingualTexts" }],
146
+ },
147
+ },
148
+ },
149
+ };
12
150
  const translateGraph = {
13
151
  version: 0.5,
14
152
  nodes: {
@@ -18,9 +156,10 @@ const translateGraph = {
18
156
  targetLangs: {},
19
157
  mergeStudioResult: {
20
158
  isResult: true,
21
- agent: "mergeObjectAgent",
159
+ agent: "copyAgent",
22
160
  inputs: {
23
- items: [{ multiLingual: ":beatsMap.mergeMultiLingualData" }],
161
+ version: "1.1",
162
+ multiLingual: ":beatsMap.mergeMultiLingualData",
24
163
  },
25
164
  },
26
165
  beatsMap: {
@@ -34,127 +173,13 @@ const translateGraph = {
34
173
  rowKey: "beat",
35
174
  compositeResult: true,
36
175
  },
37
- graph: {
38
- version: 0.5,
39
- nodes: {
40
- // for cache
41
- multiLingual: {
42
- agent: (namedInputs) => {
43
- return (namedInputs.rows && namedInputs.rows[namedInputs.index]) || {};
44
- },
45
- inputs: {
46
- index: ":__mapIndex",
47
- rows: ":context.multiLingual",
48
- },
49
- },
50
- preprocessMultiLingual: {
51
- agent: "mapAgent",
52
- inputs: {
53
- beat: ":beat",
54
- multiLingual: ":multiLingual",
55
- rows: ":targetLangs",
56
- lang: ":context.studio.script.lang",
57
- context: ":context",
58
- beatIndex: ":__mapIndex",
59
- },
60
- params: {
61
- compositeResult: true,
62
- rowKey: "targetLang",
63
- },
64
- graph: {
65
- version: 0.5,
66
- nodes: {
67
- localizedTexts: {
68
- inputs: {
69
- targetLang: ":targetLang", // for cache
70
- beat: ":beat", // for cache
71
- multiLingual: ":multiLingual", // for cache
72
- lang: ":lang", // for cache
73
- beatIndex: ":beatIndex", // for cache
74
- mulmoContext: ":context", // for cache
75
- system: translateSystemPrompt,
76
- prompt: translatePrompts,
77
- },
78
- passThrough: {
79
- lang: ":targetLang",
80
- },
81
- output: {
82
- text: ".text",
83
- },
84
- // return { lang, text } <- localizedText
85
- agent: "openAIAgent",
86
- },
87
- splitText: {
88
- agent: (namedInputs) => {
89
- const { localizedText, targetLang } = namedInputs;
90
- // Cache
91
- if (localizedText.texts) {
92
- return localizedText;
93
- }
94
- if (targetLang === "ja") {
95
- return {
96
- ...localizedText,
97
- texts: recursiveSplitJa(localizedText.text),
98
- };
99
- }
100
- // not split
101
- return {
102
- ...localizedText,
103
- texts: [localizedText.text],
104
- };
105
- // return { lang, text, texts }
106
- },
107
- inputs: {
108
- targetLang: ":targetLang",
109
- localizedText: ":localizedTexts",
110
- },
111
- },
112
- ttsTexts: {
113
- agent: (namedInputs) => {
114
- const { localizedText } = namedInputs;
115
- // cache
116
- if (localizedText.ttsTexts) {
117
- return localizedText;
118
- }
119
- return {
120
- ...localizedText,
121
- ttsTexts: localizedText.texts,
122
- };
123
- },
124
- inputs: {
125
- targetLang: ":targetLang",
126
- localizedText: ":splitText",
127
- },
128
- isResult: true,
129
- },
130
- },
131
- },
132
- },
133
- mergeLocalizedText: {
134
- agent: "arrayToObjectAgent",
135
- inputs: {
136
- items: ":preprocessMultiLingual.ttsTexts",
137
- },
138
- params: {
139
- key: "lang",
140
- },
141
- },
142
- mergeMultiLingualData: {
143
- isResult: true,
144
- agent: "mergeObjectAgent",
145
- inputs: {
146
- items: [":multiLingual", { multiLingualTexts: ":mergeLocalizedText" }],
147
- },
148
- },
149
- },
150
- },
176
+ graph: beatGraph,
151
177
  },
152
178
  writeOutput: {
153
- // console: { before: true },
154
179
  agent: "fileWriteAgent",
155
180
  inputs: {
156
181
  file: ":outputMultilingualFilePath",
157
- text: ":mergeStudioResult.multiLingual.toJSON()",
182
+ text: ":mergeStudioResult.toJSON()",
158
183
  },
159
184
  },
160
185
  },
@@ -170,7 +195,7 @@ const localizedTextCacheAgentFilter = async (context, next) => {
170
195
  return { text: beat.text };
171
196
  }
172
197
  // The original text is unchanged and the target language text is present
173
- if (multiLingual.multiLingualTexts?.[lang]?.text === beat.text && multiLingual.multiLingualTexts[targetLang]?.text) {
198
+ if (multiLingual.cacheKey === multiLingual.multiLingualTexts[targetLang]?.cacheKey) {
174
199
  return { text: multiLingual.multiLingualTexts[targetLang].text };
175
200
  }
176
201
  try {
@@ -185,9 +210,49 @@ const agentFilters = [
185
210
  {
186
211
  name: "localizedTextCacheAgentFilter",
187
212
  agent: localizedTextCacheAgentFilter,
188
- nodeIds: ["localizedTexts"],
213
+ nodeIds: ["localizedText"],
189
214
  },
190
215
  ];
216
+ export const translateBeat = async (index, context, targetLangs, args) => {
217
+ const { settings, callbacks } = args ?? {};
218
+ // Validate inputs
219
+ if (index < 0 || index >= context.studio.script.beats.length) {
220
+ throw new Error(`Invalid beat index: ${index}. Must be between 0 and ${context.studio.script.beats.length - 1}`);
221
+ }
222
+ if (!targetLangs || targetLangs.length === 0) {
223
+ throw new Error("targetLangs must be a non-empty array");
224
+ }
225
+ try {
226
+ const fileName = MulmoStudioContextMethods.getFileName(context);
227
+ const outDirPath = MulmoStudioContextMethods.getOutDirPath(context);
228
+ const outputMultilingualFilePath = getOutputMultilingualFilePath(outDirPath, fileName);
229
+ mkdir(outDirPath);
230
+ const config = settings2GraphAIConfig(settings, process.env);
231
+ assert(!!config?.openAIAgent?.apiKey, "The OPENAI_API_KEY environment variable is missing or empty");
232
+ const graph = new GraphAI(beatGraph, { ...vanillaAgents, fileWriteAgent, openAIAgent }, { agentFilters, config });
233
+ graph.injectValue("context", context);
234
+ graph.injectValue("targetLangs", targetLangs);
235
+ graph.injectValue("beat", context.studio.script.beats[index]);
236
+ graph.injectValue("__mapIndex", index);
237
+ if (callbacks) {
238
+ callbacks.forEach((callback) => {
239
+ graph.registerCallback(callback);
240
+ });
241
+ }
242
+ const results = await graph.run();
243
+ const multiLingual = getMultiLingual(outputMultilingualFilePath, context.studio.beats.length);
244
+ multiLingual[index] = results.mergeMultiLingualData;
245
+ const data = {
246
+ version: currentMulmoScriptVersion,
247
+ multiLingual,
248
+ };
249
+ fs.writeFileSync(outputMultilingualFilePath, JSON.stringify(data, null, 2), "utf8");
250
+ writingMessage(outputMultilingualFilePath);
251
+ }
252
+ catch (error) {
253
+ GraphAILogger.log(error);
254
+ }
255
+ };
191
256
  export const translate = async (context, args) => {
192
257
  const { settings, callbacks } = args ?? {};
193
258
  try {
@@ -196,10 +261,7 @@ export const translate = async (context, args) => {
196
261
  const outDirPath = MulmoStudioContextMethods.getOutDirPath(context);
197
262
  const outputMultilingualFilePath = getOutputMultilingualFilePath(outDirPath, fileName);
198
263
  mkdir(outDirPath);
199
- const langs = (context.multiLingual ?? []).map((x) => Object.keys(x.multiLingualTexts)).flat(); // existing langs in multiLingual
200
- const targetLangs = [
201
- ...new Set([context.studio.script.lang, langs, context.lang, context.studio.script.captionParams?.lang].flat().filter((x) => !isNull(x))),
202
- ];
264
+ const targetLangs = [...new Set([context.lang, context.studio.script.captionParams?.lang].filter((x) => !isNull(x)))];
203
265
  const config = settings2GraphAIConfig(settings, process.env);
204
266
  assert(!!config?.openAIAgent?.apiKey, "The OPENAI_API_KEY environment variable is missing or empty");
205
267
  const graph = new GraphAI(translateGraph, { ...vanillaAgents, fileWriteAgent, openAIAgent }, { agentFilters, config });
@@ -221,4 +283,5 @@ export const translate = async (context, args) => {
221
283
  finally {
222
284
  MulmoStudioContextMethods.setSessionState(context, "multiLingual", false);
223
285
  }
286
+ return context;
224
287
  };
@@ -94,7 +94,7 @@ const voiceOverProcess = (context, mediaDurations, movieDuration, beatDurations,
94
94
  if (voiceStartAt) {
95
95
  const remainingDuration = movieDuration - voiceStartAt;
96
96
  const duration = remaining - remainingDuration;
97
- userAssert(duration >= 0, `Invalid startAt: At index(${idx}), avaiable duration(${duration}) < 0`);
97
+ userAssert(duration >= 0, `Invalid startAt: At index(${idx}), available duration(${duration}) < 0`);
98
98
  beatDurations.push(duration);
99
99
  subBeatDurations.silenceDuration = duration - subBeatDurations.audioDuration;
100
100
  userAssert(subBeatDurations.silenceDuration >= 0, `Duration Overwrap: At index(${idx}), silenceDuration(${subBeatDurations.silenceDuration}) < 0`);
@@ -27,7 +27,7 @@ export const ttsNijivoiceAgent = async ({ params, namedInputs, config, }) => {
27
27
  try {
28
28
  const voiceRes = await fetch(url, options);
29
29
  const voiceJson = await voiceRes.json();
30
- if (voiceJson && voiceJson.generatedVoice && voiceJson.generatedVoice.audioFileDownloadUrl) {
30
+ if (voiceJson?.generatedVoice?.audioFileDownloadUrl) {
31
31
  const audioRes = await fetch(voiceJson.generatedVoice.audioFileDownloadUrl);
32
32
  const buffer = Buffer.from(await audioRes.arrayBuffer());
33
33
  return { buffer };
@@ -1,2 +1,3 @@
1
1
  export * from "./types/index.js";
2
2
  export * from "./utils/provider2agent.js";
3
+ export * from "./utils/const.js";
@@ -1,3 +1,4 @@
1
1
  // Entry point for universal code
2
2
  export * from "./types/index.js";
3
3
  export * from "./utils/provider2agent.js";
4
+ export * from "./utils/const.js";
@@ -1,4 +1,7 @@
1
- import { MulmoScript } from "../types/index.js";
1
+ import { type MulmoScript, type MulmoStudioMultiLingual } from "../types/index.js";
2
2
  export declare const MulmoScriptMethods: {
3
3
  validate(script: any): MulmoScript;
4
4
  };
5
+ export declare const MulmoStudioMultiLingualMethod: {
6
+ validate(jsonData: any, studioBeatsLength: number): MulmoStudioMultiLingual;
7
+ };
@@ -1,4 +1,4 @@
1
- import { mulmoScriptSchema } from "../types/index.js";
1
+ import { mulmoScriptSchema, mulmoStudioMultiLingualFileSchema } from "../types/index.js";
2
2
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
3
  const validate_1_0 = (script) => {
4
4
  if (script.speechParams?.provider) {
@@ -32,3 +32,16 @@ export const MulmoScriptMethods = {
32
32
  return mulmoScriptSchema.parse(validatedScript);
33
33
  },
34
34
  };
35
+ export const MulmoStudioMultiLingualMethod = {
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ validate(jsonData, studioBeatsLength) {
38
+ // TODO version check
39
+ const result = mulmoStudioMultiLingualFileSchema.safeParse(jsonData);
40
+ const dataSet = result.success ? result.data.multiLingual : [];
41
+ while (dataSet.length < studioBeatsLength) {
42
+ dataSet.push({ multiLingualTexts: {} });
43
+ }
44
+ dataSet.length = studioBeatsLength;
45
+ return dataSet;
46
+ },
47
+ };