Automated YouTube Notes Pipeline for Obsidian
Search YouTube via API, extract metadata, and create structured Obsidian notes with frontmatter automatically.
Watching a YouTube video and manually creating an Obsidian note with the title, channel, description, and your timestamps is tedious. Automate it. Search YouTube via API, extract video metadata, and generate Obsidian-ready Markdown notes with frontmatter. This pipeline runs in under a second per video.
What this pipeline does
- Searches YouTube for videos matching your topic or URL
- Extracts structured metadata: title, channel, publish date, description, thumbnail
- Generates an Obsidian Markdown note with YAML frontmatter
- Creates bidirectional links to related topic notes in your vault
- Saves directly to your vault folder
Be honest: metadata only, not transcripts
This pipeline extracts video metadata from YouTube search results. It does not transcribe the video. For transcripts, you need the YouTube Data API with captions access or a third-party transcription service like AssemblyAI or Whisper. The search API gives you everything visible on the search results page: title, channel, description snippet, publish date, view count, and duration. That is enough for a useful reference note, but not a full content summary.
The core pipeline
import requests, os, re
from datetime import date
H = {'x-api-key': os.environ['SCAVIO_API_KEY']}
URL = 'https://api.scavio.dev/api/v1/search'
def youtube_to_obsidian(query: str, vault_path: str, num_results: int = 5):
"""Search YouTube and create Obsidian notes for each result."""
resp = requests.post(URL, headers=H,
json={'platform': 'youtube', 'query': query, 'num': num_results},
timeout=15)
videos = resp.json().get('organic_results', [])
created = []
for video in videos:
title = video.get('title', 'Untitled')
slug = re.sub(r'[^a-z0-9]+', '-', title.lower()).strip('-')
frontmatter = f"""---
title: "{title}"
channel: "{video.get('channel', {}).get('name', 'Unknown')}"
published: "{video.get('date', 'Unknown')}"
url: "{video.get('link', '')}"
duration: "{video.get('duration', 'Unknown')}"
type: youtube-note
created: {date.today().isoformat()}
tags: [youtube, {query.replace(' ', '-')}]
---"""
body = f"""# {title}
## Source
- Channel: {video.get('channel', {}).get('name', 'Unknown')}
- URL: {video.get('link', '')}
- Duration: {video.get('duration', 'Unknown')}
## Description
{video.get('snippet', 'No description available.')}
## My Notes
-
## Related
- [[{query}]]
"""
filepath = f"{vault_path}/youtube/{slug}.md"
os.makedirs(f"{vault_path}/youtube", exist_ok=True)
with open(filepath, 'w') as f:
f.write(f"{frontmatter}\n\n{body}")
created.append(filepath)
return created
notes = youtube_to_obsidian('fastapi deployment 2026', '/path/to/vault')
print(f"Created {len(notes)} notes")Templater integration
If you use the Obsidian Templater plugin, you can trigger this pipeline from within Obsidian. Create a template that calls a local Python script and inserts the result.
// Obsidian Templater user script: youtube_search.js
// Place in your Templater scripts folder
async function youtube_search(tp) {
const query = await tp.system.prompt("YouTube search query:");
if (!query) return "No query provided.";
// Call local API proxy (Python Flask running on localhost)
const resp = await fetch('http://localhost:5111/youtube-search', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({query: query, num: 3})
});
const data = await resp.json();
let output = "";
for (const video of data.videos) {
output += "## " + video.title + "\n";
output += "- Channel: " + video.channel + "\n";
output += "- URL: " + video.url + "\n";
output += "- Duration: " + video.duration + "\n\n";
}
return output;
}
module.exports = youtube_search;The local API proxy
Templater cannot call external APIs directly with API keys. Run a small Flask server locally that proxies the search request.
from flask import Flask, request, jsonify
import requests, os
app = Flask(__name__)
H = {'x-api-key': os.environ['SCAVIO_API_KEY']}
URL = 'https://api.scavio.dev/api/v1/search'
@app.route('/youtube-search', methods=['POST'])
def youtube_search():
data = request.json
query = data.get('query', '')
num = data.get('num', 5)
resp = requests.post(URL, headers=H,
json={'platform': 'youtube', 'query': query, 'num': num},
timeout=15)
results = resp.json().get('organic_results', [])
videos = [{
'title': r.get('title', ''),
'channel': r.get('channel', {}).get('name', ''),
'url': r.get('link', ''),
'duration': r.get('duration', ''),
'snippet': r.get('snippet', '')
} for r in results]
return jsonify({'videos': videos})
if __name__ == '__main__':
app.run(port=5111)Batch processing a playlist topic
Research a topic thoroughly by searching multiple related queries and creating a note for each unique video found.
import requests, os
H = {'x-api-key': os.environ['SCAVIO_API_KEY']}
URL = 'https://api.scavio.dev/api/v1/search'
def batch_research(topic: str, sub_queries: list[str]) -> list[dict]:
"""Search multiple related queries, deduplicate results."""
seen_urls = set()
unique_videos = []
for q in sub_queries:
resp = requests.post(URL, headers=H,
json={'platform': 'youtube', 'query': f'{topic} {q}'}, timeout=15)
for r in resp.json().get('organic_results', []):
url = r.get('link', '')
if url not in seen_urls:
seen_urls.add(url)
unique_videos.append(r)
return unique_videos
videos = batch_research('kubernetes', ['tutorial beginner', 'production setup', 'debugging'])
print(f"Found {len(videos)} unique videos across 3 sub-queries")Cost
A typical research session might search 10-20 queries. That is 10-20 API credits. At 500 free credits per month, you can run 25-50 research sessions before hitting the limit. For heavy researchers, $30/mo for 7,000 credits covers daily research without thinking about usage. Each credit costs $0.005.