Клик по времени не работал: в скрипте страницы черновика снова оказался настоящий перевод строки вместо экранированного, и весь скрипт не выполнялся. Тот же огрех, что был в странице разбора. Проверка парности кавычек теперь охватывает обе страницы. Плеер приклеен к верху на обеих страницах: спорное место слушают, не прокручивая наверх. Черновик подсвечивал слово при несогласии любой модели - жёлтым выходил почти весь текст. Теперь по умолчанию нужно несогласие большинства: подсветки втрое меньше (152 слова против 385), но в чистой части 95,4 % верных вместо 98 %. Строгий режим доступен ссылкой; порог применяется при показе, поэтому переключение не требует пересборки. Режим ensemble: вторая модель подставляется там, где основная промолчала. Задаётся настройкой и параметром запроса - одну запись можно прогнать в обоих режимах и сравнить на странице разбора. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
50 lines
2.4 KiB
Python
50 lines
2.4 KiB
Python
"""Обработка задачи в отдельном процессе.
|
|
|
|
Замеры показали, что sherpa-onnx и onnxruntime держат GIL: две задачи в двух
|
|
потоках идут ровно столько же, сколько подряд (выигрыш 1.04x). Поэтому
|
|
процессор простаивает, а воркеры-потоки бесполезны. В процессах те же две
|
|
задачи дают 1.59x даже с загрузкой моделей в каждом.
|
|
|
|
Модуль намеренно не импортирует app.main: на Windows дочерний процесс
|
|
поднимается через spawn и заново импортирует модуль с функцией. Если бы
|
|
это был main, в каждом процессе поднимался бы ещё один веб-сервер.
|
|
"""
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from app.pipeline import Pipeline
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
# Живёт внутри процесса-воркера: модели грузятся один раз на процесс.
|
|
_pipeline: Pipeline | None = None
|
|
|
|
|
|
def init_worker(models_dir: str, threads: int, replacements_path: str, base_dir: str) -> None:
|
|
"""Готовит процесс к работе: грузит модели один раз."""
|
|
global _pipeline
|
|
logging.basicConfig(level=logging.WARNING)
|
|
_pipeline = Pipeline(models_dir=Path(models_dir), threads=threads,
|
|
replacements_path=Path(replacements_path),
|
|
base_dir=Path(base_dir))
|
|
_pipeline.warmup()
|
|
|
|
|
|
def run_job(audio_path: str, num_speakers: int, ffmpeg: str,
|
|
normalize: bool = True, recover_gaps: bool = True,
|
|
diarize: bool = False, ensemble: bool = False) -> dict:
|
|
"""Переводит файл в WAV и распознаёт. Выполняется в процессе-воркере."""
|
|
import tempfile
|
|
|
|
from app.pipeline import to_wav16k
|
|
|
|
if _pipeline is None:
|
|
raise RuntimeError("процесс-воркер не инициализирован")
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
wav = Path(tmp) / "audio.wav"
|
|
to_wav16k(Path(audio_path), wav, ffmpeg, normalize)
|
|
return _pipeline.transcribe(wav, num_speakers=num_speakers,
|
|
recover_gaps=recover_gaps, diarize=diarize,
|
|
ensemble=ensemble)
|