@hyperframes/engine 0.8.158 → 0.8.159

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.d.ts CHANGED
@@ -50,8 +50,9 @@ export { directorySizeBytes, gcExtractionCache } from "./services/extractionCach
50
50
  export { resolveReferencedStart, resolveReferencedDuration, type RefResolverEl, type RefResolverDoc, } from "./services/referenceResolver.js";
51
51
  export { createVideoFrameInjector } from "./services/videoFrameInjector.js";
52
52
  export { MIXED_AUDIO_FILENAME, parseAudioElements, parseMasterFxChain, processCompositionAudio, } from "./services/audioMixer.js";
53
+ export { audioFailureDiagnostics } from "./services/audioFailureDiagnostic.js";
53
54
  export { cloneCaptureWarning, cloneCaptureWarnings } from "./services/captureWarning.js";
54
- export type { AudioElement, AudioFailureReason, AudioFailureStage, AudioProcessingFailure, AudioTrack, AudioVolumeKeyframe, MixResult, } from "./services/audioMixer.types.js";
55
+ export type { AudioElement, AudioFailureReason, AudioFailureStage, AudioProcessingFailure, AudioFailureDiagnostic, AudioSubprocessEvidence, AudioTrack, AudioVolumeKeyframe, MixResult, } from "./services/audioMixer.types.js";
55
56
  export { calculateOptimalWorkers, computeWorkerSizing, selectVerifySampleIndicesForTask, verifyDiskDrawElementSamples, distributeFrames, distributeFramesInterleaved, executeParallelCapture, mergeWorkerFrames, getSystemResources, shouldDisableBrowserPoolForParallelWorker, type WorkerTask, type WorkerResult, type WorkerSizing, type WorkerSizingBound, type ParallelProgress, } from "./services/parallelCoordinator.js";
56
57
  export { createFileServer, type FileServerOptions, type FileServerHandle, } from "./services/fileServer.js";
57
58
  export { quantizeTimeToFrame, MEDIA_VISUAL_STYLE_PROPERTIES } from "@hyperframes/core";
package/dist/index.js CHANGED
@@ -54,6 +54,7 @@ export { directorySizeBytes, gcExtractionCache } from "./services/extractionCach
54
54
  export { resolveReferencedStart, resolveReferencedDuration, } from "./services/referenceResolver.js";
55
55
  export { createVideoFrameInjector } from "./services/videoFrameInjector.js";
56
56
  export { MIXED_AUDIO_FILENAME, parseAudioElements, parseMasterFxChain, processCompositionAudio, } from "./services/audioMixer.js";
57
+ export { audioFailureDiagnostics } from "./services/audioFailureDiagnostic.js";
57
58
  export { cloneCaptureWarning, cloneCaptureWarnings } from "./services/captureWarning.js";
58
59
  // ── Parallel rendering ─────────────────────────────────────────────────────────
59
60
  export { calculateOptimalWorkers, computeWorkerSizing, selectVerifySampleIndicesForTask, verifyDiskDrawElementSamples, distributeFrames, distributeFramesInterleaved, executeParallelCapture, mergeWorkerFrames, getSystemResources, shouldDisableBrowserPoolForParallelWorker, } from "./services/parallelCoordinator.js";
@@ -0,0 +1,4 @@
1
+ import type { AudioFailureDiagnostic, AudioSubprocessEvidence } from "./audioMixer.types.js";
2
+ export declare function audioSubprocessEvidence(value: unknown): AudioSubprocessEvidence;
3
+ /** Re-project at the wire boundary: neither typed callers nor warning details are a privacy boundary. */
4
+ export declare function audioFailureDiagnostics(values: readonly unknown[]): AudioFailureDiagnostic[];
@@ -0,0 +1,110 @@
1
+ const STAGES = new Set([
2
+ "source",
3
+ "download",
4
+ "probe",
5
+ "extract",
6
+ "prepare",
7
+ "mix",
8
+ "silence",
9
+ "cancelled",
10
+ "internal",
11
+ ]);
12
+ const REASONS = new Set([
13
+ "source_not_found",
14
+ "download_failed",
15
+ "probe_failed",
16
+ "invalid_media",
17
+ "ffmpeg_unsupported",
18
+ "ffmpeg_timeout",
19
+ "ffmpeg_unavailable",
20
+ "external_interruption",
21
+ "ffmpeg_failed",
22
+ "cancelled",
23
+ "internal",
24
+ ]);
25
+ const TERMINATIONS = new Set(["exit", "abort", "deadline", "inactivity", "spawn_error"]);
26
+ const SIGNALS = new Set([
27
+ "SIGABRT",
28
+ "SIGBUS",
29
+ "SIGFPE",
30
+ "SIGHUP",
31
+ "SIGILL",
32
+ "SIGINT",
33
+ "SIGKILL",
34
+ "SIGPIPE",
35
+ "SIGQUIT",
36
+ "SIGSEGV",
37
+ "SIGTERM",
38
+ "SIGTRAP",
39
+ "SIGXCPU",
40
+ "SIGXFSZ",
41
+ ]);
42
+ const CODES = new Set([
43
+ "EACCES",
44
+ "EAGAIN",
45
+ "EBUSY",
46
+ "EIO",
47
+ "EISDIR",
48
+ "EMFILE",
49
+ "ENAMETOOLONG",
50
+ "ENFILE",
51
+ "ENOENT",
52
+ "ENOEXEC",
53
+ "ENOMEM",
54
+ "ENOSPC",
55
+ "ENOTDIR",
56
+ "EPERM",
57
+ "ETIMEDOUT",
58
+ ]);
59
+ function isStage(value) {
60
+ return typeof value === "string" && STAGES.has(value);
61
+ }
62
+ function isReason(value) {
63
+ return typeof value === "string" && REASONS.has(value);
64
+ }
65
+ function isTermination(value) {
66
+ return typeof value === "string" && TERMINATIONS.has(value);
67
+ }
68
+ function record(value) {
69
+ return value !== null && typeof value === "object";
70
+ }
71
+ export function audioSubprocessEvidence(value) {
72
+ if (!record(value))
73
+ return {};
74
+ const evidence = {};
75
+ if (typeof value.exitCode === "number" &&
76
+ Number.isInteger(value.exitCode) &&
77
+ value.exitCode >= -2147483648 &&
78
+ value.exitCode <= 4294967295)
79
+ evidence.exitCode = value.exitCode;
80
+ if (typeof value.signal === "string" && SIGNALS.has(value.signal))
81
+ evidence.signal = value.signal;
82
+ if (isTermination(value.terminationReason))
83
+ evidence.terminationReason = value.terminationReason;
84
+ const error = record(value.error) ? value.error : value;
85
+ if (typeof error.code === "string" && CODES.has(error.code))
86
+ evidence.code = error.code;
87
+ return evidence;
88
+ }
89
+ /** Re-project at the wire boundary: neither typed callers nor warning details are a privacy boundary. */
90
+ export function audioFailureDiagnostics(values) {
91
+ const result = [];
92
+ for (const value of values.slice(0, 16)) {
93
+ if (!record(value) ||
94
+ !isStage(value.stage) ||
95
+ !isReason(value.reason) ||
96
+ (value.owner !== "user" && value.owner !== "system") ||
97
+ typeof value.retryable !== "boolean")
98
+ continue;
99
+ result.push({
100
+ stage: value.stage,
101
+ reason: value.reason,
102
+ owner: value.owner,
103
+ retryable: value.retryable,
104
+ ...(record(value.subprocess)
105
+ ? { subprocess: audioSubprocessEvidence(value.subprocess) }
106
+ : {}),
107
+ });
108
+ }
109
+ return result;
110
+ }
@@ -10,6 +10,7 @@ import { clampFadesToDuration, readElementFades } from "@hyperframes/core/audio-
10
10
  import { downloadToTemp, isHttpUrl, UrlDownloadError, writeUrlDownloadTelemetry, } from "../utils/urlDownloader.js";
11
11
  import { DEFAULT_CONFIG } from "../config.js";
12
12
  import { formatFfmpegError, runFfmpeg } from "../utils/runFfmpeg.js";
13
+ import { audioSubprocessEvidence } from "./audioFailureDiagnostic.js";
13
14
  import { unwrapTemplate } from "../utils/htmlTemplate.js";
14
15
  import { resolveMediaElementSrc, resolveProjectRelativeSrc } from "./videoFrameExtractor.js";
15
16
  import { resolveReferencedStart } from "./referenceResolver.js";
@@ -347,6 +348,7 @@ function ffmpegFailure(stage, result, elementId) {
347
348
  owner,
348
349
  retryable,
349
350
  elementId,
351
+ subprocess: audioSubprocessEvidence(result),
350
352
  detail: boundedDetail(result.error?.message
351
353
  ? `${formatFfmpegError(result.exitCode, stderr)}: ${result.error.message}`
352
354
  : formatFfmpegError(result.exitCode, stderr)),
@@ -843,10 +845,10 @@ async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal,
843
845
  degradedAutomation = true;
844
846
  }
845
847
  }
846
- if (signal?.aborted)
847
- return { success: false, error: "Group sub-mix cancelled" };
848
- if (!result.success)
849
- return { success: false, error: formatFfmpegError(result.exitCode, result.stderr) };
848
+ if (signal?.aborted || !result.success) {
849
+ const failure = ffmpegFailure("mix", result);
850
+ return { success: false, error: failure.detail, failure };
851
+ }
850
852
  return { success: true, degradedAutomation };
851
853
  }
852
854
  /**
@@ -1215,10 +1217,7 @@ export async function processCompositionAudio(elements, baseDir, workDir, output
1215
1217
  const subMix = await mixGroupMembers(memberTracks, groupWavPath, totalDuration, effectiveSignal, config);
1216
1218
  if (!subMix.success) {
1217
1219
  failures.push({
1218
- stage: "mix",
1219
- reason: "ffmpeg_failed",
1220
- owner: "system",
1221
- retryable: false,
1220
+ ...subMix.failure,
1222
1221
  elementId: groupId,
1223
1222
  detail: boundedDetail(`Group sub-mix failed for group ${groupId}: ${subMix.error ?? "unknown"}`),
1224
1223
  });
@@ -56,11 +56,19 @@ export interface AudioTrack {
56
56
  }
57
57
  export type AudioFailureStage = "source" | "download" | "probe" | "extract" | "prepare" | "mix" | "silence" | "cancelled" | "internal";
58
58
  export type AudioFailureReason = "source_not_found" | "download_failed" | "probe_failed" | "invalid_media" | "ffmpeg_unsupported" | "ffmpeg_timeout" | "ffmpeg_unavailable" | "external_interruption" | "ffmpeg_failed" | "cancelled" | "internal";
59
+ export interface AudioSubprocessEvidence {
60
+ exitCode?: number;
61
+ signal?: string;
62
+ terminationReason?: "exit" | "abort" | "deadline" | "inactivity" | "spawn_error";
63
+ code?: string;
64
+ }
65
+ export type AudioFailureDiagnostic = Pick<AudioProcessingFailure, "stage" | "reason" | "owner" | "retryable" | "subprocess">;
59
66
  export interface AudioProcessingFailure {
60
67
  stage: AudioFailureStage;
61
68
  reason: AudioFailureReason;
62
69
  owner: "user" | "system";
63
70
  retryable: boolean;
71
+ subprocess?: AudioSubprocessEvidence;
64
72
  elementId?: string;
65
73
  /** Bounded diagnostic text; never includes the authored source URL/path. */
66
74
  detail: string;
@@ -5,6 +5,10 @@ export function cloneCaptureWarning(warning) {
5
5
  details: warning.details
6
6
  ? {
7
7
  ...warning.details,
8
+ audioFailures: warning.details.audioFailures?.map((failure) => ({
9
+ ...failure,
10
+ ...(failure.subprocess ? { subprocess: { ...failure.subprocess } } : {}),
11
+ })),
8
12
  sources: warning.details.sources ? [...warning.details.sources] : undefined,
9
13
  failureReasons: warning.details.failureReasons
10
14
  ? [...warning.details.failureReasons]
package/dist/types.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * The engine's page contract. Any web page that wants to be rendered
5
5
  * as video must expose `window.__hf` implementing the HfProtocol interface.
6
6
  */
7
+ import type { AudioFailureDiagnostic } from "./services/audioMixer.types.js";
7
8
  import type { Fps } from "@hyperframes/core";
8
9
  import type { ChromeMemoryStats } from "./services/chromeMemorySampler.js";
9
10
  import type { MotionBlurOptions } from "./services/motionBlur.js";
@@ -30,6 +31,7 @@ export interface CaptureWarning {
30
31
  sources?: string[];
31
32
  pendingCompositionIds?: string[];
32
33
  timeoutMs?: number;
34
+ audioFailures?: AudioFailureDiagnostic[];
33
35
  failureReasons?: string[];
34
36
  failureStages?: string[];
35
37
  failureOwner?: "user" | "system";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/engine",
3
- "version": "0.8.158",
3
+ "version": "0.8.159",
4
4
  "description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -50,8 +50,8 @@
50
50
  "linkedom": "^0.18.12",
51
51
  "puppeteer": "^25.8.0",
52
52
  "puppeteer-core": "^25.8.0",
53
- "@hyperframes/parsers": "^0.8.158",
54
- "@hyperframes/core": "^0.8.158"
53
+ "@hyperframes/core": "^0.8.159",
54
+ "@hyperframes/parsers": "^0.8.159"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/node": "^25.0.10",