lib_aiui.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. """
  2. _on_open
  3. {'action': 'started', 'data': '', 'sid': 'awa027d6d87@dx000118c0e4f8a15800', 'code': '0', 'desc': 'success'}
  4. {'action': 'result', 'data': {'sub': 'iat', 'auth_id': '473c6d01e01d5105499d83b7d293c4bd', 'text': {'bg': 0, 'ed': 0, 'ls': False, 'pgs': 'apd', 'sn': 1, 'ws': [{'bg': 0, 'cw': [{'sc': 0, 'w': '今天'}]}, {'bg': 0, 'cw': [{'sc': 0, 'w': '天气'}]}]}, 'result_id': 1, 'is_last': False, 'is_finish': False, 'json_args': {'language': 'zh-cn', 'accent': 'mandarin'}}, 'sid': 'awa027d6d87@dx000118c0e4f8a15800', 'code': '0', 'desc': 'success'}
  5. {'code': '0', 'sub': 'iat', 'is_last': False, 'is_finish': False, 'pgs': 'apd', 'service': 'raw', 'rawText': '今天天气'}
  6. {'action': 'result', 'data': {'sub': 'iat', 'auth_id': '473c6d01e01d5105499d83b7d293c4bd', 'text': {'bg': 0, 'ed': 0, 'ls': False, 'pgs': 'rpl', 'rg': [1, 1], 'sn': 2, 'ws': [{'bg': 0, 'cw': [{'sc': 0, 'w': '今天'}]}, {'bg': 0, 'cw': [{'sc': 0, 'w': '天气'}]}, {'bg': 0, 'cw': [{'sc': 0, 'w': '怎么样'}]}]}, 'result_id': 2, 'is_last': False, 'is_finish': False, 'json_args': {'language': 'zh-cn', 'accent': 'mandarin'}}, 'sid': 'awa027d6d87@dx000118c0e4f8a15800', 'code': '0', 'desc': 'success'}
  7. {'code': '0', 'sub': 'iat', 'is_last': False, 'is_finish': False, 'pgs': 'rpl', 'service': 'raw', 'rawText': '今天天气怎么样'}
  8. {'action': 'result', 'data': {'sub': 'iat', 'auth_id': '473c6d01e01d5105499d83b7d293c4bd', 'text': {'bg': 0, 'ed': 0, 'ls': False, 'pgs': 'rpl', 'rg': [1, 2], 'sn': 3, 'ws': [{'bg': 0, 'cw': [{'sc': 0, 'w': '今天'}]}, {'bg': 0, 'cw': [{'sc': 0, 'w': '天气'}]}, {'bg': 0, 'cw': [{'sc': 0, 'w': '怎么样'}]}]}, 'result_id': 3, 'is_last': False, 'is_finish': False, 'json_args': {'language': 'zh-cn', 'accent': 'mandarin'}}, 'sid': 'awa027d6d87@dx000118c0e4f8a15800', 'code': '0', 'desc': 'success'}
  9. {'code': '0', 'sub': 'iat', 'is_last': False, 'is_finish': False, 'pgs': 'rpl', 'service': 'raw', 'rawText': '今天天气怎么样'}
  10. {'action': 'result', 'data': {'sub': 'iat', 'auth_id': '473c6d01e01d5105499d83b7d293c4bd', 'text': {'bg': 0, 'ed': 0, 'ls': False, 'pgs': 'apd', 'sn': 4, 'ws': [{'bg': 0, 'cw': [{'sc': 0, 'w': ''}]}]}, 'result_id': 4, 'is_last': False, 'is_finish': False, 'json_args': {'language': 'zh-cn', 'accent': 'mandarin'}}, 'sid': 'awa027d6d87@dx000118c0e4f8a15800', 'code': '0', 'desc': 'success'}
  11. {'code': '0', 'sub': 'iat', 'is_last': False, 'is_finish': False, 'pgs': 'apd', 'service': 'raw', 'rawText': ''}
  12. {'action': 'result', 'data': {'sub': 'iat', 'auth_id': '473c6d01e01d5105499d83b7d293c4bd', 'text': {'bg': 0, 'ed': 0, 'ls': True, 'pgs': 'rpl', 'rg': [4, 4], 'sn': 5, 'ws': [{'bg': 0, 'cw': [{'sc': 0, 'w': '?'}]}]}, 'result_id': 5, 'is_last': True, 'is_finish': False, 'json_args': {'language': 'zh-cn', 'accent': 'mandarin'}}, 'sid': 'awa027d6d87@dx000118c0e4f8a15800', 'code': '0', 'desc': 'success'}
  13. {'code': '0', 'sub': 'iat', 'is_last': True, 'is_finish': False, 'pgs': 'rpl', 'service': 'raw', 'rawText': '?'}
  14. {'action': 'result', 'data': {'sub': 'nlp', 'auth_id': '473c6d01e01d5105499d83b7d293c4bd', 'intent': {'answer': {'text': '我昨天忘记看天气预报了', 'type': 'T'}, 'category': 'IFLYTEK.weather', 'data': {'error': {'code': 0, 'message': ''}, 'result': []}, 'dialog_stat': 'DataValid', 'rc': 3, 'save_history': True, 'semantic': [{'intent': 'QUERY', 'slots': [{'name': 'datetime', 'normValue': '{"datetime":"2023-11-28","suggestDatetime":"2023-11-28"}', 'value': '今天'}, {'name': 'location.city', 'normValue': 'CURRENT_CITY', 'value': 'CURRENT_CITY'}, {'name': 'location.poi', 'normValue': 'CURRENT_POI', 'value': 'CURRENT_POI'}, {'name': 'location.type', 'normValue': 'LOC_POI', 'value': 'LOC_POI'}, {'name': 'queryType', 'value': '内容'}, {'name': 'subfocus', 'value': '天气状态'}]}], 'service': 'weather', 'shouldEndSession': 'true', 'sid': 'awa027d6d87@dx000118c0e4f8a15800', 'state': {'fg::weather::default::default': {'state': 'default'}}, 'text': '今天天气怎么样?', 'uuid': 'awa027d6d87@dx000118c0e4f8a15800', 'version': '173.0'}, 'result_id': 1, 'is_last': True, 'is_finish': True}, 'sid': 'awa027d6d87@dx000118c0e4f8a15800', 'code': '0', 'desc': 'success'}
  15. {'code': '0', 'sub': 'nlp', 'is_last': True, 'is_finish': True, 'service': 'weather', 'rawText': '今天天气怎么样?'}
  16. _on_error
  17. 1
  18. _on_error
  19. 1
  20. _on_close
  21. """
  22. from PyQt5.QtCore import QObject, QUrl, pyqtSignal
  23. from PyQt5.QtWebSockets import QWebSocket, QWebSocketProtocol
  24. import base64
  25. import hashlib
  26. import json
  27. import time
  28. import uuid
  29. from util import my_util, constants
  30. ORIGIN = "http://wsapi.xfyun.cn"
  31. BASE_URL = "ws://wsapi.xfyun.cn/v1/aiui"
  32. # BASE_URL = "wss://wsapi.xfyun.cn/v1/aiui"
  33. # 结束标识
  34. END_TAG = "--end--"
  35. def get_auth_id():
  36. mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
  37. return hashlib.md5(":".join([mac[e:e + 2] for e in range(0, 11, 2)]).encode("utf-8")).hexdigest()
  38. class AiuiManager(QObject):
  39. trigger = pyqtSignal(dict)
  40. _app_id = "ab154863"
  41. _api_key = "5a584b63730d3450d64cdd65f17f56d0"
  42. def __init__(self):
  43. super(AiuiManager, self).__init__()
  44. self._last_status = 0
  45. self._last_raw_text = ""
  46. self._last_pgs = None
  47. self._param = {"result_level": "complete", "auth_id": get_auth_id(), "data_type": "audio",
  48. "aue": "raw", "scene": "main", "sample_rate": "16000", "cloud_vad_eos": "60000"}
  49. self.ws = None
  50. def regist_engine(self, aue="raw"):
  51. self.update_params({"aue": aue})
  52. def update_params(self, params):
  53. if params:
  54. self._param.update(params)
  55. def start_speech(self):
  56. print("start_speech 1")
  57. # try:
  58. # self.ws.close()
  59. # except:
  60. # pass
  61. self.ws = QWebSocket(origin=ORIGIN, version=QWebSocketProtocol.VersionLatest)
  62. # 信号连接
  63. self.ws.connected.connect(self._on_open)
  64. self.ws.error.connect(self._on_error)
  65. self.ws.disconnected.connect(self._on_close)
  66. self.ws.textMessageReceived.connect(self._on_message)
  67. self._create_url()
  68. self.ws.open(QUrl(f"{self.ws_url}"))
  69. print("start_speech 2")
  70. def _callback(self, data: dict):
  71. self.trigger.emit(data)
  72. def _create_url(self):
  73. _param_b64 = base64.b64encode(json.dumps(self._param).encode('utf-8'))
  74. _cur_time = str(int(time.time()))
  75. _tmp = self._api_key + _cur_time + _param_b64.decode()
  76. _md5 = hashlib.md5()
  77. _md5.update(_tmp.encode(encoding='utf-8'))
  78. check_sum = _md5.hexdigest()
  79. self.ws_url = BASE_URL + "?appid=" + self._app_id + "&checksum=" + check_sum + "&curtime=" + _cur_time + "&param=" + _param_b64.decode()
  80. # 连接建立时
  81. def _on_open(self):
  82. print('_on_open')
  83. pass
  84. # 连接异常时
  85. def _on_error(self, error_code):
  86. print('_on_error')
  87. print(error_code)
  88. # self.ws.close()
  89. # self._callback(my_util.gen_q_data(constants.AITEST_AIUI_ERROR, self.ws.errorString()))
  90. pass
  91. # 接收到文本消息时
  92. def _on_message(self, message):
  93. data = json.loads(str(message))
  94. print(data)
  95. _action = data["action"]
  96. self._sid = data["sid"]
  97. if _action == "started": # 正常连接
  98. d = f"连接已建立,sid:{self._sid}"
  99. self._callback(my_util.gen_q_data(constants.AITEST_AIUI_LOG, d))
  100. self._callback(my_util.gen_q_data(constants.AITEST_AIUI_START, True))
  101. elif _action == "result":
  102. _data = my_util.parse_aiui_v2_result(data)
  103. print(_data)
  104. # text = self.parse_aiui_engine_stream_result(_data)
  105. # if text:
  106. # self._callback(my_util.gen_q_data(constants.AITEST_AIUI_RESULT, text))
  107. if _data.get("is_finish"):
  108. self._callback(
  109. my_util.gen_q_data(constants.AITEST_AIUI_NLP, json.dumps(data["data"], ensure_ascii=False)))
  110. self._callback(my_util.gen_q_data(constants.AITEST_AIUI_RESULT, _data["rawText"]))
  111. else:
  112. self._callback(my_util.gen_q_data(constants.AITEST_AIUI_ERROR, data))
  113. # 连接断开时
  114. def _on_close(self):
  115. print('_on_close')
  116. d = f"连接已断开!,sid:{self._sid}"
  117. self._callback(my_util.gen_q_data(constants.AITEST_AIUI_LOG, d))
  118. def audio_write(self, buf: bytes):
  119. if self.ws:
  120. self.ws.sendBinaryMessage(buf)
  121. def stop_speech(self):
  122. if self.ws:
  123. d = f"sid:{self._sid},发送结束标识!!!"
  124. self._callback(my_util.gen_q_data(constants.AITEST_AIUI_LOG, d))
  125. self.ws.sendBinaryMessage(bytes(END_TAG.encode("utf-8")))
  126. def parse_aiui_engine_stream_result(self, data: dict):
  127. sub = data.get("sub")
  128. if sub != "iat":
  129. return ""
  130. raw_text = data.get("rawText")
  131. is_last = data.get("is_last")
  132. last_text = data.get("last_text")
  133. pgs = data.get("pgs")
  134. status = 0
  135. if pgs == "apd":
  136. if my_util.symbol_in_word(raw_text):
  137. status = 1
  138. if not self._last_status: # 短句
  139. text = self._last_raw_text + raw_text
  140. else:
  141. text = raw_text
  142. elif self._last_pgs == "rpl" and not self._last_status:
  143. status = 1
  144. text = self._last_raw_text
  145. else:
  146. status = 0
  147. text = raw_text
  148. else:
  149. if my_util.symbol_in_word(raw_text):
  150. status = 1
  151. text = raw_text
  152. else:
  153. status = 0
  154. text = raw_text
  155. self._last_status = status
  156. self._last_pgs = pgs
  157. self._last_raw_text = text
  158. if status == 1:
  159. _text = text
  160. else:
  161. _text = ""
  162. return _text