Skip to content

Architecture Overview

Sapari is an async-first video processing platform. The web server handles API requests and dispatches work to background workers. Real-time updates flow back to the frontend via Server-Sent Events.

System Architecture

flowchart TB
    subgraph Frontend["Frontend (React)"]
        UI[Dashboard / Timeline / Export]
    end

    subgraph Landing["Landing (Astro)"]
        LP[Marketing Site]
    end

    subgraph Backend["FastAPI Backend"]
        API[API Routes]
        SVC[Services]
        SSE[SSE Endpoint]
    end

    subgraph Storage["Data Stores"]
        PG[(PostgreSQL)]
        RD[(Redis)]
        RMQ[(RabbitMQ)]
        R2[(R2 Storage)]
    end

    subgraph Workers["Background Workers"]
        DW[Download Worker]
        PW[Proxy Worker]
        AW[Analysis Worker]
        RW[Render Worker]
        AEW[Asset Edit Worker]
        EW[Email Worker]
    end

    UI -->|REST API| API
    SSE -->|Events| UI
    API --> SVC
    SVC --> PG
    SVC -->|Queue Tasks| RMQ
    SVC -->|Presigned URLs| R2

    RMQ -->|Consume| DW
    RMQ -->|Consume| PW
    RMQ -->|Consume| AW
    RMQ -->|Consume| RW
    RMQ -->|Consume| AEW

    DW -->|Store| R2
    PW -->|Read/Write| R2
    AW -->|Read| R2
    RW -->|Read/Write| R2
    AEW -->|Read/Write| R2

    DW -->|Publish Events| RD
    PW -->|Publish Events| RD
    AW -->|Publish Events| RD
    RW -->|Publish Events| RD

    RD -->|Subscribe| SSE

Request Flow

A typical video editing flow from upload to export:

sequenceDiagram
    participant C as Client
    participant API as FastAPI
    participant R2 as R2 Storage
    participant Q as RabbitMQ
    participant W as Worker

    Note over C,W: 1. Upload
    C->>API: POST /clips/presign
    API->>C: Presigned PUT URL
    C->>R2: PUT (direct upload)
    C->>API: POST /clips/confirm
    API->>R2: HEAD object (actual size for quota recheck)
    API->>Q: Queue process_clip_artifacts

    Note over C,W: 2. Process
    W->>R2: Download clip
    W->>W: Extract audio, waveform
    W->>R2: Store artifacts
    W-->>C: ClipReadyEvent (SSE)

    Note over C,W: 3. Analyze
    C->>API: POST /projects/{id}/analyze
    API->>Q: Queue analyze_project
    W->>W: Whisper + LLM detection
    W->>API: Create Edit records
    W-->>C: AnalysisCompleteEvent (SSE)

    Note over C,W: 4. Render
    C->>API: POST /projects/{id}/exports
    API->>Q: Queue render_export
    W->>R2: Download clips
    W->>W: Apply cuts (FFmpeg)
    W->>R2: Upload export
    W-->>C: ExportCompleteEvent (SSE)

    Note over C,W: 5. Download
    C->>API: GET /exports/{id}/download
    API->>C: Presigned download URL
    C->>R2: GET (direct download)

Tech Stack

Layer Technology
API FastAPI + SQLAlchemy (async)
Database PostgreSQL
Cache + Events Redis (pub/sub, sessions, result backend)
Message Broker RabbitMQ (priority queues for task routing)
Task Workers TaskIQ
Object Storage Cloudflare R2 (S3-compatible)
Video Processing FFmpeg
Transcription OpenAI Whisper API
LLM Analysis DeepSeek Reasoner + GPT-5 + GPT-5 Mini
Frontend React 19 + Vite + React Query
Landing Astro

Core Patterns

flowchart LR
    subgraph Pattern1["Presigned URLs"]
        A1[Client] -->|1. Request URL| A2[API]
        A2 -->|2. Signed URL| A1
        A1 -->|3. Direct upload/download| A3[R2]
    end

Presigned URLs: Clients upload and download directly from R2. The API never touches file bytes, just generates signed URLs with 1-hour expiry.

flowchart LR
    subgraph Pattern2["Event-Driven Updates"]
        B1[Worker] -->|Publish| B2[Redis Pub/Sub]
        B2 -->|Subscribe| B3[SSE Endpoint]
        B3 -->|Stream| B4[Frontend]
        B4 -->|Invalidate| B5[React Query]
    end

Event-driven updates: Workers publish events to Redis pub/sub. The SSE endpoint streams them to connected clients. This avoids polling and gives instant feedback.

flowchart LR
    subgraph Pattern3["Pipeline Architecture"]
        C1[Load Audio] --> C2[Transcribe]
        C2 --> C3[Detect Silences]
        C2 --> C4[Detect False Starts]
        C3 --> C5[Validate]
        C4 --> C5
        C5 --> C6[Create Edits]
    end

Pipeline architecture: Analysis and render use DAG-based pipelines where each step is a class with execute(). Steps can run in parallel when they don't depend on each other.

Soft deletes: Most entities have is_deleted and deleted_at fields. We don't actually delete data.

Key Files

Component Location
API routes backend/src/interfaces/api/v1/
Business logic backend/src/modules/
Workers backend/src/workers/
Event system backend/src/infrastructure/events/
Storage client backend/src/infrastructure/storage/client.py
Settings backend/src/infrastructure/config/settings.py

← Environment Variables Data Flow →