stenograf 0.2.0__tar.gz → 0.2.2__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.
- {stenograf-0.2.0 → stenograf-0.2.2}/.claude/skills/verify/SKILL.md +28 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/.github/workflows/release.yml +57 -5
- {stenograf-0.2.0 → stenograf-0.2.2}/CLAUDE.md +7 -2
- {stenograf-0.2.0 → stenograf-0.2.2}/PKG-INFO +23 -6
- {stenograf-0.2.0 → stenograf-0.2.2}/PLAN.md +205 -9
- {stenograf-0.2.0 → stenograf-0.2.2}/README.md +21 -5
- stenograf-0.2.2/install.ps1 +28 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/README.md +3 -1
- stenograf-0.2.2/native/stenodiar/build.ps1 +14 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/pyproject.toml +2 -1
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/capture/streaming.py +8 -3
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/capture/windows.py +6 -2
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/cli/doctor_cmd.py +39 -14
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/cli/run.py +10 -6
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/cli/settings_cmd.py +1 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/cli/start.py +1 -1
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/cli/transcribe.py +4 -1
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/diarization/speakrs.py +11 -5
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/loaders.py +58 -22
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/settings.py +24 -6
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/shortcut.py +46 -2
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/ui/flow.py +5 -1
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/ui/home.py +4 -1
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/ui/meeting.py +3 -1
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/ui/setup.py +16 -13
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/ui/transcribe.py +2 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_capture_streaming.py +37 -5
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_capture_windows.py +11 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_cli.py +98 -12
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_cli_notes.py +40 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_diarization_speakrs.py +18 -1
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_settings.py +11 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_shortcut.py +54 -7
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_ui.py +70 -23
- {stenograf-0.2.0 → stenograf-0.2.2}/uv.lock +1 -1
- {stenograf-0.2.0 → stenograf-0.2.2}/.github/workflows/ci.yml +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/.gitignore +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/LICENSE +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/PLAN-AEC.md +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/PLAN-CLEANUP.md +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/README.md +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/adjudicate.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/aec_rig.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/aec_score.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/backends.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/common.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/der.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/diarize.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/extract.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/live.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/manifest.example.json +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/parity.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/rttm.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/scan_languages.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/score.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/eval/transcribe.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/hatch_build.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/install.sh +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/helper/Info.plist +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/helper/build.sh +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/helper/main.swift +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/spike/Info.plist +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/spike/build.sh +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/spike/main.swift +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/.gitignore +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/Cargo.lock +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/Cargo.toml +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/build.sh +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/src/main.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/VENDOR.md +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/Cargo.toml +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/LICENSE +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/README.md +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/binarize.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/clustering/ahc.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/clustering/plda.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/clustering/vbx.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/clustering.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/coreml/array.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/coreml/path.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/coreml/runtime.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/coreml.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/embedding/batch.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/embedding/chunk.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/embedding/fbank.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/embedding/load/sessions.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/embedding/load.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/embedding/native/loaders.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/embedding/native.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/embedding/paths.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/embedding/run.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/embedding/session.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/embedding/tail.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/embedding/tensor.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/embedding.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/segmentation/native.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/segmentation/parallel/batch.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/segmentation/parallel/single.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/segmentation/parallel.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/segmentation/run.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/segmentation/tensor.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference/segmentation.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/inference.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/lib.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/linalg.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/metrics.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/models.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/builder.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/chunk_embedding/collect.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/chunk_embedding/error.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/chunk_embedding/gpu.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/chunk_embedding/orchestrate.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/chunk_embedding/prep.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/chunk_embedding.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/clustering.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/concurrent.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/config.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/post_inference.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/queued.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/tests.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/types/data.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/types/error.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/types/extract.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/types/layout.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline/types.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/pipeline.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/powerset.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/reconstruct.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/segment.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/native/stenodiar/vendor/speakrs/src/utils.rs +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/__init__.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/__main__.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/aec.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/asr/__init__.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/asr/base.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/asr/parakeet.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/asr/parakeet_onnx.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/asr/providers.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/asr/registry.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/asr/tokens.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/audio.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/capture/__init__.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/capture/base.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/capture/file.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/capture/linux.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/capture/macos.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/cli/__init__.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/cli/format.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/cli/notes.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/cli/profiles.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/config.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/diarization/__init__.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/diarization/base.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/diarization/sherpa.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/doctor.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/glossary.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/lid.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/live.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/models.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/notes/__init__.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/notes/backend.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/notes/command.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/notes/export.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/notes/generate.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/notes/mlx.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/notes/model.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/notes/ollama.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/notes/prompt.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/output.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/pipeline.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/profiles.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/recording.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/session.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/transcript.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/ui/__init__.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/ui/_fps.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/ui/app.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/ui/doctor.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/ui/notes.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/ui/settings.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/ui/widgets.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/vad.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/src/stenograf/view.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/conftest.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/fake_parec.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/fake_stenocap.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_aec.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_asr_parakeet.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_asr_parakeet_onnx.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_asr_providers.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_asr_registry.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_asr_tokens.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_audio.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_build_hook.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_capture_file.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_capture_linux.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_capture_macos.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_config.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_diarization_sherpa.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_diarization_sherpa_unit.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_doctor.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_echo_dedup.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_eval_der.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_glossary.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_lid.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_live.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_live_orchestration.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_meeting.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_models.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_notes_backends.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_notes_export.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_notes_generate.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_notes_model.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_output.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_pipeline.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_profiles.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_recording.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_session.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_transcript.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_vad.py +0 -0
- {stenograf-0.2.0 → stenograf-0.2.2}/tests/test_view.py +0 -0
|
@@ -8,6 +8,34 @@ description: How to run and observe stenograf end-to-end (TUI, live pass, finali
|
|
|
8
8
|
The product surface is the `steno` CLI / Textual TUI. Everything is drivable
|
|
9
9
|
headlessly via `--replay` — no mic, no system tap, no native helper needed.
|
|
10
10
|
|
|
11
|
+
## Headless TUI smoke — real stack, no TTY, any platform
|
|
12
|
+
|
|
13
|
+
CLI-mode green does NOT imply the launcher works: the CLI does its loader
|
|
14
|
+
work *before* Textual owns stdio, the launcher does it *under* a live app —
|
|
15
|
+
a code path no `--plain`/`--replay` run touches (this is how the win32
|
|
16
|
+
EBADF meeting-start crash shipped despite a fully validated CLI,
|
|
17
|
+
2026-07-12). Textual's `run_test` pilot drives the real launcher without a
|
|
18
|
+
terminal, so "the TUI needs eyes" is true only for rendering — plumbing is
|
|
19
|
+
verifiable headlessly:
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
app = StenografApp()
|
|
23
|
+
async with app.run_test(size=(100, 40)) as pilot:
|
|
24
|
+
await pilot.click("#start") # Home -> setup form
|
|
25
|
+
await pilot.pause()
|
|
26
|
+
app.screen._submit() # form defaults -> meeting screen
|
|
27
|
+
# ... pilot.pause() until MeetingScreen._phase is CAPTURING (model load
|
|
28
|
+
# takes seconds), press "q" to stop+finalize, then inspect
|
|
29
|
+
# app._notifications — a "Meeting failed" toast is the failure signal.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Patch `stenograf.output.default_output_home` to a scratch dir first. On a
|
|
33
|
+
machine with audio devices this runs the REAL provider + models end to end.
|
|
34
|
+
When a test must fake (CI has no audio), fake at the hardware boundary
|
|
35
|
+
(`capture.windows.default_devices`, the provider class), never at
|
|
36
|
+
`loaders.make_provider`/`load_backends` — faking the orchestration seam is
|
|
37
|
+
exactly what hid the EBADF crash from the UI suite.
|
|
38
|
+
|
|
11
39
|
## Build & launch
|
|
12
40
|
|
|
13
41
|
No build step; run from the repo with uv:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
name: release
|
|
2
2
|
|
|
3
|
-
# Tag v<version> → build the wheel matrix, smoke-test clean
|
|
4
|
-
#
|
|
5
|
-
# the artifacts to a GitHub release. workflow_dispatch runs everything
|
|
6
|
-
# the publish step, as a dry run.
|
|
3
|
+
# Tag v<version> → build the wheel matrix, smoke-test a clean install on every
|
|
4
|
+
# supported OS, publish to PyPI (Trusted Publishing/OIDC — no token secrets) and
|
|
5
|
+
# attach the artifacts to a GitHub release. workflow_dispatch runs everything
|
|
6
|
+
# except the publish step, as a dry run.
|
|
7
7
|
|
|
8
8
|
on:
|
|
9
9
|
push:
|
|
@@ -110,8 +110,60 @@ jobs:
|
|
|
110
110
|
# installs, the CLI runs, and the headless model download works.
|
|
111
111
|
- run: steno setup --models-only
|
|
112
112
|
|
|
113
|
+
smoke-windows:
|
|
114
|
+
# Windows ships the same pure wheel Linux does (no native helpers), so there
|
|
115
|
+
# is nothing extra to *build* — but nothing verified a clean Windows install
|
|
116
|
+
# before publish either, which is how 0.2.1 shipped a launcher that died on
|
|
117
|
+
# every meeting start. The launcher/TUI itself is covered by the headless
|
|
118
|
+
# real-stack smoke in ci.yml's windows unit job; this leg is the *installed
|
|
119
|
+
# artifact* check: the wheel installs, the CLI renders through a redirected
|
|
120
|
+
# stdout, and the pipeline runs end to end from a file.
|
|
121
|
+
needs: build-linux
|
|
122
|
+
runs-on: windows-latest
|
|
123
|
+
timeout-minutes: 45
|
|
124
|
+
defaults:
|
|
125
|
+
run:
|
|
126
|
+
shell: bash # wheel globs + $HOME; the runner ships Git bash
|
|
127
|
+
steps:
|
|
128
|
+
- uses: astral-sh/setup-uv@v8.3.2
|
|
129
|
+
- uses: actions/download-artifact@v8
|
|
130
|
+
with:
|
|
131
|
+
name: dist-linux
|
|
132
|
+
path: dist
|
|
133
|
+
# uv installs tool executables to ~/.local/bin on Windows too.
|
|
134
|
+
- run: echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
135
|
+
- run: uv tool install -p 3.13 ./dist/stenograf-*py3-none-any.whl
|
|
136
|
+
- run: steno --version
|
|
137
|
+
# ffmpeg is the bundled imageio-ffmpeg binary here — nothing to install.
|
|
138
|
+
- run: steno setup --models-only
|
|
139
|
+
- name: The report renders through a redirected stdout
|
|
140
|
+
# doctor *exits 1* on a runner by design: it has no audio endpoint, so
|
|
141
|
+
# the (non-optional) capture check fails. What this asserts is that the
|
|
142
|
+
# report renders at all — piped Windows stdout uses the legacy code page,
|
|
143
|
+
# and click.echo once crashed outright on the ✓/✗ glyphs (fixed by
|
|
144
|
+
# reconfiguring the streams with errors="replace"; this pins it down).
|
|
145
|
+
run: |
|
|
146
|
+
steno doctor > doctor.txt 2>&1 || true
|
|
147
|
+
cat doctor.txt
|
|
148
|
+
grep -q "ASR backend" doctor.txt
|
|
149
|
+
grep -q "ffmpeg" doctor.txt
|
|
150
|
+
- name: Pipeline smoke on a synthetic WAV
|
|
151
|
+
# Live WASAPI capture needs hardware no runner has; --replay drives the
|
|
152
|
+
# same pipeline from a file.
|
|
153
|
+
run: |
|
|
154
|
+
uv run --no-project --python 3.13 python - <<'EOF'
|
|
155
|
+
import math, struct, wave
|
|
156
|
+
with wave.open("smoke.wav", "wb") as w:
|
|
157
|
+
w.setnchannels(1); w.setsampwidth(2); w.setframerate(16000)
|
|
158
|
+
w.writeframes(b"".join(
|
|
159
|
+
struct.pack("<h", int(8000 * math.sin(2 * math.pi * 440 * i / 16000)))
|
|
160
|
+
for i in range(16000 * 3)))
|
|
161
|
+
EOF
|
|
162
|
+
steno start --replay smoke.wav --local 1 --remote 0 --lang en --out smoke-out
|
|
163
|
+
test -f smoke-out/transcript.json
|
|
164
|
+
|
|
113
165
|
publish:
|
|
114
|
-
needs: [smoke-macos, smoke-linux]
|
|
166
|
+
needs: [smoke-macos, smoke-linux, smoke-windows]
|
|
115
167
|
if: github.ref_type == 'tag'
|
|
116
168
|
runs-on: ubuntu-latest
|
|
117
169
|
timeout-minutes: 15
|
|
@@ -38,8 +38,13 @@ real PipeWire); GitHub Actions Ubuntu is the stable-distro CI reference.
|
|
|
38
38
|
import-broken, and Voxtral needs 4.x); generation must stay bound to the
|
|
39
39
|
thread that imported `mlx_lm` (guarded in code). **Linux CPU fallback =
|
|
40
40
|
llama-cpp-python — its wheels are OFF-PyPI** (extra index URL required);
|
|
41
|
-
Ollama stays the default when a GPU box is available. Windows (
|
|
42
|
-
|
|
41
|
+
Ollama stays the default when a GPU box is available. **Windows (decided
|
|
42
|
+
2026-07-12): Ollama-default, no in-process backend unless Ollama proves
|
|
43
|
+
insufficient — then llama-cpp-python CPU GGUF (Phi-4-mini), same off-PyPI
|
|
44
|
+
wheel friction as Linux.** onnxruntime-genai-directml was rejected
|
|
45
|
+
(DX12-GPU-only on the EOL DirectML EP; coexistence with our
|
|
46
|
+
onnxruntime-directml would actually have been fine — it depends on the
|
|
47
|
+
same flavor). Details: PLAN.md §5 Phase 6 Track D.
|
|
43
48
|
- **MLX on background threads**: materialize weights on the load thread or
|
|
44
49
|
inference dies with "no Stream(gpu, 0)"; verify MLX-threading changes
|
|
45
50
|
against the real backend, not mocks.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stenograf
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Accuracy-first local meeting transcription for German and English. Audio stays in RAM; only text is written.
|
|
5
5
|
Project-URL: Repository, https://github.com/daniel-om-weber/stenograf
|
|
6
6
|
Project-URL: Issues, https://github.com/daniel-om-weber/stenograf/issues
|
|
@@ -11,6 +11,7 @@ Keywords: diarization,local,meetings,privacy,speech-to-text,transcription
|
|
|
11
11
|
Classifier: Development Status :: 3 - Alpha
|
|
12
12
|
Classifier: Environment :: Console
|
|
13
13
|
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
15
|
Classifier: Operating System :: POSIX :: Linux
|
|
15
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
17
|
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
@@ -103,6 +104,19 @@ installs the current main branch; building from the repository compiles the
|
|
|
103
104
|
capture helper on your machine, which needs the Xcode command-line tools
|
|
104
105
|
(`xcode-select --install`).
|
|
105
106
|
|
|
107
|
+
### Windows
|
|
108
|
+
|
|
109
|
+
Same one command, run from PowerShell — installs uv if missing, then stenograf,
|
|
110
|
+
then the launcher:
|
|
111
|
+
|
|
112
|
+
```powershell
|
|
113
|
+
powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/daniel-om-weber/stenograf/main/install.ps1 | iex"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Windows never prompts for the microphone, so capture stays silent until you flip
|
|
117
|
+
the toggle under Settings > Privacy & security > Microphone. Windows Terminal is
|
|
118
|
+
recommended for the live-caption TUI.
|
|
119
|
+
|
|
106
120
|
### From a checkout
|
|
107
121
|
|
|
108
122
|
```sh
|
|
@@ -236,11 +250,14 @@ dir = "~/Documents/Obsidian/Meetings" # optional: also write one combined
|
|
|
236
250
|
With `[notes.export]` set, every summarized meeting also produces a single
|
|
237
251
|
self-contained markdown note (frontmatter, summary, action items, collapsible
|
|
238
252
|
transcript) — drop the dir inside an Obsidian vault and meetings file
|
|
239
|
-
themselves.
|
|
240
|
-
appends your house style to the built-in prompt,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
253
|
+
themselves. Three more levers in `[notes]`: `instructions = "~/style.md"`
|
|
254
|
+
appends your house style to the built-in prompt, `thinking = false` skips the
|
|
255
|
+
mlx model's reasoning pass (faster, less careful), and `auto = true` makes
|
|
256
|
+
notes the default for every meeting — `steno start` summarizes without
|
|
257
|
+
`--notes`, and the launcher's "Generate notes after the meeting" switch starts
|
|
258
|
+
on. Out of the box notes never run unless you ask (`--notes`, the launcher
|
|
259
|
+
switch, or `steno notes` afterwards); with `auto = true`, `--no-notes` still
|
|
260
|
+
skips them for one run. A notes failure never touches the transcript.
|
|
244
261
|
|
|
245
262
|
## Naming speakers across meetings
|
|
246
263
|
|
|
@@ -938,15 +938,211 @@ SIGINT-based macOS-provider teardown now skipped on win32) and **one real produc
|
|
|
938
938
|
bug**: on Windows, piped/redirected output uses the legacy code page and `click.echo`
|
|
939
939
|
crashed on ✓/← — the CLI entry now reconfigures stdout/stderr with
|
|
940
940
|
`errors="replace"` on win32. `livekit` win_amd64 wheels confirmed (1.1.13 installs;
|
|
941
|
-
AEC behaviour on Windows hardware still to be exercised).
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
941
|
+
AEC behaviour on Windows hardware still to be exercised).
|
|
942
|
+
|
|
943
|
+
**Closed since the capture track (recorded 2026-07-12):**
|
|
944
|
+
|
|
945
|
+
- **ASR GPU acceleration (2026-07-11, commit 293545f):** `[asr] provider` setting /
|
|
946
|
+
`STENOGRAF_ASR_PROVIDER` (`cpu` default | `dml` | `cuda` | `auto`) with an
|
|
947
|
+
availability pre-check + canary decode + CPU fallback (`asr/parakeet_onnx.py`,
|
|
948
|
+
`asr/providers.py`). pyproject ships `onnxruntime-directml` on win32 via marker —
|
|
949
|
+
every ORT flavor installs the same package directory and silently clobbers the
|
|
950
|
+
others (verified), so extras can't express the swap. Measured on an RTX 4080 SUPER:
|
|
951
|
+
DML transcripts byte-identical to CPU, 107× vs 16× realtime. Gotcha for future EPs:
|
|
952
|
+
ORT does **not** raise on an unlisted provider — it warns and runs on the rest, so
|
|
953
|
+
always pre-check `get_available_providers()`.
|
|
954
|
+
- The `windows-latest` CI job (unit suite; pyright stays macOS-only for the
|
|
955
|
+
marker-dep imports) and a `cargo check` leg for stenodiar's `cuda` feature.
|
|
956
|
+
- The `Operating System :: Microsoft :: Windows` classifier (2026-07-12).
|
|
957
|
+
- `steno setup` messaging (2026-07-12): a win32 branch reads `mic_access_blocked()`
|
|
958
|
+
up front and fails before the model download, mirroring the macOS grant step —
|
|
959
|
+
and, when allowed, tells the user Windows will never show a prompt.
|
|
960
|
+
- The `shortcut.py` win32 branch (2026-07-12): a Desktop `Stenograf.cmd` — the
|
|
961
|
+
"simpler" option from the two scoped here, since `steno` is a console app (a
|
|
962
|
+
`.lnk` would open a console window anyway) and a text wrapper regenerates without
|
|
963
|
+
COM. The Desktop is resolved through the shell's `User Shell Folders` registry key
|
|
964
|
+
(OneDrive folder backup redirects it on a large share of Windows 11 machines;
|
|
965
|
+
`~/Desktop` would be an invisible decoy there), and the wrapper ends in
|
|
966
|
+
`if errorlevel 1 pause` so a startup crash stays readable instead of vanishing
|
|
967
|
+
with the console window.
|
|
968
|
+
|
|
969
|
+
What remains for **full Windows support** is planned in the next subsection.
|
|
970
|
+
|
|
971
|
+
### Phase 6 — remaining-work plan (planned 2026-07-12)
|
|
972
|
+
|
|
973
|
+
**Definition of done:** a fresh Windows 11 machine goes one-liner install →
|
|
974
|
+
`steno setup` → live meeting → diarized transcript + notes in
|
|
975
|
+
`Documents\Meetings`, at parity with Linux, with no manual step beyond the
|
|
976
|
+
mic privacy toggle. Four tracks: **A gates the "supported" claim**; B, C, D
|
|
977
|
+
are independent of each other and of A (but A first — everything else builds
|
|
978
|
+
on a verified core).
|
|
979
|
+
|
|
980
|
+
**Track A — real-hardware validation (the gate).**
|
|
981
|
+
|
|
982
|
+
State (desktop, 2026-07-12, Ryzen 9 5900X + RTX 4080 SUPER, Sharkoon Gaming
|
|
983
|
+
DAC Pro S, output = headphones): **the automatable core is verified on the
|
|
984
|
+
desktop; items 1–2 pass, item 3's live path smoke-passes, the ≥30-min/AEC gate
|
|
985
|
+
and the notebook re-run stay open.** Measured on a 56.4 s piper-TTS clip
|
|
986
|
+
(en_US-lessac-medium, the ci.yml sentence with "provisioning"/"October"):
|
|
987
|
+
|
|
988
|
+
- **Item 1 — transcribe, PASS.** Warm CPU run (weights pre-cached): 8.3 s wall
|
|
989
|
+
→ 6.8× RT end-to-end, but that is model-load-bound on a short clip; the
|
|
990
|
+
tool's own transcribe-only figure is **14.9× RT** (3.8 s). Sentinels present;
|
|
991
|
+
"backend"→"backhand" is the known TTS mishearing. `provider = "dml"`
|
|
992
|
+
(`STENOGRAF_ASR_PROVIDER=dml`) engaged ("asr: accelerated (DirectML)"), no
|
|
993
|
+
silent CPU fallback: transcript **byte-identical** to CPU (SHA-256 match on
|
|
994
|
+
transcript.{txt,md,json}), transcribe-only **35.3× RT** (1.6 s) →
|
|
995
|
+
**2.37× DML speedup** over CPU compute. Output files are clean UTF-8/ASCII,
|
|
996
|
+
no code-page mangling (the only mojibake seen — a UTF-16 BOM and a `✓`→`?`
|
|
997
|
+
glyph — is PowerShell's `*>` redirect + console codepage, not the tool).
|
|
998
|
+
- **Item 2 — `steno start --replay`, PASS.** End-to-end via the `verify` flow
|
|
999
|
+
(mic replay, in-room): live caption emitted, finalize + transcript written,
|
|
1000
|
+
both sentinels present.
|
|
1001
|
+
- **Item 3 (desktop smoke) — live WASAPI capture, PASS.** `--local 0 --remote
|
|
1002
|
+
1` captured the Sharkoon **loopback** render stream (default output =
|
|
1003
|
+
headphones, yet loopback still taps render); both sentinels present on a
|
|
1004
|
+
single clean playback. Back-to-back double playback reorders/garbles the
|
|
1005
|
+
ASR (a playback-overlap harness artifact, not a capture bug). Bonus
|
|
1006
|
+
`--local 1 --remote 1` mic+system smoke: **both channels captured**, **AEC
|
|
1007
|
+
armed** ("echo cancellation: on (mic cancelled against system audio)"),
|
|
1008
|
+
stderr clean — **no far-end-missing / armed-backstop warnings**, and the
|
|
1009
|
+
**silent-mic watchdog stayed quiet** (no false-positive). With headphones
|
|
1010
|
+
output the mic heard nothing intelligible and all content arrived via the
|
|
1011
|
+
system channel — expected on this box.
|
|
1012
|
+
- **Launcher meeting start was broken on Windows — found by the by-eye check,
|
|
1013
|
+
fixed 2026-07-12:** every meeting died instantly with `[Errno 9] Bad file
|
|
1014
|
+
descriptor` — click.echo under Textual's redirected stdio trips click's
|
|
1015
|
+
Windows-only console probe (`msvcrt.get_osfhandle` on the proxy fd → EBADF)
|
|
1016
|
+
at `make_provider`'s capture-device line. The loader seams now take
|
|
1017
|
+
`announce=` (None = click CLI behavior; the TUIs pass their status sinks),
|
|
1018
|
+
which also surfaces real loader progress in the meeting header. Verified by
|
|
1019
|
+
driving the real launcher headlessly: capture → finalize → saved.
|
|
1020
|
+
- **Still open (needs the notebook, real speakers):** the ≥30-min
|
|
1021
|
+
**speakers-not-headphones** AEC meeting (far-end re-anchor / echo-leak across
|
|
1022
|
+
long system-silence gaps — item 3's AEC bullet and item 4's long run); the
|
|
1023
|
+
**TUI-by-eye** check in Windows Terminal (live captions, resize, clean Ctrl-C);
|
|
1024
|
+
and re-running items 1–3 on the **notebook's GPU** (different DML vendor tier).
|
|
1025
|
+
|
|
1026
|
+
1. Real `steno transcribe` on a meeting-length recording with downloaded
|
|
1027
|
+
models (fp32 ONNX, CPU): correctness + RTF on the notebook. Repeat with
|
|
1028
|
+
`[asr] provider = "dml"` — byte-identity + speedup were verified on the
|
|
1029
|
+
desktop's RTX 4080; re-confirm on the notebook's GPU (different vendor
|
|
1030
|
+
tier is exactly what DML is for).
|
|
1031
|
+
2. `steno start --replay` end to end (the `verify` skill flow) on Windows.
|
|
1032
|
+
3. A real live meeting on the notebook — **speakers, not headphones**, both
|
|
1033
|
+
channels captured. Checks, in order of risk:
|
|
1034
|
+
- **AEC**: livekit's APM is installed but unexercised on Windows, and
|
|
1035
|
+
laptop mics genuinely hear the speakers. Watch `far_end_missing_ticks`
|
|
1036
|
+
and the armed-backstop warning across WASAPI loopback silence gaps; a
|
|
1037
|
+
0.5 s re-anchor moves the far-end alignment, so confirm the AEC
|
|
1038
|
+
re-converges after long system silence instead of leaking echo.
|
|
1039
|
+
- TUI in Windows Terminal: live captions, resize, clean Ctrl-C shutdown.
|
|
1040
|
+
- The silent-mic watchdog must not false-positive on real hardware.
|
|
1041
|
+
- Finalize with diarization (sherpa known-count path) + notes via Ollama;
|
|
1042
|
+
transcript + notes land in `Documents\Meetings`, no code-page mangling.
|
|
1043
|
+
4. Run the checklist **twice**: one short smoke, one ≥30-min meeting with
|
|
1044
|
+
long system-silence stretches — the gap/zero-fill/re-anchor path only
|
|
1045
|
+
shows up in the long one. Record results here; anything broken becomes a
|
|
1046
|
+
task in this section.
|
|
1047
|
+
|
|
1048
|
+
**Track B — stenodiar (estimated speaker counts).**
|
|
1049
|
+
|
|
1050
|
+
State: **the patched CPU build is measured on Windows (2026-07-12, desktop
|
|
1051
|
+
Ryzen 9 5900X 12C/24T).** Prior state (2026-07-11): CPU 3.0× RT but still
|
|
1052
|
+
~1.1 cores, which **predated** the two vendored speakrs patches; CUDA 60× RT
|
|
1053
|
+
works via pip `nvidia-*-cu12` DLLs on PATH + `onnxruntime_providers_cuda.dll`
|
|
1054
|
+
beside the exe; rustup + VS 2022 Build Tools are installed there. The CPU exe
|
|
1055
|
+
is self-contained (~40 MB, ORT statically linked).
|
|
1056
|
+
|
|
1057
|
+
1. **Done.** Rebuilt from the patched vendor tree (`native/stenodiar/vendor/`,
|
|
1058
|
+
threads + model-list patches; CPU default features, `cargo build --release
|
|
1059
|
+
--locked`) and re-measured on a 300 s mono-16 kHz cut of AMI ES2004a
|
|
1060
|
+
Mix-Headset: **34.6–35.1 s wall/segment → 8.6× RT, ~6.0 cores** (208 s
|
|
1061
|
+
CPU-time / 34.6 s wall), 3 speakers / 46 turns, byte-identical across the
|
|
1062
|
+
two timed back-to-back runs. Right at the Linux **8.2×** figure (the
|
|
1063
|
+
intra-op-thread patch is the same lever; embedding threads still cap at 8),
|
|
1064
|
+
and a ~2.9× jump over the pre-patch Windows 3.0× — well past the ≥4× bar.
|
|
1065
|
+
2. Shipping decision (recommendation, not yet implemented): **GitHub Releases
|
|
1066
|
+
attachment + a documented drop location**, not wheel bundling — a 40 MB
|
|
1067
|
+
binary for an optional, sherpa-fallback feature does not belong in every
|
|
1068
|
+
platform wheel, and `hatch_build.py` already keeps stenodiar off non-mac
|
|
1069
|
+
wheels. `find_stenodiar` now handles the `.exe` suffix on win32 (via
|
|
1070
|
+
`_HELPER_FILENAME`) and, as a dev/drop fallback, also checks
|
|
1071
|
+
`native/stenodiar/target/release/`; the packaged-bin lookup
|
|
1072
|
+
(`stenograf/bin/stenodiar.exe`) and `STENOGRAF_DIAR_HELPER` override are the
|
|
1073
|
+
two Release-install landing spots.
|
|
1074
|
+
3. **Done.** `build.ps1` twin of `build.sh` added (CPU/ORT default, `--locked`,
|
|
1075
|
+
copies `stenodiar.exe` next to the script; CUDA stays a manual `--features
|
|
1076
|
+
cuda`); `native/README.md` and the `find_stenodiar` not-found message point
|
|
1077
|
+
Windows at it.
|
|
1078
|
+
|
|
1079
|
+
Exit: **met** — `steno doctor` finds the helper on Windows (the target/release
|
|
1080
|
+
fallback discovers a plain `cargo build`), and estimated-count diarization runs
|
|
1081
|
+
at 8.6× RT (≥4× bar cleared).
|
|
1082
|
+
|
|
1083
|
+
**Track C — install story (the Phase-7 Windows leg).**
|
|
1084
|
+
|
|
1085
|
+
1. `install.ps1` mirroring `install.sh`: install uv via the astral installer
|
|
1086
|
+
(`irm https://astral.sh/uv/install.ps1 | iex` — **not** winget, whose uv
|
|
1087
|
+
lands off-PATH), then `uv tool install --upgrade stenograf` (the
|
|
1088
|
+
`<3.14` python cap already steers uv to 3.12/3.13), then `steno setup`.
|
|
1089
|
+
Documented invocation is the piped one-liner — a downloaded, double-clicked
|
|
1090
|
+
`.ps1` hits execution policy.
|
|
1091
|
+
2. CI parity with `install.sh`'s `sh -n` test: syntax-check via
|
|
1092
|
+
`[ScriptBlock]::Create((Get-Content -Raw install.ps1))` (runs on the
|
|
1093
|
+
windows-latest job, skipped elsewhere) + the same content asserts
|
|
1094
|
+
(`tool install --upgrade stenograf`, ends in `setup`).
|
|
1095
|
+
3. README: a Windows install section — the one-liner, the privacy-toggle
|
|
1096
|
+
note, Windows Terminal recommended.
|
|
1097
|
+
4. **Done (2026-07-13).** `release.yml` gained a `smoke-windows` leg and
|
|
1098
|
+
`publish` now waits on it: a clean `uv tool install` of the pure wheel on
|
|
1099
|
+
`windows-latest`, then `steno --version`, `setup --models-only`, a doctor
|
|
1100
|
+
render, and a `--replay` pipeline run asserting `transcript.json`. Windows
|
|
1101
|
+
builds nothing of its own (it ships the same pure wheel as Linux), so the
|
|
1102
|
+
gap was never in *building* — it was that **no Windows install was exercised
|
|
1103
|
+
before PyPI**, which is how 0.2.1 published a launcher that died on every
|
|
1104
|
+
meeting start. Two notes for whoever touches this job: `steno doctor` exits
|
|
1105
|
+
**1** on a runner by design (no audio endpoint → the non-optional capture
|
|
1106
|
+
check fails), so the step tolerates that and asserts only that the report
|
|
1107
|
+
*renders* — piped Windows stdout is legacy-code-page, the bug class that once
|
|
1108
|
+
crashed `click.echo` on the ✓ glyph; and the replay clip is a 440 Hz tone, so
|
|
1109
|
+
it finalizes to **0 entries** — the assertion is that the transcript is
|
|
1110
|
+
written at all, not its content. The launcher/TUI stays covered by ci.yml's
|
|
1111
|
+
windows unit job (headless real-stack smoke), not here.
|
|
1112
|
+
|
|
1113
|
+
Exit: on a fresh VM (or fresh user account), the one-liner reaches
|
|
1114
|
+
"setup complete" and drops the Desktop launcher.
|
|
1115
|
+
|
|
1116
|
+
**Track D — on-device notes backend — DECIDED 2026-07-12 (no code; the
|
|
1117
|
+
build gate stays "Ollama proves insufficient").**
|
|
1118
|
+
|
|
1119
|
+
Re-researched against the DirectML EOL. The clobber fear was unfounded:
|
|
1120
|
+
`onnxruntime-genai-directml` (0.14.1, 2026-06-02, still released) does not
|
|
1121
|
+
bundle its own ORT — it pip-depends on **`onnxruntime-directml`**, the very
|
|
1122
|
+
flavor the ASR already ships, so it coexists cleanly (the CPU flavor
|
|
1123
|
+
`onnxruntime-genai` depends on base `onnxruntime` and IS disqualified by the
|
|
1124
|
+
one-flavor rule). **Rejected anyway**: DX12-GPU-only with no CPU fallback,
|
|
1125
|
+
built entirely on the EOL DirectML EP — the wrong bet when Microsoft's
|
|
1126
|
+
investment moved to Windows ML. The successors aren't pip-reachable today:
|
|
1127
|
+
Windows ML genai is NuGet/C#-only Preview (no Python wheel); Foundry Local
|
|
1128
|
+
(GA 2026-06) requires a DX12 GPU and pulls `onnxruntime-core`/-genai (real
|
|
1129
|
+
conflict risk), an Ollama-class service rather than an in-process analog.
|
|
1130
|
+
|
|
1131
|
+
**Decision: stay Ollama-default on Windows. If an in-process pip backend is
|
|
1132
|
+
ever needed, use llama-cpp-python (CPU GGUF)** — the same fallback CLAUDE.md
|
|
1133
|
+
already records for Linux CPU, same off-PyPI extra-index friction, zero ORT
|
|
1134
|
+
entanglement. Caveat: the official wheel index's Windows cp313 CPU wheel is
|
|
1135
|
+
stale (0.3.2) — build from source (VS Build Tools installed on the desktop)
|
|
1136
|
+
or a third-party index. Model: Phi-4-mini stays fine as Q4 GGUF (Qwen2.5-3B /
|
|
1137
|
+
Llama-3.2-3B as smaller CPU-friendly alternatives). Revisit the ONNX route
|
|
1138
|
+
only when Windows ML genai ships an out-of-preview Python wheel.
|
|
1139
|
+
|
|
1140
|
+
**Watch-items (not tasks):** `onnxruntime-directml` wheels ceasing to track
|
|
1141
|
+
ORT patch releases (swap the marker back to plain `onnxruntime`; the provider
|
|
1142
|
+
setting degrades to CPU with a warning — designed in); mid-meeting default-
|
|
1143
|
+
device switches stay unfollowed on Windows (WASAPI has no `@DEFAULT_MONITOR@`
|
|
1144
|
+
alias — accepted asymmetry vs Linux); Windows ML as the eventual multi-vendor
|
|
1145
|
+
EP path for ASR, once dynamic-shape conformer models are validated there.
|
|
950
1146
|
|
|
951
1147
|
---
|
|
952
1148
|
|
|
@@ -68,6 +68,19 @@ installs the current main branch; building from the repository compiles the
|
|
|
68
68
|
capture helper on your machine, which needs the Xcode command-line tools
|
|
69
69
|
(`xcode-select --install`).
|
|
70
70
|
|
|
71
|
+
### Windows
|
|
72
|
+
|
|
73
|
+
Same one command, run from PowerShell — installs uv if missing, then stenograf,
|
|
74
|
+
then the launcher:
|
|
75
|
+
|
|
76
|
+
```powershell
|
|
77
|
+
powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/daniel-om-weber/stenograf/main/install.ps1 | iex"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Windows never prompts for the microphone, so capture stays silent until you flip
|
|
81
|
+
the toggle under Settings > Privacy & security > Microphone. Windows Terminal is
|
|
82
|
+
recommended for the live-caption TUI.
|
|
83
|
+
|
|
71
84
|
### From a checkout
|
|
72
85
|
|
|
73
86
|
```sh
|
|
@@ -201,11 +214,14 @@ dir = "~/Documents/Obsidian/Meetings" # optional: also write one combined
|
|
|
201
214
|
With `[notes.export]` set, every summarized meeting also produces a single
|
|
202
215
|
self-contained markdown note (frontmatter, summary, action items, collapsible
|
|
203
216
|
transcript) — drop the dir inside an Obsidian vault and meetings file
|
|
204
|
-
themselves.
|
|
205
|
-
appends your house style to the built-in prompt,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
217
|
+
themselves. Three more levers in `[notes]`: `instructions = "~/style.md"`
|
|
218
|
+
appends your house style to the built-in prompt, `thinking = false` skips the
|
|
219
|
+
mlx model's reasoning pass (faster, less careful), and `auto = true` makes
|
|
220
|
+
notes the default for every meeting — `steno start` summarizes without
|
|
221
|
+
`--notes`, and the launcher's "Generate notes after the meeting" switch starts
|
|
222
|
+
on. Out of the box notes never run unless you ask (`--notes`, the launcher
|
|
223
|
+
switch, or `steno notes` afterwards); with `auto = true`, `--no-notes` still
|
|
224
|
+
skips them for one run. A notes failure never touches the transcript.
|
|
209
225
|
|
|
210
226
|
## Naming speakers across meetings
|
|
211
227
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# stenograf installer — one command sets up everything:
|
|
2
|
+
#
|
|
3
|
+
# powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/daniel-om-weber/stenograf/main/install.ps1 | iex"
|
|
4
|
+
#
|
|
5
|
+
# Installs uv if missing, installs stenograf as a uv tool, then runs
|
|
6
|
+
# `steno setup` (permission prompts, desktop launcher, model downloads).
|
|
7
|
+
# Safe to re-run: every step is idempotent and re-running upgrades stenograf.
|
|
8
|
+
$ErrorActionPreference = "Stop"
|
|
9
|
+
|
|
10
|
+
$uvHome = Join-Path $env:USERPROFILE ".local\bin\uv.exe"
|
|
11
|
+
if (-not (Get-Command uv -ErrorAction SilentlyContinue) -and -not (Test-Path $uvHome)) {
|
|
12
|
+
Write-Host "installing uv (https://docs.astral.sh/uv/) ..."
|
|
13
|
+
powershell -ExecutionPolicy Bypass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
14
|
+
}
|
|
15
|
+
$uv = (Get-Command uv -ErrorAction SilentlyContinue).Source
|
|
16
|
+
if (-not $uv) { $uv = $uvHome }
|
|
17
|
+
|
|
18
|
+
& $uv tool install --upgrade stenograf
|
|
19
|
+
|
|
20
|
+
# A freshly created uv bin dir isn't on this shell's PATH yet — ask uv where it is.
|
|
21
|
+
$steno = (Get-Command steno -ErrorAction SilentlyContinue).Source
|
|
22
|
+
if (-not $steno) { $steno = Join-Path (& $uv tool dir --bin) "steno.exe" }
|
|
23
|
+
|
|
24
|
+
& $steno setup
|
|
25
|
+
|
|
26
|
+
Write-Host ""
|
|
27
|
+
Write-Host "stenograf is installed. Start it from the desktop launcher above,"
|
|
28
|
+
Write-Host "or run: steno"
|
|
@@ -11,7 +11,9 @@ VBx can; stenograf routes estimate-mode diarization through it and known
|
|
|
11
11
|
counts through sherpa (see `stenograf/diarization/speakrs.py`). Raw mono
|
|
12
12
|
16 kHz s16le PCM goes in on stdin, JSON speaker turns come out on stdout.
|
|
13
13
|
Build with `stenodiar/build.sh` (needs a Rust toolchain: `brew install rust`);
|
|
14
|
-
|
|
14
|
+
on Windows use `stenodiar/build.ps1` (rustup + VS Build Tools; CPU/ORT default,
|
|
15
|
+
CUDA a manual `--features cuda` opt-in); no signing needed — it touches no
|
|
16
|
+
TCC-guarded resource. The first run per
|
|
15
17
|
machine downloads models and compiles them for CoreML (minutes; `--warmup`
|
|
16
18
|
does it eagerly). Without the binary, stenograf silently falls back to
|
|
17
19
|
sherpa-only diarization.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Build the stenodiar diarization helper and drop the binary next to this
|
|
2
|
+
# script, where stenograf's dev fallback looks for it (twin of build.sh for
|
|
3
|
+
# Windows). Needs a Rust toolchain (rustup) and the VS Build Tools linker. No
|
|
4
|
+
# signing: stenodiar touches no guarded resource, so an unsigned binary is fine.
|
|
5
|
+
$ErrorActionPreference = "Stop"
|
|
6
|
+
Set-Location $PSScriptRoot
|
|
7
|
+
|
|
8
|
+
# Default features = ORT CPU (matches the shipped Linux/Windows path); CUDA is a
|
|
9
|
+
# manual opt-in — cargo build --release --features cuda (nvidia-*-cu12 DLLs +
|
|
10
|
+
# onnxruntime_providers_cuda.dll on PATH). CoreML is macOS-only (build.sh).
|
|
11
|
+
cargo build --release --locked
|
|
12
|
+
Copy-Item target\release\stenodiar.exe stenodiar.exe -Force
|
|
13
|
+
|
|
14
|
+
Write-Output "built: $(Join-Path (Get-Location) 'stenodiar.exe')"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "stenograf"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "Accuracy-first local meeting transcription for German and English. Audio stays in RAM; only text is written."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -15,6 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Development Status :: 3 - Alpha",
|
|
16
16
|
"Environment :: Console",
|
|
17
17
|
"Operating System :: MacOS",
|
|
18
|
+
"Operating System :: Microsoft :: Windows",
|
|
18
19
|
"Operating System :: POSIX :: Linux",
|
|
19
20
|
"Programming Language :: Python :: 3.12",
|
|
20
21
|
"Topic :: Multimedia :: Sound/Audio :: Speech",
|
|
@@ -193,9 +193,14 @@ class QueueStreamingProvider[TransportT](CaptureProvider):
|
|
|
193
193
|
try:
|
|
194
194
|
self._pump(channel, transport)
|
|
195
195
|
finally:
|
|
196
|
-
|
|
197
|
-
self.
|
|
198
|
-
|
|
196
|
+
try:
|
|
197
|
+
if not self._stop_event.is_set():
|
|
198
|
+
self.stop()
|
|
199
|
+
finally:
|
|
200
|
+
# The sentinel is the only way frames() learns this channel is
|
|
201
|
+
# done, so it must survive a teardown that raises — otherwise
|
|
202
|
+
# frames() waits on an empty queue with every pump already dead.
|
|
203
|
+
self._queue.put(channel)
|
|
199
204
|
|
|
200
205
|
def _emit(self, channel: Channel, samples: np.ndarray) -> None:
|
|
201
206
|
"""Stamp a frame onto the session clock and hand it to ``frames()``."""
|
|
@@ -245,10 +245,14 @@ class WindowsCaptureProvider(QueueStreamingProvider[None]):
|
|
|
245
245
|
# Pumps notice the stop event within one frame read (~frame_ms +
|
|
246
246
|
# WASAPI's silence threshold) and release their devices on the way
|
|
247
247
|
# out. Skip the current thread: stop() also runs *from* a pump on an
|
|
248
|
-
# unexpected stream death.
|
|
248
|
+
# unexpected stream death. Skip threads that have not started either:
|
|
249
|
+
# a pump dying this early can land inside start()'s register-then-start
|
|
250
|
+
# window, and joining an unstarted thread raises. Nothing is lost by
|
|
251
|
+
# skipping — the stop event is already set, so a pump that starts after
|
|
252
|
+
# this exits on its first loop check.
|
|
249
253
|
current = threading.current_thread()
|
|
250
254
|
for thread in self._threads.values():
|
|
251
|
-
if thread is not current:
|
|
255
|
+
if thread is not current and thread.ident is not None:
|
|
252
256
|
thread.join(timeout=5)
|
|
253
257
|
|
|
254
258
|
|
|
@@ -35,18 +35,22 @@ def doctor() -> None:
|
|
|
35
35
|
help="Skip the permission prompts and only download the models (headless machines, CI).",
|
|
36
36
|
)
|
|
37
37
|
def setup(models_only: bool) -> None:
|
|
38
|
-
"""One-time setup:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
system audio) now instead of at the start of your first meeting —
|
|
42
|
-
is recorded
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
"""One-time setup: capture permissions, desktop launcher, model downloads.
|
|
39
|
+
|
|
40
|
+
macOS: launches the capture helper so both permission prompts (mic +
|
|
41
|
+
system audio) appear now instead of at the start of your first meeting —
|
|
42
|
+
nothing is recorded; the grant is scoped to the app the helper was
|
|
43
|
+
launched from, so re-run this from each terminal app (or IDE) you will
|
|
44
|
+
run meetings from. Windows: no prompt exists, so the microphone privacy
|
|
45
|
+
toggle is checked and named instead. Then installs a double-clickable
|
|
46
|
+
launcher and downloads every model the first meeting would otherwise
|
|
47
|
+
stop to fetch; the models are cached machine-wide.
|
|
47
48
|
"""
|
|
48
|
-
if not models_only
|
|
49
|
-
|
|
49
|
+
if not models_only:
|
|
50
|
+
if sys.platform == "darwin":
|
|
51
|
+
_grant_capture_permissions() # only macOS gates capture behind TCC prompts
|
|
52
|
+
elif sys.platform == "win32":
|
|
53
|
+
_check_windows_mic_access() # no prompt exists — read the privacy toggle now
|
|
50
54
|
|
|
51
55
|
# The launcher lands before the model download: the download can fail (and
|
|
52
56
|
# models fetch on first use anyway), the shortcut shouldn't be lost to that.
|
|
@@ -56,10 +60,10 @@ def setup(models_only: bool) -> None:
|
|
|
56
60
|
|
|
57
61
|
if (shortcut := install_shortcut()) is not None:
|
|
58
62
|
click.echo(click.style("✓", fg="green") + f" launcher installed: {shortcut}")
|
|
59
|
-
if sys.platform
|
|
60
|
-
click.echo(" Double-click it to start stenograf — no terminal needed.")
|
|
61
|
-
else:
|
|
63
|
+
if sys.platform.startswith("linux"): # menu entry; macOS/Windows land on the Desktop
|
|
62
64
|
click.echo(' Look for "Stenograf" in your application menu.')
|
|
65
|
+
else:
|
|
66
|
+
click.echo(" Double-click it to start stenograf — no terminal needed.")
|
|
63
67
|
|
|
64
68
|
# Permissions first (they need the user at the keyboard), then the long
|
|
65
69
|
# unattended part: everything a first meeting would otherwise stop to fetch.
|
|
@@ -73,6 +77,27 @@ def setup(models_only: bool) -> None:
|
|
|
73
77
|
click.echo(click.style("✓", fg="green") + " setup complete.")
|
|
74
78
|
|
|
75
79
|
|
|
80
|
+
def _check_windows_mic_access() -> None:
|
|
81
|
+
"""Fail setup loud when the Windows mic privacy toggle denies capture.
|
|
82
|
+
|
|
83
|
+
Windows never prompts desktop apps for the microphone (no TCC
|
|
84
|
+
equivalent) — a denied toggle just makes the stream deliver zeros — so
|
|
85
|
+
setup reads the consent store up front, mirroring the macOS grant step's
|
|
86
|
+
fail-before-models behavior, and tells the user no prompt is coming.
|
|
87
|
+
"""
|
|
88
|
+
from stenograf.capture.windows import mic_access_blocked
|
|
89
|
+
|
|
90
|
+
if (blocked := mic_access_blocked()) is not None:
|
|
91
|
+
raise click.ClickException(f"{blocked}, then re-run `steno setup`")
|
|
92
|
+
click.echo(
|
|
93
|
+
click.style("✓", fg="green") + " microphone access is allowed in Windows privacy settings."
|
|
94
|
+
)
|
|
95
|
+
click.echo(
|
|
96
|
+
" Windows shows no permission prompt — if the mic ever records only silence, "
|
|
97
|
+
"check Settings > Privacy & security > Microphone."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
76
101
|
def _grant_capture_permissions() -> None:
|
|
77
102
|
if sys.platform != "darwin":
|
|
78
103
|
raise click.ClickException(
|