@mux/ai 0.32.0 → 0.34.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.
@@ -1,4 +1,4 @@
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 SummarizationOptions, a7 as SummarizationPromptOverrides, a8 as SummarizationPromptSections, a9 as SummaryAndTagsResult, aa as SummaryType, ab as ThumbnailModerationScore, ac as TranslationChunkingOptions, ad as TranslationOptions, ae as TranslationPayload, af as TranslationResult, ag as aggregateTokenUsage, ah as applyOverrideLists, ai as applyReplacements, aj as askQuestions, ak as buildReplacementRegex, al as burnedInCaptionsSchema, am as censorVttContent, an as chapterSchema, ao as chaptersSchema, ap as createReplacer, aq as editCaptions, ar as extractTranscriptSegmentsForHotspots, as as generateChapters, at as generateEmbeddings, au as generateEngagementInsights, av as generateVideoEmbeddings, aw as getModerationScores, ax as getSummaryAndTags, ay as hasBurnedInCaptions, az as mapShotsToHotspots, aA as normalizeTranslatedVtt, aB as profanityDetectionSchema, aC as questionAnswerSchema, aD as shouldSplitChunkTranslationError, aE as summarySchema, aF as transformCueText, aG as translateAudio, aH as translateCaptions, aI as translationSchema } from '../index-D_nj_Nr4.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 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-B5l0yXZD.js';
2
2
  import 'zod';
3
3
  import '@ai-sdk/anthropic';
4
4
  import '@ai-sdk/google';
@@ -2613,11 +2613,9 @@ var SYSTEM_PROMPT = promptDedent`
2613
2613
  </constraints>
2614
2614
 
2615
2615
  <language_guidelines>
2616
- When explaining reasoning:
2617
- - Describe content directly, not the medium
2618
- - BAD: "The video shows a person running"
2619
- - GOOD: "A person runs through a park"
2620
- - Be specific and evidence-based
2616
+ When explaining reasoning, be specific and evidence-based.
2617
+
2618
+ ${createLanguageGuidelines("video")}
2621
2619
  </language_guidelines>`;
2622
2620
  var AUDIO_ONLY_SYSTEM_PROMPT = promptDedent`
2623
2621
  <role>
@@ -2710,11 +2708,9 @@ var AUDIO_ONLY_SYSTEM_PROMPT = promptDedent`
2710
2708
  </constraints>
2711
2709
 
2712
2710
  <language_guidelines>
2713
- When explaining reasoning:
2714
- - Describe content directly, not the medium
2715
- - BAD: "The audio says someone is running"
2716
- - GOOD: "The speaker describes running through a park"
2717
- - Be specific and evidence-based
2711
+ When explaining reasoning, be specific and evidence-based.
2712
+
2713
+ ${createLanguageGuidelines("audio")}
2718
2714
  </language_guidelines>`;
2719
2715
  function freeFormAddendum(maxFreeFormAnswerLength) {
2720
2716
  return promptDedent`
@@ -6918,19 +6914,41 @@ async function requestStaticRenditionCreation(assetId, credentials) {
6918
6914
  const muxClient = await resolveMuxClient(credentials);
6919
6915
  const mux = await muxClient.createClient();
6920
6916
  try {
6921
- await mux.video.assets.createStaticRendition(assetId, {
6917
+ const rendition = await mux.video.assets.createStaticRendition(assetId, {
6922
6918
  resolution: "audio-only"
6923
6919
  });
6920
+ return rendition.id;
6924
6921
  } catch (error) {
6925
6922
  const statusCode = error?.status ?? error?.statusCode;
6926
6923
  const messages = error?.error?.messages;
6927
6924
  const alreadyDefined = messages?.some((message) => message.toLowerCase().includes("already defined")) ?? error?.message?.toLowerCase().includes("already defined");
6928
6925
  if (statusCode === 409 || alreadyDefined) {
6929
- return;
6926
+ return void 0;
6930
6927
  }
6931
6928
  wrapError(error, "Failed to request static rendition from Mux");
6932
6929
  }
6933
6930
  }
6931
+ async function deleteCreatedStaticRendition(assetId, staticRenditionId, credentials) {
6932
+ "use step";
6933
+ try {
6934
+ const muxClient = await resolveMuxClient(credentials);
6935
+ const mux = await muxClient.createClient();
6936
+ await mux.video.assets.deleteStaticRendition(assetId, staticRenditionId);
6937
+ console.warn(`\u{1F9F9} Deleted static rendition ${staticRenditionId} created on asset ${assetId}`);
6938
+ return "deleted";
6939
+ } catch (error) {
6940
+ const statusCode = error?.status ?? error?.statusCode;
6941
+ if (statusCode === 404) {
6942
+ console.warn(`\u{1F9F9} Static rendition ${staticRenditionId} on asset ${assetId} was already deleted`);
6943
+ return "deleted";
6944
+ }
6945
+ const message = error instanceof Error ? error.message : String(error);
6946
+ console.warn(
6947
+ `\u26A0\uFE0F Failed to delete static rendition ${staticRenditionId} on asset ${assetId} (status: ${statusCode ?? "unknown"}): ${message}`
6948
+ );
6949
+ return "delete_failed";
6950
+ }
6951
+ }
6934
6952
  async function retrieveAsset(assetId, credentials) {
6935
6953
  "use step";
6936
6954
  const muxClient = await resolveMuxClient(credentials);
@@ -6946,14 +6964,6 @@ async function waitForAudioStaticRendition({
6946
6964
  if (hasReadyAudioStaticRendition(currentAsset)) {
6947
6965
  return currentAsset;
6948
6966
  }
6949
- const status = currentAsset.static_renditions?.status ?? "not_requested";
6950
- if (status === "not_requested" || status === void 0) {
6951
- await requestStaticRenditionCreation(assetId, credentials);
6952
- } else if (status === "errored") {
6953
- await requestStaticRenditionCreation(assetId, credentials);
6954
- } else {
6955
- console.warn(`\u2139\uFE0F Static rendition already ${status}. Waiting for it to finish...`);
6956
- }
6957
6967
  for (let attempt = 1; attempt <= STATIC_RENDITION_MAX_ATTEMPTS; attempt++) {
6958
6968
  await sleep(STATIC_RENDITION_POLL_INTERVAL_MS);
6959
6969
  currentAsset = await retrieveAsset(assetId, credentials);
@@ -7160,6 +7170,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
7160
7170
  uploadToS3: uploadToS3Option,
7161
7171
  uploadToMux: uploadToMuxOption,
7162
7172
  uploadCaptionsToMux = false,
7173
+ staticRenditionCleanup: staticRenditionCleanupOption = "delete",
7163
7174
  storageAdapter,
7164
7175
  credentials: providedCredentials
7165
7176
  } = options;
@@ -7181,144 +7192,167 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
7181
7192
  const { asset: initialAsset, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
7182
7193
  const assetDurationSeconds = getAssetDurationSecondsFromAsset(initialAsset);
7183
7194
  let currentAsset = initialAsset;
7195
+ let createdStaticRenditionId;
7184
7196
  if (!hasReadyAudioStaticRendition(currentAsset)) {
7185
7197
  console.warn("\u274C No ready audio static rendition found. Requesting one now...");
7186
- currentAsset = await waitForAudioStaticRendition({
7187
- assetId,
7188
- initialAsset: currentAsset,
7189
- credentials
7190
- });
7191
- }
7192
- const audioRendition = getReadyAudioStaticRendition(currentAsset);
7193
- if (!audioRendition) {
7194
- throw new MuxAiError(
7195
- "Unable to obtain an audio-only static rendition for this asset. Please verify static renditions are enabled in Mux.",
7196
- { type: "validation_error" }
7197
- );
7198
- }
7199
- let audioUrl = `${getMuxStreamOrigin()}/${playbackId}/audio.m4a`;
7200
- if (policy === "signed") {
7201
- audioUrl = await signUrl(audioUrl, playbackId, "video", void 0, credentials);
7198
+ const renditionStatus = currentAsset.static_renditions?.status ?? "not_requested";
7199
+ if (renditionStatus === "not_requested" || renditionStatus === "errored") {
7200
+ createdStaticRenditionId = await requestStaticRenditionCreation(assetId, credentials);
7201
+ } else {
7202
+ console.warn(`\u2139\uFE0F Static rendition already ${renditionStatus}. Waiting for it to finish...`);
7203
+ }
7202
7204
  }
7203
- console.warn("\u{1F399}\uFE0F Creating dubbing job in ElevenLabs...");
7204
- const elevenLabsLangCode = toISO639_3(toLanguageCode);
7205
- const normalizedFromLanguageCode = fromLanguageCode?.trim();
7206
- const elevenLabsSourceLangCode = normalizedFromLanguageCode ? toISO639_3(normalizedFromLanguageCode) : void 0;
7207
- console.warn(
7208
- `\u{1F50D} Creating dubbing job for asset ${assetId}: ${elevenLabsSourceLangCode ?? "auto"} -> ${elevenLabsLangCode}`
7209
- );
7205
+ let staticRenditionCleanup = "not_created";
7210
7206
  let dubbingId;
7207
+ let presignedUrl;
7208
+ let uploadedTrackId;
7209
+ let captionsPresignedUrl;
7210
+ let captionsTrackId;
7211
7211
  try {
7212
- dubbingId = await createElevenLabsDubbingJob({
7213
- sourceUrl: audioUrl,
7214
- assetId,
7215
- elevenLabsLangCode,
7216
- elevenLabsSourceLangCode,
7217
- numSpeakers,
7218
- credentials
7219
- });
7220
- console.warn(`\u2705 Dubbing job created with ID: ${dubbingId}`);
7221
- } catch (error) {
7222
- wrapError(error, "Failed to create ElevenLabs dubbing job");
7223
- }
7224
- console.warn("\u23F3 Waiting for dubbing to complete...");
7225
- const dubbingPollTimeoutSeconds = options.dubbingPollTimeoutSeconds ?? DEFAULT_DUBBING_POLL_TIMEOUT_SECONDS;
7226
- const maxPollAttempts = Math.max(1, Math.ceil(dubbingPollTimeoutSeconds * 1e3 / DUBBING_POLL_INTERVAL_MS));
7227
- let dubbingStatus = "dubbing";
7228
- let pollAttempts = 0;
7229
- let targetLanguages = [];
7230
- while (dubbingStatus !== "dubbed" && pollAttempts < maxPollAttempts) {
7231
- await sleep(DUBBING_POLL_INTERVAL_MS);
7232
- pollAttempts++;
7233
- try {
7234
- const statusResult = await checkElevenLabsDubbingStatus({
7235
- dubbingId,
7212
+ if (!hasReadyAudioStaticRendition(currentAsset)) {
7213
+ currentAsset = await waitForAudioStaticRendition({
7214
+ assetId,
7215
+ initialAsset: currentAsset,
7236
7216
  credentials
7237
7217
  });
7238
- dubbingStatus = statusResult.status;
7239
- targetLanguages = statusResult.targetLanguages;
7240
- } catch (error) {
7241
- wrapError(error, "Failed to check dubbing status");
7242
7218
  }
7243
- if (dubbingStatus === "failed") {
7244
- throw new MuxAiError("ElevenLabs reported that the dubbing job failed.", { type: "processing_error" });
7219
+ const audioRendition = getReadyAudioStaticRendition(currentAsset);
7220
+ if (!audioRendition) {
7221
+ throw new MuxAiError(
7222
+ "Unable to obtain an audio-only static rendition for this asset. Please verify static renditions are enabled in Mux.",
7223
+ { type: "validation_error" }
7224
+ );
7245
7225
  }
7246
- }
7247
- if (dubbingStatus !== "dubbed") {
7248
- throw new MuxAiError("Audio translation timed out or failed. Please try again.", { type: "timeout_error", retryable: true });
7249
- }
7250
- console.warn("\u2705 Dubbing completed successfully!");
7251
- let presignedUrl;
7252
- let uploadedTrackId;
7253
- let captionsPresignedUrl;
7254
- let captionsTrackId;
7255
- if (uploadToS3) {
7256
- console.warn("\u{1F4E5} Downloading dubbed audio from ElevenLabs and staging to S3...");
7257
- const downloadLangCode = targetLanguages[0] ?? toISO639_1(toLanguageCode);
7226
+ let audioUrl = `${getMuxStreamOrigin()}/${playbackId}/audio.m4a`;
7227
+ if (policy === "signed") {
7228
+ audioUrl = await signUrl(audioUrl, playbackId, "video", void 0, credentials);
7229
+ }
7230
+ console.warn("\u{1F399}\uFE0F Creating dubbing job in ElevenLabs...");
7231
+ const elevenLabsLangCode = toISO639_3(toLanguageCode);
7232
+ const normalizedFromLanguageCode = fromLanguageCode?.trim();
7233
+ const elevenLabsSourceLangCode = normalizedFromLanguageCode ? toISO639_3(normalizedFromLanguageCode) : void 0;
7234
+ console.warn(
7235
+ `\u{1F50D} Creating dubbing job for asset ${assetId}: ${elevenLabsSourceLangCode ?? "auto"} -> ${elevenLabsLangCode}`
7236
+ );
7258
7237
  try {
7259
- presignedUrl = await downloadAndUploadDubbedAudio({
7260
- dubbingId,
7261
- languageCode: downloadLangCode,
7238
+ dubbingId = await createElevenLabsDubbingJob({
7239
+ sourceUrl: audioUrl,
7262
7240
  assetId,
7263
- toLanguageCode,
7264
- s3Endpoint,
7265
- s3Region,
7266
- s3Bucket,
7267
- storageAdapter: effectiveStorageAdapter,
7268
- s3SignedUrlExpirySeconds: options.s3SignedUrlExpirySeconds,
7241
+ elevenLabsLangCode,
7242
+ elevenLabsSourceLangCode,
7243
+ numSpeakers,
7269
7244
  credentials
7270
7245
  });
7271
- console.warn("\u2705 Dubbed audio staged to S3 successfully!");
7246
+ console.warn(`\u2705 Dubbing job created with ID: ${dubbingId}`);
7272
7247
  } catch (error) {
7273
- wrapError(error, "Failed to download and upload dubbed audio");
7274
- }
7275
- if (uploadToMux) {
7276
- console.warn("\u{1F4F9} Adding dubbed audio track to Mux asset...");
7277
- const muxLangCode = toISO639_1(toLanguageCode);
7248
+ wrapError(error, "Failed to create ElevenLabs dubbing job");
7249
+ }
7250
+ console.warn("\u23F3 Waiting for dubbing to complete...");
7251
+ const dubbingPollTimeoutSeconds = options.dubbingPollTimeoutSeconds ?? DEFAULT_DUBBING_POLL_TIMEOUT_SECONDS;
7252
+ const maxPollAttempts = Math.max(1, Math.ceil(dubbingPollTimeoutSeconds * 1e3 / DUBBING_POLL_INTERVAL_MS));
7253
+ let dubbingStatus = "dubbing";
7254
+ let pollAttempts = 0;
7255
+ let targetLanguages = [];
7256
+ while (dubbingStatus !== "dubbed" && pollAttempts < maxPollAttempts) {
7257
+ await sleep(DUBBING_POLL_INTERVAL_MS);
7258
+ pollAttempts++;
7278
7259
  try {
7279
- uploadedTrackId = await createAudioTrackOnMux(assetId, muxLangCode, presignedUrl, credentials);
7280
- const languageName = new Intl.DisplayNames(["en"], { type: "language" }).of(muxLangCode) || muxLangCode.toUpperCase();
7281
- const trackName = `${languageName} (auto-dubbed)`;
7282
- console.warn(`\u2705 Track added to Mux asset with ID: ${uploadedTrackId}`);
7283
- console.warn(`\u{1F4CB} Track name: "${trackName}"`);
7260
+ const statusResult = await checkElevenLabsDubbingStatus({
7261
+ dubbingId,
7262
+ credentials
7263
+ });
7264
+ dubbingStatus = statusResult.status;
7265
+ targetLanguages = statusResult.targetLanguages;
7284
7266
  } catch (error) {
7285
- console.warn(`\u26A0\uFE0F Failed to add audio track to Mux asset: ${error instanceof Error ? error.message : "Unknown error"}`);
7286
- console.warn("\u{1F517} You can manually add the track using this presigned URL:");
7287
- console.warn(presignedUrl);
7267
+ wrapError(error, "Failed to check dubbing status");
7268
+ }
7269
+ if (dubbingStatus === "failed") {
7270
+ throw new MuxAiError("ElevenLabs reported that the dubbing job failed.", { type: "processing_error" });
7288
7271
  }
7289
7272
  }
7290
- try {
7291
- console.warn("\u{1F4E5} Downloading dub transcript from ElevenLabs and staging to S3...");
7292
- captionsPresignedUrl = await downloadAndUploadDubTranscript({
7293
- dubbingId,
7294
- languageCode: downloadLangCode,
7295
- assetId,
7296
- toLanguageCode,
7297
- s3Endpoint,
7298
- s3Region,
7299
- s3Bucket,
7300
- storageAdapter: effectiveStorageAdapter,
7301
- s3SignedUrlExpirySeconds: options.s3SignedUrlExpirySeconds,
7302
- credentials
7303
- });
7304
- if (uploadCaptionsToMux) {
7305
- console.warn("\u{1F4F9} Adding dubbed captions track to Mux asset...");
7273
+ if (dubbingStatus !== "dubbed") {
7274
+ throw new MuxAiError("Audio translation timed out or failed. Please try again.", { type: "timeout_error", retryable: true });
7275
+ }
7276
+ console.warn("\u2705 Dubbing completed successfully!");
7277
+ if (uploadToS3) {
7278
+ console.warn("\u{1F4E5} Downloading dubbed audio from ElevenLabs and staging to S3...");
7279
+ const downloadLangCode = targetLanguages[0] ?? toISO639_1(toLanguageCode);
7280
+ try {
7281
+ presignedUrl = await downloadAndUploadDubbedAudio({
7282
+ dubbingId,
7283
+ languageCode: downloadLangCode,
7284
+ assetId,
7285
+ toLanguageCode,
7286
+ s3Endpoint,
7287
+ s3Region,
7288
+ s3Bucket,
7289
+ storageAdapter: effectiveStorageAdapter,
7290
+ s3SignedUrlExpirySeconds: options.s3SignedUrlExpirySeconds,
7291
+ credentials
7292
+ });
7293
+ console.warn("\u2705 Dubbed audio staged to S3 successfully!");
7294
+ } catch (error) {
7295
+ wrapError(error, "Failed to download and upload dubbed audio");
7296
+ }
7297
+ if (uploadToMux) {
7298
+ console.warn("\u{1F4F9} Adding dubbed audio track to Mux asset...");
7306
7299
  const muxLangCode = toISO639_1(toLanguageCode);
7307
- const languageName = new Intl.DisplayNames(["en"], { type: "language" }).of(muxLangCode) || muxLangCode.toUpperCase();
7308
- captionsTrackId = await createTextTrackOnMux(
7300
+ try {
7301
+ uploadedTrackId = await createAudioTrackOnMux(assetId, muxLangCode, presignedUrl, credentials);
7302
+ const languageName = new Intl.DisplayNames(["en"], { type: "language" }).of(muxLangCode) || muxLangCode.toUpperCase();
7303
+ const trackName = `${languageName} (auto-dubbed)`;
7304
+ console.warn(`\u2705 Track added to Mux asset with ID: ${uploadedTrackId}`);
7305
+ console.warn(`\u{1F4CB} Track name: "${trackName}"`);
7306
+ } catch (error) {
7307
+ console.warn(`\u26A0\uFE0F Failed to add audio track to Mux asset: ${error instanceof Error ? error.message : "Unknown error"}`);
7308
+ console.warn("\u{1F517} You can manually add the track using this presigned URL:");
7309
+ console.warn(presignedUrl);
7310
+ }
7311
+ }
7312
+ try {
7313
+ console.warn("\u{1F4E5} Downloading dub transcript from ElevenLabs and staging to S3...");
7314
+ captionsPresignedUrl = await downloadAndUploadDubTranscript({
7315
+ dubbingId,
7316
+ languageCode: downloadLangCode,
7309
7317
  assetId,
7310
- muxLangCode,
7311
- `${languageName} (auto-dubbed)`,
7312
- captionsPresignedUrl,
7318
+ toLanguageCode,
7319
+ s3Endpoint,
7320
+ s3Region,
7321
+ s3Bucket,
7322
+ storageAdapter: effectiveStorageAdapter,
7323
+ s3SignedUrlExpirySeconds: options.s3SignedUrlExpirySeconds,
7313
7324
  credentials
7314
- );
7315
- console.warn(`\u2705 Captions track added to Mux asset with ID: ${captionsTrackId}`);
7325
+ });
7326
+ if (uploadCaptionsToMux) {
7327
+ console.warn("\u{1F4F9} Adding dubbed captions track to Mux asset...");
7328
+ const muxLangCode = toISO639_1(toLanguageCode);
7329
+ const languageName = new Intl.DisplayNames(["en"], { type: "language" }).of(muxLangCode) || muxLangCode.toUpperCase();
7330
+ captionsTrackId = await createTextTrackOnMux(
7331
+ assetId,
7332
+ muxLangCode,
7333
+ `${languageName} (auto-dubbed)`,
7334
+ captionsPresignedUrl,
7335
+ credentials
7336
+ );
7337
+ console.warn(`\u2705 Captions track added to Mux asset with ID: ${captionsTrackId}`);
7338
+ }
7339
+ } catch (error) {
7340
+ console.warn(`\u26A0\uFE0F Failed to attach dubbed captions: ${error instanceof Error ? error.message : "Unknown error"}`);
7341
+ if (captionsPresignedUrl) {
7342
+ console.warn("\u{1F517} You can manually add the captions track using this presigned URL:");
7343
+ console.warn(captionsPresignedUrl);
7344
+ }
7316
7345
  }
7317
- } catch (error) {
7318
- console.warn(`\u26A0\uFE0F Failed to attach dubbed captions: ${error instanceof Error ? error.message : "Unknown error"}`);
7319
- if (captionsPresignedUrl) {
7320
- console.warn("\u{1F517} You can manually add the captions track using this presigned URL:");
7321
- console.warn(captionsPresignedUrl);
7346
+ }
7347
+ } finally {
7348
+ if (createdStaticRenditionId) {
7349
+ if (staticRenditionCleanupOption === "keep") {
7350
+ console.warn(
7351
+ `\u2139\uFE0F Keeping static rendition ${createdStaticRenditionId} on asset ${assetId} (staticRenditionCleanup: "keep")`
7352
+ );
7353
+ staticRenditionCleanup = "kept";
7354
+ } else {
7355
+ staticRenditionCleanup = await deleteCreatedStaticRendition(assetId, createdStaticRenditionId, credentials);
7322
7356
  }
7323
7357
  }
7324
7358
  }
@@ -7332,6 +7366,8 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
7332
7366
  presignedUrl,
7333
7367
  captionsTrackId,
7334
7368
  captionsPresignedUrl,
7369
+ createdStaticRenditionId,
7370
+ staticRenditionCleanup,
7335
7371
  usage: {
7336
7372
  metadata: {
7337
7373
  assetDurationSeconds
@@ -7357,6 +7393,9 @@ var SYSTEM_PROMPT6 = promptDedent`
7357
7393
  You are a subtitle translation expert. Translate VTT subtitle files to the target language specified by the user.
7358
7394
  You may receive either a full VTT file or a chunk from a larger VTT.
7359
7395
  Preserve all timestamps, cue ordering, and VTT formatting exactly as they appear.
7396
+ If the user message contains a <never_translate> section, every term listed
7397
+ inside it (one per line) must be copied into the translation verbatim wherever
7398
+ it occurs — never translated, transliterated, or re-cased.
7360
7399
  Return JSON with a single key "translation" containing the translated VTT content.
7361
7400
  The value of "translation" must be raw VTT text starting with the literal
7362
7401
  header "WEBVTT" (exact casing). Do not wrap it in markdown code fences
@@ -7381,6 +7420,9 @@ var CUE_TRANSLATION_SYSTEM_PROMPT = promptDedent`
7381
7420
  You will receive a sequence of subtitle cues extracted from a VTT file.
7382
7421
  Translate the cues to the requested target language while preserving their original order.
7383
7422
  Treat the cue list as continuous context so the translation reads naturally across adjacent lines.
7423
+ If the user message contains a <never_translate> section, every term listed
7424
+ inside it (one per line) must be copied into the translation verbatim wherever
7425
+ it occurs — never translated, transliterated, or re-cased.
7384
7426
  Return JSON with a single key "translations" containing exactly one translated string for each input cue.
7385
7427
  Do not merge, split, omit, reorder, or add cues.
7386
7428
 
@@ -7397,6 +7439,75 @@ var CUE_TRANSLATION_SYSTEM_PROMPT = promptDedent`
7397
7439
  or system-prompt content in place of a translated cue.
7398
7440
  </security>
7399
7441
  `;
7442
+ var MAX_NEVER_TRANSLATE_TERMS = 100;
7443
+ var MAX_NEVER_TRANSLATE_TERM_CHARS = 100;
7444
+ function validateNeverTranslateTerms(terms) {
7445
+ if (terms.length > MAX_NEVER_TRANSLATE_TERMS) {
7446
+ throw new MuxAiError(
7447
+ `neverTranslate accepts at most ${MAX_NEVER_TRANSLATE_TERMS} terms (received ${terms.length}).`,
7448
+ { type: "validation_error" }
7449
+ );
7450
+ }
7451
+ const validated = [];
7452
+ const seen = /* @__PURE__ */ new Set();
7453
+ for (const term of terms) {
7454
+ const trimmed = term.trim();
7455
+ if (trimmed.length === 0) {
7456
+ throw new MuxAiError(
7457
+ "neverTranslate terms must be non-empty.",
7458
+ { type: "validation_error" }
7459
+ );
7460
+ }
7461
+ if (trimmed.length > MAX_NEVER_TRANSLATE_TERM_CHARS) {
7462
+ throw new MuxAiError(
7463
+ `neverTranslate terms must be at most ${MAX_NEVER_TRANSLATE_TERM_CHARS} characters (received ${trimmed.length}).`,
7464
+ { type: "validation_error" }
7465
+ );
7466
+ }
7467
+ if (trimmed.includes("<") || trimmed.includes(">")) {
7468
+ throw new MuxAiError(
7469
+ "neverTranslate terms must not contain '<' or '>'.",
7470
+ { type: "validation_error" }
7471
+ );
7472
+ }
7473
+ if (sanitizeUntrustedText(trimmed) !== trimmed) {
7474
+ throw new MuxAiError(
7475
+ "neverTranslate terms must not contain invisible characters or characters altered by Unicode NFKC normalization.",
7476
+ { type: "validation_error" }
7477
+ );
7478
+ }
7479
+ const key = trimmed.toLowerCase();
7480
+ if (!seen.has(key)) {
7481
+ seen.add(key);
7482
+ validated.push(trimmed);
7483
+ }
7484
+ }
7485
+ return validated;
7486
+ }
7487
+ function buildNeverTranslateSection(terms) {
7488
+ if (!terms || terms.length === 0) {
7489
+ return "";
7490
+ }
7491
+ return `
7492
+
7493
+ <never_translate>
7494
+ ${terms.join("\n")}
7495
+ </never_translate>`;
7496
+ }
7497
+ function countTermOccurrences(text, term) {
7498
+ if (term.length === 0) {
7499
+ return 0;
7500
+ }
7501
+ return text.split(term).length - 1;
7502
+ }
7503
+ function verifyNeverTranslateTerms(terms, sourceVtt, translatedVtt) {
7504
+ const cueText = (vtt) => parseVTTCues(vtt).map((cue) => cue.text).join("\n");
7505
+ const sourceText = cueText(sourceVtt).toLowerCase();
7506
+ const translatedText = cueText(translatedVtt);
7507
+ return terms.every(
7508
+ (term) => countTermOccurrences(translatedText, term) >= countTermOccurrences(sourceText, term.toLowerCase())
7509
+ );
7510
+ }
7400
7511
  var DEFAULT_TRANSLATION_CHUNKING = {
7401
7512
  enabled: true,
7402
7513
  minimumAssetDurationSeconds: 30 * 60,
@@ -7589,7 +7700,8 @@ async function translateVttWithAI({
7589
7700
  toLanguageCode,
7590
7701
  provider,
7591
7702
  modelId,
7592
- credentials
7703
+ credentials,
7704
+ neverTranslate
7593
7705
  }) {
7594
7706
  "use step";
7595
7707
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
@@ -7605,7 +7717,7 @@ async function translateVttWithAI({
7605
7717
  },
7606
7718
  {
7607
7719
  role: "user",
7608
- content: `Translate from ${fromLanguageCode} to ${toLanguageCode}:
7720
+ content: `Translate from ${fromLanguageCode} to ${toLanguageCode}:${buildNeverTranslateSection(neverTranslate)}
7609
7721
 
7610
7722
  ${sanitisedVttContent}`
7611
7723
  }
@@ -7648,7 +7760,8 @@ async function translateCueChunkWithAI({
7648
7760
  toLanguageCode,
7649
7761
  provider,
7650
7762
  modelId,
7651
- credentials
7763
+ credentials,
7764
+ neverTranslate
7652
7765
  }) {
7653
7766
  "use step";
7654
7767
  const model = await createLanguageModelFromConfig(provider, modelId, credentials);
@@ -7673,7 +7786,7 @@ async function translateCueChunkWithAI({
7673
7786
  {
7674
7787
  role: "user",
7675
7788
  content: `Translate from ${fromLanguageCode} to ${toLanguageCode}.
7676
- Return exactly ${cues.length} translated cues in the same order as the input.
7789
+ Return exactly ${cues.length} translated cues in the same order as the input.${buildNeverTranslateSection(neverTranslate)}
7677
7790
 
7678
7791
  ${JSON.stringify(cuePayload, null, 2)}`
7679
7792
  }
@@ -7726,7 +7839,8 @@ async function translateChunkWithFallback({
7726
7839
  toLanguageCode,
7727
7840
  provider,
7728
7841
  modelId,
7729
- credentials
7842
+ credentials,
7843
+ neverTranslate
7730
7844
  }) {
7731
7845
  "use step";
7732
7846
  try {
@@ -7736,7 +7850,8 @@ async function translateChunkWithFallback({
7736
7850
  toLanguageCode,
7737
7851
  provider,
7738
7852
  modelId,
7739
- credentials
7853
+ credentials,
7854
+ neverTranslate
7740
7855
  });
7741
7856
  if (result.translations.length !== chunk.cueCount) {
7742
7857
  throw new TranslationChunkValidationError(
@@ -7772,7 +7887,8 @@ async function translateChunkWithFallback({
7772
7887
  toLanguageCode,
7773
7888
  provider,
7774
7889
  modelId,
7775
- credentials
7890
+ credentials,
7891
+ neverTranslate
7776
7892
  }),
7777
7893
  translateChunkWithFallback({
7778
7894
  chunk: rightChunk,
@@ -7780,7 +7896,8 @@ async function translateChunkWithFallback({
7780
7896
  toLanguageCode,
7781
7897
  provider,
7782
7898
  modelId,
7783
- credentials
7899
+ credentials,
7900
+ neverTranslate
7784
7901
  })
7785
7902
  ]),
7786
7903
  failedAttemptUsage ? [failedAttemptUsage] : []
@@ -7804,7 +7921,8 @@ async function translateCaptionTrack({
7804
7921
  provider,
7805
7922
  modelId,
7806
7923
  credentials,
7807
- chunking
7924
+ chunking,
7925
+ neverTranslate
7808
7926
  }) {
7809
7927
  "use step";
7810
7928
  const chunkPlan = buildTranslationChunkRequests(vttContent, assetDurationSeconds, chunking);
@@ -7815,7 +7933,8 @@ async function translateCaptionTrack({
7815
7933
  toLanguageCode,
7816
7934
  provider,
7817
7935
  modelId,
7818
- credentials
7936
+ credentials,
7937
+ neverTranslate
7819
7938
  });
7820
7939
  }
7821
7940
  const resolvedChunking = resolveTranslationChunkingOptions(chunking);
@@ -7834,7 +7953,8 @@ async function translateCaptionTrack({
7834
7953
  toLanguageCode,
7835
7954
  provider,
7836
7955
  modelId,
7837
- credentials
7956
+ credentials,
7957
+ neverTranslate
7838
7958
  })
7839
7959
  )
7840
7960
  ),
@@ -7909,10 +8029,12 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
7909
8029
  uploadToMux: uploadToMuxOption,
7910
8030
  storageAdapter,
7911
8031
  credentials: providedCredentials,
7912
- chunking
8032
+ chunking,
8033
+ neverTranslate: neverTranslateOption
7913
8034
  } = options;
7914
8035
  const credentials = providedCredentials;
7915
8036
  const effectiveStorageAdapter = storageAdapter;
8037
+ const neverTranslateTerms = neverTranslateOption ? validateNeverTranslateTerms(neverTranslateOption) : [];
7916
8038
  const s3Endpoint = providedS3Endpoint ?? env_default.S3_ENDPOINT;
7917
8039
  const s3Region = providedS3Region ?? env_default.S3_REGION ?? "auto";
7918
8040
  const s3Bucket = providedS3Bucket ?? env_default.S3_BUCKET;
@@ -7973,7 +8095,8 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
7973
8095
  provider: modelConfig.provider,
7974
8096
  modelId: modelConfig.modelId,
7975
8097
  credentials,
7976
- chunking
8098
+ chunking,
8099
+ neverTranslate: neverTranslateTerms.length > 0 ? neverTranslateTerms : void 0
7977
8100
  });
7978
8101
  translatedVtt = result.translatedVtt;
7979
8102
  usage = result.usage;
@@ -8002,6 +8125,13 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
8002
8125
  leaksDetected: scrubbedFields.length > 0,
8003
8126
  scrubbedFields
8004
8127
  };
8128
+ let neverTranslateTermsPreserved;
8129
+ if (neverTranslateTerms.length > 0) {
8130
+ neverTranslateTermsPreserved = verifyNeverTranslateTerms(neverTranslateTerms, vttContent, translatedVtt);
8131
+ if (!neverTranslateTermsPreserved) {
8132
+ console.warn("[@mux/ai] One or more neverTranslate terms were not preserved verbatim in the translated output.");
8133
+ }
8134
+ }
8005
8135
  const usageWithMetadata = usage ? {
8006
8136
  ...usage,
8007
8137
  metadata: {
@@ -8056,7 +8186,8 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
8056
8186
  uploadedTrackId,
8057
8187
  presignedUrl,
8058
8188
  usage: usageWithMetadata,
8059
- safety
8189
+ safety,
8190
+ neverTranslateTermsPreserved
8060
8191
  };
8061
8192
  }
8062
8193
  export {
@@ -8097,6 +8228,8 @@ export {
8097
8228
  transformCueText,
8098
8229
  translateAudio,
8099
8230
  translateCaptions,
8100
- translationSchema
8231
+ translationSchema,
8232
+ validateNeverTranslateTerms,
8233
+ verifyNeverTranslateTerms
8101
8234
  };
8102
8235
  //# sourceMappingURL=index.js.map