@mux/ai 0.28.1 → 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 +232 -45
- 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 +230 -44
- package/dist/workflows/index.js.map +1 -1
- package/package.json +7 -1
|
@@ -97,6 +97,24 @@ interface MuxAIOptions {
|
|
|
97
97
|
/** Optional storage adapter for upload and presigning operations. */
|
|
98
98
|
storageAdapter?: StorageAdapter;
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Asset-relative time range used to limit workflow analysis.
|
|
102
|
+
*
|
|
103
|
+
* Times are expressed in seconds. The start is inclusive and the end is
|
|
104
|
+
* exclusive. Either boundary may be omitted to analyze from the beginning or
|
|
105
|
+
* through the end of the asset, respectively.
|
|
106
|
+
*/
|
|
107
|
+
interface WorkflowScope {
|
|
108
|
+
/** Inclusive start offset from the beginning of the asset, in seconds. */
|
|
109
|
+
startTime?: number;
|
|
110
|
+
/** Exclusive end offset from the beginning of the asset, in seconds. */
|
|
111
|
+
endTime?: number;
|
|
112
|
+
}
|
|
113
|
+
/** Base options for workflows that can analyze a bounded part of an asset. */
|
|
114
|
+
interface ScopedMuxAIOptions extends MuxAIOptions {
|
|
115
|
+
/** Optional asset-relative time range to analyze. */
|
|
116
|
+
scope?: WorkflowScope;
|
|
117
|
+
}
|
|
100
118
|
/**
|
|
101
119
|
* Workflow credentials.
|
|
102
120
|
*
|
|
@@ -356,4 +374,4 @@ declare function getShotsForAsset(assetId: string, options?: ShotRequestOptions)
|
|
|
356
374
|
*/
|
|
357
375
|
declare function waitForShotsForAsset(assetId: string, options?: WaitForShotsOptions): Promise<CompletedShotsResult>;
|
|
358
376
|
|
|
359
|
-
export { type AssetTextTrack as A,
|
|
377
|
+
export { type AssetTextTrack as A, getHotspotsForPlaybackId as B, type ChunkEmbedding as C, getHotspotsForVideo as D, type Encrypted as E, getShotsForAsset as F, requestShotsForAsset as G, type Hotspot as H, type ImageSubmissionMode as I, waitForShotsForAsset as J, type MuxAIOptions as M, type PlaybackAsset as P, type StoragePutObjectInput as S, type TextChunk as T, type UsageMetadata as U, type VTTChunkingConfig as V, type WorkflowCredentials as W, type StoragePresignGetObjectInput as a, type ChunkingStrategy as b, type EncryptedPayload as c, type MuxAsset as d, type PlaybackPolicy as e, type ScopedMuxAIOptions as f, type StorageAdapter as g, type TokenChunkingConfig as h, type TokenUsage as i, type ToneType as j, type VideoEmbeddingsResult as k, type WorkflowCredentialsInput as l, type WorkflowMuxClient as m, type WorkflowScope as n, decryptFromWorkflow as o, encryptForWorkflow as p, type CompletedShotsResult as q, type ErroredShotsResult as r, type HotspotOptions as s, type HotspotResponse as t, type PendingShotsResult as u, type Shot as v, type ShotRequestOptions as w, type ShotsResult as x, type WaitForShotsOptions as y, getHotspotsForAsset as z };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { A as AskQuestionsOptions, j as AskQuestionsResult, k as AskQuestionsType, l as AudioTranslationOptions, m as AudioTranslationResult, n as AutoCensorProfanityOptions, B as BurnedInCaptionsAnalysis, o as BurnedInCaptionsOptions, p as BurnedInCaptionsPromptOverrides, q as BurnedInCaptionsPromptSections, r as BurnedInCaptionsResult, C as CaptionReplacement, s as CensorMode, u as Chapter, v as ChapterSystemPromptSections, w as ChaptersOptions, x as ChaptersPromptOverrides, y as ChaptersPromptSections, z as ChaptersResult, D as ChaptersType, E as DEFAULT_DESCRIPTION_LENGTH, F as DEFAULT_SUMMARY_KEYWORD_LIMIT, G as DEFAULT_TITLE_LENGTH, H as EditCaptionsOptions, J as EditCaptionsResult, K as EmbeddingsOptions, M as EmbeddingsResult, N as EngagementInsightsOptions, O as EngagementInsightsResult, P as GOOGLE_VISION_LIKELIHOOD_TO_SCORE, Q as HIVE_SEXUAL_CATEGORIES, R as HIVE_VIOLENCE_CATEGORIES, T as HiveModerationOutput, U as HiveModerationSource, V as ModerationOptions, W as ModerationProvider, X as ModerationResult, Y as MomentInsight, Z as OverallInsight, _ as ProfanityDetectionPayload, $ as Question, a0 as QuestionAnswer, a1 as QuestionAnswerType, a2 as ReplacementRecord, a3 as SummarizationOptions, a4 as SummarizationPromptOverrides, a5 as SummarizationPromptSections, a6 as SummaryAndTagsResult, a7 as SummaryType, a8 as ThumbnailModerationScore, a9 as TranslationChunkingOptions, aa as TranslationOptions, ab as TranslationPayload, ac as TranslationResult, ad as aggregateTokenUsage, ae as applyOverrideLists, af as applyReplacements, ag as askQuestions, ah as buildReplacementRegex, ai as burnedInCaptionsSchema, aj as censorVttContent, ak as chapterSchema, al as chaptersSchema, am as createReplacer, an as editCaptions, ao as extractTranscriptSegmentsForHotspots, ap as generateChapters, aq as generateEmbeddings, ar as generateEngagementInsights, as as generateVideoEmbeddings, at as getModerationScores, au as getSummaryAndTags, av as hasBurnedInCaptions, aw as mapShotsToHotspots, ax as normalizeTranslatedVtt, ay as profanityDetectionSchema, az as questionAnswerSchema, aA as shouldSplitChunkTranslationError, aB as summarySchema, aC as transformCueText, aD as translateAudio, aE as translateCaptions, aF as translationSchema } from '../index-
|
|
1
|
+
export { A as AskQuestionsOptions, j as AskQuestionsResult, k as AskQuestionsType, l as AudioTranslationOptions, m as AudioTranslationResult, n as AutoCensorProfanityOptions, B as BurnedInCaptionsAnalysis, o as BurnedInCaptionsOptions, p as BurnedInCaptionsPromptOverrides, q as BurnedInCaptionsPromptSections, r as BurnedInCaptionsResult, C as CaptionReplacement, s as CensorMode, u as Chapter, v as ChapterSystemPromptSections, w as ChaptersOptions, x as ChaptersPromptOverrides, y as ChaptersPromptSections, z as ChaptersResult, D as ChaptersType, E as DEFAULT_DESCRIPTION_LENGTH, F as DEFAULT_SUMMARY_KEYWORD_LIMIT, G as DEFAULT_TITLE_LENGTH, H as EditCaptionsOptions, J as EditCaptionsResult, K as EmbeddingsOptions, M as EmbeddingsResult, N as EngagementInsightsOptions, O as EngagementInsightsResult, P as GOOGLE_VISION_LIKELIHOOD_TO_SCORE, Q as HIVE_SEXUAL_CATEGORIES, R as HIVE_VIOLENCE_CATEGORIES, T as HiveModerationOutput, U as HiveModerationSource, V as ModerationOptions, W as ModerationProvider, X as ModerationResult, Y as MomentInsight, Z as OverallInsight, _ as ProfanityDetectionPayload, $ as Question, a0 as QuestionAnswer, a1 as QuestionAnswerType, a2 as ReplacementRecord, a3 as SummarizationOptions, a4 as SummarizationPromptOverrides, a5 as SummarizationPromptSections, a6 as SummaryAndTagsResult, a7 as SummaryType, a8 as ThumbnailModerationScore, a9 as TranslationChunkingOptions, aa as TranslationOptions, ab as TranslationPayload, ac as TranslationResult, ad as aggregateTokenUsage, ae as applyOverrideLists, af as applyReplacements, ag as askQuestions, ah as buildReplacementRegex, ai as burnedInCaptionsSchema, aj as censorVttContent, ak as chapterSchema, al as chaptersSchema, am as createReplacer, an as editCaptions, ao as extractTranscriptSegmentsForHotspots, ap as generateChapters, aq as generateEmbeddings, ar as generateEngagementInsights, as as generateVideoEmbeddings, at as getModerationScores, au as getSummaryAndTags, av as hasBurnedInCaptions, aw as mapShotsToHotspots, ax as normalizeTranslatedVtt, ay as profanityDetectionSchema, az as questionAnswerSchema, aA as shouldSplitChunkTranslationError, aB as summarySchema, aC as transformCueText, aD as translateAudio, aE as translateCaptions, aF as translationSchema } from '../index-DJirK4fh.js';
|
|
2
2
|
import 'zod';
|
|
3
3
|
import '@ai-sdk/anthropic';
|
|
4
4
|
import '@ai-sdk/google';
|
|
5
5
|
import '@ai-sdk/openai';
|
|
6
|
-
import '../shots-
|
|
6
|
+
import '../shots-BKd7TMFN.js';
|
|
7
7
|
import '@mux/mux-node';
|
package/dist/workflows/index.js
CHANGED
|
@@ -1298,6 +1298,68 @@ async function withRetry(fn, {
|
|
|
1298
1298
|
throw lastError || new Error("Retry failed with unknown error");
|
|
1299
1299
|
}
|
|
1300
1300
|
|
|
1301
|
+
// src/lib/workflow-scope.ts
|
|
1302
|
+
function hasWorkflowScopeBoundaries(scope) {
|
|
1303
|
+
return scope?.startTime !== void 0 || scope?.endTime !== void 0;
|
|
1304
|
+
}
|
|
1305
|
+
function resolveWorkflowScope(scope, assetDurationSeconds) {
|
|
1306
|
+
if (assetDurationSeconds === void 0 || !Number.isFinite(assetDurationSeconds) || assetDurationSeconds <= 0) {
|
|
1307
|
+
throw new MuxAiError("Asset has no valid duration.", { type: "validation_error" });
|
|
1308
|
+
}
|
|
1309
|
+
const startTime = scope?.startTime ?? 0;
|
|
1310
|
+
const endTime = scope?.endTime ?? assetDurationSeconds;
|
|
1311
|
+
if (!Number.isFinite(startTime) || startTime < 0) {
|
|
1312
|
+
throw new MuxAiError(
|
|
1313
|
+
`scope.startTime must be a finite, non-negative number (received ${startTime}).`,
|
|
1314
|
+
{ type: "validation_error" }
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
if (!Number.isFinite(endTime) || endTime < 0) {
|
|
1318
|
+
throw new MuxAiError(
|
|
1319
|
+
`scope.endTime must be a finite, non-negative number (received ${endTime}).`,
|
|
1320
|
+
{ type: "validation_error" }
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
if (endTime > assetDurationSeconds) {
|
|
1324
|
+
throw new MuxAiError(
|
|
1325
|
+
`scope.endTime (${endTime}) cannot exceed the asset duration (${assetDurationSeconds}).`,
|
|
1326
|
+
{ type: "validation_error" }
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
if (startTime >= endTime) {
|
|
1330
|
+
throw new MuxAiError(
|
|
1331
|
+
`scope.startTime (${startTime}) must be less than scope.endTime (${endTime}).`,
|
|
1332
|
+
{ type: "validation_error" }
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
return { startTime, endTime };
|
|
1336
|
+
}
|
|
1337
|
+
function resolveRenderableVideoScope(scope, assetDurationSeconds, videoTrackDurationSeconds) {
|
|
1338
|
+
if (!hasWorkflowScopeBoundaries(scope)) {
|
|
1339
|
+
return void 0;
|
|
1340
|
+
}
|
|
1341
|
+
const resolvedScope = resolveWorkflowScope(scope, assetDurationSeconds);
|
|
1342
|
+
let renderableEndTime = resolvedScope.endTime;
|
|
1343
|
+
if (videoTrackDurationSeconds !== void 0 && Number.isFinite(videoTrackDurationSeconds) && videoTrackDurationSeconds >= 0) {
|
|
1344
|
+
renderableEndTime = Math.min(videoTrackDurationSeconds, resolvedScope.endTime);
|
|
1345
|
+
}
|
|
1346
|
+
const renderableScope = {
|
|
1347
|
+
startTime: Math.min(resolvedScope.startTime, renderableEndTime),
|
|
1348
|
+
endTime: renderableEndTime
|
|
1349
|
+
};
|
|
1350
|
+
if (renderableScope.startTime >= renderableScope.endTime) {
|
|
1351
|
+
throw new MuxAiError("The requested scope does not include any renderable video.", {
|
|
1352
|
+
type: "validation_error"
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
return renderableScope;
|
|
1356
|
+
}
|
|
1357
|
+
function timeRangesOverlap(firstStart, firstEnd, second) {
|
|
1358
|
+
const secondStart = second.startTime ?? 0;
|
|
1359
|
+
const secondEnd = second.endTime ?? Number.POSITIVE_INFINITY;
|
|
1360
|
+
return firstStart < secondEnd && firstEnd > secondStart;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1301
1363
|
// src/lib/mux-url.ts
|
|
1302
1364
|
var DEFAULT_MUX_IMAGE_ORIGIN = "https://image.mux.com";
|
|
1303
1365
|
var DEFAULT_MUX_STREAM_ORIGIN = "https://stream.mux.com";
|
|
@@ -1384,13 +1446,22 @@ async function signUrl(url, playbackId, type = "video", params, credentials) {
|
|
|
1384
1446
|
|
|
1385
1447
|
// src/primitives/storyboards.ts
|
|
1386
1448
|
var DEFAULT_STORYBOARD_WIDTH = 640;
|
|
1387
|
-
async function getStoryboardUrl(playbackId, width = DEFAULT_STORYBOARD_WIDTH, shouldSign = false, credentials) {
|
|
1449
|
+
async function getStoryboardUrl(playbackId, width = DEFAULT_STORYBOARD_WIDTH, shouldSign = false, credentials, scope) {
|
|
1388
1450
|
"use step";
|
|
1389
1451
|
const baseUrl = getMuxStoryboardBaseUrl(playbackId);
|
|
1452
|
+
const params = { width };
|
|
1453
|
+
if (scope?.startTime !== void 0) {
|
|
1454
|
+
params.asset_start_time = scope.startTime;
|
|
1455
|
+
}
|
|
1456
|
+
if (scope?.endTime !== void 0) {
|
|
1457
|
+
params.asset_end_time = scope.endTime;
|
|
1458
|
+
}
|
|
1390
1459
|
if (shouldSign) {
|
|
1391
|
-
return signUrl(baseUrl, playbackId, "storyboard",
|
|
1460
|
+
return signUrl(baseUrl, playbackId, "storyboard", params, credentials);
|
|
1392
1461
|
}
|
|
1393
|
-
return `${baseUrl}
|
|
1462
|
+
return `${baseUrl}?${new URLSearchParams(
|
|
1463
|
+
Object.entries(params).map(([key, value]) => [key, String(value)])
|
|
1464
|
+
).toString()}`;
|
|
1394
1465
|
}
|
|
1395
1466
|
|
|
1396
1467
|
// src/lib/language-codes.ts
|
|
@@ -1829,6 +1900,23 @@ function parseVTTCues(vttContent) {
|
|
|
1829
1900
|
}
|
|
1830
1901
|
return cues;
|
|
1831
1902
|
}
|
|
1903
|
+
function filterVttByScope(vttContent, scope) {
|
|
1904
|
+
if (!scope || scope.startTime === void 0 && scope.endTime === void 0) {
|
|
1905
|
+
return vttContent;
|
|
1906
|
+
}
|
|
1907
|
+
const { preamble, cueBlocks } = splitVttPreambleAndCueBlocks(vttContent);
|
|
1908
|
+
const scopedCueBlocks = cueBlocks.filter((cueBlock) => {
|
|
1909
|
+
const cue = parseVTTCues(cueBlock)[0];
|
|
1910
|
+
return cue ? timeRangesOverlap(cue.startTime, cue.endTime, scope) : false;
|
|
1911
|
+
}).map((cueBlock, index) => {
|
|
1912
|
+
const lines = cueBlock.split("\n");
|
|
1913
|
+
if (/^\d+$/.test(lines[0]?.trim()) && lines[1]?.includes("-->")) {
|
|
1914
|
+
lines[0] = String(index + 1);
|
|
1915
|
+
}
|
|
1916
|
+
return lines.join("\n");
|
|
1917
|
+
});
|
|
1918
|
+
return buildVttFromCueBlocks(scopedCueBlocks, preamble);
|
|
1919
|
+
}
|
|
1832
1920
|
function splitVttPreambleAndCueBlocks(vttContent) {
|
|
1833
1921
|
const normalizedContent = normalizeLineEndings(vttContent).trim();
|
|
1834
1922
|
if (!normalizedContent) {
|
|
@@ -1933,6 +2021,7 @@ async function fetchTranscriptForAsset(asset, playbackId, options = {}) {
|
|
|
1933
2021
|
const {
|
|
1934
2022
|
languageCode,
|
|
1935
2023
|
cleanTranscript = true,
|
|
2024
|
+
scope,
|
|
1936
2025
|
shouldSign,
|
|
1937
2026
|
credentials,
|
|
1938
2027
|
required = false
|
|
@@ -1964,9 +2053,15 @@ async function fetchTranscriptForAsset(asset, playbackId, options = {}) {
|
|
|
1964
2053
|
return { transcriptText: "", transcriptUrl, track };
|
|
1965
2054
|
}
|
|
1966
2055
|
const rawVtt = await response.text();
|
|
1967
|
-
const
|
|
1968
|
-
|
|
1969
|
-
|
|
2056
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
2057
|
+
const scopedVtt = filterVttByScope(rawVtt, effectiveScope);
|
|
2058
|
+
const transcriptText = cleanTranscript ? extractTextFromVTT(scopedVtt) : scopedVtt;
|
|
2059
|
+
const hasScopedCues = !effectiveScope || parseVTTCues(scopedVtt).length > 0;
|
|
2060
|
+
if (required && (!transcriptText.trim() || !hasScopedCues)) {
|
|
2061
|
+
throw new MuxAiError(
|
|
2062
|
+
effectiveScope ? "Transcript has no cues in the requested scope." : "Transcript is empty.",
|
|
2063
|
+
{ type: "validation_error" }
|
|
2064
|
+
);
|
|
1970
2065
|
}
|
|
1971
2066
|
return { transcriptText, transcriptUrl, track };
|
|
1972
2067
|
} catch (error) {
|
|
@@ -2476,7 +2571,8 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
|
|
|
2476
2571
|
imageSubmissionMode = "url",
|
|
2477
2572
|
imageDownloadOptions,
|
|
2478
2573
|
storyboardWidth = 640,
|
|
2479
|
-
credentials
|
|
2574
|
+
credentials,
|
|
2575
|
+
scope
|
|
2480
2576
|
} = options ?? {};
|
|
2481
2577
|
const normalizedQuestions = questions.map((q, idx) => normalizeQuestion(q, idx));
|
|
2482
2578
|
const hasFreeForm = normalizedQuestions.some((q) => q.mode.kind === "freeForm");
|
|
@@ -2488,6 +2584,15 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
|
|
|
2488
2584
|
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
|
|
2489
2585
|
const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
|
|
2490
2586
|
const isAudioOnly = isAudioOnlyAsset(assetData);
|
|
2587
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
2588
|
+
const storyboardScope = isAudioOnly ? void 0 : resolveRenderableVideoScope(
|
|
2589
|
+
effectiveScope,
|
|
2590
|
+
assetDurationSeconds,
|
|
2591
|
+
getVideoTrackDurationSecondsFromAsset(assetData)
|
|
2592
|
+
);
|
|
2593
|
+
if (isAudioOnly && effectiveScope) {
|
|
2594
|
+
resolveWorkflowScope(effectiveScope, assetDurationSeconds);
|
|
2595
|
+
}
|
|
2491
2596
|
if (isAudioOnly && !includeTranscript) {
|
|
2492
2597
|
throw new MuxAiError(
|
|
2493
2598
|
"Audio-only assets require a transcript. Set includeTranscript: true and ensure the asset has a ready text track (captions/subtitles).",
|
|
@@ -2506,7 +2611,8 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
|
|
|
2506
2611
|
cleanTranscript,
|
|
2507
2612
|
shouldSign: policy === "signed",
|
|
2508
2613
|
credentials,
|
|
2509
|
-
required: isAudioOnly
|
|
2614
|
+
required: isAudioOnly,
|
|
2615
|
+
scope: effectiveScope
|
|
2510
2616
|
}) : void 0;
|
|
2511
2617
|
const transcriptText = transcriptResult?.transcriptText ?? "";
|
|
2512
2618
|
const userPrompt = buildUserPrompt({
|
|
@@ -2535,7 +2641,8 @@ async function askQuestionsInternal(assetId, questions, options, collectedUsage)
|
|
|
2535
2641
|
playbackId,
|
|
2536
2642
|
storyboardWidth,
|
|
2537
2643
|
policy === "signed",
|
|
2538
|
-
credentials
|
|
2644
|
+
credentials,
|
|
2645
|
+
storyboardScope
|
|
2539
2646
|
);
|
|
2540
2647
|
imageUrl = storyboardUrl;
|
|
2541
2648
|
if (imageSubmissionMode === "base64") {
|
|
@@ -2818,6 +2925,7 @@ async function hasBurnedInCaptionsInternal(assetId, options, collectedUsage) {
|
|
|
2818
2925
|
imageDownloadOptions,
|
|
2819
2926
|
promptOverrides,
|
|
2820
2927
|
credentials,
|
|
2928
|
+
scope,
|
|
2821
2929
|
...config
|
|
2822
2930
|
} = options;
|
|
2823
2931
|
const userPrompt = buildUserPrompt2(promptOverrides);
|
|
@@ -2828,7 +2936,18 @@ async function hasBurnedInCaptionsInternal(assetId, options, collectedUsage) {
|
|
|
2828
2936
|
});
|
|
2829
2937
|
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
|
|
2830
2938
|
const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
|
|
2831
|
-
const
|
|
2939
|
+
const storyboardScope = resolveRenderableVideoScope(
|
|
2940
|
+
scope,
|
|
2941
|
+
assetDurationSeconds,
|
|
2942
|
+
getVideoTrackDurationSecondsFromAsset(assetData)
|
|
2943
|
+
);
|
|
2944
|
+
const imageUrl = await getStoryboardUrl(
|
|
2945
|
+
playbackId,
|
|
2946
|
+
640,
|
|
2947
|
+
policy === "signed",
|
|
2948
|
+
credentials,
|
|
2949
|
+
storyboardScope
|
|
2950
|
+
);
|
|
2832
2951
|
let analysisResponse;
|
|
2833
2952
|
if (imageSubmissionMode === "base64") {
|
|
2834
2953
|
const base64Data = await fetchImageAsBase642(imageUrl, imageDownloadOptions);
|
|
@@ -3027,7 +3146,8 @@ function buildUserPrompt3({
|
|
|
3027
3146
|
promptOverrides,
|
|
3028
3147
|
minChaptersPerHour = 3,
|
|
3029
3148
|
maxChaptersPerHour = 8,
|
|
3030
|
-
languageName
|
|
3149
|
+
languageName,
|
|
3150
|
+
scope
|
|
3031
3151
|
}) {
|
|
3032
3152
|
const contextSections = [
|
|
3033
3153
|
{
|
|
@@ -3042,6 +3162,8 @@ function buildUserPrompt3({
|
|
|
3042
3162
|
const dynamicChapterGuidelines = dedent4`
|
|
3043
3163
|
- Create at least ${minChaptersPerHour} and at most ${maxChaptersPerHour} chapters per hour of content
|
|
3044
3164
|
- Use start times in seconds (not HH:MM:SS)
|
|
3165
|
+
${scope ? `- Analyze only the range from ${scope.startTime}s (inclusive) to ${scope.endTime}s (exclusive)
|
|
3166
|
+
- The first chapter must start at ${scope.startTime}s` : ""}
|
|
3045
3167
|
- Chapter start times should be non-decreasing
|
|
3046
3168
|
- Do not include text before or after the JSON`;
|
|
3047
3169
|
const mergedOverrides = {
|
|
@@ -3068,7 +3190,8 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
3068
3190
|
minChaptersPerHour,
|
|
3069
3191
|
maxChaptersPerHour,
|
|
3070
3192
|
credentials,
|
|
3071
|
-
outputLanguageCode
|
|
3193
|
+
outputLanguageCode,
|
|
3194
|
+
scope
|
|
3072
3195
|
} = options;
|
|
3073
3196
|
const modelConfig = resolveLanguageModelConfig({
|
|
3074
3197
|
...options,
|
|
@@ -3077,6 +3200,8 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
3077
3200
|
});
|
|
3078
3201
|
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
|
|
3079
3202
|
const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
|
|
3203
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
3204
|
+
const resolvedScope = effectiveScope ? resolveWorkflowScope(effectiveScope, assetDurationSeconds) : void 0;
|
|
3080
3205
|
const isAudioOnly = isAudioOnlyAsset(assetData);
|
|
3081
3206
|
const signingContext = await resolveMuxSigningContext(credentials);
|
|
3082
3207
|
if (policy === "signed" && !signingContext) {
|
|
@@ -3091,7 +3216,8 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
3091
3216
|
cleanTranscript: false,
|
|
3092
3217
|
// keep timestamps for chapter segmentation
|
|
3093
3218
|
shouldSign: policy === "signed",
|
|
3094
|
-
credentials
|
|
3219
|
+
credentials,
|
|
3220
|
+
scope: effectiveScope
|
|
3095
3221
|
});
|
|
3096
3222
|
if (!transcriptResult.track || !transcriptResult.transcriptText) {
|
|
3097
3223
|
const availableLanguages = readyTextTracks.map((t) => t.language_code).filter(Boolean).join(", ");
|
|
@@ -3112,7 +3238,8 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
3112
3238
|
promptOverrides,
|
|
3113
3239
|
minChaptersPerHour,
|
|
3114
3240
|
maxChaptersPerHour,
|
|
3115
|
-
languageName
|
|
3241
|
+
languageName,
|
|
3242
|
+
scope: resolvedScope
|
|
3116
3243
|
});
|
|
3117
3244
|
let chaptersData = null;
|
|
3118
3245
|
try {
|
|
@@ -3134,7 +3261,9 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
3134
3261
|
throw new MuxAiError(`Failed to generate chapters for asset ${assetId}.`);
|
|
3135
3262
|
}
|
|
3136
3263
|
const { chapters: chaptersPayload, usage } = chaptersData;
|
|
3137
|
-
const validChapters = chaptersPayload.chapters.filter((chapter) => typeof chapter.startTime === "number" && typeof chapter.title === "string").
|
|
3264
|
+
const validChapters = chaptersPayload.chapters.filter((chapter) => typeof chapter.startTime === "number" && typeof chapter.title === "string").filter(
|
|
3265
|
+
(chapter) => resolvedScope ? chapter.startTime >= resolvedScope.startTime && chapter.startTime < resolvedScope.endTime : true
|
|
3266
|
+
).sort((a, b) => a.startTime - b.startTime);
|
|
3138
3267
|
if (validChapters.length === 0) {
|
|
3139
3268
|
throw new MuxAiError(`Failed to generate valid chapters for asset ${assetId}.`);
|
|
3140
3269
|
}
|
|
@@ -3160,8 +3289,9 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
3160
3289
|
if (scrubbedChapters.length === 0) {
|
|
3161
3290
|
throw new MuxAiError(`Failed to generate valid chapters for asset ${assetId}.`);
|
|
3162
3291
|
}
|
|
3163
|
-
|
|
3164
|
-
|
|
3292
|
+
const firstChapterStartTime = resolvedScope?.startTime ?? 0;
|
|
3293
|
+
if (scrubbedChapters[0].startTime !== firstChapterStartTime) {
|
|
3294
|
+
scrubbedChapters[0].startTime = firstChapterStartTime;
|
|
3165
3295
|
}
|
|
3166
3296
|
const usageWithMetadata = {
|
|
3167
3297
|
...usage,
|
|
@@ -4192,11 +4322,16 @@ async function generateEmbeddingsInternal(assetId, options, collectedUsage) {
|
|
|
4192
4322
|
languageCode,
|
|
4193
4323
|
chunkingStrategy = { type: "token", maxTokens: 500, overlap: 100 },
|
|
4194
4324
|
batchSize = 5,
|
|
4195
|
-
credentials
|
|
4325
|
+
credentials,
|
|
4326
|
+
scope
|
|
4196
4327
|
} = options;
|
|
4197
4328
|
const embeddingModel = resolveEmbeddingModelConfig({ ...options, provider, model });
|
|
4198
4329
|
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
|
|
4199
4330
|
const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
|
|
4331
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
4332
|
+
if (effectiveScope) {
|
|
4333
|
+
resolveWorkflowScope(effectiveScope, assetDurationSeconds);
|
|
4334
|
+
}
|
|
4200
4335
|
const signingContext = await resolveMuxSigningContext(credentials);
|
|
4201
4336
|
if (policy === "signed" && !signingContext) {
|
|
4202
4337
|
throw new Error(
|
|
@@ -4209,7 +4344,8 @@ async function generateEmbeddingsInternal(assetId, options, collectedUsage) {
|
|
|
4209
4344
|
cleanTranscript: !useVttChunking,
|
|
4210
4345
|
shouldSign: policy === "signed",
|
|
4211
4346
|
credentials,
|
|
4212
|
-
required: true
|
|
4347
|
+
required: true,
|
|
4348
|
+
scope: effectiveScope
|
|
4213
4349
|
});
|
|
4214
4350
|
const transcriptText = transcriptResult.transcriptText;
|
|
4215
4351
|
const chunks = useVttChunking ? chunkVTTCues(
|
|
@@ -5048,27 +5184,32 @@ function planSamplingTimestamps(options) {
|
|
|
5048
5184
|
// src/primitives/thumbnails.ts
|
|
5049
5185
|
async function getThumbnailUrls(playbackId, duration, options = {}) {
|
|
5050
5186
|
"use step";
|
|
5051
|
-
const { interval = 10, width = 640, shouldSign = false, maxSamples, credentials } = options;
|
|
5187
|
+
const { interval = 10, width = 640, shouldSign = false, maxSamples, credentials, scope } = options;
|
|
5188
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
5189
|
+
const resolvedScope = effectiveScope ? resolveWorkflowScope(effectiveScope, duration) : { startTime: 0, endTime: duration };
|
|
5190
|
+
const rangeDuration = resolvedScope.endTime - resolvedScope.startTime;
|
|
5052
5191
|
let timestamps = [];
|
|
5053
|
-
if (
|
|
5054
|
-
const spacing =
|
|
5192
|
+
if (rangeDuration <= 50) {
|
|
5193
|
+
const spacing = rangeDuration / 6;
|
|
5055
5194
|
for (let i = 1; i <= 5; i++) {
|
|
5056
|
-
|
|
5195
|
+
const time = resolvedScope.startTime + i * spacing;
|
|
5196
|
+
timestamps.push(effectiveScope ? Number(time.toFixed(3)) : Math.round(time));
|
|
5057
5197
|
}
|
|
5058
5198
|
} else {
|
|
5059
|
-
for (let time =
|
|
5199
|
+
for (let time = resolvedScope.startTime; time < resolvedScope.endTime; time += interval) {
|
|
5060
5200
|
timestamps.push(time);
|
|
5061
5201
|
}
|
|
5062
5202
|
}
|
|
5063
5203
|
if (maxSamples !== void 0 && timestamps.length > maxSamples) {
|
|
5064
5204
|
const newTimestamps = [];
|
|
5065
|
-
newTimestamps.push(
|
|
5205
|
+
newTimestamps.push(resolvedScope.startTime);
|
|
5066
5206
|
if (maxSamples >= 2) {
|
|
5067
|
-
const
|
|
5207
|
+
const lastTime = effectiveScope ? Math.max(resolvedScope.startTime, resolvedScope.endTime - 1e-3) : resolvedScope.endTime;
|
|
5208
|
+
const spacing = (lastTime - resolvedScope.startTime) / (maxSamples - 1);
|
|
5068
5209
|
for (let i = 1; i < maxSamples - 1; i++) {
|
|
5069
|
-
newTimestamps.push(spacing * i);
|
|
5210
|
+
newTimestamps.push(resolvedScope.startTime + spacing * i);
|
|
5070
5211
|
}
|
|
5071
|
-
newTimestamps.push(
|
|
5212
|
+
newTimestamps.push(lastTime);
|
|
5072
5213
|
}
|
|
5073
5214
|
timestamps = newTimestamps;
|
|
5074
5215
|
}
|
|
@@ -5521,12 +5662,24 @@ async function requestGoogleVisionModeration(images, maxConcurrent = 5, submissi
|
|
|
5521
5662
|
}));
|
|
5522
5663
|
return processConcurrently(targets, moderateImageWithGoogleVision, maxConcurrent);
|
|
5523
5664
|
}
|
|
5665
|
+
function toThumbnailTimeSeconds(timestampMs) {
|
|
5666
|
+
return Number((timestampMs / 1e3).toFixed(2));
|
|
5667
|
+
}
|
|
5668
|
+
function filterTimestampsWithinExclusiveScope(timestampsMs, scope) {
|
|
5669
|
+
if (!scope) {
|
|
5670
|
+
return timestampsMs;
|
|
5671
|
+
}
|
|
5672
|
+
return timestampsMs.filter((timestampMs) => {
|
|
5673
|
+
const time = toThumbnailTimeSeconds(timestampMs);
|
|
5674
|
+
return time >= scope.startTime && time < scope.endTime;
|
|
5675
|
+
});
|
|
5676
|
+
}
|
|
5524
5677
|
async function getThumbnailUrlsFromTimestamps(playbackId, timestampsMs, options) {
|
|
5525
5678
|
"use step";
|
|
5526
5679
|
const { width, shouldSign, credentials } = options;
|
|
5527
5680
|
const baseUrl = getMuxThumbnailBaseUrl(playbackId);
|
|
5528
5681
|
const urlPromises = timestampsMs.map(async (tsMs) => {
|
|
5529
|
-
const time =
|
|
5682
|
+
const time = toThumbnailTimeSeconds(tsMs);
|
|
5530
5683
|
const url = shouldSign ? await signUrl(baseUrl, playbackId, "thumbnail", { time, width }, credentials) : `${baseUrl}?time=${time}&width=${width}`;
|
|
5531
5684
|
return { url, time };
|
|
5532
5685
|
});
|
|
@@ -5545,7 +5698,8 @@ async function getModerationScores(assetId, options = {}) {
|
|
|
5545
5698
|
maxConcurrent = 5,
|
|
5546
5699
|
imageSubmissionMode = "url",
|
|
5547
5700
|
imageDownloadOptions,
|
|
5548
|
-
credentials: providedCredentials
|
|
5701
|
+
credentials: providedCredentials,
|
|
5702
|
+
scope
|
|
5549
5703
|
} = options;
|
|
5550
5704
|
const credentials = providedCredentials;
|
|
5551
5705
|
const { asset, playbackId, policy } = await getPlaybackIdForAsset(assetId, credentials);
|
|
@@ -5556,7 +5710,16 @@ async function getModerationScores(assetId, options = {}) {
|
|
|
5556
5710
|
(d) => d != null
|
|
5557
5711
|
);
|
|
5558
5712
|
const duration = candidateDurations.length > 0 ? Math.min(...candidateDurations) : 0;
|
|
5713
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
5714
|
+
const resolvedScope = effectiveScope ? resolveWorkflowScope(effectiveScope, assetDurationSeconds) : void 0;
|
|
5559
5715
|
const isAudioOnly = isAudioOnlyAsset(asset);
|
|
5716
|
+
const renderableScope = resolvedScope ? {
|
|
5717
|
+
startTime: Math.min(resolvedScope.startTime, duration),
|
|
5718
|
+
endTime: Math.min(resolvedScope.endTime, duration)
|
|
5719
|
+
} : void 0;
|
|
5720
|
+
if (!isAudioOnly && renderableScope && renderableScope.startTime >= renderableScope.endTime) {
|
|
5721
|
+
throw new Error("The requested scope does not include any renderable video.");
|
|
5722
|
+
}
|
|
5560
5723
|
const signingContext = await resolveMuxSigningContext(credentials);
|
|
5561
5724
|
if (policy === "signed" && !signingContext) {
|
|
5562
5725
|
throw new Error(
|
|
@@ -5573,7 +5736,8 @@ async function getModerationScores(assetId, options = {}) {
|
|
|
5573
5736
|
cleanTranscript: true,
|
|
5574
5737
|
shouldSign: policy === "signed",
|
|
5575
5738
|
credentials,
|
|
5576
|
-
required: true
|
|
5739
|
+
required: true,
|
|
5740
|
+
scope: effectiveScope
|
|
5577
5741
|
});
|
|
5578
5742
|
if (provider === "openai") {
|
|
5579
5743
|
thumbnailScores = await requestOpenAITranscriptModeration(
|
|
@@ -5591,17 +5755,21 @@ async function getModerationScores(assetId, options = {}) {
|
|
|
5591
5755
|
throw new Error(`Unsupported moderation provider: ${exhaustiveCheck}`);
|
|
5592
5756
|
}
|
|
5593
5757
|
} else {
|
|
5594
|
-
const
|
|
5758
|
+
const scopedDuration = renderableScope ? renderableScope.endTime - renderableScope.startTime : duration;
|
|
5759
|
+
const estimatedIntervalCount = scopedDuration <= 50 ? 5 : Math.ceil(scopedDuration / thumbnailInterval);
|
|
5595
5760
|
const thumbnailUrls = maxSamples !== void 0 && estimatedIntervalCount > maxSamples ? await getThumbnailUrlsFromTimestamps(
|
|
5596
5761
|
playbackId,
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5762
|
+
filterTimestampsWithinExclusiveScope(
|
|
5763
|
+
planSamplingTimestamps({
|
|
5764
|
+
duration_sec: duration,
|
|
5765
|
+
max_candidates: maxSamples,
|
|
5766
|
+
trim_start_sec: renderableScope?.startTime ?? (duration > 2 ? Math.min(5, Math.max(1, duration / 6)) : 0),
|
|
5767
|
+
trim_end_sec: renderableScope ? duration - renderableScope.endTime : duration > 2 ? Math.min(5, Math.max(1, duration / 6)) : 0,
|
|
5768
|
+
fps: videoTrackFps,
|
|
5769
|
+
base_cadence_hz: thumbnailInterval > 0 ? 1 / thumbnailInterval : void 0
|
|
5770
|
+
}),
|
|
5771
|
+
renderableScope
|
|
5772
|
+
),
|
|
5605
5773
|
{
|
|
5606
5774
|
width: thumbnailWidth,
|
|
5607
5775
|
shouldSign: policy === "signed",
|
|
@@ -5611,7 +5779,8 @@ async function getModerationScores(assetId, options = {}) {
|
|
|
5611
5779
|
interval: thumbnailInterval,
|
|
5612
5780
|
width: thumbnailWidth,
|
|
5613
5781
|
shouldSign: policy === "signed",
|
|
5614
|
-
credentials
|
|
5782
|
+
credentials,
|
|
5783
|
+
scope: renderableScope
|
|
5615
5784
|
});
|
|
5616
5785
|
thumbnailCount = thumbnailUrls.length;
|
|
5617
5786
|
if (provider === "openai") {
|
|
@@ -6126,7 +6295,8 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
6126
6295
|
titleLength,
|
|
6127
6296
|
descriptionLength,
|
|
6128
6297
|
tagCount,
|
|
6129
|
-
outputLanguageCode
|
|
6298
|
+
outputLanguageCode,
|
|
6299
|
+
scope
|
|
6130
6300
|
} = options ?? {};
|
|
6131
6301
|
if (!VALID_TONES.includes(tone)) {
|
|
6132
6302
|
throw new MuxAiError(
|
|
@@ -6143,6 +6313,15 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
6143
6313
|
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, workflowCredentials);
|
|
6144
6314
|
const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
|
|
6145
6315
|
const isAudioOnly = isAudioOnlyAsset(assetData);
|
|
6316
|
+
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
6317
|
+
const storyboardScope = isAudioOnly ? void 0 : resolveRenderableVideoScope(
|
|
6318
|
+
effectiveScope,
|
|
6319
|
+
assetDurationSeconds,
|
|
6320
|
+
getVideoTrackDurationSecondsFromAsset(assetData)
|
|
6321
|
+
);
|
|
6322
|
+
if (isAudioOnly && effectiveScope) {
|
|
6323
|
+
resolveWorkflowScope(effectiveScope, assetDurationSeconds);
|
|
6324
|
+
}
|
|
6146
6325
|
if (isAudioOnly && !includeTranscript) {
|
|
6147
6326
|
throw new MuxAiError(
|
|
6148
6327
|
"Audio-only assets require a transcript. Set includeTranscript: true and ensure the asset has a ready text track (captions/subtitles).",
|
|
@@ -6161,7 +6340,8 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
6161
6340
|
cleanTranscript,
|
|
6162
6341
|
shouldSign: policy === "signed",
|
|
6163
6342
|
credentials: workflowCredentials,
|
|
6164
|
-
required: isAudioOnly
|
|
6343
|
+
required: isAudioOnly,
|
|
6344
|
+
scope: effectiveScope
|
|
6165
6345
|
}) : void 0;
|
|
6166
6346
|
const transcriptText = transcriptResult?.transcriptText ?? "";
|
|
6167
6347
|
const resolvedLanguageCode = outputLanguageCode && outputLanguageCode !== "auto" ? outputLanguageCode : getReliableLanguageCode(transcriptResult?.track) ?? getReliableLanguageCode(getReadyTextTracks(assetData)[0]);
|
|
@@ -6192,7 +6372,13 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
6192
6372
|
workflowCredentials
|
|
6193
6373
|
);
|
|
6194
6374
|
} else {
|
|
6195
|
-
const storyboardUrl = await getStoryboardUrl(
|
|
6375
|
+
const storyboardUrl = await getStoryboardUrl(
|
|
6376
|
+
playbackId,
|
|
6377
|
+
640,
|
|
6378
|
+
policy === "signed",
|
|
6379
|
+
workflowCredentials,
|
|
6380
|
+
storyboardScope
|
|
6381
|
+
);
|
|
6196
6382
|
imageUrl = storyboardUrl;
|
|
6197
6383
|
if (imageSubmissionMode === "base64") {
|
|
6198
6384
|
const downloadResult = await downloadImageAsBase64(storyboardUrl, imageDownloadOptions);
|