WannaCut is a desktop video editor built on a two-process architecture powered by Tauri. A Rust backend process handles all heavy, privileged work — file I/O, FFmpeg subprocess management, HTTP asset streaming, and cloud font fetching — while a React 19 TypeScript SPA running inside Tauri’s WebView handles the entire editing UI, timeline logic, and GPU-accelerated rendering via Three.js. The two processes communicate exclusively through Tauri’sDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/ter-9001/WannaCut/llms.txt
Use this file to discover all available pages before exploring further.
invoke() IPC bridge, with no shared memory.
Overview
The application is split into two distinct layers that communicate via Tauri’s IPC bridge:Frontend — Tauri WebView
- React 19 + TypeScript single-page application
- Three.js WebGL renderer for both preview and export
- All editor state managed via React
useStateanduseRef invoke()calls to the Rust backend for every file and media operation
Backend — Tauri Rust Process
- File system operations (project CRUD, asset import/delete)
- FFmpeg and FFprobe spawned as child subprocesses
tiny_httpHTTP server on port1234serving asset files to the WebView- Cloud font manifest fetching via
reqwest
invoke('command_name', { ...params }) from @tauri-apps/api/core. The Rust backend exposes ~27 named Tauri commands that return serialized JSON. There is no REST API, no WebSocket, and no shared file-based IPC — only Tauri’s native invoke mechanism.
Frontend Layer
The entire editor UI lives insrc/ as a React 19 + TypeScript SPA. There is no router — the app renders either the Project Manager view or the Editor view depending on state.
Component Structure
App.tsx
The monolithic root component. It owns all application state: clips, assets, tracks, playhead position, history stacks, project config, export status, and more. It also contains all event handlers for timeline interactions, keyboard shortcuts, drag-and-drop, playback, export, and project management. No external state library (Redux, Zustand, etc.) is used.
ItensAside.tsx
The left sidebar with four tabs: Media (imported assets), Text (available fonts), Effects (video/audio effects library), and Transitions. Assets and fonts are draggable onto the timeline via the HTML5 Drag and Drop API.
PropertiesAside.tsx
The right-side properties panel. When a clip is selected, it surfaces controls for volume, opacity, zoom, speed, position, rotation, blend modes, fade in/out, font settings (for text clips), and keyframe controls. Updates flow back up to
App.tsx via setClips.SettingsModal.tsx
A full-screen modal for both project settings (resolution, FPS, name, background color, sample rate) and app settings (workspace path, GPU preference). Calls
save_project_config on save, which can rename the project directory if the name changes.Waveform.tsx
A canvas-based component that renders an audio waveform visualization for audio clips on the timeline. It reads the asset path and draws the waveform in real time as a semi-transparent overlay on the clip block.
renderBridge.tsx
The bridge between the React editor and the Three.js render engine. It exports
getDrawFrameFunction(), exportVideo(), and the RenderEngineContext / ExportOptions interfaces. The actual draw function (professionalDrawFrame) is loaded from a private submodule at ./Render/Render.The Three.js Render Engine
Both the real-time preview player and the final video export share the same Three.js WebGL pipeline. There is no separate “offline renderer” — the export uses an offscreenWebGLRenderer instance that calls the same drawFrame function used during preview.
Initialization
A singleWebGLRenderer, Scene, and PerspectiveCamera are created once when the <canvas> element mounts, inside a useEffect in App.tsx:
{x: 100, y: 200} appears exactly 100×200 pixels from the top-left corner.
Scene Graph
Clips are rendered asTHREE.Group objects. The groupsRef (useRef<Map<string, THREE.Group>>) maps each clip’s id to its Group in the scene. When a clip is updated (position, scale, opacity keyframe, etc.), the corresponding Group’s properties are mutated directly — no React re-render required.
Preview Loop
The preview renders at a throttled 10 FPS to avoid GPU pressure during editing:requestAnimationFrame rate (~60 Hz) via currentTimeRef, while setCurrentTime (React state) is updated only every ~100 ms to avoid excessive re-renders.
Audio Architecture
WannaCut uses a dual-audio system: one path for interactive preview during editing, and a separate offline path for high-quality export.Preview Audio — HTMLAudioElement per clip
During playback, each audio-bearing clip gets an
HTMLAudioElement managed in audioPlayersRef (a Map<string, HTMLAudioElement>). The element’s src is set to the tiny_http server URL (http://127.0.0.1:1234/...) so the WebView can stream files from the local filesystem. Volume, speed, and seek position are updated on every playback tick.Source Monitor Audio — audioRef2
The Source Monitor (the left auxiliary preview panel) uses a separate
audioRef2 element. This keeps the main timeline audio isolated from the preview monitor, allowing the user to scrub assets independently without affecting playback.Export Audio — OfflineAudioContext
During export,
renderAudioOffline() (from the private ./Render/Render submodule) uses the Web Audio API’s OfflineAudioContext to mix all audio clips faster than real time. It replicates the exact same audio effect chain used during preview — pitch, alien, microphone, volume keyframes, speed — to guarantee that the exported audio matches what you hear while editing. The result is saved as a WAV file in the project directory.Asset Streaming — tiny_http on port 1234
The Rust backend starts a
tiny_http server on port 1234 when the app launches. This server serves raw asset bytes from the local filesystem to the WebView. This is necessary because Tauri’s WebView security model blocks direct file:// access to arbitrary paths; the HTTP server provides a safe, content-type-correct alternative.FFmpeg & FFprobe
WannaCut bundles its own copies of FFmpeg and FFprobe insrc-tauri/bin/ and src-tauri/libs/. The Rust backend spawns them as child processes via tauri-plugin-shell and the tokio async runtime. No system-installed FFmpeg is required.
FFmpeg is used for:
- Extracting audio tracks from video files (
extract_audio) - Generating timeline thumbnails (
generate_thumbnail) - Assembling the final MP4 from a PNG frame sequence + WAV audio (
assemble_exported_video)
- Reading media duration (
get_duration) - Extracting a specific video frame as a base64 PNG (
get_video_frame) - Reading video/image dimensions (
get_asset_dimensions)
State Management
WannaCut uses no external state library. All state is plain ReactuseState and useRef. This is a deliberate design choice to keep the dependency tree small and avoid abstraction overhead in a performance-sensitive application.
Key State Refs
| Ref | Type | Purpose |
|---|---|---|
currentTimeRef | useRef<number> | Source of truth for playhead time. Updated on every RAF frame at 60 Hz. Avoids React re-renders for smooth playhead movement. |
topClips | useRef<Clip[]> | Clips visible at currentTime. Updated at 10 FPS by updatePreview(). Read by the Three.js draw function. |
drawFrameEngine | useRef<Function> | The dynamically loaded render function from the private submodule. Set once on mount via getDrawFrameFunction(). |
clipboardRef | useRef<Clip[]> | Copy/paste clipboard. Stored as a ref (not state) so that handlePaste always reads the latest value without stale closure issues. |
audioPlayersRef | useRef<Map<string, HTMLAudioElement>> | One HTMLAudioElement per active audio clip. Keyed by clip ID. Managed entirely outside React state for low-latency audio sync. |
groupsRef | useRef<Map<string, THREE.Group>> | Maps clip IDs to their Three.js scene objects. Allows the render engine to update individual clips without touching React state. |
Key React State
| State | Purpose |
|---|---|
clips / assets / tracks | The primary timeline data. Changes here trigger the auto-save debounce (500 ms). |
projectConfig | Resolution, FPS, background color, sample rate. Passed into the render engine context. |
history / redoStack | 100-step undo/redo. Snapshots are { clips, assets, tracks } objects. |
isPlaying | Controls the requestAnimationFrame playback loop and audio element play()/pause(). |
Rust Dependencies
The Rust backend (src-tauri/Cargo.toml) uses the following key crates:
| Crate | Version | Role |
|---|---|---|
tauri | 2 (protocol-asset) | Core Tauri framework, asset protocol for safe file serving |
tauri-plugin-shell | 2 | Spawning FFmpeg/FFprobe/yt-dlp child processes |
tauri-plugin-dialog | 2.6 | Native open/save file dialogs |
tauri-plugin-fs | 2 | Filesystem access |
tauri-plugin-localhost | 2 | tiny_http integration for the asset streaming server |
tokio | 1 (full) | Async runtime for all Rust command handlers |
serde / serde_json | 1 | Serialization of all IPC payloads |
image | 0.24 | PNG frame encoding during export |
base64 | 0.21 | Encoding frames as base64 data URLs for get_video_frame |
tiny_http | 0.12 | Lightweight HTTP server for asset streaming on port 1234 |
reqwest | 0.13 (json) | HTTP client for fetch_cloud_fonts |
percent-encoding | 2.3 | URL-encoding asset paths for the HTTP server |
fs_extra | 1.3 | Extended filesystem utilities (recursive copy/delete) |
JS Dependencies
The frontend (package.json) uses the following key packages:
| Package | Version | Role |
|---|---|---|
react / react-dom | 19 | UI framework |
three | 0.183 | 3D/2D WebGL rendering engine |
@react-three/fiber | 9 | React renderer for Three.js (used by the render submodule) |
@react-three/drei | 10 | Three.js helpers and abstractions |
@tauri-apps/api | 2 | invoke, convertFileSrc, event listeners |
framer-motion | 12 | UI animations (clip motion, modal transitions, notifications) |
tone | 15 | Web Audio abstraction used in the audio effect chain |
lucide-react | 0.563 | Icon set throughout the editor UI |
tailwindcss | 4 | Utility-first CSS |
vite | 7 | Build tool and dev server |
typescript | ~5.8 | Type checking |