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

news2025/1/12 9:45:05

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;
       // 开辟线程存入 redis
       poolSend.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() {
            // 音频流接口的 URL
            var 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.coloradmin.cn/o/1609499.html

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

相关文章

Odoo讨论+聊天模块:一体化内部协作平台,赋能高效沟通与业务流程协作

Odoo讨论聊天模块&#xff1a;一体化内部协作平台&#xff0c;赋能高效沟通与业务流程协作 Odoo 讨论模块是一个集成了即时通讯、文件共享、业务关联、权限控制等功能于一体的内部协作工具&#xff0c;允许用户通过跨模块的聊天窗口或通过专用的“讨论”面板互相发送消息、分享…

利用redis和fastapi实现本地与平台策略进行交互

redis简介: 在pandas一文有详细使用方法(一文教会pandas-CSDN博客)&#xff0c;具体可视化软件有redisstudio等。它是一个由 Salvatore Sanfilippo 写的 key-value 存储系统&#xff0c;是跨平台的非关系型数据库。 Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支…

Redis: 在项目中的应用

文章目录 一、Redis的共享session应用二、分布式缓存1、缓存2、缓存一致性问题解决方案&#xff08;缓存更新策略&#xff09;&#xff08;1&#xff09;作用&#xff08;2&#xff09;三种策略&#xff08;3&#xff09;主动更新策略&#xff08;数据库、缓存不一致解决方案&a…

DFS专题:电话号码的字母组合

DFS专题&#xff1a;电话号码的字母组合 题目链接: 17.电话号码的字母组合 参考题解&#xff1a; 代码随想录 题目描述 代码思路 将数字到字母的映射用字符串数组表示出来。然后利用回溯算法&#xff0c;解决n个for循环的问题&#xff0c;枚举出每一种符合要求的情况。 代…

C++友元命名空间数据类型内联重载缺省

1.友元 &#xff08;1&#xff09;全局函数作为友元 利用友元这个语法&#xff0c;我们可在函数里访问类里面定义的私有成员&#xff1b; 先定义了一个默认构造函数对类里面的成员进行初始化&#xff1b;goodgay是一个全局的函数&#xff0c;我们想要直接打印私有成员变量就…

订单到期关闭如何实现

在电商、支付等系统中&#xff0c;一般都是先创建订单&#xff08;支付单&#xff09;&#xff0c;再给用户一定的时间进行支付&#xff0c;如果没有按时支付的话&#xff0c;就需要把之前的订单&#xff08;支付单&#xff09;取消掉。这种类似的场景有很多&#xff0c;还有比…

仓库管理系统哪个好用?看仓储出入库系统如何智慧管理库存-亿发

企业中的仓库扮演着至关重要的角色&#xff0c;负责产品的存储和分发。作为物流的重要节点&#xff0c;仓库不仅需要确保产品安全存放&#xff0c;还要保证及时的配送服务。同时&#xff0c;仓库还需要完成货物信息记录、库存管理和品质管理等任务。因此&#xff0c;仓储管理的…

DFS专题:二叉树的最大深度

力扣题目&#xff1a;二叉树的最大深度 题目链接: 104.二叉树的最大深度 题目描述 代码思路 设置两个变量&#xff0c;max来记录最大值&#xff0c;sum来记录路径的节点数量。利用dfs对二叉树进行搜索&#xff0c;遇到节点&#xff0c;则sum1&#xff1b;遇到叶子节点&#…

数据恢复如何工作?电脑最佳数据恢复软件分析

数据丢失是数字世界不合适的部分&#xff0c;迟早会影响许多计算机用户。 如果您不小心 #delete 了重要的 #file&#xff0c;可能很难找回它并造成不必要的压力。 点击发推文 幸运的是&#xff0c;即使您没有备份已删除的文件&#xff0c;PC的数据恢复软件也可以帮助您恢复已…

昂科烧录器支持Nuvoton新唐科技的低功耗微控制器M482SIDAE

芯片烧录行业领导者-昂科技术近日发布最新的烧录软件更新及新增支持的芯片型号列表&#xff0c;其中Nuvoton新唐科技的低功耗微控制器M482SIDAE已经被昂科的通用烧录平台AP8000所支持。 M482SIDAE以Arm Cortex-M4F为核心&#xff0c;是带有DSP指令集的高效能低功耗微控制器。其…

WPF Extended.Wpf.Toolkit 加载界面

1、NuGet 中安装 Extended.Wpf.Toolkit 。 2、在MainWindow.xaml中添加xmlns:tk"http://schemas.xceed.com/wpf/xaml/toolkit" 。 MainWindow.xaml 代码如下。 <Window x:Class"WPF_Extended_Wpf_Toolkit_Loading.MainWindow" xmlns"ht…

apipost、postman等工具上传图片测试flask、fastapi的文件api接口

参考&#xff1a;https://blog.csdn.net/qq_15821487/article/details/119354129 https://www.cnblogs.com/wyxjava/p/16076176.html 选择from-data&#xff0c;下拉选择file上传文件发送即可

【动态规划 区间dp 位运算】3117. 划分数组得到最小的值之和

本文涉及知识点 动态规划 区间dp 位运算 LeetCode3117. 划分数组得到最小的值之和 给你两个数组 nums 和 andValues&#xff0c;长度分别为 n 和 m。 数组的 值 等于该数组的 最后一个 元素。 你需要将 nums 划分为 m 个 不相交的连续 子数组&#xff0c;对于第 ith 个子数组…

vscode设置conda默认python环境,简单有效

本地conda 可能安装了各种环境&#xff0c;默认的vscode总是base环境&#xff0c;这时你想要在vscode调试python代码&#xff0c;使用默认的环境没有安装对应的包就会遇到报错解决这个问题的方法很简单ctrlshiftp 调出命令面板 再输入 select interpreter , 选择 python 选择解…

在Spring Boot中使用POI完成一个excel报表导入数据到MySQL的功能

最近看了自己玩过的很多项目&#xff0c;忽然发现有一个在实际开发中我们经常用到的功能&#xff0c;但是我没有正儿八经的玩过这个功能&#xff0c;那就是在Spring Boot中实现一个excel报表的导入导出功能&#xff0c;这篇博客&#xff0c;主要是围绕excel报表数据导入进行&am…

一例Mozi僵尸网络的挖矿蠕虫分析(workminer)

概述 这是一个Linux平台的挖矿蠕虫&#xff0c;使用了go和C混合编译而成&#xff0c;主要通过爆破SSH口令进行传播&#xff0c;属于Mozi僵尸网络。其中GO代码负责SSH相关的爆破传播&#xff0c;以及对Config的处理&#xff0c;C代码则负责处理加入Mozi P2P网络&#xff0c;拉取…

c++11 标准模板(STL)本地化库 - 平面类别(std::collate) - 定义字典序比较和字符串的散列(二)

本地化库 本地环境设施包含字符分类和字符串校对、数值、货币及日期/时间格式化和分析&#xff0c;以及消息取得的国际化支持。本地环境设置控制流 I/O 、正则表达式库和 C 标准库的其他组件的行为。 平面类别 定义字典序比较和字符串的散列 std::collate 类 std::collate 封…

Redis的Stream 和 实现队列的方式【List、SortedSet、发布订阅、Stream、Java】

Redis队列与Stream、Redis 6多线程详解 Redis队列与StreamStream总述常用操作命令生产端消费端单消费者消费组消息消费 Redis队列几种实现的总结基于List的 LPUSHBRPOP 的实现基于Sorted-Set的实现PUB/SUB&#xff0c;订阅/发布模式基于Stream类型的实现与Java的集成 消息队列问…

MySQL高级(索引-性能分析-profile)

show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪去了。通过 have_profiling参数&#xff0c;能够看到当前MySQL 是否支持 profile 操作&#xff1a; select have_profiling 默认 profiling 是关闭的 select profiling; 可以通过 set 语句在 session / global 级…

【python】flask操作数据库工具SQLAlchemy,详细用法和应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…