feat: format subtitle
This commit is contained in:
@@ -95,33 +95,18 @@ export const DEFAULT_TRANBOX_SETTING = {
|
|||||||
enSug: OPT_SUG_YOUDAO, // 英文建议
|
enSug: OPT_SUG_YOUDAO, // 英文建议
|
||||||
};
|
};
|
||||||
|
|
||||||
const SUBTITLE_WINDOW_STYLE = `container-type: inline-size;
|
const SUBTITLE_WINDOW_STYLE = `padding: 0.5em 1em;
|
||||||
position: absolute;
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
bottom: 10%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 80%;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: rgba(0, 0, 0, 0.7);
|
|
||||||
color: white;
|
color: white;
|
||||||
text-align: center;
|
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
text-shadow: 1px 1px 2px black;
|
text-shadow: 1px 1px 2px black;
|
||||||
pointer-events: none;
|
|
||||||
z-index: 2147483647;
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
cursor: grab;
|
transition: opacity 0.2s ease-in-out;
|
||||||
transition: opacity 0.2s ease-in-out;`;
|
display: inline-block`;
|
||||||
|
|
||||||
const SUBTITLE_ORIGIN_STYLE = `margin:0;
|
const SUBTITLE_ORIGIN_STYLE = `font-size: clamp(1.5rem, 3cqw, 3rem);`;
|
||||||
padding: 0;
|
|
||||||
opacity: 0.8;
|
|
||||||
font-size: clamp(1.5rem, 3cqw, 3rem);`;
|
|
||||||
|
|
||||||
const SUBTITLE_TRANSLATION_STYLE = `margin:0;
|
const SUBTITLE_TRANSLATION_STYLE = `font-size: clamp(1.5rem, 3cqw, 3rem);`;
|
||||||
padding: 0;
|
|
||||||
opacity: 1;
|
|
||||||
font-size: clamp(1.5rem, 3cqw, 3rem);`;
|
|
||||||
|
|
||||||
export const DEFAULT_SUBTITLE_SETTING = {
|
export const DEFAULT_SUBTITLE_SETTING = {
|
||||||
enabled: true, // 是否开启
|
enabled: true, // 是否开启
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ export const loadingSvg = `
|
|||||||
export const createLogoSvg = ({
|
export const createLogoSvg = ({
|
||||||
width = "100%",
|
width = "100%",
|
||||||
height = "100%",
|
height = "100%",
|
||||||
viewBox = "-13 -14 60 60",
|
viewBox = "-20 -20 70 70",
|
||||||
|
isSelected = false,
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
const svgNS = "http://www.w3.org/2000/svg";
|
const svgNS = "http://www.w3.org/2000/svg";
|
||||||
const svgElement = document.createElementNS(svgNS, "svg");
|
const svgElement = document.createElementNS(svgNS, "svg");
|
||||||
@@ -51,5 +52,14 @@ export const createLogoSvg = ({
|
|||||||
svgElement.appendChild(path1);
|
svgElement.appendChild(path1);
|
||||||
svgElement.appendChild(path2);
|
svgElement.appendChild(path2);
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
const redLine = document.createElementNS(svgNS, "path");
|
||||||
|
redLine.setAttribute("d", "M0 36 L32 36");
|
||||||
|
redLine.setAttribute("stroke", "red");
|
||||||
|
redLine.setAttribute("stroke-width", "3");
|
||||||
|
redLine.setAttribute("stroke-linecap", "round");
|
||||||
|
svgElement.appendChild(redLine);
|
||||||
|
}
|
||||||
|
|
||||||
return svgElement;
|
return svgElement;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -362,3 +362,15 @@ export const truncateWords = (str, maxLength) => {
|
|||||||
const truncated = str.slice(0, maxLength);
|
const truncated = str.slice(0, maxLength);
|
||||||
return truncated.slice(0, truncated.lastIndexOf(" ")) + " …";
|
return truncated.slice(0, truncated.lastIndexOf(" ")) + " …";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机数
|
||||||
|
* @param {*} min
|
||||||
|
* @param {*} max
|
||||||
|
* @param {*} integer
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const randomBetween = (min, max, integer = true) => {
|
||||||
|
const value = Math.random() * (max - min) + min;
|
||||||
|
return integer ? Math.floor(value) : value;
|
||||||
|
};
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export class BilingualSubtitleManager {
|
|||||||
destroy() {
|
destroy() {
|
||||||
logger.info("Bilingual Subtitle Manager: Destroying...");
|
logger.info("Bilingual Subtitle Manager: Destroying...");
|
||||||
this.#removeEventListeners();
|
this.#removeEventListeners();
|
||||||
this.#captionWindowEl?.parentElement?.remove();
|
this.#captionWindowEl?.parentElement?.parentElement?.remove();
|
||||||
this.#formattedSubtitles = [];
|
this.#formattedSubtitles = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,14 +60,36 @@ export class BilingualSubtitleManager {
|
|||||||
*/
|
*/
|
||||||
#createCaptionWindow() {
|
#createCaptionWindow() {
|
||||||
const container = document.createElement("div");
|
const container = document.createElement("div");
|
||||||
container.className = `kiss-caption-window-container notranslate`;
|
container.className = `kiss-caption-container notranslate`;
|
||||||
container.style.cssText = `position:absolute; width:100%; height:100%; left:0; top:0;`;
|
Object.assign(container.style, {
|
||||||
|
position: "absolute",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
left: "0",
|
||||||
|
top: "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
const paper = document.createElement("div");
|
||||||
|
paper.className = `kiss-caption-paper`;
|
||||||
|
Object.assign(paper.style, {
|
||||||
|
position: "absolute",
|
||||||
|
width: "80%",
|
||||||
|
left: "50%",
|
||||||
|
bottom: "10%",
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
textAlign: "center",
|
||||||
|
cursor: "grab",
|
||||||
|
containerType: "inline-size",
|
||||||
|
pointerEvents: "none",
|
||||||
|
zIndex: "2147483647",
|
||||||
|
});
|
||||||
|
|
||||||
this.#captionWindowEl = document.createElement("div");
|
this.#captionWindowEl = document.createElement("div");
|
||||||
this.#captionWindowEl.className = `kiss-caption-window`;
|
this.#captionWindowEl.className = `kiss-caption-window`;
|
||||||
this.#captionWindowEl.style.cssText = this.#setting.windowStyle;
|
this.#captionWindowEl.style.cssText = this.#setting.windowStyle;
|
||||||
|
|
||||||
container.appendChild(this.#captionWindowEl);
|
paper.appendChild(this.#captionWindowEl);
|
||||||
|
container.appendChild(paper);
|
||||||
|
|
||||||
const videoContainer = this.#videoEl.parentElement?.parentElement;
|
const videoContainer = this.#videoEl.parentElement?.parentElement;
|
||||||
if (!videoContainer) {
|
if (!videoContainer) {
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import { logger } from "../libs/log.js";
|
|||||||
import { apiTranslate } from "../apis/index.js";
|
import { apiTranslate } from "../apis/index.js";
|
||||||
import { BilingualSubtitleManager } from "./BilingualSubtitleManager.js";
|
import { BilingualSubtitleManager } from "./BilingualSubtitleManager.js";
|
||||||
import { getGlobalVariable } from "./globalVariable.js";
|
import { getGlobalVariable } from "./globalVariable.js";
|
||||||
import { MSG_XHR_DATA_YOUTUBE } from "../config";
|
import { MSG_XHR_DATA_YOUTUBE, APP_NAME } from "../config";
|
||||||
import { truncateWords } from "../libs/utils.js";
|
import { truncateWords, sleep } from "../libs/utils.js";
|
||||||
import { createLogoSvg } from "../libs/svg.js";
|
import { createLogoSvg } from "../libs/svg.js";
|
||||||
|
import { randomBetween } from "../libs/utils.js";
|
||||||
|
|
||||||
const VIDEO_SELECT = "#container video";
|
const VIDEO_SELECT = "#container video";
|
||||||
const CONTORLS_SELECT = ".ytp-right-controls";
|
const CONTORLS_SELECT = ".ytp-right-controls";
|
||||||
@@ -15,6 +16,9 @@ class YouTubeCaptionProvider {
|
|||||||
#videoId = "";
|
#videoId = "";
|
||||||
#subtitles = [];
|
#subtitles = [];
|
||||||
#managerInstance = null;
|
#managerInstance = null;
|
||||||
|
#toggleButton = null;
|
||||||
|
#enabled = false;
|
||||||
|
#ytControls = null;
|
||||||
|
|
||||||
constructor(setting = {}) {
|
constructor(setting = {}) {
|
||||||
this.#setting = setting;
|
this.#setting = setting;
|
||||||
@@ -52,9 +56,21 @@ class YouTubeCaptionProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async #doubleClick() {
|
||||||
|
const button = this.#ytControls.querySelector(
|
||||||
|
"button.ytp-subtitles-button"
|
||||||
|
);
|
||||||
|
if (button) {
|
||||||
|
await sleep(randomBetween(50, 100));
|
||||||
|
button.click();
|
||||||
|
await sleep(randomBetween(500, 1000));
|
||||||
|
button.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#injectToggleButton() {
|
#injectToggleButton() {
|
||||||
const controls = document.querySelector(CONTORLS_SELECT);
|
this.#ytControls = document.querySelector(CONTORLS_SELECT);
|
||||||
if (!controls) {
|
if (!this.#ytControls) {
|
||||||
logger.warn("Youtube Provider: Could not find YouTube player controls.");
|
logger.warn("Youtube Provider: Could not find YouTube player controls.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -68,30 +84,28 @@ class YouTubeCaptionProvider {
|
|||||||
const toggleButton = document.createElement("button");
|
const toggleButton = document.createElement("button");
|
||||||
toggleButton.className =
|
toggleButton.className =
|
||||||
"ytp-button notranslate kiss-bilingual-subtitle-button";
|
"ytp-button notranslate kiss-bilingual-subtitle-button";
|
||||||
toggleButton.title = "Toggle Bilingual Subtitles";
|
toggleButton.title = APP_NAME;
|
||||||
Object.assign(toggleButton.style, {
|
Object.assign(toggleButton.style, {
|
||||||
color: "white",
|
color: "white",
|
||||||
opacity: "0.8",
|
opacity: "0.5",
|
||||||
});
|
});
|
||||||
|
|
||||||
toggleButton.appendChild(createLogoSvg());
|
toggleButton.appendChild(createLogoSvg());
|
||||||
kissControls.appendChild(toggleButton);
|
kissControls.appendChild(toggleButton);
|
||||||
|
|
||||||
toggleButton.onclick = () => {
|
toggleButton.onclick = () => {
|
||||||
if (!this.#managerInstance) {
|
if (!this.#enabled) {
|
||||||
logger.info(`Youtube Provider: Feature toggled ON.`);
|
logger.info(`Youtube Provider: Feature toggled ON.`);
|
||||||
toggleButton.style.opacity = "1";
|
|
||||||
this.#setting.enabled = true;
|
|
||||||
this.#startManager();
|
this.#startManager();
|
||||||
} else {
|
} else {
|
||||||
logger.info(`Youtube Provider: Feature toggled OFF.`);
|
logger.info(`Youtube Provider: Feature toggled OFF.`);
|
||||||
toggleButton.style.opacity = "0.5";
|
|
||||||
this.#setting.enabled = false;
|
|
||||||
this.#destroyManager();
|
this.#destroyManager();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
this.#toggleButton = toggleButton;
|
||||||
|
this.#ytControls.before(kissControls);
|
||||||
|
|
||||||
controls.before(kissControls);
|
this.#doubleClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
#findCaptionTrack(ytPlayer) {
|
#findCaptionTrack(ytPlayer) {
|
||||||
@@ -144,6 +158,10 @@ class YouTubeCaptionProvider {
|
|||||||
|
|
||||||
async #handleInterceptedRequest(url, responseText) {
|
async #handleInterceptedRequest(url, responseText) {
|
||||||
try {
|
try {
|
||||||
|
if (!responseText) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const ytPlayer = await getGlobalVariable("ytInitialPlayerResponse");
|
const ytPlayer = await getGlobalVariable("ytInitialPlayerResponse");
|
||||||
const captionTrack = this.#findCaptionTrack(ytPlayer);
|
const captionTrack = this.#findCaptionTrack(ytPlayer);
|
||||||
if (!captionTrack) {
|
if (!captionTrack) {
|
||||||
@@ -169,13 +187,13 @@ class YouTubeCaptionProvider {
|
|||||||
responseText
|
responseText
|
||||||
);
|
);
|
||||||
if (!subtitleEvents) {
|
if (!subtitleEvents) {
|
||||||
logger.warn("Youtube Provider: SubtitleEvents not got.");
|
logger.info("Youtube Provider: SubtitleEvents not got.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const subtitles = this.#formatSubtitles(subtitleEvents);
|
const subtitles = this.#formatSubtitles(subtitleEvents);
|
||||||
if (subtitles.length === 0) {
|
if (subtitles.length === 0) {
|
||||||
logger.warn("Youtube Provider: No subtitles after format.");
|
logger.info("Youtube Provider: No subtitles after format.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,17 +207,22 @@ class YouTubeCaptionProvider {
|
|||||||
this.#subtitles = subtitles;
|
this.#subtitles = subtitles;
|
||||||
this.#videoId = videoId;
|
this.#videoId = videoId;
|
||||||
|
|
||||||
this.#destroyManager();
|
if (this.#toggleButton) {
|
||||||
|
this.#toggleButton.style.opacity = subtitles.length ? "1" : "0.5";
|
||||||
|
}
|
||||||
|
|
||||||
if (this.#setting.enabled) {
|
if (this.#enabled) {
|
||||||
|
this.#destroyManager();
|
||||||
this.#startManager();
|
this.#startManager();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#startManager() {
|
#startManager() {
|
||||||
if (this.#managerInstance) {
|
if (this.#enabled || this.#managerInstance) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.#enabled = true;
|
||||||
|
this.#toggleButton?.replaceChildren(createLogoSvg({ isSelected: true }));
|
||||||
|
|
||||||
const videoEl = document.querySelector(VIDEO_SELECT);
|
const videoEl = document.querySelector(VIDEO_SELECT);
|
||||||
if (!videoEl) {
|
if (!videoEl) {
|
||||||
@@ -210,14 +233,12 @@ class YouTubeCaptionProvider {
|
|||||||
if (this.#subtitles?.length === 0) {
|
if (this.#subtitles?.length === 0) {
|
||||||
// todo: 等待并给出用户提示
|
// todo: 等待并给出用户提示
|
||||||
logger.info("Youtube Provider: No subtitles");
|
logger.info("Youtube Provider: No subtitles");
|
||||||
|
this.#doubleClick();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Youtube Provider: Starting manager...");
|
logger.info("Youtube Provider: Starting manager...");
|
||||||
|
|
||||||
const ytCaption = document.querySelector(YT_CAPTION_SELECT);
|
|
||||||
ytCaption && (ytCaption.style.display = "none");
|
|
||||||
|
|
||||||
this.#managerInstance = new BilingualSubtitleManager({
|
this.#managerInstance = new BilingualSubtitleManager({
|
||||||
videoEl,
|
videoEl,
|
||||||
formattedSubtitles: this.#subtitles,
|
formattedSubtitles: this.#subtitles,
|
||||||
@@ -225,21 +246,29 @@ class YouTubeCaptionProvider {
|
|||||||
setting: this.#setting,
|
setting: this.#setting,
|
||||||
});
|
});
|
||||||
this.#managerInstance.start();
|
this.#managerInstance.start();
|
||||||
|
|
||||||
|
const ytCaption = document.querySelector(YT_CAPTION_SELECT);
|
||||||
|
ytCaption && (ytCaption.style.display = "none");
|
||||||
}
|
}
|
||||||
|
|
||||||
#destroyManager() {
|
#destroyManager() {
|
||||||
if (this.#managerInstance) {
|
if (!this.#enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#enabled = false;
|
||||||
|
this.#toggleButton?.replaceChildren(createLogoSvg());
|
||||||
|
|
||||||
logger.info("Youtube Provider: Destroying manager...");
|
logger.info("Youtube Provider: Destroying manager...");
|
||||||
|
|
||||||
const ytCaption = document.querySelector(YT_CAPTION_SELECT);
|
const ytCaption = document.querySelector(YT_CAPTION_SELECT);
|
||||||
ytCaption && (ytCaption.style.display = "block");
|
ytCaption && (ytCaption.style.display = "block");
|
||||||
|
|
||||||
|
if (this.#managerInstance) {
|
||||||
this.#managerInstance.destroy();
|
this.#managerInstance.destroy();
|
||||||
this.#managerInstance = null;
|
this.#managerInstance = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: 没有标点断句的处理
|
|
||||||
#formatSubtitles(data) {
|
#formatSubtitles(data) {
|
||||||
const events = data?.events;
|
const events = data?.events;
|
||||||
if (!Array.isArray(events)) return [];
|
if (!Array.isArray(events)) return [];
|
||||||
@@ -300,10 +329,179 @@ class YouTubeCaptionProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.map((line) => ({
|
const isPoor = this.#isQualityPoor(lines);
|
||||||
...line,
|
if (isPoor) {
|
||||||
duration: Math.max(0, line.end - line.start),
|
return this.#processSubtitles(data);
|
||||||
text: truncateWords(line.text.trim().replace(/\s+/g, " "), 300),
|
}
|
||||||
|
|
||||||
|
return lines.map((item) => ({
|
||||||
|
...item,
|
||||||
|
duration: Math.max(0, item.end - item.start),
|
||||||
|
text: truncateWords(item.text.trim().replace(/\s+/g, " "), 250),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#isQualityPoor(lines, lengthThreshold = 250, percentageThreshold = 0.1) {
|
||||||
|
if (lines.length === 0) return false;
|
||||||
|
const longLinesCount = lines.filter(
|
||||||
|
(line) => line.text.length > lengthThreshold
|
||||||
|
).length;
|
||||||
|
return longLinesCount / lines.length > percentageThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#processSubtitles(data, { timeout = 1500, maxWords = 15 } = {}) {
|
||||||
|
const groupedPauseWords = {
|
||||||
|
1: new Set([
|
||||||
|
"actually",
|
||||||
|
"also",
|
||||||
|
"although",
|
||||||
|
"and",
|
||||||
|
"anyway",
|
||||||
|
"as",
|
||||||
|
"basically",
|
||||||
|
"because",
|
||||||
|
"but",
|
||||||
|
"eventually",
|
||||||
|
"frankly",
|
||||||
|
"honestly",
|
||||||
|
"hopefully",
|
||||||
|
"however",
|
||||||
|
"if",
|
||||||
|
"instead",
|
||||||
|
"it's",
|
||||||
|
"just",
|
||||||
|
"let's",
|
||||||
|
"like",
|
||||||
|
"literally",
|
||||||
|
"maybe",
|
||||||
|
"meanwhile",
|
||||||
|
"nevertheless",
|
||||||
|
"nonetheless",
|
||||||
|
"now",
|
||||||
|
"okay",
|
||||||
|
"or",
|
||||||
|
"otherwise",
|
||||||
|
"perhaps",
|
||||||
|
"personally",
|
||||||
|
"probably",
|
||||||
|
"right",
|
||||||
|
"since",
|
||||||
|
"so",
|
||||||
|
"suddenly",
|
||||||
|
"that's",
|
||||||
|
"then",
|
||||||
|
"there's",
|
||||||
|
"therefore",
|
||||||
|
"though",
|
||||||
|
"thus",
|
||||||
|
"unless",
|
||||||
|
"until",
|
||||||
|
"well",
|
||||||
|
"while",
|
||||||
|
]),
|
||||||
|
2: new Set([
|
||||||
|
"after all",
|
||||||
|
"at first",
|
||||||
|
"at least",
|
||||||
|
"even if",
|
||||||
|
"even though",
|
||||||
|
"for example",
|
||||||
|
"for instance",
|
||||||
|
"i believe",
|
||||||
|
"i guess",
|
||||||
|
"i mean",
|
||||||
|
"i suppose",
|
||||||
|
"i think",
|
||||||
|
"in fact",
|
||||||
|
"in the end",
|
||||||
|
"of course",
|
||||||
|
"then again",
|
||||||
|
"to be fair",
|
||||||
|
"you know",
|
||||||
|
"you see",
|
||||||
|
]),
|
||||||
|
3: new Set([
|
||||||
|
"as a result",
|
||||||
|
"by the way",
|
||||||
|
"in other words",
|
||||||
|
"in that case",
|
||||||
|
"in this case",
|
||||||
|
"to be clear",
|
||||||
|
"to be honest",
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
const sentences = [];
|
||||||
|
let currentBuffer = [];
|
||||||
|
let bufferWordCount = 0;
|
||||||
|
|
||||||
|
const joinSegs = (segs) => ({
|
||||||
|
text: segs
|
||||||
|
.map((s) => s.text)
|
||||||
|
.join(" ")
|
||||||
|
.trim(),
|
||||||
|
start: segs[0].start,
|
||||||
|
end: segs[segs.length - 1].end,
|
||||||
|
});
|
||||||
|
|
||||||
|
const flushBuffer = () => {
|
||||||
|
if (currentBuffer.length > 0) {
|
||||||
|
sentences.push(joinSegs(currentBuffer));
|
||||||
|
}
|
||||||
|
currentBuffer = [];
|
||||||
|
bufferWordCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
data.events?.forEach((event) => {
|
||||||
|
event.segs?.forEach((seg, j) => {
|
||||||
|
const text = seg.utf8?.trim() || "";
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
const start = event.tStartMs + (seg.tOffsetMs ?? 0);
|
||||||
|
const lastSegment = currentBuffer[currentBuffer.length - 1];
|
||||||
|
|
||||||
|
if (lastSegment) {
|
||||||
|
if (!lastSegment.end) {
|
||||||
|
lastSegment.end = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEndOfSentence = /[.?!\]]$/.test(lastSegment.text);
|
||||||
|
const isTimeout = start - lastSegment.end > timeout;
|
||||||
|
const isWordLimitExceeded = bufferWordCount >= maxWords;
|
||||||
|
const startsWithPauseWord = groupedPauseWords["1"].has(
|
||||||
|
text.toLowerCase().split(" ")[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
// todo: 考虑连词开头
|
||||||
|
const isNewClause =
|
||||||
|
(startsWithPauseWord && currentBuffer.length > 1) ||
|
||||||
|
text.startsWith("[");
|
||||||
|
|
||||||
|
if (
|
||||||
|
isEndOfSentence ||
|
||||||
|
isTimeout ||
|
||||||
|
isWordLimitExceeded ||
|
||||||
|
isNewClause
|
||||||
|
) {
|
||||||
|
flushBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSegment = { text, start };
|
||||||
|
if (j === event.segs.length - 1) {
|
||||||
|
currentSegment.end = event.tStartMs + event.dDurationMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBuffer.push(currentSegment);
|
||||||
|
bufferWordCount += text.split(/\s+/).length;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
flushBuffer();
|
||||||
|
|
||||||
|
return sentences.map((item) => ({
|
||||||
|
...item,
|
||||||
|
duration: item.end - item.start,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ const providers = [
|
|||||||
|
|
||||||
export function runSubtitle({ href, setting, rule }) {
|
export function runSubtitle({ href, setting, rule }) {
|
||||||
try {
|
try {
|
||||||
|
const subtitleSetting = setting.subtitleSetting || DEFAULT_SUBTITLE_SETTING;
|
||||||
|
if (!subtitleSetting.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const provider = providers.find((item) => isMatch(href, item.pattern));
|
const provider = providers.find((item) => isMatch(href, item.pattern));
|
||||||
if (provider) {
|
if (provider) {
|
||||||
const id = "kiss-translator-injector";
|
const id = "kiss-translator-injector";
|
||||||
@@ -22,7 +27,7 @@ export function runSubtitle({ href, setting, rule }) {
|
|||||||
setting.transApis.find((api) => api.apiSlug === rule.apiSlug) ||
|
setting.transApis.find((api) => api.apiSlug === rule.apiSlug) ||
|
||||||
DEFAULT_API_SETTING;
|
DEFAULT_API_SETTING;
|
||||||
provider.start({
|
provider.start({
|
||||||
...(setting.subtitleSetting || DEFAULT_SUBTITLE_SETTING),
|
...subtitleSetting,
|
||||||
apiSetting,
|
apiSetting,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,16 +104,6 @@ export default function SubtitleSetting() {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
label={i18n("background_styles")}
|
|
||||||
name="windowStyle"
|
|
||||||
value={windowStyle}
|
|
||||||
onChange={handleChange}
|
|
||||||
maxRows={10}
|
|
||||||
multiline
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
label={i18n("origin_styles")}
|
label={i18n("origin_styles")}
|
||||||
@@ -134,6 +124,16 @@ export default function SubtitleSetting() {
|
|||||||
multiline
|
multiline
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("background_styles")}
|
||||||
|
name="windowStyle"
|
||||||
|
value={windowStyle}
|
||||||
|
onChange={handleChange}
|
||||||
|
maxRows={10}
|
||||||
|
multiline
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user