feat(cli): добавлен батч-режим и конфигурационный файл (шаги 8–12)

- Зачем:
  - обработка нескольких файлов за один вызов с загрузкой модели один раз.
  - хранение дефолтов (модель, язык, устройство) в .transcriber.toml.
- Что:
  - добавлен config.py: поиск .transcriber.toml (CWD → ~/.config), парсинг, валидация, приоритет CLI > конфиг > хардкод.
  - рефакторинг transcriber.py: выделены load_model() и _transcribe_file() с TranscribeFileResult для переиспользования модели в батче.
  - добавлены expand_globs() с дедупликацией и has_existing_transcript() в utils.py.
  - CLI: files: list[Path], --force/-f, prescan-first батч с итоговой статистикой и временем, Status-спиннер для прогресса.
  - README: секции батч-режим, конфигурационный файл, --force в таблице опций.
  - ADR-002: зафиксированы архитектурные решения (prescan-first, TranscribeFileResult, конфиг без мержа).
- Проверка:
  - uv run pytest -q — 94 passed, 1 skipped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-18 20:57:19 +03:00
co-authored by Claude Opus 4.6
parent b46827a283
commit e28232ef50
13 changed files with 1227 additions and 103 deletions
+44
View File
@@ -7,7 +7,9 @@ import pytest
from local_transcriber.utils import (
build_output_path,
detect_device,
expand_globs,
get_gpu_name,
has_existing_transcript,
validate_input_file,
)
@@ -82,3 +84,45 @@ def test_get_gpu_name_success():
with patch("subprocess.run", return_value=mock_result):
result = get_gpu_name()
assert result == "NVIDIA GeForce RTX 3060"
def test_expand_globs_no_patterns(tmp_path):
a = tmp_path / "a.mp3"
b = tmp_path / "b.mp3"
result = expand_globs([a, b])
assert result == [a, b]
def test_expand_globs_with_star(tmp_path):
(tmp_path / "x.mp3").write_bytes(b"fake")
(tmp_path / "y.mp3").write_bytes(b"fake")
(tmp_path / "z.txt").write_bytes(b"fake")
result = expand_globs([Path(str(tmp_path / "*.mp3"))])
assert len(result) == 2
assert all(p.suffix == ".mp3" for p in result)
def test_expand_globs_no_match(tmp_path):
result = expand_globs([Path(str(tmp_path / "*.wav"))])
assert result == []
def test_expand_globs_deduplicates(tmp_path):
f = tmp_path / "a.mp3"
f.write_bytes(b"fake")
result = expand_globs([f, Path(str(tmp_path / "*.mp3"))])
assert len(result) == 1
def test_has_existing_transcript_true(tmp_path):
audio = tmp_path / "meeting.mp3"
audio.write_bytes(b"fake")
transcript = tmp_path / "meeting-transcript.md"
transcript.write_text("content")
assert has_existing_transcript(audio) is True
def test_has_existing_transcript_false(tmp_path):
audio = tmp_path / "meeting.mp3"
audio.write_bytes(b"fake")
assert has_existing_transcript(audio) is False