# CLI Pipeline Architecture: Article/YouTube Summarizer ## Context Building a Python CLI tool that processes URLs (articles or YouTube videos) through a pipeline of nodes. Each node extracts/transforms data, saves to a temp file, then passes context to the next node. --- ## Architecture Overview ### Pipeline Flow ``` ┌─────────┐ │ URL │ ──→ validate & detect type (regex) └─────────┘ ↓ ┌───────────────────────────┐ │ Type Router │ │ ├─ YouTube URL → yt-dlp │ │ └─ Article URL → fetch │ └───────────────────────────┘ ↓ ┌─────────────────────────────┐ │ Node 1: Metadata Extractor │ │ (yt-dlp metadata + content)│ │ ↓ saves to temp file │ └─────────────────────────────┘ ↓ ┌────────────────────────┐ │ Node 2: Summarizer │ │ (Ollama: summary) │ │ ↓ reads temp file │ │ ↓ saves to temp file │ └────────────────────────┘ ↓ ┌───────────────────────┐ │ Node 3: Mindmap │ │ (Ollama: mindmap) │ │ ↓ reads temp file │ │ ↓ saves to temp file │ └───────────────────────┘ ↓ ┌────────────────────────┐ │ Node 4: TTS Generator │ │ (Ollama: tts) │ │ ↓ reads temp file │ │ ↓ saves to temp file │ └────────────────────────┘ ↓ ┌────────────────────────────┐ │ Node 5: Markdown Renderer │ │ (assembles all outputs) │ └────────────────────────────┘ ``` --- ## Node Details ### Node 1: Metadata Extractor **Input:** YouTube URL (extracted via regex) **Tools:** `yt-dlp` metadata API **Output files:** - `temp_meta.json` - metadata (title, author, description, duration) - `temp_content.txt` - full transcript/text content **Error handling:** Stop if yt-dlp fails (invalid URL, private video, etc.) ### Node 2: Summarizer **Input:** `temp_meta.json` + `temp_content.txt` **Tools:** API call **Output files:** - `temp_summary.txt` - brief summary paragraph ```python import requests def ask_gemma4(prompt: str, model: str = "gemma4") -> str: response = requests.post( "http://localhost:11434/api/generate", json={ "model": model, "prompt": prompt, "stream": False } ) response.raise_for_status() return response.json()["response"] result = ask_gemma4("Summarize") print(result) ``` ### Node 3: Mindmap Creator **Input:** `temp_summary.txt` + `temp_content.txt` **Tools:** Ollama API call **Output files:** - `temp_mindmap.txt` - structured mindmap (markdown hierarchy) ```python import requests def ask_gemma4(prompt: str, model: str = "gemma4") -> str: response = requests.post( "http://localhost:11434/api/generate", json={ "model": model, "prompt": prompt, "stream": False } ) response.raise_for_status() return response.json()["response"] result = ask_gemma4("Generate a mindmap") print(result) ``` ### Node 4: TTS Generator **Input:** `temp_summary.txt` (or full content if preferred) **Tools:** OpenAI API call **Output files:** - `temp_audio.wav` or `.mp3` - audio file ```bash openai_audio_speech() { # $1: request body # $2: output file curl -s --fail-with-body \ -X POST "https://api.openai.com/v1/audio/speech" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -d "$1" \ --output "$2" local exit=$? if [ $exit -ne 0 ]; then error_message "An error occured while processing the request" return 1 fi return 0 } # needs to be converted to Python requests call ``` ### Node 5: Markdown Renderer **Input:** All temp files **Output:** Single markdown file with: - Title, author, duration (if YouTube) - Summary section - Mindmap section - Audio player link (local path) --- ## File Structure ``` summarize/ ├── summarize.py # Main CLI entry point ├── nodes/ # Node implementations │ ├── __init__.py │ ├── base.py # Abstract base class for nodes │ ├── router.py # URL type detection & routing │ ├── metadata.py # yt-dlp extraction │ ├── summarizer.py # Ollama summary │ ├── mindmap.py # Ollama mindmap │ ├── tts.py # Ollama TTS │ └── renderer.py # Markdown assembly ├── temp/ # Temp directory for intermediate files └── requirements.txt # Dependencies ``` --- ## Key Design Patterns ### 1. Temp File Pipeline Each node: 1. Reads from input temp files 2. Processes data 3. Writes to output temp files 4. Passes file paths to next node ### 2. Abstract Base Class ```python class Node(ABC): @abstractmethod def execute(self, temp_dir: str) -> dict[str, str]: """Execute node and return list of output file paths""" ``` ### 3. URL Detection ```python # Auto-detect YouTube URLs using regex YOUTUBE_PATTERNS = [ r'https?://www\.youtube\.com/watch\?v=\S+', r'https?://youtu\.be/\S+', r'https?://m\.youtube\.com/watch\?v=\S+', ] ``` ### 4. Error Propagation - Each node catches its own exceptions - Raises `PipelineError` on failure - Main loop stops on first exception - Prints error message and exits with code 1 --- ## Dependencies ```text yt-dlp>=2024.4.0 requests>=2.28.0 ``` --- ## Verification Plan 1. Test with YouTube URL: ```bash python summarize.py https://www.youtube.com/watch?v=dQw4w9WgXcQ ``` 2. Test with article URL: ```bash python summarize.py https://blog.annimon.com/tui-tiki/ ``` 3. Verify temp files created in `./temp/` 4. Verify markdown output contains all sections --- ## Critical Files - `summarize.py` - CLI entry point - `nodes/base.py` - Node abstraction - `nodes/router.py` - URL detection - `nodes/metadata.py` - yt-dlp extraction - `nodes/summarizer.py` - Ollama API call summary - `nodes/mindmap.py` - Ollama mindmap - `nodes/tts.py` - OpenAI API call TTS - `nodes/renderer.py` - Markdown assembly