summarizer/README.md
Clinton Billedeaux cea352b576
Add local document summarization and custom prompt mode
Summarize local PDF, Word, and plain text files via a new DocumentService
(PdfPig + DocumentFormat.OpenXml), and add a Custom summary mode that uses
the user's own instructions as the system prompt. Update the console UI,
transcript saving, and README to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 10:56:57 -05:00

6 KiB
Raw Permalink Blame History

YouTube & Document Summarizer

A .NET 10 console app that summarizes YouTube videos and local documents — text, Markdown, CSV, PDF, Word, OpenDocument, and Excel — using an LLM (OpenAI or a local Ollama model). One prompt accepts either a YouTube URL or a file path; everything else (summarize, save, display) runs through the same pipeline.


Prerequisites

  • .NET 10 SDK

  • yt-dlp on your PATH — used to fetch YouTube metadata and captions. No YouTube API key needed.

    pip install yt-dlp
    
  • Local Ollama (recommended, free) or an OpenAI API key


Setup

cd summarize

# Edit appsettings.json with your LLM endpoint/key (see Configuration below)
nano appsettings.json

dotnet restore
dotnet run

Using it

The app loops on a single prompt:

Enter a YouTube URL or a local file path (or 'q' to quit):
  • YouTube: paste any watch?v=..., youtu.be/..., /shorts/..., or /embed/... URL (or a bare 11-character video ID).
  • Local document: paste or type a path to an existing file. Supported types: .txt .md .csv .pdf .docx .odt .xlsx. The kind of input is auto-detected — no need to say which one it is.
  • q quits.

For each input you're asked:

  1. Save transcript to file?y/n, defaults to yes on Enter. Saves the extracted transcript/document text plus the summary to ~/Downloads/transcripts/.
  2. Choose summary mode — an arrow-key list, Standard pre-selected:
    • Standard — detailed bullet-point summary (the default; Enter picks it)
    • Personal Filter — a 12 sentence summary plus an ACT / MONITOR / IGNORE relevance verdict against personal priorities (time, finances, health, family, service to others)
    • Custom — write your own instructions on the spot (multi-line; finish with a blank line). Replaces the built-in prompt entirely, for when you want something other than a summary or a verdict — a table, an extraction task, a specific format, etc.

Ctrl+C cancels the current operation without killing the app. The terminal tab title tracks progress (Summarize while idle, Summarize <title> while working on something).


Configuration Reference

All settings live in appsettings.json (bind to AppSettings in AppSettings.cs):

Key Description Default
LLM:BaseUrl API endpoint — https://api.openai.com/v1 for OpenAI, http://localhost:11434/v1 for Ollama https://api.openai.com/v1
LLM:ApiKey API key (any non-empty value works for Ollama) (empty — required for OpenAI)
LLM:Model Chat model, e.g. gpt-4o-mini (OpenAI) or qwen3:14b (Ollama) gpt-4o-mini
LLM:MaxTokens Max tokens in the summary response 1500
LLM:TimeoutSeconds Max time to wait per API call 100
Summarizer:ChunkWordLimit Word count above which a transcript/document is split into chunks (map-reduce, see below). Must stay comfortably above ~200 — the fixed chunk overlap — or chunking will error on short inputs. 3000
Summarizer:ShowTranscript Print the full extracted text before summarizing false

Any value can be overridden with an environment variable using __ as the section separator (handy for CI/containers):

export LLM__ApiKey="sk-..."
export LLM__Model="gpt-4o"
dotnet run

Architecture

Program.cs
│  Main loop → auto-detects YouTube URL vs. local file path
│
├── YouTubeService            — shells out to yt-dlp
│     ├── ExtractVideoId()      — URL parsing
│     ├── GetVideoMetadataAsync() — video title/channel/date/duration
│     └── GetTranscriptAsync()   — caption download + VTT/SRT/timedtext parsing
│
├── DocumentService            — reads a local file
│     ├── BuildMetadata()        — title/type/modified-date from the file
│     └── ExtractTextAsync()     — dispatches by extension:
│           .txt/.md/.csv → read directly
│           .pdf          → PdfPig
│           .docx/.xlsx   → DocumentFormat.OpenXml
│           .odt          → hand-rolled zip + XML (ODF content.xml)
│
│  Both feed the same VideoMetadata / VideoTranscript shapes into:
│
├── SummarizerService
│     ├── SummarizeAsync()      — routes to single-pass or chunked, per mode
│     │                           (Standard / Personal Filter / Custom)
│     ├── SinglePassSummarize() — one LLM call for short inputs
│     └── ChunkedSummarizeAsync() — map-reduce for long inputs
│
├── TranscriptFileService      — saves transcript + summary to a .txt file
│
└── ConsoleRenderer             — all terminal output (Spectre.Console):
                                  banner, prompts, progress spinners, and
                                  the final summary panel/grid

Source Quality Transparency

The app tracks how the text was obtained and flags it accordingly:

Source Badge Warning shown?
Owner-published captions ✓ Owner-published No
Community-contributed captions ✓ Community captions Minor note
Auto-generated captions (ASR) ~ Auto-generated Yes — accuracy caveat
No captions (metadata only) ✗ Metadata only Yes — limited accuracy
Local document ✓ Local document No

Long-Input Strategy

Transcripts/documents exceeding ChunkWordLimit words use a map-reduce approach — split into overlapping chunks (200-word overlap preserves context at boundaries), each chunk summarized independently, then combined into one coherent result. This applies to every mode, including Custom (the combine step gets an auto-generated wrapper prompt asking the model to merge the partial responses while still honoring your instructions). Handles hour-long videos, long reports, and multi-sheet spreadsheets without hitting model context limits.