Metadata-Version: 2.4
Name: martino-summarize
Version: 0.3.2
Summary: Local-first multi-source video summarization with any OpenAI-compatible LLM
Author: Martino Piaggi
License: MIT
Project-URL: Homepage, https://github.com/martinopiaggi/summarize
Project-URL: Documentation, https://summarize.martino.im
Project-URL: Repository, https://github.com/martinopiaggi/summarize
Project-URL: Issues, https://github.com/martinopiaggi/summarize/issues
Project-URL: Changelog, https://github.com/martinopiaggi/summarize/blob/main/CHANGELOG.md
Keywords: video,summarization,youtube,transcription,llm,notebooklm
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Video
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: youtube-transcript-api>=1.2.3
Requires-Dist: pytubefix>=1.6.3
Requires-Dist: groq>=0.4.2
Requires-Dist: openai>=1.3.7
Requires-Dist: yt-dlp>=2026.8.19
Requires-Dist: aiohttp>=3.9.0
Requires-Dist: requests>=2.31.0
Requires-Dist: wget>=3.2
Requires-Dist: google-api-python-client>=2.0.0
Requires-Dist: google-auth-httplib2>=0.1.0
Requires-Dist: google-auth-oauthlib>=0.4.1
Requires-Dist: dropbox>=11.36.2
Requires-Dist: ffmpeg-python>=0.2.0
Requires-Dist: pyyaml>=6.0
Provides-Extra: whisper
Requires-Dist: openai-whisper; extra == "whisper"
Provides-Extra: litellm
Requires-Dist: litellm<1.87,>=1.80.0; extra == "litellm"
Provides-Extra: server
Requires-Dist: fastapi>=0.100.0; extra == "server"
Requires-Dist: uvicorn[standard]>=0.23.0; extra == "server"
Requires-Dist: python-multipart>=0.0.6; extra == "server"
Provides-Extra: all
Requires-Dist: openai-whisper; extra == "all"
Requires-Dist: litellm<1.87,>=1.80.0; extra == "all"
Requires-Dist: fastapi>=0.100.0; extra == "all"
Requires-Dist: uvicorn[standard]>=0.23.0; extra == "all"
Requires-Dist: python-multipart>=0.0.6; extra == "all"
Dynamic: license-file

# Video Summarizer

<p align="center">
    <img alt="Video summarizer demo" src="./summarize_sample.gif">
</p>

> Turn any video — a lecture, TikTok, or Drive recording — into distilled markdown: Q&A, fact-checks, tutorials, Mermaid diagrams, essays, and more. 

- **11+ sources**: Local-first summarization for YouTube, Instagram, TikTok, X, Reddit, Facebook, Drive, Dropbox, local files
- **Bring your own API keys**: Works with any OpenAI-compatible LLM, Perplexity models, LiteLLM
- **CLI · Streamlit · HTTP API · Docker · Raycast · Agent skill**
- **Transcript cache** + optional JEV prefiltering + optional Cobalt sidecar for yt-dlp fallbacks + optional vision mode
- **Documentation**: https://summarize.martino.im
- **Background**: [more on this project](https://martino.im/Summarize.html)

## Quick Start

Requires Python 3.7+, **ffmpeg** on `PATH`, and an OpenAI-compatible API key in `.env`. 
Recommended: install with `pipx` for an isolated environment.

[Groq](https://groq.com/) (`GROQ_API_KEY`) offers a free tier; `OPENAI_API_KEY` works with `--provider openai`.

```bash
pipx install martino-summarize
summarizer --init-config
echo "GROQ_API_KEY=your_key_here" > .env
summarizer --source "https://www.youtube.com/watch?v=arj7oStGLkU"
```

Output: `summaries/watch_YYYYMMDD_HHMMSS.md`. 

Configuration lives in `summarizer.yaml` and `.env`. 

Prefer Docker? 

```bash
git clone https://github.com/martinopiaggi/summarize.git && cd summarize
cp summarizer.docker.yaml summarizer.yaml
echo "GROQ_API_KEY=your_key_here" > .env
docker compose up -d    # → http://localhost:8501
```

Or pull the pre-built image: `docker pull ghcr.io/martinopiaggi/summarize:latest`. 

## Optional JEV prefilter

Full guide: [JEV Prefiltering](https://summarize.martino.im/features/jev-prefiltering).

Check **Use JEV prefiltering** to reveal two optional fields:

- **I want to include only…** — e.g. `a particular concept about this video to filter`. Selects that subject even when it is not the main topic. Blank means general relevance to the video.
- **I want to exclude…** — e.g. `Sponsorship and self-promotion`. Removes matching passages. Blank means no additional exclusions. Exclusions win when a passage matches both fields.

JEV selects original passages **before** the existing LLM request. It does not rewrite the transcript, change the summary prompt/model, or modify the cached transcript. The selected passages stay in source order. With JEV off, the existing pipeline is unchanged.

```yaml
defaults:
  use-jev-prefiltering: true
  jev-provider: openrouter
  jev-include: "a particular concept about this video to filter"
  jev-exclude: "Sponsorship and self-promotion"
  jev-keep-ratio: 0.35
```

Uses the existing OpenRouter (default) or TypeSafe provider's API key, with a JEV model instead of its chat model. [`/systemone`](https://openrouter.ai/docs/guides/community/typesafe-sdk) is the structured scoring endpoint, not a system prompt. No separate provider entry is needed.

CLI: `--use-jev-prefiltering --jev-include "X" --jev-exclude "Sponsorship"`. YAML accepts `jev-include` / `jev-exclude`; HTTP single/batch/upload requests accept `jev_include` / `jev_exclude`. Empty strings clear configured rules.

**Compression and limits:**
- Bounded batched scoring requests per eligible chunk (more than one for large chunks), with existing concurrency limits and no retries. The summary chunk size is unchanged. Independent inclusion/exclusion scores prevent a high inclusion score from overriding an exclusion.
- The default budget is about **35% of each original chunk**, including when using exclusion only. It is a ceiling, not a quota: irrelevant text never fills unused space. Whole units are retained; one best matching unit can exceed the budget. Set `jev-keep-ratio: 1.0` in YAML to retain all qualifying units instead of ranking down to 35%.
- All-rejected chunks are omitted; if none remain, return a no-match message without calling the LLM.
- Every non-empty text chunk is scored when enabled, including single-unit chunks. On timeout, malformed response, HTTP error or an unsplittable request, explicit rules stop before any summary request rather than sending unfiltered text. With both fields blank, scoring failure warns and falls back to the original chunk.
- Visual mode and grammar correction bypass JEV entirely, with a warning that selection rules do not apply.
- JEV works with any configured summary `chunk-size`: it divides oversized scoring work into bounded requests (28 KB state / 60 KB total JSON each), then ranks all scored units across the original chunk. Short captions may be merged locally to fit; no source text is deleted. Explicit-rule requests are planned before sending any scoring or summary requests. A single unsplittable unit or scoring error stops explicit-rule runs rather than leaking unfiltered text.
- Semantic classification is not guaranteed. Long chunks use roughly 800–1,200-character units; an exclusion in a mixed unit drops the whole unit, potentially losing useful neighboring text. Compare with an unfiltered summary for important material.

Progress reports retained characters, requests, exclusions, fallbacks and elapsed time. Automated tests use mocked scores; live classification quality, cost and latency are not benchmarked.

## Contributing

```bash
git clone https://github.com/martinopiaggi/summarize.git && cd summarize
pip install -e ".[all]" pytest && pytest tests/
```

See [CONTRIBUTING.md](CONTRIBUTING.md). License: [MIT](LICENSE).
