39 lines
2.2 KiB
Python
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()
|