Skip to main content

Documentation 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.

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’s 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 useState and useRef
  • 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_http HTTP server on port 1234 serving asset files to the WebView
  • Cloud font manifest fetching via reqwest
IPC Bridge: The frontend calls 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 in src/ 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 offscreen WebGLRenderer instance that calls the same drawFrame function used during preview.

Initialization

A single WebGLRenderer, Scene, and PerspectiveCamera are created once when the <canvas> element mounts, inside a useEffect in App.tsx:
const renderer = new THREE.WebGLRenderer({
  canvas: canvasRef.current,
  alpha: true,
  antialias: true,
  powerPreference: "high-performance"
});
renderer.setSize(projectConfig.width, projectConfig.height, false);
renderer.outputColorSpace = THREE.SRGBColorSpace;

const fov  = 45;
const zPos = (projectConfig.height / 2) / Math.tan((fov * Math.PI) / 360);
camera.position.set(projectConfig.width / 2, -projectConfig.height / 2, zPos);
camera.lookAt(projectConfig.width / 2, -projectConfig.height / 2, 0);
This camera setup maps pixel coordinates directly onto the canvas, so a clip at {x: 100, y: 200} appears exactly 100×200 pixels from the top-left corner.

Scene Graph

Clips are rendered as THREE.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:
const FPS_target   = 10;
const frameInterval = 1000 / FPS_target; // 100ms

useEffect(() => {
  updatePreview(currentTime);  // filter clips visible at currentTime
  updateAudio();               // filter audio clips for playback

  const now = performance.now();
  if (now - lastDrawTimeRef.current >= frameInterval) {
    newDrawFrame();
    lastDrawTimeRef.current = now;
  }
}, [currentTime, clips, drawFrameEngine.current]);
The playhead itself moves at the full 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.
1

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.
2

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.
3

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.
4

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 in src-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)
FFprobe is used for:
  • 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 React useState 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

RefTypePurpose
currentTimeRefuseRef<number>Source of truth for playhead time. Updated on every RAF frame at 60 Hz. Avoids React re-renders for smooth playhead movement.
topClipsuseRef<Clip[]>Clips visible at currentTime. Updated at 10 FPS by updatePreview(). Read by the Three.js draw function.
drawFrameEngineuseRef<Function>The dynamically loaded render function from the private submodule. Set once on mount via getDrawFrameFunction().
clipboardRefuseRef<Clip[]>Copy/paste clipboard. Stored as a ref (not state) so that handlePaste always reads the latest value without stale closure issues.
audioPlayersRefuseRef<Map<string, HTMLAudioElement>>One HTMLAudioElement per active audio clip. Keyed by clip ID. Managed entirely outside React state for low-latency audio sync.
groupsRefuseRef<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

StatePurpose
clips / assets / tracksThe primary timeline data. Changes here trigger the auto-save debounce (500 ms).
projectConfigResolution, FPS, background color, sample rate. Passed into the render engine context.
history / redoStack100-step undo/redo. Snapshots are { clips, assets, tracks } objects.
isPlayingControls the requestAnimationFrame playback loop and audio element play()/pause().

Rust Dependencies

The Rust backend (src-tauri/Cargo.toml) uses the following key crates:
CrateVersionRole
tauri2 (protocol-asset)Core Tauri framework, asset protocol for safe file serving
tauri-plugin-shell2Spawning FFmpeg/FFprobe/yt-dlp child processes
tauri-plugin-dialog2.6Native open/save file dialogs
tauri-plugin-fs2Filesystem access
tauri-plugin-localhost2tiny_http integration for the asset streaming server
tokio1 (full)Async runtime for all Rust command handlers
serde / serde_json1Serialization of all IPC payloads
image0.24PNG frame encoding during export
base640.21Encoding frames as base64 data URLs for get_video_frame
tiny_http0.12Lightweight HTTP server for asset streaming on port 1234
reqwest0.13 (json)HTTP client for fetch_cloud_fonts
percent-encoding2.3URL-encoding asset paths for the HTTP server
fs_extra1.3Extended filesystem utilities (recursive copy/delete)

JS Dependencies

The frontend (package.json) uses the following key packages:
PackageVersionRole
react / react-dom19UI framework
three0.1833D/2D WebGL rendering engine
@react-three/fiber9React renderer for Three.js (used by the render submodule)
@react-three/drei10Three.js helpers and abstractions
@tauri-apps/api2invoke, convertFileSrc, event listeners
framer-motion12UI animations (clip motion, modal transitions, notifications)
tone15Web Audio abstraction used in the audio effect chain
lucide-react0.563Icon set throughout the editor UI
tailwindcss4Utility-first CSS
vite7Build tool and dev server
typescript~5.8Type checking

Build docs developers (and LLMs) love