lynda.com 字幕翻译

将 lynda.com 的字幕译为中文,支持搜狗、彩云、Google翻译接口

目前为 2019-03-06 提交的版本。查看 最新版本

// ==UserScript==
// @name         lynda.com 字幕翻译
// @description  将 lynda.com 的字幕译为中文,支持搜狗、彩云、Google翻译接口
// @namespace    https://github.com/journey-ad
// @version      0.2.4
// @icon         https://cdn.lynda.com/static/favicon.ico
// @author       journey-ad
// @match        *://www.lynda.com/*
// @license      MIT
// @run-at       document-end
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function () {
    "use strict";

    var transServer = "caiyun"; // 配置翻译接口,可选值为 caiyun, sogou, google

    (function () { // 翻译目录
        var videoNameList = document.querySelectorAll('.video-name'),
            s = [],
            r = [];

        videoNameList.forEach(function (videoName, i) {
            s[i] = videoName.innerText;
        })

        translate(s.join('\n'), function (data) {
            r = data.split('\n');
            videoNameList.forEach(function (videoName, i) {
                videoName.innerText = r[i] + ' (' + videoName.innerText + ')';
            })
        })
    }())

    var entries = null;
    window.transTimer = window.setInterval(init, 100); // 用定时器检查待翻译文本是否已准备好

    function init() {
        if (unsafeWindow.mejs && getDeepProperty(mejs, "players.mep_0.selectedTrack")) {
            window.clearInterval(window.transTimer); // 清除定时器

            addTranslateBtn();
            entries = mejs.players.mep_0.selectedTrack.entries;
            Object.defineProperty(mejs.players.mep_0.selectedTrack, "entries", {
                get: function get() {
                    return entries;
                },
                set: function set(data) {
                    entries = data;

                    if (entries.text) {
                        console.log("字幕文本可用");
                        transText();
                    }
                }
            });
            console.log("字幕文本可用");
            transText();
        }
    }

    function addTranslateBtn() {
        window.isTranslateEnable = true;
        var controls = document.getElementsByClassName("mejs-controls")[0],
            css = ".mejs-container .mejs-controls .mejs-button.translate {right: 110px;}@media (min-width: 768px){.mejs-container .mejs-controls .mejs-button.translate {right: 132px;}.mejs-container .mejs-controls .mejs-button.mejs-playback-rate-button {right: 166px;}}.modal.video-modal .modal-player-cont .mejs-container .mejs-controls .mejs-button.translate {right: 90px;}#translate-btn {height: 16px;margin-top: 2px;font-size: 16px;font-weight: bold;color: #ccc;transition: all .3s;}#translate-btn:hover {color: #fff;}#translate-btn.enable {color: #ffba00;}",
            container = document.createElement("div");

        if (typeof GM_addStyle != "undefined") {
            GM_addStyle(css);
        } else if (typeof PRO_addStyle != "undefined") {
            PRO_addStyle(css);
        } else if (typeof addStyle != "undefined") {
            addStyle(css);
        } else {
            var node = document.createElement("style");
            node.type = "text/css";
            node.appendChild(document.createTextNode(css));
            var heads = document.getElementsByTagName("head");

            if (heads.length > 0) {
                heads[0].appendChild(node);
            } else {
                // no head yet, stick it whereever
                document.documentElement.appendChild(node);
            }
        }

        controls.appendChild(container);
        container.outerHTML = '<div class="mejs-button translate"><button class="enable" id="translate-btn" type="button">文</button></div>';
        var transBtn = document.getElementById("translate-btn");
        transBtn.addEventListener("click", function () {
            transBtn.classList.toggle("enable");
            window.isTranslateEnable = !window.isTranslateEnable;
        }, false);
    }

    function transText() {
        var s = "",
            r = "",
            arr = [],
            num = 0,
            count = 0,
            subtitle = entries.text,
            subtitleTrans = [];
        subtitle.forEach(function (e) {
            // 去除每条字幕的换行符并按行排列
            s += e.replace(/\r?\n|\r/g, " ") + "\n";
        });
        num = translate(s, function (data, index) {
            // 调用翻译方法并处理回调
            count++;
            arr[index] = data; // 按分块原始下标放回结果数组

            if (count >= num) {
                // 所有翻译文本已取回
                r = arr.join("\n");
                subtitleTrans = r.split("\n");

                mejs.players.mep_0.displayCaptions = function () {
                    // 重写displayCaptions方法
                    var subtitle = null;

                    if ("undefined" != typeof mejs.players.mep_0.tracks) {
                        var t,
                            e = mejs.players.mep_0,
                            i = e.selectedTrack;

                        if (null !== i && i.isLoaded) {
                            for (t = 0; t < i.entries.times.length; t++) {
                                if (e.media.currentTime >= i.entries.times[t].start && e.media.currentTime <= i.entries.times[t].stop) {
                                    if (window.isTranslateEnable) {
                                        // 拼接双语字幕
                                        subtitle = subtitleTrans[t] + "\n" + i.entries.text[t].replace(/\r?\n|\r/g, " ");
                                    } else {
                                        subtitle = i.entries.text[t];
                                    }

                                    return e.captionsText.html(subtitle).attr("class", "mejs-captions-text " + (i.entries.times[t].identifier || "")), void e.captions.show();
                                }
                            }

                            e.captions.hide();
                        } else e.captions.hide();
                    }
                };

                console.log(r);
                console.log("使用 " + transServer + " 翻译完成");
            }
        });
    }

    function translate(str, callback) {
        var textArr = [],
            count = 1;

        if (str.length > 5000) {
            //大于5000字符分块翻译
            var strArr = str.split("\n"),
                i = 0;
            strArr.forEach(function (v) {
                textArr[i] = textArr[i] || "";

                if ((textArr[i] + v).length > (i + 1) * 5000) {
                    // 若加上此行后长度超出5000字符则分块
                    i++;
                    textArr[i] = "";
                }

                textArr[i] += v + "\n";
            });
            count = i + 1; // 记录块的数量
        } else {
            textArr[0] = str;
        }

        textArr.forEach(function (text, index) {
            // 遍历每块分别进行翻译
            server({
                text: text.trim(),
                index: index
            }, callback);
        });
        return count; // 返回分块数量
    }

    function server() {
        var list = {
            sogou: function sogou(r, callback) {
                var KEY = "b33bf8c58706155663d1ad5dba4192dc"; // 硬编码于搜狗网页翻译js

                var data = {
                    "from": "auto",
                    "to": "zh-CHS",
                    "client": "pc",
                    "fr": "browser_pc",
                    "text": r.text,
                    "pid": "sogou-dict-vr",
                    "useDetect": "on",
                    "useDetectResult": "on",
                    "oxford": "on",
                    "isReturnSugg": "on",
                    "needQc": 1,
                    "s": md5("autozh-CHS".concat(r.text).concat(KEY)) // 签名算法

                };
                GM_xmlhttpRequest({
                    method: "POST",
                    url: "https://fanyi.sogou.com/reventondc/translateV1",
                    headers: {
                        "accept": "application/json",
                        "content-type": "application/x-www-form-urlencoded; charset=UTF-8"
                    },
                    data: serialize(data),
                    onload: function onload(response) {
                        var result = JSON.parse(response.responseText);
                        callback(result.data.translate.dit, r.index); // 执行回调,在回调中拼接
                    }
                });
            },
            caiyun: function caiyun(r, callback) {
                var data = {
                    "source": r.text.split("\n"),
                    "trans_type": "en2zh",
                    "request_id": "web_fanyi",
                    "media": "text",
                    "os_type": "web",
                    "dict": true,
                    "cached": true,
                    "replaced": true
                };
                GM_xmlhttpRequest({
                    method: "POST",
                    url: "https://api.interpreter.caiyunai.com/v1/translator",
                    headers: {
                        "accept": "application/json",
                        "content-type": "application/json; charset=UTF-8",
                        "X-Authorization": "token:cy4fgbil24jucmh8jfr5"
                    },
                    data: JSON.stringify(data),
                    onload: function onload(response) {
                        var result = JSON.parse(response.responseText);
                        callback(result.target.join("\n"), r.index); // 执行回调,在回调中拼接
                    }
                });
            },
            google: function google(r, callback) {
                var data = {
                    "q": r.text,
                    "client": "webapp",
                    "sl": "auto",
                    "tl": "zh-CN",
                    "hl": "zh-CN",
                    "dt": "t",
                    "otf": 1,
                    "pc": 1,
                    "ssel": 0,
                    "tsel": 0,
                    "kc": 5,
                    "tk": tk(r.text)
                };
                GM_xmlhttpRequest({
                    method: "POST",
                    url: "https://translate.google.cn/translate_a/single",
                    headers: {
                        "accept": "application/json",
                        "content-type": "application/x-www-form-urlencoded; charset=UTF-8"
                    },
                    data: serialize(data),
                    onload: function onload(response) {
                        var result = JSON.parse(response.responseText),
                            arr = [];
                        result[0].forEach(function (t) {
                            t && arr.push(t[0]);
                        });
                        callback(arr.join(""), r.index); // 执行回调,在回调中拼接
                    }
                });
            }
        };

        return list[transServer].apply(null, arguments);
    };

    function getDeepProperty(obj, propstr) {
        var prop = propstr.split(".");

        for (var i = 0; i < prop.length; i++) {
            if (typeof obj === "object") obj = obj[prop[i]];
        }

        return obj;
    }

    function serialize(obj) {
        return Object.keys(obj).map(function (k) {
            return encodeURIComponent(k) + "=" + encodeURIComponent(obj[k]).replace("%20", "+");
        }).join("&");
    }

    function md5(str) {
        var k = [],
            i = 0;

        for (i = 0; i < 64;) {
            k[i] = 0 | Math.abs(Math.sin(++i)) * 4294967296;
        }

        var b,
            c,
            d,
            j,
            x = [],
            str2 = unescape(encodeURI(str)),
            a = str2.length,
            h = [b = 1732584193, c = -271733879, ~b, ~c];

        for (i = 0; i <= a;) {
            x[i >> 2] |= (str2.charCodeAt(i) || 128) << 8 * (i++ % 4);
        }

        x[str = (a + 8 >> 6) * 16 + 14] = a * 8;
        i = 0;

        for (; i < str; i += 16) {
            a = h;
            j = 0;

            for (; j < 64;) {
                a = [d = a[3], (b = a[1] | 0) + ((d = a[0] + [b & (c = a[2]) | ~b & d, d & b | ~d & c, b ^ c ^ d, c ^ (b | ~d)][a = j >> 4] + (k[j] + (x[[j, 5 * j + 1, 3 * j + 5, 7 * j][a] % 16 + i] | 0))) << (a = [7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21][4 * a + j++ % 4]) | d >>> 32 - a), b, c];
            }

            for (j = 4; j;) {
                h[--j] = h[j] + a[j];
            }
        }

        str = "";

        for (; j < 32;) {
            str += (h[j >> 3] >> (1 ^ j++ & 7) * 4 & 15).toString(16);
        }

        return str;
    }

    function tk(a) {
        var tkk = "429175.1243284773",
            Jo = null,
            b,
            c,
            d;

        function Ho(a) {
            return function () {
                return a;
            };
        }

        function Io(a, b) {
            for (var c = 0; c < b.length - 2; c += 3) {
                var d = b.charAt(c + 2);
                d = "a" <= d ? d.charCodeAt(0) - 87 : Number(d);
                d = "+" == b.charAt(c + 1) ? a >>> d : a << d;
                a = "+" == b.charAt(c) ? a + d & 4294967295 : a ^ d;
            }

            return a;
        }

        if (null !== Jo) b = Jo;
        else {
            b = Ho(String.fromCharCode(84));
            c = Ho(String.fromCharCode(75));
            b = [b(), b()];
            b[1] = c();
            b = (Jo = tkk || "") || "";
        }
        d = Ho(String.fromCharCode(116));
        c = Ho(String.fromCharCode(107));
        d = [d(), d()];
        d[1] = c();
        d = b.split(".");
        b = Number(d[0]) || 0;

        for (var e = [], f = 0, g = 0; g < a.length; g++) {
            var k = a.charCodeAt(g);
            128 > k ? e[f++] = k : (2048 > k ? e[f++] = k >> 6 | 192 : (55296 == (k & 64512) && g + 1 < a.length && 56320 == (a.charCodeAt(g + 1) & 64512) ? (k = 65536 + ((k & 1023) << 10) + (a.charCodeAt(++g) & 1023), e[f++] = k >> 18 | 240, e[f++] = k >> 12 & 63 | 128) : e[f++] = k >> 12 | 224, e[f++] = k >> 6 & 63 | 128), e[f++] = k & 63 | 128);
        }

        a = b;

        for (f = 0; f < e.length; f++) {
            a += e[f], a = Io(a, "+-a^+6");
        }

        a = Io(a, "+-3^+b+-f");
        a ^= Number(d[1]) || 0;
        0 > a && (a = (a & 2147483647) + 2147483648);
        a %= 1E6;
        return a.toString() + "." + (a ^ b);
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址