4

搞事情:用“图床”传视频,自带免费 CDN 加速(已复活!)

 2 years ago
source link: https://akarin.dev/2020/02/07/alicdn-video-hosting/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
✨小透明・宸✨
2020-02-07 19:24:46

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

之前的更新

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

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

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

“非公开”的图床?

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

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

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

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

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

H046b02b37060460b978f1b0fc000aad4E.png

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

  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 形式保存在服务器上,也可以通过 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……(忽略因为字符编码造成的乱码)

Ha6d4f5bc8b8e41889a7bd1caaca284f78.jpg

使用 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 [email protected]

自动上传脚本

看上去是 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 字节就可以了 ∠( ᐛ 」∠)_

截取的操作在 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/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK