Youtube double language subtitle / Youtube 双语字幕

Youtube double language subtitle / Youtube 双语字幕. 如果不能自动加载,请关闭字幕再次打开即可。默认语言为浏览器首选语言。

目前为 2022-12-25 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Youtube double language subtitle / Youtube 双语字幕
  3. // @version 1.8.0
  4. // @description Youtube double language subtitle / Youtube 双语字幕. 如果不能自动加载,请关闭字幕再次打开即可。默认语言为浏览器首选语言。
  5. // @author Coink
  6. // @match *://www.youtube.com/watch?v=*
  7. // @match *://www.youtube.com
  8. // @match *://www.youtube.com/*
  9. // @require https://unpkg.com/ajax-hook@2.1.3/dist/ajaxhook.min.js
  10. // @grant none
  11. // @run-at document-start
  12. // @namespace https://github.com/CoinkWang/Y2BDoubleSubs
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. let localeLang = document.documentElement.lang || navigator.language || 'en' // follow the language used in YouTube Page
  17. // localeLang = 'zh' // uncomment this line to define the language you wish here
  18. ah.proxy({
  19. onRequest: (config, handler) => {
  20. handler.next(config);
  21. },
  22. onResponse: (response, handler) => {
  23. if (response.config.url.includes('/api/timedtext') && !response.config.url.includes('&translate_h00ked')) {
  24. let xhr = new XMLHttpRequest();
  25. // Use RegExp to clean '&tlang=...' in our xhr request params while using Y2B auto translate.
  26. let url = response.config.url
  27. url = url.replace(/(^|[&?])tlang=[^&]*/g, '')
  28. url = `${url}&tlang=${localeLang}&translate_h00ked`
  29. xhr.open('GET', url, false);
  30. xhr.send();
  31. let defaultJson = null
  32. if (response.response) {
  33. const jsonResponse = JSON.parse(response.response)
  34. if (jsonResponse.events) defaultJson = jsonResponse
  35. }
  36. const localeJson = JSON.parse(xhr.response)
  37. let isOfficialSub = true;
  38. for (const defaultJsonEvent of defaultJson.events) {
  39. if (defaultJsonEvent.segs && defaultJsonEvent.segs.length > 1) {
  40. isOfficialSub = false;
  41. break;
  42. }
  43. }
  44. // Merge default subs with locale language subs
  45. if (isOfficialSub) {
  46. // when length of segments are the same
  47. for (let i = 0, len = defaultJson.events.length; i < len; i++) {
  48. const defaultJsonEvent = defaultJson.events[i]
  49. if (!defaultJsonEvent.segs) continue
  50. const localeJsonEvent = localeJson.events[i]
  51. if (`${defaultJsonEvent.segs[0].utf8}`.trim() !== `${localeJsonEvent.segs[0].utf8}`.trim()) {
  52. // avoid merge subs while the are the same
  53. defaultJsonEvent.segs[0].utf8 += ('\n' + localeJsonEvent.segs[0].utf8)
  54. }
  55. }
  56. response.response = JSON.stringify(defaultJson)
  57. } else {
  58. // when length of segments are not the same (e.g. automatic generated english subs)
  59. let pureLocalEvents = localeJson.events.filter(event => event.aAppend !== 1 && event.segs)
  60. for (const defaultJsonEvent of defaultJson.events) {
  61. if (!defaultJsonEvent.segs) continue
  62. let currentStart = defaultJsonEvent.tStartMs,
  63. currentEnd = currentStart + defaultJsonEvent.dDurationMs
  64. let currentLocalEvents = pureLocalEvents.filter(pe => currentStart <= pe.tStartMs && pe.tStartMs < currentEnd)
  65. let localLine = ''
  66. for (const ev of currentLocalEvents) {
  67. for (const seg of ev.segs) {
  68. localLine += seg.utf8
  69. }
  70. localLine += ''; // add ZWSP to avoid words stick together
  71. }
  72. let defaultLine = ''
  73. for (const seg of defaultJsonEvent.segs) {
  74. defaultLine += seg.utf8
  75. }
  76. defaultJsonEvent.segs[0].utf8 = defaultLine + '\n' + localLine
  77. defaultJsonEvent.segs = [defaultJsonEvent.segs[0]]
  78. }
  79. response.response = JSON.stringify(defaultJson)
  80. }
  81. }
  82. handler.resolve(response)
  83. }
  84. })
  85. })();

QingJ © 2025

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