Video
The video editor puts a timeline in your page: video, pictures, titles and sound on as many tracks as you need, trimmed, split, moved and rippled with snapping to edits and markers. Each clip takes a speed, volume and fades, a crop and its place on the frame; transitions sit between clips; titles come in fading, rising or typed out; captions are written along the timeline and burned in or saved as WebVTT and SRT.
The preview is frame-accurate, with J, K and L to shuttle. Export makes an MP4 (H.264 and AAC) or a WebM file with the browser's own encoders and our own file writer, so nothing is sent anywhere to be rendered, in the frame size you choose - including 9:16 and square for social. The project saves as JSON and reopens with its files.
Try it with your own files in confidence: they are opened by your browser, on your device. Nothing is uploaded, no copy reaches us or anyone else, and nothing is kept once you close this tab.
A café's opening teaser
Seventeen seconds made in the page: two short clips recorded from animations here, two pictures, an end card and a music bed, with titles on a track above and captions. Play it, trim and move clips, split with S, change speed, fades and transitions in the inspector, then Export video - or add video, sound and pictures of your own.
A vertical cut for social
The same café as a 9:16 story for a phone screen, made of pictures and titles only, each picture cropped to fill the tall frame. Add a title where the playhead is, split a clip, undo.
// Node 18 or later, from the Gold package: before the night's exports, check each saved project's files are there. import { existsSync, readFileSync } from 'node:fs'; import { readVideoProject, missingMedia, videoDuration, timecode } from './dist/node/bmx-video-editor.mjs'; const { project, problems } = readVideoProject(readFileSync('teaser.json', 'utf8')); const onDisk = new Set(project.media.filter(m => existsSync(`media/${m.name}`)).map(m => m.id)); for (const m of missingMedia(project, onDisk)) console.warn(`${project.title}: ${m.name} is missing`); console.log(`${project.title}: ${timecode(videoDuration(project), project.fps)}, ${project.captions.length} captions, ${problems.length} problems`);