前几天刷新闻的时候,看到了一个代码仓库 YouTubeDrive 的介绍,他的作者在多年前用 Wolfram 语言(也就是 Mathematica) 编写了一段代码,可以将油管变成一个无限容量的大网盘,看完介绍之后我脑子里直接蹦出无数个“卧槽”,心里顿生“这样也行”的膜拜之情,不禁提问:这个是薅羊毛的最高境界吗?

说起 Wolfram 语言和 Mathematica,不得不提一下,它是我学习的第一个编程语言(汇编是第二个)。多年前我是在学校机房学的,当时只用它画画数学公式的图,今天看着 YouTubeDrive 的实现,才了解到 Wolfram 还可以这样用。

首先,和原作者一样,我也不提倡这样把 YouTube 当成一个无限容量的网盘使用,相同的道理,我也不提倡在国内把 B 站或者任意视频平台当做位无限容量的网盘来用。

昨天在看完 YouTubeDrive 源码之后,想着要不我也用 Python 实现一个,正好傍晚溜娃的时候,啥也不能干,就边走边想,我用 Python 应该怎么来实现一个? 在坐着等娃的时候,就用手机查下资料,大概了解了一下 numpy 和 opencv 的使用,晚上吃过晚饭就开始写了,很快也实现了一个可以工作的 Python 版的。

我觉得这个想法还是挺有意思的,虽然不太靠谱,但是真的好玩。不想去读 Wolfram 源码的朋友可以看看我接下来的分析,看看是如何把文件变成 YouTube 视频的进行存储的。

磁盘上的文件是什么

在我们的计算机(无论电脑还是手机都是计算机)磁盘里,存放了很多很多的文件,例如图片,文档,视频等等,每种文件都有自己的类型,或者叫格式,一般我们都会给文件添加一个后缀名展示文件的类型,但是对于电脑而言,无论是什么文件,它都只是由比特(bit) 0 和 1 组成的数据。

例如这张图片:

用 vim 执行命令 :%!xxd,打开是这样的:

图片文件的头 4 个字节是 ffd8(其实 ffd8 说明了这个是 JPEG 格式的文件),ffd8 是 16 进制的表示法,比 2 进制看起来更清晰。

在计算机里,用 8 个 bit 表示一个 byte,即 1 个字节有 8 位,一个字节需要用 2 个 16 进制数来表示,例如:ffd8 是 2 个字节。

视频文件又是什么

视频文件也是有各种类型的,对计算机而言,视频文件也是由比特 0 和 1 组成的数据,但是视频文件的头部是有规律的,这样计算机就能区分去视频文件的类型。

视频通过播放器播放,人类去看视频的时候,眼睛看到的其实是一帧一帧播放的图片,超过每秒 20 帧,也就是一秒中播放 20 张图片,人类的大脑就会认为这是一个连续的画面,这样的画面就叫做视频,存放这些图片数据的就是视频文件。

我们姑且不讨论视频中声音的部分,在后续我把文件转成视频的过程中,没有产生声音。其实声音也可以用来存储数据的,比如我们可以把文件编码成摩斯密码声波存储到视频中去。

图片是什么

既然视频是由图片组成的,那图片又是什么?我们在日常的工作或者生活中,会常常需要调整图片长宽大小,我们可以简单的用像素来表述图片的长宽,例如上面手提包图片的长宽是 1024x768, 表示长有 1024 个像素点,宽有 768 个像素点。

每一个像素点都有一种颜色,这个颜色我们可以用 RGB 来表示。例如这样的颜色值#ff0000,表示红色,#00ff00 表示绿色,#0000ff 表示蓝色,用 3 个字节就可以表示颜色值了,颜色值还有另外的表示方法,例如红色可以用这样来表示:(ff, 00, 00) 或者 (255, 0, 0),用了方便人类阅读,我们使用 (255, 0, 0) 这种方式来表示像素点的颜色。

任意文件如何图片来表示

文件是一个连续的由 0 和 1 组成的二进制数据,我们可以用一个一维的十进制数组来表示这个文件:假设文件由 n 个 bit 组成,那么这个一维数组的长度就是 n,数组的每一个元素对应着文件中的每一个 bit。我们再给十进制数组的每一个元素乘以 255 后,会得到一个新的数组,如图:

这样十进制数组每 3 个元素就可以表示一个像素点的颜色了

我们只要准备一个足够大的图片,就可以把这个数组中的数据拷贝到这个图片中去,这样就可以把文件转成图片了。

用视频来存放图片

我们不需要准备一张足够大的图片,我们可以用很多张小的图片来分块存储这些像素点,但是考虑到 YouTube 视频在上传后会被平台转成不同的画幅大小,所以我们可以把图片分成不同的尺寸,比如我们可以把图片分成 1280x720。

在代码实现过程中,一张原始图片的长宽像素是 64 x 36,这样每张图片可以存储 2304 个比特位的数据,考虑到视频在平台被转码压缩后会有颜色变化,我们用 20x20 的小色块来表示一个像素点,那么 1280x720 的视频一帧恰好也是存储 2304 个比特位。

视频我选择的 fps 是 20,也就是每秒播放 20 张图片,计算一些简单的计算,很容易就可以得到一个完整的视频。

将视频转回文件

由于视频在转码的过程中会有一些数据丢失,在转回的过程中,需要对 20x20 的色块进行采样,计算出平均值后就很轻易的反推出比特位是 0 还是 1 了。

代码实现