main.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  1. """
  2. 调用讯飞演示笔获取录音
  3. QObject::connect: Cannot queue arguments of type 'QTextCursor'
  4. (Make sure 'QTextCursor' is registered using qRegisterMetaType().)
  5. """
  6. import os
  7. import sys
  8. from functools import partial
  9. import json
  10. import sounddevice as sd
  11. import numpy as np
  12. import threading
  13. import queue
  14. import time
  15. import pyautogui
  16. import win32gui
  17. from datetime import datetime, timedelta
  18. from PyQt5 import uic, QtCore
  19. from PyQt5.QtCore import QSize, Qt, QUrl
  20. from PyQt5.QtGui import QIcon, QPixmap, QDesktopServices
  21. from PyQt5.QtWidgets import QMainWindow, QPushButton, QTextBrowser, QLabel, QApplication, QSystemTrayIcon, QMenu, \
  22. QAction, QVBoxLayout, QHBoxLayout, QWidget, QListWidget, QStackedWidget, QListWidgetItem, QScrollArea
  23. from ctypes import cdll, byref, c_void_p, CFUNCTYPE, c_char_p, c_uint64, c_int64
  24. import mspeech_ui_thr as mspeech
  25. # from libs import lib_aiui, lib_asr_dipai, lib_player, mock_button
  26. from libs import lib_player, mock_button
  27. import lib_grpc
  28. import lib_to_ws
  29. from util import constants
  30. from pynput import keyboard
  31. from windows.DraggableWindow import DraggableWindow
  32. from windows.SettingsPage import SettingsPage
  33. from windows.AboutPage import AboutPage
  34. from FileSettingPage import FileSettingPage
  35. from logger_config import logger
  36. import config
  37. from EverythingResultWin import ResultWindow
  38. # from BusinessHadler import BusinessHadler
  39. # socketHadler = None # 全局变量
  40. TTS_FLAG = False
  41. class MainUI(QMainWindow):
  42. wake_up_successful = False
  43. instance = None
  44. def __init__(self):
  45. super().__init__(flags=Qt.WindowType.Window)
  46. MainUI.instance = self # 保存类的实例以供静态方法调用
  47. self._run_flag = False
  48. self.tts_player = None
  49. # 读取配置文件中的引擎类型
  50. self.engine_type = config.config["ENGINE_TYPE"]
  51. # 读取配置文件中的客户端模式
  52. self.mode_type = config.config["MODE_TYPE"]
  53. # 系统麦克风所需变量
  54. self.samplerate = 16000
  55. self.channels = 1
  56. self.recording = False
  57. self._audio_queue = queue.Queue()
  58. self.buffer = b""
  59. # 记录语音唤醒所需变量
  60. self.sessionID_wakeup = None
  61. self.dll = None
  62. self.last_recognition_time = None # 用于记录上次识别的时间
  63. self.timer = None # 用于定时检查是否超过5分钟
  64. # 初始化监听快捷键,当客户端模式配置为系统麦克风时才需要初始化
  65. self.vad_flag = False
  66. if self.mode_type == "1":
  67. self.vad_flag = True
  68. self._init_wakeup()
  69. self.init_record_device()
  70. # 初始化演示笔,当客户端模式配置为演示笔时才需要初始化
  71. if self.mode_type == "0":
  72. self.init_thr()
  73. # 初始话客户端页面
  74. self.init_ui()
  75. def init_thr(self):
  76. # 子线程
  77. self._mspeech = mspeech.Mspeech()
  78. self._mspeech.daemon = True
  79. self._mspeech.start()
  80. # 处理子线程发送的数据
  81. self._mspeech.sign_thread_send.connect(self._proc)
  82. # 业务处理(处理子进程和子线程发送的数据)
  83. def _proc(self, result):
  84. try:
  85. code = result["code"]
  86. if code != constants.MSPEECH_AIUI_SEND_DATA:
  87. print("UI_main_proc_real", code)
  88. if code == constants.MSPEECH_AIUI_SEND_DATA: # aiui
  89. QtCore.QTimer.singleShot(0, partial(self._ist.audio_write, result["data"]))
  90. elif code == constants.MSPEECH_AIUI_RESET_DICTATION: # aiui
  91. self._start_record_pen()
  92. elif code == constants.MSPEECH_AIUI_STOP_WS: # aiui
  93. self._stop_record_pen()
  94. except Exception as e:
  95. print(e)
  96. pass
  97. def init_ui(self):
  98. self.resize(800, 600)
  99. self.center()
  100. self.setWindowTitle('语音智控')
  101. self.setWindowIcon(QIcon("images/logo.ico"))
  102. self.init_system_tray()
  103. self.init_main_ui()
  104. self.show()
  105. # 初始化识别文本组件
  106. self.init_d_ui()
  107. asr_show = config.config["ASR_TXT_SHOW"]
  108. if asr_show == "0":
  109. self.d_window.hide()
  110. else:
  111. self.d_window.show()
  112. self._init_ist()
  113. def init_main_ui(self):
  114. '''加载界面ui'''
  115. with open('css/QListWidgetQSS.qss', 'r') as f: # 导入QListWidget的qss样式
  116. self.list_style = f.read()
  117. # 中心部件
  118. self.central_widget = QWidget()
  119. self.setCentralWidget(self.central_widget)
  120. self.main_layout = QHBoxLayout(self.central_widget) # 窗口的整体布局
  121. self.main_layout.setContentsMargins(0, 0, 0, 0)
  122. self.left_widget = QListWidget() # 左侧选项列表
  123. self.left_widget.setStyleSheet(self.list_style)
  124. self.main_layout.addWidget(self.left_widget)
  125. self.right_widget = QStackedWidget()
  126. self.main_layout.addWidget(self.right_widget)
  127. self.left_widget.currentRowChanged.connect(self.right_widget.setCurrentIndex) # list和右侧窗口的index对应绑定
  128. self.left_widget.setFrameShape(QListWidget.NoFrame) # 去掉边框
  129. self.left_widget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 隐藏滚动条
  130. self.left_widget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
  131. # 左侧选项的添加
  132. self.item = QListWidgetItem('基础设置', self.left_widget)
  133. self.item.setSizeHint(QSize(30, 60))
  134. self.item.setTextAlignment(Qt.AlignCenter) # 居中显示
  135. # 设置页面
  136. self.settingsPage = SettingsPage()
  137. self.right_widget.addWidget(self.settingsPage)
  138. # 配置修改后的信号
  139. self.settingsPage.floatingWindowEnableCheckBox.stateChanged.connect(self.handleFloatingWindowStateChanged)
  140. self.settingsPage.colorLineEdit.textChanged.connect(self.handleColorTextChanged)
  141. self.item = QListWidgetItem('个人配置', self.left_widget)
  142. self.item.setSizeHint(QSize(30, 60))
  143. self.item.setTextAlignment(Qt.AlignCenter) # 居中显示
  144. # 设置页面
  145. self.fileSettingPage = FileSettingPage(config.local_commands)
  146. self.right_widget.addWidget(self.fileSettingPage)
  147. self.item = QListWidgetItem('关于', self.left_widget)
  148. self.item.setSizeHint(QSize(30, 60))
  149. self.item.setTextAlignment(Qt.AlignCenter) # 居中显示
  150. # 设置页面
  151. self.aboutPage = AboutPage()
  152. self.right_widget.addWidget(self.aboutPage)
  153. # 默认选中“设置”
  154. self.left_widget.setCurrentRow(0)
  155. # 初始化可拖拽界面,显示语音识别文字
  156. def init_d_ui(self):
  157. # 创建悬浮窗口
  158. self.d_window = DraggableWindow()
  159. # 主布局
  160. mainLayout = QVBoxLayout()
  161. # 为了居中语音图标,使用水平布局
  162. iconLayout = QHBoxLayout()
  163. labelIcon = QLabel()
  164. labelIcon.setPixmap(QPixmap("images/123.png").scaled(80, 80, Qt.KeepAspectRatio))
  165. iconLayout.addStretch() # 在图标前面添加伸展空间
  166. iconLayout.addWidget(labelIcon)
  167. iconLayout.addStretch() # 在图标后面添加伸展空间
  168. # 设置语音识别文本
  169. self.text_label = QLabel()
  170. self.text_label.setWordWrap(True) # 设置文本自动换行
  171. self.text_label.setAlignment(Qt.AlignCenter) # 设置文本居中对齐
  172. asr_color = config.config["ASR_TXT_COLOR"]
  173. self.text_label.setStyleSheet(f"color: {asr_color};") # 设置文本颜色
  174. # 创建滚动区域并将文本标签放置其中,解决文本过长时,上下部分被遮挡
  175. scrollArea = QScrollArea()
  176. scrollArea.setWidgetResizable(True) # 设置滚动区域自适应大小
  177. scrollArea.setWidget(self.text_label) # 将文本标签放置在滚动区域内
  178. # 设置滚动区域的背景和边框都透明
  179. scrollArea.setStyleSheet("background: transparent; border: none;")
  180. mainLayout.addLayout(iconLayout) # 将图标布局添加到主布局
  181. # mainLayout.addWidget(self.text_label) # 将文本标签添加到主布局
  182. mainLayout.addWidget(scrollArea) # 将滚动区域添加到主布局
  183. self.d_window.setLayout(mainLayout)
  184. self.d_window.adjustSize()
  185. # 获取屏幕的宽度和高度
  186. screen = app.primaryScreen()
  187. screen_geometry = screen.geometry()
  188. screen_width = screen_geometry.width()
  189. screen_height = screen_geometry.height()
  190. # 计算悬浮窗口的坐标
  191. window_x = screen_width - self.d_window.width()
  192. window_y = screen_height - self.d_window.height() - 80
  193. # 移动悬浮窗口到右下角
  194. self.d_window.move(window_x, window_y)
  195. # 初始化系统托盘图标
  196. def init_system_tray(self):
  197. # 创建系统托盘图标
  198. self.tray_icon = QSystemTrayIcon(self)
  199. self.tray_icon.setIcon(QIcon("images/logo.ico"))
  200. # 创建托盘菜单
  201. self.tray_menu = QMenu(self)
  202. self.quit_action = QAction("退出", self)
  203. # 将动作添加到菜单
  204. self.tray_menu.addAction(self.quit_action)
  205. # 将菜单设置为托盘图标的菜单
  206. self.tray_icon.setContextMenu(self.tray_menu)
  207. # 连接系统托盘图标的 activated 信号到槽函数,实现在系统托盘点击图标,窗口恢复
  208. self.tray_icon.activated.connect(self.restore_window)
  209. self.tray_icon.show() # 显示系统托盘图标
  210. # 系统托盘图标,点击恢复窗口
  211. def restore_window(self, reason):
  212. if reason == QSystemTrayIcon.ActivationReason.Trigger:
  213. self.showNormal() # 恢复窗口正常大小
  214. # 重写closeEvent方法,阻止关闭窗口,只隐藏
  215. def closeEvent(self, event):
  216. event.ignore() # 忽略关闭事件
  217. self.hide() # 隐藏窗口
  218. # 初始化ist引擎
  219. def _init_ist(self):
  220. # AIUI websocket
  221. # self._ist = lib_aiui.AiuiManager()
  222. # 帝派 websocket
  223. # self._ist = lib_asr_dipai.DiPaiASRWebSocket()
  224. # grpc服务
  225. # self._ist = lib_grpc.AudioRecorderClient()
  226. if self.engine_type == "1":
  227. pass
  228. # self._ist = lib_aiui.AiuiManager()
  229. elif self.engine_type == "2":
  230. pass
  231. # self._ist = lib_asr_dipai.DiPaiASRWebSocket()
  232. else:
  233. logger.info(f"引擎类型:{self.engine_type}")
  234. self._ist = lib_grpc.AudioRecorderClient(self.vad_flag)
  235. self._ist.trigger.connect(self._proc_aiui)
  236. logger.info("ISR引擎初始化成功!")
  237. def _tts_player_callback(self, msg):
  238. print(msg)
  239. if msg == "--end--":
  240. # self._tts_play_end = True
  241. # self._send_data(my_util.gen_q_data(constants.TTS_STEAM_END, None))
  242. self.tts_player.quit()
  243. # 麦克风唤醒模式下,TTS播报完成后,重新开启麦克风
  244. if self.mode_type == "1":
  245. self._restart_record_mic()
  246. elif msg == "--stop--":
  247. self._tts_play_end = False
  248. def tts_play(self, text, vcn=0, speed_mode=0):
  249. _duration = lib_player.calculate_duration(text, speed_mode)
  250. # self._send_data(my_util.gen_q_data(constants.TTS_STEAM_DURATION, _duration))
  251. # _info = {
  252. # "text": text,
  253. # "vcn": vcn,
  254. # "speed": speed_mode
  255. # }
  256. # _info = {
  257. # "text": text,
  258. # "vcn": "50000",
  259. # "speed": speed_mode
  260. # }
  261. _info = {
  262. "text": text,
  263. "vcn": "bingjie",
  264. "speed": "0"
  265. }
  266. # 上一个播放器处于暂停状态
  267. if self.tts_player and self.tts_player.is_paused():
  268. self.tts_player.resume()
  269. return
  270. self.tts_player = lib_player.TTSSteamPlayer(self._tts_player_callback)
  271. self.tts_player.play(_info)
  272. def tts_pause(self):
  273. if self.tts_player:
  274. self.tts_player.pause()
  275. def tts_stop(self):
  276. if self.tts_player:
  277. self.tts_player.stop()
  278. self.tts_player.quit()
  279. # 开启录音---演示笔
  280. def _start_record_pen(self):
  281. print("开始录音")
  282. if not self._run_flag:
  283. self._run_flag = True
  284. self._start_ist_pen()
  285. # 停止录音---演示笔
  286. def _stop_record_pen(self):
  287. print("结束录音")
  288. self._run_flag = False
  289. self._start_ist_pen(stop=True)
  290. # 启动识别引擎---演示笔
  291. def _start_ist_pen(self, stop=False):
  292. if stop:
  293. print("stop")
  294. self._ist.stop_speech()
  295. else:
  296. print("start")
  297. self._ist.start_speech()
  298. # 开启录音---系统麦克风
  299. def _start_record_mic(self):
  300. if not self.recording:
  301. print('开启录音')
  302. logger.info('开启录音')
  303. self.recording = True
  304. # threading.Thread(target=self._read_buf_from_cffi_backend, name="read_record_buf").start()
  305. self._start_ist_mic()
  306. def _restart_record_mic(self):
  307. print("重新开启录音_restart_record_mic")
  308. self.recording = True
  309. # threading.Thread(target=self._read_buf_from_cffi_backend, name="read_record_buf").start()
  310. self._start_ist_mic()
  311. # 停止录音---系统麦克风
  312. def _stop_record_mic(self):
  313. print("_stop_record结束录音")
  314. self.recording = False
  315. self._ist.stop_running()
  316. # 如果有现存的计时器,先取消
  317. if self.timer and self.timer.is_alive():
  318. self.timer.cancel()
  319. def _start_ist_mic(self, stop=False):
  320. if stop:
  321. self._ist.stop_speech()
  322. else:
  323. self._ist.start_speech()
  324. threading.Thread(target=self.send_buf_to_engine, name="send_buf_to_engine").start()
  325. # 发送音频逻辑
  326. def send_buf_to_engine(self):
  327. # print("send_buf_to_engine")
  328. while self.recording:
  329. if not self._audio_queue.empty():
  330. buf = self._audio_queue.get()
  331. # print("发送音频数据长度")
  332. # print(len(buf))
  333. # self.ist.send_message(buf)
  334. QtCore.QTimer.singleShot(0, partial(self._ist.audio_write, buf))
  335. else:
  336. time.sleep(0.02)
  337. # 初始化录音设备
  338. def init_record_device(self):
  339. try:
  340. self._recorder = sd.RawInputStream(samplerate=self.samplerate, channels=self.channels, blocksize=640,
  341. device=sd.default.device, dtype=np.int16, callback=self.audio_callback)
  342. except Exception as e:
  343. self._recorder = None
  344. logger.info(f"初始化错误:{e}")
  345. else:
  346. logger.info(f"设备初始化成功!现在可以开始录音了!当前设置采样率为:16000,声道数为:1")
  347. self._recorder.start()
  348. # 从缓冲区读取录音设备录制的音频数据
  349. def _read_buf_from_cffi_backend(self):
  350. while self.recording:
  351. if not self._audio_queue.empty():
  352. buf = self._audio_queue.get()
  353. self._ist.audio_write(buf)
  354. else:
  355. time.sleep(0.02)
  356. def _proc_aiui(self, msg):
  357. code = msg.get("code")
  358. data = msg.get("data")
  359. # IST引擎开始
  360. if code == constants.AITEST_AIUI_START:
  361. if data:
  362. logger.info(f"IST引擎启动成功!")
  363. # IST引擎异常
  364. elif code == constants.AITEST_AIUI_ERROR:
  365. logger.info(f"IST错误:{data}")
  366. # IST引擎结果
  367. elif code == constants.AITEST_AIUI_RESULT:
  368. logger.info(f"IST转写结果:{data}")
  369. self.text_label.setText(data)
  370. elif code == constants.AITEST_AIUI_LOG:
  371. logger.info(data)
  372. # elif code == constants.AITEST_AIUI_NLP:
  373. # logger.info(data)
  374. # print('开始业务逻辑')
  375. # nlp_date = json.loads(str(data))
  376. # intent = nlp_date['intent']
  377. # if intent != {} and intent['rc'] == 0:
  378. # intent_action = intent['semantic'][0]['intent']
  379. # print("intent_action: ", intent_action)
  380. # # 遍历词槽
  381. # intent_solts = intent['semantic'][0]['slots']
  382. # print("intent_solts: ", intent_solts)
  383. #
  384. # # # 方式1:使用这种方式,当语音命令为打开浏览器操作时,
  385. # # # webbrowser.open()会阻塞_proc方法,导致AIUI重新发送消息给websocket,会导致_proc再执行一次,从而打开2次浏览器的BUG
  386. # # # tts_text = socketHadler.handler(intent_action, intent_solts)
  387. #
  388. # # 方式2:为解决方式1的BUG,在调用业务方式时,先断开再重连的方式
  389. # # 断开信号连接
  390. # # PYQT6 中测试没有这个问题,断开再重连的代码注释掉
  391. # self._ist.trigger.disconnect(self._proc_aiui)
  392. # tts_text = socketHadler.handler(intent_action, intent_solts)
  393. # # 重新连接信号
  394. # self._ist.trigger.connect(self._proc_aiui)
  395. #
  396. # # # 方式3:在socketHadler里面使用以下方式打开,也可以避免打开2次web页面问题
  397. # # # import os
  398. # # # os.system('start http://101.37.148.192:8080/')
  399. #
  400. # if len(tts_text) != 0:
  401. # self.tts_stop()
  402. # self.tts_play(tts_text)
  403. # else:
  404. # self.tts_stop()
  405. # self.tts_play("我没有理解您说的话")
  406. # print("我没有理解你说的话啊")
  407. # elif code == constants.AITEST_DIPAI_NLP:
  408. # # 帝派语义结果
  409. # print('开始业务逻辑' + data)
  410. # # 替换所有的竖线字符
  411. # json_data = data.replace("|", "")
  412. # nlp_date = json.loads(str(json_data), strict=False)
  413. # results = nlp_date['result']['results']
  414. # print(results)
  415. # if results:
  416. # intent_action = results[0][0]['mind_name']
  417. # print("intent_action: ", intent_action)
  418. # # 遍历词槽
  419. # intent_solts = results[0][0]['slots']
  420. # print("intent_solts: ", intent_solts)
  421. #
  422. # # # 方式1:使用这种方式,当语音命令为打开浏览器操作时,
  423. # # # webbrowser.open()会阻塞_proc方法,导致AIUI重新发送消息给websocket,会导致_proc再执行一次,从而打开2次浏览器的BUG
  424. # # # tts_text = socketHadler.handler(intent_action, intent_solts)
  425. #
  426. # # 方式2:为解决方式1的BUG,在调用业务方式时,先断开再重连的方式
  427. # # 断开信号连接
  428. # # PYQT6 中测试没有这个问题,断开再重连的代码注释掉
  429. # # self.ist.signal.disconnect(self._proc)
  430. # tts_text = socketHadler.handler_dipai(intent_action, intent_solts)
  431. # # 重新连接信号
  432. # # self.ist.signal.connect(self._proc)
  433. #
  434. # # # 方式3:在socketHadler里面使用以下方式打开,也可以避免打开2次web页面问题
  435. # # # import os
  436. # # # os.system('start http://101.37.148.192:8080/')
  437. #
  438. # if len(tts_text) != 0:
  439. # self.tts_stop()
  440. # self.tts_play(tts_text)
  441. # else:
  442. # self.tts_play("我没有理解您说的话")
  443. # print("我没有理解你说的话啊")
  444. elif code == constants.AITEST_GPRC_NLP:
  445. print(f'开始业务逻辑AITEST_GPRC_NLP: {data}')
  446. nlp_date = json.loads(str(data))
  447. # 记录语义结果是否成功,成功代表有交互
  448. is_nlp_success = True
  449. # 判断执行动作是否是TTS播报,如果是TTS播报,必须等TTS播报完成后,再重新开启麦克风
  450. is_tts = False
  451. # 通过json中包含的data,判断是经过语义理解的
  452. if 'data' in nlp_date:
  453. output = nlp_date['data']['output']
  454. action_tag = output[:2] # 截取前2个字符
  455. if action_tag == "##": # 流程节点
  456. action_type = output[2] # 截取第4个字符
  457. action_content = output[3:] # 截取第4个字符
  458. # 1:tts播报 2:播放录音 3:打开网页 4:页面跳转 5:大屏切换 6:自定义指令
  459. if action_type == "1":
  460. self.text_label.setText(action_content)
  461. self.tts_stop()
  462. self.tts_play(action_content)
  463. elif action_type == "2":
  464. pass
  465. elif action_type == "3":
  466. os.system(f'start {action_content}')
  467. time.sleep(2) # 根据实际情况调整等待时间
  468. # 发送 F11 键按下事件
  469. pyautogui.press('f11')
  470. elif action_type == "5":
  471. pass
  472. else:
  473. lib_to_ws.send_action_ws(action_type, action_content)
  474. else: # 问答知识库,直接播报
  475. if output:
  476. self.text_label.setText(output)
  477. self.tts_stop()
  478. is_tts = True
  479. self.tts_play(output)
  480. elif 'action_type' in nlp_date:
  481. action_type = nlp_date['action_type']
  482. action_content = nlp_date['action_content']
  483. if action_type == "0":
  484. os.system(f'start {action_content}')
  485. elif action_type == "1":
  486. os.system(f'start {action_content}')
  487. elif action_type == "2":
  488. if action_content == "上一页":
  489. pyautogui.press('pageup')
  490. elif action_content == "下一页":
  491. pyautogui.press('pagedown')
  492. else:
  493. pass
  494. elif action_type == "3":
  495. if action_content == "previous_window":
  496. # 模拟按下 Alt+Tab 键组合
  497. pyautogui.keyDown('alt')
  498. pyautogui.press('tab')
  499. pyautogui.keyUp('alt')
  500. else:
  501. self.switch_to_window(action_content)
  502. elif action_type == "9":
  503. self.e_window = ResultWindow([]) # 创建 ResultWindow 对象,传递一个空的数据列表
  504. self.e_window.searchIatFiles(action_content) # 调用 searchFiles 方法
  505. self.e_window.setWindowFlags(Qt.WindowStaysOnTopHint)
  506. self.e_window.show() # 显示窗口
  507. else:
  508. pass
  509. else:
  510. print("无交互动作")
  511. is_nlp_success = False
  512. # self.tts_stop()
  513. # is_tts = True
  514. # self.tts_play("我没有理解您说的话")
  515. # print("我没有理解你说的话啊")
  516. # 麦克风模式下,需要持续不断的发送音频,上一个动作执行完毕后,再重新开启麦克风
  517. if self.mode_type == "1":
  518. if is_nlp_success:
  519. print(datetime.now())
  520. self.last_recognition_time = datetime.now() # 记录当前时间
  521. # 如果执行动作不是TTS播报,立即重新开启麦克风,如果是TTS播报,再TTS播报回调中,等TTS播报完了再重新开启
  522. if not is_tts:
  523. self._restart_record_mic()
  524. else:
  525. pass
  526. def center(self):
  527. qr = self.frameGeometry()
  528. cp = self.screen().availableGeometry().center()
  529. qr.moveCenter(cp)
  530. self.move(qr.topLeft())
  531. def handleFloatingWindowStateChanged(self, state):
  532. if state == Qt.Checked:
  533. self.d_window.show()
  534. else:
  535. self.d_window.hide()
  536. def handleColorTextChanged(self, text):
  537. self.text_label.setStyleSheet(f"color: {text};") # 设置文本颜色
  538. def switch_to_window(self, window_title):
  539. # 获取所有顶级窗口的句柄和标题
  540. def enum_handler(hwnd, window_titles):
  541. if win32gui.IsWindowVisible(hwnd):
  542. title = win32gui.GetWindowText(hwnd)
  543. if title:
  544. window_titles.append((hwnd, title))
  545. window_titles = []
  546. win32gui.EnumWindows(enum_handler, window_titles)
  547. print(window_titles)
  548. # 遍历窗口标题,找到匹配的窗口
  549. for hwnd, title in window_titles:
  550. if window_title in title:
  551. print(title)
  552. # 切换到指定窗口
  553. # win32gui.ShowWindow(hwnd, 9) # 还原窗口
  554. win32gui.SetForegroundWindow(hwnd) # 将窗口置于前台
  555. time.sleep(0.1) # 等待一小段时间确保窗口已激活
  556. # 确保窗口最大化
  557. win32gui.ShowWindow(hwnd, 3)
  558. return True
  559. return False
  560. def audio_callback(self, indata, frames, time, status):
  561. if status:
  562. logger.info(f"Sounddevice status: {status}")
  563. audio_data = bytes(indata)
  564. if self.wake_up_successful:
  565. self.buffer += audio_data
  566. while len(self.buffer) >= 640:
  567. self._audio_queue.put(self.buffer[:640])
  568. self.buffer = self.buffer[640:]
  569. else:
  570. ret = self.dll.QIVWAudioWrite(self.sessionID_wakeup, audio_data, len(audio_data), 2)
  571. if ret != 0:
  572. logger.info(f'QIVWAudioWrite ret => {ret}')
  573. raise sd.CallbackAbort
  574. def _init_wakeup(self):
  575. def py_ivw_callback(sessionID, msg, param1, param2, info, userData):
  576. logger.info(f"sessionID => {sessionID}")
  577. logger.info(f"msg => {msg}")
  578. logger.info(f"param1 => {param1}")
  579. logger.info(f"param2 => {param2}")
  580. logger.info(f"info => {info}")
  581. logger.info(f"userData => {userData}")
  582. if msg == 1: # 唤醒成功的消息类型
  583. print("唤醒成功")
  584. logger.info("唤醒成功")
  585. # 唤醒成功后,重新初始化引擎
  586. self._ist.start_running()
  587. self.tts_play("我在呢,有什么需要")
  588. MainUI.wake_up_successful = True
  589. self.last_recognition_time = datetime.now() # 记录当前时间
  590. self._start_timer() # 开启定时任务校验
  591. # 这里不需要记录音频,否则会收到TTS播报的音频,再TTS回调函数中开启了
  592. # self._start_record_mic()
  593. CALLBACKFUNC = CFUNCTYPE(None, c_char_p, c_uint64, c_uint64, c_uint64, c_void_p, c_void_p)
  594. self.pCallbackFunc = CALLBACKFUNC(py_ivw_callback)
  595. try:
  596. msc_load_library = r'.\bin\msc_x64.dll'
  597. app_id = 'ab154863' # 填写自己的app_id
  598. ivw_threshold = '0:1450'
  599. jet_path = os.getcwd() + r'.\bin\msc\res\ivw\wakeupresource.jet'
  600. work_dir = 'fo|' + jet_path
  601. except Exception as e:
  602. logger.error(f'初始化错误:{e}')
  603. return
  604. self.dll = cdll.LoadLibrary(msc_load_library)
  605. if not self._setup_wakeup_session(self.dll, app_id, ivw_threshold, work_dir):
  606. return
  607. def _setup_wakeup_session(self, dll, app_id, ivw_threshold, jet_path):
  608. MSP_SUCCESS = 0
  609. errorCode = c_int64()
  610. Login_params = f"appid={app_id},engine_start=ivw"
  611. Login_params = bytes(Login_params, encoding="utf8")
  612. ret = dll.MSPLogin(None, None, Login_params)
  613. if MSP_SUCCESS != ret:
  614. logger.error(f"MSPLogin failed, error code is: {ret}")
  615. return False
  616. Begin_params = f"sst=wakeup,ivw_threshold={ivw_threshold},ivw_res_path={jet_path}"
  617. Begin_params = bytes(Begin_params, encoding="utf8")
  618. dll.QIVWSessionBegin.restype = c_char_p
  619. self.sessionID_wakeup = dll.QIVWSessionBegin(None, Begin_params, byref(errorCode))
  620. if MSP_SUCCESS != errorCode.value:
  621. logger.error(f"QIVWSessionBegin failed, error code is: {errorCode.value}")
  622. return False
  623. dll.QIVWRegisterNotify.argtypes = [c_char_p, c_void_p, c_void_p]
  624. ret = dll.QIVWRegisterNotify(self.sessionID_wakeup, self.pCallbackFunc, None)
  625. if MSP_SUCCESS != ret:
  626. logger.error(f"QIVWRegisterNotify failed, error code is: {ret}")
  627. return False
  628. return True
  629. def _start_timer(self):
  630. # 如果有现存的计时器,先取消
  631. if self.timer and self.timer.is_alive():
  632. self.timer.cancel()
  633. self.timer = threading.Timer(30, self._check_inactivity) # 每分钟检查一次
  634. self.timer.start()
  635. def _check_inactivity(self):
  636. print("检查是否超时")
  637. if self.last_recognition_time and datetime.now() - self.last_recognition_time > timedelta(minutes=1):
  638. logger.info("超过2分钟未识别,进入休眠模式")
  639. print("已进入休眠模式")
  640. MainUI.wake_up_successful = False
  641. self._stop_record_mic()
  642. else:
  643. self._start_timer() # 继续下一次检查
  644. # def create_socket_handler():
  645. # print('create_socket_handler')
  646. # global socketHadler
  647. # if socketHadler is None:
  648. # socketHadler = BusinessHadler()
  649. # app.aboutToQuit.connect(socketHadler.cleanup) # 关闭应用时进行清理操作
  650. def closeApp(self, event):
  651. print('close event')
  652. if self.mode_type == "1":
  653. self._stop_record_mic()
  654. QApplication.quit()
  655. if __name__ == '__main__':
  656. app = QApplication(sys.argv)
  657. win = MainUI()
  658. # 为菜单动作添加槽函数
  659. # win.quit_action.triggered.connect(app.quit)
  660. win.quit_action.triggered.connect(win.closeApp)
  661. # create_socket_handler()
  662. sys.exit(app.exec_())