chore: import upstream snapshot with attribution
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 (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
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 (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user