搞事情:用“图床”传视频,自带免费 CDN 加速(已失效,科普向)
✨小透明・宸✨
2020-02-07 19:24:46

更新:目前 https://kfupload.alibaba.com/mupload 这个上传接口已经失效了,不过仍然还有很多视频网站正在使用类似的方法通过其他的接口上传视频。这篇文章似乎被转发到了很多地方,如果你想要了解这类网站上传和使用视频切片的原理的话,把这篇作为科普向倒也不错?

在开头位置先推荐一下 @SomeBottle 发布在自己博客上的教程《用图床看视频的体验·改》,非常详细,在这篇教程的基础上补充了包括“iOS 环境下如何播放”在内的很多东西~

之前的更新

目前 alicdn 似乎又取消了文件格式检测,仍然可以像之前一样上传非图片类型的文件,只需要上传时填写文件名的那个字段使用图片的扩展名即可~

但是不能上传 TS,估计是被视频床的事情弄怕了 2333

当然还是有办法绕过这个限制。HLS 协议支持使用加密,操作方法是将所有的 TS 使用某个密钥和 IV 通过 AES-128-CBC 进行加密,然后将密钥以二进制写入文件中并上传,在 M3U8 中添加字段 #EXT-X-KEY 写上密钥文件的 URL 和 IV。加密以后的文件就不是 TS 了,仍然可以正常上传 | ू•ૅω•́)

在之前的上传脚本上改一改就可以进行加密了,至于怎么改?请独立完成作业(逃

这里是演示。代码完全复制自 hls.js 的示例,所以在 iOS 上大概也能播放……?


由于添加了文件格式检测,该方法已失效。

上传图片仍然是没问题的(不包括 WebP),但是无法再上传新的非图片文件,视频当然也不行了┐(´-`)┌

另外现在获取文件时有极低概率出现 429 错误,如果介意的话建议更换其它图床 (=’_‘=)

传视频的替代品:Catbox,Onedrive 5TB 配合直链生成工具,或许还有下一个不会检测文件格式的图床……?


一种理论上可行的方法:

众所周知,很多文件格式都有文件头和文件尾,解码图片时会直接无视图片的文件尾之后的数据。以前在网上经常会流传的“图种”就是利用了这个原理,在图片后面加上压缩包的数据即可在各大论坛上直接发送,压缩软件打开“图种”的时候又会无视压缩包文件头之前的数据,所以不影响正常打开。

利用这个手段同样可以解决文件格式检测的问题,只要用一个尽可能小的 1x1 的图片放在 TS 切片前面就可以在任意图床(只要不进行二压)正常上传了。至于播放……只考虑在网页上播放视频的话,hls.js 也支持对获取切片的 loader 进行重写,因此可以在这里把文件开头的那张图片去掉,应该就可以正常播放视频了~

由于 iOS 本身就支持播放 HLS,而且并不能使用 hls.js,因此无法在获取视频切片这一步上动手脚,所以这个办法在 iOS 上是无法使用的……

通过 M3U8 格式的 #EXT-X-BYTERANGE 字段似乎可以绕过?

占坑,写出来以后再更新(咕咕咕)

测试成功(σ′▽‵)′▽‵)σ 这里是演示,具体操作方法请翻到最后


封面图:Pixiv ID: 78997162 「綾あか」 by うめか

先说结论,作为测试翻出了硬盘里的 LL 的四个多小时的 Final Live,自己压制成 720p 和 360p 后上传到“图床”上,播放非常流畅,一点问题都没有~

这里是演示| ᐕ)୨ 需要自动上传脚本的可以直接翻到最后。

“非公开”的图床?

图床这种东西一抓一大把,比如 SM.MS 之类的图床本身就是用来提供图片外链服务的。

那……难道还有用途不是图片外链的图床吗?其实是有的(´゚ω゚`)

比如说最知名的图床:微博图床,因为使用简单(各种轮子足够多)、速度快(真・全球 CDN 加速)、有保障(在可见的相当一段长的时间内微博几乎不可能关闭)而被广泛使用,虽然也有强制二压图片、给图片加水印的不足之处……不过对于这种免费的“服务”,不能要求太多?

但是微博显然不是专门做图床的,它只是个社交网站。据说是因为发微博的时候上传图片的接口存在漏洞,已登录用户只要选择了图片就会立刻上传(不论有没有发送微博),而且这个接口只需要已登录用户的 Cookie,上传图片得到的 URL 直接拿去外链也不会有“该图片来自……未经允许不可引用”之类的提示,于是就被拿来当图床使用了( ε:)

甚至还出现了一些图床,号称“全球 CDN 加速”、“图片永久储存”、“高速稳定”,实际上后端对接的也是微博图床,这就有点那啥……不过至少也是提供了一个上传工具嘛(摊手

由此可见,一个图片上传接口只要满足以下几点,就有被拿去薅羊毛作为图床的可能:

  1. 没有添加防盗链措施
  2. 上传图片无须复杂的身份验证,甚至允许匿名上传
  3. 不限制图片的上传次数

比如这里就列举了一大堆符合以上条件的“非公开”图床(部分已失效),谁能想到一个图片搜索功能都可以拿来做图床的?!

如果还认为“检查文件格式 === 检查扩展名”的话……

这次要被玩坏的主角就是上面那个连接里提到过的福报厂的图片上传接口,也是本站一直在使用的接口(。•̀ᴗ-)✧

先说明一下调用方法,以及总结一下它的属性:

https://kfupload.alibaba.com/mupload

POST 参数类型说明
fileFile文件的二进制数据
nameString文件名
sceneString固定为 aeMessageCenterV2ImageRule
  • 支持 JPG、PNG、GIF 格式(?)
  • 文件大小限制为 5MB
  • 不会对图片(?)进行二压
  • 无需身份验证
  • 没有上传频率限制,大概吧(连续上传过数百个文件也没被封 IP 什么的)
  • 没有图片内容审核,大概吧(上传过一张“好孩子看不见”的图片,观察了至少半个月也没被删除)

返回数据示例:

{
    "fs_url": "H2db4a9284928493eb92b0676277dc60a3.jpg",
    "code": "0",
    "size": "42585",
    "width": "960",
    "url": "https://ae01.alicdn.com/kf/H2db4a9284928493eb92b0676277dc60a3.jpg",
    "hash": "26e29271d884f443e3e2660decd3776b",
    "height": "540"
}

所有的数据居然都是字符串类型,这是坠痛苦的

看上去是很正常的图片上传接口,但是实际上它对于文件格式的检测仅仅是根据扩展名(也就是 name 字段)。也就是说,只要随便选择一个 5MB 以内的文件,然后上传时在 name 字段写上图片的文件名(比如 image.jpg),仍然可以正常上传!(๑˙ー˙๑)

比如 https://ae01.alicdn.com/kf/Hcb7a3052f6894bfab549f41d324a68dbR.jpg 这个链接,看上去是 JPG 图片,打开后却无法显示。如果下载下来然后将扩展名改成 m4a 的话……实际上是一个音频文件啦~

利用这个漏洞就可以实现上传任意类型的文件了,比如这个接口本来不支持上传 WebP 图片,但是通过改扩展名就可以解决(本站的 WebP 图片也是用这种方式上传的)。不过对于视频文件来说,其文件大小通常都在数十或数百 MB,甚至以 GB 为单位的数量级上,5 MB 的大小限制显然是不够用的(除非是○手、○音上的那些短视频……),那还能怎么办呢……

视频分片上传基本原理

压缩包有分卷压缩,视频也可以拆成好几段然后然后组合到一起再播放,具体的实现手段就是 HLS 协议了。

HTTP Live Streaming(缩写是 HLS)是由苹果公司提出基于 HTTP 的流媒体网络传输协议。是苹果公司 QuickTime X 和 iPhone 软件系统的一部分。

它的工作原理是把整个流分成一个个小的基于 HTTP 的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。

在开始一个流媒体会话时,客户端会下载一个包含元数据的 M3U8 文件,用于寻找可用的媒体流。

维基百科:HTTP Live Streaming

播放 HLS 协议的视频流需要一个 M3U8 文件,这个文件按照一定格式存储了每一小段视频的 URL 以及时长。

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:5.000,
http://tp-fad-files.huanxi.com/38c6b150aefe7a7150ce64b4f12f0538/1581043918/vod01/20200124/7cfe90e6-752b-4cba-87f0-10e2b188b007/4944/4944_1579831306_3865413_1505.ts
#EXTINF:5.000,
http://tp-fad-files.huanxi.com/34be054e87e4a7ad08609adaa42b53a7/1581043919/vod01/20200124/7cfe90e6-752b-4cba-87f0-10e2b188b007/4944/4944_1579831306_3631912_6505.ts

...

#EXTINF:5.000,
http://tp-fad-files.huanxi.com/a467e02ed4c84cddcc72b4b8062bf46a/1581043919/vod01/20200124/7cfe90e6-752b-4cba-87f0-10e2b188b007/4944/4944_1579831388_4072629_7596505.ts
#EXTINF:0.880,
http://tp-fad-files.huanxi.com/05dfd6187eee5353498aafe175c7747e/1581043919/vod01/20200124/7cfe90e6-752b-4cba-87f0-10e2b188b007/4944/4944_1579831388_7758824_7601505.ts
#EXT-X-ENDLIST

除此之外,HLS 协议也支持自适应切换视频流的功能。在 M3U8 里面再写上若干个视频流(也是 M3U8)的 URL,并给每个流标记上码率、分辨率、视频编码等数据(例如:#EXT-X-STREAM-INF:BANDWIDTH=1200000,RESOLUTION=640x360,CODECS="avc1.42e00a,mp4a.40.2",NAME=360p),这样客户端就可以根据网速和播放器的支持情况自动切换视频流。

HLS 使用到的 TS 视频文件,全称 Transport Stream,主要应用于实时传送视频数据,因此需要保证每个单独的 TS 都可以独立播放(相当于一个小的视频文件)。反过来,将若干个 TS 的二进制数据按顺序直接拼接到一起,就可以组成一个较长的视频。服务端源源不断地将视频数据转换为 TS 然后输出(可以把较早的 TS 删除),客户端不断获取 M3U8 文件来获取最新的几个 TS,这样就实现了视频直播;如果视频已经以 TS 形式保存在服务器上,也可以通过 HLS 依次获取然后播放,这样实现的就是视频点播。

只要能将自己手上的视频切割成 TS 然后逐个上传到“图床”,使用 HLS 协议不就能正常播放了吗!(੭ ˙ᗜ˙ )੭

准备上传

目前在网页上播放视频的主流格式仍然是 MP4 封装的使用 H.264 + AAC 编码的视频文件,这里也只针对这种格式进行处理。

使用 FFmpeg 的两行命令就可以实现将视频转换为 TS,再对 TS 进行切割以及生成 M3U8 播放列表的工作:

ffmpeg -i video.mp4 -c copy -vbsf h264_mp4toannexb -absf aac_adtstoasc video.ts
ffmpeg -i video.ts -c copy -f segment -segment_list video.m3u8 %d.ts

因为是对视频直接进行分割,所以这个操作是无损的,不需要重新编码,速度非常快(唯一的限制是硬盘读写速度),对视频的画质也没有影响。执行后会在当前目录下生成一个 video.m3u8 以及一大堆的 0.ts1.ts……(忽略因为字符编码造成的乱码)

使用 MPC-HC、VLC 等支持 HLS 的播放器打开 M3U8 就可以播放,如果将 TS 全部上传到“图床”然后用得到的 URL 替换掉 M3U8 里面的 0.ts1.ts……那就相当于上传了整个视频。还记得那个“图床”的限制吗?文件大小限制不能超过 5 MB,因此切割出来的 TS 也不应该超过 5 MB,但是切割 TS 的大小并不是可以随意设定的……

这里就需要提到一些视频格式的基础知识了(逃

为了提高压缩率,H.264 编码标准将视频的帧分为 I 帧、P 帧、B 帧三类,后两者可以存储前后帧的图像变化,然后参考其他帧“预测”出画面数据;但 I 帧保存的是完整的一帧;部分 I 帧也是 IDR 帧,每一个 IDR 帧之后的帧都不能参考 IDR 帧之前的内容

在无损的要求下,由于 IDR 帧的特性,视频分割的最小单位是以 IDR 帧(关键帧)进行分割得到的小段,又称 GOP(Group Of Pictures)。每个 GOP 的帧序列以 I 帧开头和结尾,例如 IBBBPBBBPBBBI,不能再继续分割。如果视频的码率较高且 GOP 过长的话,就很容易出现某个 GOP 已经超过了 5 MB 的情况。

简单的解决方法是将这些大小超过 5 MB 的 GOP 切出来的 TS 丢给 FFmpeg 进行二压,不改变分辨率和帧率的话并不会影响整个视频的播放;但是在问题相当严重的情况下,最好是二压整个视频,降低码率,注意将 x264 的参数 keyint 改低一些,保证不会出现过长的 GOP。

除了超出限制的文件,过小的文件也是需要注意的。上面的截图中的大部分 TS 都只有数百 KB,虽然可以上传,但是文件数量实在太多,加载时需要发送大量 HTTP 请求严重影响性能。可以将相邻的几个大小加起来刚好达到 5 MB 的 TS 的二进制数据按顺序合并起来,不过一开始的几个 TS 例外(只需要合并到 2 MB 就可以了?),否则按下“播放”按钮然后要等上很久才能播放第一个 TS 还是有点难受的……

上传视频也可以使用 curl 在命令行中完成,得到的响应会直接输出到 stdout。也可以使用其它编程语言的网络请求模块,不过不知道为什么使用 Python 的 requests 模块一直都无法上传……?

curl https://kfupload.alibaba.com/mupload -X POST -F scene=aeMessageCenterV2ImageRule -F name=image.jpg -F file=@video.ts

自动上传脚本

看上去是 Windows,实际上是 Kali 的 Windows 界面模式(滑稽

但是它真的预装了 PowerShell

出门右转 GitHub Gist 查看和下载脚本( ॑꒳ ॑ )

运行脚本需要 PowerShell 运行环境、FFmpeg 和 curl

  • 输入一个 MP4 封装的 H264 + AAC 编码的视频的路径(如果有需要,请自行对视频进行二压处理)
  • 在相同目录下生成一个以随机 GUID 命名的临时文件夹,保存视频切割的 TS 并生成 M3U8(video.m3u8
  • 如果存在超过 5 MB 的 TS 则给出提示,需要手动进行二压后才能继续上传
  • 自动合并相邻的 TS,使文件大小接近 5 MB(前三个合并的 TS 为 2 MB)
  • 将合并后的 TS 全部上传到“图床”,生成写入了“图床”的 URL 的 M3U8(video_online.m3u8
  • 临时文件夹内的 TS 和 video.m3u8 可以自行决定是否删除
  • 脚本没有任何错误处理,所以如果出现了各种奇怪的事情大概率是视频文件本身的问题……大概吧(小声

video_online.m3u8 存放到自己的服务器上(或者也上传到“图床”?!),在网页上使用 hls.js 就可以播放了。

(实际上最前面的演示就是这么做的,还用到了自适应切换,如果网速比较快的话播放一段时间就可以发现视频的清晰度从 360p 无缝衔接到了 720p)


因为 Python 无法实现上传所以还是继续用 PowerShell 写脚本了~正好 PowerShell 6.0 已经支持跨平台了,所以做了一点微小的适配,在 Linux 下也能跑٩( ‘ω’ )و

当然 Linux 用户别忘了 apt-get install powershell,Windows 自带充话费送的 PowerShell 运行环境。

脚本需要使用 FFmpeg,Windows 用户请出门右转这里下载,然后ffmpeg.exe 所在的目录添加到 PATH 或者去脚本的三十几行的位置手动修改路径,Linux 用户请 apt-get install ffmpeg;还需要使用 curl,在 Linux 下应该是标配了,Windows 用户请出门右转这里下载,同样需要添加到 PATH 或者修改脚本里的路径。

在开始检测文件格式之后……

目前 alicdn 的这个接口已经添加了图片的文件格式检测,如果上传的文件数据不是图片,即使自己改了 name 字段也会报错。不过借鉴“图种”的原理,把正常的图片数据插入到文件最前面就可以了~

Stack Overflow 上查了一下,最小的 1x1 像素的 GIF 图片可以做到只有 24 字节。把它加到 M3U8 和 TS 切片的最前面,得到的“图片”是可以上传到 alicdn 的(理论上也支持所有图床),然后读取的时候再截掉前 24 字节就可以了 ∠( ᐛ 」∠)_

TS 文件以一个 0x47 作为文件头,据说不截取也是可以的,但是我没有额外测试过。

截取的操作在 hls.js 的 loader 上加一个 Hook 即可实现,不过遗憾的是这样的话将视频下载到本地就不能直接播放了……这个 Hook minify 以后只有三百多字节,在 new Hls(...) 之前执行即可~

(function (Hls, offset) {
    var load = Hls.DefaultConfig.loader.prototype.load;
    Hls.DefaultConfig.loader.prototype.load = function (context, config, callbacks) {
        if (context.type === 'manifest' || context.type === 'level' || context.responseType === 'arraybuffer') {
            var onSuccess = callbacks.onSuccess;
            callbacks.onSuccess = function (response, stats, context) {
                response.data = response.data.slice(offset);
                return onSuccess.call(this, response, stats, context);
            };
        }
        return load.call(this, context, config, callbacks);
    }
})(Hls, 24);

出门右转 GitHub Gist 下载更新后的脚本( ॑꒳ ॑ ) 这个脚本最后会输出一个 video_online.m3u8.gif,自行上传到任意图床即可(同样也可以将脚本中的上传部分自行修改为其它图床)。这里是演示~(可以注意到,M3U8 并没有存在 alicdn 上,不过 TS 切片还是使用 alicdn 上传)

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。不允许内容农场类网站、CSDN 用户和微信公众号转载。
本文作者:✨小透明・宸✨
本文链接:https://akarin.dev/2020/02/07/alicdn-video-hosting/
chevron_left 上一篇 下一篇 chevron_right