Ein kleiner Django-Monolith, der YouTube-URLs annimmt, Rezeptinformationen aus Transkripten extrahiert und als Katalog speichert. Die KI-Extraktion läuft über LM Studio mit der OpenAI-kompatiblen lokalen API.
make setup
make serverExtraktionen laufen über Huey. Starte in einem zweiten Terminal den Worker:
make workerNach Code- oder Migrationsänderungen kannst du den Server mit Ctrl-C stoppen und erneut mit
make dev starten. make dev führt zuerst Migrationen aus und startet dann den Dev-Server auf
http://127.0.0.1:8000/.
LM Studio muss mit aktiviertem lokalen Server laufen, typischerweise:
http://localhost:1234/v1
Optional kannst du das Modell oder die URL per Environment überschreiben:
export LM_STUDIO_BASE_URL=http://localhost:1234/v1
export LM_STUDIO_MODEL=google/gemma-4-e2bStandardmäßig verwendet die App dieses LM-Studio-Modell:
google/gemma-4-e2b
Du kannst es jederzeit per LM_STUDIO_MODEL überschreiben. Das ist relevant, weil Rezeptinger von LM Studio eine strukturierte JSON-Antwort erwartet. Chat- oder Instruct-Modelle befolgen solche Ausgabevorgaben meist zuverlässiger als Base-Modelle; Embedding-Modelle sind für die Extraktion nicht geeignet.
Wenn LM_STUDIO_MODEL leer ist, versucht die App ein geeignetes geladenes Modell über /v1/models zu finden. Dabei werden Chat/Instruct-nahe Modelle bevorzugt und Embedding-Modelle ignoriert.
Der Django-Admin ist lokal unter http://127.0.0.1:8000/admin/ erreichbar. Falls noch kein Admin-User existiert, lege ihn so an:
uv run python manage.py createsuperuserDie Rezeptinger-Einstellungen wie LM-Studio-URL, Modell, Transkript-Limit, Sprachpräferenz und
Extraktionsprompt werden nur im Admin unter App settings bearbeitet.
Das Docker-Image nutzt Gunicorn, führt beim Start automatisch Migrationen aus und speichert SQLite
standardmäßig unter /data/db.sqlite3. Statische Dateien werden beim Image-Build gesammelt und über
WhiteNoise ausgeliefert.
Image bauen:
make docker-buildDockerfile und Compose-Konfiguration prüfen:
make docker-checkContainer-Image lokal mit Trivy auf HIGH/CRITICAL Findings scannen:
make docker-scanTags im Format vX.Y.Z lösen in GitHub Actions den Docker-Workflow aus. Dabei werden GHCR-Images
für die Version, latest und den Commit-SHA veröffentlicht und anschließend ein GitHub Release mit
generierten Release Notes erstellt.
Container mit benanntem Volume starten:
docker run --rm --name rezeptinger-local -p 8000:8000 \
-e LM_STUDIO_BASE_URL=http://host.docker.internal:1234/v1 \
-v rezeptinger-data:/data \
rezeptinger:localDie App ist danach unter http://127.0.0.1:8000/ erreichbar.
Empfohlen für den lokalen Docker-Betrieb ist Compose. Es baut das lokale Image, bindet Port 8000
und speichert SQLite/Huey dauerhaft im benannten Volume rezeptinger-data:
docker compose up --buildDie App ist danach unter http://127.0.0.1:8000/ erreichbar.
Wenn Extraktionen verarbeitet werden sollen, starte den optionalen Worker mit:
docker compose --profile worker up --buildLogs und Stop:
docker compose logs -f
docker compose downDer Container hat einen Docker-Healthcheck gegen /health/. Den Status siehst du mit:
docker compose psDer Prozess läuft im Container als non-root User rezeptinger. Das Volume /data ist für diesen
User beschreibbar, damit SQLite und Huey dort Dateien anlegen können.
Das Datenvolume bleibt bei docker compose down erhalten. Entferne es nur bewusst:
docker compose down -vIm Container ist localhost der Container selbst. Für LM Studio auf dem Host nutzt du auf Docker Desktop:
http://host.docker.internal:1234/v1
Ohne Docker ist die passende lokale URL weiterhin:
http://localhost:1234/v1
Empfohlen für Docker: benanntes Volume. Die Daten bleiben erhalten, auch wenn der Container mit --rm entfernt wird:
docker run --rm --name rezeptinger-local -p 8000:8000 \
-e LM_STUDIO_BASE_URL=http://host.docker.internal:1234/v1 \
-v rezeptinger-data:/data \
rezeptinger:localAlternative: lokale Projekt-db.sqlite3 direkt in den Container mounten:
docker run --rm --name rezeptinger-local -p 8000:8000 \
-e LM_STUDIO_BASE_URL=http://host.docker.internal:1234/v1 \
-v "$PWD/db.sqlite3:/data/db.sqlite3" \
rezeptinger:localNutze dabei nicht gleichzeitig den lokalen Django-Runserver und den Container gegen dieselbe SQLite-Datei.
Wenn der Container im Vordergrund läuft, stoppst du ihn mit Ctrl-C.
Wenn er im Hintergrund gestartet wurde:
docker logs -f rezeptinger-local
docker stop rezeptinger-localLM_STUDIO_BASE_URL=http://host.docker.internal:1234/v1
LM_STUDIO_MODEL=google/gemma-4-e2b
LM_STUDIO_MAX_TOKENS=8192
SQLITE_DATABASE_PATH=/data/db.sqlite3
YT_DLP_COOKIES_FILE=Wenn du im Container ein anderes Modell nutzen willst, setze LM_STUDIO_MODEL beim Start explizit mit -e LM_STUDIO_MODEL=....
Der Webserver legt Extraktionen nur in die Queue. Für die eigentliche Verarbeitung muss zusätzlich ein Worker laufen.
Lokal:
uv run python manage.py run_hueyMit Docker im zweiten Terminal:
docker run --rm --name rezeptinger-worker \
-e LM_STUDIO_BASE_URL=http://host.docker.internal:1234/v1 \
-v rezeptinger-data:/data \
rezeptinger:local \
python manage.py run_hueyWenn du die lokale db.sqlite3 mountest, nutze für Web und Worker denselben Mount:
docker run --rm --name rezeptinger-worker \
-e LM_STUDIO_BASE_URL=http://host.docker.internal:1234/v1 \
-v "$PWD/db.sqlite3:/data/db.sqlite3" \
-v "$PWD/huey.sqlite3:/data/huey.sqlite3" \
rezeptinger:local \
python manage.py run_hueyFür Tests oder Spezialfälle kann Huey synchron laufen:
HUEY_IMMEDIATE=1 uv run python manage.py testDie App nutzt yt-dlp, um Metadaten und Untertitel/Auto-Untertitel zu lesen. Videos ohne nutzbares Transkript können derzeit nicht zuverlässig ausgewertet werden.
Wenn YouTube HTTP 429: Too Many Requests meldet, blockt YouTube die aktuelle Abrufrate oder IP temporär. Warte dann etwas und versuche es erneut. Falls das häufiger passiert, kannst du yt-dlp mit einer Cookie-Datei aus deinem Browser starten:
export YT_DLP_COOKIES_FILE=/pfad/zu/cookies.txtIm Docker-Setup muss die Datei zusätzlich in den Container gemountet und der Container mit -e YT_DLP_COOKIES_FILE=/data/cookies.txt gestartet werden.
Im Browser findest du unter Tools ein Bookmarklet. Damit kannst du eine geöffnete YouTube-Seite
direkt an Rezeptinger senden.
So richtest du es ein:
- Starte Rezeptinger lokal und öffne http://127.0.0.1:8000/bookmarklet/.
- Ziehe den Button
An Rezeptinger sendenin die Lesezeichenleiste deines Browsers. - Öffne ein YouTube-Video.
- Klicke in der Lesezeichenleiste auf
An Rezeptinger senden.
Rezeptinger übernimmt die aktuelle YouTube-URL, startet die Extraktion und öffnet die Quell-Detailseite. Das Bookmarklet akzeptiert nur YouTube-URLs.
Extraktion starten:
curl -X POST http://127.0.0.1:8000/api/extractions/ \
-H 'Content-Type: application/json' \
-d '{"url":"https://www.youtube.com/watch?v=..."}'Die Antwort enthält status_url, die du pollen kannst:
curl http://127.0.0.1:8000/api/extractions/1/Sobald status den Wert done hat, ist im Feld recipe das extrahierte Rezept enthalten.
Im Browser findest du die Funktionen unter Daten.
Headless Export:
curl http://127.0.0.1:8000/data/export/ -o rezeptinger.jsonHeadless Import:
curl -X POST http://127.0.0.1:8000/data/import/ \
-H 'Content-Type: application/json' \
--data-binary @rezeptinger.json