移动端如何实现智能语音交互

news2024/11/16 2:39:52

智能语音交互(Intelligent Speech Interaction)是基于语音识别、语音合成、自然语言理解等技术,为企业在多种实际应用场景下,赋予产品“能听、会说、懂你”式的智能人机交互功能。适用于智能问答、智能质检、法庭庭审实时记录、实时演讲字幕、访谈录音转写等场景,在金融、司法、电商等多个领域均有应用。

 

 

一句话识别

对时长较短(一分钟以内)的语音进行识别,适用于较短的语音交互场景,如语音搜索、语音指令、语音短消息等,可集成在各类App、智能家电、智能助手等产品中。更多信息,请参见一句话识别接口说明。

实时语音识别

对不限时长的音频流做实时识别,达到“边说边出文字”的效果,内置智能断句,可提供每句话开始结束时间。可用于视频实时直播字幕、实时会议记录、实时法庭庭审记录、智能语音助手等场景。更多信息,请参见实时语音识别接口说明。

录音文件识别

对用户上传的录音文件进行识别,可用于呼叫中心语音质检、庭审数据库录入、会议记录总结、医院病历录入等场景。更多信息,请参见录音文件识别接口说明。

说明

针对免费用户,系统可在24小时内完成识别并返回识别文本;针对付费客户,系统可在3小时之内完成识别并返回识别文本,一次性上传大规模数据(半小时内上传超过500小时时长的录音)的除外。有大规模数据转写需求的客户,可与售前专家另行沟通。

语音合成

通过先进的深度学习技术,将文本转换成自然流畅的语音。目前有多种音色可供选择,并提供调节语速、语调、音量等功能。适用于智能客服、语音交互、文学有声阅读和无障碍播报等场景。更多信息,请参见语音合成接口说明。

语音合成CosyVoice大模型

语音合成CosyVoice大模型服务是依托大规模预训练语言模型,深度融合文本理解和语音生成的一项新型语音合成技术,能够精准解析并诠释各类文本内容,将其转化为宛如真人般的自然语音。

离线语音合成

在弱网或无网状态下,通过设备本地的语音合成模型,将文本转换成自然流畅的语音。

目前有多种音色可供选择,并提供调节语速、语调、音量等功能。适用于车载导航、智能硬件、文学有声阅读和无障碍播报等场景。以SDK的方式集成,支持多种不同硬件平台。按照设备激活数量收费,收费更加灵活可控。更多信息,请参见离线语音合成接口说明。

语音合成声音定制(企业版)

为您提供深度定制的TTS(Text to Speech)声音功能:使用先进的深度学习技术,用更少的数据量,更快速高效地定制高表现力的TTS声音。将自然流畅的声音输出到服务或设备上。

如果您想体验定制的声音、了解定制流程,请查看语音合成声音定制(企业版)。如有任何需求和疑问,请联系:nls_support@service.aliyun.com

自学习平台

您可以使用自学习平台提升识别效果。它提供了训练热词自学习语言模型两种方式。语音识别服务中,通过添加热词和使用热词模型来改善识别结果。在司法、金融等领域,利用语言模型定制进行优化,提高该业务场景下的识别准确率。

学习路线

  • 快速入门:快速体验智能语音交互服务。

  • 产品定价:了解智能语音交互服务的计费情况。

  • 开发指南:掌握相关术语、获取Access Token等内容。

  • 管控台指南:详细了解管控台提供的各项功能。

  • 接口参考选择需要的服务:一句话识别、实时语音识别、录音文件识别、语音合成等。

  • 自学习平台:通过自学习平台的热词、语言模型定制提升识别效果。

  • 最佳实践:了解智能语音交互服务的最佳实现方式。

  • 常见问题:查询常见问题的解决方案。

为了避免在移动端App或者桌面端工具中保存固定AccessKey ID和AccessKey Secret可能引起的泄露风险,您可以通过在App服务端创建Token并下发到移动端使用,或使用STS临时访问凭证调用语音服务两种方式,更加安全地访问智能语音交互服务。

方案一:通过App服务端创建Token并下发到移动端使用

前提条件

已开通智能语音交互服务,并根据产品文档调试成功,具体操作,请参见开通服务。

适用场景

如果您作为移动App开发者或者桌面端开发者,希望您的用户调用阿里云智能语音交互产品的语音合成、一句话识别、实时识别等服务时,为避免在移动端App或者桌面端工具中保存固定AccessKey ID和AccessKey Secret可能引起的泄露风险,您可以使用App服务端下发语音Token调用服务。

交互流程

 

  1. App端向用户应用服务器请求一个调用智能语音交互接口所依赖的语音Token,此处使用您自有的通信协议即可,比如用户登录时自动请求或服务端自动下发,或定时向应用服务器发起请求。

  2. 用户应用服务器向阿里云智能语音服务发起创建语音Token的真正请求,此处请您使用阿里云SDK或智能语音交互SDK来创建Token,创建Token所需的AccessKey ID和AccessKey Secret保存在您的应用服务器上。由于语音Token具有时效性,您可以在有效期范围内直接返回给App端,无需每次都向智能语音交互服务请求新的Token。

  3. 智能语音交互服务返回给应用服务器一个语音Token信息,包括Token字符串及有效期时间,在有效期内,您可以多次复用该Token而无需重新创建,Token的使用不受不同用户、不同设备的限制。

  4. 用户应用服务器将Token返回给App端,此时App端可以缓存并使用该Token,直到Token失效。当Token失效时,App端需要向应用服务器申请新的Token。假设Token凭证有效期为24小时,App端可以在Token过期前1到2小时主动向应用服务器请求更新Token。

  5. App端使用获取到的Token构建请求,向阿里云智能语音交互公共云发起调用,比如调用实时语音识别、一句话识别、语音合成等接口(不包括录音文件识别、录音文件识别闲时版等离线类接口),更多信息,请参见阿里云智能语音交互相关文档。

此方案无需过多额外设置或开发,将AccessKey ID和AccessKey Secret保存到移动端改为保存到用户自己的服务端,并通过服务端创建语音Token再下发给移动端使用,兼容了使用安全性及开发便捷性。

方案二:使用STS临时访问凭证调用语音服务

阿里云STS(Security Token Service)是阿里云提供的一种临时访问权限管理服务。您可以通过STS服务给其他用户颁发临时访问凭证,该用户可使用临时访问凭证,在规定时间内调用智能语音交互的录音文件识别服务(含闲时版)。临时访问凭证无需透露您的长期密钥,保障您的账户更加安全。

前提条件

已确保当前账号为阿里云账号或者被授予AliyunRAMFullAccess权限的RAM用户。关于为RAM用户授权的具体步骤,请参见为RAM用户授权。

适用场景

如果您作为移动App开发者或者桌面端开发者,希望您的用户调用阿里云智能语音交互产品的录音文件识别等服务时,避免在移动端App或者桌面端工具中保存固定AccessKey ID和AccessKey Secret可能引起的泄露风险,您可以使用STS授权用户调用服务。

交互流程

 

  1. App端向用户应用服务器请求STS临时访问凭证,此处使用用户自有的通信协议即可,比如用户登录时自动请求或服务端自动下发,或定时向应用服务器发起请求。

  2. 用户应用服务器向阿里云STS服务发起STS请求,此处请使用阿里云SDK,根据应用服务器自身保存的固定AK向STS请求生成一个临时凭证。

  3. STS返回给应用服务器一个临时访问凭证,包括临时访问密钥(AccessKey ID和AccessKey Secret)、安全令牌(SecurityToken)、该凭证的过期时间等信息。

  4. 用户应用服务器将临时凭证返回给App端,此时App端可以缓存并使用该凭证,直到凭证失效。当凭证失效时,App端需要向应用服务器申请新的临时访问凭证。假设临时访问凭证有效期为1小时,App端可以每30分钟或者每50分钟的频率向应用服务器请求更新临时访问凭证。

  5. App端使用获取到的临时凭证构建请求,向阿里云智能语音交互公共云发起调用,更多信息,请参见阿里云智能语音交互相关开发文档。

    本文以录音文件识别为例,为您介绍相关示例代码。

操作步骤

步骤一:创建RAM用户
  1. 登录RAM控制台。

  2. 在左侧导航栏,选择身份管理 > 用户

  3. 单击创建用户

  4. 输入登录名称显示名称

  5. 访问方式区域下,选择Open API 调用访问,然后单击确定

    image

  6. 单击复制,保存访问密钥(AccessKey ID 和 AccessKey Secret)。

步骤二:为RAM用户授予请求AssumeRole的权限
  1. 在已创建的RAM用户右侧,单击对应的添加权限

  2. 在添加权限页面,选择AliyunSTSAssumeRoleAccess系统策略。

    image

  3. 单击确定

步骤三:创建用于获取临时访问凭证的角色
  1. 在左侧导航栏,选择身份管理 > 角色

  2. 单击创建角色,可信实体类型选择阿里云账号,单击下一步

  3. 填写角色名称(此处以stsrole为例),选择当前云账号

    image

  4. 单击完成。完成角色创建后,单击关闭

  5. 在RAM角色管理页面,搜索框输入角色名称stsrole。

  6. 单击复制,保存角色的ARN。

    image

步骤四:为角色授予调用录音文件识别接口的权限
  1. 创建上传文件的自定义权限策略。

    1. 在左侧导航栏,选择权限管理 > 权限策略

    2. 单击创建权限策略

    3. 创建权限策略页面,单击脚本编辑

      如果您需要角色具备调用录音文件识别、录音文件识别闲时版服务,请参考以下配置示例。

      重要

      以下示例仅供参考。您需要根据实际需求配置更细粒度的授权策略,防止出现权限过大的风险。关于更细粒度的授权策略配置详情,请参见通过RAM或STS服务向其他用户授权。

      {
          "Version": "1",
          "Statement": [
              {
                  "Action": "nls:SubmitTask",
                  "Resource": "*",
                  "Effect": "Allow"
              },
              {
                  "Action": "nls:GetTaskResult",
                  "Resource": "*",
                  "Effect": "Allow"
              }
          ]
      }
    4. 策略配置完成后,单击继续编辑基本信息

    5. 基本信息区域,填写策略名称(此处以nls-stsuser-policy为例),然后单击确定

      image

  2. 为上面创建的RAM角色stsrole授予自定义权限策略。

    1. 在左侧导航栏,选择身份管理 > 角色

    2. 角色页面,找到目标RAM角色stsrole。

    3. 单击RAM角色stsrole右侧的添加权限

    4. 添加权限页面下的自定义策略页签,选择已创建的自定义权限策略nls-stsuser-policy。

    5. 单击确定

      image

步骤五:服务端获取STS临时访问凭证

需要注意,此步骤并不是在客户端侧(移动端App或桌面端)调用的,您可以在自己的应用服务器侧,通过您持有的固定AccessKey ID和AccessKey Secret(即步骤一创建RAM用户时保存的账户信息)创建STS临时访问凭证,然后将该凭证通过您已有的交互链路返回给客户端侧。客户端侧获取到临时访问凭证后,再调用步骤六中的录音文件识别服务。

  • 方式一:使用阿里云SDK(推荐)

    您可以使用多语言STS SDK获取临时访问凭证。

    以下Java代码用于获取临时访问凭证。

    引入POM依赖:

       <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-core</artifactId>
                <version>4.5.6</version>
            </dependency>
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-sts</artifactId>
                <version>3.0.0</version>
            </dependency>

    完整示例代码如下:

    import com.aliyuncs.CommonRequest;
    import com.aliyuncs.CommonResponse;
    import com.aliyuncs.DefaultAcsClient;
    import com.aliyuncs.IAcsClient;
    import com.aliyuncs.auth.sts.AssumeRoleRequest;
    import com.aliyuncs.auth.sts.AssumeRoleResponse;
    import com.aliyuncs.exceptions.ClientException;
    import com.aliyuncs.http.MethodType;
    import com.aliyuncs.http.ProtocolType;
    import com.aliyuncs.profile.DefaultProfile;
    import com.aliyuncs.profile.IClientProfile;
    
    public class CreateStsCredentialsDemo {
    
        public static void main(String args[]) throws ClientException {
            //同region STS的OpenAPI地址
            String endpoint = "sts.aliyuncs.com";
    
            // 填写步骤一生成的访问密钥AccessKey ID和AccessKey Secret。
            String accessKeyId = "******";
            String accessKeySecret = "******";
    
            // 填写步骤三获取的角色ARN, 一般是: acs:ram::********:role/***
            String roleArn = "******";
    
            // 自定义角色会话名称,用来区分不同的令牌,例如可填写为nls-role-session-99。
            String roleSession = "nls-role-session-99";
    
            // 添加endpoint(直接使用STS endpoint,前两个参数留空,无需添加region ID)
            DefaultProfile.addEndpoint("", "Sts", endpoint);
            // 构造default profile(参数留空,无需添加region ID)
            IClientProfile profile = DefaultProfile.getProfile("", accessKeyId, accessKeySecret);
            // 用profile构造client
            DefaultAcsClient client = new DefaultAcsClient(profile);
            final AssumeRoleRequest request = new AssumeRoleRequest();
            request.setRoleArn(roleArn);
            request.setRoleSessionName(roleSession);
            final AssumeRoleResponse response = client.getAcsResponse(request);
    
            String stsAccessKeyId = response.getCredentials().getAccessKeyId();
            String stsAccessKeySecret = response.getCredentials().getAccessKeySecret();
            String stsToken = response.getCredentials().getSecurityToken();
    
            System.out.println("Expiration: " + response.getCredentials().getExpiration());
            System.out.println("Access Key Id: " + stsAccessKeyId);
            System.out.println("Access Key Secret: " + stsAccessKeySecret);
            System.out.println("Security Token: " + stsToken);
            System.out.println("RequestId: " + response.getRequestId());
        }
    }

  • 方式二:使用REST API

    您可以通过调用STS服务接口AssumeRole获取临时访问凭证。

步骤六:客户端使用临时访问凭证调用录音文件识别服务(或录音文件识别闲时版)

如果您使用的是录音文件识别闲时版服务,本文流程及以下示例代码都可复用。

为了调用录音文件识别闲时版服务,下方的示例代码的产品信息需要从:

PRODUCT = "nls-filetrans"
DOMAIN = "filetrans.cn-shanghai.aliyuncs.com"
API_VERSION = "2018-08-17"

改为:

PRODUCT = "SpeechFileTranscriberLite"
DOMAIN = "speechfiletranscriberlite.cn-shanghai.aliyuncs.com"
API_VERSION = "2021-12-21"

Java代码演示通过STS临时访问凭证调用录音文件识别服务

引入POM依赖:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.5.6</version>
        </dependency>

以下是Java代码演示通过STS临时访问凭证调用录音文件转写服务的完整示例:

import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;

public class FileTransByStsJavaDemo {
    // 地域ID,常量,固定值。
    public static final String REGIONID = "cn-shanghai";
    public static final String ENDPOINTNAME = "cn-shanghai";
    public static final String PRODUCT = "nls-filetrans";
    public static final String DOMAIN = "filetrans.cn-shanghai.aliyuncs.com";
    public static final String API_VERSION = "2018-08-17";
    public static final String POST_REQUEST_ACTION = "SubmitTask";
    public static final String GET_REQUEST_ACTION = "GetTaskResult";
    // 请求参数
    public static final String KEY_APP_KEY = "appkey";
    public static final String KEY_FILE_LINK = "file_link";
    public static final String KEY_VERSION = "version";
    public static final String KEY_ENABLE_WORDS = "enable_words";
    // 响应参数
    public static final String KEY_TASK = "Task";
    public static final String KEY_TASK_ID = "TaskId";
    public static final String KEY_STATUS_TEXT = "StatusText";
    public static final String KEY_RESULT = "Result";
    // 状态值
    public static final String STATUS_SUCCESS = "SUCCESS";
    private static final String STATUS_RUNNING = "RUNNING";
    private static final String STATUS_QUEUEING = "QUEUEING";
    // 阿里云鉴权client
    IAcsClient client;
    public FileTransByStsJavaDemo(String stsAccessKeyId, String stsAccessKeySecret, String stsToken) {
        // 设置endpoint
        try {
            DefaultProfile.addEndpoint(ENDPOINTNAME, REGIONID, PRODUCT, DOMAIN);
        } catch (ClientException e) {
            e.printStackTrace();
        }
        // 创建DefaultAcsClient实例并初始化
        DefaultProfile profile = DefaultProfile.getProfile(REGIONID, stsAccessKeyId, stsAccessKeySecret, stsToken);
        this.client = new DefaultAcsClient(profile);
    }
    public String submitFileTransRequest(String appKey, String fileLink) {
        /**
         * 1. 创建CommonRequest,设置请求参数。
         */
        CommonRequest postRequest = new CommonRequest();
        // 设置域名
        postRequest.setDomain(DOMAIN);
        // 设置API的版本号,格式为YYYY-MM-DD。
        postRequest.setVersion(API_VERSION);
        // 设置action
        postRequest.setAction(POST_REQUEST_ACTION);
        // 设置产品名称
        postRequest.setProduct(PRODUCT);
        /**
         * 2. 设置录音文件识别请求参数,以JSON字符串的格式设置到请求Body中。
         */
        JSONObject taskObject = new JSONObject();
        // 设置appkey
        taskObject.put(KEY_APP_KEY, appKey);
        // 设置音频文件访问链接
        taskObject.put(KEY_FILE_LINK, fileLink);
        // 新接入请使用4.0版本,已接入(默认2.0)如需维持现状,请注释掉该参数设置。
        taskObject.put(KEY_VERSION, "4.0");
        // 设置是否输出词信息,默认为false,开启时需要设置version为4.0及以上。
        taskObject.put(KEY_ENABLE_WORDS, true);
        String task = taskObject.toJSONString();
        System.out.println(task);
        // 设置以上JSON字符串为Body参数。
        postRequest.putBodyParameter(KEY_TASK, task);
        // 设置为POST方式的请求。
        postRequest.setMethod(MethodType.POST);
        /**
         * 3. 提交录音文件识别请求,获取录音文件识别请求任务的ID,以供识别结果查询使用。
         */
        String taskId = null;
        try {
            CommonResponse postResponse = client.getCommonResponse(postRequest);
            System.err.println("提交录音文件识别请求的响应:" + postResponse.getData());
            if (postResponse.getHttpStatus() == 200) {
                JSONObject result = JSONObject.parseObject(postResponse.getData());
                String statusText = result.getString(KEY_STATUS_TEXT);
                if (STATUS_SUCCESS.equals(statusText)) {
                    taskId = result.getString(KEY_TASK_ID);
                }
            }
        } catch (ClientException e) {
            e.printStackTrace();
        }
        return taskId;
    }
    public String getFileTransResult(String taskId) {
        /**
         * 1. 创建CommonRequest,设置任务ID。
         */
        CommonRequest getRequest = new CommonRequest();
        // 设置域名
        getRequest.setDomain(DOMAIN);
        // 设置API版本
        getRequest.setVersion(API_VERSION);
        // 设置action
        getRequest.setAction(GET_REQUEST_ACTION);
        // 设置产品名称
        getRequest.setProduct(PRODUCT);
        // 设置任务ID为查询参数
        getRequest.putQueryParameter(KEY_TASK_ID, taskId);
        // 设置为GET方式的请求
        getRequest.setMethod(MethodType.GET);
        /**
         * 2. 提交录音文件识别结果查询请求
         * 以轮询的方式进行识别结果的查询,直到服务端返回的状态描述为“SUCCESS”或错误描述,则结束轮询。
         */
        String result = null;
        while (true) {
            try {
                CommonResponse getResponse = client.getCommonResponse(getRequest);
                System.err.println("识别查询结果:" + getResponse.getData());
                if (getResponse.getHttpStatus() != 200) {
                    break;
                }
                JSONObject rootObj = JSONObject.parseObject(getResponse.getData());
                String statusText = rootObj.getString(KEY_STATUS_TEXT);
                if (STATUS_RUNNING.equals(statusText) || STATUS_QUEUEING.equals(statusText)) {
                    // 继续轮询,注意设置轮询时间间隔。
                    Thread.sleep(10000);
                }
                else {
                    // 状态信息为成功,返回识别结果;状态信息为异常,返回空。
                    if (STATUS_SUCCESS.equals(statusText)) {
                        result = rootObj.getString(KEY_RESULT);
                        // 状态信息为成功,但没有识别结果,则可能是由于文件里全是静音、噪音等导致识别为空。
                        if(result == null) {
                            result = "";
                        }
                    }
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }
    public static void main(String args[]) throws Exception {
        // 相比非STS模式,此时此处填写之前生成的STS临时访问凭证。
        // 特别需要注意的是:临时访问凭证存在有效期,在有效期内您可以一直使用,但是请在过期前及时再次获取
        final String stsAccessKeyId = "STS.******";
        final String stsAccessKeySecret = "******";
        final String stsToken = "******";
        final String appKey = "******";
        
        String fileLink = "https://gw.alipayobjects.com/os/bmw-prod/0574ee2e-f494-45a5-820f-63aee583045a.wav";
        FileTransByStsJavaDemo demo = new FileTransByStsJavaDemo(stsAccessKeyId, stsAccessKeySecret, stsToken);
        // 第一步:提交录音文件识别请求,获取任务ID用于后续的识别结果轮询。
        String taskId = demo.submitFileTransRequest(appKey, fileLink);
        if (taskId != null) {
            System.out.println("录音文件识别请求成功,task_id: " + taskId);
        }
        else {
            System.out.println("录音文件识别请求失败!");
            return;
        }
        // 第二步:根据任务ID轮询识别结果。
        String result = demo.getFileTransResult(taskId);
        if (result != null) {
            System.out.println("录音文件识别结果查询成功:" + result);
        }
        else {
            System.out.println("录音文件识别结果查询失败!");
        }
    }
}

Python代码演示通过STS临时访问凭证调用录音文件识别服务

# -*- coding: utf-8 -*-
import json
import time
from aliyunsdkcore.acs_exception.exceptions import ClientException
from aliyunsdkcore.acs_exception.exceptions import ServerException
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.auth.credentials import StsTokenCredential
from aliyunsdkcore.request import CommonRequest

REGION_ID = "cn-shanghai"
DOMAIN = "filetrans.cn-shanghai.aliyuncs.com"
PRODUCT = "nls-filetrans"
API_VERSION = "2018-08-17"
POST_REQUEST_ACTION = "SubmitTask"
GET_REQUEST_ACTION = "GetTaskResult"
KEY_APP_KEY = "appkey"
KEY_FILE_LINK = "file_link"
KEY_VERSION = "version"
KEY_TASK = "Task"
KEY_TASK_ID = "TaskId"
KEY_STATUS_TEXT = "StatusText"

def submitTask(stsAkId, stsAkSecret, stsToken, appKey, fileLink):
    # 创建AcsClient实例
    stsTokenCredential = StsTokenCredential(stsAkId, stsAkSecret, stsToken)
    client = AcsClient(region_id=REGION_ID, credential=stsTokenCredential)
    # 创建提交录音文件识别请求,并设置请求参数。
    postRequest = CommonRequest()
    postRequest.set_domain(DOMAIN)
    postRequest.set_version(API_VERSION)
    postRequest.set_product(PRODUCT)
    postRequest.set_action_name(POST_REQUEST_ACTION)
    postRequest.set_method('POST')

    task = {KEY_APP_KEY : appKey, KEY_FILE_LINK : fileLink, KEY_VERSION : "4.0", "enable_words" : False}
    task = json.dumps(task)
    print(task)
    postRequest.add_body_params(KEY_TASK, task)
    taskId = ""
    try :
        # 提交录音文件识别请求,处理服务端返回的响应。
        postResponse = client.do_action_with_exception(postRequest)
        postResponse = json.loads(postResponse)
        print(postResponse)
        # 获取录音文件识别请求任务的ID,以供识别结果查询使用。
        statusText = postResponse[KEY_STATUS_TEXT]
        if statusText == "SUCCESS" :
            print("录音文件识别请求成功响应!")
            taskId = postResponse[KEY_TASK_ID]
            print("taskId = " + taskId)
        else :
            print("录音文件识别请求失败!")
    except ServerException as e:
        print(e)
    except ClientException as e:
        print(e)

    print(taskId)
    return taskId

def queryResult(stsAkId, stsAkSecret, stsToken, taskId):
    # 创建CommonRequest,设置任务ID。
    stsTokenCredential = StsTokenCredential(stsAkId, stsAkSecret, stsToken)
    client = AcsClient(region_id=REGION_ID, credential=stsTokenCredential)

    getRequest = CommonRequest()
    getRequest.set_domain(DOMAIN)
    getRequest.set_version(API_VERSION)
    getRequest.set_product(PRODUCT)
    getRequest.set_action_name(GET_REQUEST_ACTION)
    getRequest.set_method('GET')
    getRequest.add_query_param(KEY_TASK_ID, taskId)

    # 提交录音文件识别结果查询请求
    # 以轮询的方式进行识别结果的查询,直到服务端返回的状态描述符为"SUCCESS"、"SUCCESS_WITH_NO_VALID_FRAGMENT",或者为错误描述,则结束轮询。
    statusText = ""
    while True :
        try :
            getResponse = client.do_action_with_exception(getRequest)
            getResponse = json.loads(getResponse)
            print (json.dumps(getResponse).decode('unicode-escape'))
            statusText = getResponse[KEY_STATUS_TEXT]
            if statusText == "RUNNING" or statusText == "QUEUEING" :
                # 继续轮询
                time.sleep(5)
            else :
                # 退出轮询
                break
        except ServerException as e:
            print (e)
        except ClientException as e:
            print (e)
    if statusText == "SUCCESS" :
        print ("录音文件识别成功!")
    else :
        print ("录音文件识别失败!")

# 智能语音交互Appkey,获取Appkey请前往控制台:https://nls-portal.console.aliyun.com/applist
appKey = '******'
# 测试录音的url
fileLink = "https://gw.alipayobjects.com/os/bmw-prod/0574ee2e-f494-45a5-820f-63aee583045a.wav"

# 相比非STS模式,此时此处填写之前生成的STS临时访问凭证(含STS账号信息及SecurityToken)
# 特别需要注意的是:临时访问凭证存在有效期,在有效期内您可以一直使用,但是请在过期前及时再次获取
stsAkId = 'STS.******'
stsAkSecret = '******'
stsToken = '************'
print("--- submit one file ---")
taskId = submitTask(stsAkId, stsAkSecret, stsToken, appKey, fileLink)

print("--- query result ---")
queryResult(stsAkId, stsAkSecret, stsToken, taskId)

Go代码演示通过STS临时访问凭证调用录音文件转写服务

关于阿里云SDK Go语言版本的安装操作,请参见Go SDK 依赖及STS SDK。

以下是Go代码演示通过STS临时访问凭证调用录音文件转写服务的完整示例:

package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
)

func main() {
	// 地域ID,固定值。
	const REGION_ID string = "cn-shanghai"
	const ENDPOINT_NAME string = "cn-shanghai"
	const PRODUCT string = "nls-filetrans"
	const DOMAIN string = "filetrans.cn-shanghai.aliyuncs.com"
	const API_VERSION string = "2018-08-17"
	const POST_REQUEST_ACTION string = "SubmitTask"
	const GET_REQUEST_ACTION string = "GetTaskResult"
	// 请求参数
	const KEY_APP_KEY string = "appkey"
	const KEY_FILE_LINK string = "file_link"
	const KEY_VERSION string = "version"
	const KEY_ENABLE_WORDS string = "enable_words"
	// 响应参数
	const KEY_TASK string = "Task"
	const KEY_TASK_ID string = "TaskId"
	const KEY_STATUS_TEXT string = "StatusText"
	const KEY_RESULT string = "Result"
	// 状态值
	const STATUS_SUCCESS string = "SUCCESS"
	const STATUS_RUNNING string = "RUNNING"
	const STATUS_QUEUEING string = "QUEUEING"

	// 相比非STS模式,此时此处填写之前生成的STS临时访问凭证(含STS账号信息及SecurityToken)
	// 特别需要注意的是:临时访问凭证存在有效期,在有效期内您可以一直使用,但是请在过期前及时再次获取
	var accessKeyId string = "STS.******"
	var accessKeySecret string = "******"
	var stsToken string = ""******""
  var appKey string = "请填写您的appkey"

	var fileLink string = "https://gw.alipayobjects.com/os/bmw-prod/0574ee2e-f494-45a5-820f-63aee583045a.wav"
	client, err := sdk.NewClientWithStsToken(REGION_ID, accessKeyId, accessKeySecret, stsToken)
	if err != nil {
		panic(err)
	}
	postRequest := requests.NewCommonRequest()
	postRequest.Domain = DOMAIN
	postRequest.Version = API_VERSION
	postRequest.Product = PRODUCT
	postRequest.ApiName = POST_REQUEST_ACTION
	postRequest.Method = "POST"
	mapTask := make(map[string]string)
	mapTask[KEY_APP_KEY] = appKey
	mapTask[KEY_FILE_LINK] = fileLink
	// 新接入请使用4.0版本,已接入(默认2.0)如需维持现状,请注释掉该参数设置。
	mapTask[KEY_VERSION] = "4.0"
	// 设置是否输出词信息,默认为false。开启时需要设置version为4.0。
	mapTask[KEY_ENABLE_WORDS] = "false"
	task, err := json.Marshal(mapTask)
	if err != nil {
		panic(err)
	}
	postRequest.FormParams[KEY_TASK] = string(task)
	postResponse, err := client.ProcessCommonRequest(postRequest)
	if err != nil {
		panic(err)
	}
	postResponseContent := postResponse.GetHttpContentString()
	fmt.Println(postResponseContent)
	if postResponse.GetHttpStatus() != 200 {
		fmt.Println("录音文件识别请求失败,Http错误码: ", postResponse.GetHttpStatus())
		return
	}
	var postMapResult map[string]interface{}
	err = json.Unmarshal([]byte(postResponseContent), &postMapResult)
	if err != nil {
		panic(err)
	}
	var taskId string = ""
	var statusText string = ""
	statusText = postMapResult[KEY_STATUS_TEXT].(string)
	if statusText == STATUS_SUCCESS {
		fmt.Println("录音文件识别请求成功响应!")
		taskId = postMapResult[KEY_TASK_ID].(string)
	} else {
		fmt.Println("录音文件识别请求失败!")
		return
	}
	getRequest := requests.NewCommonRequest()
	getRequest.Domain = DOMAIN
	getRequest.Version = API_VERSION
	getRequest.Product = PRODUCT
	getRequest.ApiName = GET_REQUEST_ACTION
	getRequest.Method = "GET"
	getRequest.QueryParams[KEY_TASK_ID] = taskId
	statusText = ""
	for true {
		getResponse, err := client.ProcessCommonRequest(getRequest)
		if err != nil {
			panic(err)
		}
		getResponseContent := getResponse.GetHttpContentString()
		fmt.Println("识别查询结果:", getResponseContent)
		if getResponse.GetHttpStatus() != 200 {
			fmt.Println("识别结果查询请求失败,Http错误码:", getResponse.GetHttpStatus())
			break
		}
		var getMapResult map[string]interface{}
		err = json.Unmarshal([]byte(getResponseContent), &getMapResult)
		if err != nil {
			panic(err)
		}
		statusText = getMapResult[KEY_STATUS_TEXT].(string)
		if statusText == STATUS_RUNNING || statusText == STATUS_QUEUEING {
			time.Sleep(10 * time.Second)
		} else {
			break
		}
	}
	if statusText == STATUS_SUCCESS {
		fmt.Println("录音文件识别成功!")
	} else {
		fmt.Println("录音文件识别失败!")
	}
}

Node.js代码演示通过STS临时访问凭证调用录音文件识别服务

请您提前安装阿里云Core SDK、STS SDK及录音文件转写识别SDK,前两个SDK的安装,请参见Node.js SDK安装及示例。简单步骤如下:

npm install @alicloud/pop-core --save  
npm install @alicloud/sts-sdk --save   
npm install @alicloud/nls-filetrans-2018-08-17 --save

以下是Node.js代码演示通过STS临时访问凭证调用录音文件转写服务的完整示例:

'use strict';
const Client = require('@alicloud/nls-filetrans-2018-08-17');
function fileTrans(akID, akSecret, stsToken, appKey, fileLink) {
    //地域ID,固定值。
    var ENDPOINT = 'http://filetrans.cn-hangzhou.aliyuncs.com';
    var API_VERSION = '2018-08-17';
    /**
     * 创建阿里云鉴权client
     */
    var client = new Client({
        accessKeyId: akID, 
        secretAccessKey: akSecret,
        securityToken: stsToken,
        endpoint: ENDPOINT,
        apiVersion: API_VERSION
    });
    /**
     * 提交录音文件识别请求,请求参数组合成JSON格式的字符串作为task的值。
     * 请求参数appkey:项目appkey,获取Appkey请前往控制台:https://nls-portal.console.aliyun.com/applist
     * 请求参数file_link:需要识别的录音文件。
     */
    var task = {
        appkey : appKey,
        file_link : fileLink,
        version : "4.0",        // 新接入请使用4.0版本,已接入(默认2.0)如需维持现状,请注释掉该参数设置。
        enable_words : false     // 设置是否输出词信息,默认值为false,开启时需要设置version为4.0。
    };
    task = JSON.stringify(task);
    var taskParams = {
        Task : task
    };
    var options = {
            method: 'POST'
        };
    // 提交录音文件识别请求,处理服务端返回的响应。
    client.submitTask(taskParams, options).then((response) => {
        console.log(response);
        // 服务端响应信息的状态描述StatusText。
        var statusText = response.StatusText;
        if (statusText != 'SUCCESS') {
            console.log('录音文件识别请求响应失败!')
            return;
        }
        console.log('录音文件识别请求响应成功!');
        // 获取录音文件识别请求任务的TaskId,以供识别结果查询使用。
        var taskId = response.TaskId;
        /**
         * 以TaskId为查询参数,提交识别结果查询请求。
         * 以轮询的方式进行识别结果的查询,直到服务端返回的状态描述为"SUCCESS"、SUCCESS_WITH_NO_VALID_FRAGMENT,
         * 或者为错误描述,则结束轮询。
        */
        var taskIdParams = {
            TaskId : taskId
        };
        var timer = setInterval(() => {
            client.getTaskResult(taskIdParams).then((response) => {
                console.log('识别结果查询响应:');
                console.log(response);
                var statusText = response.StatusText;
                if (statusText == 'RUNNING' || statusText == 'QUEUEING') {
                    // 继续轮询,注意间隔周期。
                }
                else {
                    if (statusText == 'SUCCESS' || statusText == 'SUCCESS_WITH_NO_VALID_FRAGMENT') {
                        console.log('录音文件识别成功:');
                        var sentences = response.Result;
                        console.log(sentences);
                    }
                    else {
                        console.log('录音文件识别失败!');
                    }
                    // 退出轮询
                    clearInterval(timer);
                }
            }).catch((error) => {
                console.error(error);
                // 异常情况,退出轮询。
                clearInterval(timer);
            });
        }, 10000);
    }).catch((error) => {
        console.error(error);
    });
}

// 相比非STS模式,此时此处填写之前生成的STS临时访问凭证(含STS账号信息及SecurityToken)
// 特别需要注意的是:临时访问凭证存在有效期,在有效期内您可以一直使用,但是请在过期前及时再次获取
var akId = 'STS.******';
var akSecret = '******';
var stsToken = '******';
var appKey = '请填写您的appkey';
var fileLink = 'https://gw.alipayobjects.com/os/bmw-prod/0574ee2e-f494-45a5-820f-63aee583045a.wav';

fileTrans(akId, akSecret, stsToken, appKey, fileLink);

PHP代码演示通过STS临时访问凭证调用录音文件识别服务

请您提前安装阿里云Core SDK、STS SDK的PHP语言版本,详细安装请参见Node.js SDK安装及示例。简单步骤如下:

composer require alibabacloud/sdk
composer require alibabacloud/sts

以下是PHP代码演示通过STS临时访问凭证调用录音文件识别服务的完整示例:

<?php
require __DIR__ . '/vendor/autoload.php';
use AlibabaCloud\Client\AlibabaCloud;
use AlibabaCloud\Client\Exception\ClientException;
use AlibabaCloud\Client\Exception\ServerException;

class NLSFileTrans {
    // 请求参数
    private const KEY_APP_KEY = "appkey";
    private const KEY_FILE_LINK = "file_link";
    private const KEY_VERSION = "version";
    private const KEY_ENABLE_WORDS = "enable_words";
    // 响应参数
    private const KEY_TASK_ID = "TaskId";
    private const KEY_STATUS_TEXT = "StatusText";
    private const KEY_RESULT = "Result";
    // 状态值
    private const STATUS_SUCCESS = "SUCCESS";
    private const STATUS_RUNNING = "RUNNING";
    private const STATUS_QUEUEING = "QUEUEING";

    function submitFileTransRequest($appKey, $fileLink) {
        // 获取task JSON字符串,包含appkey和file_link参数等。
        $taskArr = array(self::KEY_APP_KEY => $appKey, self::KEY_FILE_LINK => $fileLink, self::KEY_VERSION => "4.0", self::KEY_ENABLE_WORDS => FALSE);
        $task = json_encode($taskArr);
        print $task . "\n";
        try {
            // 提交请求,返回服务端的响应。
            $submitTaskResponse = AlibabaCloud::nlsFiletrans()
                                              ->v20180817()
                                              ->submitTask()
                                              ->host("filetrans.cn-shanghai.aliyuncs.com")
                                              ->withTask($task)
                                              ->request();

            print $submitTaskResponse . "\n";
            // 获取录音文件识别请求任务的ID,以供识别结果查询使用。
            $taskId = NULL;
            $statusText = $submitTaskResponse[self::KEY_STATUS_TEXT];
            if (strcmp(self::STATUS_SUCCESS, $statusText) == 0) {
                $taskId = $submitTaskResponse[self::KEY_TASK_ID];
            }
            return $taskId;
        } catch (ClientException $exception) {
            // 获取错误消息
            print_r($exception->getErrorMessage());
        } catch (ServerException $exception) {
            // 获取错误消息
            print_r($exception->getErrorMessage());
        }
    }
    function getFileTransResult($taskId) {
        $result = NULL;
        while (TRUE) {
            try {
                $getResultResponse = AlibabaCloud::nlsFiletrans()
                                                 ->v20180817()
                                                 ->getTaskResult()
                                                 ->host("filetrans.cn-shanghai.aliyuncs.com")
                                                 ->withTaskId($taskId)
                                                 ->request();
                print "识别查询结果: " . $getResultResponse . "\n";
                $statusText = $getResultResponse[self::KEY_STATUS_TEXT];
                if (strcmp(self::STATUS_RUNNING, $statusText) == 0 || strcmp(self::STATUS_QUEUEING, $statusText) == 0) {
                    // 继续轮询
                    sleep(10);
                }
                else {
                    if (strcmp(self::STATUS_SUCCESS, $statusText) == 0) {
                        $result = $getResultResponse;
                    }
                    // 退出轮询
                    break;
                }
            } catch (ClientException $exception) {
                // 获取错误消息
                print_r($exception->getErrorMessage());
            } catch (ServerException $exception) {
                // 获取错误消息
                print_r($exception->getErrorMessage());
            }
        }
        return $result;
    }
}
$accessKeyId = "STS.******";
$accessKeySecret = "******";
$stsToken = "******";
$appKey = "请填写您的appkey";
$fileLink = "https://gw.alipayobjects.com/os/bmw-prod/0574ee2e-f494-45a5-820f-63aee583045a.wav";
/**
  * 第一步:设置一个全局客户端。
  * 使用阿里云RAM账号的AccessKey ID和AccessKey Secret进行鉴权。
 */
Alibabacloud::stsClient($accessKeyId, $accessKeySecret, $stsToken)
                ->regionId("cn-shanghai")
                ->asGlobalClient();

$fileTrans = new NLSFileTrans();
/**
  *  第二步:提交录音文件识别请求,获取任务ID,用于后续的识别结果轮询。
 */
$taskId = $fileTrans->submitFileTransRequest($appKey, $fileLink);
if ($taskId != NULL) {
    print "录音文件识别请求成功,task_id: " . $taskId . "\n";
}
else {
    print "录音文件识别请求失败!";
    return ;
}
/**
  * 第三步:根据任务ID轮询识别结果。
 */
$result = $fileTrans->getFileTransResult($taskId);
if ($result != NULL) {
    print "录音文件识别结果查询成功: " . $result . "\n";
}
else {
    print "录音文件识别结果查询失败!";
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2144124.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CICD 持续集成与持续交付

目录 一 CICD是什么 1.1 持续集成&#xff08;Continuous Integration&#xff09; 1.2 持续部署&#xff08;Continuous Deployment&#xff09; 1.3 持续交付&#xff08;Continuous Delivery&#xff09; 二 git工具使用 2.1 git简介 2.2 git 工作流程 三 部署git …

IntelliJ IDEA 2024.1 新特性下载安装激活方法

概述 IntelliJ IDEA 2024.1 发布了一系列令人期待新特性&#xff0c;可以帮助您提高开发效率。比如&#xff1a;全行代码补全、SpringBean 补全和自动装配、多语句内联端点、新版终端、编辑器中粘性行、AI Assistant 编码助手、改进的日志工作流、重命名嵌入提示、为整行代码提…

【北京迅为】《STM32MP157开发板使用手册》- 第三十三章Cortex-M4 DMA实验

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

《锐捷AP 胖模式配置示例》

目录 WEB配置方式: 1. 登录 AP 管理界面 2. 配置无线服务 3. 配置射频参数 4. 配置 VLAN (如果需要) 5. 配置 IP 地址 6. 其他高级设置(根据需求) 命令行配置: 1. 进入特权模式 2. 进入全局配置模式 3. 配置管理 IP 地址 4. 创建无线 SSID 5. 配置 SSID 加密…

Selenium打开浏览器后闪退问题解决

笔者这两天在做一个自动化方案&#xff0c;用来优化数据统计。其中一部分数据需要通过云上堡垒机跳转访问&#xff0c;而这个堡垒机在笔者日常使用的火狐浏览器上运行不是很正常&#xff08;表现在有些复制粘贴按钮显示不太灵敏&#xff09;。 但在Edge浏览器上基本正常&#…

工行软件开发中心积极推进低代码平台建设,助力金融业务快速研发

工行软件开发中心融合现有研发体系,打造全链路可视化研发。平台整体架构建立于行内新一代前后端分离研发体系之上,引入可视化技术,构建业务研发资产,承接现有服务体系,基于数据模型驱动技术及代码扩展能力,快速实现应用开发,并整合行内研发支撑体系,实现应用的快速构建…

python定时发送邮件的功能如何实现自动化?

Python定时发送邮件教程&#xff1f;如何用Python发送电子邮件&#xff1f; Python定时发送邮件不仅能够帮助我们自动处理日常的邮件发送任务&#xff0c;还能在特定时间点触发邮件发送&#xff0c;确保信息的及时传达。AokSend将详细探讨如何利用Python实现定时发送邮件的自动…

开放式耳机好用吗?哪个开放式耳机好用?

现在市面上的开放式耳机真的越来越火了&#xff0c;所以很多小伙伴也会来问我&#xff0c;有哪些品牌值得入手&#xff0c;开放式耳机到底好不好用的这个问题&#xff0c;作为专业的开放式耳机测评博主对于这个问题当然是信手拈来啦&#xff0c;这篇文章就来告诉大家如何才能选…

算法基础-扩展欧几里得算法

扩展欧几里得 public class Main {public static void main(String[] args) {Scanner in new Scanner(System.in);int n in.nextInt();while (n-- > 0) {int a in.nextInt();int b in.nextInt();int[] m exgcd(a, b);System.out.println(m[0] " " m[1]);}}…

【ollama 下载不下来的问题解决】

国内从官网下载ollama经常遇到下载不下来&#xff0c;或者卡住的问题&#xff0c;今天给大家分享解决这个问题的方法。 官网 官网地址&#xff1a;https://ollama.com/download 但是官网地址&#xff0c;就一直卡住&#xff0c;一直下载不下来&#xff0c;所以选用下面的方法…

【精选书籍】ChatGLM3大模型本地化部署、应用开发与微调的全面解析

前言 大模型领域既是繁星点点的未知宇宙&#xff0c;也是蕴含无数可能的广阔天地&#xff0c;正是这一独特的魅力&#xff0c;令无数的探索者为之倾倒&#xff0c;为之奋斗。随着大模型应用逐渐走入人们的日常生活&#xff0c;支撑它的深度学习技术也开始登上更为广阔和深远的…

【C++】日期类基础题

个人主页&#xff1a;CSDN_小八哥向前冲~ 所属专栏&#xff1a;C入门 一些C基础题目&#xff0c;帮你巩固一下&#xff01; 目录 关于内存问题 栈和堆基础问题 计算日期到天数的转换 日期差值 日期累加 打印日期 关于内存问题 答案&#xff1a;D B 第一题&#xff…

java重点学习-JVM类加载器+垃圾回收

12.7类加载器 JVM只会运行二进制文件&#xff0c;类加载器的作用就是将字节码文件加载到JVM中&#xff0c;从而让Java程序能够启动起来。 类加载器有哪些 启动类加载器(BootStrap ClassLoader):加载JAVA HOME/jre/lib目录下的库扩展类加载器(ExtClassLoader):主要加载JAVA HOME…

Tensorflow—第五讲卷积神经网络

本讲概述 卷积实际上就是特征提取。本讲我们先了解学习卷积神经网络基础知识&#xff0c;再一步步地学习搭建卷积神经网络&#xff0c;最后会运用卷积神经网络对cifar10 数据集分类。在本讲的最后附上几个经典卷积神经网络&#xff1a;LeNet、AlexNet、VGGNet、InceptionNet和…

开发小程序

由于之前购入的阿里云ECS放着落灰&#xff0c;碰巧又看到个有趣的项目&#xff0c;于是就做了个生成头像的小程序…由于第一次完整发布小程序&#xff0c;记录一下遇到的问题 小程序名称&#xff1a;靓仔创意头像 &#x1f602; 关于小程序 接口请求&#xff0c;在开发过程中…

少儿编程小游戏 | Scratch 射击游戏《开枪!》

在线玩&#xff1a;Scratch射击游戏 : “开枪&#xff01;” 免费下载-小虎鲸Scratch资源站 随着科技的飞速发展&#xff0c;编程已经成为孩子们未来必备的技能之一。而Scratch作为一款专为少儿设计的编程工具&#xff0c;通过可视化的编程方式&#xff0c;让孩子们在玩游戏的过…

JAVA-集合相关

HashMap如何解决哈希冲突的&#xff1f; 计算hash值&#xff0c;基于hashCode计算冲突之后&#xff0c;先是使用链式寻址法当链表长度大于8&#xff0c;且hash表的容量大于60的时候&#xff0c;再添加元素则转化成红黑树 为什么计算hash值是&#xff0c;是将hash地址的值右移1…

代码随想录训练营 Day62打卡 图论part11 Floyd 算法 A * 算法

代码随想录训练营 Day62打卡 图论part11 Floyd 算法 例题&#xff1a;卡码97. 小明逛公园 题目描述 小明喜欢去公园散步&#xff0c;公园内布置了许多的景点&#xff0c;相互之间通过小路连接&#xff0c;小明希望在观看景点的同时&#xff0c;能够节省体力&#xff0c;走最短…

C++速通LeetCode中等第3题-字母异位词分组

双指针法&#xff1a;两个指针分别指向左右边界&#xff0c;记录最大面积&#xff0c;由于面积由短板决定&#xff0c;两个指针中较短的短指针向内移动一格&#xff0c;再次记录最大面积&#xff0c; 直到两指针相遇&#xff0c;得出答案。 class Solution { public:int maxAr…

PyQt / PySide + Pywin32 + ctypes 自定义标题栏窗口 + 完全还原 Windows 原生窗口边框特效项目

项目地址&#xff1a; GitHub - github201014/PyQt-NativeWindow: A class of window include nativeEvent, use PySide or PyQt and Pywin32 and ctypesA class of window include nativeEvent, use PySide or PyQt and Pywin32 and ctypes - github201014/PyQt-NativeWindow…