@hyperframes/engine 0.8.179 → 0.8.181
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/config.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/services/frameCapture.d.ts +0 -4
- package/dist/services/frameCapture.js +0 -25
- package/dist/services/videoFrameExtractor.js +26 -15
- package/dist/utils/ffprobe.d.ts +43 -0
- package/dist/utils/ffprobe.js +3 -3
- package/dist/utils/runFfmpeg.d.ts +2 -0
- package/dist/utils/runFfmpeg.js +17 -3
- package/package.json +3 -3
package/dist/config.d.ts
CHANGED
|
@@ -165,7 +165,7 @@ export interface EngineConfig {
|
|
|
165
165
|
streamingEncodeDurationCapEnabled: boolean;
|
|
166
166
|
/** Timeout for FFmpeg frame encoding (ms). Default: 600_000 */
|
|
167
167
|
ffmpegEncodeTimeout: number;
|
|
168
|
-
/**
|
|
168
|
+
/** FFmpeg mux/faststart timeout (ms); extraction waits this long for each new frame. Default: 300_000 */
|
|
169
169
|
ffmpegProcessTimeout: number;
|
|
170
170
|
/**
|
|
171
171
|
* Inactivity timeout for FFmpeg streaming encode (ms). The timer resets on
|
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,7 @@ export { extractMediaMetadata, extractVideoMetadata, extractFinalVideoFrameTimes
|
|
|
64
64
|
export { NOT_MEDIA_PAYLOAD, NotMediaPayloadError, assertMediaPayload, fingerprintElementId, isNotMediaPayload, } from "./utils/notMediaPayload.js";
|
|
65
65
|
export { assertPublicHttpsUrl, isBlockedNetworkHost, downloadToTemp, fetchPublicHttpsText, isHttpUrl, safeDownloadUrlIdentity, writeUrlDownloadTelemetry, type SafeDownloadUrlIdentity, type UrlDownloadOptions, type UrlDownloadTelemetry, type PublicHttpsTextOptions, } from "./utils/urlDownloader.js";
|
|
66
66
|
export { runFfmpeg, formatFfmpegError, describeFfmpegFailure, isExternalFfmpegInterruption, type RunFfmpegOptions, type RunFfmpegResult, } from "./utils/runFfmpeg.js";
|
|
67
|
+
export { aacSegmentCount } from "./services/aacEncode.js";
|
|
67
68
|
export { ManagedChildProcess, type ManagedChildProcessOptions, type ManagedChildProcessOutcome, type ManagedProcessTerminationReason, } from "./utils/managedChildProcess.js";
|
|
68
69
|
export { assertConfiguredFfmpegBinariesExist, getFfmpegBinary, getFfprobeBinary, FFMPEG_PATH_ENV, FFPROBE_PATH_ENV, } from "./utils/ffmpegBinaries.js";
|
|
69
70
|
export { trackChildProcess, killTrackedProcesses } from "./utils/processTracker.js";
|
package/dist/index.js
CHANGED
|
@@ -69,6 +69,7 @@ export { extractMediaMetadata, extractVideoMetadata, extractFinalVideoFrameTimes
|
|
|
69
69
|
export { NOT_MEDIA_PAYLOAD, NotMediaPayloadError, assertMediaPayload, fingerprintElementId, isNotMediaPayload, } from "./utils/notMediaPayload.js";
|
|
70
70
|
export { assertPublicHttpsUrl, isBlockedNetworkHost, downloadToTemp, fetchPublicHttpsText, isHttpUrl, safeDownloadUrlIdentity, writeUrlDownloadTelemetry, } from "./utils/urlDownloader.js";
|
|
71
71
|
export { runFfmpeg, formatFfmpegError, describeFfmpegFailure, isExternalFfmpegInterruption, } from "./utils/runFfmpeg.js";
|
|
72
|
+
export { aacSegmentCount } from "./services/aacEncode.js";
|
|
72
73
|
export { ManagedChildProcess, } from "./utils/managedChildProcess.js";
|
|
73
74
|
export { assertConfiguredFfmpegBinariesExist, getFfmpegBinary, getFfprobeBinary, FFMPEG_PATH_ENV, FFPROBE_PATH_ENV, } from "./utils/ffmpegBinaries.js";
|
|
74
75
|
export { trackChildProcess, killTrackedProcesses } from "./utils/processTracker.js";
|
|
@@ -428,10 +428,6 @@ export declare function recordSubTimelineWarning(session: CaptureSession, timeou
|
|
|
428
428
|
* PRINFRA-300 (blank hook scene, zero diagnostics, "fixed" by re-render).
|
|
429
429
|
*/
|
|
430
430
|
export declare function buildLiveMapWarning(libraries: readonly string[]): CaptureWarning;
|
|
431
|
-
/** Out-of-band videos a poster now sizes, so dropping their media cannot move layout. */
|
|
432
|
-
export declare function releasableVideoIds(options: CaptureOptions): string[];
|
|
433
|
-
/** FFmpeg supplies these videos' frames, so their own players would only hold decoders and buffers in every worker. */
|
|
434
|
-
export declare function releaseInjectedVideoMedia(page: Page, videoIds: readonly string[]): Promise<void>;
|
|
435
431
|
export declare function applyVideoMetadataHints(page: Page, hints: readonly CaptureVideoMetadataHint[] | undefined): Promise<void>;
|
|
436
432
|
/** Thrown once a VFX chain fails: the frames it paints would render without the effect. */
|
|
437
433
|
export declare class VfxFailureError extends Error {
|
|
@@ -1529,28 +1529,6 @@ async function decodeAllImages(page) {
|
|
|
1529
1529
|
}));
|
|
1530
1530
|
});
|
|
1531
1531
|
}
|
|
1532
|
-
/** Out-of-band videos a poster now sizes, so dropping their media cannot move layout. */
|
|
1533
|
-
export function releasableVideoIds(options) {
|
|
1534
|
-
const hinted = new Set((options.videoMetadataHints ?? []).map((hint) => hint.id));
|
|
1535
|
-
return (options.skipReadinessVideoIds ?? []).filter((id) => hinted.has(id));
|
|
1536
|
-
}
|
|
1537
|
-
/** FFmpeg supplies these videos' frames, so their own players would only hold decoders and buffers in every worker. */
|
|
1538
|
-
export async function releaseInjectedVideoMedia(page, videoIds) {
|
|
1539
|
-
if (videoIds.length === 0)
|
|
1540
|
-
return;
|
|
1541
|
-
await page.evaluate((ids) => {
|
|
1542
|
-
for (const id of ids) {
|
|
1543
|
-
const video = window.__hfMediaEl?.(id) ?? document.getElementById(id);
|
|
1544
|
-
if (!(video instanceof HTMLVideoElement))
|
|
1545
|
-
continue;
|
|
1546
|
-
video.pause();
|
|
1547
|
-
for (const source of video.querySelectorAll("source"))
|
|
1548
|
-
source.remove();
|
|
1549
|
-
video.removeAttribute("src");
|
|
1550
|
-
video.load();
|
|
1551
|
-
}
|
|
1552
|
-
}, [...videoIds]);
|
|
1553
|
-
}
|
|
1554
1532
|
export async function applyVideoMetadataHints(page, hints) {
|
|
1555
1533
|
if (!hints || hints.length === 0)
|
|
1556
1534
|
return;
|
|
@@ -1785,7 +1763,6 @@ export async function initializeSession(session) {
|
|
|
1785
1763
|
assertVfxIntact(session);
|
|
1786
1764
|
recordSubTimelineWarning(session, pageReadyTimeout);
|
|
1787
1765
|
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
|
|
1788
|
-
await releaseInjectedVideoMedia(page, releasableVideoIds(session.options));
|
|
1789
1766
|
logInitPhase("applyVideoMetadataHints complete");
|
|
1790
1767
|
// Run independent readiness checks in parallel — videos, images, fonts,
|
|
1791
1768
|
// and Tailwind don't depend on each other's completion.
|
|
@@ -1916,7 +1893,6 @@ export async function initializeSession(session) {
|
|
|
1916
1893
|
assertVfxIntact(session);
|
|
1917
1894
|
recordSubTimelineWarning(session, pageReadyTimeout);
|
|
1918
1895
|
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
|
|
1919
|
-
await releaseInjectedVideoMedia(page, releasableVideoIds(session.options));
|
|
1920
1896
|
logInitPhase("applyVideoMetadataHints complete");
|
|
1921
1897
|
// Run independent readiness checks in parallel — videos, images, fonts,
|
|
1922
1898
|
// and Tailwind don't depend on each other's completion.
|
|
@@ -2522,7 +2498,6 @@ export async function createStaticVerificationPage(session) {
|
|
|
2522
2498
|
knownUnregisteredIds: session.subTimelineWaitMemo.unregisteredIds,
|
|
2523
2499
|
});
|
|
2524
2500
|
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
|
|
2525
|
-
await releaseInjectedVideoMedia(page, releasableVideoIds(session.options));
|
|
2526
2501
|
const skipVideoIds = session.options.skipReadinessVideoIds ?? [];
|
|
2527
2502
|
await Promise.all([
|
|
2528
2503
|
pollVideosReady(page, skipVideoIds, pageReadyTimeout),
|
|
@@ -16,7 +16,7 @@ import { resolveProjectRelativeSrc } from "@hyperframes/parsers/asset-resolution
|
|
|
16
16
|
import { MEDIA_RENDER_ID_ATTR, isAudibleVideoElement, fpsToFfmpegArg, fpsToNumber, MEDIA_DURATION_CLAMP_EPSILON_SECONDS, normalizeRateSpec, readElementRateSpec, shiftRateLane, sourceTimeAt, timeAtSourceTime, exportClipWindow, hasClipStarted, isInClipWindow, parseStrictFiniteTimingNumber, readMediaStart, toFps, firstFrameColourArgs, hdrToSdrToneMapFilter, parseFirstFrameColour, } from "@hyperframes/core";
|
|
17
17
|
import { resolveReferencedStart } from "./referenceResolver.js";
|
|
18
18
|
import { isKnownInactiveTimelineWindow } from "./mediaTimelineWindow.js";
|
|
19
|
-
import { extractFinalVideoFrameTimestamp, extractMediaMetadata, } from "../utils/ffprobe.js";
|
|
19
|
+
import { extractFinalVideoFrameTimestamp, extractMediaMetadata, mediaFileIdentity, readMetadataCache, writeMetadataCache, } from "../utils/ffprobe.js";
|
|
20
20
|
import { inputAlphaOpaqueWarning, probeInputAlphaPlane } from "../utils/alphaPlaneProbe.js";
|
|
21
21
|
import { redactUrlQueriesInError, urlInputArgs } from "../utils/urlInput.js";
|
|
22
22
|
import { analyzeCompositionHdr, isHdrColorSpace as isHdrColorSpaceUtil, } from "../utils/hdr.js";
|
|
@@ -417,12 +417,23 @@ export function parseImageElements(html) {
|
|
|
417
417
|
}
|
|
418
418
|
return images;
|
|
419
419
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
420
|
+
const firstFrameColourCache = new Map();
|
|
421
|
+
/** The first shown frame's colour tags (see {@link firstFrameColourArgs}), probed once per source file. */
|
|
422
|
+
function readFirstFrameColour(videoPath) {
|
|
423
|
+
const identity = mediaFileIdentity(videoPath);
|
|
424
|
+
const cached = readMetadataCache(firstFrameColourCache, videoPath, identity);
|
|
425
|
+
if (cached)
|
|
426
|
+
return cached;
|
|
427
|
+
// In an extraction slot, so it never adds a decoder; unsignalled, so one cancel cannot cut it short for others.
|
|
428
|
+
const probe = inExtractionSlot(() => runFfmpeg([...urlInputArgs(videoPath), ...firstFrameColourArgs(videoPath)])).then((result) => {
|
|
429
|
+
// A failed probe answers this cut only; the next one probes again.
|
|
430
|
+
if (!result.success && firstFrameColourCache.get(videoPath)?.promise === probe) {
|
|
431
|
+
firstFrameColourCache.delete(videoPath);
|
|
432
|
+
}
|
|
433
|
+
return parseFirstFrameColour(result.stderr);
|
|
424
434
|
});
|
|
425
|
-
|
|
435
|
+
writeMetadataCache(firstFrameColourCache, videoPath, identity, probe);
|
|
436
|
+
return probe;
|
|
426
437
|
}
|
|
427
438
|
/** Chrome plays untagged VP9 and AV1 as BT.601, H.264 and VP8 as BT.709 from 720 coded lines (assumed for the rest). */
|
|
428
439
|
const CHROME_BT601_UNTAGGED_CODECS = new Set(["vp9", "av1"]);
|
|
@@ -434,10 +445,9 @@ function chromeGuessForUntaggedMatrix(metadata) {
|
|
|
434
445
|
return [`setparams=colorspace=${hd ? "bt709" : "smpte170m"}`];
|
|
435
446
|
}
|
|
436
447
|
/**
|
|
437
|
-
* Longest stretch of output one ffmpeg process extracts
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
* same absolute times as a single pass, so their frames are byte-identical to it.
|
|
448
|
+
* Longest stretch of output one ffmpeg process extracts; longer ranges split into segments
|
|
449
|
+
* that run side by side. Segments sample the same absolute times as a single pass, so their
|
|
450
|
+
* frames are byte-identical to it.
|
|
441
451
|
*/
|
|
442
452
|
const EXTRACTION_SEGMENT_SECONDS = 120;
|
|
443
453
|
/** Frame times since file start are `PTS*TB + seek`; snapping them to `lead + k/FR` gives every process one grid. */
|
|
@@ -624,7 +634,7 @@ outputDirOverride) {
|
|
|
624
634
|
vfFilters.push(SDR_TO_HDR_COLORSPACE_FILTER);
|
|
625
635
|
}
|
|
626
636
|
if (toneMappedToSdr) {
|
|
627
|
-
const frameColour = await readFirstFrameColour(videoPath
|
|
637
|
+
const frameColour = await readFirstFrameColour(videoPath);
|
|
628
638
|
vfFilters.push(hdrToSdrToneMapFilter(metadata.colorSpace ?? {}, frameColour));
|
|
629
639
|
}
|
|
630
640
|
if (toneMappedToSdr || (!isHdr && !options.sdrToHdrTransfer)) {
|
|
@@ -637,7 +647,8 @@ outputDirOverride) {
|
|
|
637
647
|
if (format === "png")
|
|
638
648
|
encodeArgs.push("-compression_level", "1");
|
|
639
649
|
encodeArgs.push("-y", outputPattern);
|
|
640
|
-
|
|
650
|
+
// A long 4K run can take far longer than the timeout and still be healthy; only a stall is a hang.
|
|
651
|
+
const runOptions = { signal, stallTimeout: ffmpegProcessTimeout };
|
|
641
652
|
const filterArgsFor = (seek) => {
|
|
642
653
|
const filters = [...vfFilters];
|
|
643
654
|
filters.splice(samplingAt, 0, ...samplingFilters(seek));
|
|
@@ -677,8 +688,8 @@ outputDirOverride) {
|
|
|
677
688
|
const hdrPrefix = options.sdrToHdrTransfer
|
|
678
689
|
? `SDR→HDR conversion failed (colorspace filter in extract pass, target ${options.sdrToHdrTransfer}): `
|
|
679
690
|
: "";
|
|
680
|
-
const timedOut = processResult.terminationReason === "
|
|
681
|
-
const timeoutSuffix = timedOut ? ` (
|
|
691
|
+
const timedOut = processResult.terminationReason === "inactivity";
|
|
692
|
+
const timeoutSuffix = timedOut ? ` (no new frame for ${ffmpegProcessTimeout} ms)` : "";
|
|
682
693
|
const diagnostic = `${hdrPrefix}FFmpeg exited with code ${processResult.exitCode}${timeoutSuffix}: ` +
|
|
683
694
|
processResult.stderr.slice(-500);
|
|
684
695
|
if (timedOut) {
|
|
@@ -1562,7 +1573,7 @@ export async function extractAllVideoFrames(videos, baseDir, options, signal, co
|
|
|
1562
1573
|
: join(options.outputDir, group.groupId);
|
|
1563
1574
|
try {
|
|
1564
1575
|
rmSync(tempDir, { recursive: true, force: true });
|
|
1565
|
-
// A long union can
|
|
1576
|
+
// A long union can fail even when each shorter
|
|
1566
1577
|
// member range succeeds. Do not retry the optimization itself; preserve
|
|
1567
1578
|
// the established grouped→direct fallback and apply bounded retries only
|
|
1568
1579
|
// to the individual source ranges below.
|
package/dist/utils/ffprobe.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export interface MediaProbeCacheEntry<T = FFProbeOutput> {
|
|
2
|
+
identity: string | null;
|
|
3
|
+
promise: Promise<T>;
|
|
4
|
+
}
|
|
1
5
|
export interface VideoColorSpace {
|
|
2
6
|
/** Color transfer characteristics, e.g. "bt709", "smpte2084", "arib-std-b67" */
|
|
3
7
|
colorTransfer: string;
|
|
@@ -48,6 +52,42 @@ export interface AudioMetadata {
|
|
|
48
52
|
/** Seconds after the file's media time 0 at which its last audio or video stream starts. */
|
|
49
53
|
latestStreamLeadSeconds?: number;
|
|
50
54
|
}
|
|
55
|
+
interface FFProbeStream {
|
|
56
|
+
codec_type: string;
|
|
57
|
+
codec_name?: string;
|
|
58
|
+
/** e.g. "LC", "HE-AAC", "HE-AACv2" — where the SBR marker lives, since
|
|
59
|
+
* codec_name is plain "aac" for all of them. */
|
|
60
|
+
profile?: string;
|
|
61
|
+
width?: number;
|
|
62
|
+
height?: number;
|
|
63
|
+
duration?: string;
|
|
64
|
+
start_time?: string;
|
|
65
|
+
nb_frames?: string;
|
|
66
|
+
nb_read_packets?: string;
|
|
67
|
+
pix_fmt?: string;
|
|
68
|
+
r_frame_rate?: string;
|
|
69
|
+
avg_frame_rate?: string;
|
|
70
|
+
sample_rate?: string;
|
|
71
|
+
channels?: number;
|
|
72
|
+
color_range?: string;
|
|
73
|
+
color_transfer?: string;
|
|
74
|
+
color_primaries?: string;
|
|
75
|
+
color_space?: string;
|
|
76
|
+
tags?: Record<string, string>;
|
|
77
|
+
disposition?: {
|
|
78
|
+
attached_pic?: number;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
interface FFProbeFormat {
|
|
82
|
+
duration?: string;
|
|
83
|
+
start_time?: string;
|
|
84
|
+
bit_rate?: string;
|
|
85
|
+
format_name?: string;
|
|
86
|
+
}
|
|
87
|
+
interface FFProbeOutput {
|
|
88
|
+
streams: FFProbeStream[];
|
|
89
|
+
format: FFProbeFormat;
|
|
90
|
+
}
|
|
51
91
|
interface StillImageMetadata {
|
|
52
92
|
width: number;
|
|
53
93
|
height: number;
|
|
@@ -58,6 +98,9 @@ export interface MediaProbeProfile {
|
|
|
58
98
|
hasAudioStream: boolean;
|
|
59
99
|
visualKind: "none" | "still" | "moving";
|
|
60
100
|
}
|
|
101
|
+
export declare function mediaFileIdentity(filePath: string): string | null;
|
|
102
|
+
export declare function readMetadataCache<T>(cache: Map<string, MediaProbeCacheEntry<T>>, key: string, identity: string | null): Promise<T> | undefined;
|
|
103
|
+
export declare function writeMetadataCache<T>(cache: Map<string, MediaProbeCacheEntry<T>>, key: string, identity: string | null, promise: Promise<T>): void;
|
|
61
104
|
/**
|
|
62
105
|
* Probe stream capabilities without assuming the caller's element type.
|
|
63
106
|
* File extensions and HTTP MIME are deliberately ignored: extensionless
|
package/dist/utils/ffprobe.js
CHANGED
|
@@ -209,7 +209,7 @@ function isPngImageProbe(output) {
|
|
|
209
209
|
return (formatNames.includes("png_pipe") ||
|
|
210
210
|
output.streams.some((stream) => stream.codec_type === "video" && stream.codec_name?.toLowerCase() === "png"));
|
|
211
211
|
}
|
|
212
|
-
function mediaFileIdentity(filePath) {
|
|
212
|
+
export function mediaFileIdentity(filePath) {
|
|
213
213
|
try {
|
|
214
214
|
const stat = statSync(filePath, { bigint: true });
|
|
215
215
|
return [stat.dev, stat.ino, stat.size, stat.mtimeNs, stat.ctimeNs].join(":");
|
|
@@ -218,11 +218,11 @@ function mediaFileIdentity(filePath) {
|
|
|
218
218
|
return null;
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
-
function readMetadataCache(cache, key, identity) {
|
|
221
|
+
export function readMetadataCache(cache, key, identity) {
|
|
222
222
|
const cached = cache.get(key);
|
|
223
223
|
return cached?.identity === identity ? cached.promise : undefined;
|
|
224
224
|
}
|
|
225
|
-
function writeMetadataCache(cache, key, identity, promise) {
|
|
225
|
+
export function writeMetadataCache(cache, key, identity, promise) {
|
|
226
226
|
const entry = { identity, promise };
|
|
227
227
|
cache.set(key, entry);
|
|
228
228
|
void promise.then(() => {
|
|
@@ -8,6 +8,8 @@ import { type ManagedProcessTerminationReason } from "./managedChildProcess.js";
|
|
|
8
8
|
export interface RunFfmpegOptions {
|
|
9
9
|
signal?: AbortSignal;
|
|
10
10
|
timeout?: number;
|
|
11
|
+
/** Replaces `timeout`: stop only once this long passes without a new output frame. */
|
|
12
|
+
stallTimeout?: number;
|
|
11
13
|
onStderr?: (line: string) => void;
|
|
12
14
|
}
|
|
13
15
|
export interface RunFfmpegResult {
|
package/dist/utils/runFfmpeg.js
CHANGED
|
@@ -147,11 +147,25 @@ const ABORTED_BEFORE_START = {
|
|
|
147
147
|
export async function runFfmpeg(args, opts) {
|
|
148
148
|
if (opts?.signal?.aborted)
|
|
149
149
|
return { ...ABORTED_BEFORE_START };
|
|
150
|
-
const
|
|
150
|
+
const stallTimeout = opts?.stallTimeout;
|
|
151
|
+
let framesWritten = 0;
|
|
152
|
+
const readStats = ffmpegStatsReader(({ frames }) => {
|
|
153
|
+
if (frames === undefined || frames <= framesWritten)
|
|
154
|
+
return;
|
|
155
|
+
framesWritten = frames;
|
|
156
|
+
managed.markActivity();
|
|
157
|
+
});
|
|
151
158
|
const managed = new ManagedChildProcess(spawnFfmpeg(args), {
|
|
152
159
|
signal: opts?.signal,
|
|
153
|
-
|
|
154
|
-
|
|
160
|
+
...(stallTimeout === undefined
|
|
161
|
+
? { deadlineAtMs: Date.now() + (opts?.timeout ?? DEFAULT_TIMEOUT) }
|
|
162
|
+
: { inactivityTimeoutMs: stallTimeout }),
|
|
163
|
+
onStderr: stallTimeout === undefined
|
|
164
|
+
? opts?.onStderr
|
|
165
|
+
: (chunk) => {
|
|
166
|
+
readStats(chunk);
|
|
167
|
+
opts?.onStderr?.(chunk);
|
|
168
|
+
},
|
|
155
169
|
});
|
|
156
170
|
return toResult(await managed.wait());
|
|
157
171
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/engine",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.181",
|
|
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/core": "^0.8.
|
|
54
|
-
"@hyperframes/parsers": "^0.8.
|
|
53
|
+
"@hyperframes/core": "^0.8.181",
|
|
54
|
+
"@hyperframes/parsers": "^0.8.181"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/node": "^25.0.10",
|