@mux/ai 0.45.0 → 0.47.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.45.0";
8
+ var version = "0.47.0";
9
9
 
10
10
  // src/lib/language-codes.ts
11
11
  var ISO639_1_TO_3 = {
@@ -1055,7 +1055,7 @@ function resolveDirectMuxCredentials(record) {
1055
1055
  function createWorkflowMuxClient(options) {
1056
1056
  return {
1057
1057
  async createClient() {
1058
- const { default: MuxClient } = await import("@mux/mux-node");
1058
+ const { default: MuxClient } = await import("@mux/ts");
1059
1059
  return new MuxClient({
1060
1060
  tokenId: options.tokenId,
1061
1061
  tokenSecret: options.tokenSecret,
@@ -1195,17 +1195,24 @@ function findNameCollisionTextTracks(asset, target) {
1195
1195
  }
1196
1196
  return listTracks(asset, targetType(target)).filter((track) => normalizeTrackName(track.name) === targetName);
1197
1197
  }
1198
+ function countTracks(tracks, label, singularVerb, pluralVerb) {
1199
+ const kind = label.toLowerCase();
1200
+ if (tracks.length === 1) {
1201
+ return `${kind === "audio" ? "An" : "A"} ${kind} track ${singularVerb}`;
1202
+ }
1203
+ return `${tracks.length} ${kind} tracks ${pluralVerb}`;
1204
+ }
1205
+ function itOrThem(tracks) {
1206
+ return tracks.length === 1 ? "it" : "them";
1207
+ }
1198
1208
  function describeTracks(tracks) {
1199
1209
  return tracks.map((track) => `${track.name ?? "(unnamed)"} [${track.language_code ?? "?"}, ${track.type === "audio" ? track.primary ? "primary audio" : "audio" : track.text_source ?? "unknown source"}, ${track.status ?? "ready"}]`).join("; ");
1200
1210
  }
1201
- function planTextTrackReplacement(asset, target, policy, options = {}) {
1211
+ function planTextTrackReplacement(asset, target, policy) {
1202
1212
  const label = targetType(target) === "audio" ? "Audio" : "Text";
1203
- const keep = new Set(options.keepTrackIds ?? []);
1204
1213
  const conflicts = /* @__PURE__ */ new Map();
1205
1214
  for (const track of [...findSameLanguageTextTracks(asset, target), ...findNameCollisionTextTracks(asset, target)]) {
1206
- if (!keep.has(track.id)) {
1207
- conflicts.set(track.id, track);
1208
- }
1215
+ conflicts.set(track.id, track);
1209
1216
  }
1210
1217
  const conflicting = [...conflicts.values()];
1211
1218
  if (conflicting.length === 0) {
@@ -1214,7 +1221,7 @@ function planTextTrackReplacement(asset, target, policy, options = {}) {
1214
1221
  if (policy === "fail") {
1215
1222
  return {
1216
1223
  kind: "blocked",
1217
- reason: `${label} track(s) already exist for language '${target.languageCode}' or name '${target.name}': ${describeTracks(conflicting)}. Set replaceExistingTracks to replace them.`,
1224
+ reason: `${countTracks(conflicting, label, "already exists", "already exist")} for language '${target.languageCode}' or name '${target.name}': ${describeTracks(conflicting)}. Set replaceExistingTracks to replace ${itOrThem(conflicting)}.`,
1218
1225
  tracks: conflicting.map(summarizeTextTrack)
1219
1226
  };
1220
1227
  }
@@ -1231,13 +1238,26 @@ function planTextTrackReplacement(asset, target, policy, options = {}) {
1231
1238
  if (notGenerated.length > 0) {
1232
1239
  return {
1233
1240
  kind: "blocked",
1234
- reason: `${label} track(s) that are not Mux-generated exist for language '${target.languageCode}' or name '${target.name}': ${describeTracks(notGenerated)}. Use replaceExistingTracks: "replace_all" to replace them.`,
1241
+ reason: `${countTracks(notGenerated, label, "that is not Mux-generated exists", "that are not Mux-generated exist")} for language '${target.languageCode}' or name '${target.name}': ${describeTracks(notGenerated)}. Use replaceExistingTracks: "replace_all" to replace ${itOrThem(notGenerated)}.`,
1235
1242
  tracks: notGenerated.map(summarizeTextTrack)
1236
1243
  };
1237
1244
  }
1238
1245
  }
1239
1246
  return { kind: "replace", toDelete: conflicting.map(summarizeTextTrack) };
1240
1247
  }
1248
+ function planSourceTrackReplacement(asset, sourceTrackId, target, policy) {
1249
+ const colliding = findNameCollisionTextTracks(asset, target);
1250
+ const blocking = policy === "fail" ? colliding : colliding.filter((track) => track.id !== sourceTrackId);
1251
+ if (blocking.length > 0) {
1252
+ return {
1253
+ kind: "blocked",
1254
+ reason: `${countTracks(blocking, "Text", "already has", "already have")} the name '${target.name}': ${describeTracks(blocking)}. Choose a different trackName.`,
1255
+ tracks: blocking.map(summarizeTextTrack)
1256
+ };
1257
+ }
1258
+ const source = policy === "replace" ? listTracks(asset, targetType(target)).find((track) => track.id === sourceTrackId) : void 0;
1259
+ return source ? { kind: "replace", toDelete: [summarizeTextTrack(source)] } : { kind: "clear" };
1260
+ }
1241
1261
  function isDuplicateTrackNameError(error) {
1242
1262
  if (typeof error !== "object" || error === null) {
1243
1263
  return false;
@@ -1293,6 +1313,14 @@ async function createTextTrackOnMux(assetId, languageCode, trackName, presignedU
1293
1313
  }
1294
1314
  async function replaceAndCreateTrack(input) {
1295
1315
  "use step";
1316
+ return writeTrack(input, (asset) => planTextTrackReplacement(asset, input.target, input.policy));
1317
+ }
1318
+ async function replaceSourceTrack(input) {
1319
+ "use step";
1320
+ const target = { ...input.target, type: "text" };
1321
+ return writeTrack({ ...input, target }, (asset) => planSourceTrackReplacement(asset, input.sourceTrackId, target, input.policy));
1322
+ }
1323
+ async function writeTrack(input, plan) {
1296
1324
  const muxClient = await resolveMuxClient(input.credentials);
1297
1325
  const mux = await muxClient.createClient();
1298
1326
  const type = targetType(input.target);
@@ -1312,12 +1340,12 @@ async function replaceAndCreateTrack(input) {
1312
1340
  }
1313
1341
  return failed(error);
1314
1342
  }
1315
- const plan = planTextTrackReplacement(asset, input.target, input.policy, { keepTrackIds: input.keepTrackIds });
1316
- if (plan.kind === "blocked") {
1317
- return { ...plan, deleted };
1343
+ const planned = plan(asset);
1344
+ if (planned.kind === "blocked") {
1345
+ return { ...planned, deleted };
1318
1346
  }
1319
- if (plan.kind === "replace") {
1320
- for (const track of plan.toDelete) {
1347
+ if (planned.kind === "replace") {
1348
+ for (const track of planned.toDelete) {
1321
1349
  try {
1322
1350
  await mux.video.assets.deleteTrack(input.assetId, track.id);
1323
1351
  deleted.push(track);
@@ -2182,25 +2210,22 @@ async function getHeatmapForPlaybackId(playbackId, options = {}) {
2182
2210
  "use step";
2183
2211
  return fetchHeatmap("playback-ids", playbackId, options);
2184
2212
  }
2185
- function transformHeatmapResponse(response) {
2186
- return {
2187
- assetId: response.data.asset_id,
2188
- videoId: response.data.video_id,
2189
- playbackId: response.data.playback_id,
2190
- heatmap: response.data.heatmap,
2191
- timeframe: response.timeframe
2192
- };
2193
- }
2194
2213
  async function fetchHeatmap(identifierType, id, options) {
2195
2214
  "use step";
2196
2215
  const { timeframe = "7:days", credentials } = options;
2197
2216
  const muxClient = await getMuxClientFromEnv(credentials);
2198
2217
  const mux = await muxClient.createClient();
2199
- const queryParams = new URLSearchParams();
2200
- queryParams.append("timeframe[]", timeframe);
2201
- const path = `/data/v1/engagement/${identifierType}/${id}/heatmap?${queryParams.toString()}`;
2202
- const response = await mux.get(path);
2203
- return transformHeatmapResponse(response);
2218
+ const query = { timeframe: [timeframe] };
2219
+ const resource = identifierType === "playback-ids" ? mux.data.engagement.playbackIds : mux.data.engagement[identifierType];
2220
+ const response = await resource.heatmap(id, query);
2221
+ const { data } = response;
2222
+ return {
2223
+ assetId: data.asset_id ?? (identifierType === "assets" ? id : void 0),
2224
+ videoId: data.video_id ?? (identifierType === "videos" ? id : void 0),
2225
+ playbackId: data.playback_id ?? (identifierType === "playback-ids" ? id : void 0),
2226
+ heatmap: data.heatmap,
2227
+ timeframe: response.timeframe
2228
+ };
2204
2229
  }
2205
2230
 
2206
2231
  // src/primitives/hotspots.ts
@@ -2219,37 +2244,34 @@ async function getHotspotsForPlaybackId(playbackId, options = {}) {
2219
2244
  const response = await fetchHotspots("playback-ids", playbackId, options);
2220
2245
  return response.hotspots;
2221
2246
  }
2222
- function transformHotspotResponse(response) {
2223
- return {
2224
- assetId: response.data.asset_id,
2225
- videoId: response.data.video_id,
2226
- playbackId: response.data.playback_id,
2227
- hotspots: response.data.hotspots.map((h) => ({
2228
- startMs: h.start_ms,
2229
- endMs: h.end_ms,
2230
- score: h.score
2231
- }))
2232
- };
2233
- }
2234
2247
  async function fetchHotspots(identifierType, id, options) {
2235
2248
  "use step";
2236
2249
  const {
2237
2250
  limit = 5,
2238
2251
  orderDirection = "desc",
2239
- orderBy = "score",
2240
2252
  timeframe = "7:days",
2241
2253
  credentials
2242
2254
  } = options;
2243
2255
  const muxClient = await getMuxClientFromEnv(credentials);
2244
2256
  const mux = await muxClient.createClient();
2245
- const queryParams = new URLSearchParams();
2246
- queryParams.append("limit", String(limit));
2247
- queryParams.append("order_direction", orderDirection);
2248
- queryParams.append("order_by", orderBy);
2249
- queryParams.append("timeframe[]", timeframe);
2250
- const path = `/data/v1/engagement/${identifierType}/${id}/hotspots?${queryParams.toString()}`;
2251
- const response = await mux.get(path);
2252
- return transformHotspotResponse(response);
2257
+ const query = {
2258
+ limit,
2259
+ order_direction: orderDirection,
2260
+ timeframe: [timeframe]
2261
+ };
2262
+ const resource = identifierType === "playback-ids" ? mux.data.engagement.playbackIds : mux.data.engagement[identifierType];
2263
+ const response = await resource.hotspots(id, query);
2264
+ const { data } = response;
2265
+ return {
2266
+ assetId: data.asset_id ?? (identifierType === "assets" ? id : void 0),
2267
+ videoId: data.video_id ?? (identifierType === "videos" ? id : void 0),
2268
+ playbackId: data.playback_id ?? (identifierType === "playback-ids" ? id : void 0),
2269
+ hotspots: data.hotspots.map((h) => ({
2270
+ startMs: h.start_ms,
2271
+ endMs: h.end_ms,
2272
+ score: h.score
2273
+ }))
2274
+ };
2253
2275
  }
2254
2276
 
2255
2277
  // src/primitives/shots.ts
@@ -2257,9 +2279,6 @@ var DEFAULT_POLL_INTERVAL_MS = 2e3;
2257
2279
  var MIN_POLL_INTERVAL_MS = 1e3;
2258
2280
  var DEFAULT_MAX_ATTEMPTS = 60;
2259
2281
  var SHOTS_ALREADY_REQUESTED_MESSAGE = "shots generation has already been requested";
2260
- function getShotsPath(assetId) {
2261
- return `/video/v1/assets/${assetId}/shots`;
2262
- }
2263
2282
  function mapManifestShots(shots) {
2264
2283
  let usedDeprecatedField = false;
2265
2284
  const mapped = shots.map((shot, index) => {
@@ -2299,29 +2318,35 @@ async function fetchShotsFromManifest(shotsManifestUrl) {
2299
2318
  }
2300
2319
  return mapManifestShots(manifest.shots);
2301
2320
  }
2302
- async function transformShotsResponse(response) {
2303
- switch (response.data.status) {
2321
+ async function transformShotsResponse(data) {
2322
+ switch (data.status) {
2304
2323
  case "pending":
2305
2324
  return {
2306
2325
  status: "pending",
2307
- createdAt: response.data.created_at
2326
+ createdAt: data.created_at
2308
2327
  };
2309
- case "errored":
2328
+ case "errored": {
2329
+ const details = data.errors ?? data.error;
2310
2330
  return {
2311
2331
  status: "errored",
2312
- createdAt: response.data.created_at,
2313
- error: response.data.error
2332
+ createdAt: data.created_at,
2333
+ error: {
2334
+ type: details?.type ?? "unknown",
2335
+ messages: details?.messages ?? []
2336
+ }
2314
2337
  };
2338
+ }
2315
2339
  case "completed":
2340
+ if (!data.shots_manifest_url) {
2341
+ throw new Error("Completed shots response is missing shots_manifest_url");
2342
+ }
2316
2343
  return {
2317
2344
  status: "completed",
2318
- createdAt: response.data.created_at,
2319
- shots: await fetchShotsFromManifest(response.data.shots_manifest_url)
2345
+ createdAt: data.created_at,
2346
+ shots: await fetchShotsFromManifest(data.shots_manifest_url)
2320
2347
  };
2321
- default: {
2322
- const exhaustiveCheck = response.data;
2323
- throw new Error(`Unsupported shots response: ${JSON.stringify(exhaustiveCheck)}`);
2324
- }
2348
+ default:
2349
+ throw new Error(`Unsupported shots status '${data.status}'`);
2325
2350
  }
2326
2351
  }
2327
2352
  function sleep(ms) {
@@ -2329,7 +2354,8 @@ function sleep(ms) {
2329
2354
  }
2330
2355
  function isShotsAlreadyRequestedError(error) {
2331
2356
  const statusCode = error?.status ?? error?.statusCode;
2332
- const messages = error?.error?.messages;
2357
+ const body = error?.error;
2358
+ const messages = body?.error?.messages ?? body?.messages;
2333
2359
  const lowerCaseMessages = messages?.map((message) => message.toLowerCase()) ?? [];
2334
2360
  const errorMessage = error instanceof Error ? error.message.toLowerCase() : "";
2335
2361
  return statusCode === 400 && (lowerCaseMessages.some((message) => message.includes(SHOTS_ALREADY_REQUESTED_MESSAGE)) || errorMessage.includes(SHOTS_ALREADY_REQUESTED_MESSAGE));
@@ -2339,11 +2365,8 @@ async function requestShotsForAsset(assetId, options = {}) {
2339
2365
  const { credentials } = options;
2340
2366
  const muxClient = await getMuxClientFromEnv(credentials);
2341
2367
  const mux = await muxClient.createClient();
2342
- const response = await mux.post(
2343
- getShotsPath(assetId),
2344
- { body: {} }
2345
- );
2346
- const result = await transformShotsResponse(response);
2368
+ const data = await mux.video.assets.generateShots(assetId, {});
2369
+ const result = await transformShotsResponse(data);
2347
2370
  if (result.status !== "pending") {
2348
2371
  throw new Error(
2349
2372
  `Expected pending status after requesting shots for asset '${assetId}', received '${result.status}'`
@@ -2356,10 +2379,8 @@ async function getShotsForAsset(assetId, options = {}) {
2356
2379
  const { credentials } = options;
2357
2380
  const muxClient = await getMuxClientFromEnv(credentials);
2358
2381
  const mux = await muxClient.createClient();
2359
- const response = await mux.get(
2360
- getShotsPath(assetId)
2361
- );
2362
- return await transformShotsResponse(response);
2382
+ const data = await mux.video.assets.retrieveShots(assetId);
2383
+ return await transformShotsResponse(data);
2363
2384
  }
2364
2385
  async function waitForShotsForAsset(assetId, options = {}) {
2365
2386
  "use step";
@@ -2402,12 +2423,8 @@ async function waitForShotsForAsset(assetId, options = {}) {
2402
2423
 
2403
2424
  // src/lib/url-signing.ts
2404
2425
  async function createSigningClient(context) {
2405
- const { default: MuxClient } = await import("@mux/mux-node");
2426
+ const { default: MuxClient } = await import("@mux/ts");
2406
2427
  return new MuxClient({
2407
- // These are not needed for signing, but the SDK requires them
2408
- // Using empty strings as we only need the jwt functionality
2409
- tokenId: env_default.MUX_TOKEN_ID || "",
2410
- tokenSecret: env_default.MUX_TOKEN_SECRET || "",
2411
2428
  jwtSigningKey: context.keyId,
2412
2429
  jwtPrivateKey: context.keySecret
2413
2430
  });
@@ -5507,7 +5524,13 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
5507
5524
  { type: "validation_error" }
5508
5525
  );
5509
5526
  }
5510
- const replaceExistingTracks = replaceExistingTracksOption ?? "replace_all";
5527
+ if (replaceExistingTracksOption !== void 0 && replaceExistingTracksOption !== "replace" && replaceExistingTracksOption !== "fail") {
5528
+ throw new MuxAiError(
5529
+ `replaceExistingTracks must be "replace" or "fail" for editCaptions (received "${String(replaceExistingTracksOption)}").`,
5530
+ { type: "validation_error" }
5531
+ );
5532
+ }
5533
+ const replaceExistingTracks = replaceExistingTracksOption ?? "replace";
5511
5534
  if (!legacyTrackNaming && replaceExistingTracks === "fail" && !providedTrackName) {
5512
5535
  throw new MuxAiError(
5513
5536
  `trackName is required when replaceExistingTracks is "fail": the edited track cannot reuse the source track's name while the source is kept.`,
@@ -5563,15 +5586,14 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
5563
5586
  languageCode: sourceLanguageCode,
5564
5587
  name: providedTrackName ?? sourceName
5565
5588
  };
5566
- const keepTrackIds = replaceExistingTracks === "fail" ? [trackId] : [];
5567
5589
  if (replaceExistingTracks === "fail" && normalizeTrackName(outputTrack.name) === normalizeTrackName(sourceName)) {
5568
5590
  throw new MuxAiError(
5569
- `trackName "${outputTrack.name}" matches the source track's name. Mux track names must be unique; choose a different trackName or use replaceExistingTracks: "replace_all" to replace the source.`,
5591
+ `trackName "${outputTrack.name}" matches the source track's name. Mux track names must be unique; choose a different trackName or use replaceExistingTracks: "replace" to replace the source.`,
5570
5592
  { type: "validation_error" }
5571
5593
  );
5572
5594
  }
5573
5595
  if (uploadToMux && !legacyTrackNaming) {
5574
- const plan = planTextTrackReplacement(assetData, outputTrack, replaceExistingTracks, { keepTrackIds });
5596
+ const plan = planSourceTrackReplacement(assetData, trackId, outputTrack, replaceExistingTracks);
5575
5597
  if (plan.kind === "blocked") {
5576
5598
  throw new MuxAiError(plan.reason, { type: "validation_error" });
5577
5599
  }
@@ -5693,14 +5715,14 @@ async function editCaptionsInternal(assetId, trackId, options, collectedUsage) {
5693
5715
  } else if (uploadToMux) {
5694
5716
  let outcome;
5695
5717
  try {
5696
- outcome = await replaceAndCreateTextTrack({
5718
+ outcome = await replaceSourceTrack({
5697
5719
  assetId,
5720
+ sourceTrackId: trackId,
5698
5721
  target: outputTrack,
5699
5722
  policy: replaceExistingTracks,
5700
5723
  presignedUrl,
5701
5724
  closedCaptions: sourceTrack.closed_captions,
5702
5725
  passthrough: trackPassthrough,
5703
- keepTrackIds,
5704
5726
  credentials
5705
5727
  });
5706
5728
  } catch (error) {
@@ -9900,10 +9922,12 @@ export {
9900
9922
  isValidISO639_3,
9901
9923
  normalizeTrackLanguageCode,
9902
9924
  normalizeTrackName,
9925
+ planSourceTrackReplacement,
9903
9926
  planTextTrackReplacement,
9904
9927
  primitives_exports as primitives,
9905
9928
  replaceAndCreateTextTrack,
9906
9929
  replaceAndCreateTrack,
9930
+ replaceSourceTrack,
9907
9931
  setWorkflowCredentialsProvider,
9908
9932
  summarizeTextTrack,
9909
9933
  toISO639_1,