{"_id":"@busyexplore/zotonic-video-engine","name":"@busyexplore/zotonic-video-engine","dist-tags":{"latest":"0.0.1-beta.0"},"versions":{"0.0.1-beta.0":{"name":"@busyexplore/zotonic-video-engine","private":false,"version":"0.0.1-beta.0","type":"module","main":"dist/zotonic-video-engine.es.js","module":"dist/zotonic-video-engine.umd.js","scripts":{"dev":"set NODE_OPTIONS=--openssl-legacy-provider && vite","build":"vite build","lint":"eslint .","preview":"vite preview","deploy":"npm version prerelease --preid=beta --no-git-tag-version && npm publish --tag beta"},"dependencies":{"@busyexplore/zotonic-audio-util":"^0.0.1-beta.2","@busyexplore/zotonic-command-emitter":"0.0.1-beta.8","@busyexplore/zotonic-lip-sync":"^0.0.1-beta.6","mediabunny":"^1.54.0","react":"^16.8 || ^17 || ^18","react-dom":"^16.8 || ^17 || ^18"},"peerDependencies":{"react":"^16.8 || ^17 || ^18","react-dom":"^16.8 || ^17 || ^18"},"devDependencies":{"@eslint/js":"^9.29.0","@types/react":"^19.1.8","@types/react-dom":"^19.1.6","@vitejs/plugin-react":"^4.6.0","eslint":"^9.29.0","eslint-plugin-react-hooks":"^5.2.0","eslint-plugin-react-refresh":"^0.4.20","globals":"^16.2.0","vite":"^6.3.5"},"_id":"@busyexplore/zotonic-video-engine@0.0.1-beta.0","gitHead":"30e92ef3aa4d329375dbae5cc5f2609304a08486","description":"A React hook that wraps a canvas-based video compositor — feed it frames drawn to a `<canvas>`, an audio track, and a frame range, and it composites, crops, and exports a video. Processing/render state is broadcast as events via the re-exported `useWatchC","_nodeVersion":"20.9.0","_npmVersion":"10.1.0","dist":{"integrity":"sha512-Uod78hiCXEL1IabfU2x8TT7Ey3xZmhYBu/Y0fxT8D8lNx+Z//XaBDukYLraO6uvv2sR/LzKpvvEz0HnN7PBCwQ==","shasum":"ea2fbff9ef3356247b72c50b940f55def641a804","tarball":"https://registry.npmjs.org/@busyexplore/zotonic-video-engine/-/zotonic-video-engine-0.0.1-beta.0.tgz","fileCount":4,"unpackedSize":460363,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEWkc8K6IyL4JifQL3zdx3BITy2BdwSil6I98NkXONgSAiAgMZBnrW5Qlbn0Ohd42tMPuMp+NuHEop2wCNHfzFDQ/Q=="}]},"_npmUser":{"name":"busyexplore","email":"suprise.mlimi97@gmail.com"},"directories":{},"maintainers":[{"name":"busyexplore","email":"suprise.mlimi97@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/zotonic-video-engine_0.0.1-beta.0_1787592668394_0.7886822840250907"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-24T17:31:08.066Z","0.0.1-beta.0":"2026-08-24T17:31:08.586Z","modified":"2026-08-24T17:31:08.876Z"},"maintainers":[{"name":"busyexplore","email":"suprise.mlimi97@gmail.com"}],"description":"A React hook that wraps a canvas-based video compositor — feed it frames drawn to a `<canvas>`, an audio track, and a frame range, and it composites, crops, and exports a video. Processing/render state is broadcast as events via the re-exported `useWatchC","readme":"# @busyexplore/zotonic-video-engine\n\nA React hook that wraps a canvas-based video compositor — feed it frames drawn to a `<canvas>`, an audio track, and a frame range, and it composites, crops, and exports a video. Processing/render state is broadcast as events via the re-exported `useWatchCommandEmitter`, so distant components can react without prop drilling.\n\n## Features\n\n- 🖼️ **Frame-by-frame canvas compositing** — draw each frame yourself, hand it to the engine, and it assembles the video\n- ✂️ **Center-crop support** — render to a larger \"working area\" (e.g. for overscan/bleed) and crop down to the camera region on export\n- 🔊 **Audio muxing** — attach an audio source and a frame range to sync it against your rendered frames\n- 📡 **Broadcasts processing/render lifecycle events** (start, stop, rendering progress, update, error, ready) so a progress bar or toast can live anywhere in your app\n- ⚛️ **Single hook API** — `useZotonicVideoEngine()`\n\n## Installation\n\n```bash\nnpm install @busyexplore/zotonic-video-engine\n```\n\n```bash\nyarn add @busyexplore/zotonic-video-engine\n```\n\n```bash\npnpm add @busyexplore/zotonic-video-engine\n```\n\n## Quick start\n\n```jsx\nimport { useRef, useState } from \"react\";\nimport {useZotonicVideoEngine} from \"@busyexplore/zotonic-video-engine\";\n\nfunction Renderer() {\n  const engine = useZotonicVideoEngine();\n  const canvasRef = useRef(null);\n  const [rendering, setRendering] = useState(false);\n\n  const render = async () => {\n    setRendering(true);\n\n    engine.setExternalCanvasInfo(1280, 720, 2560, 1440); // camera size, working-area size\n    engine.setFPS(24);\n    engine.setTotalFrames(24 * 3); // 3 seconds\n\n    await engine.loadAudio(audioInput, 0, 24 * 3);\n\n    canvasRef.current.width = 2560;\n    canvasRef.current.height = 1440;\n\n    for (let frame = 0; frame < engine.getTotalFrames(); frame++) {\n      const ctx = canvasRef.current.getContext(\"2d\");\n      // ...draw your scene for this frame onto ctx...\n\n      await engine.drawFrame(frame, canvasRef.current, true); // true = crop to camera region\n    }\n\n    await engine.exportVideo(false, true, \"animation.webm\");\n    setRendering(false);\n  };\n\n  return (\n    <div>\n      <canvas ref={canvasRef} width={1280} height={720} />\n      <button onClick={render} disabled={rendering}>\n        {rendering ? \"Rendering…\" : \"Render\"}\n      </button>\n    </div>\n  );\n}\n```\n\n## API\n\n### `useZotonicVideoEngine()`\n\nReact hook, no arguments. Fires `ACTION_READY` shortly after mount. Returns:\n\n| Key | Type | Description |\n|---|---|---|\n| `setExternalCanvasInfo` | `(cameraWidth?, cameraHeight?, workingAreaWidth?, workingAreaHeight?) => void` | Sets the visible \"camera\" (output) size and the larger \"working area\" you draw onto. Defaults to `1280×720` for all four if omitted. |\n| `setFPS` | `(fps?: number) => void` | Sets the frame rate (default `24`). |\n| `setTotalFrames` | `(totalFrames?: number) => void` | Sets how many frames the render will produce (default `24`). |\n| `getTotalFrames` | `() => number` | Returns the total frame count previously set. |\n| `loadAudio` | `(audioInput, startFrame?: number, endFrame?: number) => Promise<void>` | Attaches an audio source, trimmed/aligned to `[startFrame, endFrame]`. |\n| `drawFrame` | `(currentFrame: number, sourceCanvas: HTMLCanvasElement, cropCenter?: boolean) => Promise<void>` | Takes the frame you drew on `sourceCanvas` (at working-area size) and commits it as `currentFrame`. If `cropCenter` is `true`, crops down to the centered camera region set via `setExternalCanvasInfo`. |\n| `nextFrame` | `(currentFrame?: number) => void` | Advances the engine's internal frame cursor. |\n| `getCanvaContext` | `() => CanvasRenderingContext2D` | Returns the engine's internal canvas 2D context. |\n| `exportVideo` | `(returnBuffer?: boolean, autoDownload?: boolean, fileName?: string) => Promise<Buffer \\| void>` | Finalizes and exports the rendered video. If `returnBuffer` is `true`, resolves with the file's buffer; if `autoDownload` is `true`, triggers a browser download as `fileName` (default `\"file.webm\"`). |\n| `dispose` | `() => void` | Tears down the current engine instance and replaces it with a fresh one, carrying over the current `fps` and `totalFrames`. |\n| `useWatchCommandEmitter` | `hook` | Re-exported so other components can subscribe to video-engine events. See [Listening for events](#listening-for-events). |\n\n> `setExternalCanvasInfo`, `setFPS`, and `setTotalFrames` configure the *next* render — call them before your `drawFrame` loop, not mid-loop.\n\n### `VIDEO_ENGINE_TOOL`\n\nCommand/action name constants, importable directly:\n\n```js\nimport { VIDEO_ENGINE_TOOL } from \"@busyexplore/zotonic-video-engine\";\n\nexport const VIDEO_ENGINE_TOOL = {\n  ACTION_START_PROCESSING: \"START_PROCESSING\",\n  ACTION_STOP_PROCESSING: \"STOP_PROCESSING\",\n  ACTION_RENDERING: \"ACTION_RENDERING\",\n  ACTION_UPDATE: \"update\",\n  ACTION_READY: \"ready\",\n  ACTION_ERROR: \"ERROR\",\n  VIDEO_ENGINE_COMMAND: \"VIDEO_ENGINE\",\n};\n```\n\n| Action | Emitted when |\n|---|---|\n| `ACTION_READY` | The hook has mounted and the engine is ready to use |\n| `ACTION_START_PROCESSING` | A render/export operation begins |\n| `ACTION_RENDERING` | A frame has been drawn/committed — event data includes `current` (frame index), useful for progress tracking |\n| `ACTION_UPDATE` | Engine state has changed and fresh data is available — event data is the relevant payload |\n| `ACTION_STOP_PROCESSING` | A render/export operation finishes (success path) |\n| `ACTION_ERROR` | An operation (e.g. loading audio, exporting) failed — event data has a `message` |\n\nAll events are emitted under `VIDEO_ENGINE_TOOL.VIDEO_ENGINE_COMMAND`, so filter on that first, then switch on `action`.\n\n## Listening for events\n\nBecause `useWatchCommandEmitter` is re-exported from this package, any component can subscribe to render progress without receiving props from the component that owns the render loop. It subscribes for the lifetime of the calling component and cleans up automatically on unmount:\n\n```jsx\nimport  { useZotonicVideoEngine,VIDEO_ENGINE_TOOL, useWatchCommandEmitter } from \"@busyexplore/zotonic-video-engine\";\n\nfunction RenderProgress() {\n  const { useWatchCommandEmitter } = useWatchCommandEmitter();\n\n  useWatchCommandEmitter((command, action, data) => {\n    if (command !== VIDEO_ENGINE_TOOL.VIDEO_ENGINE_COMMAND) return;\n\n    switch (action) {\n      case VIDEO_ENGINE_TOOL.ACTION_START_PROCESSING:\n        console.log(\"Render started…\");\n        break;\n      case VIDEO_ENGINE_TOOL.ACTION_RENDERING:\n        console.log(\"Frame:\", data.current);\n        break;\n      case VIDEO_ENGINE_TOOL.ACTION_STOP_PROCESSING:\n        console.log(\"Render finished\");\n        break;\n      case VIDEO_ENGINE_TOOL.ACTION_ERROR:\n        console.error(data.message);\n        break;\n    }\n  });\n\n  return null;\n}\n```\n\nThis is useful for surfacing a global progress bar or toast from a component that isn't the one calling `drawFrame`/`exportVideo`.\n\n## Behavior notes\n\n- **`drawFrame` expects working-area coordinates** — draw your full scene (including any off-camera bleed) at the `workingAreaWidth`/`workingAreaHeight` size set in `setExternalCanvasInfo`, then let `cropCenter: true` handle cropping down to the camera region on export.\n- **Configure before you loop** — `setExternalCanvasInfo`, `setFPS`, and `setTotalFrames` should be called once before the `drawFrame` loop starts; changing them mid-render is not supported.\n- **Events are global, not scoped to the calling component** — every subscriber in your app receives every emitted command, so filter by `command` (and `action`) in your listener.\n- **`dispose` preserves fps/totalFrames** — calling it tears down and replaces the underlying engine instance, but the frame rate and total frame count you'd already set carry over to the new instance.\n- **`exportVideo` can both return and download** — pass `returnBuffer: true` if you need the raw file (e.g. to upload it) in addition to, or instead of, triggering a browser download.\n\n**Happy rendering!** 🎬","readmeFilename":"README.md","_rev":"1-8461d2c42992bcd599b8daf51396b8b6"}