mirror of
https://github.com/EDeev/api_processor.git
synced 2026-10-07 20:49:34 +03:00
Распознавание речи: исправлены ошибки
- любой файл, кроме WAV 16 кГц моно, падал с NameError: вызывался несуществующий convert.convert_audio_to_wav; - из длинной записи оставалась только последняя фраза: бралось лишь FinalResult(), а Vosk отдаёт текст по фразам через Result(); - общий временный audio.wav в текущей папке — одновременные запросы перезаписывали друг другу аудио; теперь свой временный файл на запрос; - ffmpeg — из PATH (или FFMPEG_BINARY) вместо Windows-пути к ffmpeg.exe; - модель загружается один раз на процесс, а не на каждый запрос; - ошибка конвертации возвращается клиенту, а не печатается и не превращается в пустой текст.
This commit is contained in:
parent
c004e79119
commit
fc2f750cee
2 changed files with 63 additions and 33 deletions
|
|
@ -1,51 +1,81 @@
|
||||||
import os, wave, vosk, ffmpeg
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import threading
|
||||||
|
import wave
|
||||||
|
|
||||||
MODEL_PATH = r"models/vosk-model-small-ru-0.22"
|
import ffmpeg
|
||||||
FFMPEG_PATH = r"models/ffmpeg/bin/ffmpeg.exe"
|
import vosk
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
def convert_audio_to_wav(input_file, output_file, FFMPEG_PATH):
|
logger = logging.getLogger(__name__)
|
||||||
|
vosk.SetLogLevel(-1)
|
||||||
|
|
||||||
|
_model = None
|
||||||
|
_model_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
class RecognitionError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_model():
|
||||||
|
"""Модель Vosk загружается один раз на процесс (раньше — на каждый запрос, это секунды и сотни МБ)"""
|
||||||
|
global _model
|
||||||
|
with _model_lock:
|
||||||
|
if _model is None:
|
||||||
|
if not os.path.isdir(settings.VOSK_MODEL_PATH):
|
||||||
|
raise RecognitionError(f"Модель Vosk не найдена: {settings.VOSK_MODEL_PATH}")
|
||||||
|
_model = vosk.Model(str(settings.VOSK_MODEL_PATH))
|
||||||
|
return _model
|
||||||
|
|
||||||
|
|
||||||
|
def convert_audio_to_wav(input_file, output_file):
|
||||||
|
"""WAV 16 кГц моно для Vosk, с шумоподавлением и нормализацией громкости"""
|
||||||
try:
|
try:
|
||||||
(
|
(
|
||||||
ffmpeg
|
ffmpeg
|
||||||
.input(input_file)
|
.input(input_file)
|
||||||
.output(output_file, format='wav', acodec='pcm_s16le', ar='16000', ac=1,
|
.output(output_file, format='wav', acodec='pcm_s16le', ar='16000', ac=1,
|
||||||
af='acompressor,afftdn,dynaudnorm,aresample=16000') # 16kHz для Vosk
|
af='acompressor,afftdn,dynaudnorm,aresample=16000') # 16kHz для Vosk
|
||||||
.global_args('-loglevel', 'quiet')
|
.global_args('-loglevel', 'error')
|
||||||
.run(cmd=FFMPEG_PATH, overwrite_output=True)
|
.run(cmd=settings.FFMPEG_BINARY, overwrite_output=True, capture_stdout=True, capture_stderr=True)
|
||||||
)
|
)
|
||||||
print(f"Конвертация завершена: {output_file}")
|
|
||||||
except ffmpeg.Error as e:
|
except ffmpeg.Error as e:
|
||||||
print("Ошибка при конвертации:", e.stderr.decode())
|
raise RecognitionError("Не удалось прочитать аудиофайл") from e
|
||||||
|
|
||||||
|
|
||||||
vosk.SetLogLevel(-1)
|
def is_vosk_ready_wav(path) -> bool:
|
||||||
|
try:
|
||||||
|
with wave.open(path, "rb") as wf:
|
||||||
|
return wf.getnchannels() == 1 and wf.getsampwidth() == 2 and wf.getframerate() == 16000
|
||||||
|
except (wave.Error, EOFError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def recognize_speech(audio_path) -> str:
|
def recognize_speech(audio_path) -> str:
|
||||||
if not os.path.exists(MODEL_PATH):
|
model = get_model()
|
||||||
print("Ошибка: Модель не найдена!")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
model = vosk.Model(MODEL_PATH)
|
# временный файл — свой на каждый запрос (раньше общий audio.wav в текущей папке:
|
||||||
|
# одновременные запросы перезаписывали друг другу аудио)
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
if not (audio_path.lower().endswith(".wav") and is_vosk_ready_wav(audio_path)):
|
||||||
|
wav_path = os.path.join(tmp, "audio.wav")
|
||||||
|
convert_audio_to_wav(audio_path, wav_path)
|
||||||
|
audio_path = wav_path
|
||||||
|
|
||||||
if audio_path.split('.')[-1] != "wav":
|
# Vosk отдаёт текст по фразам: раньше бралось только FinalResult(), и в длинной записи
|
||||||
convert.convert_audio_to_wav(audio_path, "audio.wav", FFMPEG_PATH)
|
# оставалась лишь последняя фраза
|
||||||
audio_path = "audio.wav"
|
parts = []
|
||||||
else:
|
|
||||||
with wave.open(audio_path, "rb") as wf:
|
with wave.open(audio_path, "rb") as wf:
|
||||||
if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getframerate() != 16000:
|
|
||||||
convert.convert_audio_to_wav(audio_path, "audio.wav", FFMPEG_PATH)
|
|
||||||
audio_path = "audio.wav"
|
|
||||||
|
|
||||||
|
|
||||||
with wave.open(audio_path, "rb") as wf: # использование vosk
|
|
||||||
recognizer = vosk.KaldiRecognizer(model, wf.getframerate())
|
recognizer = vosk.KaldiRecognizer(model, wf.getframerate())
|
||||||
while True:
|
while True:
|
||||||
data = wf.readframes(3200)
|
data = wf.readframes(4000)
|
||||||
if not data:
|
if not data:
|
||||||
break
|
break
|
||||||
recognizer.AcceptWaveform(data)
|
if recognizer.AcceptWaveform(data):
|
||||||
|
parts.append(json.loads(recognizer.Result()).get("text", ""))
|
||||||
|
parts.append(json.loads(recognizer.FinalResult()).get("text", ""))
|
||||||
|
|
||||||
if audio_path == "audio.wav":
|
return " ".join(p for p in parts if p)
|
||||||
os.remove(audio_path)
|
|
||||||
|
|
||||||
return recognizer.FinalResult().split(": \"")[-1][:-3]
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue