Youtube dual subtitle / Youtube 双语字幕全平台

Youtube dual subtitle / Youtube 双语字幕全平台。移动端(mobile)修复,双端适用,而且支持 Via 浏览器。

当前为 2023-04-26 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Youtube dual subtitle / Youtube 双语字幕全平台
  3. // @version 2.0.0
  4. // @description Youtube dual subtitle / Youtube 双语字幕全平台。移动端(mobile)修复,双端适用,而且支持 Via 浏览器。
  5. // @author Coink & BITO
  6. // @match *://www.youtube.com/watch?v=*
  7. // @match *://www.youtube.com
  8. // @match *://www.youtube.com/*
  9. // @match *://m.youtube.com/watch?v=*
  10. // @match *://m.youtube.com
  11. // @match *://m.youtube.com/*
  12. // @require https://unpkg.com/ajax-hook@2.1.3/dist/ajaxhook.min.js
  13. // @grant none
  14. // @run-at document-start
  15. // @namespace https://github.com/jk278/youtube-dual-subtitle
  16. // ==/UserScript==
  17.  
  18. /*
  19. 如果未自动加载,请切换字幕或关闭后再打开即可。默认语言为浏览器首选语言。
  20. */
  21.  
  22. (function () {
  23. 'use strict';
  24. // 检测浏览器首选语言,如果没有,设置为英语
  25. let localeLang = navigator.language.split('-')[0] || 'en'; // 跟随 YouTube 页面所用语言
  26. // localeLang = 'zh' // 取消注释此行以在此处定义您希望的语言
  27. // 启用双语字幕
  28. function enableSubs() {
  29. ah.proxy({
  30. onRequest: (config, handler) => {
  31. handler.next(config); // 处理下一个请求
  32. },
  33. onResponse: (response, handler) => {
  34. // 如果请求的 URL 包含 '/api/timedtext' 并且没有 '&translate_h00ked',则表示请求双语字幕
  35. if (response.config.url.includes('/api/timedtext') && !response.config.url.includes('&translate_h00ked')) {
  36. let xhr = new XMLHttpRequest(); // 创建新的 XMLHttpRequest
  37. // 使用 RegExp 清除我们的 xhr 请求参数中的 '&tlang=...',同时使用 Y2B 自动翻译
  38. let url = response.config.url.replace(/(^|[&?])tlang=[^&]*/g, '');
  39. url = `${url}&tlang=${localeLang}&translate_h00ked`;
  40. xhr.open('GET', url, false); // 打开 xhr 请求
  41. xhr.send(); // 发送 xhr 请求
  42. let defaultJson = null; // 声明默认 JSON 变量
  43. if (response.response) {
  44. const jsonResponse = JSON.parse(response.response);
  45. if (jsonResponse.events) defaultJson = jsonResponse;
  46. }
  47. const localeJson = JSON.parse(xhr.response); // 解析 xhr 响应
  48. let isOfficialSub = true;
  49. for (const defaultJsonEvent of defaultJson.events) {
  50. if (defaultJsonEvent.segs && defaultJsonEvent.segs.length > 1) {
  51. isOfficialSub = false;
  52. break;
  53. }
  54. }
  55. // 将默认字幕与本地语言字幕合并
  56. if (isOfficialSub) {
  57. // 如果片段长度相同
  58. for (let i = 0, len = defaultJson.events.length; i < len; i++) {
  59. const defaultJsonEvent = defaultJson.events[i];
  60. if (!defaultJsonEvent.segs) continue;
  61. const localeJsonEvent = localeJson.events[i];
  62. if (`${defaultJsonEvent.segs[0].utf8}`.trim() !== `${localeJsonEvent.segs[0].utf8}`.trim()) {
  63. // 避免在两者相同时合并字幕
  64. defaultJsonEvent.segs[0].utf8 += ('\n' + localeJsonEvent.segs[0].utf8);
  65. }
  66. }
  67. response.response = JSON.stringify(defaultJson); // 更新响应
  68. } else {
  69. // 如果片段长度不同(例如:自动生成的英语字幕)
  70. let pureLocalEvents = localeJson.events.filter(event => event.aAppend !== 1 && event.segs);
  71. for (const defaultJsonEvent of defaultJson.events) {
  72. if (!defaultJsonEvent.segs) continue;
  73. let currentStart = defaultJsonEvent.tStartMs,
  74. currentEnd = currentStart + defaultJsonEvent.dDurationMs;
  75. let currentLocalEvents = pureLocalEvents.filter(pe => currentStart <= pe.tStartMs && pe.tStartMs < currentEnd);
  76. let localLine = '';
  77. for (const ev of currentLocalEvents) {
  78. for (const seg of ev.segs) {
  79. localLine += seg.utf8;
  80. }
  81. localLine += ''; // 添加零宽空格,以避免单词粘在一起
  82. }
  83. let defaultLine = '';
  84. for (const seg of defaultJsonEvent.segs) {
  85. defaultLine += seg.utf8;
  86. }
  87. defaultJsonEvent.segs[0].utf8 = defaultLine + '\n' + localLine;
  88. defaultJsonEvent.segs = [defaultJsonEvent.segs[0]];
  89. }
  90. response.response = JSON.stringify(defaultJson); // 更新响应
  91. }
  92. }
  93. handler.resolve(response); // 处理响应
  94. }
  95. });
  96. }
  97. // 当文档加载完成并且字幕可用时,调用 enableSubs 函数启用双语字幕
  98. if (document.readyState === 'complete') {
  99. enableSubs(); // 如果文档已经加载完成,则启用双语字幕
  100. } else {
  101. window.addEventListener('load', enableSubs); // 如果文档尚未加载完成,添加事件监听器以在加载完成时启用双语字幕
  102. }
  103. })();