mulmocast 2.6.23 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +24 -0
  2. package/lib/actions/bundle.js +2 -1
  3. package/lib/actions/pdf.js +4 -1
  4. package/lib/agents/image_genai_agent.js +5 -2
  5. package/lib/agents/image_openai_agent.js +2 -1
  6. package/lib/agents/image_replicate_agent.js +2 -1
  7. package/lib/agents/lipsync_replicate_agent.js +2 -1
  8. package/lib/agents/media_mock_agent.js +2 -1
  9. package/lib/agents/movie_genai_agent.js +17 -3
  10. package/lib/agents/movie_replicate_agent.js +2 -1
  11. package/lib/agents/sound_effect_replicate_agent.js +6 -2
  12. package/lib/agents/tts_elevenlabs_agent.js +4 -3
  13. package/lib/agents/tts_gemini_agent.js +4 -1
  14. package/lib/agents/tts_kotodama_agent.js +7 -3
  15. package/lib/cli/commands/audio/builder.d.ts +2 -14
  16. package/lib/cli/commands/audio/builder.js +2 -2
  17. package/lib/cli/commands/audio/handler.d.ts +2 -0
  18. package/lib/cli/commands/audio/handler.js +5 -1
  19. package/lib/cli/commands/image/builder.d.ts +2 -14
  20. package/lib/cli/commands/image/builder.js +2 -2
  21. package/lib/cli/commands/image/handler.d.ts +2 -0
  22. package/lib/cli/commands/image/handler.js +5 -1
  23. package/lib/cli/commands/movie/builder.d.ts +2 -14
  24. package/lib/cli/commands/movie/builder.js +2 -2
  25. package/lib/cli/commands/movie/handler.d.ts +2 -0
  26. package/lib/cli/commands/movie/handler.js +5 -1
  27. package/lib/cli/commands/pdf/builder.d.ts +2 -14
  28. package/lib/cli/commands/pdf/builder.js +2 -2
  29. package/lib/cli/commands/pdf/handler.d.ts +2 -0
  30. package/lib/cli/commands/pdf/handler.js +5 -1
  31. package/lib/cli/commands/translate/builder.d.ts +3 -15
  32. package/lib/cli/commands/translate/builder.js +2 -2
  33. package/lib/cli/commands/translate/handler.d.ts +2 -0
  34. package/lib/cli/commands/translate/handler.js +5 -1
  35. package/lib/cli/common.d.ts +5 -0
  36. package/lib/cli/common.js +13 -0
  37. package/lib/cli/helpers.d.ts +2 -0
  38. package/lib/cli/helpers.js +19 -0
  39. package/lib/index.common.d.ts +2 -0
  40. package/lib/index.common.js +2 -0
  41. package/lib/methods/mulmo_media_source.js +9 -13
  42. package/lib/methods/mulmo_presentation_style.d.ts +2 -0
  43. package/lib/methods/mulmo_presentation_style.js +9 -6
  44. package/lib/types/index.d.ts +1 -0
  45. package/lib/types/index.js +1 -0
  46. package/lib/types/provider2agent.d.ts +17 -0
  47. package/lib/types/provider2agent.js +79 -0
  48. package/lib/types/usage.d.ts +21 -0
  49. package/lib/utils/estimate_usage.d.ts +11 -0
  50. package/lib/utils/estimate_usage.js +332 -0
  51. package/lib/utils/estimate_usage_format.d.ts +2 -0
  52. package/lib/utils/estimate_usage_format.js +64 -0
  53. package/lib/utils/fetch.d.ts +28 -0
  54. package/lib/utils/fetch.js +52 -0
  55. package/lib/utils/file.js +2 -1
  56. package/lib/utils/image_plugins/bg_image_util.js +7 -20
  57. package/lib/utils/openai_client.js +6 -0
  58. package/lib/utils/replicate_usage.d.ts +4 -1
  59. package/lib/utils/replicate_usage.js +25 -5
  60. package/lib/utils/sdk_timeout.d.ts +3 -0
  61. package/lib/utils/sdk_timeout.js +7 -0
  62. package/lib/utils/zip.js +2 -2
  63. package/package.json +13 -12
@@ -1,10 +1,14 @@
1
1
  import { translate } from "../../../actions/index.js";
2
- import { dumpUsageIfRequested, initializeContext } from "../../helpers.js";
2
+ import { dumpUsageIfRequested, initializeContext, printUsageEstimate } from "../../helpers.js";
3
3
  export const handler = async (argv) => {
4
4
  const context = await initializeContext(argv);
5
5
  if (!context) {
6
6
  process.exit(1);
7
7
  }
8
+ if (argv.estimate) {
9
+ printUsageEstimate(context, "translate", argv.json);
10
+ return;
11
+ }
8
12
  await translate(context);
9
13
  dumpUsageIfRequested(context);
10
14
  };
@@ -1,4 +1,9 @@
1
1
  import type { Argv } from "yargs";
2
+ export declare const estimateOptions: (yargs: Argv) => Argv<{
3
+ estimate: boolean;
4
+ } & {
5
+ json: boolean;
6
+ }>;
2
7
  export declare const commonOptions: (yargs: Argv) => Argv<{
3
8
  o: string | undefined;
4
9
  } & {
package/lib/cli/common.js CHANGED
@@ -1,4 +1,17 @@
1
1
  import { languages } from "../types/const.js";
2
+ export const estimateOptions = (yargs) => {
3
+ return yargs
4
+ .option("estimate", {
5
+ describe: "Estimate API usage (tokens / characters / seconds / cost) and exit without generating",
6
+ type: "boolean",
7
+ default: false,
8
+ })
9
+ .option("json", {
10
+ describe: "With --estimate, print the raw JSON records instead of a table",
11
+ type: "boolean",
12
+ default: false,
13
+ });
14
+ };
2
15
  export const commonOptions = (yargs) => {
3
16
  return yargs
4
17
  .option("o", {
@@ -1,7 +1,9 @@
1
+ import { type EstimateAction } from "../utils/estimate_usage.js";
1
2
  import type { CliArgs } from "../types/cli_types.js";
2
3
  import { FileObject, InitOptions, MulmoStudioContext } from "../types/index.js";
3
4
  export declare const runTranslateIfNeeded: (context: MulmoStudioContext, includeCaption?: boolean) => Promise<void>;
4
5
  export declare const dumpUsageIfRequested: (context: MulmoStudioContext) => void;
6
+ export declare const printUsageEstimate: (context: MulmoStudioContext, action: EstimateAction, asJson?: boolean) => void;
5
7
  export declare const setGraphAILogger: (verbose: boolean | undefined, logValues?: Record<string, unknown>) => void;
6
8
  export declare const getFileObject: (args: {
7
9
  basedir?: string;
@@ -8,6 +8,8 @@ import { outDirName, imageDirName, audioDirName } from "../types/const.js";
8
8
  import { MulmoStudioContextMethods } from "../methods/mulmo_studio_context.js";
9
9
  import { translate } from "../actions/translate.js";
10
10
  import { initializeContextFromFiles } from "../utils/context.js";
11
+ import { estimateUsage, actionEstimateProcesses } from "../utils/estimate_usage.js";
12
+ import { formatUsageEstimates } from "../utils/estimate_usage_format.js";
11
13
  export const runTranslateIfNeeded = async (context, includeCaption = false) => {
12
14
  if (MulmoStudioContextMethods.needTranslate(context, includeCaption)) {
13
15
  GraphAILogger.log("run translate");
@@ -60,6 +62,23 @@ export const dumpUsageIfRequested = (context) => {
60
62
  fs.writeFileSync(setting, json, "utf-8");
61
63
  GraphAILogger.info(`usage written to ${setting}`);
62
64
  };
65
+ // Pre-run estimate for --estimate: scoped to what the invoked action would consume,
66
+ // resolved with the same context (script, presentation style, -l, caption lang) as a real run.
67
+ export const printUsageEstimate = (context, action, asJson = false) => {
68
+ const script = context.studio.script;
69
+ const lang = context.lang;
70
+ // The caption language triggers translation only where the real run does:
71
+ // movie (runTranslateIfNeeded with includeCaption) and the translate action itself.
72
+ const captionLang = action === "movie" || action === "translate" ? script.captionParams?.lang : undefined;
73
+ const targetLangs = [...new Set([lang, captionLang].filter((l) => !!l))];
74
+ const records = estimateUsage(script, {
75
+ presentationStyle: context.presentationStyle,
76
+ langs: lang ? [lang] : undefined,
77
+ targetLangs,
78
+ processes: actionEstimateProcesses[action],
79
+ });
80
+ GraphAILogger.info(asJson ? JSON.stringify(records, null, 2) : formatUsageEstimates(records));
81
+ };
63
82
  export const setGraphAILogger = (verbose, logValues) => {
64
83
  if (verbose) {
65
84
  if (logValues) {
@@ -5,6 +5,8 @@ export * from "./utils/string.js";
5
5
  export * from "./utils/utils.js";
6
6
  export * from "./utils/prompt.js";
7
7
  export * from "./utils/error_cause.js";
8
+ export * from "./utils/estimate_usage.js";
9
+ export * from "./utils/estimate_usage_format.js";
8
10
  export * from "./methods/mulmo_presentation_style.js";
9
11
  export * from "./methods/mulmo_script.js";
10
12
  export * from "./methods/mulmo_studio_context.js";
@@ -6,6 +6,8 @@ export * from "./utils/string.js";
6
6
  export * from "./utils/utils.js";
7
7
  export * from "./utils/prompt.js";
8
8
  export * from "./utils/error_cause.js";
9
+ export * from "./utils/estimate_usage.js";
10
+ export * from "./utils/estimate_usage_format.js";
9
11
  export * from "./methods/mulmo_presentation_style.js";
10
12
  export * from "./methods/mulmo_script.js";
11
13
  export * from "./methods/mulmo_studio_context.js";
@@ -1,6 +1,7 @@
1
1
  import fs from "fs";
2
2
  import { GraphAILogger, assert } from "graphai";
3
3
  import { getFullPath, getReferenceImagePath, resolveAssetPath } from "../utils/file.js";
4
+ import { safeFetch, DEFAULT_FETCH_TIMEOUT_MS } from "../utils/fetch.js";
4
5
  import { downLoadReferenceImageError, getTextError, imageReferenceUnknownMediaError, downloadImagePluginError, imagePluginUnknownMediaError, mediaSourceToDataUrlError, mediaSourceFileNotFoundError, mediaSourceUnknownKindError, } from "../utils/error_cause.js";
5
6
  // for image reference
6
7
  export const getExtention = (contentType, url) => {
@@ -18,7 +19,7 @@ export const getExtention = (contentType, url) => {
18
19
  return "png"; // default
19
20
  };
20
21
  const downLoadReferenceImage = async (context, key, url) => {
21
- const response = await fetch(url);
22
+ const response = await safeFetch(url);
22
23
  assert(response.ok, `Failed to download reference image: ${url}`, false, downLoadReferenceImageError(key, url));
23
24
  const buffer = Buffer.from(await response.arrayBuffer());
24
25
  // Detect file extension from Content-Type header or URL
@@ -38,26 +39,21 @@ function pluginSourceFixExtention(path, imageType) {
38
39
  return path;
39
40
  }
40
41
  // end of util
41
- const DEFAULT_FETCH_TIMEOUT_MS = 30000;
42
42
  // Convert URL to data URL (base64 encoded)
43
43
  const urlToDataUrl = async (url, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) => {
44
- const controller = new AbortController();
45
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
46
44
  try {
47
- const response = await fetch(url, { signal: controller.signal });
45
+ const response = await safeFetch(url, {}, timeoutMs);
48
46
  assert(response.ok, `Failed to fetch: ${url}`, false, mediaSourceToDataUrlError(url));
49
47
  const buffer = Buffer.from(await response.arrayBuffer());
50
48
  const contentType = response.headers.get("content-type") || "image/png";
51
49
  return `data:${contentType};base64,${buffer.toString("base64")}`;
52
50
  }
53
51
  catch (error) {
54
- if (error instanceof Error && error.name === "AbortError") {
55
- throw new Error(`Fetch timeout: ${url}`, { cause: mediaSourceToDataUrlError(url) });
52
+ // assert() failures already carry the structured cause — keep it as-is.
53
+ if (error instanceof Error && error.cause) {
54
+ throw error;
56
55
  }
57
- throw new Error(`Fetch failed: ${url}`, { cause: mediaSourceToDataUrlError(url) });
58
- }
59
- finally {
60
- clearTimeout(timeoutId);
56
+ throw new Error(`Fetch failed: ${url}: ${error instanceof Error ? error.message : String(error)}`, { cause: mediaSourceToDataUrlError(url) });
61
57
  }
62
58
  };
63
59
  /** Map file extension to MIME type for data URLs */
@@ -92,7 +88,7 @@ export const MulmoMediaSourceMethods = {
92
88
  return mediaSource.text;
93
89
  }
94
90
  if (mediaSource.kind === "url") {
95
- const response = await fetch(mediaSource.url);
91
+ const response = await safeFetch(mediaSource.url);
96
92
  assert(response.ok, `Failed to download mermaid code text: ${mediaSource.url}`, false, getTextError(mediaSource.url)); // TODO: index
97
93
  return await response.text();
98
94
  }
@@ -126,7 +122,7 @@ export const MulmoMediaSourceMethods = {
126
122
  },
127
123
  async imagePluginSource(mediaSource, context, expectImagePath, imageType) {
128
124
  if (mediaSource.kind === "url") {
129
- const response = await fetch(mediaSource.url);
125
+ const response = await safeFetch(mediaSource.url);
130
126
  assert(response.ok, `Failed to download image plugin: ${imageType} ${mediaSource.url}`, false, downloadImagePluginError(mediaSource.url, imageType)); // TODO: key, id, index
131
127
  const buffer = Buffer.from(await response.arrayBuffer());
132
128
  // Detect file extension from Content-Type header or URL
@@ -27,6 +27,7 @@ export declare const MulmoPresentationStyleMethods: {
27
27
  */
28
28
  getResolvedSlideTheme(presentationStyle: MulmoPresentationStyle, beat: MulmoBeat): SlideTheme;
29
29
  getDefaultSpeaker(presentationStyle: MulmoPresentationStyle): string;
30
+ getSpeakerData(presentationStyle: MulmoPresentationStyle, beat: MulmoBeat, lang: string | undefined): SpeakerData;
30
31
  getSpeaker(context: MulmoStudioContext, beat: MulmoBeat, targetLang: string | undefined): SpeakerData;
31
32
  getMovieTransition(context: MulmoStudioContext, beat: MulmoBeat): MulmoTransition | null;
32
33
  getText2ImageProvider(provider: Text2ImageProvider | undefined): Text2ImageProvider;
@@ -222,6 +223,7 @@ export declare const MulmoPresentationStyleMethods: {
222
223
  video?: string;
223
224
  audio: string;
224
225
  image?: string;
226
+ price_per_sec?: number;
225
227
  }>;
226
228
  };
227
229
  /** Concurrency for image/movie generation graph (uses min of imageParams/movieParams) */
@@ -73,19 +73,22 @@ export const MulmoPresentationStyleMethods = {
73
73
  }
74
74
  return keys[0];
75
75
  },
76
- getSpeaker(context, beat, targetLang) {
77
- userAssert(!!context.presentationStyle?.speechParams?.speakers, "presentationStyle.speechParams.speakers is not set!!");
78
- const speakerId = beat?.speaker ?? MulmoPresentationStyleMethods.getDefaultSpeaker(context.presentationStyle);
79
- const speaker = context.presentationStyle.speechParams.speakers[speakerId];
76
+ getSpeakerData(presentationStyle, beat, lang) {
77
+ userAssert(!!presentationStyle?.speechParams?.speakers, "presentationStyle.speechParams.speakers is not set!!");
78
+ const speakerId = beat?.speaker ?? MulmoPresentationStyleMethods.getDefaultSpeaker(presentationStyle);
79
+ const speaker = presentationStyle.speechParams.speakers[speakerId];
80
80
  userAssert(!!speaker, `speaker is not set: speaker "${speakerId}"`);
81
81
  // Check if the speaker has a language-specific version.
82
- // Normally, lang is determined by the context, but lang may be specified when using the API.
83
- const lang = targetLang ?? context.lang ?? context?.studio?.script?.lang;
84
82
  if (speaker.lang && lang && speaker.lang[lang]) {
85
83
  return speaker.lang[lang];
86
84
  }
87
85
  return speaker;
88
86
  },
87
+ getSpeaker(context, beat, targetLang) {
88
+ // Normally, lang is determined by the context, but lang may be specified when using the API.
89
+ const lang = targetLang ?? context.lang ?? context?.studio?.script?.lang;
90
+ return MulmoPresentationStyleMethods.getSpeakerData(context.presentationStyle, beat, lang);
91
+ },
89
92
  getMovieTransition(context, beat) {
90
93
  const transitionData = beat.movieParams?.transition ?? context.presentationStyle.movieParams?.transition;
91
94
  if (!transitionData)
@@ -2,3 +2,4 @@ export * from "./type.js";
2
2
  export * from "./schema.js";
3
3
  export * from "./viewer.js";
4
4
  export * from "./usage.js";
5
+ export * from "./provider2agent.js";
@@ -2,3 +2,4 @@ export * from "./type.js";
2
2
  export * from "./schema.js";
3
3
  export * from "./viewer.js";
4
4
  export * from "./usage.js";
5
+ export * from "./provider2agent.js";
@@ -158,6 +158,7 @@ export declare const provider2LipSyncAgent: {
158
158
  video?: string;
159
159
  audio: string;
160
160
  image?: string;
161
+ price_per_sec?: number;
161
162
  }>;
162
163
  };
163
164
  };
@@ -213,4 +214,20 @@ export declare const llm: (keyof typeof provider2LLMAgent)[];
213
214
  export type LLM = keyof typeof provider2LLMAgent;
214
215
  export declare const htmlLLMProvider: string[];
215
216
  export declare const getModelDuration: (provider: keyof typeof provider2MovieAgent, model: string, movieDuration?: number) => number | undefined;
217
+ export type ModelPricing = {
218
+ unit: "tokens" | "chars" | "seconds" | "images";
219
+ inputPerMTokensUSD?: number;
220
+ outputPerMTokensUSD?: number;
221
+ perMCharsUSD?: number;
222
+ perSecUSD?: number;
223
+ perImageUSD?: number;
224
+ asOf: string;
225
+ };
226
+ export declare const gptImageOutputTokens: Record<string, {
227
+ low: number;
228
+ medium: number;
229
+ high: number;
230
+ }>;
231
+ export declare const modelPricing: Record<string, Record<string, ModelPricing>>;
232
+ export declare const getModelPricing: (provider: string, model: string) => ModelPricing | undefined;
216
233
  export {};
@@ -517,3 +517,82 @@ export const getModelDuration = (provider, model, movieDuration) => {
517
517
  }
518
518
  return durations?.[0];
519
519
  };
520
+ // gpt-image-1 / gpt-image-1-mini emit a fixed number of image output tokens per size × quality.
521
+ // Later gpt-image models (1.5 / 2) use variable resolutions, so this table is only an approximation for them.
522
+ // Source: https://developers.openai.com/api/docs/guides/image-generation (verified 2026-07-03)
523
+ export const gptImageOutputTokens = {
524
+ "1024x1024": { low: 272, medium: 1056, high: 4160 },
525
+ "1024x1536": { low: 408, medium: 1584, high: 6240 },
526
+ "1536x1024": { low: 400, medium: 1568, high: 6208 },
527
+ };
528
+ // Spot-checked against replicate.com model pages on this date (seedance-1-lite, kling-v2.1, omni-human).
529
+ // Some replicate prices vary by resolution/variant; price_per_sec holds the rate for the typical configuration
530
+ // (e.g. seedance-1-lite at 720p, kling-v2.1 standard).
531
+ const REPLICATE_PRICING_AS_OF = "2026-07-03";
532
+ const replicateMoviePricing = Object.fromEntries(Object.entries(provider2MovieAgent.replicate.modelParams).map(([model, params]) => [
533
+ model,
534
+ { unit: "seconds", perSecUSD: params.price_per_sec, asOf: REPLICATE_PRICING_AS_OF },
535
+ ]));
536
+ const replicateLipSyncPricing = Object.fromEntries(Object.entries(provider2LipSyncAgent.replicate.modelParams)
537
+ .filter(([, params]) => params.price_per_sec !== undefined)
538
+ .map(([model, params]) => [model, { unit: "seconds", perSecUSD: params.price_per_sec, asOf: REPLICATE_PRICING_AS_OF }]));
539
+ export const modelPricing = {
540
+ openai: {
541
+ // https://developers.openai.com/api/docs/models/gpt-4o-mini-tts
542
+ "gpt-4o-mini-tts": { unit: "tokens", inputPerMTokensUSD: 0.6, outputPerMTokensUSD: 12, asOf: "2026-07-03" },
543
+ "tts-1": { unit: "chars", perMCharsUSD: 15, asOf: "2026-07-03" },
544
+ "tts-1-hd": { unit: "chars", perMCharsUSD: 30, asOf: "2026-07-03" },
545
+ // https://developers.openai.com/api/docs/models/gpt-image-1 (image input tokens, $10/1M, are not modeled)
546
+ "gpt-image-1": { unit: "tokens", inputPerMTokensUSD: 5, outputPerMTokensUSD: 40, asOf: "2026-07-03" },
547
+ "gpt-image-1.5": { unit: "tokens", inputPerMTokensUSD: 5, outputPerMTokensUSD: 32, asOf: "2026-07-03" },
548
+ "gpt-image-2": { unit: "tokens", inputPerMTokensUSD: 5, outputPerMTokensUSD: 30, asOf: "2026-07-03" },
549
+ "gpt-image-1-mini": { unit: "tokens", inputPerMTokensUSD: 2, outputPerMTokensUSD: 8, asOf: "2026-07-03" },
550
+ // https://developers.openai.com/api/docs/models/gpt-5 etc.
551
+ "gpt-5": { unit: "tokens", inputPerMTokensUSD: 1.25, outputPerMTokensUSD: 10, asOf: "2026-07-03" },
552
+ "gpt-5-mini": { unit: "tokens", inputPerMTokensUSD: 0.25, outputPerMTokensUSD: 2, asOf: "2026-07-03" },
553
+ "gpt-4o": { unit: "tokens", inputPerMTokensUSD: 2.5, outputPerMTokensUSD: 10, asOf: "2026-07-03" },
554
+ },
555
+ gemini: {
556
+ // https://ai.google.dev/gemini-api/docs/pricing
557
+ "gemini-2.5-flash-preview-tts": { unit: "tokens", inputPerMTokensUSD: 0.5, outputPerMTokensUSD: 10, asOf: "2026-07-03" },
558
+ "gemini-2.5-pro-preview-tts": { unit: "tokens", inputPerMTokensUSD: 1, outputPerMTokensUSD: 20, asOf: "2026-07-03" },
559
+ },
560
+ google: {
561
+ // https://ai.google.dev/gemini-api/docs/pricing ($0.039/image ≒ 1290 output tokens at $30/1M)
562
+ "gemini-2.5-flash-image": { unit: "images", inputPerMTokensUSD: 0.3, perImageUSD: 0.039, asOf: "2026-07-03" },
563
+ // Veo per second of generated video (720p). veo-2.0-generate-001 and veo-3.0-generate-001
564
+ // were shut down on 2026-06-30, so they intentionally have no price.
565
+ "veo-3.1-generate-preview": { unit: "seconds", perSecUSD: 0.4, asOf: "2026-07-03" },
566
+ "veo-3.1-lite-generate-preview": { unit: "seconds", perSecUSD: 0.05, asOf: "2026-07-03" },
567
+ // Google Cloud Text-to-Speech per 1M characters, keyed by voice tier.
568
+ // https://cloud.google.com/text-to-speech/pricing
569
+ "tts-standard": { unit: "chars", perMCharsUSD: 4, asOf: "2026-07-03" },
570
+ "tts-wavenet": { unit: "chars", perMCharsUSD: 4, asOf: "2026-07-03" },
571
+ "tts-neural2": { unit: "chars", perMCharsUSD: 16, asOf: "2026-07-03" },
572
+ "tts-chirp3-hd": { unit: "chars", perMCharsUSD: 30, asOf: "2026-07-03" },
573
+ "tts-studio": { unit: "chars", perMCharsUSD: 160, asOf: "2026-07-03" },
574
+ },
575
+ anthropic: {
576
+ // https://platform.claude.com/docs/en/docs/about-claude/pricing
577
+ "claude-sonnet-4-5-20250929": { unit: "tokens", inputPerMTokensUSD: 3, outputPerMTokensUSD: 15, asOf: "2026-07-03" },
578
+ },
579
+ elevenlabs: {
580
+ // https://elevenlabs.io/pricing/api ($0.10 per 1k chars for multilingual/v3, $0.05 for flash/turbo)
581
+ eleven_v3: { unit: "chars", perMCharsUSD: 100, asOf: "2026-07-03" },
582
+ eleven_multilingual_v2: { unit: "chars", perMCharsUSD: 100, asOf: "2026-07-03" },
583
+ eleven_turbo_v2_5: { unit: "chars", perMCharsUSD: 50, asOf: "2026-07-03" },
584
+ eleven_turbo_v2: { unit: "chars", perMCharsUSD: 50, asOf: "2026-07-03" },
585
+ eleven_flash_v2_5: { unit: "chars", perMCharsUSD: 50, asOf: "2026-07-03" },
586
+ eleven_flash_v2: { unit: "chars", perMCharsUSD: 50, asOf: "2026-07-03" },
587
+ },
588
+ replicate: {
589
+ ...replicateMoviePricing,
590
+ ...replicateLipSyncPricing,
591
+ // https://replicate.com/bytedance/seedream-4 ($0.03 per output image)
592
+ // zsxkib/mmaudio is intentionally unpriced: it bills by GPU time (~$0.005 per run), not by clip duration.
593
+ "bytedance/seedream-4": { unit: "images", perImageUSD: 0.03, asOf: "2026-07-03" },
594
+ },
595
+ };
596
+ export const getModelPricing = (provider, model) => {
597
+ return modelPricing[provider]?.[model];
598
+ };
@@ -30,3 +30,24 @@ export type UsageCollectorAPI = {
30
30
  clear(): void;
31
31
  readonly size: number;
32
32
  };
33
+ export type EstimatePrecision = "exact" | "estimated";
34
+ export type EstimatedMetric = {
35
+ value: number;
36
+ precision: EstimatePrecision;
37
+ };
38
+ export type UsageEstimateProcess = "tts" | "image" | "htmlImage" | "movie" | "soundEffect" | "lipSync" | "translate" | "imageReference" | "movieReference";
39
+ export type UsageEstimate = {
40
+ process: UsageEstimateProcess;
41
+ beatIndex?: number;
42
+ refKey?: string;
43
+ lang?: string;
44
+ provider: string;
45
+ model: string;
46
+ inputTokens?: EstimatedMetric;
47
+ outputTokens?: EstimatedMetric;
48
+ inputChars?: EstimatedMetric;
49
+ predictSec?: EstimatedMetric;
50
+ imageCount?: EstimatedMetric;
51
+ costUSD?: number;
52
+ pricingAsOf?: string;
53
+ };
@@ -0,0 +1,11 @@
1
+ import type { MulmoScript, MulmoPresentationStyle } from "../types/index.js";
2
+ import type { UsageEstimate, UsageEstimateProcess } from "../types/usage.js";
3
+ export type EstimateUsageOptions = {
4
+ langs?: string[];
5
+ targetLangs?: string[];
6
+ presentationStyle?: MulmoPresentationStyle;
7
+ processes?: UsageEstimateProcess[];
8
+ };
9
+ export type EstimateAction = "audio" | "images" | "pdf" | "movie" | "translate";
10
+ export declare const actionEstimateProcesses: Record<EstimateAction, UsageEstimateProcess[]>;
11
+ export declare const estimateUsage: (script: MulmoScript, options?: EstimateUsageOptions) => UsageEstimate[];