Ver a proveniência

慧播调整音频列表目标数据

private_deployment
谈文友 há 1 ano
ascendente
cometimento
fb66cb0a30
11 ficheiros alterados com 321 adições e 25 eliminações
  1. +131
    -0
      suimangCApi/src/main/java/com/iformall/controller/UserLiveController.java
  2. +16
    -5
      suimangService/src/main/java/com/iformall/domain/po/WxCVoiceTable.java
  3. +3
    -1
      suimangService/src/main/java/com/iformall/domain/po/sm/PreviewParam.java
  4. +1
    -1
      suimangService/src/main/java/com/iformall/mapper/WxCVideoMapper.java
  5. +2
    -0
      suimangService/src/main/java/com/iformall/service/WxCVideoService.java
  6. +7
    -0
      suimangService/src/main/java/com/iformall/service/WxCVoiceService.java
  7. +6
    -0
      suimangService/src/main/java/com/iformall/service/impl/WxCVideoServiceImpl.java
  8. +123
    -6
      suimangService/src/main/java/com/iformall/service/impl/WxCVoiceServiceImpl.java
  9. +1
    -0
      suimangService/src/main/java/com/iformall/sm/AiPreviewParam.java
  10. +6
    -0
      suimangService/src/main/resources/mapper/WxCVideoMapper.xml
  11. +25
    -12
      suimangService/src/main/resources/mapper/WxCVoiceMapper.xml

+ 131
- 0
suimangCApi/src/main/java/com/iformall/controller/UserLiveController.java Ver ficheiro

@@ -2,18 +2,29 @@ package com.iformall.controller;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.code.kaptcha.Producer;
import com.iformall.annotation.AuthIgnore;
import com.iformall.common.ErrorCode;
import com.iformall.common.ResultData;
import com.iformall.domain.po.WxCUserBasicInfo;
import com.iformall.domain.po.WxCVideoTable;
import com.iformall.domain.po.sm.PhotoSpeakVideo;
import com.iformall.domain.po.sm.PreviewParam;
import com.iformall.domain.po.sm.VoiceInfo;
import com.iformall.domain.vo.WxCLiveLoginVo;
import com.iformall.enums.EnumVideoStatus;
import com.iformall.exception.MallinkException;
import com.iformall.service.*;
import com.iformall.service.sm.VoiceInfoService;
import com.iformall.sm.AiPreviewParam;
import com.iformall.utils.Constant;
import com.iformall.utils.PasswordHelper;
import com.iformall.utils.RedisCacheUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@@ -26,9 +37,16 @@ import springfox.documentation.spring.web.json.Json;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

@RestController
@@ -59,6 +77,8 @@ public class UserLiveController extends BaseController {

@Autowired
private WxCUserAuthorityService wxCUserAuthorityService;
@Autowired
private VoiceInfoService voiceInfoService;


@AuthIgnore
@@ -251,4 +271,115 @@ public class UserLiveController extends BaseController {

return wxCUserAuthorityService.getAuthor(id, code, type, resourceId);
}


@AuthIgnore
@ApiOperation(value = "tts", notes = "{\"username\",\"string\",\"gen_txt\",\"string\",\"voice_id\",\"string\",\"voice_style\",\"string\",\"speed\",\"int\"}")
@PostMapping("/audiotts")
public Map<String,Object> voicePreview(@RequestBody Map<String, String> params) {
logger.debug("[" + getIpAddr() + "] UserLiveController::voicePreview");

String phone = params.get("username");
WxCUserBasicInfo infoByPhone = wxCUserBasicInfoService.findInfoByPhone(getTenantInfo(), phone);
Long id = infoByPhone.getId();
AiPreviewParam param = new AiPreviewParam();
if (params.get("voice_id") == null) {
Map<String,Object> status = new HashMap<>();
status.put("code",ErrorCode.SYS_SERVER_ERROR.getCode());
status.put("msg","音色ID不能为空");
return status;
// return new ResultData(ErrorCode.SYS_SERVER_ERROR.getCode(), "音色ID不能为空");
}
if (StringUtils.isBlank(params.get("gen_txt"))) {
Map<String,Object> status = new HashMap<>();
status.put("code",ErrorCode.SYS_SERVER_ERROR.getCode());
status.put("msg","需要生成的文字不能为空");
return status;
//return new ResultData(ErrorCode.SYS_SERVER_ERROR.getCode(), "需要生成的文字不能为空");
}if (Integer.parseInt(params.get("speed"))==-1){
param.setSpeed(0);
}
param.setSpeed(Integer.parseInt(params.get("speed")));
param.setVoice_id(params.get("voice_id"));
param.setVoice_style(params.get("voice_style"));
param.setGen_txt(params.get("gen_txt"));
Map<String, Object> resultMap = wxCVoiceService.voicePreview(param);
Map<String,Object> info = new HashMap<>();
info.put("log_id",id);
info.put("server_type","audio tts");
resultMap.put("info",info);

return resultMap;
}


@AuthIgnore
@SneakyThrows
@ApiOperation(value = "tts", notes = "{\"username\",\"string\",\"gen_txt\",\"string\",\"voice_id\",\"string\",\"voice_style\",\"string\",\"speed\",\"int\"}")
@GetMapping("exportVideo")
public void exportVideo(@RequestBody Map<String, String> params , HttpServletRequest request, HttpServletResponse response) {
logger.debug("[" + getIpAddr() + "] UserLiveController::exportVideo");

response.reset();

File tmpFile = null;
OutputStream outputStream = null;
WxCUserBasicInfo infoByPhone = wxCUserBasicInfoService.findInfoByPhone(getTenantInfo(), params.get("username"));
Long id = infoByPhone.getId();
try {
outputStream = response.getOutputStream();
} catch (IOException e) {
throw new MallinkException(ErrorCode.SYS_SERVER_ERROR);
}

try{
if(id == null){
throw new MallinkException(ErrorCode.SYS_PARAMETER_NOT_NULL);
}
WxCVideoTable videoTable = wxCVideoService.selectOne(id,Long.parseLong(params.get("resource_id")));



//获取响应的输出流
InputStream inputStream = new URL(videoTable.getDemo()).openStream();


response.setHeader("Content-Disposition", "attachment; filename="
+ URLEncoder.encode(videoTable.getId()+".mp4", "UTF-8"));
//解决编码问题
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type","application/octet-stream");

byte[] cache = new byte[1024 * 300];
int flag;
while ((flag = inputStream.read(cache))!=-1){
outputStream.write(cache, 0, flag);
}
// }

}catch(MallinkException e){
ResultData resultData = new ResultData(e.getErrorCode(), e.getMessage());
//解决编码问题
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type","application/json");
outputStream.write(JSONObject.toJSONString(resultData).getBytes(StandardCharsets.UTF_8));

}finally {
if (tmpFile != null) {
tmpFile.delete();
}
outputStream.flush();
outputStream.close();
}
}
@AuthIgnore
@ApiOperation("选择声音选择风格")
@GetMapping("/chooseType")
@ApiImplicitParam(name = "id", value = "id", dataType = "Long", paramType = "query", required = true)
public ResultData chooseType(Long id) {
logger.debug("[" + getIpAddr() + "] MouldPatchController::chooseType");
List<VoiceInfo> voiceInfos = wxCVoiceService.chooseType(id);
System.out.println("voiceInfos = " + voiceInfos);
return new ResultData(wxCVoiceService.chooseType(id));
}
}

+ 16
- 5
suimangService/src/main/java/com/iformall/domain/po/WxCVoiceTable.java Ver ficheiro

@@ -5,7 +5,10 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.ToString;

@TableName(value = "wx_c_voice")
import java.util.ArrayList;
import java.util.List;

@TableName(value = "voice_info")
@Data
@ToString(callSuper = true)
public class WxCVoiceTable {
@@ -23,17 +26,25 @@ public class WxCVoiceTable {
@io.swagger.annotations.ApiModelProperty(value = "地区显示名称", name = "local_display_name")
private String localDisPlayName;

@io.swagger.annotations.ApiModelProperty(value = "声纹ID", name = "voice_id")
@io.swagger.annotations.ApiModelProperty(value = "声纹ID", name = "id")
private int voiceId;
@io.swagger.annotations.ApiModelProperty(value = "声纹名称(用于调微软接口)", name = "voice_name")
@io.swagger.annotations.ApiModelProperty(value = "声纹名称(用于调微软接口)", name = "local_name")
private String voiceName;
@io.swagger.annotations.ApiModelProperty(value = "声纹展示名称", name = "voice_display_name")
@io.swagger.annotations.ApiModelProperty(value = "声纹展示名称", name = "display_name")
private String voiceDisplayName;
@io.swagger.annotations.ApiModelProperty(value = "性别(男1 女0)", name = "gender")
@io.swagger.annotations.ApiModelProperty(value = "性别(男1 女0)", name = "sex")
private int gender;
@io.swagger.annotations.ApiModelProperty(value = "声纹类型 共享:0 定制:1", name = "class")
private int voiceType;

@io.swagger.annotations.ApiModelProperty(value = "声纹列表", name = "mould_sm_id")
private String MouldSmId;

@io.swagger.annotations.ApiModelProperty(value = "声纹列表", name = "style_list")
private String styleList ;



@io.swagger.annotations.ApiModelProperty(value = "声纹风格名称(用于微软接口)", name = "style_name")
private String styleName;
@io.swagger.annotations.ApiModelProperty(value = "声纹风格展示名称", name = "style_display_name")


+ 3
- 1
suimangService/src/main/java/com/iformall/domain/po/sm/PreviewParam.java Ver ficheiro

@@ -8,4 +8,6 @@ public class PreviewParam {
private String voiceId;
private String voiceStyle;
private String gender;
}
private int speed;

}

+ 1
- 1
suimangService/src/main/java/com/iformall/mapper/WxCVideoMapper.java Ver ficheiro

@@ -12,5 +12,5 @@ public interface WxCVideoMapper extends CommonMapper<WxCVideoTable, String> {
List<WxCVideoTable> getById(Long id);


WxCVideoTable selectOne(Long id, long resource_id);
}

+ 2
- 0
suimangService/src/main/java/com/iformall/service/WxCVideoService.java Ver ficheiro

@@ -9,4 +9,6 @@ public interface WxCVideoService {


Map<String,Object> getById(Long id);

WxCVideoTable selectOne(Long id, long resource_id);
}

+ 7
- 0
suimangService/src/main/java/com/iformall/service/WxCVoiceService.java Ver ficheiro

@@ -1,9 +1,16 @@
package com.iformall.service;

import com.iformall.common.ResultData;
import com.iformall.domain.po.sm.VoiceInfo;
import com.iformall.sm.AiPreviewParam;

import java.util.List;
import java.util.Map;

public interface WxCVoiceService {
Map<String,Object> getById(Long id);

List<VoiceInfo> chooseType(Long id);

Map<String,Object> voicePreview(AiPreviewParam aiPreviewParam);
}

+ 6
- 0
suimangService/src/main/java/com/iformall/service/impl/WxCVideoServiceImpl.java Ver ficheiro

@@ -71,6 +71,12 @@ public class WxCVideoServiceImpl implements WxCVideoService,IExcelExportServer {
return avatarVos;
}

@Override
public WxCVideoTable selectOne(Long id, long resource_id) {

return wxCVideoMapper.selectOne(id,resource_id);
}

@Override
public List<Object> selectListForExcelExport(Object queryParams, int page) {
WxCUserBasicInfo basicInfo = (WxCUserBasicInfo) queryParams;


+ 123
- 6
suimangService/src/main/java/com/iformall/service/impl/WxCVoiceServiceImpl.java Ver ficheiro

@@ -1,12 +1,26 @@
package com.iformall.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.iformall.common.ErrorCode;
import com.iformall.common.ResultData;
import com.iformall.domain.po.WxCVideoTable;
import com.iformall.domain.po.WxCVoiceTable;
import com.iformall.domain.po.sm.VoiceInfo;
import com.iformall.domain.vo.VoiceInfoVo;
import com.iformall.enums.EnumClassType;
import com.iformall.enums.EnumSpeakType;
import com.iformall.mapper.VoiceMapper;
import com.iformall.mapper.WxCVoiceMapper;
import com.iformall.service.WxCVoiceService;
import com.iformall.sm.AiPreviewParam;
import com.iformall.sm.AiPreviewResult;
import com.iformall.sm.AiVideoHelper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.*;

@@ -16,6 +30,13 @@ public class WxCVoiceServiceImpl implements WxCVoiceService {
@Autowired
WxCVoiceMapper wxCVoiceMapper;

@Autowired
VoiceMapper voiceMapper;

private final static String str = "\uD83D\uDD57";
private final static String url = "https://suimang.oss-accelerate.aliyuncs.com/builtin/tts_all_sample/";
private final static String end = ".wav";


@Override
public Map<String,Object> getById(Long id) {
@@ -28,7 +49,7 @@ public class WxCVoiceServiceImpl implements WxCVoiceService {
for (WxCVoiceTable wxCVoiceTable : resultList) {
HashMap<String, Object> audio = new HashMap<>();
HashMap<String, Object> voice = new HashMap<>();
HashMap<String, Object> style = new HashMap<>();
data.put("username", wxCVoiceTable.getUserName());
data.put("current_time", new Date(System.currentTimeMillis()));
audioList.add(audio);
@@ -43,17 +64,42 @@ public class WxCVoiceServiceImpl implements WxCVoiceService {
voice.put("voiceid", wxCVoiceTable.getVoiceId());
voice.put("expire_time", wxCVoiceTable.getExpireTime());
voice.put("voicename", wxCVoiceTable.getVoiceName());
voice.put("voicetype", wxCVoiceTable.getVoiceType());
voice.put("voicetype", EnumClassType.SHARE.getCode());
voice.put("displayname", wxCVoiceTable.getVoiceDisplayName());
voice.put("gender", wxCVoiceTable.getGender());
List styleList = new ArrayList();
styleList.add(style);


List<String> strings = JSON.parseArray(wxCVoiceTable.getStyleList(), String.class);
for (String y : strings) {
// List list = new ArrayList<>();
HashMap<String, Object> style = new HashMap<>();
style.put("stylename", y);
style.put("displayname", EnumSpeakType.getEnum(y).getMessage());
style.put("styledemo", url + wxCVoiceTable.getMouldSmId() + "_" + y + end);
//list.add(style);
styleList.add(style);
}



// for (String s : Arrays.asList(wxCVoiceTable.getStyleList())) {
//// VoiceInfoVo infoVo = new VoiceInfoVo();
//// infoVo.setName(EnumSpeakType.getEnum(s).getMessage());
//// infoVo.setEngName(s);
//// infoVo.setUrl(url + wxCVoiceTable.getMouldSmId() + "_" + s + end);
// style.put("stylename", s);
// style.put("displayname", EnumSpeakType.getEnum(s).getMessage());
// style.put("styledemo", url + wxCVoiceTable.getMouldSmId() + "_" + s + end);
// styleList.add(style);
// }
// styleList.add(style);
voice.put("styleList", styleList);


style.put("stylename", wxCVoiceTable.getStyleName());
style.put("displayname", wxCVoiceTable.getStyleDisplayName());
style.put("styledemo", wxCVoiceTable.getStyleDemo());
// style.put("stylename", wxCVoiceTable.getStyleName());
// style.put("displayname", wxCVoiceTable.getStyleDisplayName());
// style.put("styledemo", wxCVoiceTable.getStyleDemo());


}
@@ -71,4 +117,75 @@ public class WxCVoiceServiceImpl implements WxCVoiceService {
result.put("status",status);
return result;
}

@Override
public Map<String,Object> voicePreview(AiPreviewParam aiPreviewParam) {
VoiceInfo voiceInfo = voiceMapper.selectOne(new LambdaQueryWrapper<VoiceInfo>().eq(VoiceInfo::getIsDel, 0).eq(VoiceInfo::getId, aiPreviewParam.getVoice_id()));
if (ObjectUtils.isEmpty(voiceInfo)){
Map<String,Object> status = new HashMap<>();
status.put("code",ErrorCode.SYS_SERVER_ERROR.getCode());
status.put("msg","声音信息不存在");
return status;
// return new ResultData(ErrorCode.SYS_SERVER_ERROR.getCode(), "声音信息不存在");
}
AiPreviewParam param = new AiPreviewParam();
param.setGen_txt(aiPreviewParam.getGen_txt().replaceAll(str,"[*]"));
param.setVoice_id(voiceInfo.getMouldSmId());
param.setVoice_style(StringUtils.isBlank(aiPreviewParam.getVoice_style()) ? EnumSpeakType.default_0.getMessage() : aiPreviewParam.getVoice_style());
param.setGender(voiceInfo.getSex() == 1 ? "male" : "female");
AiPreviewResult result = AiVideoHelper.voicePreview(param);
// if (result.isSuccess()){
// Map<String,Object> status = new HashMap<>();
// status.put("data",new ResultData(result.getTime()));
//
// return status;
// //return new ResultData(result.getTime());
// }
Map<String,Object> resultMap = new HashMap<>();
Map<String,Object> data = new HashMap<>();
data.put("ttsurl",result.getUrl());

Map<String,Object> status = new HashMap<>();
status.put("code",1000);
status.put("msg","success");
resultMap.put("data",data);
resultMap.put("status",status);
return resultMap;
//return new ResultData(result.getCode(), result.getMsgInfo(result.getCode(),result.getMsg()));
}

@Override
public List<VoiceInfo> chooseType(Long id) {
List<VoiceInfo> voiceInfos = voiceMapper.selectList(
new LambdaQueryWrapper<VoiceInfo>().eq(VoiceInfo::getLanguageId, id).orderByAsc(VoiceInfo::getAgeType).orderByAsc(VoiceInfo::getDisplayName));
voiceInfos.forEach(x -> {
if (StringUtils.isNotEmpty(x.getStyleList())) {
List<String> strings = JSON.parseArray(x.getStyleList(), String.class);
List<VoiceInfoVo> list = new ArrayList<>();
strings.forEach(y->{
VoiceInfoVo infoVo = new VoiceInfoVo();
infoVo.setName(EnumSpeakType.getEnum(y).getMessage());
infoVo.setEngName(y);
infoVo.setUrl(url + x.getMouldSmId() + "_" + y + end);
list.add(infoVo);
});

VoiceInfoVo infoVo = new VoiceInfoVo();
infoVo.setName(EnumSpeakType.getEnum(0).getMessage());
infoVo.setUrl(url + x.getMouldSmId() + "_" + "default" + end);
infoVo.setEngName("default");
list.add(infoVo);
x.setStyle(list);
} else {
List<VoiceInfoVo> list = Lists.newArrayList();
VoiceInfoVo infoVo = new VoiceInfoVo();
infoVo.setName(EnumSpeakType.getEnum(0).getMessage());
infoVo.setEngName("default");
infoVo.setUrl(url + x.getMouldSmId() + "_" + "default" + end);
list.add(infoVo);
x.setStyle(list);
}
});
return voiceInfos;
}
}

+ 1
- 0
suimangService/src/main/java/com/iformall/sm/AiPreviewParam.java Ver ficheiro

@@ -8,5 +8,6 @@ public class AiPreviewParam {
private String voice_id;
private String voice_style;
private String gender;
private int speed;

}

+ 6
- 0
suimangService/src/main/resources/mapper/WxCVideoMapper.xml Ver ficheiro

@@ -26,6 +26,12 @@ left join wx_c_avatar wcav on wcav.id =wca.resource_id
where wca.resource_id
in (select wca.resource_id from wx_c_author wca WHERE wca.user_id =#{id} AND wca.type = 0 )
</select>
<select id="selectOne" resultType="com.iformall.domain.po.WxCVideoTable">
SELECT wca.user_name,expire_time,wcav.id,wcav.name,wcav.image,wcav.model,wcav.preinfo,wcav.demo
from wx_c_author wca
left join wx_c_avatar wcav on wcav.id =wca.resource_id
where wca.resource_id =#{resource_id} and wca.user_id = #{id}
</select>


</mapper>

+ 25
- 12
suimangService/src/main/resources/mapper/WxCVoiceMapper.xml Ver ficheiro

@@ -5,13 +5,18 @@
<resultMap id="BaseResultMap" type="com.iformall.domain.po.WxCVoiceTable">

<result column="user_name" jdbcType="VARCHAR" property="userName"/>
<result column="local_name" jdbcType="VARCHAR" property="localelName"/>
<result column="local_display_name" jdbcType="VARCHAR" property="localDisPlayName"/>
<result column="voice_id" jdbcType="INTEGER" property="voiceId"/>
<result column="name" jdbcType="VARCHAR" property="localelName"/>
<result column="chinese_name" jdbcType="VARCHAR" property="localDisPlayName"/>
<result column="id" jdbcType="INTEGER" property="voiceId"/>
<result column="class" jdbcType="INTEGER" property="voiceType"/>
<result column="voice_name" jdbcType="VARCHAR" property="voiceName"/>
<result column="voice_display_name" jdbcType="VARCHAR" property="voiceDisplayName"/>
<result column="gender" jdbcType="INTEGER" property="gender"/>
<result column="local_name" jdbcType="VARCHAR" property="voiceName"/>
<result column="display_name" jdbcType="VARCHAR" property="voiceDisplayName"/>
<result column="sex" jdbcType="INTEGER" property="gender"/>

<result column="style_list" jdbcType="VARCHAR" property="styleList"/>

<result column="mould_sm_id" jdbcType="VARCHAR" property="MouldSmId"/>

<result column="style_name" jdbcType="VARCHAR" property="styleName"/>
<result column="style_display_name" jdbcType="VARCHAR" property="styleDisplayName"/>
<result column="style_demo" jdbcType="VARCHAR" property="styleDemo"/>
@@ -19,13 +24,21 @@
</resultMap>


<!-- <select id="getById" resultMap="BaseResultMap">-->
<!--select wca.user_name,wca.resource_id,wca.expire_time,wcv.id, wcv.localel_id ,wcv.id ,wcv.voice_name,wcv.voice_display_name ,wcv.gender , wcv.class , wcvl.id,wcvl.local_name,wcvl.local_display_name,wcvs.id,wcvs.voice_id,wcvs.style_name,wcvs.style_display_name,wcvs.style_demo-->
<!--from wx_c_author wca-->
<!--INNER JOIN wx_c_voice wcv on wca.resource_id = wcv.id-->
<!--and wca.type = 1-->
<!--join wx_c_voice_local wcvl on wcv.localel_id = wcvl.id-->
<!--join wx_c_voice_style wcvs on wcv.id=wcvs.voice_id where wca.user_id = #{id}-->
<!-- </select> -->
<select id="getById" resultMap="BaseResultMap">
select wca.user_name,wca.resource_id,wca.expire_time,wcv.id, wcv.localel_id ,wcv.id ,wcv.voice_name,wcv.voice_display_name ,wcv.gender , wcv.class , wcvl.id,wcvl.local_name,wcvl.local_display_name,wcvs.id,wcvs.voice_id,wcvs.style_name,wcvs.style_display_name,wcvs.style_demo
from wx_c_author wca
INNER JOIN wx_c_voice wcv on wca.resource_id = wcv.id
and wca.type = 1
join wx_c_voice_local wcvl on wcv.localel_id = wcvl.id
join wx_c_voice_style wcvs on wcv.id=wcvs.voice_id where wca.user_id = #{id}
select wca.user_name,wca.resource_id,wca.expire_time,vi.id,vi.language_id,vi.sex,vi.display_name,vi.local_name,vi.style_list,
vl.name,vl.chinese_name
FROM wx_c_author wca
join voice_info vi on wca.resource_id = vi.id
join voice_language vl on vi.language_id =vl.id
WHERE wca.user_id=#{id}
</select>




Carregando…
Cancelar
Guardar