Drop to add {{ activeTab === 'video' ? 'video, image, or audio files' : 'audio files' }}

{{ activeTab === 'video' ? 'Video and image files become clips on the timeline — audio becomes a background track.' : 'Audio files become tracks to merge, in order.' }}

Writer's Media

Basic clip editing — trim, speed, transitions, right in your browser.

Video
Audio

Drop video or image files anywhere on the page, paste, or click to add clips to the timeline — drop an audio file to add it as a background track

Drop audio files anywhere on the page, paste, or click to add tracks to merge, in order

{{ saveProjectMessage }}

Save/Load Project needs Chrome or Edge.

Loading…

No saved projects yet.

{{ p.name }}
{{ p.clips.length }} clip{{ p.clips.length === 1 ? '' : 's' }} · {{ new Date(p.savedAt).toLocaleString() }}

Couldn't reconnect these files automatically — locate them again:

{{ entry.fileName }}
Background audio tracks ({{ audioTracks.length }})
{{ track.name }} Needs file

This track's file needs to be located again — use "Choose file" in the panel above. Its start/end/volume/mute settings will be restored once relinked.

No clips yet — add a video above to start building your timeline.
{{ idx + 1 }}. {{ clip.name }} Needs file {{ (clip.end - clip.start).toFixed(1) }}s

This clip's file needs to be located again — use "Choose file" in the "Couldn't reconnect these files automatically" panel above. It'll stay at this position once relinked.

s

A clip that doesn't match gets letterboxed (black bars), not cropped or stretched. A lower resolution than your source clips renders faster.

{{ ffmpegLoading ? 'Loading video engine…' : renderProgress >= 100 ? 'Finishing the video — this might take some time.' : `${renderStage || 'Rendering'}… ${renderProgress}%` }} · {{ renderElapsed }}s elapsed

{{ renderError }}

Preview (256p — for checking transitions/order, not final quality)

Rendering happens entirely in your browser (ffmpeg.wasm) — nothing is uploaded anywhere. The video engine (~30MB) loads the first time you render.

No tracks yet — add audio files above to start merging.
{{ idx + 1 }}. {{ clip.title || clip.name }} {{ (clip.end - clip.start).toFixed(1) }}s

{{ ffmpegLoading ? 'Loading audio engine…' : renderProgress >= 100 ? 'Finishing up — this might take a moment.' : `${renderStage || 'Merging'}… ${renderProgress}%` }} · {{ renderElapsed }}s elapsed

{{ renderError }}

Tracks are merged in order with hard cuts, then encoded entirely in your browser — nothing is uploaded anywhere.