import{_ as l,c as t,b as n,a as o,d as a,e as p,w as d,r,o as i}from"./app-Dgsdh8A6.js";const c={};function D(y,s){const e=r("RouteLink");return i(),t("div",null,[s[3]||(s[3]=n("h1",{id:"智能防挡弹幕",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#智能防挡弹幕"},[n("span",null,"智能防挡弹幕")])],-1)),s[4]||(s[4]=n("p",null,"B 站部分视频提供“智能防挡弹幕”功能,其原理是提供了一个 webmask 二进制文件,其中保存了视频中各个位置的 svg 格式蒙版。",-1)),s[5]||(s[5]=n("p",null,"首先需要获取 webmask 资源的地址。",-1)),s[6]||(s[6]=n("h2",{id:"获取-webmask-资源地址",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#获取-webmask-资源地址"},[n("span",null,"获取 webmask 资源地址")])],-1)),n("p",null,[s[1]||(s[1]=a("通过 ")),p(e,{to:"/docs/video/player.html"},{default:d(()=>s[0]||(s[0]=[a("web 播放器资源接口")])),_:1,__:[0]}),s[2]||(s[2]=a(" 获取 webmask 二进制文件的地址。"))]),s[7]||(s[7]=o(`

webmask 资源

获取的 url 没有权鉴,不需要后面的参数也可以获取。

示例:

curl -G https://upos-sz-staticcos-cmask.bilivideo.com/cmaskboss/825851971_30_0.webmask >> 825851971_30_0.webmask

下载后是二进制文件。

webmask 二进制读取

参考:andrewvy/webmask-renderer

nameoffsetlengthtypedesc
mask04char'MASK' 文件头
version44int是1
vU84?不知道是干什么的
Ly124int后续数据的段数
time_1168long第一段对应视频开始时间
offset_1248long第一段蒙版信息开始处对应二进制偏移
...............
time_{Ly}16+(Ly-1)*168longLy 段对应视频开始时间
offset_{Ly}24+(Ly-1)*168longLy 段蒙版信息开始处对应二进制偏移
segments_1由前面offset_1提供offset_2-offset_1 计算得到binary蒙版信息块,使用 gzip 压缩
...............

蒙版信息块是经过 gzip 压缩文本得到的二进制。解压缩后得到一个字节串。

前 16 字节是两个 long,记为 leftright,暂时不清楚其作用。left 似乎和平均每张蒙版的时间有关。right 总是 i*10000i 是从 0 开始数的数据段次序。

后面是各个 svg 文本直接拼起来,可以直接通过 svg 格式头 data:image/svg+xml;base64, 分开。

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: ])
`,13))])}const b=l(c,[["render",D]]),u=JSON.parse('{"path":"/docs/danmaku/webmask.html","title":"智能防挡弹幕","lang":"zh-CN","frontmatter":{},"git":{"updatedTime":1714889183000,"contributors":[{"name":"holazzer","username":"holazzer","email":"45134013+holazzer@users.noreply.github.com","commits":1,"url":"https://github.com/holazzer"},{"name":"z0z0r4","username":"z0z0r4","email":"z0z0r4@outlook.com","commits":1,"url":"https://github.com/z0z0r4"}],"changelog":[{"hash":"1d557be039a887ef25bc25da0c396eb5e03eb42e","time":1714889183000,"email":"45134013+holazzer@users.noreply.github.com","author":"Han Zhang","message":"智能防挡弹幕 (#659)","coAuthors":[{"name":"z0z0r4","email":"z0z0r4@outlook.com"}]}]},"filePathRelative":"docs/danmaku/webmask.md"}');export{b as comp,u as data};