hyperframes 0.8.23 → 0.8.25

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/cli.js CHANGED
@@ -50,7 +50,7 @@ var VERSION;
50
50
  var init_version = __esm({
51
51
  "src/version.ts"() {
52
52
  "use strict";
53
- VERSION = true ? "0.8.23" : "0.0.0-dev";
53
+ VERSION = true ? "0.8.25" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -73411,7 +73411,22 @@ async function downloadWithRetry(url, localPath, timeoutMs, signal, onTransientR
73411
73411
  return await runDownloadAttempt(url, localPath, timeoutMs, attempt + 1, options, signal);
73412
73412
  } catch (error) {
73413
73413
  const classified = classifyDownloadFailure(error);
73414
- if (!classified.locallyRetryable || attempt >= maxTransientRetries) throw classified;
73414
+ if (!classified.locallyRetryable || attempt >= maxTransientRetries) {
73415
+ const identity2 = safeDownloadUrlIdentity(url);
73416
+ throw new UrlDownloadError(
73417
+ classified.kind,
73418
+ classified.retryable,
73419
+ classified.message,
73420
+ classified.status,
73421
+ {
73422
+ ...classified.telemetry,
73423
+ urlFingerprint: identity2.urlFingerprint,
73424
+ initialHost: identity2.host,
73425
+ attempt: attempt + 1
73426
+ },
73427
+ classified.locallyRetryable
73428
+ );
73429
+ }
73415
73430
  if (classified.retryable) onTransientRetry?.(classified);
73416
73431
  const identity = safeDownloadUrlIdentity(url);
73417
73432
  emitDownloadTelemetry(options, {
@@ -87249,6 +87264,37 @@ function extractionFrameCountForDuration(durationSeconds, fps, isVFR) {
87249
87264
  function sdrToHdrTransformKey(transfer) {
87250
87265
  return `sdr2hdr-${transfer}`;
87251
87266
  }
87267
+ function safeVideoExtractionSourceIdentity(source) {
87268
+ if (!isHttpUrl(source)) return null;
87269
+ const identity = safeDownloadUrlIdentity(source);
87270
+ return {
87271
+ sourceFingerprint: `sha256:${identity.urlFingerprint}`,
87272
+ host: identity.host ?? "other"
87273
+ };
87274
+ }
87275
+ function downloadStatusClass(error) {
87276
+ const status = error.status ?? error.telemetry?.status;
87277
+ if (typeof status === "number" && status >= 400 && status < 500) return "http_4xx";
87278
+ if (typeof status === "number" && status >= 500 && status < 600) return "http_5xx";
87279
+ if (error.kind === "timeout") return "timeout";
87280
+ if (error.kind === "network") return "network";
87281
+ return "other";
87282
+ }
87283
+ function downloadFailureGroup(source, error) {
87284
+ const sourceIdentity = safeVideoExtractionSourceIdentity(source);
87285
+ const failureHost = error.telemetry?.finalHost ?? error.telemetry?.initialHost;
87286
+ const attempt = error.telemetry?.attempt;
87287
+ return {
87288
+ ...sourceIdentity ? { sourceFingerprint: sourceIdentity.sourceFingerprint } : {},
87289
+ ...failureHost ?? sourceIdentity?.host ? { host: failureHost ?? sourceIdentity?.host } : {},
87290
+ statusClass: downloadStatusClass(error),
87291
+ retry: {
87292
+ phase: "download",
87293
+ used: typeof attempt === "number" && attempt >= 2 ? 1 : 0,
87294
+ budget: 1
87295
+ }
87296
+ };
87297
+ }
87252
87298
  function isVideoSourceExtractionError(error) {
87253
87299
  return typeof error === "object" && error !== null && "hyperframesVideoSourceExtractionError" in error && error.hyperframesVideoSourceExtractionError === true;
87254
87300
  }
@@ -88040,6 +88086,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
88040
88086
  videoId: video.id,
88041
88087
  kind: classified.kind,
88042
88088
  retryable: classified.retryable,
88089
+ ...err instanceof UrlDownloadError ? { group: downloadFailureGroup(video.src, err) } : {},
88043
88090
  error: classified.diagnostic
88044
88091
  });
88045
88092
  }
@@ -93137,6 +93184,7 @@ __export(src_exports, {
93137
93184
  runFfmpeg: () => runFfmpeg,
93138
93185
  runVideoExtractionWithRetry: () => runVideoExtractionWithRetry,
93139
93186
  safeDownloadUrlIdentity: () => safeDownloadUrlIdentity,
93187
+ safeVideoExtractionSourceIdentity: () => safeVideoExtractionSourceIdentity,
93140
93188
  sampleRgb48le: () => sampleRgb48le,
93141
93189
  scaleProtocolTimeoutForComposition: () => scaleProtocolTimeoutForComposition,
93142
93190
  selectVerifySampleIndicesForTask: () => selectVerifySampleIndicesForTask,
@@ -110027,6 +110075,7 @@ function collectProjectSignatureFiles(projectDir, dir, files) {
110027
110075
  files.push({
110028
110076
  file,
110029
110077
  mtimeMs: stat3.mtimeMs,
110078
+ ctimeMs: stat3.ctimeMs,
110030
110079
  size: stat3.size,
110031
110080
  textContentEligible: isTextContentEligible(file, stat3.size)
110032
110081
  });
@@ -110048,6 +110097,7 @@ function collectProjectSignatureManifestFiles(projectDir, files) {
110048
110097
  files.push({
110049
110098
  file,
110050
110099
  mtimeMs: stat3.mtimeMs,
110100
+ ctimeMs: stat3.ctimeMs,
110051
110101
  size: stat3.size,
110052
110102
  textContentEligible: isTextContentEligible(file, stat3.size)
110053
110103
  });
@@ -110063,6 +110113,8 @@ function createProjectFingerprint(projectDir, files) {
110063
110113
  hash2.update("\0");
110064
110114
  hash2.update(String(entry.mtimeMs));
110065
110115
  hash2.update("\0");
110116
+ hash2.update(String(entry.ctimeMs));
110117
+ hash2.update("\0");
110066
110118
  hash2.update(entry.textContentEligible ? "text" : "binary");
110067
110119
  hash2.update("\0");
110068
110120
  }
@@ -111075,6 +111127,9 @@ function validateGsapMutationRequest(c3, body) {
111075
111127
  if (!body || typeof body !== "object" || !("type" in body) || !body.type) {
111076
111128
  return c3.json({ error: "mutation type required" }, 400);
111077
111129
  }
111130
+ if (containsRawGsapExpression(body)) {
111131
+ return c3.json({ error: "raw JavaScript expressions are not accepted" }, 400);
111132
+ }
111078
111133
  const unsafeFields = findUnsafeMutationValues(body);
111079
111134
  if (unsafeFields.length > 0) return rejectUnsafeMutationValues(c3, unsafeFields);
111080
111135
  if (body.type === "shift-positions-batch" && (!("shifts" in body) || !Array.isArray(body.shifts))) {
@@ -111082,6 +111137,12 @@ function validateGsapMutationRequest(c3, body) {
111082
111137
  }
111083
111138
  return null;
111084
111139
  }
111140
+ function containsRawGsapExpression(value) {
111141
+ if (typeof value === "string") return value.startsWith("__raw:");
111142
+ if (Array.isArray(value)) return value.some(containsRawGsapExpression);
111143
+ if (!value || typeof value !== "object") return false;
111144
+ return Object.values(value).some(containsRawGsapExpression);
111145
+ }
111085
111146
  async function prepareGsapMutationScript(c3, res, firstMutation) {
111086
111147
  const beforeHtml = readFileSync52(res.absPath, "utf-8");
111087
111148
  let html = beforeHtml;
@@ -112380,27 +112441,32 @@ function registerFileRoutes(api, adapter2) {
112380
112441
  parsed.body.operations
112381
112442
  );
112382
112443
  if (patched === originalContent) {
112444
+ const version22 = fileContentVersion(originalContent);
112445
+ c3.header("ETag", version22);
112383
112446
  return c3.json({
112384
112447
  ok: true,
112385
112448
  changed: false,
112386
112449
  matched,
112387
112450
  content: originalContent,
112388
- path: ctx.filePath
112451
+ path: ctx.filePath,
112452
+ version: version22
112389
112453
  });
112390
112454
  }
112391
- const { backupPath } = writeMutationResult(
112455
+ const { backupPath, version: version2 } = writeMutationResult(
112392
112456
  c3,
112393
112457
  ctx.project.dir,
112394
112458
  ctx.filePath,
112395
112459
  ctx.absPath,
112396
112460
  patched
112397
112461
  );
112462
+ c3.header("ETag", version2);
112398
112463
  return c3.json({
112399
112464
  ok: true,
112400
112465
  changed: true,
112401
112466
  matched,
112402
112467
  content: patched,
112403
112468
  path: ctx.filePath,
112469
+ version: version2,
112404
112470
  backupPath
112405
112471
  });
112406
112472
  });
@@ -113605,8 +113671,9 @@ function registerThumbnailRoutes(api, adapter2) {
113605
113671
  if (!adapter2.generateThumbnail) {
113606
113672
  return c3.json({ error: "Thumbnails not available" }, 501);
113607
113673
  }
113608
- const project = await adapter2.resolveProject(c3.req.param("id"));
113609
- if (!project) return c3.json({ error: "not found" }, 404);
113674
+ const resolved2 = await resolveProjectAndSignature(adapter2, c3.req.param("id"));
113675
+ if (!resolved2) return c3.json({ error: "not found" }, 404);
113676
+ const { project, signature: projectSignature } = resolved2;
113610
113677
  let compPath = decodeURIComponent(
113611
113678
  c3.req.path.replace(`/projects/${project.id}/thumbnail/`, "").split("?")[0] ?? ""
113612
113679
  );
@@ -113659,6 +113726,7 @@ function registerThumbnailRoutes(api, adapter2) {
113659
113726
  const cacheDir = join112(project.dir, ".thumbnails");
113660
113727
  const selectorKey = selector ? `_${selector.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 80)}_${selectorIndex ?? 0}` : "";
113661
113728
  const urlVersionKey = urlVersion ? `_${urlVersion.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32)}` : "";
113729
+ const projectSignatureKey = `_${createHash42("sha1").update(projectSignature).digest("hex").slice(0, 16)}`;
113662
113730
  const outputScale = outputMode === "source" ? 1 : outputMode === "storyboard" ? Math.min(
113663
113731
  1,
113664
113732
  STORYBOARD_MAX_OUTPUT_DIMENSION / compW,
@@ -113666,7 +113734,7 @@ function registerThumbnailRoutes(api, adapter2) {
113666
113734
  ) : Math.min(1, THUMBNAIL_MAX_OUTPUT_WIDTH / compW, THUMBNAIL_MAX_OUTPUT_HEIGHT / compH);
113667
113735
  const outputWidth = Math.max(1, Math.round(compW * outputScale));
113668
113736
  const outputHeight = Math.max(1, Math.round(compH * outputScale));
113669
- const cacheKey = `${THUMBNAIL_CACHE_VERSION}${urlVersionKey}${manualEditsKey}${motionKey}${sourceKey}_${format}_${outputMode}_${compPath.replace(/\//g, "_")}_${compW}x${compH}_${outputWidth}x${outputHeight}_${sourceMtime}_${seekTime.toFixed(2)}${selectorKey}.${format === "png" ? "png" : "jpg"}`;
113737
+ const cacheKey = `${THUMBNAIL_CACHE_VERSION}${urlVersionKey}${projectSignatureKey}${manualEditsKey}${motionKey}${sourceKey}_${format}_${outputMode}_${compPath.replace(/\//g, "_")}_${compW}x${compH}_${outputWidth}x${outputHeight}_${sourceMtime}_${seekTime.toFixed(2)}${selectorKey}.${format === "png" ? "png" : "jpg"}`;
113670
113738
  const cachePath2 = join112(cacheDir, cacheKey);
113671
113739
  if (!prunedCacheDirs.has(cacheDir)) {
113672
113740
  prunedCacheDirs.add(cacheDir);
@@ -113700,6 +113768,11 @@ function registerThumbnailRoutes(api, adapter2) {
113700
113768
  signal
113701
113769
  });
113702
113770
  if (!generated) return null;
113771
+ const afterGeneration = await resolveProjectAndSignature(adapter2, project.id);
113772
+ const freshSignature = createProjectSignature(project.dir);
113773
+ if (!afterGeneration || afterGeneration.project.dir !== project.dir || afterGeneration.signature !== projectSignature || freshSignature !== projectSignature) {
113774
+ return generated;
113775
+ }
113703
113776
  if (!existsSync82(cacheDir)) mkdirSync52(cacheDir, { recursive: true });
113704
113777
  writeThumbnailAtomically(cachePath2, generated);
113705
113778
  return generated;
@@ -125740,12 +125813,88 @@ var init_planValidation = __esm({
125740
125813
  }
125741
125814
  });
125742
125815
 
125816
+ // ../producer/src/services/render/extractionFailureMetadata.ts
125817
+ function stringOrEmpty(value) {
125818
+ return value === void 0 ? "" : value;
125819
+ }
125820
+ function retrySortTuple(retry) {
125821
+ return retry === void 0 ? ["", -1, -1] : [retry.phase, retry.used, retry.budget];
125822
+ }
125823
+ function extractionFailureGroupSortTuple(group) {
125824
+ const retry = retrySortTuple(group.retry);
125825
+ return [
125826
+ group.kind,
125827
+ stringOrEmpty(group.sourceFingerprint),
125828
+ stringOrEmpty(group.host),
125829
+ stringOrEmpty(group.statusClass),
125830
+ retry[0],
125831
+ retry[1],
125832
+ retry[2]
125833
+ ];
125834
+ }
125835
+ function compareExtractionFailureGroups(left, right) {
125836
+ const leftTuple = extractionFailureGroupSortTuple(left);
125837
+ const rightTuple = extractionFailureGroupSortTuple(right);
125838
+ for (let index = 0; index < leftTuple.length; index += 1) {
125839
+ const leftValue = leftTuple[index];
125840
+ const rightValue = rightTuple[index];
125841
+ if (leftValue < rightValue) return -1;
125842
+ if (leftValue > rightValue) return 1;
125843
+ }
125844
+ return 0;
125845
+ }
125846
+ function extractionFailureGroupIdentityKey(group) {
125847
+ return JSON.stringify(extractionFailureGroupSortTuple(group));
125848
+ }
125849
+ var init_extractionFailureMetadata = __esm({
125850
+ "../producer/src/services/render/extractionFailureMetadata.ts"() {
125851
+ "use strict";
125852
+ }
125853
+ });
125854
+
125743
125855
  // ../producer/src/services/render/stages/extractVideosStage.ts
125744
125856
  import { existsSync as existsSync51 } from "fs";
125745
125857
  import { join as join50 } from "path";
125746
125858
  function shouldCopyExtractedFrames(platform11) {
125747
125859
  return platform11 === "win32";
125748
125860
  }
125861
+ function buildExtractionFailureMetadata(inputs) {
125862
+ const kindCounts = /* @__PURE__ */ new Map();
125863
+ const grouped = /* @__PURE__ */ new Map();
125864
+ for (const input2 of inputs) {
125865
+ kindCounts.set(input2.kind, (kindCounts.get(input2.kind) ?? 0) + input2.affectedElementCount);
125866
+ const key2 = extractionFailureGroupIdentityKey(input2);
125867
+ const existing = grouped.get(key2);
125868
+ if (existing) {
125869
+ existing.affectedElementCount += input2.affectedElementCount;
125870
+ } else {
125871
+ grouped.set(key2, { ...input2 });
125872
+ }
125873
+ }
125874
+ const allGroups = [...grouped.values()].sort(compareExtractionFailureGroups);
125875
+ return {
125876
+ schemaVersion: 1,
125877
+ kindCounts: [...kindCounts].map(([kind, affectedElementCount]) => ({
125878
+ kind,
125879
+ affectedElementCount
125880
+ })).sort((a, b2) => a.kind < b2.kind ? -1 : a.kind > b2.kind ? 1 : 0),
125881
+ groups: allGroups.slice(0, MAX_EXTRACTION_FAILURE_GROUPS),
125882
+ omittedGroupCount: Math.max(0, allGroups.length - MAX_EXTRACTION_FAILURE_GROUPS)
125883
+ };
125884
+ }
125885
+ function extractionFailureMetadataFromResult(result) {
125886
+ return buildExtractionFailureMetadata(
125887
+ result.errors.map((failure) => ({
125888
+ kind: failure.kind ?? "internal",
125889
+ affectedElementCount: 1,
125890
+ ...failure.group
125891
+ }))
125892
+ );
125893
+ }
125894
+ function safeVideoExtractionSourceLogMetadata(source) {
125895
+ const identity = safeVideoExtractionSourceIdentity(source);
125896
+ return identity ? { sourceType: "remote", ...identity } : { sourceType: "local" };
125897
+ }
125749
125898
  function resolveVideoExtractionPolicy(env = process.env) {
125750
125899
  const rawMode = env.HF_VIDEO_EXTRACTION_FAILURE_MODE?.trim().toLowerCase();
125751
125900
  const failureMode = rawMode === "observe" || rawMode === "off" ? rawMode : "enforce";
@@ -125779,7 +125928,8 @@ function buildVideoExtractionStageError(result) {
125779
125928
  return new VideoExtractionStageError(
125780
125929
  retryable ? "VIDEO_EXTRACTION_FAILED" : "VIDEO_SOURCE_UNRENDERABLE",
125781
125930
  retryable,
125782
- failures
125931
+ failures,
125932
+ extractionFailureMetadataFromResult(result)
125783
125933
  );
125784
125934
  }
125785
125935
  function buildHdrProbeStageError(failures) {
@@ -125794,7 +125944,13 @@ function buildHdrProbeStageError(failures) {
125794
125944
  return new VideoExtractionStageError(
125795
125945
  retryable ? "VIDEO_EXTRACTION_FAILED" : "VIDEO_SOURCE_UNRENDERABLE",
125796
125946
  retryable,
125797
- summary
125947
+ summary,
125948
+ buildExtractionFailureMetadata(
125949
+ failures.map((failure) => ({
125950
+ kind: failure.kind,
125951
+ affectedElementCount: 1
125952
+ }))
125953
+ )
125798
125954
  );
125799
125955
  }
125800
125956
  function isHdrProbeFailure(failure) {
@@ -125921,7 +126077,10 @@ async function runExtractVideosStage(input2) {
125921
126077
  const totalVideos = composition.videos.length;
125922
126078
  for (let i2 = 0; i2 < totalVideos; i2++) {
125923
126079
  const v2 = composition.videos[i2];
125924
- log2?.info(`Extracting frames from video ${i2 + 1}/${totalVideos}: ${v2.src}`);
126080
+ log2?.info(
126081
+ `Extracting frames from video ${i2 + 1}/${totalVideos}`,
126082
+ safeVideoExtractionSourceLogMetadata(v2.src)
126083
+ );
125925
126084
  }
125926
126085
  extractionResult = await extractAllVideoFrames(
125927
126086
  composition.videos,
@@ -125993,7 +126152,7 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
125993
126152
  existingAudioSrcs.add(video.src);
125994
126153
  }
125995
126154
  }
125996
- var VideoExtractionStageError;
126155
+ var MAX_EXTRACTION_FAILURE_GROUPS, VideoExtractionStageError;
125997
126156
  var init_extractVideosStage = __esm({
125998
126157
  "../producer/src/services/render/stages/extractVideosStage.ts"() {
125999
126158
  "use strict";
@@ -126001,19 +126160,35 @@ var init_extractVideosStage = __esm({
126001
126160
  init_renderOrchestrator();
126002
126161
  init_shared();
126003
126162
  init_encoderInterruption();
126163
+ init_extractionFailureMetadata();
126164
+ MAX_EXTRACTION_FAILURE_GROUPS = 8;
126004
126165
  VideoExtractionStageError = class extends Error {
126005
- constructor(code, retryable, failures) {
126166
+ constructor(code, retryable, failures, extractionFailure = buildExtractionFailureMetadata(
126167
+ failures.map((failure) => ({
126168
+ kind: failure.kind,
126169
+ affectedElementCount: failure.count
126170
+ }))
126171
+ )) {
126006
126172
  const total = failures.reduce((sum, failure) => sum + failure.count, 0);
126007
126173
  const breakdown = failures.map((failure) => `${failure.kind}=${failure.count}`).join(",");
126008
126174
  super(`Video extraction failed for ${total} source(s) [${code}; ${breakdown}]`);
126009
126175
  this.code = code;
126010
126176
  this.retryable = retryable;
126011
126177
  this.failures = failures;
126178
+ this.extractionFailure = extractionFailure;
126012
126179
  this.name = "VideoExtractionStageError";
126180
+ this.publicMetadata = { extractionFailure };
126013
126181
  }
126014
126182
  code;
126015
126183
  retryable;
126016
126184
  failures;
126185
+ extractionFailure;
126186
+ /**
126187
+ * Producer servers may expose this explicitly public, JSON-compatible data
126188
+ * without knowing its schema. Boundary adapters remain responsible for
126189
+ * validating and applying policy to it.
126190
+ */
126191
+ publicMetadata;
126017
126192
  };
126018
126193
  }
126019
126194
  });
@@ -132321,10 +132496,12 @@ function extractSafeRenderErrorMetadata(error) {
132321
132496
  if (!errorCode || typeof error !== "object" || error === null) return void 0;
132322
132497
  const owner = "owner" in error ? error.owner : void 0;
132323
132498
  const retryable = "retryable" in error ? error.retryable : void 0;
132499
+ const publicMetadata = "publicMetadata" in error ? error.publicMetadata : void 0;
132324
132500
  return {
132325
132501
  errorCode,
132326
132502
  errorOwner: owner === "user" || owner === "system" ? owner : void 0,
132327
- retryable: typeof retryable === "boolean" ? retryable : void 0
132503
+ retryable: typeof retryable === "boolean" ? retryable : void 0,
132504
+ ...isPlainObject4(publicMetadata) ? { errorMetadata: publicMetadata } : {}
132328
132505
  };
132329
132506
  }
132330
132507
  function parseServerFps(value) {
@@ -132646,6 +132823,7 @@ async function writeRenderStreamFailure(input2) {
132646
132823
  errorCode: safeError?.errorCode,
132647
132824
  errorOwner: safeError?.errorOwner,
132648
132825
  retryable: safeError?.retryable,
132826
+ errorMetadata: safeError?.errorMetadata,
132649
132827
  stage: job.currentStage,
132650
132828
  elapsedMs,
132651
132829
  errorDetails: job.errorDetails ?? null,
@@ -132771,6 +132949,7 @@ function createRenderHandlers(options = {}) {
132771
132949
  errorCode: safeError?.errorCode,
132772
132950
  errorOwner: safeError?.errorOwner,
132773
132951
  retryable: safeError?.retryable,
132952
+ errorMetadata: safeError?.errorMetadata,
132774
132953
  stage: job.currentStage,
132775
132954
  durationMs,
132776
132955
  errorDetails: job.errorDetails ?? null
@@ -62903,7 +62903,22 @@ async function downloadWithRetry(url, localPath, timeoutMs, signal, onTransientR
62903
62903
  return await runDownloadAttempt(url, localPath, timeoutMs, attempt + 1, options, signal);
62904
62904
  } catch (error) {
62905
62905
  const classified = classifyDownloadFailure(error);
62906
- if (!classified.locallyRetryable || attempt >= maxTransientRetries) throw classified;
62906
+ if (!classified.locallyRetryable || attempt >= maxTransientRetries) {
62907
+ const identity2 = safeDownloadUrlIdentity(url);
62908
+ throw new UrlDownloadError(
62909
+ classified.kind,
62910
+ classified.retryable,
62911
+ classified.message,
62912
+ classified.status,
62913
+ {
62914
+ ...classified.telemetry,
62915
+ urlFingerprint: identity2.urlFingerprint,
62916
+ initialHost: identity2.host,
62917
+ attempt: attempt + 1
62918
+ },
62919
+ classified.locallyRetryable
62920
+ );
62921
+ }
62907
62922
  if (classified.retryable) onTransientRetry?.(classified);
62908
62923
  const identity = safeDownloadUrlIdentity(url);
62909
62924
  emitDownloadTelemetry(options, {
@@ -74981,6 +74996,37 @@ function extractionFrameCountForDuration(durationSeconds, fps, isVFR) {
74981
74996
  function sdrToHdrTransformKey(transfer) {
74982
74997
  return `sdr2hdr-${transfer}`;
74983
74998
  }
74999
+ function safeVideoExtractionSourceIdentity(source) {
75000
+ if (!isHttpUrl(source)) return null;
75001
+ const identity = safeDownloadUrlIdentity(source);
75002
+ return {
75003
+ sourceFingerprint: `sha256:${identity.urlFingerprint}`,
75004
+ host: identity.host ?? "other"
75005
+ };
75006
+ }
75007
+ function downloadStatusClass(error) {
75008
+ const status = error.status ?? error.telemetry?.status;
75009
+ if (typeof status === "number" && status >= 400 && status < 500) return "http_4xx";
75010
+ if (typeof status === "number" && status >= 500 && status < 600) return "http_5xx";
75011
+ if (error.kind === "timeout") return "timeout";
75012
+ if (error.kind === "network") return "network";
75013
+ return "other";
75014
+ }
75015
+ function downloadFailureGroup(source, error) {
75016
+ const sourceIdentity = safeVideoExtractionSourceIdentity(source);
75017
+ const failureHost = error.telemetry?.finalHost ?? error.telemetry?.initialHost;
75018
+ const attempt = error.telemetry?.attempt;
75019
+ return {
75020
+ ...sourceIdentity ? { sourceFingerprint: sourceIdentity.sourceFingerprint } : {},
75021
+ ...failureHost ?? sourceIdentity?.host ? { host: failureHost ?? sourceIdentity?.host } : {},
75022
+ statusClass: downloadStatusClass(error),
75023
+ retry: {
75024
+ phase: "download",
75025
+ used: typeof attempt === "number" && attempt >= 2 ? 1 : 0,
75026
+ budget: 1
75027
+ }
75028
+ };
75029
+ }
74984
75030
  function isVideoSourceExtractionError(error) {
74985
75031
  return typeof error === "object" && error !== null && "hyperframesVideoSourceExtractionError" in error && error.hyperframesVideoSourceExtractionError === true;
74986
75032
  }
@@ -75772,6 +75818,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
75772
75818
  videoId: video.id,
75773
75819
  kind: classified.kind,
75774
75820
  retryable: classified.retryable,
75821
+ ...err instanceof UrlDownloadError ? { group: downloadFailureGroup(video.src, err) } : {},
75775
75822
  error: classified.diagnostic
75776
75823
  });
75777
75824
  }
@@ -80869,6 +80916,7 @@ __export(src_exports, {
80869
80916
  runFfmpeg: () => runFfmpeg,
80870
80917
  runVideoExtractionWithRetry: () => runVideoExtractionWithRetry,
80871
80918
  safeDownloadUrlIdentity: () => safeDownloadUrlIdentity,
80919
+ safeVideoExtractionSourceIdentity: () => safeVideoExtractionSourceIdentity,
80872
80920
  sampleRgb48le: () => sampleRgb48le,
80873
80921
  scaleProtocolTimeoutForComposition: () => scaleProtocolTimeoutForComposition,
80874
80922
  selectVerifySampleIndicesForTask: () => selectVerifySampleIndicesForTask,
@@ -90808,9 +90856,83 @@ function validateRenderDuration(input) {
90808
90856
  init_src();
90809
90857
  import { existsSync as existsSync26 } from "fs";
90810
90858
  import { join as join30 } from "path";
90859
+
90860
+ // ../producer/src/services/render/extractionFailureMetadata.ts
90861
+ function stringOrEmpty(value) {
90862
+ return value === void 0 ? "" : value;
90863
+ }
90864
+ function retrySortTuple(retry) {
90865
+ return retry === void 0 ? ["", -1, -1] : [retry.phase, retry.used, retry.budget];
90866
+ }
90867
+ function extractionFailureGroupSortTuple(group) {
90868
+ const retry = retrySortTuple(group.retry);
90869
+ return [
90870
+ group.kind,
90871
+ stringOrEmpty(group.sourceFingerprint),
90872
+ stringOrEmpty(group.host),
90873
+ stringOrEmpty(group.statusClass),
90874
+ retry[0],
90875
+ retry[1],
90876
+ retry[2]
90877
+ ];
90878
+ }
90879
+ function compareExtractionFailureGroups(left, right) {
90880
+ const leftTuple = extractionFailureGroupSortTuple(left);
90881
+ const rightTuple = extractionFailureGroupSortTuple(right);
90882
+ for (let index = 0; index < leftTuple.length; index += 1) {
90883
+ const leftValue = leftTuple[index];
90884
+ const rightValue = rightTuple[index];
90885
+ if (leftValue < rightValue) return -1;
90886
+ if (leftValue > rightValue) return 1;
90887
+ }
90888
+ return 0;
90889
+ }
90890
+ function extractionFailureGroupIdentityKey(group) {
90891
+ return JSON.stringify(extractionFailureGroupSortTuple(group));
90892
+ }
90893
+
90894
+ // ../producer/src/services/render/stages/extractVideosStage.ts
90811
90895
  function shouldCopyExtractedFrames(platform3) {
90812
90896
  return platform3 === "win32";
90813
90897
  }
90898
+ var MAX_EXTRACTION_FAILURE_GROUPS = 8;
90899
+ function buildExtractionFailureMetadata(inputs) {
90900
+ const kindCounts = /* @__PURE__ */ new Map();
90901
+ const grouped = /* @__PURE__ */ new Map();
90902
+ for (const input of inputs) {
90903
+ kindCounts.set(input.kind, (kindCounts.get(input.kind) ?? 0) + input.affectedElementCount);
90904
+ const key2 = extractionFailureGroupIdentityKey(input);
90905
+ const existing = grouped.get(key2);
90906
+ if (existing) {
90907
+ existing.affectedElementCount += input.affectedElementCount;
90908
+ } else {
90909
+ grouped.set(key2, { ...input });
90910
+ }
90911
+ }
90912
+ const allGroups = [...grouped.values()].sort(compareExtractionFailureGroups);
90913
+ return {
90914
+ schemaVersion: 1,
90915
+ kindCounts: [...kindCounts].map(([kind, affectedElementCount]) => ({
90916
+ kind,
90917
+ affectedElementCount
90918
+ })).sort((a, b) => a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0),
90919
+ groups: allGroups.slice(0, MAX_EXTRACTION_FAILURE_GROUPS),
90920
+ omittedGroupCount: Math.max(0, allGroups.length - MAX_EXTRACTION_FAILURE_GROUPS)
90921
+ };
90922
+ }
90923
+ function extractionFailureMetadataFromResult(result) {
90924
+ return buildExtractionFailureMetadata(
90925
+ result.errors.map((failure) => ({
90926
+ kind: failure.kind ?? "internal",
90927
+ affectedElementCount: 1,
90928
+ ...failure.group
90929
+ }))
90930
+ );
90931
+ }
90932
+ function safeVideoExtractionSourceLogMetadata(source) {
90933
+ const identity = safeVideoExtractionSourceIdentity(source);
90934
+ return identity ? { sourceType: "remote", ...identity } : { sourceType: "local" };
90935
+ }
90814
90936
  function resolveVideoExtractionPolicy(env = process.env) {
90815
90937
  const rawMode = env.HF_VIDEO_EXTRACTION_FAILURE_MODE?.trim().toLowerCase();
90816
90938
  const failureMode = rawMode === "observe" || rawMode === "off" ? rawMode : "enforce";
@@ -90818,18 +90940,32 @@ function resolveVideoExtractionPolicy(env = process.env) {
90818
90940
  return { failureMode, maxTransientRetries };
90819
90941
  }
90820
90942
  var VideoExtractionStageError = class extends Error {
90821
- constructor(code, retryable, failures) {
90943
+ constructor(code, retryable, failures, extractionFailure = buildExtractionFailureMetadata(
90944
+ failures.map((failure) => ({
90945
+ kind: failure.kind,
90946
+ affectedElementCount: failure.count
90947
+ }))
90948
+ )) {
90822
90949
  const total = failures.reduce((sum, failure) => sum + failure.count, 0);
90823
90950
  const breakdown = failures.map((failure) => `${failure.kind}=${failure.count}`).join(",");
90824
90951
  super(`Video extraction failed for ${total} source(s) [${code}; ${breakdown}]`);
90825
90952
  this.code = code;
90826
90953
  this.retryable = retryable;
90827
90954
  this.failures = failures;
90955
+ this.extractionFailure = extractionFailure;
90828
90956
  this.name = "VideoExtractionStageError";
90957
+ this.publicMetadata = { extractionFailure };
90829
90958
  }
90830
90959
  code;
90831
90960
  retryable;
90832
90961
  failures;
90962
+ extractionFailure;
90963
+ /**
90964
+ * Producer servers may expose this explicitly public, JSON-compatible data
90965
+ * without knowing its schema. Boundary adapters remain responsible for
90966
+ * validating and applying policy to it.
90967
+ */
90968
+ publicMetadata;
90833
90969
  };
90834
90970
  function throwIfEncoderInterrupted(result) {
90835
90971
  const interrupted = result.errors.find((failure) => failure.kind === "external_interruption");
@@ -90853,7 +90989,8 @@ function buildVideoExtractionStageError(result) {
90853
90989
  return new VideoExtractionStageError(
90854
90990
  retryable ? "VIDEO_EXTRACTION_FAILED" : "VIDEO_SOURCE_UNRENDERABLE",
90855
90991
  retryable,
90856
- failures
90992
+ failures,
90993
+ extractionFailureMetadataFromResult(result)
90857
90994
  );
90858
90995
  }
90859
90996
  function buildHdrProbeStageError(failures) {
@@ -90868,7 +91005,13 @@ function buildHdrProbeStageError(failures) {
90868
91005
  return new VideoExtractionStageError(
90869
91006
  retryable ? "VIDEO_EXTRACTION_FAILED" : "VIDEO_SOURCE_UNRENDERABLE",
90870
91007
  retryable,
90871
- summary
91008
+ summary,
91009
+ buildExtractionFailureMetadata(
91010
+ failures.map((failure) => ({
91011
+ kind: failure.kind,
91012
+ affectedElementCount: 1
91013
+ }))
91014
+ )
90872
91015
  );
90873
91016
  }
90874
91017
  function isHdrProbeFailure(failure) {
@@ -90995,7 +91138,10 @@ async function runExtractVideosStage(input) {
90995
91138
  const totalVideos = composition.videos.length;
90996
91139
  for (let i = 0; i < totalVideos; i++) {
90997
91140
  const v = composition.videos[i];
90998
- log?.info(`Extracting frames from video ${i + 1}/${totalVideos}: ${v.src}`);
91141
+ log?.info(
91142
+ `Extracting frames from video ${i + 1}/${totalVideos}`,
91143
+ safeVideoExtractionSourceLogMetadata(v.src)
91144
+ );
90999
91145
  }
91000
91146
  extractionResult = await extractAllVideoFrames(
91001
91147
  composition.videos,
@@ -96941,10 +97087,12 @@ function extractSafeRenderErrorMetadata(error) {
96941
97087
  if (!errorCode || typeof error !== "object" || error === null) return void 0;
96942
97088
  const owner = "owner" in error ? error.owner : void 0;
96943
97089
  const retryable = "retryable" in error ? error.retryable : void 0;
97090
+ const publicMetadata = "publicMetadata" in error ? error.publicMetadata : void 0;
96944
97091
  return {
96945
97092
  errorCode,
96946
97093
  errorOwner: owner === "user" || owner === "system" ? owner : void 0,
96947
- retryable: typeof retryable === "boolean" ? retryable : void 0
97094
+ retryable: typeof retryable === "boolean" ? retryable : void 0,
97095
+ ...isPlainObject3(publicMetadata) ? { errorMetadata: publicMetadata } : {}
96948
97096
  };
96949
97097
  }
96950
97098
  function parseServerFps(value) {
@@ -97266,6 +97414,7 @@ async function writeRenderStreamFailure(input) {
97266
97414
  errorCode: safeError?.errorCode,
97267
97415
  errorOwner: safeError?.errorOwner,
97268
97416
  retryable: safeError?.retryable,
97417
+ errorMetadata: safeError?.errorMetadata,
97269
97418
  stage: job.currentStage,
97270
97419
  elapsedMs,
97271
97420
  errorDetails: job.errorDetails ?? null,
@@ -97391,6 +97540,7 @@ function createRenderHandlers(options = {}) {
97391
97540
  errorCode: safeError?.errorCode,
97392
97541
  errorOwner: safeError?.errorOwner,
97393
97542
  retryable: safeError?.retryable,
97543
+ errorMetadata: safeError?.errorMetadata,
97394
97544
  stage: job.currentStage,
97395
97545
  durationMs,
97396
97546
  errorDetails: job.errorDetails ?? null
@@ -97748,8 +97898,8 @@ async function runFontLocalize(io, localize) {
97748
97898
  }
97749
97899
 
97750
97900
  // src/version.ts
97751
- var VERSION = true ? "0.8.23" : "0.0.0-dev";
97752
- var PRODUCER_VERSION = true ? "0.8.23" : "0.0.0-dev";
97901
+ var VERSION = true ? "0.8.25" : "0.0.0-dev";
97902
+ var PRODUCER_VERSION = true ? "0.8.25" : "0.0.0-dev";
97753
97903
 
97754
97904
  // src/fontLocalizeCli.ts
97755
97905
  async function readStdin() {