tencentTTS.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. # -*- coding: utf-8 -*-
  2. from ..builder import TTSEngines
  3. from ..engineBase import BaseTTSEngine
  4. import hashlib
  5. import hmac
  6. import time
  7. import json
  8. from uuid import uuid4
  9. from datetime import datetime, timezone
  10. from typing import Tuple, Dict
  11. from digitalHuman.protocol import *
  12. from digitalHuman.utils import logger, httpxAsyncClient
  13. from pydantic import BaseModel
  14. from typing import List, Optional
  15. from decimal import Decimal
  16. __all__ = ["TencentApiTts"]
  17. MAX_INPUT_LENGTH = 150
  18. # neutral(中性)、sad(悲伤)、happy(高兴)、angry(生气)、fear(恐惧)、sajiao(撒娇)、amaze(震惊)、disgusted(厌恶)、peaceful(平静)
  19. # 中性、悲伤、高兴、生气、恐惧、撒娇、震惊、厌恶、平静
  20. class TencentVoiceEmotion(StrEnum):
  21. NEUTRAL = "neutral"
  22. SAD = "sad"
  23. HAPPY = "happy"
  24. ANGRY = "angry"
  25. FEAR = "fear"
  26. SAJIAO = "sajiao"
  27. AMAZE = "amaze"
  28. DISGUSTED = "disgusted"
  29. PEACEFUL = "peaceful"
  30. class TencentVoiceDesc(BaseModel):
  31. id: int
  32. name: str
  33. gender: GENDER_TYPE
  34. language: str
  35. multi_emotional: bool
  36. VOICE_LIST = [
  37. TencentVoiceDesc(id=501000, name="智斌", gender=GENDER_TYPE.MALE, language="中文", multi_emotional=False),
  38. TencentVoiceDesc(id=501001, name="智兰", gender=GENDER_TYPE.FEMALE, language="中文", multi_emotional=False),
  39. TencentVoiceDesc(id=501002, name="智菊", gender=GENDER_TYPE.FEMALE, language="中文", multi_emotional=False),
  40. TencentVoiceDesc(id=501003, name="智宇", gender=GENDER_TYPE.MALE, language="中文", multi_emotional=False),
  41. TencentVoiceDesc(id=501004, name="月华", gender=GENDER_TYPE.FEMALE, language="中文", multi_emotional=False),
  42. TencentVoiceDesc(id=501005, name="飞镜", gender=GENDER_TYPE.MALE, language="中文", multi_emotional=False),
  43. TencentVoiceDesc(id=501006, name="千嶂", gender=GENDER_TYPE.MALE, language="中文", multi_emotional=False),
  44. TencentVoiceDesc(id=501007, name="浅草", gender=GENDER_TYPE.MALE, language="中文", multi_emotional=False),
  45. TencentVoiceDesc(id=501008, name="WeJames", gender=GENDER_TYPE.MALE, language="英文", multi_emotional=False),
  46. TencentVoiceDesc(id=501009, name="WeWinny", gender=GENDER_TYPE.FEMALE, language="中文", multi_emotional=False),
  47. TencentVoiceDesc(id=601000, name="爱小溪", gender=GENDER_TYPE.FEMALE, language="中文", multi_emotional=True),
  48. TencentVoiceDesc(id=601001, name="爱小洛", gender=GENDER_TYPE.FEMALE, language="中文", multi_emotional=True),
  49. TencentVoiceDesc(id=601002, name="爱小辰", gender=GENDER_TYPE.MALE, language="中文", multi_emotional=True),
  50. TencentVoiceDesc(id=601003, name="爱小荷", gender=GENDER_TYPE.FEMALE, language="中文", multi_emotional=True),
  51. TencentVoiceDesc(id=601004, name="爱小树", gender=GENDER_TYPE.MALE, language="中文", multi_emotional=True),
  52. TencentVoiceDesc(id=601005, name="爱小静", gender=GENDER_TYPE.FEMALE, language="中文", multi_emotional=True),
  53. TencentVoiceDesc(id=601006, name="爱小耀", gender=GENDER_TYPE.MALE, language="中文", multi_emotional=True),
  54. TencentVoiceDesc(id=601007, name="爱小叶", gender=GENDER_TYPE.FEMALE, language="中文", multi_emotional=True),
  55. TencentVoiceDesc(id=601008, name="爱小豪", gender=GENDER_TYPE.MALE, language="中文", multi_emotional=True),
  56. TencentVoiceDesc(id=601009, name="爱小芊", gender=GENDER_TYPE.FEMALE, language="中文", multi_emotional=True),
  57. TencentVoiceDesc(id=601010, name="爱小娇", gender=GENDER_TYPE.FEMALE, language="中文", multi_emotional=True),
  58. TencentVoiceDesc(id=601011, name="爱小川", gender=GENDER_TYPE.MALE, language="中文", multi_emotional=True),
  59. TencentVoiceDesc(id=601012, name="爱小璟", gender=GENDER_TYPE.FEMALE, language="中文", multi_emotional=True),
  60. TencentVoiceDesc(id=601013, name="爱小伊", gender=GENDER_TYPE.FEMALE, language="中文", multi_emotional=True),
  61. TencentVoiceDesc(id=601014, name="爱小简", gender=GENDER_TYPE.MALE, language="中文", multi_emotional=True),
  62. ]
  63. class TencentCloudApiKey(BaseModel):
  64. secret_id: str
  65. secret_key: str
  66. def findVoice(name: str) -> Optional[TencentVoiceDesc]:
  67. for voice in VOICE_LIST:
  68. if voice.name == name:
  69. return voice
  70. return None
  71. def sign(key, msg: str):
  72. return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
  73. @TTSEngines.register("Tencent-API")
  74. class TencentApiTts(BaseTTSEngine):
  75. def setup(self):
  76. self._url = "https://tts.tencentcloudapi.com"
  77. def _buildRequest(
  78. self,
  79. input: TextMessage,
  80. tencentApiKey: TencentCloudApiKey,
  81. voice: str,
  82. volume: float,
  83. speed: float,
  84. emotionCategory: str = TencentVoiceEmotion.NEUTRAL
  85. ) -> Tuple[Dict, str]:
  86. service = "tts"
  87. host = "tts.tencentcloudapi.com"
  88. version = "2019-08-23"
  89. action = "TextToVoice"
  90. algorithm = "TC3-HMAC-SHA256"
  91. timestamp = int(time.time())
  92. date = datetime.fromtimestamp(timestamp, timezone.utc).strftime("%Y-%m-%d")
  93. tencentVoice = findVoice(voice)
  94. if not tencentVoice:
  95. raise ValueError("voice not found")
  96. params = {
  97. "Text": input.data,
  98. "SessionId": str(uuid4()),
  99. "VoiceType": tencentVoice.id,
  100. # "Codec": "wav",
  101. "Codec": "mp3",
  102. "Volume": volume,
  103. "Speed": speed,
  104. "EmotionCategory": emotionCategory
  105. }
  106. payload = json.dumps(params)
  107. # ************* 步骤 1:拼接规范请求串 *************
  108. http_request_method = "POST"
  109. canonical_uri = "/"
  110. canonical_querystring = ""
  111. ct = "application/json; charset=utf-8"
  112. canonical_headers = "content-type:%s\nhost:%s\nx-tc-action:%s\n" % (ct, host, action.lower())
  113. signed_headers = "content-type;host;x-tc-action"
  114. hashed_request_payload = hashlib.sha256(payload.encode("utf-8")).hexdigest()
  115. canonical_request = (http_request_method + "\n" +
  116. canonical_uri + "\n" +
  117. canonical_querystring + "\n" +
  118. canonical_headers + "\n" +
  119. signed_headers + "\n" +
  120. hashed_request_payload)
  121. # ************* 步骤 2:拼接待签名字符串 *************
  122. credential_scope = date + "/" + service + "/" + "tc3_request"
  123. hashed_canonical_request = hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()
  124. string_to_sign = (algorithm + "\n" +
  125. str(timestamp) + "\n" +
  126. credential_scope + "\n" +
  127. hashed_canonical_request)
  128. # ************* 步骤 3:计算签名 *************
  129. secret_date = sign(("TC3" + tencentApiKey.secret_key).encode("utf-8"), date)
  130. secret_service = sign(secret_date, service)
  131. secret_signing = sign(secret_service, "tc3_request")
  132. signature = hmac.new(secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
  133. # ************* 步骤 4:拼接 Authorization *************
  134. authorization = (algorithm + " " +
  135. "Credential=" + tencentApiKey.secret_id + "/" + credential_scope + ", " +
  136. "SignedHeaders=" + signed_headers + ", " +
  137. "Signature=" + signature)
  138. # ************* 步骤 5:构造并发起请求 *************
  139. headers = {
  140. "Authorization": authorization,
  141. "Content-Type": "application/json; charset=utf-8",
  142. "Host": host,
  143. "X-TC-Action": action,
  144. "X-TC-Timestamp": str(timestamp),
  145. "X-TC-Version": version
  146. }
  147. return (headers, payload)
  148. async def voices(self, **kwargs) -> List[VoiceDesc]:
  149. return [VoiceDesc(name=v.name, gender=v.gender) for v in VOICE_LIST]
  150. async def run(self, input: TextMessage, **kwargs) -> AudioMessage:
  151. # 参数校验
  152. paramters = self.checkParameter(**kwargs)
  153. voice = paramters["voice"]
  154. speed = paramters["speed"]
  155. volume = paramters["volume"]
  156. SECRECT_ID = paramters["secret_id"]
  157. SECRECT_KEY = paramters["secret_key"]
  158. tencentCloudApiKey = TencentCloudApiKey(secret_id=SECRECT_ID, secret_key=SECRECT_KEY)
  159. headers, payload = self._buildRequest(input, tencentCloudApiKey, voice, volume, speed)
  160. logger.debug(f"[TTS] Engine input: {input.data}")
  161. response = await httpxAsyncClient.post(self._url, headers=headers, data=payload)
  162. if response.status_code != 200:
  163. raise RuntimeError(f"Builtin tts api error: {response.status_code}")
  164. audio = response.json()["Response"]["Audio"]
  165. message = AudioMessage(
  166. data=audio,
  167. sampleRate=16000,
  168. sampleWidth=2,
  169. )
  170. return message