diff --git a/docs/danmaku/webmask.md b/docs/danmaku/webmask.md new file mode 100644 index 0000000..ab0fff8 --- /dev/null +++ b/docs/danmaku/webmask.md @@ -0,0 +1,80 @@ +# 智能防挡弹幕 + +B 站部分视频提供“智能防挡弹幕”功能,其原理是提供了一个 webmask 二进制文件,其中保存了视频中各个位置的 svg 格式蒙版。 + +首先需要获取 webmask 资源的地址。 + +## 获取 webmask 资源地址 + +通过 [web 播放器资源接口](../video/player.md) 获取 webmask 二进制文件的地址。 + + +## webmask 资源 + +获取的 url 没有权鉴,不需要后面的参数也可以获取。 + +示例: + +```shell +curl -G https://upos-sz-staticcos-cmask.bilivideo.com/cmaskboss/825851971_30_0.webmask >> 825851971_30_0.webmask +``` + +下载后是二进制文件。 + + +## webmask 二进制读取 + +参考:[andrewvy/webmask-renderer](https://github.com/andrewvy/webmask-renderer) + +name | offset | length | type | desc +--- | ------ | ------ | ---- | ---- +mask | 0 | 4 | char | 'MASK' 文件头 +version| 4 | 4 | int | 是1 +vU | 8 | 4 | ? | 不知道是干什么的 +Ly | 12 | 4 | int | 后续数据的段数 +time_1 | 16 | 8 | long | 第一段对应视频开始时间 +offset_1 | 24 | 8 | long | 第一段蒙版信息开始处对应二进制偏移 +...|...|...|...|... +time_{Ly} |16+(Ly-1)*16| 8 | long | 第 `Ly` 段对应视频开始时间 +offset_{Ly} |24+(Ly-1)*16| 8 | long | 第 `Ly` 段蒙版信息开始处对应二进制偏移 +segments_1| 由前面offset_1提供 | 由 `offset_2-offset_1` 计算得到 | binary | 蒙版信息块,使用 gzip 压缩 +...|...|...|...|... + + +蒙版信息块是经过 gzip 压缩文本得到的二进制。解压缩后得到一个字节串。 + +前 16 字节是两个 long,记为 `left` 和 `right`,暂时不清楚其作用。`left` 似乎和平均每张蒙版的时间有关。`right` 总是 `i*10000`,`i` 是从 0 开始数的数据段次序。 + +后面是各个 svg 文本直接拼起来,可以直接通过 svg 格式头 `data:image/svg+xml;base64,` 分开。 + + +Python 示例: +```python +from struct import unpack +import gzip + +f = open('你的 webmask', 'rb') +buf = f.read() +_Ly = buf[12:16] +Ly = unpack('>i', _Ly)[0] # 大端序 int + +times = [] +offsets = [] +for idx in range(Ly): + op = 16 + idx * 16 # 个人习惯,我算偏移时喜欢用 `op` 和 `ed` 作为开始和结束的名字。 + time = unpack('>q', buf[op: op+8])[0] + offset = unpack('>q', buf[op+8: op+16])[0] + times.append(time) + offsets.append(offset) + +frames = [] +for idx in range(Ly): + op = offsets[idx] + if idx == Ly - 1: ed = len(buf) + else: ed = offsets[idx+1] + ba = buf[op: ed] + bad = gzip.decompress(ba) + badl = bad.split(b'data:image/svg+xml;base64,') + # badl[0]是16字节,`left` 和 `right` + frames.append(badl[1: ]) +``` diff --git a/docs/video/player.md b/docs/video/player.md new file mode 100644 index 0000000..1d39959 --- /dev/null +++ b/docs/video/player.md @@ -0,0 +1,153 @@ +# web 播放器信息 + +web 播放器的信息接口,提供正常播放需要的元数据,包括:智能防挡弹幕、字幕、章节看点等。 + +> https://api.bilibili.com/x/player/wbi/v2 + +*请求方式:GET* + +**url 参数:** + +| 参数名 | 类型 | 内容 | 必要性 | 备注 | +| ------ | ---- | --------- | ----------- | ----------------- | +| aid | num | 稿件 avid | 必要 (可选) | aid 与 bvid 任选 | +| bvid | str | 稿件 bvid | 必要 (可选) | aid 与 bvid 任选 | +| cid | num | 稿件 cid | 必要 | | +| w_rid | str | 未知 | 不必要 | | +| wts | num | 当前 unix 时间戳 | 不必要 | | + + +**json 回复:** + +根对象: + +| 字段 | 类型 | 内容 | 备注 | +| ------- | ---- | -------- | --------------------------- | +| code | num | 返回值 | 0:成功
-400:请求错误 | +| message | str | 错误信息 | 默认为 0 | +| ttl | num | 1 | | +| data | obj | 数据本体 | | + +`data` 对象: + +| 字段 | 类型 | 内容 | 备注 | +| --------- | ----- | -------- | ---- | +|aid | num | 视频 aid | | +|bvid | str | 视频 bvid | | +|cid | num | 视频 cid | | +|dm_mask | obj | | webmask 信息(如果没有这一项,说明这个视频没有防挡功能) | +|subtitle | obj | | 字幕信息(需要登录,不登录此项内容为 `[]` )| +|view_points| array | | 章节看点信息 | +| 其他 | ... | | 主要是观看记录、使用者等级权限等个人信息 | + + +`dm_mask`对象(如果有): + +| 字段 | 类型 | 内容 | 备注 | +| --------- | ----- | -------- | ---- | +|cid | num | 视频 cid | | +|plat | num | 未知 | | +|fps | num | webmask 取样 fps | | +|time | num | 未知 | | +|mask_url | str | webmask 资源 url | | + +解析 webmask 请看 [智能防挡弹幕](../danmaku/webmask.md) + +`subtitle`对象: +| 字段 | 类型 | 内容 | 备注 | +| --------- | ----- | -------- | ---- | +|allow_submit|bool | true | | +| lan | str | "" | | +|lan_doc | str | "" | | | +|subtitles| array | | 不登录为 `[]` | + +`subtitles` 数组内的元素: + +| 字段 | 类型 | 内容 | 备注 | +| --------- | ----- | -------- | ---- | +| ai_status | num | | | +| ai_type | num | | | +| id | num | | | +|id_str | str| | 和 id 不一样 | +| is_lock | bool | | +| lan | str | 语言类型英文字母缩写 || +| lan_doc | str| 语言类型中文名称 | | +|subtitle_url|str| 资源 url 地址 | | +|type| num | 0 | | + + +`view_point` 数组内的元素: +| 字段 | 类型 | 内容 | 备注 | +| --------- | ----- | -------- | ---- | +| content | num | 章节名 | | +| from | num | | | +| to | num | | | +| type | num | | | +| imgUrl | str | 图片资源地址 | | +| logoUrl | str | "" | | + +示例: + +```shell +curl -G https://api.bilibili.com/x/player/wbi/v2?aid=515345690&cid=825851971 +``` + +```json +{ + "code": 0, + "message": "0", + "ttl": 1, + "data": { + "aid": 515345690, + "bvid": "BV1Fg411D7Jy", + ... // 省略 + "dm_mask": { + "cid": 825851971, + "plat": 0, + "fps": 30, + "time": 0, + "mask_url": "//upos-sz-staticcos-cmask.bilivideo.com/cmaskboss/825851971_30_0.webmask?trid=219266863a1442baa05086b4285ba923B&orderid=0,1&logo=00000000" + }, + "view_points": [ + { + "type": 2, + "from": 0, + "to": 27, + "content": "狗啃的", + "imgUrl": "http://i0.hdslb.com/bfs/vchapter/825851971_0.jpg", + "logoUrl": "" + }, + { + "type": 2, + "from": 27, + "to": 63, + "content": "椒牌泡菜", + "imgUrl": "http://i0.hdslb.com/bfs/vchapter/825851971_27.jpg", + "logoUrl": "" + }, ... // 省略 + ], + "subtitle": { + "allow_submit": true, + "lan": "", + "lan_doc": "", + "subtitles": [], // 未登录,下面是登录的版本 + "subtitles":[ + { + "id": 1042985852759993300, + "lan": "ai-zh", + "lan_doc": "中文(自动生成)", + "is_lock": false, + "subtitle_url": "//aisubtitle.hdslb.com/bfs/ai_subtitle/prod/5153456908258519712094280c7c2884b77929bab82f64530f?auth_key=1714795727-a8eb254b60bc4a73bc8662da51005340-0-1c305894e48e959979b163636461fb8f", + "type": 1, + "id_str": "1042985852759993344", + "ai_type": 0, + "ai_status": 2 + } + ] + } + } +} +``` + + +