@mux/ai 0.35.0 → 0.37.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.
@@ -89,6 +89,12 @@ interface WorkflowMuxClient {
89
89
  interface MuxAIOptions {
90
90
  /** Optional timeout (ms) for helper utilities that support request limits. */
91
91
  timeout?: number;
92
+ /**
93
+ * Optional asset snapshot to reuse instead of retrieving the asset from Mux.
94
+ * The snapshot must belong to `assetId`; callers should capture it immediately
95
+ * before starting the workflow.
96
+ */
97
+ assetSnapshot?: MuxAsset;
92
98
  /**
93
99
  * Optional credentials for workflow execution.
94
100
  * Use encryptForWorkflow when running in Workflow Dev Kit environments.
@@ -1,7 +1,7 @@
1
- export { A as ASK_QUESTIONS_DEFAULT_MAX_ANSWER_OPTION_LENGTH, j as ASK_QUESTIONS_DEFAULT_MAX_FREE_FORM_ANSWER_LENGTH, k as ASK_QUESTIONS_MAX_QUESTION_LENGTH, l as AskQuestionsOptions, m as AskQuestionsResult, n as AskQuestionsType, o as AudioTranslationOptions, p as AudioTranslationResult, q as AutoCensorProfanityOptions, B as BurnedInCaptionsAnalysis, r as BurnedInCaptionsOptions, s as BurnedInCaptionsPromptOverrides, u as BurnedInCaptionsPromptSections, v as BurnedInCaptionsResult, C as CaptionReplacement, w as CensorMode, x as Chapter, y as ChapterSystemPromptSections, z as ChaptersOptions, D as ChaptersPromptOverrides, E as ChaptersPromptSections, F as ChaptersResult, G as ChaptersType, H as DEFAULT_DESCRIPTION_LENGTH, J as DEFAULT_SUMMARY_KEYWORD_LIMIT, K as DEFAULT_TITLE_LENGTH, M as EditCaptionsOptions, N as EditCaptionsResult, O as EmbeddingsOptions, P as EmbeddingsResult, Q as EngagementInsightsOptions, R as EngagementInsightsResult, T as GOOGLE_VISION_LIKELIHOOD_TO_SCORE, U as HIVE_SEXUAL_CATEGORIES, V as HIVE_VIOLENCE_CATEGORIES, W as HiveModerationOutput, X as HiveModerationSource, Y as ModerationOptions, Z as ModerationProvider, _ as ModerationResult, $ as MomentInsight, a0 as OverallInsight, a1 as ProfanityDetectionPayload, a2 as Question, a3 as QuestionAnswer, a4 as QuestionAnswerType, a5 as ReplacementRecord, a6 as StaticRenditionCleanupOutcome, a7 as SummarizationOptions, a8 as SummarizationPromptOverrides, a9 as SummarizationPromptSections, aa as SummaryAndTagsResult, ab as SummaryType, ac as ThumbnailModerationScore, ad as TranslationChunkingOptions, ae as TranslationOptions, af as TranslationPayload, ag as TranslationResult, ah as aggregateTokenUsage, ai as applyOverrideLists, aj as applyReplacements, ak as askQuestions, al as buildReplacementRegex, am as burnedInCaptionsSchema, an as censorVttContent, ao as chapterSchema, ap as chaptersSchema, aq as createReplacer, ar as editCaptions, as as extractTranscriptSegmentsForHotspots, at as generateChapters, au as generateEmbeddings, av as generateEngagementInsights, aw as generateVideoEmbeddings, ax as getModerationScores, ay as getSummaryAndTags, az as hasBurnedInCaptions, aA as mapShotsToHotspots, aB as normalizeTranslatedVtt, aC as profanityDetectionSchema, aD as questionAnswerSchema, aE as shouldSplitChunkTranslationError, aF as summarySchema, aG as transformCueText, aH as translateAudio, aI as translateCaptions, aJ as translationSchema, aK as validateNeverTranslateTerms, aL as verifyNeverTranslateTerms } from '../index-fU3hmKZu.js';
1
+ export { A as ASK_QUESTIONS_DEFAULT_MAX_ANSWER_OPTION_LENGTH, j as ASK_QUESTIONS_DEFAULT_MAX_FREE_FORM_ANSWER_LENGTH, k as ASK_QUESTIONS_MAX_QUESTION_LENGTH, l as AskQuestionsOptions, m as AskQuestionsResult, n as AskQuestionsType, o as AudioTranslationOptions, p as AudioTranslationResult, q as AutoCensorProfanityOptions, B as BurnedInCaptionsAnalysis, r as BurnedInCaptionsOptions, s as BurnedInCaptionsPromptOverrides, u as BurnedInCaptionsPromptSections, v as BurnedInCaptionsResult, C as CaptionReplacement, w as CensorMode, x as Chapter, y as ChapterSystemPromptSections, z as ChaptersOptions, D as ChaptersPromptOverrides, E as ChaptersPromptSections, F as ChaptersResult, G as ChaptersType, H as DEFAULT_DESCRIPTION_LENGTH, J as DEFAULT_SUMMARY_KEYWORD_LIMIT, K as DEFAULT_TITLE_LENGTH, M as EditCaptionsOptions, N as EditCaptionsResult, O as EmbeddingsOptions, P as EmbeddingsResult, Q as EngagementInsightsOptions, R as EngagementInsightsResult, T as GOOGLE_VISION_LIKELIHOOD_TO_SCORE, U as HIVE_SEXUAL_CATEGORIES, V as HIVE_VIOLENCE_CATEGORIES, W as HiveModerationOutput, X as HiveModerationSource, Y as ModerationOptions, Z as ModerationProvider, _ as ModerationResult, $ as MomentInsight, a0 as OverallInsight, a1 as ProfanityDetectionPayload, a2 as Question, a3 as QuestionAnswer, a4 as QuestionAnswerType, a5 as ReplacementRecord, a6 as SpeakerReplacement, a7 as StaticRenditionCleanupOutcome, a8 as SummarizationOptions, a9 as SummarizationPromptOverrides, aa as SummarizationPromptSections, ab as SummaryAndTagsResult, ac as SummaryType, ad as ThumbnailModerationScore, ae as TranslationChunkingOptions, af as TranslationOptions, ag as TranslationPayload, ah as TranslationResult, ai as aggregateTokenUsage, aj as applyOverrideLists, ak as applyReplacements, al as applySpeakerReplacements, am as askQuestions, an as buildReplacementRegex, ao as burnedInCaptionsSchema, ap as censorVttContent, aq as chapterSchema, ar as chaptersSchema, as as createReplacer, at as editCaptions, au as extractTranscriptSegmentsForHotspots, av as generateChapters, aw as generateEmbeddings, ax as generateEngagementInsights, ay as generateVideoEmbeddings, az as getModerationScores, aA as getSummaryAndTags, aB as hasBurnedInCaptions, aC as mapShotsToHotspots, aD as normalizeTranslatedVtt, aE as profanityDetectionSchema, aF as questionAnswerSchema, aG as shouldSplitChunkTranslationError, aH as summarySchema, aI as transformCueText, aJ as translateAudio, aK as translateCaptions, aL as translationSchema, aM as validateNeverTranslateTerms, aN as verifyNeverTranslateTerms } from '../index-BupL84pY.js';
2
2
  import 'zod';
3
3
  import '@ai-sdk/anthropic';
4
4
  import '@ai-sdk/google';
5
5
  import '@ai-sdk/openai';
6
- import '../shots-BMbViFbZ.js';
6
+ import '../shots-CUI09Ohe.js';
7
7
  import '@mux/mux-node';
@@ -1635,9 +1635,12 @@ function toPlaybackAsset(asset) {
1635
1635
  const { id: playbackId, policy } = getPlaybackId(asset);
1636
1636
  return { asset, playbackId, policy };
1637
1637
  }
1638
- async function getPlaybackIdForAsset(assetId, credentials) {
1638
+ async function getPlaybackIdForAsset(assetId, credentials, assetSnapshot) {
1639
1639
  "use step";
1640
- const asset = await getMuxAsset(assetId, credentials);
1640
+ const asset = assetSnapshot ?? await getMuxAsset(assetId, credentials);
1641
+ if (asset.id !== assetId) {
1642
+ throw new MuxAiError("Asset snapshot does not match the requested asset ID.", { type: "validation_error" });
1643
+ }
1641
1644
  return toPlaybackAsset(asset);
1642
1645
  }
1643
1646
  async function getMuxAsset(assetId, credentials) {
@@ -3023,7 +3026,7 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
3023
3026
  model,
3024
3027
  provider
3025
3028
  });
3026
- const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
3029
+ const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials, options?.assetSnapshot);
3027
3030
  const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
3028
3031
  const isAudioOnly = isAudioOnlyAsset(assetData);
3029
3032
  const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
@@ -3377,7 +3380,7 @@ async function hasBurnedInCaptionsInternal(assetId, options, collectedUsage) {
3377
3380
  model,
3378
3381
  provider
3379
3382
  });
3380
- const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
3383
+ const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials, options.assetSnapshot);
3381
3384
  const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
3382
3385
  const storyboardScope = resolveRenderableVideoScope(
3383
3386
  scope,
@@ -3642,7 +3645,7 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
3642
3645
  model,
3643
3646
  provider
3644
3647
  });
3645
- const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
3648
+ const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials, options.assetSnapshot);
3646
3649
  const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
3647
3650
  const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
3648
3651
  const resolvedScope = effectiveScope ? resolveWorkflowScope(effectiveScope, assetDurationSeconds) : void 0;
@@ -4204,6 +4207,27 @@ function applyReplacements(rawVtt, replacements) {
4204
4207
  });
4205
4208
  return { editedVtt, replacements: records };
4206
4209
  }
4210
+ function applySpeakerReplacements(rawVtt, speakerReplacements) {
4211
+ const replacementsByLabel = new Map(
4212
+ speakerReplacements.filter((replacement) => replacement.find.length > 0).map((replacement) => [replacement.find, replacement.replace])
4213
+ );
4214
+ if (replacementsByLabel.size === 0) {
4215
+ return { editedVtt: rawVtt, replacements: [] };
4216
+ }
4217
+ const records = [];
4218
+ const editedVtt = transformCueTextBlocks(rawVtt, (cueText, cueStartTime) => {
4219
+ return cueText.replace(/^(\s*)\[([^\]\r\n]+)\](?=\s|$)/, (marker, leadingWhitespace, label) => {
4220
+ const replacement = replacementsByLabel.get(label);
4221
+ if (replacement === void 0)
4222
+ return marker;
4223
+ const before = `[${label}]`;
4224
+ const after = `[${replacement}]`;
4225
+ records.push({ cueStartTime, before, after });
4226
+ return `${leadingWhitespace}${after}`;
4227
+ });
4228
+ });
4229
+ return { editedVtt, replacements: records };
4230
+ }
4207
4231
  async function identifyProfanityWithAI({
4208
4232
  plainText,
4209
4233
  provider,
@@ -4306,6 +4330,7 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
4306
4330
  model,
4307
4331
  autoCensorProfanity: autoCensorOption,
4308
4332
  replacements: replacementsOption,
4333
+ speakerReplacements: speakerReplacementsOption,
4309
4334
  deleteOriginalTrack,
4310
4335
  uploadToS3: uploadToS3Option,
4311
4336
  uploadToMux: uploadToMuxOption,
@@ -4318,8 +4343,9 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
4318
4343
  } = options;
4319
4344
  const hasAutoCensor = !!autoCensorOption;
4320
4345
  const hasReplacements = !!replacementsOption && replacementsOption.length > 0;
4321
- if (!hasAutoCensor && !hasReplacements) {
4322
- throw new MuxAiError("At least one of autoCensorProfanity or replacements must be provided.", { type: "validation_error" });
4346
+ const hasSpeakerReplacements = !!speakerReplacementsOption && speakerReplacementsOption.length > 0;
4347
+ if (!hasAutoCensor && !hasReplacements && !hasSpeakerReplacements) {
4348
+ throw new MuxAiError("At least one of autoCensorProfanity, replacements, or speakerReplacements must be provided.", { type: "validation_error" });
4323
4349
  }
4324
4350
  if (autoCensorOption && !provider) {
4325
4351
  throw new MuxAiError("provider is required when using autoCensorProfanity.", { type: "validation_error" });
@@ -4327,6 +4353,16 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
4327
4353
  const deleteOriginal = deleteOriginalTrack !== false;
4328
4354
  const uploadToMux = uploadToMuxOption !== false;
4329
4355
  const uploadToS3 = uploadToS3Option || uploadToMux;
4356
+ for (const replacement of speakerReplacementsOption ?? []) {
4357
+ const hasInvalidFindCharacter = replacement.find.includes("[") || replacement.find.includes("]") || /[\r\n]/.test(replacement.find);
4358
+ const hasInvalidReplaceCharacter = replacement.replace.includes("[") || replacement.replace.includes("]") || /[\r\n]/.test(replacement.replace);
4359
+ if (!replacement.find || !replacement.replace || hasInvalidFindCharacter || hasInvalidReplaceCharacter) {
4360
+ throw new MuxAiError(
4361
+ "Speaker replacement labels must be non-empty and must not contain square brackets or newlines.",
4362
+ { type: "validation_error" }
4363
+ );
4364
+ }
4365
+ }
4330
4366
  const s3Endpoint = providedS3Endpoint ?? env_default.S3_ENDPOINT;
4331
4367
  const s3Region = providedS3Region ?? env_default.S3_REGION ?? "auto";
4332
4368
  const s3Bucket = providedS3Bucket ?? env_default.S3_BUCKET;
@@ -4338,7 +4374,7 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
4338
4374
  { type: "validation_error" }
4339
4375
  );
4340
4376
  }
4341
- const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
4377
+ const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials, options.assetSnapshot);
4342
4378
  const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
4343
4379
  const signingContext = await resolveMuxSigningContext(credentials);
4344
4380
  if (policy === "signed" && !signingContext) {
@@ -4408,6 +4444,16 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
4408
4444
  totalReplacementCount += censorReplacements.length;
4409
4445
  autoCensorResult = { replacements: censorReplacements };
4410
4446
  }
4447
+ let speakerReplacementsResult;
4448
+ if (speakerReplacementsOption && speakerReplacementsOption.length > 0) {
4449
+ const { editedVtt: afterSpeakerReplacements, replacements: speakerReplacements } = applySpeakerReplacements(
4450
+ editedVtt,
4451
+ speakerReplacementsOption
4452
+ );
4453
+ editedVtt = afterSpeakerReplacements;
4454
+ totalReplacementCount += speakerReplacements.length;
4455
+ speakerReplacementsResult = { replacements: speakerReplacements };
4456
+ }
4411
4457
  let replacementsResult;
4412
4458
  if (replacementsOption && replacementsOption.length > 0) {
4413
4459
  const { editedVtt: afterReplacements, replacements: staticReplacements } = applyReplacements(editedVtt, replacementsOption);
@@ -4470,6 +4516,7 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
4470
4516
  totalReplacementCount,
4471
4517
  autoCensorProfanity: autoCensorResult,
4472
4518
  replacements: replacementsResult,
4519
+ speakerReplacements: speakerReplacementsResult,
4473
4520
  uploadedTrackId,
4474
4521
  presignedUrl,
4475
4522
  usage: usageWithMetadata,
@@ -4774,7 +4821,7 @@ async function generateEmbeddingsInternal(assetId, options, collectedUsage) {
4774
4821
  scope
4775
4822
  } = options;
4776
4823
  const embeddingModel = resolveEmbeddingModelConfig({ ...options, provider, model });
4777
- const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
4824
+ const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials, options.assetSnapshot);
4778
4825
  const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
4779
4826
  const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
4780
4827
  if (effectiveScope) {
@@ -5394,7 +5441,7 @@ async function generateEngagementInsightsInternal(assetId, options, collectedUsa
5394
5441
  model,
5395
5442
  provider
5396
5443
  });
5397
- const { asset, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
5444
+ const { asset, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials, options.assetSnapshot);
5398
5445
  const assetDurationSeconds = getAssetDurationSecondsFromAsset(asset);
5399
5446
  if (!assetDurationSeconds) {
5400
5447
  throw new MuxAiError(`Asset ${assetId} has no valid duration.`, { type: "validation_error" });
@@ -6152,7 +6199,7 @@ async function getModerationScores(assetId, options = {}) {
6152
6199
  scope
6153
6200
  } = options;
6154
6201
  const credentials = providedCredentials;
6155
- const { asset, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
6202
+ const { asset, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials, options.assetSnapshot);
6156
6203
  const videoTrackDurationSeconds = getVideoTrackDurationSecondsFromAsset(asset);
6157
6204
  const videoTrackFps = getVideoTrackMaxFrameRateFromAsset(asset);
6158
6205
  const assetDurationSeconds = getAssetDurationSecondsFromAsset(asset);
@@ -6766,7 +6813,7 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
6766
6813
  provider
6767
6814
  });
6768
6815
  const workflowCredentials = credentials;
6769
- const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, workflowCredentials);
6816
+ const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, workflowCredentials, options?.assetSnapshot);
6770
6817
  const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
6771
6818
  const isAudioOnly = isAudioOnlyAsset(assetData);
6772
6819
  const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
@@ -7213,7 +7260,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
7213
7260
  if (uploadToS3 && (!s3Endpoint || !s3Bucket || !effectiveStorageAdapter && (!s3AccessKeyId || !s3SecretAccessKey))) {
7214
7261
  throw new MuxAiError("Storage configuration is required for uploading. Provide s3Endpoint and s3Bucket. If no storageAdapter is supplied, also provide s3AccessKeyId and s3SecretAccessKey in options or set S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, and S3_SECRET_ACCESS_KEY environment variables.", { type: "validation_error" });
7215
7262
  }
7216
- const { asset: initialAsset, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
7263
+ const { asset: initialAsset, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials, options.assetSnapshot);
7217
7264
  const assetDurationSeconds = getAssetDurationSecondsFromAsset(initialAsset);
7218
7265
  let currentAsset = initialAsset;
7219
7266
  let createdStaticRenditionId;
@@ -8083,7 +8130,7 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
8083
8130
  if (uploadToS3 && (!s3Endpoint || !s3Bucket || !effectiveStorageAdapter && (!s3AccessKeyId || !s3SecretAccessKey))) {
8084
8131
  throw new MuxAiError("Storage configuration is required for uploading. Provide s3Endpoint and s3Bucket. If no storageAdapter is supplied, also provide s3AccessKeyId and s3SecretAccessKey in options or set S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, and S3_SECRET_ACCESS_KEY environment variables.", { type: "validation_error" });
8085
8132
  }
8086
- const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
8133
+ const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials, options.assetSnapshot);
8087
8134
  const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
8088
8135
  const signingContext = await resolveMuxSigningContext(credentials);
8089
8136
  if (policy === "signed" && !signingContext) {
@@ -8236,6 +8283,7 @@ export {
8236
8283
  aggregateTokenUsage,
8237
8284
  applyOverrideLists,
8238
8285
  applyReplacements,
8286
+ applySpeakerReplacements,
8239
8287
  askQuestions,
8240
8288
  buildReplacementRegex,
8241
8289
  burnedInCaptionsSchema,