阿里云语音合成

By admin at 2020-04-06 • 1人收藏 • 303人看过

感谢aardio培训群: 撒旦微笑 分享代码

https://github.com/nlysh007/aliyun-tts-aardio/releases


更新:

此代码已发布到aardio扩展库, 可以去扩展库管理器里安装后双击打开使用示例!


image.png

import fonts.fontAwesome;
import win.ui;
/*DSG{{*/
var winform = win.form(text="阿里云TTS";right=353;bottom=507)
winform.add(
bk={cls="bk";text="语调";left=19;top=76;right=46;bottom=94;align="left";z=16};
bk2={cls="bk";text="语速";left=19;top=118;right=46;bottom=136;align="left";z=17};
bk3={cls="bk";text="音量";left=19;top=160;right=46;bottom=178;align="left";z=18};
custom={cls="custom";text="自定义控件";left=349;top=501;right=350;bottom=502;z=15};
pitch_rate={cls="plus";left=57;top=76;right=309;bottom=94;bgcolor=-7223;border={radius=-1};color=16754775;foreRight=18;forecolor=-18315;paddingBottom=7;paddingTop=7;z=7};
play={cls="plus";text="立即合成";left=18;top=433;right=339;bottom=490;color=16777215;font=LOGFONT(h=-16;name='FontAwesome');forecolor=16754775;iconStyle={align="left";font=LOGFONT(h=-21;name='FontAwesome');padding={left=150}};iconText=" ";notify=1;z=14};
plus4={cls="plus";left=305;top=76;right=344;bottom=94;z=10};
plus5={cls="plus";left=305;top=118;right=344;bottom=136;z=11};
plus6={cls="plus";left=305;top=160;right=344;bottom=178;z=12};
speech_rate={cls="plus";left=57;top=118;right=309;bottom=136;bgcolor=-7223;border={radius=-1};color=16754775;foreRight=18;forecolor=-18315;paddingBottom=7;paddingTop=7;z=8};
txt={cls="plus";left=15;top=196;right=336;bottom=409;align="left";border={color=-3546113;width=2};editable=true;forecolor=16777215;iconColor=12632256;iconStyle={align="right";font=LOGFONT(name='FontAwesome');padding={right=10;bottom=10};valign="bottom"};iconText="0/300";multiline=1;textPadding={left=10;top=10;right=10;bottom=10};wrap=1;z=13};
voice={cls="plus";left=183;top=17;right=336;bottom=48;bgcolor=15132390;border={color=-3546113;width=2};editable=true;font=LOGFONT(h=-13);forecolor=16777215;iconStyle={align="right";font=LOGFONT(name='FontAwesome');padding={right=8}};iconText='\uF078';notify=1;paddingRight=28;textPadding={left=2;top=6;right=1;bottom=2};z=4};
voiceItem={cls="plus";text="temp";left=183;top=48;right=336;bottom=81;border={left=1;right=1;bottom=1;color=-3546113};font=LOGFONT(name='FontAwesome';charset=0);tabstop=1;z=5};
voiceItem2={cls="plus";text="temp";left=183;top=81;right=336;bottom=114;border={left=1;right=1;bottom=1;color=-3546113};font=LOGFONT(name='FontAwesome');tabstop=1;z=6};
voiceType={cls="plus";left=17;top=17;right=170;bottom=48;bgcolor=15132390;border={color=-3546113;width=2};editable=true;font=LOGFONT(h=-13);forecolor=16777215;iconStyle={align="right";font=LOGFONT(name='FontAwesome');padding={right=8}};iconText='\uF078';notify=1;paddingRight=28;textPadding={left=2;top=6;right=1;bottom=2};z=1};
voiceTypeMenuItem={cls="plus";text="temp";left=17;top=48;right=170;bottom=81;border={left=1;right=1;bottom=1;color=-3546113};font=LOGFONT(name='FontAwesome';charset=0);tabstop=1;z=2};
voiceTypeMenuItem2={cls="plus";text="temp";left=17;top=81;right=170;bottom=114;border={left=1;right=1;bottom=1;color=-3546113};font=LOGFONT(name='FontAwesome');tabstop=1;z=3};
volume={cls="plus";left=57;top=160;right=309;bottom=178;bgcolor=-7223;border={radius=-1};color=16754775;foreRight=18;forecolor=-18315;paddingBottom=7;paddingTop=7;z=9}
)
/*}}*/

_IMPORTURL.aliyun = "https://github.com/nlysh007/aliyun-tts-aardio/releases/latest/download/aliyun.tar.lzma"
import aliyun;
import aliyun.tts;
//aliyun.tts 配置

//测试用配置,如过期请修改
var appkey = "cpl1IGovfV6I05wb"; 
//当accessKeySecret为空时,accessKeyId 即token;
var accessKeyId = "16308e3ce0ea48e2be113fad6ec87727";//有效期1天
var accessKeySecret = "";

//voice 信息表
var tab = {
                {"小云";"Xiaoyun";"标准女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K"}};
                {"小刚";"Xiaogang";"标准男声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K"}};
                {"小梦";"Xiaomeng";"标准女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K"}};
                {"小威";"Xiaowei";"标准男声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K"}};
                {"若兮";"Ruoxi";"温柔女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"思琪";"Siqi";"温柔女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"思佳";"Sijia";"标准女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"思诚";"Sicheng";"标准男声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"艾琪";"Aiqi";"温柔女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"艾佳";"Aijia";"标准女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"艾诚";"Aicheng";"标准男声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"艾达";"Aida";"标准男声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"宁儿";"Ninger";"标准女声";"通用场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
                {"瑞琳";"Ruilin";"标准女声";"通用场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
                {"阿美";"Amei";"甜美女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K"}};
                {"小雪";"Xiaoxue";"温柔女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K"}};
                {"思悦";"Siyue";"温柔女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"艾雅";"Aiya";"严厉女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"艾夏";"Aixia";"亲和女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"艾美";"Aimei";"甜美女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"艾雨";"Aiyu";"自然女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"艾悦";"Aiyue";"温柔女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"艾婧";"Aijing";"严厉女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"小美";"Xiaomei";"甜美女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
                {"艾娜";"Aina";"浙普女声";"客服场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
                {"伊娜";"Yina";"浙普女声";"客服场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
                {"思婧";"Sijing";"严厉女声";"客服场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
                {"思彤";"Sitong";"儿童音";"童声场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
                {"小北";"Xiaobei";"萝莉女声";"童声场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
                {"艾彤";"Aitong";"儿童音";"童声场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
                {"艾薇";"Aiwei";"萝莉女声";"童声场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
                {"艾宝";"Aibao";"萝莉女声";"童声场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
                {"Halen";"Halen";"英音女声";"英文场景";"仅支持英文场景";{"8K";"16K"}};
                {"Harry";"Harry";"英音男声";"英文场景";"仅支持英文场景";{"8K";"16K"}};
                {"Eric";"Eric";"英音男声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
                {"Emily";"Emily";"英音女声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
                {"Luna";"Luna";"英音女声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
                {"Luca";"Luca";"英音男声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
                {"Wendy";"Wendy";"英音女声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
                {"William";"William";"英音男声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
                {"Olivia";"Olivia";"英音女声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
                {"姗姗";"Shanshan";"粤语女声";"方言场景";"支持标准粤文(简体)及粤英文混合场景";{"8K";"16K";"24K"}}
           }	
//获取文本长度
var getTxtLen = function(txt){
    var len = 0; 
    for m in string.gmatch( txt,":|.") {    
        if(string.find(m,':')){
            len +=2;
        }else {
            len +=1;
        } 
    }
    return len; 	
}; 
// 获取 voice 类型表
var getVoiceTypes = function(param){
    var voiceTypes = {}
    for(k,v in param){      
        if(!table.find(voiceTypes,v[3])){
            table.push(voiceTypes,v[3])
        }
    
    }
    return voiceTypes; 
}; 

var trackSkin = {	
    background={
        default=0xFFC9E3FF
    };
    foreground={
        default=0xFF75B8FF
    };
    color={
        default=0xFF57A8FF;
        hover=0xFF2E93FF
    }
}
winform.pitch_rate.setTrackbarRange(-500,500);

winform.pitch_rate.progressPos = 0;
winform.plus4.text = winform.pitch_rate.progressPos;
//trackbar
winform.pitch_rate.onPosChanged = function( pos,thumbTrack ){
    if(thumbTrack){
        winform.plus4.text = pos;
    }
}
winform.pitch_rate.skin(trackSkin)

winform.speech_rate.setTrackbarRange(-500,500);
winform.speech_rate.progressPos = 0;
winform.speech_rate.skin(trackSkin)
winform.plus5.text = winform.speech_rate.progressPos;
//trackbar
winform.speech_rate.onPosChanged = function( pos,thumbTrack ){
    if(thumbTrack){
        winform.plus5.text = pos;
    }
}

winform.txt.editBox.onChange = function(){  
    var num = getTxtLen(winform.txt.text)||0;
    if(num>300){
        winform.txt.iconColor = 0xFFFF0000;
    } else {
        winform.txt.iconColor = 0xFFC0C0C0;
    }
    winform.txt.iconText = num++"/300";

    
}

winform.volume.setTrackbarRange(1,100);
winform.volume.progressPos = 50;
winform.volume.skin(trackSkin)

winform.plus6.text = winform.volume.progressPos;
//trackbar
winform.volume.onPosChanged = function( pos,thumbTrack ){
    if(thumbTrack){
        winform.plus6.text = pos;
    }
}


import win.ui.tabs;
var skin = 	{ 
            foreground={
                default = 0xFFFFFFFF;
                hover= 0xFFE6E6E6;
            };
            checked = { 
                foreground={
                    default = 0xFFE6E6E6;
                }; 
            }
        }

var voiceTypeMenu = win.ui.tabs(winform.voiceTypeMenuItem,winform.voiceTypeMenuItem2)
voiceTypeMenu.skin( skin)
var voiceMenu = win.ui.tabs(winform.voiceItem,winform.voiceItem2)
voiceMenu.skin( skin)
// 切换到弹出列表模式,并使用参数指定的控件处理键盘事件
voiceTypeMenu.initPopup(winform.voiceType.editBox)
voiceMenu.initPopup(winform.voice.editBox)

var voiceTypes = getVoiceTypes(tab);
var getInfoByType = function(tab,voiceType){

    var info = table.filter(tab,function(v,index){
                    if(v[3] = voiceType){
                        return v; 
                    }
                })	
    return info; 
}
voiceTypeMenu.clear()
voiceMenu.clear()
voiceTypeMenu.setItemTexts(voiceTypes)

winform.voiceType.editState = false;
winform.voiceType.editBox.onChange = function(){
    var voiceType = voiceTypeMenu.selText ||voiceTypes[1];
    var voice = getInfoByType(tab,voiceType)
    if(voice){
        for(k,v in voice){
            voice[k] = v[1]
        }
        voiceMenu.setItemTexts(voice) 
        winform.voice.text=voice[1];	
    }
}
winform.voiceType.text=voiceTypes[1];
// 用户点选菜单项触发此事件,strip参数是点选的控件
voiceTypeMenu.onOk = function(strip){
    winform.voiceType.setFocus(voiceTypeMenu.selText)
}
voiceMenu.onOk = function(strip){
    winform.voice.setFocus(voiceMenu.selText)
}

// 禁止共享编辑框外观状态(focus状态除外)
winform.voiceType.editState = false;
winform.voice.editState = false;
winform.voiceType.skin({
    background = { hover = 0x5E00CCFF }
    checked = { 
        iconText = '\uF077';
    }  
})

winform.voice.skin({
    background = { hover = 0x5E00CCFF }
    checked = { 
        iconText = '\uF077';
    }  
})
// 显示弹出菜单,弹出菜单会自动修改winform.voiceType的checked属性为菜单打开状态
winform.voiceType.oncommand = function(id,event){ 
    if(winform.voiceType.checked ){
        voiceTypeMenu.selText = winform.voiceType.text
        voiceTypeMenu.popup(true,winform.voiceType)
    }  
}
winform.voice.oncommand = function(id,event){  
    if(winform.voice.checked ){
        voiceMenu.selText = winform.voice.text
        voiceMenu.popup(true,winform.voice)
    }  
}

var wmp = winform.custom.createEmbed( "WMPlayer.OCX" )._object;
winform.play.oncommand = function(id,event){
    if(winform.txt.text==""){
        win.msgbox("文本不能为空")
        return ; 
    }
    if(appkey == "" or accessKeyId == ""){
        win.msgbox("请至少配置 accessKeyId,token")
        return ; 
    }
    winform.play.iconColor =0xFFFFFFFF;
    //获取对应voice的配置信息
      var voice = table.filter(tab,function(v,index){
            if(v[1]===winform.voice.text){
                return v; 
            }
        })
    
    
    //获取tts地址

     client := aliyun.tts.client(appkey,accessKeyId,accessKeySecret)
    var param =   {
            text = winform.txt.text;
            voice = voice[1][[2]];
            pitch_rate = winform.pitch_rate.progressPos;
            speech_rate = winform.speech_rate.progressPos;
            volume	= winform.volume.progressPos;
            format = "wav";
      }; 
    var url = client.getTTS(param)
    
    wmp.url = url;
    
    winform.play.disabled = true;
        winform.setInterval( 
        1000,function(){
            select(wmp.playState) {
                case 1 {
                    winform.play.disabledText = null;
                    winform.play.disabled = false;
                    winform.play.text = "立即合成"
                    return false; 
                }
                case 3,9 {
                    if(wmp.playState==3){
                        winform.play.disabledText =  {'\uF026';'\uF027';'\uF028';'\uF026'};
                        winform.play.text = null
                    }			
    
                }
                else {
                    winform.play.disabledText = null;
                    winform.play.disabled = false;
                    winform.play.text = "立即合成"
                    return false; 
                }
            }
    
        } 
    )	

}


winform.show() 
win.loopMessage();

如果不能自动下载aliyun扩展库的话, 

aliyun.zip


5 个回复 | 最后更新于 2020-04-08
2020-04-06   #1

感谢分享,非常棒。


有几个小的建议,

库的命名上我们还是要斟酌一下,aardio 的库得益于简洁统一的命名 - 其实让我们轻松了许多。

例如上次的Zint调整到libzint,就是去掉首字母大写(名字空间统一这样命名),使用更精确的名称代替短名称。


类似上面这个扩展库,使用了三个名字空间 aliyun,aliyun.tss,aliyun.util,

据我的了解,aliyun的接口很多(例如我就写过一个aliyun.oss),这样可能会覆盖其他人的代码,而实际上这个扩展库里aliyun基本是空的(就是导入aliyun.tss).

另外一个aliyun.util我看了一下,似乎合并到aliyun.tss里代码会更紧凑,可读性反而更好一些,代码并不总是拆分开就更好。


这个token的刷新似乎也有一些问题,有判断过期时间,然后可以直接指定token,

如果直接指定token,然后又过期了,还是是不是还会调用到getToken?!然后这个getToken里又没有需要的accessKeySecret参数。


代码里有这样一句:

if(accessKeySecret and accessKeySecret!=="")

是不是可以缩写为 if(#accessKeySecret) 更简单一些?!


另外还有这么一段代码:

getToken = function(param){

var signTab =  { /*默认参数*/ };

if(param){ signTab = param; } 


是不是像下面这样反过来写读起来会轻松一些:

getToken = function(param){ 

if(param){ param = { /*默认参数*/ } } 


上面签名参数中的几个字段,例如:

Timestamp = tostring(..time(, "%Y-%m-%dT%H:%M:%SZ").utc()); 

SignatureNonce = tostring(..win.guid.create());


其中tostring的调用是不必要的,iso8601实际上可以用 time.iso8601()直接创建,

所以可以略写如下:

Timestamp = ..time.iso8601();

SignatureNonce =..win.guid.create();


库开头第一行的 

// Aliyun TTS客户端

这一行的格式有个要求:

//后面不能有空格,然后是库的名字(首字母不要大写),然后是空格加描述,

所以应该写为 //tts 语音合成


另外类似嗓音类型的配置表,这个相对固定可以放到扩展库里是不是更好一些?!


我对扩展库的代码进行了一些改进,合并为一个扩展库,源码如下:

//tts 语音合成
import crypt.hmac;
import crypt.bin; 
import inet.url; 
import win.guid;
import web.rest.jsonClient;  
namespace aliyun.tts;
    
class client{
    ctor(appkey,accessKeyId,accessKeySecret){
        this = ..web.rest.jsonClient(); 
        this.appkey = appkey
        this.accessKeyId = accessKeyId;
        this.accessKeySecret = accessKeySecret;
        this.ttsUrl = "https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts";
        this.tokenUrl = "https://nls-meta.cn-shanghai.aliyuncs.com/"; 
        
        this.getAccessToken = function(param){ 
            if(!param){
                param = {
                    Format = "JSON";
                    AccessKeyId = this.accessKeyId;
                    Action = "CreateToken";
                    Version = "2019-02-28";
                    
                    RegionId = "cn-shanghai";
                    Timestamp = ..time.iso8601();
                    SignatureMethod = "HMAC-SHA1";
                    SignatureVersion = "1.0";
                    SignatureNonce =..win.guid.create();
                };
            } 
            
            param.Signature = getSignature(this.accessKeySecret,param);
            var tokenUrl = ..inet.url.appendExtraInfo(this.tokenUrl,..inet.url.stringifyParameters(param));
            var res = this.get(tokenUrl); 
            
            this.accessToken = {
                id = res[["Token"]][["Id"]];
                expireTime = res[["Token"]][["ExpireTime"]] 
            };
            return this.accessToken;
        } 
        
        if(#this.accessKeySecret){ 
            this.accessToken =  this.getAccessToken(); 
        }
        else {
            this.accessToken = { id = this.accessKeyId };
        } 
    };

    getSpeechUrl  = function(param){
        if(!this.accessToken){
            error("未指定令牌",2);
        }
        
        if(#this.accessKeySecret){
            if(this.accessToken.expireTime and this.accessToken.expireTime<=(tonumber(..time()))){
                this.accessToken =  accessToken.getAccessToken();	
            }	
        }
        
        param.token = this.accessToken.id;
        param.appkey = this.appkey;
        return ..inet.url.appendExtraInfo(this.ttsUrl,..inet.url.stringifyParameters(param));  
    }
    
}

client.getSignature = function(accessKeySecret,param){	
    var data = ..string.join({
        "GET";..inet.url.encode("/");
        ..inet.url.encode(..inet.url.stringifyParameters(param));
    },"&");
    var signature = ..crypt.hmac.sha1(accessKeySecret++"&", data).getValue();
        signature = ..crypt.bin.encodeBase64(signature);
    return signature;
}

voices =  {
    {"小云";"Xiaoyun";"标准女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K"}};
    {"小刚";"Xiaogang";"标准男声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K"}};
    {"小梦";"Xiaomeng";"标准女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K"}};
    {"小威";"Xiaowei";"标准男声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K"}};
    {"若兮";"Ruoxi";"温柔女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"思琪";"Siqi";"温柔女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"思佳";"Sijia";"标准女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"思诚";"Sicheng";"标准男声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"艾琪";"Aiqi";"温柔女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"艾佳";"Aijia";"标准女声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"艾诚";"Aicheng";"标准男声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"艾达";"Aida";"标准男声";"通用场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"宁儿";"Ninger";"标准女声";"通用场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
    {"瑞琳";"Ruilin";"标准女声";"通用场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
    {"阿美";"Amei";"甜美女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K"}};
    {"小雪";"Xiaoxue";"温柔女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K"}};
    {"思悦";"Siyue";"温柔女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"艾雅";"Aiya";"严厉女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"艾夏";"Aixia";"亲和女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"艾美";"Aimei";"甜美女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"艾雨";"Aiyu";"自然女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"艾悦";"Aiyue";"温柔女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"艾婧";"Aijing";"严厉女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"小美";"Xiaomei";"甜美女声";"客服场景";"支持中文及中英文混合场景";{"8K";"16K";"24K"}};
    {"艾娜";"Aina";"浙普女声";"客服场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
    {"伊娜";"Yina";"浙普女声";"客服场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
    {"思婧";"Sijing";"严厉女声";"客服场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
    {"思彤";"Sitong";"儿童音";"童声场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
    {"小北";"Xiaobei";"萝莉女声";"童声场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
    {"艾彤";"Aitong";"儿童音";"童声场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
    {"艾薇";"Aiwei";"萝莉女声";"童声场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
    {"艾宝";"Aibao";"萝莉女声";"童声场景";"仅支持纯中文场景";{"8K";"16K";"24K"}};
    {"Halen";"Halen";"英音女声";"英文场景";"仅支持英文场景";{"8K";"16K"}};
    {"Harry";"Harry";"英音男声";"英文场景";"仅支持英文场景";{"8K";"16K"}};
    {"Eric";"Eric";"英音男声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
    {"Emily";"Emily";"英音女声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
    {"Luna";"Luna";"英音女声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
    {"Luca";"Luca";"英音男声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
    {"Wendy";"Wendy";"英音女声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
    {"William";"William";"英音男声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
    {"Olivia";"Olivia";"英音女声";"英文场景";"仅支持英文场景";{"8K";"16K";"24K"}};
    {"姗姗";"Shanshan";"粤语女声";"方言场景";"支持标准粤文(简体)及粤英文混合场景";{"8K";"16K";"24K"}}
} 

getVoiceTypes = function(voices){
    if(!voices) voices = self.voices;
    
    var voiceTypes = {}
    for(k,v in voices){ 
        if(!voiceTypes[v[3]]){
            ..table.push(voiceTypes,v[3]) 
            voiceTypes[v[3]] = {v}
        }
        else {
            ..table.push(voiceTypes[v[3]] ,v) 
        }
            
    }
    return voiceTypes; 
};
    
getVoiceByName = function(name,voices){
    if(!voices) voices = self.voices;
    for(k,v in voices){
        if(v[1]===name){   return v;   }	 
    } 
}

/**intellisense()
aliyun.tts = 阿里云语音合成\n https://github.com/nlysh007/aliyun-tts-aardio
aliyun.tts.client() = !aliyunTtsClient.
aliyun.tts.client(.(appkey,token) = 阿里云语音合成客户端 
aliyun.tts.client(.(appkey,accessKeyId,accessKeySecret) = 阿里云语音合成客户端 
aliyun.tts.getVoiceTypes() = 返回嗓音类型数组
aliyun.tts.getVoiceByName(.(name,voices) = 根据@name参数指定的名称在返回嗓音信息\n@voices参数可省略
aliyun.tts.voices = 可用嗓音列表
!aliyunTtsClient.getSpeechUrl(.(param) = 获取TTS语言地址
end intellisense**/





2020-04-06   #2

aliyun.tts 已经发布到 aardo 扩展库里了,这是改进后的示例(时间关系比较抢,所以范例未仔细检查,大概地改了一下):

/*
http://www.htmlayout.cn/t/374
*/
import fonts.fontAwesome;
import win.ui;
/*DSG{{*/
var winform = win.form(text="阿里云TTS";right=353;bottom=507)
winform.add(
bk={cls="bk";text="语调";left=19;top=76;right=46;bottom=94;align="left";z=16};
bk2={cls="bk";text="语速";left=19;top=118;right=46;bottom=136;align="left";z=17};
bk3={cls="bk";text="音量";left=19;top=160;right=46;bottom=178;align="left";z=18};
custom={cls="custom";text="自定义控件";left=349;top=501;right=350;bottom=502;z=15};
pitchRate={cls="plus";left=57;top=76;right=309;bottom=94;bgcolor=-7223;border={radius=-1};color=16754775;foreRight=18;forecolor=-18315;paddingBottom=7;paddingTop=7;z=7};
play={cls="plus";text="立即合成";left=18;top=433;right=339;bottom=490;color=16777215;font=LOGFONT(h=-16;name='FontAwesome');forecolor=16754775;notify=1;z=14};
plus4={cls="plus";left=305;top=76;right=344;bottom=94;z=10};
plus5={cls="plus";left=305;top=118;right=344;bottom=136;z=11};
plus6={cls="plus";left=305;top=160;right=344;bottom=178;z=12};
speechRate={cls="plus";left=57;top=118;right=309;bottom=136;bgcolor=-7223;border={radius=-1};color=16754775;foreRight=18;forecolor=-18315;paddingBottom=7;paddingTop=7;z=8};
txt={cls="plus";left=15;top=196;right=336;bottom=409;align="left";border={color=-3546113;width=2};editable=true;forecolor=16777215;iconColor=12632256;iconStyle={align="right";font=LOGFONT(name='FontAwesome');padding={right=10;bottom=10};valign="bottom"};iconText="0/300";multiline=1;textPadding={left=10;top=10;right=10;bottom=10};wrap=1;z=13};
voice={cls="plus";left=183;top=17;right=336;bottom=48;bgcolor=15132390;border={color=-3546113;width=2};editable=true;font=LOGFONT(h=-13);forecolor=16777215;iconStyle={align="right";font=LOGFONT(name='FontAwesome');padding={right=8}};iconText='\uF078';notify=1;paddingRight=28;textPadding={left=2;top=6;right=1;bottom=2};z=4};
voiceItem={cls="plus";text="temp";left=183;top=48;right=336;bottom=81;border={left=1;right=1;bottom=1;color=-3546113};font=LOGFONT(name='FontAwesome';charset=0);tabstop=1;z=5};
voiceItem2={cls="plus";text="temp";left=183;top=81;right=336;bottom=114;border={left=1;right=1;bottom=1;color=-3546113};font=LOGFONT(name='FontAwesome');tabstop=1;z=6};
voiceType={cls="plus";left=17;top=17;right=170;bottom=48;bgcolor=15132390;border={color=-3546113;width=2};editable=true;font=LOGFONT(h=-13);forecolor=16777215;iconStyle={align="right";font=LOGFONT(name='FontAwesome');padding={right=8}};iconText='\uF078';notify=1;paddingRight=28;textPadding={left=2;top=6;right=1;bottom=2};z=1};
voiceTypeMenuItem={cls="plus";text="temp";left=17;top=48;right=170;bottom=81;border={left=1;right=1;bottom=1;color=-3546113};font=LOGFONT(name='FontAwesome';charset=0);tabstop=1;z=2};
voiceTypeMenuItem2={cls="plus";text="temp";left=17;top=81;right=170;bottom=114;border={left=1;right=1;bottom=1;color=-3546113};font=LOGFONT(name='FontAwesome');tabstop=1;z=3};
volume={cls="plus";left=57;top=160;right=309;bottom=178;bgcolor=-7223;border={radius=-1};color=16754775;foreRight=18;forecolor=-18315;paddingBottom=7;paddingTop=7;z=9}
)
/*}}*/

//测试用配置,如过期请修改
var appkey = "cpl1IGovfV6I05wb"; 
//当accessKeySecret为空时,accessKeyId 即token;
var accessKeyId = "16308e3ce0ea48e2be113fad6ec87727";//有效期1天
var accessKeySecret = "";
  
var styles = {
    tabs =  { 
        foreground={
            default = 0xFFFFFFFF;
            hover= 0xFFE6E6E6;
        };
        checked = { 
            foreground={
                default = 0xFFE6E6E6;
            }; 
        }
    }
    trackbar =  {    
        background={
            default=0xFFC9E3FF
        };
        foreground={
            default=0xFF75B8FF
        };
        color={
            default=0xFF57A8FF;
            hover=0xFF2E93FF
        }
    }
    dropdown = {
        background = { hover = 0x5E00CCFF }
        checked = { 
            iconText = '\uF077';
        }  
    }
    button = {
        foreground={
            default=0xFF57A8FF;
            hover=0xFF928BB3;
            disabled=0x6657A8FF; 
        }
    }
}
 
winform.play.skin(styles.button)
winform.pitchRate.setTrackbarRange(-500,500);
winform.pitchRate.progressPos = 0;
winform.plus4.text = winform.pitchRate.progressPos;
//trackbar
winform.pitchRate.onPosChanged = function( pos,thumbTrack ){
    if(thumbTrack){
        winform.plus4.text = pos;
    }
}
  
winform.pitchRate.skin(styles.trackbar)
  
winform.speechRate.setTrackbarRange(-500,500);
winform.speechRate.progressPos = 0;
winform.speechRate.skin(styles.trackbar)
winform.plus5.text = winform.speechRate.progressPos;
//trackbar
winform.speechRate.onPosChanged = function( pos,thumbTrack ){
    if(thumbTrack){
        winform.plus5.text = pos;
    }
}
  
winform.txt.editBox.onChange = function(){  
    var num = #string.fromto(winform.txt.text,65001,0);
    if(num>300){
        winform.txt.iconColor = 0xFFFF0000;
    } else {
        winform.txt.iconColor = 0xFFC0C0C0;
    }
    winform.txt.iconText = num++"/300";
}
  
winform.volume.setTrackbarRange(1,100);
winform.volume.progressPos = 50;
winform.volume.skin(styles.trackbar)
  
winform.plus6.text = winform.volume.progressPos;
winform.volume.onPosChanged = function( pos,thumbTrack ){
    if(thumbTrack){
        winform.plus6.text = pos;
    }
}
  
import win.ui.tabs;
var voiceTypeMenu = win.ui.tabs(winform.voiceTypeMenuItem,winform.voiceTypeMenuItem2)
voiceTypeMenu.skin(styles.tabs)
 
var voiceMenu = win.ui.tabs(winform.voiceItem,winform.voiceItem2)
voiceMenu.skin(styles.tabs)
 
// 切换到弹出列表模式,并使用参数指定的控件处理键盘事件
voiceTypeMenu.initPopup(winform.voiceType.editBox)
voiceMenu.initPopup(winform.voice.editBox)
  
voiceTypeMenu.clear()
voiceMenu.clear()
 
import aliyun.tts;
var voiceTypes = aliyun.tts.getVoiceTypes();
voiceTypeMenu.setItemTexts(voiceTypes)
  
winform.voiceType.editState = false;
winform.voiceType.editBox.onChange = function(){
    var voiceType = voiceTypeMenu.selText ||voiceTypes[1];
    var voice = voiceTypes[voiceType]
    if(voice){
        for(k,v in voice){
            voice[k] = v[1]
        }
        voiceMenu.setItemTexts(voice) 
        winform.voice.text=voice[1];    
    }
}
winform.voiceType.text=voiceTypes[1];
 
// 用户点选菜单项触发此事件,strip参数是点选的控件
voiceTypeMenu.onOk = function(strip){
    winform.voiceType.setFocus(voiceTypeMenu.selText)
}
voiceMenu.onOk = function(strip){
    winform.voice.setFocus(voiceMenu.selText)
}
  
// 禁止共享编辑框外观状态(focus状态除外)
winform.voiceType.editState = false;
winform.voice.editState = false;
winform.voiceType.skin(styles.dropdown)
winform.voice.skin(styles.dropdown)
 
// 显示弹出菜单,弹出菜单会自动修改winform.voiceType的checked属性为菜单打开状态
winform.voiceType.oncommand = function(id,event){ 
    if(winform.voiceType.checked ){
        voiceTypeMenu.selText = winform.voiceType.text
        voiceTypeMenu.popup(true,winform.voiceType)
    }  
}
winform.voice.oncommand = function(id,event){  
    if(winform.voice.checked ){
        voiceMenu.selText = winform.voice.text
        voiceMenu.popup(true,winform.voice)
    }  
}
 
var wmpEmbed = winform.custom.createEmbed( "WMPlayer.OCX" );
var wmpOcx = wmpEmbed._object;
wmpEmbed.PlayStateChange = function(newState){
    select(newState) {
        case 1 {
            winform.play.disabledText = null;
            winform.play.text = "立即合成";
            return false; 
        }
        case 3,9 {
            if(wmpOcx.playState==3){
                winform.play.text = "";
                winform.play.disabledText =  {'\uF026';'\uF027';'\uF028';'\uF026'};
            }        
        }
        else {
            winform.play.disabledText = null;
            winform.play.text = "立即合成";
            return false; 
        } 
    }
}
 
winform.play.oncommand = function(id,event){
    if(winform.txt.text==""){
        win.msgbox("文本不能为空")
        return ; 
    }
    if(appkey == "" or accessKeyId == ""){
        win.msgbox("请至少配置 accessKeyId,token")
        return ; 
    } 
    
    //获取对应voice的配置信息
    var voice = aliyun.tts.getVoiceByName(winform.voice.text) 
    winform.play.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250'}
    wmpOcx.url = win.invoke(
        function(winform,param,appkey,accessKeyId,accessKeySecret){
            import aliyun.tts; 
            var client  = aliyun.tts.client(appkey,accessKeyId,accessKeySecret)
            return client.getSpeechUrl(param)
        },winform,{
                text = winform.txt.text;
                voice = voice[[2]];
                pitch_rate = winform.pitchRate.progressPos;
                speech_rate = winform.speechRate.progressPos;
                volume  = winform.volume.progressPos;
                format = "wav";
        },appkey,accessKeyId,accessKeySecret
    ); 
}

winform.show() 
win.loopMessage();


2020-04-06   #3

另外智能提示配置中的 !client 更改为了 !aliyunTtsClient , 这种应当使用更准确的名字以避免出现名字冲突( 因为都是全局有效 )

2020-04-06   #4

这个合成语音,网络原因或者文本较长可能会有一会延时,所以这里改成了多线程,不然可能误解为没有反应 - 然后连续重复提交。

winform.play.oncommand = function(id,event){
    if(winform.txt.text==""){
        win.msgbox("文本不能为空")
        return ; 
    }
    if(appkey == "" or accessKeyId == ""){
        win.msgbox("请至少配置 accessKeyId,token")
        return ; 
    } 
     
    //获取对应voice的配置信息
    var voice = aliyun.tts.getVoiceByName(winform.voice.text) 
    winform.play.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250'}
    wmpOcx.url = win.invoke(
        function(winform,param,appkey,accessKeyId,accessKeySecret){
            import aliyun.tts; 
            var client  = aliyun.tts.client(appkey,accessKeyId,accessKeySecret)
            return client.getSpeechUrl(param)
        },winform,{
                text = winform.txt.text;
                voice = voice[[2]];
                pitch_rate = winform.pitchRate.progressPos;
                speech_rate = winform.speechRate.progressPos;
                volume  = winform.volume.progressPos;
                format = "wav";
        },appkey,accessKeyId,accessKeySecret
    ); 
}


2020-04-08   #5

这个非常不错,最近做linux也遇到了,需要将文本转为语言后通过hdmi输出音频到喇叭播放,还没找到比较好的C语言代码转换方案。

登录后方可回帖

登 录
信息栏
本站永久域名:HtmLayout.Cn
纯私人站,当笔记本用的,学到哪写到哪,目前正在学aardio+halcon机器视觉.
Sciter中文在线文档Sciter在线学习文档
本 站 主 站:Stm32cube中文网
Aardio 官方站:Aardio官方
Aardio最新功能:Aardio官方更新日志
aardio在线手册Aardio在线手册
C大Aardio论坛:Aar爱好者论坛
简码教程网:简码编程
AARDIO语言QQ群:70517368
赞助商:才仁机械
下载站:非凡软件站
Loading...