YouTube双语字幕,英语翻译中文。支持移动端和桌面端,适配Via浏览器。
当前为
// ==UserScript==
// @name Youtube 双语字幕版
// @version 1.0
// @author LR
// @description YouTube双语字幕,英语翻译中文。支持移动端和桌面端,适配Via浏览器。
// @match *://www.youtube.com/*
// @match *://m.youtube.com/*
// @require https://unpkg.com/ajax-hook@latest/dist/ajaxhook.min.js
// @grant none
// @run-at document-start
// @namespace https://greasyfork.org/users/1210499
// ==/UserScript==
(function () {
'use strict';
const TARGET_LANG = 'zh'; // 设置目标翻译语言为中文
(async function enableDualSubtitles() {
// 获取翻译后的字幕数据
async function fetchTranslatedSubtitles(url) {
const cleanUrl = url.replace(/(^|[&?])tlang=[^&]*/g, '') + `&tlang=${TARGET_LANG}&translate_h00ked`;
try {
const response = await fetch(cleanUrl, { method: 'GET' });
if (!response.ok) {
throw new Error(`Failed to fetch translated subtitles: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(error);
return null;
}
}
function jaccardSimilarity(str1, str2) {
const set1 = new Set(str1.split(''));
const set2 = new Set(str2.split(''));
const intersection = new Set([...set1].filter(x => set2.has(x)));
const union = new Set([...set1, ...set2]);
return intersection.size / union.size;
}
function mergeSubtitles(defaultSubs, translatedSubs) {
const mergedSubs = JSON.parse(JSON.stringify(defaultSubs));
const translatedEvents = translatedSubs.events.filter(event => event.segs);
let tIndex = 0; // 翻译事件索引
for (let i = 0; i < mergedSubs.events.length; i++) {
const defaultEvent = mergedSubs.events[i];
if (!defaultEvent.segs) continue;
// 匹配时间最接近的翻译字幕事件
while (tIndex < translatedEvents.length && translatedEvents[tIndex].tStartMs < defaultEvent.tStartMs) {
tIndex++;
}
const translatedEvent = translatedEvents[tIndex];
if (translatedEvent) {
const defaultText = defaultEvent.segs.map(seg => seg.utf8).join('');
const translatedText = translatedEvent.segs.map(seg => seg.utf8).join('');
// 使用 Jaccard 相似性来检测文本相似性
if (jaccardSimilarity(defaultText.trim(), translatedText.trim()) < 0.8) {
defaultEvent.segs[0].utf8 = `${defaultText}\n${translatedText}`;
defaultEvent.segs = [defaultEvent.segs[0]];
}
}
}
return JSON.stringify(mergedSubs);
}
// 使用 ajax-hook 代理请求和响应,以获取并处理字幕数据
ah.proxy({
onResponse: async (response, handler) => {
if (response.config.url.includes('/api/timedtext') && !response.config.url.includes('&translate_h00ked')) {
try {
const defaultSubs = JSON.parse(response.response);
const translatedSubs = await fetchTranslatedSubtitles(response.config.url);
if (translatedSubs) {
response.response = mergeSubtitles(defaultSubs, translatedSubs);
}
} catch (error) {
console.error("Error processing subtitles:", error);
}
}
handler.resolve(response);
}
});
})();
})();