# elevenlabs-stt **English** | [简体中文](./README.md) > Turn ElevenLabs' web Speech-to-Text feature into a CLI script, using your own logged-in account to reuse the free quota. > [!IMPORTANT] > This project is not affiliated with ElevenLabs. It works by replaying the web app's internal API, which can change without notice. Use your own account and respect ElevenLabs' terms of service. ## What it does Turns the web flow at `elevenlabs.io/app/speech-to-text` (log in → upload audio → pick language/toggles → wait → export subtitles) into a single command. The script replays the web internal API (not the official paid API), so it inherits the free account's credit quota (~10,000 credits ≈ 12 min of audio/month). ## Features - Upload a local audio file for transcription (the web "Upload" path) - Four toggles: mark audio events / include subtitles / no verbatim / use speaker library - High-frequency proper-noun vocabulary (key terms) - Main language selection or auto-detect (ISO 639-3) - Export SRT / VTT / TXT / JSON / HTML / PDF / DOCX, default SRT - Pre-upload checks: file size (1000 MB hard), duration (optional ffprobe, 10-min soft warn) - One-time browser login with automatic 1-hour token refresh - Optional multi-account pool: auto-register temp-mail accounts, choose by remaining credits, and refill after transcription ## Install ```bash pip install -r requirements.txt playwright install chrome # only needed for login ``` Optional: install `ffprobe` (from ffmpeg) to enable audio-duration checks. ## Quick start ```bash # 1) One-time login (opens Chrome; log into ElevenLabs any way you like) python stt.py login # 2) Transcribe (defaults to out.srt) python stt.py transcribe audio.m4a -o out.srt # 3) Batch transcribe (each file writes its own .srt) python stt.py transcribe a.mp3 b.mp3 c.mp3 # 4) Preview the allocation plan only — no registration, no upload python stt.py transcribe a.mp3 b.mp3 c.mp3 --dry-run # 5) Force language + vocab + export VTT python stt.py transcribe audio.m4a --lang eng --vocab "V社,Major" --format vtt -o out.vtt # 6) Long audio: split on silence, transcribe each part, merge back into one file python stt.py transcribe long.m4a --split -o long.srt # Optional: inspect/warm the multi-account pool python stt.py pool status python stt.py pool warm --target 3 ``` Login saves a Firebase **refresh token** to `session.json` (gitignored). The 1-hour JWT is auto-refreshed on each run; re-run `login` only when the refresh token itself expires or you get an auth error. ## Configuration Copy `config.example.toml` → `config.toml` for persistent defaults; CLI flags override config: | Field | Description | Default | |---|---|---| | `language` | `auto`, a language name, or an ISO 639-3 code | `auto` | | `tag_audio_events` | Mark audio events | `true` | | `include_subtitles` | Include subtitles (web default off; script forces on) | `true` | | `no_verbatim` | No verbatim | `false` | | `use_speaker_library` | Use speaker library | `false` | | `vocab` | Key terms, e.g. `["Maj3r", "V社"]` | `[]` | | `export_format` | `srt`/`vtt`/`txt`/`json`/`html`/`pdf`/`docx` | `srt` | | `poll_timeout_secs` | Poll timeout in seconds | `600` | | `show_cost` | Print estimated credit cost before upload | `false` | | `max_concurrency` | Max concurrent transcriptions across accounts; `1` = serial fallback | `4` | | `stagger_secs` | Minimum gap (seconds) between consecutive upload starts; `0` = no staggering | `2.0` | ## Local Web UI (optional) Beyond the CLI, `web.py` spins up a local web page using the standard library, reusing `stt.py`'s transcription and account logic: ```bash python web.py # opens http://127.0.0.1:8756 ``` - **Transcribe page**: drag/multi-select audio or video → videos have their audio track extracted locally with `ffmpeg` (UI shows **uploading** then **extracting audio**) → the browser measures duration and estimates credits → auto-allocates accounts by remaining credits (best-fit) → "Start" does the real upload, polling, and export, auto-downloading subtitles when done. The accounts section includes a collapsed-by-default **"Advanced · manually pick accounts"** panel: checking accounts restricts allocation to the selected set (accounts too small for the batch are greyed out); manual mode errors out instead of auto-registering when credits fall short, and clearing the selection returns to auto-allocation. While transcribing, the bottom bar shows **real step-by-step progress** (done/total counter, a multi-line **active-task list** with each in-flight file and stage, latest log line), with an expandable full scrolling log. - **Accounts page**: reads `accounts.json`, with search/sort/multi-select/pagination and back-to-top, real credit refresh (selected accounts only, fetched concurrently), delete, and JSON export. - **Log in**: a dialog takes email + password, logs in via Firebase REST directly and saves the token to `accounts.json` (no browser needed). - **Start the registrar**: a dialog exposes the full parameters (mapping to `config.toml → [temp_email]` and the target full-account count); "Save" writes back to `config.toml` (two-way sync with the config file); "Start batch create" saves the config first, then runs a real `pool warm` batch registration. Registration emits step-by-step progress logs: the CLI (`stt pool warm` and transcribe-triggered auto-registration) prints them to stderr, and the Web UI register dialog shows them live with a done/target counter. - **Tools · long-audio split page**: upload long audio or video (videos are audio-extracted the same way) → the backend computes a greedy silence-based cut plan (each part fits one full free account's quota) → run the split and losslessly export the parts to local `out/` for later transcription; shares `audio_split.py` with the CLI `--split`. Both the transcribe page and this page offer a **"skip long silences" toggle** (off by default): silences ≥10s are dropped from the parts — not uploaded, not billed (the threshold is tunable in this page's advanced params), and the plan shows the total silence time saved. - When the pool can't cover all files, the transcribe page prompts you to `pool warm` first — it never registers silently. Zero extra dependencies, zero build; must be run from the project root (needs `accounts.json`, `config.toml`). ## Command reference ``` stt login one-time browser login → session.json stt transcribe