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

obra-chrome-video-capture

v0.1.0

Published

Scripted, retina-quality video and screenshot capture of web apps via headless Chrome — scenario-driven loops for portfolios, docs, and marketing.

Downloads

133

Readme

obra-chrome-video-capture

Scripted, retina-quality video and screenshot capture of web apps via headless Chrome. You describe a scenario — a URL plus a choreography of cursor moves, clicks, and holds — and the CLI records it as a looping mp4 + webm (with poster) or as a 1x/2x PNG + WebP still set. Built for design portfolios, docs, and marketing pages where screen-recorder output isn't good enough.

  • True device-pixel capture (2× by default): 2800×1672 from a 1400×836 viewport
  • 60fps output assembled from real frame timestamps (still moments cost nothing)
  • A synthetic cursor with easing and click ripples (screencasts have no cursor)
  • Deterministic and headless — no windows, no screen-recording permissions

Requirements

  • Node 18+, ffmpeg on PATH (override with CAPTURE_FFMPEG)
  • A Chromium binary — resolved from CAPTURE_CHROME, the Playwright browser cache (~/Library/Caches/ms-playwright), or system Chrome, in that order. No npx playwright install needed if any of those exist.

Usage

capture record <scenario>|all    # → static/videos/<name>.{mp4,webm} + poster
capture shoot  <scenario>|all    # → static/images/<name>{,@2x}.{png,webp}

Run bare (capture record) to list scenarios. Scenarios live in capture/scenarios/*.mjs under the consuming project; override locations with --scenarios-dir, --out-videos, --out-images.

Scenario contract

import { fadeOut } from 'obra-chrome-video-capture';

export default {
  name: 'my-demo',                 // output basename
  url: 'https://example.com/app',  // where the capture starts
  viewport: { width: 1400, height: 836 },
  deviceScaleFactor: 2,            // capture at 2x device pixels
  settle: 5000,                    // ms to wait after load before frame 1
  hide: ['.chat-bubble'],          // selectors hidden via injected CSS
  init: [() => { /* runs before every document, e.g. patch window.open */ }],
  warmup: ['https://…'],           // pages visited pre-recording to warm caches

  video: {
    fps: 60,                       // output fps (default 60)
    // width/height: omit to encode at native captured resolution
    cursorStart: { x: 56, y: 21 }, // where the cursor begins (see loop rules)
    async prepare({ page, sleep }) { /* off-camera state normalization */ },
    async run({ page, cursor, sleep }) {
      await sleep(1000);
      await cursor.clickLocator(page.locator('button:has-text("Go")'), { ms: 700 });
      await sleep(1500);
      await fadeOut(page, 600);    // outro when a seamless loop isn't possible
    },
  },

  stills: [
    { name: 'my-demo-home' },                       // uses scenario.url
    { name: 'my-demo-detail', url: '…', async setup({ page, sleep }) {} },
  ],
};

cursor API: moveTo(x, y, ms), click(x, y, {ms}), clickLocator(locator, {ms}), ensure() (re-install after a navigation), position().

Making loops seamless

  • End the choreography in the exact start state, including the cursor position — set cursorStart to where the final click leaves it.
  • If a beat changes state irreversibly (e.g. a fit % that settles differently after the first toggle), do one round-trip off-camera in video.prepare.
  • If the story ends somewhere with no UI path back, close with fadeOut() — the loop restart then reads as a deliberate cut.

Hard-won implementation notes

  • The --force-device-scale-factor launch flag in lib/browser.mjs is the entire reason videos are 2×. CDP screencasts cap at CSS-pixel resolution; per-context deviceScaleFactor emulation does NOT lift that cap, the launch flag does. Don't "simplify" it away.
  • Frame acks throttle Chrome's screencast delivery — lib/recorder.mjs acks immediately and writes frames async. Expect ~110fps delivery during motion.
  • Stills use Page.captureScreenshot, which honors emulated DPR regardless.
  • ctrl+wheel zoom in canvas apps pins the point under the mouse; to end with a target centered at zoom z1 from z0, aim the wheel at F = (s·N0 − C)/(s − 1) where s = z1/z0, N0 = target center, C = desired end position.
  • Links that open new tabs break single-tab recordings — patch them via init: window.open = (url) => { location.href = url; return window; }.