|
| 1 | +from email.mime.multipart import MIMEMultipart |
| 2 | +from email.message import Message |
| 3 | +import json |
| 4 | +import struct |
| 5 | + |
| 6 | +from flask import Flask, request, Response |
| 7 | +from google.cloud import speech |
| 8 | + |
| 9 | + |
| 10 | +app = Flask(__name__) |
| 11 | + |
| 12 | + |
| 13 | +# We know gunicorn does this, but it doesn't *say* it does this, so we must signal it manually. |
| 14 | +@app.before_request |
| 15 | +def handle_chunking(): |
| 16 | + request.environ['wsgi.input_terminated'] = 1 |
| 17 | + |
| 18 | + |
| 19 | +def parse_chunks(stream): |
| 20 | + boundary = b'--' + request.headers['content-type'].split(';')[1].split('=')[1].encode('utf-8').strip() # super lazy/brittle parsing. |
| 21 | + print("Boundary: " + boundary.decode('utf-8')) |
| 22 | + this_frame = b'' |
| 23 | + while True: |
| 24 | + content = stream.read(4096) |
| 25 | + this_frame += content |
| 26 | + end = this_frame.find(boundary) |
| 27 | + if end > -1: |
| 28 | + frame = this_frame[:end] |
| 29 | + if frame != b'': |
| 30 | + header, content = frame.split(b'\r\n\r\n', 1) |
| 31 | + print(content) |
| 32 | + yield content[:-2] |
| 33 | + this_frame = this_frame[end + len(boundary):] |
| 34 | + if content == b'': |
| 35 | + print("End of input.") |
| 36 | + break |
| 37 | + |
| 38 | + |
| 39 | +def parse_data(): |
| 40 | + boundary = b'--' + request.headers['content-type'].split(';')[1].split('=')[1].encode('utf-8').strip() # super lazy/brittle parsing. |
| 41 | + parts = request.data.split(boundary) |
| 42 | + for part in parts: |
| 43 | + if part == b'': |
| 44 | + continue |
| 45 | + yield part.split(b'\r\n\r\n', 1)[1][:-2] |
| 46 | + |
| 47 | + |
| 48 | +@app.route('/NmspServlet/', methods=["POST"]) |
| 49 | +def recognise(): |
| 50 | + |
| 51 | + client = speech.SpeechClient() |
| 52 | + stream = request.stream |
| 53 | + chunks = iter(list(parse_chunks(stream))) |
| 54 | + content = next(chunks).decode('utf-8') |
| 55 | + print(content) |
| 56 | + |
| 57 | + config = speech.types.RecognitionConfig( |
| 58 | + encoding='SPEEX_WITH_HEADER_BYTE', |
| 59 | + language_code='en-US', |
| 60 | + sample_rate_hertz=16000, |
| 61 | + ) |
| 62 | + print('beginning request') |
| 63 | + responses = client.streaming_recognize( |
| 64 | + config=speech.types.StreamingRecognitionConfig(config=config), |
| 65 | + requests=( |
| 66 | + speech.types.StreamingRecognizeRequest(audio_content=struct.pack('B', len(x)) + x) |
| 67 | + for x in chunks)) |
| 68 | + print('finished request') |
| 69 | + words = [] |
| 70 | + for response in responses: |
| 71 | + if response.results: |
| 72 | + for result in response.results: |
| 73 | + words.extend({ |
| 74 | + 'word': x, |
| 75 | + 'confidence': result.alternatives[0].confidence |
| 76 | + } for x in result.alternatives[0].transcript.split(' ')) |
| 77 | + |
| 78 | + # Now for some reason we also need to give back a mime/multipart message... |
| 79 | + parts = MIMEMultipart() |
| 80 | + response_part = Message() |
| 81 | + response_part.add_header('Content-Type', 'application/JSON; charset=utf-8') |
| 82 | + |
| 83 | + if len(words) > 0: |
| 84 | + response_part.add_header('Content-Disposition', 'form-data; name="QueryResult"') |
| 85 | + words[0]['word'] += '\\*no-space-before' |
| 86 | + response_part.set_payload(json.dumps({ |
| 87 | + 'words': [words], |
| 88 | + })) |
| 89 | + else: |
| 90 | + response_part.add_header('Content-Disposition', 'form-data; name="QueryRetry"') |
| 91 | + # Other errors probably exist, but I don't know what they are. |
| 92 | + # This is a Nuance error verbatim. |
| 93 | + response_part.set_payload(json.dumps({ |
| 94 | + "Cause": 1, |
| 95 | + "Name": "AUDIO_INFO", |
| 96 | + "Prompt": "Sorry, speech not recognized. Please try again." |
| 97 | + })) |
| 98 | + parts.attach(response_part) |
| 99 | + print(parts.as_string()) |
| 100 | + |
| 101 | + response = Response(parts.as_string().split("\n", 3)[3]) |
| 102 | + response.headers['Content-Type'] = f'multipart/form-data; boundary={parts.get_boundary()}' |
| 103 | + response.headers['Connection'] = 'close' |
| 104 | + return response |
| 105 | + |
0 commit comments