mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-27 01:23:55 +08:00
修复test_page.html没有hello消息,导致缺失audio_format的bug (#1151)
This commit is contained in:
@@ -241,9 +241,12 @@ class ConnectionHandler:
|
|||||||
|
|
||||||
def _initialize_components(self):
|
def _initialize_components(self):
|
||||||
"""初始化组件"""
|
"""初始化组件"""
|
||||||
self.prompt = self.config["prompt"]
|
if self.config.get("prompt") is not None:
|
||||||
self.change_system_prompt(self.prompt)
|
self.prompt = self.config["prompt"]
|
||||||
self.logger.bind(tag=TAG).info(f"初始化组件: prompt成功 {self.prompt[:50]}...")
|
self.change_system_prompt(self.prompt)
|
||||||
|
self.logger.bind(tag=TAG).info(
|
||||||
|
f"初始化组件: prompt成功 {self.prompt[:50]}..."
|
||||||
|
)
|
||||||
|
|
||||||
"""初始化本地组件"""
|
"""初始化本地组件"""
|
||||||
if self.vad is None:
|
if self.vad is None:
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ class AccessToken:
|
|||||||
|
|
||||||
class ASRProvider(ASRProviderBase):
|
class ASRProvider(ASRProviderBase):
|
||||||
def __init__(self, config: dict, delete_audio_file: bool):
|
def __init__(self, config: dict, delete_audio_file: bool):
|
||||||
|
super().__init__()
|
||||||
"""阿里云ASR初始化"""
|
"""阿里云ASR初始化"""
|
||||||
# 新增空值判断逻辑
|
# 新增空值判断逻辑
|
||||||
self.access_key_id = config.get("access_key_id")
|
self.access_key_id = config.get("access_key_id")
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ logger = setup_logging()
|
|||||||
|
|
||||||
class ASRProvider(ASRProviderBase):
|
class ASRProvider(ASRProviderBase):
|
||||||
def __init__(self, config: dict, delete_audio_file: bool = True):
|
def __init__(self, config: dict, delete_audio_file: bool = True):
|
||||||
|
super().__init__()
|
||||||
self.app_id = config.get("app_id")
|
self.app_id = config.get("app_id")
|
||||||
self.api_key = config.get("api_key")
|
self.api_key = config.get("api_key")
|
||||||
self.secret_key = config.get("secret_key")
|
self.secret_key = config.get("secret_key")
|
||||||
|
|||||||
@@ -8,13 +8,18 @@ logger = setup_logging()
|
|||||||
|
|
||||||
|
|
||||||
class ASRProviderBase(ABC):
|
class ASRProviderBase(ABC):
|
||||||
|
def __init__(self):
|
||||||
|
self.audio_format = "opus"
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def save_audio_to_file(self, pcm_data: List[bytes], session_id: str) -> str:
|
def save_audio_to_file(self, pcm_data: List[bytes], session_id: str) -> str:
|
||||||
"""PCM数据保存为WAV文件"""
|
"""PCM数据保存为WAV文件"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def speech_to_text(self, opus_data: List[bytes], session_id: str) -> Tuple[Optional[str], Optional[str]]:
|
async def speech_to_text(
|
||||||
|
self, opus_data: List[bytes], session_id: str
|
||||||
|
) -> Tuple[Optional[str], Optional[str]]:
|
||||||
"""将语音数据转换为文本"""
|
"""将语音数据转换为文本"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ def parse_response(res):
|
|||||||
|
|
||||||
class ASRProvider(ASRProviderBase):
|
class ASRProvider(ASRProviderBase):
|
||||||
def __init__(self, config: dict, delete_audio_file: bool):
|
def __init__(self, config: dict, delete_audio_file: bool):
|
||||||
|
super().__init__()
|
||||||
self.appid = config.get("appid")
|
self.appid = config.get("appid")
|
||||||
self.cluster = config.get("cluster")
|
self.cluster = config.get("cluster")
|
||||||
self.access_token = config.get("access_token")
|
self.access_token = config.get("access_token")
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class CaptureOutput:
|
|||||||
|
|
||||||
class ASRProvider(ASRProviderBase):
|
class ASRProvider(ASRProviderBase):
|
||||||
def __init__(self, config: dict, delete_audio_file: bool):
|
def __init__(self, config: dict, delete_audio_file: bool):
|
||||||
|
super().__init__()
|
||||||
self.model_dir = config.get("model_dir")
|
self.model_dir = config.get("model_dir")
|
||||||
self.output_dir = config.get("output_dir") # 修正配置键名
|
self.output_dir = config.get("output_dir") # 修正配置键名
|
||||||
self.delete_audio_file = delete_audio_file
|
self.delete_audio_file = delete_audio_file
|
||||||
|
|||||||
@@ -9,22 +9,29 @@ import wave
|
|||||||
import websockets
|
import websockets
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
class ASRProvider(ASRProviderBase):
|
class ASRProvider(ASRProviderBase):
|
||||||
def __init__(self, config: dict, delete_audio_file: bool):
|
def __init__(self, config: dict, delete_audio_file: bool):
|
||||||
'''
|
"""
|
||||||
Initialize the ASRProvider with server configuration.
|
Initialize the ASRProvider with server configuration.
|
||||||
:param config: Dictionary containing 'host', 'port', and 'is_ssl'.
|
:param config: Dictionary containing 'host', 'port', and 'is_ssl'.
|
||||||
:param delete_audio_file: Boolean to indicate whether to delete audio files after processing.
|
:param delete_audio_file: Boolean to indicate whether to delete audio files after processing.
|
||||||
'''
|
"""
|
||||||
self.host = config.get('host', 'localhost')
|
super().__init__()
|
||||||
self.port = config.get('port', 10095)
|
self.host = config.get("host", "localhost")
|
||||||
self.is_ssl = config.get('is_ssl', True)
|
self.port = config.get("port", 10095)
|
||||||
|
self.is_ssl = config.get("is_ssl", True)
|
||||||
self.output_dir = config.get("output_dir")
|
self.output_dir = config.get("output_dir")
|
||||||
self.delete_audio_file = delete_audio_file
|
self.delete_audio_file = delete_audio_file
|
||||||
self.uri = f"wss://{self.host}:{self.port}" if self.is_ssl else f"ws://{self.host}:{self.port}"
|
self.uri = (
|
||||||
|
f"wss://{self.host}:{self.port}"
|
||||||
|
if self.is_ssl
|
||||||
|
else f"ws://{self.host}:{self.port}"
|
||||||
|
)
|
||||||
self.ssl_context = ssl.SSLContext() if self.is_ssl else None
|
self.ssl_context = ssl.SSLContext() if self.is_ssl else None
|
||||||
if self.ssl_context:
|
if self.ssl_context:
|
||||||
self.ssl_context.check_hostname = False
|
self.ssl_context.check_hostname = False
|
||||||
@@ -45,10 +52,10 @@ class ASRProvider(ASRProviderBase):
|
|||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
async def _receive_responses(self, ws) -> None:
|
async def _receive_responses(self, ws) -> None:
|
||||||
'''
|
"""
|
||||||
Asynchronous generator to receive messages from the WebSocket.
|
Asynchronous generator to receive messages from the WebSocket.
|
||||||
Yields each message as it is received.
|
Yields each message as it is received.
|
||||||
'''
|
"""
|
||||||
text = ""
|
text = ""
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@@ -61,30 +68,35 @@ class ASRProvider(ASRProviderBase):
|
|||||||
else:
|
else:
|
||||||
text += response_data.get("text", "")
|
text += response_data.get("text", "")
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
logger.bind(tag=TAG).error("Timeout while waiting for response from WebSocket.")
|
logger.bind(tag=TAG).error(
|
||||||
|
"Timeout while waiting for response from WebSocket."
|
||||||
|
)
|
||||||
break
|
break
|
||||||
except websockets.exceptions.ConnectionClosed as e:
|
except websockets.exceptions.ConnectionClosed as e:
|
||||||
logger.bind(tag=TAG).error(f"WebSocket connection closed: {e}")
|
logger.bind(tag=TAG).error(f"WebSocket connection closed: {e}")
|
||||||
break
|
break
|
||||||
return text
|
return text
|
||||||
|
|
||||||
async def _send_data(self, ws, pcm_data: bytes, session_id: str) -> tuple:
|
async def _send_data(self, ws, pcm_data: bytes, session_id: str) -> tuple:
|
||||||
'''
|
"""
|
||||||
Internal method to handle WebSocket communication.
|
Internal method to handle WebSocket communication.
|
||||||
Reuses the persistent WebSocket connection if available.
|
Reuses the persistent WebSocket connection if available.
|
||||||
:param pcm_data: PCM audio data to send.
|
:param pcm_data: PCM audio data to send.
|
||||||
:param session_id: Unique session identifier.
|
:param session_id: Unique session identifier.
|
||||||
:return: Tuple containing recognized text and optional timestamp.
|
:return: Tuple containing recognized text and optional timestamp.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
# Send initial configuration message
|
# Send initial configuration message
|
||||||
config_message = json.dumps({
|
config_message = json.dumps(
|
||||||
"mode": "offline",
|
{
|
||||||
"chunk_size": [5, 10, 5],
|
"mode": "offline",
|
||||||
"chunk_interval": 10,
|
"chunk_size": [5, 10, 5],
|
||||||
"wav_name": session_id,
|
"chunk_interval": 10,
|
||||||
"is_speaking": True,
|
"wav_name": session_id,
|
||||||
"itn": False
|
"is_speaking": True,
|
||||||
})
|
"itn": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
await ws.send(config_message)
|
await ws.send(config_message)
|
||||||
logger.bind(tag=TAG).debug(f"Sent configuration message: {config_message}")
|
logger.bind(tag=TAG).debug(f"Sent configuration message: {config_message}")
|
||||||
|
|
||||||
@@ -97,14 +109,15 @@ class ASRProvider(ASRProviderBase):
|
|||||||
await ws.send(end_message)
|
await ws.send(end_message)
|
||||||
logger.bind(tag=TAG).debug(f"Sent end message: {end_message}")
|
logger.bind(tag=TAG).debug(f"Sent end message: {end_message}")
|
||||||
|
|
||||||
|
async def speech_to_text(
|
||||||
async def speech_to_text(self, opus_data: List[bytes], session_id: str) -> Tuple[Optional[str], Optional[str]]:
|
self, opus_data: List[bytes], session_id: str
|
||||||
'''
|
) -> Tuple[Optional[str], Optional[str]]:
|
||||||
|
"""
|
||||||
Convert speech data to text using FunASR.
|
Convert speech data to text using FunASR.
|
||||||
:param opus_data: List of Opus-encoded audio data chunks.
|
:param opus_data: List of Opus-encoded audio data chunks.
|
||||||
:param session_id: Unique session identifier.
|
:param session_id: Unique session identifier.
|
||||||
:return: Tuple containing recognized text and optional timestamp.
|
:return: Tuple containing recognized text and optional timestamp.
|
||||||
'''
|
"""
|
||||||
file_path = None
|
file_path = None
|
||||||
if self.audio_format == "pcm":
|
if self.audio_format == "pcm":
|
||||||
pcm_data = opus_data
|
pcm_data = opus_data
|
||||||
@@ -118,16 +131,19 @@ class ASRProvider(ASRProviderBase):
|
|||||||
else:
|
else:
|
||||||
file_path = self.save_audio_to_file(pcm_data, session_id)
|
file_path = self.save_audio_to_file(pcm_data, session_id)
|
||||||
|
|
||||||
async with websockets.connect(self.uri, subprotocols=["binary"], ping_interval=None, ssl=self.ssl_context) as ws:
|
async with websockets.connect(
|
||||||
|
self.uri, subprotocols=["binary"], ping_interval=None, ssl=self.ssl_context
|
||||||
|
) as ws:
|
||||||
try:
|
try:
|
||||||
# Use asyncio to handle WebSocket communication
|
# Use asyncio to handle WebSocket communication
|
||||||
send_task = asyncio.create_task(self._send_data(ws, combined_pcm_data, session_id))
|
send_task = asyncio.create_task(
|
||||||
|
self._send_data(ws, combined_pcm_data, session_id)
|
||||||
|
)
|
||||||
receive_task = asyncio.create_task(self._receive_responses(ws))
|
receive_task = asyncio.create_task(self._receive_responses(ws))
|
||||||
|
|
||||||
# Gather tasks with error handling
|
# Gather tasks with error handling
|
||||||
done, pending = await asyncio.wait(
|
done, pending = await asyncio.wait(
|
||||||
[send_task, receive_task],
|
[send_task, receive_task], return_when=asyncio.FIRST_EXCEPTION
|
||||||
return_when=asyncio.FIRST_EXCEPTION
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Cancel any pending tasks
|
# Cancel any pending tasks
|
||||||
@@ -141,11 +157,16 @@ class ASRProvider(ASRProviderBase):
|
|||||||
|
|
||||||
# Get the result from the receive task
|
# Get the result from the receive task
|
||||||
result = receive_task.result()
|
result = receive_task.result()
|
||||||
return result, file_path # Return the recognized text and timestamp (if any)
|
return (
|
||||||
|
result,
|
||||||
|
file_path,
|
||||||
|
) # Return the recognized text and timestamp (if any)
|
||||||
|
|
||||||
except websockets.exceptions.ConnectionClosed as e:
|
except websockets.exceptions.ConnectionClosed as e:
|
||||||
logger.bind(tag=TAG).error(f"WebSocket connection closed: {e}")
|
logger.bind(tag=TAG).error(f"WebSocket connection closed: {e}")
|
||||||
return "", file_path
|
return "", file_path
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.bind(tag=TAG).error(f"Error during speech-to-text conversion: {e}", exc_info=True)
|
logger.bind(tag=TAG).error(
|
||||||
|
f"Error during speech-to-text conversion: {e}", exc_info=True
|
||||||
|
)
|
||||||
return "", file_path
|
return "", file_path
|
||||||
@@ -37,6 +37,7 @@ class CaptureOutput:
|
|||||||
|
|
||||||
class ASRProvider(ASRProviderBase):
|
class ASRProvider(ASRProviderBase):
|
||||||
def __init__(self, config: dict, delete_audio_file: bool):
|
def __init__(self, config: dict, delete_audio_file: bool):
|
||||||
|
super().__init__()
|
||||||
self.model_dir = config.get("model_dir")
|
self.model_dir = config.get("model_dir")
|
||||||
self.output_dir = config.get("output_dir")
|
self.output_dir = config.get("output_dir")
|
||||||
self.delete_audio_file = delete_audio_file
|
self.delete_audio_file = delete_audio_file
|
||||||
|
|||||||
@@ -17,12 +17,14 @@ from config.logger import setup_logging
|
|||||||
TAG = __name__
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
class ASRProvider(ASRProviderBase):
|
class ASRProvider(ASRProviderBase):
|
||||||
API_URL = "https://asr.tencentcloudapi.com"
|
API_URL = "https://asr.tencentcloudapi.com"
|
||||||
API_VERSION = "2019-06-14"
|
API_VERSION = "2019-06-14"
|
||||||
FORMAT = "pcm" # 支持的音频格式:pcm, wav, mp3
|
FORMAT = "pcm" # 支持的音频格式:pcm, wav, mp3
|
||||||
|
|
||||||
def __init__(self, config: dict, delete_audio_file: bool = True):
|
def __init__(self, config: dict, delete_audio_file: bool = True):
|
||||||
|
super().__init__()
|
||||||
self.secret_id = config.get("secret_id")
|
self.secret_id = config.get("secret_id")
|
||||||
self.secret_key = config.get("secret_key")
|
self.secret_key = config.get("secret_key")
|
||||||
self.output_dir = config.get("output_dir")
|
self.output_dir = config.get("output_dir")
|
||||||
@@ -45,7 +47,9 @@ class ASRProvider(ASRProviderBase):
|
|||||||
|
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
async def speech_to_text(self, opus_data: List[bytes], session_id: str) -> Tuple[Optional[str], Optional[str]]:
|
async def speech_to_text(
|
||||||
|
self, opus_data: List[bytes], session_id: str
|
||||||
|
) -> Tuple[Optional[str], Optional[str]]:
|
||||||
"""将语音数据转换为文本"""
|
"""将语音数据转换为文本"""
|
||||||
if not opus_data:
|
if not opus_data:
|
||||||
logger.bind(tag=TAG).warn("音频数据为空!")
|
logger.bind(tag=TAG).warn("音频数据为空!")
|
||||||
@@ -72,7 +76,7 @@ class ASRProvider(ASRProviderBase):
|
|||||||
self.save_audio_to_file(pcm_data, session_id)
|
self.save_audio_to_file(pcm_data, session_id)
|
||||||
|
|
||||||
# 将音频数据转换为Base64编码
|
# 将音频数据转换为Base64编码
|
||||||
base64_audio = base64.b64encode(combined_pcm_data).decode('utf-8')
|
base64_audio = base64.b64encode(combined_pcm_data).decode("utf-8")
|
||||||
|
|
||||||
# 构建请求体
|
# 构建请求体
|
||||||
request_body = self._build_request_body(base64_audio)
|
request_body = self._build_request_body(base64_audio)
|
||||||
@@ -85,7 +89,9 @@ class ASRProvider(ASRProviderBase):
|
|||||||
result = self._send_request(request_body, timestamp, authorization)
|
result = self._send_request(request_body, timestamp, authorization)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
logger.bind(tag=TAG).debug(f"腾讯云语音识别耗时: {time.time() - start_time:.3f}s | 结果: {result}")
|
logger.bind(tag=TAG).debug(
|
||||||
|
f"腾讯云语音识别耗时: {time.time() - start_time:.3f}s | 结果: {result}"
|
||||||
|
)
|
||||||
|
|
||||||
return result, file_path
|
return result, file_path
|
||||||
|
|
||||||
@@ -102,7 +108,7 @@ class ASRProvider(ASRProviderBase):
|
|||||||
"SourceType": 1, # 音频数据来源为语音文件
|
"SourceType": 1, # 音频数据来源为语音文件
|
||||||
"VoiceFormat": self.FORMAT, # 音频格式
|
"VoiceFormat": self.FORMAT, # 音频格式
|
||||||
"Data": base64_audio, # Base64编码的音频数据
|
"Data": base64_audio, # Base64编码的音频数据
|
||||||
"DataLen": len(base64_audio) # 数据长度
|
"DataLen": len(base64_audio), # 数据长度
|
||||||
}
|
}
|
||||||
return json.dumps(request_map)
|
return json.dumps(request_map)
|
||||||
|
|
||||||
@@ -135,9 +141,11 @@ class ASRProvider(ASRProviderBase):
|
|||||||
action = "SentenceRecognition" # 接口名称
|
action = "SentenceRecognition" # 接口名称
|
||||||
|
|
||||||
# 构建规范头部信息,注意顺序和格式
|
# 构建规范头部信息,注意顺序和格式
|
||||||
canonical_headers = f"content-type:{content_type.lower()}\n" + \
|
canonical_headers = (
|
||||||
f"host:{host.lower()}\n" + \
|
f"content-type:{content_type.lower()}\n"
|
||||||
f"x-tc-action:{action.lower()}\n"
|
+ f"host:{host.lower()}\n"
|
||||||
|
+ f"x-tc-action:{action.lower()}\n"
|
||||||
|
)
|
||||||
|
|
||||||
signed_headers = "content-type;host;x-tc-action"
|
signed_headers = "content-type;host;x-tc-action"
|
||||||
|
|
||||||
@@ -145,21 +153,25 @@ class ASRProvider(ASRProviderBase):
|
|||||||
payload_hash = self._sha256_hex(request_body)
|
payload_hash = self._sha256_hex(request_body)
|
||||||
|
|
||||||
# 构建规范请求字符串
|
# 构建规范请求字符串
|
||||||
canonical_request = f"{http_request_method}\n" + \
|
canonical_request = (
|
||||||
f"{canonical_uri}\n" + \
|
f"{http_request_method}\n"
|
||||||
f"{canonical_query_string}\n" + \
|
+ f"{canonical_uri}\n"
|
||||||
f"{canonical_headers}\n" + \
|
+ f"{canonical_query_string}\n"
|
||||||
f"{signed_headers}\n" + \
|
+ f"{canonical_headers}\n"
|
||||||
f"{payload_hash}"
|
+ f"{signed_headers}\n"
|
||||||
|
+ f"{payload_hash}"
|
||||||
|
)
|
||||||
|
|
||||||
# 计算规范请求的哈希值
|
# 计算规范请求的哈希值
|
||||||
hashed_canonical_request = self._sha256_hex(canonical_request)
|
hashed_canonical_request = self._sha256_hex(canonical_request)
|
||||||
|
|
||||||
# 构建待签名字符串
|
# 构建待签名字符串
|
||||||
string_to_sign = f"{algorithm}\n" + \
|
string_to_sign = (
|
||||||
f"{timestamp}\n" + \
|
f"{algorithm}\n"
|
||||||
f"{credential_scope}\n" + \
|
+ f"{timestamp}\n"
|
||||||
f"{hashed_canonical_request}"
|
+ f"{credential_scope}\n"
|
||||||
|
+ f"{hashed_canonical_request}"
|
||||||
|
)
|
||||||
|
|
||||||
# 计算签名密钥
|
# 计算签名密钥
|
||||||
secret_date = self._hmac_sha256(f"TC3{self.secret_key}", date)
|
secret_date = self._hmac_sha256(f"TC3{self.secret_key}", date)
|
||||||
@@ -167,13 +179,17 @@ class ASRProvider(ASRProviderBase):
|
|||||||
secret_signing = self._hmac_sha256(secret_service, "tc3_request")
|
secret_signing = self._hmac_sha256(secret_service, "tc3_request")
|
||||||
|
|
||||||
# 计算签名
|
# 计算签名
|
||||||
signature = self._bytes_to_hex(self._hmac_sha256(secret_signing, string_to_sign))
|
signature = self._bytes_to_hex(
|
||||||
|
self._hmac_sha256(secret_signing, string_to_sign)
|
||||||
|
)
|
||||||
|
|
||||||
# 构建授权头
|
# 构建授权头
|
||||||
authorization = f"{algorithm} " + \
|
authorization = (
|
||||||
f"Credential={self.secret_id}/{credential_scope}, " + \
|
f"{algorithm} "
|
||||||
f"SignedHeaders={signed_headers}, " + \
|
+ f"Credential={self.secret_id}/{credential_scope}, "
|
||||||
f"Signature={signature}"
|
+ f"SignedHeaders={signed_headers}, "
|
||||||
|
+ f"Signature={signature}"
|
||||||
|
)
|
||||||
|
|
||||||
return timestamp, authorization
|
return timestamp, authorization
|
||||||
|
|
||||||
@@ -181,7 +197,9 @@ class ASRProvider(ASRProviderBase):
|
|||||||
logger.bind(tag=TAG).error(f"生成认证头失败: {e}", exc_info=True)
|
logger.bind(tag=TAG).error(f"生成认证头失败: {e}", exc_info=True)
|
||||||
raise RuntimeError(f"生成认证头失败: {e}")
|
raise RuntimeError(f"生成认证头失败: {e}")
|
||||||
|
|
||||||
def _send_request(self, request_body: str, timestamp: str, authorization: str) -> Optional[str]:
|
def _send_request(
|
||||||
|
self, request_body: str, timestamp: str, authorization: str
|
||||||
|
) -> Optional[str]:
|
||||||
"""发送请求到腾讯云API"""
|
"""发送请求到腾讯云API"""
|
||||||
headers = {
|
headers = {
|
||||||
"Content-Type": "application/json; charset=utf-8",
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
@@ -190,7 +208,7 @@ class ASRProvider(ASRProviderBase):
|
|||||||
"X-TC-Action": "SentenceRecognition",
|
"X-TC-Action": "SentenceRecognition",
|
||||||
"X-TC-Version": self.API_VERSION,
|
"X-TC-Version": self.API_VERSION,
|
||||||
"X-TC-Timestamp": timestamp,
|
"X-TC-Timestamp": timestamp,
|
||||||
"X-TC-Region": "ap-shanghai"
|
"X-TC-Region": "ap-shanghai",
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -221,16 +239,16 @@ class ASRProvider(ASRProviderBase):
|
|||||||
|
|
||||||
def _sha256_hex(self, data: str) -> str:
|
def _sha256_hex(self, data: str) -> str:
|
||||||
"""计算字符串的SHA256哈希值"""
|
"""计算字符串的SHA256哈希值"""
|
||||||
digest = hashlib.sha256(data.encode('utf-8')).digest()
|
digest = hashlib.sha256(data.encode("utf-8")).digest()
|
||||||
return self._bytes_to_hex(digest)
|
return self._bytes_to_hex(digest)
|
||||||
|
|
||||||
def _hmac_sha256(self, key, data: str) -> bytes:
|
def _hmac_sha256(self, key, data: str) -> bytes:
|
||||||
"""计算HMAC-SHA256"""
|
"""计算HMAC-SHA256"""
|
||||||
if isinstance(key, str):
|
if isinstance(key, str):
|
||||||
key = key.encode('utf-8')
|
key = key.encode("utf-8")
|
||||||
|
|
||||||
return hmac.new(key, data.encode('utf-8'), hashlib.sha256).digest()
|
return hmac.new(key, data.encode("utf-8"), hashlib.sha256).digest()
|
||||||
|
|
||||||
def _bytes_to_hex(self, bytes_data: bytes) -> str:
|
def _bytes_to_hex(self, bytes_data: bytes) -> str:
|
||||||
"""字节数组转十六进制字符串"""
|
"""字节数组转十六进制字符串"""
|
||||||
return ''.join(f"{b:02x}" for b in bytes_data)
|
return "".join(f"{b:02x}" for b in bytes_data)
|
||||||
|
|||||||
Reference in New Issue
Block a user