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

@omelhorsite/video-cli

v0.2.0

Published

omsv - command line editor for .omsv video projects, built on @omelhorsite/video-sdk: create, inspect, edit, transcribe, caption and render from the terminal, and keep projects in the omelhorsite cloud.

Readme

@omelhorsite/video-cli

omsv: create, inspect, edit, transcribe, caption and render .omsv video projects from the terminal, and keep them in the omelhorsite cloud. Built for agents as much as for people: you never need to watch the video, because the CLI draws frames and contact sheets as PNG.

bun add -g @omelhorsite/video-cli
omsv new p.omsv --width 1080 --height 1920 --fps 30 --name "Promo"
omsv add-asset p.omsv clip1.mov clip2.mp4 music.m4a logo.png
omsv grid p.omsv sheet.png              # look at the timeline
omsv render p.omsv out.mp4 --final

Needs Bun and ffmpeg in PATH. frame, grid, wave and text rendering rasterise through qlmanage, so image output is macOS only. Local transcription uses whisper-cli (brew install whisper-cpp) and a model in ~/.cache/whisper-models/; without them, the cloud transcribes.

The project file

A project is one .omsv file: a ZIP with the timeline and all of its media inside, portable and self-contained.

project.omsv
├── manifest.json        { "format": "omsv", "schema": 1, "savedAt": ... }
├── timeline.json        the Project (asset paths relative: assets/...)
└── assets/<id>-<name>   embedded media (stored uncompressed)

unzip works, but prefer the CLI: it extracts media to a content-addressed workspace, resolves paths, validates and packs again on save. Plain JSON projects from before the format existed are accepted by every command.

Commands

new        <out.omsv> [--width 1080] [--height 1920] [--fps 30] [--name s]
add-asset  <p.omsv> <media...>          embed media files in the project
add-color  <p.omsv> <name> <RRGGBB>     solid-colour image asset (flashes, backgrounds)
add-flash  <p.omsv> --at 8.0            flash transition; --at is the CUT, not the start
           [--duration 0.5] [--sound <assetId>] [--sound-volume 1] [--punch] [--wave]
asset-grid <p.omsv> <assetId> <out.png> contact sheet of a RAW asset [--every s] [--from --to]
inspect    <p.omsv> [--json]            summary; --json prints the raw Project
validate   <p.omsv> [--json]            structural checks; exit 1 on errors
transcribe <p.omsv> <assetId>           words into the project, in source time
           [--from s] [--to s] [--lang pt] [--model f.bin] [--at "23.4,28.6"]
           [--cloud] [--exact]          the omelhorsite transcriber instead of whisper
captions   <p.omsv> <out.mp4> --cloud   omelhorsite karaoke captions over the render
apply      <p.omsv> --ops '{"ops":[...]}'  declarative edits by id
exec       <p.omsv> <script.ts> [--args '{...}']  arbitrary TypeScript with save()
render     <p.omsv> <out.mp4> [--final] [--hw] [--from s] [--to s] [--preset] [--crf]
frame      <p.omsv> <out.png> --t 3.2   one composed frame at t seconds
grid       <p.omsv> <out.png> [--every s] [--cols n] [--width px]
wave       <p.omsv> <out.png>           waveform of the final mix
open       <p.omsv>                     open in the desktop app
pack       <legacy.json> <out.omsv>     embed referenced media into a zip
cloud      login | logout | whoami | ls | push <p.omsv> | pull <id|name>

Every command takes paths, never stdin, and writes where you point it.

Seeing

frame, grid and wave composite the real timeline (layers, transforms, opacity, fades, text, effects) through a cached low-resolution preview, so they are cheap to call while you iterate: edit, grid, look at the PNG, repeat. Grid labels are index assetName tSECONDS. Before choosing in-points on a raw clip, asset-grid shows one frame per second of it.

Editing

Declarative, by id (get ids from inspect --json):

omsv apply p.omsv --ops '{"ops":[
  {"op":"set-clip","id":"CLIP_ID","patch":{"start":2.5,"duration":3}},
  {"op":"split-clip","id":"CLIP_ID","at":3.2},
  {"op":"set-project","patch":{"name":"new name"}}
]}'

Ops: set-project, add-track (video|audio|text), remove-track, add-clip, set-clip, split-clip (linked audio splits too), remove-clip. A set-clip patch merges transform field by field; other fields replace.

Or with a script, for anything the ops cannot say:

import type { Project } from "@omelhorsite/video-sdk";
export default async ({ project, core, save, args }: {
  project: Project;
  core: typeof import("@omelhorsite/video-sdk");
  save: () => Promise<void>;
  args: unknown;
}) => {
  const clip = project.tracks.flatMap((t) => t.clips).find((c) => c.kind === "text");
  if (clip && clip.kind === "text") clip.text = "new text";
  await save();
};

Nothing is saved unless the script calls save().

Placement

A visual clip's transform is { x, y, scale, opacity }: x/y are the centre in normalised coordinates (0..1) and scale multiplies the size the asset gets when fitted inside the frame. To fill the frame, scale = max(projW/fitW, projH/fitH); a 16:9 source in a 1080x1920 project fits to 1080x607, so filling the height is scale ~= 3.16, then pan with x. Check with frame; never leave a face cut off.

blurBehind and shadow on a clip take a fraction of the project width, so they survive the low-resolution preview.

The flash

The one full-screen effect: a pale glow grows from the lower left, blows the frame out to white for an instant and recedes, while the image takes a zoom punch. --at is the cut; the transition wraps around it and the shutter sound fires exactly there. Fades on the clips either side should be zero.

Captions

Captions are not burned into a finished mp4. Transcribe the raw material once; the words live in the .omsv in source time and the render burns them in the same pass. Cutting, trimming or reordering never invalidates them: words follow their clips, and words in a piece that was cut disappear.

omsv transcribe p.omsv <assetId> --lang pt            # whisper.cpp, offline
omsv transcribe p.omsv <assetId> --cloud --lang pt    # omelhorsite transcriber
omsv transcribe p.omsv <assetId> --cloud --exact      # per-word timings (video asset)
omsv render p.omsv out.mp4                            # comes out captioned

Speech models place word boundaries approximately; cutting where the model says a word ends clips its tail. --at "23.43,28.62" crosses the transcript with the measured silence map and says where to cut instead. Use it before choosing a cut.

captions --cloud renders the project without captions, sends it to the omelhorsite captions renderer with the timeline's words and the project's caption style, and downloads the captioned mp4. With an OAuth sign-in the render has to stay under 64 MiB; raise --crf if it does not.

To edit the text without transcribing again, change project.captions[].words with exec; the timings stay.

Cloud

omsv cloud login            # device flow: shows a code and a link, opens the browser
omsv cloud ls               # projects in the account's oms-video/ folder
omsv cloud push p.omsv      # the whole file; replaces one with the same name
omsv cloud pull <id|name>   # the whole file back
omsv cloud whoami | logout

Credentials live in ~/.config/oms-video/credentials.json with mode 0600. --client-id and --base-url (or OMS_VIDEO_CLIENT_ID, OMS_VIDEO_BASE_URL) point at another registration or another server. Do not run two cloud commands at once: the refresh token rotates, and the second process is left holding the old one.

Speed

A render exists so someone can look at it. In order of impact, measured on an 84 s 1080x1920 project: --from/--to renders only the window that changed; plain render is fast (veryfast, crf 23) and --final is delivery quality (slow, crf 17); --hw uses the machine's hardware encoder. The bottleneck is the filter graph, not the encoder: measure before optimising.

Gotchas

  • A render writes to <dest>.part-<pid> and renames at the end. Never point two renders at the same destination.
  • The preview cache lives in the OS temp dir keyed by the timeline hash, so editing the project invalidates it. The first frame/grid/wave after an edit pays a low-resolution render; repeats are instant.
  • Assets inside the zip are stored uncompressed on purpose: saving is disk-copy fast and re-zipping never degrades anything.
  • Homebrew's ffmpeg lacks drawtext; that is fine, text is rasterised to PNG overlays.