Skip to main content
Frame is built on Tauri v2, combining a Rust backend for performance-critical operations with a modern Svelte 5 frontend for reactive UI. This architecture enables native-level performance while maintaining cross-platform compatibility.

Overview

Backend Architecture (Rust)

The Rust backend handles all media processing, process management, and system integration.

Core Modules

Located in src-tauri/src/conversion/, the backend is organized into specialized modules:
ModuleFileResponsibility
Managermanager.rsAsync task queue, concurrency control, process lifecycle
Workerworker.rsFFmpeg process execution, progress parsing
Probeprobe.rsMedia metadata extraction via FFprobe
Upscaleupscale.rsAI upscaling pipeline (frame extraction → upscale → encode)
Argsargs.rsFFmpeg argument construction
Codeccodec.rsVideo/audio codec configuration
Filtersfilters.rsVideo/audio filter chain building
Typestypes.rsShared data structures
Commandscommands.rsTauri command handlers (IPC entry points)

Conversion Manager

The ConversionManager (manager.rs:35-199) is the core orchestrator:
pub struct ConversionManager {
    sender: mpsc::Sender<ManagerMessage>,
    max_concurrency: Arc<AtomicUsize>,
    active_tasks: Arc<Mutex<HashMap<String, u32>>>,
    cancelled_tasks: Arc<Mutex<HashSet<String>>>,
}
Key Features:
  • Async Task Queue: Uses tokio::mpsc::channel for message passing (manager.rs:44)
  • Concurrency Control: Limits concurrent FFmpeg processes (default: 2) via AtomicUsize (manager.rs:46)
  • Process Management: Tracks PIDs for pause/resume/cancel operations (manager.rs:38)
  • Cross-Platform Signals:
    • Unix: SIGSTOP/SIGCONT/SIGKILL via libc (manager.rs:273-314)
    • Windows: NtSuspendProcess/NtResumeProcess via Win32 API (manager.rs:374-410)

Message Flow

Reference: manager.rs:53-191

FFmpeg Worker

The worker (worker.rs) executes FFmpeg as a Tauri sidecar process:
pub async fn run_ffmpeg_worker(
    app: AppHandle,
    tx: mpsc::Sender<ManagerMessage>,
    task: ConversionTask,
) -> Result<(), ConversionError>
Process:
  1. Build FFmpeg arguments from ConversionConfig (worker.rs:26-32)
  2. Spawn FFmpeg sidecar via tauri-plugin-shell (worker.rs:34-42)
  3. Parse stderr for progress (worker.rs:88-136):
    • Extract current time via regex: time=(\d{2}:\d{2}:\d{2}\.\d{2}) (utils.rs)
    • Calculate percentage: (current_time / total_duration) * 100
  4. Emit IPC events to frontend (worker.rs:50, 127)
Reference: worker.rs:15-158

Probe Module

Media metadata extraction via FFprobe (probe.rs:8-133):
pub async fn probe_media_file(
    app: &AppHandle,
    file_path: &str,
) -> Result<ProbeMetadata, ConversionError>
Execution:
  1. Spawn FFprobe with JSON output: -print_format json -show_format -show_streams (probe.rs:12-20)
  2. Parse JSON into FfprobeOutput struct via serde_json (probe.rs:37)
  3. Extract metadata:
    • Video: codec, resolution, frame rate, bitrate (probe.rs:49-73)
    • Audio: tracks, channels, sample rate, language (probe.rs:75-97)
    • Subtitles: codec, language, labels (probe.rs:99-113)
Reference: probe.rs:8-133

AI Upscaling Pipeline

The upscale worker (upscale.rs) implements a multi-stage pipeline: Stage 1: Frame Extraction (upscale.rs:311-430)
  • Decode video to PNG sequence via FFmpeg
  • Apply pre-upscale filters (crop, deinterlace, etc.)
  • Output to temp directory: $TEMP/frame_upscale_{id}/input/frame_%08d.png
Stage 2: AI Upscaling (upscale.rs:456-556)
  • Spawn realesrgan-ncnn-vulkan sidecar
  • Models: realesr-animevideov3-x2 (2x) or x4 (4x)
  • Thread config: load:process:save based on resolution (upscale.rs:148-171)
  • Parse stderr for frame completion: or -> indicators (upscale.rs:525-543)
Stage 3: Re-encoding (upscale.rs:558-636)
  • Encode upscaled PNG sequence back to video
  • Mux with original audio/subtitle tracks from source file
  • Apply post-upscale video codec settings
Reference: upscale.rs:238-636

Sidecar Execution Model

Frame bundles FFmpeg, FFprobe, and Real-ESRGAN as Tauri sidecars:
  1. Binary Resolution: Tauri resolves platform-specific binaries from src-tauri/binaries/
  2. Spawn: app.shell().sidecar("ffmpeg") launches subprocess (worker.rs:34-38)
  3. IPC: Communicate via stdin/stdout/stderr (CommandEvent::Stderr)
  4. Lifecycle: Track PID for pause/resume/kill operations
Configuration in src-tauri/tauri.conf.json:
"bundle": {
  "externalBin": [
    "binaries/ffmpeg",
    "binaries/ffprobe",
    "binaries/realesrgan-ncnn-vulkan"
  ]
}

Frontend Architecture (SvelteKit)

The frontend is built with Svelte 5 (Runes API) and SvelteKit as the meta-framework.

Directory Structure

src/
├── routes/
│   ├── +page.svelte          # Main application view
│   └── splash/+page.svelte   # Splash screen
├── lib/
│   ├── components/           # UI components
│   │   ├── file-list/        # File list and rows
│   │   ├── settings/         # Settings panel and tabs
│   │   ├── logs/             # Logs viewer
│   │   └── ui/               # Reusable UI primitives
│   ├── stores/               # Svelte stores ($state)
│   ├── services/             # Tauri IPC services
│   ├── features/             # Feature modules (update, etc.)
│   ├── i18n/                 # Internationalization
│   └── types.ts              # TypeScript types

State Management

Frame uses Svelte 5 Runes for reactive state:
// Example: Conversion state store
let conversions = $state<Map<string, ConversionState>>(new Map());
let activeConversions = $derived(Array.from(conversions.values()));

function addConversion(task: ConversionTask) {
  conversions.set(task.id, {
    ...task,
    progress: 0,
    status: 'queued'
  });
}
Key Runes:
  • $state: Mutable reactive state
  • $derived: Computed values (like Svelte 4’s $:)
  • $effect: Side effects (replaces lifecycle hooks)
  • $props: Component props

IPC Communication

Frontend communicates with Rust backend via Tauri commands and events: Commands (frontend → backend):
import { invoke } from '@tauri-apps/api/core';

// Queue a conversion task
await invoke('queue_conversion', { task });

// Pause/resume/cancel
await invoke('pause_conversion', { id });
await invoke('resume_conversion', { id });
await invoke('cancel_conversion', { id });
Events (backend → frontend):
import { listen } from '@tauri-apps/api/event';

// Listen for progress updates
await listen<ProgressPayload>('conversion-progress', (event) => {
  updateProgress(event.payload.id, event.payload.progress);
});

await listen<LogPayload>('conversion-log', (event) => {
  appendLog(event.payload.id, event.payload.line);
});
Reference: src-tauri/src/lib.rs:156-168 for command registration

Concurrent Processing Design

Task Queue System

Frame implements a bounded concurrency queue:
// manager.rs:201-246
async fn process_queue(
    queue: &mut VecDeque<ConversionTask>,
    running_tasks: &mut HashMap<String, ()>,
    max_concurrency: Arc<AtomicUsize>,
) {
    let limit = max_concurrency.load(Ordering::SeqCst).max(1);
    
    while running_tasks.len() < limit {
        if let Some(task) = queue.pop_front() {
            running_tasks.insert(task.id.clone(), ());
            tokio::spawn(run_ffmpeg_worker(task));
        } else {
            break;
        }
    }
}
Concurrency Control:
  • Default: 2 concurrent FFmpeg processes
  • User-adjustable: set_max_concurrency(value) command
  • Reason: FFmpeg is CPU/GPU intensive; too many concurrent processes cause thrashing
Reference: manager.rs:201-246, types.rs:71 (DEFAULT_MAX_CONCURRENCY)

Async Runtime

Frame uses Tokio as the async runtime:
# Cargo.toml
tokio = "1.49.0"
Key Patterns:
  • tokio::sync::mpsc: Message passing between manager and workers
  • tokio::spawn: Spawn async tasks
  • tauri::async_runtime::spawn: Tauri’s wrapper for spawning tasks
Reference: manager.rs:53-191

Dependencies

Rust (Backend)

CrateVersionPurpose
tauri2.9.5Application framework
tauri-plugin-shell2.3.4Sidecar process execution
tauri-plugin-dialog2.6.0Native file dialogs
tauri-plugin-fs2.4.5Filesystem operations
tauri-plugin-store2.3.0Persistent key-value storage
tokio1.49.0Async runtime
serde / serde_json1.0Serialization
regex1.12.2FFmpeg output parsing
thiserror2.0.18Error handling
Reference: src-tauri/Cargo.toml

Frontend (TypeScript/Svelte)

PackageVersionPurpose
@sveltejs/kit2.50.0Meta-framework
svelte5.47.1UI framework (Runes)
@tauri-apps/api2.9.1Tauri IPC bindings
tailwindcss4.1.18CSS framework
svelte-i18n4.0.1Internationalization
typescript5.9.3Type safety
Reference: package.json

Platform-Specific Code

Frame includes platform-specific implementations:

Window Effects

// lib.rs:27-52
#[cfg(target_os = "macos")]
fn apply_window_effect(window: &tauri::WebviewWindow) {
    window.set_effects(
        EffectsBuilder::new()
            .effect(Effect::HudWindow)  // macOS vibrancy
            .state(EffectState::Active)
            .radius(16.0)
            .build(),
    );
}

#[cfg(target_os = "windows")]
fn apply_window_effect(window: &tauri::WebviewWindow) {
    window.set_effects(
        EffectsBuilder::new()
            .effect(Effect::Acrylic)  // Windows Acrylic
            .build(),
    );
}

Process Control

  • Unix: libc::kill(pid, SIGSTOP/SIGCONT/SIGKILL) (manager.rs:273-314)
  • Windows: NtSuspendProcess/NtResumeProcess via ntdll.dll (manager.rs:374-410)
Reference: src-tauri/src/lib.rs:27-52, manager.rs:266-372

Performance Optimizations

Release Build Profile

# Cargo.toml:35-40
[profile.release]
opt-level = "z"         # Optimize for size
lto = true              # Link-time optimization
codegen-units = 1       # Single codegen unit for better optimization
strip = true            # Strip symbols
panic = "abort"         # Reduce binary size

Regex Compilation

FFmpeg output parsing uses precompiled regexes via once_cell:
// utils.rs
use once_cell::sync::Lazy;
use regex::Regex;

pub static TIME_REGEX: Lazy<Regex> = 
    Lazy::new(|| Regex::new(r"time=(\d{2}:\d{2}:\d{2}\.\d{2})").unwrap());
pub static DURATION_REGEX: Lazy<Regex> = 
    Lazy::new(|| Regex::new(r"Duration: (\d{2}:\d{2}:\d{2}\.\d{2})").unwrap());
Reference: src-tauri/src/conversion/utils.rs

Next Steps

Building from Source

Set up your development environment

Contributing

Learn the contribution workflow

Build docs developers (and LLMs) love