mulmocast 2.17.0 → 2.19.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 +110 -38
- package/assets/schemas/mulmo_script.json +100 -0
- package/lib/actions/audio.js +4 -1
- package/lib/actions/avatar.js +4 -1
- package/lib/actions/captions.js +1 -1
- package/lib/actions/image_agents.d.ts +1 -0
- package/lib/actions/image_agents.js +2 -2
- package/lib/actions/images.d.ts +2 -0
- package/lib/actions/images.js +1 -0
- package/lib/actions/pdf.js +23 -2
- package/lib/agents/add_bgm_agent.d.ts +13 -1
- package/lib/agents/add_bgm_agent.js +77 -4
- package/lib/agents/movie_genai_agent.d.ts +24 -0
- package/lib/agents/movie_genai_agent.js +56 -43
- package/lib/cli/bin.js +0 -0
- package/lib/cli/commands/bundle/builder.d.ts +2 -0
- package/lib/cli/commands/html/builder.d.ts +2 -0
- package/lib/cli/commands/markdown/builder.d.ts +2 -0
- package/lib/cli/commands/viewer/builder.d.ts +2 -0
- package/lib/cli/common.d.ts +2 -0
- package/lib/cli/common.js +5 -0
- package/lib/cli/helpers.js +3 -1
- package/lib/mcp/server.js +0 -0
- package/lib/methods/mulmo_studio_context.d.ts +6 -0
- package/lib/methods/mulmo_studio_context.js +26 -0
- package/lib/types/provider2agent.d.ts +1 -4
- package/lib/types/provider2agent.js +27 -24
- package/lib/types/schema.d.ts +64 -0
- package/lib/types/schema.js +42 -0
- package/lib/types/type.d.ts +4 -1
- package/lib/utils/context.d.ts +32 -0
- package/lib/utils/estimate_usage.js +6 -5
- package/lib/utils/gemini_omni_video.d.ts +49 -0
- package/lib/utils/gemini_omni_video.js +36 -0
- package/lib/utils/html_render.d.ts +6 -5
- package/lib/utils/html_render.js +37 -30
- package/lib/utils/image_plugins/chart.js +2 -1
- package/lib/utils/image_plugins/html_tailwind.js +5 -4
- package/lib/utils/image_plugins/markdown.js +2 -1
- package/lib/utils/image_plugins/mermaid.js +2 -1
- package/lib/utils/image_plugins/slide.js +2 -1
- package/lib/utils/image_plugins/text_slide.js +2 -1
- package/lib/utils/image_plugins/vision.js +7 -0
- package/lib/utils/jinglescript.d.ts +12 -0
- package/lib/utils/jinglescript.js +63 -0
- package/lib/utils/render_network_guard.d.ts +2 -0
- package/lib/utils/render_network_guard.js +36 -0
- package/lib/utils/render_network_policy.d.ts +15 -0
- package/lib/utils/render_network_policy.js +85 -0
- package/package.json +5 -4
- package/scripts/test/test_genai.json +10 -9
- package/scripts/test/test_genai_movie.json +1 -1
- package/scripts/test/test_image_references.json +3 -2
- package/scripts/test/test_jinglescript_sound_effects.json +101 -0
- package/scripts/test/test_movie_references.json +2 -1
- package/scripts/test/test_sound_effects.json +64 -0
- package/scripts/test/test_sound_effects_animation.json +441 -0
- package/scripts/test/test_veo31_lite.json +3 -3
- package/scripts/test/test_captions_2.json~ +0 -278
- package/scripts/test/test_en_portrait.json~ +0 -31
- package/scripts/test/test_slide_image_ref_gemini_en.json~ +0 -289
- package/scripts/test/test_vertexai.json~ +0 -21
package/lib/types/schema.d.ts
CHANGED
|
@@ -4985,6 +4985,22 @@ export declare const mulmoMovieParamsSchema: z.ZodObject<{
|
|
|
4985
4985
|
concurrency: z.ZodOptional<z.ZodNumber>;
|
|
4986
4986
|
generateAudio: z.ZodOptional<z.ZodBoolean>;
|
|
4987
4987
|
}, z.core.$strip>;
|
|
4988
|
+
export declare const mulmoBeatSoundEffectSchema: z.ZodObject<{
|
|
4989
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4990
|
+
kind: z.ZodLiteral<"url">;
|
|
4991
|
+
url: z.ZodURL;
|
|
4992
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4993
|
+
kind: z.ZodLiteral<"path">;
|
|
4994
|
+
path: z.ZodString;
|
|
4995
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4996
|
+
kind: z.ZodLiteral<"jinglescript">;
|
|
4997
|
+
score: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
4998
|
+
}, z.core.$strict>], "kind">;
|
|
4999
|
+
startAt: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
5000
|
+
volume: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
5001
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
5002
|
+
loop: z.ZodOptional<z.ZodBoolean>;
|
|
5003
|
+
}, z.core.$strict>;
|
|
4988
5004
|
export declare const mulmoBeatSchema: z.ZodObject<{
|
|
4989
5005
|
speaker: z.ZodOptional<z.ZodString>;
|
|
4990
5006
|
text: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
@@ -9086,6 +9102,22 @@ export declare const mulmoBeatSchema: z.ZodObject<{
|
|
|
9086
9102
|
imagePrompt: z.ZodOptional<z.ZodString>;
|
|
9087
9103
|
moviePrompt: z.ZodOptional<z.ZodString>;
|
|
9088
9104
|
soundEffectPrompt: z.ZodOptional<z.ZodString>;
|
|
9105
|
+
soundEffects: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
9106
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
9107
|
+
kind: z.ZodLiteral<"url">;
|
|
9108
|
+
url: z.ZodURL;
|
|
9109
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
9110
|
+
kind: z.ZodLiteral<"path">;
|
|
9111
|
+
path: z.ZodString;
|
|
9112
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
9113
|
+
kind: z.ZodLiteral<"jinglescript">;
|
|
9114
|
+
score: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
9115
|
+
}, z.core.$strict>], "kind">;
|
|
9116
|
+
startAt: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
9117
|
+
volume: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
9118
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
9119
|
+
loop: z.ZodOptional<z.ZodBoolean>;
|
|
9120
|
+
}, z.core.$strict>>>;
|
|
9089
9121
|
htmlPrompt: z.ZodOptional<z.ZodObject<{
|
|
9090
9122
|
systemPrompt: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
9091
9123
|
prompt: z.ZodString;
|
|
@@ -14353,6 +14385,22 @@ export declare const mulmoScriptSchema: z.ZodObject<{
|
|
|
14353
14385
|
imagePrompt: z.ZodOptional<z.ZodString>;
|
|
14354
14386
|
moviePrompt: z.ZodOptional<z.ZodString>;
|
|
14355
14387
|
soundEffectPrompt: z.ZodOptional<z.ZodString>;
|
|
14388
|
+
soundEffects: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
14389
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
14390
|
+
kind: z.ZodLiteral<"url">;
|
|
14391
|
+
url: z.ZodURL;
|
|
14392
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
14393
|
+
kind: z.ZodLiteral<"path">;
|
|
14394
|
+
path: z.ZodString;
|
|
14395
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
14396
|
+
kind: z.ZodLiteral<"jinglescript">;
|
|
14397
|
+
score: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
14398
|
+
}, z.core.$strict>], "kind">;
|
|
14399
|
+
startAt: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
14400
|
+
volume: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
14401
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
14402
|
+
loop: z.ZodOptional<z.ZodBoolean>;
|
|
14403
|
+
}, z.core.$strict>>>;
|
|
14356
14404
|
htmlPrompt: z.ZodOptional<z.ZodObject<{
|
|
14357
14405
|
systemPrompt: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
14358
14406
|
prompt: z.ZodString;
|
|
@@ -19144,6 +19192,22 @@ export declare const mulmoStudioSchema: z.ZodObject<{
|
|
|
19144
19192
|
imagePrompt: z.ZodOptional<z.ZodString>;
|
|
19145
19193
|
moviePrompt: z.ZodOptional<z.ZodString>;
|
|
19146
19194
|
soundEffectPrompt: z.ZodOptional<z.ZodString>;
|
|
19195
|
+
soundEffects: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
19196
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
19197
|
+
kind: z.ZodLiteral<"url">;
|
|
19198
|
+
url: z.ZodURL;
|
|
19199
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
19200
|
+
kind: z.ZodLiteral<"path">;
|
|
19201
|
+
path: z.ZodString;
|
|
19202
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
19203
|
+
kind: z.ZodLiteral<"jinglescript">;
|
|
19204
|
+
score: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
19205
|
+
}, z.core.$strict>], "kind">;
|
|
19206
|
+
startAt: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
19207
|
+
volume: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
19208
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
19209
|
+
loop: z.ZodOptional<z.ZodBoolean>;
|
|
19210
|
+
}, z.core.$strict>>>;
|
|
19147
19211
|
htmlPrompt: z.ZodOptional<z.ZodObject<{
|
|
19148
19212
|
systemPrompt: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
19149
19213
|
prompt: z.ZodString;
|
package/lib/types/schema.js
CHANGED
|
@@ -612,6 +612,44 @@ export const mulmoMovieParamsSchema = z.object({
|
|
|
612
612
|
concurrency: z.number().int().positive().optional().describe("Max concurrent movie generation requests"),
|
|
613
613
|
generateAudio: z.boolean().optional().describe("Request audio generation in the video (model-dependent)"),
|
|
614
614
|
});
|
|
615
|
+
export const mulmoBeatSoundEffectSchema = z
|
|
616
|
+
.object({
|
|
617
|
+
source: z
|
|
618
|
+
.discriminatedUnion("kind", [
|
|
619
|
+
z.object({ kind: z.literal("url"), url: URLStringSchema }).strict(),
|
|
620
|
+
z.object({ kind: z.literal("path"), path: z.string().min(1) }).strict(),
|
|
621
|
+
z
|
|
622
|
+
.object({
|
|
623
|
+
kind: z.literal("jinglescript"),
|
|
624
|
+
score: z.record(z.string(), z.unknown()).describe("JingleScript score (format jinglescript/1), rendered to audio when the audio is generated"),
|
|
625
|
+
})
|
|
626
|
+
.strict(),
|
|
627
|
+
])
|
|
628
|
+
.describe("Audio file of the sound effect (url or path), or a JingleScript score to synthesize"),
|
|
629
|
+
startAt: z
|
|
630
|
+
.number()
|
|
631
|
+
.min(0)
|
|
632
|
+
.optional()
|
|
633
|
+
.default(0)
|
|
634
|
+
.describe("Seconds from when the beat appears on screen (the same clock as its html animation) at which the sound effect plays. The first beat's narration starts audioParams.introPadding later"),
|
|
635
|
+
volume: z
|
|
636
|
+
.number()
|
|
637
|
+
.min(0)
|
|
638
|
+
.max(4)
|
|
639
|
+
.optional()
|
|
640
|
+
.default(1.0)
|
|
641
|
+
.describe("Volume of the sound effect. 1.0 is the gain of the narration at its default volume; audioParams.audioVolume and ttsVolume do not affect sound effects"),
|
|
642
|
+
duration: z
|
|
643
|
+
.number()
|
|
644
|
+
.positive()
|
|
645
|
+
.optional()
|
|
646
|
+
.describe("Maximum play length in seconds; the effect is cut (with a short fade-out) after it. Default: the whole file, even past the end of the beat"),
|
|
647
|
+
loop: z
|
|
648
|
+
.boolean()
|
|
649
|
+
.optional()
|
|
650
|
+
.describe("Repeat the effect until duration, or until the beat leaves the screen when duration is not set (e.g. a ticking clock)"),
|
|
651
|
+
})
|
|
652
|
+
.strict();
|
|
615
653
|
export const mulmoBeatSchema = z
|
|
616
654
|
.object({
|
|
617
655
|
speaker: speakerIdSchema.optional(),
|
|
@@ -643,6 +681,10 @@ export const mulmoBeatSchema = z
|
|
|
643
681
|
imagePrompt: z.string().optional(),
|
|
644
682
|
moviePrompt: z.string().optional(),
|
|
645
683
|
soundEffectPrompt: z.string().optional(),
|
|
684
|
+
soundEffects: z
|
|
685
|
+
.array(mulmoBeatSoundEffectSchema)
|
|
686
|
+
.optional()
|
|
687
|
+
.describe("Sound effects mixed into the audio track during this beat. Multiple effects may overlap."),
|
|
646
688
|
htmlPrompt: htmlPromptParamsSchema.optional(),
|
|
647
689
|
enableLipSync: z.boolean().optional().describe("Enable lip sync generation for this beat"),
|
|
648
690
|
hidden: z.boolean().optional().describe("Hide this beat from the presentation"),
|
package/lib/types/type.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type CallbackFunction } from "graphai";
|
|
2
2
|
import type { UsageCollectorAPI } from "./usage.js";
|
|
3
|
-
import { langSchema, localizedTextSchema, mulmoBeatSchema, mulmoScriptSchema, mulmoStudioSchema, mulmoStudioBeatSchema, mulmoStoryboardSchema, mulmoStoryboardSceneSchema, mulmoStudioMultiLingualSchema, mulmoStudioMultiLingualArraySchema, mulmoStudioMultiLingualDataSchema, mulmoStudioMultiLingualFileSchema, speakerDictionarySchema, speakerSchema, mulmoSpeechParamsSchema, mulmoImageParamsSchema, mulmoImageParamsImagesValueSchema, mulmoImageParamsImagesSchema, mulmoFillOptionSchema, mulmoTransitionSchema, mulmoVideoFilterSchema, mulmoMovieParamsSchema, mulmoSoundEffectParamsSchema, mulmoLipSyncParamsSchema, textSlideParamsSchema, speechOptionsSchema, speakerDataSchema, mulmoCanvasDimensionSchema, mulmoPromptTemplateSchema, mulmoPromptTemplateFileSchema, text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoPresentationStyleSchema, multiLingualTextsSchema, mulmoImageAssetSchema, mulmoMermaidMediaSchema, mulmoRemotionMediaSchema, remotionCodeSourceSchema, mulmoTextSlideMediaSchema, mulmoMarkdownMediaSchema, mulmoImageMediaSchema, mulmoChartMediaSchema, mediaSourceSchema, mediaSourceMermaidSchema, backgroundImageSchema, backgroundImageSourceSchema, mulmoSessionStateSchema, mulmoOpenAIImageModelSchema, mulmoGoogleImageModelSchema, mulmoGoogleMovieModelSchema, mulmoReplicateMovieModelSchema, mulmoImagePromptMediaSchema, mulmoImageReferenceSchema, mulmoMovieMediaSchema, mulmoMoviePromptMediaSchema, markdownLayoutSchema, row2Schema, grid2x2Schema, mulmoAvatarParamsSchema, avatarPositionSchema, speakerAvatarSchema, mulmoAvatarTrackSchema } from "./schema.js";
|
|
3
|
+
import { langSchema, localizedTextSchema, mulmoBeatSchema, mulmoScriptSchema, mulmoStudioSchema, mulmoStudioBeatSchema, mulmoStoryboardSchema, mulmoStoryboardSceneSchema, mulmoStudioMultiLingualSchema, mulmoStudioMultiLingualArraySchema, mulmoStudioMultiLingualDataSchema, mulmoStudioMultiLingualFileSchema, speakerDictionarySchema, speakerSchema, mulmoSpeechParamsSchema, mulmoImageParamsSchema, mulmoImageParamsImagesValueSchema, mulmoImageParamsImagesSchema, mulmoFillOptionSchema, mulmoTransitionSchema, mulmoVideoFilterSchema, mulmoMovieParamsSchema, mulmoSoundEffectParamsSchema, mulmoBeatSoundEffectSchema, mulmoLipSyncParamsSchema, textSlideParamsSchema, speechOptionsSchema, speakerDataSchema, mulmoCanvasDimensionSchema, mulmoPromptTemplateSchema, mulmoPromptTemplateFileSchema, text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoPresentationStyleSchema, multiLingualTextsSchema, mulmoImageAssetSchema, mulmoMermaidMediaSchema, mulmoRemotionMediaSchema, remotionCodeSourceSchema, mulmoTextSlideMediaSchema, mulmoMarkdownMediaSchema, mulmoImageMediaSchema, mulmoChartMediaSchema, mediaSourceSchema, mediaSourceMermaidSchema, backgroundImageSchema, backgroundImageSourceSchema, mulmoSessionStateSchema, mulmoOpenAIImageModelSchema, mulmoGoogleImageModelSchema, mulmoGoogleMovieModelSchema, mulmoReplicateMovieModelSchema, mulmoImagePromptMediaSchema, mulmoImageReferenceSchema, mulmoMovieMediaSchema, mulmoMoviePromptMediaSchema, markdownLayoutSchema, row2Schema, grid2x2Schema, mulmoAvatarParamsSchema, avatarPositionSchema, speakerAvatarSchema, mulmoAvatarTrackSchema } from "./schema.js";
|
|
4
4
|
import { pdf_modes, pdf_sizes, storyToScriptGenerateMode } from "./const.js";
|
|
5
5
|
import type { LLM } from "./provider2agent.js";
|
|
6
6
|
import { z } from "zod";
|
|
@@ -43,6 +43,7 @@ export type MulmoStudioMultiLingualFile = z.infer<typeof mulmoStudioMultiLingual
|
|
|
43
43
|
export type MultiLingualTexts = z.infer<typeof multiLingualTextsSchema>;
|
|
44
44
|
export type MulmoMovieParams = z.infer<typeof mulmoMovieParamsSchema>;
|
|
45
45
|
export type MulmoSoundEffectParams = z.infer<typeof mulmoSoundEffectParamsSchema>;
|
|
46
|
+
export type MulmoBeatSoundEffect = z.infer<typeof mulmoBeatSoundEffectSchema>;
|
|
46
47
|
export type MulmoLipSyncParams = z.infer<typeof mulmoLipSyncParamsSchema>;
|
|
47
48
|
export type MulmoAvatarParams = z.infer<typeof mulmoAvatarParamsSchema>;
|
|
48
49
|
export type MulmoAvatarPosition = z.infer<typeof avatarPositionSchema>;
|
|
@@ -77,6 +78,7 @@ export type MulmoStudioContext = {
|
|
|
77
78
|
presentationStyle: MulmoPresentationStyle;
|
|
78
79
|
multiLingual: MulmoStudioMultiLingualArray;
|
|
79
80
|
usageCollector?: UsageCollectorAPI;
|
|
81
|
+
strictNetwork?: boolean;
|
|
80
82
|
};
|
|
81
83
|
export type ScriptingParams = {
|
|
82
84
|
urls: string[];
|
|
@@ -162,6 +164,7 @@ export type InitOptions = {
|
|
|
162
164
|
c?: string;
|
|
163
165
|
p?: string;
|
|
164
166
|
g?: boolean;
|
|
167
|
+
strictNetwork?: boolean;
|
|
165
168
|
};
|
|
166
169
|
export type PublicAPIArgs = {
|
|
167
170
|
settings?: Record<string, string>;
|
package/lib/utils/context.d.ts
CHANGED
|
@@ -2504,6 +2504,22 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
|
|
|
2504
2504
|
imagePrompt?: string | undefined;
|
|
2505
2505
|
moviePrompt?: string | undefined;
|
|
2506
2506
|
soundEffectPrompt?: string | undefined;
|
|
2507
|
+
soundEffects?: {
|
|
2508
|
+
source: {
|
|
2509
|
+
kind: "url";
|
|
2510
|
+
url: string;
|
|
2511
|
+
} | {
|
|
2512
|
+
kind: "path";
|
|
2513
|
+
path: string;
|
|
2514
|
+
} | {
|
|
2515
|
+
kind: "jinglescript";
|
|
2516
|
+
score: Record<string, unknown>;
|
|
2517
|
+
};
|
|
2518
|
+
startAt: number;
|
|
2519
|
+
volume: number;
|
|
2520
|
+
duration?: number | undefined;
|
|
2521
|
+
loop?: boolean | undefined;
|
|
2522
|
+
}[] | undefined;
|
|
2507
2523
|
htmlPrompt?: {
|
|
2508
2524
|
systemPrompt: string;
|
|
2509
2525
|
prompt: string;
|
|
@@ -5143,6 +5159,22 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
|
|
|
5143
5159
|
imagePrompt?: string | undefined;
|
|
5144
5160
|
moviePrompt?: string | undefined;
|
|
5145
5161
|
soundEffectPrompt?: string | undefined;
|
|
5162
|
+
soundEffects?: {
|
|
5163
|
+
source: {
|
|
5164
|
+
kind: "url";
|
|
5165
|
+
url: string;
|
|
5166
|
+
} | {
|
|
5167
|
+
kind: "path";
|
|
5168
|
+
path: string;
|
|
5169
|
+
} | {
|
|
5170
|
+
kind: "jinglescript";
|
|
5171
|
+
score: Record<string, unknown>;
|
|
5172
|
+
};
|
|
5173
|
+
startAt: number;
|
|
5174
|
+
volume: number;
|
|
5175
|
+
duration?: number | undefined;
|
|
5176
|
+
loop?: boolean | undefined;
|
|
5177
|
+
}[] | undefined;
|
|
5146
5178
|
htmlPrompt?: {
|
|
5147
5179
|
systemPrompt: string;
|
|
5148
5180
|
prompt: string;
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
// assume every asset is generated fresh.
|
|
5
5
|
import { Tiktoken } from "js-tiktoken/lite";
|
|
6
6
|
import o200k_base from "js-tiktoken/ranks/o200k_base";
|
|
7
|
-
import { provider2TTSAgent, provider2MovieAgent, gptImageOutputTokens, isGptImage25Model, getModelDuration, defaultMovieModel,
|
|
7
|
+
import { provider2TTSAgent, provider2MovieAgent, gptImageOutputTokens, isGptImage25Model, getModelDuration, defaultMovieModel, getModelPricing, defaultProviders, } from "../types/provider2agent.js";
|
|
8
8
|
import { text2SpeechProviderSchema, text2MovieProviderSchema } from "../types/schema.js";
|
|
9
9
|
import { MulmoPresentationStyleMethods } from "../methods/mulmo_presentation_style.js";
|
|
10
10
|
import { MulmoBeatMethods } from "../methods/mulmo_beat.js";
|
|
11
11
|
import { geminiTtsInputText } from "./gemini_tts.js";
|
|
12
|
+
import { geminiOmniTotalSec, isGeminiOmniVideoModel } from "./gemini_omni_video.js";
|
|
12
13
|
import { imagePrompt, htmlImageSystemPrompt, translateSystemPrompt, translatePrompts } from "./prompt.js";
|
|
13
14
|
const MILLION = 1_000_000;
|
|
14
15
|
const LATIN_CHARS_PER_TOKEN = 4;
|
|
@@ -217,8 +218,8 @@ const beatDurationMetric = (beat) => {
|
|
|
217
218
|
}
|
|
218
219
|
return estimated(beat.text ? estimateSpeechSec(beat.text) : DEFAULT_MOVIE_DURATION_SEC);
|
|
219
220
|
};
|
|
220
|
-
//
|
|
221
|
-
const
|
|
221
|
+
// Gemini Omni beats past one segment are extended rather than capped, and Google bills every generated second.
|
|
222
|
+
const isGeminiOmniMovie = (provider, model) => provider === "google" && isGeminiOmniVideoModel(model);
|
|
222
223
|
const snapMovieDuration = (provider, model, duration) => {
|
|
223
224
|
if (!isKeyOf(provider2MovieAgent[provider].modelParams, model)) {
|
|
224
225
|
return duration;
|
|
@@ -234,8 +235,8 @@ const estimateMovieGeneration = (style, beat, beatIndex, refKey) => {
|
|
|
234
235
|
}
|
|
235
236
|
const model = info.movieParams?.model ?? defaultMovieModel(provider, info.movieParams);
|
|
236
237
|
const duration = beat ? beatDurationMetric(beat) : estimated(DEFAULT_MOVIE_DURATION_SEC);
|
|
237
|
-
const predictSec =
|
|
238
|
-
? metric(
|
|
238
|
+
const predictSec = isGeminiOmniMovie(provider, model)
|
|
239
|
+
? metric(geminiOmniTotalSec(duration.value), duration.precision === "exact")
|
|
239
240
|
: snapMovieDuration(provider, model, duration);
|
|
240
241
|
return attachCost({ process: refKey === undefined ? "movie" : "movieReference", beatIndex, refKey, provider, model, predictSec });
|
|
241
242
|
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export declare const GEMINI_OMNI_MIN_SEGMENT_SEC = 3;
|
|
2
|
+
export declare const GEMINI_OMNI_MAX_SEGMENT_SEC = 10;
|
|
3
|
+
export declare const GEMINI_OMNI_MAX_TOTAL_SEC = 40;
|
|
4
|
+
export declare const GEMINI_OMNI_RESOLUTION = "720p";
|
|
5
|
+
export declare const isGeminiOmniVideoModel: (model: string) => boolean;
|
|
6
|
+
export declare const geminiOmniTotalSec: (requestedSec: number) => number;
|
|
7
|
+
export declare const geminiOmniSegments: (requestedSec: number) => number[];
|
|
8
|
+
export declare const geminiOmniAspectRatio: (canvasSize: {
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
}) => string;
|
|
12
|
+
export type GeminiOmniImage = {
|
|
13
|
+
data: string;
|
|
14
|
+
mime_type: string;
|
|
15
|
+
};
|
|
16
|
+
export type GeminiOmniVideoRequestInput = {
|
|
17
|
+
model: string;
|
|
18
|
+
prompt: string;
|
|
19
|
+
aspectRatio: string;
|
|
20
|
+
durationSec: number;
|
|
21
|
+
frames?: GeminiOmniImage[];
|
|
22
|
+
previousInteractionId?: string;
|
|
23
|
+
};
|
|
24
|
+
export declare const geminiOmniExtensionPrompt: (prompt: string) => string;
|
|
25
|
+
export declare const buildGeminiOmniVideoRequest: ({ model, prompt, aspectRatio, durationSec, frames, previousInteractionId }: GeminiOmniVideoRequestInput) => {
|
|
26
|
+
response_format: {
|
|
27
|
+
type: "video";
|
|
28
|
+
aspect_ratio: string;
|
|
29
|
+
resolution: string;
|
|
30
|
+
duration: string;
|
|
31
|
+
};
|
|
32
|
+
previous_interaction_id?: string | undefined;
|
|
33
|
+
model: string;
|
|
34
|
+
input: string | ({
|
|
35
|
+
data: string;
|
|
36
|
+
mime_type: string;
|
|
37
|
+
type: "image";
|
|
38
|
+
} | {
|
|
39
|
+
type: "text";
|
|
40
|
+
text: string;
|
|
41
|
+
})[];
|
|
42
|
+
};
|
|
43
|
+
export type GeminiOmniVideoRequest = ReturnType<typeof buildGeminiOmniVideoRequest>;
|
|
44
|
+
type InteractionVideo = {
|
|
45
|
+
data?: string;
|
|
46
|
+
mime_type?: string;
|
|
47
|
+
};
|
|
48
|
+
export declare const videoFromInteraction: (video: InteractionVideo | undefined) => Buffer;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getAspectRatio } from "./utils.js";
|
|
2
|
+
// Gemini Omni video goes through the Interactions API: one call makes 3 to 10 whole seconds, and a call that
|
|
3
|
+
// names the previous interaction extends that video, up to 40 seconds in all.
|
|
4
|
+
export const GEMINI_OMNI_MIN_SEGMENT_SEC = 3;
|
|
5
|
+
export const GEMINI_OMNI_MAX_SEGMENT_SEC = 10;
|
|
6
|
+
export const GEMINI_OMNI_MAX_TOTAL_SEC = 40;
|
|
7
|
+
export const GEMINI_OMNI_RESOLUTION = "720p";
|
|
8
|
+
const GEMINI_OMNI_ASPECT_RATIOS = ["16:9", "9:16"];
|
|
9
|
+
export const isGeminiOmniVideoModel = (model) => model.startsWith("gemini-omni-");
|
|
10
|
+
export const geminiOmniTotalSec = (requestedSec) => {
|
|
11
|
+
if (!Number.isFinite(requestedSec))
|
|
12
|
+
throw new RangeError(`Invalid video duration: ${requestedSec}`);
|
|
13
|
+
return Math.min(Math.max(Math.ceil(requestedSec), GEMINI_OMNI_MIN_SEGMENT_SEC), GEMINI_OMNI_MAX_TOTAL_SEC);
|
|
14
|
+
};
|
|
15
|
+
// Split evenly so no segment falls under the 3-second minimum (11s is 6 + 5, not 10 + 1).
|
|
16
|
+
export const geminiOmniSegments = (requestedSec) => {
|
|
17
|
+
const totalSec = geminiOmniTotalSec(requestedSec);
|
|
18
|
+
const count = Math.ceil(totalSec / GEMINI_OMNI_MAX_SEGMENT_SEC);
|
|
19
|
+
const baseSec = Math.floor(totalSec / count);
|
|
20
|
+
return Array.from({ length: count }, (_, index) => baseSec + (index < totalSec % count ? 1 : 0));
|
|
21
|
+
};
|
|
22
|
+
export const geminiOmniAspectRatio = (canvasSize) => getAspectRatio(canvasSize, GEMINI_OMNI_ASPECT_RATIOS);
|
|
23
|
+
export const geminiOmniExtensionPrompt = (prompt) => `The scene continues: ${prompt}`;
|
|
24
|
+
export const buildGeminiOmniVideoRequest = ({ model, prompt, aspectRatio, durationSec, frames = [], previousInteractionId }) => ({
|
|
25
|
+
model,
|
|
26
|
+
input: frames.length > 0 ? [...frames.map((frame) => ({ type: "image", ...frame })), { type: "text", text: prompt }] : prompt,
|
|
27
|
+
...(previousInteractionId ? { previous_interaction_id: previousInteractionId } : {}),
|
|
28
|
+
response_format: { type: "video", aspect_ratio: aspectRatio, resolution: GEMINI_OMNI_RESOLUTION, duration: `${durationSec}s` },
|
|
29
|
+
});
|
|
30
|
+
export const videoFromInteraction = (video) => {
|
|
31
|
+
if (!video?.data)
|
|
32
|
+
throw new Error("No video data returned");
|
|
33
|
+
if (video.mime_type !== "video/mp4")
|
|
34
|
+
throw new Error(`Gemini Omni returned ${video.mime_type ?? "video of an unknown type"}, expected video/mp4`);
|
|
35
|
+
return Buffer.from(video.data, "base64");
|
|
36
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { type RenderNetworkOptions } from "./render_network_policy.js";
|
|
2
|
+
export declare const renderHTMLToImage: (html: string, outputPath: string, width: number, height: number, isMermaid?: boolean, omitBackground?: boolean, network?: RenderNetworkOptions) => Promise<void>;
|
|
2
3
|
/**
|
|
3
4
|
* Render an animated HTML page frame-by-frame using Puppeteer.
|
|
4
5
|
*
|
|
@@ -9,18 +10,18 @@ export declare const renderHTMLToImage: (html: string, outputPath: string, width
|
|
|
9
10
|
*
|
|
10
11
|
* The user-defined render() function may be sync or async.
|
|
11
12
|
*/
|
|
12
|
-
export declare const renderHTMLToFrames: (html: string, outputDir: string, width: number, height: number, totalFrames: number, fps: number) => Promise<string[]>;
|
|
13
|
+
export declare const renderHTMLToFrames: (html: string, outputDir: string, width: number, height: number, totalFrames: number, fps: number, network?: RenderNetworkOptions) => Promise<string[]>;
|
|
13
14
|
/**
|
|
14
15
|
* Record an animated HTML page as video using Puppeteer's screencast API.
|
|
15
16
|
* The animation plays in real-time via requestAnimationFrame, and
|
|
16
17
|
* page.screencast() captures frames directly to an mp4 file.
|
|
17
18
|
*/
|
|
18
|
-
export declare const renderHTMLToVideo: (html: string, videoPath: string, width: number, height: number, totalFrames: number, fps: number) => Promise<void>;
|
|
19
|
+
export declare const renderHTMLToVideo: (html: string, videoPath: string, width: number, height: number, totalFrames: number, fps: number, network?: RenderNetworkOptions) => Promise<void>;
|
|
19
20
|
/**
|
|
20
21
|
* Render the final frame of an animated HTML page as a static image.
|
|
21
22
|
* Loads the animated HTML, calls window.renderFinal() to set all animations
|
|
22
23
|
* to their end state, then takes a screenshot. Used for PDF/thumbnail generation.
|
|
23
24
|
*/
|
|
24
|
-
export declare const renderHTMLToFinalFrame: (html: string, outputPath: string, width: number, height: number) => Promise<void>;
|
|
25
|
-
export declare const renderMarkdownToImage: (markdown: string, style: string, outputPath: string, width: number, height: number) => Promise<void>;
|
|
25
|
+
export declare const renderHTMLToFinalFrame: (html: string, outputPath: string, width: number, height: number, network?: RenderNetworkOptions) => Promise<void>;
|
|
26
|
+
export declare const renderMarkdownToImage: (markdown: string, style: string, outputPath: string, width: number, height: number, network?: RenderNetworkOptions) => Promise<void>;
|
|
26
27
|
export declare const interpolate: (template: string, data: Record<string, string>) => string;
|
package/lib/utils/html_render.js
CHANGED
|
@@ -4,6 +4,8 @@ import nodePath from "node:path";
|
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import { marked } from "marked";
|
|
6
6
|
import puppeteer from "puppeteer";
|
|
7
|
+
import { guardRenderPage } from "./render_network_guard.js";
|
|
8
|
+
import { strictNetworkLaunchArgs, withRenderContentSecurityPolicy } from "./render_network_policy.js";
|
|
7
9
|
import { GraphAILogger } from "graphai";
|
|
8
10
|
const isCI = process.env.CI === "true";
|
|
9
11
|
const VIDEO_LOAD_TIMEOUT_MS = 15000;
|
|
@@ -83,15 +85,16 @@ const resolveWaitUntil = (html) => {
|
|
|
83
85
|
* and navigate via page.goto (setContent uses about:blank origin
|
|
84
86
|
* which blocks file:// loading).
|
|
85
87
|
*/
|
|
86
|
-
const
|
|
88
|
+
const renderTempPagePath = () => nodePath.join(os.tmpdir(), `mulmocast_render_${crypto.randomUUID()}.html`);
|
|
89
|
+
const loadHtmlIntoPage = async (page, html, timeout_ms, forcedFilePath) => {
|
|
87
90
|
const waitUntil = resolveWaitUntil(html);
|
|
88
91
|
const hasFileUrls = /file:\/\//.test(html);
|
|
89
92
|
const hasExternalScripts = /script src=["']https?:\/\//.test(html);
|
|
90
93
|
// puppeteer's setContent does not accept "networkidle0" (no navigation, so network-idle states aren't meaningful in types).
|
|
91
94
|
// Route any HTML that needs network-idle semantics through the page.goto path to preserve the original wait behavior.
|
|
92
95
|
const needsNetworkIdle = waitUntil === "networkidle0";
|
|
93
|
-
if (hasFileUrls || hasExternalScripts || needsNetworkIdle) {
|
|
94
|
-
const tmpFile =
|
|
96
|
+
if (forcedFilePath || hasFileUrls || hasExternalScripts || needsNetworkIdle) {
|
|
97
|
+
const tmpFile = forcedFilePath ?? renderTempPagePath();
|
|
95
98
|
fs.writeFileSync(tmpFile, html);
|
|
96
99
|
try {
|
|
97
100
|
await page.goto(`file://${tmpFile}`, { waitUntil, timeout: timeout_ms });
|
|
@@ -109,14 +112,28 @@ const loadHtmlIntoPage = async (page, html, timeout_ms) => {
|
|
|
109
112
|
await page.setContent(html, { waitUntil, timeout: timeout_ms });
|
|
110
113
|
}
|
|
111
114
|
};
|
|
112
|
-
|
|
115
|
+
const RENDER_LOAD_TIMEOUT_MS = 30000;
|
|
116
|
+
const renderLaunchArgs = ({ strictNetwork = false }) => [
|
|
117
|
+
...(isCI ? ["--no-sandbox"] : []),
|
|
118
|
+
"--allow-file-access-from-files",
|
|
119
|
+
...(strictNetwork ? strictNetworkLaunchArgs() : []),
|
|
120
|
+
];
|
|
121
|
+
// Strict mode always loads through a file: the guard's WebRTC removal never reaches a setContent document.
|
|
122
|
+
const loadGuardedPage = async (page, html, allowedFileRoots) => {
|
|
123
|
+
const pagePath = renderTempPagePath();
|
|
124
|
+
await guardRenderPage(page, [pagePath, ...allowedFileRoots]);
|
|
125
|
+
await loadHtmlIntoPage(page, withRenderContentSecurityPolicy(html), RENDER_LOAD_TIMEOUT_MS, pagePath);
|
|
126
|
+
};
|
|
127
|
+
const openRenderPage = async (browser, html, { strictNetwork = false, allowedFileRoots = [] }) => {
|
|
128
|
+
const page = await browser.newPage();
|
|
129
|
+
await (strictNetwork ? loadGuardedPage(page, html, allowedFileRoots) : loadHtmlIntoPage(page, html, RENDER_LOAD_TIMEOUT_MS));
|
|
130
|
+
return page;
|
|
131
|
+
};
|
|
132
|
+
export const renderHTMLToImage = async (html, outputPath, width, height, isMermaid = false, omitBackground = false, network = {}) => {
|
|
113
133
|
// Use Puppeteer to render HTML to an image
|
|
114
|
-
const browser = await puppeteer.launch({
|
|
115
|
-
args: isCI ? ["--no-sandbox", "--allow-file-access-from-files"] : ["--allow-file-access-from-files"],
|
|
116
|
-
});
|
|
134
|
+
const browser = await puppeteer.launch({ args: renderLaunchArgs(network) });
|
|
117
135
|
try {
|
|
118
|
-
const page = await browser
|
|
119
|
-
await loadHtmlIntoPage(page, html, 30000);
|
|
136
|
+
const page = await openRenderPage(browser, html, network);
|
|
120
137
|
// Adjust page settings if needed (like width, height, etc.)
|
|
121
138
|
await page.setViewport({ width, height });
|
|
122
139
|
// height:100% ensures background fills viewport; only reset html, let body styles come from custom CSS
|
|
@@ -158,14 +175,10 @@ export const renderHTMLToImage = async (html, outputPath, width, height, isMerma
|
|
|
158
175
|
*
|
|
159
176
|
* The user-defined render() function may be sync or async.
|
|
160
177
|
*/
|
|
161
|
-
export const renderHTMLToFrames = async (html, outputDir, width, height, totalFrames, fps) => {
|
|
162
|
-
const browser = await puppeteer.launch({
|
|
163
|
-
args: isCI ? ["--no-sandbox", "--allow-file-access-from-files"] : ["--allow-file-access-from-files"],
|
|
164
|
-
});
|
|
178
|
+
export const renderHTMLToFrames = async (html, outputDir, width, height, totalFrames, fps, network = {}) => {
|
|
179
|
+
const browser = await puppeteer.launch({ args: renderLaunchArgs(network) });
|
|
165
180
|
try {
|
|
166
|
-
const page = await browser
|
|
167
|
-
// Wait for Tailwind CSS CDN to load
|
|
168
|
-
await loadHtmlIntoPage(page, html, 30000);
|
|
181
|
+
const page = await openRenderPage(browser, html, network);
|
|
169
182
|
await page.setViewport({ width, height });
|
|
170
183
|
await page.addStyleTag({ content: "html{height:100%;margin:0;padding:0;overflow:hidden}" });
|
|
171
184
|
// Scale content to fit viewport (same logic as renderHTMLToImage)
|
|
@@ -199,14 +212,11 @@ export const renderHTMLToFrames = async (html, outputDir, width, height, totalFr
|
|
|
199
212
|
* The animation plays in real-time via requestAnimationFrame, and
|
|
200
213
|
* page.screencast() captures frames directly to an mp4 file.
|
|
201
214
|
*/
|
|
202
|
-
export const renderHTMLToVideo = async (html, videoPath, width, height, totalFrames, fps) => {
|
|
215
|
+
export const renderHTMLToVideo = async (html, videoPath, width, height, totalFrames, fps, network = {}) => {
|
|
203
216
|
const duration_ms = (totalFrames / fps) * 1000;
|
|
204
|
-
const browser = await puppeteer.launch({
|
|
205
|
-
args: isCI ? ["--no-sandbox", "--allow-file-access-from-files"] : ["--allow-file-access-from-files"],
|
|
206
|
-
});
|
|
217
|
+
const browser = await puppeteer.launch({ args: renderLaunchArgs(network) });
|
|
207
218
|
try {
|
|
208
|
-
const page = await browser
|
|
209
|
-
await loadHtmlIntoPage(page, html, 30000);
|
|
219
|
+
const page = await openRenderPage(browser, html, network);
|
|
210
220
|
await page.setViewport({ width, height });
|
|
211
221
|
await page.addStyleTag({ content: "html{height:100%;margin:0;padding:0;overflow:hidden}" });
|
|
212
222
|
await scaleContentToFit(page, width, height);
|
|
@@ -242,13 +252,10 @@ export const renderHTMLToVideo = async (html, videoPath, width, height, totalFra
|
|
|
242
252
|
* Loads the animated HTML, calls window.renderFinal() to set all animations
|
|
243
253
|
* to their end state, then takes a screenshot. Used for PDF/thumbnail generation.
|
|
244
254
|
*/
|
|
245
|
-
export const renderHTMLToFinalFrame = async (html, outputPath, width, height) => {
|
|
246
|
-
const browser = await puppeteer.launch({
|
|
247
|
-
args: isCI ? ["--no-sandbox", "--allow-file-access-from-files"] : ["--allow-file-access-from-files"],
|
|
248
|
-
});
|
|
255
|
+
export const renderHTMLToFinalFrame = async (html, outputPath, width, height, network = {}) => {
|
|
256
|
+
const browser = await puppeteer.launch({ args: renderLaunchArgs(network) });
|
|
249
257
|
try {
|
|
250
|
-
const page = await browser
|
|
251
|
-
await loadHtmlIntoPage(page, html, 30000);
|
|
258
|
+
const page = await openRenderPage(browser, html, network);
|
|
252
259
|
await page.setViewport({ width, height });
|
|
253
260
|
await page.addStyleTag({ content: "html{height:100%;margin:0;padding:0;overflow:hidden}" });
|
|
254
261
|
await scaleContentToFit(page, width, height);
|
|
@@ -266,11 +273,11 @@ export const renderHTMLToFinalFrame = async (html, outputPath, width, height) =>
|
|
|
266
273
|
await browser.close();
|
|
267
274
|
}
|
|
268
275
|
};
|
|
269
|
-
export const renderMarkdownToImage = async (markdown, style, outputPath, width, height) => {
|
|
276
|
+
export const renderMarkdownToImage = async (markdown, style, outputPath, width, height, network = {}) => {
|
|
270
277
|
const header = `<head><style>${style}</style></head>`;
|
|
271
278
|
const body = await marked(markdown);
|
|
272
279
|
const html = `<html>${header}<body>${body}</body></html>`;
|
|
273
|
-
await renderHTMLToImage(html, outputPath, width, height);
|
|
280
|
+
await renderHTMLToImage(html, outputPath, width, height, false, false, network);
|
|
274
281
|
};
|
|
275
282
|
export const interpolate = (template, data) => {
|
|
276
283
|
return template.replace(/\$\{(.*?)\}/g, (_, key) => data[key.trim()] ?? "");
|
|
@@ -3,6 +3,7 @@ import { renderHTMLToImage, interpolate } from "../html_render.js";
|
|
|
3
3
|
import { parrotingImagePath, generateUniqueId } from "./utils.js";
|
|
4
4
|
import { resolveCombinedStyle } from "./bg_image_util.js";
|
|
5
5
|
import { chartHtml, escapedChartTemplateValues, resolveChartPlugins, stringifyChartData } from "./chart_html.js";
|
|
6
|
+
import { MulmoStudioContextMethods } from "../../methods/mulmo_studio_context.js";
|
|
6
7
|
export const imageType = "chart";
|
|
7
8
|
const processChart = async (params) => {
|
|
8
9
|
const { beat, imagePath, canvasSize } = params;
|
|
@@ -19,7 +20,7 @@ const processChart = async (params) => {
|
|
|
19
20
|
chart_width: chart_width.toString(),
|
|
20
21
|
chart_plugins: resolveChartPlugins(chartType),
|
|
21
22
|
});
|
|
22
|
-
await renderHTMLToImage(htmlData, imagePath, canvasSize.width, canvasSize.height);
|
|
23
|
+
await renderHTMLToImage(htmlData, imagePath, canvasSize.width, canvasSize.height, false, false, MulmoStudioContextMethods.getRenderNetworkOptions(params.context));
|
|
23
24
|
return imagePath;
|
|
24
25
|
};
|
|
25
26
|
const dumpHtml = async (params) => {
|
|
@@ -7,6 +7,7 @@ import { framesToVideo } from "../ffmpeg_utils.js";
|
|
|
7
7
|
import { parrotingImagePath } from "./utils.js";
|
|
8
8
|
import { swipeElementsToHtml, swipeElementsToScript } from "../swipe_to_html.js";
|
|
9
9
|
import { htmlTailwindMarkup } from "./html_tailwind_markup.js";
|
|
10
|
+
import { MulmoStudioContextMethods } from "../../methods/mulmo_studio_context.js";
|
|
10
11
|
export const imageType = "html_tailwind";
|
|
11
12
|
/**
|
|
12
13
|
* Resolve image:name references to file:// absolute paths using imageRefs.
|
|
@@ -178,13 +179,13 @@ const processHtmlTailwindAnimated = async (params) => {
|
|
|
178
179
|
// imagePath is set to the .mp4 path by imagePluginAgent for animated beats
|
|
179
180
|
const videoPath = imagePath;
|
|
180
181
|
if (animConfig.movie) {
|
|
181
|
-
await renderHTMLToVideo(htmlData, videoPath, canvasSize.width, canvasSize.height, totalFrames, fps);
|
|
182
|
+
await renderHTMLToVideo(htmlData, videoPath, canvasSize.width, canvasSize.height, totalFrames, fps, MulmoStudioContextMethods.getRenderNetworkOptions(params.context));
|
|
182
183
|
}
|
|
183
184
|
else {
|
|
184
185
|
const framesDir = videoPath.replace(/\.[^/.]+$/, "_frames");
|
|
185
186
|
fs.mkdirSync(framesDir, { recursive: true });
|
|
186
187
|
try {
|
|
187
|
-
await renderHTMLToFrames(htmlData, framesDir, canvasSize.width, canvasSize.height, totalFrames, fps);
|
|
188
|
+
await renderHTMLToFrames(htmlData, framesDir, canvasSize.width, canvasSize.height, totalFrames, fps, MulmoStudioContextMethods.getRenderNetworkOptions(params.context));
|
|
188
189
|
await framesToVideo(framesDir, videoPath, fps, canvasSize.width, canvasSize.height);
|
|
189
190
|
}
|
|
190
191
|
finally {
|
|
@@ -197,7 +198,7 @@ const processHtmlTailwindAnimated = async (params) => {
|
|
|
197
198
|
// even when exact duration is unknown (e.g., PDF generation without audio).
|
|
198
199
|
const finalFramePath = imagePath.replace(/_animated\.mp4$/, ".png");
|
|
199
200
|
const finalHtml = buildAnimatedHtml(params, FINAL_FRAME_TOTAL, fps);
|
|
200
|
-
await renderHTMLToFinalFrame(finalHtml, finalFramePath, canvasSize.width, canvasSize.height);
|
|
201
|
+
await renderHTMLToFinalFrame(finalHtml, finalFramePath, canvasSize.width, canvasSize.height, MulmoStudioContextMethods.getRenderNetworkOptions(params.context));
|
|
201
202
|
// Return video path when video was generated, otherwise return the static PNG path
|
|
202
203
|
return duration !== undefined ? imagePath : finalFramePath;
|
|
203
204
|
};
|
|
@@ -216,7 +217,7 @@ const processHtmlTailwindStatic = async (params) => {
|
|
|
216
217
|
const resolvedAllRefs = resolveMovieRefs(resolvedImageRefs, params.movieRefs ?? {});
|
|
217
218
|
const resolvedImages = resolveRelativeImagePaths(resolvedAllRefs, context.fileDirs.mulmoFileDirPath);
|
|
218
219
|
const htmlData = resolveRelativeModelPathsInScript(resolvedImages, context.fileDirs.mulmoFileDirPath);
|
|
219
|
-
await renderHTMLToImage(htmlData, imagePath, canvasSize.width, canvasSize.height);
|
|
220
|
+
await renderHTMLToImage(htmlData, imagePath, canvasSize.width, canvasSize.height, false, false, MulmoStudioContextMethods.getRenderNetworkOptions(params.context));
|
|
220
221
|
return imagePath;
|
|
221
222
|
};
|
|
222
223
|
const processHtmlTailwind = async (params) => {
|
|
@@ -5,6 +5,7 @@ import { resolveCombinedStyle } from "./bg_image_util.js";
|
|
|
5
5
|
import { renderMarkdownContent, renderMarkdownLayout, layoutToMarkdown, toMarkdownString } from "./markdown_layout.js";
|
|
6
6
|
import { resolveImageRefs, resolveMovieRefs } from "./html_tailwind.js";
|
|
7
7
|
import { isObject } from "graphai";
|
|
8
|
+
import { MulmoStudioContextMethods } from "../../methods/mulmo_studio_context.js";
|
|
8
9
|
/**
|
|
9
10
|
* This path renders once to a PNG, so a random element id is fine. The browser path needs
|
|
10
11
|
* a stable one and supplies its own — that generator is the only thing the two callers of
|
|
@@ -77,7 +78,7 @@ const processMarkdown = async (params) => {
|
|
|
77
78
|
const { html: rawHtml, hasMermaid } = await generateHtml(params);
|
|
78
79
|
const resolvedImages = resolveImageRefs(rawHtml, params.imageRefs ?? {});
|
|
79
80
|
const html = resolveMovieRefs(resolvedImages, params.movieRefs ?? {});
|
|
80
|
-
await renderHTMLToImage(html, imagePath, canvasSize.width, canvasSize.height, hasMermaid);
|
|
81
|
+
await renderHTMLToImage(html, imagePath, canvasSize.width, canvasSize.height, hasMermaid, false, MulmoStudioContextMethods.getRenderNetworkOptions(params.context));
|
|
81
82
|
return imagePath;
|
|
82
83
|
};
|
|
83
84
|
const dumpHtml = async (params) => {
|
|
@@ -5,6 +5,7 @@ import { parrotingImagePath, generateUniqueId } from "./utils.js";
|
|
|
5
5
|
import { escapedMermaidTemplateValues, mermaidHtml } from "./mermaid_html.js";
|
|
6
6
|
import { resolveCombinedStyle } from "./bg_image_util.js";
|
|
7
7
|
import { resolveImageRefs, resolveMovieRefs, resolveRelativeImagePaths } from "./html_tailwind.js";
|
|
8
|
+
import { MulmoStudioContextMethods } from "../../methods/mulmo_studio_context.js";
|
|
8
9
|
export const imageType = "mermaid";
|
|
9
10
|
/** Mermaid markup for this path. The markup itself is shared with the browser path. */
|
|
10
11
|
export const generateMermaidHtml = (code, title) => mermaidHtml(code, generateUniqueId("mermaid"), title);
|
|
@@ -23,7 +24,7 @@ const processMermaid = async (params) => {
|
|
|
23
24
|
const resolvedImageRefs = resolveImageRefs(rawHtml, params.imageRefs ?? {});
|
|
24
25
|
const resolvedAllRefs = resolveMovieRefs(resolvedImageRefs, params.movieRefs ?? {});
|
|
25
26
|
const htmlData = resolveRelativeImagePaths(resolvedAllRefs, context.fileDirs.mulmoFileDirPath);
|
|
26
|
-
await renderHTMLToImage(htmlData, imagePath, canvasSize.width, canvasSize.height, true);
|
|
27
|
+
await renderHTMLToImage(htmlData, imagePath, canvasSize.width, canvasSize.height, true, false, MulmoStudioContextMethods.getRenderNetworkOptions(context));
|
|
27
28
|
}
|
|
28
29
|
return imagePath;
|
|
29
30
|
};
|
|
@@ -7,6 +7,7 @@ import { pathToDataUrl } from "../../methods/mulmo_media_source.js";
|
|
|
7
7
|
import { MulmoMediaSourceMethods } from "../../methods/mulmo_media_source.js";
|
|
8
8
|
import { MulmoPresentationStyleMethods } from "../../methods/mulmo_presentation_style.js";
|
|
9
9
|
import { imageAction, imageFileTarget, unknownMediaType } from "../error_cause.js";
|
|
10
|
+
import { MulmoStudioContextMethods } from "../../methods/mulmo_studio_context.js";
|
|
10
11
|
export const imageType = "slide";
|
|
11
12
|
const slideImageRefError = (refKey) => ({
|
|
12
13
|
type: unknownMediaType,
|
|
@@ -167,7 +168,7 @@ const processSlide = async (params) => {
|
|
|
167
168
|
const reference = beat.image.reference;
|
|
168
169
|
const resolvedBranding = await resolveAndConvertBranding(params);
|
|
169
170
|
const html = generateSlideHTML(theme, slide, reference, resolvedBranding);
|
|
170
|
-
await renderHTMLToImage(html, imagePath, canvasSize.width, canvasSize.height);
|
|
171
|
+
await renderHTMLToImage(html, imagePath, canvasSize.width, canvasSize.height, false, false, MulmoStudioContextMethods.getRenderNetworkOptions(params.context));
|
|
171
172
|
return imagePath;
|
|
172
173
|
};
|
|
173
174
|
/**
|