@mux/ai 0.41.0 → 0.42.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-C3PW73qH.d.ts → index-NkSOF92U.d.ts} +11 -72
- package/dist/index.d.ts +3 -2
- package/dist/index.js +231 -82
- package/dist/index.js.map +1 -1
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/index.js.map +1 -1
- package/dist/prompts/index.d.ts +335 -0
- package/dist/prompts/index.js +490 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/workflows/index.d.ts +2 -1
- package/dist/workflows/index.js +230 -81
- package/dist/workflows/index.js.map +1 -1
- package/package.json +7 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { SafetyReport, PromptOverrides, SceneContextItemV1 } from './prompts/index.js';
|
|
2
3
|
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
3
4
|
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
4
5
|
import { createOpenAI } from '@ai-sdk/openai';
|
|
@@ -314,56 +315,6 @@ interface ImageDownloadOptions {
|
|
|
314
315
|
exponentialBackoff?: boolean;
|
|
315
316
|
}
|
|
316
317
|
|
|
317
|
-
/**
|
|
318
|
-
* Which detector fired on a leak, or `null` when the text is clean.
|
|
319
|
-
*
|
|
320
|
-
* - `canary` — the system-prompt canary token appeared in the output.
|
|
321
|
-
* Near-zero false-positive rate; treat as a confirmed leak.
|
|
322
|
-
* - `prompt_tag` — an XML-like tag taken from our prompt structure
|
|
323
|
-
* appeared in the output. Low false-positive rate.
|
|
324
|
-
* - `encoded_blob` — a long run of base64- or hex-shaped characters
|
|
325
|
-
* appeared in a field expected to hold prose. Heuristic; some false
|
|
326
|
-
* positives on hashes or tokens in tech-content transcripts.
|
|
327
|
-
* - `unexpected_key` — the model emitted a JSON field not declared in
|
|
328
|
-
* the schema (see {@link detectUnexpectedKeys}). Stripped silently
|
|
329
|
-
* by zod's default `.strip()` mode; this signal surfaces the
|
|
330
|
-
* smuggling attempt rather than letting it go unseen.
|
|
331
|
-
*/
|
|
332
|
-
type LeakReason = "canary" | "prompt_tag" | "encoded_blob" | "unexpected_key" | null;
|
|
333
|
-
/**
|
|
334
|
-
* Per-field record of a detected leak. Collected across every scrub
|
|
335
|
-
* performed inside a single workflow invocation and returned to the
|
|
336
|
-
* caller in the workflow result's `safety` field.
|
|
337
|
-
*/
|
|
338
|
-
interface ScrubbedFieldReport {
|
|
339
|
-
/**
|
|
340
|
-
* Identifier for the field that leaked. Format is workflow-specific
|
|
341
|
-
* (e.g. "reasoning[0]", "moment_insight[2]", "description"). Intended
|
|
342
|
-
* for debugging and observability; do not parse programmatically.
|
|
343
|
-
*/
|
|
344
|
-
field: string;
|
|
345
|
-
/** Which detector fired — see {@link LeakReason}. */
|
|
346
|
-
reason: Exclude<LeakReason, null>;
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Aggregate safety report returned from workflows that perform
|
|
350
|
-
* output-side scrubbing.
|
|
351
|
-
*
|
|
352
|
-
* Operators can use this to alert on suspected prompt-injection traffic
|
|
353
|
-
* without inspecting log warnings. Shape is stable across workflows so a
|
|
354
|
-
* single observer can consume it uniformly.
|
|
355
|
-
*/
|
|
356
|
-
interface SafetyReport {
|
|
357
|
-
/** `true` when at least one field was suppressed by the scrubber. */
|
|
358
|
-
leaksDetected: boolean;
|
|
359
|
-
/**
|
|
360
|
-
* One entry per suppressed field. Empty (but present) when no leaks
|
|
361
|
-
* were detected. Iteration order matches the order scrubs ran, so
|
|
362
|
-
* callers can correlate entries with workflow structure if they wish.
|
|
363
|
-
*/
|
|
364
|
-
scrubbedFields: ScrubbedFieldReport[];
|
|
365
|
-
}
|
|
366
|
-
|
|
367
318
|
type SupportedProvider = "openai" | "anthropic" | "google" | "baseten" | "openai-compatible";
|
|
368
319
|
type SupportedEmbeddingProvider = "openai" | "google" | "baseten" | "openai-compatible";
|
|
369
320
|
type OpenAIModelId = Parameters<ReturnType<typeof createOpenAI>["chat"]>[0];
|
|
@@ -629,28 +580,6 @@ type AskQuestionsType = z.infer<AskQuestionsSchema>;
|
|
|
629
580
|
*/
|
|
630
581
|
declare function askQuestions(assetId: string, questions: Question[], options?: AskQuestionsOptions): Promise<AskQuestionsResult>;
|
|
631
582
|
|
|
632
|
-
/**
|
|
633
|
-
* A single section of a prompt, rendered as an XML-like tag.
|
|
634
|
-
*/
|
|
635
|
-
interface PromptSection {
|
|
636
|
-
/** The XML tag name for this section */
|
|
637
|
-
tag: string;
|
|
638
|
-
/** The content inside the tag */
|
|
639
|
-
content: string;
|
|
640
|
-
/** Optional attributes to add to the tag (e.g., { format: "plain text" }) */
|
|
641
|
-
attributes?: Record<string, string>;
|
|
642
|
-
}
|
|
643
|
-
/**
|
|
644
|
-
* Configuration for building a prompt section.
|
|
645
|
-
* Can be a full PromptSection object, just a string (content only), or undefined to use default.
|
|
646
|
-
*/
|
|
647
|
-
type SectionOverride = string | PromptSection | undefined;
|
|
648
|
-
/**
|
|
649
|
-
* User-provided overrides for prompt sections.
|
|
650
|
-
* Each key can override the corresponding section's content or full definition.
|
|
651
|
-
*/
|
|
652
|
-
type PromptOverrides<TSections extends string> = Partial<Record<TSections, SectionOverride>>;
|
|
653
|
-
|
|
654
583
|
/** Structured payload returned from `hasBurnedInCaptions`. */
|
|
655
584
|
interface BurnedInCaptionsResult {
|
|
656
585
|
assetId: string;
|
|
@@ -859,6 +788,11 @@ interface ChaptersOptions extends ScopedMuxAIOptions {
|
|
|
859
788
|
* Falls back to unconstrained (LLM decides) if no language metadata is available.
|
|
860
789
|
*/
|
|
861
790
|
outputLanguageCode?: string;
|
|
791
|
+
/**
|
|
792
|
+
* Ordered scene context used to align chapter starts with scene boundaries.
|
|
793
|
+
* When omitted or empty, chapter generation retains its existing behavior.
|
|
794
|
+
*/
|
|
795
|
+
sceneContext?: readonly SceneContextItemV1[];
|
|
862
796
|
}
|
|
863
797
|
/**
|
|
864
798
|
* Sections of the chaptering system prompt that can be overridden.
|
|
@@ -1409,6 +1343,11 @@ interface SummarizationOptions extends ScopedMuxAIOptions {
|
|
|
1409
1343
|
imageSubmissionMode?: ImageSubmissionMode;
|
|
1410
1344
|
/** Fine-tune storyboard downloads when `imageSubmissionMode` === 'base64'. */
|
|
1411
1345
|
imageDownloadOptions?: ImageDownloadOptions;
|
|
1346
|
+
/**
|
|
1347
|
+
* Ordered, normalized scenes that augment the storyboard and any transcript
|
|
1348
|
+
* with whole-asset progression. Scene context is treated as untrusted evidence.
|
|
1349
|
+
*/
|
|
1350
|
+
sceneContext?: readonly SceneContextItemV1[];
|
|
1412
1351
|
/**
|
|
1413
1352
|
* Override specific sections of the user prompt.
|
|
1414
1353
|
* Useful for customizing the AI's output for specific use cases (SEO, social media, etc.)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
export { I as ISO639_1, a as ISO639_3, L as LanguageCodePair, M as MUX_TRACK_PASSTHROUGH_MAX_CHARS, R as ReplaceAndCreateTextTrackInput, b as ReplaceAndCreateTextTrackResult, c as ReplaceAndCreateTrackInput, d as ReplaceExistingTracksPolicy, e as ReplaceableTrackType, S as SupportedISO639_1, f as SupportedISO639_3, T as TextTrackReplacementPlan, g as TextTrackSummary, h as TextTrackTarget, i as TrackSummary, j as TrackTarget, k as buildMuxAiTrackPassthrough, l as findNameCollisionTextTracks, m as findSameLanguageTextTracks, n as getLanguageCodePair, o as getLanguageName, p as isDuplicateTrackNameError, q as isMuxGeneratedTextTrack, r as isUndeterminedLanguageCode, s as isValidISO639_1, t as isValidISO639_3, u as normalizeTrackLanguageCode, v as normalizeTrackName, w as planTextTrackReplacement, x as replaceAndCreateTextTrack, y as replaceAndCreateTrack, z as summarizeTextTrack, A as toISO639_1, B as toISO639_3, C as validateTrackPassthrough, D as workflows } from './index-
|
|
1
|
+
export { I as ISO639_1, a as ISO639_3, L as LanguageCodePair, M as MUX_TRACK_PASSTHROUGH_MAX_CHARS, R as ReplaceAndCreateTextTrackInput, b as ReplaceAndCreateTextTrackResult, c as ReplaceAndCreateTrackInput, d as ReplaceExistingTracksPolicy, e as ReplaceableTrackType, S as SupportedISO639_1, f as SupportedISO639_3, T as TextTrackReplacementPlan, g as TextTrackSummary, h as TextTrackTarget, i as TrackSummary, j as TrackTarget, k as buildMuxAiTrackPassthrough, l as findNameCollisionTextTracks, m as findSameLanguageTextTracks, n as getLanguageCodePair, o as getLanguageName, p as isDuplicateTrackNameError, q as isMuxGeneratedTextTrack, r as isUndeterminedLanguageCode, s as isValidISO639_1, t as isValidISO639_3, u as normalizeTrackLanguageCode, v as normalizeTrackName, w as planTextTrackReplacement, x as replaceAndCreateTextTrack, y as replaceAndCreateTrack, z as summarizeTextTrack, A as toISO639_1, B as toISO639_3, C as validateTrackPassthrough, D as workflows } from './index-NkSOF92U.js';
|
|
2
2
|
import { W as WorkflowCredentials, S as StoragePutObjectInput, a as StoragePresignGetObjectInput } from './shots-CUI09Ohe.js';
|
|
3
3
|
export { A as AssetTextTrack, C as ChunkEmbedding, b as ChunkingStrategy, E as Encrypted, c as EncryptedPayload, I as ImageSubmissionMode, M as MuxAIOptions, d as MuxAsset, P as PlaybackAsset, e as PlaybackPolicy, f as ScopedMuxAIOptions, g as StorageAdapter, T as TextChunk, h as TokenChunkingConfig, i as TokenUsage, j as ToneType, U as UsageMetadata, V as VTTChunkingConfig, k as VideoEmbeddingsResult, l as WorkflowCredentialsInput, m as WorkflowMuxClient, n as WorkflowScope, o as decryptFromWorkflow, p as encryptForWorkflow } from './shots-CUI09Ohe.js';
|
|
4
4
|
import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from '@workflow/serde';
|
|
5
5
|
export { i as primitives } from './index-C2Pk7bbF.js';
|
|
6
6
|
import 'zod';
|
|
7
|
+
import './prompts/index.js';
|
|
7
8
|
import '@ai-sdk/anthropic';
|
|
8
9
|
import '@ai-sdk/google';
|
|
9
10
|
import '@ai-sdk/openai';
|
|
10
11
|
import '@mux/mux-node';
|
|
11
12
|
|
|
12
|
-
var version = "0.
|
|
13
|
+
var version = "0.42.0";
|
|
13
14
|
|
|
14
15
|
type MuxAiErrorType = "validation_error" | "processing_error" | "timeout_error" | "content_policy_error";
|
|
15
16
|
/**
|
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.42.0";
|
|
9
9
|
|
|
10
10
|
// src/lib/language-codes.ts
|
|
11
11
|
var ISO639_1_TO_3 = {
|
|
@@ -446,6 +446,7 @@ var PROMPT_TAG_NAMES = [
|
|
|
446
446
|
"answer_guidelines",
|
|
447
447
|
"relevance_filtering",
|
|
448
448
|
"transcript_guidance",
|
|
449
|
+
"scene_guidance",
|
|
449
450
|
"capabilities",
|
|
450
451
|
"language_guidelines",
|
|
451
452
|
"tone_guidance",
|
|
@@ -4768,6 +4769,17 @@ var AUDIO_ONLY_SYSTEM_PROMPT_OVERRIDES = {
|
|
|
4768
4769
|
You receive a timestamped transcript from audio-only content with lines in the form "[12s] Transcript text".
|
|
4769
4770
|
Use those timestamps as anchors to determine chapter start times in seconds.`
|
|
4770
4771
|
};
|
|
4772
|
+
var SCENE_CONTEXT_SYSTEM_PROMPT_OVERRIDES = {
|
|
4773
|
+
context: dedent4`
|
|
4774
|
+
You receive a timestamped transcript with lines in the form "[12s] Caption text" and an ordered scene context.
|
|
4775
|
+
Transcript timestamps are in seconds. Scene start and end timestamps are in milliseconds.
|
|
4776
|
+
Use the scene progression to choose logical chapter groupings and scene starts as chapter boundaries.`,
|
|
4777
|
+
constraints: dedent4`
|
|
4778
|
+
- Only use information present in the transcript and scene context
|
|
4779
|
+
- Return structured data that matches the requested JSON schema
|
|
4780
|
+
- Do not add commentary or extra text outside the JSON
|
|
4781
|
+
- When a <language> section is provided, all chapter titles MUST be written in that language`
|
|
4782
|
+
};
|
|
4771
4783
|
var chaptersPromptBuilder = createPromptBuilder({
|
|
4772
4784
|
template: {
|
|
4773
4785
|
task: {
|
|
@@ -4805,6 +4817,7 @@ var chaptersPromptBuilder = createPromptBuilder({
|
|
|
4805
4817
|
});
|
|
4806
4818
|
function buildUserPrompt3({
|
|
4807
4819
|
timestampedTranscript,
|
|
4820
|
+
sceneContext,
|
|
4808
4821
|
promptOverrides,
|
|
4809
4822
|
minChaptersPerHour = 3,
|
|
4810
4823
|
maxChaptersPerHour = 8,
|
|
@@ -4818,14 +4831,31 @@ function buildUserPrompt3({
|
|
|
4818
4831
|
attributes: { format: "seconds" }
|
|
4819
4832
|
}
|
|
4820
4833
|
];
|
|
4834
|
+
if (sceneContext?.length) {
|
|
4835
|
+
contextSections.push({
|
|
4836
|
+
tag: "scene_context",
|
|
4837
|
+
content: JSON.stringify(sceneContext, null, 2),
|
|
4838
|
+
attributes: { timestamps: "milliseconds", trust: "untrusted_evidence" }
|
|
4839
|
+
});
|
|
4840
|
+
}
|
|
4821
4841
|
if (languageName) {
|
|
4822
4842
|
contextSections.push(createLanguageSection(languageName));
|
|
4823
4843
|
}
|
|
4844
|
+
const sceneBoundarySeconds = getSceneBoundarySeconds(sceneContext).filter(
|
|
4845
|
+
(boundary) => scope ? boundary >= scope.startTime && boundary < scope.endTime : true
|
|
4846
|
+
);
|
|
4847
|
+
let firstChapterGuideline = "";
|
|
4848
|
+
if (sceneBoundarySeconds.length > 0) {
|
|
4849
|
+
firstChapterGuideline = `- The first chapter must start at ${sceneBoundarySeconds[0]}s, the first scene boundary inside the analyzed range`;
|
|
4850
|
+
} else if (scope) {
|
|
4851
|
+
firstChapterGuideline = `- The first chapter must start at ${scope.startTime}s`;
|
|
4852
|
+
}
|
|
4824
4853
|
const dynamicChapterGuidelines = dedent4`
|
|
4825
4854
|
- Create at least ${minChaptersPerHour} and at most ${maxChaptersPerHour} chapters per hour of content
|
|
4826
4855
|
- Use start times in seconds (not HH:MM:SS)
|
|
4827
|
-
${scope ? `- Analyze only the range from ${scope.startTime}s (inclusive) to ${scope.endTime}s (exclusive)
|
|
4828
|
-
|
|
4856
|
+
${scope ? `- Analyze only the range from ${scope.startTime}s (inclusive) to ${scope.endTime}s (exclusive)` : ""}
|
|
4857
|
+
${firstChapterGuideline}
|
|
4858
|
+
${sceneContext?.length ? "- Use the ordered scene context to identify chapter-sized runs of consecutive scenes\n- Every chapter startTime must exactly equal a scene start_ms divided by 1000; never split a scene" : ""}
|
|
4829
4859
|
- Chapter start times should be non-decreasing
|
|
4830
4860
|
- Do not include text before or after the JSON`;
|
|
4831
4861
|
const mergedOverrides = {
|
|
@@ -4834,6 +4864,32 @@ function buildUserPrompt3({
|
|
|
4834
4864
|
};
|
|
4835
4865
|
return chaptersPromptBuilder.buildWithContext(mergedOverrides, contextSections);
|
|
4836
4866
|
}
|
|
4867
|
+
function getSceneBoundarySeconds(sceneContext) {
|
|
4868
|
+
if (!sceneContext?.length) {
|
|
4869
|
+
return [];
|
|
4870
|
+
}
|
|
4871
|
+
return [...new Set(sceneContext.map((scene) => scene.start_ms / 1e3))].sort((a, b) => a - b);
|
|
4872
|
+
}
|
|
4873
|
+
function alignChaptersToSceneBoundaries(chapters, sceneBoundarySeconds) {
|
|
4874
|
+
if (sceneBoundarySeconds.length === 0) {
|
|
4875
|
+
return chapters;
|
|
4876
|
+
}
|
|
4877
|
+
const chapterByBoundary = /* @__PURE__ */ new Map();
|
|
4878
|
+
for (const chapter of chapters) {
|
|
4879
|
+
const startTime = sceneBoundarySeconds.reduce(
|
|
4880
|
+
(closest, boundary) => Math.abs(boundary - chapter.startTime) < Math.abs(closest - chapter.startTime) ? boundary : closest
|
|
4881
|
+
);
|
|
4882
|
+
const distance = Math.abs(startTime - chapter.startTime);
|
|
4883
|
+
const existing = chapterByBoundary.get(startTime);
|
|
4884
|
+
if (!existing || distance === 0 && existing.distance !== 0) {
|
|
4885
|
+
chapterByBoundary.set(startTime, {
|
|
4886
|
+
chapter: { ...chapter, startTime },
|
|
4887
|
+
distance
|
|
4888
|
+
});
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
return [...chapterByBoundary.values()].map(({ chapter }) => chapter).sort((a, b) => a.startTime - b.startTime);
|
|
4892
|
+
}
|
|
4837
4893
|
async function generateChapters(assetId, options = {}) {
|
|
4838
4894
|
"use workflow";
|
|
4839
4895
|
const collectedUsage = [];
|
|
@@ -4853,7 +4909,8 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
4853
4909
|
maxChaptersPerHour,
|
|
4854
4910
|
credentials,
|
|
4855
4911
|
outputLanguageCode,
|
|
4856
|
-
scope
|
|
4912
|
+
scope,
|
|
4913
|
+
sceneContext
|
|
4857
4914
|
} = options;
|
|
4858
4915
|
const modelConfig = resolveLanguageModelConfig({
|
|
4859
4916
|
...options,
|
|
@@ -4895,8 +4952,18 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
4895
4952
|
}
|
|
4896
4953
|
const resolvedLanguageCode = outputLanguageCode && outputLanguageCode !== "auto" ? outputLanguageCode : getReliableLanguageCode(transcriptResult.track) ?? languageCode;
|
|
4897
4954
|
const languageName = resolvedLanguageCode ? getLanguageName(resolvedLanguageCode) : void 0;
|
|
4955
|
+
const sceneBoundarySeconds = getSceneBoundarySeconds(sceneContext).filter(
|
|
4956
|
+
(boundary) => resolvedScope ? boundary >= resolvedScope.startTime && boundary < resolvedScope.endTime : true
|
|
4957
|
+
);
|
|
4958
|
+
if (sceneContext?.length && sceneBoundarySeconds.length === 0) {
|
|
4959
|
+
throw new MuxAiError(
|
|
4960
|
+
"Scene context has no chapter boundaries within the requested scope.",
|
|
4961
|
+
{ type: "validation_error" }
|
|
4962
|
+
);
|
|
4963
|
+
}
|
|
4898
4964
|
const userPrompt = buildUserPrompt3({
|
|
4899
4965
|
timestampedTranscript,
|
|
4966
|
+
sceneContext,
|
|
4900
4967
|
promptOverrides,
|
|
4901
4968
|
minChaptersPerHour,
|
|
4902
4969
|
maxChaptersPerHour,
|
|
@@ -4905,7 +4972,11 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
4905
4972
|
});
|
|
4906
4973
|
let chaptersData = null;
|
|
4907
4974
|
try {
|
|
4908
|
-
const
|
|
4975
|
+
const systemPromptOverrides = {
|
|
4976
|
+
...isAudioOnly ? AUDIO_ONLY_SYSTEM_PROMPT_OVERRIDES : {},
|
|
4977
|
+
...sceneContext?.length ? SCENE_CONTEXT_SYSTEM_PROMPT_OVERRIDES : {}
|
|
4978
|
+
};
|
|
4979
|
+
const systemPrompt = chapterSystemPromptBuilder.build(systemPromptOverrides);
|
|
4909
4980
|
chaptersData = await generateChaptersWithAI({
|
|
4910
4981
|
provider: modelConfig.provider,
|
|
4911
4982
|
modelId: modelConfig.modelId,
|
|
@@ -4923,9 +4994,13 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
4923
4994
|
throw new MuxAiError(`Failed to generate chapters for asset ${assetId}.`);
|
|
4924
4995
|
}
|
|
4925
4996
|
const { chapters: chaptersPayload, usage } = chaptersData;
|
|
4926
|
-
const
|
|
4997
|
+
const scopedChapters = chaptersPayload.chapters.filter((chapter) => typeof chapter.startTime === "number" && typeof chapter.title === "string").filter(
|
|
4927
4998
|
(chapter) => resolvedScope ? chapter.startTime >= resolvedScope.startTime && chapter.startTime < resolvedScope.endTime : true
|
|
4928
4999
|
).sort((a, b) => a.startTime - b.startTime);
|
|
5000
|
+
const validChapters = alignChaptersToSceneBoundaries(
|
|
5001
|
+
scopedChapters,
|
|
5002
|
+
sceneBoundarySeconds
|
|
5003
|
+
);
|
|
4929
5004
|
if (validChapters.length === 0) {
|
|
4930
5005
|
throw new MuxAiError(`Failed to generate valid chapters for asset ${assetId}.`);
|
|
4931
5006
|
}
|
|
@@ -4951,9 +5026,11 @@ async function generateChaptersInternal(assetId, options, collectedUsage) {
|
|
|
4951
5026
|
if (scrubbedChapters.length === 0) {
|
|
4952
5027
|
throw new MuxAiError(`Failed to generate valid chapters for asset ${assetId}.`);
|
|
4953
5028
|
}
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
scrubbedChapters[0].startTime
|
|
5029
|
+
if (sceneBoundarySeconds.length === 0) {
|
|
5030
|
+
const firstChapterStartTime = resolvedScope?.startTime ?? 0;
|
|
5031
|
+
if (scrubbedChapters[0].startTime !== firstChapterStartTime) {
|
|
5032
|
+
scrubbedChapters[0].startTime = firstChapterStartTime;
|
|
5033
|
+
}
|
|
4957
5034
|
}
|
|
4958
5035
|
const usageWithMetadata = {
|
|
4959
5036
|
...usage,
|
|
@@ -7060,14 +7137,44 @@ function buildDescriptionGuidance(wordCount, contentType) {
|
|
|
7060
7137
|
Write in present tense. Be specific about what is discussed or presented rather than making assumptions.
|
|
7061
7138
|
Focus on the spoken content and any key insights, dialogue, or narrative elements.`;
|
|
7062
7139
|
}
|
|
7063
|
-
function
|
|
7140
|
+
function buildSceneAwareDescriptionGuidance(wordCount) {
|
|
7141
|
+
if (wordCount < DESCRIPTION_LENGTH_THRESHOLD_SMALL) {
|
|
7142
|
+
return dedent6`A brief summary of the video in no more than ${wordCount} words. Shorter is fine.
|
|
7143
|
+
Focus on the single most important subject or action across the ordered scenes.
|
|
7144
|
+
Use the storyboard as direct visual evidence and the scene context to preserve progression.
|
|
7145
|
+
Write in present tense.`;
|
|
7146
|
+
}
|
|
7147
|
+
if (wordCount > DESCRIPTION_LENGTH_THRESHOLD_LARGE) {
|
|
7148
|
+
return dedent6`A detailed summary that describes what happens across the ordered scenes.
|
|
7149
|
+
Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
|
|
7150
|
+
Be thorough: cover subjects, actions, setting, progression, and notable details supported across scenes.
|
|
7151
|
+
Use the storyboard as direct visual evidence and the scene context to preserve progression.
|
|
7152
|
+
Write in present tense. If the transcript provides dialogue or narration, incorporate key points while preserving scene progression.`;
|
|
7153
|
+
}
|
|
7154
|
+
return dedent6`A summary that describes what happens across the ordered scenes.
|
|
7155
|
+
Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
|
|
7156
|
+
Cover the main subjects, actions, setting, and notable progression supported across scenes.
|
|
7157
|
+
Use the storyboard as direct visual evidence and the scene context to preserve progression.
|
|
7158
|
+
Write in present tense. If the transcript provides dialogue or narration, incorporate key points while preserving scene progression.`;
|
|
7159
|
+
}
|
|
7160
|
+
function createSummarizationBuilder({ titleLength, descriptionLength, tagCount } = {}, { hasSceneContext = false } = {}) {
|
|
7064
7161
|
const titleLimit = titleLength ?? DEFAULT_TITLE_LENGTH;
|
|
7065
7162
|
const keywordLimit = tagCount ?? DEFAULT_SUMMARY_KEYWORD_LIMIT;
|
|
7163
|
+
const task = hasSceneContext ? "Analyze the storyboard frames and ordered scene context together, then generate metadata that captures the full progression of the video content." : "Analyze the storyboard frames and generate metadata that captures the essence of the video content.";
|
|
7164
|
+
const qualityGuidelines = hasSceneContext ? dedent6`
|
|
7165
|
+
- Follow scene_index order to understand what begins, develops, and concludes
|
|
7166
|
+
- Use scene narratives and concepts as grounded evidence, preserving consistent terminology across the output
|
|
7167
|
+
- Use the storyboard as direct visual evidence for whole-asset details
|
|
7168
|
+
- Balance brevity with informativeness` : dedent6`
|
|
7169
|
+
- Examine all frames to understand the full context and progression
|
|
7170
|
+
- Be precise: "golden retriever" is better than "dog" when identifiable
|
|
7171
|
+
- Capture the narrative: what begins, develops, and concludes
|
|
7172
|
+
- Balance brevity with informativeness`;
|
|
7066
7173
|
return createPromptBuilder({
|
|
7067
7174
|
template: {
|
|
7068
7175
|
task: {
|
|
7069
7176
|
tag: "task",
|
|
7070
|
-
content:
|
|
7177
|
+
content: task
|
|
7071
7178
|
},
|
|
7072
7179
|
title: {
|
|
7073
7180
|
tag: "title_requirements",
|
|
@@ -7080,7 +7187,7 @@ function createSummarizationBuilder({ titleLength, descriptionLength, tagCount }
|
|
|
7080
7187
|
},
|
|
7081
7188
|
description: {
|
|
7082
7189
|
tag: "description_requirements",
|
|
7083
|
-
content: buildDescriptionGuidance(descriptionLength ?? DEFAULT_DESCRIPTION_LENGTH, "video")
|
|
7190
|
+
content: hasSceneContext ? buildSceneAwareDescriptionGuidance(descriptionLength ?? DEFAULT_DESCRIPTION_LENGTH) : buildDescriptionGuidance(descriptionLength ?? DEFAULT_DESCRIPTION_LENGTH, "video")
|
|
7084
7191
|
},
|
|
7085
7192
|
keywords: {
|
|
7086
7193
|
tag: "keywords_requirements",
|
|
@@ -7096,11 +7203,7 @@ function createSummarizationBuilder({ titleLength, descriptionLength, tagCount }
|
|
|
7096
7203
|
},
|
|
7097
7204
|
qualityGuidelines: {
|
|
7098
7205
|
tag: "quality_guidelines",
|
|
7099
|
-
content:
|
|
7100
|
-
- Examine all frames to understand the full context and progression
|
|
7101
|
-
- Be precise: "golden retriever" is better than "dog" when identifiable
|
|
7102
|
-
- Capture the narrative: what begins, develops, and concludes
|
|
7103
|
-
- Balance brevity with informativeness`
|
|
7206
|
+
content: qualityGuidelines
|
|
7104
7207
|
}
|
|
7105
7208
|
},
|
|
7106
7209
|
sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
|
|
@@ -7152,59 +7255,83 @@ function createAudioOnlyBuilder({ titleLength, descriptionLength, tagCount } = {
|
|
|
7152
7255
|
sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
|
|
7153
7256
|
});
|
|
7154
7257
|
}
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7258
|
+
function createVideoSystemPrompt(hasSceneContext) {
|
|
7259
|
+
let sceneContext = "";
|
|
7260
|
+
let sceneGuidance = "";
|
|
7261
|
+
if (hasSceneContext) {
|
|
7262
|
+
sceneContext = promptDedent`
|
|
7263
|
+
Ordered scene context is also provided as structured evidence of the video's progression.
|
|
7264
|
+
Entries use stable, zero-based scene_index values and absolute millisecond timestamps.
|
|
7265
|
+
Read scenes in scene_index order to understand what begins, develops, and concludes.`;
|
|
7266
|
+
sceneGuidance = promptDedent`
|
|
7267
|
+
<scene_guidance>
|
|
7268
|
+
- Use scene titles, narratives, and concepts to understand temporal progression
|
|
7269
|
+
- Use storyboard frames as direct visual evidence for subjects, actions, settings, and details
|
|
7270
|
+
- Preserve consistent terminology from the scene context across the title, description, and keywords
|
|
7271
|
+
- Treat optional scene fields as evidence only when present; do not infer omitted details
|
|
7272
|
+
- Consider all provided evidence together; if derived scene context conflicts with storyboard visuals, trust the visuals
|
|
7273
|
+
</scene_guidance>`;
|
|
7274
|
+
}
|
|
7275
|
+
const transcriptEvidence = hasSceneContext ? "storyboard frames and ordered scenes" : "visual frames";
|
|
7276
|
+
const descriptionPriority = hasSceneContext ? "Use the scene context for temporal structure and the storyboard for direct visual details, enriching both with transcript insights" : "Prioritize visual content for the description, but enrich it with transcript insights";
|
|
7277
|
+
const groundingConstraint = hasSceneContext ? "Use all provided evidence together. Ground each claim in the storyboard, scene context, or transcript" : "Only describe what is clearly observable in the frames or explicitly stated in the transcript";
|
|
7278
|
+
return promptDedent`
|
|
7279
|
+
<role>
|
|
7280
|
+
You are a video content analyst specializing in storyboard interpretation and multimodal analysis.
|
|
7281
|
+
</role>
|
|
7282
|
+
|
|
7283
|
+
<context>
|
|
7284
|
+
You receive storyboard images containing multiple sequential frames extracted from a video.
|
|
7285
|
+
${STORYBOARD_FRAME_INSTRUCTIONS}
|
|
7286
|
+
${sceneContext}
|
|
7287
|
+
</context>
|
|
7288
|
+
|
|
7289
|
+
${sceneGuidance}
|
|
7290
|
+
|
|
7291
|
+
<transcript_guidance>
|
|
7292
|
+
When a transcript is provided alongside the storyboard:
|
|
7293
|
+
- Use it to understand spoken content, dialogue, narration, and audio context
|
|
7294
|
+
- Correlate transcript content with ${transcriptEvidence} to build a complete picture
|
|
7295
|
+
- Extract key terminology, names, and specific language used by speakers
|
|
7296
|
+
- Let the transcript inform keyword selection, especially for topics not visually obvious
|
|
7297
|
+
- ${descriptionPriority}
|
|
7298
|
+
- If transcript and visuals conflict, trust the visual evidence
|
|
7299
|
+
</transcript_guidance>
|
|
7300
|
+
|
|
7301
|
+
<capabilities>
|
|
7302
|
+
- Extract meaning from visual sequences
|
|
7303
|
+
- Identify subjects, actions, settings, and narrative arcs
|
|
7304
|
+
- Generate accurate, searchable metadata
|
|
7305
|
+
- Synthesize visual and transcript information when provided
|
|
7306
|
+
</capabilities>
|
|
7307
|
+
|
|
7308
|
+
<security>
|
|
7309
|
+
${NON_DISCLOSURE_CONSTRAINT}
|
|
7310
|
+
|
|
7311
|
+
${UNTRUSTED_USER_INPUT_NOTICE}
|
|
7312
|
+
|
|
7313
|
+
${VISUAL_TEXT_AS_CONTENT}
|
|
7314
|
+
|
|
7315
|
+
${CANARY_TRIPWIRE}
|
|
7316
|
+
</security>
|
|
7317
|
+
|
|
7318
|
+
<constraints>
|
|
7319
|
+
- ${groundingConstraint}
|
|
7320
|
+
- ${NO_FABRICATION_CONSTRAINT}
|
|
7321
|
+
- ${METADATA_BOUNDARY_WARNING}
|
|
7322
|
+
- ${STRUCTURED_DATA_CONSTRAINT}
|
|
7323
|
+
- Output only the JSON object; no markdown or extra text
|
|
7324
|
+
- When a <language> section is provided, all output text MUST be written in that language
|
|
7325
|
+
</constraints>
|
|
7326
|
+
|
|
7327
|
+
<tone_guidance>
|
|
7328
|
+
${TONE_GUIDANCE}
|
|
7329
|
+
</tone_guidance>
|
|
7330
|
+
|
|
7331
|
+
<language_guidelines>
|
|
7332
|
+
${createLanguageGuidelines("video")}
|
|
7333
|
+
</language_guidelines>`;
|
|
7334
|
+
}
|
|
7208
7335
|
var AUDIO_ONLY_SYSTEM_PROMPT3 = promptDedent`
|
|
7209
7336
|
<role>
|
|
7210
7337
|
You are an audio content analyst specializing in transcript analysis and metadata generation.
|
|
@@ -7255,6 +7382,16 @@ var AUDIO_ONLY_SYSTEM_PROMPT3 = promptDedent`
|
|
|
7255
7382
|
<language_guidelines>
|
|
7256
7383
|
${createLanguageGuidelines("audio")}
|
|
7257
7384
|
</language_guidelines>`;
|
|
7385
|
+
function createSceneContextSection(sceneContext) {
|
|
7386
|
+
return {
|
|
7387
|
+
tag: "scene_context",
|
|
7388
|
+
content: JSON.stringify(sceneContext),
|
|
7389
|
+
attributes: {
|
|
7390
|
+
format: "json",
|
|
7391
|
+
order: "scene_index_ascending"
|
|
7392
|
+
}
|
|
7393
|
+
};
|
|
7394
|
+
}
|
|
7258
7395
|
function buildUserPrompt5({
|
|
7259
7396
|
tone,
|
|
7260
7397
|
transcriptText,
|
|
@@ -7264,7 +7401,8 @@ function buildUserPrompt5({
|
|
|
7264
7401
|
titleLength,
|
|
7265
7402
|
descriptionLength,
|
|
7266
7403
|
tagCount,
|
|
7267
|
-
languageName
|
|
7404
|
+
languageName,
|
|
7405
|
+
sceneContext
|
|
7268
7406
|
}) {
|
|
7269
7407
|
const contextSections = [createToneSection(TONE_INSTRUCTIONS[tone])];
|
|
7270
7408
|
if (languageName) {
|
|
@@ -7279,11 +7417,15 @@ function buildUserPrompt5({
|
|
|
7279
7417
|
const format = isCleanTranscript ? "plain text" : "WebVTT";
|
|
7280
7418
|
contextSections.push(createTranscriptSection(transcriptText, format));
|
|
7281
7419
|
}
|
|
7420
|
+
const hasSceneContext = !isAudioOnly && Boolean(sceneContext?.length);
|
|
7421
|
+
if (hasSceneContext && sceneContext) {
|
|
7422
|
+
contextSections.push(createSceneContextSection(sceneContext));
|
|
7423
|
+
}
|
|
7282
7424
|
const constraints = { titleLength, descriptionLength, tagCount };
|
|
7283
|
-
const promptBuilder = isAudioOnly ? createAudioOnlyBuilder(constraints) : createSummarizationBuilder(constraints);
|
|
7425
|
+
const promptBuilder = isAudioOnly ? createAudioOnlyBuilder(constraints) : createSummarizationBuilder(constraints, { hasSceneContext });
|
|
7284
7426
|
return promptBuilder.buildWithContext(promptOverrides, contextSections);
|
|
7285
7427
|
}
|
|
7286
|
-
async function
|
|
7428
|
+
async function analyzeVideo(imageDataUrl, provider, modelId, userPrompt, systemPrompt, descriptionLength, credentials) {
|
|
7287
7429
|
"use step";
|
|
7288
7430
|
const model = await createLanguageModelFromConfig(provider, modelId, credentials);
|
|
7289
7431
|
const schema = buildSummarySchema(descriptionLength);
|
|
@@ -7299,10 +7441,10 @@ async function analyzeStoryboard2(imageDataUrl, provider, modelId, userPrompt, s
|
|
|
7299
7441
|
messages: [
|
|
7300
7442
|
{
|
|
7301
7443
|
role: "user",
|
|
7302
|
-
content: [
|
|
7444
|
+
content: imageDataUrl ? [
|
|
7303
7445
|
{ type: "text", text: userPrompt },
|
|
7304
7446
|
{ type: "image", image: imageDataUrl }
|
|
7305
|
-
]
|
|
7447
|
+
] : userPrompt
|
|
7306
7448
|
}
|
|
7307
7449
|
]
|
|
7308
7450
|
}));
|
|
@@ -7412,6 +7554,7 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
7412
7554
|
cleanTranscript = true,
|
|
7413
7555
|
imageSubmissionMode = "url",
|
|
7414
7556
|
imageDownloadOptions,
|
|
7557
|
+
sceneContext,
|
|
7415
7558
|
promptOverrides,
|
|
7416
7559
|
credentials,
|
|
7417
7560
|
titleLength,
|
|
@@ -7435,6 +7578,7 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
7435
7578
|
const { asset: assetData, playbackId, policy } = await getPlaybackIdForAsset(assetId, workflowCredentials, options?.assetSnapshot);
|
|
7436
7579
|
const assetDurationSeconds = getAssetDurationSecondsFromAsset(assetData);
|
|
7437
7580
|
const isAudioOnly = isAudioOnlyAsset(assetData);
|
|
7581
|
+
const hasSceneContext = !isAudioOnly && Boolean(sceneContext?.length);
|
|
7438
7582
|
const effectiveScope = hasWorkflowScopeBoundaries(scope) ? scope : void 0;
|
|
7439
7583
|
const storyboardScope = isAudioOnly ? void 0 : resolveRenderableVideoScope(
|
|
7440
7584
|
effectiveScope,
|
|
@@ -7451,7 +7595,8 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
7451
7595
|
);
|
|
7452
7596
|
}
|
|
7453
7597
|
const signingContext = await resolveMuxSigningContext(workflowCredentials);
|
|
7454
|
-
|
|
7598
|
+
const requiresSignedPlaybackAccess = includeTranscript || !isAudioOnly;
|
|
7599
|
+
if (policy === "signed" && requiresSignedPlaybackAccess && !signingContext) {
|
|
7455
7600
|
throw new MuxAiError(
|
|
7456
7601
|
"Signed playback ID requires signing credentials. Set MUX_SIGNING_KEY and MUX_PRIVATE_KEY environment variables.",
|
|
7457
7602
|
{ type: "validation_error" }
|
|
@@ -7477,11 +7622,15 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
7477
7622
|
titleLength,
|
|
7478
7623
|
descriptionLength,
|
|
7479
7624
|
tagCount,
|
|
7480
|
-
languageName
|
|
7625
|
+
languageName,
|
|
7626
|
+
sceneContext: hasSceneContext ? sceneContext : void 0
|
|
7481
7627
|
});
|
|
7482
7628
|
let analysisResponse;
|
|
7483
7629
|
let imageUrl;
|
|
7484
|
-
|
|
7630
|
+
let systemPrompt = AUDIO_ONLY_SYSTEM_PROMPT3;
|
|
7631
|
+
if (!isAudioOnly) {
|
|
7632
|
+
systemPrompt = createVideoSystemPrompt(hasSceneContext);
|
|
7633
|
+
}
|
|
7485
7634
|
const effectiveDescriptionLength = descriptionLength ?? DEFAULT_DESCRIPTION_LENGTH;
|
|
7486
7635
|
try {
|
|
7487
7636
|
if (isAudioOnly) {
|
|
@@ -7504,7 +7653,7 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
7504
7653
|
imageUrl = storyboardUrl;
|
|
7505
7654
|
if (imageSubmissionMode === "base64") {
|
|
7506
7655
|
const downloadResult = await downloadImageAsBase64(storyboardUrl, imageDownloadOptions);
|
|
7507
|
-
analysisResponse = await
|
|
7656
|
+
analysisResponse = await analyzeVideo(
|
|
7508
7657
|
downloadResult.base64Data,
|
|
7509
7658
|
modelConfig.provider,
|
|
7510
7659
|
modelConfig.modelId,
|
|
@@ -7514,7 +7663,7 @@ async function getSummaryAndTagsInternal(assetId, options, collectedUsage) {
|
|
|
7514
7663
|
workflowCredentials
|
|
7515
7664
|
);
|
|
7516
7665
|
} else {
|
|
7517
|
-
analysisResponse = await
|
|
7666
|
+
analysisResponse = await analyzeVideo(
|
|
7518
7667
|
storyboardUrl,
|
|
7519
7668
|
modelConfig.provider,
|
|
7520
7669
|
modelConfig.modelId,
|
|
@@ -8099,7 +8248,7 @@ import { z as z9 } from "zod";
|
|
|
8099
8248
|
var translationSchema = z9.object({
|
|
8100
8249
|
translation: z9.string()
|
|
8101
8250
|
});
|
|
8102
|
-
var
|
|
8251
|
+
var SYSTEM_PROMPT5 = promptDedent`
|
|
8103
8252
|
You are a subtitle translation expert. Translate VTT subtitle files to the target language specified by the user.
|
|
8104
8253
|
You may receive either a full VTT file or a chunk from a larger VTT.
|
|
8105
8254
|
Preserve all timestamps, cue ordering, and VTT formatting exactly as they appear.
|
|
@@ -8424,7 +8573,7 @@ async function translateVttWithAI({
|
|
|
8424
8573
|
model,
|
|
8425
8574
|
maxRetries: 0,
|
|
8426
8575
|
output: Output7.object({ schema: translationSchema }),
|
|
8427
|
-
system:
|
|
8576
|
+
system: SYSTEM_PROMPT5,
|
|
8428
8577
|
messages: [
|
|
8429
8578
|
{
|
|
8430
8579
|
role: "user",
|