lib_tts.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. # -*- encoding: utf-8 -*-
  2. """
  3. @Desc: 语音合成(朗读)功能
  4. # 合成小语种需要传输小语种文本、使用小语种发音人vcn、tte=unicode以及修改文本编码方式
  5. # 错误码链接:https://www.xfyun.cn/document/error-code (code返回错误码时必看)
  6. """
  7. import _thread as thread
  8. import base64
  9. import datetime
  10. import hashlib
  11. import hmac
  12. import json
  13. import os
  14. import ssl
  15. import sys
  16. import time
  17. from datetime import datetime
  18. from threading import Thread
  19. from time import mktime
  20. from urllib.parse import urlencode
  21. from wsgiref.handlers import format_date_time
  22. import websocket
  23. URL = 'wss://tts-api.xfyun.cn/v2/tts'
  24. APPID = 'ab154863'
  25. APIKey = '5a584b63730d3450d64cdd65f17f56d0'
  26. APISecret = 'NGE3NGIyZDBlNWU0YTg3YjQ5YTJlY2Q2'
  27. def get_proxy():
  28. _proxy = ""
  29. proxy_type = ""
  30. host = ""
  31. port = ""
  32. user = ""
  33. pswd = ""
  34. try:
  35. _proxy = os.environ["http_proxy"]
  36. except:
  37. pass
  38. else:
  39. try:
  40. addr = _proxy.split("//")[1]
  41. proxy_type_str = _proxy.split("//")[0]
  42. proxy_type = proxy_type_str[:-1]
  43. port = addr.split(":")[-1]
  44. if "@" in addr: # 有用户密码
  45. host_str = addr.split("@")[-1]
  46. host = host_str.split(f":{port}")[0]
  47. auth_str = addr.split(host_str)[0]
  48. user = auth_str.split(":")[0]
  49. pswd_str = auth_str.split(":")[-1]
  50. pswd = pswd_str[:-1]
  51. else:
  52. host = addr.split(":")[0]
  53. except:
  54. pass
  55. return _proxy, proxy_type, host, port, user, pswd
  56. def get_home_env():
  57. if sys.platform == "win32":
  58. return 'APPDATA'
  59. return 'HOME'
  60. def gen_tmp_mp3_file():
  61. _dir = os.path.join(os.environ[get_home_env()], "xAssistant")
  62. if not os.path.exists(_dir):
  63. os.makedirs(_dir)
  64. _tmp_dir = os.path.join(_dir, "tmp")
  65. if not os.path.exists(_tmp_dir):
  66. os.makedirs(_tmp_dir)
  67. _path = os.path.join(_tmp_dir, f"{int(time.time())}.mp3")
  68. return _path
  69. class WsManager:
  70. # 上一块音频的合成进度
  71. _last_ced = 0
  72. # 上一块音频数据
  73. _last_buf = None
  74. # 音频数据块
  75. _audio = bytearray()
  76. # 初始化
  77. def __init__(self):
  78. self.Data = None
  79. self.Text = None
  80. self.vcn = None
  81. self._ws = None
  82. self.BusinessArgs = None
  83. self.APPID = APPID
  84. self.APIKey = APIKey
  85. self.APISecret = APISecret
  86. self.callback = None
  87. self.d = None
  88. # 公共参数(common)
  89. self.CommonArgs = {"app_id": self.APPID}
  90. self.ws_url = self.create_url()
  91. websocket.enableTrace(False)
  92. def run(self, text: str, vcn: str, speed_mode: int, call_back):
  93. self.callback = call_back
  94. speed = self.get_speed_value(speed_mode)
  95. _text = text + "\n"
  96. self.add_parameters(_text, speed, vcn)
  97. self._ws = websocket.WebSocketApp(self.ws_url, on_open=self.on_open, on_message=self.on_message,
  98. on_error=self.on_error,
  99. on_close=self.on_close)
  100. _proxy, proxy_type, host, port, user, pswd = get_proxy()
  101. try:
  102. if all([_proxy, proxy_type, host, port, user, pswd]):
  103. proxy_auth = (user, pswd)
  104. elif all([_proxy, proxy_type, host, port]):
  105. proxy_auth = None
  106. else:
  107. host = None
  108. port = None
  109. proxy_type = None
  110. proxy_auth = None
  111. # os.environ["no_proxy"] = "wsapi.xfyun.cn"
  112. self._ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE},
  113. http_proxy_host=host,
  114. http_proxy_port=port,
  115. proxy_type=proxy_type,
  116. http_proxy_auth=proxy_auth
  117. )
  118. except:
  119. return
  120. def stop(self):
  121. if self._ws:
  122. self._ws.close()
  123. # 抛出音频
  124. def _cb_audio(self):
  125. buf = bytes(self._audio)
  126. if buf:
  127. data = {"buf": buf}
  128. self.callback(data)
  129. self._audio.clear() # 清空音频块
  130. # 生成url
  131. def create_url(self):
  132. # 生成RFC1123格式的时间戳
  133. now = datetime.now()
  134. date = format_date_time(mktime(now.timetuple()))
  135. # 拼接字符串
  136. signature_origin = "host: " + "ws-api.xfyun.cn" + "\n"
  137. signature_origin += "date: " + date + "\n"
  138. signature_origin += "GET " + "/v2/tts " + "HTTP/1.1"
  139. # 进行hmac-sha256进行加密
  140. signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'),
  141. digestmod=hashlib.sha256).digest()
  142. signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8')
  143. authorization_origin = "api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"" % (
  144. self.APIKey, "hmac-sha256", "host date request-line", signature_sha)
  145. authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')
  146. # 将请求的鉴权参数组合为字典
  147. v = {
  148. "authorization": authorization,
  149. "date": date,
  150. "host": "ws-api.xfyun.cn"
  151. }
  152. # 拼接鉴权参数,生成url
  153. url = URL + '?' + urlencode(v)
  154. return url
  155. def add_parameters(self, word, speed, vcn):
  156. self.Text = word
  157. self.vcn = vcn
  158. # 业务参数(business),更多个性化参数可在官网查看
  159. self.BusinessArgs = {"aue": "raw", "auf": "audio/L16;rate=16000", "speed": speed, "vcn": vcn, "tte": "utf8"}
  160. self.Data = {"status": 2, "text": str(base64.b64encode(self.Text.encode('utf-8')), "UTF8")}
  161. # 使用小语种须使用以下方式,此处的unicode指的是 utf16小端的编码方式,即"UTF-16LE"”
  162. # self.Data = {"status": 2, "text": str(base64.b64encode(self.Text.encode('utf-16')), "UTF8")}
  163. self.d = {"common": self.CommonArgs,
  164. "business": self.BusinessArgs,
  165. "data": self.Data,
  166. }
  167. self._audio.clear()
  168. self._last_ced = 0
  169. self._last_buf = None
  170. @staticmethod
  171. def get_speed_value(speed_mode):
  172. if speed_mode == 1:
  173. speed = 80
  174. elif speed_mode == 2:
  175. speed = 30
  176. else:
  177. speed = 50
  178. return speed
  179. # 收到websocket连接建立的处理
  180. def on_open(self, ws):
  181. def run(*args):
  182. self._ws.send(json.dumps(self.d))
  183. thread.start_new_thread(run, ())
  184. def on_message(self, ws, message):
  185. message = json.loads(message)
  186. code = message["code"]
  187. sid = message["sid"]
  188. status = message["data"]["status"]
  189. ced = int(message["data"]["ced"])
  190. # logger.debug(f"接收到了回调:{len(audio)}")
  191. if status == 2:
  192. # if self.vcn in ["aisjiuxu", ]:
  193. # self._audio += self._last_buf
  194. self._cb_audio() # 抛出音频
  195. ws.close()
  196. data = {"end": 1}
  197. self.callback(data)
  198. else:
  199. audio_str = message["data"]["audio"]
  200. audio = base64.b64decode(audio_str)
  201. if code != 0:
  202. err_msg = message["message"]
  203. self.callback({"error": [sid, err_msg, code]})
  204. return
  205. if self._last_ced < ced:
  206. self._cb_audio() # 抛出音频
  207. # 因播放器原因 这里一些发音人的首帧音频需要拼接两次
  208. self._audio += bytearray(audio) # 拼接句首音频
  209. # 20220428 不多拼接一次可能会出现句首字不发音现象,拼接后可能会出现句首重复发音现象 暂无法解决
  210. # if self.vcn in ["x2_mingge", "x2_yifei", "x2_catherine", "x2_john"]:
  211. # self._audio += bytearray(audio) # 拼接句首音频
  212. else:
  213. self._audio += bytearray(audio) # 拼接句中音频
  214. self._last_buf = bytearray(audio)
  215. self._last_ced = ced
  216. # except Exception as e:
  217. # logger.error(f"receive msg,but parse exception:{e}")
  218. # 收到websocket关闭的处理
  219. @staticmethod
  220. def on_close(ws, *args):
  221. pass
  222. # 收到websocket错误的处理
  223. def on_error(self, ws, error):
  224. self.callback({"error": error})
  225. class TTSManager(Thread):
  226. def __init__(self, text, vcn, speed_mode, cb_fn, audio_queue):
  227. super(TTSManager, self).__init__()
  228. self.APPID = APPID
  229. self.APIKey = APIKey
  230. self.APISecret = APISecret
  231. self.callback = cb_fn
  232. self.audio_queue = audio_queue
  233. self._ws = None
  234. self.code = None
  235. self.sid = None
  236. self._speed = self.get_speed_value(speed_mode)
  237. # 公共参数(common)
  238. self.CommonArgs = {"app_id": self.APPID}
  239. # 业务参数(business),更多个性化参数可在官网查看
  240. self.BusinessArgs = {"aue": "raw", "auf": "audio/L16;rate=16000", "vcn": f"{vcn}", "speed": self._speed,
  241. "tte": "utf8"}
  242. self.Data = {"status": 2, "text": str(base64.b64encode(text.encode('utf-8')), "UTF8")}
  243. self.d = {"common": self.CommonArgs,
  244. "business": self.BusinessArgs,
  245. "data": self.Data,
  246. }
  247. # 生成url
  248. def create_url(self):
  249. # 生成RFC1123格式的时间戳
  250. now = datetime.now()
  251. date = format_date_time(mktime(now.timetuple()))
  252. # 拼接字符串
  253. signature_origin = "host: " + "ws-api.xfyun.cn" + "\n"
  254. signature_origin += "date: " + date + "\n"
  255. signature_origin += "GET " + "/v2/tts " + "HTTP/1.1"
  256. # 进行hmac-sha256进行加密
  257. signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'),
  258. digestmod=hashlib.sha256).digest()
  259. signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8')
  260. authorization_origin = "api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"" % (
  261. self.APIKey, "hmac-sha256", "host date request-line", signature_sha)
  262. authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')
  263. # 将请求的鉴权参数组合为字典
  264. v = {
  265. "authorization": authorization,
  266. "date": date,
  267. "host": "ws-api.xfyun.cn"
  268. }
  269. # 拼接鉴权参数,生成url
  270. url = URL + '?' + urlencode(v)
  271. return url
  272. def run(self):
  273. ws_url = self.create_url()
  274. self._ws = websocket.WebSocketApp(ws_url, on_open=self.on_open, on_message=self.on_message,
  275. on_error=self.on_error,
  276. on_close=self.on_close)
  277. _proxy, proxy_type, host, port, user, pswd = get_proxy()
  278. try:
  279. if all([_proxy, proxy_type, host, port, user, pswd]):
  280. self._ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE},
  281. http_proxy_host=host,
  282. http_proxy_port=port,
  283. proxy_type=proxy_type,
  284. http_proxy_auth=(user, pswd)
  285. )
  286. elif all([_proxy, proxy_type, host, port]):
  287. self._ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE},
  288. http_proxy_host=host,
  289. http_proxy_port=port,
  290. proxy_type=proxy_type
  291. )
  292. else:
  293. self._ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
  294. except:
  295. return
  296. # 收到websocket连接建立的处理
  297. def on_open(self, ws):
  298. def run(*args):
  299. ws.send(json.dumps(self.d))
  300. thread.start_new_thread(run, ())
  301. def on_message(self, ws, message):
  302. try:
  303. message = json.loads(message)
  304. self.code = message["code"]
  305. self.sid = message["sid"]
  306. status = message["data"]["status"]
  307. if status == 2:
  308. ws.close()
  309. data = {"end": 1}
  310. self.callback(data)
  311. if self.code != 0:
  312. err_msg = message["message"]
  313. if "NoneType" not in str(err_msg):
  314. self.callback({"error": [self.sid, err_msg, self.code]})
  315. else:
  316. audio = message["data"]["audio"]
  317. audio = base64.b64decode(audio)
  318. self.audio_queue.put(audio)
  319. except Exception as e:
  320. if "NoneType" not in str(e):
  321. self.callback({"error": [self.sid, str(e), self.code]})
  322. # 收到websocket关闭的处理
  323. @staticmethod
  324. def on_close(ws, *args):
  325. pass
  326. # 收到websocket错误的处理
  327. def on_error(self, ws, error):
  328. if "NoneType" not in str(error):
  329. self.callback({"error": [self.sid, str(error), self.code]})
  330. @staticmethod
  331. def get_speed_value(speed_mode):
  332. if speed_mode == 1:
  333. speed = 80
  334. elif speed_mode == 2:
  335. speed = 30
  336. else:
  337. speed = 50
  338. return speed
  339. def stop(self):
  340. self._ws.close()
  341. # TTS工作线程
  342. class TTSManagerNew(Thread):
  343. def __init__(self, text, vcn, speed_mode, queue):
  344. super(TTSManagerNew, self).__init__()
  345. self.APPID = APPID
  346. self.APIKey = APIKey
  347. self.APISecret = APISecret
  348. self.daemon = True
  349. self.queue = queue
  350. self._ws = None
  351. self.code = None
  352. self.sid = None
  353. self._speed = self.get_speed_value(speed_mode)
  354. # 公共参数(common)
  355. self.CommonArgs = {"app_id": self.APPID}
  356. # 业务参数(business),更多个性化参数可在官网查看
  357. self.BusinessArgs = {
  358. # "aue": "lame",
  359. "aue": "raw",
  360. # 'sfl': 1,
  361. "auf": "audio/L16;rate=16000",
  362. "vcn": f"{vcn}",
  363. "speed": self._speed,
  364. "tte": "utf8"}
  365. self.Data = {"status": 2, "text": str(base64.b64encode(text.encode('utf-8')), "UTF8")}
  366. self.d = {"common": self.CommonArgs,
  367. "business": self.BusinessArgs,
  368. "data": self.Data,
  369. }
  370. # 生成url
  371. def create_url(self):
  372. # 生成RFC1123格式的时间戳
  373. now = datetime.now()
  374. date = format_date_time(mktime(now.timetuple()))
  375. # 拼接字符串
  376. signature_origin = "host: " + "ws-api.xfyun.cn" + "\n"
  377. signature_origin += "date: " + date + "\n"
  378. signature_origin += "GET " + "/v2/tts " + "HTTP/1.1"
  379. # 进行hmac-sha256进行加密
  380. signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'),
  381. digestmod=hashlib.sha256).digest()
  382. signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8')
  383. authorization_origin = "api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"" % (
  384. self.APIKey, "hmac-sha256", "host date request-line", signature_sha)
  385. authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')
  386. # 将请求的鉴权参数组合为字典
  387. v = {
  388. "authorization": authorization,
  389. "date": date,
  390. "host": "ws-api.xfyun.cn"
  391. }
  392. # 拼接鉴权参数,生成url
  393. url = URL + '?' + urlencode(v)
  394. return url
  395. def run(self):
  396. ws_url = self.create_url()
  397. self._ws = websocket.WebSocketApp(ws_url, on_open=self.on_open, on_message=self.on_message,
  398. on_error=self.on_error,
  399. on_close=self.on_close)
  400. _proxy, proxy_type, host, port, user, pswd = get_proxy()
  401. try:
  402. if all([_proxy, proxy_type, host, port, user, pswd]):
  403. self._ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE},
  404. http_proxy_host=host,
  405. http_proxy_port=port,
  406. proxy_type=proxy_type,
  407. http_proxy_auth=(user, pswd)
  408. )
  409. elif all([_proxy, proxy_type, host, port]):
  410. self._ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE},
  411. http_proxy_host=host,
  412. http_proxy_port=port,
  413. proxy_type=proxy_type
  414. )
  415. else:
  416. self._ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
  417. except:
  418. return
  419. # 收到websocket连接建立的处理
  420. def on_open(self, ws):
  421. def run(*args):
  422. ws.send(json.dumps(self.d))
  423. thread.start_new_thread(run, ())
  424. def on_message(self, ws, message):
  425. try:
  426. message = json.loads(message)
  427. self.code = message["code"]
  428. self.sid = message["sid"]
  429. status = message["data"]["status"]
  430. if status == 2:
  431. audio = message["data"]["audio"]
  432. audio = base64.b64decode(audio)
  433. self.queue.put(audio)
  434. pass
  435. self.queue.put("--end--")
  436. if self.code != 0:
  437. err_msg = message["message"]
  438. if "NoneType" not in str(err_msg):
  439. self.queue.put(f"error:{self.sid},{err_msg},{self.code}")
  440. else:
  441. audio = message["data"]["audio"]
  442. audio = base64.b64decode(audio)
  443. self.queue.put(audio)
  444. except Exception as e:
  445. if "NoneType" not in str(e):
  446. self.queue.put(f"error:{self.sid},{str(e)},{self.code}")
  447. # 收到websocket关闭的处理
  448. @staticmethod
  449. def on_close(ws, *args):
  450. pass
  451. # 收到websocket错误的处理
  452. def on_error(self, ws, error):
  453. if "NoneType" not in str(error):
  454. self.queue.put(f"error:{self.sid},{str(error)},{self.code}")
  455. @staticmethod
  456. def get_speed_value(speed_mode):
  457. if speed_mode == 1:
  458. speed = 80
  459. elif speed_mode == 2:
  460. speed = 30
  461. else:
  462. speed = 50
  463. return speed
  464. def stop(self):
  465. self._ws.close()