feat(cli): реализован шаг 5 — CLI-связка всех модулей с исправлениями из ревью
- Зачем:
- шаг 5 плана: нужен рабочий CLI-happy path, связывающий utils / transcriber / formatter.
- ревью этапов 4–5 выявило два medium-бага в formatter и отсутствие тестов для CLI.
- Что:
- cli.py: все опции по PRD 3.2 (--model, --language, --output, --device, --compute-type, --verbose),
rich Status + stderr-консоль, предупреждение на пустую речь, статистика времени.
- transcriber.py: добавлена ensure_model_available() с проверкой кэша HF и валидацией
локальной директории; on_status callback для передачи прогресса в CLI; обработка
ImportError при отсутствии socksio через SOCKS proxy.
- formatter.py: исправлен overflow в format_timestamp (0.995 → 00:01.00 вместо 00:00.100);
сегменты теперь пишутся с явным пробелом и strip() независимо от whisper-формата текста.
- deps: добавлен socksio>=1.0.0 для поддержки SOCKS proxy при загрузке модели.
- tests: test_cli.py (8 тестов на CLI-контракт), расширены test_formatter.py и test_transcriber.py.
- Проверка:
- uv run pytest — 42 passed.
- uv run transcribe --help показывает все опции.
This commit is contained in:
+3
-3
@@ -216,7 +216,7 @@
|
||||
|
||||
> PRD-ссылки: 3.1 (flow), 3.2 (CLI-интерфейс)
|
||||
|
||||
- [ ] Typer command с аргументами и опциями по PRD 3.2:
|
||||
- [x] Typer command с аргументами и опциями по PRD 3.2:
|
||||
- `file: Path` — позиционный аргумент
|
||||
- `--model` / `-m` → default `"large-v3"`
|
||||
- `--language` / `-l` → default `"auto"`
|
||||
@@ -224,7 +224,7 @@
|
||||
- `--device` / `-d` → default `"auto"`
|
||||
- `--compute-type` → default `"int8"`
|
||||
- `--verbose` / `-v` → flag, default False
|
||||
- [ ] Happy path flow:
|
||||
- [x] Happy path flow:
|
||||
1. `check_ffmpeg()`
|
||||
2. `validate_input_file(file)`
|
||||
3. `detect_device(device)` → получить device; `--compute-type` используется как есть (независим от device)
|
||||
@@ -236,7 +236,7 @@
|
||||
9. `write_transcript(...)`
|
||||
10. `console.print("✓ Транскрипт сохранён: <путь>", style="green")`
|
||||
11. Статистика: кол-во сегментов, время работы (замерить через `time.monotonic()`)
|
||||
- [ ] Exit codes: 0 — успех (включая пустую речь), 1 — ошибка
|
||||
- [x] Exit codes: 0 — успех (включая пустую речь), 1 — ошибка
|
||||
|
||||
**Критерий готовности**: `uv run transcribe test.mp3` — создаёт корректный .md файл (проверить на локальной машине с реальным файлом).
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Review: Stages 4-5 (Rerun)
|
||||
|
||||
## Executive Summary
|
||||
|
||||
| Severity | Count |
|
||||
|----------|-------|
|
||||
| CRITICAL | 0 |
|
||||
| HIGH | 0 |
|
||||
| MEDIUM | 0 |
|
||||
| LOW | 0 |
|
||||
|
||||
**Overall Risk:** LOW
|
||||
**Recommendation:** APPROVE
|
||||
|
||||
## What Was Rechecked
|
||||
|
||||
- `src/local_transcriber/formatter.py`
|
||||
- `src/local_transcriber/cli.py`
|
||||
- `tests/test_formatter.py`
|
||||
- `tests/test_cli.py`
|
||||
|
||||
## Result
|
||||
|
||||
No new findings.
|
||||
|
||||
Previously reported issues for stages 4-5 are addressed:
|
||||
|
||||
- centisecond carry in `format_timestamp()` is fixed
|
||||
- transcript formatting no longer depends on leading whitespace in `seg.text`
|
||||
- CLI now has automated tests for happy path, options, empty speech warning, output path handling, and error exit code
|
||||
|
||||
## Verification
|
||||
|
||||
- `uv run pytest` -> 31 passed
|
||||
- `.venv/bin/transcribe --help` -> works
|
||||
- spot checks:
|
||||
- `format_timestamp(0.995)` -> `00:01.00`
|
||||
- `format_timestamp(59.995)` -> `01:00.00`
|
||||
- `format_timestamp(3599.995, use_hours=True)` -> `01:00:00.00`
|
||||
|
||||
## Residual Risk
|
||||
|
||||
- Step 6 error-handling polish is still not implemented, so user-facing error formatting remains intentionally incomplete at this stage
|
||||
@@ -0,0 +1,138 @@
|
||||
# Review: Stages 4-5
|
||||
|
||||
## Executive Summary
|
||||
|
||||
| Severity | Count |
|
||||
|----------|-------|
|
||||
| CRITICAL | 0 |
|
||||
| HIGH | 0 |
|
||||
| MEDIUM | 2 |
|
||||
| LOW | 1 |
|
||||
|
||||
**Overall Risk:** MEDIUM
|
||||
**Recommendation:** CONDITIONAL
|
||||
|
||||
**Key Metrics:**
|
||||
- Files analyzed: 4
|
||||
- Verified commands: `pytest`, CLI help, mocked CLI happy path
|
||||
- Test coverage gaps: 1 user-facing module (`cli.py`)
|
||||
- High blast radius changes: 0
|
||||
- Security regressions detected: 0
|
||||
|
||||
## What Changed
|
||||
|
||||
**Commit Range:** `dfc5f46..WORKTREE`
|
||||
**Commits:** `0d1a734`, plus uncommitted step 5 changes
|
||||
|
||||
| File | Risk | Notes |
|
||||
|------|------|-------|
|
||||
| `src/local_transcriber/formatter.py` | MEDIUM | Output contract and markdown formatting |
|
||||
| `tests/test_formatter.py` | LOW | Unit coverage for formatter |
|
||||
| `src/local_transcriber/cli.py` | MEDIUM | Main user-facing flow and file writing |
|
||||
| `docs/plan.md` | LOW | Checkbox updates for step 5 |
|
||||
|
||||
## Findings
|
||||
|
||||
### MEDIUM: `format_timestamp()` can emit invalid centiseconds like `.100`
|
||||
|
||||
**File:** `src/local_transcriber/formatter.py:7`
|
||||
**Test Coverage:** NO
|
||||
|
||||
The implementation rounds centiseconds independently from the integral seconds:
|
||||
|
||||
```python
|
||||
total_seconds = int(seconds)
|
||||
centiseconds = int(round((seconds - total_seconds) * 100))
|
||||
```
|
||||
|
||||
For values such as `0.995`, this produces `00:00.100` instead of carrying into the next second.
|
||||
|
||||
**Reproduction:**
|
||||
- `format_timestamp(0.995)` returns `00:00.100`
|
||||
|
||||
**Impact:**
|
||||
- Breaks the PRD timestamp format contract (`SS.ss` must always have exactly two fractional digits)
|
||||
- Can produce malformed transcript timestamps on real segment boundaries
|
||||
|
||||
**Recommendation:**
|
||||
- Round the full timestamp first and then split into components, or normalize `centiseconds == 100` by incrementing seconds
|
||||
- Add a regression test for `0.995`
|
||||
|
||||
### MEDIUM: Transcript formatting depends on segment text already containing a leading space
|
||||
|
||||
**File:** `src/local_transcriber/formatter.py:62`
|
||||
**Test Coverage:** PARTIAL
|
||||
|
||||
Segment lines are written as:
|
||||
|
||||
```python
|
||||
f"[{start} - {end}]{seg.text}"
|
||||
```
|
||||
|
||||
This only matches the PRD format if `seg.text` already starts with a space. The current tests mask that dependency by building fixtures with leading spaces.
|
||||
|
||||
**Reproduction:**
|
||||
- A mocked CLI run with a segment text of `"Hello"` writes:
|
||||
- `[00:00.00 - 00:01.00]Hello`
|
||||
- Expected:
|
||||
- `[00:00.00 - 00:01.00] Hello`
|
||||
|
||||
**Impact:**
|
||||
- Output format becomes model-dependent instead of being guaranteed by the formatter
|
||||
- Any future normalization in `transcriber.py` will immediately break transcript formatting
|
||||
|
||||
**Recommendation:**
|
||||
- Normalize segment text inside the formatter, e.g. `seg.text.strip()` plus an explicit single space after `]`
|
||||
- Add a test case where segment text has no leading whitespace
|
||||
|
||||
### LOW: Step 5 has no automated tests for the public CLI contract
|
||||
|
||||
**File:** `src/local_transcriber/cli.py:16`
|
||||
**Test Coverage:** NO
|
||||
|
||||
The user-facing entrypoint is now wired end to end, but there is still no `test_cli.py` coverage for:
|
||||
|
||||
- option parsing
|
||||
- warning path for empty speech
|
||||
- default output-path generation
|
||||
- exit-code behavior on failure
|
||||
|
||||
This already matters because the mocked CLI happy path is what exposed the missing-space formatting bug above.
|
||||
|
||||
## Test Coverage Analysis
|
||||
|
||||
**Executed checks:**
|
||||
- `uv run pytest` -> 22 tests passed
|
||||
- `.venv/bin/transcribe --help` -> works
|
||||
- mocked `CliRunner` happy path -> exit code 0 and output file written
|
||||
|
||||
**Coverage gaps:**
|
||||
|
||||
| Area | Gap | Risk |
|
||||
|------|-----|------|
|
||||
| `formatter.py` | No edge-case test for centisecond carry | MEDIUM |
|
||||
| `formatter.py` | No test for segment text without leading whitespace | MEDIUM |
|
||||
| `cli.py` | No automated tests at all | LOW |
|
||||
|
||||
## Blast Radius Analysis
|
||||
|
||||
The blast radius is still low because this is a small CLI project, but `cli.py` is now the single public entrypoint. Any formatting or wiring defect directly affects all users.
|
||||
|
||||
| Function | Exposure | Risk | Priority |
|
||||
|----------|----------|------|----------|
|
||||
| `main()` | All CLI invocations | MEDIUM | P1 |
|
||||
| `format_transcript()` | All saved transcript files | MEDIUM | P1 |
|
||||
| `format_timestamp()` | Every segment line | MEDIUM | P1 |
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Immediate
|
||||
|
||||
- [ ] Fix centisecond carry handling in `format_timestamp()`
|
||||
- [ ] Stop relying on leading whitespace in `seg.text`
|
||||
- [ ] Add formatter regression tests for both cases
|
||||
|
||||
### Before Step 6
|
||||
|
||||
- [ ] Add `tests/test_cli.py` with a mocked happy path and one error path
|
||||
- [ ] Assert output file contents through the CLI layer, not only through direct formatter calls
|
||||
Reference in New Issue
Block a user