hyperframes 0.6.97 → 0.6.98

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 (77) hide show
  1. package/dist/beat-analyzer.global.js +326 -0
  2. package/dist/cli.js +2479 -1961
  3. package/dist/commands/layout-audit.browser.js +86 -0
  4. package/dist/hyperframe-runtime.js +22 -22
  5. package/dist/hyperframe.manifest.json +1 -1
  6. package/dist/hyperframe.runtime.iife.js +22 -22
  7. package/dist/skills/hyperframes-cli/SKILL.md +67 -103
  8. package/dist/skills/hyperframes-cli/references/doctor-browser.md +45 -0
  9. package/dist/skills/hyperframes-cli/references/init-and-scaffold.md +51 -0
  10. package/dist/skills/hyperframes-cli/references/lambda.md +132 -0
  11. package/dist/skills/hyperframes-cli/references/lint-validate-inspect.md +93 -0
  12. package/dist/skills/hyperframes-cli/references/preview-render.md +107 -0
  13. package/dist/skills/hyperframes-cli/references/upgrade-info-misc.md +75 -0
  14. package/dist/studio/assets/hyperframes-player-DgsMQSvV.js +418 -0
  15. package/dist/studio/assets/index-B62bDCQv.css +1 -0
  16. package/dist/studio/assets/index-Ce3pBm_I.js +252 -0
  17. package/dist/studio/assets/{index-HveJ0MuV.js → index-D-ET9M0b.js} +1 -1
  18. package/dist/studio/assets/index-D-bS9Dxx.js +1 -0
  19. package/dist/studio/index.html +2 -2
  20. package/dist/templates/_shared/AGENTS.md +46 -21
  21. package/dist/templates/_shared/CLAUDE.md +16 -14
  22. package/package.json +3 -2
  23. package/dist/pngDecodeBlitWorker.js +0 -239
  24. package/dist/skills/gsap/SKILL.md +0 -240
  25. package/dist/skills/gsap/references/effects.md +0 -297
  26. package/dist/skills/gsap/scripts/extract-audio-data.py +0 -188
  27. package/dist/skills/hyperframes/SKILL.md +0 -491
  28. package/dist/skills/hyperframes/data-in-motion.md +0 -19
  29. package/dist/skills/hyperframes/house-style.md +0 -73
  30. package/dist/skills/hyperframes/palettes/bold-energetic.md +0 -14
  31. package/dist/skills/hyperframes/palettes/clean-corporate.md +0 -14
  32. package/dist/skills/hyperframes/palettes/dark-premium.md +0 -14
  33. package/dist/skills/hyperframes/palettes/jewel-rich.md +0 -14
  34. package/dist/skills/hyperframes/palettes/monochrome.md +0 -14
  35. package/dist/skills/hyperframes/palettes/nature-earth.md +0 -14
  36. package/dist/skills/hyperframes/palettes/neon-electric.md +0 -14
  37. package/dist/skills/hyperframes/palettes/pastel-soft.md +0 -14
  38. package/dist/skills/hyperframes/palettes/warm-editorial.md +0 -14
  39. package/dist/skills/hyperframes/patterns.md +0 -191
  40. package/dist/skills/hyperframes/references/audio-reactive.md +0 -76
  41. package/dist/skills/hyperframes/references/beat-direction.md +0 -171
  42. package/dist/skills/hyperframes/references/captions.md +0 -163
  43. package/dist/skills/hyperframes/references/css-patterns.md +0 -373
  44. package/dist/skills/hyperframes/references/design-picker.md +0 -117
  45. package/dist/skills/hyperframes/references/dynamic-techniques.md +0 -102
  46. package/dist/skills/hyperframes/references/html-in-canvas-patterns.md +0 -507
  47. package/dist/skills/hyperframes/references/motion-principles.md +0 -150
  48. package/dist/skills/hyperframes/references/narration.md +0 -92
  49. package/dist/skills/hyperframes/references/prompt-expansion.md +0 -68
  50. package/dist/skills/hyperframes/references/techniques.md +0 -525
  51. package/dist/skills/hyperframes/references/text-effects.md +0 -64
  52. package/dist/skills/hyperframes/references/transcript-guide.md +0 -107
  53. package/dist/skills/hyperframes/references/transitions/catalog.md +0 -117
  54. package/dist/skills/hyperframes/references/transitions/css-3d.md +0 -12
  55. package/dist/skills/hyperframes/references/transitions/css-blur.md +0 -51
  56. package/dist/skills/hyperframes/references/transitions/css-cover.md +0 -43
  57. package/dist/skills/hyperframes/references/transitions/css-destruction.md +0 -95
  58. package/dist/skills/hyperframes/references/transitions/css-dissolve.md +0 -66
  59. package/dist/skills/hyperframes/references/transitions/css-distortion.md +0 -45
  60. package/dist/skills/hyperframes/references/transitions/css-grid.md +0 -10
  61. package/dist/skills/hyperframes/references/transitions/css-light.md +0 -49
  62. package/dist/skills/hyperframes/references/transitions/css-mechanical.md +0 -30
  63. package/dist/skills/hyperframes/references/transitions/css-other.md +0 -25
  64. package/dist/skills/hyperframes/references/transitions/css-push.md +0 -41
  65. package/dist/skills/hyperframes/references/transitions/css-radial.md +0 -37
  66. package/dist/skills/hyperframes/references/transitions/css-scale.md +0 -24
  67. package/dist/skills/hyperframes/references/transitions.md +0 -138
  68. package/dist/skills/hyperframes/references/typography.md +0 -175
  69. package/dist/skills/hyperframes/references/video-composition.md +0 -62
  70. package/dist/skills/hyperframes/scripts/animation-map.mjs +0 -601
  71. package/dist/skills/hyperframes/scripts/contrast-report.mjs +0 -348
  72. package/dist/skills/hyperframes/scripts/package-loader.mjs +0 -269
  73. package/dist/skills/hyperframes/templates/design-picker.html +0 -1432
  74. package/dist/skills/hyperframes/visual-styles.md +0 -443
  75. package/dist/studio/assets/hyperframes-player-Daj5djxa.js +0 -418
  76. package/dist/studio/assets/index-B0twsRu0.css +0 -1
  77. package/dist/studio/assets/index-Cfye9xzo.js +0 -251
@@ -1,4 +1,4 @@
1
- import{n as Qi}from"./index-Cfye9xzo.js";/*!
1
+ import{n as Qi}from"./index-Ce3pBm_I.js";/*!
2
2
  * Copyright (c) 2026-present, Vanilagy and contributors
3
3
  *
4
4
  * This Source Code Form is subject to the terms of the Mozilla Public
@@ -0,0 +1 @@
1
+ import{g as P}from"./index-Ce3pBm_I.js";function j(c,d){for(var s=0;s<d.length;s++){const a=d[s];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in c)){const l=Object.getOwnPropertyDescriptor(a,i);l&&Object.defineProperty(c,i,l.get?l:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(c,Symbol.toStringTag,{value:"Module"}))}var v={},w;function k(){if(w)return v;w=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default=d;var c=window.OfflineAudioContext||window.webkitOfflineAudioContext;function d(e){var r=a(e);return r.start(0),[i,y,O(e.sampleRate),s].reduce(function(t,o){return o(t)},r.buffer.getChannelData(0))}function s(e){return e.sort(function(r,t){return t.count-r.count}).splice(0,5)[0].tempo}function a(e){var r=e.length,t=e.numberOfChannels,o=e.sampleRate,n=new c(t,r,o),u=n.createBufferSource();u.buffer=e;var f=n.createBiquadFilter();return f.type="lowpass",u.connect(f),f.connect(n.destination),u}function i(e){for(var r=[],t=.9,o=.3,n=15;r.length<n&&t>=o;)r=l(e,t),t-=.05;if(r.length<n)throw new Error("Could not find enough samples for a reliable detection.");return r}function l(e,r){for(var t=[],o=0,n=e.length;o<n;o+=1)e[o]>r&&(t.push(o),o+=1e4);return t}function y(e){var r=[];return e.forEach(function(t,o){for(var n=function(x){var g=e[o+x]-t,_=r.some(function(h){if(h.interval===g)return h.count+=1});_||r.push({interval:g,count:1})},u=0;u<10;u+=1)n(u)}),r}function O(e){return function(r){var t=[];return r.forEach(function(o){if(o.interval!==0){for(var n=60/(o.interval/e);n<90;)n*=2;for(;n>180;)n/=2;n=Math.round(n);var u=t.some(function(f){if(f.tempo===n)return f.count+=o.count});u||t.push({tempo:n,count:o.count})}}),t}}return v}var p,b;function q(){return b||(b=1,p=k().default),p}var m=q();const A=P(m),D=j({__proto__:null,default:A},[m]);export{D as i};
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <title>HyperFrames Studio</title>
8
- <script type="module" crossorigin src="/assets/index-Cfye9xzo.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-B0twsRu0.css">
8
+ <script type="module" crossorigin src="/assets/index-Ce3pBm_I.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-B62bDCQv.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -1,14 +1,28 @@
1
1
  # HyperFrames Composition Project
2
2
 
3
- ## Skills
3
+ ## Skills — USE THESE FIRST
4
4
 
5
- This project uses AI agent skills for framework-specific patterns. Install them if not already present:
5
+ **Always invoke the relevant skill before writing or modifying compositions.** Skills encode framework-specific patterns (e.g., `window.__timelines` registration, `data-*` attribute semantics, shader-compatible CSS rules) that are NOT in generic web docs. Skipping them produces broken compositions.
6
6
 
7
- ```bash
8
- npx skills add heygen-com/hyperframes
9
- ```
7
+ **Doing anything with HyperFrames?** Start at `/hyperframes-read-first` — it tells you what HyperFrames can do and which skill or workflow handles your intent (make a video, TTS / BGM, prep footage, author / animate, render, install blocks), and routes every "make me a video" request to the right workflow. Read it first, especially when there's no project context to orient you. The video workflows it routes to:
8
+
9
+ - `/product-launch-video` — a **product** URL or brief / script → 60-90s product launch / SaaS / promo video.
10
+ - `/website-to-video` — a **general** website / URL → a video _of_ the site (tour / showcase / social clip from captured visuals); a product **launch / promo** is `/product-launch-video`.
11
+ - `/faceless-explainer` — arbitrary text (topic / article / notes), **no URL, no website capture** → 60-90s faceless explainer.
12
+ - `/embedded-captions` — an existing talking-head video (MP4) → the same footage with captions / subtitles added (rail + embed, or pure-cinematic embed); the footage itself is untouched.
13
+ - `/graphic-overlays` — an existing talking-head / interview / podcast video (MP4) → the same footage **packaged with designed graphic overlays** (kinetic titles, lower-thirds, data callouts, pull-quotes, side panels, pip) synced to the transcript; the clip plays unchanged underneath. (Plain captions/subtitles → `/embedded-captions`.)
14
+ - `/pr-to-video` — a GitHub PR (URL / `owner/repo#N` / "this PR") → 30-90s code-change explainer (changelog / feature reveal / fix / refactor).
15
+ - `/motion-graphics` — a short (typically under 10s) design-led **motion graphic**, motion-is-the-message, no narration: kinetic type, a stat / number count-up, a chart, a logo sting, a lower-third / overlay, or an animated tweet / headline / captured-page highlight; rendered to MP4 or a transparent overlay. Longer / narrated / custom → `/general-video`.
16
+ - `/general-video` — fallback for any other video (title card, longer brand / sizzle reel, multi-scene montage, static loop, custom composition); the original hyperframes authoring flow, any length.
17
+
18
+ **Porting an existing composition?** `/remotion-to-hyperframes` translates a Remotion (React) composition into HyperFrames HTML — a source migration, separate from the creation workflows above.
19
+
20
+ The domain skills (`/hyperframes-core`, `/hyperframes-animation`, `/hyperframes-creative`, `/hyperframes-cli`, `/hyperframes-media`, `/hyperframes-registry`) and the full capability map live inside `/hyperframes-read-first` — it is the single source of truth for which skill handles which intent.
21
+
22
+ > **Tailwind v4 projects** (`hyperframes init --tailwind`): see `/hyperframes-core` → `references/tailwind.md`.
10
23
 
11
- Skills encode patterns like `window.__timelines` registration, `data-*` attribute semantics, Tailwind v4 browser-runtime styling for `--tailwind` projects, and shader-compatible CSS rules that are not in generic web docs. Using them produces correct compositions from the start.
24
+ > **Skills not available?** Ask the user to run `npx hyperframes skills` and restart their
25
+ > agent session, or install manually: `npx skills add heygen-com/hyperframes`.
12
26
 
13
27
  ## Commands
14
28
 
@@ -17,46 +31,57 @@ npm run dev # start the preview server (long-running — keep it alive
17
31
  npm run check # lint + validate + inspect
18
32
  npm run render # render to MP4
19
33
  npm run publish # publish and get a shareable link
34
+ npx hyperframes lint --verbose # include info-level findings
35
+ npx hyperframes lint --json # machine-readable output for CI
20
36
  npx hyperframes docs <topic> # reference docs in terminal
21
37
  ```
22
38
 
23
39
  > **`npm run dev` is a long-running server, not a one-shot command.** It blocks until stopped.
24
- > Always run it as a background process so it stays alive while you edit compositions.
25
- > Running it in the foreground will time out and kill the server, breaking the browser preview.
40
+ > In Claude Code, always run it with `run_in_background: true`. Never run it as a foreground
41
+ > command it will time out and the server will die, breaking the browser preview.
42
+
43
+ ## Documentation
44
+
45
+ **For quick reference**, use the local CLI docs command (no network required):
46
+
47
+ ```bash
48
+ npx hyperframes docs <topic>
49
+ ```
50
+
51
+ Topics: `data-attributes`, `gsap`, `compositions`, `rendering`, `examples`, `troubleshooting`
52
+
53
+ **For full documentation**, discover pages via the machine-readable index — do NOT guess URLs:
54
+
55
+ ```
56
+ https://hyperframes.heygen.com/llms.txt
57
+ ```
26
58
 
27
59
  ## Project Structure
28
60
 
29
61
  - `index.html` — main composition (root timeline)
30
62
  - `compositions/` — sub-compositions referenced via `data-composition-src`
31
- - `assets/` — media files (video, audio, images)
32
63
  - `meta.json` — project metadata (id, name)
33
64
  - `transcript.json` — whisper word-level transcript (if generated)
34
65
 
35
- ## Linting — Always Run After Changes
66
+ ## Linting — ALWAYS RUN AFTER CHANGES
36
67
 
37
- After creating or editing any `.html` composition, run the full check before considering the task complete:
68
+ After creating or editing any `.html` composition, **always** run the full check before considering the task complete:
38
69
 
39
70
  ```bash
40
71
  npm run check
41
72
  ```
42
73
 
43
- Fix all errors before presenting the result.
74
+ Fix all errors before presenting the result. Inspect warnings should be reviewed before rendering.
44
75
 
45
76
  ## Key Rules
46
77
 
47
78
  1. Every timed element needs `data-start`, `data-duration`, and `data-track-index`
48
- 2. Visible timed elements **must** have `class="clip"` — the framework uses this for visibility control
49
- 3. GSAP timelines must be paused and registered on `window.__timelines`:
79
+ 2. Elements with timing **MUST** have `class="clip"` — the framework uses this for visibility control
80
+ 3. Timelines must be paused and registered on `window.__timelines`:
50
81
  ```js
51
82
  window.__timelines = window.__timelines || {};
52
83
  window.__timelines["composition-id"] = gsap.timeline({ paused: true });
53
84
  ```
54
85
  4. Videos use `muted` with a separate `<audio>` element for the audio track
55
- 5. Sub-compositions use `data-composition-src="compositions/file.html"`
86
+ 5. Sub-compositions use `data-composition-src="compositions/file.html"` to reference other HTML files
56
87
  6. Only deterministic logic — no `Date.now()`, no `Math.random()`, no network fetches
57
-
58
- ## Documentation
59
-
60
- Full docs: https://hyperframes.heygen.com/introduction
61
-
62
- Machine-readable index for AI tools: https://hyperframes.heygen.com/llms.txt
@@ -4,20 +4,22 @@
4
4
 
5
5
  **Always invoke the relevant skill before writing or modifying compositions.** Skills encode framework-specific patterns (e.g., `window.__timelines` registration, `data-*` attribute semantics, shader-compatible CSS rules) that are NOT in generic web docs. Skipping them produces broken compositions.
6
6
 
7
- | Skill | Command | When to use |
8
- | -------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------- |
9
- | **hyperframes** | `/hyperframes` | Creating or editing HTML compositions, captions, TTS, audio-reactive animation, marker highlights |
10
- | **hyperframes-cli** | `/hyperframes-cli` | Dev-loop CLI: init, lint, inspect, preview, render, doctor |
11
- | **hyperframes-media** | `/hyperframes-media` | Asset preprocessing: tts (Kokoro), transcribe (Whisper), remove-background (u2net) |
12
- | **hyperframes-registry** | `/hyperframes-registry` | Installing blocks and components via `hyperframes add` |
13
- | **website-to-hyperframes** | `/website-to-hyperframes` | Capturing a URL and turning it into a video full website-to-video pipeline |
14
- | **tailwind** | `/tailwind` | Tailwind v4 browser-runtime styles for projects created with `hyperframes init --tailwind` |
15
- | **gsap** | `/gsap` | GSAP animations for HyperFrames tweens, timelines, easing, performance |
16
- | **animejs** | `/animejs` | Anime.js animations registered on `window.__hfAnime` |
17
- | **css-animations** | `/css-animations` | CSS keyframes that HyperFrames can pause and seek |
18
- | **lottie** | `/lottie` | `lottie-web` and dotLottie players registered on `window.__hfLottie` |
19
- | **three** | `/three` | Three.js scenes rendered from HyperFrames `hf-seek` events |
20
- | **waapi** | `/waapi` | Web Animations API motion driven through `document.getAnimations()` |
7
+ **Doing anything with HyperFrames?** Start at `/hyperframes-read-first` — it tells you what HyperFrames can do and which skill or workflow handles your intent (make a video, TTS / BGM, prep footage, author / animate, render, install blocks), and routes every "make me a video" request to the right workflow. Read it first, especially when there's no project context to orient you. The video workflows it routes to:
8
+
9
+ - `/product-launch-video` — a **product** URL or brief / script 60-90s product launch / SaaS / promo video.
10
+ - `/website-to-video` a **general** website / URL a video _of_ the site (tour / showcase / social clip from captured visuals); a product **launch / promo** is `/product-launch-video`.
11
+ - `/faceless-explainer` arbitrary text (topic / article / notes), **no URL, no website capture** → 60-90s faceless explainer.
12
+ - `/embedded-captions` an existing talking-head video (MP4) → the same footage with captions / subtitles added (rail + embed, or pure-cinematic embed); the footage itself is untouched.
13
+ - `/graphic-overlays` — an existing talking-head / interview / podcast video (MP4) → the same footage **packaged with designed graphic overlays** (kinetic titles, lower-thirds, data callouts, pull-quotes, side panels, pip) synced to the transcript; the clip plays unchanged underneath. (Plain captions/subtitles → `/embedded-captions`.)
14
+ - `/pr-to-video` a GitHub PR (URL / `owner/repo#N` / "this PR") → 30-90s code-change explainer (changelog / feature reveal / fix / refactor).
15
+ - `/motion-graphics` a short (typically under 10s) design-led **motion graphic**, motion-is-the-message, no narration: kinetic type, a stat / number count-up, a chart, a logo sting, a lower-third / overlay, or an animated tweet / headline / captured-page highlight; rendered to MP4 or a transparent overlay. Longer / narrated / custom → `/general-video`.
16
+ - `/general-video` fallback for any other video (title card, longer brand / sizzle reel, multi-scene montage, static loop, custom composition); the original hyperframes authoring flow, any length.
17
+
18
+ **Porting an existing composition?** `/remotion-to-hyperframes` translates a Remotion (React) composition into HyperFrames HTML — a source migration, separate from the creation workflows above.
19
+
20
+ The domain skills (`/hyperframes-core`, `/hyperframes-animation`, `/hyperframes-creative`, `/hyperframes-cli`, `/hyperframes-media`, `/hyperframes-registry`) and the full capability map live inside `/hyperframes-read-first` — it is the single source of truth for which skill handles which intent.
21
+
22
+ > **Tailwind v4 projects** (`hyperframes init --tailwind`): see `/hyperframes-core` → `references/tailwind.md`.
21
23
 
22
24
  > **Skills not available?** Ask the user to run `npx hyperframes skills` and restart their
23
25
  > agent session, or install manually: `npx skills add heygen-com/hyperframes`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperframes",
3
- "version": "0.6.97",
3
+ "version": "0.6.98",
4
4
  "description": "HyperFrames CLI — create, preview, and render HTML video compositions",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,9 +17,10 @@
17
17
  "scripts": {
18
18
  "test": "vitest run",
19
19
  "dev": "tsx src/cli.ts",
20
- "build": "bun run build:fonts && tsup && bun run build:runtime && bun run build:copy",
20
+ "build": "bun run build:fonts && tsup && bun run build:runtime && bun run build:beat-analyzer && bun run build:copy",
21
21
  "build:fonts": "node scripts/build-fonts.mjs",
22
22
  "build:runtime": "tsx scripts/build-runtime.ts",
23
+ "build:beat-analyzer": "node scripts/build-beat-analyzer.mjs",
23
24
  "build:copy": "node scripts/build-copy.mjs",
24
25
  "typecheck": "tsc --noEmit"
25
26
  },
@@ -1,239 +0,0 @@
1
- import { createRequire as __hf_createRequire } from "node:module";
2
- import { fileURLToPath as __hf_fileURLToPath } from "node:url";
3
- import { dirname as __hf_dirname } from "node:path";
4
- var require = __hf_createRequire(import.meta.url);
5
- var __filename = __hf_fileURLToPath(import.meta.url);
6
- var __dirname = __hf_dirname(__filename);
7
-
8
- // ../producer/src/services/pngDecodeBlitWorker.ts
9
- import { parentPort } from "worker_threads";
10
-
11
- // ../engine/src/utils/alphaBlit.ts
12
- import { inflateSync } from "zlib";
13
- function paeth(a, b, c) {
14
- const p = a + b - c;
15
- const pa = Math.abs(p - a);
16
- const pb = Math.abs(p - b);
17
- const pc = Math.abs(p - c);
18
- if (pa <= pb && pa <= pc) return a;
19
- if (pb <= pc) return b;
20
- return c;
21
- }
22
- function decodePngRaw(buf, caller) {
23
- if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71 || buf[4] !== 13 || buf[5] !== 10 || buf[6] !== 26 || buf[7] !== 10) {
24
- throw new Error(`${caller}: not a PNG file`);
25
- }
26
- let pos = 8;
27
- let width = 0;
28
- let height = 0;
29
- let bitDepth = 0;
30
- let colorType = 0;
31
- let interlace = 0;
32
- let sawIhdr = false;
33
- const idatChunks = [];
34
- while (pos + 12 <= buf.length) {
35
- const chunkLen = buf.readUInt32BE(pos);
36
- const chunkType = buf.toString("ascii", pos + 4, pos + 8);
37
- const chunkData = buf.subarray(pos + 8, pos + 8 + chunkLen);
38
- if (chunkType === "IHDR") {
39
- width = chunkData.readUInt32BE(0);
40
- height = chunkData.readUInt32BE(4);
41
- bitDepth = chunkData[8] ?? 0;
42
- colorType = chunkData[9] ?? 0;
43
- interlace = chunkData[12] ?? 0;
44
- sawIhdr = true;
45
- } else if (chunkType === "IDAT") {
46
- idatChunks.push(Buffer.from(chunkData));
47
- } else if (chunkType === "IEND") {
48
- break;
49
- }
50
- pos += 12 + chunkLen;
51
- }
52
- if (!sawIhdr) {
53
- throw new Error(`${caller}: PNG missing IHDR chunk`);
54
- }
55
- if (colorType !== 2 && colorType !== 6) {
56
- throw new Error(`${caller}: unsupported color type ${colorType} (expected 2=RGB or 6=RGBA)`);
57
- }
58
- if (interlace !== 0) {
59
- throw new Error(
60
- `${caller}: Adam7-interlaced PNGs are not supported (interlace method ${interlace})`
61
- );
62
- }
63
- const channels = colorType === 6 ? 4 : 3;
64
- const bpp = channels * (bitDepth / 8);
65
- const stride = width * bpp;
66
- const compressed = Buffer.concat(idatChunks);
67
- const decompressed = inflateSync(compressed);
68
- const rawPixels = Buffer.allocUnsafe(height * stride);
69
- const prevRow = new Uint8Array(stride);
70
- const currRow = new Uint8Array(stride);
71
- let srcPos = 0;
72
- for (let y = 0; y < height; y++) {
73
- const filterType = decompressed[srcPos++] ?? 0;
74
- const rawRow = decompressed.subarray(srcPos, srcPos + stride);
75
- srcPos += stride;
76
- switch (filterType) {
77
- case 0:
78
- currRow.set(rawRow);
79
- break;
80
- case 1:
81
- for (let x = 0; x < stride; x++) {
82
- currRow[x] = (rawRow[x] ?? 0) + (x >= bpp ? currRow[x - bpp] ?? 0 : 0) & 255;
83
- }
84
- break;
85
- case 2:
86
- for (let x = 0; x < stride; x++) {
87
- currRow[x] = (rawRow[x] ?? 0) + (prevRow[x] ?? 0) & 255;
88
- }
89
- break;
90
- case 3:
91
- for (let x = 0; x < stride; x++) {
92
- const left = x >= bpp ? currRow[x - bpp] ?? 0 : 0;
93
- const up = prevRow[x] ?? 0;
94
- currRow[x] = (rawRow[x] ?? 0) + Math.floor((left + up) / 2) & 255;
95
- }
96
- break;
97
- case 4:
98
- for (let x = 0; x < stride; x++) {
99
- const left = x >= bpp ? currRow[x - bpp] ?? 0 : 0;
100
- const up = prevRow[x] ?? 0;
101
- const upLeft = x >= bpp ? prevRow[x - bpp] ?? 0 : 0;
102
- currRow[x] = (rawRow[x] ?? 0) + paeth(left, up, upLeft) & 255;
103
- }
104
- break;
105
- default:
106
- throw new Error(`${caller}: unknown filter type ${filterType} at row ${y}`);
107
- }
108
- rawPixels.set(currRow, y * stride);
109
- prevRow.set(currRow);
110
- }
111
- return { width, height, bitDepth, colorType, rawPixels };
112
- }
113
- function decodePng(buf) {
114
- const { width, height, bitDepth, colorType, rawPixels } = decodePngRaw(buf, "decodePng");
115
- if (bitDepth !== 8) {
116
- throw new Error(`decodePng: unsupported bit depth ${bitDepth} (expected 8)`);
117
- }
118
- const output = new Uint8Array(width * height * 4);
119
- if (colorType === 6) {
120
- output.set(rawPixels);
121
- } else {
122
- for (let i = 0; i < width * height; i++) {
123
- output[i * 4 + 0] = rawPixels[i * 3 + 0] ?? 0;
124
- output[i * 4 + 1] = rawPixels[i * 3 + 1] ?? 0;
125
- output[i * 4 + 2] = rawPixels[i * 3 + 2] ?? 0;
126
- output[i * 4 + 3] = 255;
127
- }
128
- }
129
- return { width, height, data: output };
130
- }
131
- function buildSrgbToSignalLut(transfer) {
132
- const lut = new Uint16Array(256);
133
- const hlgA = 0.17883277;
134
- const hlgB = 1 - 4 * hlgA;
135
- const hlgC = 0.5 - hlgA * Math.log(4 * hlgA);
136
- const pqM1 = 0.1593017578125;
137
- const pqM2 = 78.84375;
138
- const pqC1 = 0.8359375;
139
- const pqC2 = 18.8515625;
140
- const pqC3 = 18.6875;
141
- const pqMaxNits = 1e4;
142
- const sdrNits = 203;
143
- for (let i = 0; i < 256; i++) {
144
- if (transfer === "srgb") {
145
- lut[i] = i * 257;
146
- continue;
147
- }
148
- const v = i / 255;
149
- const linear = v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
150
- let signal;
151
- if (transfer === "hlg") {
152
- signal = linear <= 1 / 12 ? Math.sqrt(3 * linear) : hlgA * Math.log(12 * linear - hlgB) + hlgC;
153
- } else {
154
- const Lp = Math.max(0, linear * sdrNits / pqMaxNits);
155
- const Lm1 = Math.pow(Lp, pqM1);
156
- signal = Math.pow((pqC1 + pqC2 * Lm1) / (1 + pqC3 * Lm1), pqM2);
157
- }
158
- lut[i] = Math.min(65535, Math.round(signal * 65535));
159
- }
160
- return lut;
161
- }
162
- var SRGB_TO_SRGB_16 = buildSrgbToSignalLut("srgb");
163
- var SRGB_TO_HLG = buildSrgbToSignalLut("hlg");
164
- var SRGB_TO_PQ = buildSrgbToSignalLut("pq");
165
- function getSrgbToSignalLut(transfer) {
166
- if (transfer === "pq") return SRGB_TO_PQ;
167
- if (transfer === "hlg") return SRGB_TO_HLG;
168
- return SRGB_TO_SRGB_16;
169
- }
170
- function blitRgba8OverRgb48le(domRgba, canvas, width, height, transfer = "hlg") {
171
- const pixelCount = width * height;
172
- const lut = getSrgbToSignalLut(transfer);
173
- for (let i = 0; i < pixelCount; i++) {
174
- const da = domRgba[i * 4 + 3] ?? 0;
175
- if (da === 0) {
176
- continue;
177
- } else if (da === 255) {
178
- const r16 = lut[domRgba[i * 4 + 0] ?? 0] ?? 0;
179
- const g16 = lut[domRgba[i * 4 + 1] ?? 0] ?? 0;
180
- const b16 = lut[domRgba[i * 4 + 2] ?? 0] ?? 0;
181
- canvas.writeUInt16LE(r16, i * 6);
182
- canvas.writeUInt16LE(g16, i * 6 + 2);
183
- canvas.writeUInt16LE(b16, i * 6 + 4);
184
- } else {
185
- const alpha = da / 255;
186
- const invAlpha = 1 - alpha;
187
- const hdrR = (canvas[i * 6 + 0] ?? 0) | (canvas[i * 6 + 1] ?? 0) << 8;
188
- const hdrG = (canvas[i * 6 + 2] ?? 0) | (canvas[i * 6 + 3] ?? 0) << 8;
189
- const hdrB = (canvas[i * 6 + 4] ?? 0) | (canvas[i * 6 + 5] ?? 0) << 8;
190
- const domR = lut[domRgba[i * 4 + 0] ?? 0] ?? 0;
191
- const domG = lut[domRgba[i * 4 + 1] ?? 0] ?? 0;
192
- const domB = lut[domRgba[i * 4 + 2] ?? 0] ?? 0;
193
- canvas.writeUInt16LE(Math.round(domR * alpha + hdrR * invAlpha), i * 6);
194
- canvas.writeUInt16LE(Math.round(domG * alpha + hdrG * invAlpha), i * 6 + 2);
195
- canvas.writeUInt16LE(Math.round(domB * alpha + hdrB * invAlpha), i * 6 + 4);
196
- }
197
- }
198
- }
199
-
200
- // ../producer/src/services/pngDecodeBlitWorker.ts
201
- if (!parentPort) {
202
- console.warn("[pngDecodeBlitWorker] no parentPort; module loaded on main thread");
203
- } else {
204
- parentPort.on("message", (msg) => {
205
- const { png, pngOffset, pngLength, dest, destOffset, destLength, width, height, transfer } = msg;
206
- const pngBuf = Buffer.from(png, pngOffset, pngLength);
207
- const destBuf = Buffer.from(dest, destOffset, destLength);
208
- try {
209
- const decodeStart = Date.now();
210
- const { data: rgba } = decodePng(pngBuf);
211
- const decodeMs = Date.now() - decodeStart;
212
- const blitStart = Date.now();
213
- blitRgba8OverRgb48le(
214
- rgba,
215
- destBuf,
216
- width,
217
- height,
218
- transfer
219
- );
220
- const blitMs = Date.now() - blitStart;
221
- const reply = {
222
- ok: true,
223
- png,
224
- dest,
225
- decodeMs,
226
- blitMs
227
- };
228
- parentPort.postMessage(reply, [png, dest]);
229
- } catch (err) {
230
- const reply = {
231
- ok: false,
232
- error: err instanceof Error ? err.message : String(err),
233
- png,
234
- dest
235
- };
236
- parentPort.postMessage(reply, [png, dest]);
237
- }
238
- });
239
- }