@mux/ai 0.2.0 → 0.3.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.
- package/README.md +61 -16
- package/dist/{index-DyTSka2R.d.ts → index-BcNDGOI6.d.ts} +12 -24
- package/dist/{index-CMZYZcj6.d.ts → index-D3fZHu0h.d.ts} +2 -10
- package/dist/index.d.ts +3 -3
- package/dist/index.js +155 -160
- package/dist/index.js.map +1 -1
- package/dist/primitives/index.d.ts +2 -2
- package/dist/primitives/index.js +19 -8
- package/dist/primitives/index.js.map +1 -1
- package/dist/{types-ktXDZ93V.d.ts → types-DzOQNn9R.d.ts} +3 -25
- package/dist/workflows/index.d.ts +2 -2
- package/dist/workflows/index.js +155 -160
- package/dist/workflows/index.js.map +1 -1
- package/package.json +14 -14
package/dist/workflows/index.js
CHANGED
|
@@ -66,22 +66,22 @@ function requireEnv(value, name) {
|
|
|
66
66
|
}
|
|
67
67
|
return value;
|
|
68
68
|
}
|
|
69
|
-
function createLanguageModelFromConfig(provider, modelId
|
|
69
|
+
function createLanguageModelFromConfig(provider, modelId) {
|
|
70
70
|
switch (provider) {
|
|
71
71
|
case "openai": {
|
|
72
|
-
const apiKey =
|
|
72
|
+
const apiKey = env_default.OPENAI_API_KEY;
|
|
73
73
|
requireEnv(apiKey, "OPENAI_API_KEY");
|
|
74
74
|
const openai = createOpenAI({ apiKey });
|
|
75
75
|
return openai(modelId);
|
|
76
76
|
}
|
|
77
77
|
case "anthropic": {
|
|
78
|
-
const apiKey =
|
|
78
|
+
const apiKey = env_default.ANTHROPIC_API_KEY;
|
|
79
79
|
requireEnv(apiKey, "ANTHROPIC_API_KEY");
|
|
80
80
|
const anthropic = createAnthropic({ apiKey });
|
|
81
81
|
return anthropic(modelId);
|
|
82
82
|
}
|
|
83
83
|
case "google": {
|
|
84
|
-
const apiKey =
|
|
84
|
+
const apiKey = env_default.GOOGLE_GENERATIVE_AI_API_KEY;
|
|
85
85
|
requireEnv(apiKey, "GOOGLE_GENERATIVE_AI_API_KEY");
|
|
86
86
|
const google = createGoogleGenerativeAI({ apiKey });
|
|
87
87
|
return google(modelId);
|
|
@@ -92,16 +92,16 @@ function createLanguageModelFromConfig(provider, modelId, credentials) {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
-
function createEmbeddingModelFromConfig(provider, modelId
|
|
95
|
+
function createEmbeddingModelFromConfig(provider, modelId) {
|
|
96
96
|
switch (provider) {
|
|
97
97
|
case "openai": {
|
|
98
|
-
const apiKey =
|
|
98
|
+
const apiKey = env_default.OPENAI_API_KEY;
|
|
99
99
|
requireEnv(apiKey, "OPENAI_API_KEY");
|
|
100
100
|
const openai = createOpenAI({ apiKey });
|
|
101
101
|
return openai.embedding(modelId);
|
|
102
102
|
}
|
|
103
103
|
case "google": {
|
|
104
|
-
const apiKey =
|
|
104
|
+
const apiKey = env_default.GOOGLE_GENERATIVE_AI_API_KEY;
|
|
105
105
|
requireEnv(apiKey, "GOOGLE_GENERATIVE_AI_API_KEY");
|
|
106
106
|
const google = createGoogleGenerativeAI({ apiKey });
|
|
107
107
|
return google.textEmbeddingModel(modelId);
|
|
@@ -117,7 +117,7 @@ function resolveLanguageModel(options = {}) {
|
|
|
117
117
|
const modelId = options.model || DEFAULT_LANGUAGE_MODELS[provider];
|
|
118
118
|
switch (provider) {
|
|
119
119
|
case "openai": {
|
|
120
|
-
const apiKey =
|
|
120
|
+
const apiKey = env_default.OPENAI_API_KEY;
|
|
121
121
|
requireEnv(apiKey, "OPENAI_API_KEY");
|
|
122
122
|
const openai = createOpenAI({
|
|
123
123
|
apiKey
|
|
@@ -129,7 +129,7 @@ function resolveLanguageModel(options = {}) {
|
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
case "anthropic": {
|
|
132
|
-
const apiKey =
|
|
132
|
+
const apiKey = env_default.ANTHROPIC_API_KEY;
|
|
133
133
|
requireEnv(apiKey, "ANTHROPIC_API_KEY");
|
|
134
134
|
const anthropic = createAnthropic({
|
|
135
135
|
apiKey
|
|
@@ -141,7 +141,7 @@ function resolveLanguageModel(options = {}) {
|
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
143
|
case "google": {
|
|
144
|
-
const apiKey =
|
|
144
|
+
const apiKey = env_default.GOOGLE_GENERATIVE_AI_API_KEY;
|
|
145
145
|
requireEnv(apiKey, "GOOGLE_GENERATIVE_AI_API_KEY");
|
|
146
146
|
const google = createGoogleGenerativeAI({
|
|
147
147
|
apiKey
|
|
@@ -163,7 +163,7 @@ function resolveEmbeddingModel(options = {}) {
|
|
|
163
163
|
const modelId = options.model || DEFAULT_EMBEDDING_MODELS[provider];
|
|
164
164
|
switch (provider) {
|
|
165
165
|
case "openai": {
|
|
166
|
-
const apiKey =
|
|
166
|
+
const apiKey = env_default.OPENAI_API_KEY;
|
|
167
167
|
requireEnv(apiKey, "OPENAI_API_KEY");
|
|
168
168
|
const openai = createOpenAI({
|
|
169
169
|
apiKey
|
|
@@ -175,7 +175,7 @@ function resolveEmbeddingModel(options = {}) {
|
|
|
175
175
|
};
|
|
176
176
|
}
|
|
177
177
|
case "google": {
|
|
178
|
-
const apiKey =
|
|
178
|
+
const apiKey = env_default.GOOGLE_GENERATIVE_AI_API_KEY;
|
|
179
179
|
requireEnv(apiKey, "GOOGLE_GENERATIVE_AI_API_KEY");
|
|
180
180
|
const google = createGoogleGenerativeAI({
|
|
181
181
|
apiKey
|
|
@@ -194,13 +194,45 @@ function resolveEmbeddingModel(options = {}) {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
// src/lib/client-factory.ts
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
197
|
+
function getMuxCredentialsFromEnv() {
|
|
198
|
+
const muxTokenId = env_default.MUX_TOKEN_ID;
|
|
199
|
+
const muxTokenSecret = env_default.MUX_TOKEN_SECRET;
|
|
200
|
+
if (!muxTokenId || !muxTokenSecret) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
"Mux credentials are required. Set MUX_TOKEN_ID and MUX_TOKEN_SECRET environment variables."
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
return { muxTokenId, muxTokenSecret };
|
|
206
|
+
}
|
|
207
|
+
function getApiKeyFromEnv(provider) {
|
|
208
|
+
const envVarMap = {
|
|
209
|
+
openai: env_default.OPENAI_API_KEY,
|
|
210
|
+
anthropic: env_default.ANTHROPIC_API_KEY,
|
|
211
|
+
google: env_default.GOOGLE_GENERATIVE_AI_API_KEY,
|
|
212
|
+
hive: env_default.HIVE_API_KEY,
|
|
213
|
+
elevenlabs: env_default.ELEVENLABS_API_KEY
|
|
214
|
+
};
|
|
215
|
+
const apiKey = envVarMap[provider];
|
|
216
|
+
if (!apiKey) {
|
|
217
|
+
const envVarNames = {
|
|
218
|
+
openai: "OPENAI_API_KEY",
|
|
219
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
220
|
+
google: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
221
|
+
hive: "HIVE_API_KEY",
|
|
222
|
+
elevenlabs: "ELEVENLABS_API_KEY"
|
|
223
|
+
};
|
|
224
|
+
throw new Error(
|
|
225
|
+
`${provider} API key is required. Set ${envVarNames[provider]} environment variable.`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return apiKey;
|
|
229
|
+
}
|
|
230
|
+
async function validateCredentials(requiredProvider) {
|
|
231
|
+
const muxTokenId = env_default.MUX_TOKEN_ID;
|
|
232
|
+
const muxTokenSecret = env_default.MUX_TOKEN_SECRET;
|
|
233
|
+
const openaiApiKey = env_default.OPENAI_API_KEY;
|
|
234
|
+
const anthropicApiKey = env_default.ANTHROPIC_API_KEY;
|
|
235
|
+
const googleApiKey = env_default.GOOGLE_GENERATIVE_AI_API_KEY;
|
|
204
236
|
if (!muxTokenId || !muxTokenSecret) {
|
|
205
237
|
throw new Error(
|
|
206
238
|
"Mux credentials are required. Provide muxTokenId and muxTokenSecret in options or set MUX_TOKEN_ID and MUX_TOKEN_SECRET environment variables."
|
|
@@ -230,9 +262,8 @@ async function validateCredentials(options, requiredProvider) {
|
|
|
230
262
|
};
|
|
231
263
|
}
|
|
232
264
|
async function createWorkflowConfig(options, provider) {
|
|
233
|
-
"use step";
|
|
234
265
|
const providerToUse = provider || options.provider || "openai";
|
|
235
|
-
const credentials = await validateCredentials(
|
|
266
|
+
const credentials = await validateCredentials(providerToUse);
|
|
236
267
|
const resolved = resolveLanguageModel({
|
|
237
268
|
...options,
|
|
238
269
|
provider: providerToUse
|
|
@@ -353,11 +384,12 @@ function getPlaybackId(asset) {
|
|
|
353
384
|
"No public or signed playback ID found for this asset. A public or signed playback ID is required. DRM playback IDs are not currently supported."
|
|
354
385
|
);
|
|
355
386
|
}
|
|
356
|
-
async function getPlaybackIdForAsset(
|
|
387
|
+
async function getPlaybackIdForAsset(assetId) {
|
|
357
388
|
"use step";
|
|
389
|
+
const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();
|
|
358
390
|
const mux = new Mux({
|
|
359
|
-
tokenId:
|
|
360
|
-
tokenSecret:
|
|
391
|
+
tokenId: muxTokenId,
|
|
392
|
+
tokenSecret: muxTokenSecret
|
|
361
393
|
});
|
|
362
394
|
const asset = await mux.video.assets.retrieve(assetId);
|
|
363
395
|
const { id: playbackId, policy } = getPlaybackId(asset);
|
|
@@ -440,10 +472,9 @@ function createToneSection(instruction) {
|
|
|
440
472
|
|
|
441
473
|
// src/lib/url-signing.ts
|
|
442
474
|
import Mux2 from "@mux/mux-node";
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const
|
|
446
|
-
const keySecret = config.muxPrivateKey ?? env_default.MUX_PRIVATE_KEY;
|
|
475
|
+
function getMuxSigningContextFromEnv() {
|
|
476
|
+
const keyId = env_default.MUX_SIGNING_KEY;
|
|
477
|
+
const keySecret = env_default.MUX_PRIVATE_KEY;
|
|
447
478
|
if (!keyId || !keySecret) {
|
|
448
479
|
return void 0;
|
|
449
480
|
}
|
|
@@ -480,10 +511,11 @@ async function signUrl(url, playbackId, context, type = "video", params) {
|
|
|
480
511
|
|
|
481
512
|
// src/primitives/storyboards.ts
|
|
482
513
|
var DEFAULT_STORYBOARD_WIDTH = 640;
|
|
483
|
-
async function getStoryboardUrl(playbackId, width = DEFAULT_STORYBOARD_WIDTH,
|
|
514
|
+
async function getStoryboardUrl(playbackId, width = DEFAULT_STORYBOARD_WIDTH, shouldSign = false) {
|
|
484
515
|
"use step";
|
|
485
516
|
const baseUrl = `https://image.mux.com/${playbackId}/storyboard.png`;
|
|
486
|
-
if (
|
|
517
|
+
if (shouldSign) {
|
|
518
|
+
const signingContext = getMuxSigningContextFromEnv();
|
|
487
519
|
return signUrl(baseUrl, playbackId, signingContext, "storyboard", { width });
|
|
488
520
|
}
|
|
489
521
|
return `${baseUrl}?width=${width}`;
|
|
@@ -587,16 +619,11 @@ async function analyzeStoryboard({
|
|
|
587
619
|
imageDataUrl,
|
|
588
620
|
provider,
|
|
589
621
|
modelId,
|
|
590
|
-
credentials,
|
|
591
622
|
userPrompt,
|
|
592
623
|
systemPrompt
|
|
593
624
|
}) {
|
|
594
625
|
"use step";
|
|
595
|
-
const model = createLanguageModelFromConfig(
|
|
596
|
-
provider,
|
|
597
|
-
modelId,
|
|
598
|
-
credentials
|
|
599
|
-
);
|
|
626
|
+
const model = createLanguageModelFromConfig(provider, modelId);
|
|
600
627
|
const response = await generateObject({
|
|
601
628
|
model,
|
|
602
629
|
schema: burnedInCaptionsSchema,
|
|
@@ -641,14 +668,14 @@ async function hasBurnedInCaptions(assetId, options = {}) {
|
|
|
641
668
|
{ ...config, model },
|
|
642
669
|
provider
|
|
643
670
|
);
|
|
644
|
-
const { playbackId, policy } = await getPlaybackIdForAsset(
|
|
645
|
-
const signingContext =
|
|
671
|
+
const { playbackId, policy } = await getPlaybackIdForAsset(assetId);
|
|
672
|
+
const signingContext = getMuxSigningContextFromEnv();
|
|
646
673
|
if (policy === "signed" && !signingContext) {
|
|
647
674
|
throw new Error(
|
|
648
675
|
"Signed playback ID requires signing credentials. Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables."
|
|
649
676
|
);
|
|
650
677
|
}
|
|
651
|
-
const imageUrl = await getStoryboardUrl(playbackId, 640, policy === "signed"
|
|
678
|
+
const imageUrl = await getStoryboardUrl(playbackId, 640, policy === "signed");
|
|
652
679
|
let analysisResponse;
|
|
653
680
|
if (imageSubmissionMode === "base64") {
|
|
654
681
|
const base64Data = await fetchImageAsBase64(imageUrl, imageDownloadOptions);
|
|
@@ -656,7 +683,6 @@ async function hasBurnedInCaptions(assetId, options = {}) {
|
|
|
656
683
|
imageDataUrl: base64Data,
|
|
657
684
|
provider: workflowConfig.provider,
|
|
658
685
|
modelId: workflowConfig.modelId,
|
|
659
|
-
credentials: workflowConfig.credentials,
|
|
660
686
|
userPrompt,
|
|
661
687
|
systemPrompt: SYSTEM_PROMPT
|
|
662
688
|
});
|
|
@@ -665,7 +691,6 @@ async function hasBurnedInCaptions(assetId, options = {}) {
|
|
|
665
691
|
imageDataUrl: imageUrl,
|
|
666
692
|
provider: workflowConfig.provider,
|
|
667
693
|
modelId: workflowConfig.modelId,
|
|
668
|
-
credentials: workflowConfig.credentials,
|
|
669
694
|
userPrompt,
|
|
670
695
|
systemPrompt: SYSTEM_PROMPT
|
|
671
696
|
});
|
|
@@ -835,17 +860,18 @@ function parseVTTCues(vttContent) {
|
|
|
835
860
|
}
|
|
836
861
|
return cues;
|
|
837
862
|
}
|
|
838
|
-
async function buildTranscriptUrl(playbackId, trackId,
|
|
863
|
+
async function buildTranscriptUrl(playbackId, trackId, shouldSign = false) {
|
|
839
864
|
"use step";
|
|
840
865
|
const baseUrl = `https://stream.mux.com/${playbackId}/text/${trackId}.vtt`;
|
|
841
|
-
if (
|
|
866
|
+
if (shouldSign) {
|
|
867
|
+
const signingContext = getMuxSigningContextFromEnv();
|
|
842
868
|
return signUrl(baseUrl, playbackId, signingContext, "video");
|
|
843
869
|
}
|
|
844
870
|
return baseUrl;
|
|
845
871
|
}
|
|
846
872
|
async function fetchTranscriptForAsset(asset, playbackId, options = {}) {
|
|
847
873
|
"use step";
|
|
848
|
-
const { languageCode, cleanTranscript = true,
|
|
874
|
+
const { languageCode, cleanTranscript = true, shouldSign } = options;
|
|
849
875
|
const track = findCaptionTrack(asset, languageCode);
|
|
850
876
|
if (!track) {
|
|
851
877
|
return { transcriptText: "" };
|
|
@@ -853,7 +879,7 @@ async function fetchTranscriptForAsset(asset, playbackId, options = {}) {
|
|
|
853
879
|
if (!track.id) {
|
|
854
880
|
return { transcriptText: "", track };
|
|
855
881
|
}
|
|
856
|
-
const transcriptUrl = await buildTranscriptUrl(playbackId, track.id,
|
|
882
|
+
const transcriptUrl = await buildTranscriptUrl(playbackId, track.id, shouldSign);
|
|
857
883
|
try {
|
|
858
884
|
const response = await fetch(transcriptUrl);
|
|
859
885
|
if (!response.ok) {
|
|
@@ -879,16 +905,11 @@ var chaptersSchema = z3.object({
|
|
|
879
905
|
async function generateChaptersWithAI({
|
|
880
906
|
provider,
|
|
881
907
|
modelId,
|
|
882
|
-
credentials,
|
|
883
908
|
timestampedTranscript,
|
|
884
909
|
systemPrompt
|
|
885
910
|
}) {
|
|
886
911
|
"use step";
|
|
887
|
-
const model = createLanguageModelFromConfig(
|
|
888
|
-
provider,
|
|
889
|
-
modelId,
|
|
890
|
-
credentials
|
|
891
|
-
);
|
|
912
|
+
const model = createLanguageModelFromConfig(provider, modelId);
|
|
892
913
|
const response = await withRetry(
|
|
893
914
|
() => generateObject2({
|
|
894
915
|
model,
|
|
@@ -931,8 +952,8 @@ async function generateChapters(assetId, languageCode, options = {}) {
|
|
|
931
952
|
"use workflow";
|
|
932
953
|
const { provider = "openai", model } = options;
|
|
933
954
|
const config = await createWorkflowConfig({ ...options, model }, provider);
|
|
934
|
-
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(
|
|
935
|
-
const signingContext =
|
|
955
|
+
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId);
|
|
956
|
+
const signingContext = getMuxSigningContextFromEnv();
|
|
936
957
|
if (policy === "signed" && !signingContext) {
|
|
937
958
|
throw new Error(
|
|
938
959
|
"Signed playback ID requires signing credentials. Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables."
|
|
@@ -942,7 +963,7 @@ async function generateChapters(assetId, languageCode, options = {}) {
|
|
|
942
963
|
languageCode,
|
|
943
964
|
cleanTranscript: false,
|
|
944
965
|
// keep timestamps for chapter segmentation
|
|
945
|
-
|
|
966
|
+
shouldSign: policy === "signed"
|
|
946
967
|
});
|
|
947
968
|
if (!transcriptResult.track || !transcriptResult.transcriptText) {
|
|
948
969
|
const availableLanguages = getReadyTextTracks(assetData).map((t) => t.language_code).filter(Boolean).join(", ");
|
|
@@ -959,7 +980,6 @@ async function generateChapters(assetId, languageCode, options = {}) {
|
|
|
959
980
|
chaptersData = await generateChaptersWithAI({
|
|
960
981
|
provider: config.provider,
|
|
961
982
|
modelId: config.modelId,
|
|
962
|
-
credentials: config.credentials,
|
|
963
983
|
timestampedTranscript,
|
|
964
984
|
systemPrompt: SYSTEM_PROMPT2
|
|
965
985
|
});
|
|
@@ -1093,11 +1113,10 @@ function averageEmbeddings(embeddings) {
|
|
|
1093
1113
|
async function generateSingleChunkEmbedding({
|
|
1094
1114
|
chunk,
|
|
1095
1115
|
provider,
|
|
1096
|
-
modelId
|
|
1097
|
-
credentials
|
|
1116
|
+
modelId
|
|
1098
1117
|
}) {
|
|
1099
1118
|
"use step";
|
|
1100
|
-
const model = createEmbeddingModelFromConfig(provider, modelId
|
|
1119
|
+
const model = createEmbeddingModelFromConfig(provider, modelId);
|
|
1101
1120
|
const response = await withRetry(
|
|
1102
1121
|
() => embed({
|
|
1103
1122
|
model,
|
|
@@ -1123,13 +1142,9 @@ async function generateVideoEmbeddings(assetId, options = {}) {
|
|
|
1123
1142
|
chunkingStrategy = { type: "token", maxTokens: 500, overlap: 100 },
|
|
1124
1143
|
batchSize = 5
|
|
1125
1144
|
} = options;
|
|
1126
|
-
const credentials = await validateCredentials(options, provider === "google" ? "google" : "openai");
|
|
1127
1145
|
const embeddingModel = resolveEmbeddingModel({ ...options, provider, model });
|
|
1128
|
-
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(
|
|
1129
|
-
|
|
1130
|
-
assetId
|
|
1131
|
-
);
|
|
1132
|
-
const signingContext = await resolveSigningContext(options);
|
|
1146
|
+
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId);
|
|
1147
|
+
const signingContext = getMuxSigningContextFromEnv();
|
|
1133
1148
|
if (policy === "signed" && !signingContext) {
|
|
1134
1149
|
throw new Error(
|
|
1135
1150
|
"Signed playback ID requires signing credentials. Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables."
|
|
@@ -1139,7 +1154,7 @@ async function generateVideoEmbeddings(assetId, options = {}) {
|
|
|
1139
1154
|
const transcriptResult = await fetchTranscriptForAsset(assetData, playbackId, {
|
|
1140
1155
|
languageCode,
|
|
1141
1156
|
cleanTranscript: !useVttChunking,
|
|
1142
|
-
|
|
1157
|
+
shouldSign: policy === "signed"
|
|
1143
1158
|
});
|
|
1144
1159
|
if (!transcriptResult.track || !transcriptResult.transcriptText) {
|
|
1145
1160
|
const availableLanguages = getReadyTextTracks(assetData).map((t) => t.language_code).filter(Boolean).join(", ");
|
|
@@ -1168,8 +1183,7 @@ async function generateVideoEmbeddings(assetId, options = {}) {
|
|
|
1168
1183
|
(chunk) => generateSingleChunkEmbedding({
|
|
1169
1184
|
chunk,
|
|
1170
1185
|
provider: embeddingModel.provider,
|
|
1171
|
-
modelId: embeddingModel.modelId
|
|
1172
|
-
credentials
|
|
1186
|
+
modelId: embeddingModel.modelId
|
|
1173
1187
|
})
|
|
1174
1188
|
)
|
|
1175
1189
|
);
|
|
@@ -1204,7 +1218,7 @@ async function generateVideoEmbeddings(assetId, options = {}) {
|
|
|
1204
1218
|
// src/primitives/thumbnails.ts
|
|
1205
1219
|
async function getThumbnailUrls(playbackId, duration, options = {}) {
|
|
1206
1220
|
"use step";
|
|
1207
|
-
const { interval = 10, width = 640,
|
|
1221
|
+
const { interval = 10, width = 640, shouldSign = false } = options;
|
|
1208
1222
|
const timestamps = [];
|
|
1209
1223
|
if (duration <= 50) {
|
|
1210
1224
|
const spacing = duration / 6;
|
|
@@ -1218,7 +1232,8 @@ async function getThumbnailUrls(playbackId, duration, options = {}) {
|
|
|
1218
1232
|
}
|
|
1219
1233
|
const baseUrl = `https://image.mux.com/${playbackId}/thumbnail.png`;
|
|
1220
1234
|
const urlPromises = timestamps.map(async (time) => {
|
|
1221
|
-
if (
|
|
1235
|
+
if (shouldSign) {
|
|
1236
|
+
const signingContext = getMuxSigningContextFromEnv();
|
|
1222
1237
|
return signUrl(baseUrl, playbackId, signingContext, "thumbnail", { time, width });
|
|
1223
1238
|
}
|
|
1224
1239
|
return `${baseUrl}?time=${time}&width=${width}`;
|
|
@@ -1279,19 +1294,20 @@ async function processConcurrently(items, processor, maxConcurrent = 5) {
|
|
|
1279
1294
|
}
|
|
1280
1295
|
return results;
|
|
1281
1296
|
}
|
|
1282
|
-
async function requestOpenAIModeration(imageUrls,
|
|
1297
|
+
async function requestOpenAIModeration(imageUrls, model, maxConcurrent = 5, submissionMode = "url", downloadOptions) {
|
|
1283
1298
|
"use step";
|
|
1284
1299
|
const targetUrls = submissionMode === "base64" ? (await downloadImagesAsBase64(imageUrls, downloadOptions, maxConcurrent)).map(
|
|
1285
|
-
(img) => ({ url: img.url, image: img.base64Data,
|
|
1286
|
-
) : imageUrls.map((url) => ({ url, image: url,
|
|
1300
|
+
(img) => ({ url: img.url, image: img.base64Data, model })
|
|
1301
|
+
) : imageUrls.map((url) => ({ url, image: url, model }));
|
|
1287
1302
|
const moderate = async (entry) => {
|
|
1288
1303
|
"use step";
|
|
1304
|
+
const apiKey = getApiKeyFromEnv("openai");
|
|
1289
1305
|
try {
|
|
1290
1306
|
const res = await fetch("https://api.openai.com/v1/moderations", {
|
|
1291
1307
|
method: "POST",
|
|
1292
1308
|
headers: {
|
|
1293
1309
|
"Content-Type": "application/json",
|
|
1294
|
-
"Authorization": `Bearer ${
|
|
1310
|
+
"Authorization": `Bearer ${apiKey}`
|
|
1295
1311
|
},
|
|
1296
1312
|
body: JSON.stringify({
|
|
1297
1313
|
model: entry.model,
|
|
@@ -1337,7 +1353,7 @@ function getHiveCategoryScores(classes, categoryNames) {
|
|
|
1337
1353
|
const scores = categoryNames.map((category) => scoreMap[category] || 0);
|
|
1338
1354
|
return Math.max(...scores, 0);
|
|
1339
1355
|
}
|
|
1340
|
-
async function requestHiveModeration(imageUrls,
|
|
1356
|
+
async function requestHiveModeration(imageUrls, maxConcurrent = 5, submissionMode = "url", downloadOptions) {
|
|
1341
1357
|
"use step";
|
|
1342
1358
|
const targets = submissionMode === "base64" ? (await downloadImagesAsBase64(imageUrls, downloadOptions, maxConcurrent)).map((img) => ({
|
|
1343
1359
|
url: img.url,
|
|
@@ -1352,6 +1368,7 @@ async function requestHiveModeration(imageUrls, apiKey, maxConcurrent = 5, submi
|
|
|
1352
1368
|
}));
|
|
1353
1369
|
const moderate = async (entry) => {
|
|
1354
1370
|
"use step";
|
|
1371
|
+
const apiKey = getApiKeyFromEnv("hive");
|
|
1355
1372
|
try {
|
|
1356
1373
|
const formData = new FormData();
|
|
1357
1374
|
if (entry.source.kind === "url") {
|
|
@@ -1408,10 +1425,9 @@ async function getModerationScores(assetId, options = {}) {
|
|
|
1408
1425
|
imageSubmissionMode = "url",
|
|
1409
1426
|
imageDownloadOptions
|
|
1410
1427
|
} = options;
|
|
1411
|
-
const
|
|
1412
|
-
const { asset, playbackId, policy } = await getPlaybackIdForAsset(credentials, assetId);
|
|
1428
|
+
const { asset, playbackId, policy } = await getPlaybackIdForAsset(assetId);
|
|
1413
1429
|
const duration = asset.duration || 0;
|
|
1414
|
-
const signingContext =
|
|
1430
|
+
const signingContext = getMuxSigningContextFromEnv();
|
|
1415
1431
|
if (policy === "signed" && !signingContext) {
|
|
1416
1432
|
throw new Error(
|
|
1417
1433
|
"Signed playback ID requires signing credentials. Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables."
|
|
@@ -1420,30 +1436,20 @@ async function getModerationScores(assetId, options = {}) {
|
|
|
1420
1436
|
const thumbnailUrls = await getThumbnailUrls(playbackId, duration, {
|
|
1421
1437
|
interval: thumbnailInterval,
|
|
1422
1438
|
width: thumbnailWidth,
|
|
1423
|
-
|
|
1439
|
+
shouldSign: policy === "signed"
|
|
1424
1440
|
});
|
|
1425
1441
|
let thumbnailScores;
|
|
1426
1442
|
if (provider === "openai") {
|
|
1427
|
-
const apiKey = credentials.openaiApiKey;
|
|
1428
|
-
if (!apiKey) {
|
|
1429
|
-
throw new Error("OpenAI API key is required for moderation. Set OPENAI_API_KEY or pass openaiApiKey.");
|
|
1430
|
-
}
|
|
1431
1443
|
thumbnailScores = await requestOpenAIModeration(
|
|
1432
1444
|
thumbnailUrls,
|
|
1433
|
-
apiKey,
|
|
1434
1445
|
model || "omni-moderation-latest",
|
|
1435
1446
|
maxConcurrent,
|
|
1436
1447
|
imageSubmissionMode,
|
|
1437
1448
|
imageDownloadOptions
|
|
1438
1449
|
);
|
|
1439
1450
|
} else if (provider === "hive") {
|
|
1440
|
-
const hiveApiKey = options.hiveApiKey || env_default.HIVE_API_KEY;
|
|
1441
|
-
if (!hiveApiKey) {
|
|
1442
|
-
throw new Error("Hive API key is required for moderation. Set HIVE_API_KEY or pass hiveApiKey.");
|
|
1443
|
-
}
|
|
1444
1451
|
thumbnailScores = await requestHiveModeration(
|
|
1445
1452
|
thumbnailUrls,
|
|
1446
|
-
hiveApiKey,
|
|
1447
1453
|
maxConcurrent,
|
|
1448
1454
|
imageSubmissionMode,
|
|
1449
1455
|
imageDownloadOptions
|
|
@@ -1476,9 +1482,10 @@ var summarySchema = z4.object({
|
|
|
1476
1482
|
title: z4.string(),
|
|
1477
1483
|
description: z4.string()
|
|
1478
1484
|
});
|
|
1485
|
+
var VALID_TONES = ["neutral", "playful", "professional"];
|
|
1479
1486
|
var TONE_INSTRUCTIONS = {
|
|
1480
|
-
|
|
1481
|
-
|
|
1487
|
+
neutral: "Provide a clear, straightforward analysis.",
|
|
1488
|
+
playful: "Channel your inner diva! Answer with maximum sass, wit, and playful attitude. Don't hold back - be cheeky, clever, and delightfully snarky. Make it pop!",
|
|
1482
1489
|
professional: "Provide a professional, executive-level analysis suitable for business reporting."
|
|
1483
1490
|
};
|
|
1484
1491
|
var summarizationPromptBuilder = createPromptBuilder({
|
|
@@ -1594,13 +1601,9 @@ function buildUserPrompt2({
|
|
|
1594
1601
|
}
|
|
1595
1602
|
return summarizationPromptBuilder.buildWithContext(promptOverrides, contextSections);
|
|
1596
1603
|
}
|
|
1597
|
-
async function analyzeStoryboard2(imageDataUrl,
|
|
1604
|
+
async function analyzeStoryboard2(imageDataUrl, provider, modelId, userPrompt, systemPrompt) {
|
|
1598
1605
|
"use step";
|
|
1599
|
-
const model = createLanguageModelFromConfig(
|
|
1600
|
-
workflowConfig.provider,
|
|
1601
|
-
workflowConfig.modelId,
|
|
1602
|
-
workflowConfig.credentials
|
|
1603
|
-
);
|
|
1606
|
+
const model = createLanguageModelFromConfig(provider, modelId);
|
|
1604
1607
|
const response = await generateObject3({
|
|
1605
1608
|
model,
|
|
1606
1609
|
schema: summarySchema,
|
|
@@ -1657,7 +1660,7 @@ async function getSummaryAndTags(assetId, options) {
|
|
|
1657
1660
|
const {
|
|
1658
1661
|
provider = "openai",
|
|
1659
1662
|
model,
|
|
1660
|
-
tone = "
|
|
1663
|
+
tone = "neutral",
|
|
1661
1664
|
includeTranscript = true,
|
|
1662
1665
|
cleanTranscript = true,
|
|
1663
1666
|
imageSubmissionMode = "url",
|
|
@@ -1665,12 +1668,17 @@ async function getSummaryAndTags(assetId, options) {
|
|
|
1665
1668
|
abortSignal: _abortSignal,
|
|
1666
1669
|
promptOverrides
|
|
1667
1670
|
} = options ?? {};
|
|
1671
|
+
if (!VALID_TONES.includes(tone)) {
|
|
1672
|
+
throw new Error(
|
|
1673
|
+
`Invalid tone "${tone}". Valid tones are: ${VALID_TONES.join(", ")}`
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1668
1676
|
const config = await createWorkflowConfig(
|
|
1669
1677
|
{ ...options, model },
|
|
1670
1678
|
provider
|
|
1671
1679
|
);
|
|
1672
|
-
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(
|
|
1673
|
-
const signingContext =
|
|
1680
|
+
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId);
|
|
1681
|
+
const signingContext = getMuxSigningContextFromEnv();
|
|
1674
1682
|
if (policy === "signed" && !signingContext) {
|
|
1675
1683
|
throw new Error(
|
|
1676
1684
|
"Signed playback ID requires signing credentials. Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables."
|
|
@@ -1678,7 +1686,7 @@ async function getSummaryAndTags(assetId, options) {
|
|
|
1678
1686
|
}
|
|
1679
1687
|
const transcriptText = includeTranscript ? (await fetchTranscriptForAsset(assetData, playbackId, {
|
|
1680
1688
|
cleanTranscript,
|
|
1681
|
-
|
|
1689
|
+
shouldSign: policy === "signed"
|
|
1682
1690
|
})).transcriptText : "";
|
|
1683
1691
|
const userPrompt = buildUserPrompt2({
|
|
1684
1692
|
tone,
|
|
@@ -1686,19 +1694,20 @@ async function getSummaryAndTags(assetId, options) {
|
|
|
1686
1694
|
isCleanTranscript: cleanTranscript,
|
|
1687
1695
|
promptOverrides
|
|
1688
1696
|
});
|
|
1689
|
-
const imageUrl = await getStoryboardUrl(playbackId, 640, policy === "signed"
|
|
1697
|
+
const imageUrl = await getStoryboardUrl(playbackId, 640, policy === "signed");
|
|
1690
1698
|
let analysisResponse;
|
|
1691
1699
|
try {
|
|
1692
1700
|
if (imageSubmissionMode === "base64") {
|
|
1693
1701
|
const downloadResult = await downloadImageAsBase64(imageUrl, imageDownloadOptions);
|
|
1694
1702
|
analysisResponse = await analyzeStoryboard2(
|
|
1695
1703
|
downloadResult.base64Data,
|
|
1696
|
-
config,
|
|
1704
|
+
config.provider,
|
|
1705
|
+
config.modelId,
|
|
1697
1706
|
userPrompt,
|
|
1698
1707
|
SYSTEM_PROMPT3
|
|
1699
1708
|
);
|
|
1700
1709
|
} else {
|
|
1701
|
-
analysisResponse = await withRetry(() => analyzeStoryboard2(imageUrl, config, userPrompt, SYSTEM_PROMPT3));
|
|
1710
|
+
analysisResponse = await withRetry(() => analyzeStoryboard2(imageUrl, config.provider, config.modelId, userPrompt, SYSTEM_PROMPT3));
|
|
1702
1711
|
}
|
|
1703
1712
|
} catch (error) {
|
|
1704
1713
|
throw new Error(
|
|
@@ -1903,11 +1912,12 @@ function getReadyAudioStaticRendition(asset) {
|
|
|
1903
1912
|
);
|
|
1904
1913
|
}
|
|
1905
1914
|
var hasReadyAudioStaticRendition = (asset) => Boolean(getReadyAudioStaticRendition(asset));
|
|
1906
|
-
async function requestStaticRenditionCreation(
|
|
1915
|
+
async function requestStaticRenditionCreation(assetId) {
|
|
1907
1916
|
"use step";
|
|
1917
|
+
const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();
|
|
1908
1918
|
const mux = new Mux3({
|
|
1909
|
-
tokenId:
|
|
1910
|
-
tokenSecret:
|
|
1919
|
+
tokenId: muxTokenId,
|
|
1920
|
+
tokenSecret: muxTokenSecret
|
|
1911
1921
|
});
|
|
1912
1922
|
try {
|
|
1913
1923
|
await mux.video.assets.createStaticRendition(assetId, {
|
|
@@ -1926,13 +1936,13 @@ async function requestStaticRenditionCreation(credentials, assetId) {
|
|
|
1926
1936
|
}
|
|
1927
1937
|
async function waitForAudioStaticRendition({
|
|
1928
1938
|
assetId,
|
|
1929
|
-
credentials,
|
|
1930
1939
|
initialAsset
|
|
1931
1940
|
}) {
|
|
1932
1941
|
"use step";
|
|
1942
|
+
const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();
|
|
1933
1943
|
const mux = new Mux3({
|
|
1934
|
-
tokenId:
|
|
1935
|
-
tokenSecret:
|
|
1944
|
+
tokenId: muxTokenId,
|
|
1945
|
+
tokenSecret: muxTokenSecret
|
|
1936
1946
|
});
|
|
1937
1947
|
let currentAsset = initialAsset;
|
|
1938
1948
|
if (hasReadyAudioStaticRendition(currentAsset)) {
|
|
@@ -1940,9 +1950,9 @@ async function waitForAudioStaticRendition({
|
|
|
1940
1950
|
}
|
|
1941
1951
|
const status = currentAsset.static_renditions?.status ?? "not_requested";
|
|
1942
1952
|
if (status === "not_requested" || status === void 0) {
|
|
1943
|
-
await requestStaticRenditionCreation(
|
|
1953
|
+
await requestStaticRenditionCreation(assetId);
|
|
1944
1954
|
} else if (status === "errored") {
|
|
1945
|
-
await requestStaticRenditionCreation(
|
|
1955
|
+
await requestStaticRenditionCreation(assetId);
|
|
1946
1956
|
} else {
|
|
1947
1957
|
console.warn(`\u2139\uFE0F Static rendition already ${status}. Waiting for it to finish...`);
|
|
1948
1958
|
}
|
|
@@ -1978,10 +1988,10 @@ async function createElevenLabsDubbingJob({
|
|
|
1978
1988
|
audioBuffer,
|
|
1979
1989
|
assetId,
|
|
1980
1990
|
elevenLabsLangCode,
|
|
1981
|
-
elevenLabsApiKey,
|
|
1982
1991
|
numSpeakers
|
|
1983
1992
|
}) {
|
|
1984
1993
|
"use step";
|
|
1994
|
+
const elevenLabsApiKey = getApiKeyFromEnv("elevenlabs");
|
|
1985
1995
|
const audioBlob = new Blob([audioBuffer], { type: "audio/mp4" });
|
|
1986
1996
|
const formData = new FormData();
|
|
1987
1997
|
formData.append("file", audioBlob);
|
|
@@ -2002,10 +2012,10 @@ async function createElevenLabsDubbingJob({
|
|
|
2002
2012
|
return dubbingData.dubbing_id;
|
|
2003
2013
|
}
|
|
2004
2014
|
async function checkElevenLabsDubbingStatus({
|
|
2005
|
-
dubbingId
|
|
2006
|
-
elevenLabsApiKey
|
|
2015
|
+
dubbingId
|
|
2007
2016
|
}) {
|
|
2008
2017
|
"use step";
|
|
2018
|
+
const elevenLabsApiKey = getApiKeyFromEnv("elevenlabs");
|
|
2009
2019
|
const statusResponse = await fetch(`https://api.elevenlabs.io/v1/dubbing/${dubbingId}`, {
|
|
2010
2020
|
headers: {
|
|
2011
2021
|
"xi-api-key": elevenLabsApiKey
|
|
@@ -2022,10 +2032,10 @@ async function checkElevenLabsDubbingStatus({
|
|
|
2022
2032
|
}
|
|
2023
2033
|
async function downloadDubbedAudioFromElevenLabs({
|
|
2024
2034
|
dubbingId,
|
|
2025
|
-
languageCode
|
|
2026
|
-
elevenLabsApiKey
|
|
2035
|
+
languageCode
|
|
2027
2036
|
}) {
|
|
2028
2037
|
"use step";
|
|
2038
|
+
const elevenLabsApiKey = getApiKeyFromEnv("elevenlabs");
|
|
2029
2039
|
const audioUrl = `https://api.elevenlabs.io/v1/dubbing/${dubbingId}/audio/${languageCode}`;
|
|
2030
2040
|
const audioResponse = await fetch(audioUrl, {
|
|
2031
2041
|
headers: {
|
|
@@ -2043,14 +2053,14 @@ async function uploadDubbedAudioToS3({
|
|
|
2043
2053
|
toLanguageCode,
|
|
2044
2054
|
s3Endpoint,
|
|
2045
2055
|
s3Region,
|
|
2046
|
-
s3Bucket
|
|
2047
|
-
s3AccessKeyId,
|
|
2048
|
-
s3SecretAccessKey
|
|
2056
|
+
s3Bucket
|
|
2049
2057
|
}) {
|
|
2050
2058
|
"use step";
|
|
2051
2059
|
const { S3Client, GetObjectCommand } = await import("@aws-sdk/client-s3");
|
|
2052
2060
|
const { Upload } = await import("@aws-sdk/lib-storage");
|
|
2053
2061
|
const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
|
|
2062
|
+
const s3AccessKeyId = env_default.S3_ACCESS_KEY_ID;
|
|
2063
|
+
const s3SecretAccessKey = env_default.S3_SECRET_ACCESS_KEY;
|
|
2054
2064
|
const s3Client = new S3Client({
|
|
2055
2065
|
region: s3Region,
|
|
2056
2066
|
endpoint: s3Endpoint,
|
|
@@ -2083,11 +2093,12 @@ async function uploadDubbedAudioToS3({
|
|
|
2083
2093
|
console.warn(`\u{1F517} Generated presigned URL (expires in 1 hour)`);
|
|
2084
2094
|
return presignedUrl;
|
|
2085
2095
|
}
|
|
2086
|
-
async function createAudioTrackOnMux(
|
|
2096
|
+
async function createAudioTrackOnMux(assetId, languageCode, presignedUrl) {
|
|
2087
2097
|
"use step";
|
|
2098
|
+
const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();
|
|
2088
2099
|
const mux = new Mux3({
|
|
2089
|
-
tokenId:
|
|
2090
|
-
tokenSecret:
|
|
2100
|
+
tokenId: muxTokenId,
|
|
2101
|
+
tokenSecret: muxTokenSecret
|
|
2091
2102
|
});
|
|
2092
2103
|
const languageName = new Intl.DisplayNames(["en"], { type: "language" }).of(languageCode) || languageCode.toUpperCase();
|
|
2093
2104
|
const trackName = `${languageName} (auto-dubbed)`;
|
|
@@ -2114,21 +2125,20 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
2114
2125
|
if (provider !== "elevenlabs") {
|
|
2115
2126
|
throw new Error("Only ElevenLabs provider is currently supported for audio translation");
|
|
2116
2127
|
}
|
|
2117
|
-
const credentials = await validateCredentials(options);
|
|
2118
2128
|
const elevenLabsKey = elevenLabsApiKey ?? env_default.ELEVENLABS_API_KEY;
|
|
2119
2129
|
const s3Endpoint = options.s3Endpoint ?? env_default.S3_ENDPOINT;
|
|
2120
2130
|
const s3Region = options.s3Region ?? env_default.S3_REGION ?? "auto";
|
|
2121
2131
|
const s3Bucket = options.s3Bucket ?? env_default.S3_BUCKET;
|
|
2122
|
-
const s3AccessKeyId =
|
|
2123
|
-
const s3SecretAccessKey =
|
|
2132
|
+
const s3AccessKeyId = env_default.S3_ACCESS_KEY_ID;
|
|
2133
|
+
const s3SecretAccessKey = env_default.S3_SECRET_ACCESS_KEY;
|
|
2124
2134
|
if (!elevenLabsKey) {
|
|
2125
2135
|
throw new Error("ElevenLabs API key is required. Provide elevenLabsApiKey in options or set ELEVENLABS_API_KEY environment variable.");
|
|
2126
2136
|
}
|
|
2127
2137
|
if (uploadToMux && (!s3Endpoint || !s3Bucket || !s3AccessKeyId || !s3SecretAccessKey)) {
|
|
2128
2138
|
throw new Error("S3 configuration is required for uploading to Mux. Provide s3Endpoint, s3Bucket, s3AccessKeyId, and s3SecretAccessKey in options or set S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, and S3_SECRET_ACCESS_KEY environment variables.");
|
|
2129
2139
|
}
|
|
2130
|
-
const { asset: initialAsset, playbackId, policy } = await getPlaybackIdForAsset(
|
|
2131
|
-
const signingContext =
|
|
2140
|
+
const { asset: initialAsset, playbackId, policy } = await getPlaybackIdForAsset(assetId);
|
|
2141
|
+
const signingContext = getMuxSigningContextFromEnv();
|
|
2132
2142
|
if (policy === "signed" && !signingContext) {
|
|
2133
2143
|
throw new Error(
|
|
2134
2144
|
"Signed playback ID requires signing credentials. Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables."
|
|
@@ -2139,7 +2149,6 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
2139
2149
|
console.warn("\u274C No ready audio static rendition found. Requesting one now...");
|
|
2140
2150
|
currentAsset = await waitForAudioStaticRendition({
|
|
2141
2151
|
assetId,
|
|
2142
|
-
credentials,
|
|
2143
2152
|
initialAsset: currentAsset
|
|
2144
2153
|
});
|
|
2145
2154
|
}
|
|
@@ -2169,7 +2178,6 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
2169
2178
|
audioBuffer,
|
|
2170
2179
|
assetId,
|
|
2171
2180
|
elevenLabsLangCode,
|
|
2172
|
-
elevenLabsApiKey: elevenLabsKey,
|
|
2173
2181
|
numSpeakers
|
|
2174
2182
|
});
|
|
2175
2183
|
console.warn(`\u2705 Dubbing job created with ID: ${dubbingId}`);
|
|
@@ -2186,8 +2194,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
2186
2194
|
pollAttempts++;
|
|
2187
2195
|
try {
|
|
2188
2196
|
const statusResult = await checkElevenLabsDubbingStatus({
|
|
2189
|
-
dubbingId
|
|
2190
|
-
elevenLabsApiKey: elevenLabsKey
|
|
2197
|
+
dubbingId
|
|
2191
2198
|
});
|
|
2192
2199
|
dubbingStatus = statusResult.status;
|
|
2193
2200
|
targetLanguages = statusResult.targetLanguages;
|
|
@@ -2230,8 +2237,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
2230
2237
|
}
|
|
2231
2238
|
dubbedAudioBuffer = await downloadDubbedAudioFromElevenLabs({
|
|
2232
2239
|
dubbingId,
|
|
2233
|
-
languageCode: downloadLangCode
|
|
2234
|
-
elevenLabsApiKey: elevenLabsKey
|
|
2240
|
+
languageCode: downloadLangCode
|
|
2235
2241
|
});
|
|
2236
2242
|
console.warn("\u2705 Dubbed audio downloaded successfully!");
|
|
2237
2243
|
} catch (error) {
|
|
@@ -2246,9 +2252,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
2246
2252
|
toLanguageCode,
|
|
2247
2253
|
s3Endpoint,
|
|
2248
2254
|
s3Region,
|
|
2249
|
-
s3Bucket
|
|
2250
|
-
s3AccessKeyId,
|
|
2251
|
-
s3SecretAccessKey
|
|
2255
|
+
s3Bucket
|
|
2252
2256
|
});
|
|
2253
2257
|
} catch (error) {
|
|
2254
2258
|
throw new Error(`Failed to upload audio to S3: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -2257,7 +2261,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
2257
2261
|
let uploadedTrackId;
|
|
2258
2262
|
const muxLangCode = toISO639_1(toLanguageCode);
|
|
2259
2263
|
try {
|
|
2260
|
-
uploadedTrackId = await createAudioTrackOnMux(
|
|
2264
|
+
uploadedTrackId = await createAudioTrackOnMux(assetId, muxLangCode, presignedUrl);
|
|
2261
2265
|
const languageName = new Intl.DisplayNames(["en"], { type: "language" }).of(muxLangCode) || muxLangCode.toUpperCase();
|
|
2262
2266
|
const trackName = `${languageName} (auto-dubbed)`;
|
|
2263
2267
|
console.warn(`\u2705 Track added to Mux asset with ID: ${uploadedTrackId}`);
|
|
@@ -2299,15 +2303,10 @@ async function translateVttWithAI({
|
|
|
2299
2303
|
toLanguageCode,
|
|
2300
2304
|
provider,
|
|
2301
2305
|
modelId,
|
|
2302
|
-
credentials,
|
|
2303
2306
|
abortSignal
|
|
2304
2307
|
}) {
|
|
2305
2308
|
"use step";
|
|
2306
|
-
const languageModel = createLanguageModelFromConfig(
|
|
2307
|
-
provider,
|
|
2308
|
-
modelId,
|
|
2309
|
-
credentials
|
|
2310
|
-
);
|
|
2309
|
+
const languageModel = createLanguageModelFromConfig(provider, modelId);
|
|
2311
2310
|
const response = await generateObject4({
|
|
2312
2311
|
model: languageModel,
|
|
2313
2312
|
schema: translationSchema,
|
|
@@ -2339,14 +2338,14 @@ async function uploadVttToS3({
|
|
|
2339
2338
|
toLanguageCode,
|
|
2340
2339
|
s3Endpoint,
|
|
2341
2340
|
s3Region,
|
|
2342
|
-
s3Bucket
|
|
2343
|
-
s3AccessKeyId,
|
|
2344
|
-
s3SecretAccessKey
|
|
2341
|
+
s3Bucket
|
|
2345
2342
|
}) {
|
|
2346
2343
|
"use step";
|
|
2347
2344
|
const { S3Client, GetObjectCommand } = await import("@aws-sdk/client-s3");
|
|
2348
2345
|
const { Upload } = await import("@aws-sdk/lib-storage");
|
|
2349
2346
|
const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
|
|
2347
|
+
const s3AccessKeyId = env_default.S3_ACCESS_KEY_ID;
|
|
2348
|
+
const s3SecretAccessKey = env_default.S3_SECRET_ACCESS_KEY;
|
|
2350
2349
|
const s3Client = new S3Client({
|
|
2351
2350
|
region: s3Region,
|
|
2352
2351
|
endpoint: s3Endpoint,
|
|
@@ -2377,11 +2376,12 @@ async function uploadVttToS3({
|
|
|
2377
2376
|
});
|
|
2378
2377
|
return presignedUrl;
|
|
2379
2378
|
}
|
|
2380
|
-
async function createTextTrackOnMux(
|
|
2379
|
+
async function createTextTrackOnMux(assetId, languageCode, trackName, presignedUrl) {
|
|
2381
2380
|
"use step";
|
|
2381
|
+
const { muxTokenId, muxTokenSecret } = getMuxCredentialsFromEnv();
|
|
2382
2382
|
const mux = new Mux4({
|
|
2383
|
-
tokenId:
|
|
2384
|
-
tokenSecret:
|
|
2383
|
+
tokenId: muxTokenId,
|
|
2384
|
+
tokenSecret: muxTokenSecret
|
|
2385
2385
|
});
|
|
2386
2386
|
const trackResponse = await mux.video.assets.createTrack(assetId, {
|
|
2387
2387
|
type: "text",
|
|
@@ -2403,15 +2403,13 @@ async function translateCaptions(assetId, fromLanguageCode, toLanguageCode, opti
|
|
|
2403
2403
|
s3Endpoint: providedS3Endpoint,
|
|
2404
2404
|
s3Region: providedS3Region,
|
|
2405
2405
|
s3Bucket: providedS3Bucket,
|
|
2406
|
-
s3AccessKeyId: providedS3AccessKeyId,
|
|
2407
|
-
s3SecretAccessKey: providedS3SecretAccessKey,
|
|
2408
2406
|
uploadToMux: uploadToMuxOption
|
|
2409
2407
|
} = options;
|
|
2410
2408
|
const s3Endpoint = providedS3Endpoint ?? env_default.S3_ENDPOINT;
|
|
2411
2409
|
const s3Region = providedS3Region ?? env_default.S3_REGION ?? "auto";
|
|
2412
2410
|
const s3Bucket = providedS3Bucket ?? env_default.S3_BUCKET;
|
|
2413
|
-
const s3AccessKeyId =
|
|
2414
|
-
const s3SecretAccessKey =
|
|
2411
|
+
const s3AccessKeyId = env_default.S3_ACCESS_KEY_ID;
|
|
2412
|
+
const s3SecretAccessKey = env_default.S3_SECRET_ACCESS_KEY;
|
|
2415
2413
|
const uploadToMux = uploadToMuxOption !== false;
|
|
2416
2414
|
const config = await createWorkflowConfig(
|
|
2417
2415
|
{ ...options, model },
|
|
@@ -2420,8 +2418,8 @@ async function translateCaptions(assetId, fromLanguageCode, toLanguageCode, opti
|
|
|
2420
2418
|
if (uploadToMux && (!s3Endpoint || !s3Bucket || !s3AccessKeyId || !s3SecretAccessKey)) {
|
|
2421
2419
|
throw new Error("S3 configuration is required for uploading to Mux. Provide s3Endpoint, s3Bucket, s3AccessKeyId, and s3SecretAccessKey in options or set S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, and S3_SECRET_ACCESS_KEY environment variables.");
|
|
2422
2420
|
}
|
|
2423
|
-
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(
|
|
2424
|
-
const signingContext =
|
|
2421
|
+
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId);
|
|
2422
|
+
const signingContext = getMuxSigningContextFromEnv();
|
|
2425
2423
|
if (policy === "signed" && !signingContext) {
|
|
2426
2424
|
throw new Error(
|
|
2427
2425
|
"Signed playback ID requires signing credentials. Provide muxSigningKey and muxPrivateKey in options or set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables."
|
|
@@ -2455,7 +2453,6 @@ async function translateCaptions(assetId, fromLanguageCode, toLanguageCode, opti
|
|
|
2455
2453
|
toLanguageCode,
|
|
2456
2454
|
provider: config.provider,
|
|
2457
2455
|
modelId: config.modelId,
|
|
2458
|
-
credentials: config.credentials,
|
|
2459
2456
|
abortSignal: options.abortSignal
|
|
2460
2457
|
});
|
|
2461
2458
|
translatedVtt = result.translatedVtt;
|
|
@@ -2486,9 +2483,7 @@ async function translateCaptions(assetId, fromLanguageCode, toLanguageCode, opti
|
|
|
2486
2483
|
toLanguageCode,
|
|
2487
2484
|
s3Endpoint,
|
|
2488
2485
|
s3Region,
|
|
2489
|
-
s3Bucket
|
|
2490
|
-
s3AccessKeyId,
|
|
2491
|
-
s3SecretAccessKey
|
|
2486
|
+
s3Bucket
|
|
2492
2487
|
});
|
|
2493
2488
|
} catch (error) {
|
|
2494
2489
|
throw new Error(`Failed to upload VTT to S3: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -2497,7 +2492,7 @@ async function translateCaptions(assetId, fromLanguageCode, toLanguageCode, opti
|
|
|
2497
2492
|
try {
|
|
2498
2493
|
const languageName = getLanguageName(toLanguageCode);
|
|
2499
2494
|
const trackName = `${languageName} (auto-translated)`;
|
|
2500
|
-
uploadedTrackId = await createTextTrackOnMux(
|
|
2495
|
+
uploadedTrackId = await createTextTrackOnMux(assetId, toLanguageCode, trackName, presignedUrl);
|
|
2501
2496
|
} catch (error) {
|
|
2502
2497
|
console.warn(`Failed to add track to Mux asset: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2503
2498
|
}
|