Skip to content

Repository files navigation

Meme Swap

Your face. Any meme.

AI face swapping for GIFs β€” running 100% locally on your Mac.
No cloud. No uploads. Just memes.

⬇️ Download for Mac Β· 🌐 Landing page Β· Installation Β· Architecture Β· Contributing


Release macOS TypeScript Turborepo pnpm FaceFusion PRs Welcome License: MIT Sponsor


meme-swap wraps the FaceFusion AI engine in a fully typed TypeScript pipeline, and ships it as three apps that share one local engine:

Surface Stack What you get
🌐 Web app Next.js 16 Drag & drop face swap, Giphy search, model settings, live progress
πŸ–₯️ Desktop app Electron Native macOS app with guided first-run setup, packaged as a .dmg
πŸ€– MCP server Model Context Protocol Lets AI assistants (Claude, Cursor…) run face swaps as a tool

Why it's different

  • πŸ”’ Private by design β€” models, processing and results all live in ~/.meme-swap/ on your machine. Your face never leaves your Mac, and swapping works fully offline once installed.
  • ⚑ Apple Silicon fast β€” inference runs through CoreML (Neural Engine + GPU) via ONNX Runtime, with CPU and CUDA fallbacks.
  • 🧠 A real AI pipeline β€” not just a swap: inswapper_128 for identity transfer, CodeFormer face restoration, Real-ESRGAN Γ—2 frame upscaling, expression restorer and lip syncer, orchestrated per-frame.
  • 🎞️ GIF-native β€” FFmpeg handles GIF ↔ MP4 both ways with palette-optimized output, so memes stay memes.
  • πŸ”Ž Giphy, plugged in β€” search trending memes right inside the app with hashtag shortcuts; pick a GIF and it's fetched and converted automatically (with a curated offline library when no API key is set).

A Quick Look

Meme Swap web app β€” dark UI with built-in Giphy GIF library

Built-in Giphy search with trending memes and hashtag pills

Giphy built in β€” search, click, swap. No downloads, no tabs.

Face swap ready to launch β€” target GIF, source face and quality presets

Ready to swap β€” target GIF, source face, quality presets. One button.


Table of Contents


Architecture

graph TB
    subgraph "Applications"
        FE["Web Frontend\n(Next.js 16)"]
        DE["Desktop App\n(Electron)"]
        MCP["MCP Server\n(HTTP/SSE)"]
    end

    subgraph "Shared Packages"
        FC["@meme-swap/faceswap-core\nFaceFusion wrapper"]
        VP["@meme-swap/video-processor\nFFmpeg wrapper"]
        AC["@meme-swap/api-client\n(Giphy client)"]
        I18N["@meme-swap/i18n\nTranslations"]
    end

    subgraph "Local engine β€” ~/.meme-swap/"
        FF["FaceFusion\n(Python venv + ONNX models)"]
        FFMPEG["FFmpeg\n(system)"]
    end

    FE --> FC
    FE --> VP
    DE --> FC
    DE --> VP
    MCP --> FC
    MCP --> VP

    FC --> FF
    VP --> FFMPEG
Loading

Processing Pipeline

sequenceDiagram
    participant U as User
    participant App as App (Web/Desktop/MCP)
    participant VP as video-processor
    participant FC as faceswap-core
    participant FF as FaceFusion

    U->>App: Upload face image + target GIF
    App->>VP: gifToMp4() if target is GIF
    VP-->>App: MP4 video
    App->>FC: runFaceSwap(source, target.mp4)
    FC->>FF: spawn python3 facefusion.py (headless-run)
    FF-->>FC: processed MP4 (+ live progress)
    FC-->>App: result path
    App->>VP: mp4ToGif() if output should be GIF
    VP-->>App: final GIF
    App-->>U: Download URL
Loading

AI models in play

Processor Model Role
Face swapper inswapper_128 / inswapper_128_fp16 Identity transfer, frame by frame
Face enhancer codeformer Restores and sharpens the swapped face
Frame enhancer real_esrgan_x2 Optional Γ—2 super-resolution of the whole frame
Expression restorer expression_restorer Keeps the original grimaces and blinks
Lip syncer optional Re-syncs mouth movement to the source audio

Execution providers: coreml (default on Apple Silicon) β†’ cpu fallback, cuda supported.


Project Structure

meme-swap/
β”œβ”€β”€ apps/
β”‚   β”œβ”€β”€ frontend/             # Next.js 16 web application
β”‚   β”œβ”€β”€ desktop/              # Electron desktop application
β”‚   └── mcp-server/           # MCP server (HTTP/SSE transport)
β”‚
β”œβ”€β”€ packages/
β”‚   β”œβ”€β”€ faceswap-core/        # TypeScript wrapper for FaceFusion
β”‚   β”œβ”€β”€ video-processor/      # FFmpeg wrapper (GIF ↔ MP4)
β”‚   β”œβ”€β”€ api-client/           # Giphy API client (search + trending, with localStorage/IPC/env fallback chain)
β”‚   └── i18n/                 # Shared translations (EN/FR)
β”‚
β”œβ”€β”€ website/                  # Landing page (GitHub Pages)
β”‚
β”œβ”€β”€ scripts/
β”‚   └── setup-facefusion.mjs  # FaceFusion one-time installer (CLI wrapper around @meme-swap/installer-core)
β”‚
β”œβ”€β”€ docs/
β”‚   β”œβ”€β”€ architecture.md       # Detailed architecture notes
β”‚   β”œβ”€β”€ development.md        # Local development guide
β”‚   └── adr/                  # Architecture Decision Records
β”‚
β”œβ”€β”€ .github/workflows/        # CI β€” auto-deploys website/ to GitHub Pages
β”œβ”€β”€ turbo.json                # Turborepo pipeline
└── pnpm-workspace.yaml       # pnpm workspace config

Note: FaceFusion is installed globally at ~/.meme-swap/facefusion/ and is never bundled inside the repo. All apps resolve it from that path at runtime. Deleting ~/.meme-swap/ removes every model, cache and result.


Prerequisites

Tool Version Install
Node.js β‰₯ 18 nodejs.org
pnpm β‰₯ 9 npm i -g pnpm
Python β‰₯ 3.9 brew install python
FFmpeg any brew install ffmpeg
Git β‰₯ 2 pre-installed on macOS

Installation

Option 1 β€” Download the desktop app (recommended)

Grab the latest .dmg (Apple Silicon) from the Releases page, open it and drag Meme Swap.app into Applications. The guided first-run setup installs FaceFusion into ~/.meme-swap/ for you.

⚠️ First launch: the app is not signed or notarized

The build has no Apple Developer ID (that's a paid Apple program), so it is only ad-hoc signed and not notarized. On first launch macOS will block it with a message like "Apple could not verify 'Meme Swap' is free of malware." This is expected β€” the app is safe, macOS just can't verify an unnotarized developer. To allow it:

  1. Try to open Meme Swap.app once (double-click) β€” you'll get the warning. Click Done.
  2. Open System Settings β†’ Privacy & Security.
  3. Scroll down to the Security section β€” you'll see ""Meme Swap" was blocked to protect your Mac."
  4. Click Open Anyway, then confirm with your password or Touch ID.

macOS remembers this, so subsequent launches open normally.

Prefer the Terminal?

Removing the download quarantine flag has the same effect:

xattr -cr "/Applications/Meme Swap.app"

Option 2 β€” Build from source

# 1. Clone & install the monorepo
git clone https://github.com/Tlahey/meme-swap.git && cd meme-swap
pnpm install

# 2. One-time: install FaceFusion + Python venv into ~/.meme-swap
pnpm install:facefusion

# 3. Configure environment (optional)
cp .env.example .env.local

# 4. Build all packages
pnpm build

Running the Apps

Web Frontend

pnpm frontend:dev
# β†’ http://localhost:3010

Desktop App (Electron)

pnpm desktop:dev

Build a distributable DMG

pnpm build            # 1. Build all shared packages
pnpm desktop:build    # 2. Compile the desktop TypeScript + copy assets
pnpm desktop:package  # 3. Package into a .dmg via electron-builder

The .dmg is output to apps/desktop/dist/. Double-click it to install Meme Swap.app into your Applications folder.

Note: gatekeeperAssess is disabled in the build config, so macOS may show an unverified developer warning. Right-click β†’ Open to bypass it, or sign the app with an Apple Developer certificate.

MCP Server

# Build first
pnpm build --filter=mcp-server

# Start
cd apps/mcp-server && pnpm start
# β†’ http://localhost:3001

To use the MCP server with an AI client (e.g. Cursor), add to your MCP config:

{
  "mcpServers": {
    "meme-swap": {
      "command": "node",
      "args": ["<absolute-path>/apps/mcp-server/build/index.js"]
    }
  }
}

Available MCP tools:

  • run_faceswap β€” perform a face swap given a source image and target media path

See apps/mcp-server/README.md for the full API reference.


Packages

@meme-swap/faceswap-core

TypeScript wrapper around the FaceFusion Python CLI.

import { runFaceSwap } from '@meme-swap/faceswap-core';

const result = await runFaceSwap({
  sourcePath: './face.jpg',
  targetPath: './target.mp4',
  outputPath: './output.mp4',
  executionProviders: ['coreml', 'cpu'], // Apple Silicon
  faceSwapperModel: 'inswapper_128_fp16',
  faceEnhancerModel: 'codeformer',
  threadCount: 4,
  logLevel: 'info',
  onProgress: ({ step, percent }) => console.log(step, percent),
});

if (result.success) {
  console.log('Output:', result.outputPath);
}
Option Type Default Description
sourcePath string β€” Source face image
targetPath string β€” Target video (must be MP4)
outputPath string β€” Output file path
executionProviders ('coreml' | 'cpu' | 'cuda')[] ['coreml','cpu'] Hardware accelerators
faceSelectorMode string β€” 'many', 'one', 'reference'
faceSwapperModel string β€” e.g. inswapper_128_fp16
faceEnhancerModel string β€” e.g. codeformer
frameEnhancerModel string β€” e.g. real_esrgan_x2
expressionRestorerModel string β€” Restores original expressions
lipSyncerModel string β€” Re-syncs lips to source audio
threadCount number auto Parallel execution threads
logLevel 'debug' | 'info' | 'warning' | 'error' 'info' Log verbosity
onProgress (p) => void β€” Live progress callback (analysing / extracting / processing / merging)

@meme-swap/video-processor

FFmpeg wrapper for format conversion.

import { gifToMp4, mp4ToGif } from '@meme-swap/video-processor';

// GIF β†’ MP4 (required before running FaceFusion)
await gifToMp4({ inputPath: './input.gif', outputPath: './input.mp4' });

// MP4 β†’ GIF (for final output)
await mp4ToGif({
  inputPath: './output.mp4',
  outputPath: './output.gif',
  fps: 10,
  maxWidth: 480,
});

Environment Variables

Copy .env.example to .env.local and configure:

# Optional β€” used by the Giphy API client for GIF search; falls back to a curated GIF list if unset
GIPHY_API_KEY=your_key_here

# FaceFusion execution providers (default: coreml,cpu on Apple Silicon)
FACEFUSION_EXECUTION_PROVIDERS=coreml,cpu

# Thread count for FaceFusion (default: auto-detected)
FACEFUSION_THREAD_COUNT=8

# Port for the web frontend dev server
PORT=3010

Landing Page & Docs

The project landing page lives in website/ and is deployed automatically to tlahey.github.io/meme-swap by the deploy-pages GitHub Actions workflow on every push to main that touches website/**.

One-time setup: in the repo settings, set Settings β†’ Pages β†’ Source to GitHub Actions.

More docs:


Support the Project

Meme Swap is free, open-source, and built in my spare time. If it's useful to you, consider sponsoring on GitHub β€” it helps keep the project maintained and moving forward.


Contributing

See CONTRIBUTING.md for the full guide.

Quick summary:

  1. Fork the repo and create a branch: feature/<name> or fix/<name>
  2. Follow the code style rules β€” TypeScript strict mode, named exports, async/await
  3. Open a pull request with a clear description

Responsible Use

Meme Swap is built for entertainment and creative use. Always get consent before swapping someone's face, and never use it to deceive, impersonate or harm anyone. You are responsible for the content you create.


License

MIT Β© Tlahey


Acknowledgments

About

🎭 Put your face in any GIF β€” AI face swapping running 100% locally on your Mac (FaceFusion + CoreML). Web app, desktop app & MCP server.

Topics

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages