如何更新缩略图生成器(附实例)

464 阅读7分钟

有时你找到一部完美的电影,从头到尾看了一遍,第二天你想给你的朋友看一些搞笑的片段。为了找到正确的时刻,你通常会使用缩略图预览。我们在DStv平台上启用了这一功能,对Showmax的所有资产也做了同样的处理*(Showmax和DStv Now是MultiChoice互联视频部门的两个流媒体服务)*。

你可以把缩略图预览想象成一个功能,在这里我们显示你正在寻找的视频部分的小屏幕截图。这个功能在流媒体服务中非常常见,但实现的过程可能不同:

Example of the thumbnail preview缩略图预览的例子

直到现在在DStv上

我们有一个解决方案,在大多数时候是可靠的,而且工作得很好,但运行服务的要求越来越多,所以我们不得不重写它,使它更 "可观察"。

旧的解决方案需要docker来运行,这意味着我们已经有了一个重量级的依赖。另一个问题是,我们在Python 2上运行(自2020年以来,它不再被支持,没有错误或安全修复)。这不是一个好方法,但该组件运行良好,没有任何关键问题,所以我们把注意力放在了其他地方。

说到监控,我们没有输出任何指标或日志,所以我们在调试和处理诸如文件权限、磁盘空间或只是一些罕见的随机bug方面受到很大限制。

If it works it works

输出文件

我们的缩略图生成器从源视频中创建两个文件:一个精灵文件和一个VTT文件。

一个精灵文件是由每隔N秒从视频中生成的所有小缩略图组成的一个大图像。我们把所有这些放到一个文件里。

Sprite file萌芽文件

VTT文件是一个以网络视频文本轨迹(WebVTT)格式保存的文本文件。在我们的例子中,这个文件包含有时间和图像的行,当用户在刷新视频时,这些行将被显示出来。无论质量如何,我们对每个视频资产只需要一个VTT文件,因为在给定时间内的长度和图像总是相同的。

每一行意味着什么?

  • WEBVTT是文件格式
  • Img 1是正在使用的屏幕截图的编号。它们大多是一排从1-N的数字
  • 00:00:00.000 → 00:00:05.000是我们在刷新视频时显示这个预览的时间
  • 100392602_21BJGPT1_800_SUN_sprite.jpg是我们用于预览的精灵文件的名称。
  • #xywh=0,0,150,84是精灵文件中预览的位置

WEBVTT

Img 1
00:00:00.000 -> 00:00:05.000
100392602 21BJGPT1_800 SUN sprite.jpg#xywh=0,0,150,84

Img 2
00:00:05.000 -> 00:00:15.000
100392602_21BJGPT1_800_SUN_sprite.jpg#xywh=150,0,150,84

Img 3
00:00:15.000--> 00:00:25.000
100392602_21BJGPT1_800_SUN_sprite.jpg#xywh=300,0,150,84

Img 4
00:00:25.000 -> 00:00:35.000
100392602_21BJGPT1_800_SUN_sprite.jpg#xywh=450,0,150,84

Img 5
00:00:35.000 -> 00:00:45.000
100392602_21BJGPT1_800_SUN_sprite.jpg#xywh=600,0,150,84

Img 6
00:00:45.000 -> 00:00:55.000
100392602_21BJGPT1_800_SUN_sprite.jpg#xywh=750,0,150,84

Img 7
00:00:55.000 -> 00:01:05.000
100392602_21BJGPT1_800_SUN_sprite.jpg#xywh=900,0,150,84

Img 8
00:01:05.000 -> 00:01:15.000
100392602_21BJGPT1_800_SUN_sprite.jpg#xywh=1050,0,150,84

DSTv的生成过程

从我们拥有的选项中挑选最高的比特率

我们使用最高的比特率来保证预览的质量。尽管我们要将最终的图像缩减,但最好不要缩减低比特率。最终的图像可能会变得很糟糕,并破坏用户体验。

解密视频

我们需要对最高比特率的视频进行解密,因为我们所有的视频都是加密的,即使是存储在我们的仓库里--这是工作室和内容所有者的要求。

生成屏幕截图

我们将解密后的视频,每隔10秒生成 "屏幕截图",并将其保存在精灵文件中供以后使用。

生成精灵

我们已经生成了很多截图,现在是时候把它们拿出来并创建一个大图像。我们把它们放到一张图片中,以减少用户在滚动时看到预览前的请求数量和所需时间。然后这个大图片的大小也会减少,因为滚动时的预览视频很小,我们不需要所有的额外质量。

生成VTT文件

我们现在有了所有截图的图像,我们需要一种方法来告诉播放器每个预览在视频中的特定时间的位置。

清理

现在我们已经完成了所有的工作,我们只需要删除所有的遗留物,如解码的视频,生成的截图,以及所有我们创建的文件夹,只留下图像和VTT文件。

这几个步骤保证了输出的内容对我们在DSTv的玩家来说是可用的。

我们在第二次尝试中做得更好

由于DSTv视频库每天都在增长,我们需要优化视频缩略图生成过程的速度,并使其更加透明,以便进行调试和监控。

它可能看起来生成两个文件很简单,但有很多问题可能出现在路上。你可能会耗尽存储磁盘上的可用空间,遇到你正在运行的命令的意外输出,以及其他东西。所有这些都迫使我们在所有可能的故障点上有度量和日志信息。这是重做生成器的一个很大的动机。

我们做的第一件事就是放弃了Python,选择了Go作为我们的语言,因为它的速度、goroutines,以及我们已经有了一些用这种语言编写的组件和代码(例如sockrusfqdn。我们使用goroutines来处理队列中的工人。

我们做的第二件事是放弃了Docker作为运行生成器的环境,而是直接在主机上运行它。这提高了性能,并消除了对一个需要安装的大包的依赖。

第三件事是为重试机制引入更多逻辑。旧的解决方案只是在发生一些错误时通过源视频的可用比特率进行迭代。我们为重试增加了一个限制,以避免永远阻塞队列,并增加了对生成重复请求的检查。结合指标和更好的记录,我们有一个重试机制,让我们知道是否有问题,并且只重试一定的次数。

与旧版本相比,我们增加的最后一件事是指标和更好的日志记录(正如上一节所暗示的)。由于我们将缩略图生成器作为一个服务来运行,我们将端口和端点上的指标导出到我们的Prometheus实例中。然后我们就可以把这些指标用于仪表盘和警报。说到仪表盘,你肯定需要一个面板来显示你收到了多少个生成请求以及最后一个生成请求运行了多长时间。

我们正在收集/输出的指标的例子包括:

  • 为播放器生成输出的时间
  • 收到的生成请求的数量
  • 已处理的生成请求的数量
  • 失败的生成请求的数量
  • 由于存储动作而产生的错误
  • 由于运行命令而产生的错误
    还有更多...

Victory

计划中的未来改进

我们已经用新的语言重写了旧的解决方案,并且使用了新的技术,但是由于我们需要和以前一样的输出,所以过程仍然是一样的。随着时间的推移,我们计划减少需要运行的命令数量,对所有从视频生成的图像只使用ffmpeg

其他肯定会做的改变是新的指标和基于可能和将要出现的问题的警报。