Greasy Fork

Greasy Fork is available in English.

lynda.com 字幕翻译

lynda.com 字幕翻译脚本,并支持下载视频和字幕文件

当前为 2019-11-03 提交的版本,查看 最新版本

// ==UserScript==
// @name         lynda.com 字幕翻译
// @description  lynda.com 字幕翻译脚本,并支持下载视频和字幕文件
// @namespace    https://github.com/journey-ad
// @version      0.3.5
// @icon         https://cdn.lynda.com/static/favicon.ico
// @author       journey-ad
// @match        *://www.lynda.com/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/download.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/fingerprint2.min.js
// @license      MIT
// @run-at       document-end
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @grant        GM_notification
// ==/UserScript==
(function () {
  "use strict";

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

  before();
  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;}@media (max-width: 768px){.mejs-container .mejs-captions-layer {font-size: 16px; line-height:18px;}}",
      container = document.createElement("div");
    addStyle(css);
    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 addDownloadTab() {
    window.loading = true;
    var courseId = unsafeWindow.lynda.courseId,
      videoId = unsafeWindow.currentVideoId,
      elVideoName = document.querySelector('#course-page .course-toc .toc-items .current .video-name'),
      videoName = '{{ index }}_{{ zh }}_{{ en }}'.render({
        index: elVideoName.dataset.index,
        en: elVideoName.dataset.original.replace(/\s+/g, '_'),
        zh: elVideoName.dataset.translate.replace(/\s+/g, '')
      }),
      panelTemplate = videoDownloadTabTemplate,
      videoData = {
        "class": '',
        style: document.getElementsByClassName('tab-video-download')[0].classList.contains('selected') ? 'display:block' : 'display:none',
        subtitleNameZH: videoName + '_chs.srt',
        subtitleNameEN: videoName + '_eng.srt'
      },
      _TIMECODE_REGEX = /\[(\d+:\d+:\d+[\.,]\d+)\]/;
    getVideoUrl(courseId, videoId, function (data) {
      data[0]['qualities'].forEach(function (quality) {
        videoData['videoName' + quality] = videoName + '_' + quality + 'p.' + (quality === '64' ? 'mp3' : 'mp4');
        videoData['videoUrl' + quality] = data[0]['urls'][quality];
      });
      fixSubtitles(videoId, function (subtitles) {
        var zhSrt = '',
          enSrt = '',
          seqCounter = 0;

        for (var pos = 0; pos < subtitles.length - 1; pos++) {
          var seqCurrent = subtitles[pos];

          var mCurrent = _TIMECODE_REGEX.exec(seqCurrent['Timecode']);

          if (mCurrent === null) continue;
          var seqNext = subtitles[pos + 1];

          var mNext = _TIMECODE_REGEX.exec(seqNext['Timecode']);

          if (mNext === null) continue;
          var appearTime = mCurrent[1].replace('.', ',').concat('0');
          var disappearTime = mNext[1].replace('.', ',').concat('0');
          var text = seqCurrent['Caption'].trim();

          if (text) {
            seqCounter++;
            text = text.replace(/\r?\n|\r/g, " ").trim();
            zhSrt += '{{ num }}\r\n{{ from }} --> {{ to }}\r\n{{ translate }}\r\n{{ text }}\r\n\r\n'.render({
              num: seqCounter,
              from: appearTime,
              to: disappearTime,
              translate: window.subtitleTrans[seqCounter - 1],
              text: text
            });
            enSrt += '{{ num }}\r\n{{ from }} --> {{ to }}\r\n{{ text }}\r\n\r\n'.render({
              num: seqCounter,
              from: appearTime,
              to: disappearTime,
              text: text
            });
          }
        }

        window.downloadPanel = document.getElementById('tab-video-download');
        window.downloadPanel.outerHTML = panelTemplate.render(videoData);
        document.getElementById('tab-video-download').addEventListener('click', function (e) {
          var el = e.target;

          while (el.tagName !== 'LI') {
            el = el.parentNode;

            if (el === document.getElementById('tab-video-download')) {
              el = null;
              break;
            }
          }

          if (el) {
            if (el.classList.contains('subtitle')) {
              var lang = el.dataset.lang;

              if (lang === 'zh') {
                download(zhSrt, videoData['subtitleNameZH'], 'text/plain');
              } else {
                download(enSrt, videoData['subtitleNameEN'], 'text/plain');
              }
            } else if (el.classList.contains('quality')) {
              el.classList.add('disable');
              el.classList.add('progress');
              var elProg = el.getElementsByClassName('file-prog')[0],
                prog = 0,
                quality = el.dataset.quality;
              elProg.innerText = prog + '%';
              GM_download({
                url: videoData['videoUrl' + quality],
                name: videoData['videoName' + quality],
                saveAs: true,
                onload: function onload() {
                  GM_notification("下载完成", videoData['videoName' + quality]);
                  el.classList.remove('disable');
                  el.classList.remove('progress');
                },
                onerror: function onerror() {
                  GM_notification("下载失败", videoData['videoName' + quality]);
                  el.classList.remove('disable');
                },
                onprogress: function onprogress(e) {
                  prog = (e.loaded / e.total * 100).toFixed(2);
                  elProg.innerText = prog + '%';
                  el.style.backgroundSize = prog + '% auto';
                }
              });
              GM_notification("开始下载", videoData['videoName' + quality]);
            }
          }
        });
        window.loading = false;
        document.getElementById('tab-video-download').classList.remove('loading');
        console.log("下载面板添加完成");
      });
    });
  }

  function getVideoUrl(courseId, videoId, callback) {
    GM_xmlhttpRequest({
      method: "GET",
      url: "https://www.lynda.com/ajax/course/".concat(courseId, "/").concat(videoId, "/play"),
      headers: {
        "accept": "application/json"
      },
      onload: function onload(response) {
        var data = JSON.parse(response.responseText);
        callback(data);
      }
    });
  }

  function fixSubtitles(videoId, callback) {
    GM_xmlhttpRequest({
      method: "GET",
      url: "https://www.lynda.com/ajax/player?videoId=".concat(videoId, "&type=transcript"),
      headers: {
        "accept": "application/json"
      },
      onload: function onload(response) {
        var data = JSON.parse(response.responseText);
        callback(data);
      }
    });
  }

  function transText() {
    document.getElementById('tab-video-download').classList.add('loading');
    window.subtitleTrans = [];
    var s = "",
      r = "",
      arr = [],
      num = 0,
      count = 0,
      subtitle = entries.text;
    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");
        window.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 = (window.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 + " 翻译完成");

        if (!window.loading) {
          addDownloadTab();
        }
      }
    });
  }

  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,
          "browser_id": window.transConfig.caiyun.browser_id
        };
        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:qgemv4jr1y38jyq6vhvi",
            "T-Authorization": window.transConfig.caiyun.jwt
          },
          data: JSON.stringify(data),
          onload: function onload(response) {
            var result = JSON.parse(response.responseText);

            var index = function index(t) {
              return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".indexOf(t); // 遍历密文 返回在字母表中的索引 非字母返回-1
            };

            var encode = function encode(e) {
              return e.split("").map(function (t) {
                return index(t) > -1 ? "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm"[index(t)] : t; // 若返回值大于-1 则取密码表对应位数的密值 否则返回其自身 并拼接为新字符串
              }).join("").replace(/[-_]/g, function (e) {
                return "-" == e ? "+" : "/";
              }).replace(/[^A-Za-z0-9\+\/]/g, ""); // 将转换后的字符串中的-转为+,_转为/ 并去空,得到base64编码字符串
            };

            var btou = function btou(e) { // 然后!&^@%#*&$%!(@$
              return e.replace(/[À-ß][€-¿]|[à-ï][€-¿]{2}|[ð-÷][€-¿]{3}/g, function (e) {
                switch (e.length) {
                  case 4:
                    var t = ((7 & e.charCodeAt(0)) << 18 | (63 & e.charCodeAt(1)) << 12 | (63 & e.charCodeAt(2)) << 6 | 63 & e.charCodeAt(3)) - 65536;
                    return String.fromCharCode(55296 + (t >>> 10)) + String.fromCharCode(56320 + (1023 & t));

                  case 3:
                    return String.fromCharCode((15 & e.charCodeAt(0)) << 12 | (63 & e.charCodeAt(1)) << 6 | 63 & e.charCodeAt(2));

                  default:
                    return String.fromCharCode((31 & e.charCodeAt(0)) << 6 | 63 & e.charCodeAt(1));
                }
              });
            };

            var encodeArr = result.target.map(function (words) {
              var base64 = encode(words); // "6Vh55c6p" -> "6Iu55p6c"
              return btou(atob(base64)); // "6Iu55p6c" -> "苹果" -> "苹果"
            });
            callback(encodeArr.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);
  }

  var liVideoDownloadTemplate = "\n<li class=\"tab-video-download\" data-tab=\"video-download\">\n    <a href=\"#tab\" id=\"video-download-tab\" aria-controls=\"tab-video-download\" tabindex=\"0\">Download</a>\n</li>";
  var videoDownloadTabTemplate = "\n<section id=\"tab-video-download\" class=\"{{ class }}\" aria-hidden=\"true\" style=\"{{ style }}\">\n    <div class=\"video-download-tab \" id=\"video-download\" tabindex=\"0\">\n        <div class=\"content unlocked\">\n            <div class=\"col-xs-12 col-sm-9\">\n                <div class=\"headline\">\n                    \u4E0B\u8F7D\u89C6\u9891\u548C\u5B57\u5E55\u6587\u4EF6\n                </div>\n                <p>\u5B57\u5E55\u6587\u4EF6\u683C\u5F0F\u4E3A.srt\uFF0C\u4E0B\u8F7D\u5728\u540E\u53F0\u8FDB\u884C\uFF0C\u8BF7\u8010\u5FC3\u7B49\u5F85</p>\n                <ul class=\"video-download-popover\">\n                    <li class=\"subtitle subtitle-zh\" data-lang=\"zh\">\n                        <a href=\"javascript:void(0)\" class=\"course-file clearfix\" tabindex=\"0\">\n                            <span class=\"file-name col-xs-8 col-sm-9 col-xl-10\">{{ subtitleNameZH }}</span>\n                            <span class=\"file-type\">[CHS]</span>\n                        </a>\n                    </li>\n                    <li class=\"subtitle subtitle-en\" data-lang=\"en\">\n                        <a href=\"javascript:void(0)\" class=\"course-file clearfix\" tabindex=\"0\">\n                            <span class=\"file-name col-xs-8 col-sm-9 col-xl-10\">{{ subtitleNameEN }}</span>\n                            <span class=\"file-type\">[ENG]</span>\n                        </a>\n                    </li>\n                    <hr>\n                    <li class=\"quality quality-720\" data-quality=\"720\">\n                        <a href=\"javascript:void(0)\" class=\"course-file clearfix\" tabindex=\"0\">\n                            <span class=\"file-name col-xs-8 col-sm-9 col-xl-10\">{{ videoName720 }}</span>\n                            <span class=\"file-prog\">-%</span>\n                            <span class=\"file-type\">[HD720P]</span>\n                        </a>\n                    </li>\n                    <li class=\"quality quality-540\" data-quality=\"540\">\n                        <a href=\"javascript:void(0)\" class=\"course-file clearfix\" tabindex=\"0\">\n                            <span class=\"file-name col-xs-8 col-sm-9 col-xl-10\">{{ videoName540 }}</span>\n                            <span class=\"file-prog\">-%</span>\n                            <span class=\"file-type\">[SD540P]</span>\n                        </a>\n                    </li>\n                    <li class=\"quality quality-360\" data-quality=\"360\">\n                        <a href=\"javascript:void(0)\" class=\"course-file clearfix\" tabindex=\"0\">\n                            <span class=\"file-name col-xs-8 col-sm-9 col-xl-10\">{{ videoName360 }}</span>\n                            <span class=\"file-prog\">-%</span>\n                            <span class=\"file-type\">[360P]</span>\n                        </a>\n                    </li>\n                    <li class=\"quality quality-64\" data-quality=\"64\">\n                        <a href=\"javascript:void(0)\" class=\"course-file clearfix\" tabindex=\"0\">\n                            <span class=\"file-name col-xs-8 col-sm-9 col-xl-10\">{{ videoName64 }}</span>\n                            <span class=\"file-prog\">-%</span>\n                            <span class=\"file-type\">[Audio]</span>\n                        </a>\n                    </li>\n                </ul>\n            </div>\n            <div class=\"hidden-xs col-sm-3\">\n                <div class=\"files unlocked\">\n                    <div>\n                        <i class=\"lyndacon project-files\"></i>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n    <div class=\"video-download-disclaimer\">\n        <p>\u4E0B\u8F7D\u6B64\u6587\u4EF6\u610F\u5473\u7740\u4F60\u540C\u610F\u4F7F\u7528\u6B64\u6587\u4EF6\u7684\u884C\u4E3A\u987B\u7B26\u5408<a href=\"/aboutus/lotterms.aspx\" target=\"_blank\">\u670D\u52A1\u6761\u6B3E</a>\u4E2D\u7684\u7EA6\u5B9A\uFF0C\u4F60\u53EA\u80FD\u4EE5\u4E2A\u4EBA\u7528\u9014\u5728\u8BFE\u7A0B\u8BA2\u9605\u671F\u95F4\u89C2\u770B\u6B64\u6587\u4EF6\u4E2D\u7684\u5185\u5BB9\uFF0C\u4E0D\u5141\u8BB8\u516C\u5F00\u6216\u6563\u5E03\u6B64\u6587\u4EF6\u5185\u5BB9</p>\n    </div>\n</section>";

  function addStyle(css) {
    if (typeof GM_addStyle != "undefined") {
      GM_addStyle(css);
    } else if (typeof PRO_addStyle != "undefined") {
      PRO_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);
      }
    }
  }

  String.prototype.render = function (context) {
    return this.replace(/{{(.*?)}}/g, function (_match, key) {
      return context[key.trim()];
    });
  };

  Number.prototype.pad = function (size) {
    var s = String(this);

    while (s.length < (size || 2)) {
      s = "0" + s;
    }

    return s;
  };

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

  function addDownloadPannel() {
    // 翻译目录&添加下载面板HTML
    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.dataset.index = i.pad(2), videoName.dataset.original = videoName.innerText;
        videoName.dataset.translate = r[i];
        videoName.innerText = r[i] + ' (' + videoName.innerText + ')';
      });
    }); // 添加下载面板HTML

    var css = '.video-download-tab{position:relative}.video-download-tab.fade .content{max-height:300px;padding-bottom:30px}.video-download-tab .content{max-height:338px;min-height:220px;overflow-y:auto;padding:30px 20px;}.video-download-tab .content .col-xs-6{position:static}.video-download-tab .content.locked{min-height:280px}.video-download-tab .content.unlocked li a:hover{background:#f7f7f7;text-decoration:none}.video-download-tab .headline{font-size:15px;font-weight:700;line-height:18px}.video-download-tab hr{margin:6px 0;}.video-download-tab .video-download-popover+.headline{margin-top:10px}.video-download-tab p{line-height:30px;margin:0}.video-download-tab li.disable{pointer-events:none;opacity:.5;}.video-download-tab li.progress{border-radius:6px;background:linear-gradient(#eee,#eee);background-size:0% auto;background-repeat:no-repeat;}.video-download-tab li .file-prog{display:none;}.video-download-tab li.progress .file-prog{display:inline-block;}.video-download-tab .quality-64 a .file-type{color:#9E9E9E;}.video-download-tab .quality-720 a .file-type,.video-download-tab .subtitle-zh a .file-type{color:#4ec57d;}.video-download-tab li a,.video-download-tab li>span{display:block;line-height:1em;padding:10px}.video-download-tab li a .lock,.video-download-tab li>span .lock{color:#999;font-size:10px;margin:-3px 0 0 -13px;vertical-align:text-top}.video-download-tab li a .file-type,.video-download-tab li>span .file-type{float:right;font-weight:bold;color:#4e5bc5;}.video-download-tab li a .file-name,.video-download-tab li>span .file-name{display:inline-block;line-height:1em;max-width:610px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.video-download-tab .files{margin-top:5px;text-align:center;z-index:2;float:right;margin-right:15px}.video-download-tab .files.unlocked .project-files{color:#ddd}.video-download-tab .files .project-files{color:#666;font-size:120px}.video-download-tab .files .discover{font-size:17px;line-height:21px}.video-download-tab .files button{display:block;margin-top:15px;width:100%}.video-download-disclaimer{margin-top:20px;border-top:2px solid #eee;padding:20px 30px}.video-download-disclaimer p{line-height:1.4em;margin-bottom:0}#tab-video-download.loading{pointer-events:none;}#tab-video-download.loading .video-download-tab .content{overflow:hidden;}#tab-video-download.loading p,#tab-video-download.loading span,#tab-video-download.loading .headline,#tab-video-download.loading .video-download-disclaimer a{box-sizing:border-box;color:transparent;background:#eee;padding:6px;}#tab-video-download.loading .video-download-disclaimer a,#tab-video-download.loading .headline{display:none;}#tab-video-download.loading p,#tab-video-download.loading span.file-name{box-sizing:border-box;color:transparent;background:#eee;padding:15px;margin:10px 0;animation-duration:2s;animation-fill-mode:forwards;animation-iteration-count:infinite;animation-name:placeHolderShimmer;animation-timing-function:linear;background:#f6f7f8;background:linear-gradient(to right,#eeeeee 8%,#dddddd 18%,#eeeeee 33%);background-size:1400px 100px;}#tab-video-download.loading span.file-name{padding:6px;margin:0;}@keyframes placeHolderShimmer{0%{background-position:-200px 0}33%,100%{background-position:900px 0}}';
    addStyle(css);
    window.downloadTab = document.createElement('li');
    document.getElementById('course-tabs').appendChild(window.downloadTab);
    window.downloadTab.outerHTML = liVideoDownloadTemplate;
    window.downloadPanel = document.createElement('section');
    document.getElementsByClassName('tab-container')[0].appendChild(window.downloadPanel);
    window.downloadPanel.outerHTML = videoDownloadTabTemplate.render({
      "class": 'loading',
      style: document.getElementsByClassName('tab-video-download')[0].classList.contains('selected') ? 'display:block' : 'display:none'
    });
    window.downloadPanel = document.getElementById('tab-video-download');
  };

  function before() {
    window.transConfig = {
      caiyun: {}
    };

    if (transServer === 'caiyun') {
      Fingerprint2 && Fingerprint2.get({}, function (components) {
        var values = components.map(function (component) {
          return component.value;
        });
        window.transConfig.caiyun.browser_id = Fingerprint2.x64hash128(values.join(''), 233);
        GM_xmlhttpRequest({
          method: "POST",
          url: "https://api.interpreter.caiyunai.com/v1/user/jwt/generate",
          headers: {
            "accept": "application/json",
            "content-type": "application/json; charset=UTF-8",
            "X-Authorization": "token:qgemv4jr1y38jyq6vhvi"
          },
          data: JSON.stringify({
            "browser_id": window.transConfig.caiyun.browser_id
          }),
          onload: function onload(response) {
            var result = JSON.parse(response.responseText);
            window.transConfig.caiyun.jwt = result.jwt;
            addDownloadPannel();
          }
        });
      });
    } else {
      addDownloadPannel();
    }
  }
})();