在ffplay播放时加个进度条

我经常用 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 滤镜 输出到画面上。

  1. 得到 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

  1. 拼接。

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%

  1. 转义并放进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://younggift.net/

微信公众号 杨贵福
----
以下是我曾经发布博客的站点,有些旧文。
----
豆瓣 - 因为审核"我的日记",不再更新。
https://www.douban.com/people/younggift/?_i=0098558fqLUL9h

CSDN – 因为要求我登记手机号码的原因是“为了您的安全”,不再更新。
https://blog.csdn.net/younggift?type=blog

blogsopt – 因为从我的机器不可达,无法更新

Leave a Reply

Your email address will not be published. Required fields are marked *