@mux/ai 0.43.0 → 0.45.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-BCyjRRuI.d.ts → index-BD4acrwt.d.ts} +3 -6
- package/dist/{index-C2Pk7bbF.d.ts → index-D6VGaoPm.d.ts} +1 -1
- package/dist/index.d.ts +14 -7
- package/dist/index.js +244 -185
- package/dist/index.js.map +1 -1
- package/dist/primitives/index.d.ts +2 -2
- package/dist/primitives/index.js +64 -23
- package/dist/primitives/index.js.map +1 -1
- package/dist/prompts/index.d.ts +77 -305
- package/dist/prompts/index.js +218 -2
- package/dist/prompts/index.js.map +1 -1
- package/dist/{shots-CUI09Ohe.d.ts → shots-B7G0IIF1.d.ts} +2 -0
- package/dist/summarization-BFmhXeAu.d.ts +351 -0
- package/dist/workflows/index.d.ts +3 -3
- package/dist/workflows/index.js +210 -155
- package/dist/workflows/index.js.map +1 -1
- package/package.json +1 -1
package/dist/workflows/index.js
CHANGED
|
@@ -33,12 +33,16 @@ var EnvSchema = z.object({
|
|
|
33
33
|
),
|
|
34
34
|
MUX_SIGNING_KEY: optionalString("Mux signing key ID for signed playback URLs.", "Used to sign playback URLs"),
|
|
35
35
|
MUX_PRIVATE_KEY: optionalString("Mux signing private key for signed playback URLs.", "Used to sign playback URLs"),
|
|
36
|
+
MUX_CUSTOM_DOMAIN: optionalString(
|
|
37
|
+
"Mux domain for image.* and stream.* playback URLs, e.g. staging.mux.com or a custom domain (defaults to mux.com).",
|
|
38
|
+
"Mux custom domain"
|
|
39
|
+
),
|
|
36
40
|
MUX_IMAGE_URL_OVERRIDE: optionalString(
|
|
37
|
-
"Override for Mux image base URL (defaults to https://image.mux.com).",
|
|
41
|
+
"Deprecated: use MUX_CUSTOM_DOMAIN. Override for Mux image base URL (defaults to https://image.mux.com).",
|
|
38
42
|
"Mux image URL override"
|
|
39
43
|
),
|
|
40
44
|
MUX_STREAM_URL_OVERRIDE: optionalString(
|
|
41
|
-
"Override for Mux stream base URL (defaults to https://stream.mux.com).",
|
|
45
|
+
"Deprecated: use MUX_CUSTOM_DOMAIN. Override for Mux stream base URL (defaults to https://stream.mux.com).",
|
|
42
46
|
"Mux stream URL override"
|
|
43
47
|
),
|
|
44
48
|
MUX_BASE_URL: optionalString(
|
|
@@ -157,7 +161,18 @@ function parseEnv() {
|
|
|
157
161
|
}
|
|
158
162
|
return parsedEnv.data;
|
|
159
163
|
}
|
|
164
|
+
var hasWarnedAboutDeprecatedUrlOverrides = false;
|
|
165
|
+
function warnOnDeprecatedUrlOverrides(parsed) {
|
|
166
|
+
if (hasWarnedAboutDeprecatedUrlOverrides || !parsed.MUX_IMAGE_URL_OVERRIDE && !parsed.MUX_STREAM_URL_OVERRIDE) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
hasWarnedAboutDeprecatedUrlOverrides = true;
|
|
170
|
+
console.warn(
|
|
171
|
+
"MUX_IMAGE_URL_OVERRIDE and MUX_STREAM_URL_OVERRIDE are deprecated and will be removed in a future release. Set MUX_CUSTOM_DOMAIN instead (e.g. MUX_CUSTOM_DOMAIN=staging.mux.com for image.staging.mux.com and stream.staging.mux.com)."
|
|
172
|
+
);
|
|
173
|
+
}
|
|
160
174
|
var env = parseEnv();
|
|
175
|
+
warnOnDeprecatedUrlOverrides(env);
|
|
161
176
|
var env_default = env;
|
|
162
177
|
|
|
163
178
|
// src/lib/prompt-fragments.ts
|
|
@@ -1824,8 +1839,7 @@ function timeRangesOverlap(firstStart, firstEnd, second) {
|
|
|
1824
1839
|
}
|
|
1825
1840
|
|
|
1826
1841
|
// src/lib/mux-url.ts
|
|
1827
|
-
var
|
|
1828
|
-
var DEFAULT_MUX_STREAM_ORIGIN = "https://stream.mux.com";
|
|
1842
|
+
var DEFAULT_MUX_DOMAIN = "mux.com";
|
|
1829
1843
|
function normalizeOrigin(value, envVarName) {
|
|
1830
1844
|
const trimmed = value.trim();
|
|
1831
1845
|
const candidate = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
|
|
@@ -1845,29 +1859,56 @@ function normalizeOrigin(value, envVarName) {
|
|
|
1845
1859
|
}
|
|
1846
1860
|
return parsed.origin;
|
|
1847
1861
|
}
|
|
1848
|
-
function
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1851
|
-
|
|
1862
|
+
function buildPlaybackOrigin(service, customDomain, source) {
|
|
1863
|
+
const invalidDomainError = new Error(
|
|
1864
|
+
`Invalid ${source}. Provide a bare hostname (e.g. "media.example.com") with no scheme, port, credentials, query params, hash fragments, or path.`
|
|
1865
|
+
);
|
|
1866
|
+
const trimmed = customDomain.trim();
|
|
1867
|
+
if (!trimmed) {
|
|
1868
|
+
throw invalidDomainError;
|
|
1869
|
+
}
|
|
1870
|
+
let parsed;
|
|
1871
|
+
try {
|
|
1872
|
+
parsed = new URL(`https://${service}.${trimmed}`);
|
|
1873
|
+
} catch {
|
|
1874
|
+
throw invalidDomainError;
|
|
1875
|
+
}
|
|
1876
|
+
if (parsed.port || parsed.username || parsed.password || parsed.search || parsed.hash || parsed.pathname !== "/") {
|
|
1877
|
+
throw invalidDomainError;
|
|
1852
1878
|
}
|
|
1853
|
-
return
|
|
1879
|
+
return parsed.origin;
|
|
1854
1880
|
}
|
|
1855
|
-
function
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1881
|
+
async function resolveMuxPlaybackOrigin(service, credentials) {
|
|
1882
|
+
"use step";
|
|
1883
|
+
const { muxCustomDomain } = await resolveWorkflowCredentials(credentials);
|
|
1884
|
+
if (muxCustomDomain) {
|
|
1885
|
+
return buildPlaybackOrigin(service, muxCustomDomain, "muxCustomDomain");
|
|
1886
|
+
}
|
|
1887
|
+
const legacyOverrideEnvVarName = service === "image" ? "MUX_IMAGE_URL_OVERRIDE" : "MUX_STREAM_URL_OVERRIDE";
|
|
1888
|
+
const legacyOverride = env_default[legacyOverrideEnvVarName];
|
|
1889
|
+
if (legacyOverride) {
|
|
1890
|
+
return normalizeOrigin(legacyOverride, legacyOverrideEnvVarName);
|
|
1859
1891
|
}
|
|
1860
|
-
|
|
1892
|
+
if (env_default.MUX_CUSTOM_DOMAIN) {
|
|
1893
|
+
return buildPlaybackOrigin(service, env_default.MUX_CUSTOM_DOMAIN, "MUX_CUSTOM_DOMAIN");
|
|
1894
|
+
}
|
|
1895
|
+
return buildPlaybackOrigin(service, DEFAULT_MUX_DOMAIN, "custom domain");
|
|
1896
|
+
}
|
|
1897
|
+
async function getMuxImageOrigin(credentials) {
|
|
1898
|
+
return resolveMuxPlaybackOrigin("image", credentials);
|
|
1899
|
+
}
|
|
1900
|
+
async function getMuxStreamOrigin(credentials) {
|
|
1901
|
+
return resolveMuxPlaybackOrigin("stream", credentials);
|
|
1861
1902
|
}
|
|
1862
|
-
function getMuxImageBaseUrl(playbackId, assetType) {
|
|
1863
|
-
const origin = getMuxImageOrigin();
|
|
1903
|
+
async function getMuxImageBaseUrl(playbackId, assetType, credentials) {
|
|
1904
|
+
const origin = await getMuxImageOrigin(credentials);
|
|
1864
1905
|
return `${origin}/${playbackId}/${assetType}.png`;
|
|
1865
1906
|
}
|
|
1866
|
-
function getMuxStoryboardBaseUrl(playbackId) {
|
|
1867
|
-
return getMuxImageBaseUrl(playbackId, "storyboard");
|
|
1907
|
+
async function getMuxStoryboardBaseUrl(playbackId, credentials) {
|
|
1908
|
+
return getMuxImageBaseUrl(playbackId, "storyboard", credentials);
|
|
1868
1909
|
}
|
|
1869
|
-
function getMuxThumbnailBaseUrl(playbackId) {
|
|
1870
|
-
return getMuxImageBaseUrl(playbackId, "thumbnail");
|
|
1910
|
+
async function getMuxThumbnailBaseUrl(playbackId, credentials) {
|
|
1911
|
+
return getMuxImageBaseUrl(playbackId, "thumbnail", credentials);
|
|
1871
1912
|
}
|
|
1872
1913
|
|
|
1873
1914
|
// src/lib/url-signing.ts
|
|
@@ -1911,7 +1952,7 @@ async function signUrl(url, playbackId, type = "video", params, credentials) {
|
|
|
1911
1952
|
var DEFAULT_STORYBOARD_WIDTH = 640;
|
|
1912
1953
|
async function getStoryboardUrl(playbackId, width = DEFAULT_STORYBOARD_WIDTH, shouldSign = false, credentials, scope) {
|
|
1913
1954
|
"use step";
|
|
1914
|
-
const baseUrl = getMuxStoryboardBaseUrl(playbackId);
|
|
1955
|
+
const baseUrl = await getMuxStoryboardBaseUrl(playbackId, credentials);
|
|
1915
1956
|
const params = { width };
|
|
1916
1957
|
if (scope?.startTime !== void 0) {
|
|
1917
1958
|
params.asset_start_time = scope.startTime;
|
|
@@ -2473,7 +2514,7 @@ function concatenateVttSegments(segments, preamble = "WEBVTT") {
|
|
|
2473
2514
|
}
|
|
2474
2515
|
async function buildTranscriptUrl(playbackId, trackId, shouldSign = false, credentials) {
|
|
2475
2516
|
"use step";
|
|
2476
|
-
const baseUrl = `${getMuxStreamOrigin()}/${playbackId}/text/${trackId}.vtt`;
|
|
2517
|
+
const baseUrl = `${await getMuxStreamOrigin(credentials)}/${playbackId}/text/${trackId}.vtt`;
|
|
2477
2518
|
if (shouldSign) {
|
|
2478
2519
|
return signUrl(baseUrl, playbackId, "video", void 0, credentials);
|
|
2479
2520
|
}
|
|
@@ -3480,8 +3521,32 @@ async function hasBurnedInCaptionsInternal(assetId, options, collectedUsage) {
|
|
|
3480
3521
|
|
|
3481
3522
|
// src/workflows/chapters.ts
|
|
3482
3523
|
import { generateText as generateText3, Output as Output3 } from "ai";
|
|
3483
|
-
import
|
|
3524
|
+
import dedent5 from "dedent";
|
|
3484
3525
|
import { z as z5 } from "zod";
|
|
3526
|
+
|
|
3527
|
+
// src/prompts/chapters.ts
|
|
3528
|
+
import dedent4 from "dedent";
|
|
3529
|
+
function createChapterGuidance({
|
|
3530
|
+
terminologySource = "transcript"
|
|
3531
|
+
} = {}) {
|
|
3532
|
+
return {
|
|
3533
|
+
qualityGuidelines: {
|
|
3534
|
+
tag: "quality_guidelines",
|
|
3535
|
+
content: dedent4`
|
|
3536
|
+
- Create chapters at topic shifts or clear transitions
|
|
3537
|
+
- Keep chapter titles concise and descriptive`
|
|
3538
|
+
},
|
|
3539
|
+
titleGuidelines: {
|
|
3540
|
+
tag: "title_guidelines",
|
|
3541
|
+
content: dedent4`
|
|
3542
|
+
- Keep titles concise and descriptive
|
|
3543
|
+
- Avoid filler or generic labels like "Chapter 1"
|
|
3544
|
+
${terminologySource === "transcript" ? "- Use the transcript's terminology" : "- Use terminology from the provided evidence"}`
|
|
3545
|
+
}
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
// src/workflows/chapters.ts
|
|
3485
3550
|
var chapterSchema = z5.object({
|
|
3486
3551
|
startTime: z5.number(),
|
|
3487
3552
|
title: z5.string().max(300)
|
|
@@ -3539,6 +3604,7 @@ async function generateChaptersWithAI({
|
|
|
3539
3604
|
unexpectedChapterKeys
|
|
3540
3605
|
};
|
|
3541
3606
|
}
|
|
3607
|
+
var chapterGuidance = createChapterGuidance();
|
|
3542
3608
|
var chapterSystemPromptBuilder = createPromptBuilder({
|
|
3543
3609
|
template: {
|
|
3544
3610
|
role: {
|
|
@@ -3547,13 +3613,13 @@ var chapterSystemPromptBuilder = createPromptBuilder({
|
|
|
3547
3613
|
},
|
|
3548
3614
|
context: {
|
|
3549
3615
|
tag: "context",
|
|
3550
|
-
content:
|
|
3616
|
+
content: dedent5`
|
|
3551
3617
|
You receive a timestamped transcript with lines in the form "[12s] Caption text".
|
|
3552
3618
|
Use those timestamps as anchors to determine chapter start times in seconds.`
|
|
3553
3619
|
},
|
|
3554
3620
|
security: {
|
|
3555
3621
|
tag: "security",
|
|
3556
|
-
content:
|
|
3622
|
+
content: dedent5`
|
|
3557
3623
|
${NON_DISCLOSURE_CONSTRAINT}
|
|
3558
3624
|
|
|
3559
3625
|
${UNTRUSTED_USER_INPUT_NOTICE}
|
|
@@ -3562,34 +3628,32 @@ var chapterSystemPromptBuilder = createPromptBuilder({
|
|
|
3562
3628
|
},
|
|
3563
3629
|
constraints: {
|
|
3564
3630
|
tag: "constraints",
|
|
3565
|
-
content:
|
|
3631
|
+
content: dedent5`
|
|
3566
3632
|
- Only use information present in the transcript
|
|
3567
3633
|
- Return structured data that matches the requested JSON schema
|
|
3568
3634
|
- Do not add commentary or extra text outside the JSON
|
|
3569
3635
|
- When a <language> section is provided, all chapter titles MUST be written in that language`
|
|
3570
3636
|
},
|
|
3571
3637
|
qualityGuidelines: {
|
|
3572
|
-
|
|
3573
|
-
content:
|
|
3574
|
-
|
|
3575
|
-
- Keep chapter titles concise and descriptive
|
|
3576
|
-
- Ensure the first chapter starts at 0 seconds`
|
|
3638
|
+
...chapterGuidance.qualityGuidelines,
|
|
3639
|
+
content: `${chapterGuidance.qualityGuidelines.content}
|
|
3640
|
+
- Ensure the first chapter starts at 0 seconds`
|
|
3577
3641
|
}
|
|
3578
3642
|
},
|
|
3579
3643
|
sectionOrder: ["role", "context", "security", "constraints", "qualityGuidelines"]
|
|
3580
3644
|
});
|
|
3581
3645
|
var AUDIO_ONLY_SYSTEM_PROMPT_OVERRIDES = {
|
|
3582
3646
|
role: "You are an audio editor and transcript analyst specializing in segmenting content into logical chapters.",
|
|
3583
|
-
context:
|
|
3647
|
+
context: dedent5`
|
|
3584
3648
|
You receive a timestamped transcript from audio-only content with lines in the form "[12s] Transcript text".
|
|
3585
3649
|
Use those timestamps as anchors to determine chapter start times in seconds.`
|
|
3586
3650
|
};
|
|
3587
3651
|
var SCENE_CONTEXT_SYSTEM_PROMPT_OVERRIDES = {
|
|
3588
|
-
context:
|
|
3652
|
+
context: dedent5`
|
|
3589
3653
|
You receive a timestamped transcript with lines in the form "[12s] Caption text" and an ordered scene context.
|
|
3590
3654
|
Transcript timestamps are in seconds. Scene start and end timestamps are in milliseconds.
|
|
3591
3655
|
Use the scene progression to choose logical chapter groupings and scene starts as chapter boundaries.`,
|
|
3592
|
-
constraints:
|
|
3656
|
+
constraints: dedent5`
|
|
3593
3657
|
- Only use information present in the transcript and scene context
|
|
3594
3658
|
- Return structured data that matches the requested JSON schema
|
|
3595
3659
|
- Do not add commentary or extra text outside the JSON
|
|
@@ -3603,7 +3667,7 @@ var chaptersPromptBuilder = createPromptBuilder({
|
|
|
3603
3667
|
},
|
|
3604
3668
|
outputFormat: {
|
|
3605
3669
|
tag: "output_format",
|
|
3606
|
-
content:
|
|
3670
|
+
content: dedent5`
|
|
3607
3671
|
Return valid JSON in this exact shape:
|
|
3608
3672
|
{
|
|
3609
3673
|
"chapters": [
|
|
@@ -3620,13 +3684,7 @@ var chaptersPromptBuilder = createPromptBuilder({
|
|
|
3620
3684
|
content: ""
|
|
3621
3685
|
// Placeholder - always overridden with dynamic content
|
|
3622
3686
|
},
|
|
3623
|
-
titleGuidelines:
|
|
3624
|
-
tag: "title_guidelines",
|
|
3625
|
-
content: dedent4`
|
|
3626
|
-
- Keep titles concise and descriptive
|
|
3627
|
-
- Avoid filler or generic labels like "Chapter 1"
|
|
3628
|
-
- Use the transcript's terminology`
|
|
3629
|
-
}
|
|
3687
|
+
titleGuidelines: chapterGuidance.titleGuidelines
|
|
3630
3688
|
},
|
|
3631
3689
|
sectionOrder: ["task", "outputFormat", "chapterGuidelines", "titleGuidelines"]
|
|
3632
3690
|
});
|
|
@@ -3665,7 +3723,7 @@ function buildUserPrompt3({
|
|
|
3665
3723
|
} else if (scope) {
|
|
3666
3724
|
firstChapterGuideline = `- The first chapter must start at ${scope.startTime}s`;
|
|
3667
3725
|
}
|
|
3668
|
-
const dynamicChapterGuidelines =
|
|
3726
|
+
const dynamicChapterGuidelines = dedent5`
|
|
3669
3727
|
- Create at least ${minChaptersPerHour} and at most ${maxChaptersPerHour} chapters per hour of content
|
|
3670
3728
|
- Use start times in seconds (not HH:MM:SS)
|
|
3671
3729
|
${scope ? `- Analyze only the range from ${scope.startTime}s (inclusive) to ${scope.endTime}s (exclusive)` : ""}
|
|
@@ -5323,7 +5381,7 @@ async function generateVideoEmbeddings(assetId, options = {}) {
|
|
|
5323
5381
|
|
|
5324
5382
|
// src/workflows/engagement-insights.ts
|
|
5325
5383
|
import { generateText as generateText5, Output as Output5 } from "ai";
|
|
5326
|
-
import
|
|
5384
|
+
import dedent6 from "dedent";
|
|
5327
5385
|
import { z as z7 } from "zod";
|
|
5328
5386
|
|
|
5329
5387
|
// src/primitives/heatmap.ts
|
|
@@ -5592,7 +5650,7 @@ var AUDIO_ONLY_SYSTEM_PROMPT2 = promptDedent`
|
|
|
5592
5650
|
- Connect engagement data to content features
|
|
5593
5651
|
</quality_guidelines>
|
|
5594
5652
|
`;
|
|
5595
|
-
var INSIGHT_GUIDELINES =
|
|
5653
|
+
var INSIGHT_GUIDELINES = dedent6`
|
|
5596
5654
|
Provide explanatory insights that describe WHY moments are engaging.
|
|
5597
5655
|
Focus on observable patterns and viewer behavior correlation.
|
|
5598
5656
|
Example: "The cooking demonstration at 2:15 has 3x average engagement
|
|
@@ -5610,7 +5668,7 @@ var engagementInsightsPromptBuilder = createPromptBuilder({
|
|
|
5610
5668
|
},
|
|
5611
5669
|
outputFormat: {
|
|
5612
5670
|
tag: "output_format",
|
|
5613
|
-
content:
|
|
5671
|
+
content: dedent6`
|
|
5614
5672
|
Return valid JSON matching the provided schema.
|
|
5615
5673
|
Include insights for each hotspot and overall engagement trends.
|
|
5616
5674
|
Each momentInsight must include the hotspotIndex matching the input.
|
|
@@ -5618,7 +5676,7 @@ var engagementInsightsPromptBuilder = createPromptBuilder({
|
|
|
5618
5676
|
},
|
|
5619
5677
|
visualContext: {
|
|
5620
5678
|
tag: "visual_context",
|
|
5621
|
-
content:
|
|
5679
|
+
content: dedent6`
|
|
5622
5680
|
Images are provided for each hotspot timestamp.
|
|
5623
5681
|
A storyboard overview image may also be included showing the full video timeline.
|
|
5624
5682
|
Use visual evidence from these images to support your engagement analysis.
|
|
@@ -5632,14 +5690,14 @@ function buildUserPrompt4(hotspots, transcriptSegments, heatmap, isAudioOnly) {
|
|
|
5632
5690
|
const transcript = transcriptSegments[idx]?.text || "(no transcript)";
|
|
5633
5691
|
const startTimestamp = secondsToTimestamp(h.startMs / 1e3);
|
|
5634
5692
|
const endTimestamp = secondsToTimestamp(h.endMs / 1e3);
|
|
5635
|
-
return
|
|
5693
|
+
return dedent6`
|
|
5636
5694
|
Hotspot ${idx} (hotspotIndex: ${idx}):
|
|
5637
5695
|
- Time Range: ${startTimestamp} - ${endTimestamp}
|
|
5638
5696
|
- Engagement Score: ${h.score.toFixed(2)} (0=low, 1=high)
|
|
5639
5697
|
- Transcript: ${JSON.stringify(transcript)}
|
|
5640
5698
|
`;
|
|
5641
5699
|
}).join("\n\n");
|
|
5642
|
-
const heatmapData =
|
|
5700
|
+
const heatmapData = dedent6`
|
|
5643
5701
|
Engagement Heatmap (${heatmap.length} values, each representing 1/${heatmap.length}th of the video, higher = more re-watches):
|
|
5644
5702
|
[${heatmap.map((v) => v.toFixed(2)).join(", ")}]
|
|
5645
5703
|
`;
|
|
@@ -5705,7 +5763,7 @@ function mapShotsToHotspots(shots, hotspots) {
|
|
|
5705
5763
|
}
|
|
5706
5764
|
async function getThumbnailUrlsForHotspots(playbackId, hotspots, options = {}) {
|
|
5707
5765
|
const { width = 640, shouldSign = false, credentials } = options;
|
|
5708
|
-
const baseUrl = getMuxThumbnailBaseUrl(playbackId);
|
|
5766
|
+
const baseUrl = await getMuxThumbnailBaseUrl(playbackId, credentials);
|
|
5709
5767
|
const urlPromises = hotspots.map(async (h) => {
|
|
5710
5768
|
const time = Math.floor(h.startMs / 1e3);
|
|
5711
5769
|
if (shouldSign) {
|
|
@@ -6806,7 +6864,7 @@ async function getThumbnailUrls(playbackId, duration, options = {}) {
|
|
|
6806
6864
|
}
|
|
6807
6865
|
timestamps = newTimestamps;
|
|
6808
6866
|
}
|
|
6809
|
-
const baseUrl = getMuxThumbnailBaseUrl(playbackId);
|
|
6867
|
+
const baseUrl = await getMuxThumbnailBaseUrl(playbackId, credentials);
|
|
6810
6868
|
const urlPromises = timestamps.map(async (time) => {
|
|
6811
6869
|
const url = shouldSign ? await signUrl(baseUrl, playbackId, "thumbnail", { time, width }, credentials) : `${baseUrl}?time=${time}&width=${width}`;
|
|
6812
6870
|
return { url, time };
|
|
@@ -7270,7 +7328,7 @@ function filterTimestampsWithinExclusiveScope(timestampsMs, scope) {
|
|
|
7270
7328
|
async function getThumbnailUrlsFromTimestamps(playbackId, timestampsMs, options) {
|
|
7271
7329
|
"use step";
|
|
7272
7330
|
const { width, shouldSign, credentials } = options;
|
|
7273
|
-
const baseUrl = getMuxThumbnailBaseUrl(playbackId);
|
|
7331
|
+
const baseUrl = await getMuxThumbnailBaseUrl(playbackId, credentials);
|
|
7274
7332
|
const times = [...new Set(timestampsMs.map(toThumbnailTimeSeconds))];
|
|
7275
7333
|
const urlPromises = times.map(async (time) => {
|
|
7276
7334
|
const url = shouldSign ? await signUrl(baseUrl, playbackId, "thumbnail", { time, width }, credentials) : `${baseUrl}?time=${time}&width=${width}`;
|
|
@@ -7458,7 +7516,6 @@ async function getModerationScores(assetId, options = {}) {
|
|
|
7458
7516
|
|
|
7459
7517
|
// src/workflows/summarization.ts
|
|
7460
7518
|
import { generateText as generateText7, Output as Output7 } from "ai";
|
|
7461
|
-
import dedent6 from "dedent";
|
|
7462
7519
|
import { z as z9 } from "zod";
|
|
7463
7520
|
|
|
7464
7521
|
// src/lib/markdown-normalization.ts
|
|
@@ -7468,124 +7525,128 @@ function normalizeMarkdownDescription(description) {
|
|
|
7468
7525
|
return description.replace(ESCAPED_LINE_BREAK_REGEX, "\n").replace(/([^\n]) {2,}([*-]) (?=\S)/g, "$1\n$2 ").replace(/([.!?:;]) ([*-]) (?=\S)/g, "$1\n$2 ").replace(MULTIPLE_BLANK_LINES_REGEX, "\n\n");
|
|
7469
7526
|
}
|
|
7470
7527
|
|
|
7471
|
-
// src/
|
|
7528
|
+
// src/prompts/summarization.ts
|
|
7529
|
+
import dedent7 from "dedent";
|
|
7472
7530
|
var DEFAULT_SUMMARY_KEYWORD_LIMIT = 10;
|
|
7473
7531
|
var DEFAULT_TITLE_LENGTH = 10;
|
|
7474
7532
|
var DEFAULT_DESCRIPTION_LENGTH = 50;
|
|
7475
|
-
var summarySchema = z9.object({
|
|
7476
|
-
keywords: z9.array(z9.string().max(200)),
|
|
7477
|
-
title: z9.string().max(500),
|
|
7478
|
-
description: z9.string().max(1e4)
|
|
7479
|
-
});
|
|
7480
|
-
function buildSummarySchema(descriptionLength) {
|
|
7481
|
-
const descriptionMaxChars = Math.max(2e3, descriptionLength * 15);
|
|
7482
|
-
return z9.object({
|
|
7483
|
-
keywords: z9.array(z9.string().max(200)),
|
|
7484
|
-
title: z9.string().max(500),
|
|
7485
|
-
description: z9.string().max(descriptionMaxChars)
|
|
7486
|
-
});
|
|
7487
|
-
}
|
|
7488
|
-
var VALID_TONES = ["neutral", "playful", "professional"];
|
|
7489
|
-
var TONE_INSTRUCTIONS = {
|
|
7490
|
-
neutral: "Provide a clear, straightforward analysis.",
|
|
7491
|
-
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!",
|
|
7492
|
-
professional: "Provide a professional, executive-level analysis suitable for business reporting."
|
|
7493
|
-
};
|
|
7494
7533
|
var DESCRIPTION_LENGTH_THRESHOLD_SMALL = 25;
|
|
7495
7534
|
var DESCRIPTION_LENGTH_THRESHOLD_LARGE = 100;
|
|
7496
7535
|
function buildDescriptionGuidance(wordCount, contentType) {
|
|
7497
7536
|
if (wordCount < DESCRIPTION_LENGTH_THRESHOLD_SMALL) {
|
|
7498
7537
|
if (contentType === "video") {
|
|
7499
|
-
return
|
|
7538
|
+
return dedent7`A brief summary of the video in no more than ${wordCount} words. Shorter is fine.
|
|
7500
7539
|
Focus on the single most important subject or action.
|
|
7501
7540
|
Write in present tense.`;
|
|
7502
7541
|
}
|
|
7503
|
-
return
|
|
7542
|
+
return dedent7`A brief summary of the audio content in no more than ${wordCount} words. Shorter is fine.
|
|
7504
7543
|
Focus on the single most important topic or theme.
|
|
7505
7544
|
Write in present tense.`;
|
|
7506
7545
|
}
|
|
7507
7546
|
if (wordCount > DESCRIPTION_LENGTH_THRESHOLD_LARGE) {
|
|
7508
7547
|
if (contentType === "video") {
|
|
7509
|
-
return
|
|
7548
|
+
return dedent7`A detailed summary that describes what happens across the video.
|
|
7510
7549
|
Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
|
|
7511
7550
|
Be thorough: cover subjects, actions, setting, progression, and any notable details visible across frames.
|
|
7512
7551
|
Write in present tense. Be specific about observable details rather than making assumptions.
|
|
7513
7552
|
If the transcript provides dialogue or narration, incorporate key points but prioritize visual content.`;
|
|
7514
7553
|
}
|
|
7515
|
-
return
|
|
7554
|
+
return dedent7`A detailed summary that describes the audio content.
|
|
7516
7555
|
Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
|
|
7517
7556
|
Be thorough: cover topics, speakers, themes, progression, and any notable insights.
|
|
7518
7557
|
Write in present tense. Be specific about what is discussed or presented rather than making assumptions.
|
|
7519
7558
|
Focus on the spoken content and any key insights, dialogue, or narrative elements.`;
|
|
7520
7559
|
}
|
|
7521
7560
|
if (contentType === "video") {
|
|
7522
|
-
return
|
|
7561
|
+
return dedent7`A summary that describes what happens across the video.
|
|
7523
7562
|
Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
|
|
7524
7563
|
Cover the main subjects, actions, setting, and any notable progression visible across frames.
|
|
7525
7564
|
Write in present tense. Be specific about observable details rather than making assumptions.
|
|
7526
7565
|
If the transcript provides dialogue or narration, incorporate key points but prioritize visual content.`;
|
|
7527
7566
|
}
|
|
7528
|
-
return
|
|
7567
|
+
return dedent7`A summary that describes the audio content.
|
|
7529
7568
|
Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
|
|
7530
7569
|
Cover the main topics, speakers, themes, and any notable progression in the discussion or narration.
|
|
7531
7570
|
Write in present tense. Be specific about what is discussed or presented rather than making assumptions.
|
|
7532
7571
|
Focus on the spoken content and any key insights, dialogue, or narrative elements.`;
|
|
7533
7572
|
}
|
|
7534
|
-
function buildSceneAwareDescriptionGuidance(wordCount) {
|
|
7573
|
+
function buildSceneAwareDescriptionGuidance(wordCount, hasStoryboard) {
|
|
7574
|
+
const storyboardGuidance = hasStoryboard ? "Use the storyboard as direct visual evidence and the scene context to preserve progression." : "Use the ordered scene context as grounded evidence of the content and its progression.";
|
|
7535
7575
|
if (wordCount < DESCRIPTION_LENGTH_THRESHOLD_SMALL) {
|
|
7536
|
-
return
|
|
7576
|
+
return dedent7`A brief summary of the video in no more than ${wordCount} words. Shorter is fine.
|
|
7537
7577
|
Focus on the single most important subject or action across the ordered scenes.
|
|
7538
|
-
|
|
7578
|
+
${storyboardGuidance}
|
|
7539
7579
|
Write in present tense.`;
|
|
7540
7580
|
}
|
|
7541
7581
|
if (wordCount > DESCRIPTION_LENGTH_THRESHOLD_LARGE) {
|
|
7542
|
-
return
|
|
7582
|
+
return dedent7`A detailed summary that describes what happens across the ordered scenes.
|
|
7543
7583
|
Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
|
|
7544
7584
|
Be thorough: cover subjects, actions, setting, progression, and notable details supported across scenes.
|
|
7545
|
-
|
|
7585
|
+
${storyboardGuidance}
|
|
7546
7586
|
Write in present tense. If the transcript provides dialogue or narration, incorporate key points while preserving scene progression.`;
|
|
7547
7587
|
}
|
|
7548
|
-
return
|
|
7588
|
+
return dedent7`A summary that describes what happens across the ordered scenes.
|
|
7549
7589
|
Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
|
|
7550
7590
|
Cover the main subjects, actions, setting, and notable progression supported across scenes.
|
|
7551
|
-
|
|
7591
|
+
${storyboardGuidance}
|
|
7552
7592
|
Write in present tense. If the transcript provides dialogue or narration, incorporate key points while preserving scene progression.`;
|
|
7553
7593
|
}
|
|
7554
|
-
function
|
|
7594
|
+
function createSummarizationGuidance({
|
|
7595
|
+
mediaType = "video",
|
|
7596
|
+
titleLength,
|
|
7597
|
+
descriptionLength,
|
|
7598
|
+
tagCount,
|
|
7599
|
+
hasSceneContext = false,
|
|
7600
|
+
hasStoryboard = true
|
|
7601
|
+
} = {}) {
|
|
7555
7602
|
const titleLimit = titleLength ?? DEFAULT_TITLE_LENGTH;
|
|
7603
|
+
const descriptionLimit = descriptionLength ?? DEFAULT_DESCRIPTION_LENGTH;
|
|
7556
7604
|
const keywordLimit = tagCount ?? DEFAULT_SUMMARY_KEYWORD_LIMIT;
|
|
7557
|
-
const
|
|
7558
|
-
const
|
|
7559
|
-
|
|
7560
|
-
-
|
|
7561
|
-
-
|
|
7562
|
-
-
|
|
7563
|
-
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7605
|
+
const isAudioOnly = mediaType === "audio";
|
|
7606
|
+
const sceneAware = !isAudioOnly && hasSceneContext;
|
|
7607
|
+
const qualityGuidelines = isAudioOnly ? dedent7`
|
|
7608
|
+
- Analyze the full transcript to understand context and themes
|
|
7609
|
+
- Be precise: use specific terminology when mentioned
|
|
7610
|
+
- Capture the narrative: what is introduced, discussed, and concluded
|
|
7611
|
+
- Balance brevity with informativeness` : sceneAware ? dedent7`
|
|
7612
|
+
- Follow scene_index order to understand what begins, develops, and concludes
|
|
7613
|
+
- Use scene narratives and concepts as grounded evidence, preserving consistent terminology across the output
|
|
7614
|
+
${hasStoryboard ? "- Use the storyboard as direct visual evidence for whole-asset details" : "- Ground whole-asset details in the provided scene context and transcript when available"}
|
|
7615
|
+
- Balance brevity with informativeness` : dedent7`
|
|
7616
|
+
- Examine all frames to understand the full context and progression
|
|
7617
|
+
- Be precise: "golden retriever" is better than "dog" when identifiable
|
|
7618
|
+
- Capture the narrative: what begins, develops, and concludes
|
|
7619
|
+
- Balance brevity with informativeness`;
|
|
7620
|
+
return {
|
|
7621
|
+
title: {
|
|
7622
|
+
tag: "title_requirements",
|
|
7623
|
+
content: isAudioOnly ? dedent7`
|
|
7624
|
+
A concise, label-style title — not a sentence or description.
|
|
7625
|
+
Never exceed ${titleLimit} words, but shorter is better.
|
|
7626
|
+
Think of how a podcast episode title or playlist entry would read — e.g. "Weekly News Roundup" or "Interview with Dr. Smith".
|
|
7627
|
+
Start with the primary subject or topic. Never begin with "An audio of" or similar phrasing.
|
|
7628
|
+
Use specific nouns over lengthy descriptions. Avoid clauses, conjunctions, or narrative structure.` : dedent7`
|
|
7576
7629
|
A concise, label-style title — not a sentence or description.
|
|
7577
7630
|
Never exceed ${titleLimit} words, but shorter is better.
|
|
7578
7631
|
Think of how a video card title, playlist entry, or file name would read — e.g. "Predator: Badlands Trailer" or "Chef Prepares Holiday Feast".
|
|
7579
7632
|
Start with the primary subject or topic. Never begin with "A video of" or similar phrasing.
|
|
7580
7633
|
Use specific nouns over lengthy descriptions. Avoid clauses, conjunctions, or narrative structure.`
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7634
|
+
},
|
|
7635
|
+
description: {
|
|
7636
|
+
tag: "description_requirements",
|
|
7637
|
+
content: sceneAware ? buildSceneAwareDescriptionGuidance(descriptionLimit, hasStoryboard) : buildDescriptionGuidance(descriptionLimit, mediaType)
|
|
7638
|
+
},
|
|
7639
|
+
keywords: {
|
|
7640
|
+
tag: "keywords_requirements",
|
|
7641
|
+
content: isAudioOnly ? dedent7`
|
|
7642
|
+
Specific, searchable terms (up to ${keywordLimit}) that capture:
|
|
7643
|
+
- Primary topics and themes
|
|
7644
|
+
- Speakers or presenters (if named)
|
|
7645
|
+
- Key concepts and terminology
|
|
7646
|
+
- Content type (interview, lecture, music, etc.)
|
|
7647
|
+
- Genre or style (if applicable)
|
|
7648
|
+
Prefer concrete nouns and relevant terms over abstract concepts.
|
|
7649
|
+
Use lowercase. Avoid redundant or overly generic terms like "audio" or "content".` : dedent7`
|
|
7589
7650
|
Specific, searchable terms (up to ${keywordLimit}) that capture:
|
|
7590
7651
|
- Primary subjects (people, animals, objects)
|
|
7591
7652
|
- Actions and activities being performed
|
|
@@ -7594,57 +7655,51 @@ function createSummarizationBuilder({ titleLength, descriptionLength, tagCount }
|
|
|
7594
7655
|
- Style or genre (if applicable)
|
|
7595
7656
|
Prefer concrete nouns and action verbs over abstract concepts.
|
|
7596
7657
|
Use lowercase. Avoid redundant or overly generic terms like "video" or "content".`
|
|
7658
|
+
},
|
|
7659
|
+
qualityGuidelines: { tag: "quality_guidelines", content: qualityGuidelines }
|
|
7660
|
+
};
|
|
7661
|
+
}
|
|
7662
|
+
|
|
7663
|
+
// src/workflows/summarization.ts
|
|
7664
|
+
var summarySchema = z9.object({
|
|
7665
|
+
keywords: z9.array(z9.string().max(200)),
|
|
7666
|
+
title: z9.string().max(500),
|
|
7667
|
+
description: z9.string().max(1e4)
|
|
7668
|
+
});
|
|
7669
|
+
function buildSummarySchema(descriptionLength) {
|
|
7670
|
+
const descriptionMaxChars = Math.max(2e3, descriptionLength * 15);
|
|
7671
|
+
return z9.object({
|
|
7672
|
+
keywords: z9.array(z9.string().max(200)),
|
|
7673
|
+
title: z9.string().max(500),
|
|
7674
|
+
description: z9.string().max(descriptionMaxChars)
|
|
7675
|
+
});
|
|
7676
|
+
}
|
|
7677
|
+
var VALID_TONES = ["neutral", "playful", "professional"];
|
|
7678
|
+
var TONE_INSTRUCTIONS = {
|
|
7679
|
+
neutral: "Provide a clear, straightforward analysis.",
|
|
7680
|
+
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!",
|
|
7681
|
+
professional: "Provide a professional, executive-level analysis suitable for business reporting."
|
|
7682
|
+
};
|
|
7683
|
+
function createSummarizationBuilder(constraints = {}, { hasSceneContext = false } = {}) {
|
|
7684
|
+
return createPromptBuilder({
|
|
7685
|
+
template: {
|
|
7686
|
+
task: {
|
|
7687
|
+
tag: "task",
|
|
7688
|
+
content: 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."
|
|
7597
7689
|
},
|
|
7598
|
-
|
|
7599
|
-
tag: "quality_guidelines",
|
|
7600
|
-
content: qualityGuidelines
|
|
7601
|
-
}
|
|
7690
|
+
...createSummarizationGuidance({ ...constraints, hasSceneContext })
|
|
7602
7691
|
},
|
|
7603
7692
|
sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
|
|
7604
7693
|
});
|
|
7605
7694
|
}
|
|
7606
|
-
function createAudioOnlyBuilder(
|
|
7607
|
-
const titleLimit = titleLength ?? DEFAULT_TITLE_LENGTH;
|
|
7608
|
-
const keywordLimit = tagCount ?? DEFAULT_SUMMARY_KEYWORD_LIMIT;
|
|
7695
|
+
function createAudioOnlyBuilder(constraints = {}) {
|
|
7609
7696
|
return createPromptBuilder({
|
|
7610
7697
|
template: {
|
|
7611
7698
|
task: {
|
|
7612
7699
|
tag: "task",
|
|
7613
7700
|
content: "Analyze the transcript and generate metadata that captures the essence of the audio content."
|
|
7614
7701
|
},
|
|
7615
|
-
|
|
7616
|
-
tag: "title_requirements",
|
|
7617
|
-
content: dedent6`
|
|
7618
|
-
A concise, label-style title — not a sentence or description.
|
|
7619
|
-
Never exceed ${titleLimit} words, but shorter is better.
|
|
7620
|
-
Think of how a podcast episode title or playlist entry would read — e.g. "Weekly News Roundup" or "Interview with Dr. Smith".
|
|
7621
|
-
Start with the primary subject or topic. Never begin with "An audio of" or similar phrasing.
|
|
7622
|
-
Use specific nouns over lengthy descriptions. Avoid clauses, conjunctions, or narrative structure.`
|
|
7623
|
-
},
|
|
7624
|
-
description: {
|
|
7625
|
-
tag: "description_requirements",
|
|
7626
|
-
content: buildDescriptionGuidance(descriptionLength ?? DEFAULT_DESCRIPTION_LENGTH, "audio")
|
|
7627
|
-
},
|
|
7628
|
-
keywords: {
|
|
7629
|
-
tag: "keywords_requirements",
|
|
7630
|
-
content: dedent6`
|
|
7631
|
-
Specific, searchable terms (up to ${keywordLimit}) that capture:
|
|
7632
|
-
- Primary topics and themes
|
|
7633
|
-
- Speakers or presenters (if named)
|
|
7634
|
-
- Key concepts and terminology
|
|
7635
|
-
- Content type (interview, lecture, music, etc.)
|
|
7636
|
-
- Genre or style (if applicable)
|
|
7637
|
-
Prefer concrete nouns and relevant terms over abstract concepts.
|
|
7638
|
-
Use lowercase. Avoid redundant or overly generic terms like "audio" or "content".`
|
|
7639
|
-
},
|
|
7640
|
-
qualityGuidelines: {
|
|
7641
|
-
tag: "quality_guidelines",
|
|
7642
|
-
content: dedent6`
|
|
7643
|
-
- Analyze the full transcript to understand context and themes
|
|
7644
|
-
- Be precise: use specific terminology when mentioned
|
|
7645
|
-
- Capture the narrative: what is introduced, discussed, and concluded
|
|
7646
|
-
- Balance brevity with informativeness`
|
|
7647
|
-
}
|
|
7702
|
+
...createSummarizationGuidance({ ...constraints, mediaType: "audio" })
|
|
7648
7703
|
},
|
|
7649
7704
|
sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
|
|
7650
7705
|
});
|
|
@@ -8457,7 +8512,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
|
|
|
8457
8512
|
{ type: "validation_error" }
|
|
8458
8513
|
);
|
|
8459
8514
|
}
|
|
8460
|
-
let audioUrl = `${getMuxStreamOrigin()}/${playbackId}/audio.m4a`;
|
|
8515
|
+
let audioUrl = `${await getMuxStreamOrigin(credentials)}/${playbackId}/audio.m4a`;
|
|
8461
8516
|
if (policy === "signed") {
|
|
8462
8517
|
audioUrl = await signUrl(audioUrl, playbackId, "video", void 0, credentials);
|
|
8463
8518
|
}
|