@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.
@@ -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 DEFAULT_MUX_IMAGE_ORIGIN = "https://image.mux.com";
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 getMuxImageOrigin() {
1849
- const override = env_default.MUX_IMAGE_URL_OVERRIDE;
1850
- if (!override) {
1851
- return DEFAULT_MUX_IMAGE_ORIGIN;
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 normalizeOrigin(override, "MUX_IMAGE_URL_OVERRIDE");
1879
+ return parsed.origin;
1854
1880
  }
1855
- function getMuxStreamOrigin() {
1856
- const override = env_default.MUX_STREAM_URL_OVERRIDE;
1857
- if (!override) {
1858
- return DEFAULT_MUX_STREAM_ORIGIN;
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
- return normalizeOrigin(override, "MUX_STREAM_URL_OVERRIDE");
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 dedent4 from "dedent";
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: dedent4`
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: dedent4`
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: dedent4`
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
- tag: "quality_guidelines",
3573
- content: dedent4`
3574
- - Create chapters at topic shifts or clear transitions
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: dedent4`
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: dedent4`
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: dedent4`
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: dedent4`
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 = dedent4`
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 dedent5 from "dedent";
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 = dedent5`
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: dedent5`
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: dedent5`
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 dedent5`
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 = dedent5`
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/workflows/summarization.ts
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 dedent6`A brief summary of the video in no more than ${wordCount} words. Shorter is fine.
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 dedent6`A brief summary of the audio content in no more than ${wordCount} words. Shorter is fine.
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 dedent6`A detailed summary that describes what happens across the video.
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 dedent6`A detailed summary that describes the audio content.
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 dedent6`A summary that describes what happens across the video.
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 dedent6`A summary that describes the audio content.
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 dedent6`A brief summary of the video in no more than ${wordCount} words. Shorter is fine.
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
- Use the storyboard as direct visual evidence and the scene context to preserve progression.
7578
+ ${storyboardGuidance}
7539
7579
  Write in present tense.`;
7540
7580
  }
7541
7581
  if (wordCount > DESCRIPTION_LENGTH_THRESHOLD_LARGE) {
7542
- return dedent6`A detailed summary that describes what happens across the ordered scenes.
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
- Use the storyboard as direct visual evidence and the scene context to preserve progression.
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 dedent6`A summary that describes what happens across the ordered scenes.
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
- Use the storyboard as direct visual evidence and the scene context to preserve progression.
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 createSummarizationBuilder({ titleLength, descriptionLength, tagCount } = {}, { hasSceneContext = false } = {}) {
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 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.";
7558
- const qualityGuidelines = hasSceneContext ? dedent6`
7559
- - Follow scene_index order to understand what begins, develops, and concludes
7560
- - Use scene narratives and concepts as grounded evidence, preserving consistent terminology across the output
7561
- - Use the storyboard as direct visual evidence for whole-asset details
7562
- - Balance brevity with informativeness` : dedent6`
7563
- - Examine all frames to understand the full context and progression
7564
- - Be precise: "golden retriever" is better than "dog" when identifiable
7565
- - Capture the narrative: what begins, develops, and concludes
7566
- - Balance brevity with informativeness`;
7567
- return createPromptBuilder({
7568
- template: {
7569
- task: {
7570
- tag: "task",
7571
- content: task
7572
- },
7573
- title: {
7574
- tag: "title_requirements",
7575
- content: dedent6`
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
- description: {
7583
- tag: "description_requirements",
7584
- content: hasSceneContext ? buildSceneAwareDescriptionGuidance(descriptionLength ?? DEFAULT_DESCRIPTION_LENGTH) : buildDescriptionGuidance(descriptionLength ?? DEFAULT_DESCRIPTION_LENGTH, "video")
7585
- },
7586
- keywords: {
7587
- tag: "keywords_requirements",
7588
- content: dedent6`
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
- qualityGuidelines: {
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({ titleLength, descriptionLength, tagCount } = {}) {
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
- title: {
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
  }