diff --git a/src/main/java/com/peanut/common/utils/BaiduVoicesUtils.java b/src/main/java/com/peanut/common/utils/BaiduVoicesUtils.java index 7ff66d5d..a4ff33f9 100644 --- a/src/main/java/com/peanut/common/utils/BaiduVoicesUtils.java +++ b/src/main/java/com/peanut/common/utils/BaiduVoicesUtils.java @@ -2,21 +2,101 @@ package com.peanut.common.utils; import java.io.*; +import java.math.BigDecimal; import java.net.HttpURLConnection; import java.net.URL; -import java.util.Arrays; -import java.util.UUID; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.*; + import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; +import com.jacob.activeX.ActiveXComponent; +import com.jacob.com.Dispatch; +import com.jacob.com.Variant; +import org.apache.commons.lang.StringUtils; +import org.bytedeco.javacv.FFmpegFrameGrabber; +import org.jaudiotagger.audio.AudioFileIO; +import org.jaudiotagger.audio.mp3.MP3AudioHeader; +import org.jaudiotagger.audio.mp3.MP3File; +import org.jaudiotagger.tag.FieldDataInvalidException; +import org.jaudiotagger.tag.TagException; import org.joda.time.DateTime; +import org.springframework.util.FileCopyUtils; public class BaiduVoicesUtils { // 填写申请百度语音申请的appkey 申请地址:百度AI开放平台 private final static String appKey = "WOKN473V1o1gL8WMsAkfJZIY"; private final static String secretKey = "CpgbKdB9aZu2esnq5lMdqRZG37Jn3a76"; + //http://aipe-speech.bj.bcebos.com/text_to_speech/2024-12-06/6752c65ee615a10001435c06/speech/0.null?authorization=bce-auth-v1%2FALTAKjI91nE52nvtDNRgFlUCVz%2F2024-12-06T09%3A41%3A51Z%2F259200%2F%2F08aed96bc0e5808a83c1a80ee3a925866a2a7ac5b017e29670fe7951f713bbf6 + + public static void main(String[] arg){ + try { + System.out.println(new Date().getTime()); +// File audioFile = new File("F:\\111.wav");// 创建一个文件对象 +// AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(audioFile);// 获音频文件格式 +// float sampleRate = fileFormat.getFormat().getSampleRate(); +// int samplesizeInBits = fileFormat.getFormat().getSampleSizeInBits(); +// int aaa = fileFormat.getByteLength(); +// System.out.println("sampleRate: " + sampleRate); +// System.out.println("samplesizeInBits: " + samplesizeInBits); +// System.out.println("aaa: " + aaa); + String text = "天地之道者 ,理也 ; 万物之始者 , 气也 ; 气之聚散者 , 象也; 物之终始者 ,数也 。为 医者尤需明理 ,理不明则法不清 , 法不清则 方药无凭 。 医之理者 ,病理也 , 然欲明病理 ,先知生理 ,故太湖无 中医基础理论课程而有生理 、病理 。生之理者 ,《 内经》 所谓人事 也 ,至于天人之学 , 尚需于太湖国学院求之 。经谓精光之论 , 大圣"; +// shortText("补充一点,桂枝的主要有效成分是挥发油,大概占桂枝 重量的0.7%左右,不到1%。桂枝挥发油有一个特点,它由呼 吸道排出,对呼吸道炎症有明显的抗炎、祛痰、止咳作用,所 以在麻黄汤里,桂枝既增强了麻黄的发汗作用,又增强了杏仁 的化痰止咳平喘作用。"); + JacobUtil.text("F:\\a.mp3",text,100,0); +// List urls = new ArrayList<>(); +// urls.add("https://ehh-private-01.oss-cn-beijing.aliyuncs.com/2024/12/09/f56a35760be042debd177dbaac5e4e2e.mp3"); +// urls.add("https://ehh-private-01.oss-cn-beijing.aliyuncs.com/2024/12/09/dc5ffa920a91485982bac19f788ce33b.mp3"); +// urls.add("https://ehh-private-01.oss-cn-beijing.aliyuncs.com/2024/12/09/dc5ffa920a91485982bac19f788ce33b.mp3"); +// urls.add("https://ehh-private-01.oss-cn-beijing.aliyuncs.com/2024/12/09/dc5ffa920a91485982bac19f788ce33b.mp3"); +// File file = File.createTempFile(UUID.randomUUID().toString(),".mp3",new File("..\\")); +// mergeVoices(urls); + + +// MP3File file = new MP3File("F:/a.mp3"); +// MP3AudioHeader audioHeader = (MP3AudioHeader)file.getAudioHeader(); +// int intLen = audioHeader.getTrackLength(); +// System.out.println(intLen); + + System.out.println(new Date().getTime()); + }catch (Exception e) { + e.printStackTrace(); + } + } + + + public static String mergeVoices(List urls){ + try { + File outFile = File.createTempFile(UUID.randomUUID().toString(),".mp3",new File("..\\")); + OutputStream os = new FileOutputStream(outFile) ; + for (String url : urls) { + HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + InputStream is = conn.getInputStream(); + byte[] b = new byte[128]; + int len = 0; + int index = 0; + while((len = is.read(b))!=-1){ + index++; + if(index==1){ + continue ; + } + os.write(b, 0, len) ; + } + is.close(); + } + String fileUrl = uploadFile(new FileInputStream(outFile),".mp3"); + outFile.delete(); + os.flush(); + os.close() ; + return fileUrl; + }catch (Exception e) { + e.printStackTrace(); + return ""; + } + } public static String shortText(String content){ @@ -34,7 +114,19 @@ public class BaiduVoicesUtils { String contentType = conn.getContentType(); if (contentType.contains("mp3")) { byte[] bytes = ConnUtil.getResponseBytes(conn); - String fileUrl = uploadFile(new ByteArrayInputStream(bytes),UUID.randomUUID().toString()+".mp3"); + String fileUrl = uploadFile(new ByteArrayInputStream(bytes),".mp3"); + if (StringUtils.isNotBlank(fileUrl)){ + //获取音频时长 + File file = File.createTempFile(UUID.randomUUID().toString(),".mp3",new File("..\\")); + FileCopyUtils.copy(bytes,file); + MP3File mp3File = new MP3File(file); + MP3AudioHeader audioHeader = (MP3AudioHeader)mp3File.getAudioHeader(); + double intLen = audioHeader.getPreciseTrackLength(); + BigDecimal bd = new BigDecimal(intLen); + bd = bd.setScale(4, BigDecimal.ROUND_HALF_UP); + file.delete(); + fileUrl+=","+bd.toString(); + } return fileUrl; } else { System.err.println("ERROR: content-type= " + contentType); @@ -48,52 +140,67 @@ public class BaiduVoicesUtils { } } //长文字查询 - public void queryVoice(String id) throws Exception { - URL url = new URL("https://aip.baidubce.com/rpc/2.0/tts/v1/query?access_token=" + getAccessToken()); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Accept", "application/json"); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setDoOutput(true); - OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream()); - JSONObject jsonObject = new JSONObject(); - jsonObject.put("task_ids", Arrays.asList(id)); - writer.write(jsonObject.toString()); - writer.flush(); - writer.close(); - String result = ConnUtil.getResponseString(conn); - JSONArray tasksInfo = (JSONArray)(JSONObject.parseObject(result).get("tasks_info")); - JSONObject i = (JSONObject)tasksInfo.get(0); - String taskStatus = i.get("task_status").toString(); - if ("Running".equals(taskStatus)){ - queryVoice(id); + public static String queryVoice(String id){ + try { + URL url = new URL("https://aip.baidubce.com/rpc/2.0/tts/v1/query?access_token=" + getAccessToken()); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Accept", "application/json"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setDoOutput(true); + OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream()); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("task_ids", Arrays.asList(id)); + writer.write(jsonObject.toString()); + writer.flush(); + writer.close(); + String result = ConnUtil.getResponseString(conn); + JSONArray tasksInfo = (JSONArray)(JSONObject.parseObject(result).get("tasks_info")); + JSONObject info = (JSONObject)tasksInfo.get(0); + String taskStatus = info.get("task_status").toString(); + if ("Success".equals(taskStatus)){ + JSONObject taskResult = (JSONObject)info.get("task_result"); + String speechUrl = taskResult.get("speech_url").toString(); + URL urll = new URL(speechUrl); + HttpURLConnection connn = (HttpURLConnection) urll.openConnection(); + String fileUrl = uploadFile(connn.getInputStream(),UUID.randomUUID().toString()+".mp3"); + return fileUrl; + } + return ""; + }catch (Exception e) { + e.printStackTrace(); + return ""; } - System.out.println("taskId:" + result); } //长文字转换 - public String excute(String content) throws Exception { - URL url = new URL("https://aip.baidubce.com/rpc/2.0/tts/v1/create?access_token=" + getAccessToken()); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Accept", "application/json"); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setDoOutput(true); - OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream()); - JSONObject jsonObject = new JSONObject(); - jsonObject.put("text",content); - jsonObject.put("voice",1);//基础音库:度小宇=1,度小美=0,度逍遥(基础)=3,度丫丫=4;精品音库:度逍遥(精品)=5003,度小鹿=5118,度博文=106,度小童=110,度小萌=111,度米朵=103,度小娇=5。默认为度小美 - jsonObject.put("lang", "zh");//固定值zh。语言选择,目前只有中英文混合模式,填写固定值zh - jsonObject.put("speed", 3);//取值0-15,默认为5中语速 - jsonObject.put("pitch", 5);//取值0-15,默认为5中语调 - jsonObject.put("volume", 8);//取值0-15,默认为5中音量(取值为0时为音量最小值,并非为无声) - jsonObject.put("enable_subtitle", 1);//取值范围0, 1, 2,默认为0。0表示不开启字幕,1表示开启句级别字幕,2表示开启词级别字幕 - writer.write(jsonObject.toString()); - writer.flush(); - writer.close(); - String result = ConnUtil.getResponseString(conn); - String taskId = JSONObject.parseObject(result).get("task_id").toString(); - System.out.println("taskId result json:" + taskId); - return taskId; + public static String longText(String content) { + try { + URL url = new URL("https://aip.baidubce.com/rpc/2.0/tts/v1/create?access_token=" + getAccessToken()); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Accept", "application/json"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setDoOutput(true); + OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream()); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("text",content); + jsonObject.put("voice",5003);//基础音库:度小宇=1,度小美=0,度逍遥(基础)=3,度丫丫=4;精品音库:度逍遥(精品)=5003,度小鹿=5118,度博文=106,度小童=110,度小萌=111,度米朵=103,度小娇=5。默认为度小美 + jsonObject.put("lang", "zh");//固定值zh。语言选择,目前只有中英文混合模式,填写固定值zh + jsonObject.put("speed", 4);//取值0-15,默认为5中语速 + jsonObject.put("pitch", 5);//取值0-15,默认为5中语调 + jsonObject.put("volume", 8);//取值0-15,默认为5中音量(取值为0时为音量最小值,并非为无声) + jsonObject.put("enable_subtitle", 1);//取值范围0, 1, 2,默认为0。0表示不开启字幕,1表示开启句级别字幕,2表示开启词级别字幕 + writer.write(jsonObject.toString()); + writer.flush(); + writer.close(); + String result = ConnUtil.getResponseString(conn); + String taskId = JSONObject.parseObject(result).get("task_id").toString(); + System.out.println("taskId:" + taskId); + return taskId; + }catch (Exception e) { + e.printStackTrace(); + return ""; + } } public static String getAccessToken() throws Exception { diff --git a/src/main/java/com/peanut/modules/book/controller/BookChapterContentController.java b/src/main/java/com/peanut/modules/book/controller/BookChapterContentController.java index 1d854c2a..6f27b3f7 100644 --- a/src/main/java/com/peanut/modules/book/controller/BookChapterContentController.java +++ b/src/main/java/com/peanut/modules/book/controller/BookChapterContentController.java @@ -1,7 +1,8 @@ package com.peanut.modules.book.controller; -import java.io.File; -import java.io.FileInputStream; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -10,14 +11,18 @@ import com.alibaba.druid.util.StringUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.peanut.common.utils.BaiduVoicesUtils; +import com.peanut.common.utils.ConnUtil; import com.peanut.modules.book.service.*; import com.peanut.modules.common.entity.*; import com.peanut.modules.oss.service.OssService; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; +import org.bytedeco.javacv.FFmpegFrameGrabber; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockMultipartFile; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.FileCopyUtils; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -65,24 +70,124 @@ public class BookChapterContentController { return R.ok().put("voices", voices); } - //书籍全部章节转成音频 - @RequestMapping("/bookToVoices") - public R bookToVoices(@RequestBody Map params){ - // 1、创建服务,创建线程池 + //按一句话转成短音频 + @RequestMapping("/bookToShortVoices") + public R bookToShortVoices(@RequestBody Map params){ ExecutorService service = Executors.newFixedThreadPool(5); List list = bookChapterContentService.list(new LambdaQueryWrapper() .eq(BookChapterContentEntity::getBookId,params.get("bookId"))); for (BookChapterContentEntity bcc:list){ if (!bcc.getContent().contains("https")&&!"".equals(bcc.getContent())){ service.execute(new Runnable() { - @Override - public void run() { + @Override + public void run() { + try { //调用百度语音合成 API String voices = BaiduVoicesUtils.shortText(bcc.getContent()); - bcc.setVoices(voices); + if (voices.contains(",")){ + bcc.setVoices(voices.split(",")[0]); + bcc.setVoicesSize(voices.split(",")[1]); + }else { + bcc.setVoices(voices); + } bookChapterContentService.updateById(bcc); + }catch (Exception e) { + e.printStackTrace(); } - }); + } + }); + } + } + return R.ok(); + } + + //章节下所有音频合并成一个音频 + @RequestMapping("/mergeVoices") + public R mergeVoices(@RequestBody Map params){ + List bccs = bookChapterContentService.list(new LambdaQueryWrapper() + .eq(BookChapterContentEntity::getBookChatperId,params.get("bookChapterId")) + .orderByAsc(BookChapterContentEntity::getNumber)); + List list = new ArrayList<>(); + for (BookChapterContentEntity bcc : bccs) { + if (!StringUtils.isEmpty(bcc.getVoices())){ + list.add(bcc.getVoices()); + } + } + String url = BaiduVoicesUtils.mergeVoices(list); + if (!StringUtils.isEmpty(url)){ + BookChapterEntity bc = bookChapterService.getById(params.get("bookChapterId").toString()); + bc.setVoices(url); + bookChapterService.updateById(bc); + return R.ok(); + }else { + return R.error(); + } + } + + //所有章节合并音频 + @RequestMapping("/mergeVoicesByBookId") + @Transactional + public R mergeVoicesByBookId(@RequestBody Map params){ + StringBuilder sb = new StringBuilder(); + List bcs = bookChapterService.list(new LambdaQueryWrapper() + .eq(BookChapterEntity::getBookId,params.get("bookId")) + .orderByAsc(BookChapterEntity::getNumber)); + for (BookChapterEntity bc:bcs) { + List bccs = bookChapterContentService.list(new LambdaQueryWrapper() + .eq(BookChapterContentEntity::getBookChatperId,bc.getId()) + .orderByAsc(BookChapterContentEntity::getNumber)); + List list = new ArrayList<>(); + for (BookChapterContentEntity bcc : bccs) { + if (!StringUtils.isEmpty(bcc.getVoices())){ + list.add(bcc.getVoices()); + } + } + String url = BaiduVoicesUtils.mergeVoices(list); + if (!StringUtils.isEmpty(url)){ + bc.setVoices(url); + bookChapterService.updateById(bc); + }else { + sb.append(bc.getChapter()+"-"+bc.getContent()+"错误"); + } + } + return R.ok().put("info",sb.toString()); + } + + //按章节转成长音频 + @RequestMapping("/bookToLongVoices") + public R bookToLongVoices(@RequestBody Map params){ + List bookChapters = bookChapterService.list(new LambdaQueryWrapper() + .eq(BookChapterEntity::getBookId,params.get("bookId"))); + for (BookChapterEntity bc:bookChapters){ + StringBuffer sb = new StringBuffer(); + List bookChapterContents = bookChapterContentService.list(new LambdaQueryWrapper() + .eq(BookChapterContentEntity::getBookChatperId,bc.getId())); + for (BookChapterContentEntity bcc:bookChapterContents){ + if (!bcc.getContent().contains("https")&&!"".equals(bcc.getContent())){ + sb.append(bcc.getContent()); + } + } + if (sb.length() > 0) { + //调用百度语音合成 API + String voices = BaiduVoicesUtils.longText(sb.toString()); + bc.setVoices(voices); + bookChapterService.updateById(bc); + } + } + return R.ok(); + } + + //获取长音频 + @RequestMapping("/queryBookVoices") + public R queryBookVoices(@RequestBody Map params){ + List bookChapters = bookChapterService.list(new LambdaQueryWrapper() + .eq(BookChapterEntity::getBookId,params.get("bookId"))); + for (BookChapterEntity bc:bookChapters){ + if (!StringUtils.isEmpty(bc.getVoices())&&!bc.getVoices().contains("http")) { + //调用百度语音合成 API + String voices = BaiduVoicesUtils.queryVoice(bc.getVoices()); + bc.setVoices(voices); + bookChapterService.updateById(bc); } } return R.ok(); diff --git a/src/main/java/com/peanut/modules/bookAbroad/controller/HomeController.java b/src/main/java/com/peanut/modules/bookAbroad/controller/HomeController.java index b8e4fddb..0c4d8b99 100644 --- a/src/main/java/com/peanut/modules/bookAbroad/controller/HomeController.java +++ b/src/main/java/com/peanut/modules/bookAbroad/controller/HomeController.java @@ -11,6 +11,7 @@ import com.peanut.modules.common.entity.*; import com.peanut.modules.master.service.BookAbroadToLableService; import com.peanut.modules.bookAbroad.service.BookAbroadLableService; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; import org.apache.http.client.utils.DateUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; @@ -187,14 +188,30 @@ public class HomeController { //章节内容 @RequestMapping("/getBookChapterContent") public R getBookChapterContent(@RequestBody Map params) { - Page contentPage = bookChapterContentService.page(new Page<>( - Long.parseLong(params.get("current").toString()), Long.parseLong(params.get("limit").toString())), - new LambdaQueryWrapper() + List contentPage = bookChapterContentService.list(new LambdaQueryWrapper() .eq(BookChapterContentEntity::getBookChatperId,params.get("chapterId")) .orderByAsc(BookChapterContentEntity::getNumber)); return R.ok().put("contentPage",contentPage); } + //听书时章节内容 + @RequestMapping("/getBookChapterContentListen") + public R getBookChapterContentListen(@RequestBody Map params) { + List bccs = bookChapterContentService.list(new LambdaQueryWrapper() + .eq(BookChapterContentEntity::getBookChatperId,params.get("chapterId")) + .notLike(BookChapterContentEntity::getContent,"http") + .orderByAsc(BookChapterContentEntity::getNumber)); + //给每段话加上在总音频开始的秒数 + double startInt = 0; + for (BookChapterContentEntity bcc : bccs) { + if (StringUtils.isNotBlank(bcc.getVoicesSize())){ + bcc.setVoicesStart((int) startInt); + startInt+=Double.parseDouble(bcc.getVoicesSize()); + } + } + return R.ok().put("bookChapterContents",bccs); + } + //已读\已买人数 @RequestMapping("/getBookReadCount") public R getBookReadCount(@RequestBody Map params){ diff --git a/src/main/java/com/peanut/modules/common/entity/BookChapterContentEntity.java b/src/main/java/com/peanut/modules/common/entity/BookChapterContentEntity.java index 04248e5b..fe1b06d9 100644 --- a/src/main/java/com/peanut/modules/common/entity/BookChapterContentEntity.java +++ b/src/main/java/com/peanut/modules/common/entity/BookChapterContentEntity.java @@ -44,6 +44,10 @@ public class BookChapterContentEntity implements Serializable { */ private String voices; + private String voicesSize; + @TableField(exist = false) + private int voicesStart; + private String otherContent;//如果内容是图片存放图片宽高 /** *