Files

39 lines
2.2 KiB
Python

"""Private, local Russian speech recognition. The model stays on the Pi."""
import json, subprocess, threading
from pathlib import Path
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from vosk import Model, KaldiRecognizer, SetLogLevel
SetLogLevel(-1)
model = Model('/model/vosk-model-small-ru-0.22')
lock = threading.Semaphore(1)
root = Path('/app/Data/Attachments').resolve()
class Handler(BaseHTTPRequestHandler):
def answer(self, status, body):
content = json.dumps(body, ensure_ascii=False).encode()
self.send_response(status); self.send_header('Content-Type','application/json'); self.send_header('Content-Length',str(len(content))); self.end_headers(); self.wfile.write(content)
def do_GET(self):
self.answer(200, {'status':'ok','engine':'vosk','language':'ru'})
def do_POST(self):
if self.path != '/transcribe': return self.answer(404, {})
try:
length = int(self.headers.get('Content-Length','0'))
if length <= 0 or length > 4096: return self.answer(400, {})
data = json.loads(self.rfile.read(length))
path = Path(data['path']).resolve()
if not path.is_relative_to(root) or not path.is_file(): return self.answer(403, {'error':'audio path rejected'})
with lock:
decoded = subprocess.run(['ffmpeg','-nostdin','-loglevel','error','-threads','1','-i',str(path),'-t','600','-ar','16000','-ac','1','-f','s16le','pipe:1'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=90, check=True)
recognizer = KaldiRecognizer(model, 16000)
words = []
for offset in range(0,len(decoded.stdout),8000):
if recognizer.AcceptWaveform(decoded.stdout[offset:offset+8000]): words.append(json.loads(recognizer.Result()).get('text',''))
words.append(json.loads(recognizer.FinalResult()).get('text',''))
self.answer(200, {'text':' '.join(part for part in words if part)})
except Exception:
self.answer(422, {'error':'audio could not be decoded'})
def log_message(self, *_): pass
ThreadingHTTPServer(('0.0.0.0',8766), Handler).serve_forever()