localcaption 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {localcaption-0.3.0 → localcaption-0.4.0}/CHANGELOG.md +35 -0
  2. {localcaption-0.3.0 → localcaption-0.4.0}/CONTRIBUTING.md +4 -1
  3. {localcaption-0.3.0 → localcaption-0.4.0}/PKG-INFO +133 -34
  4. {localcaption-0.3.0 → localcaption-0.4.0}/README.md +130 -33
  5. {localcaption-0.3.0 → localcaption-0.4.0}/docs/diagrams/architecture.mmd +4 -2
  6. {localcaption-0.3.0 → localcaption-0.4.0}/docs/diagrams/pipeline.mmd +1 -1
  7. {localcaption-0.3.0 → localcaption-0.4.0}/docs/diagrams/sequence.mmd +5 -4
  8. {localcaption-0.3.0 → localcaption-0.4.0}/pyproject.toml +4 -1
  9. {localcaption-0.3.0 → localcaption-0.4.0}/scripts/install.sh +2 -2
  10. {localcaption-0.3.0 → localcaption-0.4.0}/scripts/setup.sh +2 -2
  11. localcaption-0.4.0/src/localcaption/backends/__init__.py +1 -0
  12. localcaption-0.4.0/src/localcaption/backends/faster_whisper.py +120 -0
  13. localcaption-0.4.0/src/localcaption/backends/whisper_cpp.py +58 -0
  14. localcaption-0.4.0/src/localcaption/batch.py +254 -0
  15. localcaption-0.4.0/src/localcaption/chapters.py +244 -0
  16. {localcaption-0.3.0 → localcaption-0.4.0}/src/localcaption/cli.py +150 -13
  17. {localcaption-0.3.0 → localcaption-0.4.0}/src/localcaption/download.py +11 -3
  18. {localcaption-0.3.0 → localcaption-0.4.0}/src/localcaption/errors.py +1 -1
  19. localcaption-0.4.0/src/localcaption/index.py +172 -0
  20. {localcaption-0.3.0 → localcaption-0.4.0}/src/localcaption/models.py +2 -2
  21. localcaption-0.4.0/src/localcaption/pipeline.py +237 -0
  22. localcaption-0.4.0/src/localcaption/summary.py +163 -0
  23. localcaption-0.4.0/src/localcaption/summary_prompt.txt +18 -0
  24. localcaption-0.4.0/src/localcaption/whisper.py +148 -0
  25. localcaption-0.4.0/tests/conftest.py +22 -0
  26. localcaption-0.4.0/tests/test_backends.py +286 -0
  27. localcaption-0.4.0/tests/test_batch.py +274 -0
  28. localcaption-0.4.0/tests/test_chapters.py +163 -0
  29. {localcaption-0.3.0 → localcaption-0.4.0}/tests/test_cli_doctor.py +33 -0
  30. localcaption-0.4.0/tests/test_cli_search.py +86 -0
  31. localcaption-0.4.0/tests/test_cli_transcribe.py +328 -0
  32. {localcaption-0.3.0 → localcaption-0.4.0}/tests/test_imports.py +1 -1
  33. localcaption-0.4.0/tests/test_index.py +193 -0
  34. {localcaption-0.3.0 → localcaption-0.4.0}/tests/test_models.py +5 -0
  35. localcaption-0.4.0/tests/test_pipeline.py +719 -0
  36. localcaption-0.4.0/tests/test_summary.py +305 -0
  37. {localcaption-0.3.0 → localcaption-0.4.0}/tests/test_whisper_paths.py +20 -1
  38. localcaption-0.3.0/src/localcaption/pipeline.py +0 -92
  39. localcaption-0.3.0/src/localcaption/whisper.py +0 -103
  40. localcaption-0.3.0/tests/test_cli_transcribe.py +0 -66
  41. localcaption-0.3.0/tests/test_pipeline.py +0 -215
  42. {localcaption-0.3.0 → localcaption-0.4.0}/.github/FUNDING.yml +0 -0
  43. {localcaption-0.3.0 → localcaption-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  44. {localcaption-0.3.0 → localcaption-0.4.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  45. {localcaption-0.3.0 → localcaption-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  46. {localcaption-0.3.0 → localcaption-0.4.0}/.github/pull_request_template.md +0 -0
  47. {localcaption-0.3.0 → localcaption-0.4.0}/.github/workflows/ci.yml +0 -0
  48. {localcaption-0.3.0 → localcaption-0.4.0}/.github/workflows/release.yml +0 -0
  49. {localcaption-0.3.0 → localcaption-0.4.0}/.gitignore +0 -0
  50. {localcaption-0.3.0 → localcaption-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  51. {localcaption-0.3.0 → localcaption-0.4.0}/LICENSE +0 -0
  52. {localcaption-0.3.0 → localcaption-0.4.0}/SECURITY.md +0 -0
  53. {localcaption-0.3.0 → localcaption-0.4.0}/docs/RELEASING.md +0 -0
  54. {localcaption-0.3.0 → localcaption-0.4.0}/docs/diagrams/architecture.png +0 -0
  55. {localcaption-0.3.0 → localcaption-0.4.0}/docs/diagrams/pipeline.png +0 -0
  56. {localcaption-0.3.0 → localcaption-0.4.0}/docs/diagrams/sequence.png +0 -0
  57. {localcaption-0.3.0 → localcaption-0.4.0}/scripts/uninstall.sh +0 -0
  58. {localcaption-0.3.0 → localcaption-0.4.0}/src/localcaption/__init__.py +0 -0
  59. {localcaption-0.3.0 → localcaption-0.4.0}/src/localcaption/__main__.py +0 -0
  60. {localcaption-0.3.0 → localcaption-0.4.0}/src/localcaption/_logging.py +0 -0
  61. {localcaption-0.3.0 → localcaption-0.4.0}/src/localcaption/audio.py +0 -0
  62. {localcaption-0.3.0 → localcaption-0.4.0}/src/localcaption/installer.py +0 -0
  63. {localcaption-0.3.0 → localcaption-0.4.0}/tests/__init__.py +0 -0
  64. {localcaption-0.3.0 → localcaption-0.4.0}/tests/test_cli_model.py +0 -0
  65. {localcaption-0.3.0 → localcaption-0.4.0}/tests/test_installer.py +0 -0
  66. {localcaption-0.3.0 → localcaption-0.4.0}/tests/test_models_download.py +0 -0
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-09-05
11
+
12
+ ### Added
13
+ - Pluggable transcription backends. `whisper.cpp` stays the default;
14
+ `faster-whisper` is available via `pip install localcaption[faster]`.
15
+ Select with `--backend {whisper-cpp,faster-whisper}` or
16
+ `$LOCALCAPTION_BACKEND`. `whisper_dir` is only required for whisper.cpp.
17
+ - **Batch mode.** `localcaption --batch urls.txt` transcribes each non-empty,
18
+ non-`#` line sequentially (URLs or local files). Output goes to
19
+ `<out>/<videoId>/` so items cannot clobber each other; if
20
+ `<videoId>/<videoId>.txt` already exists the item is skipped. A summary
21
+ table is printed at the end. Exit 0 if every item succeeded or was
22
+ skipped, 1 if any failed. Python helper: `localcaption.batch.transcribe_urls`.
23
+ - **Local auto-summary via Ollama.** `localcaption <url-or-file> --summary`
24
+ POSTs the `.txt` transcript to a local Ollama instance
25
+ (`http://localhost:11434/api/generate`) and writes `<id>.summary.md`
26
+ next to it. `--summary-model` (default `llama3.1:8b`) and
27
+ `--summary-prompt PATH` override the model and the built-in prompt
28
+ (TL;DR, key points, notable quotes, action items). If Ollama is down,
29
+ times out, or returns a bad response, the pipeline still succeeds and
30
+ prints a warning. `HTTP_PROXY` is ignored for this call.
31
+ - **YouTube chapters.** When yt-dlp reports chapter markers, write
32
+ `<id>.chapters.json` and a `<id>.chaptered.md` with headings like
33
+ `## 00:00 Intro`. The raw whisper `<id>.txt` is unchanged.
34
+ - **`localcaption search <term>`.** Grep previously transcribed videos via a
35
+ JSONL index at `~/.local/share/localcaption/index.jsonl` (override with
36
+ `LOCALCAPTION_INDEX_PATH`). Matches are ranked by hit count and printed
37
+ with timestamps when whisper JSON/SRT is available.
38
+
39
+ ### Changed
40
+ - Default whisper model is now `small.en` instead of `base.en`. Better
41
+ accuracy on accents and proper nouns; `tiny.en` remains the fast
42
+ fallback via `--model tiny.en`. Installer, `setup.sh`, and
43
+ `doctor --fix` follow the same default.
44
+
10
45
  ## [0.3.0] - 2026-08-16
11
46
 
12
47
  ### Added
@@ -24,11 +24,14 @@ src/localcaption/
24
24
  ├── __main__.py # python -m localcaption
25
25
  ├── _logging.py # tiny stdout logger (no logging-module config)
26
26
  ├── audio.py # ffmpeg → 16 kHz mono WAV (stage 2)
27
+ ├── batch.py # public Python API: transcribe_urls(...)
27
28
  ├── cli.py # argparse entry point (the `localcaption` script)
28
29
  ├── download.py # yt-dlp Python API wrapper (stage 1)
29
30
  ├── errors.py # exception hierarchy
30
31
  ├── pipeline.py # public Python API: transcribe_url(...)
31
- └── whisper.py # whisper.cpp invocation (stage 3)
32
+ ├── summary.py # optional local Ollama summary
33
+ ├── whisper.py # Backend protocol + transcribe() dispatcher
34
+ └── backends/ # whisper.cpp (default) and faster-whisper
32
35
  scripts/
33
36
  └── setup.sh # bootstraps whisper.cpp + venv + model
34
37
  tests/ # pytest suite
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: localcaption
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Fully-local video → transcript pipeline using yt-dlp, ffmpeg, and whisper.cpp. Supports YouTube, Vimeo, Twitch, and 1000+ sites. No API keys.
5
5
  Project-URL: Homepage, https://github.com/jatinkrmalik/localcaption
6
6
  Project-URL: Repository, https://github.com/jatinkrmalik/localcaption
@@ -51,6 +51,8 @@ Provides-Extra: dev
51
51
  Requires-Dist: pytest-cov>=4; extra == 'dev'
52
52
  Requires-Dist: pytest>=7; extra == 'dev'
53
53
  Requires-Dist: ruff>=0.5; extra == 'dev'
54
+ Provides-Extra: faster
55
+ Requires-Dist: faster-whisper>=1.0; extra == 'faster'
54
56
  Description-Content-Type: text/markdown
55
57
 
56
58
  # localcaption
@@ -81,7 +83,7 @@ Description-Content-Type: text/markdown
81
83
  |---|---|
82
84
  | Download best audio | [`yt-dlp`](https://github.com/yt-dlp/yt-dlp) (YouTube, Vimeo, Twitch, 1000+ sites) |
83
85
  | Re-encode to 16 kHz mono WAV | [`ffmpeg`](https://ffmpeg.org/) |
84
- | Transcribe locally | [`whisper.cpp`](https://github.com/ggerganov/whisper.cpp) |
86
+ | Transcribe locally | [`whisper.cpp`](https://github.com/ggerganov/whisper.cpp) (default) or [`faster-whisper`](https://github.com/SYSTRAN/faster-whisper) |
85
87
 
86
88
  Nothing is uploaded to a third-party service. No OpenAI / Google / DeepL keys
87
89
  required. Runs happily on a laptop.
@@ -118,8 +120,8 @@ localcaption doctor --fix # ~2 min on an M-series Mac
118
120
  `doctor --fix` is idempotent and end-to-end: it installs missing system
119
121
  tools (`ffmpeg`/`cmake` via `brew`/`apt`), clones + builds whisper.cpp at
120
122
  the canonical XDG location, downloads the default model, and re-runs the
121
- diagnostics to confirm everything works. Pick a different model with
122
- `--model small.en`.
123
+ diagnostics to confirm everything works. Pick a faster model with
124
+ `--model tiny.en`.
123
125
 
124
126
  Prefer to do it yourself? Two equivalent options:
125
127
 
@@ -130,13 +132,13 @@ curl -fsSL https://raw.githubusercontent.com/jatinkrmalik/localcaption/main/scri
130
132
  # Option B: DIY, anywhere you like:
131
133
  git clone https://github.com/ggerganov/whisper.cpp /path/to/whisper.cpp
132
134
  cd /path/to/whisper.cpp && cmake -B build && cmake --build build -j --config Release
133
- bash models/download-ggml-model.sh base.en
135
+ bash models/download-ggml-model.sh small.en
134
136
  export LOCALCAPTION_WHISPER_DIR=/path/to/whisper.cpp # add to your shell rc
135
137
  ```
136
138
 
137
139
  > 💡 The `install.sh` bootstrap is just `pipx install localcaption` followed
138
140
  > by `localcaption doctor --fix`, same logic, single source of truth.
139
- > Override the default model with `WHISPER_MODEL=small.en bash install.sh`.
141
+ > Override the default model with `WHISPER_MODEL=tiny.en bash install.sh`.
140
142
 
141
143
  After install, verify everything is wired up:
142
144
 
@@ -148,7 +150,7 @@ localcaption doctor --fix # diagnostic + auto-repair anything missing
148
150
  ### Uninstall
149
151
 
150
152
  To completely remove `localcaption` and everything it installed (the
151
- binary, whisper.cpp build, and ggml models (about 200 MB total):
153
+ binary, whisper.cpp build, and ggml models (about 500 MB total):
152
154
 
153
155
  ```bash
154
156
  # pipx + whisper.cpp + models, with confirmation prompts:
@@ -159,7 +161,7 @@ bash scripts/uninstall.sh
159
161
  ```
160
162
 
161
163
  Useful flags: `--dry-run` (preview), `--yes` (skip prompts),
162
- `--keep-models` (uninstall the binary but keep the 200 MB whisper.cpp +
164
+ `--keep-models` (uninstall the binary but keep the ~500 MB whisper.cpp +
163
165
  models cache for next time).
164
166
 
165
167
  Sample output:
@@ -180,7 +182,7 @@ whisper.cpp:
180
182
  searching: /Users/you/.local/share/localcaption/whisper.cpp
181
183
  ✅ directory exists
182
184
  ✅ binary built (.../build/bin/whisper-cli)
183
- ✅ models present (ggml-base.en.bin)
185
+ ✅ models present (ggml-small.en.bin)
184
186
 
185
187
  All checks passed. You're good to go: localcaption <url>
186
188
  ```
@@ -191,7 +193,7 @@ download the default model, then re-verify:
191
193
 
192
194
  ```bash
193
195
  localcaption doctor --fix # repair everything
194
- localcaption doctor --fix --model small.en # …with a specific model
196
+ localcaption doctor --fix --model tiny.en # …with a faster/smaller model
195
197
  ```
196
198
 
197
199
  ### Dev install (contributors)
@@ -223,16 +225,28 @@ localcaption "https://vimeo.com/148751763"
223
225
  # Local video/audio files
224
226
  localcaption /path/to/video.mp4
225
227
  localcaption ./recording.wav
228
+
229
+ # Batch: one URL or local path per line (# comments and blank lines ignored)
230
+ localcaption --batch urls.txt -o transcripts/ -m small.en
231
+
232
+ # Transcript + local summary (requires a running Ollama)
233
+ localcaption "https://www.youtube.com/watch?v=dQw4w9WgXcQ" --summary
234
+ localcaption ./talk.mp4 --summary --summary-model llama3.1:8b
226
235
  ```
227
236
 
228
237
  | flag | default | what it does |
229
238
  |---|---|---|
230
- | `-m`, `--model` | `base.en` | whisper model name (`tiny.en`, `base.en`, `small.en`, `medium.en`, `large-v3`, …) |
239
+ | `-m`, `--model` | `small.en` | whisper model name (`tiny.en`, `base.en`, `small.en`, `medium.en`, `large-v3`, …) |
231
240
  | `-o`, `--out` | `./transcripts` | output directory |
232
241
  | `-l`, `--language` | `auto` | ISO language code, or `auto` to let whisper detect it |
233
- | `--whisper-dir` | auto-detect¹ | path to a built whisper.cpp checkout |
242
+ | `--backend` | `whisper-cpp` | transcription backend: `whisper-cpp` or `faster-whisper`. `$LOCALCAPTION_BACKEND` if the flag is omitted |
243
+ | `--whisper-dir` | auto-detect¹ | path to a built whisper.cpp checkout (whisper-cpp backend) |
234
244
  | `--keep-audio` | off | keep the downloaded audio + intermediate WAV in `<out>/.work/` |
235
245
  | `--no-print` | off | don't echo the transcript to stdout |
246
+ | `--batch FILE` | off | transcribe each non-empty, non-`#` line in FILE sequentially |
247
+ | `--summary` | off | after transcription, write `<id>.summary.md` via local Ollama |
248
+ | `--summary-model` | `llama3.1:8b` | Ollama model used by `--summary` |
249
+ | `--summary-prompt` | built-in | path to a prompt template (`{transcript}` is replaced if present) |
236
250
 
237
251
  ¹ `--whisper-dir` resolution order:
238
252
  1. The explicit flag value, if given.
@@ -240,10 +254,52 @@ localcaption ./recording.wav
240
254
  3. `./whisper.cpp` (dev checkout).
241
255
  4. `~/.local/share/localcaption/whisper.cpp` (where `install.sh` puts it).
242
256
 
243
- Outputs `<videoId>.txt`, `.srt`, `.vtt`, and `.json` in the chosen directory. For local files, the output filename is derived from the input file's name.
257
+ Outputs `<videoId>.txt`, `.srt`, `.vtt`, and `.json` in the chosen directory. For local files, the output filename is derived from the input file's name. With `--summary`, also writes `<videoId>.summary.md`. When the source has chapter markers (typical on YouTube), also writes `<videoId>.chapters.json` and `<videoId>.chaptered.md`. The raw whisper `.txt` is left unchanged.
258
+
259
+ `--batch FILE` writes each item into `<out>/<videoId>/` and skips any video
260
+ whose `.txt` is already there, so you can re-run a list after a failure.
261
+ Local paths are relative to the list file (and `~` is expanded). The process
262
+ is sequential (whisper.cpp already saturates the machine). Exit 0 if
263
+ everything succeeded or was skipped, 1 otherwise.
244
264
 
245
265
  You can also invoke it as a module: `python -m localcaption <url-or-file>`.
246
266
 
267
+ ### faster-whisper (optional)
268
+
269
+ `whisper.cpp` is the default backend and needs no extra Python packages.
270
+ To use [faster-whisper](https://github.com/SYSTRAN/faster-whisper) instead
271
+ (CTranslate2, typically faster on CPU/CUDA, including Windows):
272
+
273
+ ```bash
274
+ pip install 'localcaption[faster]'
275
+ # pipx:
276
+ pipx inject localcaption faster-whisper
277
+
278
+ localcaption --backend faster-whisper "https://www.youtube.com/watch?v=..."
279
+ # or:
280
+ export LOCALCAPTION_BACKEND=faster-whisper
281
+ ```
282
+
283
+ faster-whisper downloads its own CTranslate2 weights on first use; it does
284
+ not read ggml files from `--whisper-dir`. `--model` names (`base.en`,
285
+ `small.en`, `large-v3`, ...) match the usual Whisper sizes.
286
+
287
+ ### Summaries (optional)
288
+
289
+ If [Ollama](https://ollama.com) is running locally, `--summary` sends the
290
+ `.txt` transcript to `http://localhost:11434/api/generate` and writes
291
+ `<id>.summary.md` next to it. The built-in prompt asks for a TL;DR, key
292
+ points, notable quotes, and action items.
293
+
294
+ ```bash
295
+ localcaption <url-or-file> --summary
296
+ localcaption <url-or-file> --summary --summary-model mistral
297
+ localcaption <url-or-file> --summary --summary-prompt ./my_prompt.txt
298
+ ```
299
+
300
+ If Ollama isn't reachable, localcaption logs a warning and still exits 0.
301
+ The transcript files are unchanged.
302
+
247
303
  ### Subcommands
248
304
 
249
305
  | Subcommand | What it does |
@@ -255,12 +311,31 @@ You can also invoke it as a module: `python -m localcaption <url-or-file>`.
255
311
  | `localcaption model info <name>` | Show metadata about a single model. |
256
312
  | `localcaption model download <name>` | Download a model with progress bar + atomic writes. |
257
313
  | `localcaption model rm <name>` | Remove an installed model to free disk space. |
314
+ | `localcaption search <term>` | Grep previously transcribed videos. Ranked matches with timestamps. |
315
+
316
+ ### Search past transcripts
317
+
318
+ Each successful transcription upserts one JSON line in
319
+ `~/.local/share/localcaption/index.jsonl` (`id`, `url`, `title`, `duration`,
320
+ `chapters`, `transcript`). Re-running the same id replaces that row. Override
321
+ the path with `LOCALCAPTION_INDEX_PATH`.
322
+
323
+ ```bash
324
+ localcaption search "install"
325
+ # vid123 02:30 First, let's install pip
326
+ # Lecture on Python tooling
327
+ ```
328
+
329
+ Search is a case-insensitive substring. Hits are ranked by how often the term
330
+ appears (title matches get a small boost). Timestamps come from the sibling
331
+ whisper `.json` or `.srt` when those files are still next to the `.txt`.
258
332
 
259
333
  ### Managing models
260
334
 
261
- `localcaption` ships with a default `base.en` model (~142 MB). For better
262
- quality or non-English audio, switch models with `--model <name>`. If the
263
- model isn't already installed, you'll be prompted to download it:
335
+ `localcaption` defaults to `small.en` (~466 MB), downloaded by
336
+ `doctor --fix` or on first use. For a faster run use `--model tiny.en`;
337
+ for non-English audio, pick a multilingual model. If the model isn't
338
+ already installed, you'll be prompted to download it:
264
339
 
265
340
  ```bash
266
341
  $ localcaption --model small.en "https://www.youtube.com/watch?v=..."
@@ -289,9 +364,9 @@ localcaption --model small.en --auto-download "https://www.youtube.com/..."
289
364
 
290
365
  | Model | Size | Best for |
291
366
  |---|---|---|
292
- | `tiny.en` | 75 MB | Quick drafts, English only, low-resource environments |
293
- | `base.en` | 142 MB | Current install default, fast & decent |
294
- | `small.en` | 466 MB | **Recommended for English**, great accuracy/speed balance |
367
+ | `tiny.en` | 75 MB | Fast fallback, English only, low-resource environments |
368
+ | `base.en` | 142 MB | Faster than `small.en`, lower accuracy |
369
+ | `small.en` | 466 MB | **Install default**, English, accuracy/speed balance |
295
370
  | `medium.en` | 1.5 GB | High accuracy English, ~3× slower than `small.en` |
296
371
  | `large-v3` | 3.0 GB | Best accuracy, multilingual, slow |
297
372
  | `large-v3-turbo` | 1.6 GB | Near-large quality at ~half the size, great compromise |
@@ -308,17 +383,37 @@ result = transcribe_url(
308
383
  "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
309
384
  out_dir=Path("transcripts"),
310
385
  whisper_dir=Path("whisper.cpp"),
311
- model="base.en",
386
+ model="small.en",
387
+ summary=True, # optional; writes .summary.md via local Ollama
312
388
  )
313
389
  print(result.transcripts.txt.read_text())
390
+
391
+ # faster-whisper (pip install 'localcaption[faster]') does not need whisper_dir:
392
+ # transcribe_url(url, out_dir=Path("transcripts"), backend="faster-whisper")
393
+ ```
394
+
395
+ Batch from Python:
396
+
397
+ ```python
398
+ from pathlib import Path
399
+ from localcaption.batch import read_url_list, transcribe_urls
400
+
401
+ result = transcribe_urls(
402
+ read_url_list(Path("urls.txt")),
403
+ out_dir=Path("transcripts"),
404
+ whisper_dir=Path("whisper.cpp"),
405
+ model="small.en",
406
+ )
407
+ print(result.summary())
314
408
  ```
315
409
 
316
410
  ## Architecture
317
411
 
318
412
  `localcaption` is intentionally tiny: an orchestrator (`pipeline.py`) drives
319
413
  three single-responsibility stages, each wrapping one external tool. The
320
- modules are split this way so that a contributor can swap, say, `whisper.cpp`
321
- for `faster-whisper` without touching `download.py` or `audio.py`.
414
+ transcribe stage is a small `Backend` protocol; `whisper.cpp` is the default
415
+ implementation and `faster-whisper` is an optional extra. Swapping backends
416
+ does not touch `download.py` or `audio.py`.
322
417
 
323
418
  ### Module map
324
419
 
@@ -327,8 +422,9 @@ for `faster-whisper` without touching `download.py` or `audio.py`.
327
422
  | Layer | Files | Responsibility |
328
423
  |---|---|---|
329
424
  | Entry points | `cli.py`, `__main__.py` | argparse, exit codes, stdout formatting |
330
- | Orchestration | `pipeline.py` | public Python API: `transcribe_url(...)` |
331
- | Pipeline stages | `download.py`, `audio.py`, `whisper.py` | one external tool each |
425
+ | Orchestration | `pipeline.py`, `batch.py` | public Python API: `transcribe_url(...)`, `transcribe_urls(...)` |
426
+ | Pipeline stages | `download.py`, `audio.py`, `whisper.py`, `backends/`, `summary.py` | download, re-encode, transcribe (pluggable), optional Ollama summary |
427
+ | Chapters & search | `chapters.py`, `index.py` | YouTube chapter sidecars + JSONL search index |
332
428
  | Support | `errors.py`, `_logging.py` | exception hierarchy, tiny logger |
333
429
 
334
430
  ### Runtime sequence
@@ -349,9 +445,11 @@ the subprocess hops to yt-dlp, ffmpeg, and whisper.cpp. The intermediate
349
445
  ## Benchmarks
350
446
 
351
447
  Wall-clock times for the **complete** pipeline (yt-dlp download → ffmpeg
352
- re-encode → whisper.cpp transcription), measured with the default `base.en`
353
- model. Numbers will vary with your network speed and CPU/GPU; treat them as
354
- order-of-magnitude reference, not a competitive benchmark.
448
+ re-encode → whisper.cpp transcription), measured with `base.en` (the
449
+ previous default). These have **not** been re-run on `small.en`; expect
450
+ transcription to take longer. Numbers will vary with your network speed
451
+ and CPU/GPU; treat them as order-of-magnitude reference, not a
452
+ competitive benchmark.
355
453
 
356
454
  | Video | Length | Wall-clock | Speed vs. realtime | Hardware |
357
455
  |---|---|---|---|---|
@@ -364,15 +462,16 @@ order-of-magnitude reference, not a competitive benchmark.
364
462
 
365
463
  ```bash
366
464
  # Apple Silicon, macOS, whisper.cpp built with Metal,
367
- # model: ggml-base.en, language: auto, no other heavy processes.
465
+ # model: ggml-base.en (matches the table above; not the current default),
466
+ # language: auto, no other heavy processes.
368
467
 
369
- time localcaption --no-print -o /tmp/lc-bench-1 \
468
+ time localcaption --model base.en --no-print -o /tmp/lc-bench-1 \
370
469
  "https://www.youtube.com/watch?v=PSRJfaAYkW4"
371
470
 
372
- time localcaption --no-print -o /tmp/lc-bench-2 \
471
+ time localcaption --model base.en --no-print -o /tmp/lc-bench-2 \
373
472
  "https://www.youtube.com/watch?v=aircAruvnKk"
374
473
 
375
- time localcaption --no-print -o /tmp/lc-bench-3 \
474
+ time localcaption --model base.en --no-print -o /tmp/lc-bench-3 \
376
475
  "https://www.youtube.com/watch?v=BYizgB2FcAQ"
377
476
  ```
378
477
 
@@ -383,8 +482,8 @@ hardware in the **Hardware** column.
383
482
 
384
483
  ## Notes
385
484
 
386
- - Bigger models = better quality but slower. `base.en` is a good default;
387
- try `small.en` if you have the patience and `tiny.en` for instant results.
485
+ - Bigger models = better quality but slower. `small.en` is the default;
486
+ use `--model tiny.en` when you want speed over accuracy.
388
487
  - Apple Silicon: whisper.cpp's CMake build uses Metal automatically, you'll
389
488
  see `ggml_metal_init` in the logs.
390
489
  - The pipeline accepts any URL `yt-dlp` supports (Vimeo, Twitch VODs, Twitter/X,
@@ -411,7 +510,7 @@ criteria, and discussion):
411
510
  | [#4](https://github.com/jatinkrmalik/localcaption/issues/4) | Speaker diarization with pyannote.audio (`--diarize`) | `stretch`, `help wanted` |
412
511
  | [#5](https://github.com/jatinkrmalik/localcaption/issues/5) | YouTube chapters & grep-able search index | `enhancement` |
413
512
  | [#6](https://github.com/jatinkrmalik/localcaption/issues/6) | Pluggable transcription backends (faster-whisper / MLX) | `help wanted` |
414
- | ~~[#1](https://github.com/jatinkrmalik/localcaption/issues/1)~~ | ~~Switch default model from `base.en` to `small.en`~~ | _superseded by #7_ |
513
+ | [#1](https://github.com/jatinkrmalik/localcaption/issues/1) | Switch default model from `base.en` to `small.en` | _unreleased_ ✅ |
415
514
 
416
515
  **Have an idea?** Open a
417
516
  [feature request](https://github.com/jatinkrmalik/localcaption/issues/new/choose),