🏠 Home 

Youtube double language subtitle / Youtube 双语字幕

Youtube double language subtitle / Youtube 双语字幕. 如果不能自动加载,请关闭字幕再次打开即可。默认语言为浏览器首选语言。


安装此脚本?
// ==UserScript==
// @name         Youtube double language subtitle / Youtube 双语字幕
// @version      1.8.0
// @description  Youtube double language subtitle / Youtube 双语字幕. 如果不能自动加载,请关闭字幕再次打开即可。默认语言为浏览器首选语言。
// @author       Coink
// @match        *://www.youtube.com/watch?v=*
// @match        *://www.youtube.com
// @match        *://www.youtube.com/*
// @require      https://unpkg.com/[email protected]/dist/ajaxhook.min.js
// @grant        none
// @run-at       document-start
// @namespace    https://github.com/CoinkWang/Y2BDoubl###bs
// ==/UserScript==
(function() {
let localeLang = document.documentElement.lang || navigator.language || 'en' // follow the language used in YouTube Page
// localeLang = 'zh'  // uncomment this line to define the language you wish here
ah.proxy({
onRequest: (config, handler) => {
handler.next(config);
},
onResponse: (response, handler) => {
if (response.config.url.includes('/api/timedtext') && !response.config.url.includes('&translate_h00ked')) {
let xhr = new XMLHttpRequest();
// Use RegExp to clean '&tlang=...' in our xhr request params while using Y2B auto translate.
let url = response.config.url
url = url.replace(/(^|[&?])tlang=[^&]*/g, '')
url = `${url}&tlang=${localeLang}&translate_h00ked`
xhr.open('GET', url, false);
xhr.send();
let defaultJson = null
if (response.response) {
const jsonResponse = JSON.parse(response.response)
if (jsonResponse.events) defaultJson = jsonResponse
}
const localeJson = JSON.parse(xhr.response)
let isOfficialSub = true;
for (const defaultJsonEvent of defaultJson.events) {
if (defaultJsonEvent.segs && defaultJsonEvent.segs.length > 1) {
isOfficialSub = false;
break;
}
}
// Merge default subs with locale language subs
if (isOfficialSub) {
// when length of segments are the same
for (let i = 0, len = defaultJson.events.length; i < len; i++) {
const defaultJsonEvent = defaultJson.events[i]
if (!defaultJsonEvent.segs) continue
const localeJsonEvent = localeJson.events[i]
if (`${defaultJsonEvent.segs[0].utf8}`.trim() !== `${localeJsonEvent.segs[0].utf8}`.trim()) {
// avoid merge subs while the are the same
defaultJsonEvent.segs[0].utf8 += ('\n' + localeJsonEvent.segs[0].utf8)
}
}
response.response = JSON.stringify(defaultJson)
} else {
// when length of segments are not the same (e.g. automatic generated english subs)
let pureLocalEvents = localeJson.events.filter(event => event.aAppend !== 1 && event.segs)
for (const defaultJsonEvent of defaultJson.events) {
if (!defaultJsonEvent.segs) continue
let currentStart = defaultJsonEvent.tStartMs,
currentEnd = currentStart + defaultJsonEvent.dDurationMs
let currentLocalEvents = pureLocalEvents.filter(pe => currentStart <= pe.tStartMs && pe.tStartMs < currentEnd)
let localLine = ''
for (const ev of currentLocalEvents) {
for (const seg of ev.segs) {
localLine += seg.utf8
}
localLine += ''; // add ZWSP to avoid words stick together
}
let defaultLine = ''
for (const seg of defaultJsonEvent.segs) {
defaultLine += seg.utf8
}
defaultJsonEvent.segs[0].utf8 = defaultLine + '\n' + localLine
defaultJsonEvent.segs = [defaultJsonEvent.segs[0]]
}
response.response = JSON.stringify(defaultJson)
}
}
handler.resolve(response)
}
})
})();