Instagram and TikTok extraction silently breaks whenever yt-dlp falls behind upstream API changes, and fixing it required a manual image rebuild each time. Updating at container start ties the fix to the Pi's weekly reboot instead, so the extractor stays current without operator intervention. A failed update never blocks startup, because the network may not be up yet right after boot. .gitattributes pins LF for shell scripts: a CRLF checkout on Windows would make the entrypoint unrunnable inside the Linux container. Claude-Session: https://claude.ai/code/session_01QkiLqAtFamcbFXDnCtZSPm
Rezept0r
Self-hosted Rezept-Extraktor fuer Webseiten und Videos mit Import nach Tandoor Recipes.
Optimiert fuer den Betrieb auf einem Raspberry Pi 3 (512 MB Container-Limit, 1 Worker).
Features
- Website-Extraktion -- Rezepte von beliebigen Kochseiten (Chefkoch, Lecker, BBC Good Food, etc.)
- Video-Extraktion -- YouTube, TikTok, Instagram Reels, Vimeo, Reddit, Twitter/X
- 3-stufige Website-Analyse:
recipe-scrapers-> LD+JSON / Schema.org -> GPT-4o-mini Fallback - 4-stufige Video-Analyse: yt-dlp Metadaten -> Caption-Check -> Audio-Download -> Whisper-Transkription -> GPT-4o-mini
- Naehrwert-Berechnung: Bereits extrahiert -> USDA FoodData Central -> GPT-4o-mini Schaetzung
- Tandoor-Import per Klick (REST API + Bild-Upload)
- Rezept-Verlauf in lokaler SQLite-Datenbank
- Echtzeit-Fortschritt via Server-Sent Events (SSE)
Unterstuetzte Plattformen
| Typ | Quellen |
|---|---|
| Video | YouTube, TikTok, Instagram Reels, Vimeo, Twitch, Twitter/X, Facebook, Reddit |
| Website | Alle Seiten mit Rezept-Schema (Schema.org), sowie beliebige Artikel per KI-Extraktion |
Voraussetzungen
- Docker und Docker Compose
- OpenAI API-Key (fuer Whisper-Transkription und GPT-4o-mini)
- Optional: Tandoor-Instanz mit API-Token
- Optional: USDA FoodData Central API-Key
Installation
1. Repository klonen
git clone <repo-url>
cd Rezept0r
2. Umgebungsvariablen konfigurieren
.env-Datei im Projektroot erstellen:
OPENAI_API_KEY=sk-...
USDA_API_KEY=DEMO_KEY
TANDOOR_BASE_URL=http://tandoor:8080
TANDOOR_API_TOKEN=tda_...
| Variable | Pflicht | Beschreibung |
|---|---|---|
OPENAI_API_KEY |
Ja | OpenAI API-Key fuer Whisper + GPT-4o-mini |
USDA_API_KEY |
Nein | USDA FoodData Central Key (Standard: DEMO_KEY) |
TANDOOR_BASE_URL |
Nein | URL der Tandoor-Instanz |
TANDOOR_API_TOKEN |
Nein | Tandoor Bearer-Token |
TMP_DIR |
Nein | Verzeichnis fuer temporaere Audio-Dateien (Standard: tmp, also <Projekt>/tmp bzw. im Container /app/tmp) |
FFMPEG_LOCATION |
Nein | Pfad zu ffmpeg (nur fuer lokale Windows-Entwicklung) |
Bilder und Verlauf-DB liegen immer fest unter <Projekt>/data bzw. im Container unter /app/data:
- Bilder:
data/images - SQLite:
data/history.db
3. Starten
docker compose up -d --build
Die App ist unter http://localhost:88 erreichbar.
Logs anzeigen
docker compose logs -f
Lokale Entwicklung (ohne Docker)
python -m venv .venv
source .venv/Scripts/activate # Windows
# source .venv/bin/activate # Linux/Mac
pip install -r requirements.txt
uvicorn main:app --reload --host 0.0.0.0 --port 8000
Zusaetzlich muss yt-dlp und ffmpeg lokal installiert sein fuer Video-Extraktion.
Architektur
Browser
|
| GET /api/extract/stream?url=... (SSE)
v
FastAPI (uvicorn, 1 Worker)
|
+-- Plattform-Erkennung (URL-Regex)
| |
| +-- Website? --> recipe-scrapers -> LD+JSON -> GPT-4o-mini
| +-- Video? --> yt-dlp Metadaten -> Caption-Check -> Audio/Whisper -> GPT-4o-mini
|
+-- Naehrwert-Anreicherung (USDA / GPT-4o-mini)
+-- SQLite-Verlauf speichern
+-- ExtractResponse an Browser (SSE event: result)
|
| POST /api/import (nach Klick auf "Importieren")
v
Tandoor REST API
Dateistruktur
main.py FastAPI-App, Routen, Health-Check
app/
config.py Pydantic-Settings aus .env
models.py Alle Pydantic-Schemas (Recipe, Tandoor*, SSE-Events)
api/
extract.py GET /api/extract/stream (SSE-Endpoint)
tandoor.py POST /api/import
history.py GET /api/history, DELETE /api/history/{id}
extractors/
base.py URL-Plattformerkennung (Video vs. Website)
website.py 3-stufige Website-Extraktion
video.py 4-stufige Video-Extraktion (yt-dlp + Whisper)
services/
openai_service.py Whisper-Transkription + GPT-4o-mini Extraktion
nutrition.py 3-stufige Naehrwert-Berechnung
tandoor_service.py Tandoor REST-Client + Bild-Upload
history_service.py SQLite-Verlauf (CRUD)
static/
index.html SPA-Frontend
style.css Styling
app.js Frontend-Logik (SSE, UI)
API-Endpunkte
| Methode | Pfad | Beschreibung |
|---|---|---|
GET |
/ |
Frontend (SPA) |
GET |
/api/extract/stream?url=... |
Rezept extrahieren (SSE-Stream) |
POST |
/api/import |
Rezept nach Tandoor importieren |
GET |
/api/history |
Extraktions-Verlauf abrufen |
DELETE |
/api/history/{id} |
Verlaufseintrag loeschen |
GET |
/api/health |
Health-Check |
SSE-Events
Der Extraktions-Endpoint streamt drei Event-Typen:
status-- Fortschrittsupdates (stage,message,progress0-100)result-- Fertiges Rezept als JSON (ExtractResponse)error-- Fehlermeldung
Bekannte Probleme & Loesungen
TikTok: Verbindung verweigert im Docker-Container (Pi-hole)
Wenn Pi-hole als DNS-Server im Heimnetzwerk laeuft, kann der Docker-Container TikTok-Domains nicht aufloesen — Pi-hole gibt eine Sinkhole-IP zurueck, die Verbindung wird sofort verweigert. Ausserhalb des Containers funktioniert TikTok normal, weil der Pi-Host einen anderen DNS-Pfad nutzt.
Loesung: In docker-compose.yml einen oeffentlichen DNS-Server eintragen:
services:
rezeptor:
dns:
- 1.1.1.1
- 8.8.8.8
Danach docker compose up -d (kein Rebuild noetig).
Technische Hinweise
- yt-dlp wird immer als Subprocess aufgerufen (
subprocess.runvialoop.run_in_executor()), nie ueber die Python-API, um den Event-Loop nicht zu blockieren - Whisper hat ein Dateigroessen-Limit von 25 MB -- zu lange Videos fuehren zu einem Fehler
- recipe-scrapers laedt HTML selbststaendig -- es wird die URL uebergeben, nicht vorgeladenes HTML
- Bild-Upload nach Tandoor erfolgt per
PATCHmit Multipart -- derContent-Type-Header wird nicht manuell gesetzt - SSE-Header
X-Accel-Buffering: noist gesetzt fuer Nginx-Proxy-Kompatibilitaet - Container laeuft mit 512 MB Memory-Limit und ohne Swap (SD-Karten-Schutz)
Lizenz
Privates Projekt.