@mux/ai 0.30.0 → 0.31.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.
@@ -1255,6 +1255,10 @@ interface AudioTranslationResult {
1255
1255
  dubbingId: string;
1256
1256
  uploadedTrackId?: string;
1257
1257
  presignedUrl?: string;
1258
+ /** Mux text track ID for the dubbed captions, present when `uploadCaptionsToMux` is true and the upload succeeded. */
1259
+ captionsTrackId?: string;
1260
+ /** Presigned URL for the dub's translated transcript (WebVTT) staged to S3. */
1261
+ captionsPresignedUrl?: string;
1258
1262
  /** Workflow usage metadata (asset duration, thumbnails, etc.). */
1259
1263
  usage?: TokenUsage;
1260
1264
  }
@@ -1290,6 +1294,13 @@ interface AudioTranslationOptions extends MuxAIOptions {
1290
1294
  * required for track creation.
1291
1295
  */
1292
1296
  uploadToMux?: boolean;
1297
+ /**
1298
+ * When true the dub's translated transcript (the same translation that was
1299
+ * voiced) is attached as a subtitles text track on the Mux asset. Implies
1300
+ * `uploadToS3: true`. Defaults to false. The SDK does not check for existing
1301
+ * text tracks — callers decide conflict semantics before enabling this.
1302
+ */
1303
+ uploadCaptionsToMux?: boolean;
1293
1304
  /** Optional storage adapter override for upload + presign operations. */
1294
1305
  storageAdapter?: StorageAdapter;
1295
1306
  /** Expiry duration in seconds for S3 presigned GET URLs. Defaults to 86400 (24 hours). */
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { I as ISO639_1, a as ISO639_3, L as LanguageCodePair, S as SupportedISO639_1, b as SupportedISO639_3, g as getLanguageCodePair, c as getLanguageName, i as isUndeterminedLanguageCode, d as isValidISO639_1, e as isValidISO639_3, t as toISO639_1, f as toISO639_3, h as workflows } from './index-D3cB2ltG.js';
1
+ export { I as ISO639_1, a as ISO639_3, L as LanguageCodePair, S as SupportedISO639_1, b as SupportedISO639_3, g as getLanguageCodePair, c as getLanguageName, i as isUndeterminedLanguageCode, d as isValidISO639_1, e as isValidISO639_3, t as toISO639_1, f as toISO639_3, h as workflows } from './index-ooikuED7.js';
2
2
  import { W as WorkflowCredentials, S as StoragePutObjectInput, a as StoragePresignGetObjectInput } from './shots-Bh0GrvLx.js';
3
3
  export { A as AssetTextTrack, C as ChunkEmbedding, b as ChunkingStrategy, E as Encrypted, c as EncryptedPayload, I as ImageSubmissionMode, M as MuxAIOptions, d as MuxAsset, P as PlaybackAsset, e as PlaybackPolicy, f as ScopedMuxAIOptions, g as StorageAdapter, T as TextChunk, h as TokenChunkingConfig, i as TokenUsage, j as ToneType, U as UsageMetadata, V as VTTChunkingConfig, k as VideoEmbeddingsResult, l as WorkflowCredentialsInput, m as WorkflowMuxClient, n as WorkflowScope, o as decryptFromWorkflow, p as encryptForWorkflow } from './shots-Bh0GrvLx.js';
4
4
  import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from '@workflow/serde';
@@ -9,7 +9,7 @@ import '@ai-sdk/google';
9
9
  import '@ai-sdk/openai';
10
10
  import '@mux/mux-node';
11
11
 
12
- var version = "0.30.0";
12
+ var version = "0.31.0";
13
13
 
14
14
  type MuxAiErrorType = "validation_error" | "processing_error" | "timeout_error" | "content_policy_error";
15
15
  /**
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // package.json
8
- var version = "0.30.0";
8
+ var version = "0.31.0";
9
9
 
10
10
  // src/lib/language-codes.ts
11
11
  var ISO639_1_TO_3 = {
@@ -3167,13 +3167,23 @@ function createLanguageSection(languageName) {
3167
3167
  }
3168
3168
 
3169
3169
  // src/lib/retry.ts
3170
+ import { DownloadError } from "ai";
3170
3171
  var DEFAULT_RETRY_OPTIONS = {
3171
3172
  maxRetries: 3,
3172
3173
  baseDelay: 2e3,
3173
3174
  maxDelay: 1e4
3174
3175
  };
3175
3176
  function defaultShouldRetry(error, _attempt) {
3176
- return Boolean(error.message && error.message.includes("Timeout while downloading"));
3177
+ if (error.message.includes("Timeout while downloading")) {
3178
+ return true;
3179
+ }
3180
+ const isDownloadError = DownloadError.isInstance(error);
3181
+ const isSerializedDownloadError = error.name === "AI_DownloadError";
3182
+ if (!isDownloadError && !isSerializedDownloadError) {
3183
+ return false;
3184
+ }
3185
+ const statusCode = error.statusCode;
3186
+ return statusCode === void 0 || statusCode === 408 || statusCode === 425 || statusCode === 429 || statusCode >= 500;
3177
3187
  }
3178
3188
  function calculateDelay(attempt, baseDelay, maxDelay) {
3179
3189
  const exponentialDelay = baseDelay * 2 ** (attempt - 1);
@@ -4098,14 +4108,14 @@ async function hasBurnedInCaptionsInternal(assetId, options, collectedUsage) {
4098
4108
  credentials
4099
4109
  });
4100
4110
  } else {
4101
- analysisResponse = await analyzeStoryboard({
4111
+ analysisResponse = await withRetry(() => analyzeStoryboard({
4102
4112
  imageDataUrl: imageUrl,
4103
4113
  provider: modelConfig.provider,
4104
4114
  modelId: modelConfig.modelId,
4105
4115
  userPrompt,
4106
4116
  systemPrompt: SYSTEM_PROMPT2,
4107
4117
  credentials
4108
- });
4118
+ }));
4109
4119
  }
4110
4120
  collectedUsage.push(analysisResponse.usage);
4111
4121
  if (!analysisResponse.result) {
@@ -7158,6 +7168,59 @@ async function downloadAndUploadDubbedAudio({
7158
7168
  console.warn(`\u{1F517} Generated presigned URL (expires in ${expiryHours} hour${expiryHours === 1 ? "" : "s"})`);
7159
7169
  return presignedUrl;
7160
7170
  }
7171
+ async function downloadAndUploadDubTranscript({
7172
+ dubbingId,
7173
+ languageCode,
7174
+ assetId,
7175
+ toLanguageCode,
7176
+ s3Endpoint,
7177
+ s3Region,
7178
+ s3Bucket,
7179
+ storageAdapter,
7180
+ s3SignedUrlExpirySeconds,
7181
+ credentials
7182
+ }) {
7183
+ "use step";
7184
+ const elevenLabsApiKey = await getApiKeyFromEnv("elevenlabs", credentials);
7185
+ const transcriptUrl = `https://api.elevenlabs.io/v1/dubbing/${dubbingId}/transcripts/${languageCode}/format/webvtt`;
7186
+ const transcriptResponse = await fetch(transcriptUrl, {
7187
+ headers: {
7188
+ "xi-api-key": elevenLabsApiKey
7189
+ }
7190
+ });
7191
+ if (!transcriptResponse.ok) {
7192
+ throw new Error(`Failed to fetch dub transcript: ${transcriptResponse.statusText}`);
7193
+ }
7194
+ const transcriptBody = await transcriptResponse.json();
7195
+ const transcriptVtt = transcriptBody.webvtt;
7196
+ if (!transcriptVtt) {
7197
+ throw new Error("Dub transcript response did not include webvtt content");
7198
+ }
7199
+ const s3AccessKeyId = env_default.S3_ACCESS_KEY_ID;
7200
+ const s3SecretAccessKey = env_default.S3_SECRET_ACCESS_KEY;
7201
+ const vttKey = `audio-translations/${assetId}/auto-to-${toLanguageCode}-${Date.now()}.vtt`;
7202
+ await putObjectWithStorageAdapter({
7203
+ accessKeyId: s3AccessKeyId,
7204
+ secretAccessKey: s3SecretAccessKey,
7205
+ endpoint: s3Endpoint,
7206
+ region: s3Region,
7207
+ bucket: s3Bucket,
7208
+ key: vttKey,
7209
+ body: transcriptVtt,
7210
+ contentType: "text/vtt"
7211
+ }, storageAdapter);
7212
+ const presignedUrl = await createPresignedGetUrlWithStorageAdapter({
7213
+ accessKeyId: s3AccessKeyId,
7214
+ secretAccessKey: s3SecretAccessKey,
7215
+ endpoint: s3Endpoint,
7216
+ region: s3Region,
7217
+ bucket: s3Bucket,
7218
+ key: vttKey,
7219
+ expiresInSeconds: s3SignedUrlExpirySeconds ?? 86400
7220
+ }, storageAdapter);
7221
+ console.warn(`\u2705 Dub transcript uploaded successfully to: ${vttKey}`);
7222
+ return presignedUrl;
7223
+ }
7161
7224
  async function createAudioTrackOnMux(assetId, languageCode, presignedUrl, credentials) {
7162
7225
  "use step";
7163
7226
  const muxClient = await resolveMuxClient(credentials);
@@ -7184,6 +7247,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
7184
7247
  // 0 = auto-detect
7185
7248
  uploadToS3: uploadToS3Option,
7186
7249
  uploadToMux: uploadToMuxOption,
7250
+ uploadCaptionsToMux = false,
7187
7251
  storageAdapter,
7188
7252
  credentials: providedCredentials
7189
7253
  } = options;
@@ -7193,7 +7257,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
7193
7257
  const credentials = providedCredentials;
7194
7258
  const effectiveStorageAdapter = storageAdapter;
7195
7259
  const uploadToMux = uploadToMuxOption !== false;
7196
- const uploadToS3 = uploadToS3Option || uploadToMux;
7260
+ const uploadToS3 = uploadToS3Option || uploadToMux || uploadCaptionsToMux;
7197
7261
  const s3Endpoint = options.s3Endpoint ?? env_default.S3_ENDPOINT;
7198
7262
  const s3Region = options.s3Region ?? env_default.S3_REGION ?? "auto";
7199
7263
  const s3Bucket = options.s3Bucket ?? env_default.S3_BUCKET;
@@ -7274,10 +7338,12 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
7274
7338
  console.warn("\u2705 Dubbing completed successfully!");
7275
7339
  let presignedUrl;
7276
7340
  let uploadedTrackId;
7341
+ let captionsPresignedUrl;
7342
+ let captionsTrackId;
7277
7343
  if (uploadToS3) {
7278
7344
  console.warn("\u{1F4E5} Downloading dubbed audio from ElevenLabs and staging to S3...");
7345
+ const downloadLangCode = targetLanguages[0] ?? toISO639_1(toLanguageCode);
7279
7346
  try {
7280
- const downloadLangCode = targetLanguages[0] ?? toISO639_1(toLanguageCode);
7281
7347
  presignedUrl = await downloadAndUploadDubbedAudio({
7282
7348
  dubbingId,
7283
7349
  languageCode: downloadLangCode,
@@ -7309,6 +7375,40 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
7309
7375
  console.warn(presignedUrl);
7310
7376
  }
7311
7377
  }
7378
+ try {
7379
+ console.warn("\u{1F4E5} Downloading dub transcript from ElevenLabs and staging to S3...");
7380
+ captionsPresignedUrl = await downloadAndUploadDubTranscript({
7381
+ dubbingId,
7382
+ languageCode: downloadLangCode,
7383
+ assetId,
7384
+ toLanguageCode,
7385
+ s3Endpoint,
7386
+ s3Region,
7387
+ s3Bucket,
7388
+ storageAdapter: effectiveStorageAdapter,
7389
+ s3SignedUrlExpirySeconds: options.s3SignedUrlExpirySeconds,
7390
+ credentials
7391
+ });
7392
+ if (uploadCaptionsToMux) {
7393
+ console.warn("\u{1F4F9} Adding dubbed captions track to Mux asset...");
7394
+ const muxLangCode = toISO639_1(toLanguageCode);
7395
+ const languageName = new Intl.DisplayNames(["en"], { type: "language" }).of(muxLangCode) || muxLangCode.toUpperCase();
7396
+ captionsTrackId = await createTextTrackOnMux(
7397
+ assetId,
7398
+ muxLangCode,
7399
+ `${languageName} (auto-dubbed)`,
7400
+ captionsPresignedUrl,
7401
+ credentials
7402
+ );
7403
+ console.warn(`\u2705 Captions track added to Mux asset with ID: ${captionsTrackId}`);
7404
+ }
7405
+ } catch (error) {
7406
+ console.warn(`\u26A0\uFE0F Failed to attach dubbed captions: ${error instanceof Error ? error.message : "Unknown error"}`);
7407
+ if (captionsPresignedUrl) {
7408
+ console.warn("\u{1F517} You can manually add the captions track using this presigned URL:");
7409
+ console.warn(captionsPresignedUrl);
7410
+ }
7411
+ }
7312
7412
  }
7313
7413
  const targetLanguage = getLanguageCodePair(toLanguageCode);
7314
7414
  return {
@@ -7318,6 +7418,8 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
7318
7418
  dubbingId,
7319
7419
  uploadedTrackId,
7320
7420
  presignedUrl,
7421
+ captionsTrackId,
7422
+ captionsPresignedUrl,
7321
7423
  usage: {
7322
7424
  metadata: {
7323
7425
  assetDurationSeconds