feat(config): добавлена настройка диаризации в TOML #52
No files matched your search
@@ -223,8 +223,8 @@ transcribe --uninstall-menu
|
|||||||
| `--device` | `-d` | `auto` | Устройство (auto, cpu, cuda, openvino, openvino-gpu, openvino-cpu, onnx) |
|
| `--device` | `-d` | `auto` | Устройство (auto, cpu, cuda, openvino, openvino-gpu, openvino-cpu, onnx) |
|
||||||
| `--compute-type` | — | float16 (CUDA) / int8 (ONNX/OpenVINO) / float32 (CPU) | Тип вычислений |
|
| `--compute-type` | — | float16 (CUDA) / int8 (ONNX/OpenVINO) / float32 (CPU) | Тип вычислений |
|
||||||
| `--threads` | `-t` | 0 (авто) | Потоки CPU (рекомендуется = число физ. ядер) |
|
| `--threads` | `-t` | 0 (авто) | Потоки CPU (рекомендуется = число физ. ядер) |
|
||||||
| `--diarize` | — | — | Разделить текст на реплики говорящих |
|
| `--diarize` / `--no-diarize` | — | `false` | Включить или отключить разделение на реплики говорящих |
|
||||||
| `--speakers` | — | авто | Ожидаемое число говорящих; включает `--diarize` |
|
| `--speakers` | — | авто | Ожидаемое число говорящих; включает диаризацию и несовместим с `--no-diarize` |
|
||||||
| `--force` | `-f` | — | Перезаписать существующие транскрипты |
|
| `--force` | `-f` | — | Перезаписать существующие транскрипты |
|
||||||
| `--verbose` | `-v` | — | Подробный вывод |
|
| `--verbose` | `-v` | — | Подробный вывод |
|
||||||
|
|
||||||
@@ -282,6 +282,7 @@ device = "openvino-cpu"
|
|||||||
model = "large-v3-turbo"
|
model = "large-v3-turbo"
|
||||||
compute_type = "int8"
|
compute_type = "int8"
|
||||||
language = "ru"
|
language = "ru"
|
||||||
|
diarize = true
|
||||||
```
|
```
|
||||||
|
|
||||||
Порядок поиска:
|
Порядок поиска:
|
||||||
@@ -289,6 +290,8 @@ language = "ru"
|
|||||||
2. `~/.config/transcriber/config.toml`
|
2. `~/.config/transcriber/config.toml`
|
||||||
|
|
||||||
Приоритет: **CLI-аргумент > конфиг > device-aware дефолт > встроенный дефолт**.
|
Приоритет: **CLI-аргумент > конфиг > device-aware дефолт > встроенный дефолт**.
|
||||||
|
`--diarize` и `--no-diarize` позволяют переопределить `diarize` из конфига для
|
||||||
|
отдельного запуска.
|
||||||
|
|
||||||
При `device = "auto"` выбирается CUDA, если доступен `nvidia-smi`, иначе ONNX.
|
При `device = "auto"` выбирается CUDA, если доступен `nvidia-smi`, иначе ONNX.
|
||||||
Явный `device` из CLI или конфига отключает этот автоматический выбор.
|
Явный `device` из CLI или конфига отключает этот автоматический выбор.
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
## Не входит
|
## Не входит
|
||||||
|
|
||||||
- Автоматическое включение диаризации без флага и `--no-diarize`.
|
- Аппаратно-зависимое автоматическое включение диаризации.
|
||||||
- Параллельный запуск ASR и диаризации.
|
- Параллельный запуск ASR и диаризации.
|
||||||
- Сопоставление `Speaker N` с именами участников.
|
- Сопоставление `Speaker N` с именами участников.
|
||||||
- Постоянный кеш разметки говорящих, голосовые эмбеддинги и диагностические
|
- Постоянный кеш разметки говорящих, голосовые эмбеддинги и диагностические
|
||||||
@@ -33,10 +33,12 @@
|
|||||||
|
|
||||||
## Пользовательский интерфейс
|
## Пользовательский интерфейс
|
||||||
|
|
||||||
- `--diarize` включает диаризацию. По умолчанию она выключена; ключ в
|
- `--diarize` включает диаризацию, `--no-diarize` явно выключает. Булев ключ
|
||||||
`.transcriber.toml` в первой версии не добавляется.
|
`diarize` в `.transcriber.toml` задаёт значение по умолчанию; без него
|
||||||
|
диаризация выключена. CLI-флаги имеют приоритет над конфигом.
|
||||||
- `--speakers N`, где `N >= 1`, задаёт известное число участников и сам включает
|
- `--speakers N`, где `N >= 1`, задаёт известное число участников и сам включает
|
||||||
диаризацию. Без него число кластеров определяется автоматически.
|
диаризацию. Без него число кластеров определяется автоматически. Сочетание
|
||||||
|
`--speakers N` с `--no-diarize` считается ошибкой использования.
|
||||||
- `--threads N` остаётся единым бюджетом активного CPU-прохода. Значение целиком
|
- `--threads N` остаётся единым бюджетом активного CPU-прохода. Значение целиком
|
||||||
получает сначала ASR, затем диаризация; `0` оставляет настройки библиотек.
|
получает сначала ASR, затем диаризация; `0` оставляет настройки библиотек.
|
||||||
- `--verbose` показывает в консоли прогресс, число кластеров и интервалов,
|
- `--verbose` показывает в консоли прогресс, число кластеров и интервалов,
|
||||||
|
|||||||
@@ -11,7 +11,13 @@ import typer
|
|||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.status import Status
|
from rich.status import Status
|
||||||
|
|
||||||
from .config import apply_device_defaults, load_config, resolve_defaults
|
from .config import (
|
||||||
|
CliValues,
|
||||||
|
ResolvedConfig,
|
||||||
|
apply_device_defaults,
|
||||||
|
load_config,
|
||||||
|
resolve_defaults,
|
||||||
|
)
|
||||||
from .context_menu import install_menu as install_context_menu
|
from .context_menu import install_menu as install_context_menu
|
||||||
from .context_menu import uninstall_menu as uninstall_context_menu
|
from .context_menu import uninstall_menu as uninstall_context_menu
|
||||||
from .diarization import build_speaker_transcript
|
from .diarization import build_speaker_transcript
|
||||||
@@ -261,9 +267,9 @@ def main(
|
|||||||
min=0,
|
min=0,
|
||||||
help="Потоки CPU (0 = дефолт библиотеки; рекомендуется = число физ. ядер)",
|
help="Потоки CPU (0 = дефолт библиотеки; рекомендуется = число физ. ядер)",
|
||||||
),
|
),
|
||||||
diarize: bool = typer.Option(
|
diarize: bool | None = typer.Option(
|
||||||
False,
|
None,
|
||||||
"--diarize",
|
"--diarize/--no-diarize",
|
||||||
help="Разделить транскрипт на реплики говорящих",
|
help="Разделить транскрипт на реплики говорящих",
|
||||||
),
|
),
|
||||||
speakers: int | None = typer.Option(
|
speakers: int | None = typer.Option(
|
||||||
@@ -328,15 +334,24 @@ def main(
|
|||||||
)
|
)
|
||||||
raise SystemExit(2)
|
raise SystemExit(2)
|
||||||
|
|
||||||
|
if diarize is False and speakers is not None:
|
||||||
|
console.print(
|
||||||
|
"--no-diarize и --speakers несовместимы.",
|
||||||
|
style="red bold",
|
||||||
|
)
|
||||||
|
raise SystemExit(2)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config = load_config()
|
config = load_config()
|
||||||
cli_values = {
|
cli_values: CliValues = {
|
||||||
"model": model,
|
"model": model,
|
||||||
"language": language,
|
"language": language,
|
||||||
"device": device,
|
"device": device,
|
||||||
"compute_type": compute_type,
|
"compute_type": compute_type,
|
||||||
|
"diarize": diarize,
|
||||||
}
|
}
|
||||||
defaults = resolve_defaults(cli_values, config)
|
defaults = resolve_defaults(cli_values, config)
|
||||||
|
diarize_enabled = defaults["diarize"] is True or speakers is not None
|
||||||
|
|
||||||
resolved_device = detect_device(defaults["device"])
|
resolved_device = detect_device(defaults["device"])
|
||||||
defaults = apply_device_defaults(defaults, resolved_device, cli_values, config)
|
defaults = apply_device_defaults(defaults, resolved_device, cli_values, config)
|
||||||
@@ -363,7 +378,7 @@ def main(
|
|||||||
force,
|
force,
|
||||||
ct_explicit,
|
ct_explicit,
|
||||||
cpu_threads=threads,
|
cpu_threads=threads,
|
||||||
diarize=diarize or speakers is not None,
|
diarize=diarize_enabled,
|
||||||
speakers=speakers,
|
speakers=speakers,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -374,7 +389,7 @@ def main(
|
|||||||
verbose,
|
verbose,
|
||||||
ct_explicit,
|
ct_explicit,
|
||||||
cpu_threads=threads,
|
cpu_threads=threads,
|
||||||
diarize=diarize or speakers is not None,
|
diarize=diarize_enabled,
|
||||||
speakers=speakers,
|
speakers=speakers,
|
||||||
)
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
@@ -408,7 +423,7 @@ def main(
|
|||||||
|
|
||||||
def _run_single(
|
def _run_single(
|
||||||
file: Path,
|
file: Path,
|
||||||
defaults: dict[str, str],
|
defaults: ResolvedConfig,
|
||||||
output: Path | None,
|
output: Path | None,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
compute_type_explicit: bool = False,
|
compute_type_explicit: bool = False,
|
||||||
@@ -552,7 +567,7 @@ def _run_single(
|
|||||||
|
|
||||||
def _run_batch(
|
def _run_batch(
|
||||||
files: list[Path],
|
files: list[Path],
|
||||||
defaults: dict[str, str],
|
defaults: ResolvedConfig,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
force: bool,
|
force: bool,
|
||||||
compute_type_explicit: bool = False,
|
compute_type_explicit: bool = False,
|
||||||
|
|||||||
@@ -3,12 +3,48 @@
|
|||||||
import tomllib
|
import tomllib
|
||||||
import warnings
|
import warnings
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TypedDict, TypeVar, cast
|
||||||
|
|
||||||
HARDCODED_DEFAULTS: dict[str, str] = {
|
type ConfigValue = str | bool
|
||||||
|
_T = TypeVar("_T")
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigValues(TypedDict, total=False):
|
||||||
|
"""Проверенные значения из TOML-конфига."""
|
||||||
|
|
||||||
|
model: str
|
||||||
|
language: str
|
||||||
|
device: str
|
||||||
|
compute_type: str
|
||||||
|
diarize: bool
|
||||||
|
|
||||||
|
|
||||||
|
class CliValues(TypedDict, total=False):
|
||||||
|
"""Явные значения CLI до разрешения каскада."""
|
||||||
|
|
||||||
|
model: str | None
|
||||||
|
language: str | None
|
||||||
|
device: str | None
|
||||||
|
compute_type: str | None
|
||||||
|
diarize: bool | None
|
||||||
|
|
||||||
|
|
||||||
|
class ResolvedConfig(TypedDict):
|
||||||
|
"""Полная конфигурация после разрешения каскада."""
|
||||||
|
|
||||||
|
model: str
|
||||||
|
language: str
|
||||||
|
device: str
|
||||||
|
compute_type: str
|
||||||
|
diarize: bool
|
||||||
|
|
||||||
|
|
||||||
|
HARDCODED_DEFAULTS: ResolvedConfig = {
|
||||||
"model": "medium",
|
"model": "medium",
|
||||||
"language": "ru",
|
"language": "ru",
|
||||||
"device": "auto",
|
"device": "auto",
|
||||||
"compute_type": "float32",
|
"compute_type": "float32",
|
||||||
|
"diarize": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
DEVICE_DEFAULTS: dict[str, dict[str, str]] = {
|
DEVICE_DEFAULTS: dict[str, dict[str, str]] = {
|
||||||
@@ -46,7 +82,7 @@ def find_config_file() -> Path | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def load_config(path: Path | None = None) -> dict[str, str]:
|
def load_config(path: Path | None = None) -> ConfigValues:
|
||||||
"""Загружает и валидирует TOML-конфиг.
|
"""Загружает и валидирует TOML-конфиг.
|
||||||
|
|
||||||
Неизвестные ключи вызывают предупреждение (а не ошибку) для forward
|
Неизвестные ключи вызывают предупреждение (а не ошибку) для forward
|
||||||
@@ -70,11 +106,19 @@ def load_config(path: Path | None = None) -> dict[str, str]:
|
|||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
result: dict[str, str] = {}
|
result: dict[str, ConfigValue] = {}
|
||||||
for key in _VALID_KEYS:
|
for key in _VALID_KEYS:
|
||||||
if key not in data:
|
if key not in data:
|
||||||
continue
|
continue
|
||||||
value = data[key]
|
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):
|
if not isinstance(value, str):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Значение '{key}' в {path} должно быть строкой, получено {type(value).__name__}"
|
f"Значение '{key}' в {path} должно быть строкой, получено {type(value).__name__}"
|
||||||
@@ -88,31 +132,53 @@ def load_config(path: Path | None = None) -> dict[str, str]:
|
|||||||
raise ValueError(f"Значение 'language' в {path} не может быть пустым")
|
raise ValueError(f"Значение 'language' в {path} не может быть пустым")
|
||||||
result[key] = value
|
result[key] = value
|
||||||
|
|
||||||
return result
|
return cast(ConfigValues, result)
|
||||||
|
|
||||||
|
|
||||||
def resolve_defaults(
|
def resolve_defaults(cli_values: CliValues, config: ConfigValues) -> ResolvedConfig:
|
||||||
cli_values: dict[str, str | None], config: dict[str, str]
|
|
||||||
) -> dict[str, str]:
|
|
||||||
"""Каскад приоритетов: CLI > конфиг-файл > hardcoded-дефолты."""
|
"""Каскад приоритетов: CLI > конфиг-файл > hardcoded-дефолты."""
|
||||||
result: dict[str, str] = {}
|
return {
|
||||||
for key in HARDCODED_DEFAULTS:
|
"model": _resolve_value(
|
||||||
cli_val = cli_values.get(key)
|
cli_values.get("model"), config.get("model"), HARDCODED_DEFAULTS["model"]
|
||||||
if cli_val is not None:
|
),
|
||||||
result[key] = cli_val
|
"language": _resolve_value(
|
||||||
elif key in config:
|
cli_values.get("language"),
|
||||||
result[key] = config[key]
|
config.get("language"),
|
||||||
else:
|
HARDCODED_DEFAULTS["language"],
|
||||||
result[key] = HARDCODED_DEFAULTS[key]
|
),
|
||||||
return result
|
"device": _resolve_value(
|
||||||
|
cli_values.get("device"),
|
||||||
|
config.get("device"),
|
||||||
|
HARDCODED_DEFAULTS["device"],
|
||||||
|
),
|
||||||
|
"compute_type": _resolve_value(
|
||||||
|
cli_values.get("compute_type"),
|
||||||
|
config.get("compute_type"),
|
||||||
|
HARDCODED_DEFAULTS["compute_type"],
|
||||||
|
),
|
||||||
|
"diarize": _resolve_value(
|
||||||
|
cli_values.get("diarize"),
|
||||||
|
config.get("diarize"),
|
||||||
|
HARDCODED_DEFAULTS["diarize"],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_value(cli_value: _T | None, config_value: _T | None, default: _T) -> _T:
|
||||||
|
"""Выбирает первое явно заданное значение каскада."""
|
||||||
|
if cli_value is not None:
|
||||||
|
return cli_value
|
||||||
|
if config_value is not None:
|
||||||
|
return config_value
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
def apply_device_defaults(
|
def apply_device_defaults(
|
||||||
defaults: dict[str, str],
|
defaults: ResolvedConfig,
|
||||||
resolved_device: str,
|
resolved_device: str,
|
||||||
cli_values: dict[str, str | None],
|
cli_values: CliValues,
|
||||||
config: dict[str, str],
|
config: ConfigValues,
|
||||||
) -> dict[str, str]:
|
) -> ResolvedConfig:
|
||||||
"""Применяет device-aware дефолты для model и compute_type,
|
"""Применяет device-aware дефолты для model и compute_type,
|
||||||
если они не были явно заданы через CLI или конфиг."""
|
если они не были явно заданы через CLI или конфиг."""
|
||||||
device_defs = DEVICE_DEFAULTS.get(resolved_device, {})
|
device_defs = DEVICE_DEFAULTS.get(resolved_device, {})
|
||||||
@@ -124,4 +190,4 @@ def apply_device_defaults(
|
|||||||
if cli_values.get(key) is None and key not in config:
|
if cli_values.get(key) is None and key not in config:
|
||||||
if key in device_defs:
|
if key in device_defs:
|
||||||
result[key] = device_defs[key]
|
result[key] = device_defs[key]
|
||||||
return result
|
return cast(ResolvedConfig, result)
|
||||||
+72
-3
@@ -519,6 +519,27 @@ def test_cli_rejects_nonpositive_speaker_count(tmp_path):
|
|||||||
assert out.exit_code == 2
|
assert out.exit_code == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_rejects_no_diarize_with_speakers_before_model_load(tmp_path):
|
||||||
|
audio = tmp_path / "meeting.mp3"
|
||||||
|
audio.write_bytes(b"fake")
|
||||||
|
load_model = MagicMock()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
|
patch("local_transcriber.cli.load_model", load_model),
|
||||||
|
):
|
||||||
|
out = runner.invoke(
|
||||||
|
app,
|
||||||
|
[str(audio), "--no-diarize", "--speakers", "2"],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert out.exit_code == 2
|
||||||
|
assert "--no-diarize и --speakers несовместимы" in out.output
|
||||||
|
load_model.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
def test_cli_verbose_passes_on_segment_callback(tmp_path):
|
def test_cli_verbose_passes_on_segment_callback(tmp_path):
|
||||||
audio = tmp_path / "test.mp3"
|
audio = tmp_path / "test.mp3"
|
||||||
audio.write_bytes(b"fake")
|
audio.write_bytes(b"fake")
|
||||||
@@ -917,7 +938,13 @@ def test_cli_batch_two_files(tmp_path):
|
|||||||
assert "2 обработано" in out.output
|
assert "2 обработано" in out.output
|
||||||
|
|
||||||
|
|
||||||
def test_cli_batch_reuses_one_diarizer_for_all_nonempty_files(tmp_path):
|
@pytest.mark.parametrize(
|
||||||
|
("config", "cli_args"),
|
||||||
|
[({}, ["--diarize"]), ({"diarize": True}, [])],
|
||||||
|
)
|
||||||
|
def test_cli_batch_reuses_one_diarizer_for_all_nonempty_files(
|
||||||
|
tmp_path, config, cli_args
|
||||||
|
):
|
||||||
first = tmp_path / "first.mp3"
|
first = tmp_path / "first.mp3"
|
||||||
second = tmp_path / "second.mp3"
|
second = tmp_path / "second.mp3"
|
||||||
first.write_bytes(b"fake")
|
first.write_bytes(b"fake")
|
||||||
@@ -941,7 +968,7 @@ def test_cli_batch_reuses_one_diarizer_for_all_nonempty_files(tmp_path):
|
|||||||
)
|
)
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value=config),
|
||||||
patch(
|
patch(
|
||||||
"local_transcriber.cli.validate_input_file",
|
"local_transcriber.cli.validate_input_file",
|
||||||
side_effect=lambda path: path,
|
side_effect=lambda path: path,
|
||||||
@@ -958,7 +985,7 @@ def test_cli_batch_reuses_one_diarizer_for_all_nonempty_files(tmp_path):
|
|||||||
) as load_diarizer,
|
) as load_diarizer,
|
||||||
patch("local_transcriber.cli.write_transcript") as write,
|
patch("local_transcriber.cli.write_transcript") as write,
|
||||||
):
|
):
|
||||||
out = runner.invoke(app, [str(first), str(second), "--diarize"])
|
out = runner.invoke(app, [str(first), str(second), *cli_args])
|
||||||
|
|
||||||
assert out.exit_code == 0
|
assert out.exit_code == 0
|
||||||
load_diarizer.assert_called_once()
|
load_diarizer.assert_called_once()
|
||||||
@@ -1232,6 +1259,48 @@ def test_cli_config_applied(tmp_path):
|
|||||||
assert mock_load_model.call_args[0][0] == "tiny"
|
assert mock_load_model.call_args[0][0] == "tiny"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("config_value", "cli_args", "expected_calls"),
|
||||||
|
[
|
||||||
|
(True, [], 1),
|
||||||
|
(False, [], 0),
|
||||||
|
(False, ["--diarize"], 1),
|
||||||
|
(True, ["--no-diarize"], 0),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_cli_resolves_diarization_priority(
|
||||||
|
tmp_path, config_value, cli_args, expected_calls
|
||||||
|
):
|
||||||
|
audio = tmp_path / "meeting.mp3"
|
||||||
|
audio.write_bytes(b"fake")
|
||||||
|
result = _make_result(segments=[])
|
||||||
|
model = _make_model()
|
||||||
|
backend = _make_backend()
|
||||||
|
backend.word_timestamps_available = True
|
||||||
|
tfr = _make_tfr(result=result, model=model, backend=backend)
|
||||||
|
load_diarizer = MagicMock()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"local_transcriber.cli.load_config",
|
||||||
|
return_value={"diarize": config_value},
|
||||||
|
),
|
||||||
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
|
patch(
|
||||||
|
"local_transcriber.cli.load_model",
|
||||||
|
return_value=(model, "cpu", backend, "/models/medium"),
|
||||||
|
),
|
||||||
|
patch("local_transcriber.cli._transcribe_file", return_value=tfr),
|
||||||
|
patch("local_transcriber.cli.load_speaker_diarizer", load_diarizer),
|
||||||
|
patch("local_transcriber.cli.write_transcript"),
|
||||||
|
):
|
||||||
|
out = runner.invoke(app, [str(audio), *cli_args])
|
||||||
|
|
||||||
|
assert out.exit_code == 0
|
||||||
|
assert load_diarizer.call_count == expected_calls
|
||||||
|
|
||||||
|
|
||||||
def test_cli_config_overrides_auto_device(tmp_path):
|
def test_cli_config_overrides_auto_device(tmp_path):
|
||||||
audio = tmp_path / "test.mp3"
|
audio = tmp_path / "test.mp3"
|
||||||
audio.write_bytes(b"fake")
|
audio.write_bytes(b"fake")
|
||||||
|
|||||||
+34
-1
@@ -1,4 +1,3 @@
|
|||||||
from pathlib import Path
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -41,6 +40,23 @@ def test_load_config_valid(tmp_path):
|
|||||||
assert result == {"model": "small", "language": "ru"}
|
assert result == {"model": "small", "language": "ru"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("value", [True, False])
|
||||||
|
def test_load_config_accepts_diarize_boolean(tmp_path, value):
|
||||||
|
config = tmp_path / "config.toml"
|
||||||
|
config.write_text(f"diarize = {str(value).lower()}\n")
|
||||||
|
|
||||||
|
assert load_config(config) == {"diarize": value}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("toml_value", ['"true"', "1"])
|
||||||
|
def test_load_config_rejects_non_boolean_diarize(tmp_path, toml_value):
|
||||||
|
config = tmp_path / "config.toml"
|
||||||
|
config.write_text(f"diarize = {toml_value}\n")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="должно быть логическим"):
|
||||||
|
load_config(config)
|
||||||
|
|
||||||
|
|
||||||
def test_load_config_malformed(tmp_path):
|
def test_load_config_malformed(tmp_path):
|
||||||
config = tmp_path / "config.toml"
|
config = tmp_path / "config.toml"
|
||||||
config.write_text("this is not valid toml [[[")
|
config.write_text("this is not valid toml [[[")
|
||||||
@@ -100,9 +116,26 @@ def test_resolve_defaults_hardcoded_fallback():
|
|||||||
"language": "ru",
|
"language": "ru",
|
||||||
"device": "auto",
|
"device": "auto",
|
||||||
"compute_type": "float32",
|
"compute_type": "float32",
|
||||||
|
"diarize": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("cli_value", "config_value", "expected"),
|
||||||
|
[
|
||||||
|
(None, True, True),
|
||||||
|
(None, False, False),
|
||||||
|
(True, False, True),
|
||||||
|
(False, True, False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_resolve_defaults_diarize_priority(cli_value, config_value, expected):
|
||||||
|
cli = {"diarize": cli_value}
|
||||||
|
config = {"diarize": config_value}
|
||||||
|
|
||||||
|
assert resolve_defaults(cli, config)["diarize"] is expected
|
||||||
|
|
||||||
|
|
||||||
def test_apply_device_defaults_cuda():
|
def test_apply_device_defaults_cuda():
|
||||||
defaults = {"model": "medium", "language": "ru", "device": "auto", "compute_type": "float32"}
|
defaults = {"model": "medium", "language": "ru", "device": "auto", "compute_type": "float32"}
|
||||||
cli = {"model": None, "language": None, "device": None, "compute_type": None}
|
cli = {"model": None, "language": None, "device": None, "compute_type": None}
|
||||||
|
|||||||
Reference in new issue
Block a user