@mux/ai 0.7.5 → 0.8.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-B0U9upb4.d.ts → index-DP02N3iR.d.ts} +6 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +186 -120
- package/dist/index.js.map +1 -1
- package/dist/primitives/index.js +44 -2
- package/dist/primitives/index.js.map +1 -1
- package/dist/workflows/index.d.ts +1 -1
- package/dist/workflows/index.js +185 -119
- package/dist/workflows/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -523,6 +523,12 @@ interface SummarizationOptions extends MuxAIOptions {
|
|
|
523
523
|
* Useful for customizing the AI's output for specific use cases (SEO, social media, etc.)
|
|
524
524
|
*/
|
|
525
525
|
promptOverrides?: SummarizationPromptOverrides;
|
|
526
|
+
/** Desired title length in characters. */
|
|
527
|
+
titleLength?: number;
|
|
528
|
+
/** Desired description length in characters. */
|
|
529
|
+
descriptionLength?: number;
|
|
530
|
+
/** Desired number of tags. */
|
|
531
|
+
tagCount?: number;
|
|
526
532
|
}
|
|
527
533
|
declare function getSummaryAndTags(assetId: string, options?: SummarizationOptions): Promise<SummaryAndTagsResult>;
|
|
528
534
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,14 +2,14 @@ import { W as WorkflowCredentials, S as StoragePutObjectInput, a as StoragePresi
|
|
|
2
2
|
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 StorageAdapter, T as TextChunk, g as TokenChunkingConfig, h as TokenUsage, i as ToneType, U as UsageMetadata, V as VTTChunkingConfig, j as VideoEmbeddingsResult, k as WorkflowCredentialsInput, l as WorkflowMuxClient, m as decryptFromWorkflow, n as encryptForWorkflow } from './types-BRbaGW3t.js';
|
|
3
3
|
import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from '@workflow/serde';
|
|
4
4
|
export { i as primitives } from './index-Nxf6BaBO.js';
|
|
5
|
-
export { i as workflows } from './index-
|
|
5
|
+
export { i as workflows } from './index-DP02N3iR.js';
|
|
6
6
|
import '@mux/mux-node';
|
|
7
7
|
import 'zod';
|
|
8
8
|
import '@ai-sdk/anthropic';
|
|
9
9
|
import '@ai-sdk/google';
|
|
10
10
|
import '@ai-sdk/openai';
|
|
11
11
|
|
|
12
|
-
var version = "0.
|
|
12
|
+
var version = "0.8.0";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* A function that returns workflow credentials, either synchronously or asynchronously.
|
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.8.0";
|
|
9
9
|
|
|
10
10
|
// src/env.ts
|
|
11
11
|
import { z } from "zod";
|
|
@@ -30,6 +30,10 @@ var EnvSchema = z.object({
|
|
|
30
30
|
),
|
|
31
31
|
MUX_SIGNING_KEY: optionalString("Mux signing key ID for signed playback URLs.", "Used to sign playback URLs"),
|
|
32
32
|
MUX_PRIVATE_KEY: optionalString("Mux signing private key for signed playback URLs.", "Used to sign playback URLs"),
|
|
33
|
+
MUX_IMAGE_URL_OVERRIDE: optionalString(
|
|
34
|
+
"Override for Mux image base URL (defaults to https://image.mux.com).",
|
|
35
|
+
"Mux image URL override"
|
|
36
|
+
),
|
|
33
37
|
// Test-only helpers (used by this repo's integration tests)
|
|
34
38
|
MUX_TEST_ASSET_ID: optionalString("Mux asset ID used by integration tests.", "Mux test asset id"),
|
|
35
39
|
MUX_TEST_ASSET_ID_CHAPTERS: optionalString("Mux asset ID used by integration tests for chapters.", "Mux test asset id for chapters"),
|
|
@@ -1032,6 +1036,44 @@ async function fetchHotspots(identifierType, id, options) {
|
|
|
1032
1036
|
return transformHotspotResponse(response);
|
|
1033
1037
|
}
|
|
1034
1038
|
|
|
1039
|
+
// src/lib/mux-image-url.ts
|
|
1040
|
+
var DEFAULT_MUX_IMAGE_ORIGIN = "https://image.mux.com";
|
|
1041
|
+
function normalizeMuxImageOrigin(value) {
|
|
1042
|
+
const trimmed = value.trim();
|
|
1043
|
+
const candidate = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
|
|
1044
|
+
let parsed;
|
|
1045
|
+
try {
|
|
1046
|
+
parsed = new URL(candidate);
|
|
1047
|
+
} catch {
|
|
1048
|
+
throw new Error(
|
|
1049
|
+
`Invalid MUX_IMAGE_URL_OVERRIDE. Provide a hostname like "image.example.mux.com" (or a URL origin such as "https://image.example.mux.com").`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash || parsed.pathname && parsed.pathname !== "/") {
|
|
1053
|
+
throw new Error(
|
|
1054
|
+
"Invalid MUX_IMAGE_URL_OVERRIDE. Only a hostname/origin is allowed (no credentials, query params, hash fragments, or path)."
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
return parsed.origin;
|
|
1058
|
+
}
|
|
1059
|
+
function getMuxImageOrigin() {
|
|
1060
|
+
const override = env_default.MUX_IMAGE_URL_OVERRIDE;
|
|
1061
|
+
if (!override) {
|
|
1062
|
+
return DEFAULT_MUX_IMAGE_ORIGIN;
|
|
1063
|
+
}
|
|
1064
|
+
return normalizeMuxImageOrigin(override);
|
|
1065
|
+
}
|
|
1066
|
+
function getMuxImageBaseUrl(playbackId, assetType) {
|
|
1067
|
+
const origin = getMuxImageOrigin();
|
|
1068
|
+
return `${origin}/${playbackId}/${assetType}.png`;
|
|
1069
|
+
}
|
|
1070
|
+
function getMuxStoryboardBaseUrl(playbackId) {
|
|
1071
|
+
return getMuxImageBaseUrl(playbackId, "storyboard");
|
|
1072
|
+
}
|
|
1073
|
+
function getMuxThumbnailBaseUrl(playbackId) {
|
|
1074
|
+
return getMuxImageBaseUrl(playbackId, "thumbnail");
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1035
1077
|
// src/lib/url-signing.ts
|
|
1036
1078
|
async function createSigningClient(context) {
|
|
1037
1079
|
const { default: MuxClient } = await import("@mux/mux-node");
|
|
@@ -1073,7 +1115,7 @@ async function signUrl(url, playbackId, type = "video", params, credentials) {
|
|
|
1073
1115
|
var DEFAULT_STORYBOARD_WIDTH = 640;
|
|
1074
1116
|
async function getStoryboardUrl(playbackId, width = DEFAULT_STORYBOARD_WIDTH, shouldSign = false, credentials) {
|
|
1075
1117
|
"use step";
|
|
1076
|
-
const baseUrl =
|
|
1118
|
+
const baseUrl = getMuxStoryboardBaseUrl(playbackId);
|
|
1077
1119
|
if (shouldSign) {
|
|
1078
1120
|
return signUrl(baseUrl, playbackId, "storyboard", { width }, credentials);
|
|
1079
1121
|
}
|
|
@@ -1192,7 +1234,7 @@ async function getThumbnailUrls(playbackId, duration, options = {}) {
|
|
|
1192
1234
|
}
|
|
1193
1235
|
timestamps = newTimestamps;
|
|
1194
1236
|
}
|
|
1195
|
-
const baseUrl =
|
|
1237
|
+
const baseUrl = getMuxThumbnailBaseUrl(playbackId);
|
|
1196
1238
|
const urlPromises = timestamps.map(async (time) => {
|
|
1197
1239
|
if (shouldSign) {
|
|
1198
1240
|
return signUrl(baseUrl, playbackId, "thumbnail", { time, width }, credentials);
|
|
@@ -1756,16 +1798,6 @@ var SYSTEM_PROMPT = dedent`
|
|
|
1756
1798
|
- GOOD: "A person runs through a park"
|
|
1757
1799
|
- Be specific and evidence-based
|
|
1758
1800
|
</language_guidelines>`;
|
|
1759
|
-
function buildSystemPrompt(allowedAnswers) {
|
|
1760
|
-
const answerList = allowedAnswers.map((answer) => `"${answer}"`).join(", ");
|
|
1761
|
-
return `${SYSTEM_PROMPT}
|
|
1762
|
-
|
|
1763
|
-
${dedent`
|
|
1764
|
-
<response_options>
|
|
1765
|
-
Allowed answers: ${answerList}
|
|
1766
|
-
</response_options>
|
|
1767
|
-
`}`;
|
|
1768
|
-
}
|
|
1769
1801
|
var askQuestionsPromptBuilder = createPromptBuilder({
|
|
1770
1802
|
template: {
|
|
1771
1803
|
questions: {
|
|
@@ -1775,21 +1807,30 @@ var askQuestionsPromptBuilder = createPromptBuilder({
|
|
|
1775
1807
|
},
|
|
1776
1808
|
sectionOrder: ["questions"]
|
|
1777
1809
|
});
|
|
1778
|
-
function buildUserPrompt(questions, transcriptText, isCleanTranscript = true) {
|
|
1810
|
+
function buildUserPrompt(questions, allowedAnswers, transcriptText, isCleanTranscript = true) {
|
|
1779
1811
|
const questionsList = questions.map((q, idx) => `${idx + 1}. ${q.question}`).join("\n");
|
|
1780
1812
|
const questionsContent = dedent`
|
|
1781
1813
|
Please answer the following yes/no questions about this video:
|
|
1782
1814
|
|
|
1783
1815
|
${questionsList}`;
|
|
1816
|
+
const answerList = allowedAnswers.map((answer) => `"${answer}"`).join(", ");
|
|
1817
|
+
const responseOptions = dedent`
|
|
1818
|
+
<response_options>
|
|
1819
|
+
Allowed answers: ${answerList}
|
|
1820
|
+
</response_options>`;
|
|
1821
|
+
const questionsSection = askQuestionsPromptBuilder.build({ questions: questionsContent });
|
|
1784
1822
|
if (!transcriptText) {
|
|
1785
|
-
return
|
|
1823
|
+
return `${questionsSection}
|
|
1824
|
+
|
|
1825
|
+
${responseOptions}`;
|
|
1786
1826
|
}
|
|
1787
1827
|
const format = isCleanTranscript ? "plain text" : "WebVTT";
|
|
1788
|
-
const transcriptSection = createTranscriptSection(transcriptText, format);
|
|
1789
|
-
return
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1828
|
+
const transcriptSection = renderSection(createTranscriptSection(transcriptText, format));
|
|
1829
|
+
return `${transcriptSection}
|
|
1830
|
+
|
|
1831
|
+
${questionsSection}
|
|
1832
|
+
|
|
1833
|
+
${responseOptions}`;
|
|
1793
1834
|
}
|
|
1794
1835
|
async function fetchImageAsBase64(imageUrl, imageDownloadOptions) {
|
|
1795
1836
|
"use step";
|
|
@@ -1885,8 +1926,8 @@ async function askQuestions(assetId, questions, options) {
|
|
|
1885
1926
|
cleanTranscript,
|
|
1886
1927
|
shouldSign: policy === "signed"
|
|
1887
1928
|
})).transcriptText : "";
|
|
1888
|
-
const userPrompt = buildUserPrompt(questions, transcriptText, cleanTranscript);
|
|
1889
|
-
const systemPrompt =
|
|
1929
|
+
const userPrompt = buildUserPrompt(questions, allowedAnswers, transcriptText, cleanTranscript);
|
|
1930
|
+
const systemPrompt = SYSTEM_PROMPT;
|
|
1890
1931
|
const imageUrl = await getStoryboardUrl(
|
|
1891
1932
|
playbackId,
|
|
1892
1933
|
storyboardWidth,
|
|
@@ -2601,7 +2642,7 @@ function planSamplingTimestamps(options) {
|
|
|
2601
2642
|
|
|
2602
2643
|
// src/workflows/moderation.ts
|
|
2603
2644
|
var DEFAULT_THRESHOLDS = {
|
|
2604
|
-
sexual: 0.
|
|
2645
|
+
sexual: 0.8,
|
|
2605
2646
|
violence: 0.8
|
|
2606
2647
|
};
|
|
2607
2648
|
var DEFAULT_PROVIDER2 = "openai";
|
|
@@ -2866,7 +2907,7 @@ async function requestHiveModeration(imageUrls, maxConcurrent = 5, submissionMod
|
|
|
2866
2907
|
async function getThumbnailUrlsFromTimestamps(playbackId, timestampsMs, options) {
|
|
2867
2908
|
"use step";
|
|
2868
2909
|
const { width, shouldSign, credentials } = options;
|
|
2869
|
-
const baseUrl =
|
|
2910
|
+
const baseUrl = getMuxThumbnailBaseUrl(playbackId);
|
|
2870
2911
|
const urlPromises = timestampsMs.map(async (tsMs) => {
|
|
2871
2912
|
const time = Number((tsMs / 1e3).toFixed(2));
|
|
2872
2913
|
if (shouldSign) {
|
|
@@ -3043,96 +3084,106 @@ var TONE_INSTRUCTIONS = {
|
|
|
3043
3084
|
playful: "Channel your inner diva! Answer with maximum sass, wit, and playful attitude. Don't hold back - be cheeky, clever, and delightfully snarky. Make it pop!",
|
|
3044
3085
|
professional: "Provide a professional, executive-level analysis suitable for business reporting."
|
|
3045
3086
|
};
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
template: {
|
|
3093
|
-
task: {
|
|
3094
|
-
tag: "task",
|
|
3095
|
-
content: "Analyze the transcript and generate metadata that captures the essence of the audio content."
|
|
3096
|
-
},
|
|
3097
|
-
title: {
|
|
3098
|
-
tag: "title_requirements",
|
|
3099
|
-
content: dedent4`
|
|
3100
|
-
A short, compelling headline that immediately communicates the subject or topic.
|
|
3101
|
-
Aim for brevity - typically under 10 words. Think of how a podcast title or audio description would read.
|
|
3102
|
-
Start with the primary subject, action, or topic - never begin with "An audio of" or similar phrasing.
|
|
3103
|
-
Use active, specific language.`
|
|
3104
|
-
},
|
|
3105
|
-
description: {
|
|
3106
|
-
tag: "description_requirements",
|
|
3107
|
-
content: dedent4`
|
|
3108
|
-
A concise summary (2-4 sentences) that describes the audio content.
|
|
3109
|
-
Cover the main topics, speakers, themes, and any notable progression in the discussion or narration.
|
|
3110
|
-
Write in present tense. Be specific about what is discussed or presented rather than making assumptions.
|
|
3111
|
-
Focus on the spoken content and any key insights, dialogue, or narrative elements.`
|
|
3087
|
+
function createSummarizationBuilder({ titleLength, descriptionLength, tagCount } = {}) {
|
|
3088
|
+
const titleBrevity = titleLength != null ? `Aim for approximately ${titleLength} characters.` : "Aim for brevity - typically under 10 words.";
|
|
3089
|
+
const descConstraint = descriptionLength != null ? `approximately ${descriptionLength} characters` : "2-4 sentences";
|
|
3090
|
+
const keywordLimit = tagCount ?? SUMMARY_KEYWORD_LIMIT;
|
|
3091
|
+
return createPromptBuilder({
|
|
3092
|
+
template: {
|
|
3093
|
+
task: {
|
|
3094
|
+
tag: "task",
|
|
3095
|
+
content: "Analyze the storyboard frames and generate metadata that captures the essence of the video content."
|
|
3096
|
+
},
|
|
3097
|
+
title: {
|
|
3098
|
+
tag: "title_requirements",
|
|
3099
|
+
content: dedent4`
|
|
3100
|
+
A short, compelling headline that immediately communicates the subject or action.
|
|
3101
|
+
${titleBrevity} Think of how a news headline or video card title would read.
|
|
3102
|
+
Start with the primary subject, action, or topic - never begin with "A video of" or similar phrasing.
|
|
3103
|
+
Use active, specific language.`
|
|
3104
|
+
},
|
|
3105
|
+
description: {
|
|
3106
|
+
tag: "description_requirements",
|
|
3107
|
+
content: dedent4`
|
|
3108
|
+
A concise summary (${descConstraint}) that describes what happens across the video.
|
|
3109
|
+
Cover the main subjects, actions, setting, and any notable progression visible across frames.
|
|
3110
|
+
Write in present tense. Be specific about observable details rather than making assumptions.
|
|
3111
|
+
If the transcript provides dialogue or narration, incorporate key points but prioritize visual content.`
|
|
3112
|
+
},
|
|
3113
|
+
keywords: {
|
|
3114
|
+
tag: "keywords_requirements",
|
|
3115
|
+
content: dedent4`
|
|
3116
|
+
Specific, searchable terms (up to ${keywordLimit}) that capture:
|
|
3117
|
+
- Primary subjects (people, animals, objects)
|
|
3118
|
+
- Actions and activities being performed
|
|
3119
|
+
- Setting and environment
|
|
3120
|
+
- Notable objects or tools
|
|
3121
|
+
- Style or genre (if applicable)
|
|
3122
|
+
Prefer concrete nouns and action verbs over abstract concepts.
|
|
3123
|
+
Use lowercase. Avoid redundant or overly generic terms like "video" or "content".`
|
|
3124
|
+
},
|
|
3125
|
+
qualityGuidelines: {
|
|
3126
|
+
tag: "quality_guidelines",
|
|
3127
|
+
content: dedent4`
|
|
3128
|
+
- Examine all frames to understand the full context and progression
|
|
3129
|
+
- Be precise: "golden retriever" is better than "dog" when identifiable
|
|
3130
|
+
- Capture the narrative: what begins, develops, and concludes
|
|
3131
|
+
- Balance brevity with informativeness`
|
|
3132
|
+
}
|
|
3112
3133
|
},
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3134
|
+
sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
function createAudioOnlyBuilder({ titleLength, descriptionLength, tagCount } = {}) {
|
|
3138
|
+
const titleBrevity = titleLength != null ? `Aim for approximately ${titleLength} characters.` : "Aim for brevity - typically under 10 words.";
|
|
3139
|
+
const descConstraint = descriptionLength != null ? `approximately ${descriptionLength} characters` : "2-4 sentences";
|
|
3140
|
+
const keywordLimit = tagCount ?? SUMMARY_KEYWORD_LIMIT;
|
|
3141
|
+
return createPromptBuilder({
|
|
3142
|
+
template: {
|
|
3143
|
+
task: {
|
|
3144
|
+
tag: "task",
|
|
3145
|
+
content: "Analyze the transcript and generate metadata that captures the essence of the audio content."
|
|
3146
|
+
},
|
|
3147
|
+
title: {
|
|
3148
|
+
tag: "title_requirements",
|
|
3149
|
+
content: dedent4`
|
|
3150
|
+
A short, compelling headline that immediately communicates the subject or topic.
|
|
3151
|
+
${titleBrevity} Think of how a podcast title or audio description would read.
|
|
3152
|
+
Start with the primary subject, action, or topic - never begin with "An audio of" or similar phrasing.
|
|
3153
|
+
Use active, specific language.`
|
|
3154
|
+
},
|
|
3155
|
+
description: {
|
|
3156
|
+
tag: "description_requirements",
|
|
3157
|
+
content: dedent4`
|
|
3158
|
+
A concise summary (${descConstraint}) that describes the audio content.
|
|
3159
|
+
Cover the main topics, speakers, themes, and any notable progression in the discussion or narration.
|
|
3160
|
+
Write in present tense. Be specific about what is discussed or presented rather than making assumptions.
|
|
3161
|
+
Focus on the spoken content and any key insights, dialogue, or narrative elements.`
|
|
3162
|
+
},
|
|
3163
|
+
keywords: {
|
|
3164
|
+
tag: "keywords_requirements",
|
|
3165
|
+
content: dedent4`
|
|
3166
|
+
Specific, searchable terms (up to ${keywordLimit}) that capture:
|
|
3167
|
+
- Primary topics and themes
|
|
3168
|
+
- Speakers or presenters (if named)
|
|
3169
|
+
- Key concepts and terminology
|
|
3170
|
+
- Content type (interview, lecture, music, etc.)
|
|
3171
|
+
- Genre or style (if applicable)
|
|
3172
|
+
Prefer concrete nouns and relevant terms over abstract concepts.
|
|
3173
|
+
Use lowercase. Avoid redundant or overly generic terms like "audio" or "content".`
|
|
3174
|
+
},
|
|
3175
|
+
qualityGuidelines: {
|
|
3176
|
+
tag: "quality_guidelines",
|
|
3177
|
+
content: dedent4`
|
|
3178
|
+
- Analyze the full transcript to understand context and themes
|
|
3179
|
+
- Be precise: use specific terminology when mentioned
|
|
3180
|
+
- Capture the narrative: what is introduced, discussed, and concluded
|
|
3181
|
+
- Balance brevity with informativeness`
|
|
3182
|
+
}
|
|
3124
3183
|
},
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
- Analyze the full transcript to understand context and themes
|
|
3129
|
-
- Be precise: use specific terminology when mentioned
|
|
3130
|
-
- Capture the narrative: what is introduced, discussed, and concluded
|
|
3131
|
-
- Balance brevity with informativeness`
|
|
3132
|
-
}
|
|
3133
|
-
},
|
|
3134
|
-
sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
|
|
3135
|
-
});
|
|
3184
|
+
sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
|
|
3185
|
+
});
|
|
3186
|
+
}
|
|
3136
3187
|
var SYSTEM_PROMPT3 = dedent4`
|
|
3137
3188
|
<role>
|
|
3138
3189
|
You are a video content analyst specializing in storyboard interpretation and multimodal analysis.
|
|
@@ -3247,14 +3298,18 @@ function buildUserPrompt4({
|
|
|
3247
3298
|
transcriptText,
|
|
3248
3299
|
isCleanTranscript = true,
|
|
3249
3300
|
promptOverrides,
|
|
3250
|
-
isAudioOnly = false
|
|
3301
|
+
isAudioOnly = false,
|
|
3302
|
+
titleLength,
|
|
3303
|
+
descriptionLength,
|
|
3304
|
+
tagCount
|
|
3251
3305
|
}) {
|
|
3252
3306
|
const contextSections = [createToneSection(TONE_INSTRUCTIONS[tone])];
|
|
3253
3307
|
if (transcriptText) {
|
|
3254
3308
|
const format = isCleanTranscript ? "plain text" : "WebVTT";
|
|
3255
3309
|
contextSections.push(createTranscriptSection(transcriptText, format));
|
|
3256
3310
|
}
|
|
3257
|
-
const
|
|
3311
|
+
const constraints = { titleLength, descriptionLength, tagCount };
|
|
3312
|
+
const promptBuilder = isAudioOnly ? createAudioOnlyBuilder(constraints) : createSummarizationBuilder(constraints);
|
|
3258
3313
|
return promptBuilder.buildWithContext(promptOverrides, contextSections);
|
|
3259
3314
|
}
|
|
3260
3315
|
async function analyzeStoryboard2(imageDataUrl, provider, modelId, userPrompt, systemPrompt, credentials) {
|
|
@@ -3324,7 +3379,7 @@ async function analyzeAudioOnly(provider, modelId, userPrompt, systemPrompt, cre
|
|
|
3324
3379
|
}
|
|
3325
3380
|
};
|
|
3326
3381
|
}
|
|
3327
|
-
function normalizeKeywords(keywords) {
|
|
3382
|
+
function normalizeKeywords(keywords, limit = SUMMARY_KEYWORD_LIMIT) {
|
|
3328
3383
|
if (!Array.isArray(keywords) || keywords.length === 0) {
|
|
3329
3384
|
return [];
|
|
3330
3385
|
}
|
|
@@ -3341,7 +3396,7 @@ function normalizeKeywords(keywords) {
|
|
|
3341
3396
|
}
|
|
3342
3397
|
uniqueLowercase.add(lower);
|
|
3343
3398
|
normalized.push(trimmed);
|
|
3344
|
-
if (normalized.length ===
|
|
3399
|
+
if (normalized.length === limit) {
|
|
3345
3400
|
break;
|
|
3346
3401
|
}
|
|
3347
3402
|
}
|
|
@@ -3358,7 +3413,10 @@ async function getSummaryAndTags(assetId, options) {
|
|
|
3358
3413
|
imageSubmissionMode = "url",
|
|
3359
3414
|
imageDownloadOptions,
|
|
3360
3415
|
promptOverrides,
|
|
3361
|
-
credentials
|
|
3416
|
+
credentials,
|
|
3417
|
+
titleLength,
|
|
3418
|
+
descriptionLength,
|
|
3419
|
+
tagCount
|
|
3362
3420
|
} = options ?? {};
|
|
3363
3421
|
if (!VALID_TONES.includes(tone)) {
|
|
3364
3422
|
throw new Error(
|
|
@@ -3396,7 +3454,10 @@ async function getSummaryAndTags(assetId, options) {
|
|
|
3396
3454
|
transcriptText,
|
|
3397
3455
|
isCleanTranscript: cleanTranscript,
|
|
3398
3456
|
promptOverrides,
|
|
3399
|
-
isAudioOnly
|
|
3457
|
+
isAudioOnly,
|
|
3458
|
+
titleLength,
|
|
3459
|
+
descriptionLength,
|
|
3460
|
+
tagCount
|
|
3400
3461
|
});
|
|
3401
3462
|
let analysisResponse;
|
|
3402
3463
|
let imageUrl;
|
|
@@ -3453,7 +3514,7 @@ async function getSummaryAndTags(assetId, options) {
|
|
|
3453
3514
|
assetId,
|
|
3454
3515
|
title: analysisResponse.result.title,
|
|
3455
3516
|
description: analysisResponse.result.description,
|
|
3456
|
-
tags: normalizeKeywords(analysisResponse.result.keywords),
|
|
3517
|
+
tags: normalizeKeywords(analysisResponse.result.keywords, tagCount ?? SUMMARY_KEYWORD_LIMIT),
|
|
3457
3518
|
storyboardUrl: imageUrl,
|
|
3458
3519
|
// undefined for audio-only assets
|
|
3459
3520
|
usage: {
|
|
@@ -4073,6 +4134,7 @@ import { z as z6 } from "zod";
|
|
|
4073
4134
|
var translationSchema = z6.object({
|
|
4074
4135
|
translation: z6.string()
|
|
4075
4136
|
});
|
|
4137
|
+
var SYSTEM_PROMPT4 = 'You are a subtitle translation expert. Translate VTT subtitle files to the target language specified by the user. Preserve all timestamps and VTT formatting exactly as they appear. Return JSON with a single key "translation" containing the translated VTT content.';
|
|
4076
4138
|
async function fetchVttFromMux(vttUrl) {
|
|
4077
4139
|
"use step";
|
|
4078
4140
|
const vttResponse = await fetch(vttUrl);
|
|
@@ -4095,9 +4157,13 @@ async function translateVttWithAI({
|
|
|
4095
4157
|
model,
|
|
4096
4158
|
output: Output5.object({ schema: translationSchema }),
|
|
4097
4159
|
messages: [
|
|
4160
|
+
{
|
|
4161
|
+
role: "system",
|
|
4162
|
+
content: SYSTEM_PROMPT4
|
|
4163
|
+
},
|
|
4098
4164
|
{
|
|
4099
4165
|
role: "user",
|
|
4100
|
-
content: `Translate
|
|
4166
|
+
content: `Translate from ${fromLanguageCode} to ${toLanguageCode}:
|
|
4101
4167
|
|
|
4102
4168
|
${vttContent}`
|
|
4103
4169
|
}
|