我经常用 ffmpeg 中的 ffplay 播放视频。Ffplay 是个简单工具,目的只是辅助展示 ffmpeg 的功能,没有特意为用户友好做优化。每次播放的需求类似,所以我总用类似的命令行,这不算问题。但是,没有进度条,不知道还剩多少时间,就是个麻烦事。特别是,我每次一般只看半小时,断断续续地看,经常需要回忆看了多久、计算还剩多久,需要再看几次,啥时候能看完。
虽然也不能说完全没有进度,在播放的视频后面,控制台里,有个时间,如下图中左下角的2563.03秒。以秒为单位显示,对于口算巨差的人来说觉得挺麻烦。
1. 需求/效果
想了想见过的播放器,因陋就简,可以是下面这样。
(1) 进度条。在画面的最下方,画一条非常细的线。从左到右整个面面的宽度代表100%,红色的范围表示播放的百分比。如上图所示,正播放到50%左右。为了突出进度的百分比,在红色的后面画一条同宽度灰色的线,从最左到最右,作为红线的背景。
(2) 进度文字。在画面的最下方附近,写 已播放的时分秒 / 全片长度的时分秒。如上图所示,播放到 00:52:43.745,全片共 01:41:17。
2. 代码
以下放在一个批处理bat文件中,如下。播放不同视频时,修改第1行和第2行中的两个变量的值,其中a是视频,b是字幕。
set a="input.mp4"
set b="input.mp4.srt"
rem ------------------------------------
for /f %%i in ('ffprobe -v error -show_entries format^=duration -of csv^=p^=0 %a%') do set duration=%%i
set /a duration_int=%duration%
rem 计算小时、分钟和秒
set /a hours=%duration_int% / 3600
set /a remaining_seconds=%duration_int% %% 3600
set /a minutes=%remaining_seconds% / 60
set /a seconds=%remaining_seconds% %% 60
rem 格式化输出,确保不足两位时前面补 0
if %hours% lss 10 set hours=0%hours%
if %minutes% lss 10 set minutes=0%minutes%
if %seconds% lss 10 set seconds=0%seconds%
set duration_hms=%hours%\:%minutes%\:%seconds%
rem ffplay %a% -vf ^
rem "drawbox=w=iw:h=3:y=ih-3:color=gray,^
ffplay %a% -vf ^
"drawbox=w=iw:h=3:y=ih-3:color=gray,^
drawtext=text='.':x=t/%duration%*w-3-1000:y=h-3:fontcolor=red:fontsize=1:box=1:boxcolor=red:boxh=3:boxw=3+1000,^
drawtext=fontfile=C\\:/Windows/Fonts/arial.ttf:text='%%{pts\:hms}/%duration_hms%':box=1:x=(w-tw)/2:y=h-(lh),^
subtitles='%b%'" -ss 0
3. 技术路线和关键技术
需要能实时、动态地读入以下几个变量,全片的时长、当前播放到的时长。
需要能实时、动态地在画面上输出 文字、进度条。
需要变量类型的转换和计算,可能涉及到 以秒为单位的时长 和 时分秒格式的时长 间的相互转换,可能涉及到比例即除法的计算(播放百分比),可能涉及到浮点乘法计算(播放百分比*画面宽度像素数)。
如果占用资源过高,需要考虑如何提高性能,降低机器负载。
变量定义。以下讨论中,以变量 a (引用时为 %a%) 代表视频文件
set a="input.mp4"
3.1 全片时长
用下述代码获取全片时长。
for /f %%i in ('ffprobe -v error -show_entries format^=duration -of csv^=p^=0 %a%') do set duration=%%i
通过ffprobe得到duration,以秒为单位,置到变量duration中。For循环的目的是遍历输出。
这个变量 duration 将在后续代码中 总时长的文字、进度条 中都要使用。
3.2 当前进度的文字输出
当前进度使用 ffmpeg 的 drawtext 滤镜的内置变量 pts,以hms格式输出。
…drawtext=fontfile=C\\:/Windows/Fonts/arial.ttf:text='%%{pts\:hms}/%duration_hms%':box=1:x=(w-tw)/2:y=h-(lh),
3.3 总时长的文字输出
总时长的文字,由 上文提到的全片时长 duration,以秒为单位,经过计算得到时、分、秒,再拼成变量 duration_hms,使用 ffmpeg 的 drawtext 滤镜 输出到画面上。
- 得到 duration,与上文相同,这里抄一遍。
for /f %%i in ('ffprobe -v error -show_entries format^=duration -of csv^=p^=0 %a%') do set duration=%%i
(2) 去除整数部分,然后求 时、分、秒。
set /a duration_int=%duration%
rem 计算小时、分钟和秒
set /a hours=%duration_int% / 3600
set /a remaining_seconds=%duration_int% %% 3600
set /a minutes=%remaining_seconds% / 60
set /a seconds=%remaining_seconds% %% 60
- 拼接。
rem 格式化输出,确保不足两位时前面补 0
if %hours% lss 10 set hours=0%hours%
if %minutes% lss 10 set minutes=0%minutes%
if %seconds% lss 10 set seconds=0%seconds%
- 转义并放进ffmpeg的drawtext滤镜
set duration_hms=%hours%\:%minutes%\:%seconds%
…drawtext=fontfile=C\\:/Windows/Fonts/arial.ttf:text='%%{pts\:hms}/%duration_hms%':box=1:x=(w-tw)/2:y=h-(lh),^
3.4 进度条绘制
(1) 背景灰色的进度条,用 ffmpeg 的 drawbox 滤镜。
… drawbox=w=iw:h=3:y=ih-3:color=gray,^
(2) 当前进度的值,来自 ffmpeg 的 drawtext 滤镜的内置变量 t。
总时间长度来自上文提到过的由 ffprobe得到的变量 duration。
drawtext=text='.':x=t/%duration%*w-3-1000:y=h-3:fontcolor=red:fontsize=1:box=1:boxcolor=red:boxh=3:boxw=3+1000,^
(3) 进度条
drawtext=text='.':x=t/%duration%*w-3-1000:y=h-3:fontcolor=red:fontsize=1:box=1:boxcolor=red:boxh=3:boxw=3+1000,^
用 drawtext画个盒子。
drawtext=text='.'
fontsize=1:box=1:
用drawtext而不用 drawbox的原因,是 drawbox支持的变量少得可怜,不足以完成目的。在这里,输出个”.”只是占位,drawbox中的 box 才是关键部分。
红色盒子,高度3。
boxh=3
盒子长度1000多,假设足够长。
boxw=3+1000
纵坐标,在画面的最下方。
y=h-3
盒子的初始横坐标为 -3-1000,即只露出3个像素。随着播放进度,盒子向右移动。当播放到100%,盒子刚好在画面最侧留下3个像素。
x=t/%duration%*w-3-1000
这个颜色为红色、高度3、宽度1000+3(足够长)的盒子,显示在画面上就是红色进度条。它的位置随进度而向右移动,即上面代码中的 变量x的值 t/%duration%*w-3-1000。之所以改变位置而不是改变长度,是因为我没有找到手段 令 盒子的长度boxw 的值 <= 当前进度和总长度的函数,似乎ffmpeg的drawtext滤镜不支持。
(4)对其他可能方案的讨论
在 drawbox 上尝试浪费的时间最长。后来,下定决心读了 ffmpeg 手册的滤镜一章(册?),才确认上述技术方案。
在 boxw 上浪费的时间其次。我在 ffmpeg 手册中没有找到不行的原因,此处语焉不详,似乎应该能行才对,但是实测不行。
还做了一个方案,在最下面画一个红点,而不是一条线。感觉看起来不够突出,留作备选方案。代码片断如下。
ffplay %1 -vf "drawbox=w=iw:h=3:y=ih-3:color=gray,drawtext=fontfile=C\\:/Windows/Fonts/arial.ttf:text='%%{pts\:hms}':box=1:x=(w-tw)/2:y=h-(lh),drawtext=text='.':x=t/%duration%*w-3:y=h-3:fontcolor=red:fontsize=1:box=1:boxcolor=red:boxh=3:boxw=3"
3.5 转义和换行
在命令行、在批处理bat中、在ffplay的命令行参数中都涉及到转义,对单引号、双引号、冒号、反斜线的转义,还有在单引号对、双引号对中的不得(不是“不必”,“不必”是可有可无的意思,这个词也相当令人恼火)转义。烦死我了。AI的解读经常是错的,不如没有,除了能提醒我某些bug看起来转义造成的。
Bash也需要转义。
换行。为了提高可读性,希望拆开一长行代码。用什么符号换行(即转义),在双引号对里、在单引号对里,分别不同。
我甚至想到是不是应该写个工具,专门解决这个问题,或者已经有这样的工具存在了?又想起 ANTLR stringtemplate,php,jsp 里的各种转义,感觉头大。算了吧,好使就得了。
3.6 变量
在bat文件(不是在cmd中直接执行,涉及转义)中,按下述方式可在画面上输出 pts, n, t。
set a="input.bat"
ffplay %a% -vf ^
drawtext=fontfile=C\\:/Windows/Fonts/arial.ttf:text="pts='%%{pts}' n='%%{n}' t='%%{expr\:t}' p='%%{expr\:pkt_pos}' d='%%{expr\:duration}':box=1:x=(w-tw)/2:y=h-(lh)"
rem 参见 https://ffmpeg.org/ffplay-all.html#drawtext_005fexpansion
4.AI的贡献
AI辅助,主要贡献来自豆包,她所做的贡献包括 最初提供的错误路线启发了我,以及在我描述具体的子需求以后提供技术方案或技术原型思路。DeepSeek和Kimi也参与了讨论,他俩基本没有正面贡献,净添乱了。
此文也发布在以下站点。
----
知乎 https://www.zhihu.com/people/yang-gui-fu-52
微信公众号 杨贵福
----
以下是我曾经发布博客的站点,有些旧文。
----
豆瓣 - 因为审核"我的日记",不再更新。
https://www.douban.com/people/younggift/?_i=0098558fqLUL9h
CSDN – 因为要求我登记手机号码的原因是“为了您的安全”,不再更新。
https://blog.csdn.net/younggift?type=blog
blogsopt – 因为从我的机器不可达,无法更新