Metadata-Version: 2.4
Name: transcript-studio
Version: 0.1.0
Summary: Transcribe any English video (any length) to SRT/TXT/VTT/JSON with speaker labels — 100% local, no cloud.
License-Expression: MIT
Keywords: transcription,subtitles,whisper,diarization,srt,video
Classifier: Programming Language :: Python :: 3
Classifier: Environment :: Console
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: faster-whisper>=1.0
Requires-Dist: onnxruntime>=1.17
Requires-Dist: ctranslate2==4.6.0
Requires-Dist: setuptools<81
Requires-Dist: numpy>=1.24
Requires-Dist: streamlit>=1.30
Requires-Dist: yt-dlp>=2024.4.9
Requires-Dist: sherpa-onnx>=1.12
Requires-Dist: requests>=2.31
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: edge-tts>=6.1; extra == "dev"
Dynamic: license-file

# 🎙 Transcript Studio

Transcribe **any English video — any length — into SRT / TXT / VTT / JSON**, with
**per-speaker labels you can rename** (Speaker 1 → "Alice"). 100% local: no cloud,
no API keys, no accounts.

## Features

- **🗣 Transcription** — [faster-whisper](https://github.com/SYSTRAN/faster-whisper)
  (offline, free) with Silero VAD for clean timestamps. English by default,
  `--language auto` to detect. Multi-hour videos work; progress is shown live.
- **👥 Speaker detection** — a real speaker-verification model
  ([NeMo Titanet](https://github.com/k2-fsa/sherpa-onnx/releases/tag/speaker-recongition-models)
  via [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx), ONNX, no torch)
  embeds every transcript segment and clusters the voices. The ~40 MB model
  downloads automatically on first use (cached in
  `%LOCALAPPDATA%\transcript-studio\models`). If it can't be installed, the app
  falls back to lightweight mel-based voice profiles. Built for **panels and
  interviews**: auto-detects up to 6 speakers, or force an exact count up to 8
  (host + 7 guests).
- **✏️ Rename speakers** — labels come as `Speaker 1/2/…` ordered by first
  appearance; give them real names in the UI or with `--rename` and the names
  are baked into every exported file.
- **📦 Exports** — **SRT** and **TXT** (with timestamps + speaker names), plus
  **WebVTT** and **JSON** as extras. Subtitle cues are auto-split to ≤6 s / ≤110 chars.
- **🌐 Input** — local video *or* audio files, uploads, or any URL (via yt-dlp).
- **⚡ Caching** — re-runs of the same file are instant (transcript + embeddings
  are cached; changing names/speaker-count re-exports immediately).

## Install

Requirements: Python 3.11+, [FFmpeg](https://ffmpeg.org/download.html) on PATH.

```powershell
cd D:\Cline\transcript-studio

# isolated env (recommended — uv can fetch Python 3.11 itself)
uv venv .venv --python 3.11
uv pip install -r requirements.txt -p "D:\Cline\transcript-studio\.venv\Scripts\python.exe"
uv pip install -e . -p "D:\Cline\transcript-studio\.venv\Scripts\python.exe"   # adds the `transcript-studio` command

# or once published to PyPI:
# pip install transcript-studio
```

> **`ImportError: DLL load failed ... onnxruntime_pybind11_state`** — this machine's
> `System32` only has the 2015 MSVC runtime. Copy the newer set shipped with Python
> 3.13 next to the ONNX DLLs (no admin needed):
>
> ```powershell
> $src = "$env:LOCALAPPDATA\Programs\Python\Python313"
> $dst = "D:\Cline\transcript-studio\.venv\Lib\site-packages\onnxruntime\capi"
> Copy-Item "$src\msvcp140*.dll","$src\vcruntime140*.dll","$src\concrt140.dll" $dst
> ```

## Quick start

```powershell
# UI (opens http://localhost:8502) — double-click the desktop shortcut
python -m transcript_studio ui

# CLI: SRT + TXT next to the video, auto speaker count
python -m transcript_studio run "C:\Videos\podcast.mp4"

# rename speakers, pick formats, force the speaker count
python -m transcript_studio run "C:\Videos\podcast.mp4" `
    --rename "Speaker 1=Alice,Speaker 2=Bob" `
    --speakers 2 --formats srt,txt,vtt,json -o D:\outputs

# long video: auto-parallel (up to 4 workers) is already the default
python -m transcript_studio run "D:\Videos\podcast-ep12.mp4"

# fastest possible local run (coarse cue timing) — combine batching + workers
python -m transcript_studio run "D:\Videos\podcast-ep12.mp4" --fast --workers 4

# trickier audio: bigger model (slower on CPU), English-only
python -m transcript_studio run "D:\Videos\noisy-panel.mp4" --model small.en --speakers 4

# from YouTube (audio-only download, fast)
python -m transcript_studio run "https://youtu.be/XXXX"

# options
python -m transcript_studio run --help
```

### CLI flags (`run`)

| flag | default | meaning |
|---|---|---|
| `-o, --output` | next to input file | output folder |
| `--model` | `base.en` | `tiny(.en)`/`base(.en)`/`small(.en)`/`medium`/`distil-*`/`large-v3-turbo`/`large-v3` |
| `--language` | `en` | language code, or `auto` |
| `--fast` | off | batched decoding: ≈40 % faster, but longer subtitle cues |
| `--batch-size` | `0` (off) | batched decode batch size (`--fast` implies 8) |
| `--workers` | `0` (auto) | parallel chunk workers: auto = 1 short clips, up to 4 for long videos |
| `--chunk-length` | `30` | decoding window seconds (fast mode); smaller = finer cues |
| `--speakers` | `0` (auto) | `0` = auto-detect, `1`–`8` = force count (panels) |
| `--rename` | – | `Speaker 1=Alice,Speaker 2=Bob` (bare `1=Alice` also works) |
| `--formats` | `srt,txt` | any of `srt,vtt,txt,json` |
| `--no-vad` | off | keep quiet speech (no silence cut) |
| `--pause` | off | wait for Enter before exiting (used by the drag-&-drop shortcut) |

## Speed (measured on this machine)

Measured on an **Intel i5-8350U (4 cores / 8 threads, no GPU)** with the default
`base.en` model — WER is word error rate against known ground truth:

| input | mode | speed | WER vs truth |
|---|---|---|---|
| 56 s clip | single process | **6.0×** | 1.0 % |
| 9 min audio | single process | 2.9× | 1.36 % |
| 9 min audio | **auto / `--workers 4`** | **8.4×** | **0.97 %** |
| 9 min audio | `--workers 4 --fast` | **16.9×** | 3.20 % (coarse cues) |

What that means in practice:

- **Short clips (up to ~8 min)** run single-process — process overhead isn't worth it.
- **Long videos automatically use 4 parallel workers** (`--workers 0` = auto): chunks
  are cut at the quietest point near the boundary, each chunk decodes **6 s of
  overlap** on both sides, and only segments whose midpoint falls inside its
  keep-zone are merged back — so the result has neither gaps nor duplicates.
  Measured: **~3× faster *and* slightly more accurate** than a single pass
  (0.97 % vs 1.36 % WER).
- **`--fast`** adds batched decoding (≈40 % faster on short audio; 16.9× combined
  with workers on long audio), but it produces **longer subtitle cues** whose inner
  timing is interpolated — good for a quick text transcript, not for precise
  subtitles (its text accuracy also drops a little: 3.2 % vs 1.0 % WER).
- **Close other heavy apps first.** On this 4-core CPU, background processes
  (Streamlit servers, browsers, agents) can cut throughput by 2× or more — during
  testing one orphaned server alone was burning 2 full cores.
- Big models (`distil-large-v3`, `large-v3-turbo`, `large-v3`) need a GPU to be fast —
  measured: `distil-large-v3` runs at **0.85×** realtime on this CPU (slower than
  real time), while `base.en` runs at 6×.

### If you need real speed: a cloud API key

This CPU has a hard ceiling of roughly 5–17× realtime. A hosted speech API removes it
entirely — e.g. Groq's `whisper-large-v3-turbo` (very fast, ~$0.04 per hour of audio)
or OpenAI's `gpt-4o-transcribe-diarize` (fast **and** speaker-labelled in one call).
There is **no "Cline API" to call** — Cline is this coding agent, not a speech service.
If you drop a key into the environment (`GROQ_API_KEY` / `OPENAI_API_KEY`), I can wire
a `--backend groq|openai` path into the app in a few minutes.

## Tests

```powershell
python -m pytest        # 27 tests: exporters, clustering, embeddings, sources
```

`tests/make_two_voice.py` and `tests/make_multi_voice.py` synthesize 2- and
4-voice conversations with ground-truth turn timelines (edge-tts, needs
network); `tests/eval_diarization.py` scores the real pipeline against them —
see the panel table below.

## Multi-speaker panels (3–4+ guests)

Works out of the box — leave **Speakers = Auto**, or pass the exact count when
you know it (`--speakers 4`, UI dropdown 1–8) to be extra safe:

```powershell
python -m transcript_studio run "C:\Videos\panel.mp4" --speakers 4 --rename "1=Host,2=Ann,3=Ben,4=Chloe"
```

Measured on synthetic ground truth (same pipeline as production):

| clip | auto result | per-segment accuracy |
|---|---|---|
| 2 voices (1 F, 1 M) | 2 speakers | 100 % |
| **4 voices (2 F + 2 M, incl. short backchannels)** | **4 speakers** | **100 %** |
| 1 voice (84 s) | 1 speaker | no false speakers |
| 30-min lecture (144 segments) | 1 speaker | no content-based false split |

Reproduce it yourself:

```powershell
python tests/make_multi_voice.py        # needs network (edge-tts) — builds a 4-voice clip
python tests/eval_diarization.py --audio tests/data/multi_voice.wav `
    --truth tests/data/multi_voice.truth.json --speakers 4 --diagnose
```

Guidance: **prefer `Auto`** or the true count. Forcing a *higher* number than
reality doesn't mix people up — it just fragments one voice into two labels
(e.g. 8 labels for 4 people, each still internally consistent).

## How speaker detection works (and its limits)

1. Whisper segments are extended into the **half-gaps around them** (silence
   only — never a neighbour's speech), chunked to ≤30 s windows, and embedded
   by Titanet into 192-dim L2-normalized vectors.
2. Vectors are clustered with k-means (deterministic); the speaker count comes
   from silhouette scoring with a minimum-cluster guard, or is forced by
   `--speakers`.
3. Labels are ordered by first appearance → `Speaker 1`, `Speaker 2`, …

Embeddings are content-invariant, so short punchy lines from the *same* voice
stay together (a plain pitch/timbre heuristic could not tell that apart).
Honest limits: overlapping speech (several people talking at once) is assigned
to the dominant voice of that segment; Whisper may merge a very short
backchannel ("Agreed.") into the next turn — such segments go to the majority
speaker. Voices that are literally identical (clones) may merge; the fallback
mel profile is cruder — install `sherpa-onnx` (it's in `requirements.txt`) for
the good path.

## Use it from another computer (MacBook / phone on the same Wi-Fi)

The UI is a local web server, so any device on your network can drive it — the
heavy work still runs on this laptop.

1. **Start the server on the laptop** — double-click `Start_Transcript_Studio.bat`
   (binds `0.0.0.0:8502`, so it accepts connections from other devices).
2. **Allow it through the firewall — once** — double-click `Allow_LAN_access.bat`
   and accept the UAC prompt (adds an inbound TCP-8502 rule; needs administrator,
   which is why it's a separate file).
3. **On the MacBook**, open `http://192.168.0.203:8502` (check the laptop's
   current Wi-Fi address with `ipconfig`; it changes if the network does).
   Upload the audio/video → pick formats → download the **.srt** it produces.

Server settings live in `.streamlit/config.toml` (`address = "0.0.0.0"`,
`port = 8502`, `maxUploadSize = 4096` MB).

**Away from home:** install [Tailscale](https://tailscale.com/download) on both
machines and sign in — the MacBook then reaches the laptop from anywhere at
`http://<laptop-tailscale-name>:8502`, still private to your own devices, and no
firewall rule or public URL is involved.

## License

MIT — see [LICENSE](LICENSE).
