短视频文案提取的简单实现

过春风十里,尽荠麦青青。春天总是让人舒坦,而今年的三月,也因为与媳妇结婚十年,显得格外不同。两人奢侈的请了一天假,瞒着孩子,重游西湖,去寻找13年前的冰棍店(给当时还是同事的她买了最贵的一个雪糕-8元),去寻找13年前卖红豆钥匙扣的大爷(她送我了一个绿豆的钥匙扣-纯洁的友谊),去坐一坐13年前坐过的那条凳子... 正当沉浸在浪漫的回忆中时,一个许久未曾联系的好友,突然来了消息,相约安吉大竹海。以前觉得老家的房前屋后都是竹子已是清幽之至,原来漫山遍野的竹子亦是别有一番风味。一群娃在草地上尽情的踢球,瞧,娃玩得多开心。

过春风十里,尽荠麦青青。春天总是让人舒坦,而今年的三月,也因为与媳妇结婚十年,显得格外不同。两人奢侈的请了一天假,瞒着孩子,重游西湖,去寻找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() {// 开始下载@Overridepublic void start() {log.info("Start download file...");}// 每隔 10% 记录一次日志@Overridepublic void progress(long total) {//log.info("Download file progress: {} ", total);}@Overridepublic 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*/@Overridepublic 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*/@Overridepublic 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:*/@Overridepublic 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;又搞了一个简单的小程序...一顿操作之后,终于上线了。有兴趣的同学可以扫码体验下。

小程序名称 :智能配音实用工具;

小程序二维码:

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

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

相关文章

python超详细知识点汇总整理

1、注释以及编码格式的声明 单行注释:# (后面放上被注释的内容)多行注释:字符段落的上下加上三引号 举个例子: ‘’’ …‘’’编码格式的声明:#coding:utf-8 或者是 #codingutf-8 2、代码编写格式和一些琐碎说明 同…

机器学习入门:概念、步骤、分类与实践

博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的宝典!《IDEA开发秘籍》 — 提升你的IDEA技能!《100天精通鸿蒙》 …

【APP_TYC】数据采集案例天眼APP查_查壳脱壳反编译_③

是不是生活太艰难 还是活色生香 我们都遍体鳞伤 也慢慢坏了心肠 你得到你想要的吗 换来的是铁石心肠 可曾还有什么人 再让你幻想 🎵 朴树《清白之年》 查壳 工具介绍Frida-dexDump Frida-dexDump简介 Frida-dexDump是基于Frida的一个工具&…

layui laydate日期初始化的一些坑

layui laydate日期初始化的一些坑 背景坑一:利用class属性初始化时间控件失败坑二:后加载页面时间控件初始化失败坑三:结束时间需要默认追加23:59:59 背景 在日常开发中,总是会不可避免的用到日期插件,不同的日期插件…

【ENSP】交换机和路由器之间实现静态路由

1.概念 三层交换机只能在Vlanif逻辑口配置iP地址 路由器的每一个端口都是另外一个网段 2.实现方法 交换机允许对应vlan通行,配置vlanif的ip地址,做静态路由 路由器,进接口配置对应的ip,路由器和交换机相连的口,进入他的…

【实战】验证可执行文件可靠性| Windows 应急响应

0x01 简介 相信部分朋友已经看过我们的 《Windows 应急响应手册》了,我们这边也得到部分朋友的正向反馈,包括工具、方法等。 Windows 版的应急响应手册中常规安全检查部分第一版就包含了 30 多个检查项目,按照我们的风格,每个检…

备忘录删除了怎么恢复?解锁4个简单方法

误删除苹果备忘录是一个常见的问题,而且很容易导致我们遗失重要信息的情况。但是,如果您不幸误删了备忘录,也不必过分担心,因为有几种简单的方法可以帮助您恢复这些备忘录。备忘录删除了怎么恢复?在本文中,…

R语言使用dietaryindex包计算NHANES数据多种营养指数(2)

健康饮食指数 (HEI) 是评估一组食物是否符合美国人膳食指南 (DGA) 的指标。Dietindex包提供用户友好的简化方法,将饮食摄入数据标准化为基于指数的饮食模式,从而能够评估流行病学和临床研究中对这些模式的遵守情况,从而促进精准营养。 该软件…

2023年财报大揭秘:下一个倒闭的新势力呼之欲出

3月25日,零跑汽车公布了他们2023年的财报。财报数据显示,零跑亏损了42亿元。恰逢近段时间众多新势力车企皆公布了年报,而亏损也成了大家避不开的话题。那今天就让我们一起盘点一下各个车企的财报吧! 2023年财报大揭秘:…

Sip-6002D 双按键SIP对讲求助终端

Sip-6002D 双按键SIP对讲求助终端 一、描述 SV-6002TP是我司的一款壁挂式一键求助对讲终端,具有10/100M以太网接口,支持G.711、G.722音频解码,其接收SIP网络的音频数据,实时解码播放,还配置了麦克风输入和扬声器输出…

UI的设计

一、RGB888的显示 即红色,绿色,蓝色都为8位,即通常说的24位色。可以很好显示各种过渡颜色。从硬件上,R、G、B三基色的连接线各需要有8根,即24根数据线;软件上存储的数据量也需要24位,即3个字节&…

led驱动恒流电源0-10v可控硅调光电源控制芯片SM2318EA

LED驱动恒流电源、0-10V可控硅调光电源以及控制芯片是LED照明系统中重要的组成部分。它们共同协作,实现对LED灯的亮度、颜色等特性的精确控制。 1. LED驱动恒流电源:由于LED的伏安特性是非线性的,且其亮度与电流直接相关,因此需要…

Keil MDK如何主题美化和代码美化

目录 概要 下载地址:Keil MDK主题美化和代码美化 保存插件 配置MDK 使用方法 MDK配色 概要 编写代码时,缩进和括号的使用对于代码的清晰度和可读性至关重要。手动调整这些格式细节不仅费时,还容易出错。幸运的是,有许多工具…

python如何获取word文档的总页数

最近在搞AI. 遇到了一个问题,就是要进行doc文档的解析。并且需要展示每个文档的总页数。 利用AI. 分别尝试了chatGPT, 文心一言, github copilot,Kimi 等工具,给出来的答案都不尽如人意。 给的最多的查询方式就是下面这种。 这个…

JVM篇详细分析

JVM总体图 程序计数器: 线程私有的,每个线程一份,内部保存字节码的行号,用于记录正在执行字节码指令的地址。(可通过javap -v XX.class命令查看) java堆: 线程共享的区域,用来保存对…

Codeforces Round #818 (Div. 2) A-C

人类智慧 A. 题意&#xff1a;求满足1<a,b<n且lcm(a,b)/gcd(a,b)<3的(a,b)的个数 转化 a/gcd*b*gcd<3 可以划归为1*2 1*1 2*1 3*1 1*3 则可以转变成一个统计倍数问题 #include<bits/stdc.h> using namespace std; using ll long long; using pii pair&…

电脑最高可以装多少内存?电脑内存怎么装?

大家好&#xff0c;我是来自兼容性之家的&#xff01; 通常我们的家用电脑主机有8到16GB的运行内存。 极少数高端用户会使用32至64GB内存。 比较高端的工作站的内存在128GB左右。 同时&#xff0c;家用电脑的硬盘容量约为1TB。 那么你有没有想过一台电脑可以拥有的最大内存量…

Spring Boot 工程开发常见问题解决方案,日常开发全覆盖

本文是 SpringBoot 开发的干货集中营&#xff0c;涵盖了日常开发中遇到的诸多问题&#xff0c;通篇着重讲解如何快速解决问题&#xff0c;部分重点问题会讲解原理&#xff0c;以及为什么要这样做。便于大家快速处理实践中经常遇到的小问题&#xff0c;既方便自己也方便他人&…

氮气柜常用的制作材质有哪些?

氮气柜主要用于存储对湿度敏感或需要在低氧环境中保存的精密部件、电子元器件、化学品、文物等&#xff0c;需要确保柜体的密闭性和内部环境的稳定&#xff0c;以防止氧化、受潮或变质。 常见的材质有冷轧钢板&#xff0c;冷轧钢板通过冷轧工艺使钢材组织更紧密&#xff0c;从而…

代码随想录算法训练营 Day31 贪心算法1

Day31 贪心算法1 理论基础 贪心算法的本质&#xff1a;找到每个阶段的局部最优&#xff0c;从而去推导全局最优 贪心的两个极端&#xff1a;要么觉得特别简单&#xff0c;要么觉得特别难 贪心无套路 不像二叉树、递归&#xff0c;有固定模式 贪心题目的思考方式 做题的时候…