您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
专门针对油管手机版:自动打开声音,自动开启字幕并机翻中英双语,自动跳广告。安卓浏览器可使用Firefox或者旧版本yandex。有问题联系:知乎@邓强龙,不一定每个视频都有中英字幕。 测试中英双语字幕视频网址: https: //m.youtube.com/watch?v=xFQGKwVijaM&t=2s
当前为
// ==UserScript== // @name YouTube移动端中英双语字幕 // @namespace https://github.com/wlpha/Youtube-Automatic-Translation-V2 // @match *://m.youtube.com // @match *://m.youtube.com/* // @match *://m.youtube.com/watch?v=* // @grant unsafeWindow // @author github@warmilk // @version 1.0.0 // @description 专门针对油管手机版:自动打开声音,自动开启字幕并机翻中英双语,自动跳广告。安卓浏览器可使用Firefox或者旧版本yandex。有问题联系:知乎@邓强龙,不一定每个视频都有中英字幕。 测试中英双语字幕视频网址: https: //m.youtube.com/watch?v=xFQGKwVijaM&t=2s // ==/UserScript== (function() { const FORMET_SUFFIX = '&fmt=json3&xorb=2&xobt=3&xovt=3&tlang=' const THIS = this; //当前作用域的window对象 const ELEMENTID = { //字幕轨道的HTML元素的id属性值 zh: 'QIANG_LONG_CAPTION_zh', //中文 en: 'QIANG_LONG_CAPTION_en', //英文 } var styleOptions = { // 样式配置项 height: '18%', //中英字幕的父容器相对于<video>区域的高度占比 }; var captionFetchUrl = { zh: '', //中文字幕str格式array json的下载地址 en: '', //英文字幕str格式array json的下载地址 } var isHasCaption = { //该视频是否存在某种语言的字幕 zh: false, en: false, } var videoNode //video标签的dom节点 function isVideoAdsTime() { var ad = document.querySelector('.ad-showing'); var skipAdButton = document.querySelector('.ytp-ad-skip-button'); if (skipAdButton) { skipAdButton.click(); } return ad != null; } //自动跳过广告 function FuckAds() { setInterval(function() { try { isVideoAdsTime(); } catch (err) {} }, 1000); } //自动点击左上角音量按钮,打开声音 function OpenVolume() { var volumeButton = document.querySelector('.ytp-unmute.ytp-popup.ytp-button.ytp-unmute-animated.ytp-unmute-shrink'); volumeButton.click(); return volumeButton != null; } function GenerateCaptionsUrl(toLang) { var url = THIS.ytInitialPlayerResponse.captions.playerCaptionsTracklistRenderer.captionTracks[0].baseUrl; if (url.indexOf('&lang=zh-Hant') !== -1) { url = url.replace('&lang=zh-Hant', '&lang=zh-Hans'); } return url + FORMET_SUFFIX + toLang; } //获取字幕的请求地址 function GetCaptionUrl() { try { // if (THIS == null) { // setTimeout(function() { // continue; // }, 1000); // } var captionTracks = THIS.ytInitialPlayerResponse.captions.playerCaptionsTracklistRenderer.captionTracks if (captionTracks.length > 0) { //判断是否存在字幕可用 for (var i = 0; i < captionTracks.length; i++) { var item = captionTracks[i]; switch (item.languageCode) { case 'zh-Hans': isHasCaption.zh = true; console.log('这个视频有中文字幕(zh-Hans)'); break; case 'zh-CN': console.log('这个视频有中文字幕(zh-CN)'); break; case 'en': isHasCaption.en = true; console.log('这个视频有英文字幕(en)'); break; default: isHasCaption.en = true; isHasCaption.zh = true; break; } } captionFetchUrl.zh = GenerateCaptionsUrl('zh-Hans') captionFetchUrl.en = GenerateCaptionsUrl('en') } else { console.log('这个视频没有字幕'); } } catch (err) {} } function both_ZH_EN(parentNode) { var wrapper_zh = document.createElement('div'); var wrapper_en = document.createElement('div'); parentNode.appendChild(wrapper_zh) parentNode.appendChild(wrapper_en) var caption_zh = document.createElement('div'); var caption_en = document.createElement('div'); wrapper_zh.appendChild(caption_zh) wrapper_en.appendChild(caption_en) parentNode.style.pointerEvents = 'none'; parentNode.style.position = 'absolute'; parentNode.style.zIndex = 2; parentNode.style.width = '100%'; parentNode.style.height = styleOptions.height; // parentNode.style.backgroundColor = 'red'; parentNode.style.bottom = 0; parentNode.style.left = 0; wrapper_zh.style.position = wrapper_en.style.position = 'absolute'; wrapper_zh.style.zIndex = wrapper_en.style.zIndex = 3; wrapper_zh.style.width = wrapper_en.style.width = '100%'; wrapper_zh.style.height = wrapper_en.style.height = '50%'; wrapper_zh.style.left = wrapper_en.style.left = 0; wrapper_zh.style.textAlign = wrapper_en.style.textAlign = 'center'; // wrapper_zh.style.backgroundColor = 'yellow'; // wrapper_en.style.backgroundColor = 'blue'; wrapper_zh.style.bottom = 0; wrapper_en.style.top = 0; caption_zh.style.backgroundColor = caption_en.style.backgroundColor = '#000'; caption_zh.style.color = caption_en.style.color = '#fff'; caption_zh.style.display = caption_en.style.display = 'inline-block'; caption_zh.id = ELEMENTID.zh; caption_en.id = ELEMENTID.en; caption_zh.innerText = ''; caption_en.innerText = ''; } function just_EN(parentNode) { var wrapper_en = document.createElement('div'); parentNode.appendChild(wrapper_en) var caption_en = document.createElement('div'); wrapper_en.appendChild(caption_en) parentNode.style.pointerEvents = 'none'; parentNode.style.position = 'absolute'; parentNode.style.zIndex = 2; parentNode.style.width = '100%'; parentNode.style.height = styleOptions.height; // parentNode.style.backgroundColor = 'red'; parentNode.style.bottom = 0; parentNode.style.left = 0; wrapper_en.style.position = 'absolute'; wrapper_en.style.zIndex = 3; wrapper_en.style.width = '100%'; wrapper_en.style.height = '50%'; wrapper_en.style.left = 0; wrapper_en.style.textAlign = 'center'; wrapper_en.style.bottom = 0; caption_en.style.backgroundColor = '#000'; caption_en.style.color = '#fff'; caption_en.style.display = 'inline-block'; caption_en.id = ELEMENTID.en; caption_en.innerText = ''; } function just_ZH(parentNode) { var wrapper_zh = document.createElement('div'); parentNode.appendChild(wrapper_zh) var caption_zh = document.createElement('div'); wrapper_zh.appendChild(caption_zh) parentNode.style.pointerEvents = 'none'; parentNode.style.position = 'absolute'; parentNode.style.zIndex = 2; parentNode.style.width = '100%'; parentNode.style.height = styleOptions.height; // parentNode.style.backgroundColor = 'red'; parentNode.style.bottom = 0; parentNode.style.left = 0; wrapper_zh.style.position = 'absolute'; wrapper_zh.style.zIndex = 3; wrapper_zh.style.width = '100%'; wrapper_zh.style.height = '50%'; wrapper_zh.style.left = 0; wrapper_zh.style.textAlign = 'center'; wrapper_zh.style.bottom = 0; caption_zh.style.backgroundColor = '#000'; caption_zh.style.color = '#fff'; caption_zh.style.display = 'inline-block'; caption_zh.id = ELEMENTID.en; caption_zh.innerText = ''; } //创建字幕控件的 HTML dom结构 function CreateCaptionWidget() { var videoPlayerWrapper = document.querySelector('#player-container-id') var captionWidget = document.createElement('div'); videoPlayerWrapper.appendChild(captionWidget) if (isHasCaption.zh && isHasCaption.en) { both_ZH_EN(captionWidget); } if (!isHasCaption.zh && isHasCaption.en) { just_EN(captionWidget); } if (isHasCaption.zh && !isHasCaption.en) { just_ZH(captionWidget); } if (!isHasCaption.zh && !isHasCaption.en) { return; } } /** * 发请求获取YouTube提供的str格式的字幕 * * @param ajaxUrl {String} 获取字幕的请求url * @param languageTag {String} 字幕语言的标记,zh:中文,en:英文 * @param callbackFn {Function} 成功fetch字幕数据后的回调函数 */ function FetchCaptionFromYoutube(ajaxUrl, languageTag) { var fullUrl = location.origin + ajaxUrl var langMap = { 'en': '【英文】', 'zh': '【中文】', } if (ajaxUrl !== '') { fetch(ajaxUrl) .then(function(response) { response.json().then(function(response) { var resultList = response.events; console.log('获取', langMap[languageTag], '字幕成功。url:', fullUrl); InjectCaptionsToPage(resultList, languageTag) }).catch(function(err) { console.log(err); console.log('获取', langMap[languageTag], '字幕失败。url:', fullUrl); }); }).catch(function(err) { console.log('获取', langMap[languageTag], '字幕失败。url:', fullUrl); }); } } function GetTackNode(languageTag) { var id = '#' + ELEMENTID[languageTag] return document.querySelector(id) } // 生成某一帧的字幕 function GenerateOnceFrameCaption(captionContentList, targetNode) { videoNode.currentTime = videoNode.currentTime * 1000; //<video>元素的currentTime属性单位为秒 if (captionContentList != null && captionContentList instanceof Array) { for (var i = 0; i < captionContentList.length; i++) { var item = captionContentList[i]; if (!item || !item.segs || !(item.segs instanceof Array)) { continue; } if (videoNode.currentTime >= item.tStartMs && videoNode.currentTime <= item.tStartMs + item.dDurationMs) { if (targetNode !== null) { try { var text = []; for (var k = 0; k < item.segs.length; k++) { text.push(item.segs[k].utf8); } var displayText = text.join(' '); displayText = displayText.replace(/\s+/ig, ' '); if (targetNode.innerText !== displayText) { targetNode.innerText = displayText; } } catch (err) { continue; } } break; } } } } /** * 把字幕数据注入到页面(str就是time txt也就是带时间轴的txt) * * @param captionContentList {Array} 字幕数据 * @param languageTag {String} zh:中文,en:英文 */ function InjectCaptionsToPage(captionContentList, languageTag) { setInterval(function() { var targetNode = GetTackNode(languageTag); //{HTMLnode} 字幕轨道的html元素节点(注入数据的目标节点) videoNode = document.querySelector('video.video-stream.html5-main-video') //<video>标签对应的元素 GenerateOnceFrameCaption(captionContentList, targetNode) }, 300); } function Main() { GetCaptionUrl(); CreateCaptionWidget(); FetchCaptionFromYoutube(captionFetchUrl.zh, 'zh'); FetchCaptionFromYoutube(captionFetchUrl.en, 'en'); } window.addEventListener("load", function() { OpenVolume(); //FuckAds(); Main(); }) // window.addEventListener("popstate", function() { // console.log("popstate"); // Main(); // }) // window.addEventListener("pushState", function() { // console.log("pushState"); // Main(); // }) // window.addEventListener("replaceState", function() { // console.log("replaceState"); // Main(); // }) // window.addEventListener("hashchange", function() { // console.log("hashchange"); // Main(); // }) // THIS.history.forward = function() { // console.log('forward'); // } })();