@mux/ai 0.28.0 → 0.29.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-bjQi1ZCj.d.ts → index-CXwsu679.d.ts} +18 -4
- package/dist/{index-DUVBpnj5.d.ts → index-DJirK4fh.d.ts} +13 -13
- package/dist/index.d.ts +5 -5
- package/dist/index.js +249 -89
- package/dist/index.js.map +1 -1
- package/dist/primitives/index.d.ts +2 -2
- package/dist/primitives/index.js +96 -15
- package/dist/primitives/index.js.map +1 -1
- package/dist/{shots-mGJfYGiO.d.ts → shots-BKd7TMFN.d.ts} +19 -1
- package/dist/workflows/index.d.ts +2 -2
- package/dist/workflows/index.js +247 -88
- package/dist/workflows/index.js.map +1 -1
- package/package.json +7 -1
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// package.json
|
|
8
|
-
var version = "0.
|
|
8
|
+
var version = "0.29.0";
|
|
9
9
|
|
|
10
10
|
// src/lib/language-codes.ts
|
|
11
11
|
var ISO639_1_TO_3 = {
|
|
@@ -1372,6 +1372,7 @@ __export(primitives_exports, {
|
|
|
1372
1372
|
extractTextFromVTT: () => extractTextFromVTT,
|
|
1373
1373
|
extractTimestampedTranscript: () => extractTimestampedTranscript,
|
|
1374
1374
|
fetchTranscriptForAsset: () => fetchTranscriptForAsset,
|
|
1375
|
+
filterVttByScope: () => filterVttByScope,
|
|
1375
1376
|
findCaptionTrack: () => findCaptionTrack,
|
|
1376
1377
|
getHeatmapForAsset: () => getHeatmapForAsset,
|
|
1377
1378
|
getHeatmapForPlaybackId: () => getHeatmapForPlaybackId,
|
|
@@ -1908,13 +1909,22 @@ async function signUrl(url, playbackId, type = "video", params, credentials) {
|
|
|
1908
1909
|
|
|
1909
1910
|
// src/primitives/storyboards.ts
|
|
1910
1911
|
var DEFAULT_STORYBOARD_WIDTH = 640;
|
|
1911
|
-
async function getStoryboardUrl(playbackId, width = DEFAULT_STORYBOARD_WIDTH, shouldSign = false, credentials) {
|
|
1912
|
+
async function getStoryboardUrl(playbackId, width = DEFAULT_STORYBOARD_WIDTH, shouldSign = false, credentials, scope) {
|
|
1912
1913
|
"use step";
|
|
1913
1914
|
const baseUrl = getMuxStoryboardBaseUrl(playbackId);
|
|
1915
|
+
const params = { width };
|
|
1916
|
+
if (scope?.startTime !== void 0) {
|
|
1917
|
+
params.asset_start_time = scope.startTime;
|
|
1918
|
+
}
|
|
1919
|
+
if (scope?.endTime !== void 0) {
|
|
1920
|
+
params.asset_end_time = scope.endTime;
|
|
1921
|
+
}
|
|
1914
1922
|
if (shouldSign) {
|
|
1915
|
-
return signUrl(baseUrl, playbackId, "storyboard",
|
|
1923
|
+
return signUrl(baseUrl, playbackId, "storyboard", params, credentials);
|
|
1916
1924
|
}
|
|
1917
|
-
return `${baseUrl}
|
|
1925
|
+
return `${baseUrl}?${new URLSearchParams(
|
|
1926
|
+
Object.entries(params).map(([key, value]) => [key, String(value)])
|
|
1927
|
+
).toString()}`;
|
|
1918
1928
|
}
|
|
1919
1929
|
|
|
1920
1930
|
// src/primitives/text-chunking.ts
|
|
@@ -2155,30 +2165,97 @@ function chunkText(text, strategy) {
|
|
|
2155
2165
|
}
|
|
2156
2166
|
}
|
|
2157
2167
|
|
|
2168
|
+
// src/lib/workflow-scope.ts
|
|
2169
|
+
function hasWorkflowScopeBoundaries(scope) {
|
|
2170
|
+
return scope?.startTime !== void 0 || scope?.endTime !== void 0;
|
|
2171
|
+
}
|
|
2172
|
+
function resolveWorkflowScope(scope, assetDurationSeconds) {
|
|
2173
|
+
if (assetDurationSeconds === void 0 || !Number.isFinite(assetDurationSeconds) || assetDurationSeconds <= 0) {
|
|
2174
|
+
throw new MuxAiError("Asset has no valid duration.", { type: "validation_error" });
|
|
2175
|
+
}
|
|
2176
|
+
const startTime = scope?.startTime ?? 0;
|
|
2177
|
+
const endTime = scope?.endTime ?? assetDurationSeconds;
|
|
2178
|
+
if (!Number.isFinite(startTime) || startTime < 0) {
|
|
2179
|
+
throw new MuxAiError(
|
|
2180
|
+
`scope.startTime must be a finite, non-negative number (received ${startTime}).`,
|
|
2181
|
+
{ type: "validation_error" }
|
|
2182
|
+
);
|
|
2183
|
+
}
|
|
2184
|
+
if (!Number.isFinite(endTime) || endTime < 0) {
|
|
2185
|
+
throw new MuxAiError(
|
|
2186
|
+
`scope.endTime must be a finite, non-negative number (received ${endTime}).`,
|
|
2187
|
+
{ type: "validation_error" }
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
if (endTime > assetDurationSeconds) {
|
|
2191
|
+
throw new MuxAiError(
|
|
2192
|
+
`scope.endTime (${endTime}) cannot exceed the asset duration (${assetDurationSeconds}).`,
|
|
2193
|
+
{ type: "validation_error" }
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2196
|
+
if (startTime >= endTime) {
|
|
2197
|
+
throw new MuxAiError(
|
|
2198
|
+
`scope.startTime (${startTime}) must be less than scope.endTime (${endTime}).`,
|
|
2199
|
+
{ type: "validation_error" }
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
return { startTime, endTime };
|
|
2203
|
+
}
|
|
2204
|
+
function resolveRenderableVideoScope(scope, assetDurationSeconds, videoTrackDurationSeconds) {
|
|
2205
|
+
if (!hasWorkflowScopeBoundaries(scope)) {
|
|
2206
|
+
return void 0;
|
|
2207
|
+
}
|
|
2208
|
+
const resolvedScope = resolveWorkflowScope(scope, assetDurationSeconds);
|
|
2209
|
+
let renderableEndTime = resolvedScope.endTime;
|
|
2210
|
+
if (videoTrackDurationSeconds !== void 0 && Number.isFinite(videoTrackDurationSeconds) && videoTrackDurationSeconds >= 0) {
|
|
2211
|
+
renderableEndTime = Math.min(videoTrackDurationSeconds, resolvedScope.endTime);
|
|
2212
|
+
}
|
|
2213
|
+
const renderableScope = {
|
|
2214
|
+
startTime: Math.min(resolvedScope.startTime, renderableEndTime),
|
|
2215
|
+
endTime: renderableEndTime
|
|
2216
|
+
};
|
|
2217
|
+
if (renderableScope.startTime >= renderableScope.endTime) {
|
|
2218
|
+
throw new MuxAiError("The requested scope does not include any renderable video.", {
|
|
2219
|
+
type: "validation_error"
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
return renderableScope;
|
|
2223
|
+
}
|
|
2224
|
+
function timeRangesOverlap(firstStart, firstEnd, second) {
|
|
2225
|
+
const secondStart = second.startTime ?? 0;
|
|
2226
|
+
const secondEnd = second.endTime ?? Number.POSITIVE_INFINITY;
|
|
2227
|
+
return firstStart < secondEnd && firstEnd > secondStart;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2158
2230
|
// src/primitives/thumbnails.ts
|
|
2159
2231
|
async function getThumbnailUrls(playbackId, duration, options = {}) {
|
|
2160
2232
|
"use step";
|
|
2161
|
-
const { interval = 10, width = 640, shouldSign = false, maxSamples, credentials } = options;
|
|
2233
|
+
const { interval = 10, width = 640, shouldSign = false, maxSamples, credentials, scope } = options;
|
|
2234
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
2235
|
+
const resolvedScope = effectiveScope ? resolveWorkflowScope(effectiveScope, duration) : { startTime: 0, endTime: duration };
|
|
2236
|
+
const rangeDuration = resolvedScope.endTime - resolvedScope.startTime;
|
|
2162
2237
|
let timestamps = [];
|
|
2163
|
-
if (
|
|
2164
|
-
const spacing =
|
|
2238
|
+
if (rangeDuration <= 50) {
|
|
2239
|
+
const spacing = rangeDuration / 6;
|
|
2165
2240
|
for (let i = 1; i <= 5; i++) {
|
|
2166
|
-
|
|
2241
|
+
const time = resolvedScope.startTime + i * spacing;
|
|
2242
|
+
timestamps.push(effectiveScope ? Number(time.toFixed(3)) : Math.round(time));
|
|
2167
2243
|
}
|
|
2168
2244
|
} else {
|
|
2169
|
-
for (let time =
|
|
2245
|
+
for (let time = resolvedScope.startTime; time < resolvedScope.endTime; time += interval) {
|
|
2170
2246
|
timestamps.push(time);
|
|
2171
2247
|
}
|
|
2172
2248
|
}
|
|
2173
2249
|
if (maxSamples !== void 0 && timestamps.length > maxSamples) {
|
|
2174
2250
|
const newTimestamps = [];
|
|
2175
|
-
newTimestamps.push(
|
|
2251
|
+
newTimestamps.push(resolvedScope.startTime);
|
|
2176
2252
|
if (maxSamples >= 2) {
|
|
2177
|
-
const
|
|
2253
|
+
const lastTime = effectiveScope ? Math.max(resolvedScope.startTime, resolvedScope.endTime - 1e-3) : resolvedScope.endTime;
|
|
2254
|
+
const spacing = (lastTime - resolvedScope.startTime) / (maxSamples - 1);
|
|
2178
2255
|
for (let i = 1; i < maxSamples - 1; i++) {
|
|
2179
|
-
newTimestamps.push(spacing * i);
|
|
2256
|
+
newTimestamps.push(resolvedScope.startTime + spacing * i);
|
|
2180
2257
|
}
|
|
2181
|
-
newTimestamps.push(
|
|
2258
|
+
newTimestamps.push(lastTime);
|
|
2182
2259
|
}
|
|
2183
2260
|
timestamps = newTimestamps;
|
|
2184
2261
|
}
|
|
@@ -2512,6 +2589,23 @@ function parseVTTCues(vttContent) {
|
|
|
2512
2589
|
}
|
|
2513
2590
|
return cues;
|
|
2514
2591
|
}
|
|
2592
|
+
function filterVttByScope(vttContent, scope) {
|
|
2593
|
+
if (!scope || scope.startTime === void 0 && scope.endTime === void 0) {
|
|
2594
|
+
return vttContent;
|
|
2595
|
+
}
|
|
2596
|
+
const { preamble, cueBlocks } = splitVttPreambleAndCueBlocks(vttContent);
|
|
2597
|
+
const scopedCueBlocks = cueBlocks.filter((cueBlock) => {
|
|
2598
|
+
const cue = parseVTTCues(cueBlock)[0];
|
|
2599
|
+
return cue ? timeRangesOverlap(cue.startTime, cue.endTime, scope) : false;
|
|
2600
|
+
}).map((cueBlock, index) => {
|
|
2601
|
+
const lines = cueBlock.split("\n");
|
|
2602
|
+
if (/^\d+$/.test(lines[0]?.trim()) && lines[1]?.includes("-->")) {
|
|
2603
|
+
lines[0] = String(index + 1);
|
|
2604
|
+
}
|
|
2605
|
+
return lines.join("\n");
|
|
2606
|
+
});
|
|
2607
|
+
return buildVttFromCueBlocks(scopedCueBlocks, preamble);
|
|
2608
|
+
}
|
|
2515
2609
|
function splitVttPreambleAndCueBlocks(vttContent) {
|
|
2516
2610
|
const normalizedContent = normalizeLineEndings(vttContent).trim();
|
|
2517
2611
|
if (!normalizedContent) {
|
|
@@ -2616,6 +2710,7 @@ async function fetchTranscriptForAsset(asset, playbackId, options = {}) {
|
|
|
2616
2710
|
const {
|
|
2617
2711
|
languageCode,
|
|
2618
2712
|
cleanTranscript = true,
|
|
2713
|
+
scope,
|
|
2619
2714
|
shouldSign,
|
|
2620
2715
|
credentials,
|
|
2621
2716
|
required = false
|
|
@@ -2647,9 +2742,15 @@ async function fetchTranscriptForAsset(asset, playbackId, options = {}) {
|
|
|
2647
2742
|
return { transcriptText: "", transcriptUrl, track };
|
|
2648
2743
|
}
|
|
2649
2744
|
const rawVtt = await response.text();
|
|
2650
|
-
const
|
|
2651
|
-
|
|
2652
|
-
|
|
2745
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
2746
|
+
const scopedVtt = filterVttByScope(rawVtt, effectiveScope);
|
|
2747
|
+
const transcriptText = cleanTranscript ? extractTextFromVTT(scopedVtt) : scopedVtt;
|
|
2748
|
+
const hasScopedCues = !effectiveScope || parseVTTCues(scopedVtt).length > 0;
|
|
2749
|
+
if (required && (!transcriptText.trim() || !hasScopedCues)) {
|
|
2750
|
+
throw new MuxAiError(
|
|
2751
|
+
effectiveScope ? "Transcript has no cues in the requested scope." : "Transcript is empty.",
|
|
2752
|
+
{ type: "validation_error" }
|
|
2753
|
+
);
|
|
2653
2754
|
}
|
|
2654
2755
|
return { transcriptText, transcriptUrl, track };
|
|
2655
2756
|
} catch (error) {
|
|
@@ -3431,7 +3532,8 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
|
|
|
3431
3532
|
imageSubmissionMode = "url",
|
|
3432
3533
|
imageDownloadOptions,
|
|
3433
3534
|
storyboardWidth = 640,
|
|
3434
|
-
credentials
|
|
3535
|
+
credentials,
|
|
3536
|
+
scope
|
|
3435
3537
|
} = options ?? {};
|
|
3436
3538
|
const normalizedQuestions = questions.map((q, idx) => normalizeQuestion(q, idx));
|
|
3437
3539
|
const hasFreeForm = normalizedQuestions.some((q) => q.mode.kind === "freeForm");
|
|
@@ -3443,6 +3545,15 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
|
|
|
3443
3545
|
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
|
|
3444
3546
|
const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
|
|
3445
3547
|
const isAudioOnly = isAudioOnlyAsset(assetData);
|
|
3548
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
3549
|
+
const storyboardScope = isAudioOnly ? void 0 : resolveRenderableVideoScope(
|
|
3550
|
+
effectiveScope,
|
|
3551
|
+
assetDurationSeconds,
|
|
3552
|
+
getVideoTrackDurationSecondsFromAsset(assetData)
|
|
3553
|
+
);
|
|
3554
|
+
if (isAudioOnly && effectiveScope) {
|
|
3555
|
+
resolveWorkflowScope(effectiveScope, assetDurationSeconds);
|
|
3556
|
+
}
|
|
3446
3557
|
if (isAudioOnly && !includeTranscript) {
|
|
3447
3558
|
throw new MuxAiError(
|
|
3448
3559
|
"Audio-only assets require a transcript. Set includeTranscript: true and ensure the asset has a ready text track (captions/subtitles).",
|
|
@@ -3461,7 +3572,8 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
|
|
|
3461
3572
|
cleanTranscript,
|
|
3462
3573
|
shouldSign: policy === "signed",
|
|
3463
3574
|
credentials,
|
|
3464
|
-
required: isAudioOnly
|
|
3575
|
+
required: isAudioOnly,
|
|
3576
|
+
scope: effectiveScope
|
|
3465
3577
|
}) : void 0;
|
|
3466
3578
|
const transcriptText = transcriptResult?.transcriptText ?? "";
|
|
3467
3579
|
const userPrompt = buildUserPrompt({
|
|
@@ -3490,7 +3602,8 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
|
|
|
3490
3602
|
playbackId,
|
|
3491
3603
|
storyboardWidth,
|
|
3492
3604
|
policy === "signed",
|
|
3493
|
-
credentials
|
|
3605
|
+
credentials,
|
|
3606
|
+
storyboardScope
|
|
3494
3607
|
);
|
|
3495
3608
|
imageUrl = storyboardUrl;
|
|
3496
3609
|
if (imageSubmissionMode === "base64") {
|
|
@@ -3773,6 +3886,7 @@ async function hasBurnedInCaptionsInternal(assetId, options, collectedUsage) {
|
|
|
3773
3886
|
imageDownloadOptions,
|
|
3774
3887
|
promptOverrides,
|
|
3775
3888
|
credentials,
|
|
3889
|
+
scope,
|
|
3776
3890
|
...config
|
|
3777
3891
|
} = options;
|
|
3778
3892
|
const userPrompt = buildUserPrompt2(promptOverrides);
|
|
@@ -3783,7 +3897,18 @@ async function hasBurnedInCaptionsInternal(assetId, options, collectedUsage) {
|
|
|
3783
3897
|
});
|
|
3784
3898
|
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
|
|
3785
3899
|
const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
|
|
3786
|
-
const
|
|
3900
|
+
const storyboardScope = resolveRenderableVideoScope(
|
|
3901
|
+
scope,
|
|
3902
|
+
assetDurationSeconds,
|
|
3903
|
+
getVideoTrackDurationSecondsFromAsset(assetData)
|
|
3904
|
+
);
|
|
3905
|
+
const imageUrl = await getStoryboardUrl(
|
|
3906
|
+
playbackId,
|
|
3907
|
+
640,
|
|
3908
|
+
policy === "signed",
|
|
3909
|
+
credentials,
|
|
3910
|
+
storyboardScope
|
|
3911
|
+
);
|
|
3787
3912
|
let analysisResponse;
|
|
3788
3913
|
if (imageSubmissionMode === "base64") {
|
|
3789
3914
|
const base64Data = await fetchImageAsBase642(imageUrl, imageDownloadOptions);
|
|
@@ -3982,7 +4107,8 @@ function buildUserPrompt3({
|
|
|
3982
4107
|
promptOverrides,
|
|
3983
4108
|
minChaptersPerHour = 3,
|
|
3984
4109
|
maxChaptersPerHour = 8,
|
|
3985
|
-
languageName
|
|
4110
|
+
languageName,
|
|
4111
|
+
scope
|
|
3986
4112
|
}) {
|
|
3987
4113
|
const contextSections = [
|
|
3988
4114
|
{
|
|
@@ -3997,6 +4123,8 @@ function buildUserPrompt3({
|
|
|
3997
4123
|
const dynamicChapterGuidelines = dedent4`
|
|
3998
4124
|
- Create at least ${minChaptersPerHour} and at most ${maxChaptersPerHour} chapters per hour of content
|
|
3999
4125
|
- Use start times in seconds (not HH:MM:SS)
|
|
4126
|
+
${scope ? `- Analyze only the range from ${scope.startTime}s (inclusive) to ${scope.endTime}s (exclusive)
|
|
4127
|
+
- The first chapter must start at ${scope.startTime}s` : ""}
|
|
4000
4128
|
- Chapter start times should be non-decreasing
|
|
4001
4129
|
- Do not include text before or after the JSON`;
|
|
4002
4130
|
const mergedOverrides = {
|
|
@@ -4023,7 +4151,8 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
4023
4151
|
minChaptersPerHour,
|
|
4024
4152
|
maxChaptersPerHour,
|
|
4025
4153
|
credentials,
|
|
4026
|
-
outputLanguageCode
|
|
4154
|
+
outputLanguageCode,
|
|
4155
|
+
scope
|
|
4027
4156
|
} = options;
|
|
4028
4157
|
const modelConfig = resolveLanguageModelConfig({
|
|
4029
4158
|
...options,
|
|
@@ -4032,6 +4161,8 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
4032
4161
|
});
|
|
4033
4162
|
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
|
|
4034
4163
|
const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
|
|
4164
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
4165
|
+
const resolvedScope = effectiveScope ? resolveWorkflowScope(effectiveScope, assetDurationSeconds) : void 0;
|
|
4035
4166
|
const isAudioOnly = isAudioOnlyAsset(assetData);
|
|
4036
4167
|
const signingContext = await resolveMuxSigningContext(credentials);
|
|
4037
4168
|
if (policy === "signed" && !signingContext) {
|
|
@@ -4046,7 +4177,8 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
4046
4177
|
cleanTranscript: false,
|
|
4047
4178
|
// keep timestamps for chapter segmentation
|
|
4048
4179
|
shouldSign: policy === "signed",
|
|
4049
|
-
credentials
|
|
4180
|
+
credentials,
|
|
4181
|
+
scope: effectiveScope
|
|
4050
4182
|
});
|
|
4051
4183
|
if (!transcriptResult.track || !transcriptResult.transcriptText) {
|
|
4052
4184
|
const availableLanguages = readyTextTracks.map((t) => t.language_code).filter(Boolean).join(", ");
|
|
@@ -4067,7 +4199,8 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
4067
4199
|
promptOverrides,
|
|
4068
4200
|
minChaptersPerHour,
|
|
4069
4201
|
maxChaptersPerHour,
|
|
4070
|
-
languageName
|
|
4202
|
+
languageName,
|
|
4203
|
+
scope: resolvedScope
|
|
4071
4204
|
});
|
|
4072
4205
|
let chaptersData = null;
|
|
4073
4206
|
try {
|
|
@@ -4089,7 +4222,9 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
4089
4222
|
throw new MuxAiError(`Failed to generate chapters for asset ${assetId}.`);
|
|
4090
4223
|
}
|
|
4091
4224
|
const { chapters: chaptersPayload, usage } = chaptersData;
|
|
4092
|
-
const validChapters = chaptersPayload.chapters.filter((chapter) => typeof chapter.startTime === "number" && typeof chapter.title === "string").
|
|
4225
|
+
const validChapters = chaptersPayload.chapters.filter((chapter) => typeof chapter.startTime === "number" && typeof chapter.title === "string").filter(
|
|
4226
|
+
(chapter) => resolvedScope ? chapter.startTime >= resolvedScope.startTime && chapter.startTime < resolvedScope.endTime : true
|
|
4227
|
+
).sort((a, b) => a.startTime - b.startTime);
|
|
4093
4228
|
if (validChapters.length === 0) {
|
|
4094
4229
|
throw new MuxAiError(`Failed to generate valid chapters for asset ${assetId}.`);
|
|
4095
4230
|
}
|
|
@@ -4115,8 +4250,9 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
4115
4250
|
if (scrubbedChapters.length === 0) {
|
|
4116
4251
|
throw new MuxAiError(`Failed to generate valid chapters for asset ${assetId}.`);
|
|
4117
4252
|
}
|
|
4118
|
-
|
|
4119
|
-
|
|
4253
|
+
const firstChapterStartTime = resolvedScope?.startTime ?? 0;
|
|
4254
|
+
if (scrubbedChapters[0].startTime !== firstChapterStartTime) {
|
|
4255
|
+
scrubbedChapters[0].startTime = firstChapterStartTime;
|
|
4120
4256
|
}
|
|
4121
4257
|
const usageWithMetadata = {
|
|
4122
4258
|
...usage,
|
|
@@ -4695,11 +4831,16 @@ async function generateEmbeddingsInternal(assetId, options, collectedUsage) {
|
|
|
4695
4831
|
languageCode,
|
|
4696
4832
|
chunkingStrategy = { type: "token", maxTokens: 500, overlap: 100 },
|
|
4697
4833
|
batchSize = 5,
|
|
4698
|
-
credentials
|
|
4834
|
+
credentials,
|
|
4835
|
+
scope
|
|
4699
4836
|
} = options;
|
|
4700
4837
|
const embeddingModel = resolveEmbeddingModelConfig({ ...options, provider, model });
|
|
4701
4838
|
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
|
|
4702
4839
|
const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
|
|
4840
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
4841
|
+
if (effectiveScope) {
|
|
4842
|
+
resolveWorkflowScope(effectiveScope, assetDurationSeconds);
|
|
4843
|
+
}
|
|
4703
4844
|
const signingContext = await resolveMuxSigningContext(credentials);
|
|
4704
4845
|
if (policy === "signed" && !signingContext) {
|
|
4705
4846
|
throw new Error(
|
|
@@ -4712,7 +4853,8 @@ async function generateEmbeddingsInternal(assetId, options, collectedUsage) {
|
|
|
4712
4853
|
cleanTranscript: !useVttChunking,
|
|
4713
4854
|
shouldSign: policy === "signed",
|
|
4714
4855
|
credentials,
|
|
4715
|
-
required: true
|
|
4856
|
+
required: true,
|
|
4857
|
+
scope: effectiveScope
|
|
4716
4858
|
});
|
|
4717
4859
|
const transcriptText = transcriptResult.transcriptText;
|
|
4718
4860
|
const chunks = useVttChunking ? chunkVTTCues(
|
|
@@ -5843,12 +5985,24 @@ async function requestGoogleVisionModeration(images, maxConcurrent = 5, submissi
|
|
|
5843
5985
|
}));
|
|
5844
5986
|
return processConcurrently(targets, moderateImageWithGoogleVision, maxConcurrent);
|
|
5845
5987
|
}
|
|
5988
|
+
function toThumbnailTimeSeconds(timestampMs) {
|
|
5989
|
+
return Number((timestampMs / 1e3).toFixed(2));
|
|
5990
|
+
}
|
|
5991
|
+
function filterTimestampsWithinExclusiveScope(timestampsMs, scope) {
|
|
5992
|
+
if (!scope) {
|
|
5993
|
+
return timestampsMs;
|
|
5994
|
+
}
|
|
5995
|
+
return timestampsMs.filter((timestampMs) => {
|
|
5996
|
+
const time = toThumbnailTimeSeconds(timestampMs);
|
|
5997
|
+
return time >= scope.startTime && time < scope.endTime;
|
|
5998
|
+
});
|
|
5999
|
+
}
|
|
5846
6000
|
async function getThumbnailUrlsFromTimestamps(playbackId, timestampsMs, options) {
|
|
5847
6001
|
"use step";
|
|
5848
6002
|
const { width, shouldSign, credentials } = options;
|
|
5849
6003
|
const baseUrl = getMuxThumbnailBaseUrl(playbackId);
|
|
5850
6004
|
const urlPromises = timestampsMs.map(async (tsMs) => {
|
|
5851
|
-
const time =
|
|
6005
|
+
const time = toThumbnailTimeSeconds(tsMs);
|
|
5852
6006
|
const url = shouldSign ? await signUrl(baseUrl, playbackId, "thumbnail", { time, width }, credentials) : `${baseUrl}?time=${time}&width=${width}`;
|
|
5853
6007
|
return { url, time };
|
|
5854
6008
|
});
|
|
@@ -5867,7 +6021,8 @@ async function getModerationScores(assetId, options = {}) {
|
|
|
5867
6021
|
maxConcurrent = 5,
|
|
5868
6022
|
imageSubmissionMode = "url",
|
|
5869
6023
|
imageDownloadOptions,
|
|
5870
|
-
credentials: providedCredentials
|
|
6024
|
+
credentials: providedCredentials,
|
|
6025
|
+
scope
|
|
5871
6026
|
} = options;
|
|
5872
6027
|
const credentials = providedCredentials;
|
|
5873
6028
|
const { asset, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
|
|
@@ -5878,7 +6033,16 @@ async function getModerationScores(assetId, options = {}) {
|
|
|
5878
6033
|
(d) => d != null
|
|
5879
6034
|
);
|
|
5880
6035
|
const duration = candidateDurations.length > 0 ? Math.min(...candidateDurations) : 0;
|
|
6036
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
6037
|
+
const resolvedScope = effectiveScope ? resolveWorkflowScope(effectiveScope, assetDurationSeconds) : void 0;
|
|
5881
6038
|
const isAudioOnly = isAudioOnlyAsset(asset);
|
|
6039
|
+
const renderableScope = resolvedScope ? {
|
|
6040
|
+
startTime: Math.min(resolvedScope.startTime, duration),
|
|
6041
|
+
endTime: Math.min(resolvedScope.endTime, duration)
|
|
6042
|
+
} : void 0;
|
|
6043
|
+
if (!isAudioOnly && renderableScope && renderableScope.startTime >= renderableScope.endTime) {
|
|
6044
|
+
throw new Error("The requested scope does not include any renderable video.");
|
|
6045
|
+
}
|
|
5882
6046
|
const signingContext = await resolveMuxSigningContext(credentials);
|
|
5883
6047
|
if (policy === "signed" && !signingContext) {
|
|
5884
6048
|
throw new Error(
|
|
@@ -5895,7 +6059,8 @@ async function getModerationScores(assetId, options = {}) {
|
|
|
5895
6059
|
cleanTranscript: true,
|
|
5896
6060
|
shouldSign: policy === "signed",
|
|
5897
6061
|
credentials,
|
|
5898
|
-
required: true
|
|
6062
|
+
required: true,
|
|
6063
|
+
scope: effectiveScope
|
|
5899
6064
|
});
|
|
5900
6065
|
if (provider === "openai") {
|
|
5901
6066
|
thumbnailScores = await requestOpenAITranscriptModeration(
|
|
@@ -5913,17 +6078,21 @@ async function getModerationScores(assetId, options = {}) {
|
|
|
5913
6078
|
throw new Error(`Unsupported moderation provider: ${exhaustiveCheck}`);
|
|
5914
6079
|
}
|
|
5915
6080
|
} else {
|
|
5916
|
-
const
|
|
6081
|
+
const scopedDuration = renderableScope ? renderableScope.endTime - renderableScope.startTime : duration;
|
|
6082
|
+
const estimatedIntervalCount = scopedDuration <= 50 ? 5 : Math.ceil(scopedDuration / thumbnailInterval);
|
|
5917
6083
|
const thumbnailUrls = maxSamples !== void 0 && estimatedIntervalCount > maxSamples ? await getThumbnailUrlsFromTimestamps(
|
|
5918
6084
|
playbackId,
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
6085
|
+
filterTimestampsWithinExclusiveScope(
|
|
6086
|
+
planSamplingTimestamps({
|
|
6087
|
+
duration_sec: duration,
|
|
6088
|
+
max_candidates: maxSamples,
|
|
6089
|
+
trim_start_sec: renderableScope?.startTime ?? (duration > 2 ? Math.min(5, Math.max(1, duration / 6)) : 0),
|
|
6090
|
+
trim_end_sec: renderableScope ? duration - renderableScope.endTime : duration > 2 ? Math.min(5, Math.max(1, duration / 6)) : 0,
|
|
6091
|
+
fps: videoTrackFps,
|
|
6092
|
+
base_cadence_hz: thumbnailInterval > 0 ? 1 / thumbnailInterval : void 0
|
|
6093
|
+
}),
|
|
6094
|
+
renderableScope
|
|
6095
|
+
),
|
|
5927
6096
|
{
|
|
5928
6097
|
width: thumbnailWidth,
|
|
5929
6098
|
shouldSign: policy === "signed",
|
|
@@ -5933,7 +6102,8 @@ async function getModerationScores(assetId, options = {}) {
|
|
|
5933
6102
|
interval: thumbnailInterval,
|
|
5934
6103
|
width: thumbnailWidth,
|
|
5935
6104
|
shouldSign: policy === "signed",
|
|
5936
|
-
credentials
|
|
6105
|
+
credentials,
|
|
6106
|
+
scope: renderableScope
|
|
5937
6107
|
});
|
|
5938
6108
|
thumbnailCount = thumbnailUrls.length;
|
|
5939
6109
|
if (provider === "openai") {
|
|
@@ -6448,7 +6618,8 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
6448
6618
|
titleLength,
|
|
6449
6619
|
descriptionLength,
|
|
6450
6620
|
tagCount,
|
|
6451
|
-
outputLanguageCode
|
|
6621
|
+
outputLanguageCode,
|
|
6622
|
+
scope
|
|
6452
6623
|
} = options ?? {};
|
|
6453
6624
|
if (!VALID_TONES.includes(tone)) {
|
|
6454
6625
|
throw new MuxAiError(
|
|
@@ -6465,6 +6636,15 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
6465
6636
|
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, workflowCredentials);
|
|
6466
6637
|
const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
|
|
6467
6638
|
const isAudioOnly = isAudioOnlyAsset(assetData);
|
|
6639
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
6640
|
+
const storyboardScope = isAudioOnly ? void 0 : resolveRenderableVideoScope(
|
|
6641
|
+
effectiveScope,
|
|
6642
|
+
assetDurationSeconds,
|
|
6643
|
+
getVideoTrackDurationSecondsFromAsset(assetData)
|
|
6644
|
+
);
|
|
6645
|
+
if (isAudioOnly && effectiveScope) {
|
|
6646
|
+
resolveWorkflowScope(effectiveScope, assetDurationSeconds);
|
|
6647
|
+
}
|
|
6468
6648
|
if (isAudioOnly && !includeTranscript) {
|
|
6469
6649
|
throw new MuxAiError(
|
|
6470
6650
|
"Audio-only assets require a transcript. Set includeTranscript: true and ensure the asset has a ready text track (captions/subtitles).",
|
|
@@ -6483,7 +6663,8 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
6483
6663
|
cleanTranscript,
|
|
6484
6664
|
shouldSign: policy === "signed",
|
|
6485
6665
|
credentials: workflowCredentials,
|
|
6486
|
-
required: isAudioOnly
|
|
6666
|
+
required: isAudioOnly,
|
|
6667
|
+
scope: effectiveScope
|
|
6487
6668
|
}) : void 0;
|
|
6488
6669
|
const transcriptText = transcriptResult?.transcriptText ?? "";
|
|
6489
6670
|
const resolvedLanguageCode = outputLanguageCode && outputLanguageCode !== "auto" ? outputLanguageCode : getReliableLanguageCode(transcriptResult?.track) ?? getReliableLanguageCode(getReadyTextTracks(assetData)[0]);
|
|
@@ -6514,7 +6695,13 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
6514
6695
|
workflowCredentials
|
|
6515
6696
|
);
|
|
6516
6697
|
} else {
|
|
6517
|
-
const storyboardUrl = await getStoryboardUrl(
|
|
6698
|
+
const storyboardUrl = await getStoryboardUrl(
|
|
6699
|
+
playbackId,
|
|
6700
|
+
640,
|
|
6701
|
+
policy === "signed",
|
|
6702
|
+
workflowCredentials,
|
|
6703
|
+
storyboardScope
|
|
6704
|
+
);
|
|
6518
6705
|
imageUrl = storyboardUrl;
|
|
6519
6706
|
if (imageSubmissionMode === "base64") {
|
|
6520
6707
|
const downloadResult = await downloadImageAsBase64(storyboardUrl, imageDownloadOptions);
|
|
@@ -6729,9 +6916,16 @@ async function checkElevenLabsDubbingStatus({
|
|
|
6729
6916
|
targetLanguages: statusData.target_languages ?? []
|
|
6730
6917
|
};
|
|
6731
6918
|
}
|
|
6732
|
-
async function
|
|
6919
|
+
async function downloadAndUploadDubbedAudio({
|
|
6733
6920
|
dubbingId,
|
|
6734
6921
|
languageCode,
|
|
6922
|
+
assetId,
|
|
6923
|
+
toLanguageCode,
|
|
6924
|
+
s3Endpoint,
|
|
6925
|
+
s3Region,
|
|
6926
|
+
s3Bucket,
|
|
6927
|
+
storageAdapter,
|
|
6928
|
+
s3SignedUrlExpirySeconds,
|
|
6735
6929
|
credentials
|
|
6736
6930
|
}) {
|
|
6737
6931
|
"use step";
|
|
@@ -6745,19 +6939,7 @@ async function downloadDubbedAudioFromElevenLabs({
|
|
|
6745
6939
|
if (!audioResponse.ok) {
|
|
6746
6940
|
throw new Error(`Failed to fetch dubbed audio: ${audioResponse.statusText}`);
|
|
6747
6941
|
}
|
|
6748
|
-
|
|
6749
|
-
}
|
|
6750
|
-
async function uploadDubbedAudioToS3({
|
|
6751
|
-
dubbedAudioBuffer,
|
|
6752
|
-
assetId,
|
|
6753
|
-
toLanguageCode,
|
|
6754
|
-
s3Endpoint,
|
|
6755
|
-
s3Region,
|
|
6756
|
-
s3Bucket,
|
|
6757
|
-
storageAdapter,
|
|
6758
|
-
s3SignedUrlExpirySeconds
|
|
6759
|
-
}) {
|
|
6760
|
-
"use step";
|
|
6942
|
+
const dubbedAudio = new Uint8Array(await audioResponse.arrayBuffer());
|
|
6761
6943
|
const s3AccessKeyId = env_default.S3_ACCESS_KEY_ID;
|
|
6762
6944
|
const s3SecretAccessKey = env_default.S3_SECRET_ACCESS_KEY;
|
|
6763
6945
|
const audioKey = `audio-translations/${assetId}/auto-to-${toLanguageCode}-${Date.now()}.m4a`;
|
|
@@ -6768,7 +6950,7 @@ async function uploadDubbedAudioToS3({
|
|
|
6768
6950
|
region: s3Region,
|
|
6769
6951
|
bucket: s3Bucket,
|
|
6770
6952
|
key: audioKey,
|
|
6771
|
-
body:
|
|
6953
|
+
body: dubbedAudio,
|
|
6772
6954
|
contentType: "audio/mp4"
|
|
6773
6955
|
}, storageAdapter);
|
|
6774
6956
|
const presignedUrl = await createPresignedGetUrlWithStorageAdapter({
|
|
@@ -6902,46 +7084,24 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
6902
7084
|
let presignedUrl;
|
|
6903
7085
|
let uploadedTrackId;
|
|
6904
7086
|
if (uploadToS3) {
|
|
6905
|
-
console.warn("\u{1F4E5} Downloading dubbed audio from ElevenLabs...");
|
|
6906
|
-
let dubbedAudioBuffer;
|
|
7087
|
+
console.warn("\u{1F4E5} Downloading dubbed audio from ElevenLabs and staging to S3...");
|
|
6907
7088
|
try {
|
|
6908
|
-
const
|
|
6909
|
-
|
|
6910
|
-
(lang) => lang === requestedLangCode
|
|
6911
|
-
) ?? targetLanguages.find(
|
|
6912
|
-
(lang) => lang.toLowerCase() === requestedLangCode.toLowerCase()
|
|
6913
|
-
);
|
|
6914
|
-
if (!downloadLangCode && targetLanguages.length > 0) {
|
|
6915
|
-
downloadLangCode = targetLanguages[0];
|
|
6916
|
-
console.warn(`\u26A0\uFE0F Requested language "${requestedLangCode}" not found in target_languages. Using "${downloadLangCode}" instead.`);
|
|
6917
|
-
}
|
|
6918
|
-
if (!downloadLangCode) {
|
|
6919
|
-
downloadLangCode = requestedLangCode;
|
|
6920
|
-
console.warn(`\u26A0\uFE0F No target_languages available from ElevenLabs status. Using requested language code: ${requestedLangCode}`);
|
|
6921
|
-
}
|
|
6922
|
-
dubbedAudioBuffer = await downloadDubbedAudioFromElevenLabs({
|
|
7089
|
+
const downloadLangCode = targetLanguages[0] ?? toISO639_1(toLanguageCode);
|
|
7090
|
+
presignedUrl = await downloadAndUploadDubbedAudio({
|
|
6923
7091
|
dubbingId,
|
|
6924
7092
|
languageCode: downloadLangCode,
|
|
6925
|
-
credentials
|
|
6926
|
-
});
|
|
6927
|
-
console.warn("\u2705 Dubbed audio downloaded successfully!");
|
|
6928
|
-
} catch (error) {
|
|
6929
|
-
wrapError(error, "Failed to download dubbed audio");
|
|
6930
|
-
}
|
|
6931
|
-
console.warn("\u{1F4E4} Uploading dubbed audio to S3-compatible storage...");
|
|
6932
|
-
try {
|
|
6933
|
-
presignedUrl = await uploadDubbedAudioToS3({
|
|
6934
|
-
dubbedAudioBuffer,
|
|
6935
7093
|
assetId,
|
|
6936
7094
|
toLanguageCode,
|
|
6937
7095
|
s3Endpoint,
|
|
6938
7096
|
s3Region,
|
|
6939
7097
|
s3Bucket,
|
|
6940
7098
|
storageAdapter: effectiveStorageAdapter,
|
|
6941
|
-
s3SignedUrlExpirySeconds: options.s3SignedUrlExpirySeconds
|
|
7099
|
+
s3SignedUrlExpirySeconds: options.s3SignedUrlExpirySeconds,
|
|
7100
|
+
credentials
|
|
6942
7101
|
});
|
|
7102
|
+
console.warn("\u2705 Dubbed audio staged to S3 successfully!");
|
|
6943
7103
|
} catch (error) {
|
|
6944
|
-
wrapError(error, "Failed to upload audio
|
|
7104
|
+
wrapError(error, "Failed to download and upload dubbed audio");
|
|
6945
7105
|
}
|
|
6946
7106
|
if (uploadToMux) {
|
|
6947
7107
|
console.warn("\u{1F4F9} Adding dubbed audio track to Mux asset...");
|