@mux/ai 0.20.0 → 0.21.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.
@@ -120,6 +120,8 @@ interface WorkflowCredentials {
120
120
  anthropicApiKey?: string;
121
121
  /** Optional direct Google API key for per-request credential injection. */
122
122
  googleApiKey?: string;
123
+ /** Optional direct Google Vision API key for per-request credential injection. */
124
+ googleVisionApiKey?: string;
123
125
  /** Optional direct Hive API key for per-request credential injection. */
124
126
  hiveApiKey?: string;
125
127
  /** Optional direct ElevenLabs API key for per-request credential injection. */
@@ -1,7 +1,7 @@
1
- export { A as AskQuestionsOptions, a as AskQuestionsResult, b as AskQuestionsType, c as AudioTranslationOptions, d as AudioTranslationResult, e as AutoCensorProfanityOptions, B as BurnedInCaptionsAnalysis, f as BurnedInCaptionsOptions, g as BurnedInCaptionsPromptOverrides, h as BurnedInCaptionsPromptSections, j as BurnedInCaptionsResult, C as CaptionReplacement, k as CensorMode, l as Chapter, m as ChapterSystemPromptSections, n as ChaptersOptions, o as ChaptersPromptOverrides, p as ChaptersPromptSections, q as ChaptersResult, r as ChaptersType, D as DEFAULT_DESCRIPTION_LENGTH, s as DEFAULT_SUMMARY_KEYWORD_LIMIT, t as DEFAULT_TITLE_LENGTH, E as EditCaptionsOptions, u as EditCaptionsResult, v as EmbeddingsOptions, w as EmbeddingsResult, x as EngagementInsightsOptions, y as EngagementInsightsResult, H as HIVE_SEXUAL_CATEGORIES, z as HIVE_VIOLENCE_CATEGORIES, F as HiveModerationOutput, G as HiveModerationSource, M as ModerationOptions, I as ModerationProvider, J as ModerationResult, K as MomentInsight, O as OverallInsight, P as ProfanityDetectionPayload, Q as Question, L as QuestionAnswer, N as QuestionAnswerType, R as ReplacementRecord, S as SummarizationOptions, T as SummarizationPromptOverrides, U as SummarizationPromptSections, V as SummaryAndTagsResult, W as SummaryType, X as ThumbnailModerationScore, Y as TranslationChunkingOptions, Z as TranslationOptions, _ as TranslationPayload, $ as TranslationResult, a0 as aggregateTokenUsage, a1 as applyOverrideLists, a2 as applyReplacements, a3 as askQuestions, a4 as buildReplacementRegex, a5 as burnedInCaptionsSchema, a6 as censorVttContent, a7 as chapterSchema, a8 as chaptersSchema, a9 as createReplacer, aa as editCaptions, ab as extractTranscriptSegmentsForHotspots, ac as generateChapters, ad as generateEmbeddings, ae as generateEngagementInsights, af as generateVideoEmbeddings, ag as getModerationScores, ah as getSummaryAndTags, ai as hasBurnedInCaptions, aj as mapShotsToHotspots, ak as normalizeTranslatedVtt, al as profanityDetectionSchema, am as questionAnswerSchema, an as shouldSplitChunkTranslationError, ao as summarySchema, ap as transformCueText, aq as translateAudio, ar as translateCaptions, as as translationSchema } from '../index-DXMIJjUa.js';
1
+ export { A as AskQuestionsOptions, a as AskQuestionsResult, b as AskQuestionsType, c as AudioTranslationOptions, d as AudioTranslationResult, e as AutoCensorProfanityOptions, B as BurnedInCaptionsAnalysis, f as BurnedInCaptionsOptions, g as BurnedInCaptionsPromptOverrides, h as BurnedInCaptionsPromptSections, j as BurnedInCaptionsResult, C as CaptionReplacement, k as CensorMode, l as Chapter, m as ChapterSystemPromptSections, n as ChaptersOptions, o as ChaptersPromptOverrides, p as ChaptersPromptSections, q as ChaptersResult, r as ChaptersType, D as DEFAULT_DESCRIPTION_LENGTH, s as DEFAULT_SUMMARY_KEYWORD_LIMIT, t as DEFAULT_TITLE_LENGTH, E as EditCaptionsOptions, u as EditCaptionsResult, v as EmbeddingsOptions, w as EmbeddingsResult, x as EngagementInsightsOptions, y as EngagementInsightsResult, G as GOOGLE_VISION_LIKELIHOOD_TO_SCORE, H as HIVE_SEXUAL_CATEGORIES, z as HIVE_VIOLENCE_CATEGORIES, F as HiveModerationOutput, I as HiveModerationSource, M as ModerationOptions, J as ModerationProvider, K as ModerationResult, L as MomentInsight, O as OverallInsight, P as ProfanityDetectionPayload, Q as Question, N as QuestionAnswer, R as QuestionAnswerType, S as ReplacementRecord, T as SummarizationOptions, U as SummarizationPromptOverrides, V as SummarizationPromptSections, W as SummaryAndTagsResult, X as SummaryType, Y as ThumbnailModerationScore, Z as TranslationChunkingOptions, _ as TranslationOptions, $ as TranslationPayload, a0 as TranslationResult, a1 as aggregateTokenUsage, a2 as applyOverrideLists, a3 as applyReplacements, a4 as askQuestions, a5 as buildReplacementRegex, a6 as burnedInCaptionsSchema, a7 as censorVttContent, a8 as chapterSchema, a9 as chaptersSchema, aa as createReplacer, ab as editCaptions, ac as extractTranscriptSegmentsForHotspots, ad as generateChapters, ae as generateEmbeddings, af as generateEngagementInsights, ag as generateVideoEmbeddings, ah as getModerationScores, ai as getSummaryAndTags, aj as hasBurnedInCaptions, ak as mapShotsToHotspots, al as normalizeTranslatedVtt, am as profanityDetectionSchema, an as questionAnswerSchema, ao as shouldSplitChunkTranslationError, ap as summarySchema, aq as transformCueText, ar as translateAudio, as as translateCaptions, at as translationSchema } from '../index-DwNtsg-u.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-BFYOfBgN.js';
6
+ import '../shots-mGJfYGiO.js';
7
7
  import '@mux/mux-node';
@@ -186,6 +186,7 @@ var EnvSchema = z.object({
186
186
  GOOGLE_GENERATIVE_AI_API_KEY: optionalString("Google Generative AI API key for Gemini-backed workflows.", "Google Generative AI API key"),
187
187
  ELEVENLABS_API_KEY: optionalString("ElevenLabs API key for audio translation.", "ElevenLabs API key"),
188
188
  HIVE_API_KEY: optionalString("Hive Visual Moderation API key.", "Hive API key"),
189
+ GOOGLE_VISION_API_KEY: optionalString("Google Vision API key for SafeSearch moderation.", "Google Vision API key"),
189
190
  // S3-Compatible Storage (required for translation & audio dubbing)
190
191
  S3_ENDPOINT: optionalString("S3-compatible endpoint for uploads.", "S3 endpoint"),
191
192
  S3_REGION: optionalString("S3 region (defaults to 'auto' when omitted)."),
@@ -809,23 +810,26 @@ function resolveProviderApiKeyFromCredentials(provider, resolved) {
809
810
  const openaiApiKey = readString(record, "openaiApiKey");
810
811
  const anthropicApiKey = readString(record, "anthropicApiKey");
811
812
  const googleApiKey = readString(record, "googleApiKey");
813
+ const googleVisionApiKey = readString(record, "googleVisionApiKey");
812
814
  const hiveApiKey = readString(record, "hiveApiKey");
813
815
  const elevenLabsApiKey = readString(record, "elevenLabsApiKey");
814
816
  const apiKeyMap = {
815
- openai: openaiApiKey ?? env_default.OPENAI_API_KEY,
816
- anthropic: anthropicApiKey ?? env_default.ANTHROPIC_API_KEY,
817
- google: googleApiKey ?? env_default.GOOGLE_GENERATIVE_AI_API_KEY,
818
- hive: hiveApiKey ?? env_default.HIVE_API_KEY,
819
- elevenlabs: elevenLabsApiKey ?? env_default.ELEVENLABS_API_KEY
817
+ "openai": openaiApiKey ?? env_default.OPENAI_API_KEY,
818
+ "anthropic": anthropicApiKey ?? env_default.ANTHROPIC_API_KEY,
819
+ "google": googleApiKey ?? env_default.GOOGLE_GENERATIVE_AI_API_KEY,
820
+ "google-vision-api": googleVisionApiKey ?? env_default.GOOGLE_VISION_API_KEY,
821
+ "hive": hiveApiKey ?? env_default.HIVE_API_KEY,
822
+ "elevenlabs": elevenLabsApiKey ?? env_default.ELEVENLABS_API_KEY
820
823
  };
821
824
  const apiKey = apiKeyMap[provider];
822
825
  if (!apiKey) {
823
826
  const envVarNames = {
824
- openai: "OPENAI_API_KEY",
825
- anthropic: "ANTHROPIC_API_KEY",
826
- google: "GOOGLE_GENERATIVE_AI_API_KEY",
827
- hive: "HIVE_API_KEY",
828
- elevenlabs: "ELEVENLABS_API_KEY"
827
+ "openai": "OPENAI_API_KEY",
828
+ "anthropic": "ANTHROPIC_API_KEY",
829
+ "google": "GOOGLE_GENERATIVE_AI_API_KEY",
830
+ "google-vision-api": "GOOGLE_VISION_API_KEY",
831
+ "hive": "HIVE_API_KEY",
832
+ "elevenlabs": "ELEVENLABS_API_KEY"
829
833
  };
830
834
  throw new Error(
831
835
  `${provider} API key is required. Provide ${provider} credentials via workflow credentials or set ${envVarNames[provider]} environment variable.`
@@ -4859,6 +4863,15 @@ var OPENAI_MODERATION_BASE_DELAY_MS = 750;
4859
4863
  var OPENAI_MODERATION_MAX_DELAY_MS = 3e3;
4860
4864
  var MIN_SAMPLE_COVERAGE_FOR_CONFIDENT_THRESHOLDING = 0.5;
4861
4865
  var MIN_SUCCESSFUL_THUMBNAILS_FOR_CONFIDENT_THRESHOLDING = 3;
4866
+ var GOOGLE_VISION_ENDPOINT = "https://vision.googleapis.com/v1/images:annotate";
4867
+ var GOOGLE_VISION_LIKELIHOOD_TO_SCORE = {
4868
+ UNKNOWN: 0,
4869
+ VERY_UNLIKELY: 0.2,
4870
+ UNLIKELY: 0.4,
4871
+ POSSIBLE: 0.6,
4872
+ LIKELY: 0.8,
4873
+ VERY_LIKELY: 1
4874
+ };
4862
4875
  var HIVE_ENDPOINT = "https://api.thehive.ai/api/v2/task/sync";
4863
4876
  var HIVE_SEXUAL_CATEGORIES = [
4864
4877
  "general_nsfw",
@@ -5187,6 +5200,98 @@ async function requestHiveModeration(images, maxConcurrent = 5, submissionMode =
5187
5200
  }));
5188
5201
  return await processConcurrently(targets, moderateImageWithHive, maxConcurrent);
5189
5202
  }
5203
+ async function moderateImageWithGoogleVision(entry) {
5204
+ "use step";
5205
+ try {
5206
+ const apiKey = await getApiKeyFromEnv("google-vision-api", entry.credentials);
5207
+ const imageField = entry.isBase64 ? { content: entry.image } : { source: { imageUri: entry.image } };
5208
+ const controller = new AbortController();
5209
+ const timeout = setTimeout(() => controller.abort(), 15e3);
5210
+ let res;
5211
+ try {
5212
+ res = await fetch(GOOGLE_VISION_ENDPOINT, {
5213
+ method: "POST",
5214
+ headers: {
5215
+ "Accept": "application/json",
5216
+ "Content-Type": "application/json",
5217
+ "X-goog-api-key": apiKey
5218
+ },
5219
+ body: JSON.stringify({
5220
+ requests: [
5221
+ {
5222
+ image: imageField,
5223
+ features: [{ type: "SAFE_SEARCH_DETECTION" }]
5224
+ }
5225
+ ]
5226
+ }),
5227
+ signal: controller.signal
5228
+ });
5229
+ } catch (err) {
5230
+ if (err?.name === "AbortError") {
5231
+ throw new Error("Google Vision request timed out after 15s");
5232
+ }
5233
+ throw err;
5234
+ } finally {
5235
+ clearTimeout(timeout);
5236
+ }
5237
+ const json = await res.json().catch(() => void 0);
5238
+ if (!res.ok) {
5239
+ throw new Error(
5240
+ `Google Vision moderation error: ${res.status} ${res.statusText} - ${JSON.stringify(json)}`
5241
+ );
5242
+ }
5243
+ const perImage = json?.responses?.[0];
5244
+ if (perImage?.error) {
5245
+ throw new Error(
5246
+ `Google Vision per-image error: ${perImage.error.code} ${perImage.error.message || "Unknown error"}`
5247
+ );
5248
+ }
5249
+ const annotation = perImage?.safeSearchAnnotation;
5250
+ if (!annotation) {
5251
+ throw new Error(`Google Vision response missing safeSearchAnnotation: ${JSON.stringify(json)}`);
5252
+ }
5253
+ const adultLikelihood = typeof annotation.adult === "string" ? annotation.adult : "UNKNOWN";
5254
+ const violenceLikelihood = typeof annotation.violence === "string" ? annotation.violence : "UNKNOWN";
5255
+ const sexual = GOOGLE_VISION_LIKELIHOOD_TO_SCORE[adultLikelihood] ?? 0;
5256
+ const violence = GOOGLE_VISION_LIKELIHOOD_TO_SCORE[violenceLikelihood] ?? 0;
5257
+ return {
5258
+ url: entry.url,
5259
+ time: entry.time,
5260
+ sexual,
5261
+ violence,
5262
+ error: false
5263
+ };
5264
+ } catch (error) {
5265
+ return {
5266
+ url: entry.url,
5267
+ time: entry.time,
5268
+ sexual: 0,
5269
+ violence: 0,
5270
+ error: true,
5271
+ errorMessage: error instanceof Error ? error.message : String(error)
5272
+ };
5273
+ }
5274
+ }
5275
+ async function requestGoogleVisionModeration(images, maxConcurrent = 5, submissionMode = "url", downloadOptions, credentials) {
5276
+ "use step";
5277
+ const imageUrls = images.map((img) => img.url);
5278
+ const timeByUrl = new Map(images.map((img) => [img.url, img.time]));
5279
+ const targets = submissionMode === "base64" ? (await downloadImagesAsBase64(imageUrls, downloadOptions, maxConcurrent)).map((img) => ({
5280
+ url: img.url,
5281
+ time: timeByUrl.get(img.url),
5282
+ // Vision REST wants raw base64; downloadImagesAsBase64 returns a data URI.
5283
+ image: img.base64Data.startsWith("data:") ? img.base64Data.split(",")[1] : img.base64Data,
5284
+ isBase64: true,
5285
+ credentials
5286
+ })) : images.map((img) => ({
5287
+ url: img.url,
5288
+ time: img.time,
5289
+ image: img.url,
5290
+ isBase64: false,
5291
+ credentials
5292
+ }));
5293
+ return processConcurrently(targets, moderateImageWithGoogleVision, maxConcurrent);
5294
+ }
5190
5295
  async function getThumbnailUrlsFromTimestamps(playbackId, timestampsMs, options) {
5191
5296
  "use step";
5192
5297
  const { width, shouldSign, credentials } = options;
@@ -5250,8 +5355,11 @@ async function getModerationScores(assetId, options = {}) {
5250
5355
  );
5251
5356
  } else if (provider === "hive") {
5252
5357
  throw new Error("Hive does not support transcript moderation in this workflow. Use provider: 'openai' for audio-only assets.");
5358
+ } else if (provider === "google-vision-api") {
5359
+ throw new Error("google-vision-api is image-only and does not support transcript moderation. Use provider: 'openai' for audio-only assets.");
5253
5360
  } else {
5254
- throw new Error(`Unsupported moderation provider: ${provider}`);
5361
+ const exhaustiveCheck = provider;
5362
+ throw new Error(`Unsupported moderation provider: ${exhaustiveCheck}`);
5255
5363
  }
5256
5364
  } else {
5257
5365
  const estimatedIntervalCount = duration <= 50 ? 5 : Math.ceil(duration / thumbnailInterval);
@@ -5294,8 +5402,17 @@ async function getModerationScores(assetId, options = {}) {
5294
5402
  imageDownloadOptions,
5295
5403
  credentials
5296
5404
  );
5405
+ } else if (provider === "google-vision-api") {
5406
+ thumbnailScores = await requestGoogleVisionModeration(
5407
+ thumbnailUrls,
5408
+ maxConcurrent,
5409
+ imageSubmissionMode,
5410
+ imageDownloadOptions,
5411
+ credentials
5412
+ );
5297
5413
  } else {
5298
- throw new Error(`Unsupported moderation provider: ${provider}`);
5414
+ const exhaustiveCheck = provider;
5415
+ throw new Error(`Unsupported moderation provider: ${exhaustiveCheck}`);
5299
5416
  }
5300
5417
  }
5301
5418
  const failed = thumbnailScores.filter((s) => s.error);
@@ -6999,6 +7116,7 @@ export {
6999
7116
  DEFAULT_DESCRIPTION_LENGTH,
7000
7117
  DEFAULT_SUMMARY_KEYWORD_LIMIT,
7001
7118
  DEFAULT_TITLE_LENGTH,
7119
+ GOOGLE_VISION_LIKELIHOOD_TO_SCORE,
7002
7120
  HIVE_SEXUAL_CATEGORIES,
7003
7121
  HIVE_VIOLENCE_CATEGORIES,
7004
7122
  aggregateTokenUsage,