mulmocast 2.15.0 → 2.17.0
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 +2 -8
- package/lib/actions/image_references.d.ts +25 -1
- package/lib/actions/image_references.js +18 -9
- package/lib/agents/image_genai_agent.js +3 -3
- package/lib/agents/image_openai_agent.js +4 -3
- package/lib/agents/movie_genai_agent.js +5 -7
- package/lib/agents/tts_gemini_agent.d.ts +56 -0
- package/lib/agents/tts_gemini_agent.js +43 -38
- package/lib/data/scriptTemplates.js +2 -2
- package/lib/data/templateDataSet.js +1 -1
- package/lib/methods/mulmo_presentation_style.js +3 -3
- package/lib/types/provider2agent.d.ts +27 -16
- package/lib/types/provider2agent.js +110 -65
- package/lib/types/schema.d.ts +1 -1
- package/lib/utils/estimate_usage.js +13 -9
- package/lib/utils/gemini_tts.d.ts +45 -0
- package/lib/utils/gemini_tts.js +35 -0
- package/lib/utils/remotion/network_guard.d.ts +5 -0
- package/lib/utils/remotion/network_guard.js +48 -0
- package/lib/utils/remotion/network_policy.d.ts +10 -0
- package/lib/utils/remotion/network_policy.js +51 -0
- package/lib/utils/remotion/render.js +49 -5
- package/package.json +2 -2
- package/scripts/templates/html.json +1 -1
- package/scripts/templates/presentation.json +1 -1
- package/scripts/test/test_genai.json +2 -2
- package/scripts/test/test_gpt_image.json +1 -1
- package/scripts/test/test_replicate.json +3 -3
- package/scripts/test/test_vertexai.json +3 -3
package/README.md
CHANGED
|
@@ -103,11 +103,6 @@ Create a `.env` file in your project directory with the following API keys:
|
|
|
103
103
|
OPENAI_API_KEY=your_openai_api_key
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
#### (Optional) For the advanced image generation model
|
|
107
|
-
```bash
|
|
108
|
-
DEFAULT_OPENAI_IMAGE_MODEL=gpt-image-1 # for the advanced image generation model
|
|
109
|
-
```
|
|
110
|
-
|
|
111
106
|
#### (Optional) For Google's image and TTS.
|
|
112
107
|
```bash
|
|
113
108
|
GEMINI_API_KEY=your_google_gemini_api_key
|
|
@@ -298,10 +293,9 @@ Optionally, you can specify *__clipboard* as the script file name to paste the s
|
|
|
298
293
|
Verify your `.env` file contains:
|
|
299
294
|
```bash
|
|
300
295
|
OPENAI_API_KEY=your_openai_api_key
|
|
301
|
-
DEFAULT_OPENAI_IMAGE_MODEL=gpt-image-1 # required for high-quality Ghibli-style images
|
|
302
296
|
```
|
|
303
297
|
|
|
304
|
-
> **Note:** Ensure your OpenAI organization is verified to access the
|
|
298
|
+
> **Note:** Ensure your OpenAI organization is verified to access the GPT Image models. Visit https://platform.openai.com/settings/organization/general and complete the "Verifications" section.
|
|
305
299
|
|
|
306
300
|
### Step 2: Generate a Ghibli-style MulmoScript
|
|
307
301
|
```bash
|
|
@@ -410,7 +404,7 @@ Payload shape:
|
|
|
410
404
|
"records": 3,
|
|
411
405
|
"byModel": [
|
|
412
406
|
{ "provider": "openai", "model": "gpt-4o-mini-tts", "records": 1, "inputChars": 20 },
|
|
413
|
-
{ "provider": "gemini", "model": "gemini-
|
|
407
|
+
{ "provider": "gemini", "model": "gemini-3.8-flash-tts", "records": 1, "inputTokens": 9, "outputTokens": 59, "totalTokens": 68 },
|
|
414
408
|
{ "provider": "elevenlabs", "model": "eleven_multilingual_v2", "records": 1, "inputChars": 18 }
|
|
415
409
|
],
|
|
416
410
|
"snapshot": [ /* per-call UsageRecord[] */ ]
|
|
@@ -1,6 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MulmoPresentationStyleMethods } from "../methods/index.js";
|
|
2
|
+
import { MulmoStudioContext, MulmoBeat, MulmoImagePromptMedia, MulmoCanvasDimension } from "../types/index.js";
|
|
2
3
|
import type { ResolvedImageReference } from "../methods/mulmo_image_prompt_media.js";
|
|
3
4
|
export type { ResolvedImageReference } from "../methods/mulmo_image_prompt_media.js";
|
|
5
|
+
type ImageAgentInfo = ReturnType<typeof MulmoPresentationStyleMethods.getImageAgentInfo>;
|
|
6
|
+
type MovieAgentInfo = ReturnType<typeof MulmoPresentationStyleMethods.getMovieAgentInfo>;
|
|
7
|
+
export declare const referenceImageGraphParams: (imageAgentInfo: ImageAgentInfo, canvasSize: MulmoCanvasDimension) => {
|
|
8
|
+
model: string | undefined;
|
|
9
|
+
moderation: string | undefined;
|
|
10
|
+
canvasSize: {
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
quality: string | undefined;
|
|
15
|
+
vertexai_project: string | undefined;
|
|
16
|
+
vertexai_location: string | undefined;
|
|
17
|
+
};
|
|
18
|
+
export declare const referenceMovieGraphParams: (movieAgentInfo: MovieAgentInfo, canvasSize: MulmoCanvasDimension) => {
|
|
19
|
+
model: string | undefined;
|
|
20
|
+
canvasSize: {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
};
|
|
24
|
+
generateAudio: boolean | undefined;
|
|
25
|
+
vertexai_project: string | undefined;
|
|
26
|
+
vertexai_location: string | undefined;
|
|
27
|
+
};
|
|
4
28
|
export declare const generateReferenceImage: (inputs: {
|
|
5
29
|
context: MulmoStudioContext;
|
|
6
30
|
key: string;
|
|
@@ -6,6 +6,22 @@ import { MulmoPresentationStyleMethods, MulmoMediaSourceMethods, MulmoImagePromp
|
|
|
6
6
|
import { ImageMediaType, } from "../types/index.js";
|
|
7
7
|
import { imageOpenaiAgent, mediaMockAgent, imageGenAIAgent, imageReplicateAgent, movieGenAIAgent, movieReplicateAgent } from "../agents/index.js";
|
|
8
8
|
import { agentGenerationError, imageReferenceAction, imageFileTarget, movieFileTarget } from "../utils/error_cause.js";
|
|
9
|
+
// Reference media are generated outside the beat graphs, so they must carry the same params the beats do.
|
|
10
|
+
export const referenceImageGraphParams = (imageAgentInfo, canvasSize) => ({
|
|
11
|
+
model: imageAgentInfo.imageParams.model,
|
|
12
|
+
moderation: imageAgentInfo.imageParams.moderation,
|
|
13
|
+
canvasSize,
|
|
14
|
+
quality: imageAgentInfo.imageParams.quality,
|
|
15
|
+
vertexai_project: imageAgentInfo.imageParams.vertexai_project,
|
|
16
|
+
vertexai_location: imageAgentInfo.imageParams.vertexai_location,
|
|
17
|
+
});
|
|
18
|
+
export const referenceMovieGraphParams = (movieAgentInfo, canvasSize) => ({
|
|
19
|
+
model: movieAgentInfo.movieParams.model,
|
|
20
|
+
canvasSize,
|
|
21
|
+
generateAudio: movieAgentInfo.movieParams.generateAudio,
|
|
22
|
+
vertexai_project: movieAgentInfo.movieParams.vertexai_project,
|
|
23
|
+
vertexai_location: movieAgentInfo.movieParams.vertexai_location,
|
|
24
|
+
});
|
|
9
25
|
// Resolve the normalized reference list to local file paths (with labels), in reference order.
|
|
10
26
|
// imageKey is the imageRefs key of the image being generated (used for warnings and url
|
|
11
27
|
// download file naming). A named reference that is missing from imageRefs warns and is
|
|
@@ -62,10 +78,7 @@ export const generateReferenceImage = async (inputs) => {
|
|
|
62
78
|
sessionType: "imageReference",
|
|
63
79
|
},
|
|
64
80
|
},
|
|
65
|
-
params:
|
|
66
|
-
model: imageAgentInfo.imageParams.model,
|
|
67
|
-
canvasSize: image.canvasSize ?? context.presentationStyle.canvasSize,
|
|
68
|
-
},
|
|
81
|
+
params: referenceImageGraphParams(imageAgentInfo, image.canvasSize ?? context.presentationStyle.canvasSize),
|
|
69
82
|
},
|
|
70
83
|
},
|
|
71
84
|
};
|
|
@@ -145,11 +158,7 @@ const generateReferenceMovie = async (inputs) => {
|
|
|
145
158
|
sessionType: "imageReference",
|
|
146
159
|
},
|
|
147
160
|
},
|
|
148
|
-
params:
|
|
149
|
-
model: movieAgentInfo.movieParams.model,
|
|
150
|
-
canvasSize: context.presentationStyle.canvasSize,
|
|
151
|
-
generateAudio: movieAgentInfo.movieParams.generateAudio,
|
|
152
|
-
},
|
|
161
|
+
params: referenceMovieGraphParams(movieAgentInfo, context.presentationStyle.canvasSize),
|
|
153
162
|
},
|
|
154
163
|
},
|
|
155
164
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import { GraphAILogger } from "graphai";
|
|
3
|
-
import { provider2ImageAgent, deprecatedGoogleImageModelHints, vertexAIGlobalOnlyImageModels, } from "../types/provider2agent.js";
|
|
3
|
+
import { provider2ImageAgent, deprecatedGoogleImageModelHints, vertexAIGlobalOnlyImageModels, isGeminiImageModel, vertexImageLocation, } from "../types/provider2agent.js";
|
|
4
4
|
import { apiKeyMissingError, agentIncorrectAPIKeyError, agentGenerationError, agentInvalidResponseError, imageAction, imageFileTarget, unsupportedModelTarget, hasCause, getGenAIErrorReason, resultify, } from "../utils/error_cause.js";
|
|
5
5
|
import { getAspectRatio } from "../utils/utils.js";
|
|
6
6
|
import { ASPECT_RATIOS, PRO_ASPECT_RATIOS } from "../types/const.js";
|
|
@@ -85,7 +85,7 @@ export const imageGenAIAgent = async ({ namedInputs, params, config, }) => {
|
|
|
85
85
|
const apiKey = config?.apiKey;
|
|
86
86
|
const ai = params.vertexai_project
|
|
87
87
|
? (() => {
|
|
88
|
-
const location = params.vertexai_location
|
|
88
|
+
const location = vertexImageLocation(model, params.vertexai_location);
|
|
89
89
|
if (vertexAIGlobalOnlyImageModels.has(model) && location !== "global") {
|
|
90
90
|
GraphAILogger.warn(`imageGenAIAgent: model "${model}" on Vertex AI is only available in location "global", but got "${location}". Set imageParams.vertexai_location to "global".`);
|
|
91
91
|
}
|
|
@@ -99,7 +99,7 @@ export const imageGenAIAgent = async ({ namedInputs, params, config, }) => {
|
|
|
99
99
|
}
|
|
100
100
|
return new GoogleGenAI({ apiKey, httpOptions: { timeout: GENAI_REQUEST_TIMEOUT_MS } });
|
|
101
101
|
})();
|
|
102
|
-
if (model
|
|
102
|
+
if (isGeminiImageModel(model)) {
|
|
103
103
|
const contentParams = (() => {
|
|
104
104
|
const contents = getGeminiContents(prompt, referenceImages);
|
|
105
105
|
return {
|
|
@@ -4,7 +4,7 @@ import { GraphAILogger } from "graphai";
|
|
|
4
4
|
import { toFile, AuthenticationError, RateLimitError, APIError } from "openai";
|
|
5
5
|
import { createOpenAIClient } from "../utils/openai_client.js";
|
|
6
6
|
import { safeFetch, FETCH_DOWNLOAD_TIMEOUT_MS } from "../utils/fetch.js";
|
|
7
|
-
import { provider2ImageAgent, gptImages, deprecatedOpenAIImageModelHints } from "../types/provider2agent.js";
|
|
7
|
+
import { provider2ImageAgent, gptImages, gptImageQuality, deprecatedOpenAIImageModelHints } from "../types/provider2agent.js";
|
|
8
8
|
import { apiKeyMissingError, agentGenerationError, openAIAgentGenerationError, agentIncorrectAPIKeyError, agentAPIRateLimitError, agentInvalidResponseError, imageAction, imageFileTarget, unsupportedModelTarget, } from "../utils/error_cause.js";
|
|
9
9
|
const isDeprecatedOpenAIImageModel = (model) => model in deprecatedOpenAIImageModelHints;
|
|
10
10
|
export const buildDeprecatedModelMessage = (model) => {
|
|
@@ -63,8 +63,9 @@ export const imageOpenaiAgent = async ({ namedInputs, params, config, }) => {
|
|
|
63
63
|
if (gptImages.includes(model)) {
|
|
64
64
|
imageOptions.moderation = moderation || "auto";
|
|
65
65
|
imageOptions.background = "opaque";
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
const resolvedQuality = gptImageQuality(model, quality);
|
|
67
|
+
if (resolvedQuality) {
|
|
68
|
+
imageOptions.quality = resolvedQuality;
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
const response = await (async () => {
|
|
@@ -5,7 +5,7 @@ import { apiKeyMissingError, agentGenerationError, agentInvalidResponseError, im
|
|
|
5
5
|
import { getAspectRatio } from "../utils/utils.js";
|
|
6
6
|
import { ffmpegGetMediaDuration } from "../utils/ffmpeg_utils.js";
|
|
7
7
|
import { ASPECT_RATIOS } from "../types/const.js";
|
|
8
|
-
import { getModelDuration, provider2MovieAgent, AUDIO_MODE_NEVER, AUDIO_MODE_ALWAYS } from "../types/provider2agent.js";
|
|
8
|
+
import { getModelDuration, provider2MovieAgent, AUDIO_MODE_NEVER, AUDIO_MODE_ALWAYS, defaultMovieModel, veoExtensionCount, VEO_EXTENSION_MODEL, VEO_SEGMENT_SEC, } from "../types/provider2agent.js";
|
|
9
9
|
// Per-request timeout so a stalled GenAI video API call rejects instead of hanging.
|
|
10
10
|
const GENAI_REQUEST_TIMEOUT_MS = 120_000;
|
|
11
11
|
// Wall-clock cap on the long-running video operation poll loop (Veo runs minutes).
|
|
@@ -89,14 +89,12 @@ const createVeo31Payload = (model, prompt, aspectRatio, source) => ({
|
|
|
89
89
|
...source,
|
|
90
90
|
});
|
|
91
91
|
const generateExtendedVideo = async (ai, model, prompt, aspectRatio, imagePath, requestedDuration, movieFile, isVertexAI) => {
|
|
92
|
-
const
|
|
93
|
-
const maxExtensionDuration = 8;
|
|
94
|
-
const extensionsNeeded = Math.ceil((requestedDuration - initialDuration) / maxExtensionDuration);
|
|
92
|
+
const extensionsNeeded = veoExtensionCount(requestedDuration);
|
|
95
93
|
GraphAILogger.info(`Veo 3.1 video extension: ${extensionsNeeded} extensions needed for ${requestedDuration}s target`);
|
|
96
94
|
const generateIteration = async (iteration, accumulatedDuration, previousVideo) => {
|
|
97
95
|
const isInitial = iteration === 0;
|
|
98
96
|
const remainingDuration = requestedDuration - accumulatedDuration;
|
|
99
|
-
const extensionDuration = isInitial ?
|
|
97
|
+
const extensionDuration = isInitial ? VEO_SEGMENT_SEC : (getModelDuration("google", model, remainingDuration) ?? VEO_SEGMENT_SEC);
|
|
100
98
|
const getSource = () => {
|
|
101
99
|
if (isInitial)
|
|
102
100
|
return imagePath ? { image: loadImageAsBase64(imagePath) } : undefined;
|
|
@@ -172,7 +170,7 @@ const generateStandardVideo = async (ai, model, prompt, aspectRatio, imagePath,
|
|
|
172
170
|
export const movieGenAIAgent = async ({ namedInputs, params, config, }) => {
|
|
173
171
|
const { prompt, imagePath, lastFrameImagePath, referenceImages, movieFile } = namedInputs;
|
|
174
172
|
const aspectRatio = getAspectRatio(params.canvasSize, ASPECT_RATIOS);
|
|
175
|
-
const model = params.model ??
|
|
173
|
+
const model = params.model ?? defaultMovieModel("google", params);
|
|
176
174
|
const apiKey = config?.apiKey;
|
|
177
175
|
try {
|
|
178
176
|
const requestedDuration = params.duration ?? 8;
|
|
@@ -211,7 +209,7 @@ export const movieGenAIAgent = async ({ namedInputs, params, config, }) => {
|
|
|
211
209
|
return new GoogleGenAI({ apiKey, httpOptions: { timeout: GENAI_REQUEST_TIMEOUT_MS } });
|
|
212
210
|
})();
|
|
213
211
|
// Veo 3.1: Video extension mode for videos longer than 8s
|
|
214
|
-
if (model ===
|
|
212
|
+
if (model === VEO_EXTENSION_MODEL && requestedDuration > VEO_SEGMENT_SEC && params.canvasSize) {
|
|
215
213
|
return generateExtendedVideo(ai, model, prompt, aspectRatio, imagePath, requestedDuration, movieFile, isVertexAI);
|
|
216
214
|
}
|
|
217
215
|
// Standard mode
|
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
import type { AgentFunction, AgentFunctionInfo } from "graphai";
|
|
2
|
+
import { type GenerateContentConfig } from "@google/genai";
|
|
3
|
+
import { buildGeminiInteractionsTtsRequest, type GeminiInteractionsTtsInput } from "../utils/gemini_tts.js";
|
|
2
4
|
import type { GoogleTTSAgentParams, AgentBufferResult, AgentTextInputs, AgentErrorResult } from "../types/agent.js";
|
|
5
|
+
import type { AgentUsage } from "../types/usage.js";
|
|
6
|
+
type GeminiAudio = {
|
|
7
|
+
rawPcm: Buffer;
|
|
8
|
+
sampleRate: number;
|
|
9
|
+
usage: AgentUsage | undefined;
|
|
10
|
+
};
|
|
11
|
+
type InlineAudioResponse = {
|
|
12
|
+
candidates?: {
|
|
13
|
+
content?: {
|
|
14
|
+
parts?: {
|
|
15
|
+
inlineData?: {
|
|
16
|
+
data?: string;
|
|
17
|
+
mimeType?: string;
|
|
18
|
+
};
|
|
19
|
+
}[];
|
|
20
|
+
};
|
|
21
|
+
}[];
|
|
22
|
+
usageMetadata?: {
|
|
23
|
+
promptTokenCount?: number;
|
|
24
|
+
candidatesTokenCount?: number;
|
|
25
|
+
totalTokenCount?: number;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
type InteractionAudioResponse = {
|
|
29
|
+
output_audio?: {
|
|
30
|
+
data?: string;
|
|
31
|
+
mime_type?: string;
|
|
32
|
+
sample_rate?: number;
|
|
33
|
+
};
|
|
34
|
+
usage?: {
|
|
35
|
+
total_input_tokens?: number;
|
|
36
|
+
total_output_tokens?: number;
|
|
37
|
+
total_tokens?: number;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
export type GeminiTtsClient = {
|
|
41
|
+
models: {
|
|
42
|
+
generateContent: (params: {
|
|
43
|
+
model: string;
|
|
44
|
+
contents: {
|
|
45
|
+
parts: {
|
|
46
|
+
text: string;
|
|
47
|
+
}[];
|
|
48
|
+
}[];
|
|
49
|
+
config: GenerateContentConfig;
|
|
50
|
+
}) => Promise<InlineAudioResponse>;
|
|
51
|
+
};
|
|
52
|
+
interactions: {
|
|
53
|
+
create: (params: ReturnType<typeof buildGeminiInteractionsTtsRequest>, options?: {
|
|
54
|
+
timeout?: number;
|
|
55
|
+
}) => Promise<InteractionAudioResponse>;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
export declare const generateGeminiTts: (ai: GeminiTtsClient, input: GeminiInteractionsTtsInput) => Promise<GeminiAudio>;
|
|
3
59
|
export declare const ttsGeminiAgent: AgentFunction<GoogleTTSAgentParams, AgentBufferResult | AgentErrorResult, AgentTextInputs>;
|
|
4
60
|
declare const ttsGeminiAgentInfo: AgentFunctionInfo;
|
|
5
61
|
export default ttsGeminiAgentInfo;
|
|
@@ -3,16 +3,51 @@ import { GoogleGenAI } from "@google/genai";
|
|
|
3
3
|
import { provider2TTSAgent } from "../types/provider2agent.js";
|
|
4
4
|
import { agentIncorrectAPIKeyError, apiKeyMissingError, agentGenerationError, audioAction, audioFileTarget, getGenAIErrorReason, } from "../utils/error_cause.js";
|
|
5
5
|
import { pcmToMp3 } from "../utils/ffmpeg_utils.js";
|
|
6
|
+
import { GEMINI_TTS_SAMPLE_RATE, buildGeminiInteractionsTtsRequest, geminiDirectorsNotesPrompt, pcmFromInteractionAudio, usesGeminiInteractionsTts, } from "../utils/gemini_tts.js";
|
|
6
7
|
// Per-request timeout so a stalled GenAI TTS call rejects (and GraphAI retry can
|
|
7
8
|
// recover) instead of hanging. Generous vs. normal generation time.
|
|
8
9
|
const GENAI_REQUEST_TIMEOUT_MS = 120_000;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
const geminiUsage = (model, inputTokens, outputTokens, totalTokens) => ({
|
|
11
|
+
provider: "gemini",
|
|
12
|
+
model,
|
|
13
|
+
inputTokens,
|
|
14
|
+
outputTokens,
|
|
15
|
+
totalTokens,
|
|
16
|
+
});
|
|
17
|
+
// 2.5 TTS: generateContent, direction embedded in the prompt, headerless PCM with the rate in the mimeType.
|
|
18
|
+
const generateWithContent = async (ai, { model, text, voice, instructions }) => {
|
|
19
|
+
const response = await ai.models.generateContent({
|
|
20
|
+
model,
|
|
21
|
+
contents: [{ parts: [{ text: geminiDirectorsNotesPrompt(text, instructions) }] }],
|
|
22
|
+
config: {
|
|
23
|
+
responseModalities: ["AUDIO"],
|
|
24
|
+
speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: voice } } },
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const inlineData = response.candidates?.[0]?.content?.parts?.[0]?.inlineData;
|
|
28
|
+
const pcmBase64 = inlineData?.data;
|
|
29
|
+
if (!pcmBase64 || typeof pcmBase64 !== "string")
|
|
30
|
+
throw new Error("No audio data returned");
|
|
31
|
+
// Extract sample rate from mimeType (e.g., "audio/L16;codec=pcm;rate=24000")
|
|
32
|
+
const rateMatch = inlineData?.mimeType?.match(/rate=(\d+)/);
|
|
33
|
+
const sampleRate = rateMatch ? parseInt(rateMatch[1]) : GEMINI_TTS_SAMPLE_RATE;
|
|
34
|
+
const metadata = response.usageMetadata;
|
|
35
|
+
return {
|
|
36
|
+
rawPcm: Buffer.from(pcmBase64, "base64"),
|
|
37
|
+
sampleRate,
|
|
38
|
+
usage: metadata ? geminiUsage(model, metadata.promptTokenCount, metadata.candidatesTokenCount, metadata.totalTokenCount) : undefined,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const generateWithInteractions = async (ai, input) => {
|
|
42
|
+
// Passed per request too: the Interactions client is built apart from the models client.
|
|
43
|
+
const interaction = await ai.interactions.create(buildGeminiInteractionsTtsRequest(input), { timeout: GENAI_REQUEST_TIMEOUT_MS });
|
|
44
|
+
const usage = interaction.usage;
|
|
45
|
+
return {
|
|
46
|
+
...pcmFromInteractionAudio(interaction.output_audio),
|
|
47
|
+
usage: usage ? geminiUsage(input.model, usage.total_input_tokens, usage.total_output_tokens, usage.total_tokens) : undefined,
|
|
48
|
+
};
|
|
15
49
|
};
|
|
50
|
+
export const generateGeminiTts = (ai, input) => usesGeminiInteractionsTts(input.model) ? generateWithInteractions(ai, input) : generateWithContent(ai, input);
|
|
16
51
|
export const ttsGeminiAgent = async ({ namedInputs, params, config, }) => {
|
|
17
52
|
const { text } = namedInputs;
|
|
18
53
|
const { model, voice, suppressError, instructions } = params;
|
|
@@ -25,38 +60,8 @@ export const ttsGeminiAgent = async ({ namedInputs, params, config, }) => {
|
|
|
25
60
|
const geminiResult = await (async () => {
|
|
26
61
|
try {
|
|
27
62
|
const ai = new GoogleGenAI({ apiKey, httpOptions: { timeout: GENAI_REQUEST_TIMEOUT_MS } });
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
contents: [{ parts: [{ text: getPrompt(text, instructions) }] }],
|
|
31
|
-
config: {
|
|
32
|
-
responseModalities: ["AUDIO"],
|
|
33
|
-
speechConfig: {
|
|
34
|
-
voiceConfig: {
|
|
35
|
-
prebuiltVoiceConfig: { voiceName: voice ?? provider2TTSAgent.gemini.defaultVoice },
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
const inlineData = response.candidates?.[0]?.content?.parts?.[0]?.inlineData;
|
|
41
|
-
const pcmBase64 = inlineData?.data;
|
|
42
|
-
const mimeType = inlineData?.mimeType;
|
|
43
|
-
if (!pcmBase64 || typeof pcmBase64 !== "string")
|
|
44
|
-
throw new Error("No audio data returned");
|
|
45
|
-
// Extract sample rate from mimeType (e.g., "audio/L16;codec=pcm;rate=24000")
|
|
46
|
-
const rateMatch = mimeType?.match(/rate=(\d+)/);
|
|
47
|
-
const sampleRate = rateMatch ? parseInt(rateMatch[1]) : 24000;
|
|
48
|
-
const rawPcm = Buffer.from(pcmBase64, "base64");
|
|
49
|
-
const usedModel = model ?? provider2TTSAgent.gemini.defaultModel;
|
|
50
|
-
const usage = response.usageMetadata
|
|
51
|
-
? {
|
|
52
|
-
provider: "gemini",
|
|
53
|
-
model: usedModel,
|
|
54
|
-
inputTokens: response.usageMetadata.promptTokenCount,
|
|
55
|
-
outputTokens: response.usageMetadata.candidatesTokenCount,
|
|
56
|
-
totalTokens: response.usageMetadata.totalTokenCount,
|
|
57
|
-
}
|
|
58
|
-
: undefined;
|
|
59
|
-
return { rawPcm, sampleRate, usage };
|
|
63
|
+
const request = { model: model ?? provider2TTSAgent.gemini.defaultModel, text, voice: voice ?? provider2TTSAgent.gemini.defaultVoice, instructions };
|
|
64
|
+
return await generateGeminiTts(ai, request);
|
|
60
65
|
}
|
|
61
66
|
catch (e) {
|
|
62
67
|
if (suppressError) {
|
|
@@ -484,7 +484,7 @@ export const scriptTemplates = [
|
|
|
484
484
|
],
|
|
485
485
|
filename: "html",
|
|
486
486
|
htmlImageParams: {
|
|
487
|
-
model: "claude-sonnet-4-
|
|
487
|
+
model: "claude-sonnet-4-6",
|
|
488
488
|
provider: "anthropic",
|
|
489
489
|
},
|
|
490
490
|
lang: "en",
|
|
@@ -987,7 +987,7 @@ export const scriptTemplates = [
|
|
|
987
987
|
],
|
|
988
988
|
filename: "presentation",
|
|
989
989
|
htmlImageParams: {
|
|
990
|
-
model: "claude-sonnet-4-
|
|
990
|
+
model: "claude-sonnet-4-6",
|
|
991
991
|
provider: "anthropic",
|
|
992
992
|
},
|
|
993
993
|
lang: "en",
|
|
@@ -49,7 +49,7 @@ export const templateDataSet = {
|
|
|
49
49
|
"```",
|
|
50
50
|
html: "Another LLM will generate actual slides from the prompt and data for each beat. Adding optional data would help it to generate more compelling slide. Mention the reference in one of beats, if it exists. The valid type of reference is 'article', 'paper', 'image', 'video', 'audio'. Use the JSON below as a template.\n" +
|
|
51
51
|
"```JSON\n" +
|
|
52
|
-
'{"$mulmocast":{"version":"1.1","credit":"closing"},"references":[{"url":"https://www.somegreatwebsite.com/article/123","title":"Title of the article we are referencing","type":"[TYPE OF ARTICLE: article, paper, image, video, audio]"}],"title":"[TITLE: Brief, engaging title for the topic]","htmlImageParams":{"provider":"anthropic","model":"claude-sonnet-4-
|
|
52
|
+
'{"$mulmocast":{"version":"1.1","credit":"closing"},"references":[{"url":"https://www.somegreatwebsite.com/article/123","title":"Title of the article we are referencing","type":"[TYPE OF ARTICLE: article, paper, image, video, audio]"}],"title":"[TITLE: Brief, engaging title for the topic]","htmlImageParams":{"provider":"anthropic","model":"claude-sonnet-4-6"},"lang":"en","beats":[{"text":"[NARRATION: Narration for the beat.]","htmlPrompt":{"prompt":"[PROMPT to create appropriate HTML page for the beat.]"}},{"text":"[NARRATION: Narration for the beat.]","htmlPrompt":{"prompt":"[PROMPT to create appropriate HTML page for the beat with the data.]","data":{"description":"DATA TO BE PRESENTED IN THIS BEAT (in any format)]","net_income":{"Q2 FY2024":320,"Q3 FY2024":333,"Q4 FY2024":350},"unit":"USD (Million)"}}}],"canvasSize":{"width":1536,"height":1024}}\n' +
|
|
53
53
|
"```",
|
|
54
54
|
image_prompt: "Another AI will generate images for each beat based on the image prompt of that beat. Movie prompts must be written in English. Mention the reference in one of beats, if it exists. Use the JSON below as a template.\n" +
|
|
55
55
|
"```JSON\n" +
|
|
@@ -8,7 +8,7 @@ import { userAssert } from "../utils/utils.js";
|
|
|
8
8
|
import { slideThemes } from "../data/slideThemes.js";
|
|
9
9
|
import { text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoCanvasDimensionSchema, mulmoTransitionSchema, mulmoFillOptionSchema, } from "../types/schema.js";
|
|
10
10
|
import { MulmoBeatMethods } from "./mulmo_beat.js";
|
|
11
|
-
import { provider2ImageAgent, provider2MovieAgent, provider2LLMAgent, provider2TTSAgent, provider2SoundEffectAgent, provider2LipSyncAgent, defaultProviders, getModelAudio, AUDIO_MODE_ALWAYS, } from "../types/provider2agent.js";
|
|
11
|
+
import { provider2ImageAgent, provider2MovieAgent, defaultMovieModel, provider2LLMAgent, provider2TTSAgent, provider2SoundEffectAgent, provider2LipSyncAgent, defaultProviders, getModelAudio, AUDIO_MODE_ALWAYS, } from "../types/provider2agent.js";
|
|
12
12
|
const defaultTextSlideStyles = [
|
|
13
13
|
'*,*::before,*::after{box-sizing:border-box}body,h1,h2,h3,h4,p,figure,blockquote,dl,dd{margin:0}ul[role="list"],ol[role="list"]{list-style:none}html:focus-within{scroll-behavior:smooth}body{min-height:100vh;text-rendering:optimizeSpeed;line-height:1.5}a:not([class]){text-decoration-skip-ink:auto}img,picture{max-width:100%;display:block}input,button,textarea,select{font:inherit}@media(prefers-reduced-motion:reduce){html:focus-within{scroll-behavior:auto}*,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important;scroll-behavior:auto !important}}',
|
|
14
14
|
"body { margin: 60px; margin-top: 40px; color:#333; background-color:#fff; font-size: 30px; font-family: Arial, sans-serif; box-sizing: border-box; height: 100vh }",
|
|
@@ -160,7 +160,7 @@ export const MulmoPresentationStyleMethods = {
|
|
|
160
160
|
const imageParams = { ...presentationStyle.imageParams, ...beat?.imageParams };
|
|
161
161
|
const provider = MulmoPresentationStyleMethods.getText2ImageProvider(imageParams?.provider) ?? defaultProviders.text2image;
|
|
162
162
|
const agentInfo = provider2ImageAgent[provider];
|
|
163
|
-
// The default text2image model is gpt-image-
|
|
163
|
+
// The default text2image model is gpt-image-2.5-sunburst from OpenAI.
|
|
164
164
|
const defaultImageParams = {
|
|
165
165
|
provider,
|
|
166
166
|
model: agentInfo.defaultModel,
|
|
@@ -189,7 +189,7 @@ export const MulmoPresentationStyleMethods = {
|
|
|
189
189
|
return true;
|
|
190
190
|
}
|
|
191
191
|
const provider = text2MovieProviderSchema.parse(movieParams?.provider ?? defaultProviders.text2movie);
|
|
192
|
-
const model = movieParams?.model ??
|
|
192
|
+
const model = movieParams?.model ?? defaultMovieModel(provider, movieParams);
|
|
193
193
|
return getModelAudio(provider, model)?.mode === AUDIO_MODE_ALWAYS;
|
|
194
194
|
},
|
|
195
195
|
getSoundEffectAgentInfo(presentationStyle, beat) {
|
|
@@ -42,21 +42,25 @@ export declare const provider2TTSAgent: {
|
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
44
|
export declare const gptImages: string[];
|
|
45
|
+
export declare const isGptImage25Model: (model: string) => boolean;
|
|
46
|
+
export declare const gptImageQuality: <Quality extends string>(model: string, quality?: Quality) => Quality | "high" | undefined;
|
|
45
47
|
export declare const deprecatedOpenAIImageModelHints: {
|
|
46
|
-
readonly "dall-e-2": "Use 'gpt-image-
|
|
47
|
-
readonly "dall-e-3": "Use 'gpt-image-
|
|
48
|
+
readonly "dall-e-2": "Use 'gpt-image-2.5-sunburst' or another supported model.";
|
|
49
|
+
readonly "dall-e-3": "Use 'gpt-image-2.5-sunburst' or another supported model.";
|
|
48
50
|
};
|
|
49
51
|
export type DeprecatedOpenAIImageModel = keyof typeof deprecatedOpenAIImageModelHints;
|
|
50
52
|
export declare const deprecatedGoogleImageModelHints: {
|
|
51
|
-
readonly "imagen-3.0-generate-002": "Use 'gemini-
|
|
52
|
-
readonly "imagen-4.0-generate-001": "Use 'gemini-
|
|
53
|
-
readonly "imagen-4.0-ultra-generate-001": "Use 'gemini-
|
|
54
|
-
readonly "imagen-4.0-fast-generate-001": "Use 'gemini-
|
|
55
|
-
readonly "imagen-4.0-generate-preview-06-06": "Use 'gemini-
|
|
56
|
-
readonly "imagen-4.0-ultra-generate-preview-06-06": "Use 'gemini-
|
|
53
|
+
readonly "imagen-3.0-generate-002": "Use 'gemini-3.1-flash-lite-image' or 'gemini-3-pro-image' instead.";
|
|
54
|
+
readonly "imagen-4.0-generate-001": "Use 'gemini-3.1-flash-lite-image' or 'gemini-3-pro-image' instead.";
|
|
55
|
+
readonly "imagen-4.0-ultra-generate-001": "Use 'gemini-3.1-flash-lite-image' or 'gemini-3-pro-image' instead.";
|
|
56
|
+
readonly "imagen-4.0-fast-generate-001": "Use 'gemini-3.1-flash-lite-image' or 'gemini-3-pro-image' instead.";
|
|
57
|
+
readonly "imagen-4.0-generate-preview-06-06": "Use 'gemini-3.1-flash-lite-image' or 'gemini-3-pro-image' instead.";
|
|
58
|
+
readonly "imagen-4.0-ultra-generate-preview-06-06": "Use 'gemini-3.1-flash-lite-image' or 'gemini-3-pro-image' instead.";
|
|
57
59
|
};
|
|
58
60
|
export type DeprecatedGoogleImageModel = keyof typeof deprecatedGoogleImageModelHints;
|
|
61
|
+
export declare const isGeminiImageModel: (model: string) => boolean;
|
|
59
62
|
export declare const vertexAIGlobalOnlyImageModels: ReadonlySet<string>;
|
|
63
|
+
export declare const vertexImageLocation: (model: string, location?: string) => string;
|
|
60
64
|
export declare const provider2ImageAgent: {
|
|
61
65
|
openai: {
|
|
62
66
|
agentName: string;
|
|
@@ -169,34 +173,34 @@ export declare const provider2LipSyncAgent: {
|
|
|
169
173
|
export declare const provider2LLMAgent: {
|
|
170
174
|
readonly openai: {
|
|
171
175
|
readonly agentName: "openAIAgent";
|
|
172
|
-
readonly defaultModel: "gpt-5";
|
|
176
|
+
readonly defaultModel: "gpt-5.6-sol";
|
|
173
177
|
readonly keyName: "OPENAI_API_KEY";
|
|
174
178
|
readonly baseURLKeyName: "OPENAI_BASE_URL";
|
|
175
179
|
readonly apiVersionKeyName: "OPENAI_API_VERSION";
|
|
176
180
|
readonly max_tokens: 8192;
|
|
177
|
-
readonly models: readonly ["gpt-5", "gpt-5-nano", "gpt-5-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o3", "o3-mini", "o3-pro", "o1", "o1-pro", "gpt-4o", "gpt-4o-mini"];
|
|
181
|
+
readonly models: readonly ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5", "gpt-5-nano", "gpt-5-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o3", "o3-mini", "o3-pro", "o1", "o1-pro", "gpt-4o", "gpt-4o-mini"];
|
|
178
182
|
};
|
|
179
183
|
readonly anthropic: {
|
|
180
184
|
readonly agentName: "anthropicAgent";
|
|
181
|
-
readonly defaultModel: "claude-sonnet-4-
|
|
185
|
+
readonly defaultModel: "claude-sonnet-4-6";
|
|
182
186
|
readonly max_tokens: 8192;
|
|
183
|
-
readonly models: readonly ["claude-
|
|
187
|
+
readonly models: readonly ["claude-sonnet-4-6", "claude-sonnet-4-5-20250929", "claude-haiku-4-5-20251001"];
|
|
184
188
|
readonly keyName: "ANTHROPIC_API_KEY";
|
|
185
189
|
readonly apiKeyNameOverride: "ANTHROPIC_API_TOKEN";
|
|
186
190
|
};
|
|
187
191
|
readonly gemini: {
|
|
188
192
|
readonly agentName: "geminiAgent";
|
|
189
|
-
readonly defaultModel: "gemini-
|
|
193
|
+
readonly defaultModel: "gemini-3.8-flash";
|
|
190
194
|
readonly max_tokens: 8192;
|
|
191
|
-
readonly models: readonly ["gemini-3.1-pro-preview", "gemini-3-flash-preview", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite"];
|
|
195
|
+
readonly models: readonly ["gemini-3.8-flash", "gemini-3.1-pro-preview", "gemini-3-flash-preview", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite"];
|
|
192
196
|
readonly keyName: "GEMINI_API_KEY";
|
|
193
197
|
};
|
|
194
198
|
readonly groq: {
|
|
195
199
|
readonly agentName: "groqAgent";
|
|
196
|
-
readonly defaultModel: "
|
|
200
|
+
readonly defaultModel: "openai/gpt-oss-20b";
|
|
197
201
|
readonly keyName: "GROQ_API_KEY";
|
|
198
202
|
readonly max_tokens: 4096;
|
|
199
|
-
readonly models: readonly ["
|
|
203
|
+
readonly models: readonly ["openai/gpt-oss-20b", "openai/gpt-oss-120b"];
|
|
200
204
|
};
|
|
201
205
|
readonly mock: {
|
|
202
206
|
readonly agentName: "mediaMockAgent";
|
|
@@ -229,6 +233,13 @@ export declare const ProviderName: {
|
|
|
229
233
|
export declare const isImageProvider: (provider: string | undefined) => provider is keyof typeof provider2ImageAgent;
|
|
230
234
|
export declare const getMaxImageReferenceImages: (provider: string | undefined, model: string) => number | undefined;
|
|
231
235
|
export declare const getModelAudio: (provider: keyof typeof provider2MovieAgent, model: string) => MovieAudioSpec | undefined;
|
|
236
|
+
export declare const defaultMovieModel: (provider: keyof typeof provider2MovieAgent, movieParams?: {
|
|
237
|
+
vertexai_project?: string;
|
|
238
|
+
}) => string;
|
|
239
|
+
export declare const VEO_EXTENSION_MODEL = "veo-3.1-generate-preview";
|
|
240
|
+
export declare const VEO_SEGMENT_SEC = 8;
|
|
241
|
+
export declare const veoExtensionCount: (requestedSec: number) => number;
|
|
242
|
+
export declare const veoExtendedSeconds: (requestedSec: number) => number;
|
|
232
243
|
export declare const getModelDuration: (provider: keyof typeof provider2MovieAgent, model: string, movieDuration?: number) => number | undefined;
|
|
233
244
|
export type ModelPricing = {
|
|
234
245
|
unit: "tokens" | "chars" | "seconds" | "images";
|