A desktop application for editing animated GIFs, videos, and images with a layer-based compositing model. Open a GIF, MP4, WebM, or static image, add animated GIF overlays and text with affine transforms and keyframe animation, manage frames, preview in real time, and export to GIF, MP4, or WebM.
Built with Tauri v2 (Rust backend) and Svelte 5 + Tailwind CSS 4 (frontend).
libwebkit2gtk-4.1-dev, libgtk-3-dev, libayatana-appindicator3-dev, librsvg2-dev (see Tauri prerequisites)# Install frontend dependencies
pnpm install
# Run in development mode (opens the app with hot reload)
pnpm tauri dev
# Build for production (deb, rpm, AppImage)
pnpm tauri:build
# Lint
pnpm lint
pnpm tauri:build
# Bundles are in target/release/bundle/
flatpak-builder --user --install --force-clean flatpak-build dev.quinnjr.gif-editor.yml
flatpak run dev.quinnjr.gif-editor
src-tauri/ Rust backend
src/
commands.rs Tauri IPC command handlers
compositor.rs Affine warp compositing with bilinear interpolation
error.rs Typed error enum
export.rs GIF/MP4/WebM export pipeline with audio passthrough
fonts.rs Font loading (bundled + system)
frame_source.rs FrameSource trait (GIF, video, static image)
gif_decoder.rs GIF decoding with LRU frame cache
image_source.rs Static image source with expandable timeline
layer.rs Layer model, keyframes, affine interpolation
lib.rs App setup, module registration
project.rs Project state, frame exclusion, index mapping
text_renderer.rs Text rasterization via ab_glyph
video_decoder.rs MP4/WebM decoding via ffmpeg subprocess
fonts/ Bundled font (LiberationSans-Bold)
tests/ 42 backend integration tests
src/ Svelte 5 frontend
lib/
components/
Canvas.svelte Preview canvas with drag handles and keyframe creation
ExportDialog.svelte Export settings modal with progress bar
LayerItem.svelte Layer properties (opacity, keyframe-aware)
LayerPanel.svelte Layer list panel
Timeline.svelte Thumbnails, playback, frame selection, keyframe markers
Toast.svelte Error notifications
Toolbar.svelte File open, layer creation, export trigger
stores/
project.svelte.ts Reactive project state with frame deletion
ui.svelte.ts UI state (selection, playback, preview)
utils/
canvas-renderer.ts Client-side compositing with affine transforms
commands.ts Typed Tauri invoke() wrappers
types.ts TypeScript interfaces (LayerInfo, Keyframe, etc.)
routes/
+page.svelte Main app layout
tests/ Frontend tests (Vitest + jsdom)
The app uses a two-process architecture:
transform() API for instant feedback. Interpolates keyframes client-side to match the backend. Final export always goes through the Rust backend.Communication happens via async Tauri commands (frontend calls Rust) and events (backend pushes export progress).
# Run backend tests (42 tests)
cargo test
# Run frontend tests
pnpm vitest run
# Type-check the frontend
pnpm check
# Lint
pnpm lint