@hyperframes/engine 0.8.218 → 0.8.220
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.
|
@@ -430,7 +430,7 @@ export interface IsolatedComposition {
|
|
|
430
430
|
compositionId: string;
|
|
431
431
|
elementId: string | null;
|
|
432
432
|
}
|
|
433
|
-
export declare function authorScriptsMayRegisterTimelines(runtimeAttr: string, authoredBegin: string, authoredEnd: string): Promise<boolean>;
|
|
433
|
+
export declare function authorScriptsMayRegisterTimelines(runtimeAttr: string, authoredBegin: string, authoredEnd: string, skipIds?: readonly string[]): Promise<boolean>;
|
|
434
434
|
export interface SubTimelinePollOptions {
|
|
435
435
|
intervalMs?: number;
|
|
436
436
|
getScriptLoadFailures?: () => readonly string[];
|
|
@@ -1362,9 +1362,9 @@ export async function waitForSubCompositionTimelines(session, page, timeoutMs, o
|
|
|
1362
1362
|
memo.unregisteredIds = session.pendingTimelineIds ?? [];
|
|
1363
1363
|
}
|
|
1364
1364
|
}
|
|
1365
|
-
// Runs in the page. False once every
|
|
1366
|
-
//
|
|
1367
|
-
export async function authorScriptsMayRegisterTimelines(runtimeAttr, authoredBegin, authoredEnd) {
|
|
1365
|
+
// Runs in the page. False once every script is read and none can register an id still waited on;
|
|
1366
|
+
// an unreadable, module or importing script, a computed key, or a page still loading answers true.
|
|
1367
|
+
export async function authorScriptsMayRegisterTimelines(runtimeAttr, authoredBegin, authoredEnd, skipIds = []) {
|
|
1368
1368
|
if (document.readyState !== "complete")
|
|
1369
1369
|
return true;
|
|
1370
1370
|
const memo = globalThis;
|
|
@@ -1373,38 +1373,66 @@ export async function authorScriptsMayRegisterTimelines(runtimeAttr, authoredBeg
|
|
|
1373
1373
|
...Array.from(document.querySelectorAll("script")),
|
|
1374
1374
|
...Array.from(document.querySelectorAll("template")).flatMap((t) => Array.from(t.content.querySelectorAll("script"))),
|
|
1375
1375
|
].filter((script) => !script.hasAttribute(runtimeAttr));
|
|
1376
|
+
const scan = { opaque: false, mentions: false, ids: [] };
|
|
1376
1377
|
// Code that imports another file may register through it, which is not read here.
|
|
1377
|
-
const
|
|
1378
|
+
const mayImport = (code) => /\bimport\s*\(|^\s*import\s*[\w{*"']/m.test(code);
|
|
1379
|
+
const read = (code) => {
|
|
1380
|
+
if (mayImport(code))
|
|
1381
|
+
scan.opaque = true;
|
|
1382
|
+
if (!code.includes("__timelines"))
|
|
1383
|
+
return;
|
|
1384
|
+
scan.mentions = true;
|
|
1385
|
+
const literalKey = /__timelines\s*(?:\[\s*(["'])((?:(?!\1).)+)\1\s*\]|\.([A-Za-z_$][\w$]*))/g;
|
|
1386
|
+
const rest = code
|
|
1387
|
+
.replace(/__timelines\s*=\s*(?:window\.__timelines\s*\|\|\s*)?\{\s*\}/g, "")
|
|
1388
|
+
.replace(literalKey, (_m, _q, bracketId, dotId) => {
|
|
1389
|
+
scan.ids.push(bracketId ?? dotId);
|
|
1390
|
+
return "";
|
|
1391
|
+
});
|
|
1392
|
+
if (rest.includes("__timelines"))
|
|
1393
|
+
scan.opaque = true;
|
|
1394
|
+
};
|
|
1378
1395
|
for (const script of scripts) {
|
|
1379
1396
|
if (script.type === "module")
|
|
1380
|
-
return true;
|
|
1397
|
+
return { ...scan, opaque: true };
|
|
1381
1398
|
if (!script.src) {
|
|
1382
1399
|
const text = script.textContent ?? "";
|
|
1383
|
-
|
|
1400
|
+
read(text.includes(authoredBegin)
|
|
1384
1401
|
? text
|
|
1385
1402
|
.split(authoredBegin)
|
|
1386
1403
|
.slice(1)
|
|
1387
1404
|
.map((region) => region.split(authoredEnd)[0])
|
|
1388
1405
|
.join("\n")
|
|
1389
|
-
: text;
|
|
1390
|
-
if (mayRegister(authored))
|
|
1391
|
-
return true;
|
|
1406
|
+
: text);
|
|
1392
1407
|
continue;
|
|
1393
1408
|
}
|
|
1394
1409
|
const url = new URL(script.src, location.href);
|
|
1395
1410
|
if (url.origin !== location.origin)
|
|
1396
|
-
return true;
|
|
1411
|
+
return { ...scan, opaque: true };
|
|
1397
1412
|
try {
|
|
1398
|
-
|
|
1399
|
-
return true;
|
|
1413
|
+
read(await (await fetch(url)).text());
|
|
1400
1414
|
}
|
|
1401
1415
|
catch {
|
|
1402
|
-
return true;
|
|
1416
|
+
return { ...scan, opaque: true };
|
|
1403
1417
|
}
|
|
1404
1418
|
}
|
|
1405
|
-
return
|
|
1419
|
+
return scan;
|
|
1406
1420
|
})();
|
|
1407
|
-
|
|
1421
|
+
const { opaque, mentions, ids } = memo.__hfAuthorScan;
|
|
1422
|
+
if (opaque)
|
|
1423
|
+
return true;
|
|
1424
|
+
if (!mentions)
|
|
1425
|
+
return false;
|
|
1426
|
+
const timelines = window.__timelines ?? {};
|
|
1427
|
+
return Array.from(document.querySelectorAll("[data-composition-id]")).some((host) => {
|
|
1428
|
+
const id = host.getAttribute("data-composition-id");
|
|
1429
|
+
const authoredId = host.getAttribute("data-hf-original-composition-id") ?? id;
|
|
1430
|
+
return (id &&
|
|
1431
|
+
!host.hasAttribute("data-no-timeline") &&
|
|
1432
|
+
!timelines[id] &&
|
|
1433
|
+
!skipIds.includes(id) &&
|
|
1434
|
+
ids.includes(authoredId ?? id));
|
|
1435
|
+
});
|
|
1408
1436
|
}
|
|
1409
1437
|
export async function pollSubCompositionTimelines(page, timeoutMs, { intervalMs = 150, getScriptLoadFailures, scriptFailureGraceMs = 2_000, getPageErrors, scriptErrorGraceMs = 5_000, onPending, shouldStop, knownUnregisteredIds = [], } = {}) {
|
|
1410
1438
|
// Hosts may opt out of the timeline wait with `data-no-timeline` —
|
|
@@ -1438,7 +1466,7 @@ export async function pollSubCompositionTimelines(page, timeoutMs, { intervalMs
|
|
|
1438
1466
|
ready =
|
|
1439
1467
|
Boolean(await page.evaluate(expression)) ||
|
|
1440
1468
|
(failed.length === 0 &&
|
|
1441
|
-
!(await page.evaluate(authorScriptsMayRegisterTimelines, RUNTIME_BOOTSTRAP_ATTR, AUTHORED_SOURCE_BEGIN, AUTHORED_SOURCE_END)));
|
|
1469
|
+
!(await page.evaluate(authorScriptsMayRegisterTimelines, RUNTIME_BOOTSTRAP_ATTR, AUTHORED_SOURCE_BEGIN, AUTHORED_SOURCE_END, knownUnregisteredIds)));
|
|
1442
1470
|
if (ready || shouldStop?.())
|
|
1443
1471
|
break;
|
|
1444
1472
|
const now = Date.now();
|
|
@@ -697,6 +697,9 @@ outputDirOverride) {
|
|
|
697
697
|
vfFilters.push("format=nv12");
|
|
698
698
|
}
|
|
699
699
|
const samplingAt = vfFilters.length;
|
|
700
|
+
if (metadata.sampleAspectRatio) {
|
|
701
|
+
vfFilters.push("scale=trunc(iw*sar/2)*2:ih", "setsar=1");
|
|
702
|
+
}
|
|
700
703
|
// Each slot takes the last frame starting at or before its time. Ticks of 2 ms or finer
|
|
701
704
|
// first shift half a tick, absorbing container rounding (WebM/MKV use whole ms).
|
|
702
705
|
const samplingFilters = (seek) => sampleAtOutputFps
|
package/dist/utils/ffprobe.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export interface VideoMetadata {
|
|
|
42
42
|
colorSpace: VideoColorSpace | null;
|
|
43
43
|
colorRange?: string;
|
|
44
44
|
pixelFormat?: string;
|
|
45
|
+
sampleAspectRatio?: number;
|
|
45
46
|
/** Decoded frame count from the video stream's `nb_frames`. Omitted when the
|
|
46
47
|
* container does not surface a reliable count (still images, malformed
|
|
47
48
|
* streams, or muxes that require `-count_packets` to populate). Callers
|
|
@@ -79,6 +80,7 @@ interface FFProbeStream {
|
|
|
79
80
|
nb_frames?: string;
|
|
80
81
|
nb_read_packets?: string;
|
|
81
82
|
pix_fmt?: string;
|
|
83
|
+
sample_aspect_ratio?: string;
|
|
82
84
|
r_frame_rate?: string;
|
|
83
85
|
avg_frame_rate?: string;
|
|
84
86
|
sample_rate?: string;
|
|
@@ -169,6 +171,7 @@ export declare function readTagCI(tags: Record<string, string | undefined> | und
|
|
|
169
171
|
* smallest representable 2dp rate instead.
|
|
170
172
|
*/
|
|
171
173
|
export declare function parseFrameRate(frameRateStr: string | undefined): number;
|
|
174
|
+
export declare function parseSampleAspectRatio(value: string | undefined): number | undefined;
|
|
172
175
|
/**
|
|
173
176
|
* Whether the first frame decoded with `decoder` (alpha-aware) has an alpha pixel format; undefined on any
|
|
174
177
|
* failure. ffprobe cannot pick a decoder, so ffmpeg writes the frame as rawvideo, which keeps its pix_fmt.
|
package/dist/utils/ffprobe.js
CHANGED
|
@@ -629,6 +629,13 @@ export function parseFrameRate(frameRateStr) {
|
|
|
629
629
|
// caller's 30fps default.
|
|
630
630
|
return rounded > 0 ? rounded : 0.01;
|
|
631
631
|
}
|
|
632
|
+
export function parseSampleAspectRatio(value) {
|
|
633
|
+
const [num, den] = (value ?? "").split(":").map(Number);
|
|
634
|
+
if (!num || !den || !Number.isFinite(num) || !Number.isFinite(den))
|
|
635
|
+
return undefined;
|
|
636
|
+
const ratio = num / den;
|
|
637
|
+
return Math.abs(ratio - 1) < 1e-3 ? undefined : ratio;
|
|
638
|
+
}
|
|
632
639
|
/**
|
|
633
640
|
* Whether the first frame decoded with `decoder` (alpha-aware) has an alpha pixel format; undefined on any
|
|
634
641
|
* failure. ffprobe cannot pick a decoder, so ffmpeg writes the frame as rawvideo, which keeps its pix_fmt.
|
|
@@ -756,6 +763,7 @@ export async function extractMediaMetadata(filePath) {
|
|
|
756
763
|
};
|
|
757
764
|
const colorSpace = merged.colorTransfer || merged.colorPrimaries || merged.colorSpace ? merged : null;
|
|
758
765
|
const pixelFormat = videoStream.pix_fmt || "";
|
|
766
|
+
const sampleAspectRatio = parseSampleAspectRatio(videoStream.sample_aspect_ratio);
|
|
759
767
|
const hasAlpha = videoStreamHasAlpha(videoStream);
|
|
760
768
|
const alphaDecoder = hasAlpha ? null : alphaDecoderFor(videoStream.codec_name);
|
|
761
769
|
const alphaPlaneDecoded = alphaDecoder
|
|
@@ -808,6 +816,7 @@ export async function extractMediaMetadata(filePath) {
|
|
|
808
816
|
colorSpace,
|
|
809
817
|
colorRange: videoStream.color_range,
|
|
810
818
|
pixelFormat,
|
|
819
|
+
...(sampleAspectRatio === undefined ? {} : { sampleAspectRatio }),
|
|
811
820
|
frames,
|
|
812
821
|
};
|
|
813
822
|
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/engine",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.220",
|
|
4
4
|
"description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"puppeteer": "~25.11.0",
|
|
56
56
|
"puppeteer-core": "^25.11.0",
|
|
57
57
|
"undici": "7.25.0",
|
|
58
|
-
"@hyperframes/core": "^0.8.
|
|
59
|
-
"@hyperframes/parsers": "^0.8.
|
|
58
|
+
"@hyperframes/core": "^0.8.220",
|
|
59
|
+
"@hyperframes/parsers": "^0.8.220"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/node": "^25.0.10",
|