voxhelm 0.1.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.
- voxhelm-0.1.0/.gitignore +12 -0
- voxhelm-0.1.0/.python-version +1 -0
- voxhelm-0.1.0/AGENTS.md +9 -0
- voxhelm-0.1.0/BACKLOG.md +161 -0
- voxhelm-0.1.0/PKG-INFO +425 -0
- voxhelm-0.1.0/README.md +408 -0
- voxhelm-0.1.0/config/__init__.py +1 -0
- voxhelm-0.1.0/config/asgi.py +7 -0
- voxhelm-0.1.0/config/settings.py +425 -0
- voxhelm-0.1.0/config/urls.py +19 -0
- voxhelm-0.1.0/config/wsgi.py +7 -0
- voxhelm-0.1.0/evals/known_speaker_eval.py +274 -0
- voxhelm-0.1.0/evals/known_speaker_results.md +243 -0
- voxhelm-0.1.0/evals/pp62_production_eval.py +197 -0
- voxhelm-0.1.0/jobs/__init__.py +1 -0
- voxhelm-0.1.0/jobs/apps.py +6 -0
- voxhelm-0.1.0/jobs/artifacts.py +233 -0
- voxhelm-0.1.0/jobs/media.py +190 -0
- voxhelm-0.1.0/jobs/migrations/0001_initial.py +67 -0
- voxhelm-0.1.0/jobs/migrations/0002_alter_job_job_type_alter_jobartifact_kind.py +23 -0
- voxhelm-0.1.0/jobs/migrations/0003_job_dispatch_mode_job_operator_and_more.py +35 -0
- voxhelm-0.1.0/jobs/migrations/0004_stagedmedia.py +48 -0
- voxhelm-0.1.0/jobs/migrations/0005_rename_jobs_staged_producer_76b546_idx_jobs_staged_produce_cbe898_idx_and_more.py +23 -0
- voxhelm-0.1.0/jobs/migrations/0006_alter_jobartifact_kind.py +18 -0
- voxhelm-0.1.0/jobs/migrations/0007_remote_workers.py +102 -0
- voxhelm-0.1.0/jobs/migrations/__init__.py +0 -0
- voxhelm-0.1.0/jobs/models.py +176 -0
- voxhelm-0.1.0/jobs/remote_worker_cli.py +985 -0
- voxhelm-0.1.0/jobs/remote_workers.py +1293 -0
- voxhelm-0.1.0/jobs/services.py +1510 -0
- voxhelm-0.1.0/jobs/staging.py +162 -0
- voxhelm-0.1.0/jobs/tasks.py +15 -0
- voxhelm-0.1.0/jobs/urls.py +25 -0
- voxhelm-0.1.0/jobs/views.py +184 -0
- voxhelm-0.1.0/justfile +18 -0
- voxhelm-0.1.0/lane_scheduler.py +294 -0
- voxhelm-0.1.0/manage.py +14 -0
- voxhelm-0.1.0/operators/__init__.py +1 -0
- voxhelm-0.1.0/operators/apps.py +6 -0
- voxhelm-0.1.0/operators/forms.py +65 -0
- voxhelm-0.1.0/operators/management/__init__.py +1 -0
- voxhelm-0.1.0/operators/management/commands/__init__.py +1 -0
- voxhelm-0.1.0/operators/management/commands/bootstrap_operator.py +56 -0
- voxhelm-0.1.0/operators/templates/operators/root.html +302 -0
- voxhelm-0.1.0/operators/views.py +224 -0
- voxhelm-0.1.0/pyproject.toml +77 -0
- voxhelm-0.1.0/specs/2026-03-11_voxhelm_service.md +891 -0
- voxhelm-0.1.0/specs/2026-03-12_stt_backend_benchmark_studio.md +169 -0
- voxhelm-0.1.0/specs/2026-03-13_whisperkit_re_evaluation_studio.md +274 -0
- voxhelm-0.1.0/specs/README.md +289 -0
- voxhelm-0.1.0/specs/archive/2026-03-11_voxhelm_spec_review_and_chunking.md +388 -0
- voxhelm-0.1.0/specs/archive/README.md +22 -0
- voxhelm-0.1.0/specs/archive/planning-review.md +146 -0
- voxhelm-0.1.0/specs/archive/review-prompt.md +88 -0
- voxhelm-0.1.0/specs/archive/spec-review.md +246 -0
- voxhelm-0.1.0/specs/decision-log.md +567 -0
- voxhelm-0.1.0/specs/delivery-chunks.md +1319 -0
- voxhelm-0.1.0/specs/diarization-quality-research.md +937 -0
- voxhelm-0.1.0/specs/implementation-sequence.md +474 -0
- voxhelm-0.1.0/specs/interface-map.md +498 -0
- voxhelm-0.1.0/specs/known-speaker-diarization.md +154 -0
- voxhelm-0.1.0/specs/milestones.md +489 -0
- voxhelm-0.1.0/specs/remote-transcription-workers.md +663 -0
- voxhelm-0.1.0/synthesis/__init__.py +1 -0
- voxhelm-0.1.0/synthesis/service.py +324 -0
- voxhelm-0.1.0/synthesis/views.py +139 -0
- voxhelm-0.1.0/tests/conftest.py +25 -0
- voxhelm-0.1.0/tests/test_api.py +612 -0
- voxhelm-0.1.0/tests/test_diarization.py +292 -0
- voxhelm-0.1.0/tests/test_jobs.py +1591 -0
- voxhelm-0.1.0/tests/test_known_speaker.py +614 -0
- voxhelm-0.1.0/tests/test_lane_scheduler.py +105 -0
- voxhelm-0.1.0/tests/test_media.py +28 -0
- voxhelm-0.1.0/tests/test_operator_ui.py +379 -0
- voxhelm-0.1.0/tests/test_remote_worker_cli.py +303 -0
- voxhelm-0.1.0/tests/test_remote_workers.py +2018 -0
- voxhelm-0.1.0/tests/test_service.py +720 -0
- voxhelm-0.1.0/tests/test_synthesis.py +137 -0
- voxhelm-0.1.0/tests/test_wyoming.py +431 -0
- voxhelm-0.1.0/transcriptions/__init__.py +1 -0
- voxhelm-0.1.0/transcriptions/apps.py +6 -0
- voxhelm-0.1.0/transcriptions/diarization.py +366 -0
- voxhelm-0.1.0/transcriptions/errors.py +15 -0
- voxhelm-0.1.0/transcriptions/formats.py +153 -0
- voxhelm-0.1.0/transcriptions/input_media.py +112 -0
- voxhelm-0.1.0/transcriptions/known_speaker.py +666 -0
- voxhelm-0.1.0/transcriptions/observability.py +101 -0
- voxhelm-0.1.0/transcriptions/service.py +663 -0
- voxhelm-0.1.0/transcriptions/views.py +214 -0
- voxhelm-0.1.0/transcriptions/wyoming.py +424 -0
- voxhelm-0.1.0/uv.lock +2317 -0
voxhelm-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
voxhelm-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Agent Guidelines
|
|
2
|
+
|
|
3
|
+
## Development workflow
|
|
4
|
+
|
|
5
|
+
This project uses **trunk-based development**. Do all work directly on `main`:
|
|
6
|
+
|
|
7
|
+
- Commit small, coherent changes straight to `main` and keep it releasable.
|
|
8
|
+
- Do **not** create feature branches unless the user explicitly asks for one.
|
|
9
|
+
- Pull/fast-forward `main` before starting and before pushing.
|
voxhelm-0.1.0/BACKLOG.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Voxhelm Backlog
|
|
2
|
+
|
|
3
|
+
## Remote transcription workers / `atlas.local`
|
|
4
|
+
|
|
5
|
+
Concept added on 2026-05-30: [`specs/remote-transcription-workers.md`](specs/remote-transcription-workers.md).
|
|
6
|
+
Option B is accepted for the next slice: `studio` remains the Voxhelm control plane, and `atlas.local` connects
|
|
7
|
+
outbound over an internal HTTP pull-worker API to claim leased batch transcription jobs, run local STT, upload
|
|
8
|
+
artifacts, and report results without changing producer-facing APIs. The implementation uses a remote-only
|
|
9
|
+
transcription queue and MinIO-backed artifact manifests from trusted workers. Goal completion requires a real
|
|
10
|
+
production python-podcast known-speaker diarized transcript to execute on `atlas.local`.
|
|
11
|
+
Server-side control-plane endpoints and remote dispatch mode are implemented as
|
|
12
|
+
of 2026-05-30, and the checkout-runnable `voxhelm-remote-worker` command is
|
|
13
|
+
implemented as of 2026-05-31. Public PyPI publication, deployment, edge
|
|
14
|
+
protection, and the production proof remain open.
|
|
15
|
+
|
|
16
|
+
### Implementation backlog
|
|
17
|
+
|
|
18
|
+
- [x] Normalize the accepted Option B design into `specs/interface-map.md` and keep D-23 in sync.
|
|
19
|
+
- [x] Add worker auth and liveness:
|
|
20
|
+
- worker token map, e.g. `VOXHELM_WORKER_TOKENS="atlas=..."`;
|
|
21
|
+
- `Worker` runtime/liveness model;
|
|
22
|
+
- `POST /v1/internal/workers/heartbeat`.
|
|
23
|
+
- [x] Add remote-pull job state:
|
|
24
|
+
- `execution_mode`, `assigned_worker_id`, `lease_token_hash`, `lease_expires_at`, `attempt_count`,
|
|
25
|
+
`max_attempts`, `last_worker_heartbeat_at`;
|
|
26
|
+
- SQLite-safe conditional claim update with affected-row check;
|
|
27
|
+
- attempt-scoped artifact prefixes such as `jobs/<job_id>/attempt-<n>/` to prevent zombie-worker S3 overwrites;
|
|
28
|
+
- all lease decisions use `studio` server time.
|
|
29
|
+
- [x] Add worker endpoints:
|
|
30
|
+
- `POST /v1/internal/work/claim`;
|
|
31
|
+
- `POST /v1/internal/work/{job_id}/heartbeat`;
|
|
32
|
+
- `POST /v1/internal/work/{job_id}/complete` with same-token idempotent retry after successful completion;
|
|
33
|
+
- `POST /v1/internal/work/{job_id}/fail`;
|
|
34
|
+
- heartbeat/fail/complete all verify assigned worker plus lease token hash.
|
|
35
|
+
- [x] Add `VOXHELM_TRANSCRIPTION_EXECUTION_MODE=django_tasks|remote_pull`; in `remote_pull` mode, transcribe jobs
|
|
36
|
+
are not enqueued to Django Tasks, startup requires complete shared S3 artifact settings, URL jobs are
|
|
37
|
+
allowlist-checked before queueing, and remote lease/poll/attempt settings must be positive.
|
|
38
|
+
- [ ] Add deployment/edge protection: block `/v1/internal/*` on public Traefik/macmini routes unless explicitly exposed on a private worker route.
|
|
39
|
+
- [ ] Add easy worker packaging/onboarding:
|
|
40
|
+
- [x] expose a checkout-runnable `voxhelm-remote-worker` command;
|
|
41
|
+
- [x] support checkout-based `uv run`;
|
|
42
|
+
- [x] require only Voxhelm base URL, worker id/token, artifact credentials, model/cache settings, and optional HF token;
|
|
43
|
+
- [ ] publish/install/run paths with `uv tool install` or `uvx` from public PyPI;
|
|
44
|
+
- [ ] document a repeatable new-machine setup with protected env file and launchd.
|
|
45
|
+
- [ ] Add the `atlas.local` worker command/process:
|
|
46
|
+
- [x] claim one job at a time;
|
|
47
|
+
- [x] materialize URL or staged-upload input;
|
|
48
|
+
- [x] store source/extracted artifacts in MinIO under attempt-scoped prefixes;
|
|
49
|
+
- [x] run local STT;
|
|
50
|
+
- [x] render requested transcript formats;
|
|
51
|
+
- [x] upload artifacts and post completion manifest;
|
|
52
|
+
- [ ] install and supervise the process on `atlas.local`.
|
|
53
|
+
- [ ] Add remote known-speaker diarization support:
|
|
54
|
+
- [x] choose Atlas-runs-known-speaker and document the choice;
|
|
55
|
+
- [x] support `pyannote_known_speaker` payloads from django-cast/python-podcast;
|
|
56
|
+
- [x] treat private known-speaker reference descriptors as trusted worker-only data and keep them out of logs;
|
|
57
|
+
- [x] ensure `transcript.speakers.json` and `result_metadata.diarization` match existing studio behavior in the worker contract;
|
|
58
|
+
- [ ] validate against `python-podcast/docs/known-speaker-runbook.rst` and `evals/known_speaker_results.md`.
|
|
59
|
+
- [x] Keep claim eligibility bounded by advertised capabilities: `job_type=transcribe`, batch only, and
|
|
60
|
+
known-speaker jobs only when the worker/hybrid path advertises the required pyannote/wespeaker/reference support.
|
|
61
|
+
- [ ] Preserve the C13 lane scheduler for any future local `studio` pull worker; `atlas.local` is outside that
|
|
62
|
+
host-local gate.
|
|
63
|
+
- [ ] Prove the goal with a real production python-podcast Generate Transcript run that is claimed by `atlas.local`,
|
|
64
|
+
populates the django-cast `Transcript.speakers` sidecar, clears the known-speaker quality bar, and leaves artifacts
|
|
65
|
+
retrievable through normal Voxhelm job artifact URLs.
|
|
66
|
+
|
|
67
|
+
### `atlas.local` readiness checks
|
|
68
|
+
|
|
69
|
+
Summary of RW-2 in `specs/remote-transcription-workers.md`:
|
|
70
|
+
|
|
71
|
+
- [ ] Verify `atlas.local` can reach Voxhelm on `studio` over the private network.
|
|
72
|
+
- [ ] Verify `atlas.local` can reach MinIO/S3 for trusted worker artifact upload.
|
|
73
|
+
- [ ] Verify `ffmpeg` and at least one STT backend/model locally on `atlas.local`.
|
|
74
|
+
- [ ] Verify pyannote/wespeaker or the chosen hybrid postprocessor path for known-speaker jobs.
|
|
75
|
+
- [ ] Record a short and long transcription smoke-test baseline on `atlas.local` before implementing the worker API.
|
|
76
|
+
- [ ] Decide launchd/service supervision and log location for the future remote worker process.
|
|
77
|
+
|
|
78
|
+
## Speaker diarization deployment and consumer follow-through
|
|
79
|
+
|
|
80
|
+
Status as of 2026-05-27: first Voxhelm diarization output slice is implemented and locally smoke-tested. A
|
|
81
|
+
3-minute clip from `pp_67` completed successfully with generic `Speaker 1` / `Speaker 2` labels in verbose JSON,
|
|
82
|
+
DOTe, and Podlove. Full-episode research on representative Python Podcast audio showed that anonymous pyannote
|
|
83
|
+
diarization with speaker-count hints can still merge a real speaker into another cluster, so generic diarization
|
|
84
|
+
should be treated as a fallback/debug signal for known-speaker podcasts. Known-speaker voiceprint experiments with
|
|
85
|
+
clean contributor reference material are the preferred follow-up direction.
|
|
86
|
+
|
|
87
|
+
### Voxhelm repo
|
|
88
|
+
|
|
89
|
+
- [ ] Clean up and commit the diarization implementation, including pyannote 4 `DiarizeOutput` unwrapping.
|
|
90
|
+
- [ ] Resolve the pending `jobs` migration check:
|
|
91
|
+
- `uv run python manage.py makemigrations --check --dry-run` currently reports an index-rename migration for `StagedMedia`.
|
|
92
|
+
- [ ] Close the pre-existing batch job idempotency race: concurrent submissions with the same `task_ref` and normalized payload can both miss the dedup query and create duplicate jobs. Consider a uniqueness strategy such as a normalized payload hash or locking the dedup path inside the create transaction.
|
|
93
|
+
- [ ] Keep `pyannote.audio` behind the optional `diarization` extra and verify fresh install with:
|
|
94
|
+
- `uv sync --extra diarization`
|
|
95
|
+
- [ ] Decide whether the current pyannote/torchcodec warning is acceptable in production logs. Voxhelm avoids torchcodec decoding by passing ffmpeg-decoded waveform data to pyannote, but pyannote still emits the import-time warning.
|
|
96
|
+
- [ ] Add diarization quality metadata and warnings for pathological label distributions, including tiny clusters and
|
|
97
|
+
distributions that contradict the requested speaker count.
|
|
98
|
+
- [ ] Prefer `exclusive_speaker_diarization` for transcript alignment when pyannote returns it.
|
|
99
|
+
- [ ] Add a non-default known-speaker postprocessor behind an explicit request flag. It should classify mastered
|
|
100
|
+
transcript segment windows against contributor reference embeddings, preserve anonymous pyannote labels as
|
|
101
|
+
fallback/debug metadata, and emit candidates, margins, confidence, and uncertainty flags.
|
|
102
|
+
- [ ] Keep `pyannote/wespeaker-voxceleb-resnet34-LM` as the first known-speaker embedding model candidate based on
|
|
103
|
+
the 2026-05-27 research spike; keep `pyannote/embedding` as a possible high-precision/lower-coverage alternative.
|
|
104
|
+
- [ ] Document required Hugging Face access for the configured pyannote model and gated dependencies:
|
|
105
|
+
- `pyannote/speaker-diarization-3.1`
|
|
106
|
+
- `pyannote/speaker-diarization-community-1`
|
|
107
|
+
- `pyannote/wespeaker-voxceleb-resnet34-LM`
|
|
108
|
+
- any additional gated repo pyannote reports at model load time.
|
|
109
|
+
|
|
110
|
+
### ops-library
|
|
111
|
+
|
|
112
|
+
- [ ] Extend `roles/voxhelm_deploy` defaults with diarization settings:
|
|
113
|
+
- `voxhelm_diarization_backend: "none"`
|
|
114
|
+
- `voxhelm_pyannote_model: "pyannote/speaker-diarization-3.1"`
|
|
115
|
+
- `voxhelm_huggingface_token: ""`
|
|
116
|
+
- [ ] Render these into `/etc/voxhelm/voxhelm.env`:
|
|
117
|
+
- `VOXHELM_DIARIZATION_BACKEND`
|
|
118
|
+
- `VOXHELM_PYANNOTE_MODEL`
|
|
119
|
+
- `VOXHELM_HUGGINGFACE_TOKEN`
|
|
120
|
+
- optionally `HF_TOKEN` with the same value for library compatibility.
|
|
121
|
+
- [ ] Make the role install optional dependencies when pyannote is enabled:
|
|
122
|
+
- current: `uv sync --frozen --no-dev`
|
|
123
|
+
- needed: `uv sync --frozen --no-dev --extra diarization`
|
|
124
|
+
- [ ] Ensure the token is only present in the protected env file, not inline in launchd plists or task logs.
|
|
125
|
+
- [ ] Ensure app and worker restart when the env file or dependency set changes.
|
|
126
|
+
|
|
127
|
+
### ops-control
|
|
128
|
+
|
|
129
|
+
- [ ] Add `huggingface_token` to `secrets/prod/voxhelm.yml.example`.
|
|
130
|
+
- [ ] Store the real token only in SOPS-encrypted `secrets/prod/voxhelm.yml`.
|
|
131
|
+
- [ ] Pass the secret to `local.ops_library.voxhelm_deploy` in `playbooks/deploy-voxhelm.yml`:
|
|
132
|
+
- `voxhelm_diarization_backend: "pyannote"`
|
|
133
|
+
- `voxhelm_pyannote_model: "pyannote/speaker-diarization-3.1"`
|
|
134
|
+
- `voxhelm_huggingface_token: "{{ service_secrets.huggingface_token | default('') }}"`
|
|
135
|
+
- [ ] Add deploy validation: if pyannote is enabled, fail clearly when the token is missing or still `CHANGEME`.
|
|
136
|
+
- [ ] Add/keep a short diarization smoke-test command in docs or runbook.
|
|
137
|
+
|
|
138
|
+
### django-cast
|
|
139
|
+
|
|
140
|
+
- [ ] Add private contributor voice references for reviewed clips or source ranges. These are private admin/editor
|
|
141
|
+
data, not public contributor profile metadata.
|
|
142
|
+
- [ ] Once Voxhelm has a known-speaker contract, send approved references for expected episode contributors using
|
|
143
|
+
private job artifacts, signed private URLs, or source ranges. Do not send public profile URLs.
|
|
144
|
+
- [ ] Store returned candidates, margins, confidence, raw diarization labels, and uncertainty flags as reviewable
|
|
145
|
+
transcript speaker state.
|
|
146
|
+
- [ ] Keep generic labels as fallback/debug metadata; do not auto-map `Speaker 1` to the first contributor.
|
|
147
|
+
- [ ] Decide how the current destructive contributor mapping workflow should evolve into non-destructive mapping or
|
|
148
|
+
reviewed suggestion state.
|
|
149
|
+
|
|
150
|
+
### python-podcast
|
|
151
|
+
|
|
152
|
+
- [ ] Bump/update the `django-cast` dependency after django-cast diarization support lands.
|
|
153
|
+
- [ ] Refresh `uv.lock` and apply django-cast migrations.
|
|
154
|
+
- [ ] Enable diarization through django-cast site settings or environment.
|
|
155
|
+
- [ ] Confirm the `cast_transcripts` worker remains the path for completion; full diarization must not block admin HTTP requests.
|
|
156
|
+
- [ ] Update deployment docs with expected long-running CPU-heavy diarization behavior.
|
|
157
|
+
|
|
158
|
+
### podcast-transcript CLI
|
|
159
|
+
|
|
160
|
+
- [ ] Optional: add a Voxhelm batch mode if CLI-generated transcripts should include speaker labels.
|
|
161
|
+
- [ ] Current `podcast-transcript` Voxhelm backend calls sync `/audio/transcriptions` and performs local DOTe/Podlove conversion, so it drops speaker labels.
|
voxhelm-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: voxhelm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shared local transcription service
|
|
5
|
+
Requires-Python: <3.15,>=3.14
|
|
6
|
+
Requires-Dist: boto3>=1.42.66
|
|
7
|
+
Requires-Dist: django-tasks-db>=0.12.0
|
|
8
|
+
Requires-Dist: django-tasks>=0.12.0
|
|
9
|
+
Requires-Dist: django<5.3,>=5.2
|
|
10
|
+
Requires-Dist: mlx-whisper; platform_system == 'Darwin' and platform_machine == 'arm64'
|
|
11
|
+
Requires-Dist: piper-tts<2,>=1.4.1; platform_system == 'Darwin' and platform_machine == 'arm64'
|
|
12
|
+
Requires-Dist: uvicorn>=0.35.0
|
|
13
|
+
Requires-Dist: wyoming<2,>=1.8.0
|
|
14
|
+
Provides-Extra: diarization
|
|
15
|
+
Requires-Dist: pyannote-audio>=4.0.4; extra == 'diarization'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# Voxhelm
|
|
19
|
+
|
|
20
|
+
Voxhelm is the shared local media-processing service for homelab consumers.
|
|
21
|
+
|
|
22
|
+
Milestone 1a provides a synchronous, OpenAI-compatible transcription API for
|
|
23
|
+
Archive:
|
|
24
|
+
|
|
25
|
+
- `GET /v1/health`
|
|
26
|
+
- `POST /v1/audio/transcriptions`
|
|
27
|
+
|
|
28
|
+
The current slice also adds the first Voxhelm-owned operator UI:
|
|
29
|
+
|
|
30
|
+
- `/` browser login and operator transcript console
|
|
31
|
+
- sync routing for audio URLs and uploaded audio
|
|
32
|
+
- batch routing for video URLs
|
|
33
|
+
- transcript downloads for `text`, `json`, `vtt`, `dote`, and `podlove`
|
|
34
|
+
- staged batch uploads for oversized/private/local audio via `POST /v1/uploads`
|
|
35
|
+
|
|
36
|
+
`whisper.cpp` inputs are normalized through `ffmpeg` to 16 kHz mono PCM WAV before
|
|
37
|
+
inference so AAC/M4A and other container/codec quirks do not leak into the backend.
|
|
38
|
+
|
|
39
|
+
## Local Development
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv sync
|
|
43
|
+
just test
|
|
44
|
+
uv run uvicorn config.asgi:application
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Required Environment
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
export DJANGO_SECRET_KEY="replace-me"
|
|
51
|
+
export VOXHELM_BEARER_TOKENS="archive=replace-me"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Optional settings:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
export VOXHELM_ALLOWED_HOSTS="localhost,127.0.0.1"
|
|
58
|
+
export VOXHELM_CSRF_TRUSTED_ORIGINS="https://voxhelm.example.com"
|
|
59
|
+
export VOXHELM_STT_BACKEND="whispercpp"
|
|
60
|
+
export VOXHELM_STT_FALLBACK_BACKEND="mlx"
|
|
61
|
+
export VOXHELM_MLX_MODEL="mlx-community/whisper-large-v3-mlx"
|
|
62
|
+
# Anti-hallucination decoding (defaults shown). Conditioning the Whisper decoder on
|
|
63
|
+
# previously generated text is the main cause of runaway repetition loops on long
|
|
64
|
+
# audio, so it is disabled by default. To restore upstream Whisper behaviour set the
|
|
65
|
+
# mlx flag to "true" and whisper.cpp max-context to "-1".
|
|
66
|
+
export VOXHELM_MLX_CONDITION_ON_PREVIOUS_TEXT="false"
|
|
67
|
+
export VOXHELM_WHISPERCPP_MODEL="ggml-large-v3.bin"
|
|
68
|
+
export VOXHELM_WHISPERCPP_BIN="/opt/homebrew/bin/whisper-cli"
|
|
69
|
+
export VOXHELM_WHISPERCPP_PROCESSORS="4"
|
|
70
|
+
# max-context 0 disables conditioning on previous text (the loop trigger); -1 = upstream
|
|
71
|
+
# default. suppress-nst drops non-speech tokens to curb hallucinations over music/silence.
|
|
72
|
+
export VOXHELM_WHISPERCPP_MAX_CONTEXT="0"
|
|
73
|
+
export VOXHELM_WHISPERCPP_SUPPRESS_NST="true"
|
|
74
|
+
export VOXHELM_WHISPERKIT_ENABLED="false"
|
|
75
|
+
export VOXHELM_WHISPERKIT_HOST="127.0.0.1"
|
|
76
|
+
export VOXHELM_WHISPERKIT_PORT="50060"
|
|
77
|
+
export VOXHELM_WHISPERKIT_BASE_URL="http://127.0.0.1:50060/v1"
|
|
78
|
+
export VOXHELM_WHISPERKIT_MODEL="large-v3-v20240930"
|
|
79
|
+
export VOXHELM_WHISPERKIT_AUDIO_ENCODER_COMPUTE_UNITS="cpuAndGPU"
|
|
80
|
+
export VOXHELM_WHISPERKIT_TEXT_DECODER_COMPUTE_UNITS="cpuAndGPU"
|
|
81
|
+
export VOXHELM_WHISPERKIT_CONCURRENT_WORKER_COUNT="8"
|
|
82
|
+
export VOXHELM_WHISPERKIT_CHUNKING_STRATEGY="vad"
|
|
83
|
+
export VOXHELM_WHISPERKIT_TIMEOUT_SECONDS="900"
|
|
84
|
+
export VOXHELM_STT_DEBUG_LOGGING="false"
|
|
85
|
+
export VOXHELM_DIARIZATION_BACKEND="none"
|
|
86
|
+
export VOXHELM_PYANNOTE_MODEL="pyannote/speaker-diarization-3.1"
|
|
87
|
+
export VOXHELM_HUGGINGFACE_TOKEN=""
|
|
88
|
+
export VOXHELM_MODEL_CACHE_DIR="$PWD/var/models"
|
|
89
|
+
export VOXHELM_WYOMING_STT_HOST="0.0.0.0"
|
|
90
|
+
export VOXHELM_WYOMING_STT_PORT="10300"
|
|
91
|
+
export VOXHELM_WYOMING_STT_BACKEND="mlx"
|
|
92
|
+
export VOXHELM_WYOMING_STT_MODEL=""
|
|
93
|
+
export VOXHELM_WYOMING_STT_LANGUAGE=""
|
|
94
|
+
export VOXHELM_WYOMING_STT_LANGUAGES="de,en"
|
|
95
|
+
export VOXHELM_WYOMING_STT_PROMPT=""
|
|
96
|
+
export VOXHELM_ALLOWED_URL_HOSTS="media.example.com"
|
|
97
|
+
export VOXHELM_TRUSTED_HTTP_HOSTS="internal.example.lan"
|
|
98
|
+
export VOXHELM_BATCH_MAX_STAGED_UPLOAD_BYTES="536870912"
|
|
99
|
+
export VOXHELM_STAGED_INPUT_RETENTION_SECONDS="86400"
|
|
100
|
+
export VOXHELM_TRANSCRIPTION_EXECUTION_MODE="django_tasks"
|
|
101
|
+
export VOXHELM_WORKER_TOKENS="atlas=replace-worker-token"
|
|
102
|
+
export VOXHELM_REMOTE_WORKER_LEASE_SECONDS="1800"
|
|
103
|
+
export VOXHELM_REMOTE_WORKER_POLL_SECONDS="5"
|
|
104
|
+
export VOXHELM_REMOTE_WORKER_MAX_ATTEMPTS="3"
|
|
105
|
+
export VOXHELM_BOOTSTRAP_OPERATOR_USERNAME="jochen"
|
|
106
|
+
export VOXHELM_BOOTSTRAP_OPERATOR_EMAIL=""
|
|
107
|
+
export VOXHELM_BOOTSTRAP_OPERATOR_PASSWORD="replace-me"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Bootstrap the initial operator account after migrations:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
uv run python manage.py bootstrap_operator --username jochen --password "replace-me"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Deploy-time note: the deployment layer should call the same in-app command with the real secret rather than creating the operator directly in a separate repo.
|
|
117
|
+
|
|
118
|
+
## OpenAI-Compatible API
|
|
119
|
+
|
|
120
|
+
Multipart upload:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
curl -X POST http://127.0.0.1:8000/v1/audio/transcriptions \
|
|
124
|
+
-H "Authorization: Bearer replace-me" \
|
|
125
|
+
-F "file=@sample.mp3" \
|
|
126
|
+
-F "model=gpt-4o-mini-transcribe"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
JSON URL input:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
curl -X POST http://127.0.0.1:8000/v1/audio/transcriptions \
|
|
133
|
+
-H "Authorization: Bearer replace-me" \
|
|
134
|
+
-H "Content-Type: application/json" \
|
|
135
|
+
-d '{"url":"https://example.com/sample.mp3","model":"whisper-1"}'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Batch Large-Input Contract
|
|
139
|
+
|
|
140
|
+
Stage oversized/private/local audio into Voxhelm first:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
curl -X POST http://127.0.0.1:8000/v1/uploads \
|
|
144
|
+
-H "Authorization: Bearer replace-me" \
|
|
145
|
+
-F "file=@large-private-episode.mp3"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Then submit the existing batch job with `input.kind=upload`:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
curl -X POST http://127.0.0.1:8000/v1/jobs \
|
|
152
|
+
-H "Authorization: Bearer replace-me" \
|
|
153
|
+
-H "Content-Type: application/json" \
|
|
154
|
+
-d '{
|
|
155
|
+
"job_type": "transcribe",
|
|
156
|
+
"priority": "normal",
|
|
157
|
+
"lane": "batch",
|
|
158
|
+
"backend": "auto",
|
|
159
|
+
"model": "auto",
|
|
160
|
+
"input": {"kind": "upload", "upload_id": "replace-me"},
|
|
161
|
+
"output": {"formats": ["text", "json"]},
|
|
162
|
+
"task_ref": "archive-item-123"
|
|
163
|
+
}'
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Staged uploads are stored in Voxhelm's configured artifact backend before
|
|
167
|
+
execution. Django Tasks jobs delete the temporary staged object immediately after
|
|
168
|
+
materialization; remote worker jobs delete it after a successful completion
|
|
169
|
+
records the worker-copied job-owned source artifact. Terminal remote failures
|
|
170
|
+
release the staged upload claim so the same `upload_id` can be retried until the
|
|
171
|
+
staged object expires. Unclaimed staged uploads expire after
|
|
172
|
+
`VOXHELM_STAGED_INPUT_RETENTION_SECONDS` and are opportunistically cleaned on
|
|
173
|
+
later staging/submission requests.
|
|
174
|
+
If the artifact backend, filesystem root, S3 endpoint, or bucket changes after
|
|
175
|
+
staging, submitters must stage the media again; Voxhelm rejects `upload_id`
|
|
176
|
+
values whose store identity no longer matches the active artifact store.
|
|
177
|
+
|
|
178
|
+
Current scope note: batch staged uploads are audio-only in this slice. URL
|
|
179
|
+
audio and URL video keep working on the existing path. Uploaded video and true
|
|
180
|
+
service-owned chunk splitting/stitching are still explicitly deferred.
|
|
181
|
+
|
|
182
|
+
## Batch Speaker Diarization
|
|
183
|
+
|
|
184
|
+
Batch `job_type=transcribe` requests can opt into speaker labels:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
curl -X POST http://127.0.0.1:8000/v1/jobs \
|
|
188
|
+
-H "Authorization: Bearer replace-me" \
|
|
189
|
+
-H "Content-Type: application/json" \
|
|
190
|
+
-d '{
|
|
191
|
+
"job_type": "transcribe",
|
|
192
|
+
"lane": "batch",
|
|
193
|
+
"backend": "auto",
|
|
194
|
+
"model": "auto",
|
|
195
|
+
"input": {"kind": "url", "url": "https://example.com/episode.mp3"},
|
|
196
|
+
"output": {"formats": ["json", "dote", "podlove", "vtt"]},
|
|
197
|
+
"diarization": {"enabled": true, "num_speakers": 4}
|
|
198
|
+
}'
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
When enabled, Voxhelm runs diarization after STT, aligns speaker turns to
|
|
202
|
+
transcript segments by largest timestamp overlap, and emits stable generic
|
|
203
|
+
labels such as `Speaker 1` and `Speaker 2`. If the expected speaker count is
|
|
204
|
+
known, pass `diarization.num_speakers`; alternatively pass
|
|
205
|
+
`diarization.min_speakers` and/or `diarization.max_speakers` as pyannote speaker
|
|
206
|
+
hints. Verbose JSON includes `speaker` only on labeled segments. DOTe fills
|
|
207
|
+
`speakerDesignation`; Podlove fills both `speaker` and `voice`. WebVTT
|
|
208
|
+
intentionally remains unchanged in this first slice.
|
|
209
|
+
|
|
210
|
+
The default `VOXHELM_DIARIZATION_BACKEND=none` makes requested diarization jobs
|
|
211
|
+
fail clearly instead of silently emitting unlabeled output. A guarded pyannote
|
|
212
|
+
adapter is available with `VOXHELM_DIARIZATION_BACKEND=pyannote`,
|
|
213
|
+
`VOXHELM_PYANNOTE_MODEL`, `VOXHELM_PYANNOTE_DEVICE`, and
|
|
214
|
+
`VOXHELM_HUGGINGFACE_TOKEN`. `VOXHELM_PYANNOTE_DEVICE=auto` uses Apple MPS when
|
|
215
|
+
available, then CUDA, then CPU. Install the optional model stack with:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
uv sync --extra diarization
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`VOXHELM_HUGGINGFACE_TOKEN` is required because the default pyannote pretrained
|
|
222
|
+
speaker-diarization pipeline is downloaded from Hugging Face and its model terms
|
|
223
|
+
must be accepted by the token-owning account before first use. With current
|
|
224
|
+
pyannote releases this may require access to the configured pipeline repository
|
|
225
|
+
and its gated component repositories, including
|
|
226
|
+
`pyannote/speaker-diarization-community-1`. If `VOXHELM_HUGGINGFACE_TOKEN` is
|
|
227
|
+
unset, Voxhelm also reads the common `HF_TOKEN` environment variable.
|
|
228
|
+
|
|
229
|
+
Production diarization requires all of the following:
|
|
230
|
+
|
|
231
|
+
- install dependencies with `uv sync --extra diarization`
|
|
232
|
+
- set `VOXHELM_DIARIZATION_BACKEND=pyannote`
|
|
233
|
+
- set `VOXHELM_HUGGINGFACE_TOKEN` or `HF_TOKEN`
|
|
234
|
+
- keep `VOXHELM_PYANNOTE_DEVICE=auto` or explicitly set `mps`, `cuda`, or `cpu`
|
|
235
|
+
- accept Hugging Face access for `pyannote/speaker-diarization-3.1`,
|
|
236
|
+
`pyannote/speaker-diarization-community-1`, and any gated dependency reported
|
|
237
|
+
by pyannote during model loading
|
|
238
|
+
|
|
239
|
+
The first successful run downloads model weights through the Hugging Face /
|
|
240
|
+
pyannote cache path and can take time. Long podcast episodes are CPU-heavy;
|
|
241
|
+
submit them as async batch jobs and inspect the worker logs rather than holding
|
|
242
|
+
an HTTP/admin request open.
|
|
243
|
+
|
|
244
|
+
Short smoke test:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
curl -X POST http://127.0.0.1:8000/v1/jobs \
|
|
248
|
+
-H "Authorization: Bearer replace-me" \
|
|
249
|
+
-H "Content-Type: application/json" \
|
|
250
|
+
-d '{
|
|
251
|
+
"job_type": "transcribe",
|
|
252
|
+
"lane": "batch",
|
|
253
|
+
"backend": "auto",
|
|
254
|
+
"model": "auto",
|
|
255
|
+
"input": {"kind": "url", "url": "https://example.com/short-audio.mp3"},
|
|
256
|
+
"output": {"formats": ["json", "dote", "podlove"]},
|
|
257
|
+
"diarization": {"enabled": true}
|
|
258
|
+
}'
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
After the job succeeds, verify the JSON, DOTe, and Podlove artifacts contain
|
|
262
|
+
`Speaker 1` / `Speaker 2` labels.
|
|
263
|
+
|
|
264
|
+
## Remote Pull Transcription Workers
|
|
265
|
+
|
|
266
|
+
Voxhelm can keep the producer-facing batch API unchanged while routing new
|
|
267
|
+
batch transcription jobs to trusted HTTP pull workers:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
export VOXHELM_TRANSCRIPTION_EXECUTION_MODE="remote_pull"
|
|
271
|
+
export VOXHELM_WORKER_TOKENS="atlas=replace-worker-token"
|
|
272
|
+
export VOXHELM_ARTIFACT_BACKEND="s3"
|
|
273
|
+
export VOXHELM_ARTIFACT_S3_ENDPOINT_URL="https://minio.example"
|
|
274
|
+
export VOXHELM_ARTIFACT_S3_ACCESS_KEY_ID="replace-me"
|
|
275
|
+
export VOXHELM_ARTIFACT_S3_SECRET_ACCESS_KEY="replace-me"
|
|
276
|
+
export VOXHELM_ARTIFACT_BUCKET="voxhelm"
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
In `remote_pull` mode, `job_type=transcribe` submissions through `POST /v1/jobs`
|
|
280
|
+
are persisted as normal queued Voxhelm jobs but are not enqueued into Django
|
|
281
|
+
Tasks. `job_type=synthesize` continues to use Django Tasks. Switching
|
|
282
|
+
`VOXHELM_TRANSCRIPTION_EXECUTION_MODE` back to `django_tasks` restores the
|
|
283
|
+
studio-only local transcription path without a migration.
|
|
284
|
+
`remote_pull` requires a valid `VOXHELM_WORKER_TOKENS` entry plus the shared S3
|
|
285
|
+
artifact backend and complete S3 endpoint, credential, and bucket settings at
|
|
286
|
+
startup; the local filesystem artifact backend is valid for `django_tasks` mode
|
|
287
|
+
only. `VOXHELM_TRANSCRIPTION_EXECUTION_MODE` must be either `django_tasks` or
|
|
288
|
+
`remote_pull`. The remote lease, poll, and max-attempt settings must be positive
|
|
289
|
+
integers. URL inputs are validated against `VOXHELM_ALLOWED_URL_HOSTS` when the
|
|
290
|
+
job is submitted, before a remote job can remain queued.
|
|
291
|
+
|
|
292
|
+
Worker endpoints are internal and use a separate bearer-token domain from
|
|
293
|
+
producer tokens. Startup configuration rejects any raw token value shared
|
|
294
|
+
between `VOXHELM_BEARER_TOKENS` and `VOXHELM_WORKER_TOKENS`:
|
|
295
|
+
|
|
296
|
+
- `POST /v1/internal/workers/heartbeat`
|
|
297
|
+
- `POST /v1/internal/work/claim`
|
|
298
|
+
- `POST /v1/internal/work/<job_id>/heartbeat`
|
|
299
|
+
- `POST /v1/internal/work/<job_id>/complete`
|
|
300
|
+
- `POST /v1/internal/work/<job_id>/fail`
|
|
301
|
+
|
|
302
|
+
Claims use studio server time, a bounded lease, and an atomic conditional
|
|
303
|
+
database update so concurrent workers cannot claim the same SQLite-backed job.
|
|
304
|
+
Workers at their advertised/requested active-claim capacity receive no new claim.
|
|
305
|
+
When a producer retries the same `task_ref`, Voxhelm reconciles any expired
|
|
306
|
+
remote lease first: attempts that remain move back to `queued`, while exhausted
|
|
307
|
+
attempts fail clearly.
|
|
308
|
+
Claim responses snapshot the attempt-scoped artifact prefix and non-secret
|
|
309
|
+
artifact-store identity on the job, and completion validates manifests against
|
|
310
|
+
that leased snapshot even if `VOXHELM_ARTIFACT_PREFIX`, the filesystem root, or
|
|
311
|
+
the S3 endpoint/bucket changes before the worker reports back. Voxhelm checks
|
|
312
|
+
artifact object existence and size before the short settlement transaction so a
|
|
313
|
+
slow object store does not hold SQLite's write lock. The committed artifact rows
|
|
314
|
+
keep the winning store identity so producer downloads continue to read from the
|
|
315
|
+
store that accepted the completion manifest.
|
|
316
|
+
Workers must advertise supported transcript `output_formats`, concrete STT
|
|
317
|
+
backend names, and concrete STT model names when claiming work. `auto`,
|
|
318
|
+
`whisper-1`, and `gpt-4o-mini-transcribe` match the configured default backend
|
|
319
|
+
and model; claim responses send that resolved concrete backend/model while
|
|
320
|
+
preserving the submitted aliases as `requested_backend`/`requested_model`.
|
|
321
|
+
Disabled workers are rejected before heartbeat state is updated.
|
|
322
|
+
Disabling a worker stops new claims but does not block completion, failure, or
|
|
323
|
+
lease heartbeat for a job the worker already owns. Worker completion accepts
|
|
324
|
+
only the currently assigned worker and lease token.
|
|
325
|
+
Completions must include a non-exposed job-owned source artifact plus the
|
|
326
|
+
requested transcript artifacts. Artifacts must be reported under the claimed
|
|
327
|
+
attempt prefix, for example `voxhelm/jobs/<job_id>/attempt-1/transcript.txt`;
|
|
328
|
+
Voxhelm verifies that each reported object exists in the configured artifact
|
|
329
|
+
store and that its stored size matches the manifest before marking the job
|
|
330
|
+
succeeded. The producer still downloads winning artifacts through
|
|
331
|
+
`GET /v1/jobs/<job_id>/artifacts/<name>`. Transcript and speaker-sidecar
|
|
332
|
+
artifacts must use the exact expected MIME types before Voxhelm exposes them.
|
|
333
|
+
|
|
334
|
+
Known-speaker jobs are claimable only by workers advertising the required
|
|
335
|
+
pyannote/wespeaker speaker-sidecar capability. Known-speaker reference URLs are
|
|
336
|
+
checked against `VOXHELM_ALLOWED_URL_HOSTS` at submission and again before a
|
|
337
|
+
remote claim is handed out. Uploaded known-speaker reference clips are rejected
|
|
338
|
+
before remote claim in this slice; use URL reference audio for remote-worker
|
|
339
|
+
jobs. Completion metadata stores only type-checked scalar worker fields plus
|
|
340
|
+
server-derived source, worker, attempt, and request metadata. URL completions
|
|
341
|
+
keep the server-owned job input URL as `source_url` and ignore worker-supplied
|
|
342
|
+
redirect URLs. URL-shaped strings, nested values, negative or non-finite
|
|
343
|
+
timings/summary metrics, and private known-speaker reference
|
|
344
|
+
URLs/ranges are not echoed through producer-visible job metadata. Speaker
|
|
345
|
+
sidecars are accepted only for known-speaker jobs.
|
|
346
|
+
|
|
347
|
+
Run a checkout-based worker on a trusted host with an env file containing the
|
|
348
|
+
worker token, Voxhelm URL, shared artifact credentials, local STT/model cache
|
|
349
|
+
settings, `VOXHELM_ALLOWED_URL_HOSTS`, and optional Hugging Face token:
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
uv run voxhelm-remote-worker \
|
|
353
|
+
--env-file /etc/voxhelm-worker/worker.env \
|
|
354
|
+
--once
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Use `--once` for smoke tests; omit it under launchd or another supervisor for
|
|
358
|
+
the long-running poll loop. The worker defaults to one active job, periodically
|
|
359
|
+
heartbeats the leased job while local inference runs, uploads the source,
|
|
360
|
+
optional extracted audio, requested transcript artifacts, and known-speaker
|
|
361
|
+
`transcript.speakers.json` sidecar under the claimed attempt prefix, then posts
|
|
362
|
+
the completion manifest.
|
|
363
|
+
|
|
364
|
+
Operational note: the application endpoints still require worker auth, but the
|
|
365
|
+
macmini/Traefik edge must also block `/v1/internal/*` on public routes unless a
|
|
366
|
+
deliberately private worker route is configured.
|
|
367
|
+
|
|
368
|
+
Current implementation status: the studio control-plane endpoints and
|
|
369
|
+
`remote_pull` dispatch switch are implemented, and `voxhelm-remote-worker` is
|
|
370
|
+
runnable from a repository checkout. Public PyPI publication, deployment on
|
|
371
|
+
`atlas.local`, edge protection, and the production python-podcast proof remain
|
|
372
|
+
follow-up work.
|
|
373
|
+
|
|
374
|
+
## Wyoming STT
|
|
375
|
+
|
|
376
|
+
Milestone 2 adds a separate Wyoming STT sidecar process for Home Assistant:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
uv run voxhelm-wyoming-stt
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
The sidecar reuses Voxhelm's existing STT backend layer. If
|
|
383
|
+
`VOXHELM_WYOMING_STT_MODEL` is unset, the sidecar uses the default model for
|
|
384
|
+
the configured Wyoming backend. The recommended interactive default is
|
|
385
|
+
`VOXHELM_WYOMING_STT_BACKEND=mlx`, which avoids the short-command silence
|
|
386
|
+
hallucinations seen with the current `whisper.cpp` setup on `studio`.
|
|
387
|
+
|
|
388
|
+
Set `VOXHELM_STT_DEBUG_LOGGING=true` when tuning the HA path. Voxhelm will emit
|
|
389
|
+
one structured `stt_debug` log line per transcription with the input audio
|
|
390
|
+
shape, requested and resolved backend/model/language, transcript preview, and
|
|
391
|
+
latency.
|
|
392
|
+
|
|
393
|
+
## Experimental WhisperKit Backend
|
|
394
|
+
|
|
395
|
+
WhisperKit is now available as an experimental STT backend, but it is still
|
|
396
|
+
non-default. Enable it explicitly with `VOXHELM_WHISPERKIT_ENABLED=true`, run a
|
|
397
|
+
local `whisperkit-cli serve` instance, and request either the explicit
|
|
398
|
+
`whisperkit` model alias or the configured WhisperKit model name. `whisper-1`,
|
|
399
|
+
`gpt-4o-mini-transcribe`, `auto`, and the deployed default still resolve to
|
|
400
|
+
`whisper.cpp` unless you intentionally reconfigure the backend.
|
|
401
|
+
|
|
402
|
+
The intended `studio` shape is the local server mode rather than a direct CLI
|
|
403
|
+
wrapper. The tuned sidecar settings currently map to:
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
whisperkit-cli serve \
|
|
407
|
+
--host 127.0.0.1 \
|
|
408
|
+
--port 50060 \
|
|
409
|
+
--model large-v3-v20240930 \
|
|
410
|
+
--audio-encoder-compute-units cpuAndGPU \
|
|
411
|
+
--text-decoder-compute-units cpuAndGPU \
|
|
412
|
+
--concurrent-worker-count 8 \
|
|
413
|
+
--chunking-strategy vad
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Operational caveat: keep treating WhisperKit as experimental on `studio`. The
|
|
417
|
+
benchmark follow-on kept it competitive, but the tuned long-form run still
|
|
418
|
+
logged a Metal GPU recovery error, so the deployed default remains
|
|
419
|
+
`whispercpp`.
|
|
420
|
+
|
|
421
|
+
Current limitation: the first C13 lane scheduler slice is cross-process and
|
|
422
|
+
does gate Voxhelm's HTTP, batch, and Wyoming entry points, but it does not
|
|
423
|
+
reach inside the WhisperKit sidecar itself. Once Voxhelm has admitted a
|
|
424
|
+
WhisperKit request, the sidecar's internal inference concurrency remains
|
|
425
|
+
outside that scheduler's direct control.
|