对象存储服务调用
第一篇中,我们转换后的PCM文件,还是存储在本地文件系统中。接下来,我们需要基于百度云的对象存储BOS服务,将文件上传到云端:
首先,我们需要开通BOS服务,获取相关access-key,建立相关的bucket。
其次,参考官方API,引入相关maven依赖。
最后,完成本地文件上传到云端bucket,同时将相关日志记录到本地MySQL数据库。
开通服务
具体服务开通过程忽略,补充说明下,选择百度云是因为语音转录是免费的,BOS虽然收费,但非常便宜,从本项目情况看,总共320MB左右的文件,一共花费不到1元钱,简直白菜价了。
先是获取到相关key后,在properties中配置进去:
#百度云BOSthomas.bos.access-key-id=xxxthomas.bos.secret-access-key=xxthomas.bucket-name=xxx 依赖引入
具体引入的依赖是:
<groupId>com.baidubce</groupId><artifactId>bce-java-sdk</artifactId><version>0.10.105</version>
特别提示下,该依赖会连带引入很多第三方依赖,在通过maven-helper插件分析依赖时,发现很多依赖冲突的,例如log4j、commons-logging、slf4j-log4j12等,建议一并排除掉。
同时,因为本工程并未直接依赖com.google.guava,但在bce-java-sdk中,也存在该依赖冲突。参考的解决办法是:先在bce-java-sdk中排除com.google.guava依赖,同时单独再引入com.google.guava:
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>17.0</version></dependency> 文件上传程序编写
本项目中,我已将相关功能封装到了BosFileService中,主要是基于BosClient进行文件操作:
1、获取bucket下所有文件:
bosClient.listObjects(THOMAS_BUCKET_NAME).getContents();
2、基于文件key获取7天有效期的URL:
bosClient.generatePresignedUrl(THOMAS_BUCKET_NAME, objectKey, 7 * 24 * 60 * 60);
3、上传单个文件:
PutObjectResponse response = bosClient.putObject(THOMAS_BUCKET_NAME, key, filePath.toFile());
4、上传成功时,会返回eTag,将之记录到本地数据库:
fileUploadRepo.save(SpeechFileUploadInfo.builder().eTag(eTag).fileName(fileName).build());
5、批量上传目录下所有文件:
Files.list(rootPath).forEach(path -> { if (Files.isDirectory(path)) { //递归遍历目录 count.addAndGet(batchUploadFile(path)); } else { 语言 方法
8623
抖音创业网JkcpB
抖音电脑直播权限如何申请「官方公告」9877 2006/06/10 19:40:59
//上传该文件 count.getAndAdd(uploadFile(path)); }}); 录音转写服务调用
完成文件上传到云端BOS后,接下来基于百度云AI的语音识别(录音转写)服务,提交离线转写任务:
开通免费的语音转录服务,获取相关key。
基于restful api,提交转写任务。
查询转写任务结果,将转写成功的结果,保存到本地数据库。
首先,将ai应用相关key记录在properties文件中,同时也一并记录相关api的调用路径:
thomas.ai.api-key=xxxthomas.ai.secret-key=xxxthomas.ai.access-url=https://aip.baidubce.com/oauth/2.0/tokenthomas.ai.create-url=https://aip.baidubce.com/rpc/2.0/aasr/v1/createthomas.ai.query-url=https://aip.baidubce.com/rpc/2.0/aasr/v1/query
本项目将语音转录功能封装在 SpeechService服务中。
在调用任何功能之前,需要先基于上述apikey等,获取access token,同时也可以将token缓存起来:
@Cacheable(value = "thomas-ai-token")public Optional<String> getAccessToken() { Map<String, String> params = new HashMap<>(2); params.put("client_id", API_KEY); params.put("client_secret", SECRET_KEY); //token请求URL String requestUrl = ACCESS_TOKEN_URL + "?grant_type=client_credentials" + "&client_id={client_id}" + "&client_secret={client_secret}"; String jsonStr = restTemplate.getForObject(requestUrl, String.class, params); JSONObject jsonObject = JSON.parseObject(jsonStr); return Optional.ofNullable(jsonObject.getString("access_token"));}
为方便后续调用,封装了一个通用的doPost方法:
public Optional<ResponseEntity<String>> doPost(String url, boolean needToken, Map<String, Object> values) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); //将请求参数转换为json String requestJson = JSON.toJSONString(values); HttpEntity<String> request = new HttpEntity<>(requestJson, headers); StringBuilder postUrl = new StringBuilder(url); //需要追加token if (needToken) { Optional<String> opToken = getAccessToken(); if (opToken.isPresent()) { //token存在则追加token postUrl.append("?access_token=" + opToken.get()); } else { log.error("没有获取到ACCESS TOKEN", opToken); return Optional.empty(); } } return Optional.ofNullable(restTemplate.postForEntity(postUrl.toString(), request, String.class));}
基于录音文件URL,创建文本转写任务:
//调用模式参见 https://ai.baidu.com/ai-doc/SPEECH/ck5diijktMap<String, Object> values = new HashMap<>(4);values.put("speech_url", speechUrl);values.put("format", "pcm");values.put("pid", 1537);values.put("rate", 16000);return this.doPost(CREATE_URL, true, values);
提交任务后,API返回的是taskId,该id必须保存,因为后续需要基于该id查询转写结果:
//解析返回结果中的taskid,能解析到即代表提交成功String taskId = JSON.parseObject(responseEntity.get().getBody()).getString("task_id");
将解析得到的id,保存到数据库中(本项目是基于JPA来进行数据库操作):
SpeechTaskInfo taskInfo = SpeechTaskInfo.builder() .taskId(taskId) .taskStatus(SpeechTaskStatus.Running) .pcmKey(f.getKey()) .pcmUrl(url) .build();taskInfoRepo.save(taskInfo); 转录结果查询及存储
录音转写任务提交成功,最后一步就是等待离线任务运行完成,任务状态划分如下:
/** 转写中 */Running,/** 转写成功 */Success,/** 转写失败 */Failure
在SpeechService中,封装了updateTaskResults方法,实现对任务的查询,并将转写成功的记录,记录到数据库中:
首先,遍历数据库中所有 Running状态的任务
其次,将所有任务taskId拼接后,调用任务运行结果批量查询API。
最后,判断API结果,并记录转写任务明细到数据库。
批量查询转录结果的调用非常简单:
// 技术文档 https://ai.baidu.com/ai-doc/SPEECH/6k5dilahbMap<String, Object> values = new HashMap<>(1);values.put("task_ids", taskIds);return this.doPost(QUERY_URL, true, values);
处理API返回结果时,我们是采用的阿里巴巴的fastjson,实现将api返还的json对象,转换为java对象:
SpeechLogInfo logInfo = JSON.parseObject(responseEntity.get().getBody(), SpeechLogInfo.class);// 分析每个解析任务的运行状态logInfo.getTasks_info() .stream() .filter(infoBean -> infoBean.getTask_status().equals(SpeechTaskStatus.Success.name())) .forEach(infoBean -> { // 处理每个解析成功的任务 infoBean.getTask_result().getDetailed_result().forEach(r -> { // 遍历每个解析结果,并存储到数据库中 SpeechTaskResult result = SpeechTaskResult.builder() .taskId(infoBean.getTask_id()) .beginTime(r.getBegin_time() / 1000) .endTime(r.getEnd_time() / 1000) .words(String.join("", r.getRes())) .build(); taskResultRepo.save(result); }); // 更新任务为成功状态 Optional<SpeechTaskInfo> taskInfo = taskInfoRepo.findById(infoBean.getTask_id()); if (taskInfo.isPresent()) { SpeechTaskInfo info = taskInfo.get(); //设置为成功状态 info.setTaskStatus(SpeechTaskStatus.Success); //保存到数据库 taskInfoRepo.save(info); count.incrementAndGet(); } });
补充说明下,推荐在idea中安装GsonFormat插件,实现基于json格式字符串,快速创建java对象SpeechLogInfo。
到此,我们将完成了将PCM文件上传到云端,并实现调用录音转写服务,解析得到文本内容,如果相关问题或疑问,欢迎给我留言。最后一篇,我们将实现读取数据库的转录结果,导出为一个完整的word文档,方便阅读和分享。