Turn cooking videos from Instagram Reels, TikTok, and YouTube Shorts into structured written recipes by pasting a link.
Runs entirely on your homelab as a lightweight stack alongside your self-hosted Mealie instance.
flowchart TD
A(["User pastes video URL"])
B["yt-dlp\ndownload video\n + \nextract caption"]
C{"Caption looks\nlike a recipe?"}
D["ffmpeg: extract frames\n Whisper: transcribe audio"]
E["Caption used directly\n Whisper skipped"]
F["OpenRouter LLM\nextract recipe\n + \nselect dish photo frame"]
G["Crop & optimize photo\n(crop to square)"]
H{"Push to\nMealie?"}
I["POST to Mealie\n& upload recipe photo"]
J(["Return to UI\nJSON + Markdown + Photo + Mealie link"])
A --> B
B --> C
C -->|"No -- transcribe"| D
C -->|"Yes -- skip Whisper"| E
D --> F
E --> F
F --> G
G --> H
H -->|"Enabled"| I --> J
H -->|"Disabled"| J
style E fill:#1a3a1a,stroke:#34d399,color:#34d399
style I fill:#1a2a3a,stroke:#6c63ff,color:#a78bfa
- Paste: Submit an Instagram Reel, TikTok, or YouTube Shorts URL.
- Process: The system downloads the video, transcribes spoken instructions, and extracts visual frames.
- Generate: A multimodal LLM structures the recipe and identifies the best plated shot.
- Deliver: Export the recipe as Clean Markdown/JSON, or automatically push it directly into Mealie.
Tip
Smart Shortcut: If the creator already pasted the full recipe in the video caption, the pipeline automatically detects it, skips the local transcription (Whisper) phase to save time/CPU resources, and feeds the caption directly to the LLM.
| Component | Technology | Description |
|---|---|---|
| API & Orchestration | Python 3.12 (FastAPI) | Handles endpoints, job status polling, and auth. |
| Asynchronous Queue | Celery + Redis | Manages background extraction jobs concurrently. |
| Video Downloader | yt-dlp | Downloads video streams and metadata. |
| Frame Extractor | ffmpeg | Extracts visual frames for multimodal analysis. |
| Transcriber | faster-whisper | Transcribes audio speech locally on CPU (no API cost). |
| Recipe Extractor | OpenRouter | Multi-modal LLM API (supports Gemini, Claude, GPT, etc.). |
| Recipe Target | Mealie | Optional self-hosted recipe manager integration. |
| Frontend | Vanilla HTML5 / CSS3 / JS | Clean responsive UI with Lucide Icons. |
flowchart LR
subgraph Browser["Browser"]
UI["index.html"]
end
subgraph Docker["Docker Container Space"]
subgraph FastAPIApp["sousvid container"]
Main["main.py\nFastAPI"]
Config["config.py\nSettings"]
end
subgraph Queue["redis container"]
Redis[("Redis\nBroker & Backend")]
end
subgraph Worker["worker container"]
Celery["worker.py\nCelery Worker"]
Pipeline["pipeline.py\nOrchestration"]
DL["downloader.py"]
FE["frame_extractor.py"]
TR["transcriber.py"]
LLM["llm.py"]
ME["mealie.py"]
end
end
subgraph External["External Services"]
Platforms[("TikTok / Instagram\n/ YouTube")]
OR[("OpenRouter API")]
Mealie[("Mealie\nself-hosted")]
end
UI -->|"POST /extract/submit"| Main
UI <-->|"GET /jobs/{id}"| Main
Main -->|"enqueue task"| Redis
Celery -->|"consume / poll"| Redis
Celery --> Pipeline
Pipeline --> DL & FE & TR & LLM & ME
DL <-->|"download"| Platforms
LLM <-->|"REST"| OR
ME <-->|"REST"| Mealie
For more detailed sequence diagrams and worker configuration details, see Job Queue Architecture.
- Docker & Docker Compose installed.
- An OpenRouter API key (Gemini 1.5 Flash costs roughly $0.001 per recipe).
- A self-hosted Mealie instance (optional — you can download recipes as Markdown/JSON without it).
Create a .env file in your deployment directory:
# Required Configuration
OPENROUTER_API_KEY=sk-or-your-api-key-here
OPENROUTER_MODEL=google/gemini-flash-1.5
# Optional — Leave blank to disable automatic Mealie pushes
MEALIE_URL=http://your-mealie-ip:9925
MEALIE_API_TOKEN=your-mealie-api-token-here
# Transcription Settings (Whisper)
WHISPER_MODEL=small # options: tiny | base | small | medium | large-v3
WHISPER_DEVICE=cpu # options: cpu | cuda
WHISPER_COMPUTE_TYPE=int8 # options: int8 (CPU) | float16 (GPU)
# Extraction Settings
MAX_FRAMES=6Instagram blocks unauthenticated video downloads. You must export session cookies from your browser:
- Install an extension like Get cookies.txt LOCALLY (Chrome/Edge) or Firefox equivalent.
- Log into Instagram in your web browser.
- Open the extension, click Export, and save the file.
- Create a folder named
cookiesin your deployment directory and save the file ascookies/cookies.txt.
Note
TikTok and YouTube Shorts links do not require cookies to download.
Choose one of the two options below to run the stack.
This is the easiest path for self-hosters. Use the following docker-compose.yml:
services:
sousvid:
image: leshicodes/sousvid:v1.0.0
container_name: sousvid
restart: unless-stopped
ports:
- "8000:8000"
env_file:
- .env
volumes:
- ./cookies:/app/cookies
- ./data:/app/data
depends_on:
- redis
worker:
image: leshicodes/sousvid:v1.0.0
container_name: sousvid-worker
command: celery -A app.worker.celery_app worker --loglevel=info
restart: unless-stopped
env_file:
- .env
volumes:
- whisper-cache:/root/.cache/huggingface
- ./cookies:/app/cookies
- ./data:/app/data
depends_on:
- redis
redis:
image: redis:7-alpine
container_name: sousvid-redis
restart: unless-stopped
volumes:
- redis-data:/data
volumes:
whisper-cache:
driver: local
redis-data:
driver: localRun:
docker compose up -dIf you are developing or want to compile the image locally, clone the repository and run:
docker compose up -d --build(Uses the local Dockerfile build targets specified in the repository's docker-compose.yml).
Once started, navigate to http://localhost:8000 to access the web interface.
| Variable | Default | Description |
|---|---|---|
OPENROUTER_API_KEY |
(Required) | Your OpenRouter API Key. |
OPENROUTER_MODEL |
google/gemini-flash-1.5 |
Model used for recipe processing (Gemini Flash recommended for cost/speed). |
DB_PATH |
/app/data/sousvid.db |
Local path inside container for user, service, and history database. |
WHISPER_MODEL |
small |
Size of local model. Larger models are more accurate but consume more RAM. |
WHISPER_DEVICE |
cpu |
Execution hardware: cpu or cuda. |
WHISPER_COMPUTE_TYPE |
int8 |
Model precision (int8 for CPU, float16 for GPU). |
MAX_FRAMES |
6 |
Maximum number of keyframes extracted and sent to LLM. |
COOKIES_FILE |
/app/cookies/cookies.txt |
Path inside container where Instagram cookies are mapped. |
REDIS_URL |
redis://redis:6379/0 |
Celery broker URL. |
JWT_SECRET |
(Auto-generated) | Key used to sign user auth tokens. Auto-generated on first run. |
JWT_EXPIRE_HOURS |
168 |
Expiration duration for user sessions (7 days). |
ALLOW_REGISTRATION |
true |
Set to false to disable new registrations (still allows the first user to bootstrap as admin). |
| Model Size | RAM Usage | Speed | Accuracy |
|---|---|---|---|
tiny |
~1 GB | Fastest | Low |
base |
~1.5 GB | Fast | OK |
small |
~2.5 GB | Good | Recommended (Default) |
medium |
~5 GB | Slower | Great |
large-v3 |
~10 GB | Slow (needs GPU) | Best |
| Component | Cost | Notes |
|---|---|---|
| yt-dlp, ffmpeg, Whisper | Free | Runs entirely on your local machine. |
| OpenRouter (Gemini Flash) | ~$0.001 / recipe | Recommended default. |
| OpenRouter (Claude Sonnet) | ~$0.02 / recipe | High accuracy, higher price. |
See CONTRIBUTING.md for development setup, testing, and contribution instructions. Changes are tracked in CHANGELOG.md.
pip install -r requirements-dev.txt
pytest tests/ -v
ruff check app/ tests/See ROADMAP.md for planned features and ideas under consideration.