Youtube 双语字幕全平台

Youtube 双语字幕,支持 PC、移动端 Tampermonkey,及 Via 等轻量浏览器。

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

QingJ © 2025

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