|
|
|
@ -1,143 +1,189 @@
|
|
|
|
|
# -*- coding: utf8 -*-
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
import json
|
|
|
|
|
import time
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Dict, Tuple
|
|
|
|
|
|
|
|
|
|
from aliyunsdkcore.acs_exception.exceptions import ClientException, ServerException
|
|
|
|
|
from aliyunsdkcore.client import AcsClient
|
|
|
|
|
from aliyunsdkcore.request import CommonRequest
|
|
|
|
|
# 录音文件识别
|
|
|
|
|
# https://common-buy.aliyun.com/?spm=5176.11801677.0.0.44e77a33oMMyT6&commodityCode=nlsService&orderType=UPGRADE&instanceId=1546399445482588
|
|
|
|
|
# 1. 开通商用即为付费使用服务,根据后付费梯度计费,标准单价为2.5元/小时;
|
|
|
|
|
# 2. 商用服务可在最长3小时内完成识别并返回识别文本;
|
|
|
|
|
# 3. 注意:已经“商用”后再切换“试用”,等同于立即关停线上服务,请谨慎操作;
|
|
|
|
|
|
|
|
|
|
def fileTrans(akId, akSecret, appKey, fileLink):
|
|
|
|
|
# 服务配置
|
|
|
|
|
REGION_ID = "cn-shanghai"
|
|
|
|
|
PRODUCT = "nls-filetrans"
|
|
|
|
|
DOMAIN = "filetrans.cn-shanghai.aliyuncs.com"
|
|
|
|
|
API_VERSION = "2018-08-17"
|
|
|
|
|
|
|
|
|
|
# 请求参数
|
|
|
|
|
KEY_APP_KEY = "appkey"
|
|
|
|
|
KEY_FILE_LINK = "file_link"
|
|
|
|
|
KEY_VERSION = "version"
|
|
|
|
|
KEY_ENABLE_WORDS = "enable_words"
|
|
|
|
|
KEY_TASK = "Task"
|
|
|
|
|
KEY_TASK_ID = "TaskId"
|
|
|
|
|
KEY_STATUS_TEXT = "StatusText"
|
|
|
|
|
KEY_RESULT = "Result"
|
|
|
|
|
|
|
|
|
|
# 状态码
|
|
|
|
|
STATUS_SUCCESS = "SUCCESS"
|
|
|
|
|
STATUS_RUNNING = "RUNNING"
|
|
|
|
|
STATUS_QUEUEING = "QUEUEING"
|
|
|
|
|
|
|
|
|
|
# 初始化客户端
|
|
|
|
|
client = AcsClient(akId, akSecret, REGION_ID)
|
|
|
|
|
|
|
|
|
|
# 构建任务参数
|
|
|
|
|
|
|
|
|
|
# 服务常量配置
|
|
|
|
|
CONFIG = {
|
|
|
|
|
"REGION_ID": "cn-shanghai",
|
|
|
|
|
"PRODUCT": "nls-filetrans",
|
|
|
|
|
"DOMAIN": "filetrans.cn-shanghai.aliyuncs.com",
|
|
|
|
|
"API_VERSION": "2018-08-17",
|
|
|
|
|
"MAX_RETRIES": 20,
|
|
|
|
|
"POLL_INTERVAL": 10
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TranscriptionError(Exception):
|
|
|
|
|
"""自定义语音识别异常基类"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class APIConnectionError(TranscriptionError):
|
|
|
|
|
"""API连接异常"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TaskSubmissionError(TranscriptionError):
|
|
|
|
|
"""任务提交异常"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TaskTimeoutError(TranscriptionError):
|
|
|
|
|
"""任务超时异常"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def submit_transcription_task(client: AcsClient, app_key: str, file_link: str) -> str:
|
|
|
|
|
"""提交语音识别任务
|
|
|
|
|
|
|
|
|
|
:param client: 阿里云客户端实例
|
|
|
|
|
:param app_key: 应用密钥
|
|
|
|
|
:param file_link: 音频文件URL
|
|
|
|
|
:return: 任务ID
|
|
|
|
|
:raises TaskSubmissionError: 任务提交失败时抛出
|
|
|
|
|
"""
|
|
|
|
|
task_config = {
|
|
|
|
|
KEY_APP_KEY: appKey,
|
|
|
|
|
KEY_FILE_LINK: fileLink,
|
|
|
|
|
KEY_VERSION: "4.0",
|
|
|
|
|
KEY_ENABLE_WORDS: False
|
|
|
|
|
"appkey": app_key,
|
|
|
|
|
"file_link": file_link,
|
|
|
|
|
"version": "4.0",
|
|
|
|
|
"enable_words": False
|
|
|
|
|
}
|
|
|
|
|
task_json = json.dumps(task_config)
|
|
|
|
|
print("任务配置:", task_json)
|
|
|
|
|
|
|
|
|
|
# 提交识别请求
|
|
|
|
|
postRequest = CommonRequest()
|
|
|
|
|
postRequest.set_domain(DOMAIN)
|
|
|
|
|
postRequest.set_version(API_VERSION)
|
|
|
|
|
postRequest.set_product(PRODUCT)
|
|
|
|
|
postRequest.set_action_name("SubmitTask")
|
|
|
|
|
postRequest.set_method('POST')
|
|
|
|
|
postRequest.add_body_params(KEY_TASK, task_json)
|
|
|
|
|
|
|
|
|
|
request = CommonRequest()
|
|
|
|
|
request.set_domain(CONFIG["DOMAIN"])
|
|
|
|
|
request.set_version(CONFIG["API_VERSION"])
|
|
|
|
|
request.set_product(CONFIG["PRODUCT"])
|
|
|
|
|
request.set_action_name("SubmitTask")
|
|
|
|
|
request.set_method('POST')
|
|
|
|
|
request.add_body_params("Task", json.dumps(task_config))
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
postResponse = client.do_action_with_exception(postRequest)
|
|
|
|
|
postData = json.loads(postResponse)
|
|
|
|
|
print("提交响应:", postData)
|
|
|
|
|
response = client.do_action_with_exception(request)
|
|
|
|
|
response_data = json.loads(response)
|
|
|
|
|
|
|
|
|
|
if postData.get(KEY_STATUS_TEXT) != STATUS_SUCCESS:
|
|
|
|
|
print(f"任务提交失败: {postData.get(KEY_STATUS_TEXT, '未知错误')}")
|
|
|
|
|
return None
|
|
|
|
|
if response_data.get("StatusText") != "SUCCESS":
|
|
|
|
|
raise TaskSubmissionError(f"任务提交失败: {response_data.get('StatusText', '未知错误')}")
|
|
|
|
|
|
|
|
|
|
taskId = postData[KEY_TASK_ID]
|
|
|
|
|
print(f"任务ID: {taskId}")
|
|
|
|
|
return response_data["TaskId"]
|
|
|
|
|
|
|
|
|
|
except (ServerException, ClientException) as e:
|
|
|
|
|
print(f"API请求异常: {e}")
|
|
|
|
|
return None
|
|
|
|
|
except KeyError:
|
|
|
|
|
print("响应缺少关键字段")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
# 查询任务结果
|
|
|
|
|
getRequest = CommonRequest()
|
|
|
|
|
getRequest.set_domain(DOMAIN)
|
|
|
|
|
getRequest.set_version(API_VERSION)
|
|
|
|
|
getRequest.set_product(PRODUCT)
|
|
|
|
|
getRequest.set_action_name("GetTaskResult")
|
|
|
|
|
getRequest.set_method('GET')
|
|
|
|
|
getRequest.add_query_param(KEY_TASK_ID, taskId)
|
|
|
|
|
|
|
|
|
|
retry_count = 0
|
|
|
|
|
max_retries = 20 # 最多等待200秒 (20次*10秒)
|
|
|
|
|
|
|
|
|
|
while retry_count < max_retries:
|
|
|
|
|
raise APIConnectionError(f"API连接异常: {str(e)}") from e
|
|
|
|
|
except KeyError as e:
|
|
|
|
|
raise TaskSubmissionError("响应缺少TaskId字段") from e
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def poll_transcription_result(client: AcsClient, task_id: str) -> Dict:
|
|
|
|
|
"""轮询识别任务结果
|
|
|
|
|
|
|
|
|
|
:param client: 阿里云客户端实例
|
|
|
|
|
:param task_id: 任务ID
|
|
|
|
|
:return: 识别结果字典
|
|
|
|
|
:raises TaskTimeoutError: 超时未完成时抛出
|
|
|
|
|
"""
|
|
|
|
|
request = CommonRequest()
|
|
|
|
|
request.set_domain(CONFIG["DOMAIN"])
|
|
|
|
|
request.set_version(CONFIG["API_VERSION"])
|
|
|
|
|
request.set_product(CONFIG["PRODUCT"])
|
|
|
|
|
request.set_action_name("GetTaskResult")
|
|
|
|
|
request.set_method('GET')
|
|
|
|
|
request.add_query_param("TaskId", task_id)
|
|
|
|
|
|
|
|
|
|
retries = 0
|
|
|
|
|
while retries < CONFIG["MAX_RETRIES"]:
|
|
|
|
|
try:
|
|
|
|
|
getResponse = client.do_action_with_exception(getRequest)
|
|
|
|
|
resultData = json.loads(getResponse)
|
|
|
|
|
print(f"轮询结果({retry_count + 1}/{max_retries}):", resultData)
|
|
|
|
|
|
|
|
|
|
status = resultData.get(KEY_STATUS_TEXT, "")
|
|
|
|
|
if status == STATUS_SUCCESS:
|
|
|
|
|
return resultData.get(KEY_RESULT, {})
|
|
|
|
|
elif status in (STATUS_RUNNING, STATUS_QUEUEING):
|
|
|
|
|
time.sleep(10)
|
|
|
|
|
retry_count += 1
|
|
|
|
|
response = client.do_action_with_exception(request)
|
|
|
|
|
result = json.loads(response)
|
|
|
|
|
status = result.get("StatusText", "")
|
|
|
|
|
|
|
|
|
|
if status == "SUCCESS":
|
|
|
|
|
return result.get("Result", {})
|
|
|
|
|
if status in ("RUNNING", "QUEUEING"):
|
|
|
|
|
time.sleep(CONFIG["POLL_INTERVAL"])
|
|
|
|
|
retries += 1
|
|
|
|
|
else:
|
|
|
|
|
print(f"识别失败,最终状态: {status}")
|
|
|
|
|
return None
|
|
|
|
|
raise TaskSubmissionError(f"识别失败,状态: {status}")
|
|
|
|
|
|
|
|
|
|
except (ServerException, ClientException) as e:
|
|
|
|
|
print(f"查询异常: {e}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
print("超过最大重试次数,任务未完成")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 配置信息(请替换为实际值)
|
|
|
|
|
accessKeyId = "LTAI5tE4tgpGcKWhbZg6C4bh"
|
|
|
|
|
accessKeySecret = "oizcTOZ8izbGUouboC00RcmGE8vBQ1"
|
|
|
|
|
appKey = "OIpiw501l4o6MYEe"
|
|
|
|
|
fileLink = "https://ylt.oss-cn-hangzhou.aliyuncs.com/HuangHai/123.wav"
|
|
|
|
|
|
|
|
|
|
# 执行识别
|
|
|
|
|
result = fileTrans(accessKeyId, accessKeySecret, appKey, fileLink)
|
|
|
|
|
|
|
|
|
|
# 处理结果
|
|
|
|
|
if not result:
|
|
|
|
|
print("未获取到有效结果")
|
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
|
|
text_content = []
|
|
|
|
|
if 'Sentences' in result:
|
|
|
|
|
for sentence in result['Sentences']:
|
|
|
|
|
text_content.append(sentence.get("Text", ""))
|
|
|
|
|
full_text = ''.join(text_content)
|
|
|
|
|
else:
|
|
|
|
|
print("响应中未包含有效文本")
|
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
|
|
# 保存结果
|
|
|
|
|
output_file = "识别结果.txt"
|
|
|
|
|
try:
|
|
|
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|
|
|
|
f.write(full_text)
|
|
|
|
|
print(f"结果已保存至 {output_file}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"文件保存失败: {str(e)}")
|
|
|
|
|
exit(1)
|
|
|
|
|
raise APIConnectionError(f"查询异常: {str(e)}") from e
|
|
|
|
|
|
|
|
|
|
raise TaskTimeoutError(f"超过最大重试次数({CONFIG['MAX_RETRIES']}),任务未完成")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def save_transcription_result(result: Dict, output_path: Path) -> Path:
|
|
|
|
|
"""保存识别结果到文件
|
|
|
|
|
|
|
|
|
|
:param result: 识别结果字典
|
|
|
|
|
:param output_path: 输出文件路径
|
|
|
|
|
:return: 实际保存路径
|
|
|
|
|
:raises IOError: 文件保存失败时抛出
|
|
|
|
|
"""
|
|
|
|
|
if not result.get('Sentences'):
|
|
|
|
|
raise ValueError("识别结果为空")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
full_text = ''.join(s.get("Text", "") for s in result['Sentences'])
|
|
|
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
output_path.write_text(full_text, encoding='utf-8')
|
|
|
|
|
return output_path.resolve()
|
|
|
|
|
except (IOError, PermissionError) as e:
|
|
|
|
|
raise IOError(f"文件保存失败: {str(e)}") from e
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def transcribe_audio_file(
|
|
|
|
|
access_key_id: str,
|
|
|
|
|
access_key_secret: str,
|
|
|
|
|
app_key: str,
|
|
|
|
|
audio_url: str,
|
|
|
|
|
output_path: Path = Path("识别结果.txt")
|
|
|
|
|
) -> Tuple[bool, Path, str]:
|
|
|
|
|
"""语音识别主流程
|
|
|
|
|
|
|
|
|
|
:param access_key_id: 阿里云访问密钥ID
|
|
|
|
|
:param access_key_secret: 阿里云访问密钥
|
|
|
|
|
:param app_key: 应用密钥
|
|
|
|
|
:param audio_url: 音频文件URL
|
|
|
|
|
:param output_path: 输出文件路径
|
|
|
|
|
:return: (成功标志, 输出路径, 错误信息)
|
|
|
|
|
"""
|
|
|
|
|
client = AcsClient(access_key_id, access_key_secret, CONFIG["REGION_ID"])
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
task_id = submit_transcription_task(client, app_key, audio_url)
|
|
|
|
|
print(f"任务已提交,ID: {task_id}")
|
|
|
|
|
|
|
|
|
|
result = poll_transcription_result(client, task_id)
|
|
|
|
|
saved_path = save_transcription_result(result, output_path)
|
|
|
|
|
|
|
|
|
|
return True, saved_path, ""
|
|
|
|
|
|
|
|
|
|
except TranscriptionError as e:
|
|
|
|
|
return False, Path(), str(e)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
return False, Path(), f"未处理的异常: {str(e)}"
|
|
|
|
|
|
|
|
|
|
def ShiBie(audio_url, output_path):
|
|
|
|
|
# 配置参数(应通过环境变量或配置文件获取)
|
|
|
|
|
CREDENTIALS = {
|
|
|
|
|
"access_key_id": "LTAI5tE4tgpGcKWhbZg6C4bh",
|
|
|
|
|
"access_key_secret": "oizcTOZ8izbGUouboC00RcmGE8vBQ1",
|
|
|
|
|
"app_key": "OIpiw501l4o6MYEe",
|
|
|
|
|
"audio_url":audio_url,
|
|
|
|
|
"output_path": output_path
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 执行识别
|
|
|
|
|
success, path, error = transcribe_audio_file(**CREDENTIALS)
|
|
|
|
|
|
|
|
|
|
if success:
|
|
|
|
|
print(f"✅ 识别成功,文件已保存至: {path}")
|
|
|
|
|
else:
|
|
|
|
|
print(f"❌ 识别失败: {error}")
|
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|