Skip to content

Commit

Permalink
Optimize danmaku matching for segment's skipping
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoxuan010 committed Dec 25, 2024
1 parent 8a14d22 commit 829c604
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 54 deletions.
24 changes: 18 additions & 6 deletions public/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -769,17 +769,29 @@
"checkTimeDanmakuSkip": {
"message": "检查片段附近是否已有上传的跳过信息,已有则不触发此功能"
},
"danmakuRegexPattern": {
"message": "匹配正则: "
"danmakuTimeMatchingRegexPattern": {
"message": "空降时间匹配正则: "
},
"danmakuRegexPatternPlaceholder": {
"message": "正则要求能捕获2到3组数字"
"message": "能匹配2~3组数字的正则表达式"
},
"danmakuRegexTitle": {
"message": "请输入一个匹配时间格式的正则表达式,要求能捕获2到3组数字。如果不清楚如何写正则表达式,请勿修改此项"
"message": "请输入一个匹配时间格式的正则表达式,要求能捕获2~3组数字。如果不清楚如何写正则表达式,请勿修改此项"
},
"danmakuRegexPatternDescription": {
"message": "启用基于弹幕的跳过功能,目前处于实验状态"
"danmakuTimeMatchingRegexPatternDescription": {
"message": "空降时间匹配类似“01:23”“四分五十六秒”“1分2秒”等格式的弹幕。"
},
"danmakuOffsetMatchingRegexPattern": {
"message": "时间偏移匹配正则: "
},
"danmakuOffsetRegexPatternPlaceholder": {
"message": "能匹配时间偏移指令的正则表达式"
},
"danmakuOffsetRegexTitle": {
"message": "请输入一个匹配时间偏移指令的正则表达式。如果不清楚如何写正则表达式,请勿修改此项"
},
"danmakuOffsetRegexPatternDescription": {
"message": "时间偏移匹配类似“向右12下”“右方向34次”等格式的弹幕,并以此计算空降时间。"
},
"muteSegments": {
"message": "允许片段静音而不是跳过"
Expand Down
24 changes: 18 additions & 6 deletions public/_locales/zh_CN/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -769,17 +769,29 @@
"checkTimeDanmakuSkip": {
"message": "检查片段附近是否已有上传的跳过信息,已有则不触发此功能"
},
"danmakuRegexPattern": {
"message": "匹配正则: "
"danmakuTimeMatchingRegexPattern": {
"message": "空降时间匹配正则: "
},
"danmakuRegexPatternPlaceholder": {
"message": "正则要求能捕获2到3组数字"
"message": "能匹配2~3组数字的正则表达式"
},
"danmakuRegexTitle": {
"message": "请输入一个匹配时间格式的正则表达式,要求能捕获2到3组数字。如果不清楚如何写正则表达式,请勿修改此项"
"message": "请输入一个匹配时间格式的正则表达式,要求能捕获2~3组数字。如果不清楚如何写正则表达式,请勿修改此项"
},
"danmakuRegexPatternDescription": {
"message": "启用基于弹幕的跳过功能,目前处于实验状态"
"danmakuTimeMatchingRegexPatternDescription": {
"message": "空降时间匹配类似“01:23”“四分五十六秒”“1分2秒”等格式的弹幕。"
},
"danmakuOffsetMatchingRegexPattern": {
"message": "时间偏移匹配正则: "
},
"danmakuOffsetRegexPatternPlaceholder": {
"message": "能匹配时间偏移指令的正则表达式"
},
"danmakuOffsetRegexTitle": {
"message": "请输入一个匹配时间偏移指令的正则表达式。如果不清楚如何写正则表达式,请勿修改此项"
},
"danmakuOffsetRegexPatternDescription": {
"message": "时间偏移匹配类似“向右12下”“右方向34次”等格式的弹幕,并以此计算空降时间。"
},
"muteSegments": {
"message": "允许片段静音而不是跳过"
Expand Down
24 changes: 18 additions & 6 deletions public/_locales/zh_TW/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -769,18 +769,30 @@
"checkTimeDanmakuSkip": {
"message": "檢查片段附近是否已有上傳的跳過資訊,已有則不觸發此功能"
},
"danmakuRegexPattern": {
"message": "匹配正則: "
"danmakuTimeMatchingRegexPattern": {
"message": "空降時間匹配正則: "
},
"danmakuRegexPatternPlaceholder": {
"message": "正則要求能捕獲2到3組數字"
"message": "能匹配2~3組數字的正則表達式"
},
"danmakuRegexTitle": {
"message": "請輸入一個匹配時間格式的正則表達式,要求能捕獲2到3組數字。如果不清楚如何寫正則表達式,請勿修改此項"
"message": "請輸入一個匹配時間格式的正則表達式,要求能捕獲2~3組數字。如果不清楚如何寫正則表達式,請勿修改此項"
},
"danmakuRegexPatternDescription": {
"message": "啟用基於彈幕的跳過功能,目前處於實驗狀態"
"danmakuTimeMatchingRegexPatternDescription": {
"message": "空降時間匹配類似“01:23”“四分五十六秒”“1分2秒”等格式的彈幕。"
},
"danmakuOffsetMatchingRegexPattern": {
"message": "時間偏移匹配正則: "
},
"danmakuOffsetRegexPatternPlaceholder": {
"message": "能匹配時間偏移指令的正則表達式"
},
"danmakuOffsetRegexTitle": {
"message": "請輸入一個匹配時間偏移指令的正則表達式。如果不清楚如何寫正則表達式,請勿修改此項"
},
"danmakuOffsetRegexPatternDescription": {
"message": "時間偏移匹配類似“向右12下”“右方向34次”等格式的彈幕,並以此計算空降時間。"
},
"muteSegments": {
"message": "允許片段靜音而不是跳過"
},
Expand Down
23 changes: 17 additions & 6 deletions public/options/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -751,16 +751,27 @@ <h2>__MSG_exportOtherData__</h2>
<label class="switch-label" for="checkTimeDanmakuSkip"> __MSG_checkTimeDanmakuSkip__</label>
</div>
</div>

<div data-type="text-change" data-sync="danmakuRegexPattern">
<div data-type="text-change" data-sync="danmakuTimeMatchingRegexPattern">
<div class="input-container">
<label for="danmakuRegexPattern">__MSG_danmakuRegexPattern__</label>
<input id="danmakuRegexPattern" class="option-text-box" type="text"
placeholder="__MSG_danmakuRegexPatternPlaceholder__" title="__MSG_danmakuRegexTitle__" />
<label for="danmakuTimeMatchingRegexPattern">__MSG_danmakuTimeMatchingRegexPattern__</label>
<input id="danmakuTimeMatchingRegexPattern" class="option-text-box" type="text"
placeholder="__MSG_danmakuTimeMatchingRegexPatternPlaceholder__" title="__MSG_danmakuRegexTitle__" />
<button class="text-change-set">__MSG_save__</button>
<button class="text-change-reset">__MSG_reset__</button>
</div>
<div class="small-description">__MSG_danmakuRegexPatternDescription__</div>
<div class="small-description">__MSG_danmakuTimeMatchingRegexPatternDescription__</div>
</div>

<div data-type="text-change" data-sync="danmakuOffsetMatchingRegexPattern">
<div class="input-container">
<label for="danmakuOffsetMatchingRegexPattern">__MSG_danmakuOffsetMatchingRegexPattern__</label>
<input id="danmakuOffsetMatchingRegexPattern" class="option-text-box" type="text"
placeholder="__MSG_danmakuOffsetMatchingRegexPatternPlaceholder__" title="__MSG_danmakuRegexTitle__" />
<button class="text-change-set">__MSG_save__</button>
<button class="text-change-reset">__MSG_reset__</button>
</div>
<div class="small-description">__MSG_danmakuOffsetRegexPatternDescription__</div>
</div>

</div>
Expand Down
16 changes: 7 additions & 9 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ interface SBConfig {
enableDanmakuSkip: boolean;
enableAutoSkipDanmakuSkip: boolean;
enableMenuDanmakuSkip: boolean;
danmakuRegexPattern: string;
danmakuTimeMatchingRegexPattern: string;
danmakuOffsetMatchingRegexPattern: string;
checkTimeDanmakuSkip: boolean;
muteSegments: boolean;
fullVideoSegments: boolean;
Expand Down Expand Up @@ -189,13 +190,8 @@ function migrateOldSyncFormats(config: SBConfig) {
config["serverAddress"] = CompileConfig.serverAddress;
}

// danmaku regex update since 0.5.0
const oldDanmakuRegexPatterns = [
"(?:空降\\s*)?(\\d{1,2}):(\\d{1,2})(?::(\\d{1,2}))?", // 0.5.0
];
if (oldDanmakuRegexPatterns.includes(config["danmakuRegexPattern"])) {
config["danmakuRegexPattern"] = syncDefaults.danmakuRegexPattern;
}
// "danmakuRegexPattern" 参数在 0.5.9 版本(预计)中被移除,取而代之的是 "danmakuTimeMatchingRegexPattern" 和 "danmakuOffsetMatchingRegexPattern" 参数
delete config["danmakuRegexPattern"];
}

const syncDefaults = {
Expand All @@ -217,7 +213,9 @@ const syncDefaults = {
enableDanmakuSkip: false,
enableAutoSkipDanmakuSkip: false,
enableMenuDanmakuSkip: false,
danmakuRegexPattern: "(?:空降\\s*)?(\\d{1,2})[::](\\d{1,2})(?:[::](\\d{1,2}))?$",
danmakuTimeMatchingRegexPattern:
"(?:(\\d{1,2})\\s*(?:小时|h|H|:|:|;|;|\\.|-|—)\\s*)?(?:(\\d{1,2})\\s*(?:分钟|分|:|:|;|;|\\.|-|—|m|M)\\s*)?(?:(\\d{1,2})\\s*(秒|s|S)?)",
danmakuOffsetMatchingRegexPattern: "(?:^|(右|右滑|按|右下|右向|右方向|→|⇒|⇢|⇨|⮕|🡆|🠺|🠾|🢒|👉))(\\d+)(下|次)?$",
checkTimeDanmakuSkip: true,

muteSegments: true,
Expand Down
26 changes: 5 additions & 21 deletions src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { isFirefox, isFirefoxOrSafari, isSafari, sleep, waitFor } from "./utils/
import { AnimationUtils } from "./utils/animationUtils";
import { addCleanupListener, cleanPage } from "./utils/cleanup";
import { defaultPreviewTime } from "./utils/constants";
import { parseTargetTimeFromDanmaku } from "./utils/danmakusUtils";
import { findValidElement } from "./utils/dom";
import { importTimes } from "./utils/exporter";
import { getErrorMessage, getFormattedTime } from "./utils/formating";
Expand Down Expand Up @@ -778,29 +779,12 @@ async function startSponsorSchedule(
}

function checkDanmaku(text: string, offset: number) {
const match = new RegExp(Config.config.danmakuRegexPattern).exec(text);
if (!match) {
return;
}
const targetTime = parseTargetTimeFromDanmaku(text, getVirtualTime());
if (targetTime === null) return;

const timeComponents = match
.slice(1)
.filter(Boolean)
.map((value) => parseInt(value, 10));
let hours = 0,
minutes = 0,
seconds = 0;

if (timeComponents.length === 2) {
minutes = timeComponents[0];
seconds = timeComponents[1];
} else if (timeComponents.length === 3) {
hours = timeComponents[0];
minutes = timeComponents[1];
seconds = timeComponents[2];
}
// [DEBUG]
// console.debug("检测到空降弹幕: ", text, "请求跳转到: ", targetTime);

const targetTime = hours * 3600 + minutes * 60 + seconds;
const startTime = getVirtualTime() + offset;

// ignore if the time is in the past
Expand Down
138 changes: 138 additions & 0 deletions src/utils/danmakusUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import Config from "../config";

/**
* 解析弹幕文本中的目标时间
*
* @param text 输入需要解析的弹幕文本
* @param currentTime 弹幕出现的时间
* @returns 返回弹幕指向目标时间。若无法解析,则会返回null。
*/
export function parseTargetTimeFromDanmaku(text: string, currentTime: number) {
/**
* 解析时间字符串并将其转换为总秒数。
*
* @param text - 包含需要解析的时间的输入字符串。
* @returns 由时间字符串表示的总秒数,如果时间字符串无效则返回null。
*
* 该函数使用在 `Config.config.danmakuTimeMatchingRegexPattern` 中定义的正则表达式模式
* 来匹配和提取输入字符串中的小时、分钟和秒。如果匹配成功且有效,
* 它通过将小时转换为秒、分钟转换为秒并将它们加到解析的秒数中来计算总秒数。
*/
function parseTime(text: string) {
const regex = new RegExp(Config.config.danmakuTimeMatchingRegexPattern, "g");

let match: RegExpExecArray | null;
while ((match = regex.exec(text)) !== null) {
const [, , minutes, seconds, secondsSuffix] = match;

if (seconds && (secondsSuffix || minutes)) {
const hours = parseInt(match[1] || "0");
const minutes = parseInt(match[2] || "0");
const seconds = parseInt(match[3] || "0");
return hours * 3600 + minutes * 60 + seconds;
}
}

return null;
}

/**
* 解析弹幕文本中的偏移时间。
*
* @param text - 包含偏移时间的弹幕文本。
* @returns 如果找到匹配的偏移时间,返回偏移时间(以秒为单位);否则返回 null。
*
* @remarks
* 该函数使用配置中的正则表达式模式来匹配偏移时间。匹配的偏移时间格式类似于“向右x下”,
* 其中 x 是一个整数,表示偏移的时间单位。偏移时间等价于当前时间加上 5 倍的 x 秒。
*/
function parseOffsetTime(text: string) {
const regex = new RegExp(Config.config.danmakuOffsetMatchingRegexPattern, "g");

let match: RegExpExecArray | null;
while ((match = regex.exec(text)) !== null) {
const [, direction, offset, suffix] = match;

if (offset && (direction || suffix)) {
// “向右x下”等价于当前时间 + 5x秒
return parseInt(offset) * 5;
}
}

return null;
}

text = text.replace(/[零一二三四五六七八九两壹贰叁肆伍陆柒捌玖十百千万]+/g, (cnNum) => parseChineseNumber(cnNum));

const directParsedTime = parseTime(text);
if (directParsedTime) return directParsedTime;
else {
const offsetParsedTime = parseOffsetTime(text);
if (offsetParsedTime) return offsetParsedTime + currentTime;
}
return null;
}

/**
* 将中文数字字符串转换为阿拉伯数字字符串。
*
* @param inputText - 包含中文数字的字符串。
* @returns 转换后的阿拉伯数字字符串。如果输入包含无效字符,则返回 null。
*
* @example
* ```typescript
* parseChineseNumber("一"); // 返回 "1"
* parseChineseNumber("十二"); // 返回 "12"
* parseChineseNumber("二十"); // 返回 "20"
* parseChineseNumber("二十一"); // 返回 "21"
* parseChineseNumber("一百"); // 返回 null
* ```
*/
export function parseChineseNumber(inputText: string) {
const cnChrMap: { [key: string]: number } = {
: 0,
: 1,
: 2,
: 3,
: 4,
: 5,
: 6,
: 7,
: 8,
: 9,
: 2,
: 1,
: 2,
: 3,
: 4,
: 5,
: 6,
: 7,
: 8,
: 9,
};

const cnUnitMap: { [key: string]: number } = {
: 10,
};

let num = 0;
let unit = 1;
for (let i = 0; i < inputText.length; i++) {
const chr = inputText[i];
if (chr in cnChrMap) {
num += cnChrMap[chr] * unit;
} else if (chr in cnUnitMap) {
unit = cnUnitMap[chr];
if (num === 0 && unit === 10) {
num = 1;
}
num = num * unit;
unit = 1;
} else {
return null;
}
}

return num.toString();
}

0 comments on commit 829c604

Please sign in to comment.