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-CDdN0tOz.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(`<h2 id="webmask-资源" tabindex="-1"><a class="header-anchor" href="#webmask-资源"><span>webmask 资源</span></a></h2><p>获取的 url 没有权鉴，不需要后面的参数也可以获取。</p><p>示例：</p><div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="background-color:#1E1E1E;color:#D4D4D4;"><pre class="shiki dark-plus vp-code"><code class="language-shell"><span class="line"><span style="color:#DCDCAA;">curl</span><span style="color:#569CD6;"> -G</span><span style="color:#CE9178;"> https://upos-sz-staticcos-cmask.bilivideo.com/cmaskboss/825851971_30_0.webmask</span><span style="color:#D4D4D4;"> &gt;&gt; </span><span style="color:#CE9178;">825851971_30_0.webmask</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div></div></div><p>下载后是二进制文件。</p><h2 id="webmask-二进制读取" tabindex="-1"><a class="header-anchor" href="#webmask-二进制读取"><span>webmask 二进制读取</span></a></h2><p>参考：<a href="https://github.com/andrewvy/webmask-renderer" target="_blank" rel="noopener noreferrer">andrewvy/webmask-renderer</a></p><table><thead><tr><th>name</th><th>offset</th><th>length</th><th>type</th><th>desc</th></tr></thead><tbody><tr><td>mask</td><td>0</td><td>4</td><td>char</td><td>&#39;MASK&#39; 文件头</td></tr><tr><td>version</td><td>4</td><td>4</td><td>int</td><td>是1</td></tr><tr><td>vU</td><td>8</td><td>4</td><td>?</td><td>不知道是干什么的</td></tr><tr><td>Ly</td><td>12</td><td>4</td><td>int</td><td>后续数据的段数</td></tr><tr><td>time_1</td><td>16</td><td>8</td><td>long</td><td>第一段对应视频开始时间</td></tr><tr><td>offset_1</td><td>24</td><td>8</td><td>long</td><td>第一段蒙版信息开始处对应二进制偏移</td></tr><tr><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td></tr><tr><td>time_{Ly}</td><td>16+(Ly-1)*16</td><td>8</td><td>long</td><td>第 <code>Ly</code> 段对应视频开始时间</td></tr><tr><td>offset_{Ly}</td><td>24+(Ly-1)*16</td><td>8</td><td>long</td><td>第 <code>Ly</code> 段蒙版信息开始处对应二进制偏移</td></tr><tr><td>segments_1</td><td>由前面offset_1提供</td><td>由 <code>offset_2-offset_1</code> 计算得到</td><td>binary</td><td>蒙版信息块，使用 gzip 压缩</td></tr><tr><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td></tr></tbody></table><p>蒙版信息块是经过 gzip 压缩文本得到的二进制。解压缩后得到一个字节串。</p><p>前 16 字节是两个 long，记为 <code>left</code> 和 <code>right</code>，暂时不清楚其作用。<code>left</code> 似乎和平均每张蒙版的时间有关。<code>right</code> 总是 <code>i*10000</code>，<code>i</code> 是从 0 开始数的数据段次序。</p><p>后面是各个 svg 文本直接拼起来，可以直接通过 svg 格式头 <code>data:image/svg+xml;base64,</code> 分开。</p><p>Python 示例：</p><div class="language-python line-numbers-mode" data-highlighter="shiki" data-ext="python" style="background-color:#1E1E1E;color:#D4D4D4;"><pre class="shiki dark-plus vp-code"><code class="language-python"><span class="line"><span style="color:#C586C0;">from</span><span style="color:#D4D4D4;"> struct </span><span style="color:#C586C0;">import</span><span style="color:#D4D4D4;"> unpack</span></span>
<span class="line"><span style="color:#C586C0;">import</span><span style="color:#D4D4D4;"> gzip</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4;">f = </span><span style="color:#DCDCAA;">open</span><span style="color:#D4D4D4;">(</span><span style="color:#CE9178;">&#39;你的 webmask&#39;</span><span style="color:#D4D4D4;">, </span><span style="color:#CE9178;">&#39;rb&#39;</span><span style="color:#D4D4D4;">)</span></span>
<span class="line"><span style="color:#D4D4D4;">buf = f.read()</span></span>
<span class="line"><span style="color:#D4D4D4;">_Ly = buf[</span><span style="color:#B5CEA8;">12</span><span style="color:#D4D4D4;">:</span><span style="color:#B5CEA8;">16</span><span style="color:#D4D4D4;">]</span></span>
<span class="line"><span style="color:#D4D4D4;">Ly = unpack(</span><span style="color:#CE9178;">&#39;&gt;i&#39;</span><span style="color:#D4D4D4;">, _Ly)[</span><span style="color:#B5CEA8;">0</span><span style="color:#D4D4D4;">] </span><span style="color:#6A9955;"># 大端序 int</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4;">times = []</span></span>
<span class="line"><span style="color:#D4D4D4;">offsets = []</span></span>
<span class="line"><span style="color:#C586C0;">for</span><span style="color:#D4D4D4;"> idx </span><span style="color:#C586C0;">in</span><span style="color:#DCDCAA;"> range</span><span style="color:#D4D4D4;">(Ly):</span></span>
<span class="line"><span style="color:#D4D4D4;">    op = </span><span style="color:#B5CEA8;">16</span><span style="color:#D4D4D4;"> + idx * </span><span style="color:#B5CEA8;">16</span><span style="color:#6A9955;">  # 个人习惯，我算偏移时喜欢用 \`op\` 和 \`ed\` 作为开始和结束的名字。</span></span>
<span class="line"><span style="color:#D4D4D4;">    time = unpack(</span><span style="color:#CE9178;">&#39;&gt;q&#39;</span><span style="color:#D4D4D4;">, buf[op: op+</span><span style="color:#B5CEA8;">8</span><span style="color:#D4D4D4;">])[</span><span style="color:#B5CEA8;">0</span><span style="color:#D4D4D4;">]</span></span>
<span class="line"><span style="color:#D4D4D4;">    offset = unpack(</span><span style="color:#CE9178;">&#39;&gt;q&#39;</span><span style="color:#D4D4D4;">, buf[op+</span><span style="color:#B5CEA8;">8</span><span style="color:#D4D4D4;">: op+</span><span style="color:#B5CEA8;">16</span><span style="color:#D4D4D4;">])[</span><span style="color:#B5CEA8;">0</span><span style="color:#D4D4D4;">]</span></span>
<span class="line"><span style="color:#D4D4D4;">    times.append(time)</span></span>
<span class="line"><span style="color:#D4D4D4;">    offsets.append(offset)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4;">frames = []</span></span>
<span class="line"><span style="color:#C586C0;">for</span><span style="color:#D4D4D4;"> idx </span><span style="color:#C586C0;">in</span><span style="color:#DCDCAA;"> range</span><span style="color:#D4D4D4;">(Ly):</span></span>
<span class="line"><span style="color:#D4D4D4;">    op = offsets[idx]</span></span>
<span class="line"><span style="color:#C586C0;">    if</span><span style="color:#D4D4D4;"> idx == Ly - </span><span style="color:#B5CEA8;">1</span><span style="color:#D4D4D4;">: ed = </span><span style="color:#DCDCAA;">len</span><span style="color:#D4D4D4;">(buf)</span></span>
<span class="line"><span style="color:#C586C0;">    else</span><span style="color:#D4D4D4;">: ed = offsets[idx+</span><span style="color:#B5CEA8;">1</span><span style="color:#D4D4D4;">]</span></span>
<span class="line"><span style="color:#D4D4D4;">    ba = buf[op: ed]</span></span>
<span class="line"><span style="color:#D4D4D4;">    bad = gzip.decompress(ba)</span></span>
<span class="line"><span style="color:#D4D4D4;">    badl = bad.split(</span><span style="color:#569CD6;">b</span><span style="color:#CE9178;">&#39;data:image/svg+xml;base64,&#39;</span><span style="color:#D4D4D4;">)</span></span>
<span class="line"><span style="color:#6A9955;">    # badl[0]是16字节，\`left\` 和 \`right\`</span></span>
<span class="line"><span style="color:#D4D4D4;">    frames.append(badl[</span><span style="color:#B5CEA8;">1</span><span style="color:#D4D4D4;">: ])</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>`,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};
