详细记录下用 chrome 抓取视频的教程
准备工作
widevine-l3-decryptor
安装插件 widevine-l3-decryptor,github 地址。
1
| git clone https://github.com/p00f/widevine-l3-decryptor
|
chrome 插件打开开发者模式选择“加载已解压的扩展程序”,定位到项目地址即可。
tampermonkey 和 Netflix - subtitle downloader
安装插件 tampermonkey chrome 商店地址,并安装用户脚本 Netflix - subtitle downloader greasyfork 地址
改写脚本370行左右,向 console 输出 manifest 信息
1 2 3 4 5 6 7 8 9 10 11 12
| ((parse, stringify) => { JSON.parse = function (text) { const data = parse(text); if (data && data.result && data.result.timedtexttracks && data.result.movieId) { window.dispatchEvent(new CustomEvent('netflix_sub_downloader_data', {detail: data.result})); console.log('manifest:') console.log(data) console.log(stringify(data)) } return data; };
|
netflix-1080p
安装插件 netflix-1080p,github 地址。
chrome 插件打开开发者模式选择“加载已解压的扩展程序”,定位到项目地址/src目录即可。
这是我正在改写的一个版本,其中 cadmium-playercore 是根据最新的官方版本改写的,主要是为了让 chrome 也能获取 1080p 视频。其他的功能还不能正常使用。
地址的获取
Netflix 的视频、音频、和字幕是分开的,只有视频流是采用了 DRM 加密的,音频和字幕未加密。所有的相关信息都在一个 manifest 中可以找到,这个 manifest 可以通过前文提到的魔改后的 Netflix - subtitle downloader 脚本输出到控制台。脚本会以两种形式输出,一种是直接在控制台里面输出 object,另一种是输出 JSON 字符串,方便 copy 到其他文本编辑器查看。推荐使用 vscode 来查看。
视频地址的解析
视频信息在 manifest 的 result.video_tracks.streams
属性下。Netflix 会准备很多视频流,用来根据网络情况实现码率的自动调整。我们抓取的时候只需要抓取码率最高的一条即可,一般是最后一条。
但是,用官方原版的网页播放,在试用 chrome 的时候它最高只会推 720p 的流,所以我们需要用到 netflix-1080p 这个插件。这个插件的原理就是手动改写 cadmium-playercore-xxx.xxx.xxx.js
文件里面的 profiles
。格式化之后大约在 112510 行左右
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| viewableId: n,
profiles: [ "playready-h264mpl30-dash", "playready-h264mpl31-dash", "playready-h264mpl40-dash", "playready-h264hpl30-dash", "playready-h264hpl31-dash", "playready-h264hpl40-dash", "heaac-2-dash", "heaac-2hq-dash", "heaac-5.1-dash", "simplesdh", "nflx-cmisc", "BIF240", "BIF320" ],
|
其中 playready-h264mpl40-dash
和 playready-h264hpl40-dash
是 1080p 对应的 profile。
在 20298 行左右有比较全的 profile 可选信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| k.vM = "playready-h264mpl30-dash"; k.YF = "playready-h264mpl31-dash"; k.mV = "playready-h264mpl40-dash"; k.C6a = "playready-h264hpl22-dash"; k.jV = "playready-h264hpl30-dash"; k.kV = "playready-h264hpl31-dash"; k.lV = "playready-h264hpl40-dash"; k.o8 = "hevc-main-L30-dash-cenc"; k.p8 = "hevc-main-L31-dash-cenc"; k.q8 = "hevc-main-L40-dash-cenc"; k.r8 = "hevc-main-L41-dash-cenc"; k.s8 = "hevc-main-L50-dash-cenc"; k.t8 = "hevc-main-L51-dash-cenc"; k.k8 = "hevc-main10-L30-dash-cenc"; k.l8 = "hevc-main10-L31-dash-cenc"; k.m8 = "hevc-main10-L40-dash-cenc"; k.n8 = "hevc-main10-L41-dash-cenc"; k.$F = "hevc-main10-L50-dash-cenc"; k.aG = "hevc-main10-L51-dash-cenc"; k.tV = "hevc-main10-L30-dash-cenc-prk"; k.vV = "hevc-main10-L31-dash-cenc-prk"; k.xV = "hevc-main10-L40-dash-cenc-prk"; k.zV = "hevc-main10-L41-dash-cenc-prk"; k.uV = "hevc-main10-L30-dash-cenc-prk-do"; k.wV = "hevc-main10-L31-dash-cenc-prk-do"; k.yV = "hevc-main10-L40-dash-cenc-prk-do"; k.AV = "hevc-main10-L41-dash-cenc-prk-do"; k.BV = "hevc-main10-L50-dash-cenc-prk-do"; k.CV = "hevc-main10-L51-dash-cenc-prk-do"; k.MXb = "hevc-main-L30-L31-dash-cenc-tl"; k.NXb = "hevc-main-L31-L40-dash-cenc-tl"; k.OXb = "hevc-main-L40-L41-dash-cenc-tl"; k.PXb = "hevc-main-L50-L51-dash-cenc-tl"; k.IXb = "hevc-main10-L30-L31-dash-cenc-tl"; k.JXb = "hevc-main10-L31-L40-dash-cenc-tl"; k.KXb = "hevc-main10-L40-L41-dash-cenc-tl"; k.LXb = "hevc-main10-L50-L51-dash-cenc-tl"; k.mM = "hevc-dv5-main10-L30-dash-cenc-prk"; k.nM = "hevc-dv5-main10-L31-dash-cenc-prk"; k.OF = "hevc-dv5-main10-L40-dash-cenc-prk"; k.oM = "hevc-dv5-main10-L41-dash-cenc-prk"; k.pM = "hevc-dv5-main10-L50-dash-cenc-prk"; k.UU = "hevc-dv5-main10-L51-dash-cenc-prk"; k.d8 = "hevc-hdr-main10-L30-dash-cenc"; k.e8 = "hevc-hdr-main10-L31-dash-cenc"; k.f8 = "hevc-hdr-main10-L40-dash-cenc"; k.g8 = "hevc-hdr-main10-L41-dash-cenc"; k.h8 = "hevc-hdr-main10-L50-dash-cenc"; k.i8 = "hevc-hdr-main10-L51-dash-cenc"; k.yM = "hevc-hdr-main10-L30-dash-cenc-prk"; k.zM = "hevc-hdr-main10-L31-dash-cenc-prk"; k.ZF = "hevc-hdr-main10-L40-dash-cenc-prk"; k.AM = "hevc-hdr-main10-L41-dash-cenc-prk"; k.oV = "hevc-hdr-main10-L50-dash-cenc-prk"; k.pV = "hevc-hdr-main10-L51-dash-cenc-prk"; k.xDa = "vp9-profile0-L21-dash-cenc"; k.oN = "vp9-profile0-L30-dash-cenc"; k.pN = "vp9-profile0-L31-dash-cenc"; k.qN = "vp9-profile0-L40-dash-cenc"; k.sDa = "vp9-profile2-L30-dash-cenc-prk"; k.tDa = "vp9-profile2-L31-dash-cenc-prk"; k.uDa = "vp9-profile2-L40-dash-cenc-prk"; k.vDa = "vp9-profile2-L50-dash-cenc-prk"; k.wDa = "vp9-profile2-L51-dash-cenc-prk"; k.Nsa = "av1-main-L20-dash-cbcs-prk"; k.Osa = "av1-main-L21-dash-cbcs-prk"; k.Psa = "av1-main-L30-dash-cbcs-prk"; k.Qsa = "av1-main-L31-dash-cbcs-prk"; k.Rsa = "av1-main-L40-dash-cbcs-prk"; k.Ssa = "av1-main-L41-dash-cbcs-prk"; k.Tsa = "av1-main-L50-dash-cbcs-prk"; k.Usa = "av1-main-L51-dash-cbcs-prk";
|
但不是每一种都能通过这种方式在 chrome 上获取, 大部分随意组合的 profile 都会报错。
解密密匙的获取
安装了 widevine-l3-decryptor 之后,只要是 chrome 上在放 Netflix 的视频,控制台就会输出
1
| WidevineDecryptor: Found key: 709487bfa80678a4fd32fd26cbf42b60 (KID=00000000056167750000000000000000)
|
由于 Netflix 准备了很多码率和分辨率的视频,大部分时候它们不一定公用同一个密匙,这个时候会输出很多条。不用一个个的试,manifest 里面已经包含了对应的 KID 信息
1 2 3 4 5 6 7
| "trackType": "PRIMARY", "content_profile": "playready-h264mpl40-dash", "bitrate": 6490, "peakBitrate": 0, "dimensionsCount": 2, "dimensionsLabel": "2D", "drmHeaderId": "0000000005e4d1810000000000000000",
|
你只需要把 KID 和 drmHeaderId 对上号即可。
视频流的下载
每一个视频流都有个 urls 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| "urls": [ { "url": "https://ipv4-c035-hkg001-ix.1.oca.nflxvideo.net/?o=xxx", "cdn_id": 63365 }, { "url": "https://ipv4-c033-hkg001-ix.1.oca.nflxvideo.net/?o=xxx", "cdn_id": 63363 }, { "url": "https://ipv4-c104-sea001-ix.1.oca.nflxvideo.net/?o=xxx", "cdn_id": 18327 } ]
|
包含三个下载地址,这个三个地址下的东西是一样的,只是对应不同的 cdn 而已,任选一个即可。可以直接用 chrome 下载。下载完成后重命名下载的文件, <任意名称>.mp4 即可。
视频流解密
我用的 bento4 套件,命令如下
1
| mp4decrypt --key 2:cac5936c9fcb86756c0a4f2eeb1b5642 sweet.home.s01e01.mp4 sweet.home.s01e01.d.mp4
|
其中 --key <id>:<key>
,的 id 是视频流在 mp4 文件中的 id,可以用 mediainfo 查看。一般不是 1 就是 2。key
就是 widevine-l3-decryptor 输出的解密密匙。后面接 <加密视频文件名>.mp4 <解密视频文件名>.mp4
。
音频的获取
Netflix 提供了 AAC 2.0 / 5.0 和 DDP 2.0 / 5.0 / Atmos(部分) 的音频文件,而且一般还准备了多国语言。对应的 profile 有
1 2 3 4 5 6 7 8 9 10 11 12
| c.qV = "heaac-2-dash"; c.I6a = "heaac-5.1-dash"; c.rV = "heaac-2hq-dash"; c.WW = "xheaac-dash"; c.SU = "ddplus-2.0-dash"; c.i7 = "ddplus-5.1-dash"; c.TU = "ddplus-atmos-dash"; c.j8 = "playready-heaac-2-dash"; c.K6a = "heaac-2-dash-enc"; c.P4a = "ddplus-2.0-dash-enc"; c.Q4a = "ddplus-5.1-dash-enc"; c.J6a = "playready-heaac-2-dash-enc";
|
AAC 的音频 chrome 能抓地址且能正常播放, DDP 的音频 chrome 也能抓,但会导致播放报错。一般我们是抓 DDP 5.1 640 kb/s 码率的那一条音频。如果你只想在移动设备上播放,可以只抓 AAC 2.0 heaac-2hq-dash
。
下载方式和视频流一样,文件名直接改 .mp4 后缀即可。
Atmos 音频的探测与获取 (need help)
我现在有两个问题不能很好解决
- 部分视频有 Atmos 音频,如何探测? 我知道可以用Windows store 的 app 看,我希望直接在 chrome 上就能探测。
- DDP5.1 和 DDP Atmos 的抓取我是通过改 profile 实现抓取的,但是在抓取 DDP 音频之后 chrome 会报错,影响播放,每次该来该去很麻烦,有没有一劳永逸的解决方案?
字幕的获取
字幕直接用 Netflix - subtitle downloader 能很方便的抓取 webvtt 字幕,然后用 subtitle edit 转成 srt 字幕即可。
另外你如果希望抓图片字幕,在 manifest 里面找 nflx-cmisc
格式的下载,文件名改 .zip,然后想办法转蓝光格式的图片字幕或者 vobsub,本人暂未研究。
封装
用 mkvtoolnix,一般是封装最高码率的 1080p 视频,640 k 码率的 DDP 5.1 (有 Atmos 封 Atmos),全部的 srt 字幕。具体规范以后讨论。