feat(config): добавлена настройка диаризации в TOML

- Зачем:
  - пользователю нужна постоянная настройка диаризации с явным CLI-переопределением.
- Что:
  - добавлен булев ключ diarize и каскад CLI > TOML > false.
  - добавлены --no-diarize и проверка конфликта с --speakers.
  - обновлены документация и поведенческие тесты single/batch.
- Проверка:
  - uv run pytest tests/test_config.py tests/test_cli.py -v.
  - uv run pytest: 452 passed, 1 skipped, 1 несвязанный Windows-сбой.
This commit is contained in:
Dmitriy Dementiev committed 2026-09-02 12:45:57 +03:00
1 parent 5ec9c77dfa
commit ef61ced2e5
6 files changed
+155 -27

No files matched your search

+17 -8
View File
@@ -11,7 +11,7 @@ import typer
from rich.console import Console
from rich.status import Status
from .config import apply_device_defaults, load_config, resolve_defaults
from .config import ConfigValue, apply_device_defaults, load_config, resolve_defaults
from .context_menu import install_menu as install_context_menu
from .context_menu import uninstall_menu as uninstall_context_menu
from .diarization import build_speaker_transcript
@@ -261,9 +261,9 @@ def main(
min=0,
help="Потоки CPU (0 = дефолт библиотеки; рекомендуется = число физ. ядер)",
),
diarize: bool = typer.Option(
False,
"--diarize",
diarize: bool | None = typer.Option(
None,
"--diarize/--no-diarize",
help="Разделить транскрипт на реплики говорящих",
),
speakers: int | None = typer.Option(
@@ -328,6 +328,13 @@ def main(
)
raise SystemExit(2)
if diarize is False and speakers is not None:
console.print(
"--no-diarize и --speakers несовместимы.",
style="red bold",
)
raise SystemExit(2)
try:
config = load_config()
cli_values = {
@@ -335,8 +342,10 @@ def main(
"language": language,
"device": device,
"compute_type": compute_type,
"diarize": diarize,
}
defaults = resolve_defaults(cli_values, config)
diarize_enabled = defaults["diarize"] is True or speakers is not None
resolved_device = detect_device(defaults["device"])
defaults = apply_device_defaults(defaults, resolved_device, cli_values, config)
@@ -363,7 +372,7 @@ def main(
force,
ct_explicit,
cpu_threads=threads,
diarize=diarize or speakers is not None,
diarize=diarize_enabled,
speakers=speakers,
)
else:
@@ -374,7 +383,7 @@ def main(
verbose,
ct_explicit,
cpu_threads=threads,
diarize=diarize or speakers is not None,
diarize=diarize_enabled,
speakers=speakers,
)
except KeyboardInterrupt:
@@ -408,7 +417,7 @@ def main(
def _run_single(
file: Path,
defaults: dict[str, str],
defaults: dict[str, ConfigValue],
output: Path | None,
verbose: bool,
compute_type_explicit: bool = False,
@@ -552,7 +561,7 @@ def _run_single(
def _run_batch(
files: list[Path],
defaults: dict[str, str],
defaults: dict[str, ConfigValue],
verbose: bool,
force: bool,
compute_type_explicit: bool = False,
+21 -10
View File
@@ -4,11 +4,14 @@ import tomllib
import warnings
from pathlib import Path
HARDCODED_DEFAULTS: dict[str, str] = {
type ConfigValue = str | bool
HARDCODED_DEFAULTS: dict[str, ConfigValue] = {
"model": "medium",
"language": "ru",
"device": "auto",
"compute_type": "float32",
"diarize": False,
}
DEVICE_DEFAULTS: dict[str, dict[str, str]] = {
@@ -46,7 +49,7 @@ def find_config_file() -> Path | None:
return None
def load_config(path: Path | None = None) -> dict[str, str]:
def load_config(path: Path | None = None) -> dict[str, ConfigValue]:
"""Загружает и валидирует TOML-конфиг.
Неизвестные ключи вызывают предупреждение (а не ошибку) для forward
@@ -70,11 +73,19 @@ def load_config(path: Path | None = None) -> dict[str, str]:
stacklevel=2,
)
result: dict[str, str] = {}
result: dict[str, ConfigValue] = {}
for key in _VALID_KEYS:
if key not in data:
continue
value = data[key]
if key == "diarize":
if not isinstance(value, bool):
raise ValueError(
f"Значение 'diarize' в {path} должно быть логическим, "
f"получено {type(value).__name__}"
)
result[key] = value
continue
if not isinstance(value, str):
raise ValueError(
f"Значение '{key}' в {path} должно быть строкой, получено {type(value).__name__}"
@@ -92,10 +103,10 @@ def load_config(path: Path | None = None) -> dict[str, str]:
def resolve_defaults(
cli_values: dict[str, str | None], config: dict[str, str]
) -> dict[str, str]:
cli_values: dict[str, ConfigValue | None], config: dict[str, ConfigValue]
) -> dict[str, ConfigValue]:
"""Каскад приоритетов: CLI > конфиг-файл > hardcoded-дефолты."""
result: dict[str, str] = {}
result: dict[str, ConfigValue] = {}
for key in HARDCODED_DEFAULTS:
cli_val = cli_values.get(key)
if cli_val is not None:
@@ -108,11 +119,11 @@ def resolve_defaults(
def apply_device_defaults(
defaults: dict[str, str],
defaults: dict[str, ConfigValue],
resolved_device: str,
cli_values: dict[str, str | None],
config: dict[str, str],
) -> dict[str, str]:
cli_values: dict[str, ConfigValue | None],
config: dict[str, ConfigValue],
) -> dict[str, ConfigValue]:
"""Применяет device-aware дефолты для model и compute_type,
если они не были явно заданы через CLI или конфиг."""
device_defs = DEVICE_DEFAULTS.get(resolved_device, {})