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"
|
||||
FFMPEG_PATH = r"models/ffmpeg/bin/ffmpeg.exe"
|
||||
import ffmpeg
|
||||
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:
|
||||
(
|
||||
ffmpeg
|
||||
.input(input_file)
|
||||
.output(output_file, format='wav', acodec='pcm_s16le', ar='16000', ac=1,
|
||||
af='acompressor,afftdn,dynaudnorm,aresample=16000') # 16kHz для Vosk
|
||||
.global_args('-loglevel', 'quiet')
|
||||
.run(cmd=FFMPEG_PATH, overwrite_output=True)
|
||||
.global_args('-loglevel', 'error')
|
||||
.run(cmd=settings.FFMPEG_BINARY, overwrite_output=True, capture_stdout=True, capture_stderr=True)
|
||||
)
|
||||
print(f"Конвертация завершена: {output_file}")
|
||||
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:
|
||||
if not os.path.exists(MODEL_PATH):
|
||||
print("Ошибка: Модель не найдена!")
|
||||
return ""
|
||||
model = get_model()
|
||||
|
||||
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":
|
||||
convert.convert_audio_to_wav(audio_path, "audio.wav", FFMPEG_PATH)
|
||||
audio_path = "audio.wav"
|
||||
else:
|
||||
# Vosk отдаёт текст по фразам: раньше бралось только FinalResult(), и в длинной записи
|
||||
# оставалась лишь последняя фраза
|
||||
parts = []
|
||||
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"
|
||||
recognizer = vosk.KaldiRecognizer(model, wf.getframerate())
|
||||
while True:
|
||||
data = wf.readframes(4000)
|
||||
if not data:
|
||||
break
|
||||
if recognizer.AcceptWaveform(data):
|
||||
parts.append(json.loads(recognizer.Result()).get("text", ""))
|
||||
parts.append(json.loads(recognizer.FinalResult()).get("text", ""))
|
||||
|
||||
|
||||
with wave.open(audio_path, "rb") as wf: # использование vosk
|
||||
recognizer = vosk.KaldiRecognizer(model, wf.getframerate())
|
||||
while True:
|
||||
data = wf.readframes(3200)
|
||||
if not data:
|
||||
break
|
||||
recognizer.AcceptWaveform(data)
|
||||
|
||||
if audio_path == "audio.wav":
|
||||
os.remove(audio_path)
|
||||
|
||||
return recognizer.FinalResult().split(": \"")[-1][:-3]
|
||||
return " ".join(p for p in parts if p)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue