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

@doedja/scenecut

v3.0.8

Published

Scene change detection for Node.js using Xvid's motion estimation algorithm

Readme

@doedja/scenecut

Fast, accurate scene change detection for Node.js. Xvid motion estimation compiled to WebAssembly, an online smoother, per-video adaptive calibration, and an optional worker pool for parallel analysis.

npm install -g @doedja/scenecut

CLI

# default — writes Aegisub keyframes next to the input
scenecut input.mkv

# NLE exports
scenecut video.mkv -f edl              # CMX3600 — Premiere, Resolve, Avid
scenecut video.mkv -f fcpxml           # Final Cut Pro X timeline with markers
scenecut video.mkv -f premiere         # Premiere Pro Import Markers CSV

# technical formats
scenecut video.mp4 -f json -o scenes.json
scenecut video.mp4 -f csv
scenecut video.mp4 -f timecode

# parallel worker pool
scenecut video.mkv -w true             # auto (cpus - 1, capped at 8)
scenecut video.mkv -w 4                # pin to 4 workers

scenecut anime.mkv -s high -v          # higher sensitivity, verbose
scenecut long.mkv -t 120               # abort after 2 minutes
scenecut video.mp4 --thumbnails ./thumbs

Options

| Option | Alias | Description | Default | |--------|-------|-------------|---------| | --format | -f | see formats below | aegisub | | --output | -o | Output path | {filename}_keyframes.{ext} | | --sensitivity | -s | low | medium | high | low | | --workers | -w | true | number | off | off | | --timeout | -t | Abort after N seconds | off | | --thumbnails | | Scene thumbnail directory | — | | --quiet | -q | Suppress progress | — | | --verbose | -v | Show per-scene detail | — |

Formats

| Format | Extension | Where it fits | |--------|-----------|---------------| | edl | .edl | CMX3600 — Premiere, DaVinci Resolve, Avid | | fcpxml | .fcpxml | Final Cut Pro X timeline with markers | | premiere | .markers.csv | Premiere Pro File → Import Markers | | aegisub | .txt | Aegisub keyframes (frame numbers) | | timecode (tc) | .txt | Plain HH:MM:SS.mmm list | | csv | .csv | Generic CSV | | json | .json | Full result with metadata and stats |

Sensitivity

| Level | Base threshold | When to use | |-------|---------------|-------------| | low | sSAD ≥ 150 | Hard cuts only. Default. Robust on compressed/noisy footage. | | medium | sSAD ≥ 90 | Balanced. | | high | sSAD ≥ 50 | Subtle transitions. More false positives on noise. |

The base threshold is a starting point. During the first ~2 s of video, scenecut measures the noise floor and nudges the threshold upward if the content is noisier than expected, capped at 4× the base.

Library

const { detectSceneChanges } = require('@doedja/scenecut');

const result = await detectSceneChanges('input.mp4', {
  sensitivity: 'low',
  workers: true,
  onProgress: (p) => console.log(`${p.percent}% — ${p.fps?.toFixed(1)} fps`),
  onScene:    (s) => console.log(`cut @ ${s.timecode} conf=${s.confidence?.toFixed(2)}`)
});

console.log(`${result.scenes.length} scenes`);
interface NodeDetectionOptions {
  sensitivity?: 'low' | 'medium' | 'high';             // default: 'low'
  searchRange?: 'auto' | 'small' | 'medium' | 'large'; // default: 'auto'
  workers?: number | boolean;                          // default: off
  onProgress?: (p: Progress) => void;
  onScene?: (s: SceneInfo) => void;
  signal?: AbortSignal;
}

workers: true sizes the pool to cpus - 1 (clamped to [1, 8]). Expect 2.5–3.3× speedup on 4-core machines.

Exporters are named imports too:

const { formatEdl, formatFcpxml, formatPremiereMarkers } = require('@doedja/scenecut');
const edl = formatEdl(result, 'my-clip');

Result

interface DetectionResult {
  scenes: Array<{
    frameNumber: number;
    timestamp: number;   // seconds
    timecode: string;    // HH:MM:SS.mmm
    confidence: number;  // 0–1 (sigmoid-calibrated)
    duration: number;
    frameCount: number;
  }>;
  metadata: { totalFrames, duration, fps, resolution, codec?, pixelFormat?, bitrate? };
  stats:    { processingTime, framesPerSecond };
}

Cancellation

const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 60_000);
await detectSceneChanges('input.mp4', { signal: ctrl.signal });

Thumbnails

const { extractSceneImages } = require('@doedja/scenecut');

await extractSceneImages('input.mp4',
  { sensitivity: 'low' },
  { outputDir: './thumbs', format: 'jpg', quality: 85 }
);

Browser?

Use @doedja/scenecut-web.

How it works

  1. Decode: FFmpeg streams grayscale frames via a zero-copy ring buffer.
  2. Fused fast pass (JS): sampled MAD vs. previous + drift vs. an EMA reference, EMA updated in place.
  3. Gate: if both MAD and drift are low, WASM is skipped (≈80% of frames).
  4. Motion estimation (WASM): Xvid MEanalysis with diamond + subpel refinement, SIMD SAD. With a pool, runs in parallel threads.
  5. Sigmoid calibration: rawScore → p_cut ∈ [0, 1].
  6. Fade rescue via the EMA drift signal.
  7. Adaptive warmup: first ~2 s sets the noise floor.
  8. Smoother (online NMS with refractory gap).

Performance

Measured on an M-series Mac, 1080p h.264 24 fps anime (24-min episode):

  • Single-threaded: ~170 fps sustained
  • Worker pool (4 threads): ~400–550 fps (2.5–3.3× speedup)
  • Memory: ~200–300 MB single, ~80 MB per added worker
  • Skips WASM for ~80% of frames

Requirements

  • Node.js ≥ 18
  • FFmpeg (bundled via @ffmpeg-installer/ffmpeg — no separate install needed)

Source

github.com/doedja/scenecut

License

GPL-2.0 — derived from vapoursynth-wwxd (dubhater) and the Xvid motion estimation algorithm.