chore: import upstream snapshot with attribution
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
Docs / Validate docs (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Sync skills to ClawHub / Publish changed skills (push) Waiting to run
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
Docs / Validate docs (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Sync skills to ClawHub / Publish changed skills (push) Waiting to run
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
---
|
||||
title: "@hyperframes/aws-lambda"
|
||||
description: "AWS Lambda and Step Functions adapter for distributed HyperFrames rendering."
|
||||
---
|
||||
|
||||
The AWS Lambda package runs HyperFrames' distributed render primitives on AWS. It ships the Lambda handler, a Node client SDK, and an optional CDK construct for provisioning the render stack inside an adopter's AWS account.
|
||||
|
||||
```bash
|
||||
npm install @hyperframes/aws-lambda
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
**Use `@hyperframes/aws-lambda` when you need to:**
|
||||
- Render large compositions on AWS Lambda instead of one local machine
|
||||
- Fan out `plan`, `renderChunk`, and `assemble` through Step Functions
|
||||
- Store render inputs, intermediate chunks, and outputs in S3
|
||||
- Drive renders from CI, a backend service, or a custom CLI
|
||||
- Provision the render topology from a CDK app
|
||||
|
||||
**Use a different package if you want to:**
|
||||
- Render locally or inside your own Node process - use the [CLI](/packages/cli) or [producer](/packages/producer)
|
||||
- Run the same distributed model on Google Cloud - use [gcp-cloud-run](/packages/gcp-cloud-run)
|
||||
- Build or edit composition HTML - use [studio](/packages/studio), [sdk](/packages/sdk), or [core](/packages/core)
|
||||
|
||||
<Warning>
|
||||
This package is deployment glue for AWS infrastructure. It assumes you control an AWS account, S3 bucket, Step Functions state machine, and Lambda runtime configuration.
|
||||
</Warning>
|
||||
|
||||
## Package Exports
|
||||
|
||||
| Import | Description |
|
||||
|--------|-------------|
|
||||
| `@hyperframes/aws-lambda` | Handler types, runtime helpers, S3 transport, and client SDK exports |
|
||||
| `@hyperframes/aws-lambda/handler` | Lambda handler entry point for Step Functions dispatch |
|
||||
| `@hyperframes/aws-lambda/sdk` | Lightweight Node SDK for deploying sites, starting renders, polling progress, and estimating cost |
|
||||
| `@hyperframes/aws-lambda/cdk` | Optional CDK construct for provisioning the render bucket, Lambda, and Step Functions stack |
|
||||
|
||||
`aws-cdk-lib` and `constructs` are optional peer dependencies. SDK-only consumers do not need them unless they import from `@hyperframes/aws-lambda/cdk`.
|
||||
|
||||
## Architecture
|
||||
|
||||
The Lambda adapter wraps the distributed producer primitives behind one dispatch boundary:
|
||||
|
||||
<Steps>
|
||||
<Step title="Plan">
|
||||
Downloads the project archive from S3, runs the producer planner, and uploads the plan directory.
|
||||
</Step>
|
||||
<Step title="Render chunks">
|
||||
Step Functions fans out chunk jobs. Each Lambda invocation downloads the plan assets, renders one chunk, and uploads the result.
|
||||
</Step>
|
||||
<Step title="Assemble">
|
||||
Downloads all rendered chunks plus audio assets, assembles the final output, and writes the finished file to S3.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
The handler uses `@sparticuz/chromium` by default, with a build-time fallback for bundling `chrome-headless-shell` directly when needed.
|
||||
|
||||
## Using the SDK
|
||||
|
||||
After deploying the AWS resources, use the SDK from Node:
|
||||
|
||||
```typescript
|
||||
import { deploySite, getRenderProgress, renderToLambda } from "@hyperframes/aws-lambda/sdk";
|
||||
|
||||
const site = await deploySite({
|
||||
projectDir: "./my-composition",
|
||||
bucketName: "hyperframes-render-bucket",
|
||||
});
|
||||
|
||||
const handle = await renderToLambda({
|
||||
siteHandle: site,
|
||||
bucketName: site.bucketName,
|
||||
stateMachineArn: "arn:aws:states:us-east-1:123456789012:stateMachine:hyperframes-render",
|
||||
config: {
|
||||
fps: 30,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
format: "mp4",
|
||||
chunkSize: 240,
|
||||
maxParallelChunks: 16,
|
||||
runtimeCap: "lambda",
|
||||
},
|
||||
});
|
||||
|
||||
const progress = await getRenderProgress({ executionArn: handle.executionArn });
|
||||
console.log(progress.status, progress.overallProgress, progress.costs.displayCost);
|
||||
```
|
||||
|
||||
`renderToLambda()` validates the distributed render config before starting the Step Functions execution, so invalid dimensions, formats, chunk sizes, or payload sizes fail synchronously.
|
||||
|
||||
## Using the CDK Construct
|
||||
|
||||
```typescript
|
||||
import { App, Stack } from "aws-cdk-lib";
|
||||
import { HyperframesRenderStack } from "@hyperframes/aws-lambda/cdk";
|
||||
|
||||
const app = new App();
|
||||
const stack = new Stack(app, "VideoRender");
|
||||
|
||||
const render = new HyperframesRenderStack(stack, "HyperframesRender", {
|
||||
// reservedConcurrency: 8,
|
||||
// lambdaMemoryMb: 10240,
|
||||
// chromeSource: "sparticuz",
|
||||
});
|
||||
|
||||
console.log(render.bucket.bucketName, render.stateMachine.stateMachineArn);
|
||||
```
|
||||
|
||||
## Building the Handler ZIP
|
||||
|
||||
From the monorepo:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
bun run --cwd packages/aws-lambda build:zip
|
||||
bun run --cwd packages/aws-lambda verify:zip-size
|
||||
```
|
||||
|
||||
The build stages Chromium, Puppeteer, FFmpeg, and the handler bundle into `packages/aws-lambda/dist/handler.zip`. The size verifier keeps the unzipped artifact below Lambda's deployment limit.
|
||||
|
||||
## Related Guides
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="AWS Lambda Deployment" icon="cloud" href="/deploy/aws-lambda">
|
||||
End-to-end deployment details and operational notes.
|
||||
</Card>
|
||||
<Card title="@hyperframes/producer" icon="video" href="/packages/producer">
|
||||
The distributed primitives that the Lambda handler executes.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,453 @@
|
||||
---
|
||||
title: "@hyperframes/core"
|
||||
description: "Types, HTML generation, runtime, and linter — the foundation every other package depends on."
|
||||
---
|
||||
|
||||
The core package provides the foundational types, HTML parsing/generation, runtime, and composition linter that all other Hyperframes packages build on. If you are building tooling, writing a custom integration, or extending Hyperframes itself, this is the package you need.
|
||||
|
||||
```bash
|
||||
npm install @hyperframes/core
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
<Tip>
|
||||
**Most users do not need to install `@hyperframes/core` directly.** The [CLI](/packages/cli), [producer](/packages/producer), and [studio](/packages/studio) packages all depend on core internally. You only need it if you are doing one of the things listed below.
|
||||
</Tip>
|
||||
|
||||
**Use `@hyperframes/core` when you need to:**
|
||||
- Lint compositions programmatically (CI pipelines, editor plugins)
|
||||
- Parse HTML compositions into structured TypeScript objects
|
||||
- Generate composition HTML from data (e.g., from an API or AI agent)
|
||||
- Access the Hyperframes type system for your own tooling
|
||||
- Embed the Hyperframes runtime in a custom player
|
||||
|
||||
**Use a different package if you want to:**
|
||||
- Preview compositions in the browser — use the [CLI](/packages/cli) (`npx hyperframes preview`) or [studio](/packages/studio)
|
||||
- Render compositions to MP4 — use the [CLI](/packages/cli) (`npx hyperframes render`) or [producer](/packages/producer)
|
||||
- Capture frames from a headless browser — use the [engine](/packages/engine)
|
||||
|
||||
## Package Exports
|
||||
|
||||
The core package has four entry points:
|
||||
|
||||
| Import | Description |
|
||||
|--------|-------------|
|
||||
| `@hyperframes/core` | Types, parsers, generators, adapters, runtime utilities |
|
||||
| `@hyperframes/core/lint` | Composition linter |
|
||||
| `@hyperframes/core/compiler` | Timing compiler, HTML compiler, bundler, static guard |
|
||||
| `@hyperframes/core/runtime` | Pre-built IIFE runtime for browser injection |
|
||||
|
||||
## Types
|
||||
|
||||
The core type system models compositions, timeline elements, and variables:
|
||||
|
||||
```typescript
|
||||
import type {
|
||||
TimelineElement,
|
||||
TimelineMediaElement,
|
||||
TimelineTextElement,
|
||||
TimelineCompositionElement,
|
||||
TimelineElementType, // "video" | "image" | "text" | "audio" | "composition"
|
||||
CompositionSpec,
|
||||
CompositionVariable,
|
||||
CanvasResolution, // "landscape" | "portrait" | "landscape-4k" | "portrait-4k" | "square" | "square-4k"
|
||||
Orientation, // "16:9" | "9:16"
|
||||
FrameAdapter,
|
||||
FrameAdapterContext,
|
||||
} from '@hyperframes/core';
|
||||
|
||||
// Type guards
|
||||
import {
|
||||
isTextElement,
|
||||
isMediaElement,
|
||||
isCompositionElement,
|
||||
} from '@hyperframes/core';
|
||||
|
||||
// Constants
|
||||
import {
|
||||
CANVAS_DIMENSIONS, // { landscape: { width, height }, portrait: { width, height } }
|
||||
TIMELINE_COLORS,
|
||||
DEFAULT_DURATIONS,
|
||||
} from '@hyperframes/core';
|
||||
```
|
||||
|
||||
### Variable Types
|
||||
|
||||
Compositions can expose typed variables for dynamic content:
|
||||
|
||||
```typescript
|
||||
import type {
|
||||
CompositionVariableType, // "string" | "number" | "color" | "boolean" | "enum"
|
||||
StringVariable,
|
||||
NumberVariable,
|
||||
ColorVariable,
|
||||
BooleanVariable,
|
||||
EnumVariable,
|
||||
} from '@hyperframes/core';
|
||||
```
|
||||
|
||||
### Keyframe Types
|
||||
|
||||
```typescript
|
||||
import type {
|
||||
Keyframe,
|
||||
KeyframeProperties,
|
||||
ElementKeyframes,
|
||||
StageZoom,
|
||||
StageZoomKeyframe,
|
||||
} from '@hyperframes/core';
|
||||
|
||||
import { getDefaultStageZoom } from '@hyperframes/core';
|
||||
```
|
||||
|
||||
## Parsing and Generating HTML
|
||||
|
||||
Round-trip between HTML and structured data:
|
||||
|
||||
```typescript
|
||||
import { parseHtml, generateHyperframesHtml } from '@hyperframes/core';
|
||||
import type { ParsedHtml, CompositionMetadata } from '@hyperframes/core';
|
||||
|
||||
// Parse HTML into structured data
|
||||
const parsed: ParsedHtml = parseHtml(htmlString);
|
||||
// parsed.elements, parsed.gsapScript, parsed.styles, parsed.resolution, parsed.keyframes
|
||||
|
||||
// Extract composition metadata
|
||||
import { extractCompositionMetadata } from '@hyperframes/core';
|
||||
const meta: CompositionMetadata = extractCompositionMetadata(htmlString);
|
||||
// meta.id, meta.duration, meta.width, meta.height, meta.variables
|
||||
//
|
||||
// Variable metadata is declared on the document root, for example:
|
||||
// <html
|
||||
// data-composition-id="card"
|
||||
// data-composition-duration="3"
|
||||
// data-composition-variables='[{"id":"title","label":"Title","type":"string","default":"Hello"}]'
|
||||
// >
|
||||
|
||||
// Read resolved variables inside a composition (declared defaults +
|
||||
// CLI overrides + per-instance host data-variable-values):
|
||||
import { getVariables } from '@hyperframes/core';
|
||||
const { title } = getVariables<{ title: string }>();
|
||||
|
||||
// Validate CLI / host overrides against the declared schema:
|
||||
import { validateVariables, formatVariableValidationIssue } from '@hyperframes/core';
|
||||
const issues = validateVariables({ title: 'Hello', count: 'three' }, meta.variables);
|
||||
for (const issue of issues) {
|
||||
console.warn(formatVariableValidationIssue(issue));
|
||||
}
|
||||
|
||||
// Generate HTML from structured data
|
||||
const html = generateHyperframesHtml(elements, {
|
||||
animations,
|
||||
styles,
|
||||
resolution: 'landscape',
|
||||
compositionId: 'my-video',
|
||||
});
|
||||
```
|
||||
|
||||
### Modifying HTML
|
||||
|
||||
```typescript
|
||||
import {
|
||||
updateElementInHtml,
|
||||
addElementToHtml,
|
||||
removeElementFromHtml,
|
||||
validateCompositionHtml,
|
||||
} from '@hyperframes/core';
|
||||
|
||||
// Update an element's properties
|
||||
const updatedHtml = updateElementInHtml(html, 'el-1', { start: 5 });
|
||||
|
||||
// Add a new element
|
||||
const newHtml = addElementToHtml(html, newElement);
|
||||
|
||||
// Remove an element
|
||||
const cleanHtml = removeElementFromHtml(html, 'el-1');
|
||||
|
||||
// Validate HTML structure
|
||||
const result = validateCompositionHtml(html);
|
||||
// result.valid, result.errors
|
||||
```
|
||||
|
||||
### GSAP Script Parsing
|
||||
|
||||
<Info>
|
||||
The GSAP + HTML parsing layer now lives in its own standalone package, [`@hyperframes/parsers`](/packages/parsers). Core re-exports the API below for back-compat; import from `@hyperframes/parsers` directly in new code.
|
||||
</Info>
|
||||
|
||||
```typescript
|
||||
import {
|
||||
serializeGsapAnimations,
|
||||
getAnimationsForElementId,
|
||||
validateCompositionGsap,
|
||||
keyframesToGsapAnimations,
|
||||
gsapAnimationsToKeyframes,
|
||||
} from '@hyperframes/core';
|
||||
|
||||
// GSAP parsing, mutation, and constants live in @hyperframes/parsers:
|
||||
import { parseGsapScript, SUPPORTED_PROPS, SUPPORTED_EASES } from '@hyperframes/parsers/gsap-parser';
|
||||
import { updateAnimationInScript, addAnimationToScript, removeAnimationFromScript } from '@hyperframes/parsers/gsap-writer-acorn';
|
||||
import type { GsapAnimation, GsapMethod, ParsedGsap } from '@hyperframes/core';
|
||||
|
||||
// Parse GSAP script into structured animations
|
||||
const parsed: ParsedGsap = parseGsapScript(scriptContent);
|
||||
// parsed.animations, parsed.timelineVar, parsed.preamble, parsed.postamble
|
||||
|
||||
// Serialize back to script
|
||||
const script = serializeGsapAnimations(parsed.animations);
|
||||
```
|
||||
|
||||
### HTML Generation
|
||||
|
||||
```typescript
|
||||
import {
|
||||
generateHyperframesHtml,
|
||||
generateGsapTimelineScript,
|
||||
generateHyperframesStyles,
|
||||
} from '@hyperframes/core';
|
||||
|
||||
// Generate a complete HTML composition
|
||||
const html = generateHyperframesHtml(elements, options);
|
||||
|
||||
// Generate just the GSAP script
|
||||
const script = generateGsapTimelineScript(animations, options);
|
||||
|
||||
// Generate CSS styles
|
||||
const { coreCss, customCss, googleFontsLink } = generateHyperframesStyles(
|
||||
elements, 'landscape', customStyles
|
||||
);
|
||||
```
|
||||
|
||||
### Template Utilities
|
||||
|
||||
```typescript
|
||||
import {
|
||||
generateBaseHtml,
|
||||
getStageStyles,
|
||||
GSAP_CDN,
|
||||
BASE_STYLES,
|
||||
ELEMENT_BASE_STYLES,
|
||||
MEDIA_STYLES,
|
||||
TEXT_STYLES,
|
||||
ZOOM_CONTAINER_STYLES,
|
||||
} from '@hyperframes/core';
|
||||
|
||||
// Generate base HTML structure for a resolution
|
||||
const baseHtml = generateBaseHtml('landscape');
|
||||
const styles = getStageStyles('portrait');
|
||||
```
|
||||
|
||||
## Linter
|
||||
|
||||
<Info>
|
||||
The composition linter now lives in its own package, [`@hyperframes/lint`](/packages/lint) — install it directly to lint a project or HTML string from Node without the CLI. `@hyperframes/core/lint` remains a back-compat re-export.
|
||||
</Info>
|
||||
|
||||
The composition linter checks for structural issues that would cause rendering failures or unexpected behavior. You can run it from the CLI with `npx hyperframes lint`, or call it programmatically:
|
||||
|
||||
```typescript
|
||||
import { lintHyperframeHtml, lintMediaUrls } from '@hyperframes/core/lint';
|
||||
import type {
|
||||
HyperframeLintResult,
|
||||
HyperframeLintFinding,
|
||||
HyperframeLintSeverity, // "error" | "warning" | "info"
|
||||
HyperframeLinterOptions,
|
||||
} from '@hyperframes/core/lint';
|
||||
|
||||
const result: HyperframeLintResult = lintHyperframeHtml(html, { filePath: 'index.html' });
|
||||
// result.ok, result.errorCount, result.warningCount, result.findings
|
||||
|
||||
for (const finding of result.findings) {
|
||||
console.log(finding.severity, finding.code, finding.message);
|
||||
// finding.file, finding.selector, finding.elementId, finding.fixHint, finding.snippet
|
||||
}
|
||||
|
||||
// Additional media URL validation
|
||||
const mediaFindings = lintMediaUrls(result.findings);
|
||||
```
|
||||
|
||||
Detected issues include:
|
||||
|
||||
- Missing timeline registration (`window.__timelines`)
|
||||
- Unmuted video elements (causes autoplay failures)
|
||||
- Missing `class="clip"` on timed visible elements
|
||||
- Deprecated attribute names
|
||||
- Missing composition dimensions (`data-width`, `data-height`)
|
||||
- Invalid `data-start` references to nonexistent clip IDs
|
||||
|
||||
<Info>
|
||||
For a full list of what the linter catches and how to fix each issue, see [Common Mistakes](/guides/common-mistakes) and [Troubleshooting](/guides/troubleshooting).
|
||||
</Info>
|
||||
|
||||
## Compiler
|
||||
|
||||
The compiler sub-package handles timing resolution, HTML compilation, and bundling:
|
||||
|
||||
```typescript
|
||||
// Timing compiler (browser-safe — no Node.js dependencies)
|
||||
import {
|
||||
compileTimingAttrs,
|
||||
injectDurations,
|
||||
extractResolvedMedia,
|
||||
clampDurations,
|
||||
} from '@hyperframes/core/compiler';
|
||||
import type {
|
||||
UnresolvedElement,
|
||||
ResolvedDuration,
|
||||
ResolvedMediaElement,
|
||||
CompilationResult,
|
||||
} from '@hyperframes/core/compiler';
|
||||
|
||||
// Compile timing attributes from HTML
|
||||
const compiled: CompilationResult = compileTimingAttrs(html);
|
||||
|
||||
// Inject resolved durations back into HTML
|
||||
const updatedHtml = injectDurations(html, compiled.durations);
|
||||
|
||||
// Extract resolved media elements
|
||||
const media: ResolvedMediaElement[] = extractResolvedMedia(html);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// HTML compiler (Node.js — requires media probing)
|
||||
import { compileHtml } from '@hyperframes/core/compiler';
|
||||
import type { MediaDurationProber } from '@hyperframes/core/compiler';
|
||||
|
||||
const prober: MediaDurationProber = async (src) => getDuration(src);
|
||||
const compiledHtml = await compileHtml(html, prober);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// HTML bundler (Node.js — bundles to single file)
|
||||
import { bundleToSingleHtml } from '@hyperframes/core/compiler';
|
||||
import type { BundleOptions } from '@hyperframes/core/compiler';
|
||||
|
||||
const bundled = await bundleToSingleHtml({ entryPath: './index.html', inline: true });
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Static guard — validate HTML contract
|
||||
import { validateHyperframeHtmlContract } from '@hyperframes/core/compiler';
|
||||
import type {
|
||||
HyperframeStaticGuardResult,
|
||||
HyperframeStaticFailureReason,
|
||||
} from '@hyperframes/core/compiler';
|
||||
|
||||
const guard: HyperframeStaticGuardResult = validateHyperframeHtmlContract(html);
|
||||
// guard.ok, guard.failures[]
|
||||
// Failure reasons: "missing_composition_id" | "missing_composition_dimensions"
|
||||
// | "missing_timeline_registry" | "invalid_script_syntax"
|
||||
// | "invalid_static_hyperframe_contract"
|
||||
```
|
||||
|
||||
## Runtime
|
||||
|
||||
The Hyperframes runtime manages playback, seeking, and clip lifecycle in the browser. The core package provides utilities for building and loading the runtime:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
loadHyperframeRuntimeSource,
|
||||
buildHyperframesRuntimeScript,
|
||||
HYPERFRAME_RUNTIME_ARTIFACTS,
|
||||
HYPERFRAME_RUNTIME_CONTRACT,
|
||||
HYPERFRAME_RUNTIME_GLOBALS,
|
||||
HYPERFRAME_BRIDGE_SOURCES,
|
||||
HYPERFRAME_CONTROL_ACTIONS,
|
||||
} from '@hyperframes/core';
|
||||
import type {
|
||||
HyperframeControlAction,
|
||||
HyperframesRuntimeBuildOptions,
|
||||
} from '@hyperframes/core';
|
||||
|
||||
// Load the pre-built runtime IIFE
|
||||
const runtimeSource = loadHyperframeRuntimeSource();
|
||||
|
||||
// Build a custom runtime script
|
||||
const script = buildHyperframesRuntimeScript(options);
|
||||
```
|
||||
|
||||
The pre-built runtime IIFE is available as a direct import:
|
||||
|
||||
```typescript
|
||||
import runtime from '@hyperframes/core/runtime';
|
||||
```
|
||||
|
||||
## Frame Adapters
|
||||
|
||||
The core package defines the [Frame Adapter](/concepts/frame-adapters) interface and provides the built-in GSAP adapter:
|
||||
|
||||
```typescript
|
||||
import { createGSAPFrameAdapter } from '@hyperframes/core';
|
||||
import type {
|
||||
FrameAdapter,
|
||||
FrameAdapterContext,
|
||||
GSAPTimelineLike,
|
||||
CreateGSAPFrameAdapterOptions,
|
||||
} from '@hyperframes/core';
|
||||
|
||||
// Create a GSAP frame adapter
|
||||
const adapter: FrameAdapter = createGSAPFrameAdapter({
|
||||
id: 'my-composition',
|
||||
fps: 30,
|
||||
timeline: gsapTimeline,
|
||||
});
|
||||
|
||||
// Adapter lifecycle
|
||||
await adapter.init?.(context);
|
||||
const durationFrames = adapter.getDurationFrames();
|
||||
await adapter.seekFrame(42);
|
||||
await adapter.destroy?.();
|
||||
```
|
||||
|
||||
## Media Utilities
|
||||
|
||||
```typescript
|
||||
import {
|
||||
MEDIA_VISUAL_STYLE_PROPERTIES,
|
||||
copyMediaVisualStyles,
|
||||
quantizeTimeToFrame,
|
||||
} from '@hyperframes/core';
|
||||
import type { MediaVisualStyleProperty } from '@hyperframes/core';
|
||||
|
||||
// Quantize a time value to the nearest frame boundary
|
||||
const frameTime = quantizeTimeToFrame(5.033, 30); // → 5.033... snapped to frame
|
||||
|
||||
// Copy visual styles between media elements
|
||||
copyMediaVisualStyles(fromElement, toElement);
|
||||
```
|
||||
|
||||
## Picker API
|
||||
|
||||
For element selection in editor UIs:
|
||||
|
||||
```typescript
|
||||
import type {
|
||||
HyperframePickerApi,
|
||||
HyperframePickerBoundingBox,
|
||||
HyperframePickerElementInfo,
|
||||
} from '@hyperframes/core';
|
||||
```
|
||||
|
||||
## Related Packages
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="@hyperframes/parsers" icon="code" href="/packages/parsers">
|
||||
The standalone GSAP + HTML parsing layer extracted from core.
|
||||
</Card>
|
||||
<Card title="@hyperframes/lint" icon="circle-check" href="/packages/lint">
|
||||
The composition linter as a standalone library.
|
||||
</Card>
|
||||
<Card title="@hyperframes/studio-server" icon="server" href="/packages/studio-server">
|
||||
The mountable studio preview/editor backend.
|
||||
</Card>
|
||||
<Card title="CLI" icon="terminal" href="/packages/cli">
|
||||
The easiest way to create, preview, lint, and render compositions.
|
||||
</Card>
|
||||
<Card title="Engine" icon="gear" href="/packages/engine">
|
||||
Low-level frame capture pipeline that uses core types and runtime.
|
||||
</Card>
|
||||
<Card title="Producer" icon="film" href="/packages/producer">
|
||||
Full rendering pipeline built on top of core and engine.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,417 @@
|
||||
---
|
||||
title: "@hyperframes/engine"
|
||||
description: "Seekable page-to-video capture engine using Chrome's BeginFrame API."
|
||||
---
|
||||
|
||||
The engine package provides the low-level video capture pipeline: it loads an HTML page in headless Chrome, seeks to each frame independently, and captures pixel buffers using Chrome's `HeadlessExperimental.beginFrame` API. This is the layer that makes Hyperframes rendering deterministic.
|
||||
|
||||
```bash
|
||||
npm install @hyperframes/engine
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
<Warning>
|
||||
**Most users should NOT use the engine directly.** Use the [CLI](/packages/cli) (`npx hyperframes render`) or the [producer](/packages/producer) package instead — they handle runtime injection, audio mixing, and encoding for you.
|
||||
</Warning>
|
||||
|
||||
**Use `@hyperframes/engine` when you need to:**
|
||||
- Build a custom rendering pipeline with full control over frame capture
|
||||
- Integrate Hyperframes capture into an existing video processing system
|
||||
- Capture individual frames (e.g., for thumbnails or sprite sheets) without encoding to video
|
||||
- Implement a custom encoding backend (not FFmpeg)
|
||||
|
||||
**Use a different package if you want to:**
|
||||
- Render an HTML composition to a finished MP4 or WebM — use the [producer](/packages/producer) or [CLI](/packages/cli)
|
||||
- Preview compositions in the browser — use the [CLI](/packages/cli) or [studio](/packages/studio)
|
||||
- Lint or parse composition HTML — use [core](/packages/core)
|
||||
|
||||
## How It Works
|
||||
|
||||
The engine implements a **seek-and-capture** loop that is fundamentally different from screen recording:
|
||||
|
||||
<Steps>
|
||||
<Step title="Launch headless Chrome">
|
||||
The engine starts `chrome-headless-shell`, a minimal headless Chrome binary optimized for programmatic control via the Chrome DevTools Protocol (CDP).
|
||||
</Step>
|
||||
<Step title="Load the composition">
|
||||
Your HTML composition is loaded into a browser page. The Hyperframes runtime is injected to manage timeline seeking.
|
||||
</Step>
|
||||
<Step title="Seek to each frame">
|
||||
For every frame in the video (e.g., 900 frames for a 30-second video at 30fps), the engine calls `window.__hf.seek(time)` to advance the composition to the exact timestamp. No wall clock is involved — each frame is independently positioned.
|
||||
</Step>
|
||||
<Step title="Capture via BeginFrame">
|
||||
Chrome's `HeadlessExperimental.beginFrame` API captures the compositor output as a pixel buffer. This produces pixel-perfect frames without any screen recording artifacts.
|
||||
</Step>
|
||||
<Step title="Hand off frames">
|
||||
Captured frame buffers are passed to a consumer — typically FFmpeg (via the producer) for encoding into MP4, but you can provide your own consumer.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
This approach guarantees [deterministic rendering](/concepts/determinism): the same HTML always produces the identical video, regardless of system load or timing.
|
||||
|
||||
## Configuration
|
||||
|
||||
```typescript
|
||||
import { resolveConfig, DEFAULT_CONFIG } from '@hyperframes/engine';
|
||||
import type { EngineConfig } from '@hyperframes/engine';
|
||||
|
||||
// Use defaults
|
||||
const config = DEFAULT_CONFIG;
|
||||
|
||||
// Or resolve with overrides
|
||||
const config = resolveConfig({
|
||||
// ... custom options
|
||||
});
|
||||
```
|
||||
|
||||
### Quality Presets
|
||||
|
||||
| Preset | Use Case | Speed |
|
||||
|--------|----------|-------|
|
||||
| `draft` | Fast iteration during development | Fastest |
|
||||
| `standard` | Production renders with good quality/speed balance | Moderate |
|
||||
| `high` | Final delivery, maximum quality | Slowest |
|
||||
|
||||
### FPS Options
|
||||
|
||||
| FPS | Use Case |
|
||||
|-----|----------|
|
||||
| `24` | Cinematic look, smaller file size |
|
||||
| `30` | Standard web video, good balance |
|
||||
| `60` | Smooth motion, UI animations, screen recordings |
|
||||
|
||||
## Programmatic Usage
|
||||
|
||||
The engine uses a session-based API for frame capture:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
createCaptureSession,
|
||||
initializeSession,
|
||||
captureFrame,
|
||||
captureFrameToBuffer,
|
||||
getCompositionDuration,
|
||||
closeCaptureSession,
|
||||
} from '@hyperframes/engine';
|
||||
|
||||
// 1. Create a capture session (serverUrl, outputDir, options)
|
||||
const session = await createCaptureSession(serverUrl, outputDir, {
|
||||
fps: { num: 30, den: 1 }, width: 1920, height: 1080,
|
||||
});
|
||||
|
||||
// 2. Initialize the session
|
||||
await initializeSession(session);
|
||||
|
||||
// 3. Get the total duration (async)
|
||||
const duration = await getCompositionDuration(session);
|
||||
|
||||
// 4. Capture frames
|
||||
const totalFrames = Math.ceil(duration * 30);
|
||||
for (let i = 0; i < totalFrames; i++) {
|
||||
// Capture to disk
|
||||
const result = await captureFrame(session, i);
|
||||
// result.path, result.captureTimeMs
|
||||
|
||||
// Or capture to buffer (in-memory)
|
||||
const bufResult = await captureFrameToBuffer(session, i);
|
||||
// bufResult.buffer, bufResult.captureTimeMs
|
||||
}
|
||||
|
||||
// 5. Clean up
|
||||
await closeCaptureSession(session);
|
||||
```
|
||||
|
||||
### Browser Management
|
||||
|
||||
```typescript
|
||||
import {
|
||||
acquireBrowser,
|
||||
releaseBrowser,
|
||||
resolveHeadlessShellPath,
|
||||
buildChromeArgs,
|
||||
} from '@hyperframes/engine';
|
||||
|
||||
// Acquire a browser instance (creates or reuses from pool)
|
||||
const browser = await acquireBrowser();
|
||||
|
||||
// Get the Chrome binary path
|
||||
const chromePath = await resolveHeadlessShellPath();
|
||||
|
||||
// Release when done
|
||||
await releaseBrowser(browser);
|
||||
```
|
||||
|
||||
### Encoding
|
||||
|
||||
The engine includes FFmpeg encoding utilities with support for MP4 (h264) and WebM (VP9 with alpha):
|
||||
|
||||
```typescript
|
||||
import {
|
||||
encodeFramesFromDir,
|
||||
muxVideoWithAudio,
|
||||
applyFaststart,
|
||||
detectGpuEncoder,
|
||||
getEncoderPreset,
|
||||
ENCODER_PRESETS,
|
||||
} from '@hyperframes/engine';
|
||||
|
||||
// Get format-aware encoder settings
|
||||
const mp4Preset = getEncoderPreset('standard', 'mp4');
|
||||
// { codec: "h264", pixelFormat: "yuv420p", preset: "medium", quality: 23 }
|
||||
|
||||
const webmPreset = getEncoderPreset('standard', 'webm');
|
||||
// { codec: "vp9", pixelFormat: "yuva420p", preset: "good", quality: 23 }
|
||||
|
||||
// Encode captured frames to video
|
||||
await encodeFramesFromDir(framesDir, 'frame_%06d.png', outputPath, {
|
||||
fps: { num: 30, den: 1 },
|
||||
...webmPreset,
|
||||
});
|
||||
|
||||
// Mix video with audio (uses Opus for WebM, AAC for MP4)
|
||||
await muxVideoWithAudio(videoPath, audioPath, outputPath);
|
||||
|
||||
// Apply MP4 faststart for streaming (no-op for WebM)
|
||||
await applyFaststart(inputPath, outputPath);
|
||||
|
||||
// Detect GPU encoding support
|
||||
const gpu = await detectGpuEncoder();
|
||||
// gpu: "nvenc" | "videotoolbox" | "vaapi" | "qsv" | "amf" | null
|
||||
```
|
||||
|
||||
#### WebM with VP9 Alpha
|
||||
|
||||
When encoding for transparency, use `format: "webm"` with `getEncoderPreset()`. This configures:
|
||||
- **VP9 codec** (`libvpx-vp9`) with alpha-capable `yuva420p` pixel format
|
||||
- **`-auto-alt-ref 0`** and **`alpha_mode=1`** metadata for proper alpha encoding
|
||||
- **`-row-mt 1`** for multi-threaded VP9 encoding
|
||||
- **Opus audio** in the mux step (instead of AAC for MP4)
|
||||
|
||||
### Streaming Encoder
|
||||
|
||||
For memory-efficient encoding without writing frames to disk:
|
||||
|
||||
```typescript
|
||||
import { spawnStreamingEncoder } from '@hyperframes/engine';
|
||||
|
||||
const encoder = await spawnStreamingEncoder({
|
||||
outputPath: './output.mp4',
|
||||
fps: { num: 30, den: 1 },
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
|
||||
// Feed frames directly to encoder
|
||||
encoder.writeFrame(frameBuffer);
|
||||
// ...
|
||||
const result = await encoder.finalize();
|
||||
```
|
||||
|
||||
### Video Frame Extraction
|
||||
|
||||
Extract frames from source video files for injection into the browser:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
parseVideoElements,
|
||||
extractAllVideoFrames,
|
||||
getFrameAtTime,
|
||||
createFrameLookupTable,
|
||||
FrameLookupTable,
|
||||
} from '@hyperframes/engine';
|
||||
|
||||
// Parse video elements from HTML
|
||||
const videos = parseVideoElements(html);
|
||||
|
||||
// Extract all frames from a video
|
||||
const frames = await extractAllVideoFrames(videoPath, { fps: 30 });
|
||||
|
||||
// Create a lookup table for fast frame access
|
||||
const lookup = createFrameLookupTable(frames);
|
||||
const frame = lookup.getFrame('video-1', 5.0);
|
||||
```
|
||||
|
||||
### Audio Processing
|
||||
|
||||
```typescript
|
||||
import { parseAudioElements, processCompositionAudio } from '@hyperframes/engine';
|
||||
|
||||
// Parse audio elements from HTML
|
||||
const audioElements = parseAudioElements(html);
|
||||
|
||||
// Process and mix all audio tracks
|
||||
const mixResult = await processCompositionAudio({ audioElements, duration, fps });
|
||||
```
|
||||
|
||||
### Parallel Rendering
|
||||
|
||||
```typescript
|
||||
import {
|
||||
calculateOptimalWorkers,
|
||||
distributeFrames,
|
||||
executeParallelCapture,
|
||||
getSystemResources,
|
||||
} from '@hyperframes/engine';
|
||||
|
||||
// Check system resources
|
||||
const resources = getSystemResources();
|
||||
|
||||
// Calculate optimal worker count
|
||||
const workers = calculateOptimalWorkers(totalFrames);
|
||||
|
||||
// Distribute frames across workers
|
||||
const tasks = distributeFrames(totalFrames, workers);
|
||||
|
||||
// Execute parallel capture
|
||||
const results = await executeParallelCapture(tasks);
|
||||
```
|
||||
|
||||
### File Server
|
||||
|
||||
Serve composition files over HTTP for the browser to load:
|
||||
|
||||
```typescript
|
||||
import { createFileServer } from '@hyperframes/engine';
|
||||
|
||||
const server = await createFileServer({ root: './my-video', port: 0 });
|
||||
// server.url, server.port
|
||||
// ... use server.url as the composition URL
|
||||
await server.close();
|
||||
```
|
||||
|
||||
## HDR APIs
|
||||
|
||||
The engine exports two layers of HDR support: **color-space utilities** that classify sources and configure the FFmpeg encoder, and a **WebGPU readback runtime** for capturing CSS-animated DOM directly into HDR.
|
||||
|
||||
For end-to-end HDR rendering (HDR video and image sources composited into an HDR10 MP4) use the [producer](/packages/producer) or the CLI render pipeline with HDR auto-detect / `--hdr` / `--sdr` — see [HDR Rendering](/guides/hdr). The APIs below are for custom integrations.
|
||||
|
||||
### Color space utilities
|
||||
|
||||
```typescript
|
||||
import {
|
||||
isHdrColorSpace,
|
||||
detectTransfer,
|
||||
analyzeCompositionHdr,
|
||||
getHdrEncoderColorParams,
|
||||
DEFAULT_HDR10_MASTERING,
|
||||
} from '@hyperframes/engine';
|
||||
import type { HdrTransfer, HdrEncoderColorParams, HdrMasteringMetadata } from '@hyperframes/engine';
|
||||
|
||||
// Classify a single source from its ffprobe color space
|
||||
isHdrColorSpace(colorSpace); // boolean — true for BT.2020 / PQ / HLG
|
||||
detectTransfer(colorSpace); // 'pq' | 'hlg' (gate on isHdrColorSpace first)
|
||||
|
||||
// Pick the dominant transfer across many sources
|
||||
analyzeCompositionHdr([cs1, cs2]); // { hasHdr, dominantTransfer: 'pq' | 'hlg' | null }
|
||||
|
||||
// Build the FFmpeg color params + HDR10 static metadata for x265
|
||||
const params = getHdrEncoderColorParams('pq');
|
||||
// {
|
||||
// colorPrimaries: 'bt2020',
|
||||
// colorTrc: 'smpte2084',
|
||||
// colorspace: 'bt2020nc',
|
||||
// pixelFormat: 'yuv420p10le',
|
||||
// x265ColorParams: 'colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:master-display=...:max-cll=1000,400',
|
||||
// mastering: { masterDisplay: '...', maxCll: '1000,400' },
|
||||
// }
|
||||
```
|
||||
|
||||
`getHdrEncoderColorParams` always includes both color tagging *and* the HDR10 static metadata (mastering display + content light level). Without that metadata, downstream players treat the file as SDR BT.2020 and tone-map incorrectly. Pass a custom `HdrMasteringMetadata` if you have measured per-content values; otherwise the conservative `DEFAULT_HDR10_MASTERING` defaults match how most HDR10 grading suites tag content.
|
||||
|
||||
### WebGPU HDR DOM capture
|
||||
|
||||
For capturing CSS-animated DOM directly into HDR (no FFmpeg source involved), the engine exposes a separate WebGPU pipeline:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
launchHdrBrowser,
|
||||
buildHdrChromeArgs,
|
||||
initHdrReadback,
|
||||
uploadAndReadbackHdrFrame,
|
||||
float16ToPqRgb,
|
||||
} from '@hyperframes/engine';
|
||||
|
||||
// Launch headed Chrome with WebGPU enabled
|
||||
const { browser, page } = await launchHdrBrowser({ width: 1920, height: 1080 });
|
||||
|
||||
// Inject the WebGPU readback runtime
|
||||
const ok = await initHdrReadback(page, 1920, 1080);
|
||||
|
||||
// For each frame: upload float16 pixels, read back float16 RGBA
|
||||
const { rgba16, bytesPerRow } = await uploadAndReadbackHdrFrame(page, float16Base64);
|
||||
|
||||
// Convert linear float16 → PQ-encoded 16-bit RGB suitable for piping into ffmpeg/x265
|
||||
const pqRgb = float16ToPqRgb(rgba16, width, height, bytesPerRow);
|
||||
```
|
||||
|
||||
<Warning>
|
||||
This path requires **headed Chrome with `--enable-unsafe-webgpu`** — WebGPU is unavailable in `chrome-headless-shell`. It is *not* used by the default HDR-aware render pipeline (which extracts HDR pixels from sources via FFmpeg and composites in Node). Use it only for advanced custom pipelines that need CSS animations driving HDR pixel output.
|
||||
</Warning>
|
||||
|
||||
## The `window.__hf` Protocol
|
||||
|
||||
The engine communicates with the browser page via the `window.__hf` protocol. Any page that implements this protocol can be captured by the engine — you are not limited to Hyperframes compositions.
|
||||
|
||||
```typescript
|
||||
// The page must expose this on window.__hf
|
||||
interface HfProtocol {
|
||||
duration: number; // Total duration in seconds
|
||||
seek(time: number): void; // Seek to a specific time
|
||||
media?: HfMediaElement[]; // Optional media element declarations
|
||||
}
|
||||
|
||||
interface HfMediaElement {
|
||||
elementId: string; // DOM element ID
|
||||
src: string; // Media source URL
|
||||
startTime: number; // Start time on timeline
|
||||
endTime: number; // End time on timeline
|
||||
mediaOffset?: number; // Playback offset in source
|
||||
volume?: number; // Volume (0-1)
|
||||
hasAudio?: boolean; // Whether element has audio
|
||||
}
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### BeginFrame Rendering
|
||||
|
||||
Traditional screen capture records at wall-clock speed — if your system is under load, frames get dropped. The engine uses Chrome's `HeadlessExperimental.beginFrame` to explicitly advance the compositor, producing each frame on demand. This means:
|
||||
|
||||
- **No dropped frames** — every frame is captured
|
||||
- **No timing dependency** — a 60-second video does not take 60 seconds to capture
|
||||
- **Pixel-perfect output** — the compositor produces the exact pixels it would display
|
||||
|
||||
For more on how this enables deterministic output, see [Deterministic Rendering](/concepts/determinism).
|
||||
|
||||
### Seek Contract
|
||||
|
||||
The engine relies on the Hyperframes runtime's `window.__hf.seek(time)` function. When called, `seek`:
|
||||
|
||||
1. Pauses all GSAP timelines
|
||||
2. Seeks every timeline to the exact timestamp
|
||||
3. Updates all media elements (video, audio) to match
|
||||
4. Mounts/unmounts clips based on their `data-start` and `data-duration`
|
||||
|
||||
This contract is what makes frame-by-frame capture possible — each frame is a complete, independent snapshot of the composition at that point in time.
|
||||
|
||||
### Chrome Requirements
|
||||
|
||||
The engine requires `chrome-headless-shell`, which is included when you install the package. It uses a pinned Chrome version to ensure consistent rendering across environments. For fully deterministic output (including fonts), use Docker mode via the [producer](/packages/producer).
|
||||
|
||||
## Related Packages
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Producer" icon="film" href="/packages/producer">
|
||||
Wraps the engine with runtime injection, FFmpeg encoding, and audio mixing for complete MP4 output.
|
||||
</Card>
|
||||
<Card title="Core" icon="cube" href="/packages/core">
|
||||
Provides the types, runtime, and linter that the engine depends on.
|
||||
</Card>
|
||||
<Card title="CLI" icon="terminal" href="/packages/cli">
|
||||
The easiest way to render — calls the producer (and engine) under the hood.
|
||||
</Card>
|
||||
<Card title="Studio" icon="palette" href="/packages/studio">
|
||||
Visual editor for building compositions before rendering them with the engine.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,110 @@
|
||||
---
|
||||
title: "@hyperframes/gcp-cloud-run"
|
||||
description: "Google Cloud Run and Workflows adapter for distributed HyperFrames rendering."
|
||||
---
|
||||
|
||||
The GCP Cloud Run package runs HyperFrames' distributed render primitives on Google Cloud. It ships a Cloud Run HTTP service, a Node client SDK, and a Terraform module for provisioning the bucket, service, workflow, and service accounts.
|
||||
|
||||
```bash
|
||||
npm install @hyperframes/gcp-cloud-run
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
**Use `@hyperframes/gcp-cloud-run` when you need to:**
|
||||
- Render large compositions on Google Cloud infrastructure
|
||||
- Orchestrate `plan`, `renderChunk`, and `assemble` through Cloud Workflows
|
||||
- Store project archives, chunk outputs, and final videos in GCS
|
||||
- Deploy the renderer as a Cloud Run service with a pinned Chrome runtime
|
||||
- Drive renders from CI, a backend service, or custom internal tooling
|
||||
|
||||
**Use a different package if you want to:**
|
||||
- Render locally or inside a single Node process - use the [CLI](/packages/cli) or [producer](/packages/producer)
|
||||
- Run the same distributed model on AWS - use [aws-lambda](/packages/aws-lambda)
|
||||
- Build or edit composition HTML - use [studio](/packages/studio), [sdk](/packages/sdk), or [core](/packages/core)
|
||||
|
||||
<Tip>
|
||||
Cloud Run uses a container image, so it avoids Lambda ZIP-size pressure and can install the same pinned `chrome-headless-shell` runtime used by the standard renderer.
|
||||
</Tip>
|
||||
|
||||
## Package Exports
|
||||
|
||||
| Import | Description |
|
||||
|--------|-------------|
|
||||
| `@hyperframes/gcp-cloud-run` | Server handler, event types, GCS transport, and client SDK exports |
|
||||
| `@hyperframes/gcp-cloud-run/server` | Cloud Run HTTP service entry point |
|
||||
| `@hyperframes/gcp-cloud-run/sdk` | Lightweight Node SDK for deploying sites, starting renders, polling progress, and estimating cost |
|
||||
|
||||
The published package also includes `terraform/` and a `Dockerfile` for deployment.
|
||||
|
||||
## Architecture
|
||||
|
||||
Cloud Workflows invokes one Cloud Run service with different `Action` values:
|
||||
|
||||
<Steps>
|
||||
<Step title="Plan">
|
||||
Downloads the project archive from GCS, runs the producer planner, and uploads the plan directory.
|
||||
</Step>
|
||||
<Step title="Render chunks">
|
||||
Cloud Workflows runs parallel `renderChunk` calls against the Cloud Run service. Each request renders one chunk and uploads it to GCS.
|
||||
</Step>
|
||||
<Step title="Assemble">
|
||||
Downloads all chunks and audio assets, assembles the deliverable, and uploads the final file.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
The service is intentionally close to the AWS Lambda adapter: thin cloud transport around the same `@hyperframes/producer/distributed` primitives.
|
||||
|
||||
## Deploying
|
||||
|
||||
Build and push the container, then apply the Terraform module:
|
||||
|
||||
```bash
|
||||
gcloud builds submit . \
|
||||
--tag REGION-docker.pkg.dev/PROJECT/REPO/hyperframes-render:TAG
|
||||
|
||||
terraform -chdir=node_modules/@hyperframes/gcp-cloud-run/terraform init
|
||||
terraform -chdir=node_modules/@hyperframes/gcp-cloud-run/terraform apply \
|
||||
-var project_id=PROJECT \
|
||||
-var region=us-central1 \
|
||||
-var image=REGION-docker.pkg.dev/PROJECT/REPO/hyperframes-render:TAG
|
||||
```
|
||||
|
||||
Terraform outputs the bucket name, service URL, workflow name, and region needed by the SDK.
|
||||
|
||||
## Using the SDK
|
||||
|
||||
```typescript
|
||||
import { getRenderProgress, renderToCloudRun } from "@hyperframes/gcp-cloud-run/sdk";
|
||||
|
||||
const handle = await renderToCloudRun({
|
||||
projectDir: "./my-composition",
|
||||
config: { fps: 30, width: 1920, height: 1080, format: "mp4" },
|
||||
bucketName: "hyperframes-render-my-project",
|
||||
projectId: "my-project",
|
||||
location: "us-central1",
|
||||
workflowId: "hyperframes-render",
|
||||
serviceUrl: "https://hyperframes-render-abc.us-central1.run.app",
|
||||
});
|
||||
|
||||
let progress = await getRenderProgress({ executionName: handle.executionName });
|
||||
while (progress.status === "running") {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
progress = await getRenderProgress({ executionName: handle.executionName });
|
||||
}
|
||||
|
||||
console.log(progress.status, progress.outputFile, progress.costs.displayCost);
|
||||
```
|
||||
|
||||
Pass `projectDir` for one-shot uploads, or call `deploySite()` separately and reuse the returned site handle across many renders.
|
||||
|
||||
## Related Guides
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="GCP Cloud Run Deployment" icon="cloud" href="/deploy/gcp-cloud-run">
|
||||
End-to-end deployment details and smoke-test notes.
|
||||
</Card>
|
||||
<Card title="@hyperframes/producer" icon="video" href="/packages/producer">
|
||||
The distributed primitives that the Cloud Run service executes.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
title: "@hyperframes/lint"
|
||||
description: "The composition linter as a standalone library — lint a directory or a single HTML file without the CLI."
|
||||
---
|
||||
|
||||
The lint package is the composition linter extracted from core into a dedicated, independently-installable package. It's the **single source of truth** for linting: both the CLI's `hyperframes lint` command and the render-time render-gate consume the same rule engine from here.
|
||||
|
||||
```bash
|
||||
npm install @hyperframes/lint
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
<Tip>
|
||||
This package is the payoff of running validation **as a library** instead of shelling out to the CLI. A Node app (an agent harness, a CI step, an editor plugin) can `import { lintProject } from '@hyperframes/lint'` and lint a composition directory directly — no `npx hyperframes lint` subprocess, no stdout parsing.
|
||||
</Tip>
|
||||
|
||||
**Use `@hyperframes/lint` when you need to:**
|
||||
- Lint a composition project (an index + sub-compositions) from Node
|
||||
- Lint a single HTML string programmatically
|
||||
- Gate a render on lint findings (`shouldBlockRender`)
|
||||
- Surface lint findings in your own UI or CI annotations
|
||||
|
||||
<Info>
|
||||
`@hyperframes/core/lint` still resolves (via a back-compat re-export stub), so existing imports keep working. New code should import from `@hyperframes/lint` directly.
|
||||
</Info>
|
||||
|
||||
## Package Exports
|
||||
|
||||
The lint package has a single entry point:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
lintHyperframeHtml,
|
||||
lintMediaUrls,
|
||||
lintProject,
|
||||
shouldBlockRender,
|
||||
} from '@hyperframes/lint';
|
||||
import type {
|
||||
HyperframeLintResult,
|
||||
HyperframeLintFinding,
|
||||
HyperframeLintSeverity, // "error" | "warning" | "info"
|
||||
HyperframeLinterOptions,
|
||||
ProjectLintResult,
|
||||
} from '@hyperframes/lint';
|
||||
```
|
||||
|
||||
## Linting a Single Composition
|
||||
|
||||
```typescript
|
||||
import { lintHyperframeHtml, lintMediaUrls } from '@hyperframes/lint';
|
||||
|
||||
const result = lintHyperframeHtml(html, { filePath: 'index.html' });
|
||||
// result.ok, result.errorCount, result.warningCount, result.findings
|
||||
|
||||
for (const finding of result.findings) {
|
||||
console.log(finding.severity, finding.code, finding.message);
|
||||
// finding.file, finding.selector, finding.elementId, finding.fixHint, finding.snippet
|
||||
}
|
||||
|
||||
// Additional media URL validation
|
||||
const mediaFindings = lintMediaUrls(result.findings);
|
||||
```
|
||||
|
||||
## Linting a Project
|
||||
|
||||
`lintProject` walks a composition directory — the index plus any sub-compositions — and returns aggregated findings. It takes a **directory path string**, so it's callable from any Node context with nothing but a path:
|
||||
|
||||
```typescript
|
||||
import { lintProject, shouldBlockRender } from '@hyperframes/lint';
|
||||
import type { ProjectLintResult } from '@hyperframes/lint';
|
||||
|
||||
const result: ProjectLintResult = await lintProject('./my-composition');
|
||||
// result.totalErrors, result.totalWarnings, result.results[]
|
||||
// each result entry: { file, result: HyperframeLintResult }
|
||||
|
||||
if (shouldBlockRender(false, false, result.totalErrors, result.totalWarnings)) {
|
||||
throw new Error(`Lint found ${result.totalErrors} blocking error(s)`);
|
||||
}
|
||||
```
|
||||
|
||||
## Browser usage
|
||||
|
||||
The rule engine runs **fully client-side** — no Node.js, no filesystem, no server round-trip. Import from the `@hyperframes/lint/browser` entry to validate composition HTML directly in a browser-only editor or tool:
|
||||
|
||||
```typescript
|
||||
import { lintHyperframeHtml, shouldBlockRender } from '@hyperframes/lint/browser';
|
||||
|
||||
const result = await lintHyperframeHtml(htmlString, { filePath: 'index.html' });
|
||||
if (!result.ok) {
|
||||
for (const f of result.findings) console.warn(f.code, f.message);
|
||||
}
|
||||
```
|
||||
|
||||
The browser entry exposes `lintHyperframeHtml`, `lintMediaUrls`, and `shouldBlockRender` — everything that operates on an HTML string. It is built with a browser target and contains **zero `node:` builtins**, so it bundles cleanly for the client (verified at build time).
|
||||
|
||||
<Info>
|
||||
`lintProject` (which walks a project **directory**) is filesystem-based and is **not** part of the browser entry — import it from the main `@hyperframes/lint` entry in Node.
|
||||
</Info>
|
||||
|
||||
## What the Linter Catches
|
||||
|
||||
Detected issues include:
|
||||
|
||||
- Missing timeline registration (`window.__timelines`)
|
||||
- Unmuted video elements (causes autoplay failures)
|
||||
- Missing `class="clip"` on timed visible elements
|
||||
- Deprecated attribute names
|
||||
- Missing composition dimensions (`data-width`, `data-height`)
|
||||
- Invalid `data-start` references to nonexistent clip IDs
|
||||
|
||||
<Info>
|
||||
For a full list of what the linter catches and how to fix each issue, see [Common Mistakes](/guides/common-mistakes) and [Troubleshooting](/guides/troubleshooting).
|
||||
</Info>
|
||||
|
||||
## Related Packages
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="@hyperframes/parsers" icon="code" href="/packages/parsers">
|
||||
The HTML + GSAP parsing layer the linter builds on.
|
||||
</Card>
|
||||
<Card title="@hyperframes/core" icon="cube" href="/packages/core">
|
||||
Types and runtime; re-exports the linter for back-compat.
|
||||
</Card>
|
||||
<Card title="CLI" icon="terminal" href="/packages/cli">
|
||||
`npx hyperframes lint` wraps this package.
|
||||
</Card>
|
||||
<Card title="Studio" icon="palette" href="/packages/studio">
|
||||
Surfaces lint findings in the editor.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
title: "@hyperframes/parsers"
|
||||
description: "The GSAP + HTML parser/writer suite — standalone, zero @hyperframes/* dependencies."
|
||||
---
|
||||
|
||||
The parsers package is the standalone foundation extracted from core. It owns the GSAP animation parser/writer (recast **and** acorn implementations), the HTML composition parser, hf-id stamping, and spring-ease generation. It has **no `@hyperframes/*` dependencies**, so it's the base every other package builds on.
|
||||
|
||||
```bash
|
||||
npm install @hyperframes/parsers
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
<Tip>
|
||||
**Most users do not need to install `@hyperframes/parsers` directly.** [`@hyperframes/core`](/packages/core) re-exports the parser API it needs, and the [CLI](/packages/cli) / [studio](/packages/studio) depend on it transitively. Reach for it directly only when you want the parsing layer **without** pulling in the rest of core.
|
||||
</Tip>
|
||||
|
||||
**Use `@hyperframes/parsers` when you need to:**
|
||||
- Parse HTML compositions into structured TypeScript objects
|
||||
- Parse, edit, and re-serialize GSAP timeline scripts (AST round-trip)
|
||||
- Stamp deterministic `hf-id` attributes onto a document
|
||||
- Build tooling that touches the parsing layer but doesn't need core's runtime, compiler, or generators
|
||||
|
||||
## Package Exports
|
||||
|
||||
| Import | Description |
|
||||
|--------|-------------|
|
||||
| `@hyperframes/parsers` | HTML parser, GSAP serialize/validate helpers, hf-ids, shared types |
|
||||
| `@hyperframes/parsers/gsap-parser` | GSAP parser exports, `parseGsapScript`, `SUPPORTED_PROPS`, `SUPPORTED_EASES` |
|
||||
| `@hyperframes/parsers/gsap-parser-acorn` | Acorn-based GSAP parser (browser-safe, read path) |
|
||||
| `@hyperframes/parsers/gsap-writer-acorn` | Acorn-based GSAP writer (mutation helpers) |
|
||||
| `@hyperframes/parsers/gsap-parser-recast` | Recast-based GSAP parser/writer (legacy implementation) |
|
||||
| `@hyperframes/parsers/gsap-constants` | `SUPPORTED_PROPS`, `SUPPORTED_EASES`, property groups |
|
||||
| `@hyperframes/parsers/spring-ease` | Spring-ease curve generation |
|
||||
| `@hyperframes/parsers/hf-ids` | Deterministic element id stamping |
|
||||
| `@hyperframes/parsers/slideshow` | Slideshow manifest parser (`parseSlideshowManifest`, `resolveSlideshow`) |
|
||||
| `@hyperframes/parsers/composition` | Pure, browser-safe composition primitives (data types, font aliases, URL helper) |
|
||||
| `@hyperframes/parsers/asset-paths` | Node-only asset-path rewriting helpers (`rewriteAssetPath`, …) |
|
||||
| `@hyperframes/parsers/sub-composition-validity` | Sub-composition validation utilities |
|
||||
|
||||
<Info>
|
||||
The package ships subpath entries so consumers tree-shake to what they use — importing `@hyperframes/parsers/hf-ids` (a couple KB) does **not** pull in the GSAP AST machinery (recast/babel/acorn).
|
||||
</Info>
|
||||
|
||||
## HTML Parsing
|
||||
|
||||
Round-trip between HTML and structured data:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
parseHtml,
|
||||
extractCompositionMetadata,
|
||||
validateCompositionHtml,
|
||||
} from '@hyperframes/parsers';
|
||||
import type { ParsedHtml, CompositionMetadata } from '@hyperframes/parsers';
|
||||
|
||||
// Parse HTML into structured data
|
||||
const parsed: ParsedHtml = parseHtml(htmlString);
|
||||
// parsed.elements, parsed.gsapScript, parsed.styles, parsed.resolution, parsed.keyframes
|
||||
|
||||
// Extract composition metadata (id, duration, dimensions, variables)
|
||||
const meta: CompositionMetadata = extractCompositionMetadata(htmlString);
|
||||
|
||||
// Validate HTML structure
|
||||
const result = validateCompositionHtml(htmlString);
|
||||
// result.valid, result.errors
|
||||
```
|
||||
|
||||
### Modifying HTML
|
||||
|
||||
```typescript
|
||||
import {
|
||||
updateElementInHtml,
|
||||
addElementToHtml,
|
||||
removeElementFromHtml,
|
||||
} from '@hyperframes/parsers';
|
||||
|
||||
const updated = updateElementInHtml(html, 'el-1', { start: 5 });
|
||||
const added = addElementToHtml(html, newElement);
|
||||
const cleaned = removeElementFromHtml(html, 'el-1');
|
||||
```
|
||||
|
||||
## GSAP Script Parsing
|
||||
|
||||
The acorn parser/writer is the primary path (browser-safe). Parse a timeline script, mutate it, and serialize it back without losing surrounding code:
|
||||
|
||||
```typescript
|
||||
import { parseGsapScriptAcorn, extractGsapLabels } from '@hyperframes/parsers/gsap-parser-acorn';
|
||||
import {
|
||||
updateAnimationInScript,
|
||||
addAnimationToScript,
|
||||
removeAnimationFromScript,
|
||||
updateKeyframeInScript,
|
||||
addKeyframeToScript,
|
||||
shiftPositionsInScript,
|
||||
scalePositionsInScript,
|
||||
} from '@hyperframes/parsers/gsap-writer-acorn';
|
||||
import type { GsapAnimation, GsapMethod, ParsedGsap } from '@hyperframes/parsers';
|
||||
|
||||
const parsed: ParsedGsap = parseGsapScriptAcorn(scriptContent);
|
||||
// parsed.animations, parsed.timelineVar, parsed.preamble, parsed.postamble
|
||||
|
||||
// Mutation helpers operate on the script text and preserve unrelated code
|
||||
const next = updateAnimationInScript(scriptContent, animationId, { duration: 2 });
|
||||
```
|
||||
|
||||
High-level serialize / validate / keyframe-conversion helpers are on the main entry:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
serializeGsapAnimations,
|
||||
getAnimationsForElementId,
|
||||
validateCompositionGsap,
|
||||
keyframesToGsapAnimations,
|
||||
gsapAnimationsToKeyframes,
|
||||
} from '@hyperframes/parsers';
|
||||
```
|
||||
|
||||
### GSAP Constants
|
||||
|
||||
```typescript
|
||||
import {
|
||||
SUPPORTED_PROPS, // animatable properties
|
||||
SUPPORTED_EASES, // available easing functions
|
||||
PROPERTY_GROUPS,
|
||||
} from '@hyperframes/parsers/gsap-constants';
|
||||
import type { PropertyGroupName } from '@hyperframes/parsers/gsap-constants';
|
||||
```
|
||||
|
||||
## hf-ids
|
||||
|
||||
Deterministic element identity for stable diffing and editing:
|
||||
|
||||
```typescript
|
||||
import { ensureHfIds, mintHfId } from '@hyperframes/parsers/hf-ids';
|
||||
|
||||
// Stamp hf-id attributes onto every editable element in a document
|
||||
const withIds = ensureHfIds(htmlString);
|
||||
```
|
||||
|
||||
## Spring Ease
|
||||
|
||||
```typescript
|
||||
import { generateSpringEaseData, SPRING_PRESETS } from '@hyperframes/parsers/spring-ease';
|
||||
```
|
||||
|
||||
## Related Packages
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="@hyperframes/core" icon="cube" href="/packages/core">
|
||||
Types, generators, runtime, and compiler — re-exports the parser API it needs.
|
||||
</Card>
|
||||
<Card title="@hyperframes/lint" icon="circle-check" href="/packages/lint">
|
||||
The composition linter, built on the parsers.
|
||||
</Card>
|
||||
<Card title="@hyperframes/studio-server" icon="server" href="/packages/studio-server">
|
||||
The studio preview server, built on the parsers.
|
||||
</Card>
|
||||
<Card title="CLI" icon="terminal" href="/packages/cli">
|
||||
Create, preview, lint, and render compositions.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,241 @@
|
||||
---
|
||||
title: "@hyperframes/player"
|
||||
description: "Embeddable web component for playing HyperFrames compositions in any web page."
|
||||
---
|
||||
|
||||
The player package provides a `<hyperframes-player>` custom element that embeds a HyperFrames composition anywhere — in any framework or plain HTML. Zero dependencies, 3KB gzipped.
|
||||
|
||||
```bash
|
||||
npm install @hyperframes/player
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
**Use `@hyperframes/player` when you need to:**
|
||||
- Embed a rendered composition in a website, dashboard, or app
|
||||
- Add a video-like player to a landing page or product demo
|
||||
- Show compositions in documentation or blog posts
|
||||
|
||||
**Use a different package if you want to:**
|
||||
- Edit compositions interactively — use the [studio](/packages/studio)
|
||||
- Preview during development — use the [CLI](/packages/cli) (`npx hyperframes preview`)
|
||||
- Render to MP4 — use the [CLI](/packages/cli) or [producer](/packages/producer)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Via CDN
|
||||
|
||||
```html
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@hyperframes/player"></script>
|
||||
|
||||
<hyperframes-player
|
||||
src="./my-composition/index.html"
|
||||
controls
|
||||
autoplay
|
||||
muted
|
||||
style="width: 100%; max-width: 800px; aspect-ratio: 16/9"
|
||||
></hyperframes-player>
|
||||
```
|
||||
|
||||
If you need a classic `<script>` tag instead of ESM, use the explicit global build:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/@hyperframes/player/dist/hyperframes-player.global.js"></script>
|
||||
```
|
||||
|
||||
### Via npm
|
||||
|
||||
```js
|
||||
import '@hyperframes/player';
|
||||
```
|
||||
|
||||
```html
|
||||
<hyperframes-player src="/compositions/intro.html" controls></hyperframes-player>
|
||||
```
|
||||
|
||||
## HTML Attributes
|
||||
|
||||
| Attribute | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `src` | string | required | URL or relative path to composition HTML |
|
||||
| `width` | number | 1920 | Composition width in pixels |
|
||||
| `height` | number | 1080 | Composition height in pixels |
|
||||
| `controls` | boolean | false | Show playback controls overlay |
|
||||
| `autoplay` | boolean | false | Start playing on load |
|
||||
| `loop` | boolean | false | Loop playback |
|
||||
| `muted` | boolean | false | Mute audio (set to `true` for autoplay in most browsers) |
|
||||
| `poster` | string | — | Image URL to show before first play |
|
||||
| `playback-rate` | number | 1 | Playback speed multiplier |
|
||||
|
||||
## JavaScript API
|
||||
|
||||
The player mirrors the native `<video>` element API:
|
||||
|
||||
```js
|
||||
const player = document.querySelector('hyperframes-player');
|
||||
|
||||
// Playback
|
||||
player.play();
|
||||
player.pause();
|
||||
player.seek(2.5); // seek to 2.5 seconds
|
||||
|
||||
// Properties
|
||||
player.currentTime; // number — current position in seconds
|
||||
player.currentTime = 5; // seek to 5 seconds
|
||||
player.duration; // number — total duration
|
||||
player.paused; // boolean
|
||||
player.ready; // boolean — true after composition loads
|
||||
player.playbackRate; // number — get/set speed
|
||||
player.muted; // boolean — get/set mute
|
||||
player.loop; // boolean — get/set loop
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
```js
|
||||
const player = document.querySelector('hyperframes-player');
|
||||
|
||||
player.addEventListener('ready', (e) => {
|
||||
console.log('Duration:', e.detail.duration);
|
||||
});
|
||||
|
||||
player.addEventListener('timeupdate', (e) => {
|
||||
console.log('Time:', e.detail.currentTime);
|
||||
});
|
||||
|
||||
player.addEventListener('play', () => console.log('Playing'));
|
||||
player.addEventListener('pause', () => console.log('Paused'));
|
||||
player.addEventListener('ended', () => console.log('Ended'));
|
||||
player.addEventListener('error', (e) => console.error(e.detail.message));
|
||||
```
|
||||
|
||||
| Event | Detail | Description |
|
||||
|-------|--------|-------------|
|
||||
| `ready` | `{ duration }` | Composition loaded and timeline discovered |
|
||||
| `timeupdate` | `{ currentTime }` | Fires during playback (~30fps) |
|
||||
| `play` | — | Playback started |
|
||||
| `pause` | — | Playback paused |
|
||||
| `ended` | — | Playback reached end |
|
||||
| `error` | `{ message }` | Load or runtime error |
|
||||
|
||||
## Framework Examples
|
||||
|
||||
### React
|
||||
|
||||
```jsx
|
||||
import '@hyperframes/player';
|
||||
|
||||
function VideoPreview({ src }) {
|
||||
return (
|
||||
<hyperframes-player
|
||||
src={src}
|
||||
controls
|
||||
style={{ width: '100%', maxWidth: 800 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Vue
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<hyperframes-player :src="compositionUrl" controls />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import '@hyperframes/player';
|
||||
const compositionUrl = './compositions/intro.html';
|
||||
</script>
|
||||
```
|
||||
|
||||
### Programmatic
|
||||
|
||||
```js
|
||||
import '@hyperframes/player';
|
||||
|
||||
const player = document.createElement('hyperframes-player');
|
||||
player.src = './my-composition/index.html';
|
||||
player.controls = true;
|
||||
player.addEventListener('ready', () => player.play());
|
||||
document.getElementById('player-container').appendChild(player);
|
||||
```
|
||||
|
||||
## Advanced: iframe access
|
||||
|
||||
The composition runs inside a sandboxed `<iframe>` in the player's Shadow DOM. For most use cases you don't need direct access — the JavaScript API and events above are sufficient. But if you're building an editor, recorder, or custom timeline on top of the player, you'll need to inspect the composition's DOM or read its `__player` / `__timelines` runtime objects. The `iframeElement` getter exposes the inner iframe for these consumers:
|
||||
|
||||
```js
|
||||
const player = document.querySelector('hyperframes-player');
|
||||
const iframe = player.iframeElement;
|
||||
|
||||
// Reach into the composition's DOM
|
||||
iframe.contentDocument.querySelectorAll('[data-composition-id]');
|
||||
|
||||
// Read the runtime (GSAP timelines, element registry, etc.)
|
||||
iframe.contentWindow.__timelines;
|
||||
```
|
||||
|
||||
This is the canonical way to bridge the player into editor tools like [`@hyperframes/studio`](/packages/studio). The studio exports a `resolveIframe` helper that handles both direct iframe refs and web-component refs:
|
||||
|
||||
```ts
|
||||
import { useTimelinePlayer, resolveIframe } from '@hyperframes/studio';
|
||||
|
||||
const { iframeRef } = useTimelinePlayer();
|
||||
const player = document.createElement('hyperframes-player');
|
||||
player.setAttribute('src', src);
|
||||
container.appendChild(player);
|
||||
|
||||
// Forward the inner iframe so useTimelinePlayer can drive play/pause/seek.
|
||||
iframeRef.current = resolveIframe(player);
|
||||
```
|
||||
|
||||
### React: declarative ref pattern
|
||||
|
||||
If you prefer JSX over imperative element creation, attach a ref to the web component and resolve the iframe inside an effect:
|
||||
|
||||
```tsx
|
||||
import '@hyperframes/player';
|
||||
import type { HyperframesPlayer } from '@hyperframes/player';
|
||||
import { useTimelinePlayer, resolveIframe } from '@hyperframes/studio';
|
||||
|
||||
function StudioPreview({ src }: { src: string }) {
|
||||
const { iframeRef, onIframeLoad } = useTimelinePlayer();
|
||||
const playerRef = useRef<HyperframesPlayer>(null);
|
||||
|
||||
useEffect(() => {
|
||||
iframeRef.current = resolveIframe(playerRef.current);
|
||||
});
|
||||
|
||||
return (
|
||||
<hyperframes-player
|
||||
ref={playerRef}
|
||||
src={src}
|
||||
onLoad={onIframeLoad}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<Warning>
|
||||
**Common gotcha** — if you pass the `<hyperframes-player>` element itself (not `iframeElement`) into a hook or API that expects an `<iframe>`, every `.contentWindow` / `.contentDocument` access returns `null` because the iframe lives inside the player's Shadow DOM. Timeline seek, play, pause, and DOM inspection all silently no-op. **Always extract `iframeElement` first**, or use `resolveIframe` from `@hyperframes/studio` which handles both iframe and web-component hosts transparently.
|
||||
</Warning>
|
||||
|
||||
## Architecture
|
||||
|
||||
The player uses an iframe inside a Shadow DOM container. This provides:
|
||||
|
||||
- **Isolation** — composition CSS/JS can't leak into or conflict with your page
|
||||
- **Security** — iframe sandbox restricts composition capabilities
|
||||
- **Scaling** — auto-scales the composition to fit the player's container via CSS transforms
|
||||
|
||||
The player communicates with the composition via the HyperFrames runtime bridge protocol (`postMessage`). Existing compositions work without modification.
|
||||
|
||||
## Controls
|
||||
|
||||
When the `controls` attribute is present, a minimal overlay appears at the bottom:
|
||||
|
||||
- **Play/Pause** button (left)
|
||||
- **Scrub bar** with drag support (mouse + touch)
|
||||
- **Time display** showing current / total duration (right)
|
||||
- Auto-hides after 3 seconds of inactivity, reappears on hover
|
||||
@@ -0,0 +1,371 @@
|
||||
---
|
||||
title: "@hyperframes/producer"
|
||||
description: "Full HTML-to-video rendering pipeline with encoding, audio mixing, and Docker support."
|
||||
---
|
||||
|
||||
The producer package combines the [engine's](/packages/engine) frame capture with FFmpeg encoding to deliver a complete HTML-to-video rendering pipeline. It supports MP4 (h264) and WebM (VP9 with alpha transparency), and handles runtime injection, readiness gates, audio mixing, and optional Docker-based deterministic rendering.
|
||||
|
||||
```bash
|
||||
npm install @hyperframes/producer
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
**Use `@hyperframes/producer` when you need to:**
|
||||
- Render compositions to MP4 or WebM programmatically from Node.js (e.g., in a backend service or CI pipeline)
|
||||
- Build a custom rendering service with fine-grained control over the pipeline
|
||||
- Run visual regression tests against golden baselines
|
||||
- Benchmark render performance across different configurations
|
||||
|
||||
**Use a different package if you want to:**
|
||||
- Render from the command line without writing code — use the [CLI](/packages/cli) (`npx hyperframes render`)
|
||||
- Preview compositions in the browser — use the [CLI](/packages/cli) or [studio](/packages/studio)
|
||||
- Capture frames without encoding — use the [engine](/packages/engine)
|
||||
- Lint or parse composition HTML — use [core](/packages/core)
|
||||
|
||||
<Tip>
|
||||
If you are building a web application or script that just needs to render a video, the [CLI](/packages/cli) is the fastest path. The producer package is for when you need programmatic control inside Node.js.
|
||||
</Tip>
|
||||
|
||||
## What It Does
|
||||
|
||||
The producer orchestrates the full render pipeline:
|
||||
|
||||
<Steps>
|
||||
<Step title="Load the composition HTML">
|
||||
Reads your `index.html` and any referenced sub-compositions.
|
||||
</Step>
|
||||
<Step title="Inject the Hyperframes runtime">
|
||||
Adds the runtime script that manages timeline seeking, clip lifecycle, and media playback.
|
||||
</Step>
|
||||
<Step title="Wait for readiness gates">
|
||||
Polls for `window.__playerReady` and `window.__renderReady` to ensure all assets (fonts, images, video) are loaded before capture begins.
|
||||
</Step>
|
||||
<Step title="Capture frames via the engine">
|
||||
Uses the [engine's](/packages/engine) BeginFrame pipeline to capture each frame as a pixel buffer.
|
||||
</Step>
|
||||
<Step title="Encode to MP4 or WebM via FFmpeg">
|
||||
Pipes frame buffers into FFmpeg with the selected quality preset. MP4 uses h264; WebM uses VP9 with alpha transparency support.
|
||||
</Step>
|
||||
<Step title="Mix audio tracks">
|
||||
Extracts audio from video clips and audio elements, applies `data-volume` and `data-media-start` offsets, and mixes them into the final MP4.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Programmatic Usage
|
||||
|
||||
The producer uses a two-step API: create a render job configuration, then execute it.
|
||||
|
||||
```typescript
|
||||
import { createRenderJob, executeRenderJob } from '@hyperframes/producer';
|
||||
|
||||
const job = createRenderJob({
|
||||
fps: 30,
|
||||
quality: 'standard',
|
||||
});
|
||||
|
||||
await executeRenderJob(job, './my-video', './output.mp4');
|
||||
```
|
||||
|
||||
### Render Configuration
|
||||
|
||||
```typescript
|
||||
import { createRenderJob } from '@hyperframes/producer';
|
||||
|
||||
const job = createRenderJob({
|
||||
fps: 30, // integer, or { num: 30000, den: 1001 } for NTSC
|
||||
quality: 'standard', // 'draft', 'standard', or 'high'
|
||||
format: 'mp4', // 'mp4', 'webm', 'mov', 'gif', or 'png-sequence'
|
||||
workers: 4, // Parallel render workers (1-24, or omit for auto)
|
||||
useGpu: false, // GPU-accelerated encoding
|
||||
debug: false, // Debug logging
|
||||
});
|
||||
```
|
||||
|
||||
#### WebM with Transparency
|
||||
|
||||
Set `format: 'webm'` to render with a transparent background using VP9 alpha:
|
||||
|
||||
```typescript
|
||||
const job = createRenderJob({
|
||||
fps: 30,
|
||||
quality: 'standard',
|
||||
format: 'webm',
|
||||
});
|
||||
|
||||
await executeRenderJob(job, './my-overlay', './overlay.webm');
|
||||
```
|
||||
|
||||
When `format: 'webm'`:
|
||||
- Frames are captured as PNG (preserves alpha channel)
|
||||
- Chrome's page background is set to transparent via CDP
|
||||
- FFmpeg encodes with VP9 + `yuva420p` pixel format
|
||||
- Audio is encoded as Opus (instead of AAC for MP4)
|
||||
|
||||
#### HDR Output
|
||||
|
||||
Set `hdrMode` to control HDR behavior. The producer probes every video and image source for BT.2020 / PQ / HLG color tagging — if any HDR source is found and the mode allows it, the output uses H.265 10-bit BT.2020 with HDR10 static metadata. SDR-only compositions are unaffected.
|
||||
|
||||
```typescript
|
||||
const job = createRenderJob({
|
||||
fps: 30,
|
||||
quality: 'standard',
|
||||
format: 'mp4',
|
||||
hdrMode: 'auto', // 'auto' | 'force-hdr' | 'force-sdr'
|
||||
});
|
||||
|
||||
await executeRenderJob(job, './my-video', './output.mp4');
|
||||
```
|
||||
|
||||
When `hdrMode` is `'auto'` or `'force-hdr'`:
|
||||
- Sources are probed via `ffprobe`; PQ takes precedence over HLG when both are present
|
||||
- HDR videos and images are extracted as 16-bit linear-light pixels and composited natively
|
||||
- SDR DOM overlays are converted from sRGB → BT.2020 before being layered on top
|
||||
- Output uses `libx265` with `yuv420p10le` and HDR10 mastering / content-light-level metadata
|
||||
- `format` must be `'mp4'` — `'mov'` and `'webm'` fall back to SDR
|
||||
- HDR `<img>` extraction support is **still images only**; animated GIF inputs are prepared as timeline-synced video before render
|
||||
|
||||
For full details on source requirements, fallback rules, and verification, see [HDR Rendering](/guides/hdr).
|
||||
|
||||
### Progress Callbacks
|
||||
|
||||
```typescript
|
||||
import type { ProgressCallback, RenderStatus } from '@hyperframes/producer';
|
||||
|
||||
const onProgress: ProgressCallback = (status: RenderStatus) => {
|
||||
console.log(`Status: ${status}`);
|
||||
// Statuses: "queued" | "preprocessing" | "rendering" | "encoding"
|
||||
// | "assembling" | "complete" | "failed" | "cancelled"
|
||||
};
|
||||
```
|
||||
|
||||
### Cancellation
|
||||
|
||||
```typescript
|
||||
import { RenderCancelledError } from '@hyperframes/producer';
|
||||
|
||||
try {
|
||||
await executeRenderJob(job);
|
||||
} catch (err) {
|
||||
if (err instanceof RenderCancelledError) {
|
||||
console.log(`Cancelled: ${err.reason}`);
|
||||
// reason: "user_cancelled" | "timeout" | "aborted"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## HTTP Server
|
||||
|
||||
The producer includes a built-in HTTP server for running as a rendering service:
|
||||
|
||||
```typescript
|
||||
import { startServer } from '@hyperframes/producer/server';
|
||||
|
||||
await startServer({ port: 8080 });
|
||||
```
|
||||
|
||||
### Server Endpoints
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `POST` | `/render` | Blocking render — returns JSON result |
|
||||
| `POST` | `/render/stream` | Streaming render with Server-Sent Events |
|
||||
| `POST` | `/lint` | Lint a composition for issues |
|
||||
| `GET` | `/health` | Health check |
|
||||
| `GET` | `/outputs/:token` | Download a rendered MP4 |
|
||||
|
||||
For custom server integration, use the lower-level handlers:
|
||||
|
||||
```typescript
|
||||
import { createRenderHandlers, createProducerApp } from '@hyperframes/producer/server';
|
||||
|
||||
// Get individual request handlers
|
||||
const handlers = createRenderHandlers(options);
|
||||
|
||||
// Or get a full Hono app
|
||||
const app = createProducerApp(options);
|
||||
```
|
||||
|
||||
## Docker Rendering
|
||||
|
||||
For deterministic output, the producer can render inside a Docker container with a pinned Chrome version and font set. This guarantees identical output across machines — critical for CI pipelines and production services.
|
||||
|
||||
```bash
|
||||
# Via the CLI (recommended)
|
||||
npx hyperframes render --docker --output output.mp4
|
||||
```
|
||||
|
||||
<Info>
|
||||
Docker mode requires Docker to be installed and running. Run `npx hyperframes doctor` to verify your environment. See [Deterministic Rendering](/concepts/determinism) for details on what makes Docker mode deterministic.
|
||||
</Info>
|
||||
|
||||
## Quality Presets
|
||||
|
||||
| Preset | Resolution | Encoding | Use Case |
|
||||
|--------|-----------|----------|----------|
|
||||
| `draft` | Original | Fast CRF | Quick iteration, previewing edits |
|
||||
| `standard` | Original | Balanced CRF | Production renders, sharing |
|
||||
| `high` | Original | High-quality CRF | Final delivery, archival |
|
||||
|
||||
## GPU Encoding
|
||||
|
||||
The producer supports hardware-accelerated encoding for faster renders:
|
||||
|
||||
| Platform | Encoder | Selection |
|
||||
|----------|---------|-----------|
|
||||
| NVIDIA | NVENC | Auto-detected |
|
||||
| macOS | VideoToolbox | Auto-detected |
|
||||
| Linux | VAAPI | Auto-detected |
|
||||
| Intel | QSV | Auto-detected |
|
||||
| AMD on Windows | AMF | Auto-detected |
|
||||
|
||||
When GPU encoding is enabled, Hyperframes detects the available FFmpeg hardware encoder automatically. To check your system's capabilities:
|
||||
|
||||
```bash
|
||||
npx hyperframes doctor
|
||||
```
|
||||
|
||||
The CLI enables local Chrome/WebGL GPU capture automatically and supports `--no-browser-gpu` as an opt-out. When using the producer API directly, pass an engine config override:
|
||||
|
||||
```typescript
|
||||
import { resolveConfig } from '@hyperframes/producer';
|
||||
|
||||
const job = createRenderJob({
|
||||
fps: 30,
|
||||
quality: 'standard',
|
||||
producerConfig: resolveConfig({ browserGpuMode: 'hardware' }),
|
||||
});
|
||||
```
|
||||
|
||||
## Additional Exports
|
||||
|
||||
The producer also re-exports key engine functionality for convenience:
|
||||
|
||||
| Export | Description |
|
||||
|--------|-------------|
|
||||
| `createCaptureSession()` | Create a frame capture session |
|
||||
| `initializeSession()` | Initialize session with a composition |
|
||||
| `captureFrame()` / `captureFrameToBuffer()` | Capture individual frames |
|
||||
| `closeCaptureSession()` | Clean up a capture session |
|
||||
| `getCompositionDuration()` | Get total composition duration |
|
||||
| `getCapturePerfSummary()` | Get capture performance metrics |
|
||||
| `createFileServer()` | Create an HTTP file server for serving assets |
|
||||
| `createVideoFrameInjector()` | Create a video frame injector for page |
|
||||
| `resolveConfig()` / `DEFAULT_CONFIG` | Producer configuration |
|
||||
| `createConsoleLogger()` / `defaultLogger` | Logging utilities |
|
||||
| `quantizeTimeToFrame()` | Convert time to frame boundary |
|
||||
| `resolveRenderPaths()` | Resolve render directory paths |
|
||||
| `prepareHyperframeLintBody()` / `runHyperframeLint()` | Linting utilities |
|
||||
|
||||
## Logging
|
||||
|
||||
The producer ships a small pluggable logger so callers can inject Pino, Winston, or any structured backend without taking a dependency on it.
|
||||
|
||||
```ts
|
||||
export type LogLevel = "error" | "warn" | "info" | "debug";
|
||||
|
||||
export interface ProducerLogger {
|
||||
error(message: string, meta?: Record<string, unknown>): void;
|
||||
warn(message: string, meta?: Record<string, unknown>): void;
|
||||
info(message: string, meta?: Record<string, unknown>): void;
|
||||
debug(message: string, meta?: Record<string, unknown>): void;
|
||||
isLevelEnabled?(level: LogLevel): boolean;
|
||||
}
|
||||
```
|
||||
|
||||
`createConsoleLogger(level)` returns a console-backed implementation that filters by level and JSON-stringifies the optional `meta` object. `defaultLogger` is the singleton at `level="info"`.
|
||||
|
||||
### Skipping expensive metadata in hot paths
|
||||
|
||||
`isLevelEnabled` is **optional** so existing custom loggers keep working unchanged. When you build a non-trivial meta object in a hot loop just to attach to a debug log, gate the construction with the nullish-coalescing pattern so production runs (`level=info`) pay nothing while loggers without the method behave exactly as before:
|
||||
|
||||
```ts
|
||||
// Inside a per-frame loop in the encode pipeline:
|
||||
if (i % 30 === 0 && (log.isLevelEnabled?.("debug") ?? true)) {
|
||||
const hdrEl = stackingInfo.find((e) => e.isHdr);
|
||||
log.debug("[Render] HDR layer composite frame", {
|
||||
frame: i,
|
||||
time: time.toFixed(2),
|
||||
hdrElement: hdrEl
|
||||
? { z: hdrEl.zIndex, visible: hdrEl.visible, width: hdrEl.width }
|
||||
: null,
|
||||
stackingCount: stackingInfo.length,
|
||||
activeTransition: activeTransition?.shader,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The `?? true` fallback means callers using a custom logger that does not implement `isLevelEnabled` continue to build and pass the meta object — the optimization is opt-in for logger implementations that want it.
|
||||
|
||||
## Regression Testing
|
||||
|
||||
The producer includes a regression harness for comparing render output against golden baselines. This is useful for catching visual regressions when changing the runtime, engine, or rendering pipeline.
|
||||
|
||||
```bash
|
||||
cd packages/producer
|
||||
|
||||
# Build the test Docker image
|
||||
bun run docker:build:test
|
||||
|
||||
# Run regression tests (compares output against golden baselines)
|
||||
bun run docker:test
|
||||
|
||||
# Regenerate golden baselines after intentional changes
|
||||
bun run docker:test:update
|
||||
```
|
||||
|
||||
## Benchmarking
|
||||
|
||||
Find optimal render settings for your hardware:
|
||||
|
||||
```bash
|
||||
# Via the CLI
|
||||
npx hyperframes benchmark
|
||||
|
||||
# Directly from the producer package
|
||||
cd packages/producer
|
||||
bun run benchmark
|
||||
```
|
||||
|
||||
The benchmark runs several compositions with different quality and FPS settings and reports timing for each combination.
|
||||
|
||||
## External assets (files outside `projectDir`)
|
||||
|
||||
A composition can reference absolute paths to assets outside the project
|
||||
directory — a local voiceover in `~/Downloads`, a shared-drive image, a
|
||||
generated fixture at an absolute path. The producer handles these by:
|
||||
|
||||
1. **Detection.** During compilation, the HTML compiler walks every
|
||||
`[src]` / `[href]` and every `url(...)` in `<style>`. A path that
|
||||
resolves to a file outside `projectDir` is collected into an
|
||||
`externalAssets` map.
|
||||
2. **Sanitised keys.** Each absolute path is converted into a safe,
|
||||
cross-platform relative key prefixed with `hf-ext/`. Windows
|
||||
drive-letter colons are stripped (`D:\foo\x.wav` → `hf-ext/D/foo/x.wav`)
|
||||
so that `path.join(compileDir, key)` stays inside the compile
|
||||
directory on every OS.
|
||||
3. **Copy + rewrite.** The orchestrator copies the file under
|
||||
`<compileDir>/hf-ext/...` and the HTML is rewritten to point at the
|
||||
sanitised key. The file server then serves both project-internal and
|
||||
external assets from the same root.
|
||||
|
||||
The containment check uses `path.relative()` rather than a hardcoded
|
||||
separator, so external assets work identically on macOS, Linux, and
|
||||
Windows. See `packages/producer/src/utils/paths.ts` for the helpers.
|
||||
|
||||
## Related Packages
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="CLI" icon="terminal" href="/packages/cli">
|
||||
Command-line interface that wraps the producer for rendering, previewing, and more.
|
||||
</Card>
|
||||
<Card title="Engine" icon="gear" href="/packages/engine">
|
||||
The low-level capture pipeline that the producer uses to grab frames.
|
||||
</Card>
|
||||
<Card title="Core" icon="cube" href="/packages/core">
|
||||
Types, runtime, and linter that the producer depends on.
|
||||
</Card>
|
||||
<Card title="Studio" icon="palette" href="/packages/studio">
|
||||
Visual editor for building compositions before rendering with the producer.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,227 @@
|
||||
---
|
||||
title: "@hyperframes/sdk"
|
||||
description: "Headless, framework-neutral composition editing engine for agents and custom editors."
|
||||
---
|
||||
|
||||
The SDK package provides a programmatic editing layer for HyperFrames compositions. It opens composition HTML, exposes query and mutation APIs, emits JSON patches, supports undo/redo, and can persist changes through pluggable adapters without requiring React, Studio, or a browser UI.
|
||||
|
||||
```bash
|
||||
npm install @hyperframes/sdk
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
**Use `@hyperframes/sdk` when you need to:**
|
||||
- Build a custom composition editor in your own application
|
||||
- Let an agent inspect and edit composition HTML without driving a browser UI
|
||||
- Apply batch text, style, timing, asset, variable, or animation edits from code
|
||||
- Track undo/redo and patch events for host application history
|
||||
- Persist edited HTML through memory, filesystem, or custom storage adapters
|
||||
- Layer sparse overrides on top of a reusable base composition template
|
||||
|
||||
**Use a different package if you want to:**
|
||||
- Render compositions to MP4 or WebM - use the [CLI](/packages/cli) or [producer](/packages/producer)
|
||||
- Preview and edit visually out of the box - use the [CLI](/packages/cli) (`npx hyperframes preview`) or [studio](/packages/studio)
|
||||
- Parse, lint, or generate low-level composition HTML - use [core](/packages/core)
|
||||
- Capture frames from a headless browser - use [engine](/packages/engine)
|
||||
|
||||
<Tip>
|
||||
The SDK is the right layer for product integrations and agents that need structured edits. The CLI and Studio are user-facing tools built around the same composition format; the SDK is the editing engine you embed behind your own UI or automation.
|
||||
</Tip>
|
||||
|
||||
<Note>
|
||||
This page is the package overview. For the full API — guides and a complete reference for every method, operation, type, and adapter — see the [**SDK**](/sdk/overview) tab.
|
||||
</Note>
|
||||
|
||||
## Package Exports
|
||||
|
||||
| Import | Description |
|
||||
|--------|-------------|
|
||||
| `@hyperframes/sdk` | Main editing API, types, memory/headless adapters, iframe preview adapter |
|
||||
| `@hyperframes/sdk/adapters/memory` | In-memory persistence adapter for tests, demos, and ephemeral sessions |
|
||||
| `@hyperframes/sdk/adapters/fs` | Node.js filesystem persistence adapter with version history |
|
||||
| `@hyperframes/sdk/adapters/headless` | No-op preview adapter for agents, CI, and server-side editing |
|
||||
|
||||
## Quick Start
|
||||
|
||||
Open HTML, edit explicit element IDs, then serialize the updated composition:
|
||||
|
||||
```typescript
|
||||
import { openComposition } from "@hyperframes/sdk";
|
||||
|
||||
const comp = await openComposition(html);
|
||||
|
||||
const [headlineId] = comp.find({ text: "Old headline" });
|
||||
if (headlineId) {
|
||||
comp.setText(headlineId, "New headline");
|
||||
comp.setStyle(headlineId, {
|
||||
color: "#FFD60A",
|
||||
fontSize: "96px",
|
||||
});
|
||||
}
|
||||
|
||||
const updatedHtml = comp.serialize();
|
||||
comp.dispose();
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Explicit Element IDs
|
||||
|
||||
All edits target stable HyperFrames element IDs. This makes the SDK safe for headless agents and backend jobs because mutations do not depend on mouse state or a current UI selection.
|
||||
|
||||
```typescript
|
||||
const allElements = comp.getElements();
|
||||
const imageIds = allElements
|
||||
.filter((element) => element.tag === "img")
|
||||
.map((element) => element.id);
|
||||
|
||||
for (const id of imageIds) {
|
||||
comp.setAttribute(id, "loading", "eager");
|
||||
}
|
||||
```
|
||||
|
||||
### Typed Methods
|
||||
|
||||
The common editing operations have typed convenience methods:
|
||||
|
||||
```typescript
|
||||
comp.setText("hf-title", "Launch day");
|
||||
comp.setStyle("hf-title", { color: "#ffffff", transform: "translateY(24px)" });
|
||||
comp.setAttribute("hf-logo", "src", "/assets/logo.png");
|
||||
comp.setTiming("hf-title", { start: 0.5, duration: 2.5 });
|
||||
comp.setVariableValue("brandColor", "#6C5CE7");
|
||||
comp.removeElement("hf-old-caption");
|
||||
```
|
||||
|
||||
Use `batch()` when several mutations should become one undo entry, one persist write, and one change notification:
|
||||
|
||||
```typescript
|
||||
comp.batch(() => {
|
||||
comp.setText("hf-title", "Version 2");
|
||||
comp.setStyle("hf-title", { color: "#22C55E" });
|
||||
comp.setTiming("hf-title", { start: 1, duration: 3 });
|
||||
});
|
||||
```
|
||||
|
||||
### Advanced Dispatch API
|
||||
|
||||
Agents and automation can emit data-shaped operations through `dispatch()`:
|
||||
|
||||
```typescript
|
||||
comp.dispatch({
|
||||
type: "setStyle",
|
||||
target: "hf-card",
|
||||
styles: { borderRadius: "24px" },
|
||||
});
|
||||
```
|
||||
|
||||
Before showing a UI control or applying an optional operation, call `can()`:
|
||||
|
||||
```typescript
|
||||
const result = comp.can({
|
||||
type: "setGsapTween",
|
||||
animationId: "anim-1",
|
||||
properties: { ease: "power3.out" },
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
comp.setGsapTween("anim-1", { ease: "power3.out" });
|
||||
}
|
||||
```
|
||||
|
||||
## Persistence
|
||||
|
||||
Pass a persistence adapter to autosave edits. The SDK ships memory and filesystem adapters, and host applications can implement the same `PersistAdapter` interface for S3, HTTP, IndexedDB, or app-specific storage.
|
||||
|
||||
```typescript
|
||||
import { openComposition } from "@hyperframes/sdk";
|
||||
import { createFsAdapter } from "@hyperframes/sdk/adapters/fs";
|
||||
|
||||
const comp = await openComposition(html, {
|
||||
persist: createFsAdapter({ root: "./project" }),
|
||||
persistPath: "index.html",
|
||||
});
|
||||
|
||||
comp.setText("hf-title", "Saved title");
|
||||
await comp.flush();
|
||||
```
|
||||
|
||||
Persistence failures are emitted as events instead of crashing the session:
|
||||
|
||||
```typescript
|
||||
comp.on("persist:error", ({ error }) => {
|
||||
console.error("Autosave failed:", error.message);
|
||||
});
|
||||
```
|
||||
|
||||
## Undo, Redo, and Patch Events
|
||||
|
||||
Standalone sessions include undo/redo by default:
|
||||
|
||||
```typescript
|
||||
comp.setText("hf-title", "Draft");
|
||||
comp.undo();
|
||||
comp.redo();
|
||||
```
|
||||
|
||||
Patch events let a host application mirror SDK edits into its own state, collaboration layer, or audit log:
|
||||
|
||||
```typescript
|
||||
const unsubscribe = comp.on("patch", ({ patches, inversePatches, origin }) => {
|
||||
saveToHostHistory({ patches, inversePatches, origin });
|
||||
});
|
||||
|
||||
unsubscribe();
|
||||
```
|
||||
|
||||
## Embedded Override Mode
|
||||
|
||||
For template-driven products, open a composition with an `overrides` object. The SDK applies the sparse override set on top of the base HTML, accumulates additional edits into that override set, and lets the host store only the delta.
|
||||
|
||||
```typescript
|
||||
const comp = await openComposition(templateHtml, {
|
||||
overrides: {
|
||||
"hf-title.text": "Customer-specific title",
|
||||
"hf-logo.attr.src": "/customers/acme/logo.png",
|
||||
},
|
||||
history: false,
|
||||
});
|
||||
|
||||
comp.setStyle("hf-title", { color: "#0EA5E9" });
|
||||
|
||||
const nextOverrides = comp.getOverrides();
|
||||
```
|
||||
|
||||
Use `applyPatches()` when the host owns undo/redo and needs to replay inverse patches into the SDK without creating loops.
|
||||
|
||||
## Preview Adapters
|
||||
|
||||
The SDK can run headlessly, or it can connect to a preview surface:
|
||||
|
||||
```typescript
|
||||
import { openComposition, createHeadlessAdapter } from "@hyperframes/sdk";
|
||||
|
||||
const comp = await openComposition(html, {
|
||||
preview: createHeadlessAdapter(),
|
||||
});
|
||||
```
|
||||
|
||||
For browser integrations, `createIframePreviewAdapter()` bridges the SDK to a same-origin composition iframe so hit-testing, selection, and draft preview updates can stay outside the model mutation path.
|
||||
|
||||
## Related Packages
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="@hyperframes/core" icon="cube" href="/packages/core">
|
||||
Low-level types, HTML utilities, runtime helpers, and linter APIs.
|
||||
</Card>
|
||||
<Card title="@hyperframes/studio" icon="palette" href="/packages/studio">
|
||||
Ready-made visual editor UI that can embed SDK-driven editing flows.
|
||||
</Card>
|
||||
<Card title="@hyperframes/producer" icon="video" href="/packages/producer">
|
||||
Programmatic rendering pipeline for turning edited HTML into video.
|
||||
</Card>
|
||||
<Card title="CLI" icon="terminal" href="/packages/cli">
|
||||
Command-line create, preview, lint, and render workflows.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,143 @@
|
||||
---
|
||||
title: "@hyperframes/shader-transitions"
|
||||
description: "WebGL shader transitions for HyperFrames scenes and compositions."
|
||||
---
|
||||
|
||||
The shader transitions package adds GPU-accelerated scene-to-scene transitions to HyperFrames compositions. It captures scene samples, uploads them as WebGL textures, and drives fragment-shader compositing from a GSAP timeline.
|
||||
|
||||
```bash
|
||||
npm install @hyperframes/shader-transitions
|
||||
```
|
||||
|
||||
You can also load the browser global build directly:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/@hyperframes/shader-transitions/dist/index.global.js"></script>
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
**Use `@hyperframes/shader-transitions` when you need to:**
|
||||
- Add shader-based scene transitions such as domain warp, whip pan, glitch, iris, light leak, or thermal distortion
|
||||
- Attach GPU transitions to an existing GSAP timeline
|
||||
- Build a transition picker or validation UI around the available shader registry
|
||||
- Feature-detect native HTML-in-canvas capture support
|
||||
- Use the producer's deterministic page-side compositor for render-mode captures
|
||||
|
||||
**Use a different package if you want to:**
|
||||
- Render the finished composition to video - use the [CLI](/packages/cli) or [producer](/packages/producer)
|
||||
- Edit composition structure programmatically - use [sdk](/packages/sdk)
|
||||
- Build a visual editor surface - use [studio](/packages/studio)
|
||||
|
||||
## Package Exports
|
||||
|
||||
| Import | Description |
|
||||
|--------|-------------|
|
||||
| `init()` | Creates or augments a GSAP timeline with shader transitions |
|
||||
| `SHADER_NAMES` | Array of supported shader names for validation and UI pickers |
|
||||
| `isHtmlInCanvasCaptureSupported()` | Feature-detects Chrome's native HTML-in-canvas capture path |
|
||||
| `installPageSideCompositor()` | Installs the render-mode compositor used by the producer path |
|
||||
| `isPageSideCompositingSupported()` | Checks whether page-side shader compositing is available |
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { init } from "@hyperframes/shader-transitions";
|
||||
|
||||
const timeline = init({
|
||||
bgColor: "#0a0a0a",
|
||||
accentColor: "#ff6b2b",
|
||||
scenes: ["scene-1", "scene-2", "scene-3"],
|
||||
transitions: [
|
||||
{ time: 3, shader: "domain-warp", duration: 0.8 },
|
||||
{ time: 8, shader: "light-leak", duration: 0.7 },
|
||||
],
|
||||
});
|
||||
|
||||
window.__timelines ??= {};
|
||||
window.__timelines.hero = timeline;
|
||||
```
|
||||
|
||||
Pass an existing GSAP timeline when the composition already owns the animation sequence:
|
||||
|
||||
```typescript
|
||||
const timeline = gsap.timeline({ paused: true });
|
||||
timeline.from("#headline", { opacity: 0, y: 40, duration: 0.6 });
|
||||
|
||||
init({
|
||||
bgColor: "#000000",
|
||||
scenes: ["intro", "demo", "outro"],
|
||||
transitions: [
|
||||
{ time: 5, shader: "cinematic-zoom" },
|
||||
{ time: 12, shader: "glitch", duration: 0.5 },
|
||||
],
|
||||
timeline,
|
||||
});
|
||||
```
|
||||
|
||||
If WebGL is unavailable, the package falls back to normal timeline playback without shader compositing.
|
||||
|
||||
## Available Shaders
|
||||
|
||||
| Shader | Description |
|
||||
|--------|-------------|
|
||||
| `domain-warp` | Organic noise-based warp with a glowing edge |
|
||||
| `ridged-burn` | Ridged noise burn with sparks and heat glow |
|
||||
| `whip-pan` | Horizontal motion blur simulating a fast camera pan |
|
||||
| `sdf-iris` | Circular iris wipe with a glowing ring edge |
|
||||
| `ripple-waves` | Concentric ripple distortion radiating from center |
|
||||
| `gravitational-lens` | Warping gravity well with chromatic aberration |
|
||||
| `cinematic-zoom` | Radial zoom blur with chromatic fringing |
|
||||
| `chromatic-split` | RGB channel separation expanding from center |
|
||||
| `glitch` | Digital glitch with block displacement and scanlines |
|
||||
| `swirl-vortex` | Spiral rotation with noise-based warping |
|
||||
| `thermal-distortion` | Heat shimmer rising from the bottom |
|
||||
| `flash-through-white` | Flash to white, then reveal the next scene |
|
||||
| `cross-warp-morph` | Noise-driven morph blending both scenes |
|
||||
| `light-leak` | Warm cinematic light leak with lens flare |
|
||||
|
||||
Use `SHADER_NAMES` when you need a typed list:
|
||||
|
||||
```typescript
|
||||
import { SHADER_NAMES } from "@hyperframes/shader-transitions";
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```typescript
|
||||
type TransitionConfig = {
|
||||
time: number;
|
||||
shader?: string;
|
||||
duration?: number;
|
||||
ease?: string;
|
||||
};
|
||||
|
||||
type HyperShaderConfig = {
|
||||
bgColor: string;
|
||||
accentColor?: string;
|
||||
scenes: string[];
|
||||
transitions: TransitionConfig[];
|
||||
timeline?: gsap.core.Timeline;
|
||||
compositionId?: string;
|
||||
previewCaptureFps?: number;
|
||||
};
|
||||
```
|
||||
|
||||
`shader` is optional. Omit it to use a CSS fallback transition at that point in the timeline.
|
||||
|
||||
## Preview and Render Behavior
|
||||
|
||||
Browser previews pre-capture transition samples and cache matching snapshots in IndexedDB. Cache keys include the composition ID, scene DOM/style signatures, timing, capture FPS, scale, and dimensions, so normal page refreshes can reuse samples while runtime edits invalidate only adjacent transition caches.
|
||||
|
||||
During producer renders, shader transitions use a deterministic page-side compositor instead of preview-time snapshot caching. That keeps frame capture seek-driven and avoids depending on wall-clock playback.
|
||||
|
||||
## Related Packages
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="@hyperframes/producer" icon="video" href="/packages/producer">
|
||||
Renders compositions that include shader transitions.
|
||||
</Card>
|
||||
<Card title="Transition Catalog" icon="sparkles" href="/catalog/blocks/transitions-distortion">
|
||||
Browse installable transition blocks built on the shader system.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
title: "@hyperframes/studio-server"
|
||||
description: "The studio preview/editor backend — a mountable Hono API, extracted from core."
|
||||
---
|
||||
|
||||
The studio-server package is the HTTP backend that powers studio preview and editing — project routes, file serving, preview bundling, thumbnails, and source-mutation helpers. It was extracted from `@hyperframes/core/studio-api` into a dedicated package so an embedder can mount the studio backend **without** depending on core's full surface, and so core no longer ships a web server it doesn't need at render time.
|
||||
|
||||
```bash
|
||||
npm install @hyperframes/studio-server
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
<Tip>
|
||||
**Most users do not need this package directly.** The [CLI](/packages/cli) (`npx hyperframes preview`) and [studio](/packages/studio) wire it up for you. Reach for it when you're **embedding** the studio backend into your own server.
|
||||
</Tip>
|
||||
|
||||
**Use `@hyperframes/studio-server` when you need to:**
|
||||
- Mount the studio preview/editing API into an existing Node/Hono server
|
||||
- Serve project files and bundled preview HTML to a custom frontend
|
||||
- Drive source mutations (manual edits, draft markers) from your own tooling
|
||||
|
||||
<Info>
|
||||
`@hyperframes/core/studio-api` still resolves (via a back-compat re-export stub), so existing imports keep working. New code should import from `@hyperframes/studio-server` directly.
|
||||
</Info>
|
||||
|
||||
## Package Exports
|
||||
|
||||
| Import | Description |
|
||||
|--------|-------------|
|
||||
| `@hyperframes/studio-server` | `createStudioApi`, helpers, types |
|
||||
| `@hyperframes/studio-server/source-mutation` | Source mutation utilities |
|
||||
| `@hyperframes/studio-server/screenshot-clip` | Element screenshot-clip geometry |
|
||||
| `@hyperframes/studio-server/manual-edits-render-script` | Manual-edits render body script |
|
||||
| `@hyperframes/studio-server/studio-motion-render-script` | Studio motion render body script |
|
||||
| `@hyperframes/studio-server/draft-markers` | Draft gesture-marker attributes |
|
||||
| `@hyperframes/studio-server/finite-mutation` | Finite-mutation safety checks |
|
||||
|
||||
## Mounting the API
|
||||
|
||||
`createStudioApi` returns a [Hono](https://hono.dev) app you can mount into any server. You supply a `StudioApiAdapter` that tells the API how to resolve projects, bundle preview HTML, and lint:
|
||||
|
||||
```typescript
|
||||
import { createStudioApi } from '@hyperframes/studio-server';
|
||||
import type { StudioApiAdapter, ResolvedProject } from '@hyperframes/studio-server';
|
||||
|
||||
const adapter: StudioApiAdapter = {
|
||||
listProjects: () => projects,
|
||||
resolveProject: (id) => projectsById.get(id) ?? null,
|
||||
bundle: async (projectDir) => bundleToSingleHtml(projectDir),
|
||||
lint: (html, opts) => lintHyperframeHtml(html, opts),
|
||||
runtimeUrl: '/hyperframe-runtime.js',
|
||||
rendersDir: (project) => join(project.dir, 'renders'),
|
||||
startRender: async (opts) => startRenderJob(opts),
|
||||
};
|
||||
|
||||
const api = createStudioApi(adapter); // → Hono app
|
||||
|
||||
// Mount under /api in your own Hono server
|
||||
app.route('/api', api);
|
||||
```
|
||||
|
||||
The adapter is the seam between the framework-agnostic route logic and your storage / bundling / lint implementation — the [CLI](/packages/cli) supplies a filesystem-backed adapter, but you can back it with anything.
|
||||
|
||||
## Helpers
|
||||
|
||||
```typescript
|
||||
import {
|
||||
createProjectSignature, // cache key for a project's files
|
||||
isSafePath, // path-traversal guard
|
||||
walkDir,
|
||||
getMimeType,
|
||||
buildSubCompositionHtml,
|
||||
getElementScreenshotClip,
|
||||
} from '@hyperframes/studio-server';
|
||||
import type {
|
||||
ResolvedProject,
|
||||
RenderJobState,
|
||||
LintResult,
|
||||
ScreenshotClip,
|
||||
} from '@hyperframes/studio-server';
|
||||
```
|
||||
|
||||
## Related Packages
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Studio" icon="palette" href="/packages/studio">
|
||||
The browser editor UI this server backs.
|
||||
</Card>
|
||||
<Card title="@hyperframes/parsers" icon="code" href="/packages/parsers">
|
||||
The HTML + GSAP parsing layer it builds on.
|
||||
</Card>
|
||||
<Card title="@hyperframes/core" icon="cube" href="/packages/core">
|
||||
Types and runtime; re-exports the studio API for back-compat.
|
||||
</Card>
|
||||
<Card title="CLI" icon="terminal" href="/packages/cli">
|
||||
`npx hyperframes preview` wires this server up for you.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,254 @@
|
||||
---
|
||||
title: "@hyperframes/studio"
|
||||
description: "Visual composition editor with live preview, timeline view, and hot reload."
|
||||
---
|
||||
|
||||
The studio package provides a browser-based visual editor for creating and previewing Hyperframes compositions. It gives you a real-time preview of your video, a visual timeline of all clips, and player controls for seeking and playback — all updating live as you edit your HTML.
|
||||
|
||||
```bash
|
||||
npm install @hyperframes/studio
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
**Use `@hyperframes/studio` when you need to:**
|
||||
- Build a custom composition editor UI (e.g., embedded in your own web application)
|
||||
- Integrate the Hyperframes preview player into a larger product
|
||||
- Extend the editor with custom panels, toolbars, or integrations
|
||||
|
||||
**Use a different package if you want to:**
|
||||
- Preview compositions during development — use the [CLI](/packages/cli) (`npx hyperframes preview`), which launches the studio for you
|
||||
- Render compositions to MP4 — use the [CLI](/packages/cli) or [producer](/packages/producer)
|
||||
- Capture frames programmatically — use the [engine](/packages/engine)
|
||||
|
||||
<Tip>
|
||||
**For most development workflows, you do not need to install the studio directly.** Running `npx hyperframes preview` starts the studio automatically with hot reload. Install `@hyperframes/studio` only if you are embedding the editor into your own application.
|
||||
</Tip>
|
||||
|
||||
## Running the Studio
|
||||
|
||||
### Via the CLI (recommended)
|
||||
|
||||
```bash
|
||||
npx hyperframes preview
|
||||
```
|
||||
|
||||
This starts the studio development server, opens your composition in the browser, and watches for file changes. This is the easiest way to get a live preview.
|
||||
|
||||
### From the monorepo
|
||||
|
||||
```bash
|
||||
# From the root
|
||||
bun run dev
|
||||
|
||||
# Or target the studio package directly
|
||||
bun run --filter @hyperframes/studio dev
|
||||
```
|
||||
|
||||
## Package Exports
|
||||
|
||||
The studio has two entry points:
|
||||
|
||||
| Import | Description |
|
||||
|--------|-------------|
|
||||
| `@hyperframes/studio` | React components, hooks, and types |
|
||||
| `@hyperframes/studio/tailwind-preset` | Tailwind CSS preset for studio styling |
|
||||
|
||||
Peer dependencies: `react` (19), `react-dom` (19), `zustand` (4 or 5).
|
||||
|
||||
## Components
|
||||
|
||||
### Layout
|
||||
|
||||
```typescript
|
||||
import { NLELayout, NLEPreview, CompositionBreadcrumb } from '@hyperframes/studio';
|
||||
import type { CompositionLevel } from '@hyperframes/studio';
|
||||
|
||||
// Main NLE (Non-Linear Editor) layout container
|
||||
<NLELayout>
|
||||
{/* Preview, timeline, and editor panels */}
|
||||
</NLELayout>
|
||||
|
||||
// Preview panel
|
||||
<NLEPreview />
|
||||
|
||||
// Breadcrumb navigation for nested compositions
|
||||
<CompositionBreadcrumb levels={levels} />
|
||||
```
|
||||
|
||||
### Player & Timeline
|
||||
|
||||
```typescript
|
||||
import {
|
||||
Player,
|
||||
PlayerControls,
|
||||
Timeline,
|
||||
} from '@hyperframes/studio';
|
||||
import type { TimelineElement } from '@hyperframes/studio';
|
||||
|
||||
// Embed the preview player
|
||||
<Player />
|
||||
|
||||
// Playback controls (play, pause, seek, frame-step)
|
||||
<PlayerControls />
|
||||
|
||||
// Timeline editor with scrubber
|
||||
<Timeline />
|
||||
```
|
||||
|
||||
### Editor Components
|
||||
|
||||
```typescript
|
||||
import { SourceEditor, PropertyPanel, FileTree } from '@hyperframes/studio';
|
||||
|
||||
// Code editor (CodeMirror-based) for HTML, CSS, and JavaScript
|
||||
<SourceEditor />
|
||||
|
||||
// Property inspector for selected elements
|
||||
<PropertyPanel />
|
||||
|
||||
// Project file browser
|
||||
<FileTree />
|
||||
```
|
||||
|
||||
### Full Application
|
||||
|
||||
```typescript
|
||||
import { StudioApp } from '@hyperframes/studio';
|
||||
|
||||
// The complete studio application (wraps all components)
|
||||
<StudioApp />
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
### `useTimelinePlayer`
|
||||
|
||||
Manages player state and playback control:
|
||||
|
||||
```typescript
|
||||
import { useTimelinePlayer } from '@hyperframes/studio';
|
||||
|
||||
const player = useTimelinePlayer();
|
||||
// player.play(), player.pause(), player.togglePlay(), player.seek(time)
|
||||
// player.refreshPlayer(), player.saveSeekPosition(), player.resetPlayer()
|
||||
```
|
||||
|
||||
### `usePlayerStore`
|
||||
|
||||
Zustand store for player state:
|
||||
|
||||
```typescript
|
||||
import { usePlayerStore, liveTime, formatTime } from '@hyperframes/studio';
|
||||
|
||||
const store = usePlayerStore();
|
||||
// Access current time, duration, playing state, etc.
|
||||
|
||||
// Format time for display
|
||||
const display = formatTime(liveTime.current);
|
||||
```
|
||||
|
||||
### `useElementPicker`
|
||||
|
||||
Element selection from the preview:
|
||||
|
||||
```typescript
|
||||
import { useElementPicker } from '@hyperframes/studio';
|
||||
|
||||
const picker = useElementPicker(iframeRef);
|
||||
// picker.isPickMode, picker.pickedElement
|
||||
// picker.enablePick(), picker.disablePick(), picker.clearPick()
|
||||
// picker.setStyle(prop, value), picker.setDataAttr(name, value), picker.setTextContent(text)
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Live Preview
|
||||
|
||||
The studio renders your composition in an iframe using the Hyperframes runtime. What you see in the preview is exactly what will be captured during rendering — the same runtime code, the same seek logic, the same clip lifecycle.
|
||||
|
||||
Changes to your HTML are picked up automatically through hot reload, so you can edit `index.html` in your editor and see the result in the browser within milliseconds.
|
||||
|
||||
<Note>
|
||||
The *visual* output of preview matches render exactly. Real-time *playback smoothness* depends on your hardware, because preview actually plays the composition in your browser at 30/60fps. Render doesn't have that constraint — it captures each frame individually via a seek-driven pipeline, so expensive frames make the render slower but never drop. If you see stutter in preview but the rendered mp4 is clean, that's expected. See [Performance](/guides/performance) for the patterns that most often cause it.
|
||||
</Note>
|
||||
|
||||
### Timeline View
|
||||
|
||||
The timeline panel provides a visual representation of your composition's structure:
|
||||
|
||||
- Each clip appears as a colored bar on its track
|
||||
- Bar position and width reflect `data-start` and `data-duration`
|
||||
- Visually higher rows render in front; lower rows render underneath
|
||||
- Relative timing references (e.g., `data-start="intro"`) are resolved and displayed as absolute positions
|
||||
|
||||
This makes it easy to understand the temporal structure of complex compositions with many overlapping clips.
|
||||
|
||||
### Timeline Editing
|
||||
|
||||
The timeline supports move and trim actions that persist directly back into your HTML source.
|
||||
|
||||
For a full breakdown of:
|
||||
|
||||
- what timeline editing can do today
|
||||
- how each action maps to `data-start`, `data-duration`, `data-track-index`, and `z-index`
|
||||
- which clip types support start trim
|
||||
- current limitations and mental models
|
||||
|
||||
see [Timeline Editing](/guides/timeline-editing).
|
||||
|
||||
### Player Controls
|
||||
|
||||
The studio includes a full set of playback controls:
|
||||
|
||||
- **Play / Pause** — start and stop playback
|
||||
- **Seek** — click anywhere on the timeline to jump to that point
|
||||
- **Scrub** — drag the playhead to scrub through the composition frame by frame
|
||||
- **Frame step** — advance or rewind one frame at a time for precise positioning
|
||||
|
||||
### Hot Reload
|
||||
|
||||
File changes are detected and applied without restarting the server. The preview maintains its current playback position when possible, so you can tweak an animation at the 5-second mark without having to seek back to it after every save.
|
||||
|
||||
## Architecture
|
||||
|
||||
The studio is a React application with the following structure:
|
||||
|
||||
1. **Iframe preview** — your composition HTML is loaded in an isolated iframe with the Hyperframes runtime injected. This ensures the preview uses the same rendering path as production.
|
||||
|
||||
2. **Runtime bridge** — the studio communicates with the iframe via `postMessage` to control playback (play, pause, seek) and receive state updates (current time, duration, readiness).
|
||||
|
||||
3. **Timeline component** — parses the composition using `@hyperframes/core` to extract clip timing data and renders the visual timeline panel.
|
||||
|
||||
4. **File watcher** — a development server (Vite-based) watches your project files and triggers hot module replacement when changes are detected.
|
||||
|
||||
## Tailwind CSS Preset
|
||||
|
||||
The studio exports a Tailwind CSS preset for consistent styling:
|
||||
|
||||
```typescript
|
||||
// tailwind.config.ts
|
||||
import studioPreset from '@hyperframes/studio/tailwind-preset';
|
||||
|
||||
export default {
|
||||
presets: [studioPreset],
|
||||
// ... your config
|
||||
};
|
||||
```
|
||||
|
||||
## Related Packages
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="CLI" icon="terminal" href="/packages/cli">
|
||||
Launches the studio via `npx hyperframes preview` — the easiest way to preview compositions.
|
||||
</Card>
|
||||
<Card title="Core" icon="cube" href="/packages/core">
|
||||
Types, parsing, and runtime that the studio uses for preview and timeline rendering.
|
||||
</Card>
|
||||
<Card title="Producer" icon="film" href="/packages/producer">
|
||||
Renders the compositions you build in the studio to finished MP4 files.
|
||||
</Card>
|
||||
<Card title="Engine" icon="gear" href="/packages/engine">
|
||||
The capture engine that powers production rendering of your compositions.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
Reference in New Issue
Block a user