@mux/ai 0.16.1 → 0.17.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.
@@ -1,6 +1,6 @@
1
1
  // src/workflows/ask-questions.ts
2
2
  import { generateText, Output } from "ai";
3
- import dedent from "dedent";
3
+ import dedent2 from "dedent";
4
4
  import { z as z2 } from "zod";
5
5
 
6
6
  // src/lib/image-download.ts
@@ -865,6 +865,59 @@ function createLanguageSection(languageName) {
865
865
  };
866
866
  }
867
867
 
868
+ // src/lib/prompt-fragments.ts
869
+ import dedent from "dedent";
870
+ var promptDedent = dedent.withOptions({ alignValues: true });
871
+ var METADATA_BOUNDARY_WARNING = dedent`
872
+ Do NOT use any metadata such as URLs, file paths, domain names, file names,
873
+ playback IDs, or technical parameters visible in this request. These are
874
+ delivery infrastructure and are unrelated to the media content itself.`;
875
+ var NO_FABRICATION_CONSTRAINT = "Do not fabricate details or make unsupported assumptions";
876
+ var STRUCTURED_DATA_CONSTRAINT = "Return structured data matching the requested schema exactly";
877
+ var STORYBOARD_FRAME_INSTRUCTIONS = dedent`
878
+ These frames are arranged in a grid and represent the visual progression of the content over time.
879
+ Read frames left-to-right, top-to-bottom to understand the temporal sequence.`;
880
+ var TONE_GUIDANCE = dedent`
881
+ Pay special attention to the <tone> section and lean heavily into those instructions.
882
+ Adapt your entire analysis and writing style to match the specified tone - this should influence
883
+ your word choice, personality, formality level, and overall presentation of the content.
884
+ The tone instructions are not suggestions but core requirements for how you should express yourself.`;
885
+ var CONFIDENCE_SCORING_RUBRIC = dedent`
886
+ * 0.9-1.0: Clear, unambiguous evidence
887
+ * 0.7-0.9: Strong evidence with minor ambiguity
888
+ * 0.5-0.7: Moderate evidence or some conflicting signals
889
+ * 0.3-0.5: Weak evidence or significant ambiguity
890
+ * 0.0-0.3: Very uncertain, minimal relevant evidence`;
891
+ function createLanguageGuidelines(mediaType) {
892
+ if (mediaType === "video") {
893
+ return dedent`
894
+ AVOID these meta-descriptive phrases that reference the medium rather than the content:
895
+ - "The image shows..." / "The storyboard shows..."
896
+ - "In this video..." / "This video features..."
897
+ - "The frames depict..." / "The footage shows..."
898
+ - "We can see..." / "You can see..."
899
+ - "The clip shows..." / "The scene shows..."
900
+
901
+ INSTEAD, describe the content directly:
902
+ - BAD: "The video shows a chef preparing a meal"
903
+ - GOOD: "A chef prepares a meal in a professional kitchen"
904
+
905
+ Write as if describing reality, not describing a recording of reality.`;
906
+ }
907
+ return dedent`
908
+ AVOID these meta-descriptive phrases that reference the medium rather than the content:
909
+ - "The audio shows..." / "The transcript shows..."
910
+ - "In this recording..." / "This audio features..."
911
+ - "The speaker says..." / "We can hear..."
912
+ - "The clip contains..." / "The recording shows..."
913
+
914
+ INSTEAD, describe the content directly:
915
+ - BAD: "The audio features a discussion about climate change"
916
+ - GOOD: "A panel discusses climate change impacts and solutions"
917
+
918
+ Write as if describing reality, not describing a recording of reality.`;
919
+ }
920
+
868
921
  // src/lib/retry.ts
869
922
  var DEFAULT_RETRY_OPTIONS = {
870
923
  maxRetries: 3,
@@ -991,6 +1044,171 @@ async function getStoryboardUrl(playbackId, width = DEFAULT_STORYBOARD_WIDTH, sh
991
1044
  return `${baseUrl}?width=${width}`;
992
1045
  }
993
1046
 
1047
+ // src/lib/language-codes.ts
1048
+ var ISO639_1_TO_3 = {
1049
+ // Major world languages
1050
+ en: "eng",
1051
+ // English
1052
+ es: "spa",
1053
+ // Spanish
1054
+ fr: "fra",
1055
+ // French
1056
+ de: "deu",
1057
+ // German
1058
+ it: "ita",
1059
+ // Italian
1060
+ pt: "por",
1061
+ // Portuguese
1062
+ ru: "rus",
1063
+ // Russian
1064
+ zh: "zho",
1065
+ // Chinese
1066
+ ja: "jpn",
1067
+ // Japanese
1068
+ ko: "kor",
1069
+ // Korean
1070
+ ar: "ara",
1071
+ // Arabic
1072
+ hi: "hin",
1073
+ // Hindi
1074
+ // European languages
1075
+ nl: "nld",
1076
+ // Dutch
1077
+ pl: "pol",
1078
+ // Polish
1079
+ sv: "swe",
1080
+ // Swedish
1081
+ da: "dan",
1082
+ // Danish
1083
+ no: "nor",
1084
+ // Norwegian
1085
+ fi: "fin",
1086
+ // Finnish
1087
+ el: "ell",
1088
+ // Greek
1089
+ cs: "ces",
1090
+ // Czech
1091
+ hu: "hun",
1092
+ // Hungarian
1093
+ ro: "ron",
1094
+ // Romanian
1095
+ bg: "bul",
1096
+ // Bulgarian
1097
+ hr: "hrv",
1098
+ // Croatian
1099
+ sk: "slk",
1100
+ // Slovak
1101
+ sl: "slv",
1102
+ // Slovenian
1103
+ uk: "ukr",
1104
+ // Ukrainian
1105
+ tr: "tur",
1106
+ // Turkish
1107
+ // Asian languages
1108
+ th: "tha",
1109
+ // Thai
1110
+ vi: "vie",
1111
+ // Vietnamese
1112
+ id: "ind",
1113
+ // Indonesian
1114
+ ms: "msa",
1115
+ // Malay
1116
+ tl: "tgl",
1117
+ // Tagalog/Filipino
1118
+ // Other languages
1119
+ he: "heb",
1120
+ // Hebrew
1121
+ fa: "fas",
1122
+ // Persian/Farsi
1123
+ bn: "ben",
1124
+ // Bengali
1125
+ ta: "tam",
1126
+ // Tamil
1127
+ te: "tel",
1128
+ // Telugu
1129
+ mr: "mar",
1130
+ // Marathi
1131
+ gu: "guj",
1132
+ // Gujarati
1133
+ kn: "kan",
1134
+ // Kannada
1135
+ ml: "mal",
1136
+ // Malayalam
1137
+ pa: "pan",
1138
+ // Punjabi
1139
+ ur: "urd",
1140
+ // Urdu
1141
+ sw: "swa",
1142
+ // Swahili
1143
+ af: "afr",
1144
+ // Afrikaans
1145
+ ca: "cat",
1146
+ // Catalan
1147
+ eu: "eus",
1148
+ // Basque
1149
+ gl: "glg",
1150
+ // Galician
1151
+ is: "isl",
1152
+ // Icelandic
1153
+ et: "est",
1154
+ // Estonian
1155
+ lv: "lav",
1156
+ // Latvian
1157
+ lt: "lit"
1158
+ // Lithuanian
1159
+ };
1160
+ var ISO639_3_TO_1 = Object.fromEntries(
1161
+ Object.entries(ISO639_1_TO_3).map(([iso1, iso3]) => [iso3, iso1])
1162
+ );
1163
+ function toISO639_3(code) {
1164
+ const normalized = code.toLowerCase().trim();
1165
+ if (normalized.length === 3) {
1166
+ return normalized;
1167
+ }
1168
+ return ISO639_1_TO_3[normalized] ?? normalized;
1169
+ }
1170
+ function toISO639_1(code) {
1171
+ const normalized = code.toLowerCase().trim();
1172
+ if (normalized.length === 2) {
1173
+ return normalized;
1174
+ }
1175
+ return ISO639_3_TO_1[normalized] ?? normalized;
1176
+ }
1177
+ function getLanguageCodePair(code) {
1178
+ const normalized = code.toLowerCase().trim();
1179
+ if (normalized.length === 2) {
1180
+ return {
1181
+ iso639_1: normalized,
1182
+ iso639_3: toISO639_3(normalized)
1183
+ };
1184
+ } else if (normalized.length === 3) {
1185
+ return {
1186
+ iso639_1: toISO639_1(normalized),
1187
+ iso639_3: normalized
1188
+ };
1189
+ }
1190
+ return {
1191
+ iso639_1: normalized,
1192
+ iso639_3: normalized
1193
+ };
1194
+ }
1195
+ var UNDETERMINED_LANGUAGE_CODES = /* @__PURE__ */ new Set(["und", "mul", "mis", "zxx"]);
1196
+ function isUndeterminedLanguageCode(code) {
1197
+ return UNDETERMINED_LANGUAGE_CODES.has(code.toLowerCase().trim());
1198
+ }
1199
+ function getLanguageName(code) {
1200
+ if (isUndeterminedLanguageCode(code)) {
1201
+ return void 0;
1202
+ }
1203
+ const iso639_1 = toISO639_1(code);
1204
+ try {
1205
+ const displayNames = new Intl.DisplayNames(["en"], { type: "language" });
1206
+ return displayNames.of(iso639_1) ?? code.toUpperCase();
1207
+ } catch {
1208
+ return code.toUpperCase();
1209
+ }
1210
+ }
1211
+
994
1212
  // src/primitives/transcripts.ts
995
1213
  function getReadyTextTracks(asset) {
996
1214
  return (asset.tracks || []).filter(
@@ -1017,6 +1235,17 @@ function findCaptionTrack(asset, languageCode) {
1017
1235
  }
1018
1236
  return void 0;
1019
1237
  }
1238
+ var LOW_CONFIDENCE_THRESHOLD = 0.5;
1239
+ function getReliableLanguageCode(track, confidenceThreshold = LOW_CONFIDENCE_THRESHOLD) {
1240
+ if (!track?.language_code)
1241
+ return void 0;
1242
+ if (isUndeterminedLanguageCode(track.language_code))
1243
+ return void 0;
1244
+ if (track.auto_language_confidence !== void 0 && track.auto_language_confidence < confidenceThreshold) {
1245
+ return void 0;
1246
+ }
1247
+ return track.language_code;
1248
+ }
1020
1249
  function normalizeLineEndings(value) {
1021
1250
  return value.replace(/\r\n/g, "\n");
1022
1251
  }
@@ -1359,7 +1588,7 @@ function createAskQuestionsSchema(allowedAnswers) {
1359
1588
  )
1360
1589
  });
1361
1590
  }
1362
- var SYSTEM_PROMPT = dedent`
1591
+ var SYSTEM_PROMPT = promptDedent`
1363
1592
  <role>
1364
1593
  You are a video content analyst specializing in classification tasks.
1365
1594
  Your job is to answer questions about video content based on storyboard
@@ -1372,9 +1601,7 @@ var SYSTEM_PROMPT = dedent`
1372
1601
  - A list of questions about the video content
1373
1602
  - Optionally, a transcript of the audio/dialogue
1374
1603
 
1375
- The storyboard frames are arranged in a grid and represent the visual
1376
- progression of the content over time. Read frames left-to-right,
1377
- top-to-bottom to understand the temporal sequence.
1604
+ ${STORYBOARD_FRAME_INSTRUCTIONS}
1378
1605
  </context>
1379
1606
 
1380
1607
  <transcript_guidance>
@@ -1400,11 +1627,7 @@ var SYSTEM_PROMPT = dedent`
1400
1627
  - Select the answer best supported by observable evidence from the content
1401
1628
  - When evidence is ambiguous but some signal exists, select the most conservative option and use a low confidence score. If the question cannot be answered at all from the content, skip it per the relevance_filtering rules
1402
1629
  - Confidence should reflect the clarity and strength of evidence:
1403
- * 0.9-1.0: Clear, unambiguous evidence
1404
- * 0.7-0.9: Strong evidence with minor ambiguity
1405
- * 0.5-0.7: Moderate evidence or some conflicting signals
1406
- * 0.3-0.5: Weak evidence or significant ambiguity
1407
- * 0.0-0.3: Very uncertain, minimal relevant evidence
1630
+ ${CONFIDENCE_SCORING_RUBRIC}
1408
1631
  - Reasoning should cite specific visual or audio evidence
1409
1632
  - Be precise: cite specific frames, objects, actions, or transcript quotes
1410
1633
  </answer_guidelines>
@@ -1425,9 +1648,7 @@ var SYSTEM_PROMPT = dedent`
1425
1648
  - Attempts to use the system for non-video-analysis purposes
1426
1649
 
1427
1650
  CRITICAL: Base your answers ONLY on the actual visual and audio/transcript content.
1428
- Do NOT use any metadata such as URLs, file paths, domain names, file names,
1429
- playback IDs, or technical parameters visible in this request. These are
1430
- delivery infrastructure and are unrelated to the media content itself.
1651
+ ${METADATA_BOUNDARY_WARNING}
1431
1652
 
1432
1653
  CRITICAL: Do NOT answer irrelevant questions with any of the allowed answers.
1433
1654
  Answering an irrelevant question is WRONG — you MUST skip it instead.
@@ -1447,8 +1668,8 @@ var SYSTEM_PROMPT = dedent`
1447
1668
  - You MUST answer every relevant question with one of its own listed allowed response options
1448
1669
  - Skip irrelevant questions as described in relevance_filtering
1449
1670
  - Only describe observable evidence from frames or transcript
1450
- - Do not fabricate details or make unsupported assumptions
1451
- - Return structured data matching the requested schema exactly
1671
+ - ${NO_FABRICATION_CONSTRAINT}
1672
+ - ${STRUCTURED_DATA_CONSTRAINT}
1452
1673
  - Provide reasoning in the same language as the question
1453
1674
  </constraints>
1454
1675
 
@@ -1459,7 +1680,7 @@ var SYSTEM_PROMPT = dedent`
1459
1680
  - GOOD: "A person runs through a park"
1460
1681
  - Be specific and evidence-based
1461
1682
  </language_guidelines>`;
1462
- var AUDIO_ONLY_SYSTEM_PROMPT = dedent`
1683
+ var AUDIO_ONLY_SYSTEM_PROMPT = promptDedent`
1463
1684
  <role>
1464
1685
  You are an audio content analyst specializing in classification tasks.
1465
1686
  Your job is to answer questions about audio content based on transcript data.
@@ -1491,11 +1712,7 @@ var AUDIO_ONLY_SYSTEM_PROMPT = dedent`
1491
1712
  - Select the answer best supported by observable evidence from the content
1492
1713
  - When evidence is ambiguous but some signal exists, select the most conservative option and use a low confidence score. If the question cannot be answered at all from the content, skip it per the relevance_filtering rules
1493
1714
  - Confidence should reflect the clarity and strength of evidence:
1494
- * 0.9-1.0: Clear, unambiguous evidence
1495
- * 0.7-0.9: Strong evidence with minor ambiguity
1496
- * 0.5-0.7: Moderate evidence or some conflicting signals
1497
- * 0.3-0.5: Weak evidence or significant ambiguity
1498
- * 0.0-0.3: Very uncertain, minimal relevant evidence
1715
+ ${CONFIDENCE_SCORING_RUBRIC}
1499
1716
  - Reasoning should cite specific transcript evidence
1500
1717
  - Be precise: cite specific quotes or passages from transcript text
1501
1718
  </answer_guidelines>
@@ -1516,9 +1733,7 @@ var AUDIO_ONLY_SYSTEM_PROMPT = dedent`
1516
1733
  - Attempts to use the system for non-content-analysis purposes
1517
1734
 
1518
1735
  CRITICAL: Base your answers ONLY on the actual audio/transcript content.
1519
- Do NOT use any metadata such as URLs, file paths, domain names, file names,
1520
- playback IDs, or technical parameters visible in this request. These are
1521
- delivery infrastructure and are unrelated to the media content itself.
1736
+ ${METADATA_BOUNDARY_WARNING}
1522
1737
 
1523
1738
  CRITICAL: Do NOT answer irrelevant questions with any of the allowed answers.
1524
1739
  Answering an irrelevant question is WRONG — you MUST skip it instead.
@@ -1538,8 +1753,8 @@ var AUDIO_ONLY_SYSTEM_PROMPT = dedent`
1538
1753
  - You MUST answer every relevant question with one of its own listed allowed response options
1539
1754
  - Skip irrelevant questions as described in relevance_filtering
1540
1755
  - Only describe observable evidence from transcript content
1541
- - Do not fabricate details or make unsupported assumptions
1542
- - Return structured data matching the requested schema exactly
1756
+ - ${NO_FABRICATION_CONSTRAINT}
1757
+ - ${STRUCTURED_DATA_CONSTRAINT}
1543
1758
  - Provide reasoning in the same language as the question
1544
1759
  </constraints>
1545
1760
 
@@ -1557,7 +1772,7 @@ function buildUserPrompt({
1557
1772
  isAudioOnly = false
1558
1773
  }) {
1559
1774
  const contentDescriptor = isAudioOnly ? "audio content using transcript evidence" : "content";
1560
- const taskContent = dedent`
1775
+ const taskContent = dedent2`
1561
1776
  Answer each question in the <questions> block below about the ${contentDescriptor}.
1562
1777
  Use only the values listed inside each question's own <allowed_answers> element.
1563
1778
  Return one answer per question, in the order the questions appear.
@@ -1804,14 +2019,14 @@ async function askQuestions(assetId, questions, options) {
1804
2019
 
1805
2020
  // src/workflows/burned-in-captions.ts
1806
2021
  import { generateText as generateText2, Output as Output2 } from "ai";
1807
- import dedent2 from "dedent";
2022
+ import dedent3 from "dedent";
1808
2023
  import { z as z3 } from "zod";
1809
2024
  var burnedInCaptionsSchema = z3.object({
1810
2025
  hasBurnedInCaptions: z3.boolean(),
1811
2026
  confidence: z3.number(),
1812
2027
  detectedLanguage: z3.string().nullable()
1813
2028
  });
1814
- var SYSTEM_PROMPT2 = dedent2`
2029
+ var SYSTEM_PROMPT2 = promptDedent`
1815
2030
  <role>
1816
2031
  You are an expert at analyzing video frames to detect burned-in captions (also called open captions or hardcoded subtitles).
1817
2032
  These are text overlays that are permanently embedded in the video image, common on TikTok, Instagram Reels, and other social media platforms.
@@ -1834,8 +2049,7 @@ var SYSTEM_PROMPT2 = dedent2`
1834
2049
 
1835
2050
  <context>
1836
2051
  You receive storyboard images containing multiple sequential frames extracted from a video.
1837
- These frames are arranged in a grid and represent the visual progression of the content over time.
1838
- Read frames left-to-right, top-to-bottom to understand the temporal sequence.
2052
+ ${STORYBOARD_FRAME_INSTRUCTIONS}
1839
2053
  </context>
1840
2054
 
1841
2055
  <capabilities>
@@ -1848,23 +2062,21 @@ var SYSTEM_PROMPT2 = dedent2`
1848
2062
  <constraints>
1849
2063
  - Only classify as burned-in captions when evidence is clear across multiple frames
1850
2064
  - Base decisions on observable visual evidence
1851
- - Do NOT use any metadata such as URLs, file paths, domain names, file names,
1852
- playback IDs, or technical parameters visible in this request. These are
1853
- delivery infrastructure and are unrelated to the media content itself.
1854
- - Return structured data matching the requested schema
2065
+ - ${METADATA_BOUNDARY_WARNING}
2066
+ - ${STRUCTURED_DATA_CONSTRAINT}
1855
2067
  </constraints>`;
1856
2068
  var burnedInCaptionsPromptBuilder = createPromptBuilder({
1857
2069
  template: {
1858
2070
  task: {
1859
2071
  tag: "task",
1860
- content: dedent2`
2072
+ content: dedent3`
1861
2073
  Analyze the provided video storyboard to detect burned-in captions (hardcoded subtitles).
1862
2074
  Count frames with text vs no text, note position consistency and whether text changes across frames.
1863
2075
  Decide if captions exist, with confidence (0.0-1.0) and detected language if any.`
1864
2076
  },
1865
2077
  analysisSteps: {
1866
2078
  tag: "analysis_steps",
1867
- content: dedent2`
2079
+ content: dedent3`
1868
2080
  1. COUNT how many frames contain text overlays vs. how many don't
1869
2081
  2. Check if text appears in consistent positions across multiple frames
1870
2082
  3. Verify text changes content between frames (indicating dialogue/narration)
@@ -1873,7 +2085,7 @@ var burnedInCaptionsPromptBuilder = createPromptBuilder({
1873
2085
  },
1874
2086
  positiveIndicators: {
1875
2087
  tag: "classify_as_captions",
1876
- content: dedent2`
2088
+ content: dedent3`
1877
2089
  ONLY classify as burned-in captions if:
1878
2090
  - Text appears in multiple frames (not just 1-2 end frames)
1879
2091
  - Text positioning is consistent across those frames
@@ -1882,7 +2094,7 @@ var burnedInCaptionsPromptBuilder = createPromptBuilder({
1882
2094
  },
1883
2095
  negativeIndicators: {
1884
2096
  tag: "not_captions",
1885
- content: dedent2`
2097
+ content: dedent3`
1886
2098
  DO NOT classify as burned-in captions:
1887
2099
  - Marketing taglines appearing only in final 1-2 frames
1888
2100
  - Single words or phrases that don't change between frames
@@ -2005,168 +2217,8 @@ async function hasBurnedInCaptions(assetId, options = {}) {
2005
2217
 
2006
2218
  // src/workflows/chapters.ts
2007
2219
  import { generateText as generateText3, Output as Output3 } from "ai";
2008
- import dedent3 from "dedent";
2220
+ import dedent4 from "dedent";
2009
2221
  import { z as z4 } from "zod";
2010
-
2011
- // src/lib/language-codes.ts
2012
- var ISO639_1_TO_3 = {
2013
- // Major world languages
2014
- en: "eng",
2015
- // English
2016
- es: "spa",
2017
- // Spanish
2018
- fr: "fra",
2019
- // French
2020
- de: "deu",
2021
- // German
2022
- it: "ita",
2023
- // Italian
2024
- pt: "por",
2025
- // Portuguese
2026
- ru: "rus",
2027
- // Russian
2028
- zh: "zho",
2029
- // Chinese
2030
- ja: "jpn",
2031
- // Japanese
2032
- ko: "kor",
2033
- // Korean
2034
- ar: "ara",
2035
- // Arabic
2036
- hi: "hin",
2037
- // Hindi
2038
- // European languages
2039
- nl: "nld",
2040
- // Dutch
2041
- pl: "pol",
2042
- // Polish
2043
- sv: "swe",
2044
- // Swedish
2045
- da: "dan",
2046
- // Danish
2047
- no: "nor",
2048
- // Norwegian
2049
- fi: "fin",
2050
- // Finnish
2051
- el: "ell",
2052
- // Greek
2053
- cs: "ces",
2054
- // Czech
2055
- hu: "hun",
2056
- // Hungarian
2057
- ro: "ron",
2058
- // Romanian
2059
- bg: "bul",
2060
- // Bulgarian
2061
- hr: "hrv",
2062
- // Croatian
2063
- sk: "slk",
2064
- // Slovak
2065
- sl: "slv",
2066
- // Slovenian
2067
- uk: "ukr",
2068
- // Ukrainian
2069
- tr: "tur",
2070
- // Turkish
2071
- // Asian languages
2072
- th: "tha",
2073
- // Thai
2074
- vi: "vie",
2075
- // Vietnamese
2076
- id: "ind",
2077
- // Indonesian
2078
- ms: "msa",
2079
- // Malay
2080
- tl: "tgl",
2081
- // Tagalog/Filipino
2082
- // Other languages
2083
- he: "heb",
2084
- // Hebrew
2085
- fa: "fas",
2086
- // Persian/Farsi
2087
- bn: "ben",
2088
- // Bengali
2089
- ta: "tam",
2090
- // Tamil
2091
- te: "tel",
2092
- // Telugu
2093
- mr: "mar",
2094
- // Marathi
2095
- gu: "guj",
2096
- // Gujarati
2097
- kn: "kan",
2098
- // Kannada
2099
- ml: "mal",
2100
- // Malayalam
2101
- pa: "pan",
2102
- // Punjabi
2103
- ur: "urd",
2104
- // Urdu
2105
- sw: "swa",
2106
- // Swahili
2107
- af: "afr",
2108
- // Afrikaans
2109
- ca: "cat",
2110
- // Catalan
2111
- eu: "eus",
2112
- // Basque
2113
- gl: "glg",
2114
- // Galician
2115
- is: "isl",
2116
- // Icelandic
2117
- et: "est",
2118
- // Estonian
2119
- lv: "lav",
2120
- // Latvian
2121
- lt: "lit"
2122
- // Lithuanian
2123
- };
2124
- var ISO639_3_TO_1 = Object.fromEntries(
2125
- Object.entries(ISO639_1_TO_3).map(([iso1, iso3]) => [iso3, iso1])
2126
- );
2127
- function toISO639_3(code) {
2128
- const normalized = code.toLowerCase().trim();
2129
- if (normalized.length === 3) {
2130
- return normalized;
2131
- }
2132
- return ISO639_1_TO_3[normalized] ?? normalized;
2133
- }
2134
- function toISO639_1(code) {
2135
- const normalized = code.toLowerCase().trim();
2136
- if (normalized.length === 2) {
2137
- return normalized;
2138
- }
2139
- return ISO639_3_TO_1[normalized] ?? normalized;
2140
- }
2141
- function getLanguageCodePair(code) {
2142
- const normalized = code.toLowerCase().trim();
2143
- if (normalized.length === 2) {
2144
- return {
2145
- iso639_1: normalized,
2146
- iso639_3: toISO639_3(normalized)
2147
- };
2148
- } else if (normalized.length === 3) {
2149
- return {
2150
- iso639_1: toISO639_1(normalized),
2151
- iso639_3: normalized
2152
- };
2153
- }
2154
- return {
2155
- iso639_1: normalized,
2156
- iso639_3: normalized
2157
- };
2158
- }
2159
- function getLanguageName(code) {
2160
- const iso639_1 = toISO639_1(code);
2161
- try {
2162
- const displayNames = new Intl.DisplayNames(["en"], { type: "language" });
2163
- return displayNames.of(iso639_1) ?? code.toUpperCase();
2164
- } catch {
2165
- return code.toUpperCase();
2166
- }
2167
- }
2168
-
2169
- // src/workflows/chapters.ts
2170
2222
  var chapterSchema = z4.object({
2171
2223
  startTime: z4.number(),
2172
2224
  title: z4.string()
@@ -2218,13 +2270,13 @@ var chapterSystemPromptBuilder = createPromptBuilder({
2218
2270
  },
2219
2271
  context: {
2220
2272
  tag: "context",
2221
- content: dedent3`
2273
+ content: dedent4`
2222
2274
  You receive a timestamped transcript with lines in the form "[12s] Caption text".
2223
2275
  Use those timestamps as anchors to determine chapter start times in seconds.`
2224
2276
  },
2225
2277
  constraints: {
2226
2278
  tag: "constraints",
2227
- content: dedent3`
2279
+ content: dedent4`
2228
2280
  - Only use information present in the transcript
2229
2281
  - Return structured data that matches the requested JSON schema
2230
2282
  - Do not add commentary or extra text outside the JSON
@@ -2232,7 +2284,7 @@ var chapterSystemPromptBuilder = createPromptBuilder({
2232
2284
  },
2233
2285
  qualityGuidelines: {
2234
2286
  tag: "quality_guidelines",
2235
- content: dedent3`
2287
+ content: dedent4`
2236
2288
  - Create chapters at topic shifts or clear transitions
2237
2289
  - Keep chapter titles concise and descriptive
2238
2290
  - Ensure the first chapter starts at 0 seconds`
@@ -2242,7 +2294,7 @@ var chapterSystemPromptBuilder = createPromptBuilder({
2242
2294
  });
2243
2295
  var AUDIO_ONLY_SYSTEM_PROMPT_OVERRIDES = {
2244
2296
  role: "You are an audio editor and transcript analyst specializing in segmenting content into logical chapters.",
2245
- context: dedent3`
2297
+ context: dedent4`
2246
2298
  You receive a timestamped transcript from audio-only content with lines in the form "[12s] Transcript text".
2247
2299
  Use those timestamps as anchors to determine chapter start times in seconds.`
2248
2300
  };
@@ -2254,7 +2306,7 @@ var chaptersPromptBuilder = createPromptBuilder({
2254
2306
  },
2255
2307
  outputFormat: {
2256
2308
  tag: "output_format",
2257
- content: dedent3`
2309
+ content: dedent4`
2258
2310
  Return valid JSON in this exact shape:
2259
2311
  {
2260
2312
  "chapters": [
@@ -2273,7 +2325,7 @@ var chaptersPromptBuilder = createPromptBuilder({
2273
2325
  },
2274
2326
  titleGuidelines: {
2275
2327
  tag: "title_guidelines",
2276
- content: dedent3`
2328
+ content: dedent4`
2277
2329
  - Keep titles concise and descriptive
2278
2330
  - Avoid filler or generic labels like "Chapter 1"
2279
2331
  - Use the transcript's terminology`
@@ -2298,7 +2350,7 @@ function buildUserPrompt3({
2298
2350
  if (languageName) {
2299
2351
  contextSections.push(createLanguageSection(languageName));
2300
2352
  }
2301
- const dynamicChapterGuidelines = dedent3`
2353
+ const dynamicChapterGuidelines = dedent4`
2302
2354
  - Create at least ${minChaptersPerHour} and at most ${maxChaptersPerHour} chapters per hour of content
2303
2355
  - Use start times in seconds (not HH:MM:SS)
2304
2356
  - Chapter start times should be non-decreasing
@@ -2356,7 +2408,7 @@ async function generateChapters(assetId, options = {}) {
2356
2408
  const contentLabel = isAudioOnly ? "transcript" : "caption track";
2357
2409
  throw new MuxAiError(`No usable content found in ${contentLabel}.`, { type: "validation_error" });
2358
2410
  }
2359
- const resolvedLanguageCode = outputLanguageCode && outputLanguageCode !== "auto" ? outputLanguageCode : transcriptResult.track?.language_code ?? languageCode;
2411
+ const resolvedLanguageCode = outputLanguageCode && outputLanguageCode !== "auto" ? outputLanguageCode : getReliableLanguageCode(transcriptResult.track) ?? languageCode;
2360
2412
  const languageName = resolvedLanguageCode ? getLanguageName(resolvedLanguageCode) : void 0;
2361
2413
  const userPrompt = buildUserPrompt3({
2362
2414
  timestampedTranscript,
@@ -2398,7 +2450,7 @@ async function generateChapters(assetId, options = {}) {
2398
2450
  };
2399
2451
  return {
2400
2452
  assetId,
2401
- languageCode: languageCode ?? transcriptResult.track?.language_code ?? "en",
2453
+ languageCode: languageCode ?? getReliableLanguageCode(transcriptResult.track) ?? "en",
2402
2454
  chapters: validChapters,
2403
2455
  usage: usageWithMetadata
2404
2456
  };
@@ -2406,7 +2458,7 @@ async function generateChapters(assetId, options = {}) {
2406
2458
 
2407
2459
  // src/workflows/edit-captions.ts
2408
2460
  import { generateText as generateText4, Output as Output4 } from "ai";
2409
- import dedent4 from "dedent";
2461
+ import dedent5 from "dedent";
2410
2462
  import { z as z5 } from "zod";
2411
2463
 
2412
2464
  // src/lib/mux-tracks.ts
@@ -2695,7 +2747,7 @@ var profanityDetectionSchema = z5.object({
2695
2747
  "Unique profane words or short phrases exactly as they appear in the transcript text. Include each distinct form only once (e.g., if 'fuck' and 'fucking' both appear, list both)."
2696
2748
  )
2697
2749
  });
2698
- var SYSTEM_PROMPT3 = dedent4`
2750
+ var SYSTEM_PROMPT3 = dedent5`
2699
2751
  You are a content moderation assistant. Your task is to identify profane, vulgar, or obscene
2700
2752
  words and phrases in subtitle text. Return ONLY the exact profane words or phrases as they appear
2701
2753
  in the text. Do not modify, censor, or paraphrase them. Do not include words that are merely
@@ -3411,7 +3463,7 @@ async function generateVideoEmbeddings(assetId, options = {}) {
3411
3463
 
3412
3464
  // src/workflows/engagement-insights.ts
3413
3465
  import { generateText as generateText5, Output as Output5 } from "ai";
3414
- import dedent5 from "dedent";
3466
+ import dedent6 from "dedent";
3415
3467
  import { z as z6 } from "zod";
3416
3468
 
3417
3469
  // src/primitives/heatmap.ts
@@ -3564,7 +3616,7 @@ var engagementInsightsSchema = z6.object({
3564
3616
  momentInsights: z6.array(aiMomentInsightSchema),
3565
3617
  overallInsight: aiOverallInsightSchema
3566
3618
  });
3567
- var SYSTEM_PROMPT4 = dedent5`
3619
+ var SYSTEM_PROMPT4 = promptDedent`
3568
3620
  <role>
3569
3621
  You are a video engagement analyst specializing in understanding viewer behavior.
3570
3622
  Your job is to explain why specific moments in videos have high or low engagement
@@ -3592,12 +3644,10 @@ var SYSTEM_PROMPT4 = dedent5`
3592
3644
 
3593
3645
  <constraints>
3594
3646
  - Only describe what you can see in images or read in transcripts
3595
- - Do not fabricate details or make unsupported assumptions
3596
- - Do NOT use any metadata such as URLs, file paths, domain names, file names,
3597
- playback IDs, or technical parameters visible in this request. These are
3598
- delivery infrastructure and are unrelated to the media content itself.
3647
+ - ${NO_FABRICATION_CONSTRAINT}
3648
+ - ${METADATA_BOUNDARY_WARNING}
3599
3649
  - Correlate engagement scores with observable content
3600
- - Return structured data matching the requested schema exactly
3650
+ - ${STRUCTURED_DATA_CONSTRAINT}
3601
3651
  - Each momentInsight MUST include the hotspotIndex from the input data
3602
3652
  </constraints>
3603
3653
 
@@ -3608,7 +3658,7 @@ var SYSTEM_PROMPT4 = dedent5`
3608
3658
  - Connect engagement data to content features
3609
3659
  </quality_guidelines>
3610
3660
  `;
3611
- var AUDIO_ONLY_SYSTEM_PROMPT2 = dedent5`
3661
+ var AUDIO_ONLY_SYSTEM_PROMPT2 = promptDedent`
3612
3662
  <role>
3613
3663
  You are a video engagement analyst specializing in understanding viewer behavior for audio content.
3614
3664
  Your job is to explain why specific moments have high or low engagement based on audio/dialogue and viewer watch patterns.
@@ -3635,12 +3685,10 @@ var AUDIO_ONLY_SYSTEM_PROMPT2 = dedent5`
3635
3685
 
3636
3686
  <constraints>
3637
3687
  - Only describe what you can read in the transcript
3638
- - Do not fabricate details or make unsupported assumptions
3639
- - Do NOT use any metadata such as URLs, file paths, domain names, file names,
3640
- playback IDs, or technical parameters visible in this request. These are
3641
- delivery infrastructure and are unrelated to the media content itself.
3688
+ - ${NO_FABRICATION_CONSTRAINT}
3689
+ - ${METADATA_BOUNDARY_WARNING}
3642
3690
  - Correlate engagement scores with observable content
3643
- - Return structured data matching the requested schema exactly
3691
+ - ${STRUCTURED_DATA_CONSTRAINT}
3644
3692
  - Each momentInsight MUST include the hotspotIndex from the input data
3645
3693
  </constraints>
3646
3694
 
@@ -3651,7 +3699,7 @@ var AUDIO_ONLY_SYSTEM_PROMPT2 = dedent5`
3651
3699
  - Connect engagement data to content features
3652
3700
  </quality_guidelines>
3653
3701
  `;
3654
- var INSIGHT_GUIDELINES = dedent5`
3702
+ var INSIGHT_GUIDELINES = dedent6`
3655
3703
  Provide explanatory insights that describe WHY moments are engaging.
3656
3704
  Focus on observable patterns and viewer behavior correlation.
3657
3705
  Example: "The cooking demonstration at 2:15 has 3x average engagement
@@ -3669,7 +3717,7 @@ var engagementInsightsPromptBuilder = createPromptBuilder({
3669
3717
  },
3670
3718
  outputFormat: {
3671
3719
  tag: "output_format",
3672
- content: dedent5`
3720
+ content: dedent6`
3673
3721
  Return valid JSON matching the provided schema.
3674
3722
  Include insights for each hotspot and overall engagement trends.
3675
3723
  Each momentInsight must include the hotspotIndex matching the input.
@@ -3677,7 +3725,7 @@ var engagementInsightsPromptBuilder = createPromptBuilder({
3677
3725
  },
3678
3726
  visualContext: {
3679
3727
  tag: "visual_context",
3680
- content: dedent5`
3728
+ content: dedent6`
3681
3729
  Images are provided for each hotspot timestamp.
3682
3730
  A storyboard overview image may also be included showing the full video timeline.
3683
3731
  Use visual evidence from these images to support your engagement analysis.
@@ -3691,14 +3739,14 @@ function buildUserPrompt4(hotspots, transcriptSegments, heatmap, isAudioOnly) {
3691
3739
  const transcript = transcriptSegments[idx]?.text || "(no transcript)";
3692
3740
  const startTimestamp = secondsToTimestamp(h.startMs / 1e3);
3693
3741
  const endTimestamp = secondsToTimestamp(h.endMs / 1e3);
3694
- return dedent5`
3742
+ return dedent6`
3695
3743
  Hotspot ${idx} (hotspotIndex: ${idx}):
3696
3744
  - Time Range: ${startTimestamp} - ${endTimestamp}
3697
3745
  - Engagement Score: ${h.score.toFixed(2)} (0=low, 1=high)
3698
3746
  - Transcript: "${transcript}"
3699
3747
  `;
3700
3748
  }).join("\n\n");
3701
- const heatmapData = dedent5`
3749
+ const heatmapData = dedent6`
3702
3750
  Engagement Heatmap (${heatmap.length} values, each representing 1/${heatmap.length}th of the video, higher = more re-watches):
3703
3751
  [${heatmap.map((v) => v.toFixed(2)).join(", ")}]
3704
3752
  `;
@@ -4607,7 +4655,7 @@ async function getModerationScores(assetId, options = {}) {
4607
4655
 
4608
4656
  // src/workflows/summarization.ts
4609
4657
  import { generateText as generateText6, Output as Output6 } from "ai";
4610
- import dedent6 from "dedent";
4658
+ import dedent7 from "dedent";
4611
4659
  import { z as z7 } from "zod";
4612
4660
  var DEFAULT_SUMMARY_KEYWORD_LIMIT = 10;
4613
4661
  var DEFAULT_TITLE_LENGTH = 10;
@@ -4633,36 +4681,36 @@ var DESCRIPTION_LENGTH_THRESHOLD_LARGE = 100;
4633
4681
  function buildDescriptionGuidance(wordCount, contentType) {
4634
4682
  if (wordCount < DESCRIPTION_LENGTH_THRESHOLD_SMALL) {
4635
4683
  if (contentType === "video") {
4636
- return dedent6`A brief summary of the video in no more than ${wordCount} words. Shorter is fine.
4684
+ return dedent7`A brief summary of the video in no more than ${wordCount} words. Shorter is fine.
4637
4685
  Focus on the single most important subject or action.
4638
4686
  Write in present tense.`;
4639
4687
  }
4640
- return dedent6`A brief summary of the audio content in no more than ${wordCount} words. Shorter is fine.
4688
+ return dedent7`A brief summary of the audio content in no more than ${wordCount} words. Shorter is fine.
4641
4689
  Focus on the single most important topic or theme.
4642
4690
  Write in present tense.`;
4643
4691
  }
4644
4692
  if (wordCount > DESCRIPTION_LENGTH_THRESHOLD_LARGE) {
4645
4693
  if (contentType === "video") {
4646
- return dedent6`A detailed summary that describes what happens across the video.
4694
+ return dedent7`A detailed summary that describes what happens across the video.
4647
4695
  Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
4648
4696
  Be thorough: cover subjects, actions, setting, progression, and any notable details visible across frames.
4649
4697
  Write in present tense. Be specific about observable details rather than making assumptions.
4650
4698
  If the transcript provides dialogue or narration, incorporate key points but prioritize visual content.`;
4651
4699
  }
4652
- return dedent6`A detailed summary that describes the audio content.
4700
+ return dedent7`A detailed summary that describes the audio content.
4653
4701
  Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
4654
4702
  Be thorough: cover topics, speakers, themes, progression, and any notable insights.
4655
4703
  Write in present tense. Be specific about what is discussed or presented rather than making assumptions.
4656
4704
  Focus on the spoken content and any key insights, dialogue, or narrative elements.`;
4657
4705
  }
4658
4706
  if (contentType === "video") {
4659
- return dedent6`A summary that describes what happens across the video.
4707
+ return dedent7`A summary that describes what happens across the video.
4660
4708
  Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
4661
4709
  Cover the main subjects, actions, setting, and any notable progression visible across frames.
4662
4710
  Write in present tense. Be specific about observable details rather than making assumptions.
4663
4711
  If the transcript provides dialogue or narration, incorporate key points but prioritize visual content.`;
4664
4712
  }
4665
- return dedent6`A summary that describes the audio content.
4713
+ return dedent7`A summary that describes the audio content.
4666
4714
  Never exceed ${wordCount} words, but shorter is perfectly fine. You may use multiple sentences.
4667
4715
  Cover the main topics, speakers, themes, and any notable progression in the discussion or narration.
4668
4716
  Write in present tense. Be specific about what is discussed or presented rather than making assumptions.
@@ -4679,7 +4727,7 @@ function createSummarizationBuilder({ titleLength, descriptionLength, tagCount }
4679
4727
  },
4680
4728
  title: {
4681
4729
  tag: "title_requirements",
4682
- content: dedent6`
4730
+ content: dedent7`
4683
4731
  A concise, label-style title — not a sentence or description.
4684
4732
  Never exceed ${titleLimit} words, but shorter is better.
4685
4733
  Think of how a video card title, playlist entry, or file name would read — e.g. "Predator: Badlands Trailer" or "Chef Prepares Holiday Feast".
@@ -4692,7 +4740,7 @@ function createSummarizationBuilder({ titleLength, descriptionLength, tagCount }
4692
4740
  },
4693
4741
  keywords: {
4694
4742
  tag: "keywords_requirements",
4695
- content: dedent6`
4743
+ content: dedent7`
4696
4744
  Specific, searchable terms (up to ${keywordLimit}) that capture:
4697
4745
  - Primary subjects (people, animals, objects)
4698
4746
  - Actions and activities being performed
@@ -4704,7 +4752,7 @@ function createSummarizationBuilder({ titleLength, descriptionLength, tagCount }
4704
4752
  },
4705
4753
  qualityGuidelines: {
4706
4754
  tag: "quality_guidelines",
4707
- content: dedent6`
4755
+ content: dedent7`
4708
4756
  - Examine all frames to understand the full context and progression
4709
4757
  - Be precise: "golden retriever" is better than "dog" when identifiable
4710
4758
  - Capture the narrative: what begins, develops, and concludes
@@ -4725,7 +4773,7 @@ function createAudioOnlyBuilder({ titleLength, descriptionLength, tagCount } = {
4725
4773
  },
4726
4774
  title: {
4727
4775
  tag: "title_requirements",
4728
- content: dedent6`
4776
+ content: dedent7`
4729
4777
  A concise, label-style title — not a sentence or description.
4730
4778
  Never exceed ${titleLimit} words, but shorter is better.
4731
4779
  Think of how a podcast episode title or playlist entry would read — e.g. "Weekly News Roundup" or "Interview with Dr. Smith".
@@ -4738,7 +4786,7 @@ function createAudioOnlyBuilder({ titleLength, descriptionLength, tagCount } = {
4738
4786
  },
4739
4787
  keywords: {
4740
4788
  tag: "keywords_requirements",
4741
- content: dedent6`
4789
+ content: dedent7`
4742
4790
  Specific, searchable terms (up to ${keywordLimit}) that capture:
4743
4791
  - Primary topics and themes
4744
4792
  - Speakers or presenters (if named)
@@ -4750,7 +4798,7 @@ function createAudioOnlyBuilder({ titleLength, descriptionLength, tagCount } = {
4750
4798
  },
4751
4799
  qualityGuidelines: {
4752
4800
  tag: "quality_guidelines",
4753
- content: dedent6`
4801
+ content: dedent7`
4754
4802
  - Analyze the full transcript to understand context and themes
4755
4803
  - Be precise: use specific terminology when mentioned
4756
4804
  - Capture the narrative: what is introduced, discussed, and concluded
@@ -4760,15 +4808,14 @@ function createAudioOnlyBuilder({ titleLength, descriptionLength, tagCount } = {
4760
4808
  sectionOrder: ["task", "title", "description", "keywords", "qualityGuidelines"]
4761
4809
  });
4762
4810
  }
4763
- var SYSTEM_PROMPT5 = dedent6`
4811
+ var SYSTEM_PROMPT5 = promptDedent`
4764
4812
  <role>
4765
4813
  You are a video content analyst specializing in storyboard interpretation and multimodal analysis.
4766
4814
  </role>
4767
4815
 
4768
4816
  <context>
4769
4817
  You receive storyboard images containing multiple sequential frames extracted from a video.
4770
- These frames are arranged in a grid and represent the visual progression of the content over time.
4771
- Read frames left-to-right, top-to-bottom to understand the temporal sequence.
4818
+ ${STORYBOARD_FRAME_INSTRUCTIONS}
4772
4819
  </context>
4773
4820
 
4774
4821
  <transcript_guidance>
@@ -4790,37 +4837,21 @@ var SYSTEM_PROMPT5 = dedent6`
4790
4837
 
4791
4838
  <constraints>
4792
4839
  - Only describe what is clearly observable in the frames or explicitly stated in the transcript
4793
- - Do not fabricate details or make unsupported assumptions
4794
- - Do NOT use any metadata such as URLs, file paths, domain names, file names,
4795
- playback IDs, or technical parameters visible in this request. These are
4796
- delivery infrastructure and are unrelated to the media content itself.
4797
- - Return structured data matching the requested schema
4840
+ - ${NO_FABRICATION_CONSTRAINT}
4841
+ - ${METADATA_BOUNDARY_WARNING}
4842
+ - ${STRUCTURED_DATA_CONSTRAINT}
4798
4843
  - Output only the JSON object; no markdown or extra text
4799
4844
  - When a <language> section is provided, all output text MUST be written in that language
4800
4845
  </constraints>
4801
4846
 
4802
4847
  <tone_guidance>
4803
- Pay special attention to the <tone> section and lean heavily into those instructions.
4804
- Adapt your entire analysis and writing style to match the specified tone - this should influence
4805
- your word choice, personality, formality level, and overall presentation of the content.
4806
- The tone instructions are not suggestions but core requirements for how you should express yourself.
4848
+ ${TONE_GUIDANCE}
4807
4849
  </tone_guidance>
4808
4850
 
4809
4851
  <language_guidelines>
4810
- AVOID these meta-descriptive phrases that reference the medium rather than the content:
4811
- - "The image shows..." / "The storyboard shows..."
4812
- - "In this video..." / "This video features..."
4813
- - "The frames depict..." / "The footage shows..."
4814
- - "We can see..." / "You can see..."
4815
- - "The clip shows..." / "The scene shows..."
4816
-
4817
- INSTEAD, describe the content directly:
4818
- - BAD: "The video shows a chef preparing a meal"
4819
- - GOOD: "A chef prepares a meal in a professional kitchen"
4820
-
4821
- Write as if describing reality, not describing a recording of reality.
4852
+ ${createLanguageGuidelines("video")}
4822
4853
  </language_guidelines>`;
4823
- var AUDIO_ONLY_SYSTEM_PROMPT3 = dedent6`
4854
+ var AUDIO_ONLY_SYSTEM_PROMPT3 = promptDedent`
4824
4855
  <role>
4825
4856
  You are an audio content analyst specializing in transcript analysis and metadata generation.
4826
4857
  </role>
@@ -4847,35 +4878,20 @@ var AUDIO_ONLY_SYSTEM_PROMPT3 = dedent6`
4847
4878
 
4848
4879
  <constraints>
4849
4880
  - Only describe what is explicitly stated or strongly implied in the transcript
4850
- - Do not fabricate details or make unsupported assumptions
4851
- - Do NOT use any metadata such as URLs, file paths, domain names, file names,
4852
- playback IDs, or technical parameters visible in this request. These are
4853
- delivery infrastructure and are unrelated to the media content itself.
4854
- - Return structured data matching the requested schema
4881
+ - ${NO_FABRICATION_CONSTRAINT}
4882
+ - ${METADATA_BOUNDARY_WARNING}
4883
+ - ${STRUCTURED_DATA_CONSTRAINT}
4855
4884
  - Focus entirely on audio/spoken content - there are no visual elements
4856
4885
  - Output only the JSON object; no markdown or extra text
4857
4886
  - When a <language> section is provided, all output text MUST be written in that language
4858
4887
  </constraints>
4859
4888
 
4860
4889
  <tone_guidance>
4861
- Pay special attention to the <tone> section and lean heavily into those instructions.
4862
- Adapt your entire analysis and writing style to match the specified tone - this should influence
4863
- your word choice, personality, formality level, and overall presentation of the content.
4864
- The tone instructions are not suggestions but core requirements for how you should express yourself.
4890
+ ${TONE_GUIDANCE}
4865
4891
  </tone_guidance>
4866
4892
 
4867
4893
  <language_guidelines>
4868
- AVOID these meta-descriptive phrases that reference the medium rather than the content:
4869
- - "The audio shows..." / "The transcript shows..."
4870
- - "In this recording..." / "This audio features..."
4871
- - "The speaker says..." / "We can hear..."
4872
- - "The clip contains..." / "The recording shows..."
4873
-
4874
- INSTEAD, describe the content directly:
4875
- - BAD: "The audio features a discussion about climate change"
4876
- - GOOD: "A panel discusses climate change impacts and solutions"
4877
-
4878
- Write as if describing reality, not describing a recording of reality.
4894
+ ${createLanguageGuidelines("audio")}
4879
4895
  </language_guidelines>`;
4880
4896
  function buildUserPrompt5({
4881
4897
  tone,
@@ -5049,7 +5065,7 @@ async function getSummaryAndTags(assetId, options) {
5049
5065
  required: isAudioOnly
5050
5066
  }) : void 0;
5051
5067
  const transcriptText = transcriptResult?.transcriptText ?? "";
5052
- const resolvedLanguageCode = outputLanguageCode && outputLanguageCode !== "auto" ? outputLanguageCode : transcriptResult?.track?.language_code ?? getReadyTextTracks(assetData)[0]?.language_code;
5068
+ const resolvedLanguageCode = outputLanguageCode && outputLanguageCode !== "auto" ? outputLanguageCode : getReliableLanguageCode(transcriptResult?.track) ?? getReliableLanguageCode(getReadyTextTracks(assetData)[0]);
5053
5069
  const languageName = resolvedLanguageCode ? getLanguageName(resolvedLanguageCode) : void 0;
5054
5070
  const userPrompt = buildUserPrompt5({
5055
5071
  tone,
@@ -5541,18 +5557,18 @@ import {
5541
5557
  RetryError,
5542
5558
  TypeValidationError
5543
5559
  } from "ai";
5544
- import dedent7 from "dedent";
5560
+ import dedent8 from "dedent";
5545
5561
  import { z as z8 } from "zod";
5546
5562
  var translationSchema = z8.object({
5547
5563
  translation: z8.string()
5548
5564
  });
5549
- var SYSTEM_PROMPT6 = dedent7`
5565
+ var SYSTEM_PROMPT6 = dedent8`
5550
5566
  You are a subtitle translation expert. Translate VTT subtitle files to the target language specified by the user.
5551
5567
  You may receive either a full VTT file or a chunk from a larger VTT.
5552
5568
  Preserve all timestamps, cue ordering, and VTT formatting exactly as they appear.
5553
5569
  Return JSON with a single key "translation" containing the translated VTT content.
5554
5570
  `;
5555
- var CUE_TRANSLATION_SYSTEM_PROMPT = dedent7`
5571
+ var CUE_TRANSLATION_SYSTEM_PROMPT = dedent8`
5556
5572
  You are a subtitle translation expert.
5557
5573
  You will receive a sequence of subtitle cues extracted from a VTT file.
5558
5574
  Translate the cues to the requested target language while preserving their original order.
@@ -6063,7 +6079,7 @@ async function translateCaptions(assetId, trackId, toLanguageCode, options) {
6063
6079
  }
6064
6080
  if (uploadToMux) {
6065
6081
  try {
6066
- const languageName = getLanguageName(toLanguageCode);
6082
+ const languageName = getLanguageName(toLanguageCode) ?? toLanguageCode.toUpperCase();
6067
6083
  const trackName = `${languageName} (auto-translated)`;
6068
6084
  uploadedTrackId = await createTextTrackOnMux(
6069
6085
  assetId,