Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@

Usage:
# Auto-detect changes (CI mode)
python scripts/sync_plugins_index.py
python .github/scripts/sync_plugins_index.py

# Specify changed files explicitly
python scripts/sync_plugins_index.py plugins/my.filter/my.filter.js
python .github/scripts/sync_plugins_index.py plugins/my.filter/my.filter.js

# Full rebuild — scan every plugin directory
python scripts/sync_plugins_index.py --scan
python .github/scripts/sync_plugins_index.py --scan

# Validate manifests only (no write)
python scripts/sync_plugins_index.py --validate
python .github/scripts/sync_plugins_index.py --validate

# Validate every plugin (used by CI)
python scripts/sync_plugins_index.py --validate --scan
python .github/scripts/sync_plugins_index.py --validate --scan
"""

import json
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/sync-plugins.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
branches: [main]
paths:
- 'plugins/**'
- 'scripts/sync_plugins_index.py'
- '.github/scripts/sync_plugins_index.py'
- '.github/workflows/sync-plugins.yml'

jobs:
Expand All @@ -24,10 +24,10 @@ jobs:
python-version: '3.x'

- name: Validate all plugin manifests
run: python scripts/sync_plugins_index.py --validate --scan
run: python .github/scripts/sync_plugins_index.py --validate --scan

- name: Sync plugins index
run: python scripts/sync_plugins_index.py --scan
run: python .github/scripts/sync_plugins_index.py --scan

- name: Commit & push if changed
run: |
Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/validate-plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ on:
paths:
- 'plugins/**/*.js'
- 'plugins/**/*.cjs'
- 'scripts/sync_plugins_index.py'
- 'plugins/**/test_danmaku/*.xml'
- '.github/scripts/sync_plugins_index.py'
- '.github/workflows/validate-plugin.yml'

jobs:
Expand All @@ -27,7 +28,7 @@ jobs:
run: git show origin/main:plugins.json > plugins.json 2>/dev/null || true

- name: Validate all plugin manifests
run: python scripts/sync_plugins_index.py --validate --scan
run: python .github/scripts/sync_plugins_index.py --validate --scan

- name: Test source plugin
run: node --test plugins/bilibili.link_playback/bilibili.link_playback.test.cjs
- name: Test plugins
run: node --test plugins/*/*.test.cjs
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# macOS Finder metadata
.DS_Store
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const pluginManifest = {

## CI 自动同步索引

PR 合并到 `main` 后,GitHub Actions 会自动运行同步脚本:
PR 合并到 `main` 后,GitHub Actions 会自动运行 [同步脚本](.github/scripts/sync_plugins_index.py):

- **新增插件** — 检测到 `plugins/` 下新目录,解析 `pluginManifest` 并追加到 `plugins.json`。
- **更新插件** — 检测到插件文件变更,重新解析并更新对应条目。
Expand Down
Binary file removed plugins/.DS_Store
Binary file not shown.
149 changes: 91 additions & 58 deletions plugins/better_danmaku_filter/README.md
Original file line number Diff line number Diff line change
@@ -1,88 +1,121 @@
# 智能弹幕精选(better_danmaku_filter)

NipaPlay 弹幕过滤插件,通过多维度评分算法智能筛选优质弹幕,过滤低质量内容。
NipaPlay 弹幕精选插件,按内容评分、局部重复与密度保留弹幕。v1.3.0 提供固定比例、期望条数、自适应三种方案。

在线演示:[www.retr0.xyz/better_danmaku_filter.html](https://www.retr0.xyz/better_danmaku_filter.html)
## 快速设置

## 功能
在 NipaPlay 插件设置中,可按需求选择:

- **质量评分**:根据弹幕长度、字符多样性、重复字符比例等维度打分
- **刷屏过滤**:检测短时间内重复发送的相同弹幕
- **相似去重**:基于编辑距离过滤近似重复弹幕
- **短弹幕过滤**:过滤过短的无意义弹幕
- **无意义短串过滤**:过滤无意义的短数字/字母串(如 `111`、`aaa`、`123`、`4567`),同时保留 `666`、`2333`、`awsl`、`hhh` 等常见网络用语
- **Emoji 处理**:纯 Emoji 弹幕可选择降权或直接过滤
- **英文/数字惩罚**:全英文/数字弹幕降低评分(纯数字重罚、网络用语轻微降权)
- **中文加分 / 优质词加分**:含中文的弹幕信息量更高给予加分,命中「前方高能」「细节」「名场面」等优质关键词额外加分
- **时间分布优化**:对稀疏时间段的弹幕适当加分,避免某段时间弹幕全部被过滤
- **类型区分过滤**:滚动弹幕与顶部/底部弹幕采用不同的去重和刷屏阈值
- **自动精选**:开启「自适应模式」,「自适应舒适密度」先保持默认的 `3`。想减少低分内容可调小,想多保留一些可调大。
- **保留约 98%**:关闭「自适应模式」,「期望弹幕数」填 `0`,「最终保留比例」填 `98`。
- **最多保留 4000 条**:关闭「自适应模式」,「期望弹幕数」填 `4`。保持「未达期望时按比例过滤」关闭,少于或等于 4000 条时原样保留。

## 安装
如果希望不足 4000 条时也精选,可开启「未达期望时按比例过滤」并填写比例。例如原始 2268 条、比例 30%,目标为 680 条;达到或超过 4000 条时仍按期望数截取。

将 `better_danmaku_filter.js` 导入 NipaPlay 插件系统即可。导入后在插件设置中启用。
所有设置在**下次加载弹幕**时生效。自适应默认关闭,已有用户的数量方案会继续沿用。

## 配置
## 三种方案

启用后点击插件旁的扳手图标进入设置,可调整以下参数:
| 方案 | 设置方法 | 数量约定 |
|------|----------|----------|
| 固定比例 | 关闭自适应,期望弹幕数设为 0 | 原始条数 × 保留比例,四舍五入;有足够有效数据时满足目标,非空输入目标至少为 1 条 |
| 期望条数 | 关闭自适应,期望弹幕数 > 0 | 期望值 × 1000 后四舍五入,支持 0.5 等小数;默认不足时原样保留 |
| 自适应 | 开启「自适应模式」 | 根据实际分数分布及每条弹幕前后 15 秒的密度自动调整门槛 |

### 数值参数
开启「未达期望时按比例过滤」后,原始数量严格少于期望值时改用比例;等于或超过时仍使用期望条数。关闭该开关时,不足期望数的数据原样保留。自适应开启后优先于期望数、比例及该开关。

| 参数 | 说明 | 默认值 | 范围 |
|------|------|--------|------|
| 期望弹幕数 | 目标保留弹幕数(以千为单位),0 表示使用保留比例 | 0 | 0+ |
| 最终保留比例 | 保留弹幕的百分比 | 30% | 1-100 |
| 时间窗口 | 刷屏/去重检测的时间窗口(秒) | 5 | 1-30 |
| 最短弹幕长度 | 弹幕最小字符数 | 3 | 1-10 |
| 重复字符阈值 | 单字符占比超过此值视为重复 | 60% | 30-100 |
| 全英数惩罚 | 全英文/数字弹幕的扣分值 | 30 | 0-80 |
固定比例以**本插件收到的原始数组长度**为基数,不再对去重后剩余数据二次乘比例。例如收到 2268 条,设为 98%,目标为 2223 条。上游插件已经删掉的弹幕无法恢复,因此这不代表相对原始文件的保留率。

### 开关
固定数量方案优先满足数量:短串、重复、离题等内容降权后参与排序,优先淘汰低分内容;98% 这类高比例会保留一部分降权内容。比例方案设为 100%,或期望条数方案中原始数量不超过期望数且未触发比例回退时,跳过精选。执行精选时,空白文本和无效时间不能用于满足配额,会在诊断中说明。

| 开关 | 说明 | 默认 |
|------|------|------|
| 未达期望时按比例过滤 | 实际弹幕数少于「期望弹幕数」时,仍按「最终保留比例」进行精选过滤 | 关 |
| 过滤相似弹幕 | 滚动弹幕相似度 > 93%、顶部/底部 > 82% 时去重 | 开 |
| 过滤刷屏弹幕 | 滚动弹幕同一内容 3 次以上、其他类型 2 次以上标记为刷屏 | 开 |
| 过滤过短弹幕 | 长度小于「最短弹幕长度」的弹幕直接过滤 | 开 |
| 过滤无意义短串 | 过滤 `111`/`aaa`/`123` 等无意义短数字字母串,保留常见网络用语 | 开 |
| 允许纯Emoji降权 | 开启时纯 Emoji 弹幕扣 10 分,关闭时扣 40 分 | 开 |
| 高级语义评分 | 弹幕稀疏时间段加分 + 时间分布优化 | 开 |
## 自适应模型

## 评分规则
使用一个可解释的密度压力模型,避免假定所有番剧的分数严格服从正态分布:

每条弹幕基础分 50 分,根据以下维度调整:
1. 先给内容打基础分。以本批基础分的中位数 `m` 和中位绝对偏差 `MAD` 估计中心与离散度:`s = max(8, 1.4826 × MAD)`。
2. 每条弹幕使用前后 15 秒内的原始有效条数,计算局部到达密度 `ρ = 条数 / 30`;重复内容也计入压力。
3. 以 6 秒为估算展示时长,设 `λ = 6ρ`、舒适容量 `C = max(1, round(6 × 舒适密度))`。简单泊松到达模型给出 `p = P(X > C)`,表示超出舒适容量的压力。
4. 门槛为 `clamp(m - s × (3 - 1.5p), 25, 75)`。拥挤时提高门槛,稀疏时放宽,最终保留扣分后评分达到局部门槛的内容。

- **长度**:2 字以下 -30,3-5 字 +5,6-15 字 +15,16-30 字 +10,30 字以上 +5
- **字符多样性**:字符种类占比越高加分越多(最高 +20)
- **重复字符**:单字符占比超过阈值时扣分
- **纯 Emoji**:降权 -10 或过滤 -40
- **中文加分**:含中文字符 +8
- **全英文/数字**:分级扣分——纯数字重罚(惩罚值 +15),普通全英数按配置扣分,命中网络用语白名单仅轻微降权(惩罚值 ×0.3)
- **标点过多**:标点占比 > 40% 时 -10
- **优质关键词**:命中「前方高能」「细节」「名场面」等 +8
- **弹幕类型**:所有类型平等竞争,无额外加分
- **稀疏时段**:所在时间段弹幕少于 3 条时 +15
默认舒适密度为 **3 条/秒**;调小会更严格,调大会更宽松。它用于调整低分尾部的淘汰力度,**不是输出条数的硬上限**。高分内容不会只因拥挤就被强制删掉,密集重复则会单独降权。

最终按评分从高到低排序,取前 N 条保留。
真实弹幕有集中欢呼和明显时间相关性,泊松假设是压力估算;6 秒也不是宿主渲染时长的精确测量。分数上下限和离散度下限用于防止同分、极端样本导致门槛失控。

## 工作流程
## 内容与重复评分

```
加载弹幕 → 短弹幕预过滤 → 无意义短串过滤 → 刷屏检测 → 相似去重 → 质量评分
→ 时间分布优化 → 按比例/期望数截取 → 输出精选结果
- 根据字符长度、多样性、单字重复比例、标点与英数惩罚评分;字符统计按 Unicode 字符处理。
- 保护「泪目」「哈哈」「啊?」、扩展长度的 `233333…` / `666…`、常见番剧缩写、Emoji 和常见颜文字。两字中文可能是角色名或有效评论,采用较轻的短内容扣分。
- 英数网络用语按配置惩罚的 15% 扣分;普通全英数按配置扣分,纯数字额外扣 15 分。`111` 不再与无意义短串规则自相矛盾地进入白名单。
- 时间窗口内相同内容忽略空白、常用标点与英文大小写;模糊去重采用滚动弹幕 > 93%、其他类型 > 82% 的编辑距离相似度。不同的极短词不作模糊合并。
- 重复内容扣 35 分,达到滚动弹幕 3 次/其他类型 2 次的刷屏阈值再扣 45 分。以窗口代表为锚,连续重复不会无限延长抑制时间,每个新窗口仍可保留一条代表。
- 「过滤离题与攻击内容」仅对独立签到、日期、观看次数、明显加群广告和明确指向他人的攻击扣分。剧情中的「死了」「后面」、题材相关用词不作宽泛屏蔽。
- 超过 40 字的长内容按屏幕占用扣分;「细节」「伏笔」「致敬」等仅加 5 分,不再直接奖励「高能预警」。
- 「稀疏时段保护」对前后 15 秒内不足 3 条的内容加 8 分。固定数量截取时,同分内容确定性地散布在时间轴上,避免只保留开头。

这些是本地启发式规则,不具备剧情理解或可靠的剧透识别能力。可关闭对应过滤开关处理个人偏好。超长内容只做精确去重;极密窗口的模糊比较限制为最近 200 个代表,以控制开销。

## 配置

| 参数 | 默认值 | 范围/作用 |
|------|--------|-----------|
| 自适应模式 | 关 | 开启后优先使用自适应 |
| 自适应舒适密度 | 3 | 0.2–10 条/秒,仅自适应生效 |
| 期望弹幕数 | 0 | 非负,单位千条,0 使用比例 |
| 最终保留比例 | 30% | 1–100,相对本插件收到的原始数量 |
| 未达期望时按比例过滤 | 关 | 仅固定数量方案生效 |
| 时间窗口 | 5 秒 | 1–30,刷屏和相似检测窗口 |
| 最短弹幕长度 | 3 | 1–10,过短内容降权,常见反应例外 |
| 重复字符阈值 | 60% | 30–100,单字符占比超限扣分 |
| 全英数惩罚 | 30 | 0–80 |
| 过滤相似弹幕 | 开 | 时间窗口内的相同或近似内容降权 |
| 过滤刷屏弹幕 | 开 | 短时间内反复出现的相同内容降权 |
| 过滤过短弹幕 | 开 | 按最短长度降权,保护常见情绪表达 |
| 过滤无意义短串 | 开 | 无意义数字、字母及符号短串降权 |
| 允许纯 Emoji 降权 | 开 | 开时扣 10 分,关时扣 40 分 |
| 过滤离题与攻击内容 | 开 | 保守的内容规则,可单独关闭 |
| 稀疏时段保护 | 开 | 兼容原来的 `filterAdvanced` 设置键 |

「最近筛选结果」显示实际原始数、保留数、保留率、固定目标、降权内容保留数或自适应门槛,以及淘汰原因。原因可能重叠,不能相加当作淘汰总数。设置下次加载弹幕时生效。

## 样本验证

[三份真实弹幕复测报告](ANALYSIS.md) 包含旧版对比、实际保留率与淘汰边界示例。样本导入工具将这三份 XML 的毫秒时间换算为秒;插件收到的宿主数据始终使用秒。

以下使用默认评分开关,自适应舒适密度为 2 条/秒;比例模式中的期望弹幕数为 0:

| 样本 | 原始条数 | v1.2.0 设置 98% | v1.3.0 设置 98% | v1.3.0 自适应 |
|------|---------:|----------------:|----------------:|---------------:|
| 轻音少女 第1话 | 2268 | 84.17% | 98.02% | 88.36% |
| CLANNAD AFTER STORY 第22话 | 5585 | 82.88% | 97.99% | 77.65% |
| 妄想学生会 第1话 | 3824 | 83.94% | 98.01% | 84.00% |

固定比例的微小偏差来自整数条数的四舍五入。自适应保留率由内容和局部密度决定,不会固定为某个百分比。

```bash
node --test plugins/better_danmaku_filter/better_danmaku_filter.test.cjs
node plugins/better_danmaku_filter/analyze_real_danmaku.cjs
# 可选:对比保存的 v1.2.0 原始脚本
node plugins/better_danmaku_filter/analyze_real_danmaku.cjs --baseline /path/to/v1.2.0.js
```

当「期望弹幕数」大于 0 时,默认优先按期望数截取;若实际弹幕数不足则跳过过滤。开启「未达期望时按比例过滤」后,实际弹幕数不足时会改按「最终保留比例」截取,实际弹幕数达到或超过期望值时仍按期望数截取。
纯 Node 回归不验证 NipaPlay 内的实际观看效果。缺少 `test_danmaku` 时仍运行合成数据和宿主事件测试;真实样本测试需要本地 XML 和 Python 3 标准库。

## 依赖
## 安装与依赖

- NipaPlay >= 1.10.6
- 权限:`danmaku.modify`(修改弹幕显示)、`ui.dialog`(显示提示)
将 `better_danmaku_filter.js` 导入 NipaPlay 插件系统。需要 NipaPlay >= 1.10.6,权限为 `danmaku.modify`、`ui.dialog`;JS 插件运行时不支持 Web。

---
在线演示:[www.retr0.xyz/better_danmaku_filter.html](https://www.retr0.xyz/better_danmaku_filter.html)(本次未同步在线演示)。

## 更新日志

### v1.3.0

- 修复最终比例对预过滤后剩余数据二次截取的问题,固定数量模式以原始数组长度确定目标。
- 新增自适应模式、舒适密度设置和最近筛选诊断,兼容既有数量方案与开关。
- 改善短中文、网络情绪、番剧缩写、Emoji、颜文字与持续欢呼的保留,加入保守的离题/攻击降权和超长遮挡扣分。
- 支持小数千条,约束参数范围,保留宿主扩展字段,修复同一时间/空数据边界和同分截取的时间偏置。
- 增加真实 XML 分析工具、回归测试与 CI 测试入口。

### v1.2.0

- **新增**:「未达期望时按比例过滤」开关;开启后,实际弹幕数少于期望数时仍按最终保留比例执行精选
Expand Down
Loading
Loading