Youtube 双语字幕版

YouTube双语字幕,英语翻译中文。支持移动端和桌面端,适配Via浏览器。

目前為 2024-08-23 提交的版本,檢視 最新版本

// ==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);
            }
        });

    })();

})();