Metadata-Version: 2.4
Name: pdf2notes
Version: 1.0.1
Summary: Turn a PDF page range into AI-generated study notes (Markdown) for Obsidian/Notion
Author: Sandeep Singh
License: MIT
Project-URL: Homepage, https://github.com/SandeepSinghSethi/pdf2notes
Keywords: pdf,notes,obsidian,notion,study,ai,llm
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Environment :: Console
Classifier: Intended Audience :: Education
Classifier: Topic :: Text Processing :: Markup :: Markdown
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pdfplumber>=0.11.0
Requires-Dist: openai>=1.0.0
Dynamic: license-file

# pdf2notes

Turn a page range of a PDF book into descriptive, review-ready study notes
(Markdown) — ready to drop into **Obsidian** or import into **Notion**.

Works with any **OpenAI-compatible** chat completion API: NVIDIA NIM
(`integrate.api.nvidia.com`), OpenAI, Groq, Together AI, OpenRouter, or a
local server (Ollama, vLLM, LM Studio). You bring your own API key.

## Setup

```bash
pip install -r requirements.txt
```

## Get an API key (NVIDIA NIM example — free tier available)

1. Go to https://build.nvidia.com
2. Sign in, pick a model (e.g. `meta/llama-3.1-70b-instruct`), click "Get API Key"
3. Export it:
   ```bash
   export NVIDIA_API_KEY="nvapi-xxxxxxxxxxxxxxxx"
   ```

Any other OpenAI-compatible provider works the same way — just pass
`--base-url` and `--api-key-env` for that provider.

## Usage

```bash
python pdf2notes.py --pdf book.pdf --pages 120-180 --api-key-env NVIDIA_API_KEY
```

This writes `book_notes_120-180.md` in the current folder. Requests run
concurrently and are throttled to a requests-per-minute cap, so a 100-page
range finishes in a couple of minutes instead of an hour.

If the run is interrupted, hits a persistent error, or you just Ctrl-C it,
**rerun the exact same command** — chunks already saved in the output file
are detected and skipped, so you only pay for/wait on what's missing.

### Common options

| Flag | Meaning | Default |
|---|---|---|
| `--pdf` | Path to the source PDF | required |
| `--pages` | Page range, e.g. `120-180` (1-indexed, inclusive) | required |
| `--output` | Output `.md` path | `<pdf-name>_notes_<range>.md` |
| `--style` | `descriptive`, `cornell`, `qa`, or `outline` | `descriptive` |
| `--chunk-chars` | Max source characters sent per API call | `6000` |
| `--concurrency` | Max simultaneous API requests | `5` |
| `--rpm` | Max API requests per minute (across all workers) | `40` |
| `--model` | Model name as your provider expects it | `nvidia/llama-3.3-nemotron-super-49b-v1.5` |
| `--base-url` | OpenAI-compatible API base URL | NVIDIA NIM endpoint |
| `--api-key-env` | Env var holding your key | `NVIDIA_API_KEY` |

**Tune `--rpm` to your actual plan.** NVIDIA's free tier, OpenAI's free/low
tiers, etc. all cap requests per minute — check your provider's dashboard
and set `--rpm` a bit under that. Setting it too high just means the tool
eats a 429 and backs off; setting `--concurrency` too high does the same
without helping speed once you're rpm-bound.

**Model IDs get retired.** NVIDIA (and other providers) periodically pull
old model IDs from the catalog — you'll get an HTTP 404/410 if that
happens. The tool detects this as non-retryable and fails immediately with
the error instead of burning your rate-limit budget retrying a request
that will never succeed. Check https://build.nvidia.com/models for current
IDs and pass the right one via `--model`.

### Examples

Using OpenAI instead of NVIDIA:
```bash
export OPENAI_API_KEY="sk-..."
python pdf2notes.py --pdf book.pdf --pages 1-50 \
  --base-url https://api.openai.com/v1 \
  --model gpt-4o-mini \
  --api-key-env OPENAI_API_KEY
```

Q&A flashcard-style notes instead of descriptive prose:
```bash
python pdf2notes.py --pdf book.pdf --pages 200-260 --style qa
```

## Notes on how it works

1. Extracts text from the given page range with `pdfplumber`.
2. Groups pages into chunks (default ~6000 chars each) so each API call
   covers a coherent span without blowing past context limits.
3. Sends each chunk to the model with a prompt tuned to produce **learnable
   notes**, not a shortened summary — definitions, examples, and important
   details are preserved and explained.
4. Stitches everything into one Markdown file with YAML frontmatter
   (title, source, page range, style, timestamp) so it's ready to file
   straight into Obsidian, or import into Notion via *Import → Markdown*.

## Limitations

- **Scanned/image-only PDFs** won't extract text — OCR the PDF first (e.g.
  with `pytesseract` + `pdf2image`), then run this tool on the OCR'd version.
- Very dense chunking settings (`--chunk-chars` too high) can exceed your
  model's context window — 6000–8000 is a safe default for most models.
- Quality depends on the model you point it at — bigger instruction-tuned
  models produce noticeably better notes than small ones.
- **Memory**: page text is extracted one page at a time and each page's
  internal pdfplumber cache (fonts, layout objects) is flushed immediately,
  so memory stays roughly proportional to a chunk's text, not the whole
  page range. For genuinely huge ranges (500+ pages in one run), consider
  splitting into a couple of `--pages` calls instead of one giant one.
- **A crash/segfault message *after* "Done."**: if you see a SIGSEGV or
  similar right as the process exits but *after* your notes file is
  already complete and readable, it's happening during Python interpreter
  teardown, not during note generation — a known quirk of some PDF C
  extensions (e.g. pypdfium2, used internally by pdfplumber) not tearing
  down cleanly at exit. It doesn't affect your output file. If you want to
  suppress the noise, run with `python pdf2notes.py ... ; true` in a
  script, or ignore the exit code.
