@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 +2 -1
- package/dist/index.js +1 -0
- package/dist/services/audioFailureDiagnostic.d.ts +4 -0
- package/dist/services/audioFailureDiagnostic.js +110 -0
- package/dist/services/audioMixer.js +7 -8
- package/dist/services/audioMixer.types.d.ts +8 -0
- package/dist/services/captureWarning.js +4 -0
- package/dist/types.d.ts +2 -0
- package/package.json +3 -3
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
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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
|
-
|
|
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.
|
|
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/
|
|
54
|
-
"@hyperframes/
|
|
53
|
+
"@hyperframes/core": "^0.8.159",
|
|
54
|
+
"@hyperframes/parsers": "^0.8.159"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/node": "^25.0.10",
|