基于Swt、ffmpeg、jacob、vlc、SApi、h2技术编写简单的旁白生成器
前一段时间尝试录制了几集3D编程方面的视频教程,我发现录制时最大的障碍是让脑中的思考、手上的操作和嘴里的解说保持同步,一旦三个“线程”中有一个出错,就必须停下来重新录制出错的部分,同时一心三用也会极大的增加精力消耗,减少有效录制时间。为了解决这一问题、降低视频教程的编写门槛,我尝试编写了一个将预先设定的文本插入视频中作为旁白的小工具,尝试将思考、操作和解说三个线程分解到三个时间片中运行。
一、简介:
前一段时间尝试录制了几集3D编程方面的视频教程,我发现录制时最大的障碍是让脑中的思考、手上的操作和嘴里的解说保持同步,一旦三个“线程”中有一个出错,就必须停下来重新录制出错的部分,同时一心三用也会极大的增加精力消耗,减少有效录制时间。为了解决这一问题、降低视频教程的编写门槛,我尝试编写了一个将预先设定的文本插入视频中作为旁白的小工具,尝试将思考、操作和解说三个线程分解到三个时间片中运行。
因为以前没接触过完整的java窗口程序编写,这个小工具的代码非常简陋生硬,程序名为Saltedfish(中文翻译咸鱼,本文中简称为sf)。
二、使用流程:
sf运行包可以从https://pan.baidu.com/s/1i69LBs5下载
1、程序针对安装了64位java1.7的完整版win7系统编写,参考网络上的资料,使用Java语言开发。
2、导出的运行目录如下:
其中h2是用来存储项目配置的微型数据库,sf9_lib是MyEclipse导出的依赖jar包,ffmpeg是视频处理工具,jacob是java调用com组件工具,mylist是ffmpeg合并音频流时使用的配置文件,sf9.jar是可执行jar文件,startup是启动脚本,vlc是视频播放器。
3、初次使用时需要安装vlc视频播放器,安装目录没有限制。
4、启动h2数据库:
执行h2-2017-06-10\h2\bin\h2w.bat脚本启动数据库,数据库启动成功后会使用默认浏览器弹出一个数据库控制台窗口:
使用JDBC URL:jdbc:h2:tcp://127.0.0.1/../../saltedfish访问已经建立的saltedfish数据库,用户名密码均为saltedfish。点击连接后可以看到项目表和文件表:
可以看到已经建立了“test”和“test2”两个测试项目,限于时间,sf程序里并没有做删除项目的功能,如果需要删除项目或文件可以直接在数据库中操作。
h2数据库启动后会在后台运行,关闭控制台不会影响数据库运行,可以在系统窗口的右下角看到h2数据库的小图标。
5、执行startup脚本启动sf9.jar
startup脚本内容:
java -Dfile.encoding=utf-8 -Xms1024m -Xmx1024m -jar sf9.jar
使用系统默认java,以utf-8编码执行jar包,为了防止内存不够,给java堆分配了1GB内存。
6、sf页面样式如下:
左上区域用来显示正在处理的视频片段,右上区域分为三个选项卡,最右侧的选项卡用来对项目进行操作,中间的选项卡对项目中的某一个文件进行操作,左边的选项卡是根据建议添加的对文件的另一种处理方式,但里面还没有做任何内容。
点击项目操作选项卡,在列表里选择一个项目并打开后,可以看到项目所包含的文件以水平列表的形式显示在下面,您也可以添加新的项目或者在项目里添加新的文件(目前只支持avi格式):
列表中还显示了每个文件的文件名和以毫秒数表示的文件长度,点击“导出目标文件”按钮可以把列表中的文件合并成一个视频导出(因为时间限制,这里使用的是最简单的文件合并方法,只支持编码完全相同的视频合并)
7、在列表中选择一个文件,点击“处理这一段”按钮,可以对这个文件进行操作:
类似普通视频播放器可以对视频文件进行播放、暂停,拖动滑块可以调整播放进度。
8、点击读文本按钮,选择要插入的旁白文本:
点击播放,程序在播放视频的同时也会一句接一句的朗读旁白,在某一时刻点击某一句旁白所在的按钮,会重新定位这一句旁白的位置,通过这种方式调整每一句旁白的发音时机(假设视频的长度总比旁白的长度长)
9、播放完毕或者点击停止后可以对旁白进行导出:
点击“srt”按钮时以srt字幕文件的方式导出旁白文字(限于时间,字幕以默认的utf-8编码导出,但是实验发现大部分视频播放器不支持这种编码的字幕,需要用户自己用文本工具将字幕的编码转为ANSI),点击“wav”按钮时以wav音频文件的方式导出旁白朗读音频和原视频音频的叠加音频,点击”合并”按钮则可以把叠加的wav替换到avi里(限于时间,只是导出了一个avi文件,如需要替换请自己替换文件或者修改数据库)
10、限于时间,只对动态定位旁白语句的部分使用了多线程,最后的导出部分没有使用多线程,这意味着点击导出按钮之后到导出完成之前sf不会响应用户的任何操作。
三、技术选型与设计流程:
1、选择java作为操作文件和调度各种工具的语言。
2、选择前台页面绘制方法:
Java程序的前台绘制有两种思路,一是用html*css*js网页作为前台,通过访问服务器方式(Ajax,Websocket等)调用后台的java程序工作;二是直接使用java语言编写桌面应用程序。显然第一种方式在设计前台页面时有更多的自由,但是考虑到附带产生的前后台沟通成本和用户部署成本以及思维切换成本,选择使用java直接编写桌面程序。
常用的Java桌面程序框架有三种:Swing、Awt、Swt,参考网络上的评价决定使用Swt框架,在实际使用中发现Swt具有以下缺点:
a、无法像html一样方便的选取元素,除非在程序中用专用的变量保存对象,否则再想找到就很难了。
b、不能像css一样方便的批量定义和继承ui样式,需要对每一个元素单独设置样式。
c、只有创建窗口的主线程可以修改ui的状态,其他线程想要修改ui状态(比如播放进度条的变化),需要用特殊的语法通知主线程在主线程认为合适的时候进行操作,这一点比JavaScript差距不可以道里计。
(也许这些缺点是因为作者对Swt掌握的还不够深入,但同样的时间用来学习html技术能够产生更好的效果)
3、对文字转语音工具进行选择:
常见的文字转语音方式有两种,一是通过http等协议调用百度、讯飞等“语音云”,将语音云生成的朗读结果返回给用户;二是在本地安装语音生成程序。考虑到离线操作的可能性,使用本地语音生成;考虑到安装的方便性,使用Window操作系统自带的语音朗读工具(SAPI)。
4、思考程序所必须的主干流程,针对每个流程画出页面草图。
5、根据确定的流程设计相应的文件管理办法
参考一些大型商业软件设计了两个版本,发现都需要花费不短的时间开发专门的文件管理模块,限于时间使用一个微型数据库h2Database对项目的结构进行保存。
6、根据确定的数据流在网上查询每个节点的相关知识,先编写用于实验的小例程,然后把实验完毕的小例程整合到主程序中。
四、代码结构介绍
程序代码可以在https://github.com/ljzc002/Saltedfish下载
工程结构如下:
1、程序的入口main方法在Win_sf类中,这个类继承自Swt的窗口类Shell(值得注意的是Shell类默认不可被继承,所以需要在Win_sf的构造函数中添加一行“checkSubclass();”,并且为它定义一个空方法)。
其中 :
initGui() 方法设定了整个Swt窗口的布局,并在布局的同时设定了按钮点击和进度条拖动的事件监听;
InitAssats()方法初始化了vlc播放器与左上方容器的绑定,并且通过vlc播放器的回调函数修改进度条位置;
Win_sf类的最后是按钮点击事件的响应方法,在进行事件响应时,要特别注意响应方法是否由主线程执行,如果不是要使用syncExec(同步)或asyncExec(异步)方法将响应交给主线程执行,如果是,则要注意响应方法会不会引起主线程卡顿。
2、CompositeVideoSurface和SwtEmbeddedMediaPlayer是从github下载的两个类,它们实现了通过Swt调用vlc播放器的功能。vlc的Java官方示例是使用swing框架实现的,试验过程中我尝试了使用Swt调用Awt,再用Awt调用swing的方法,结果播放器有声音无图像。
3、Event_cb类里编写了两个通过Java反射设置按钮响应函数的方法,分别是不带参数和带参数的版本。
4、Util_File类里包含了文件处理和数据库操作的相关方法,主要是常规的JDBC实现和Java流操作。
5、Comp_sf类是显示在窗口下部水平列表里的小容器的类,这个笑容器包含了对应文件的一些信息,并且可以被选中
6、Btn_sf类是窗口右上区域显示每一列旁白文本的按钮类。
7、MSTTSSpeech1类是负责通过Jacob调用Windows TTS组件的类,这个类的功能相对完善,包括了对朗读线程的初始化、开始、停止、释放四个主要环节的控制,线程之间的关系如下:
在这里语音朗读由com线程进行,ThreadDemo线程只负责对旁白语句和com线程的调度,ThreadDemo进程的消亡并不会立即令com线程停止,com线程仍会完成它当前的任务或者在接到明确的关闭命令时停止;每次播放暂停,com线程和ThreadDemo线程都会被释放,继续播放时将建立新的com线程和ThreadDemo线程。
微软TTS组件的一个缺点是无法方便的生成“特定长度的沉默”,所以只好生成许多段语音,再把它们拼接起来
对音视频文件的剪切与合并也在这个类中进行,ffmpeg是一款功能强大的开源视频处理工具,这里使用JNI技术对其进行调用,使用ffmpeg时遇到的难点主要有两处:
一是ffmpeg似乎没有直接的方法能够把一个流偏移一定长度叠加到另一个流里,所以我只好使用一个迂回的方法:
这个方法会在工作目录下产生一些中间文件
二是在使用ffmpeg前一定要搞明白要操作的流的编码格式,然后选用合适的解/编码器进行解/编码,否则会导致经过处理后的片段无法合并到一起。
sf用到的部分ffmpeg命令如下:
1 #带编码剪切 2 ffmpeg -i 2017FFFHHH-HC720P国语中字.mp4 -vcodec h264 -t 360 -ss 0 ./temp4/fh2h264.avi 3 4 #提取第一个音频流 5 ffmpeg -y -i fh2.avi -map 0:a:0 tempa.wav 6 7 #提升音频流的音量和采样数、声道数 8 ffmpeg -y -i temptts.wav -af volume=10dB -ar 48000 -ac 2 tempttsl.wav 9 10 #截取音频流 11 ffmpeg -y -i tempa.wav -t 3 -ss 0 temp1.wav 12 13 ffmpeg -y -i tempa.wav -ss 3 temp2.wav 14 15 #叠加混音 16 ffmpeg -y -i temp1.wav -i tempttsl.wav -filter_complex amix=inputs=2:duration=longest:dropout_transition=0 temp1ttsl.wav 17 18 #这种是文件层面的整合,要求文件的格式必须完全相同 19 ffmpeg -y -i "concat:temp1ttsl.wav|temp2.wav" -c copy tempa.wav 20 #这个是流层面的整合 21 ffmpeg -y -f concat -safe 0 -i mylist.txt -c copy tempa.wav 22 #mylist.txt文件 23 #file temp1ttsl.wav 24 #file temp2.wav 25 26 #最终替换音频,使用编解码器时间更长文件更小,使用copy模式则相反 27 ffmpeg -y -i fh2.avi -i bbb.wav -vcodec h264 -map 0:v -map 1 fh2bbb2.avi
8、H2_db类负责新建或关闭与h2数据库的JDBC连接,这里选用了h2数据库的TCP连接方式,并且使用了相对路径进行连接。
9、BarUpdater类是在网上找到的一个进度条更新线程,sf并没有使用它。
编写完毕的程序可以使用MyEclipse导出为可执行的jar包,更近一步的,我们也可以使用exe4j工具把jar包打包成一个exe文件,您可以在我的github上找到一个exe4j配置文件来方便您的打包过程(打包时我把sfx.jar放在了sfx_lib文件夹里)
关于打包另一个需要注意的地方是:jar和exe4j导出的exe调用dll和exe的根路径相同,但可运行jar操作文件的根路径是jar所在的文件夹,而exe4j导出的exe的操作文件的根路径则是所使用的jdk的bin目录!
五、代码引用:
sf编写过程中在网络上进行了数百次搜索,其中引用代码较多的来源如下:
1、在编写SwtUI时学习了IBM developerWorks上的Swt教程:
https://www.ibm.com/developerworks/cn/opensource/os-jface2/index.html?ca=drs-
2、在编写h2数据库模块时参考了博客园·孤傲苍狼博客:
http://www.cnblogs.com/xdp-gacl/p/4171024.html
3、为了在Swt中使用vlc,引用了github上的caprica/vlcj-swt-demo项目,该项目基于GNU GENERAL PUBLIC LICENSE协议发布
https://github.com/caprica/vlcj-swt-demo
4、jacob工具基于GNU LESSER GENERAL PUBLIC LICENSE协议发布,我不确定只是调用这个工具是否需要继承其开源协议。
可能有一些代码引用被忽略了,如果您发现被忽略的引用代码,请联系我添加它们。
sf引用了两个使用不同GNU协议的开源工具,所以我也不知道sf应该以何种方式开源,如果还有GNU以外的可授权部分,则以MIT协议开源。
限于时间和水平,sf只实现了我所需要的最简单的功能,并且存在各种缺陷和bug,如果您需要更多的功能可以自己添加。