短视频文案提取原来如此简单
结婚十年游西湖
过春风十里,尽荠麦青青。春天总是让人舒坦,而今年的三月,也因为与媳妇结婚十年,显得格外不同。两人奢侈的请了一天假,瞒着孩子,重游西湖,去寻找13年前的冰棍店(给当时还是同事的她买了最贵的一个雪糕-8元),去寻找13年前卖红豆钥匙扣的大爷(她送我了一个绿豆的钥匙扣-纯洁的友谊),去坐一坐13年前坐过的那条凳子... 正当沉浸在浪漫的回忆中时,一个许久未曾联系的好友,突然来了消息,相约安吉大竹海。以前觉得老家的房前屋后都是竹子已是清幽之至,原来漫山遍野的竹子亦是别有一番风味。一群娃在草地上尽情的踢球,瞧,娃玩得多开心。
好奇之心从此起
闲聊之余,好友展示一个叫轻抖的小程序,里面一个视频文案提取的功能吸引了我。随便复制一条抖音,快手之类的短视频的链接就可以提取视频的文案。好奇心驱使之下,开始了一段探索之路。没曾想,开始容易,放下难。
经过一番简单的思索确定了大概流程,分三个步骤:
提取视频文件 -> 音频分离 -> 音频转文字。而后就兴高采烈的编码起来了。很快现实就给当头一棒,应验了那句伴随30年的四川老谚语:说得轻巧,是根灯草(四川话念来就有味儿了)。第一个难点就是:如何根据分享的链接下载视频,还能支持各种通用平台。尝试好一会儿后放弃了,毕竟”志不在此“嘛,后来偶然发现有不少这样的平台,专门提供根据url 下载视频的接口,就直接用三方的接口了。
有了视频链接,下载到本地就简单了(然则,简单的地方可能会有坑),直接上代码,返回文件生成的InputStream。
public InputStream run(MediaDownloadReq req) { //根据url获取视频流 InputStream videoInputStream = null; try { String newName = "video-"+String.format("%s-%s", System.currentTimeMillis(), UUID.randomUUID().toString())+"."+req.getTargetFileSuffix(); File folder = new File(tempPath); if (!folder.exists()) { folder.mkdir(); } File file = HttpUtil.downloadFileFromUrl(req.getUrl(), new File(tempPath +"" + newName+""), new StreamProgress() { // 开始下载 @Override public void start() { log.info("Start download file..."); } // 每隔 10% 记录一次日志 @Override public void progress(long total) { //log.info("Download file progress: {} ", total); } @Override public void finish() { log.info("Download file success!"); } }); videoInputStream = new FileInputStream(file); file.delete(); } catch (Exception e) { log.error("获取视频流失败 req ={}", req.getUrl(), e); throw new BusinessException(ErrorCodeEnum.DOWNLOAD_VIDEO_ERROR.code(), "获取视频流失败"); } return videoInputStream; }
然后使用javacv 分离音频,这个没什么特别的地方, 通过FFmpegFrameRecorder 搜集分离的音频。也直接上代码。
public ExtractAudioRes run(ExtractAudioReq req) throws Exception { long current = System.currentTimeMillis(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); //音频记录器,extractAudio:表示文件路径,2:表示两声道 FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputStream, 2); recorder.setAudioOption("crf", "0"); recorder.setAudioQuality(0); //比特率 recorder.setAudioBitrate(256000); //采样率 //recorder.setSampleRate(16000); recorder.setSampleRate(8000); recorder.setFormat(req.getAudioFormat()); //音频编解码 recorder.setAudioCodec(avcodec.AV_CODEC_ID_PCM_S16LE); //开始记录 recorder.start(); //读取视频信息 FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(req.getVideoInputStream()); grabber.setSampleRate(8000); //FFmpegLogCallback.set(); 调试日志 // 设置采集器构造超时时间(单位微秒,1秒=1000000微秒) grabber.setOption("stimeout", String.valueOf(TimeUnit.MINUTES.toMicros(30L))); grabber.start(); recorder.setAudioChannels(grabber.getAudioChannels()); Frame f; Long audioTime = grabber.getLengthInTime() / 1000/ 1000; current = System.currentTimeMillis(); //获取音频样本,并且用recorder记录 while ((f = grabber.grabSamples()) != null) { recorder.record(f); } grabber.stop(); recorder.close(); ExtractAudioRes extractAudioRes = new ExtractAudioRes(outputStream.toByteArray(), audioTime, outputStream.size() /1024); extractAudioRes.setFormat(req.getAudioFormat()); return extractAudioRes; }
黎明前的暴风雨
写到这里时,我以为胜利就如东方红霞之下呼之欲出的红日,已然无限接近,测试一个用例完美,二个用例完美,正当准备进行一个语音转文字的阶段时,最后一个单测失败。为此,开始了一轮旷日持久的调试路。
1, http下载保存文件-解析失败- avformat_find_stream_info() error : Could not find stream information;
2.浏览器保存文件也失败;
3, 迅雷下载解析也失败;
...
我已经开始怀疑三方接口返回的视频编码有问题了;当抖音保存文件解析成功时,更加印证了我的怀疑。但是使用微信小程序 saveVideoToPhotosAlbum 保存的文件居然可以解析成功...我开始怀疑自己了。于是各种参数开始胡乱一通调整。失败了无数次后,有了一个大胆的想法,我下载的你不能解析,那javaCV你自己下载的你总能解析了吧。 果然如此。上面的代码就修改了一行。
//FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(req.getVideoInputStream()); // 直接传url FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(req.getUrl());
接下来就是根据提取的音频文件,调用腾讯云的ars 接口。之前使用Openai 的接口实现内部财务机器人时,有写过通过语音输入转文字的接口,直接拿过来放上就ok了。 一句话接口调用如下,如果是超过一分钟的,调用长语音接口就可以了。(注:一句话接口同步返回,长语音是异步回调)
/** * @param audioRecognitionReq * @description: 语音转文字 * @author: jijunjian * @date: 11/21/23 09:48 * @param: [bytes] * @return: java.lang.String */ @Override public String run(AudioRecognitionReq audioRecognitionReq) { log.info("一句话语音语音转文字开始"); AsrClient client = new AsrClient(cred, ""); SentenceRecognitionRequest req = new SentenceRecognitionRequest(); req.setSourceType(1L); req.setVoiceFormat(audioRecognitionReq.getFormat()); req.setEngSerViceType("16k_zh"); String base64Encrypted = BaseEncoding.base64().encode(audioRecognitionReq.getBytes()); req.setData(base64Encrypted); req.setDataLen(Integer.valueOf(audioRecognitionReq.getBytes().length).longValue()); String text = ""; try { SentenceRecognitionResponse resp = client.SentenceRecognition(req); log.info("语音转文字结果:{}", JSONUtil.toJsonStr(resp)); text = resp.getResult(); if (Strings.isNotBlank(text)){ return text; } return "无内容"; } catch (TencentCloudSDKException e) { log.error("语音转文字失败:{}",e); throw new BusinessException(AUDIO_RECOGNIZE_ERROR.code(), "语音转文字异常,请重试"); } }
长语音转文本也差不多。代码如下
/** * @param audioRecognitionReq * @description: 语音转文字 * @author: jijunjian * @date: 11/21/23 09:48 * @param: [bytes] * @return: java.lang.String */ @Override public String run(AudioRecognitionReq audioRecognitionReq) { log.info("极速语音转文字开始"); Credential credential = Credential.builder().secretId(AppConstant.Tencent.asrSecretId).secretKey(AppConstant.Tencent.asrSecretKey).build(); String text = ""; try { FlashRecognizer recognizer = SpeechClient.newFlashRecognizer(AppConstant.Tencent.arsAppId, credential); byte[] data = null; if (audioRecognitionReq.getBytes() != null){ data = audioRecognitionReq.getBytes(); }else { //根据文件路径获取识别语音数据 以后再实现 } //传入识别语音数据同步获取结果 FlashRecognitionRequest recognitionRequest = FlashRecognitionRequest.initialize(); recognitionRequest.setEngineType("16k_zh"); recognitionRequest.setFirstChannelOnly(1); recognitionRequest.setVoiceFormat(audioRecognitionReq.getFormat()); recognitionRequest.setSpeakerDiarization(0); recognitionRequest.setFilterDirty(0); recognitionRequest.setFilterModal(0); recognitionRequest.setFilterPunc(0); recognitionRequest.setConvertNumMode(1); recognitionRequest.setWordInfo(1); FlashRecognitionResponse response = recognizer.recognize(recognitionRequest, data); if (SuccessCode.equals(response.getCode())){ text = response.getFlashResult().get(0).getText(); return text; } log.info("极速语音转文字失败:{}", JSONUtil.toJsonStr(response)); throw new BusinessException(AUDIO_RECOGNIZE_ERROR.code(), "极速语音转换失败,请重试"); } catch (Exception e) { log.error("语音转文字失败:{}",e); throw new BusinessException(AUDIO_RECOGNIZE_ERROR.code(), "极速语音转文字异常,请重试"); } } /** * @param req * @description: filter 根据参数选 * @author: jijunjian * @date: 3/3/24 18:54 * @param: * @return: */ @Override public Boolean filter(AudioRecognitionReq req) { if (req.getAudioTime() == null || req.getAudioTime() >= AppConstant.Tencent.Max_Audio_Len || req.getAudioSize() >= AppConstant.Tencent.Max_Audio_Size){ return true; } return false; }
没有结束的结束
一开始只是凭着对文案提取好奇,没曾想,一写就停不下来;后端实现了,如果没有一个前端的呈现又感觉略有遗憾;于是又让媳妇帮忙搞了一套UI;又搞了一个简单的小程序...一顿操作之后,终于上线了。有兴趣的同学可以扫码体验下。
小程序名称 :文字转语音实用工具;
小程序二维码 : 不让放二维码,来一个链接快速体验

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Serverless 成本再优化:Knative 支持抢占式实例
作者:元毅、向先 Knative 是一款云原生、跨平台的开源 Serverless 应用编排框架,而抢占式实例是公有云中性价比较高的资源。Knative 与抢占式实例的结合可以进一步降低用户资源使用成本。本文介绍如何在 Knative 中使用抢占式实例。 背景信息 抢占式实例是一种低成本竞价型实例,您可以对阿里云当前闲置的资源出价,获得资源后运行容器,直到出价低于市场价格或者库存不足等原因导致资源回收。 Knative 是一款基于 Kubernetes 的开源 Serverless 应用编排框架,其目标是制定云原生、跨平台的 Serverless 应用编排标准。Knative 主要功能包括基于请求的自动弹性、缩容到 0、多版本管理、基于流量的灰度发布、函数部署以及事件驱动等。 Knative 与抢占实例结合 Knative 中提供了 Serverless 工作负载:Knative Service,可以基于请求自动扩缩容 Pod,使用抢占式实例的话只需要配置相应的 Pod 注解即可。而在 Virtual Node 根据 Pod 的注解自动申请对应的 ECI 资源规格。当前 Virtual...
- 下一篇
数栈+AI:数栈V6.2创新发布,让数据开发更智能
近日,以“Data+AI,构建新质生产力”为主题的袋鼠云春季发布会圆满落幕,大会带来了一系列“+AI”的数字化产品与最新行业沉淀,旨在将数据与AI紧密结合,打破传统的生产力边界,赋能企业实现更高质量、更高效率的数字化发展。会上,袋鼠云数栈产品负责人偷天带来了融合AI能力的数栈V6.2的全新发布,这不仅仅是一个简单的产品的升级,更代表着袋鼠云对未来的一次大胆预见。 数栈V6.2:数据价值最大化 数据驱动时代,数据已经成为了企业的生命线。如何高效地管理和利用这些数据,是每个企业都在探索的问题。数栈V6.2的发布,正是为了解决这一挑战,帮助企业在数据的海洋中乘风破浪。 全新发布的数栈V6.2,其核心理念是Data+AI,新版本不仅提供了大数据平台的基础功能,更通过与AI技术的深度融合,为企业提供数据智能分析与应用。这意味着企业可以通过数栈平台,实现行业内容体系的集成、灵活便捷的数据洞察、极速分析引擎的计算以及数据安全的全方位管控。除此之外袋鼠云产品解决方案还覆盖了轻量化、数据治理、信创等多个方面。这一切的设计,都是为了帮助企业优化计算存储成本、提升数据质量、推动标准规范,最终实现数据价值的最...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6