@mux/ai 0.39.0 → 0.41.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.
- package/dist/{index-Wjbq-_Z0.d.ts → index-C3PW73qH.d.ts} +206 -12
- package/dist/index.d.ts +2 -2
- package/dist/index.js +435 -122
- package/dist/index.js.map +1 -1
- package/dist/workflows/index.d.ts +1 -1
- package/dist/workflows/index.js +397 -97
- package/dist/workflows/index.js.map +1 -1
- package/package.json +17 -3
package/dist/workflows/index.js
CHANGED
|
@@ -2928,11 +2928,8 @@ async function analyzeQuestions({
|
|
|
2928
2928
|
maxRetries: 0,
|
|
2929
2929
|
output: Output.object({ schema: responseSchema }),
|
|
2930
2930
|
experimental_telemetry: { isEnabled: true },
|
|
2931
|
+
system: systemPrompt,
|
|
2931
2932
|
messages: [
|
|
2932
|
-
{
|
|
2933
|
-
role: "system",
|
|
2934
|
-
content: systemPrompt
|
|
2935
|
-
},
|
|
2936
2933
|
{
|
|
2937
2934
|
role: "user",
|
|
2938
2935
|
content: imageDataUrl ? [
|
|
@@ -3346,11 +3343,8 @@ async function analyzeStoryboard({
|
|
|
3346
3343
|
maxRetries: 0,
|
|
3347
3344
|
output: Output2.object({ schema: burnedInCaptionsSchema }),
|
|
3348
3345
|
experimental_telemetry: { isEnabled: true },
|
|
3346
|
+
system: systemPrompt,
|
|
3349
3347
|
messages: [
|
|
3350
|
-
{
|
|
3351
|
-
role: "system",
|
|
3352
|
-
content: systemPrompt
|
|
3353
|
-
},
|
|
3354
3348
|
{
|
|
3355
3349
|
role: "user",
|
|
3356
3350
|
content: [
|
|
@@ -3495,11 +3489,8 @@ async function generateChaptersWithAI({
|
|
|
3495
3489
|
model,
|
|
3496
3490
|
maxRetries: 0,
|
|
3497
3491
|
output: Output3.object({ schema: chaptersSchema }),
|
|
3492
|
+
system: systemPrompt,
|
|
3498
3493
|
messages: [
|
|
3499
|
-
{
|
|
3500
|
-
role: "system",
|
|
3501
|
-
content: systemPrompt
|
|
3502
|
-
},
|
|
3503
3494
|
{
|
|
3504
3495
|
role: "user",
|
|
3505
3496
|
content: userPrompt
|
|
@@ -3788,6 +3779,129 @@ import { generateText as generateText4, Output as Output4 } from "ai";
|
|
|
3788
3779
|
import { z as z6 } from "zod";
|
|
3789
3780
|
|
|
3790
3781
|
// src/lib/mux-tracks.ts
|
|
3782
|
+
var MUX_TRACK_PASSTHROUGH_MAX_CHARS = 255;
|
|
3783
|
+
function normalizeTrackLanguageCode(code) {
|
|
3784
|
+
if (!code) {
|
|
3785
|
+
return void 0;
|
|
3786
|
+
}
|
|
3787
|
+
const primarySubtag = code.trim().toLowerCase().split(/[-_]/)[0];
|
|
3788
|
+
if (!primarySubtag || primarySubtag === "auto" || isUndeterminedLanguageCode(primarySubtag)) {
|
|
3789
|
+
return void 0;
|
|
3790
|
+
}
|
|
3791
|
+
return toISO639_1(primarySubtag);
|
|
3792
|
+
}
|
|
3793
|
+
function normalizeTrackName(name) {
|
|
3794
|
+
const normalized = name?.trim().toLowerCase();
|
|
3795
|
+
return normalized || void 0;
|
|
3796
|
+
}
|
|
3797
|
+
function isMuxGeneratedTextTrack(track) {
|
|
3798
|
+
return track.text_source === "generated_vod" || track.text_source === "generated_live" || track.text_source === "generated_live_final";
|
|
3799
|
+
}
|
|
3800
|
+
function targetType(target) {
|
|
3801
|
+
return target.type ?? "text";
|
|
3802
|
+
}
|
|
3803
|
+
function listTracks(asset, type) {
|
|
3804
|
+
return (asset.tracks ?? []).filter((track) => track.type === type && track.status !== "deleted" && !!track.id);
|
|
3805
|
+
}
|
|
3806
|
+
function summarizeTextTrack(track) {
|
|
3807
|
+
return {
|
|
3808
|
+
id: track.id,
|
|
3809
|
+
type: track.type === "audio" ? "audio" : "text",
|
|
3810
|
+
name: track.name,
|
|
3811
|
+
languageCode: track.language_code,
|
|
3812
|
+
status: track.status,
|
|
3813
|
+
textSource: track.text_source,
|
|
3814
|
+
passthrough: track.passthrough,
|
|
3815
|
+
...track.type === "audio" ? { primary: track.primary === true } : {}
|
|
3816
|
+
};
|
|
3817
|
+
}
|
|
3818
|
+
function findSameLanguageTextTracks(asset, target) {
|
|
3819
|
+
const targetLanguage = normalizeTrackLanguageCode(target.languageCode);
|
|
3820
|
+
if (!targetLanguage) {
|
|
3821
|
+
return [];
|
|
3822
|
+
}
|
|
3823
|
+
const type = targetType(target);
|
|
3824
|
+
return listTracks(asset, type).filter(
|
|
3825
|
+
(track) => (type === "audio" || track.text_type === "subtitles") && normalizeTrackLanguageCode(track.language_code) === targetLanguage
|
|
3826
|
+
);
|
|
3827
|
+
}
|
|
3828
|
+
function findNameCollisionTextTracks(asset, target) {
|
|
3829
|
+
const targetName = normalizeTrackName(target.name);
|
|
3830
|
+
if (!targetName) {
|
|
3831
|
+
return [];
|
|
3832
|
+
}
|
|
3833
|
+
return listTracks(asset, targetType(target)).filter((track) => normalizeTrackName(track.name) === targetName);
|
|
3834
|
+
}
|
|
3835
|
+
function describeTracks(tracks) {
|
|
3836
|
+
return tracks.map((track) => `${track.name ?? "(unnamed)"} [${track.language_code ?? "?"}, ${track.type === "audio" ? track.primary ? "primary audio" : "audio" : track.text_source ?? "unknown source"}, ${track.status ?? "ready"}]`).join("; ");
|
|
3837
|
+
}
|
|
3838
|
+
function planTextTrackReplacement(asset, target, policy, options = {}) {
|
|
3839
|
+
const label = targetType(target) === "audio" ? "Audio" : "Text";
|
|
3840
|
+
const keep = new Set(options.keepTrackIds ?? []);
|
|
3841
|
+
const conflicts = /* @__PURE__ */ new Map();
|
|
3842
|
+
for (const track of [...findSameLanguageTextTracks(asset, target), ...findNameCollisionTextTracks(asset, target)]) {
|
|
3843
|
+
if (!keep.has(track.id)) {
|
|
3844
|
+
conflicts.set(track.id, track);
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
const conflicting = [...conflicts.values()];
|
|
3848
|
+
if (conflicting.length === 0) {
|
|
3849
|
+
return { kind: "clear" };
|
|
3850
|
+
}
|
|
3851
|
+
if (policy === "fail") {
|
|
3852
|
+
return {
|
|
3853
|
+
kind: "blocked",
|
|
3854
|
+
reason: `${label} track(s) already exist for language '${target.languageCode}' or name '${target.name}': ${describeTracks(conflicting)}. Set replaceExistingTracks to replace them.`,
|
|
3855
|
+
tracks: conflicting.map(summarizeTextTrack)
|
|
3856
|
+
};
|
|
3857
|
+
}
|
|
3858
|
+
const primary = conflicting.filter((track) => track.type === "audio" && track.primary === true);
|
|
3859
|
+
if (primary.length > 0) {
|
|
3860
|
+
return {
|
|
3861
|
+
kind: "blocked",
|
|
3862
|
+
reason: `The asset's primary audio track is in the way for language '${target.languageCode}' or name '${target.name}': ${describeTracks(primary)}. Primary audio cannot be replaced; choose a different trackName.`,
|
|
3863
|
+
tracks: primary.map(summarizeTextTrack)
|
|
3864
|
+
};
|
|
3865
|
+
}
|
|
3866
|
+
if (policy === "replace_generated") {
|
|
3867
|
+
const notGenerated = conflicting.filter((track) => track.type !== "text" || !isMuxGeneratedTextTrack(track));
|
|
3868
|
+
if (notGenerated.length > 0) {
|
|
3869
|
+
return {
|
|
3870
|
+
kind: "blocked",
|
|
3871
|
+
reason: `${label} track(s) that are not Mux-generated exist for language '${target.languageCode}' or name '${target.name}': ${describeTracks(notGenerated)}. Use replaceExistingTracks: "replace_all" to replace them.`,
|
|
3872
|
+
tracks: notGenerated.map(summarizeTextTrack)
|
|
3873
|
+
};
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
return { kind: "replace", toDelete: conflicting.map(summarizeTextTrack) };
|
|
3877
|
+
}
|
|
3878
|
+
function isDuplicateTrackNameError(error) {
|
|
3879
|
+
if (typeof error !== "object" || error === null) {
|
|
3880
|
+
return false;
|
|
3881
|
+
}
|
|
3882
|
+
const record = error;
|
|
3883
|
+
if (record.status !== 400) {
|
|
3884
|
+
return false;
|
|
3885
|
+
}
|
|
3886
|
+
const messages = Array.isArray(record.error?.messages) ? record.error.messages : [];
|
|
3887
|
+
const haystack = [...messages, record.message].filter((value) => typeof value === "string").map((value) => value.toLowerCase());
|
|
3888
|
+
return haystack.some((value) => value.includes("is not unique"));
|
|
3889
|
+
}
|
|
3890
|
+
function isNotFoundError(error) {
|
|
3891
|
+
return typeof error === "object" && error !== null && error.status === 404;
|
|
3892
|
+
}
|
|
3893
|
+
function buildMuxAiTrackPassthrough(workflow) {
|
|
3894
|
+
return JSON.stringify({ mux_ai: { workflow } });
|
|
3895
|
+
}
|
|
3896
|
+
function validateTrackPassthrough(passthrough) {
|
|
3897
|
+
if (passthrough !== void 0 && passthrough.length > MUX_TRACK_PASSTHROUGH_MAX_CHARS) {
|
|
3898
|
+
throw new MuxAiError(
|
|
3899
|
+
`Track passthrough must be at most ${MUX_TRACK_PASSTHROUGH_MAX_CHARS} characters (received ${passthrough.length}).`,
|
|
3900
|
+
{ type: "validation_error" }
|
|
3901
|
+
);
|
|
3902
|
+
}
|
|
3903
|
+
return passthrough;
|
|
3904
|
+
}
|
|
3791
3905
|
async function fetchVttFromMux(vttUrl) {
|
|
3792
3906
|
"use step";
|
|
3793
3907
|
const vttResponse = await fetch(vttUrl);
|
|
@@ -3796,7 +3910,7 @@ async function fetchVttFromMux(vttUrl) {
|
|
|
3796
3910
|
}
|
|
3797
3911
|
return vttResponse.text();
|
|
3798
3912
|
}
|
|
3799
|
-
async function createTextTrackOnMux(assetId, languageCode, trackName, presignedUrl, credentials) {
|
|
3913
|
+
async function createTextTrackOnMux(assetId, languageCode, trackName, presignedUrl, credentials, options = {}) {
|
|
3800
3914
|
"use step";
|
|
3801
3915
|
const muxClient = await resolveMuxClient(credentials);
|
|
3802
3916
|
const mux = await muxClient.createClient();
|
|
@@ -3805,13 +3919,92 @@ async function createTextTrackOnMux(assetId, languageCode, trackName, presignedU
|
|
|
3805
3919
|
text_type: "subtitles",
|
|
3806
3920
|
language_code: languageCode,
|
|
3807
3921
|
name: trackName,
|
|
3808
|
-
url: presignedUrl
|
|
3922
|
+
url: presignedUrl,
|
|
3923
|
+
closed_captions: options.closedCaptions,
|
|
3924
|
+
passthrough: options.passthrough
|
|
3809
3925
|
});
|
|
3810
3926
|
if (!trackResponse.id) {
|
|
3811
3927
|
throw new Error("Failed to create text track: no track ID returned from Mux");
|
|
3812
3928
|
}
|
|
3813
3929
|
return trackResponse.id;
|
|
3814
3930
|
}
|
|
3931
|
+
async function replaceAndCreateTrack(input) {
|
|
3932
|
+
"use step";
|
|
3933
|
+
const muxClient = await resolveMuxClient(input.credentials);
|
|
3934
|
+
const mux = await muxClient.createClient();
|
|
3935
|
+
const type = targetType(input.target);
|
|
3936
|
+
const deleted = [];
|
|
3937
|
+
const failed = (error) => ({
|
|
3938
|
+
kind: "create_failed",
|
|
3939
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
3940
|
+
deleted
|
|
3941
|
+
});
|
|
3942
|
+
const clearConflicts = async () => {
|
|
3943
|
+
let asset;
|
|
3944
|
+
try {
|
|
3945
|
+
asset = await mux.video.assets.retrieve(input.assetId);
|
|
3946
|
+
} catch (error) {
|
|
3947
|
+
if (deleted.length === 0) {
|
|
3948
|
+
throw error;
|
|
3949
|
+
}
|
|
3950
|
+
return failed(error);
|
|
3951
|
+
}
|
|
3952
|
+
const plan = planTextTrackReplacement(asset, input.target, input.policy, { keepTrackIds: input.keepTrackIds });
|
|
3953
|
+
if (plan.kind === "blocked") {
|
|
3954
|
+
return { ...plan, deleted };
|
|
3955
|
+
}
|
|
3956
|
+
if (plan.kind === "replace") {
|
|
3957
|
+
for (const track of plan.toDelete) {
|
|
3958
|
+
try {
|
|
3959
|
+
await mux.video.assets.deleteTrack(input.assetId, track.id);
|
|
3960
|
+
deleted.push(track);
|
|
3961
|
+
} catch (error) {
|
|
3962
|
+
if (!isNotFoundError(error)) {
|
|
3963
|
+
return failed(error);
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
return void 0;
|
|
3969
|
+
};
|
|
3970
|
+
const create = async () => {
|
|
3971
|
+
const track = await mux.video.assets.createTrack(input.assetId, {
|
|
3972
|
+
type,
|
|
3973
|
+
...type === "text" ? { text_type: "subtitles", closed_captions: input.closedCaptions } : {},
|
|
3974
|
+
language_code: input.target.languageCode,
|
|
3975
|
+
name: input.target.name.trim(),
|
|
3976
|
+
url: input.presignedUrl,
|
|
3977
|
+
passthrough: input.passthrough
|
|
3978
|
+
});
|
|
3979
|
+
if (!track.id) {
|
|
3980
|
+
throw new Error(`Failed to create ${type} track: no track ID returned from Mux`);
|
|
3981
|
+
}
|
|
3982
|
+
return track.id;
|
|
3983
|
+
};
|
|
3984
|
+
const stopped = await clearConflicts();
|
|
3985
|
+
if (stopped) {
|
|
3986
|
+
return stopped;
|
|
3987
|
+
}
|
|
3988
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
3989
|
+
try {
|
|
3990
|
+
return { kind: "created", trackId: await create(), deleted };
|
|
3991
|
+
} catch (error) {
|
|
3992
|
+
if (isDuplicateTrackNameError(error) && attempt === 0) {
|
|
3993
|
+
const stoppedOnRetry = await clearConflicts();
|
|
3994
|
+
if (stoppedOnRetry) {
|
|
3995
|
+
return stoppedOnRetry;
|
|
3996
|
+
}
|
|
3997
|
+
continue;
|
|
3998
|
+
}
|
|
3999
|
+
return failed(error);
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
return { kind: "create_failed", reason: "Mux rejected the track name as not unique after retrying.", deleted };
|
|
4003
|
+
}
|
|
4004
|
+
async function replaceAndCreateTextTrack(input) {
|
|
4005
|
+
"use step";
|
|
4006
|
+
return replaceAndCreateTrack({ ...input, target: { ...input.target, type: "text" } });
|
|
4007
|
+
}
|
|
3815
4008
|
|
|
3816
4009
|
// src/lib/s3-sigv4.ts
|
|
3817
4010
|
var AWS4_ALGORITHM = "AWS4-HMAC-SHA256";
|
|
@@ -4271,11 +4464,8 @@ async function identifyProfanityWithAI({
|
|
|
4271
4464
|
model,
|
|
4272
4465
|
maxRetries: 0,
|
|
4273
4466
|
output: Output4.object({ schema: profanityDetectionSchema }),
|
|
4467
|
+
system: SYSTEM_PROMPT3,
|
|
4274
4468
|
messages: [
|
|
4275
|
-
{
|
|
4276
|
-
role: "system",
|
|
4277
|
-
content: SYSTEM_PROMPT3
|
|
4278
|
-
},
|
|
4279
4469
|
{
|
|
4280
4470
|
role: "user",
|
|
4281
4471
|
content: `Identify all profane words and phrases in the following subtitle transcript. Return each unique profane word or phrase exactly as it appears in the text.
|
|
@@ -4306,6 +4496,7 @@ async function uploadEditedVttToS3({
|
|
|
4306
4496
|
editedVtt,
|
|
4307
4497
|
assetId,
|
|
4308
4498
|
trackId,
|
|
4499
|
+
variant = "edited",
|
|
4309
4500
|
s3Endpoint,
|
|
4310
4501
|
s3Region,
|
|
4311
4502
|
s3Bucket,
|
|
@@ -4315,7 +4506,7 @@ async function uploadEditedVttToS3({
|
|
|
4315
4506
|
"use step";
|
|
4316
4507
|
const s3AccessKeyId = env_default.S3_ACCESS_KEY_ID;
|
|
4317
4508
|
const s3SecretAccessKey = env_default.S3_SECRET_ACCESS_KEY;
|
|
4318
|
-
const vttKey = `edited/${assetId}/${trackId}
|
|
4509
|
+
const vttKey = `edited/${assetId}/${trackId}-${variant}-${Date.now()}.vtt`;
|
|
4319
4510
|
await putObjectWithStorageAdapter({
|
|
4320
4511
|
accessKeyId: s3AccessKeyId,
|
|
4321
4512
|
secretAccessKey: s3SecretAccessKey,
|
|
@@ -4365,6 +4556,9 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
|
|
|
4365
4556
|
s3Region: providedS3Region,
|
|
4366
4557
|
s3Bucket: providedS3Bucket,
|
|
4367
4558
|
trackNameSuffix,
|
|
4559
|
+
replaceExistingTracks: replaceExistingTracksOption,
|
|
4560
|
+
trackName: providedTrackName,
|
|
4561
|
+
trackPassthrough: providedTrackPassthrough,
|
|
4368
4562
|
storageAdapter,
|
|
4369
4563
|
credentials
|
|
4370
4564
|
} = options;
|
|
@@ -4377,6 +4571,21 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
|
|
|
4377
4571
|
if (autoCensorOption && !provider) {
|
|
4378
4572
|
throw new MuxAiError("provider is required when using autoCensorProfanity.", { type: "validation_error" });
|
|
4379
4573
|
}
|
|
4574
|
+
const legacyTrackNaming = deleteOriginalTrack !== void 0 || trackNameSuffix !== void 0;
|
|
4575
|
+
if (legacyTrackNaming && (replaceExistingTracksOption !== void 0 || providedTrackName !== void 0)) {
|
|
4576
|
+
throw new MuxAiError(
|
|
4577
|
+
"deleteOriginalTrack and trackNameSuffix are deprecated and cannot be combined with replaceExistingTracks or trackName.",
|
|
4578
|
+
{ type: "validation_error" }
|
|
4579
|
+
);
|
|
4580
|
+
}
|
|
4581
|
+
const replaceExistingTracks = replaceExistingTracksOption ?? "replace_all";
|
|
4582
|
+
if (!legacyTrackNaming && replaceExistingTracks === "fail" && !providedTrackName) {
|
|
4583
|
+
throw new MuxAiError(
|
|
4584
|
+
`trackName is required when replaceExistingTracks is "fail": the edited track cannot reuse the source track's name while the source is kept.`,
|
|
4585
|
+
{ type: "validation_error" }
|
|
4586
|
+
);
|
|
4587
|
+
}
|
|
4588
|
+
const trackPassthrough = validateTrackPassthrough(providedTrackPassthrough) ?? buildMuxAiTrackPassthrough("edit-captions");
|
|
4380
4589
|
const deleteOriginal = deleteOriginalTrack !== false;
|
|
4381
4590
|
const uploadToMux = uploadToMuxOption !== false;
|
|
4382
4591
|
const uploadToS3 = uploadToS3Option || uploadToMux;
|
|
@@ -4419,6 +4628,25 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
|
|
|
4419
4628
|
{ type: "validation_error" }
|
|
4420
4629
|
);
|
|
4421
4630
|
}
|
|
4631
|
+
const sourceLanguageCode = sourceTrack.language_code || "en";
|
|
4632
|
+
const sourceName = sourceTrack.name || "Subtitles";
|
|
4633
|
+
const outputTrack = {
|
|
4634
|
+
languageCode: sourceLanguageCode,
|
|
4635
|
+
name: providedTrackName ?? sourceName
|
|
4636
|
+
};
|
|
4637
|
+
const keepTrackIds = replaceExistingTracks === "fail" ? [trackId] : [];
|
|
4638
|
+
if (replaceExistingTracks === "fail" && normalizeTrackName(outputTrack.name) === normalizeTrackName(sourceName)) {
|
|
4639
|
+
throw new MuxAiError(
|
|
4640
|
+
`trackName "${outputTrack.name}" matches the source track's name. Mux track names must be unique; choose a different trackName or use replaceExistingTracks: "replace_all" to replace the source.`,
|
|
4641
|
+
{ type: "validation_error" }
|
|
4642
|
+
);
|
|
4643
|
+
}
|
|
4644
|
+
if (uploadToMux && !legacyTrackNaming) {
|
|
4645
|
+
const plan = planTextTrackReplacement(assetData, outputTrack, replaceExistingTracks, { keepTrackIds });
|
|
4646
|
+
if (plan.kind === "blocked") {
|
|
4647
|
+
throw new MuxAiError(plan.reason, { type: "validation_error" });
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4422
4650
|
const vttUrl = await buildTranscriptUrl(playbackId, trackId, policy === "signed", credentials);
|
|
4423
4651
|
let vttContent;
|
|
4424
4652
|
try {
|
|
@@ -4496,32 +4724,32 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
|
|
|
4496
4724
|
} : void 0;
|
|
4497
4725
|
let presignedUrl;
|
|
4498
4726
|
let uploadedTrackId;
|
|
4727
|
+
let replacedTracks;
|
|
4499
4728
|
if (uploadToS3) {
|
|
4729
|
+
const s3Config = {
|
|
4730
|
+
assetId,
|
|
4731
|
+
trackId,
|
|
4732
|
+
s3Endpoint,
|
|
4733
|
+
s3Region,
|
|
4734
|
+
s3Bucket,
|
|
4735
|
+
storageAdapter,
|
|
4736
|
+
s3SignedUrlExpirySeconds: options.s3SignedUrlExpirySeconds
|
|
4737
|
+
};
|
|
4500
4738
|
try {
|
|
4501
|
-
presignedUrl = await uploadEditedVttToS3({
|
|
4502
|
-
editedVtt,
|
|
4503
|
-
assetId,
|
|
4504
|
-
trackId,
|
|
4505
|
-
s3Endpoint,
|
|
4506
|
-
s3Region,
|
|
4507
|
-
s3Bucket,
|
|
4508
|
-
storageAdapter,
|
|
4509
|
-
s3SignedUrlExpirySeconds: options.s3SignedUrlExpirySeconds
|
|
4510
|
-
});
|
|
4739
|
+
presignedUrl = await uploadEditedVttToS3({ editedVtt, ...s3Config });
|
|
4511
4740
|
} catch (error) {
|
|
4512
4741
|
wrapError(error, "Failed to upload VTT to S3");
|
|
4513
4742
|
}
|
|
4514
|
-
if (uploadToMux) {
|
|
4743
|
+
if (uploadToMux && legacyTrackNaming) {
|
|
4515
4744
|
try {
|
|
4516
|
-
const
|
|
4517
|
-
const suffix = trackNameSuffix ?? "edited";
|
|
4518
|
-
const trackName = `${sourceTrack.name || "Subtitles"} (${suffix})`;
|
|
4745
|
+
const trackName = `${sourceName} (${trackNameSuffix ?? "edited"})`;
|
|
4519
4746
|
uploadedTrackId = await createTextTrackOnMux(
|
|
4520
4747
|
assetId,
|
|
4521
|
-
|
|
4748
|
+
sourceLanguageCode,
|
|
4522
4749
|
trackName,
|
|
4523
4750
|
presignedUrl,
|
|
4524
|
-
credentials
|
|
4751
|
+
credentials,
|
|
4752
|
+
{ closedCaptions: sourceTrack.closed_captions, passthrough: trackPassthrough }
|
|
4525
4753
|
);
|
|
4526
4754
|
} catch (error) {
|
|
4527
4755
|
wrapError(error, "Failed to add edited track to Mux asset");
|
|
@@ -4533,6 +4761,48 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
|
|
|
4533
4761
|
wrapError(error, "Failed to delete original track");
|
|
4534
4762
|
}
|
|
4535
4763
|
}
|
|
4764
|
+
} else if (uploadToMux) {
|
|
4765
|
+
let outcome;
|
|
4766
|
+
try {
|
|
4767
|
+
outcome = await replaceAndCreateTextTrack({
|
|
4768
|
+
assetId,
|
|
4769
|
+
target: outputTrack,
|
|
4770
|
+
policy: replaceExistingTracks,
|
|
4771
|
+
presignedUrl,
|
|
4772
|
+
closedCaptions: sourceTrack.closed_captions,
|
|
4773
|
+
passthrough: trackPassthrough,
|
|
4774
|
+
keepTrackIds,
|
|
4775
|
+
credentials
|
|
4776
|
+
});
|
|
4777
|
+
} catch (error) {
|
|
4778
|
+
wrapError(error, "Failed to add edited track to Mux asset");
|
|
4779
|
+
}
|
|
4780
|
+
if (outcome.kind !== "created") {
|
|
4781
|
+
const sourceWasDeleted = outcome.deleted.some((track) => track.id === trackId);
|
|
4782
|
+
let restoreNote = "";
|
|
4783
|
+
if (sourceWasDeleted) {
|
|
4784
|
+
try {
|
|
4785
|
+
const originalUrl = await uploadEditedVttToS3({ editedVtt: vttContent, variant: "original", ...s3Config });
|
|
4786
|
+
const restoredTrackId = await createTextTrackOnMux(
|
|
4787
|
+
assetId,
|
|
4788
|
+
sourceLanguageCode,
|
|
4789
|
+
sourceName,
|
|
4790
|
+
originalUrl,
|
|
4791
|
+
credentials,
|
|
4792
|
+
{ closedCaptions: sourceTrack.closed_captions, passthrough: sourceTrack.passthrough }
|
|
4793
|
+
);
|
|
4794
|
+
restoreNote = ` The source track was restored as ${restoredTrackId}.`;
|
|
4795
|
+
} catch (restoreError) {
|
|
4796
|
+
restoreNote = ` Restoring the source track also failed: ${restoreError instanceof Error ? restoreError.message : String(restoreError)}.`;
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
if (outcome.kind === "blocked") {
|
|
4800
|
+
throw new MuxAiError(`${outcome.reason}${restoreNote}`, { type: "validation_error" });
|
|
4801
|
+
}
|
|
4802
|
+
throw new Error(`Failed to add edited track to Mux asset: ${outcome.reason}.${restoreNote}`);
|
|
4803
|
+
}
|
|
4804
|
+
uploadedTrackId = outcome.trackId;
|
|
4805
|
+
replacedTracks = outcome.deleted;
|
|
4536
4806
|
}
|
|
4537
4807
|
}
|
|
4538
4808
|
return {
|
|
@@ -4545,6 +4815,7 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
|
|
|
4545
4815
|
replacements: replacementsResult,
|
|
4546
4816
|
speakerReplacements: speakerReplacementsResult,
|
|
4547
4817
|
uploadedTrackId,
|
|
4818
|
+
replacedTracks,
|
|
4548
4819
|
presignedUrl,
|
|
4549
4820
|
usage: usageWithMetadata,
|
|
4550
4821
|
safety: safety.report()
|
|
@@ -5390,11 +5661,8 @@ async function generateInsightsWithAI(provider, modelId, systemPrompt, userPromp
|
|
|
5390
5661
|
maxRetries: 0,
|
|
5391
5662
|
output: Output5.object({ schema: engagementInsightsSchema }),
|
|
5392
5663
|
experimental_telemetry: { isEnabled: true },
|
|
5664
|
+
system: systemPrompt,
|
|
5393
5665
|
messages: [
|
|
5394
|
-
{
|
|
5395
|
-
role: "system",
|
|
5396
|
-
content: systemPrompt
|
|
5397
|
-
},
|
|
5398
5666
|
{
|
|
5399
5667
|
role: "user",
|
|
5400
5668
|
content: [
|
|
@@ -6697,11 +6965,8 @@ async function analyzeStoryboard2(imageDataUrl, provider, modelId, userPrompt, s
|
|
|
6697
6965
|
description: "Structured summary with title, description, and keywords.",
|
|
6698
6966
|
schema
|
|
6699
6967
|
}),
|
|
6968
|
+
system: systemPrompt,
|
|
6700
6969
|
messages: [
|
|
6701
|
-
{
|
|
6702
|
-
role: "system",
|
|
6703
|
-
content: systemPrompt
|
|
6704
|
-
},
|
|
6705
6970
|
{
|
|
6706
6971
|
role: "user",
|
|
6707
6972
|
content: [
|
|
@@ -6745,11 +7010,8 @@ async function analyzeAudioOnly(provider, modelId, userPrompt, systemPrompt, des
|
|
|
6745
7010
|
description: "Structured summary with title, description, and keywords.",
|
|
6746
7011
|
schema
|
|
6747
7012
|
}),
|
|
7013
|
+
system: systemPrompt,
|
|
6748
7014
|
messages: [
|
|
6749
|
-
{
|
|
6750
|
-
role: "system",
|
|
6751
|
-
content: systemPrompt
|
|
6752
|
-
},
|
|
6753
7015
|
{
|
|
6754
7016
|
role: "user",
|
|
6755
7017
|
content: userPrompt
|
|
@@ -7241,23 +7503,6 @@ async function downloadAndUploadDubTranscript({
|
|
|
7241
7503
|
console.warn(`\u2705 Dub transcript uploaded successfully to: ${vttKey}`);
|
|
7242
7504
|
return presignedUrl;
|
|
7243
7505
|
}
|
|
7244
|
-
async function createAudioTrackOnMux(assetId, languageCode, presignedUrl, credentials) {
|
|
7245
|
-
"use step";
|
|
7246
|
-
const muxClient = await resolveMuxClient(credentials);
|
|
7247
|
-
const mux = await muxClient.createClient();
|
|
7248
|
-
const languageName = new Intl.DisplayNames(["en"], { type: "language" }).of(languageCode) || languageCode.toUpperCase();
|
|
7249
|
-
const trackName = `${languageName} (auto-dubbed)`;
|
|
7250
|
-
const trackResponse = await mux.video.assets.createTrack(assetId, {
|
|
7251
|
-
type: "audio",
|
|
7252
|
-
language_code: languageCode,
|
|
7253
|
-
name: trackName,
|
|
7254
|
-
url: presignedUrl
|
|
7255
|
-
});
|
|
7256
|
-
if (!trackResponse.id) {
|
|
7257
|
-
throw new Error("Failed to create audio track: no track ID returned from Mux");
|
|
7258
|
-
}
|
|
7259
|
-
return trackResponse.id;
|
|
7260
|
-
}
|
|
7261
7506
|
async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
7262
7507
|
"use workflow";
|
|
7263
7508
|
const {
|
|
@@ -7270,15 +7515,23 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
7270
7515
|
uploadCaptionsToMux = false,
|
|
7271
7516
|
staticRenditionCleanup: staticRenditionCleanupOption = "delete",
|
|
7272
7517
|
storageAdapter,
|
|
7273
|
-
credentials: providedCredentials
|
|
7518
|
+
credentials: providedCredentials,
|
|
7519
|
+
replaceExistingTracks = "fail",
|
|
7520
|
+
trackName: providedTrackName,
|
|
7521
|
+
trackPassthrough: providedTrackPassthrough
|
|
7274
7522
|
} = options;
|
|
7275
7523
|
if (provider !== "elevenlabs") {
|
|
7276
7524
|
throw new MuxAiError("Only ElevenLabs provider is currently supported for audio translation.", { type: "validation_error" });
|
|
7277
7525
|
}
|
|
7278
7526
|
const credentials = providedCredentials;
|
|
7279
7527
|
const effectiveStorageAdapter = storageAdapter;
|
|
7528
|
+
const trackPassthrough = validateTrackPassthrough(providedTrackPassthrough) ?? buildMuxAiTrackPassthrough("translate-audio");
|
|
7280
7529
|
const uploadToMux = uploadToMuxOption !== false;
|
|
7281
7530
|
const uploadToS3 = uploadToS3Option || uploadToMux || uploadCaptionsToMux;
|
|
7531
|
+
const muxLangCode = toISO639_1(toLanguageCode);
|
|
7532
|
+
const trackName = providedTrackName ?? `${getLanguageName(muxLangCode) ?? muxLangCode.toUpperCase()} (Auto-dubbed)`;
|
|
7533
|
+
const audioTrackTarget = { type: "audio", languageCode: muxLangCode, name: trackName };
|
|
7534
|
+
const captionsTrackTarget = { type: "text", languageCode: muxLangCode, name: trackName };
|
|
7282
7535
|
const s3Endpoint = options.s3Endpoint ?? env_default.S3_ENDPOINT;
|
|
7283
7536
|
const s3Region = options.s3Region ?? env_default.S3_REGION ?? "auto";
|
|
7284
7537
|
const s3Bucket = options.s3Bucket ?? env_default.S3_BUCKET;
|
|
@@ -7289,6 +7542,15 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
7289
7542
|
}
|
|
7290
7543
|
const { asset: initialAsset, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials, options.assetSnapshot);
|
|
7291
7544
|
const assetDurationSeconds = getAssetDurationSecondsFromAsset(initialAsset);
|
|
7545
|
+
for (const target of [
|
|
7546
|
+
...uploadToMux ? [audioTrackTarget] : [],
|
|
7547
|
+
...uploadCaptionsToMux ? [captionsTrackTarget] : []
|
|
7548
|
+
]) {
|
|
7549
|
+
const plan = planTextTrackReplacement(initialAsset, target, replaceExistingTracks);
|
|
7550
|
+
if (plan.kind === "blocked") {
|
|
7551
|
+
throw new MuxAiError(plan.reason, { type: "validation_error" });
|
|
7552
|
+
}
|
|
7553
|
+
}
|
|
7292
7554
|
let currentAsset = initialAsset;
|
|
7293
7555
|
let createdStaticRenditionId;
|
|
7294
7556
|
if (!hasReadyAudioStaticRendition(currentAsset)) {
|
|
@@ -7304,6 +7566,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
7304
7566
|
let dubbingId;
|
|
7305
7567
|
let presignedUrl;
|
|
7306
7568
|
let uploadedTrackId;
|
|
7569
|
+
let replacedTracks;
|
|
7307
7570
|
let captionsPresignedUrl;
|
|
7308
7571
|
let captionsTrackId;
|
|
7309
7572
|
try {
|
|
@@ -7394,18 +7657,29 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
7394
7657
|
}
|
|
7395
7658
|
if (uploadToMux) {
|
|
7396
7659
|
console.warn("\u{1F4F9} Adding dubbed audio track to Mux asset...");
|
|
7397
|
-
|
|
7660
|
+
let outcome;
|
|
7398
7661
|
try {
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7662
|
+
outcome = await replaceAndCreateTrack({
|
|
7663
|
+
assetId,
|
|
7664
|
+
target: audioTrackTarget,
|
|
7665
|
+
policy: replaceExistingTracks,
|
|
7666
|
+
presignedUrl,
|
|
7667
|
+
passthrough: trackPassthrough,
|
|
7668
|
+
credentials
|
|
7669
|
+
});
|
|
7404
7670
|
} catch (error) {
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7671
|
+
wrapError(error, `Failed to add dubbed audio track to Mux asset (dubbed audio is staged at: ${presignedUrl})`);
|
|
7672
|
+
}
|
|
7673
|
+
if (outcome.kind === "blocked") {
|
|
7674
|
+
throw new MuxAiError(`${outcome.reason} Dubbed audio is staged at: ${presignedUrl}`, { type: "validation_error" });
|
|
7408
7675
|
}
|
|
7676
|
+
if (outcome.kind === "create_failed") {
|
|
7677
|
+
throw new Error(`Failed to add dubbed audio track to Mux asset: ${outcome.reason}. Dubbed audio is staged at: ${presignedUrl}`);
|
|
7678
|
+
}
|
|
7679
|
+
uploadedTrackId = outcome.trackId;
|
|
7680
|
+
replacedTracks = outcome.deleted;
|
|
7681
|
+
console.warn(`\u2705 Track added to Mux asset with ID: ${uploadedTrackId}`);
|
|
7682
|
+
console.warn(`\u{1F4CB} Track name: "${trackName}"`);
|
|
7409
7683
|
}
|
|
7410
7684
|
try {
|
|
7411
7685
|
console.warn("\u{1F4E5} Downloading dub transcript from ElevenLabs and staging to S3...");
|
|
@@ -7423,15 +7697,21 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
7423
7697
|
});
|
|
7424
7698
|
if (uploadCaptionsToMux) {
|
|
7425
7699
|
console.warn("\u{1F4F9} Adding dubbed captions track to Mux asset...");
|
|
7426
|
-
const
|
|
7427
|
-
const languageName = new Intl.DisplayNames(["en"], { type: "language" }).of(muxLangCode) || muxLangCode.toUpperCase();
|
|
7428
|
-
captionsTrackId = await createTextTrackOnMux(
|
|
7700
|
+
const captionsOutcome = await replaceAndCreateTrack({
|
|
7429
7701
|
assetId,
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
captionsPresignedUrl,
|
|
7702
|
+
target: captionsTrackTarget,
|
|
7703
|
+
policy: replaceExistingTracks,
|
|
7704
|
+
presignedUrl: captionsPresignedUrl,
|
|
7705
|
+
passthrough: trackPassthrough,
|
|
7433
7706
|
credentials
|
|
7434
|
-
);
|
|
7707
|
+
});
|
|
7708
|
+
if (captionsOutcome.deleted.length > 0) {
|
|
7709
|
+
replacedTracks = [...replacedTracks ?? [], ...captionsOutcome.deleted];
|
|
7710
|
+
}
|
|
7711
|
+
if (captionsOutcome.kind !== "created") {
|
|
7712
|
+
throw new Error(captionsOutcome.reason);
|
|
7713
|
+
}
|
|
7714
|
+
captionsTrackId = captionsOutcome.trackId;
|
|
7435
7715
|
console.warn(`\u2705 Captions track added to Mux asset with ID: ${captionsTrackId}`);
|
|
7436
7716
|
}
|
|
7437
7717
|
} catch (error) {
|
|
@@ -7462,6 +7742,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
7462
7742
|
dubbingId,
|
|
7463
7743
|
uploadedTrackId,
|
|
7464
7744
|
presignedUrl,
|
|
7745
|
+
replacedTracks,
|
|
7465
7746
|
captionsTrackId,
|
|
7466
7747
|
captionsPresignedUrl,
|
|
7467
7748
|
createdStaticRenditionId,
|
|
@@ -7813,11 +8094,8 @@ async function translateVttWithAI({
|
|
|
7813
8094
|
model,
|
|
7814
8095
|
maxRetries: 0,
|
|
7815
8096
|
output: Output7.object({ schema: translationSchema }),
|
|
8097
|
+
system: SYSTEM_PROMPT6,
|
|
7816
8098
|
messages: [
|
|
7817
|
-
{
|
|
7818
|
-
role: "system",
|
|
7819
|
-
content: SYSTEM_PROMPT6
|
|
7820
|
-
},
|
|
7821
8099
|
{
|
|
7822
8100
|
role: "user",
|
|
7823
8101
|
content: `Translate from ${fromLanguageCode} to ${toLanguageCode}:${buildNeverTranslateSection(neverTranslate)}
|
|
@@ -7881,11 +8159,8 @@ async function translateCueChunkWithAI({
|
|
|
7881
8159
|
model,
|
|
7882
8160
|
maxRetries: 0,
|
|
7883
8161
|
output: Output7.object({ schema }),
|
|
8162
|
+
system: CUE_TRANSLATION_SYSTEM_PROMPT,
|
|
7884
8163
|
messages: [
|
|
7885
|
-
{
|
|
7886
|
-
role: "system",
|
|
7887
|
-
content: CUE_TRANSLATION_SYSTEM_PROMPT
|
|
7888
|
-
},
|
|
7889
8164
|
{
|
|
7890
8165
|
role: "user",
|
|
7891
8166
|
content: `Translate from ${fromLanguageCode} to ${toLanguageCode}.
|
|
@@ -8137,11 +8412,15 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
|
|
|
8137
8412
|
storageAdapter,
|
|
8138
8413
|
credentials: providedCredentials,
|
|
8139
8414
|
chunking,
|
|
8140
|
-
neverTranslate: neverTranslateOption
|
|
8415
|
+
neverTranslate: neverTranslateOption,
|
|
8416
|
+
replaceExistingTracks = "fail",
|
|
8417
|
+
trackName: providedTrackName,
|
|
8418
|
+
trackPassthrough: providedTrackPassthrough
|
|
8141
8419
|
} = options;
|
|
8142
8420
|
const credentials = providedCredentials;
|
|
8143
8421
|
const effectiveStorageAdapter = storageAdapter;
|
|
8144
8422
|
const neverTranslateTerms = neverTranslateOption ? validateNeverTranslateTerms(neverTranslateOption) : [];
|
|
8423
|
+
const trackPassthrough = validateTrackPassthrough(providedTrackPassthrough) ?? buildMuxAiTrackPassthrough("translate-captions");
|
|
8145
8424
|
const s3Endpoint = providedS3Endpoint ?? env_default.S3_ENDPOINT;
|
|
8146
8425
|
const s3Region = providedS3Region ?? env_default.S3_REGION ?? "auto";
|
|
8147
8426
|
const s3Bucket = providedS3Bucket ?? env_default.S3_BUCKET;
|
|
@@ -8182,6 +8461,16 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
|
|
|
8182
8461
|
{ type: "validation_error" }
|
|
8183
8462
|
);
|
|
8184
8463
|
}
|
|
8464
|
+
const outputTrack = {
|
|
8465
|
+
languageCode: toLanguageCode,
|
|
8466
|
+
name: providedTrackName ?? `${getLanguageName(toLanguageCode) ?? toLanguageCode.toUpperCase()} (Auto-translated)`
|
|
8467
|
+
};
|
|
8468
|
+
if (uploadToMux) {
|
|
8469
|
+
const plan = planTextTrackReplacement(assetData, outputTrack, replaceExistingTracks);
|
|
8470
|
+
if (plan.kind === "blocked") {
|
|
8471
|
+
throw new MuxAiError(plan.reason, { type: "validation_error" });
|
|
8472
|
+
}
|
|
8473
|
+
}
|
|
8185
8474
|
const vttUrl = await buildTranscriptUrl(playbackId, trackId, policy === "signed", credentials);
|
|
8186
8475
|
let vttContent;
|
|
8187
8476
|
try {
|
|
@@ -8249,6 +8538,7 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
|
|
|
8249
8538
|
const targetLanguage = getLanguageCodePair(toLanguageCode);
|
|
8250
8539
|
let presignedUrl;
|
|
8251
8540
|
let uploadedTrackId;
|
|
8541
|
+
let replacedTracks;
|
|
8252
8542
|
if (uploadToS3) {
|
|
8253
8543
|
try {
|
|
8254
8544
|
presignedUrl = await uploadVttToS3({
|
|
@@ -8266,19 +8556,28 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
|
|
|
8266
8556
|
wrapError(error, "Failed to upload VTT to S3");
|
|
8267
8557
|
}
|
|
8268
8558
|
if (uploadToMux) {
|
|
8559
|
+
let outcome;
|
|
8269
8560
|
try {
|
|
8270
|
-
|
|
8271
|
-
const trackName = `${languageName} (auto-translated)`;
|
|
8272
|
-
uploadedTrackId = await createTextTrackOnMux(
|
|
8561
|
+
outcome = await replaceAndCreateTextTrack({
|
|
8273
8562
|
assetId,
|
|
8274
|
-
|
|
8275
|
-
|
|
8563
|
+
target: outputTrack,
|
|
8564
|
+
policy: replaceExistingTracks,
|
|
8276
8565
|
presignedUrl,
|
|
8566
|
+
passthrough: trackPassthrough,
|
|
8277
8567
|
credentials
|
|
8278
|
-
);
|
|
8568
|
+
});
|
|
8279
8569
|
} catch (error) {
|
|
8280
|
-
|
|
8570
|
+
wrapError(error, "Failed to add translated track to Mux asset");
|
|
8571
|
+
}
|
|
8572
|
+
if (outcome.kind !== "created") {
|
|
8573
|
+
const deletedNote = outcome.deleted.length > 0 ? ` Tracks already deleted before the failure: ${outcome.deleted.map((track) => `${track.name ?? track.id} (${track.id})`).join(", ")}.` : "";
|
|
8574
|
+
if (outcome.kind === "blocked") {
|
|
8575
|
+
throw new MuxAiError(`${outcome.reason}${deletedNote}`, { type: "validation_error" });
|
|
8576
|
+
}
|
|
8577
|
+
throw new Error(`Failed to add translated track to Mux asset: ${outcome.reason}.${deletedNote}`);
|
|
8281
8578
|
}
|
|
8579
|
+
uploadedTrackId = outcome.trackId;
|
|
8580
|
+
replacedTracks = outcome.deleted;
|
|
8282
8581
|
}
|
|
8283
8582
|
}
|
|
8284
8583
|
return {
|
|
@@ -8291,6 +8590,7 @@ async function translateCaptionsInternal(assetId, trackId, toLanguageCode, optio
|
|
|
8291
8590
|
originalVtt: vttContent,
|
|
8292
8591
|
translatedVtt,
|
|
8293
8592
|
uploadedTrackId,
|
|
8593
|
+
replacedTracks,
|
|
8294
8594
|
presignedUrl,
|
|
8295
8595
|
usage: usageWithMetadata,
|
|
8296
8596
|
safety,
|