npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@videoflow/core

v1.0.15

Published

Core library for VideoFlow — data model, layers, flow API, and video JSON compilation

Readme

@videoflow/core

The core package of VideoFlow — define and compose videos programmatically using a fluent TypeScript API, then compile to a portable JSON format.

This is the foundation of VideoFlow. Use it to build your video's layers, animations, and timeline, then hand off the compiled JSON to a renderer.

Installation

npm install @videoflow/core

Quick Start

import VideoFlow from '@videoflow/core';

// Create a video project
const $ = new VideoFlow({
  width: 1920,
  height: 1080,
  fps: 30,
  name: 'My Video',
});

// Add layers and animate them
const title = $.addText({
  text: 'Hello, VideoFlow!',
  fontSize: 2.5,
  fontWeight: 800,
  color: '#ffffff',
});

title.fadeIn('1s');
$.wait('2s');
title.fadeOut('1s');

// Compile to portable JSON
const videoJSON = await $.compile();

// Or render directly to MP4 (auto-detects environment)
await $.renderVideo({
  outputType: 'file',
  output: './output.mp4',
});

VideoFlow Class

Main entry point for creating videos.

const $ = new VideoFlow(options?: {
  name?: string;           // Default: 'Untitled Video'
  width?: number;           // Default: 1920
  height?: number;          // Default: 1080
  fps?: number;             // Default: 30
  backgroundColor?: string; // Default: '#000000'
  autoDetectDurations?: boolean; // Probe video/audio sources at compile() to fill in their duration. Default: true
  verbose?: boolean;        // Default: false
});

Layer Hierarchy

Layers inherit their properties and settings through a class hierarchy. When you use addText, addImage, etc., your layer gets everything from every parent class:

BaseLayer              → id, timing (startTime, sourceDuration, speed, sourceStart), enabled
 ├── VisualLayer       → opacity, position, scale, rotation, anchor, borders, filters, shadows, perspective
 │    ├── MediaLayer   → source, fit
 │    │    ├── ImageLayer
 │    │    └── VideoLayer (+ volume, pan, pitch, mute)
 │    └── TextualLayer → font*, color, text stroke / shadow, letterSpacing, lineHeight, …
 │         ├── TextLayer     (+ text)
 │         └── CaptionsLayer (+ captions[], maxCharsPerLine, maxLines)
 └── AuditoryLayer     → volume, pan, pitch, mute
      └── AudioLayer   → source

The samples below list every property and setting available on each layer type (including inherited ones), with defaults and inline comments.

Common Settings (all layers)

These settings are accepted by every add* call:

{
  name?: string,           // Human-readable name, optional
  enabled?: boolean,       // Whether the layer renders at all. Default: true
  startTime?: Time,        // When the layer begins on the timeline. Default: 0
  sourceDuration?: Time,   // How long the layer plays, in source seconds. Default: until end of timeline (or end of source for video/audio)
  sourceStart?: Time,      // Skip the first N seconds of the source. Default: 0
  sourceEnd?: Time,        // Trim N seconds off the end of the source (video/audio only). Default: 0
  mediaDuration?: Time,    // Intrinsic length of the source (video/audio only). Auto-detected when omitted.
  speed?: number,          // Playback speed multiplier. 2 = twice as fast, -1 = reverse. Default: 1
  transitionIn?: {         // Enter transition. Duration defaults to 200ms.
    transition: string,    // Preset name (e.g. 'fade', 'zoom', 'riseFade')
    duration?: Time,       // Transition window. Default: '200ms'
    params?: Record<string, any>, // Preset-specific parameters
  },
  transitionOut?: {        // Exit transition, same shape as transitionIn
    transition: string,
    duration?: Time,
    params?: Record<string, any>,
  },
}

Layers also expose two read-only getters:

  • timelineDuration — how long the layer occupies on the timeline (sourceDuration / |speed|).
  • endTimestartTime + timelineDuration.

For addVideo / addAudio, mediaDuration is auto-detected at compile time (set autoDetectDurations: false on the project to opt out). You can also pass it manually to skip the probe.

Time accepts numbers (seconds) or strings like '2s', '500ms', '60f', '01:30'. See Time Format.

addLayer options

All add* methods take an optional third argument:

{
  waitFor?: Time | 'finish',  // After adding, advance the flow pointer by this much.
                              // 'finish' = wait for the layer's full duration.
  index?: number,             // Insert position in the layer stack (z-order).
}

addText — text layer

Renders animated text. Inherits from TextualLayerVisualLayerBaseLayer.

const title = $.addText(
  {
    // --- TextLayer ---
    text: 'Hello, VideoFlow!',   // Text content. Default: 'Type your text here'

    // --- TextualLayer (typography) ---
    // Sizing defaults to `em`. At the project root, 1em = 1% of project width.
    fontSize: 4,                 // Unitless = em (4em = 4% of project width). Default: 4
    fontFamily: 'Noto Sans',     // Google Font name, auto-loaded. Default: 'Noto Sans'
    fontWeight: 600,             // 100–900 or string. Default: 600
    fontStyle: 'normal',         // 'normal' | 'italic'. Default: 'normal'
    fontStretch: 100,            // In %. Default: 100
    color: '#FFFFFF',            // Text color. Default: '#FFFFFF'
    textAlign: 'center',         // 'left' | 'right' | 'center' | 'justify'. Default: 'center'
    verticalAlign: 'middle',     // 'top' | 'middle' | 'bottom'. Default: 'middle'
    padding: 0,                  // Unitless = em, or [top, right, bottom, left]. Default: 0

    textStroke: false,           // Enable stroke outline around glyphs. Default: false
    textStrokeWidth: 0,          // Stroke width (unitless = em). Default: 0
    textStrokeColor: '#000000',  // Stroke color. Default: '#000000'

    textShadow: false,           // Enable text shadow. Default: false
    textShadowColor: '#000000',  // Shadow color. Default: '#000000'
    textShadowOffset: [0, 0],    // [x, y] (unitless = em). Default: [0, 0]
    textShadowBlur: 0,           // Blur radius (unitless = em). Default: 0

    letterSpacing: '0em',        // In em or px. Default: '0em'
    lineHeight: 1,               // Unitless multiplier of font-size, or em/px. Default: 1
    wordSpacing: 0,              // In em or px. Default: 0
    textIndent: 0,               // First-line indent. Default: 0
    textTransform: 'none',       // 'none' | 'capitalize' | 'uppercase' | 'lowercase'. Default: 'none'
    textDecoration: 'none',      // 'none' | 'underline' | 'overline' | 'line-through'. Default: 'none'
    direction: 'ltr',            // 'ltr' | 'rtl'. Default: 'ltr'

    // --- VisualLayer (see "Visual Properties" below for full list) ---
    visible: true,               // Default: true
    opacity: 1,                  // 0–1. Default: 1
    position: [0.5, 0.5],        // Normalized [x, y]. Default: [0.5, 0.5] (centered)
    scale: 1,                    // Default: 1
    rotation: 0,                 // Degrees. Default: 0
    anchor: [0.5, 0.5],          // Normalized anchor point. Default: [0.5, 0.5]
    // …plus backgroundColor, border*, boxShadow*, outline*, filter*, perspective, effects

    // --- GLSL effects (see "GLSL Effects" section) ---
    effects: [
      { effect: 'pixelate', params: { size: 8 } },
    ],
  },
  {
    // --- Common settings (see above) ---
    startTime: 0,                // Default: 0
    sourceDuration: '3s',        // Default: undefined (runs to end)
    enabled: true,               // Default: true
    transitionIn:  { transition: 'fade', duration: '300ms' },
    transitionOut: { transition: 'fade', duration: '300ms' },
  }
);

addImage — image layer

Displays a static image. Inherits from MediaLayerVisualLayerBaseLayer.

const photo = $.addImage(
  {
    // --- MediaLayer ---
    fit: 'contain',              // 'contain' | 'cover'. Default: 'contain'

    // --- VisualLayer (inherited) ---
    visible: true,               // Default: true
    opacity: 1,                  // 0–1. Default: 1
    position: [0.5, 0.5],        // Normalized [x, y]. Default: centered
    scale: 1,                    // Default: 1
    rotation: 0,                 // Degrees. Default: 0
    anchor: [0.5, 0.5],          // Default: centered
    // …plus backgroundColor, border*, boxShadow*, outline*, filter*, perspective, effects
  },
  {
    source: 'https://example.com/image.jpg', // REQUIRED: URL or file path
    startTime: 0,                // Default: 0
    sourceDuration: '5s',        // Default: undefined
  }
);

addVideo — video layer

Plays a video clip with synced audio. Inherits from MediaLayerVisualLayer plus auditory properties (volume, pan, pitch, mute).

const clip = $.addVideo(
  {
    // --- MediaLayer ---
    fit: 'cover',                // 'contain' | 'cover'. Default for video: 'cover'

    // --- Auditory properties ---
    volume: 1,                   // 0 = silent, 1 = full. Default: 1
    pan: 0,                      // -1 = left, 0 = center, 1 = right. Default: 0
    pitch: 1,                    // Pitch multiplier. Default: 1
    mute: false,                 // Silence without affecting volume value. Default: false

    // --- VisualLayer (inherited) ---
    visible: true,               // Default: true
    opacity: 1,                  // Default: 1
    position: [0.5, 0.5],        // Default: centered
    scale: 1,                    // Default: 1
    rotation: 0,                 // Default: 0
    anchor: [0.5, 0.5],          // Default: centered
    // …plus backgroundColor, border*, boxShadow*, outline*, filter*, perspective, effects
  },
  {
    source: './clip.mp4',        // REQUIRED: URL or file path
    startTime: 0,                // Default: 0
    sourceDuration: '10s',       // Default: undefined (plays to end of source)
    sourceStart: 0,              // Skip first N seconds of source. Default: 0
    speed: 1,                    // Default: 1
  },
  {
    waitFor: 'finish',           // Advance flow pointer by the full clip duration
  }
);

addAudio — audio layer

Plays an audio track. No visual output. Inherits from AuditoryLayerBaseLayer.

const music = $.addAudio(
  {
    volume: 1,                   // 0–1. Default: 1
    pan: 0,                      // -1 to 1. Default: 0
    pitch: 1,                    // Pitch multiplier. Default: 1
    mute: false,                 // Default: false
  },
  {
    source: './music.mp3',       // REQUIRED: URL or file path
    startTime: 0,                // Default: 0
    sourceDuration: '30s',       // Default: undefined (plays to end of source)
    sourceStart: 0,              // Default: 0
    speed: 1,                    // Default: 1
  }
);

addCaptions — captions / subtitles layer

Displays timed caption entries from a pre-built array. Inherits from TextualLayerVisualLayerBaseLayer, so all typography and visual properties from addText also apply here.

const subs = $.addCaptions(
  {
    // --- Typography (same as addText, see TextualLayer properties above) ---
    fontSize: 3,                 // 3em = 3% of project width
    fontFamily: 'Inter',
    fontWeight: 700,
    color: '#FFFFFF',
    textStroke: true,            // Common for readable captions
    textStrokeWidth: 0.15,       // 0.15em — scales with text
    textStrokeColor: '#000000',

    // --- Visual / transform ---
    position: [0.5, 0.85],       // Centered horizontally, near bottom
    // …plus everything from VisualLayer
  },
  {
    // --- CaptionsLayer-specific settings ---
    captions: [
      { caption: 'Hello world',   startTime: 0,   endTime: 2 },
      { caption: 'From VideoFlow', startTime: 2,   endTime: 4 },
    ],                           // REQUIRED. Times are in seconds.
    maxCharsPerLine: 32,         // Wrap captions at this width. Default: 32
    maxLines: 2,                 // Max simultaneous lines. Default: 2

    // --- Common settings ---
    startTime: 0,                // Default: 0
    sourceDuration: '4s',        // Default: undefined
  }
);

Visual Properties Reference

Every visual layer (text, image, video, captions) inherits the following from VisualLayer. All of these can be passed at creation or via .set() / .animate(). Most are animatable.

Unit convention

Sizing properties default to the em unit so videos render identically at any output resolution. VideoFlow sets the project root font-size so that 1em = 1% of the project width — an unstyled fontSize: 4 resolves to 4% of the canvas width (≈ 77px on 1920, ≈ 51px on 1280). Inside a text layer, em follows the standard CSS cascade (relative to that layer's fontSize), which is typically what you want for padding/stroke/shadow around text.

Size inputs also accept explicit px strings, and borderRadius additionally accepts %. Rotations and filterHueRotate are in deg. Colours are any CSS colour string. Unitless ratios (opacity, scale, filter multipliers) stay unitless.

Transform — position, scale, rotation, anchor

Transforms use normalized 0–1 coordinates — not pixels. [0.5, 0.5] is always the center.

layer.set({
  // Position of the anchor point within the canvas.
  // [0, 0] = top-left, [0.5, 0.5] = center, [1, 1] = bottom-right.
  // Third value (z) is depth in em (1em = 1% of project width). Animatable.
  position: [0.5, 0.5],        // Default: [0.5, 0.5]

  // Scale multiplier relative to the element's natural size.
  // Can be a number (uniform) or [x, y] / [x, y, z]. Animatable.
  scale: 1,                    // Default: 1

  // Rotation in degrees, clockwise. Can be [x, y, z] for 3D rotation. Animatable.
  rotation: 0,                 // Default: 0

  // Which point on the element maps to `position`.
  // [0, 0] = top-left of element, [0.5, 0.5] = center, [1, 1] = bottom-right. Animatable.
  anchor: [0.5, 0.5],          // Default: [0.5, 0.5]

  // 3D perspective distance (unitless = em; 100em = one project-width). Animatable.
  perspective: 100,            // Default: 100
});

Position examples:

layer.set({ position: [0.5, 0.5] });   // Centered
layer.set({ position: [0, 0] });       // Top-left corner
layer.set({ position: [1, 1] });       // Bottom-right corner
layer.set({ position: [0.5, 0.85] });  // Bottom-center (good for captions)

Opacity & visibility

layer.animate(
  { opacity: 0, visible: true },
  { opacity: 1 },
  { duration: '1s' }
);
// opacity: 0–1, default 1, animatable
// visible: boolean, default true, NOT animatable (flips instantly)

Background, border, border-radius

layer.set({
  backgroundColor: 'transparent', // Default: 'transparent'. Animatable.
  borderWidth: 0,                 // Unitless = em, or [top, right, bottom, left]. Default: 0. Animatable.
  borderStyle: 'solid',           // 'none'|'solid'|'dashed'|'dotted'|'double'|'groove'|'ridge'|'inset'|'outset'. Default: 'solid'
  borderColor: '#000000',         // Default: '#000000'. Animatable.
  innerBorder: false,             // Draw border inside the layer box (box-sizing: border-box). Default: false
  borderRadius: 0,                // Unitless = em, or '%', or 4-corner array. Default: 0. Animatable.
});

Box shadow

layer.set({
  boxShadow: true,                // Must be true to render shadow. Default: false
  boxShadowColor: '#000000',      // Default: '#000000'. Animatable.
  boxShadowOffset: [0, 0],        // [x, y], unitless = em. Default: [0, 0]. Animatable.
  boxShadowBlur: 0,               // Unitless = em. Default: 0. Animatable.
  boxShadowSpread: 0,             // Unitless = em. Default: 0. Animatable.
});

Outline

layer.set({
  outlineWidth: 0,                // Unitless = em. Default: 0. Animatable.
  outlineStyle: 'none',           // Same enum as borderStyle. Default: 'none'
  outlineColor: '#000000',        // Default: '#000000'. Animatable.
  outlineOffset: 0,               // Unitless = em. Default: 0. Animatable.
});

Filters (CSS filter functions)

All filters are animatable:

layer.animate({ filterBlur: 0 }, { filterBlur: 1 }, { duration: '2s' });

// filterBlur:       unitless = em,   default 0  (1em = 1% of project width at root)
// filterBrightness: multiplier,      default 1  (>1 brighter, <1 darker)
// filterContrast:   multiplier,      default 1
// filterGrayscale:  0–1,             default 0
// filterSepia:      0–1,             default 0
// filterInvert:     0–1,             default 0
// filterHueRotate:  degrees,         default 0
// filterSaturate:   multiplier,      default 1

Transitions

Attach an enter and/or exit animation to any layer via transitionIn / transitionOut in the layer's settings. Transitions modify the layer's resolved properties during a bounded window at the layer's start or end — no manual keyframing required.

const title = $.addText(
  { text: 'Hello!', fontSize: 3 },
  {
    sourceDuration: '4s',
    transitionIn:  { transition: 'riseFade', duration: '600ms', params: { distance: 0.1 } },
    transitionOut: { transition: 'blur',     duration: '500ms', params: { amount: 8 } },
  },
);

duration defaults to 200ms and accepts any Time format. If the combined in+out duration would exceed the layer's own duration, both are scaled proportionally.

Built-in transition presets

| Preset | Effect | Params | | --- | --- | --- | | fade | Crossfades opacity | — | | zoom | Scales in/out from from factor | from?: number (default 0.8) | | blur | Sweeps a Gaussian blur in/out | amount?: number (peak blur in em, default 4) | | slideLeft | Slides in from the right | distance?: number (fraction of canvas width, default 0.25) | | slideRight | Slides in from the left | distance?: number | | slideUp | Slides in from below | distance?: number | | slideDown | Slides in from above | distance?: number | | riseFade | slideUp + fade combined | distance?: number (default 0.08) |

The same preset name works for both transitionIn and transitionOut — the transition function receives p rising from 0→1 on entry and falling from 1→0 on exit, so opacity *= p is a fade-in and a fade-out with the same code.


GLSL Effects

Attach one or more WebGL shader effects to a layer via the first-argument effects property. Effects run in array order, each pass reading from the previous output (ping-pong framebuffers), before the layer is composited.

const img = $.addImage(
  {
    fit: 'cover',
    effects: [
      { effect: 'pixelate',            params: { size: 48 } },
      { effect: 'chromaticAberration', params: { amount: 0.004 } },
      { effect: 'vignette',            params: { strength: 0.7, radius: 0.75 } },
    ],
  },
  { source: './photo.jpg', sourceDuration: '4s' },
);

The effects array is set at creation time and cannot be animated. Individual params, however, are animatable via dot-path property keys:

// Animate the 'size' param of the first 'pixelate' effect
img.animate(
  { 'effects.pixelate.size': 48 },
  { 'effects.pixelate.size': 1 },
  { duration: '2s' },
);

// When the same effect appears more than once, use an index:
img.animate({}, { 'effects.pixelate[1].size': 4 }, { duration: '1s' });

Built-in effect presets

| Effect | Description | Params | | --- | --- | --- | | chromaticAberration | Splits RGB channels horizontally | amount (default 0.005) | | pixelate | Pixel mosaic | size (pixels, default 8) | | vignette | Darkened border | strength (default 0.6), radius (default 0.8) | | rgbSplit | Directional chromatic aberration | angle (degrees, default 0), amount (default 0.005) | | invert | Colour inversion | amount (0–1, default 1) |

All params are animatable. Effects are supported in both BrowserRenderer (export) and DomRenderer (live preview).


Timeline Methods

wait

Advance the timeline by a duration.

$.wait('2s');
$.wait('500ms');
$.wait('60f');  // 60 frames

parallel

Run multiple animation branches simultaneously.

$.parallel([
  () => layer1.animate({ opacity: 0 }, { opacity: 1 }, { duration: '1s' }),
  () => layer2.fadeOut('1s'),
  () => { /* custom code */ },
]);

animate

Animate layer properties over time.

layer.animate(
  { opacity: 0, scale: 0.8 },      // Start state
  { opacity: 1, scale: 1 },        // End state
  {
    duration: '1.5s',
    easing?: 'easeOut',
    delay?: '500ms',
  }
);

set

Set properties at the current timeline position (no animation).

layer.set({
  opacity: 0.5,
  scale: 1.2,
});

fadeIn / fadeOut

Animate opacity to/from 0.

layer.fadeIn('1s', 'easeOut');
layer.fadeOut('1s', 'easeOut');

show / hide / remove

Visibility shortcuts.

layer.show();   // opacity 1
layer.hide();   // opacity 0
layer.remove(); // Remove at current time

Time Format

All time parameters accept flexible formats:

| Format | Example | Result | |--------|---------|--------| | Seconds | '5s' | 5 seconds | | Milliseconds | '500ms' | 500ms | | Minutes | '2m' | 2 minutes | | Hours | '1h' | 1 hour | | Frames | '60f' | 60 frames at current FPS | | Timecode | '01:30' | 1 min 30 sec | | Full timecode | '01:02:30' | 1 hr 2 min 30 sec |

Compilation & Rendering

compile

Convert the video to a portable VideoJSON object. This JSON can be stored, transferred, and rendered later by any VideoFlow renderer.

const videoJSON = await $.compile();
// Result: { width, height, fps, duration, layers, ... }

renderVideo

Render the video to MP4 (auto-detects environment).

await $.renderVideo({
  outputType: 'file',
  output: './video.mp4',
  verbose?: boolean,
});

renderFrame

Render a single frame.

const imageData = await $.renderFrame(0); // Frame 0

renderAudio

Render the full audio track.

const audioBuffer = await $.renderAudio();

Examples

See the examples/ folder for complete, runnable examples.

See Also

License

Apache License 2.0