@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.js CHANGED
@@ -5,7 +5,7 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // package.json
8
- var version = "0.43.0";
8
+ var version = "0.45.0";
9
9
 
10
10
  // src/lib/language-codes.ts
11
11
  var ISO639_1_TO_3 = {
@@ -204,12 +204,16 @@ var EnvSchema = z.object({
204
204
  ),
205
205
  MUX_SIGNING_KEY: optionalString("Mux signing key ID for signed playback URLs.", "Used to sign playback URLs"),
206
206
  MUX_PRIVATE_KEY: optionalString("Mux signing private key for signed playback URLs.", "Used to sign playback URLs"),
207
+ MUX_CUSTOM_DOMAIN: optionalString(
208
+ "Mux domain for image.* and stream.* playback URLs, e.g. staging.mux.com or a custom domain (defaults to mux.com).",
209
+ "Mux custom domain"
210
+ ),
207
211
  MUX_IMAGE_URL_OVERRIDE: optionalString(
208
- "Override for Mux image base URL (defaults to https://image.mux.com).",
212
+ "Deprecated: use MUX_CUSTOM_DOMAIN. Override for Mux image base URL (defaults to https://image.mux.com).",
209
213
  "Mux image URL override"
210
214
  ),
211
215
  MUX_STREAM_URL_OVERRIDE: optionalString(
212
- "Override for Mux stream base URL (defaults to https://stream.mux.com).",
216
+ "Deprecated: use MUX_CUSTOM_DOMAIN. Override for Mux stream base URL (defaults to https://stream.mux.com).",
213
217
  "Mux stream URL override"
214
218
  ),
215
219
  MUX_BASE_URL: optionalString(
@@ -328,7 +332,18 @@ function parseEnv() {
328
332
  }
329
333
  return parsedEnv.data;
330
334
  }
335
+ var hasWarnedAboutDeprecatedUrlOverrides = false;
336
+ function warnOnDeprecatedUrlOverrides(parsed) {
337
+ if (hasWarnedAboutDeprecatedUrlOverrides || !parsed.MUX_IMAGE_URL_OVERRIDE && !parsed.MUX_STREAM_URL_OVERRIDE) {
338
+ return;
339
+ }
340
+ hasWarnedAboutDeprecatedUrlOverrides = true;
341
+ console.warn(
342
+ "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)."
343
+ );
344
+ }
331
345
  var env = parseEnv();
346
+ warnOnDeprecatedUrlOverrides(env);
332
347
  var env_default = env;
333
348
 
334
349
  // src/lib/prompt-fragments.ts
@@ -1354,6 +1369,82 @@ async function replaceAndCreateTextTrack(input) {
1354
1369
  return replaceAndCreateTrack({ ...input, target: { ...input.target, type: "text" } });
1355
1370
  }
1356
1371
 
1372
+ // src/lib/mux-url.ts
1373
+ var DEFAULT_MUX_DOMAIN = "mux.com";
1374
+ function normalizeOrigin(value, envVarName) {
1375
+ const trimmed = value.trim();
1376
+ const candidate = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
1377
+ const exampleHostname = envVarName === "MUX_IMAGE_URL_OVERRIDE" ? "image.example.mux.com" : "stream.example.mux.com";
1378
+ let parsed;
1379
+ try {
1380
+ parsed = new URL(candidate);
1381
+ } catch {
1382
+ throw new Error(
1383
+ `Invalid ${envVarName}. Provide a hostname (e.g. "${exampleHostname}") or a URL origin (e.g. "https://${exampleHostname}").`
1384
+ );
1385
+ }
1386
+ if (parsed.username || parsed.password || parsed.search || parsed.hash || parsed.pathname && parsed.pathname !== "/") {
1387
+ throw new Error(
1388
+ `Invalid ${envVarName}. Only a hostname/origin is allowed (no credentials, query params, hash fragments, or path).`
1389
+ );
1390
+ }
1391
+ return parsed.origin;
1392
+ }
1393
+ function buildPlaybackOrigin(service, customDomain, source) {
1394
+ const invalidDomainError = new Error(
1395
+ `Invalid ${source}. Provide a bare hostname (e.g. "media.example.com") with no scheme, port, credentials, query params, hash fragments, or path.`
1396
+ );
1397
+ const trimmed = customDomain.trim();
1398
+ if (!trimmed) {
1399
+ throw invalidDomainError;
1400
+ }
1401
+ let parsed;
1402
+ try {
1403
+ parsed = new URL(`https://${service}.${trimmed}`);
1404
+ } catch {
1405
+ throw invalidDomainError;
1406
+ }
1407
+ if (parsed.port || parsed.username || parsed.password || parsed.search || parsed.hash || parsed.pathname !== "/") {
1408
+ throw invalidDomainError;
1409
+ }
1410
+ return parsed.origin;
1411
+ }
1412
+ function getMuxPlaybackOrigin(service, customDomain = DEFAULT_MUX_DOMAIN) {
1413
+ return buildPlaybackOrigin(service, customDomain, "custom domain");
1414
+ }
1415
+ async function resolveMuxPlaybackOrigin(service, credentials) {
1416
+ "use step";
1417
+ const { muxCustomDomain } = await resolveWorkflowCredentials(credentials);
1418
+ if (muxCustomDomain) {
1419
+ return buildPlaybackOrigin(service, muxCustomDomain, "muxCustomDomain");
1420
+ }
1421
+ const legacyOverrideEnvVarName = service === "image" ? "MUX_IMAGE_URL_OVERRIDE" : "MUX_STREAM_URL_OVERRIDE";
1422
+ const legacyOverride = env_default[legacyOverrideEnvVarName];
1423
+ if (legacyOverride) {
1424
+ return normalizeOrigin(legacyOverride, legacyOverrideEnvVarName);
1425
+ }
1426
+ if (env_default.MUX_CUSTOM_DOMAIN) {
1427
+ return buildPlaybackOrigin(service, env_default.MUX_CUSTOM_DOMAIN, "MUX_CUSTOM_DOMAIN");
1428
+ }
1429
+ return buildPlaybackOrigin(service, DEFAULT_MUX_DOMAIN, "custom domain");
1430
+ }
1431
+ async function getMuxImageOrigin(credentials) {
1432
+ return resolveMuxPlaybackOrigin("image", credentials);
1433
+ }
1434
+ async function getMuxStreamOrigin(credentials) {
1435
+ return resolveMuxPlaybackOrigin("stream", credentials);
1436
+ }
1437
+ async function getMuxImageBaseUrl(playbackId, assetType, credentials) {
1438
+ const origin = await getMuxImageOrigin(credentials);
1439
+ return `${origin}/${playbackId}/${assetType}.png`;
1440
+ }
1441
+ async function getMuxStoryboardBaseUrl(playbackId, credentials) {
1442
+ return getMuxImageBaseUrl(playbackId, "storyboard", credentials);
1443
+ }
1444
+ async function getMuxThumbnailBaseUrl(playbackId, credentials) {
1445
+ return getMuxImageBaseUrl(playbackId, "thumbnail", credentials);
1446
+ }
1447
+
1357
1448
  // node_modules/@workflow/serde/dist/index.js
1358
1449
  var WORKFLOW_SERIALIZE = /* @__PURE__ */ Symbol.for("workflow-serialize");
1359
1450
  var WORKFLOW_DESERIALIZE = /* @__PURE__ */ Symbol.for("workflow-deserialize");
@@ -2309,53 +2400,6 @@ async function waitForShotsForAsset(assetId, options = {}) {
2309
2400
  );
2310
2401
  }
2311
2402
 
2312
- // src/lib/mux-url.ts
2313
- var DEFAULT_MUX_IMAGE_ORIGIN = "https://image.mux.com";
2314
- var DEFAULT_MUX_STREAM_ORIGIN = "https://stream.mux.com";
2315
- function normalizeOrigin(value, envVarName) {
2316
- const trimmed = value.trim();
2317
- const candidate = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
2318
- const exampleHostname = envVarName === "MUX_IMAGE_URL_OVERRIDE" ? "image.example.mux.com" : "stream.example.mux.com";
2319
- let parsed;
2320
- try {
2321
- parsed = new URL(candidate);
2322
- } catch {
2323
- throw new Error(
2324
- `Invalid ${envVarName}. Provide a hostname (e.g. "${exampleHostname}") or a URL origin (e.g. "https://${exampleHostname}").`
2325
- );
2326
- }
2327
- if (parsed.username || parsed.password || parsed.search || parsed.hash || parsed.pathname && parsed.pathname !== "/") {
2328
- throw new Error(
2329
- `Invalid ${envVarName}. Only a hostname/origin is allowed (no credentials, query params, hash fragments, or path).`
2330
- );
2331
- }
2332
- return parsed.origin;
2333
- }
2334
- function getMuxImageOrigin() {
2335
- const override = env_default.MUX_IMAGE_URL_OVERRIDE;
2336
- if (!override) {
2337
- return DEFAULT_MUX_IMAGE_ORIGIN;
2338
- }
2339
- return normalizeOrigin(override, "MUX_IMAGE_URL_OVERRIDE");
2340
- }
2341
- function getMuxStreamOrigin() {
2342
- const override = env_default.MUX_STREAM_URL_OVERRIDE;
2343
- if (!override) {
2344
- return DEFAULT_MUX_STREAM_ORIGIN;
2345
- }
2346
- return normalizeOrigin(override, "MUX_STREAM_URL_OVERRIDE");
2347
- }
2348
- function getMuxImageBaseUrl(playbackId, assetType) {
2349
- const origin = getMuxImageOrigin();
2350
- return `${origin}/${playbackId}/${assetType}.png`;
2351
- }
2352
- function getMuxStoryboardBaseUrl(playbackId) {
2353
- return getMuxImageBaseUrl(playbackId, "storyboard");
2354
- }
2355
- function getMuxThumbnailBaseUrl(playbackId) {
2356
- return getMuxImageBaseUrl(playbackId, "thumbnail");
2357
- }
2358
-
2359
2403
  // src/lib/url-signing.ts
2360
2404
  async function createSigningClient(context) {
2361
2405
  const { default: MuxClient } = await import("@mux/mux-node");
@@ -2397,7 +2441,7 @@ async function signUrl(url, playbackId, type = "video", params, credentials) {
2397
2441
  var DEFAULT_STORYBOARD_WIDTH = 640;
2398
2442
  async function getStoryboardUrl(playbackId, width = DEFAULT_STORYBOARD_WIDTH, shouldSign = false, credentials, scope) {
2399
2443
  "use step";
2400
- const baseUrl = getMuxStoryboardBaseUrl(playbackId);
2444
+ const baseUrl = await getMuxStoryboardBaseUrl(playbackId, credentials);
2401
2445
  const params = { width };
2402
2446
  if (scope?.startTime !== void 0) {
2403
2447
  params.asset_start_time = scope.startTime;
@@ -2746,7 +2790,7 @@ async function getThumbnailUrls(playbackId, duration, options = {}) {
2746
2790
  }
2747
2791
  timestamps = newTimestamps;
2748
2792
  }
2749
- const baseUrl = getMuxThumbnailBaseUrl(playbackId);
2793
+ const baseUrl = await getMuxThumbnailBaseUrl(playbackId, credentials);
2750
2794
  const urlPromises = timestamps.map(async (time) => {
2751
2795
  const url = shouldSign ? await signUrl(baseUrl, playbackId, "thumbnail", { time, width }, credentials) : `${baseUrl}?time=${time}&width=${width}`;
2752
2796
  return { url, time };
@@ -3189,7 +3233,7 @@ function concatenateVttSegments(segments, preamble = "WEBVTT") {
3189
3233
  }
3190
3234
  async function buildTranscriptUrl(playbackId, trackId, shouldSign = false, credentials) {
3191
3235
  "use step";
3192
- const baseUrl = `${getMuxStreamOrigin()}/${playbackId}/text/${trackId}.vtt`;
3236
+ const baseUrl = `${await getMuxStreamOrigin(credentials)}/${playbackId}/text/${trackId}.vtt`;
3193
3237
  if (shouldSign) {
3194
3238
  return signUrl(baseUrl, playbackId, "video", void 0, credentials);
3195
3239
  }
@@ -4699,8 +4743,32 @@ async function hasBurnedInCaptionsInternal(assetId, options, collectedUsage) {
4699
4743
 
4700
4744
  // src/workflows/chapters.ts
4701
4745
  import { generateText as generateText3, Output as Output3 } from "ai";
4702
- import dedent4 from "dedent";
4746
+ import dedent5 from "dedent";
4703
4747
  import { z as z5 } from "zod";
4748
+
4749
+ // src/prompts/chapters.ts
4750
+ import dedent4 from "dedent";
4751
+ function createChapterGuidance({
4752
+ terminologySource = "transcript"
4753
+ } = {}) {
4754
+ return {
4755
+ qualityGuidelines: {
4756
+ tag: "quality_guidelines",
4757
+ content: dedent4`
4758
+ - Create chapters at topic shifts or clear transitions
4759
+ - Keep chapter titles concise and descriptive`
4760
+ },
4761
+ titleGuidelines: {
4762
+ tag: "title_guidelines",
4763
+ content: dedent4`
4764
+ - Keep titles concise and descriptive
4765
+ - Avoid filler or generic labels like "Chapter 1"
4766
+ ${terminologySource === "transcript" ? "- Use the transcript's terminology" : "- Use terminology from the provided evidence"}`
4767
+ }
4768
+ };
4769
+ }
4770
+
4771
+ // src/workflows/chapters.ts
4704
4772
  var chapterSchema = z5.object({
4705
4773
  startTime: z5.number(),
4706
4774
  title: z5.string().max(300)
@@ -4758,6 +4826,7 @@ async function generateChaptersWithAI({
4758
4826
  unexpectedChapterKeys
4759
4827
  };
4760
4828
  }
4829
+ var chapterGuidance = createChapterGuidance();
4761
4830
  var chapterSystemPromptBuilder = createPromptBuilder({
4762
4831
  template: {
4763
4832
  role: {
@@ -4766,13 +4835,13 @@ var chapterSystemPromptBuilder = createPromptBuilder({
4766
4835
  },
4767
4836
  context: {
4768
4837
  tag: "context",
4769
- content: dedent4`
4838
+ content: dedent5`
4770
4839
  You receive a timestamped transcript with lines in the form "[12s] Caption text".
4771
4840
  Use those timestamps as anchors to determine chapter start times in seconds.`
4772
4841
  },
4773
4842
  security: {
4774
4843
  tag: "security",
4775
- content: dedent4`
4844
+ content: dedent5`
4776
4845
  ${NON_DISCLOSURE_CONSTRAINT}
4777
4846
 
4778
4847
  ${UNTRUSTED_USER_INPUT_NOTICE}
@@ -4781,34 +4850,32 @@ var chapterSystemPromptBuilder = createPromptBuilder({
4781
4850
  },
4782
4851
  constraints: {
4783
4852
  tag: "constraints",
4784
- content: dedent4`
4853
+ content: dedent5`
4785
4854
  - Only use information present in the transcript
4786
4855
  - Return structured data that matches the requested JSON schema
4787
4856
  - Do not add commentary or extra text outside the JSON
4788
4857
  - When a <language> section is provided, all chapter titles MUST be written in that language`
4789
4858
  },
4790
4859
  qualityGuidelines: {
4791
- tag: "quality_guidelines",
4792
- content: dedent4`
4793
- - Create chapters at topic shifts or clear transitions
4794
- - Keep chapter titles concise and descriptive
4795
- - Ensure the first chapter starts at 0 seconds`
4860
+ ...chapterGuidance.qualityGuidelines,
4861
+ content: `${chapterGuidance.qualityGuidelines.content}
4862
+ - Ensure the first chapter starts at 0 seconds`
4796
4863
  }
4797
4864
  },
4798
4865
  sectionOrder: ["role", "context", "security", "constraints", "qualityGuidelines"]
4799
4866
  });
4800
4867
  var AUDIO_ONLY_SYSTEM_PROMPT_OVERRIDES = {
4801
4868
  role: "You are an audio editor and transcript analyst specializing in segmenting content into logical chapters.",
4802
- context: dedent4`
4869
+ context: dedent5`
4803
4870
  You receive a timestamped transcript from audio-only content with lines in the form "[12s] Transcript text".
4804
4871
  Use those timestamps as anchors to determine chapter start times in seconds.`
4805
4872
  };
4806
4873
  var SCENE_CONTEXT_SYSTEM_PROMPT_OVERRIDES = {
4807
- context: dedent4`
4874
+ context: dedent5`
4808
4875
  You receive a timestamped transcript with lines in the form "[12s] Caption text" and an ordered scene context.
4809
4876
  Transcript timestamps are in seconds. Scene start and end timestamps are in milliseconds.
4810
4877
  Use the scene progression to choose logical chapter groupings and scene starts as chapter boundaries.`,
4811
- constraints: dedent4`
4878
+ constraints: dedent5`
4812
4879
  - Only use information present in the transcript and scene context
4813
4880
  - Return structured data that matches the requested JSON schema
4814
4881
  - Do not add commentary or extra text outside the JSON
@@ -4822,7 +4889,7 @@ var chaptersPromptBuilder = createPromptBuilder({
4822
4889
  },
4823
4890
  outputFormat: {
4824
4891
  tag: "output_format",
4825
- content: dedent4`
4892
+ content: dedent5`
4826
4893
  Return valid JSON in this exact shape:
4827
4894
  {
4828
4895
  "chapters": [
@@ -4839,13 +4906,7 @@ var chaptersPromptBuilder = createPromptBuilder({
4839
4906
  content: ""
4840
4907
  // Placeholder - always overridden with dynamic content
4841
4908
  },
4842
- titleGuidelines: {
4843
- tag: "title_guidelines",
4844
- content: dedent4`
4845
- - Keep titles concise and descriptive
4846
- - Avoid filler or generic labels like "Chapter 1"
4847
- - Use the transcript's terminology`
4848
- }
4909
+ titleGuidelines: chapterGuidance.titleGuidelines
4849
4910
  },
4850
4911
  sectionOrder: ["task", "outputFormat", "chapterGuidelines", "titleGuidelines"]
4851
4912
  });
@@ -4884,7 +4945,7 @@ function buildUserPrompt3({
4884
4945
  } else if (scope) {
4885
4946
  firstChapterGuideline = `- The first chapter must start at ${scope.startTime}s`;
4886
4947
  }
4887
- const dynamicChapterGuidelines = dedent4`
4948
+ const dynamicChapterGuidelines = dedent5`
4888
4949
  - Create at least ${minChaptersPerHour} and at most ${maxChaptersPerHour} chapters per hour of content
4889
4950
  - Use start times in seconds (not HH:MM:SS)
4890
4951
  ${scope ? `- Analyze only the range from ${scope.startTime}s (inclusive) to ${scope.endTime}s (exclusive)` : ""}
@@ -5862,7 +5923,7 @@ async function generateVideoEmbeddings(assetId, options = {}) {
5862
5923
 
5863
5924
  // src/workflows/engagement-insights.ts
5864
5925
  import { generateText as generateText5, Output as Output5 } from "ai";
5865
- import dedent5 from "dedent";
5926
+ import dedent6 from "dedent";
5866
5927
  import { z as z7 } from "zod";
5867
5928
  var aiMomentInsightSchema = z7.object({
5868
5929
  hotspotIndex: z7.number(),
@@ -5985,7 +6046,7 @@ var AUDIO_ONLY_SYSTEM_PROMPT2 = promptDedent`
5985
6046
  - Connect engagement data to content features
5986
6047
  </quality_guidelines>
5987
6048
  `;
5988
- var INSIGHT_GUIDELINES = dedent5`
6049
+ var INSIGHT_GUIDELINES = dedent6`
5989
6050
  Provide explanatory insights that describe WHY moments are engaging.
5990
6051
  Focus on observable patterns and viewer behavior correlation.
5991
6052
  Example: "The cooking demonstration at 2:15 has 3x average engagement
@@ -6003,7 +6064,7 @@ var engagementInsightsPromptBuilder = createPromptBuilder({
6003
6064
  },
6004
6065
  outputFormat: {
6005
6066
  tag: "output_format",
6006
- content: dedent5`
6067
+ content: dedent6`
6007
6068
  Return valid JSON matching the provided schema.
6008
6069
  Include insights for each hotspot and overall engagement trends.
6009
6070
  Each momentInsight must include the hotspotIndex matching the input.
@@ -6011,7 +6072,7 @@ var engagementInsightsPromptBuilder = createPromptBuilder({
6011
6072
  },
6012
6073
  visualContext: {
6013
6074
  tag: "visual_context",
6014
- content: dedent5`
6075
+ content: dedent6`
6015
6076
  Images are provided for each hotspot timestamp.
6016
6077
  A storyboard overview image may also be included showing the full video timeline.
6017
6078
  Use visual evidence from these images to support your engagement analysis.
@@ -6025,14 +6086,14 @@ function buildUserPrompt4(hotspots, transcriptSegments, heatmap, isAudioOnly) {
6025
6086
  const transcript = transcriptSegments[idx]?.text || "(no transcript)";
6026
6087
  const startTimestamp = secondsToTimestamp(h.startMs / 1e3);
6027
6088
  const endTimestamp = secondsToTimestamp(h.endMs / 1e3);
6028
- return dedent5`
6089
+ return dedent6`
6029
6090
  Hotspot ${idx} (hotspotIndex: ${idx}):
6030
6091
  - Time Range: ${startTimestamp} - ${endTimestamp}
6031
6092
  - Engagement Score: ${h.score.toFixed(2)} (0=low, 1=high)
6032
6093
  - Transcript: ${JSON.stringify(transcript)}
6033
6094
  `;
6034
6095
  }).join("\n\n");
6035
- const heatmapData = dedent5`
6096
+ const heatmapData = dedent6`
6036
6097
  Engagement Heatmap (${heatmap.length} values, each representing 1/${heatmap.length}th of the video, higher = more re-watches):
6037
6098
  [${heatmap.map((v) => v.toFixed(2)).join(", ")}]
6038
6099
  `;
@@ -6098,7 +6159,7 @@ function mapShotsToHotspots(shots, hotspots) {
6098
6159
  }
6099
6160
  async function getThumbnailUrlsForHotspots(playbackId, hotspots, options = {}) {
6100
6161
  const { width = 640, shouldSign = false, credentials } = options;
6101
- const baseUrl = getMuxThumbnailBaseUrl(playbackId);
6162
+ const baseUrl = await getMuxThumbnailBaseUrl(playbackId, credentials);
6102
6163
  const urlPromises = hotspots.map(async (h) => {
6103
6164
  const time = Math.floor(h.startMs / 1e3);
6104
6165
  if (shouldSign) {
@@ -7622,7 +7683,7 @@ function filterTimestampsWithinExclusiveScope(timestampsMs, scope) {
7622
7683
  async function getThumbnailUrlsFromTimestamps(playbackId, timestampsMs, options) {
7623
7684
  "use step";
7624
7685
  const { width, shouldSign, credentials } = options;
7625
- const baseUrl = getMuxThumbnailBaseUrl(playbackId);
7686
+ const baseUrl = await getMuxThumbnailBaseUrl(playbackId, credentials);
7626
7687
  const times = [...new Set(timestampsMs.map(toThumbnailTimeSeconds))];
7627
7688
  const urlPromises = times.map(async (time) => {
7628
7689
  const url = shouldSign ? await signUrl(baseUrl, playbackId, "thumbnail", { time, width }, credentials) : `${baseUrl}?time=${time}&width=${width}`;
@@ -7810,7 +7871,6 @@ async function getModerationScores(assetId, options = {}) {
7810
7871
 
7811
7872
  // src/workflows/summarization.ts
7812
7873
  import { generateText as generateText7, Output as Output7 } from "ai";
7813
- import dedent6 from "dedent";
7814
7874
  import { z as z9 } from "zod";
7815
7875
 
7816
7876
  // src/lib/markdown-normalization.ts
@@ -7820,124 +7880,128 @@ function normalizeMarkdownDescription(description) {
7820
7880
  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");
7821
7881
  }
7822
7882
 
7823
- // src/workflows/summarization.ts
7883
+ // src/prompts/summarization.ts
7884
+ import dedent7 from "dedent";
7824
7885
  var DEFAULT_SUMMARY_KEYWORD_LIMIT = 10;
7825
7886
  var DEFAULT_TITLE_LENGTH = 10;
7826
7887
  var DEFAULT_DESCRIPTION_LENGTH = 50;
7827
- var summarySchema = z9.object({
7828
- keywords: z9.array(z9.string().max(200)),
7829
- title: z9.string().max(500),
7830
- description: z9.string().max(1e4)
7831
- });
7832
- function buildSummarySchema(descriptionLength) {
7833
- const descriptionMaxChars = Math.max(2e3, descriptionLength * 15);
7834
- return z9.object({
7835
- keywords: z9.array(z9.string().max(200)),
7836
- title: z9.string().max(500),
7837
- description: z9.string().max(descriptionMaxChars)
7838
- });
7839
- }
7840
- var VALID_TONES = ["neutral", "playful", "professional"];
7841
- var TONE_INSTRUCTIONS = {
7842
- neutral: "Provide a clear, straightforward analysis.",
7843
- 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!",
7844
- professional: "Provide a professional, executive-level analysis suitable for business reporting."
7845
- };
7846
7888
  var DESCRIPTION_LENGTH_THRESHOLD_SMALL = 25;
7847
7889
  var DESCRIPTION_LENGTH_THRESHOLD_LARGE = 100;
7848
7890
  function buildDescriptionGuidance(wordCount, contentType) {
7849
7891
  if (wordCount < DESCRIPTION_LENGTH_THRESHOLD_SMALL) {
7850
7892
  if (contentType === "video") {
7851
- return dedent6`A brief summary of the video in no more than ${wordCount} words. Shorter is fine.
7893
+ return dedent7`A brief summary of the video in no more than ${wordCount} words. Shorter is fine.
7852
7894
  Focus on the single most important subject or action.
7853
7895
  Write in present tense.`;
7854
7896
  }
7855
- return dedent6`A brief summary of the audio content in no more than ${wordCount} words. Shorter is fine.
7897
+ return dedent7`A brief summary of the audio content in no more than ${wordCount} words. Shorter is fine.
7856
7898
  Focus on the single most important topic or theme.
7857
7899
  Write in present tense.`;
7858
7900
  }
7859
7901
  if (wordCount > DESCRIPTION_LENGTH_THRESHOLD_LARGE) {
7860
7902
  if (contentType === "video") {
7861
- return dedent6`A detailed summary that describes what happens across the video.
7903
+ return dedent7`A detailed summary that describes what happens across the video.
7862
7904
  Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
7863
7905
  Be thorough: cover subjects, actions, setting, progression, and any notable details visible across frames.
7864
7906
  Write in present tense. Be specific about observable details rather than making assumptions.
7865
7907
  If the transcript provides dialogue or narration, incorporate key points but prioritize visual content.`;
7866
7908
  }
7867
- return dedent6`A detailed summary that describes the audio content.
7909
+ return dedent7`A detailed summary that describes the audio content.
7868
7910
  Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
7869
7911
  Be thorough: cover topics, speakers, themes, progression, and any notable insights.
7870
7912
  Write in present tense. Be specific about what is discussed or presented rather than making assumptions.
7871
7913
  Focus on the spoken content and any key insights, dialogue, or narrative elements.`;
7872
7914
  }
7873
7915
  if (contentType === "video") {
7874
- return dedent6`A summary that describes what happens across the video.
7916
+ return dedent7`A summary that describes what happens across the video.
7875
7917
  Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
7876
7918
  Cover the main subjects, actions, setting, and any notable progression visible across frames.
7877
7919
  Write in present tense. Be specific about observable details rather than making assumptions.
7878
7920
  If the transcript provides dialogue or narration, incorporate key points but prioritize visual content.`;
7879
7921
  }
7880
- return dedent6`A summary that describes the audio content.
7922
+ return dedent7`A summary that describes the audio content.
7881
7923
  Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
7882
7924
  Cover the main topics, speakers, themes, and any notable progression in the discussion or narration.
7883
7925
  Write in present tense. Be specific about what is discussed or presented rather than making assumptions.
7884
7926
  Focus on the spoken content and any key insights, dialogue, or narrative elements.`;
7885
7927
  }
7886
- function buildSceneAwareDescriptionGuidance(wordCount) {
7928
+ function buildSceneAwareDescriptionGuidance(wordCount, hasStoryboard) {
7929
+ 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.";
7887
7930
  if (wordCount < DESCRIPTION_LENGTH_THRESHOLD_SMALL) {
7888
- return dedent6`A brief summary of the video in no more than ${wordCount} words. Shorter is fine.
7931
+ return dedent7`A brief summary of the video in no more than ${wordCount} words. Shorter is fine.
7889
7932
  Focus on the single most important subject or action across the ordered scenes.
7890
- Use the storyboard as direct visual evidence and the scene context to preserve progression.
7933
+ ${storyboardGuidance}
7891
7934
  Write in present tense.`;
7892
7935
  }
7893
7936
  if (wordCount > DESCRIPTION_LENGTH_THRESHOLD_LARGE) {
7894
- return dedent6`A detailed summary that describes what happens across the ordered scenes.
7937
+ return dedent7`A detailed summary that describes what happens across the ordered scenes.
7895
7938
  Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
7896
7939
  Be thorough: cover subjects, actions, setting, progression, and notable details supported across scenes.
7897
- Use the storyboard as direct visual evidence and the scene context to preserve progression.
7940
+ ${storyboardGuidance}
7898
7941
  Write in present tense. If the transcript provides dialogue or narration, incorporate key points while preserving scene progression.`;
7899
7942
  }
7900
- return dedent6`A summary that describes what happens across the ordered scenes.
7943
+ return dedent7`A summary that describes what happens across the ordered scenes.
7901
7944
  Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
7902
7945
  Cover the main subjects, actions, setting, and notable progression supported across scenes.
7903
- Use the storyboard as direct visual evidence and the scene context to preserve progression.
7946
+ ${storyboardGuidance}
7904
7947
  Write in present tense. If the transcript provides dialogue or narration, incorporate key points while preserving scene progression.`;
7905
7948
  }
7906
- function createSummarizationBuilder({ titleLength, descriptionLength, tagCount } = {}, { hasSceneContext = false } = {}) {
7949
+ function createSummarizationGuidance({
7950
+ mediaType = "video",
7951
+ titleLength,
7952
+ descriptionLength,
7953
+ tagCount,
7954
+ hasSceneContext = false,
7955
+ hasStoryboard = true
7956
+ } = {}) {
7907
7957
  const titleLimit = titleLength ?? DEFAULT_TITLE_LENGTH;
7958
+ const descriptionLimit = descriptionLength ?? DEFAULT_DESCRIPTION_LENGTH;
7908
7959
  const keywordLimit = tagCount ?? DEFAULT_SUMMARY_KEYWORD_LIMIT;
7909
- 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.";
7910
- const qualityGuidelines = hasSceneContext ? dedent6`
7911
- - Follow scene_index order to understand what begins, develops, and concludes
7912
- - Use scene narratives and concepts as grounded evidence, preserving consistent terminology across the output
7913
- - Use the storyboard as direct visual evidence for whole-asset details
7914
- - Balance brevity with informativeness` : dedent6`
7915
- - Examine all frames to understand the full context and progression
7916
- - Be precise: "golden retriever" is better than "dog" when identifiable
7917
- - Capture the narrative: what begins, develops, and concludes
7918
- - Balance brevity with informativeness`;
7919
- return createPromptBuilder({
7920
- template: {
7921
- task: {
7922
- tag: "task",
7923
- content: task
7924
- },
7925
- title: {
7926
- tag: "title_requirements",
7927
- content: dedent6`
7960
+ const isAudioOnly = mediaType === "audio";
7961
+ const sceneAware = !isAudioOnly && hasSceneContext;
7962
+ const qualityGuidelines = isAudioOnly ? dedent7`
7963
+ - Analyze the full transcript to understand context and themes
7964
+ - Be precise: use specific terminology when mentioned
7965
+ - Capture the narrative: what is introduced, discussed, and concluded
7966
+ - Balance brevity with informativeness` : sceneAware ? dedent7`
7967
+ - Follow scene_index order to understand what begins, develops, and concludes
7968
+ - Use scene narratives and concepts as grounded evidence, preserving consistent terminology across the output
7969
+ ${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"}
7970
+ - Balance brevity with informativeness` : dedent7`
7971
+ - Examine all frames to understand the full context and progression
7972
+ - Be precise: "golden retriever" is better than "dog" when identifiable
7973
+ - Capture the narrative: what begins, develops, and concludes
7974
+ - Balance brevity with informativeness`;
7975
+ return {
7976
+ title: {
7977
+ tag: "title_requirements",
7978
+ content: isAudioOnly ? dedent7`
7979
+ A concise, label-style title — not a sentence or description.
7980
+ Never exceed ${titleLimit} words, but shorter is better.
7981
+ Think of how a podcast episode title or playlist entry would read — e.g. "Weekly News Roundup" or "Interview with Dr. Smith".
7982
+ Start with the primary subject or topic. Never begin with "An audio of" or similar phrasing.
7983
+ Use specific nouns over lengthy descriptions. Avoid clauses, conjunctions, or narrative structure.` : dedent7`
7928
7984
  A concise, label-style title — not a sentence or description.
7929
7985
  Never exceed ${titleLimit} words, but shorter is better.
7930
7986
  Think of how a video card title, playlist entry, or file name would read — e.g. "Predator: Badlands Trailer" or "Chef Prepares Holiday Feast".
7931
7987
  Start with the primary subject or topic. Never begin with "A video of" or similar phrasing.
7932
7988
  Use specific nouns over lengthy descriptions. Avoid clauses, conjunctions, or narrative structure.`
7933
- },
7934
- description: {
7935
- tag: "description_requirements",
7936
- content: hasSceneContext ? buildSceneAwareDescriptionGuidance(descriptionLength ?? DEFAULT_DESCRIPTION_LENGTH) : buildDescriptionGuidance(descriptionLength ?? DEFAULT_DESCRIPTION_LENGTH, "video")
7937
- },
7938
- keywords: {
7939
- tag: "keywords_requirements",
7940
- content: dedent6`
7989
+ },
7990
+ description: {
7991
+ tag: "description_requirements",
7992
+ content: sceneAware ? buildSceneAwareDescriptionGuidance(descriptionLimit, hasStoryboard) : buildDescriptionGuidance(descriptionLimit, mediaType)
7993
+ },
7994
+ keywords: {
7995
+ tag: "keywords_requirements",
7996
+ content: isAudioOnly ? dedent7`
7997
+ Specific, searchable terms (up to ${keywordLimit}) that capture:
7998
+ - Primary topics and themes
7999
+ - Speakers or presenters (if named)
8000
+ - Key concepts and terminology
8001
+ - Content type (interview, lecture, music, etc.)
8002
+ - Genre or style (if applicable)
8003
+ Prefer concrete nouns and relevant terms over abstract concepts.
8004
+ Use lowercase. Avoid redundant or overly generic terms like "audio" or "content".` : dedent7`
7941
8005
  Specific, searchable terms (up to ${keywordLimit}) that capture:
7942
8006
  - Primary subjects (people, animals, objects)
7943
8007
  - Actions and activities being performed
@@ -7946,57 +8010,51 @@ function createSummarizationBuilder({ titleLength, descriptionLength, tagCount }
7946
8010
  - Style or genre (if applicable)
7947
8011
  Prefer concrete nouns and action verbs over abstract concepts.
7948
8012
  Use lowercase. Avoid redundant or overly generic terms like "video" or "content".`
8013
+ },
8014
+ qualityGuidelines: { tag: "quality_guidelines", content: qualityGuidelines }
8015
+ };
8016
+ }
8017
+
8018
+ // src/workflows/summarization.ts
8019
+ var summarySchema = z9.object({
8020
+ keywords: z9.array(z9.string().max(200)),
8021
+ title: z9.string().max(500),
8022
+ description: z9.string().max(1e4)
8023
+ });
8024
+ function buildSummarySchema(descriptionLength) {
8025
+ const descriptionMaxChars = Math.max(2e3, descriptionLength * 15);
8026
+ return z9.object({
8027
+ keywords: z9.array(z9.string().max(200)),
8028
+ title: z9.string().max(500),
8029
+ description: z9.string().max(descriptionMaxChars)
8030
+ });
8031
+ }
8032
+ var VALID_TONES = ["neutral", "playful", "professional"];
8033
+ var TONE_INSTRUCTIONS = {
8034
+ neutral: "Provide a clear, straightforward analysis.",
8035
+ 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!",
8036
+ professional: "Provide a professional, executive-level analysis suitable for business reporting."
8037
+ };
8038
+ function createSummarizationBuilder(constraints = {}, { hasSceneContext = false } = {}) {
8039
+ return createPromptBuilder({
8040
+ template: {
8041
+ task: {
8042
+ tag: "task",
8043
+ 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."
7949
8044
  },
7950
- qualityGuidelines: {
7951
- tag: "quality_guidelines",
7952
- content: qualityGuidelines
7953
- }
8045
+ ...createSummarizationGuidance({ ...constraints, hasSceneContext })
7954
8046
  },
7955
8047
  sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
7956
8048
  });
7957
8049
  }
7958
- function createAudioOnlyBuilder({ titleLength, descriptionLength, tagCount } = {}) {
7959
- const titleLimit = titleLength ?? DEFAULT_TITLE_LENGTH;
7960
- const keywordLimit = tagCount ?? DEFAULT_SUMMARY_KEYWORD_LIMIT;
8050
+ function createAudioOnlyBuilder(constraints = {}) {
7961
8051
  return createPromptBuilder({
7962
8052
  template: {
7963
8053
  task: {
7964
8054
  tag: "task",
7965
8055
  content: "Analyze the transcript and generate metadata that captures the essence of the audio content."
7966
8056
  },
7967
- title: {
7968
- tag: "title_requirements",
7969
- content: dedent6`
7970
- A concise, label-style title — not a sentence or description.
7971
- Never exceed ${titleLimit} words, but shorter is better.
7972
- Think of how a podcast episode title or playlist entry would read — e.g. "Weekly News Roundup" or "Interview with Dr. Smith".
7973
- Start with the primary subject or topic. Never begin with "An audio of" or similar phrasing.
7974
- Use specific nouns over lengthy descriptions. Avoid clauses, conjunctions, or narrative structure.`
7975
- },
7976
- description: {
7977
- tag: "description_requirements",
7978
- content: buildDescriptionGuidance(descriptionLength ?? DEFAULT_DESCRIPTION_LENGTH, "audio")
7979
- },
7980
- keywords: {
7981
- tag: "keywords_requirements",
7982
- content: dedent6`
7983
- Specific, searchable terms (up to ${keywordLimit}) that capture:
7984
- - Primary topics and themes
7985
- - Speakers or presenters (if named)
7986
- - Key concepts and terminology
7987
- - Content type (interview, lecture, music, etc.)
7988
- - Genre or style (if applicable)
7989
- Prefer concrete nouns and relevant terms over abstract concepts.
7990
- Use lowercase. Avoid redundant or overly generic terms like "audio" or "content".`
7991
- },
7992
- qualityGuidelines: {
7993
- tag: "quality_guidelines",
7994
- content: dedent6`
7995
- - Analyze the full transcript to understand context and themes
7996
- - Be precise: use specific terminology when mentioned
7997
- - Capture the narrative: what is introduced, discussed, and concluded
7998
- - Balance brevity with informativeness`
7999
- }
8057
+ ...createSummarizationGuidance({ ...constraints, mediaType: "audio" })
8000
8058
  },
8001
8059
  sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
8002
8060
  });
@@ -8809,7 +8867,7 @@ async function translateAudio(assetId, toLanguageCode, options = {}) {
8809
8867
  { type: "validation_error" }
8810
8868
  );
8811
8869
  }
8812
- let audioUrl = `${getMuxStreamOrigin()}/${playbackId}/audio.m4a`;
8870
+ let audioUrl = `${await getMuxStreamOrigin(credentials)}/${playbackId}/audio.m4a`;
8813
8871
  if (policy === "signed") {
8814
8872
  audioUrl = await signUrl(audioUrl, playbackId, "video", void 0, credentials);
8815
8873
  }
@@ -9834,6 +9892,7 @@ export {
9834
9892
  findSameLanguageTextTracks,
9835
9893
  getLanguageCodePair,
9836
9894
  getLanguageName,
9895
+ getMuxPlaybackOrigin,
9837
9896
  isDuplicateTrackNameError,
9838
9897
  isMuxGeneratedTextTrack,
9839
9898
  isUndeterminedLanguageCode,