openAI tts Java文本转语音完整前后端代码 html

Java后端代码

maven 仓库:

<!--openAI 请求工具-->
<dependency><groupId>com.unfbx</groupId><artifactId>chatgpt-java</artifactId><version>1.1.5</version>
</dependency>

maven 仓库官方 tts 使用案例:

@SneakyThrows
@Test
public void textToSpeed() {TextToSpeech textToSpeech = TextToSpeech.builder().model(TextToSpeech.Model.TTS_1.getName()).input("OpenAI官方Api的Java SDK,可以快速接入项目使用。目前支持OpenAI官方全部接口,同时支持Tokens计算。官方github地址:https://github.com/Grt1228/chatgpt-java。欢迎star。").voice(TtsVoice.NOVA.getName()).responseFormat(TtsFormat.MP3.getName()).build();java.io.File file = new java.io.File("G:\\test.mp3");ResponseBody responseBody = client.textToSpeech(textToSpeech);InputStream inputStream = responseBody.byteStream();//创建文件if (!file.exists()) {if (!file.getParentFile().exists())file.getParentFile().mkdir();try {file.createNewFile();} catch (IOException e) {e.printStackTrace();log.error("createNewFile IOException");}}OutputStream os = null;try {os = new BufferedOutputStream(new FileOutputStream(file));byte data[] = new byte[8192];int len;while ((len = inputStream.read(data, 0, 8192)) != -1) {os.write(data, 0, len);}} catch (IOException e) {e.printStackTrace();} finally {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}try {if (os != null) {os.close();}} catch (IOException e) {e.printStackTrace();}}
}

控制器 controller

@ApiOperation("文本转语音")
@GetMapping("/textToVoice")
public ResponseEntity<InputStreamResource> textToVoice(String text, HttpServletResponse response) {response.setContentType("application/octet-stream");return translationHistoryService.textToVoice(text);
}

业务 service

@Override
public ResponseEntity<InputStreamResource> textToVoice(String text) {String audioDownloadUrl = ObjectUtils.filterObjectNull(this.lambdaQuery().select(TranslationHistory::getAudioDownloadUrl).eq(TranslationHistory::getOriginalText, text).last(SqlConstants.LIMIT_1).one(), TranslationHistory.class).getAudioDownloadUrl();InputStream inputStream = chatGptService.textToSpeed(text,audioDownloadUrl);InputStreamResource resource = new InputStreamResource(inputStream);return ResponseEntity.ok().contentType(MediaType.APPLICATION_OCTET_STREAM).body(resource);
}
public InputStream textToSpeed(String text,String audioDownloadUrl) {// 1、查询缓存String redisKey = RedisKeyUtils.getTextToSpeed(text);byte[] audioData = RedisUtils.getByte(redisKey);if (audioData != null) {log.info("从缓存中返回音频流数据");return new ByteArrayInputStream(audioData);}// 2、调用 api 响应// 将生成的音频数据读取为字节数组InputStream inputStream = null;try {log.info("从 API 中返回音频流数据");inputStream = textToSpeedIs(text);audioData = inputStreamToByteArray(inputStream);// 将音频缓存到 Redis 中RedisUtils.setValue(redisKey, audioData,7L, TimeUnit.DAYS);byte[] finalAudioData = audioData;// 开辟线程存入 redispoolSend.send(()->{// 标记首次String cacheKey = RedisKeyUtils.getFirstTextToSpeed(text);RedisUtils.setValue(cacheKey, finalAudioData,2L, TimeUnit.DAYS);});} catch (IOException e) {log.error("openAI音频调用异常:",e  );throw new RuntimeException(e);} finally {// 平常的关闭流代码太难看了,写了工具类简洁多了,自己封装一个CloseableUtils.close(inputStream);}// 将文本和对应的音频数据缓存到 Redis 中RedisUtils.setValue(redisKey, audioData, 7L, TimeUnit.DAYS);inputStream = new ByteArrayInputStream(audioData);return inputStream;
}
private InputStream textToSpeedIs(String text) throws IOException {TextToSpeech textToSpeech = TextToSpeech.builder().model(TextToSpeech.Model.TTS_1.getName()).input(text).voice(TtsVoice.NOVA.getName()).responseFormat(TtsFormat.MP3.getName()).build();// (重点,这里的方法细节就不展示了,看官方案例就知道,在哪个基础上复制粘贴封装一下方法即可)ResponseBody responseBody = chatGptStreamRequest.textToSpeech(textToSpeech,openaiKeyService.getApiKeyStrList());InputStream inputStream = responseBody.byteStream();return inputStream;
}

将 inputStream 流转换成 byte[] 数组

public static byte[] inputStreamToByteArray(InputStream inputStream) throws IOException {ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();int bufferSize = 1024;byte[] buffer = new byte[bufferSize];int len;while ((len = inputStream.read(buffer)) != -1) {byteBuffer.write(buffer, 0, len);}byte[] bytes = byteBuffer.toByteArray();byteBuffer.close();inputStream.close(); // 关闭流以释放资源return bytes;
}

后端代码就是这样,哦对了,还有 redisTemplate 的配置也分享一下,因为要将音频 byte[] 存入缓存,所以单独给 byte[] 类型配置 redisTemplate 注入:

@Bean
public RedisTemplate<String, byte[]> byteRedisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, byte[]> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);template.setKeySerializer(new StringRedisSerializer());// 使用 ByteArrayRedisSerializer 来处理字节数据template.setValueSerializer(RedisSerializer.byteArray());template.afterPropertiesSet();return template;
}
public static void setValue(String key, byte[] value, Long expireTime, TimeUnit timeUnit) {redisByteTemplate.opsForValue().set(key, value, expireTime, timeUnit);
}
public static byte[] getByte(String key) {return redisByteTemplate.opsForValue().get(key);
}

前端代码

<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>音频流测试</title>
</head>
<body><h2>音频流播放测试</h2><!-- 音频播放组件,初始时不显示 --><audio id="audioPlayer" controls style="display: none;"></audio><button id="playAudio">播放音频</button><script>document.getElementById('playAudio').addEventListener('click', function() {// 音频流接口的 URLvar audioUrl = 'http://localhost:8822/client/translation/textToVoice?text=今天很开心1';// 使用 Fetch API 请求音频流fetch(audioUrl, {method: 'GET',headers: {// 添加请求头'token': 'v0Dbf55iGiH8uSfxwrlkvlt12qb57cnj'}}).then(function(response) {// 检查响应是否成功if (response.ok) {return response.blob();}throw new Error('网络响应错误');}).then(function(blob) {// 将 Blob 转换为 URL 并设置给 <audio> 元素var url = URL.createObjectURL(blob);var audioPlayer = document.getElementById('audioPlayer');audioPlayer.src = url;audioPlayer.style.display = 'block';audioPlayer.play();}).catch(function(error) {console.error('请求音频流失败:', error);});});</script>
</body>
</html>

过程中出现的后端异常

User
No converter for [class cn.hutool.core.io.resource.InputStreamResource] with preset Content-Type 'application/octet-stream'

将 class cn.hutool.core.io.resource.InputStreamResource 切换成 org.springframework.core.io.InputStreamResource 即可

启动调试

在这里插入图片描述
嗯,完美运行,下班收工

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

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

相关文章

浅析RED和EN 18031

文章目录 前言欧盟的法律文件什么是REDRED的发展EU 2022/30法规EU 2023/2444RED与EN 18031的关系 前言 提示&#xff1a;本文大致表述了欧盟的一些立法常识&#xff0c;RED的由来与发展&#xff0c;以及它跟EN 18031的关系 因为工作原因&#xff0c;最近稍微研究了一下欧盟即将…

微波炉定时器开关

微波炉火力调节开关及定时器开关内部结构 参考链接&#xff1a; 微波炉火力调节开关及定时器开关判断好坏小经验-百度经验 (baidu.com)https://jingyan.baidu.com/article/5d6edee2d175c399eadeecfd.html微波炉拆解图示&#xff0c;微波炉结构原理&#xff0c;轻松玩转微波炉维…

DataLoader批量读取数据

使用DataLoader的小例子&#xff0c;这里CustomDataset类的__getitem__方法需要返回tensor。 加载到DataLoader中之后&#xff0c;DataLoader会通过类似字典的方式读取CustomDataset中的数据&#xff0c;达到批量处理的效果。 import torch from torch.utils.data import Datas…

AI大模型探索之路-应用篇14:认识国产开源大模型GLM

目录 前言 一、国产主流大模型概览 1. 国内主流大模型清单 2. 主流大模型综合指数 3. 大语言模型评测榜单 二、GLM大模型介绍 三、GLM大模型发展历程 四、GLM家族之基座模型GLM-130B 五、GLM家族之ChatGLM3 六、GLM家族之WebGLM 七、GLM家族之CogVLM 1. CogVLM 2. …

IP地址和物理地址的理解

1. 引言 最近在学习计算机网络相关知识&#xff0c;很长时间都有个困惑&#xff0c;为什么TCP/IP协议栈中会有2中地址&#xff0c;即&#xff1a;IP地址和物理地址&#xff08;MAC地址&#xff09;为什么同时被使用&#xff1f;它们各自的作用是啥&#xff1f; 2.我的理解 在写…

春招冲刺百题计划|栈

Java基础复习 Java数组的声明与初始化Java ArrayListJava HashMapJava String 类Java LinkedListJava Deque继承LinkedListJava Set 第一题&#xff1a;有效的括号 很简单的题&#xff0c;从大一做到现在&#xff0c;就是复习一下语法。 class Solution {public boolean i…

系统架构最佳实践 -- 新能源汽车产业架构设计

随着环保意识的增强和能源结构的转型&#xff0c;新能源汽车产业正迅速崛起成为汽车行业的新宠。构建一个完善的新能源汽车产业架构对于推动产业发展、提升竞争力至关重要。本文将从设计原则、关键技术、产业生态等方面&#xff0c;探讨如何设计与实现新能源汽车产业架构。 新能…

LabVIEW专栏六、LabVIEW项目

一、梗概 项目&#xff1a;后缀是.lvproj&#xff0c;在实际开发的过程中&#xff0c;一般是要用LabVIEW中的项目来管理代码&#xff0c;也就是说相关的VI或者外部文件&#xff0c;都要放在项目中来管理。 在LabVIEW项目中&#xff0c;是一个互相依赖的整体&#xff0c;可以包…

Python List 列表基础知识

1、什么是列表&#xff1f; 在实际开发中&#xff0c;经常需要将一组&#xff08;不只一个&#xff09;数据存储起来&#xff0c;以便后边的代码使用。说到这里&#xff0c;一些读者可能听说过数组&#xff08;Array&#xff09;&#xff0c;它就可以把多个数据挨个存储到一起…

springboot针对thymeleaf的使用总结

1、遍历 <ul th:if"${not #lists.isEmpty(nav.articleTypeList)}"><li th:each"articleType,articleTypeStat:${nav.articleTypeList}"></li> </ul> 注释&#xff1a;其中${not #lists.isEmpty(list)}先判断list非空&#xff0c…

数据结构:哈密顿回路基础

什么是哈密顿回路&#xff1f; 哈密顿回路&#xff08;Hamiltonian Cycle&#xff09;是图论中的一个概念&#xff0c;指的是在一个图中通过图的每个顶点恰好一次且仅一次&#xff0c;并最终回到起始顶点的闭合回路。在一个哈密顿回路中&#xff0c;除了起始和结束的顶点必须是…

TongRds docker 镜像做成与迁移(by liuhui)

TongRds docker 镜像做成与迁移 一&#xff0c;使用 docker commit 命令制作 TongRds docker 镜 像 1.1 拉取基础镜像 centos 并运行该镜像 拉取镜像&#xff1a;docker pull ubuntu 镜像列表&#xff1a;docker images 运行镜像&#xff1a;docker run -itd --name myubuntu…

每日一题(L2-011):玩转二叉树--建树+层序遍历

与L2-006近乎相同&#xff0c;先建树&#xff0c;然后遍历 #include<bits/stdc.h> using namespace std; int in[35]; int pre[35]; typedef struct Tree{int num;Tree* left;Tree* right; }T;T * build(int in1,int in2,int pre1,int pre2){T * tnew T;t->numpre[pr…

西宁市初中生地会考报名照片尺寸要求及手机自拍方法

西宁市初中生地会考即将到来&#xff0c;对于参加考试的同学们来说&#xff0c;准备一张符合规格的报名照片是整个报名流程中不可或缺的一环。一张规范的证件照不仅展示了学生的精神面貌&#xff0c;同时也是顺利报名的重要条件之一。本文将详细介绍西宁市初中生地会考报名所需…

C#Task<T>应用详解

C# 中的 Task< T> 是一个非常强大的并发编程工具&#xff0c;它允许我们异步执行操作并返回一个结果。在这篇博客中&#xff0c;我们将详细介绍 Task< T> 的应用&#xff0c;包括它的基本概念、创建方式、等待和取消等操作&#xff0c;以及一些常见的使用场景。 基…

LLM长度外推理论与实践

定义&#xff1a; 长度外推&#xff0c;即免训练长度外推&#xff0c;就是不需要用长序列数据进行额外的训练&#xff0c;只用短序列语料对模型进行训练&#xff0c;就可以得到一个能够处理和预测长序列的模型&#xff0c;即“Train Short, Test Long”。 判断方法&#xff1…

【论文源码实战】轻量化MobileSAM,分割一切大模型出现,模型缩小60倍,速度提高40倍

前言 MobileSAM模型是在2023年发布的&#xff0c;其对之前的SAM分割一切大模型进行了轻量化的优化处理&#xff0c;模型整体体积缩小了60倍&#xff0c;运行速度提高40倍&#xff0c;但分割效果却依旧很好。 MobileSAM在使用方法上沿用了SAM模型的接口&#xff0c;因此可以与…

Java对象克隆-浅拷贝与深拷贝

目录 1、对象的克隆 1.1 对象的浅拷贝 1.2 对象深拷贝 1、对象的克隆 1.1 对象的浅拷贝 在实际编程过程中&#xff0c;我们常常要遇到这种情况&#xff1a;有一个对象A&#xff0c;在某一时刻A中已经包含了一些有效值&#xff0c;此时可能会需要一个和A完全相同新对象B&am…

单页面应用使用Nginx部署页面路由直接访问404问题

使用Nginx发布单页面应用时候&#xff0c;因为只有一个index.html页面可能导致直接访问具体页面路由报404错误&#xff0c;如&#xff1a; 直接访问http://www.test.com正常 访问http://www/test.com/system/list 则报404 需要修改nginx配置让其所有未找到资源都返回到index.h…

论婚恋相亲交友软件的市场前景和开发方案H5小程序APP源码

随着移动互联网的快速发展和社交需求的日益增长&#xff0c;婚恋相亲交友软件小程序成为了越来越多单身人士的选择。本文将从市场前景、使用人群、盈利模式以及竞品分析等多个角度&#xff0c;综合论述这一领域的现状与发展趋势。 一、市场前景 在快节奏的现代生活中&#xf…