在线电影网站(VOD)技术简谈
背景
- 不喜欢国内媒体的月充会员+低码率
- 自己有很多高码率片源
因此初心想做一个高码率高质量的点播平台,但其实因为版权问题是没法公开和做大的,最后只能放弃,现在写篇文章讲讲技术上的事情吧。
片源
主要来自 PT 站,搜集的基本是 2160p/H.265/10bit/HDR 以及高码率(25-60Mbps),具体不多说,此为视频背景。
个人手上都是小站,在这里随缘求一个大站邀请码~~~😘
Email:[email protected]
视频处理
降码率(H.265 -> AV1)
想要在线播放视频,这些原片的码率太高了,一部电影动辄 30GB 往上,因此不可避免的需要转码。
了解一些基础后选择了转成 AV1 编码,低码率提供高质量的主流选择。
先折腾过 av1_nvenc,后来是觉得 SVT-AV1 压缩质量更好(同等码率下画质更高/同画质下码率更低),并支持 film-grain,因此后面就使用 CPU 来进行压缩了。这里主要涉及的参数有
- preset:我设为 4
- gop:涉及后续的切片时长,我设为 144
- crf:决定码率的关键参数,对于 2160p 是 24-28,对于 1080p 是 20-22。这里没有选择 CBR 和 VBR,我觉得 crf 在我的目标场景下更适合,最后输出的码率在 4-20 Mbps
- film-grain:基本在 8-12 不等,干净的动画设为 0
色彩映射(HDR -> SDR)
由于片源大部分都是 HDR,而目前 HDR 播放又没有那么主流,因此这里还是转成 SDR 的兼容性更好。
这阶段踩过很多坑,包括映射算法,和转码一起处理的管道输入输出,GPU 计算等等。
最终的技术路线是使用 libplacebo(启用 GPU 加速),算法使用 spline,再详细的参数有:
- peak_detect=1
- smoothing_period=20
- scene_threshold_low=1
- scene_threshold_high=3
- percentile=99.995
分辨率缩放
考虑到不是所有的设备都需要上 2160p 的画质,尤其是手机和平板这种移动端,看高码的 1080p 就已经足够了,因此这里还加入了分辨率缩放,从 2160p 降到 1080p。
这里也是用 libplacebo 这个库,下采样算法使用 mitchell。
音频处理
音频上的处理很简单,使用 ffmpeg 自带的 aac 编码器,全部转为 192kbps 立体声
字幕处理
字幕也是一个头疼的点,大部分片源里面封装的字幕是 PGS 这类位图字幕,本质是很多张图片+控制流信息(决定在什么时间在什么位置出现哪张图片)。目前主流的 HTML5 播放器或现有库没法解决这事,原本想用 OCR + ASS 来解决,但 OCR 效果不好,手动核对工作量太大,并且文本位置好像也没法再确定。
因此这里的技术路径是手动解析 PGS 文件。先把字幕从 MKV 容器中提取出来,然后逐个读取 PGS segment,组织成 display set 和字幕事件,把字幕位图转成无损 WebP,并生成前端可按时间轴查询的 index.json。前端播放器播放视频的同时把这些图片渲染到视频上,就有了字幕。
具体实现都是 GPT 干的,我对 PGS 文件结构与怎么排布的也不太熟悉,好在生成的代码经检查是正确可用的,搜了下 github,这好像是目前最好的 PGS Parser 了(或者说目前网页播放器支持 PGS 格式字幕的最好解决方法),感兴趣的可以联系找我要代码(因为没有单独整理出来,所以目前不好贴链接)。这里列一个目录结构:
models.py: 数据模型、常量、异常类型segments.py: 二进制 segment 读取与 PCS/WDS/PDS/ODS/END 解析palette.py: PGS 调色板合并与 YCbCrA -> RGBA 转换rle.py: PGS RLE 位图解码renderer.py: object 位图渲染与多 object 合成parser.py: display set 状态机、缓存复用、end time 推导exporter.py: WebP 导出与index.json生成cli.py: 命令行入口
切片
拿到处理后的视频和音频文件后,需要转成一个个小切片文件才能更好地进行播放,提升用户体验。
切片时间原本定的是 4 秒,后来改成了 6 秒。
这里用的是 Shaka Packager,做成 CMAF 格式(DASH 和 HLS 双协议)。
网站
前端
前端播放器 UI 比对了几个最终还是选择了 Artplayer,中文文档,界面也比较好看。播放器代码使用的是 dash.js 和 hls.js。网站用的是 Vue 框架,基本都是 AI 写的了,这里贴两张截图。
后端
因为想极致地省成本,所有的部署都是用 CloudFlare 的产品,包括 CDN、worker 和 R2。
Backblaze 的 B2 虽然更便宜且流量到 CloudFlare 也免费,但 CloudFlare CDN 的政策是只能免费代理其自己的产品,否则有封号的可能性,我想了想还是全部用 R2 了。
前后端都使用 worker 进行搭建,后端做了 HMAC 签名/鉴权,流量控制,R2 回源手动 Cache 控制等等。
加速
由于 CloudFlare 对于大陆访问给的 CDN 节点都很差,比如位于美国洛杉矶而不是更近的香港日本这些,因此在实际点播场景下还是会出现加载卡顿的情况(这个分地区和时间,我是北京联通不太好,但别人是浙江移动访问起来就还可以)。因此还是要想一下加速的方案,优选 IP 我嫌麻烦与不稳定就算了,最后解决方案是加中转节点。
具体思路是使用优质出口线路机器来充当中转服务器,用户 -> 中转 -> Cloudflare CDN 节点 -> Cloudflare R2。这个中转还是比较花钱和需要考虑性价比的,比了很久最后购买的测试机器是每月 28 元,美国洛杉矶 100M 带宽 1GB 流量,去程回程分别是:电信 163/9929、移动 CMI/CMIN2、联通 4837/9929。部署 Nginx 中转后速度一下子就上去了,这里服务器还有多余的硬盘空间可以开启 Nginx 缓存,再少一半的流量使用。如果能上线并且大量用户访问的话,这个方法可能还是挡不住(得加钱)。
为了再省省中转节点的流量,这里后端还做了根据当前播放情况来自动选择线路,如果用户直接访问 Cloudflare CDN 节点的速度还不错,播放不会卡顿,那就不走中转,而是直接连接。如果检测到经常发生卡顿,才会自动切换到中转线路。在代码实现上这里主要是监听 dash.js 事件。
最后
想把自己收藏的高质量片源分享给他人观看是一直想做的事情,可能每年总有一段时间突然想去做它,最早最早我记得是在弄 emby/jellyfin 吧,甚至想抄 jellyfin 实时转码的代码。后来还试了下 AList,也是在这个项目中了解到了 Artplayer,可惜去年出了事。
版权问题是没办法绕过的,即便是那些非常经典的随处可看的老电影,算是一个遗憾。(当然要做遵纪守法的好公民)
就这样吧,所有的代码分为转码和网站两个仓库,但因为使用了比较多的 AI,代码比较杂乱,目前没时间也不太想再去整理,因此没办法直接放仓库链接。欢迎邮件来讨论其中的技术细节。
本站不记录浏览量,但如果您觉得本内容有帮助,请点个小红心,让我知道您的喜欢。