| name | obsidian-vault-add |
| description | Add a new book, podcast, TV show, movie, person, country, or city to the Obsidian Zettelkasten vault. Creates the resource note from a template, fills in metadata from the web, downloads cover art, updates today's daily note (media only), and re-indexes qmd. Use when the user says "add book", "add podcast", "add show", "add movie", "add person", "add country", "add city", or wants to log media, people, or places in their vault. |
| allowed-tools | Bash(obsidian*), Bash(python3*), Bash(qmd*), Bash(ls*), Bash(wc*), Bash(mkdir*), WebSearch, Read, Edit, Write |
obsidian-vault-add
Add a new resource (book, podcast, TV show, movie, person, country, or city) to the vault: create the note, download a photo/cover, update today's daily note (media only), and re-index.
Argument parsing
Arguments: <type> <title> [extra info]
type (required): book, podcast, show (or tv), movie, person, country (or land), city (or stadt)
title (required): the resource title
extra info (optional): author, host, episode number, season/episode, country name (for cities), etc.
Examples:
/obsidian-vault-add book "Atlas Shrugged"
/obsidian-vault-add podcast "Aethervox Ehrenfeld"
/obsidian-vault-add show "Silicon Valley" 1.1
/obsidian-vault-add movie "Dune"
/obsidian-vault-add person "Thomas Sowell"
/obsidian-vault-add country "Japan"
/obsidian-vault-add city "Kyoto"
If type or title is missing, ask the user before proceeding.
Step-by-step workflow
1. Web search for metadata
Search for the title to gather:
Book: title, subtitle, authors, published year, genres, pages, ISBN, short description (German preferred)
Podcast: full name, host(s), language, genres, start year, description (German preferred)
TV show: full name, actors, genres, network, seasons, episodes, IMDb rating, description (German preferred)
Movie: full name, director(s), actors, genres, year, IMDb rating, description (German preferred)
Person: full name, aliases, nationality, birthday (and death if applicable), domain/occupation, Wikipedia URL, notable works (books/articles/videos), short German biography (2–4 sentences)
Country: official name, capital, continent, official language(s), currency, aliases (native name, abbreviations), short description (German preferred)
City: full name, country, region/state, language(s), aliases (native name), short description (German preferred)
2. Derive filename
Colons are not allowed in filenames. If the title contains a colon (e.g. "Book: Subtitle"):
- Replace the colon (and any surrounding spaces) with
- → filename becomes Book - Subtitle.md
- Add the original title with the colon as the first alias in the frontmatter
aliases list
3. Check if note already exists
obsidian read file="<derived-filename>" 2>/dev/null | grep -Ev "^(20[0-9]{2}-|Your Obsidian)"
If the note already exists, inform the user and stop (unless they want to update it).
4. Create note from template
| Type | Path | Template |
|---|
| book | 03 Resources/Bücher/<title>.md | Book Template |
| podcast | 03 Resources/Podcasts/<title>.md | Podcast Template |
| show | 03 Resources/Serien/<title>.md | TV Show Template |
| movie | 03 Resources/Filme/<title>.md | Movie Template |
| person | 03 Resources/Personen/<title>.md | Person Template |
| country | 03 Resources/Länder/<title>.md | Country |
| city | 03 Resources/Städte/<title>.md | City |
obsidian create path="<path>" template="<Template>" 2>/dev/null | grep -Ev "^(20[0-9]{2}-|Your Obsidian)"
5. Fill in frontmatter
Edit the note file directly (Read then Edit) — do NOT use obsidian property:set for list fields as it does not reliably handle arrays. Set all fields in one Edit call by replacing the entire frontmatter block.
IMPORTANT — preserve existing data: When updating an existing note, never discard data already present. Merge new metadata with what's there:
- Keep all existing
aliases and append any new ones not already listed
- Keep all existing
tags (only remove BOAT if present)
- Keep any fields already filled in; only add/update empty fields
- Keep all existing body content (sections, lists, notes)
Book frontmatter fields: aliases, tags: [Buch], photo, urls, published (YYYY-MM-DD), authors (list), genres (list), rating, pages
Podcast frontmatter fields: aliases, tags: [Podcast], photo, urls, hosts (list), guests, genres (list), language, rating, status, started, ended, episodes
TV show frontmatter fields: aliases, tags: [Serie], photo, urls, actors (list), genres (list), rating, imdb_rating, network, seasons, episodes
Movie frontmatter fields: aliases, tags: [Film], photo, urls, directors (list), actors (list), genres (list), rating, imdb_rating, year
Country frontmatter fields: aliases, tags: [Land], photo, urls, capital, continent, language (list), currency, visited, rating, cities (list of wikilinks to known city notes)
Person frontmatter fields: aliases (other names, pen names, maiden names), tags (Mann/Frau, nationality country, domain tags like Schriftsteller/Philosoph/etc.), photo, urls, related, birthday (YYYY-MM-DD), death (YYYY-MM-DD or empty), location, quotes (list of wikilinks), books (list of wikilinks to vault book notes), articles, podcasts, videos
City frontmatter fields: aliases, tags: [Stadt], photo, urls, country (wikilink), region, language (list), visited, rating
YAML quoting rule: Any frontmatter value that contains a colon must be wrapped in double quotes — including titles, descriptions, aliases, and all string fields. Example: title: "Book: Subtitle", aliases: ["Book: Subtitle"].
Remove the BOAT tag from tags — notes with links are not BOAT.
6. Add description to body
After the frontmatter, add a short German-language description paragraph (2–4 sentences). Keep any template section headers (## Zusammenfassung, ## Kernaussagen, ## Lieblingsfolgen, etc.).
Person body structure: short German bio paragraph, then ### Fakten (bullet list of key facts), then ### Ideen (empty, for future notes).
The image embed ![[filename.webp]] will be added after cover download (step 6) — do not add it manually.
7. Download cover art
Use inline Python (not the download scripts, which require manual list editing). Use Pillow if available for WebP output, otherwise JPEG.
Book — Open Library by title search:
import json, urllib.request, urllib.parse, io, os
query = "<title>"
url = f"https://openlibrary.org/search.json?title={urllib.parse.quote(query)}&limit=3"
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 VaultBot/1.0"})
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read())
docs = data.get("docs", [])
cover_id = next((d["cover_i"] for d in docs if d.get("cover_i")), None)
if cover_id:
img_url = f"https://covers.openlibrary.org/b/id/{cover_id}-L.jpg"
Output path: 99 Meta/assets/books/<title> (<first author>).webp
Podcast — iTunes Search API:
import json, urllib.request, urllib.parse
query = "<podcast name>"
params = urllib.parse.urlencode({"term": query, "entity": "podcast", "limit": 5})
url = f"https://itunes.apple.com/search?{params}"
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 VaultBot/1.0"})
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read())
artwork = data["results"][0].get("artworkUrl600") if data["results"] else None
Output path: 99 Meta/assets/podcasts/<title>.webp
TV show — TVMaze API:
import json, urllib.request, urllib.parse
query = "<show title>"
url = f"https://api.tvmaze.com/singlesearch/shows?q={urllib.parse.quote(query)}&embed=images"
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 VaultBot/1.0"})
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read())
image_url = data.get("image", {}).get("original") or data.get("image", {}).get("medium")
Output path: 99 Meta/assets/series/<title>.webp
Movie — Wikipedia REST API (search for poster):
import json, urllib.request, urllib.parse
title = "<movie title>"
search_url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{urllib.parse.quote(title)}"
req = urllib.request.Request(search_url, headers={"User-Agent": "Mozilla/5.0 VaultBot/1.0"})
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read())
image_url = data.get("originalimage", {}).get("source") or data.get("thumbnail", {}).get("source")
Output path: 99 Meta/assets/movies/<title>.webp
Country — Wikipedia REST API (flag or representative image):
import json, urllib.request, urllib.parse
title = "<country name>"
search_url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{urllib.parse.quote(title)}"
req = urllib.request.Request(search_url, headers={"User-Agent": "Mozilla/5.0 VaultBot/1.0"})
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read())
image_url = data.get("originalimage", {}).get("source") or data.get("thumbnail", {}).get("source")
Output path: 99 Meta/assets/countries/<title>.webp
Person — Wikipedia REST API, then resize to max 800px on longest side:
import json, urllib.request, urllib.parse
name = "<person name>"
search_url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{urllib.parse.quote(name)}"
req = urllib.request.Request(search_url, headers={"User-Agent": "Mozilla/5.0 VaultBot/1.0"})
with urllib.request.urlopen(req, timeout=15) as resp:
data = json.loads(resp.read())
image_url = data.get("originalimage", {}).get("source") or data.get("thumbnail", {}).get("source")
Output path: 99 Meta/assets/people/<title>.webp
City — Wikipedia REST API:
import json, urllib.request, urllib.parse
title = "<city name>"
search_url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{urllib.parse.quote(title)}"
req = urllib.request.Request(search_url, headers={"User-Agent": "Mozilla/5.0 VaultBot/1.0"})
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read())
image_url = data.get("originalimage", {}).get("source") or data.get("thumbnail", {}).get("source")
Output path: 99 Meta/assets/cities/<title>.webp
After successful download:
- Set
photo: <filename> in frontmatter (Edit the file)
- Add
![[<filename>]] to the note body using Edit (insert before the description paragraph) — do NOT use obsidian prepend, as it escapes the ! and produces \![[...]]
If cover download fails, note it to the user and continue without a photo.
Also add the new entry to the appropriate download script so future re-runs include it:
| Type | Script |
|---|
| book | 99 Meta/scripts/download-book-covers.py |
| podcast | 99 Meta/scripts/download-podcast-covers.py |
| show | 99 Meta/scripts/download-series-covers.py |
| movie | 99 Meta/scripts/download-movie-covers.py |
Countries, cities, and persons have no dedicated download script — skip this step for those types.
8. Update today's daily note
Skip this step for person, country, and city — they are reference notes, not media being consumed.
For media types, append to today's daily note using the correct emoji and wikilink format:
| Type | Format |
|---|
| book | 📚 [[<title>]] angefangen zu lesen (0 %) |
| podcast | 🎙️ [[<title>]]: <episode/topic if known> |
| show | 📺 [[<title>]] <season>.<episode> |
| movie | 🎬 [[<title>]] |
obsidian daily:append content="<entry>" 2>/dev/null | grep -Ev "^(20[0-9]{2}-|Your Obsidian)"
If the user provided season/episode or episode title in the arguments, use it; otherwise use a sensible placeholder or ask.
9. Re-index qmd
qmd update && qmd embed
Run this after the note is created and finalized so it's immediately searchable.
10. Report to user
Summarize what was done:
- Note path created
- Metadata filled (list key fields)
- Cover art status (saved path or skip reason)
- Daily note entry added
- qmd indexed
Error handling
- If note creation fails, check if the path already exists
- If cover download returns a tiny image (< 5 KB), skip it and warn the user
- If
obsidian daily:append fails, tell the user the entry to add manually
- If
qmd embed takes too long, run it in the background with run_in_background: true