@unrender/cli-win32-x64 0.0.0-stage → 0.0.2

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.
Files changed (80) hide show
  1. package/package.json +13 -4
  2. package/unrender/_internal/VCRUNTIME140.dll +0 -0
  3. package/unrender/_internal/VCRUNTIME140_1.dll +0 -0
  4. package/unrender/_internal/_asyncio.pyd +0 -0
  5. package/unrender/_internal/_bz2.pyd +0 -0
  6. package/unrender/_internal/_ctypes.pyd +0 -0
  7. package/unrender/_internal/_decimal.pyd +0 -0
  8. package/unrender/_internal/_hashlib.pyd +0 -0
  9. package/unrender/_internal/_lzma.pyd +0 -0
  10. package/unrender/_internal/_multiprocessing.pyd +0 -0
  11. package/unrender/_internal/_overlapped.pyd +0 -0
  12. package/unrender/_internal/_queue.pyd +0 -0
  13. package/unrender/_internal/_socket.pyd +0 -0
  14. package/unrender/_internal/_ssl.pyd +0 -0
  15. package/unrender/_internal/_uuid.pyd +0 -0
  16. package/unrender/_internal/_wmi.pyd +0 -0
  17. package/unrender/_internal/_zoneinfo.pyd +0 -0
  18. package/unrender/_internal/api-ms-win-core-console-l1-1-0.dll +0 -0
  19. package/unrender/_internal/api-ms-win-core-datetime-l1-1-0.dll +0 -0
  20. package/unrender/_internal/api-ms-win-core-debug-l1-1-0.dll +0 -0
  21. package/unrender/_internal/api-ms-win-core-errorhandling-l1-1-0.dll +0 -0
  22. package/unrender/_internal/api-ms-win-core-fibers-l1-1-0.dll +0 -0
  23. package/unrender/_internal/api-ms-win-core-fibers-l1-1-1.dll +0 -0
  24. package/unrender/_internal/api-ms-win-core-file-l1-1-0.dll +0 -0
  25. package/unrender/_internal/api-ms-win-core-file-l1-2-0.dll +0 -0
  26. package/unrender/_internal/api-ms-win-core-file-l2-1-0.dll +0 -0
  27. package/unrender/_internal/api-ms-win-core-handle-l1-1-0.dll +0 -0
  28. package/unrender/_internal/api-ms-win-core-heap-l1-1-0.dll +0 -0
  29. package/unrender/_internal/api-ms-win-core-interlocked-l1-1-0.dll +0 -0
  30. package/unrender/_internal/api-ms-win-core-kernel32-legacy-l1-1-1.dll +0 -0
  31. package/unrender/_internal/api-ms-win-core-libraryloader-l1-1-0.dll +0 -0
  32. package/unrender/_internal/api-ms-win-core-localization-l1-2-0.dll +0 -0
  33. package/unrender/_internal/api-ms-win-core-memory-l1-1-0.dll +0 -0
  34. package/unrender/_internal/api-ms-win-core-namedpipe-l1-1-0.dll +0 -0
  35. package/unrender/_internal/api-ms-win-core-processenvironment-l1-1-0.dll +0 -0
  36. package/unrender/_internal/api-ms-win-core-processthreads-l1-1-0.dll +0 -0
  37. package/unrender/_internal/api-ms-win-core-processthreads-l1-1-1.dll +0 -0
  38. package/unrender/_internal/api-ms-win-core-profile-l1-1-0.dll +0 -0
  39. package/unrender/_internal/api-ms-win-core-rtlsupport-l1-1-0.dll +0 -0
  40. package/unrender/_internal/api-ms-win-core-string-l1-1-0.dll +0 -0
  41. package/unrender/_internal/api-ms-win-core-synch-l1-1-0.dll +0 -0
  42. package/unrender/_internal/api-ms-win-core-synch-l1-2-0.dll +0 -0
  43. package/unrender/_internal/api-ms-win-core-sysinfo-l1-1-0.dll +0 -0
  44. package/unrender/_internal/api-ms-win-core-sysinfo-l1-2-0.dll +0 -0
  45. package/unrender/_internal/api-ms-win-core-timezone-l1-1-0.dll +0 -0
  46. package/unrender/_internal/api-ms-win-core-util-l1-1-0.dll +0 -0
  47. package/unrender/_internal/api-ms-win-crt-conio-l1-1-0.dll +0 -0
  48. package/unrender/_internal/api-ms-win-crt-convert-l1-1-0.dll +0 -0
  49. package/unrender/_internal/api-ms-win-crt-environment-l1-1-0.dll +0 -0
  50. package/unrender/_internal/api-ms-win-crt-filesystem-l1-1-0.dll +0 -0
  51. package/unrender/_internal/api-ms-win-crt-heap-l1-1-0.dll +0 -0
  52. package/unrender/_internal/api-ms-win-crt-locale-l1-1-0.dll +0 -0
  53. package/unrender/_internal/api-ms-win-crt-math-l1-1-0.dll +0 -0
  54. package/unrender/_internal/api-ms-win-crt-process-l1-1-0.dll +0 -0
  55. package/unrender/_internal/api-ms-win-crt-runtime-l1-1-0.dll +0 -0
  56. package/unrender/_internal/api-ms-win-crt-stdio-l1-1-0.dll +0 -0
  57. package/unrender/_internal/api-ms-win-crt-string-l1-1-0.dll +0 -0
  58. package/unrender/_internal/api-ms-win-crt-time-l1-1-0.dll +0 -0
  59. package/unrender/_internal/api-ms-win-crt-utility-l1-1-0.dll +0 -0
  60. package/unrender/_internal/base_library.zip +0 -0
  61. package/unrender/_internal/libcrypto-3.dll +0 -0
  62. package/unrender/_internal/libffi-8.dll +0 -0
  63. package/unrender/_internal/libssl-3.dll +0 -0
  64. package/unrender/_internal/pydantic-2.14.2.dist-info/INSTALLER +1 -0
  65. package/unrender/_internal/pydantic-2.14.2.dist-info/METADATA +1602 -0
  66. package/unrender/_internal/pydantic-2.14.2.dist-info/RECORD +218 -0
  67. package/unrender/_internal/pydantic-2.14.2.dist-info/REQUESTED +0 -0
  68. package/unrender/_internal/pydantic-2.14.2.dist-info/WHEEL +4 -0
  69. package/unrender/_internal/pydantic-2.14.2.dist-info/licenses/LICENSE +21 -0
  70. package/unrender/_internal/pydantic_core/_pydantic_core.cp312-win_amd64.pyd +0 -0
  71. package/unrender/_internal/pyexpat.pyd +0 -0
  72. package/unrender/_internal/python312.dll +0 -0
  73. package/unrender/_internal/select.pyd +0 -0
  74. package/unrender/_internal/setuptools/_vendor/jaraco/text/Lorem ipsum.txt +2 -0
  75. package/unrender/_internal/ucrtbase.dll +0 -0
  76. package/unrender/_internal/unicodedata.pyd +0 -0
  77. package/unrender/_internal/unrender/probe/mask.js +62 -0
  78. package/unrender/_internal/unrender/probe/probe.mjs +385 -0
  79. package/unrender/unrender.exe +0 -0
  80. package/README.md +0 -3
@@ -0,0 +1,385 @@
1
+ // Unrender probe: open a HyperFrames project in HyperFrames' own runtime and record, frame by frame, what the
2
+ // browser draws. Usage: node probe.mjs <projectDir> <out.json> [--fps N] [--hf-dist DIR]
3
+ //
4
+ // It reuses the CLI's snapshot path (bundle with fonts → serve → managed Chrome → renderSeek), so what it sees is
5
+ // what `hyperframes render` draws. Output: canvas, fonts, media, and per element a change-encoded track of its
6
+ // on-screen state (box, opacity, transform, paint order, text runs and line breaks).
7
+ import { readdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
8
+ import { join, resolve } from "node:path";
9
+ import { homedir } from "node:os";
10
+ import { pathToFileURL } from "node:url";
11
+ import { createHash } from "node:crypto";
12
+
13
+ const args = process.argv.slice(2);
14
+ const opt = (name, dflt) => {
15
+ const i = args.indexOf(name);
16
+ return i >= 0 ? args[i + 1] : dflt;
17
+ };
18
+ const [projectDir, outPath] = args.filter((a, i) => !a.startsWith("--") && !(i > 0 && args[i - 1].startsWith("--")));
19
+ if (!projectDir || !outPath) {
20
+ console.error("usage: node probe.mjs <projectDir> <out.json> [--fps N] [--hf-dist DIR]");
21
+ process.exit(2);
22
+ }
23
+
24
+ function versionOf(dist) {
25
+ try {
26
+ return JSON.parse(readFileSync(join(dist, "..", "package.json"), "utf8")).version || "0";
27
+ } catch {
28
+ return "0";
29
+ }
30
+ }
31
+ function newer(a, b) {
32
+ const pa = a.split(".").map(Number), pb = b.split(".").map(Number);
33
+ for (let i = 0; i < 3; i++) if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) > (pb[i] || 0);
34
+ return false;
35
+ }
36
+ function findDist() {
37
+ const given = opt("--hf-dist", process.env.UNRENDER_HF_DIST);
38
+ if (given) return given;
39
+ const root = join(homedir(), ".npm", "_npx");
40
+ let best = null;
41
+ for (const d of existsSync(root) ? readdirSync(root) : []) {
42
+ const dist = join(root, d, "node_modules", "hyperframes", "dist");
43
+ if (existsSync(dist) && (!best || newer(versionOf(dist), versionOf(best)))) best = dist;
44
+ }
45
+ if (!best) throw new Error("HyperFrames isn't installed: run `npx --yes hyperframes --version` once");
46
+ return best;
47
+ }
48
+ async function load(dist, prefix, names) {
49
+ const file = readdirSync(dist).find((f) => f.startsWith(prefix) && f.endsWith(".js"));
50
+ if (!file) throw new Error(`this HyperFrames version has no ${prefix}*.js; unrender needs an update`);
51
+ const mod = await import(pathToFileURL(join(dist, file)).href);
52
+ for (const n of names) if (!(n in mod)) throw new Error(`HyperFrames ${prefix} no longer exports ${n}`);
53
+ return mod;
54
+ }
55
+
56
+ // ---------------------------------------------------------------- in-page scan (runs inside Chrome)
57
+
58
+ function installScanner() {
59
+ const PHASE = { NEG: 0, BLOCK: 1, TEXT: 2, POS: 3, POSZ: 4 };
60
+ const ATOMIC = new Set(["IMG", "VIDEO", "CANVAS", "SVG", "svg", "IFRAME", "PICTURE", "OBJECT", "EMBED"]);
61
+ const INLINE = new Set(["inline", "contents"]);
62
+ const lineCache = new Map();
63
+
64
+ const alpha = (c) => {
65
+ if (!c || c === "transparent") return 0;
66
+ const m = c.match(/rgba?\(([^)]+)\)/);
67
+ if (!m) return 1;
68
+ const p = m[1].split(/[,\s/]+/).filter(Boolean);
69
+ return p.length > 3 ? parseFloat(p[3]) : 1;
70
+ };
71
+ const lin2 = (t) => {
72
+ if (!t || t === "none") return [1, 0, 0, 1];
73
+ const m = t.match(/matrix(3d)?\(([^)]+)\)/);
74
+ if (!m) return [1, 0, 0, 1];
75
+ const v = m[2].split(",").map(Number);
76
+ return m[1] ? [v[0], v[1], v[4], v[5]] : [v[0], v[1], v[2], v[3]];
77
+ };
78
+ const mul = (p, c) => [p[0] * c[0] + p[2] * c[1], p[1] * c[0] + p[3] * c[1], p[0] * c[2] + p[2] * c[3], p[1] * c[2] + p[3] * c[3]];
79
+ const inter = (a, b) => {
80
+ const x = Math.max(a[0], b[0]), y = Math.max(a[1], b[1]);
81
+ const x2 = Math.min(a[0] + a[2], b[0] + b[2]), y2 = Math.min(a[1] + a[3], b[1] + b[3]);
82
+ return x2 > x && y2 > y ? [x, y, x2 - x, y2 - y] : null;
83
+ };
84
+ const r1 = (v) => Math.round(v * 10) / 10;
85
+ const r3 = (v) => Math.round(v * 1000) / 1000;
86
+ // op: an inline child's own opacity; the block's own opacity is the element's (it fades the whole text)
87
+ const runStyle = (cs, bg, block) => ({
88
+ color: cs.color, family: cs.fontFamily, weight: cs.fontWeight, style: cs.fontStyle, size: parseFloat(cs.fontSize),
89
+ deco: cs.textDecorationLine, bg: bg || null,
90
+ ls: cs.letterSpacing, op: block ? 1 : parseFloat(cs.opacity), tf: null, // transforms never apply to inline boxes
91
+ });
92
+ const transformText = (s, tt) => (tt === "uppercase" ? s.toUpperCase() : tt === "lowercase" ? s.toLowerCase() : s);
93
+
94
+ // inline content of a block: text runs (descending into inline elements, stopping at blocks)
95
+ function textRuns(block, blockCs) {
96
+ const runs = [];
97
+ const pre = /^pre/.test(blockCs.whiteSpace);
98
+ // bg: the background of the inline box the text sits in (nested inline children inherit it visually)
99
+ const visit = (node, cs, bg, block) => {
100
+ for (const n of node.childNodes) {
101
+ if (n.nodeType === 3) {
102
+ let s = n.data;
103
+ if (!pre) s = s.replace(/\s+/g, " ");
104
+ if (s) runs.push({ node: n, text: transformText(s, cs.textTransform), tt: cs.textTransform, st: runStyle(cs, bg, block) });
105
+ } else if (n.nodeType === 1) {
106
+ if (n.tagName === "BR") {
107
+ runs.push({ node: null, text: "\n", st: runStyle(cs, bg, block) });
108
+ continue;
109
+ }
110
+ const ccs = getComputedStyle(n);
111
+ if (ccs.display === "none" || !INLINE.has(ccs.display) || ATOMIC.has(n.tagName)) continue;
112
+ visit(n, ccs, alpha(ccs.backgroundColor) > 0 ? ccs.backgroundColor : bg, false);
113
+ }
114
+ }
115
+ };
116
+ visit(block, blockCs, null, true);
117
+ return runs;
118
+ }
119
+
120
+ // the lines Chrome actually broke the text into: one Range per character, grouped by line top.
121
+ // Collapsed whitespace has no box, so it drops out on its own.
122
+ function lineBreaks(runs) {
123
+ const out = [];
124
+ const range = document.createRange();
125
+ let lastTop = null;
126
+ for (const run of runs) {
127
+ if (!run.node) {
128
+ out.push({ text: "\n", st: run.st });
129
+ lastTop = null;
130
+ continue;
131
+ }
132
+ const raw = run.node.data;
133
+ let buf = "";
134
+ for (let i = 0; i < raw.length; i++) {
135
+ range.setStart(run.node, i);
136
+ range.setEnd(run.node, i + 1);
137
+ const rect = range.getClientRects()[0];
138
+ const space = /\s/.test(raw[i]);
139
+ if (!rect || (space && rect.width === 0)) continue;
140
+ const newLine = lastTop !== null && rect.top > lastTop + rect.height * 0.5;
141
+ if (newLine) {
142
+ if (buf) out.push({ text: transformText(buf, run.tt), st: run.st });
143
+ out.push({ text: "\n", st: run.st });
144
+ buf = "";
145
+ }
146
+ if (lastTop === null || newLine) lastTop = rect.top;
147
+ buf += space ? " " : raw[i];
148
+ }
149
+ if (buf) out.push({ text: transformText(buf, run.tt), st: run.st });
150
+ }
151
+ return out;
152
+ }
153
+
154
+ function helper(el) {
155
+ if (el.classList && el.classList.contains("__render_frame__")) return true;
156
+ const p = el.previousElementSibling;
157
+ return el.tagName === "CANVAS" && !el.id && !!p && (p.tagName === "VIDEO" || (p.classList && p.classList.contains("__render_frame__")));
158
+ }
159
+
160
+ window.__urScan = function () {
161
+ const root = document.querySelector("[data-composition-id]");
162
+ if (!root) return null;
163
+ const W = window.innerWidth, H = window.innerHeight;
164
+ const view = [0, 0, W, H];
165
+ const els = {};
166
+ let order = 0;
167
+
168
+ const walk = (el, key, sel, effOp, clip, lin, prefix, flags, anc) => {
169
+ const cs = getComputedStyle(el);
170
+ if (cs.display === "none") return;
171
+ const op = effOp * parseFloat(cs.opacity || "1");
172
+ const L = mul(lin, lin2(cs.transform));
173
+ const b = el.getBoundingClientRect();
174
+ const box = [b.x, b.y, b.width, b.height];
175
+ const pos = cs.position !== "static";
176
+ const z = cs.zIndex === "auto" ? null : parseInt(cs.zIndex, 10);
177
+ const sc = el === root || (pos && z !== null) || parseFloat(cs.opacity) < 1 || cs.transform !== "none" ||
178
+ cs.filter !== "none" || cs.mixBlendMode !== "normal" || cs.isolation === "isolate" || cs.clipPath !== "none" ||
179
+ (cs.maskImage && cs.maskImage !== "none") || cs.position === "fixed" || cs.position === "sticky";
180
+ const idx = order++;
181
+ const phase = pos || sc ? (z !== null && z < 0 ? PHASE.NEG : z ? PHASE.POSZ : PHASE.POS) : PHASE.BLOCK;
182
+ const pk = [...prefix, phase, z || 0, idx];
183
+ const tag = el.tagName;
184
+ const atomic = ATOMIC.has(tag);
185
+ const f = { ...flags };
186
+ if (cs.mixBlendMode !== "normal") f.blend = 1;
187
+ if (cs.filter !== "none") f.filter = 1;
188
+ if (cs.backdropFilter && cs.backdropFilter !== "none") f.backdrop = 1;
189
+ if (cs.clipPath !== "none" || (cs.maskImage && cs.maskImage !== "none")) f.mask = 1;
190
+ const seen = clip ? inter(box, clip) : inter(box, view);
191
+ const visible = cs.visibility === "visible" && op > 0.002 && seen;
192
+
193
+ const pseudo = ["::before", "::after"].some((p) => {
194
+ const pc = getComputedStyle(el, p).content;
195
+ return pc && pc !== "none" && pc !== "normal";
196
+ });
197
+ const paintsBox = alpha(cs.backgroundColor) > 0 || cs.backgroundImage !== "none" || cs.boxShadow !== "none" ||
198
+ (parseFloat(cs.borderTopWidth) + parseFloat(cs.borderLeftWidth) + parseFloat(cs.borderRightWidth) +
199
+ parseFloat(cs.borderBottomWidth) > 0 && cs.borderTopStyle !== "none") ||
200
+ (cs.outlineStyle !== "none" && parseFloat(cs.outlineWidth) > 0) || pseudo || atomic || f.backdrop;
201
+
202
+ let text = null;
203
+ if (!INLINE.has(cs.display) && !atomic) {
204
+ const runs = textRuns(el, cs);
205
+ if (runs.some((r) => r.text.trim())) {
206
+ // the cache key carries every run's style: a word turning red keeps the text but must change the runs
207
+ const ck = JSON.stringify(runs.map((r) => [r.text, r.st])) + "@" + Math.round(b.width) + "x" + cs.fontSize;
208
+ let lines = lineCache.get(ck);
209
+ if (!lines) {
210
+ lines = lineBreaks(runs);
211
+ if (lineCache.size > 5000) lineCache.clear();
212
+ lineCache.set(ck, lines);
213
+ }
214
+ const rng = document.createRange();
215
+ rng.selectNodeContents(el);
216
+ const rects = Array.from(rng.getClientRects()).map((r) => [r1(r.x), r1(r.y), r1(r.width), r1(r.height)]);
217
+ text = {
218
+ runs: lines.map((r) => [r.text, r.st.color, r.st.family, r.st.weight, r.st.style, r.st.size, r.st.deco, r.st.bg, r.st.ls, r.st.op, r.st.tf]),
219
+ align: cs.textAlign, lh: cs.lineHeight, shadow: cs.textShadow, stroke: cs.webkitTextStrokeWidth + " " + cs.webkitTextStrokeColor,
220
+ clipText: cs.webkitBackgroundClip === "text" || cs.backgroundClip === "text" ? 1 : 0,
221
+ pad: [cs.paddingTop, cs.paddingRight, cs.paddingBottom, cs.paddingLeft].map(parseFloat),
222
+ radius: parseFloat(cs.borderTopLeftRadius) || 0, ink: rects,
223
+ };
224
+ }
225
+ }
226
+
227
+ // inline boxes belong to their text block (their background is a run style), so they aren't separate layers
228
+ if (visible && (paintsBox || text) && (!INLINE.has(cs.display) || atomic)) {
229
+ const rec = {
230
+ b: box.map(r1), c: seen.map(r1), o: r3(op), m: L.map(r3), lw: r1(el.offsetWidth || b.width), lh: r1(el.offsetHeight || b.height),
231
+ pk, pkt: pos || sc ? pk : [...prefix, PHASE.TEXT, 0, idx],
232
+ box: paintsBox ? 1 : 0,
233
+ bg: alpha(cs.backgroundColor) > 0 ? cs.backgroundColor : null,
234
+ bgi: cs.backgroundImage !== "none" ? 1 : 0,
235
+ rad: parseFloat(cs.borderTopLeftRadius) || 0, sh: cs.boxShadow !== "none" ? cs.boxShadow : null,
236
+ f: Object.keys(f).length ? f : undefined,
237
+ };
238
+ if (text) rec.t = text;
239
+ if (tag === "VIDEO" || tag === "AUDIO" || tag === "IMG") {
240
+ rec.media = { tag, src: el.getAttribute("src") || el.currentSrc || "", fit: cs.objectFit, opos: cs.objectPosition };
241
+ }
242
+ els[key] = { sel, anc, tag, id: el.id || "", cls: el.className && el.className.baseVal === undefined ? el.className : "", rec };
243
+ }
244
+ if (atomic) return;
245
+ const childClip = cs.overflow !== "visible" || cs.overflowX !== "visible" ? (clip ? inter(clip, box) || [0, 0, 0, 0] : inter(view, box)) : clip;
246
+ const childPrefix = sc || pos ? pk : prefix;
247
+ let i = 0;
248
+ for (const ch of el.children) {
249
+ if (helper(ch)) continue; // render-mode frame images after a <video>; mask.js skips them the same way
250
+ i++;
251
+ if (ch.tagName === "SCRIPT" || ch.tagName === "STYLE" || ch.tagName === "TEMPLATE") continue;
252
+ const ckey = ch.id ? "#" + ch.id : key + "/" + i;
253
+ const csel = ch.id ? "#" + CSS.escape(ch.id) : sel + " > :nth-child(" + i + ")";
254
+ walk(ch, ckey, csel, op, childClip, L, childPrefix, f, anc.concat(key));
255
+ }
256
+ };
257
+ const rootKey = root.id ? "#" + root.id : "root";
258
+ const rootSel = root.id ? "#" + CSS.escape(root.id) : "[data-composition-id=\"" + root.getAttribute("data-composition-id") + "\"]";
259
+ walk(root, rootKey, rootSel, 1, null, [1, 0, 0, 1], [], {}, []);
260
+ return els;
261
+ };
262
+
263
+ window.__urStatic = function () {
264
+ const root = document.querySelector("[data-composition-id]");
265
+ const fonts = [];
266
+ for (const sheet of Array.from(document.styleSheets)) {
267
+ let rules;
268
+ try {
269
+ rules = sheet.cssRules;
270
+ } catch {
271
+ continue;
272
+ }
273
+ for (const r of Array.from(rules || [])) {
274
+ if (r.constructor.name === "CSSFontFaceRule") {
275
+ fonts.push({ family: r.style.getPropertyValue("font-family").replace(/["']/g, "").trim(),
276
+ weight: r.style.getPropertyValue("font-weight") || "400", style: r.style.getPropertyValue("font-style") || "normal",
277
+ src: (r.style.getPropertyValue("src").match(/url\(["']?([^"')]+)["']?\)/) || [])[1] || "" });
278
+ }
279
+ }
280
+ }
281
+ const media = [];
282
+ document.querySelectorAll("video[data-start], audio[data-start], img[data-start]").forEach((m) => {
283
+ const a = (k) => m.getAttribute(k);
284
+ media.push({ tag: m.tagName, id: m.id, src: a("src") || "", start: parseFloat(a("data-start") || "0"),
285
+ duration: a("data-duration") === null ? null : parseFloat(a("data-duration")),
286
+ mediaStart: parseFloat(a("data-media-start") || a("data-playback-start") || "0"),
287
+ volume: a("data-volume") === null ? 1 : parseFloat(a("data-volume")), muted: m.muted || m.hasAttribute("muted"),
288
+ hasAudio: a("data-has-audio"), loop: m.loop || m.hasAttribute("loop"), rate: parseFloat(a("data-playback-rate") || "1"),
289
+ track: parseInt(a("data-track-index") || "0", 10) });
290
+ });
291
+ const clips = [];
292
+ root.querySelectorAll(":scope > [data-start]").forEach((c) => clips.push({ id: c.id, start: parseFloat(c.getAttribute("data-start")),
293
+ duration: parseFloat(c.getAttribute("data-duration") || "0"), tag: c.tagName, track: parseInt(c.getAttribute("data-track-index") || "0", 10) }));
294
+ return { fonts, media, clips, width: window.innerWidth, height: window.innerHeight,
295
+ fps: parseFloat(root.getAttribute("data-fps") || "30"), bg: getComputedStyle(document.body).backgroundColor,
296
+ rootBg: getComputedStyle(root).backgroundColor };
297
+ };
298
+ }
299
+
300
+ // ---------------------------------------------------------------- drive
301
+
302
+ const dist = findDist();
303
+ const { serveStaticProjectHtml } = await load(dist, "staticProjectServer-", ["serveStaticProjectHtml"]);
304
+ const { bundleWithLocalizedFonts } = await load(dist, "bundleWithLocalizedFonts-", ["bundleWithLocalizedFonts"]);
305
+ const cap = await load(dist, "captureCompositionFrame-", ["openSettledCompositionPage", "seekCompositionTimeline", "DENSE_GEOMETRY_SEEK_OPTIONS"]);
306
+
307
+ const dir = resolve(projectDir);
308
+ const html = await bundleWithLocalizedFonts(dir);
309
+ const server = await serveStaticProjectHtml(dir, html, undefined, [], false);
310
+ const started = Date.now();
311
+ let session;
312
+ try {
313
+ session = await cap.openSettledCompositionPage(html, server.url, {
314
+ renderReadyTimeoutMs: 45000, renderReadyWarningSuffix: "the probe may be inaccurate",
315
+ });
316
+ const { page } = session;
317
+ await page.evaluate(`(${installScanner.toString()})()`);
318
+ const statics = await page.evaluate(() => window.__urStatic());
319
+ const duration = await page.evaluate(() => {
320
+ const p = window.__player;
321
+ const d = p && typeof p.getDuration === "function" ? p.getDuration() : 0;
322
+ if (Number.isFinite(d) && d > 0) return d;
323
+ const r = document.querySelector("[data-composition-id][data-duration]");
324
+ return r ? parseFloat(r.getAttribute("data-duration")) : 0;
325
+ });
326
+ const fps = parseFloat(opt("--fps", String(statics.fps || 30)));
327
+ const frames = Math.max(1, Math.round(duration * fps));
328
+ // Each element keeps change-encoded channels: vis (on/off), g (box, clip, opacity, transform, layout size),
329
+ // p (paint order), s (box style), t (text). Inlined data (images, fonts) is stored once in blobs.
330
+ const blobs = {};
331
+ const blobRef = (src) => {
332
+ if (typeof src !== "string" || !src.startsWith("data:")) return src;
333
+ const id = "blob:" + createHash("sha1").update(src).digest("hex").slice(0, 16);
334
+ if (!blobs[id]) blobs[id] = src;
335
+ return id;
336
+ };
337
+ statics.fonts = statics.fonts.map((f) => ({ ...f, src: blobRef(f.src) }));
338
+ const CH = {
339
+ g: (r) => [...r.b, ...r.c, r.o, ...r.m, r.lw, r.lh],
340
+ p: (r) => [r.pk, r.pkt],
341
+ s: (r) => ({ box: r.box, bg: r.bg, bgi: r.bgi, rad: r.rad, sh: r.sh, f: r.f || null }),
342
+ t: (r) => r.t || null,
343
+ };
344
+ const elements = {};
345
+ const last = {};
346
+ for (let f = 0; f < frames; f++) {
347
+ const t = f / fps;
348
+ await cap.seekCompositionTimeline(page, t, { ...cap.DENSE_GEOMETRY_SEEK_OPTIONS, exactTime: true });
349
+ const els = (await page.evaluate(() => window.__urScan())) || {};
350
+ for (const [key, e] of Object.entries(els)) {
351
+ let el = elements[key];
352
+ if (!el) {
353
+ el = elements[key] = { sel: e.sel, anc: e.anc, tag: e.tag, id: e.id, cls: e.cls, vis: [], g: [], p: [], s: [], t: [] };
354
+ last[key] = { vis: 0 };
355
+ }
356
+ if (e.rec.media && !el.media) el.media = { ...e.rec.media, src: blobRef(e.rec.media.src) };
357
+ const L = last[key];
358
+ if (!L.vis) {
359
+ el.vis.push([f, 1]);
360
+ L.vis = 1;
361
+ }
362
+ for (const [ch, pick] of Object.entries(CH)) {
363
+ const v = JSON.stringify(pick(e.rec));
364
+ if (L[ch] !== v) {
365
+ el[ch].push([f, JSON.parse(v)]);
366
+ L[ch] = v;
367
+ }
368
+ }
369
+ }
370
+ for (const key of Object.keys(last)) {
371
+ if (last[key].vis && !(key in els)) {
372
+ elements[key].vis.push([f, 0]);
373
+ last[key] = { vis: 0 };
374
+ }
375
+ }
376
+ if (f % Math.max(1, Math.round(fps * 5)) === 0) process.stderr.write(`probe ${t.toFixed(1)}s/${duration.toFixed(1)}s\n`);
377
+ }
378
+ writeFileSync(outPath, JSON.stringify({
379
+ probe: 2, hyperframes: versionOf(dist), project: dir, duration, fps, frames, ...statics, elements, blobs,
380
+ seconds: (Date.now() - started) / 1000,
381
+ }));
382
+ } finally {
383
+ await session?.browser?.close().catch(() => {});
384
+ await server.close();
385
+ }
Binary file
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.