@hyperframes/engine 0.9.15 → 0.9.17
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 +1 -1
- package/dist/index.js +1 -1
- package/dist/services/captureFailure.js +1 -0
- package/dist/services/frameCapture.d.ts +1 -1
- package/dist/services/frameCapture.js +15 -7
- package/dist/services/videoFrameExtractor.js +23 -8
- package/dist/utils/ffprobe.d.ts +8 -0
- package/dist/utils/ffprobe.js +42 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ export type { PageMotionBlurOff } from "./types.js";
|
|
|
72
72
|
export { assertSwiftShader, readWebGlVendorInfo, SwiftShaderAssertionError, BROWSER_GPU_NOT_SOFTWARE, } from "./utils/assertSwiftShader.js";
|
|
73
73
|
export { isBrowserSafePort, listenOnBrowserSafePort, settleOnBrowserSafePort, } from "./utils/browserSafePort.js";
|
|
74
74
|
export { readWebGlVendorInfoFromCanvas } from "./utils/readWebGlVendorInfoFromCanvas.js";
|
|
75
|
-
export { extractMediaMetadata, extractVideoMetadata, extractFinalVideoFrameTimestamp, extractAudioMetadata, declareContentAddressedUrl, declareProbeFactsUrl, PROBE_FACTS_VERSION, buildProbeFactsText, probeMediaProfile, runFfprobe, analyzeKeyframeIntervals, type VideoMetadata, type AudioMetadata, type MediaProbeProfile, type KeyframeAnalysis, } from "./utils/ffprobe.js";
|
|
75
|
+
export { extractMediaMetadata, displaySize, extractVideoMetadata, extractFinalVideoFrameTimestamp, extractAudioMetadata, declareContentAddressedUrl, declareProbeFactsUrl, PROBE_FACTS_VERSION, buildProbeFactsText, probeMediaProfile, runFfprobe, analyzeKeyframeIntervals, type VideoMetadata, type AudioMetadata, type MediaProbeProfile, type KeyframeAnalysis, } from "./utils/ffprobe.js";
|
|
76
76
|
export { NOT_MEDIA_PAYLOAD, NotMediaPayloadError, assertMediaPayload, fingerprintElementId, isNotMediaPayload, } from "./utils/notMediaPayload.js";
|
|
77
77
|
export { _setDownloadFetchForTests, assertPublicHttpsUrl, downloadToTemp, fetchPublicHttpsText, isHttpUrl, safeDownloadUrlIdentity, writeUrlDownloadTelemetry, type SafeDownloadUrlIdentity, type UrlDownloadOptions, type UrlDownloadTelemetry, type PublicHttpsTextOptions, } from "./utils/urlDownloader.js";
|
|
78
78
|
export { isBlockedNetworkHost } from "./utils/networkPolicy.js";
|
package/dist/index.js
CHANGED
|
@@ -76,7 +76,7 @@ export { frameSequencePath } from "./services/extractedFrameIndex.js";
|
|
|
76
76
|
export { assertSwiftShader, readWebGlVendorInfo, SwiftShaderAssertionError, BROWSER_GPU_NOT_SOFTWARE, } from "./utils/assertSwiftShader.js";
|
|
77
77
|
export { isBrowserSafePort, listenOnBrowserSafePort, settleOnBrowserSafePort, } from "./utils/browserSafePort.js";
|
|
78
78
|
export { readWebGlVendorInfoFromCanvas } from "./utils/readWebGlVendorInfoFromCanvas.js";
|
|
79
|
-
export { extractMediaMetadata, extractVideoMetadata, extractFinalVideoFrameTimestamp, extractAudioMetadata, declareContentAddressedUrl, declareProbeFactsUrl, PROBE_FACTS_VERSION, buildProbeFactsText, probeMediaProfile, runFfprobe, analyzeKeyframeIntervals, } from "./utils/ffprobe.js";
|
|
79
|
+
export { extractMediaMetadata, displaySize, extractVideoMetadata, extractFinalVideoFrameTimestamp, extractAudioMetadata, declareContentAddressedUrl, declareProbeFactsUrl, PROBE_FACTS_VERSION, buildProbeFactsText, probeMediaProfile, runFfprobe, analyzeKeyframeIntervals, } from "./utils/ffprobe.js";
|
|
80
80
|
export { NOT_MEDIA_PAYLOAD, NotMediaPayloadError, assertMediaPayload, fingerprintElementId, isNotMediaPayload, } from "./utils/notMediaPayload.js";
|
|
81
81
|
export { _setDownloadFetchForTests, assertPublicHttpsUrl, downloadToTemp, fetchPublicHttpsText, isHttpUrl, safeDownloadUrlIdentity, writeUrlDownloadTelemetry, } from "./utils/urlDownloader.js";
|
|
82
82
|
export { isBlockedNetworkHost } from "./utils/networkPolicy.js";
|
|
@@ -27,6 +27,7 @@ const TRANSIENT_BROWSER_ERROR_PATTERNS = [
|
|
|
27
27
|
/ECONNREFUSED/i,
|
|
28
28
|
/net::ERR_NETWORK_CHANGED/i,
|
|
29
29
|
/Composition has zero duration[\s\S]*Runtime ready: false/,
|
|
30
|
+
/Composition setup did not finish/,
|
|
30
31
|
// CDP can refuse a capture call outright with this exact wording; timed-out
|
|
31
32
|
// variants of the same call hit PROTOCOL_TIMEOUT_PATTERNS, checked first.
|
|
32
33
|
// Anchored to this literal reason (not just the CDP method) so an unrelated,
|
|
@@ -427,7 +427,7 @@ export interface HfDiagnostic {
|
|
|
427
427
|
pendingBuildReadyKeys: string[];
|
|
428
428
|
rejectedBuildReadyKeys: string[];
|
|
429
429
|
}
|
|
430
|
-
export declare function buildZeroDurationDiagnostic(diag: HfDiagnostic): string;
|
|
430
|
+
export declare function buildZeroDurationDiagnostic(diag: HfDiagnostic, timeoutMs?: number): string;
|
|
431
431
|
export declare function evaluateHfDiagnostic(page: Page): Promise<HfDiagnostic>;
|
|
432
432
|
export declare function pollHfReady(page: Page, timeoutMs: number, intervalMs?: number): Promise<void>;
|
|
433
433
|
export declare function waitForSubCompositionTimelines(session: CaptureSession, page: Page, timeoutMs: number, options?: {
|
|
@@ -1217,12 +1217,16 @@ export const HF_READY_DIAGNOSTIC_EXPR = `(async function() {
|
|
|
1217
1217
|
};
|
|
1218
1218
|
})()`;
|
|
1219
1219
|
// fallow-ignore-next-line complexity
|
|
1220
|
-
export function buildZeroDurationDiagnostic(diag) {
|
|
1220
|
+
export function buildZeroDurationDiagnostic(diag, timeoutMs) {
|
|
1221
1221
|
const hints = [];
|
|
1222
1222
|
if (diag.pendingBuildReadyKeys.length > 0) {
|
|
1223
1223
|
hints.push(`window.__hf.buildReady never resolved for: ${diag.pendingBuildReadyKeys.join(", ")}. ` +
|
|
1224
1224
|
"The runtime holds render-ready until every registered buildReady promise settles. " +
|
|
1225
|
-
"Find where the composition registers that key and confirm it actually resolves."
|
|
1225
|
+
"Find where the composition registers that key and confirm it actually resolves. " +
|
|
1226
|
+
"During a render, Date and performance.now stay at 0 and requestAnimationFrame " +
|
|
1227
|
+
"callbacks wait until the first frame, so setup that awaits an animation frame or " +
|
|
1228
|
+
"the clock never finishes. Await the library's own readiness promise instead (for " +
|
|
1229
|
+
"Three.js, renderer.compileAsync).");
|
|
1226
1230
|
}
|
|
1227
1231
|
if (diag.rejectedBuildReadyKeys.length > 0) {
|
|
1228
1232
|
hints.push(`window.__hf.buildReady rejected for: ${diag.rejectedBuildReadyKeys.join(", ")}. ` +
|
|
@@ -1231,7 +1235,7 @@ export function buildZeroDurationDiagnostic(diag) {
|
|
|
1231
1235
|
if (!diag.hasPlayer) {
|
|
1232
1236
|
hints.push("window.__player was never set: the HyperFrames runtime did not initialize.");
|
|
1233
1237
|
}
|
|
1234
|
-
if (!diag.hasTimeline) {
|
|
1238
|
+
if (diag.renderReady && !diag.hasTimeline) {
|
|
1235
1239
|
hints.push("No GSAP timeline registered (window.__timelines is empty). " +
|
|
1236
1240
|
"CSS/WAAPI/Lottie animations are usually auto-detected (the runtime infers " +
|
|
1237
1241
|
"duration from the longest running animation), but this composition's duration " +
|
|
@@ -1245,7 +1249,11 @@ export function buildZeroDurationDiagnostic(diag) {
|
|
|
1245
1249
|
if (diag.hasSeek && diag.duration === 0 && diag.renderReady) {
|
|
1246
1250
|
hints.push("The runtime finished initializing but reported zero duration. This is permanent.");
|
|
1247
1251
|
}
|
|
1248
|
-
|
|
1252
|
+
const within = timeoutMs === undefined ? "" : ` within ${timeoutMs}ms`;
|
|
1253
|
+
const headline = diag.renderReady
|
|
1254
|
+
? "Composition has zero duration."
|
|
1255
|
+
: `Composition setup did not finish${within}.`;
|
|
1256
|
+
return (`[FrameCapture] ${headline}\n` +
|
|
1249
1257
|
` Runtime ready: ${diag.renderReady}, __player: ${diag.hasPlayer}, ` +
|
|
1250
1258
|
`__hf.seek: ${diag.hasSeek}, GSAP timeline: ${diag.hasTimeline}, ` +
|
|
1251
1259
|
`data-duration: ${diag.declaredDuration > 0 ? diag.declaredDuration + "s" : "not set"}\n` +
|
|
@@ -1301,7 +1309,7 @@ export async function pollHfReady(page, timeoutMs, intervalMs = 100) {
|
|
|
1301
1309
|
}
|
|
1302
1310
|
const diag = await evaluateHfDiagnostic(page);
|
|
1303
1311
|
if (diag.hasSeek && diag.duration === 0) {
|
|
1304
|
-
throw new Error(buildZeroDurationDiagnostic(diag));
|
|
1312
|
+
throw new Error(buildZeroDurationDiagnostic(diag, timeoutMs));
|
|
1305
1313
|
}
|
|
1306
1314
|
throw new Error(`[FrameCapture] window.__hf not ready after ${timeoutMs}ms. ` +
|
|
1307
1315
|
`Page must expose window.__hf = { duration, seek }.\n` +
|
|
@@ -1753,8 +1761,8 @@ export function recordSubTimelineWarning(session, timeoutMs) {
|
|
|
1753
1761
|
`${pendingSuffix} (${sources.join(", ")}). Fix the error; a composition animated by ` +
|
|
1754
1762
|
`CSS or rAF rather than a GSAP timeline must mark its host with data-no-timeline.`
|
|
1755
1763
|
: scriptFailure
|
|
1756
|
-
? `A sub-composition timeline script failed to load
|
|
1757
|
-
|
|
1764
|
+
? `A sub-composition timeline script failed to load${scriptLoadCauseSuffix(loadCauses)} ` +
|
|
1765
|
+
`(${sources.join(", ")})`
|
|
1758
1766
|
: `Sub-composition timelines did not become ready within ${timeoutMs}ms${pendingSuffix}. ` +
|
|
1759
1767
|
`This can be intentional: a composition driven by CSS animations or rAF never registers ` +
|
|
1760
1768
|
`window.__timelines[id], and marking its host with data-no-timeline skips the wait entirely. ` +
|
|
@@ -15,7 +15,7 @@ import { HF_COLOR_GRADING_ATTR } from "@hyperframes/core";
|
|
|
15
15
|
import { isPathInside } from "@hyperframes/parsers/asset-paths";
|
|
16
16
|
import { resolveProjectRelativeSrc } from "@hyperframes/parsers/asset-resolution";
|
|
17
17
|
import { alphaDecoderFor } from "@hyperframes/parsers/media-alpha";
|
|
18
|
-
import { MEDIA_RENDER_ID_ATTR, isAudibleVideoElement, fpsToFfmpegArg, fpsToNumber, MEDIA_DURATION_CLAMP_EPSILON_SECONDS, normalizeRateSpec, readElementRateSpec, shiftRateLane, sourceTimeAt, timeAtSourceTime, exportClipWindow, hasClipStarted, isInClipWindow, mediaElementTime, parseStrictFiniteTimingNumber, readMediaStart, toFps, firstFrameColourArgs, hdrToSdrToneMapFilter, parseFirstFrameColour, } from "@hyperframes/core";
|
|
18
|
+
import { MEDIA_RENDER_ID_ATTR, isAudibleVideoElement, fpsToFfmpegArg, fpsToNumber, MEDIA_DURATION_CLAMP_EPSILON_SECONDS, normalizeRateSpec, readElementRateSpec, shiftRateLane, sourceTimeAt, timeAtSourceTime, exportClipWindow, hasClipStarted, isInClipWindow, mediaElementTime, parseStrictFiniteTimingNumber, readMediaStart, toFps, firstFrameColourArgs, keyframeTransferArgs, parseKeyframeTransfers, hdrToSdrToneMapFilter, parseFirstFrameColour, readsKeyframeTransfers, } from "@hyperframes/core";
|
|
19
19
|
import { resolveReferencedStart } from "./referenceResolver.js";
|
|
20
20
|
import { isKnownInactiveTimelineWindow } from "./mediaTimelineWindow.js";
|
|
21
21
|
import { extractFinalVideoFrameTimestamp, extractMediaMetadata, ffmpegSeekSeconds, formatFfmpegSeconds, keyframeGapSeconds, mediaFileIdentity, readMetadataCache, readStoredFinalFrame, resolvePlayableVideoDuration, videoStartOnMediaTimeline, writeMetadataCache, } from "../utils/ffprobe.js";
|
|
@@ -506,19 +506,32 @@ export function parseImageElements(html) {
|
|
|
506
506
|
return images;
|
|
507
507
|
}
|
|
508
508
|
const firstFrameColourCache = new Map();
|
|
509
|
+
/** A source's SPS transfers, so a PQ or SDR keyframe keeps an HLG one at 100 nits; null when the read failed. */
|
|
510
|
+
function readKeyframeTransfers(videoPath, codec) {
|
|
511
|
+
return inExtractionSlot(() => runFfmpeg([...urlInputArgs(videoPath), ...keyframeTransferArgs(videoPath, codec)])).then((result) => (result.success ? parseKeyframeTransfers(result.stderr) : null));
|
|
512
|
+
}
|
|
509
513
|
/** The first shown frame's colour tags (see {@link firstFrameColourArgs}), probed once per source file. */
|
|
510
|
-
function readFirstFrameColour(videoPath) {
|
|
514
|
+
function readFirstFrameColour(videoPath, codec) {
|
|
511
515
|
const identity = mediaFileIdentity(videoPath);
|
|
512
516
|
const cached = readMetadataCache(firstFrameColourCache, videoPath, identity);
|
|
513
517
|
if (cached)
|
|
514
518
|
return cached;
|
|
515
519
|
// In an extraction slot, so it never adds a decoder; unsignalled, so one cancel cannot cut it short for others.
|
|
516
|
-
const probe = inExtractionSlot(() => runFfmpeg([...urlInputArgs(videoPath), ...firstFrameColourArgs(videoPath)])).then((result) => {
|
|
520
|
+
const probe = inExtractionSlot(() => runFfmpeg([...urlInputArgs(videoPath), ...firstFrameColourArgs(videoPath)])).then(async (result) => {
|
|
521
|
+
const forget = () => {
|
|
522
|
+
if (firstFrameColourCache.get(videoPath)?.promise === probe)
|
|
523
|
+
firstFrameColourCache.delete(videoPath);
|
|
524
|
+
};
|
|
517
525
|
// A failed probe answers this cut only; the next one probes again.
|
|
518
|
-
if (!result.success
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
526
|
+
if (!result.success)
|
|
527
|
+
forget();
|
|
528
|
+
const first = parseFirstFrameColour(result.stderr);
|
|
529
|
+
if (!result.success || !readsKeyframeTransfers(first))
|
|
530
|
+
return first;
|
|
531
|
+
const keyframeTransfers = await readKeyframeTransfers(videoPath, codec);
|
|
532
|
+
if (!keyframeTransfers)
|
|
533
|
+
forget();
|
|
534
|
+
return { ...first, keyframeTransfers: keyframeTransfers ?? [] };
|
|
522
535
|
});
|
|
523
536
|
writeMetadataCache(firstFrameColourCache, videoPath, identity, probe);
|
|
524
537
|
return probe;
|
|
@@ -822,7 +835,9 @@ outputDirOverride) {
|
|
|
822
835
|
args.push("-noaccurate_seek");
|
|
823
836
|
args.push("-ss", formatFfmpegSeconds(ffmpegSeek(startTime)), "-i", videoPath, "-t", formatFfmpegSeconds(duration));
|
|
824
837
|
}
|
|
825
|
-
const frameColour = toneMappedToSdr
|
|
838
|
+
const frameColour = toneMappedToSdr
|
|
839
|
+
? await readFirstFrameColour(videoPath, metadata.videoCodec)
|
|
840
|
+
: undefined;
|
|
826
841
|
const filtersFor = videoFilterChain({
|
|
827
842
|
metadata,
|
|
828
843
|
format,
|
package/dist/utils/ffprobe.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export interface VideoMetadata {
|
|
|
43
43
|
colorRange?: string;
|
|
44
44
|
pixelFormat?: string;
|
|
45
45
|
sampleAspectRatio?: number;
|
|
46
|
+
rotation?: number;
|
|
46
47
|
/** Decoded frame count from the video stream's `nb_frames`. Omitted when the
|
|
47
48
|
* container does not surface a reliable count (still images, malformed
|
|
48
49
|
* streams, or muxes that require `-count_packets` to populate). Callers
|
|
@@ -90,6 +91,9 @@ interface FFProbeStream {
|
|
|
90
91
|
color_primaries?: string;
|
|
91
92
|
color_space?: string;
|
|
92
93
|
tags?: Record<string, string>;
|
|
94
|
+
side_data_list?: {
|
|
95
|
+
rotation?: number;
|
|
96
|
+
}[];
|
|
93
97
|
disposition?: {
|
|
94
98
|
attached_pic?: number;
|
|
95
99
|
};
|
|
@@ -172,6 +176,10 @@ export declare function readTagCI(tags: Record<string, string | undefined> | und
|
|
|
172
176
|
* smallest representable 2dp rate instead.
|
|
173
177
|
*/
|
|
174
178
|
export declare function parseFrameRate(frameRateStr: string | undefined): number;
|
|
179
|
+
export declare function displaySize(metadata: Pick<VideoMetadata, "width" | "height" | "sampleAspectRatio" | "rotation">): {
|
|
180
|
+
width: number;
|
|
181
|
+
height: number;
|
|
182
|
+
};
|
|
175
183
|
export declare function parseSampleAspectRatio(value: string | undefined): number | undefined;
|
|
176
184
|
/**
|
|
177
185
|
* Whether the first frame decoded with `decoder` (alpha-aware) has an alpha pixel format; undefined on any
|
package/dist/utils/ffprobe.js
CHANGED
|
@@ -637,6 +637,18 @@ export function parseFrameRate(frameRateStr) {
|
|
|
637
637
|
// caller's 30fps default.
|
|
638
638
|
return rounded > 0 ? rounded : 0.01;
|
|
639
639
|
}
|
|
640
|
+
function displayRotation(stream) {
|
|
641
|
+
const turn = Number(stream.side_data_list?.find((data) => data.rotation !== undefined)?.rotation ??
|
|
642
|
+
stream.tags?.rotate ??
|
|
643
|
+
0);
|
|
644
|
+
return Number.isFinite(turn) && turn % 360 !== 0 ? { rotation: turn } : {};
|
|
645
|
+
}
|
|
646
|
+
export function displaySize(metadata) {
|
|
647
|
+
const width = Math.round(metadata.width * (metadata.sampleAspectRatio ?? 1));
|
|
648
|
+
return Math.abs(metadata.rotation ?? 0) % 180 === 90
|
|
649
|
+
? { width: metadata.height, height: width }
|
|
650
|
+
: { width, height: metadata.height };
|
|
651
|
+
}
|
|
640
652
|
export function parseSampleAspectRatio(value) {
|
|
641
653
|
const [num, den] = (value ?? "").split(":").map(Number);
|
|
642
654
|
if (!num || !den || !Number.isFinite(num) || !Number.isFinite(den))
|
|
@@ -777,8 +789,12 @@ export async function extractMediaMetadata(filePath) {
|
|
|
777
789
|
const alphaPlaneDecoded = alphaDecoder
|
|
778
790
|
? await probeDecodedAlphaPlane(filePath, alphaDecoder)
|
|
779
791
|
: undefined;
|
|
780
|
-
|
|
792
|
+
let containerDuration = output?.format.duration ? parseFloat(output.format.duration) : 0;
|
|
781
793
|
const streamDuration = videoStream.duration ? parseFloat(videoStream.duration) : 0;
|
|
794
|
+
// Recorder output (WebM/Matroska written to a pipe) carries no duration header: read it off the packets.
|
|
795
|
+
if (!(containerDuration > 0) && !(streamDuration > 0) && !isPng) {
|
|
796
|
+
containerDuration = (await scanPacketDuration(filePath, fps)) ?? 0;
|
|
797
|
+
}
|
|
782
798
|
const parsedStreamStart = videoStream.start_time ? parseFloat(videoStream.start_time) : 0;
|
|
783
799
|
const streamStart = Number.isFinite(parsedStreamStart) ? parsedStreamStart : 0;
|
|
784
800
|
// `nb_frames` is populated by the container demuxer; a muxer that requires
|
|
@@ -825,6 +841,7 @@ export async function extractMediaMetadata(filePath) {
|
|
|
825
841
|
colorRange: videoStream.color_range,
|
|
826
842
|
pixelFormat,
|
|
827
843
|
...(sampleAspectRatio === undefined ? {} : { sampleAspectRatio }),
|
|
844
|
+
...displayRotation(videoStream),
|
|
828
845
|
frames,
|
|
829
846
|
};
|
|
830
847
|
})();
|
|
@@ -857,6 +874,30 @@ export function readStoredFinalFrame(filePath, metadata) {
|
|
|
857
874
|
: undefined;
|
|
858
875
|
});
|
|
859
876
|
}
|
|
877
|
+
const PACKET_TAIL_CHARS = 64 * 1024;
|
|
878
|
+
/** The video stream's length from its packets (last end time), for a file whose headers state none. */
|
|
879
|
+
async function scanPacketDuration(filePath, fps) {
|
|
880
|
+
try {
|
|
881
|
+
const stdout = await runFfprobe(filePath, [
|
|
882
|
+
...["-select_streams", "v:0", "-show_entries", "packet=pts_time,duration_time"],
|
|
883
|
+
...["-of", "csv=p=0"],
|
|
884
|
+
], undefined, { retainTail: true, maxChars: PACKET_TAIL_CHARS });
|
|
885
|
+
const lines = stdout.split("\n");
|
|
886
|
+
// A tail cut at the cap starts partway through a line, and its number would read as a huge time.
|
|
887
|
+
if (stdout.length >= PACKET_TAIL_CHARS)
|
|
888
|
+
lines.shift();
|
|
889
|
+
const frame = fps > 0 ? 1 / fps : 0;
|
|
890
|
+
const ends = lines
|
|
891
|
+
.map((line) => line.trim().split(",").map(Number))
|
|
892
|
+
.filter(([pts]) => Number.isFinite(pts))
|
|
893
|
+
.map(([pts, duration]) => pts + (Number.isFinite(duration) ? duration : frame));
|
|
894
|
+
return ends.length ? Math.max(...ends) : undefined;
|
|
895
|
+
}
|
|
896
|
+
catch {
|
|
897
|
+
// expected: a file ffprobe cannot scan keeps its "no duration" verdict.
|
|
898
|
+
return undefined;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
860
901
|
/**
|
|
861
902
|
* `duration_time` of the stream's last packet (by presentation time), read from the same final
|
|
862
903
|
* second the final-frame probe reads; undefined when ffprobe reports none.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/engine",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.17",
|
|
4
4
|
"description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"puppeteer": "~25.11.0",
|
|
62
62
|
"puppeteer-core": "^25.11.0",
|
|
63
63
|
"undici": "7.25.0",
|
|
64
|
-
"@hyperframes/core": "^0.9.
|
|
65
|
-
"@hyperframes/parsers": "^0.9.
|
|
64
|
+
"@hyperframes/core": "^0.9.17",
|
|
65
|
+
"@hyperframes/parsers": "^0.9.17"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/node": "^25.0.10",
|