Extract structured recipes from YouTube cooking videos with Claude, stored in local SQLite.
Project description
Mise En Place (mep)
A personal CLI that turns YouTube cooking videos into a searchable recipe database. It pulls a video's transcript, extracts a structured recipe with Claude, and stores it in local SQLite. Everything stays on your machine.
Install
git clone https://github.com/AveryClapp/MiseEnPlace.git mep && cd mep
python -m venv .venv && source .venv/bin/activate
pip install -e .
Or simply
pip install mise-en-place
Setup
mep init
This creates ~/.mep/, prompts for the keys, and builds the database at
~/.mep/mep.db.
- An LLM key (required for extraction): set
ANTHROPIC_API_KEY(https://console.anthropic.com/ → API Keys) orOPENAI_API_KEY. For OpenAI, also install the extra:pip install 'mise-en-place[openai]'. - YouTube Data API v3 key (only needed for
--channelingestion): see below.
Provider selection: if you set only one of the two LLM keys, that provider
is used automatically. If you set both, it defaults to Anthropic; set
LLM_PROVIDER=openai (or choose it at the mep init prompt) to pick OpenAI.
An explicit LLM_PROVIDER always wins.
Keys are stored in ~/.mep/config.json. You can also set ANTHROPIC_API_KEY,
OPENAI_API_KEY, LLM_PROVIDER, or YOUTUBE_API_KEY as environment variables,
which override the config file. EXTRACTION_MODEL overrides the default model
for the chosen provider.
Getting a YouTube Data API v3 key
- Go to the Google Cloud Console.
- Create a project (top bar → project dropdown → New Project).
- In the search bar, open YouTube Data API v3 and click Enable.
- Go to APIs & Services → Credentials → Create Credentials → API key.
- Copy the key. (Optional: Edit API key → Restrict key → YouTube Data API v3.)
Single-video adds use YouTube's public oEmbed endpoint and do not need this key. It is only required to walk a channel's uploads.
Usage
mep add https://www.youtube.com/watch?v=VIDEO_ID # one video
mep add --channel @JKenjiLopezAlt --limit 10 # latest 10 from a channel
mep add --channel @JKenjiLopezAlt # whole channel
mep search "garlic confit" # full-text search
mep list # browse, newest first
mep list --tag italian --limit 20 # filter by tag
mep show 42 # full recipe
mep show 42 --servings 8 # scale ingredient amounts
mep show 42 --macros # estimated nutrition breakdown
mep set-servings 42 4 # record how many it makes
mep plan 42 # AI cooking timeline (experimental)
mep plan 42 --servings 8 # ...scaled to 8 servings
mep cook 42 # step-by-step walkthrough (experimental)
mep show 42 --parts # what each ingredient is for
mep adapt 42 # rewrite around what you have (interactive)
mep adapt 42 --have pita --sub "yogurt=sour cream" # ...or state it directly
mep cook 42 --have pita # adapt just for this cook
plan makes one Claude call to reorder a recipe's steps into an efficient
timeline and caches it. Each step is tagged hands-on or hands-off, with the
ingredients and equipment it uses, a named timer for waits, and a "prep this
during the wait" hint. The summary shows realistic wall-clock time (hands-off
waits run in the background, not added end to end). Re-run plan --regenerate
to rebuild.
cook walks that timeline live: it opens with a mise en place gather + equipment
list, then one step at a time. On a hands-off step, pressing Enter starts a named
background timer that keeps counting while you move on to the next step (like a
real kitchen timer); it rings when done. It also nudges you to preheat the oven a
couple steps ahead. Ctrl-C stops cleanly and reports any timers still running.
--servings N (on show, plan, cook) scales ingredient amounts to N
servings. It is best-effort and display-only: only leading quantities are scaled,
vague amounts like "a handful" pass through untouched, and nothing is saved. A
recipe with no recorded serving count is treated as a single serving (the batch
as written), so --servings 3 simply makes 3× the recipe. Use mep set-servings <id> <count> to record the real serving count when you know it, so scaling maps
to people instead.
Both plan and cook are experimental: the timings are AI estimates.
show --macros shows an estimated nutrition breakdown (calories, protein, carbs,
fat) for the whole recipe and per serving. It's computed lazily on first request
(one model call, then cached, so it's free afterward and costs nothing if you
never ask) and is a rough estimate from the ingredients, not exact.
show --parts breaks a recipe into its components (marinade, pita, sauce…) so
you can see what each ingredient is for. adapt rewrites the recipe around what
you already have: pick the parts you bought or made ahead and it drops the steps
and ingredients needed only to make those (keeping the steps that use them), and
applies any ingredient swaps. It then offers to save the result as a new copy,
overwrite the original, or discard it. cook --have/--sub does the same rewrite
in memory for a single cook without saving anything. These are experimental and
use Claude; the rewrite is intentionally light (it shifts and trims the recipe,
it doesn't reinvent it).
Channel ingestion is idempotent: videos already stored are skipped, so you can re-run it to pick up only what's new. Non-recipe videos and videos without transcripts are stored as empty entries (not errors) so they aren't re-fetched.
Cost per video empirically is around $0.05, maybe more depending on length.
Channels to try
Recipe-forward channels that work well (most videos are real walkthroughs with
transcripts). Single-video adds need no key; the --channel walk needs a YouTube
Data API key (see above).
| Channel | Handle |
|---|---|
| Babish Culinary Universe | @babishculinaryuniverse |
| J. Kenji López-Alt | @JKenjiLopezAlt |
| Joshua Weissman | @joshuaweissman |
| Adam Ragusea | @aragusea |
| Ethan Chlebowski | @EthanChlebowski |
| Brian Lagerstrom | @brianlagerstrom |
| Food Wishes (Chef John) | @foodwishes |
mep add https://www.youtube.com/watch?v=iErqWGwso7o # a single Babish video, no key needed
mep add --channel @aragusea --limit 5 # latest 5 from Adam Ragusea
How it works
url → transcript (youtube-transcript-api) → an LLM (Claude or OpenAI) → JSON → SQLite. Search uses SQLite FTS5 over dish name, ingredients, and channel. Vague
quantities like "a handful" are stored verbatim — nothing is normalized.
Develop
pip install -e '.[dev]'
pytest
The test suite is fully offline (no network, no API keys).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mise_en_place-0.2.1.tar.gz.
File metadata
- Download URL: mise_en_place-0.2.1.tar.gz
- Upload date:
- Size: 40.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b633e41487081b32f3e56cccd3920a96988e6ccaf790ae4992d5ee84a052617d
|
|
| MD5 |
86028ff4f247d7abf88b8ef2509d786e
|
|
| BLAKE2b-256 |
0376336a70792ef2c8a81b2b946987d10535533494dc4d5d434e5ca5c6bb0040
|
Provenance
The following attestation bundles were made for mise_en_place-0.2.1.tar.gz:
Publisher:
release.yml on AveryClapp/MiseEnPlace
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mise_en_place-0.2.1.tar.gz -
Subject digest:
b633e41487081b32f3e56cccd3920a96988e6ccaf790ae4992d5ee84a052617d - Sigstore transparency entry: 1713443938
- Sigstore integration time:
-
Permalink:
AveryClapp/MiseEnPlace@7c99fdd12ed60484e159e60c4183ae8068d02a8b -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/AveryClapp
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7c99fdd12ed60484e159e60c4183ae8068d02a8b -
Trigger Event:
push
-
Statement type:
File details
Details for the file mise_en_place-0.2.1-py3-none-any.whl.
File metadata
- Download URL: mise_en_place-0.2.1-py3-none-any.whl
- Upload date:
- Size: 38.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e1edcc507a87f33dabafec601189e1c5d71b1e5ad0b21e14ec728d43e605485
|
|
| MD5 |
071ec0feb79f12afc52fc6154822af66
|
|
| BLAKE2b-256 |
679f689651fe1442fef6a3d813373c5334e57e84bd880d2f979efa88fb457729
|
Provenance
The following attestation bundles were made for mise_en_place-0.2.1-py3-none-any.whl:
Publisher:
release.yml on AveryClapp/MiseEnPlace
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mise_en_place-0.2.1-py3-none-any.whl -
Subject digest:
1e1edcc507a87f33dabafec601189e1c5d71b1e5ad0b21e14ec728d43e605485 - Sigstore transparency entry: 1713444038
- Sigstore integration time:
-
Permalink:
AveryClapp/MiseEnPlace@7c99fdd12ed60484e159e60c4183ae8068d02a8b -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/AveryClapp
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7c99fdd12ed60484e159e60c4183ae8068d02a8b -
Trigger Event:
push
-
Statement type: