Last active
April 15, 2023 20:15
-
-
Save krast/df40017ac8d024d5cb32dbb4c4147ffc to your computer and use it in GitHub Desktop.
tampermonkey scritp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Youtube Auto Subtitle Downloader v7 | |
https://greasyfork.org/scripts/5367-youtube-auto-subtitle-downloader-v7/code/Youtube%20Auto%20Subtitle%20Downloader%20v7.user.js | |
# Download YouTube Videos as MP4 | |
https://greasyfork.org/scripts/1317-download-youtube-videos-as-mp4/code/Download%20YouTube%20Videos%20as%20MP4.user.js | |
# Youtube Subtitle Downloader v8 | |
https://greasyfork.org/scripts/5368-youtube-subtitle-downloader-v8/code/Youtube%20Subtitle%20Downloader%20v8.user.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Download YouTube Videos as MP4 | |
// @description Adds a button that lets you download YouTube videos. | |
// @homepageURL https://github.com/gantt/downloadyoutube | |
// @author Gantt | |
// @version 1.8.10 | |
// @date 2017-02-13 | |
// @namespace http://googlesystem.blogspot.com | |
// @include http://www.youtube.com/* | |
// @include https://www.youtube.com/* | |
// @exclude http://www.youtube.com/embed/* | |
// @exclude https://www.youtube.com/embed/* | |
// @match http://www.youtube.com/* | |
// @match https://www.youtube.com/* | |
// @match http://s.ytimg.com/yts/jsbin/* | |
// @match https://s.ytimg.com/yts/jsbin/* | |
// @match http://manifest.googlevideo.com/* | |
// @match https://manifest.googlevideo.com/* | |
// @match http://*.googlevideo.com/videoplayback* | |
// @match https://*.googlevideo.com/videoplayback* | |
// @match http://*.youtube.com/videoplayback* | |
// @match https://*.youtube.com/videoplayback* | |
// @connect googlevideo.com | |
// @connect ytimg.com | |
// @grant GM_xmlhttpRequest | |
// @grant GM_getValue | |
// @grant GM_setValue | |
// @run-at document-end | |
// @license MIT License | |
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAB3RJTUUH2wMOCgIoGUYEAQAAAAlwSFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjwv8YQUAAAGSUExURfi/JO/v797e3sbGxq2traWlpZSUlJycnNbW1oyEhIRaWow5OZQhIZwYGKUQEKUICK0ICJQxMYxKSoxzc4x7e4RCQpQYGKUAAK0AALUAAL0AAK0QEIxra5QpKa0YGIxSUsYAAKUhIZR7e87Ozr0ICJRSUr29vYxjY6U5OaUpKa0hIb21tZwAALUICO/Ozu/GxqUxMZSEhLUYGO/W1r0YGKVCQpQQEL0pKffe3vfW1pxra5Q5OcZCQvfn585CQr2trZx7e8ZSUs5SUq05Oc5jY9ZjY84AAKWMjM5zc957e60pKdaMjOelpbWcnLWUlLVCQsYYGMYICNbOzpQICMYhIbV7e5xaWt6cnPfv79bGxt6lpe+9vc5KSs6lpb0xMc6EhM69vbUxMbUhIb1aWs61tcZaWuecnMYxMb1KSsZjY96UlNa1td7W1r17e9a9vZwQEN6trb1jY8YQENZra+fOzr1zc85aWufe3t6MjMY5OdZaWt61tdZ7e+/n5+e9vc6MjMZra+/e3ue1tdalpd7GxrUpKalL4aAAAAABdFJOUwBA5thmAAACxklEQVR42uXX/1/SQBgH8NuAoEQ2ijgbBivJLznBAiUUKiyJSgOVAk0tKZKw75mRRt/7v4MBY8ezjW39Vs8v8rqHz/u1jbvbidC/XL8KmcpOqVT6nSjXjooGw8WfFd+QWGfE4oLbtbr++PdMOy0BDYLjEj/0xevfWIyVAI7b/aIj/9WHsRrA8Yf9bqSexVgD4Lic9kWE/LgPwPGfNfJHDO4P8Iuq+S2M9QD8oUp+nxEAcFCtfgIA/14x/9ElAKDQbNQAwN9VAiYEABy0OgsAWAnB/AcBAtVWawkAfJ4CD0BQADZavYcQgI9h3CCQjpD5PcEgwG+SwLRhIL0vz78SjAPEU3hrHODfyX4I6rUJIP0G3oExoNwFXpoB+HwXmDEFpF9IwKA5YK+Tp9fMAdUOsC6YA553gKcmgdTfAhV1oMQqADndQDmJ0AZLAsFnCIV3VYDHJLAjDkZKciAaFz/lCeBJB1glgXBrNLndBWLJ9uZGAI+keTBLANL8SnWAzWRniAC2pG+6lQF0hfjTqCIBrEvjDwiggFSLuIUoLY0vEwAbUcsnc/LlnO02HGvEz+hXEeJ5Yj+4L2vNkxOJDSnlQzliIq2synr3embiUBjmw0FyU83KX04Ob+9aAK/Ppd5deZloz4HFlCHzt3sX0x2a6LcvQb4ab8r7i+DVdqvnCq/D5ZzqdhfAcr5B9wD0PNwPEu0ZnLwK9oPgNfCQJ2fhhhITJ3E8BjeUOXA+QNQlBh5xLjemVCgKjzgzNIJFjWF4yJoKhafgIWt6VHGmjgR0HvMuTipPdWQJ6AImbBRSE8aY/sC4er5xFx5vHyB4YRRpFWUf0AL4c+dHkHZRFo9TDeB9Aa3Llwjr8FlFwB+wO/rHm0VbPae9mPini/O5h/XGxatw2I6fGHAOuhiGZVxO98lTdgutP94yaIvVdqxZdpvFYTT9X9UfqQQlTXlm8wkAAAAASUVORK5CYII= | |
// ==/UserScript== | |
(function () { | |
var FORMAT_LABEL={'18':'MP4 360p','22':'MP4 720p','43':'WebM 360p','44':'WebM 480p','45':'WebM 720p','46':'WebM 1080p','135':'MP4 480p - no audio','137':'MP4 1080p - no audio','138':'MP4 2160p - no audio','140':'M4A 128kbps - audio','264':'MP4 1440p - no audio','266':'MP4 2160p - no audio','298':'MP4 720p60 - no audio','299':'MP4 1080p60 - no audio'}; | |
var FORMAT_TYPE={'18':'mp4','22':'mp4','43':'webm','44':'webm','45':'webm','46':'webm','135':'mp4','137':'mp4','138':'mp4','140':'m4a','264':'mp4','266':'mp4','298':'mp4','299':'mp4'}; | |
var FORMAT_ORDER=['18','43','135','44','22','298','45','137','299','46','264','138','266','140']; | |
var FORMAT_RULE={'mp4':'all','webm':'none','m4a':'all'}; | |
// all=display all versions, max=only highest quality version, none=no version | |
// the default settings show all MP4 videos | |
var SHOW_DASH_FORMATS=false; | |
var BUTTON_TEXT={'ar':'تنزيل','cs':'Stáhnout','de':'Herunterladen','en':'Download','es':'Descargar','fr':'Télécharger','hi':'डाउनलोड','hu':'Letöltés','id':'Unduh','it':'Scarica','ja':'ダウンロード','ko':'내려받기','pl':'Pobierz','pt':'Baixar','ro':'Descărcați','ru':'Скачать','tr':'İndir','zh':'下载','zh-TW':'下載'}; | |
var BUTTON_TOOLTIP={'ar':'تنزيل هذا الفيديو','cs':'Stáhnout toto video','de':'Dieses Video herunterladen','en':'Download this video','es':'Descargar este vídeo','fr':'Télécharger cette vidéo','hi':'वीडियो डाउनलोड करें','hu':'Videó letöltése','id':'Unduh video ini','it':'Scarica questo video','ja':'このビデオをダウンロードする','ko':'이 비디오를 내려받기','pl':'Pobierz plik wideo','pt':'Baixar este vídeo','ro':'Descărcați acest videoclip','ru':'Скачать это видео','tr': 'Bu videoyu indir','zh':'下载此视频','zh-TW':'下載此影片'}; | |
var DECODE_RULE=[]; | |
var RANDOM=7489235179; // Math.floor(Math.random()*1234567890); | |
var CONTAINER_ID='download-youtube-video'+RANDOM; | |
var LISTITEM_ID='download-youtube-video-fmt'+RANDOM; | |
var BUTTON_ID='download-youtube-video-button'+RANDOM; | |
var DEBUG_ID='download-youtube-video-debug-info'; | |
var STORAGE_URL='download-youtube-script-url'; | |
var STORAGE_CODE='download-youtube-signature-code'; | |
var STORAGE_DASH='download-youtube-dash-enabled'; | |
var isDecodeRuleUpdated=false; | |
start(); | |
function start() { | |
var pagecontainer=document.getElementById('page-container'); | |
if (!pagecontainer) return; | |
if (/^https?:\/\/www\.youtube.com\/watch\?/.test(window.location.href)) run(); | |
var isAjax=/class[\w\s"'-=]+spf\-link/.test(pagecontainer.innerHTML); | |
var logocontainer=document.getElementById('logo-container'); | |
if (logocontainer && !isAjax) { // fix for blocked videos | |
isAjax=(' '+logocontainer.className+' ').indexOf(' spf-link ')>=0; | |
} | |
var content=document.getElementById('content'); | |
if (isAjax && content) { // Ajax UI | |
var mo=window.MutationObserver||window.WebKitMutationObserver; | |
if(typeof mo!=='undefined') { | |
var observer=new mo(function(mutations) { | |
mutations.forEach(function(mutation) { | |
if(mutation.addedNodes!==null) { | |
for (var i=0; i<mutation.addedNodes.length; i++) { | |
if (mutation.addedNodes[i].id=='watch7-main-container') { // || id=='watch7-container' | |
run(); | |
break; | |
} | |
} | |
} | |
}); | |
}); | |
observer.observe(content, {childList: true, subtree: true}); // old value: pagecontainer | |
} else { // MutationObserver fallback for old browsers | |
pagecontainer.addEventListener('DOMNodeInserted', onNodeInserted, false); | |
} | |
} | |
} | |
function onNodeInserted(e) { | |
if (e && e.target && (e.target.id=='watch7-main-container')) { // || id=='watch7-container' | |
run(); | |
} | |
} | |
function run() { | |
if (document.getElementById(CONTAINER_ID)) return; // check download container | |
var videoID, videoFormats, videoAdaptFormats, videoManifestURL, scriptURL=null; | |
var isSignatureUpdatingStarted=false; | |
var operaTable=new Array(); | |
var language=document.documentElement.getAttribute('lang'); | |
var textDirection='left'; | |
if (document.body.getAttribute('dir')=='rtl') { | |
textDirection='right'; | |
} | |
if (document.getElementById('watch7-action-buttons')) { // old UI | |
fixTranslations(language, textDirection); | |
} | |
// obtain video ID, formats map | |
var args=null; | |
var usw=(typeof this.unsafeWindow !== 'undefined')?this.unsafeWindow:window; // Firefox, Opera<15 | |
if (usw.ytplayer && usw.ytplayer.config && usw.ytplayer.config.args) { | |
args=usw.ytplayer.config.args; | |
} | |
if (args) { | |
videoID=args['video_id']; | |
videoFormats=args['url_encoded_fmt_stream_map']; | |
videoAdaptFormats=args['adaptive_fmts']; | |
videoManifestURL=args['dashmpd']; | |
debug('DYVAM - Info: Standard mode. videoID '+(videoID?videoID:'none')+'; '); | |
} | |
if (usw.ytplayer && usw.ytplayer.config && usw.ytplayer.config.assets) { | |
scriptURL=usw.ytplayer.config.assets.js; | |
} | |
if (videoID==null) { // unsafeWindow workaround (Chrome, Opera 15+) | |
var buffer=document.getElementById(DEBUG_ID+'2'); | |
if (buffer) { | |
while (buffer.firstChild) { | |
buffer.removeChild(buffer.firstChild); | |
} | |
} else { | |
buffer=createHiddenElem('pre', DEBUG_ID+'2'); | |
} | |
injectScript ('if(ytplayer&&ytplayer.config&&ytplayer.config.args){document.getElementById("'+DEBUG_ID+'2").appendChild(document.createTextNode(\'"video_id":"\'+ytplayer.config.args.video_id+\'", "js":"\'+ytplayer.config.assets.js+\'", "dashmpd":"\'+ytplayer.config.args.dashmpd+\'", "url_encoded_fmt_stream_map":"\'+ytplayer.config.args.url_encoded_fmt_stream_map+\'", "adaptive_fmts":"\'+ytplayer.config.args.adaptive_fmts+\'"\'));}'); | |
var code=buffer.innerHTML; | |
if (code) { | |
videoID=findMatch(code, /\"video_id\":\s*\"([^\"]+)\"/); | |
videoFormats=findMatch(code, /\"url_encoded_fmt_stream_map\":\s*\"([^\"]+)\"/); | |
videoFormats=videoFormats.replace(/&/g,'\\u0026'); | |
videoAdaptFormats=findMatch(code, /\"adaptive_fmts\":\s*\"([^\"]+)\"/); | |
videoAdaptFormats=videoAdaptFormats.replace(/&/g,'\\u0026'); | |
videoManifestURL=findMatch(code, /\"dashmpd\":\s*\"([^\"]+)\"/); | |
scriptURL=findMatch(code, /\"js\":\s*\"([^\"]+)\"/); | |
} | |
debug('DYVAM - Info: Injection mode. videoID '+(videoID?videoID:'none')+'; '); | |
} | |
if (videoID==null) { // if all else fails | |
var bodyContent=document.body.innerHTML; | |
if (bodyContent!=null) { | |
videoID=findMatch(bodyContent, /\"video_id\":\s*\"([^\"]+)\"/); | |
videoFormats=findMatch(bodyContent, /\"url_encoded_fmt_stream_map\":\s*\"([^\"]+)\"/); | |
videoAdaptFormats=findMatch(bodyContent, /\"adaptive_fmts\":\s*\"([^\"]+)\"/); | |
videoManifestURL=findMatch(bodyContent, /\"dashmpd\":\s*\"([^\"]+)\"/); | |
if (scriptURL==null) { | |
scriptURL=findMatch(bodyContent, /\"js\":\s*\"([^\"]+)\"/); | |
if (scriptURL) { | |
scriptURL=scriptURL.replace(/\\/g,''); | |
} | |
} | |
} | |
debug('DYVAM - Info: Brute mode. videoID '+(videoID?videoID:'none')+'; '); | |
} | |
debug('DYVAM - Info: url '+window.location.href+'; useragent '+window.navigator.userAgent); | |
if (videoID==null || videoFormats==null || videoID.length==0 || videoFormats.length==0) { | |
debug('DYVAM - Error: No config information found. YouTube must have changed the code.'); | |
return; | |
} | |
// Opera 12 extension message handler | |
if (typeof window.opera !== 'undefined' && window.opera && typeof opera.extension !== 'undefined') { | |
opera.extension.onmessage = function(event) { | |
var index=findMatch(event.data.action, /xhr\-([0-9]+)\-response/); | |
if (index && operaTable[parseInt(index,10)]) { | |
index=parseInt(index,10); | |
var trigger=(operaTable[index])['onload']; | |
if (typeof trigger === 'function' && event.data.readyState == 4) { | |
if (trigger) { | |
trigger(event.data); | |
} | |
} | |
} | |
} | |
} | |
if (!isDecodeRuleUpdated) { | |
DECODE_RULE=getDecodeRules(DECODE_RULE); | |
isDecodeRuleUpdated=true; | |
} | |
if (scriptURL) { | |
scriptURL = absoluteURL(scriptURL); | |
debug('DYVAM - Info: Full script URL: '+scriptURL); | |
fetchSignatureScript(scriptURL); | |
} | |
// video title | |
var videoTitle=document.title || 'video'; | |
videoTitle=videoTitle.replace(/\s*\-\s*YouTube$/i, '').replace(/'/g, '\'').replace(/^\s+|\s+$/g, '').replace(/\.+$/g, ''); | |
videoTitle=videoTitle.replace(/[:"\?\*]/g, '').replace(/[\|\\\/]/g, '_'); // Mac, Linux, Windows | |
if (((window.navigator.userAgent || '').toLowerCase()).indexOf('windows') >= 0) { | |
videoTitle=videoTitle.replace(/#/g, '').replace(/&/g, '_'); // Windows | |
} else { | |
videoTitle=videoTitle.replace(/#/g, '%23').replace(/&/g, '%26'); // Mac, Linux | |
} | |
// parse the formats map | |
var sep1='%2C', sep2='%26', sep3='%3D'; | |
if (videoFormats.indexOf(',')>-1||videoFormats.indexOf('&')>-1||videoFormats.indexOf('\\u0026')>-1) { | |
sep1=','; | |
sep2=(videoFormats.indexOf('&')>-1)?'&':'\\u0026'; | |
sep3='='; | |
} | |
var videoURL=new Array(); | |
var videoSignature=new Array(); | |
if (videoAdaptFormats) { | |
videoFormats=videoFormats+sep1+videoAdaptFormats; | |
} | |
var videoFormatsGroup=videoFormats.split(sep1); | |
for (var i=0;i<videoFormatsGroup.length;i++) { | |
var videoFormatsElem=videoFormatsGroup[i].split(sep2); | |
var videoFormatsPair=new Array(); | |
for (var j=0;j<videoFormatsElem.length;j++) { | |
var pair=videoFormatsElem[j].split(sep3); | |
if (pair.length==2) { | |
videoFormatsPair[pair[0]]=pair[1]; | |
} | |
} | |
if (videoFormatsPair['url']==null) continue; | |
var url=unescape(unescape(videoFormatsPair['url'])).replace(/\\\//g,'/').replace(/\\u0026/g,'&'); | |
if (videoFormatsPair['itag']==null) continue; | |
var itag=videoFormatsPair['itag']; | |
var sig=videoFormatsPair['sig']||videoFormatsPair['signature']; | |
if (sig) { | |
url=url+'&signature='+sig; | |
videoSignature[itag]=null; | |
} else if (videoFormatsPair['s']) { | |
url=url+'&signature='+decryptSignature(videoFormatsPair['s']); | |
videoSignature[itag]=videoFormatsPair['s']; | |
} | |
if (url.toLowerCase().indexOf('ratebypass')==-1) { // speed up download for dash | |
url=url+'&ratebypass=yes'; | |
} | |
if (url.toLowerCase().indexOf('http')==0) { // validate URL | |
videoURL[itag]=url+'&title='+videoTitle; | |
} | |
} | |
var showFormat=new Array(); | |
for (var category in FORMAT_RULE) { | |
var rule=FORMAT_RULE[category]; | |
for (var index in FORMAT_TYPE){ | |
if (FORMAT_TYPE[index]==category) { | |
showFormat[index]=(rule=='all'); | |
} | |
} | |
if (rule=='max') { | |
for (var i=FORMAT_ORDER.length-1;i>=0;i--) { | |
var format=FORMAT_ORDER[i]; | |
if (FORMAT_TYPE[format]==category && videoURL[format]!=undefined) { | |
showFormat[format]=true; | |
break; | |
} | |
} | |
} | |
} | |
var dashPref=getPref(STORAGE_DASH); | |
if (dashPref=='1') { | |
SHOW_DASH_FORMATS=true; | |
} else if (dashPref!='0') { | |
setPref(STORAGE_DASH,'0'); | |
} | |
var downloadCodeList=[]; | |
for (var i=0;i<FORMAT_ORDER.length;i++) { | |
var format=FORMAT_ORDER[i]; | |
if (format=='37' && videoURL[format]==undefined) { // hack for dash 1080p | |
if (videoURL['137']) { | |
format='137'; | |
} | |
showFormat[format]=showFormat['37']; | |
} else if (format=='38' && videoURL[format]==undefined) { // hack for dash 4K | |
if (videoURL['138'] && !videoURL['266']) { | |
format='138'; | |
} | |
showFormat[format]=showFormat['38']; | |
} | |
if (!SHOW_DASH_FORMATS && format.length>2) continue; | |
if (videoURL[format]!=undefined && FORMAT_LABEL[format]!=undefined && showFormat[format]) { | |
downloadCodeList.push({url:videoURL[format],sig:videoSignature[format],format:format,label:FORMAT_LABEL[format]}); | |
debug('DYVAM - Info: itag'+format+' url:'+videoURL[format]); | |
} | |
} | |
if (downloadCodeList.length==0) { | |
debug('DYVAM - Error: No download URL found. Probably YouTube uses encrypted streams.'); | |
return; // no format | |
} | |
// find parent container | |
var newWatchPage=false; | |
var parentElement=document.getElementById('watch7-action-buttons'); | |
if (parentElement==null) { | |
parentElement=document.getElementById('watch8-secondary-actions'); | |
if (parentElement==null) { | |
debug('DYVAM Error - No container for adding the download button. YouTube must have changed the code.'); | |
return; | |
} else { | |
newWatchPage=true; | |
} | |
} | |
// get button labels | |
var buttonText=(BUTTON_TEXT[language])?BUTTON_TEXT[language]:BUTTON_TEXT['en']; | |
var buttonLabel=(BUTTON_TOOLTIP[language])?BUTTON_TOOLTIP[language]:BUTTON_TOOLTIP['en']; | |
// generate download code for regular interface | |
var mainSpan=document.createElement('span'); | |
if (newWatchPage) { | |
var spanIcon=document.createElement('span'); | |
spanIcon.setAttribute('class', 'yt-uix-button-icon-wrapper'); | |
var imageIcon=document.createElement('img'); | |
imageIcon.setAttribute('src', '//s.ytimg.com/yt/img/pixel-vfl3z5WfW.gif'); | |
imageIcon.setAttribute('class', 'yt-uix-button-icon'); | |
imageIcon.setAttribute('style', 'width:20px;height:20px;background-size:20px 20px;background-repeat:no-repeat;background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABG0lEQVRYR+2W0Q3CMAxE2wkYAdiEEWADmIxuACMwCmzABpCTEmRSO7YTQX+ChECV43t2nF7GYeHPuLD+0AKwC/DnWMAp/N5qimkBuAfBdRTF/+2/AV6ZYFUxVYuicAfoHegd6B3oHfhZB+ByF+JyV8FkrAB74pqH3DU5L3iGoBURhdVODIQF4EjEkWLmmhYALOQgNIBcHHke4buhxXAAaFnaAhqbQ5QAOHHkwhZ8balkx1ICCiEBWNZ+CivdB7REHIC2ZjZK2oWklDDdB1NSdCd/Js2PqQMpSIKYVcM8kE6QCwDBNRCqOBJrW0CL8kCYxL0A1k6YxWsANAiXeC2ABOEWbwHAWrwxpzgkmA/JtIqnxTOElmPnjlkc4A3FykAhA42AxwAAAABJRU5ErkJggg==);'); | |
spanIcon.appendChild(imageIcon); | |
mainSpan.appendChild(spanIcon); | |
} | |
var spanButton=document.createElement('span'); | |
spanButton.setAttribute('class', 'yt-uix-button-content'); | |
spanButton.appendChild(document.createTextNode(buttonText+' ')); | |
mainSpan.appendChild(spanButton); | |
if (!newWatchPage) { // old UI | |
var imgButton=document.createElement('img'); | |
imgButton.setAttribute('class', 'yt-uix-button-arrow'); | |
imgButton.setAttribute('src', '//s.ytimg.com/yt/img/pixel-vfl3z5WfW.gif'); | |
mainSpan.appendChild(imgButton); | |
} | |
var listItems=document.createElement('ol'); | |
listItems.setAttribute('style', 'display:none;'); | |
listItems.setAttribute('class', 'yt-uix-button-menu'); | |
for (var i=0;i<downloadCodeList.length;i++) { | |
var listItem=document.createElement('li'); | |
var listLink=document.createElement('a'); | |
listLink.setAttribute('style', 'text-decoration:none;'); | |
listLink.setAttribute('href', downloadCodeList[i].url); | |
listLink.setAttribute('download', videoTitle+'.'+FORMAT_TYPE[downloadCodeList[i].format]); | |
var listButton=document.createElement('span'); | |
listButton.setAttribute('class', 'yt-uix-button-menu-item'); | |
listButton.setAttribute('loop', i+''); | |
listButton.setAttribute('id', LISTITEM_ID+downloadCodeList[i].format); | |
listButton.appendChild(document.createTextNode(downloadCodeList[i].label)); | |
listLink.appendChild(listButton); | |
listItem.appendChild(listLink); | |
listItems.appendChild(listItem); | |
} | |
mainSpan.appendChild(listItems); | |
var buttonElement=document.createElement('button'); | |
buttonElement.setAttribute('id', BUTTON_ID); | |
if (newWatchPage) { | |
buttonElement.setAttribute('class', 'yt-uix-button yt-uix-button-size-default yt-uix-button-opacity yt-uix-tooltip'); | |
} else { // old UI | |
buttonElement.setAttribute('class', 'yt-uix-button yt-uix-tooltip yt-uix-button-empty yt-uix-button-text'); | |
buttonElement.setAttribute('style', 'margin-top:4px; margin-left:'+((textDirection=='left')?5:10)+'px;'); | |
} | |
buttonElement.setAttribute('data-tooltip-text', buttonLabel); | |
buttonElement.setAttribute('type', 'button'); | |
buttonElement.setAttribute('role', 'button'); | |
buttonElement.addEventListener('click', function(){return false;}, false); | |
buttonElement.appendChild(mainSpan); | |
var containerSpan=document.createElement('span'); | |
containerSpan.setAttribute('id', CONTAINER_ID); | |
containerSpan.appendChild(document.createTextNode(' ')); | |
containerSpan.appendChild(buttonElement); | |
// add the button | |
if (!newWatchPage) { // watch7 | |
parentElement.appendChild(containerSpan); | |
} else { // watch8 | |
parentElement.insertBefore(containerSpan, parentElement.firstChild); | |
} | |
// REPLACEWITH if (!isSignatureUpdatingStarted) { | |
for (var i=0;i<downloadCodeList.length;i++) { | |
addFileSize(downloadCodeList[i].url, downloadCodeList[i].format); | |
} | |
// } | |
if (typeof GM_download !== 'undefined') { | |
for (var i=0;i<downloadCodeList.length;i++) { | |
var downloadFMT=document.getElementById(LISTITEM_ID+downloadCodeList[i].format); | |
var url=(downloadCodeList[i].url).toLowerCase(); | |
if (url.indexOf('clen=')>0 && url.indexOf('dur=')>0 && url.indexOf('gir=')>0 | |
&& url.indexOf('lmt=')>0) { | |
downloadFMT.addEventListener('click', downloadVideoNatively, false); | |
} | |
} | |
} | |
addFromManifest(); | |
function downloadVideoNatively(e) { | |
var elem=e.currentTarget; | |
e.returnValue=false; | |
if (e.preventDefault) { | |
e.preventDefault(); | |
} | |
var loop=elem.getAttribute('loop'); | |
if (loop) { | |
GM_download(downloadCodeList[loop].url, videoTitle+'.'+FORMAT_TYPE[downloadCodeList[loop].format]); | |
} | |
return false; | |
} | |
function addFromManifest() { // add Dash URLs from manifest file | |
var formats=['137', '138', '140']; // 137=1080p, 138=4k, 140=m4a | |
var isNecessary=true; | |
for (var i=0;i<formats.length;i++) { | |
if (videoURL[formats[i]]) { | |
isNecessary=false; | |
break; | |
} | |
} | |
if (videoManifestURL && SHOW_DASH_FORMATS && isNecessary) { | |
var matchSig=findMatch(videoManifestURL, /\/s\/([a-zA-Z0-9\.]+)\//i); | |
if (matchSig) { | |
var decryptedSig=decryptSignature(matchSig); | |
if (decryptedSig) { | |
videoManifestURL=videoManifestURL.replace('/s/'+matchSig+'/','/signature/'+decryptedSig+'/'); | |
} | |
} | |
videoManifestURL=absoluteURL(videoManifestURL); | |
debug('DYVAM - Info: manifestURL '+videoManifestURL); | |
crossXmlHttpRequest({ | |
method:'GET', | |
url:videoManifestURL, // check if URL exists | |
onload:function(response) { | |
if (response.readyState === 4 && response.status === 200 && response.responseText) { | |
debug('DYVAM - Info: maniestFileContents '+response.responseText); | |
var lastFormatFromList=downloadCodeList[downloadCodeList.length-1].format; | |
debug('DYVAM - Info: lastformat: '+lastFormatFromList); | |
for (var i=0;i<formats.length;i++) { | |
k=formats[i]; | |
if (videoURL[k] || showFormat[k]==false) continue; | |
var regexp = new RegExp('<BaseURL>(http[^<]+itag\\/'+k+'[^<]+)<\\/BaseURL>','i'); | |
var matchURL=findMatch(response.responseText, regexp); | |
debug('DYVAM - Info: matchURL itag= '+k+' url= '+matchURL); | |
if (!matchURL) continue; | |
matchURL=matchURL.replace(/&\;/g,'&'); | |
// ... | |
downloadCodeList.push( | |
{url:matchURL,sig:videoSignature[k],format:k,label:FORMAT_LABEL[k]}); | |
var downloadFMT=document.getElementById(LISTITEM_ID+lastFormatFromList); | |
var clone=downloadFMT.parentNode.parentNode.cloneNode(true); | |
clone.firstChild.firstChild.setAttribute('id', LISTITEM_ID+k); | |
clone.firstChild.setAttribute('href', matchURL); | |
downloadFMT.parentNode.parentNode.parentNode.appendChild(clone); | |
downloadFMT=document.getElementById(LISTITEM_ID+k); | |
downloadFMT.firstChild.nodeValue=FORMAT_LABEL[k]; | |
addFileSize(matchURL, k); | |
lastFormatFromList=k; | |
} | |
} | |
} | |
}); | |
} | |
} | |
function injectStyle(code) { | |
var style=document.createElement('style'); | |
style.type='text/css'; | |
style.appendChild(document.createTextNode(code)); | |
document.getElementsByTagName('head')[0].appendChild(style); | |
} | |
function injectScript(code) { | |
var script=document.createElement('script'); | |
script.type='application/javascript'; | |
script.textContent=code; | |
document.body.appendChild(script); | |
document.body.removeChild(script); | |
} | |
function debug(str) { | |
var debugElem=document.getElementById(DEBUG_ID); | |
if (!debugElem) { | |
debugElem=createHiddenElem('div', DEBUG_ID); | |
} | |
debugElem.appendChild(document.createTextNode(str+' ')); | |
} | |
function createHiddenElem(tag, id) { | |
var elem=document.createElement(tag); | |
elem.setAttribute('id', id); | |
elem.setAttribute('style', 'display:none;'); | |
document.body.appendChild(elem); | |
return elem; | |
} | |
function fixTranslations(language, textDirection) { | |
if (/^af|bg|bn|ca|cs|de|el|es|et|eu|fa|fi|fil|fr|gl|hi|hr|hu|id|it|iw|kn|lv|lt|ml|mr|ms|nl|pl|ro|ru|sl|sk|sr|sw|ta|te|th|uk|ur|vi|zu$/.test(language)) { // fix international UI | |
var likeButton=document.getElementById('watch-like'); | |
if (likeButton) { | |
var spanElements=likeButton.getElementsByClassName('yt-uix-button-content'); | |
if (spanElements) { | |
spanElements[0].style.display='none'; // hide like text | |
} | |
} | |
var marginPixels=10; | |
if (/^bg|ca|cs|el|eu|hr|it|ml|ms|pl|sl|sw|te$/.test(language)) { | |
marginPixels=1; | |
} | |
injectStyle('#watch7-secondary-actions .yt-uix-button{margin-'+textDirection+':'+marginPixels+'px!important}'); | |
} | |
} | |
function findMatch(text, regexp) { | |
var matches=text.match(regexp); | |
return (matches)?matches[1]:null; | |
} | |
function isString(s) { | |
return (typeof s==='string' || s instanceof String); | |
} | |
function isInteger(n) { | |
return (typeof n==='number' && n%1==0); | |
} | |
function absoluteURL(url) { | |
var link = document.createElement('a'); | |
link.href = url; | |
return link.href; | |
} | |
function getPref(name) { // cross-browser GM_getValue | |
var a='', b=''; | |
try {a=typeof GM_getValue.toString; b=GM_getValue.toString()} catch(e){} | |
if (typeof GM_getValue === 'function' && | |
(a === 'undefined' || b.indexOf('not supported') === -1)) { | |
return GM_getValue(name, null); // Greasemonkey, Tampermonkey, Firefox extension | |
} else { | |
var ls=null; | |
try {ls=window.localStorage||null} catch(e){} | |
if (ls) { | |
return ls.getItem(name); // Chrome script, Opera extensions | |
} | |
} | |
return; | |
} | |
function setPref(name, value) { // cross-browser GM_setValue | |
var a='', b=''; | |
try {a=typeof GM_setValue.toString; b=GM_setValue.toString()} catch(e){} | |
if (typeof GM_setValue === 'function' && | |
(a === 'undefined' || b.indexOf('not supported') === -1)) { | |
GM_setValue(name, value); // Greasemonkey, Tampermonkey, Firefox extension | |
} else { | |
var ls=null; | |
try {ls=window.localStorage||null} catch(e){} | |
if (ls) { | |
return ls.setItem(name, value); // Chrome script, Opera extensions | |
} | |
} | |
} | |
function crossXmlHttpRequest(details) { // cross-browser GM_xmlhttpRequest | |
if (typeof GM_xmlhttpRequest === 'function') { // Greasemonkey, Tampermonkey, Firefox extension, Chrome script | |
GM_xmlhttpRequest(details); | |
} else if (typeof window.opera !== 'undefined' && window.opera && typeof opera.extension !== 'undefined' && | |
typeof opera.extension.postMessage !== 'undefined') { // Opera 12 extension | |
var index=operaTable.length; | |
opera.extension.postMessage({'action':'xhr-'+index, 'url':details.url, 'method':details.method}); | |
operaTable[index]=details; | |
} else if (typeof window.opera === 'undefined' && typeof XMLHttpRequest === 'function') { // Opera 15+ extension | |
var xhr=new XMLHttpRequest(); | |
xhr.onreadystatechange = function() { | |
if (xhr.readyState == 4) { | |
if (details['onload']) { | |
details['onload'](xhr); | |
} | |
} | |
} | |
xhr.open(details.method, details.url, true); | |
xhr.send(); | |
} | |
} | |
function addFileSize(url, format) { | |
function updateVideoLabel(size, format) { | |
var elem=document.getElementById(LISTITEM_ID+format); | |
if (elem) { | |
size=parseInt(size,10); | |
if (size>=1073741824) { | |
size=parseFloat((size/1073741824).toFixed(1))+' GB'; | |
} else if (size>=1048576) { | |
size=parseFloat((size/1048576).toFixed(1))+' MB'; | |
} else { | |
size=parseFloat((size/1024).toFixed(1))+' KB'; | |
} | |
if (elem.childNodes.length>1) { | |
elem.lastChild.nodeValue=' ('+size+')'; | |
} else if (elem.childNodes.length==1) { | |
elem.appendChild(document.createTextNode(' ('+size+')')); | |
} | |
} | |
} | |
var matchSize=findMatch(url, /[&\?]clen=([0-9]+)&/i); | |
if (matchSize) { | |
updateVideoLabel(matchSize, format); | |
} else { | |
try { | |
crossXmlHttpRequest({ | |
method:'HEAD', | |
url:url, | |
onload:function(response) { | |
if (response.readyState == 4 && response.status == 200) { // add size | |
var size=0; | |
if (typeof response.getResponseHeader === 'function') { | |
size=response.getResponseHeader('Content-length'); | |
} else if (response.responseHeaders) { | |
var regexp = new RegExp('^Content\-length: (.*)$','im'); | |
var match = regexp.exec(response.responseHeaders); | |
if (match) { | |
size=match[1]; | |
} | |
} | |
if (size) { | |
updateVideoLabel(size, format); | |
} | |
} | |
} | |
}); | |
} catch(e) { } | |
} | |
} | |
function findSignatureCode(sourceCode) { | |
debug('DYVAM - Info: signature start '+getPref(STORAGE_CODE)); | |
var signatureFunctionName = | |
findMatch(sourceCode, | |
/\.set\s*\("signature"\s*,\s*([a-zA-Z0-9_$][\w$]*)\(/) | |
|| findMatch(sourceCode, | |
/\.sig\s*\|\|\s*([a-zA-Z0-9_$][\w$]*)\(/) | |
|| findMatch(sourceCode, | |
/\.signature\s*=\s*([a-zA-Z_$][\w$]*)\([a-zA-Z_$][\w$]*\)/); //old | |
if (signatureFunctionName == null) return setPref(STORAGE_CODE, 'error'); | |
signatureFunctionName=signatureFunctionName.replace('$','\\$'); | |
var regCode = new RegExp(signatureFunctionName + '\\s*=\\s*function' + | |
'\\s*\\([\\w$]*\\)\\s*{[\\w$]*=[\\w$]*\\.split\\(""\\);\n*(.+);return [\\w$]*\\.join'); | |
var regCode2 = new RegExp('function \\s*' + signatureFunctionName + | |
'\\s*\\([\\w$]*\\)\\s*{[\\w$]*=[\\w$]*\\.split\\(""\\);\n*(.+);return [\\w$]*\\.join'); | |
var functionCode = findMatch(sourceCode, regCode) || findMatch(sourceCode, regCode2); | |
debug('DYVAM - Info: signaturefunction ' + signatureFunctionName + ' -- ' + functionCode); | |
if (functionCode == null) return setPref(STORAGE_CODE, 'error'); | |
var reverseFunctionName = findMatch(sourceCode, | |
/([\w$]*)\s*:\s*function\s*\(\s*[\w$]*\s*\)\s*{\s*(?:return\s*)?[\w$]*\.reverse\s*\(\s*\)\s*}/); | |
debug('DYVAM - Info: reversefunction ' + reverseFunctionName); | |
if (reverseFunctionName) reverseFunctionName=reverseFunctionName.replace('$','\\$'); | |
var sliceFunctionName = findMatch(sourceCode, | |
/([\w$]*)\s*:\s*function\s*\(\s*[\w$]*\s*,\s*[\w$]*\s*\)\s*{\s*(?:return\s*)?[\w$]*\.(?:slice|splice)\(.+\)\s*}/); | |
debug('DYVAM - Info: slicefunction ' + sliceFunctionName); | |
if (sliceFunctionName) sliceFunctionName=sliceFunctionName.replace('$','\\$'); | |
var regSlice = new RegExp('\\.(?:'+'slice'+(sliceFunctionName?'|'+sliceFunctionName:'')+ | |
')\\s*\\(\\s*(?:[a-zA-Z_$][\\w$]*\\s*,)?\\s*([0-9]+)\\s*\\)'); // .slice(5) sau .Hf(a,5) | |
var regReverse = new RegExp('\\.(?:'+'reverse'+(reverseFunctionName?'|'+reverseFunctionName:'')+ | |
')\\s*\\([^\\)]*\\)'); // .reverse() sau .Gf(a,45) | |
var regSwap = new RegExp('[\\w$]+\\s*\\(\\s*[\\w$]+\\s*,\\s*([0-9]+)\\s*\\)'); | |
var regInline = new RegExp('[\\w$]+\\[0\\]\\s*=\\s*[\\w$]+\\[([0-9]+)\\s*%\\s*[\\w$]+\\.length\\]'); | |
var functionCodePieces=functionCode.split(';'); | |
var decodeArray=[]; | |
for (var i=0; i<functionCodePieces.length; i++) { | |
functionCodePieces[i]=functionCodePieces[i].trim(); | |
var codeLine=functionCodePieces[i]; | |
if (codeLine.length>0) { | |
var arrSlice=codeLine.match(regSlice); | |
var arrReverse=codeLine.match(regReverse); | |
debug(i+': '+codeLine+' --'+(arrSlice?' slice length '+arrSlice.length:'') +' '+(arrReverse?'reverse':'')); | |
if (arrSlice && arrSlice.length >= 2) { // slice | |
var slice=parseInt(arrSlice[1], 10); | |
if (isInteger(slice)){ | |
decodeArray.push(-slice); | |
} else return setPref(STORAGE_CODE, 'error'); | |
} else if (arrReverse && arrReverse.length >= 1) { // reverse | |
decodeArray.push(0); | |
} else if (codeLine.indexOf('[0]') >= 0) { // inline swap | |
if (i+2<functionCodePieces.length && | |
functionCodePieces[i+1].indexOf('.length') >= 0 && | |
functionCodePieces[i+1].indexOf('[0]') >= 0) { | |
var inline=findMatch(functionCodePieces[i+1], regInline); | |
inline=parseInt(inline, 10); | |
decodeArray.push(inline); | |
i+=2; | |
} else return setPref(STORAGE_CODE, 'error'); | |
} else if (codeLine.indexOf(',') >= 0) { // swap | |
var swap=findMatch(codeLine, regSwap); | |
swap=parseInt(swap, 10); | |
if (isInteger(swap) && swap>0){ | |
decodeArray.push(swap); | |
} else return setPref(STORAGE_CODE, 'error'); | |
} else return setPref(STORAGE_CODE, 'error'); | |
} | |
} | |
if (decodeArray) { | |
setPref(STORAGE_URL, scriptURL); | |
setPref(STORAGE_CODE, decodeArray.toString()); | |
DECODE_RULE=decodeArray; | |
debug('DYVAM - Info: signature '+decodeArray.toString()+' '+scriptURL); | |
// update download links and add file sizes | |
for (var i=0;i<downloadCodeList.length;i++) { | |
var elem=document.getElementById(LISTITEM_ID+downloadCodeList[i].format); | |
var url=downloadCodeList[i].url; | |
var sig=downloadCodeList[i].sig; | |
if (elem && url && sig) { | |
url=url.replace(/\&signature=[\w\.]+/, '&signature='+decryptSignature(sig)); | |
elem.parentNode.setAttribute('href', url); | |
addFileSize(url, downloadCodeList[i].format); | |
} | |
} | |
} | |
} | |
function isValidSignatureCode(arr) { // valid values: '5,-3,0,2,5', 'error' | |
if (!arr) return false; | |
if (arr=='error') return true; | |
arr=arr.split(','); | |
for (var i=0;i<arr.length;i++) { | |
if (!isInteger(parseInt(arr[i],10))) return false; | |
} | |
return true; | |
} | |
function fetchSignatureScript(scriptURL) { | |
var storageURL=getPref(STORAGE_URL); | |
var storageCode=getPref(STORAGE_CODE); | |
if (!(/,0,|^0,|,0$|\-/.test(storageCode))) storageCode=null; // hack for only positive items | |
if (storageCode && isValidSignatureCode(storageCode) && storageURL && | |
scriptURL==absoluteURL(storageURL)) return; | |
try { | |
debug('DYVAM fetch '+scriptURL); | |
isSignatureUpdatingStarted=true; | |
crossXmlHttpRequest({ | |
method:'GET', | |
url:scriptURL, | |
onload:function(response) { | |
debug('DYVAM fetch status '+response.status); | |
if (response.readyState === 4 && response.status === 200 && response.responseText) { | |
findSignatureCode(response.responseText); | |
} | |
} | |
}); | |
} catch(e) { } | |
} | |
function getDecodeRules(rules) { | |
var storageCode=getPref(STORAGE_CODE); | |
if (storageCode && storageCode!='error' && isValidSignatureCode(storageCode)) { | |
var arr=storageCode.split(','); | |
for (var i=0; i<arr.length; i++) { | |
arr[i]=parseInt(arr[i], 10); | |
} | |
rules=arr; | |
debug('DYVAM - Info: signature '+arr.toString()+' '+scriptURL); | |
} | |
return rules; | |
} | |
function decryptSignature(sig) { | |
function swap(a,b){var c=a[0];a[0]=a[b%a.length];a[b]=c;return a}; | |
function decode(sig, arr) { // encoded decryption | |
if (!isString(sig)) return null; | |
var sigA=sig.split(''); | |
for (var i=0;i<arr.length;i++) { | |
var act=arr[i]; | |
if (!isInteger(act)) return null; | |
sigA=(act>0)?swap(sigA, act):((act==0)?sigA.reverse():sigA.slice(-act)); | |
} | |
var result=sigA.join(''); | |
return result; | |
} | |
if (sig==null) return ''; | |
var arr=DECODE_RULE; | |
if (arr) { | |
var sig2=decode(sig, arr); | |
if (sig2) return sig2; | |
} else { | |
setPref(STORAGE_URL, ''); | |
setPref(STORAGE_CODE, ''); | |
} | |
return sig; | |
} | |
} | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Youtube Auto Subtitle Downloader v7 | |
// @description download youtube AUTO subtitle | |
// @include http://www.youtube.com/watch?* | |
// @include https://www.youtube.com/watch?* | |
// @require http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js | |
// @version 7 | |
// @namespace https://greasyfork.org/users/5711 | |
// ==/UserScript== | |
// Author : Cheng Zheng | |
// Email : [email protected] | |
// Github : https://github.com/1c7 | |
// Last update : 2017/Jan/27 | |
// Page first time load | |
$(document).ready(function(){ init(); }); | |
// Page jump | |
window.addEventListener("spfdone", function(e) { init(); }); | |
function init(){ | |
// 加按钮 | |
$("#eow-title").append('<a id="YT_auto"> Download Youtube Auto Subtitle | 下载 Youtube 自动字幕</a>'); | |
// 调样式 | |
$("#YT_auto").addClass('start yt-uix-button yt-uix-button-text yt-uix-tooltip'); // 样式是 Youtube 自带的. | |
$("#YT_auto").css('margin-top','2px') | |
.css('margin-left','4px') | |
.css('border','1px solid rgb(0, 183, 90)') | |
.css('cursor','pointer') | |
.css('color','rgb(255, 255, 255)') | |
.css('border-top-left-radius','3px') | |
.css('border-top-right-radius','3px') | |
.css('border-bottom-right-radius','3px') | |
.css('border-bottom-left-radius','3px') | |
.css('background-color','#00B75A'); | |
// 鼠标悬浮时改背景颜色; | |
$("#YT_auto").hover(function() { | |
$(this).css("background-color","rgb(0, 163, 80)") | |
.css("border","1px solid rgb(0, 183, 90)"); | |
}); | |
$("#YT_auto").mouseout(function() { | |
$(this).css("background-color","#00B75A"); | |
}); | |
var TITLE = unsafeWindow.ytplayer.config.args.title; // 拿视频标题 | |
var version = getChromeVersion(); // 拿 Chrome 版本 | |
// 判断 Chrome 版本来做事,Chrome 52 和 53 的文件下载方式不一样, 总不能为了兼顾 53 的让 52 的用户用不了 | |
if (version > 52){ | |
document.getElementById('YT_auto').setAttribute( | |
'download', | |
'(auto)' + TITLE + '.srt' | |
); | |
document.getElementById('YT_auto').setAttribute( | |
'href', | |
'data:Content-type: text/plain,' + get_subtitle() | |
); | |
} else { | |
$("#YT_auto").click(function(){ | |
downloadFile(TITLE+".srt",get_subtitle()); | |
}); | |
} | |
} | |
function get_subtitle(){ | |
var TTS_URL = yt.getConfig("TTS_URL"); // <- if that one not wokring, try this: yt.config.get("TTS_URL"); | |
if (!TTS_URL){ | |
$("#YT_auto").text("No Auto Subtitle | 没有英文自动字幕"); | |
return false; | |
} | |
var xml = TTS_URL + "&kind=asr&lang=en&fmt=srv1"; // fmt is very important | |
var a = "<content will be replace>"; | |
$.ajax({ | |
url: xml, | |
type: 'get', | |
async: false, | |
success: function(r) { | |
if(r === ""){ | |
$("#YT_auto").text("No Auto Subtitle | 没有英文自动字幕"); | |
return false; | |
} | |
var text = r.getElementsByTagName('text'); | |
var result = ""; // store final SRT result | |
var len = text.length; | |
for(var i=0; i<len; i++){ | |
var index = i+1; | |
var content = text[i].textContent.toString(); | |
content = content.replace(/(<([^>]+)>)/ig,""); // remove all html tag. | |
var start = text[i].getAttribute('start'); | |
var end = ""; | |
if (i+1 >= len){ | |
end = parseFloat(text[i].getAttribute('start')) + parseFloat(text[i].getAttribute('dur')); | |
}else{ | |
end = text[i+1].getAttribute('start'); | |
} | |
// ==== 开始处理数据, 把数据保存到result里. ==== | |
var new_line = "%0D%0A"; | |
result = result + index + new_line; | |
// SRT index | |
var start_time = process_time( parseFloat(start) ); | |
result = result + start_time; | |
// 拿到 开始时间 后往 result 里存 | |
result = result + ' --> '; | |
// 标准 srt 时间轴: 00:00:01,850 --> 00:00:02,720 | |
// 现在加中间的箭头 | |
var end_time = process_time( parseFloat(end) ); | |
result = result + end_time + new_line; | |
// 拿到 结束时间 后往 result 里存 | |
result = result + content + new_line + new_line; | |
// 加字幕内容 | |
} | |
// ==== srt字幕我们已经完全处理好了, 保存在result里了, 我们现在保存到用户的电脑里就行了. ==== | |
// 保存javascript字符到用户电脑里 | |
result = result.replace(/(<div><br>)*<\/div>/g, '\n'); | |
result = result.replace(/<div>/g, ''); | |
/* replaces some html entities */ | |
result = result.replace(/ /g, ' '); | |
result = result.replace(/&/g, '&'); | |
result = result.replace(/</g, '<'); | |
result = result.replace(/>/g, '>'); | |
result = result.replace(/'/g, "'"); | |
a = result; | |
} | |
}); | |
return a; | |
} | |
// Copy from: http://www.alloyteam.com/2014/01/use-js-file-download/ | |
// Chrome 53 之后这个函数失效. 52有效. | |
function downloadFile(fileName, content){ | |
var aLink = document.createElement('a'); | |
var blob = new Blob([content]); | |
var evt = document.createEvent("HTMLEvents"); | |
evt.initEvent("click", false, false); | |
aLink.download = fileName; | |
aLink.href = URL.createObjectURL(blob); | |
aLink.dispatchEvent(evt); | |
} | |
//http://stackoverflow.com/questions/4900436/how-to-detect-the-installed-chrome-version | |
function getChromeVersion() { | |
var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); | |
return raw ? parseInt(raw[2], 10) : false; | |
} | |
// Process Time. Example: start="671.33" start="37.64" start="12" start="23.029" | |
// turn to SRT time format, like: 00:00:00,090 00:00:08,460 00:10:29,350 | |
function process_time(s){ | |
// s == second | |
s = s.toFixed(3); | |
// 超棒的函数, 不论是整数还是小数都给弄成3位小数形式 | |
// 举个柚子: | |
// 671.33 -> 671.330 | |
// 671 -> 671.000 | |
// 注意函数会四舍五入. 具体读文档 | |
var array = s.split('.'); | |
// 把开始时间根据句号分割 | |
// 671.330 会分割成数组: [671, 330] | |
var Hour = 0; | |
var Minute = 0; | |
var Second = array[0]; // 671 | |
var MilliSecond = array[1]; // 330 | |
// 先声明下变量, 待会把这几个拼好就行了 | |
// 我们来处理秒数. 把"分钟"和"小时"除出来 | |
if(Second >= 60){ | |
Minute = Math.floor(Second / 60); | |
Second = Second - Minute * 60; | |
// 把 秒 拆成 分钟和秒, 比如121秒, 拆成2分钟1秒 | |
Hour = Math.floor(Minute / 60); | |
Minute = Minute - Hour * 60; | |
// 把 分钟 拆成 小时和分钟, 比如700分钟, 拆成11小时40分钟 | |
} | |
// Minute,如果位数不够两位就变成两位,下面两个if语句的作用也是一样。 | |
if (Minute < 10){ | |
Minute = '0' + Minute; | |
} | |
// Hour | |
if (Hour < 10){ | |
Hour = '0' + Hour; | |
} | |
// Second | |
if (Second < 10){ | |
Second = '0' + Second; | |
} | |
return Hour + ':' + Minute + ':' + Second + ',' + MilliSecond; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Youtube Subtitle Downloader v8 | |
// @include http://*youtube.com/watch* | |
// @include https://*youtube.com/watch* | |
// @author Cheng Zheng | |
// @copyright 2009 Tim Smart; 2011 gw111zz; 2013~2016 Cheng Zheng; | |
// @license GNU GPL v3.0 or later. http://www.gnu.org/copyleft/gpl.html | |
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js | |
// @version 8 | |
// @grant GM_xmlhttpRequest | |
// @namespace https://greasyfork.org/users/5711 | |
// @description download youtube COMPLETE subtitle | |
// ==/UserScript== | |
/* | |
Third Author : Cheng Zheng | |
Email : [email protected] | |
Last update : 2016/Sep/12 | |
Github : https://github.com/1c7/Youtube-Auto-Subtitle-Download | |
Code comments are written in Chinese. If you need help, just let me know. | |
*/ | |
// Page first time load | |
(function () { init(); })(); | |
// Page jump | |
window.addEventListener("spfdone", function(e) { init(); }); | |
function init(){ | |
unsafeWindow.VIDEO_ID = unsafeWindow.ytplayer.config.args.video_id; | |
unsafeWindow.caption_array = []; | |
inject_our_script(); | |
} | |
function inject_our_script(){ | |
var div = document.createElement('div'), | |
select = document.createElement('select'), | |
option = document.createElement('option'), | |
controls = document.getElementById('watch7-headline'); // 装视频标题的div | |
div.setAttribute( 'style', 'margin-bottom: 10px; display: inline-block; border: 1px solid rgb(0, 183, 90); cursor: pointer; color: rgb(255, 255, 255); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; background-color: #00B75A;margin-left: 4px; '); | |
select.id = 'captions_selector'; | |
select.disabled = true; | |
select.setAttribute( 'style', 'border: 1px solid rgb(0, 183, 90); cursor: pointer; color: rgb(255, 255, 255); background-color: #00B75A;'); | |
option.textContent = 'Loading...'; | |
option.selected = true; | |
select.appendChild(option); | |
// 添加这个选项, 这个选项默认被选中, 文字是"Loading..." | |
select.addEventListener('change', function() { | |
download_subtitle(this); | |
}, false); | |
// 事件侦听. | |
div.appendChild(select); | |
// 往新建的div里面放入select | |
controls.appendChild(div); | |
// 往页面上添加这个div | |
load_language_list(select); | |
// 用来载入有多少字幕的函数, 不是下载字幕的函数 | |
var a = document.createElement('a'); | |
a.style.cssText = 'display:none;'; | |
a.setAttribute("id", "ForSubtitleDownload"); | |
var body = document.getElementsByTagName('body')[0]; | |
body.appendChild(a);// 这个元素用于下载. | |
} | |
// 下字幕用的函数. | |
function download_subtitle (selector) { | |
var caption = caption_array[selector.selectedIndex - 1]; | |
if (!caption) return; | |
var language_name_1c7 = caption.lang_name; | |
var url = 'https://video.google.com/timedtext?hl=' + caption.lang_code + '&lang=' + caption.lang_code + '&name=' + caption.name + '&v=' + VIDEO_ID; | |
jQuery.get(url).done(function(r){ | |
var text = r.getElementsByTagName('text'); | |
// 拿到所有的text节点 | |
var result = ""; | |
// 保存结果的字符串 | |
for(var i=0; i<text.length; i++){ | |
var index = i+1; | |
// 这个是字幕的索引, 从1开始的, 但是因为我们的循环是从0开始的, 所以加个1 | |
var content = text[i].textContent.replace(/\n/g, " "); | |
// content 保存的是字幕内容 - 这里把换行换成了空格, 因为 Youtube 显示的多行字幕中间会有个\n, 如果不加这个replace. 两行的内容就会黏在一起. | |
var start = text[i].getAttribute('start'); | |
var end = $(text[i+1]).attr('start'); | |
if(!end){ | |
end = start + 5; | |
} | |
// ==== 开始处理数据, 把数据保存到result里. ==== | |
result = result + index + escape('\r\n'); | |
// 把序号加进去 | |
var start_time = process_time( parseFloat(start) ); | |
result = result + start_time; | |
// 拿到 开始时间 之后往result字符串里存一下 | |
result = result + ' --> '; | |
// 标准srt时间轴: 00:00:01,850 --> 00:00:02,720 | |
// 我们现在加个中间的箭头.. | |
var end_time = process_time( parseFloat(end) ); | |
result = result + end_time + escape('\r\n'); | |
// 拿到 结束时间 之后往result字符串里存一下 | |
result = result + content + escape('\r\n\r\n'); | |
// 加字幕内容 | |
} | |
result = result.replace(/'/g, "'"); | |
// 字幕里会有html实体字符..所以我们替换掉 | |
var title = '(' + language_name_1c7 + ')' + unsafeWindow.ytplayer.config.args.title + '.srt'; | |
downloadFile(title, result); | |
// 下载 | |
}).fail(function() { | |
alert("Error: No response from server."); | |
}); | |
selector.options[0].selected = true; | |
// 下载完把选项框选回第一个元素. 也就是 Download captions. | |
} | |
// 载入字幕有多少种语言的函数, 然后加到那个选项框里 | |
function load_language_list (select) { | |
GM_xmlhttpRequest({ | |
method: 'GET', | |
url: 'https://video.google.com/timedtext?hl=en&v=' + VIDEO_ID + '&type=list', | |
onload: function( xhr ) { | |
var caption, option, caption_info, | |
captions = new DOMParser().parseFromString(xhr.responseText, "text/xml").getElementsByTagName('track'); | |
if (captions.length === 0) { | |
return select.options[0].textContent = 'No captions.'; | |
} | |
for (var i = 0, il = captions.length; i < il; i++) { | |
caption = captions[i]; | |
option = document.createElement('option'); | |
caption_info = { | |
name: caption.getAttribute('name'), | |
lang_code: caption.getAttribute('lang_code'), | |
lang_name: caption.getAttribute('lang_translated') | |
}; | |
caption_array.push(caption_info); | |
option.textContent = caption_info.lang_name; | |
select.appendChild(option); | |
} | |
select.options[0].textContent = 'Download captions.'; | |
select.disabled = false; | |
} | |
}); | |
} | |
// 处理时间. 比如 start="671.33" start="37.64" start="12" start="23.029" | |
// 处理成 srt 时间, 比如 00:00:00,090 00:00:08,460 00:10:29,350 | |
function process_time(s){ | |
s = s.toFixed(3); | |
// 超棒的函数, 不论是整数还是小数都给弄成3位小数形式 | |
// 举个柚子: | |
// 671.33 -> 671.330 | |
// 671 -> 671.000 | |
// 注意函数会四舍五入. 具体读文档 | |
var array = s.split('.'); | |
// 把开始时间根据句号分割 | |
// 671.330 会分割成数组: [671, 330] | |
var Hour = 0; | |
var Minute = 0; | |
var Second = array[0]; // 671 | |
var MilliSecond = array[1]; // 330 | |
// 先声明下变量, 待会把这几个拼好就行了 | |
// 我们来处理秒数. 把"分钟"和"小时"除出来 | |
if(Second >= 60){ | |
Minute = Math.floor(Second / 60); | |
Second = Second - Minute * 60; | |
// 把 秒 拆成 分钟和秒, 比如121秒, 拆成2分钟1秒 | |
Hour = Math.floor(Minute / 60); | |
Minute = Minute - Hour * 60; | |
// 把 分钟 拆成 小时和分钟, 比如700分钟, 拆成11小时40分钟 | |
} | |
// 分钟,如果位数不够两位就变成两位,下面两个if语句的作用也是一样。 | |
if (Minute < 10){ | |
Minute = '0' + Minute; | |
} | |
// 小时 | |
if (Hour < 10){ | |
Hour = '0' + Hour; | |
} | |
// 秒 | |
if (Second < 10){ | |
Second = '0' + Second; | |
} | |
return Hour + ':' + Minute + ':' + Second + ',' + MilliSecond; | |
} | |
function downloadFile(fileName, content){ | |
var TITLE = unsafeWindow.ytplayer.config.args.title; // 拿视频标题 | |
var version = getChromeVersion(); // 拿 Chrome 版本 | |
// dummy element for download | |
if ($('#youtube-subtitle-downloader-dummy-element-for-download').length > 0) { | |
}else{ | |
$("body").append('<a id="youtube-subtitle-downloader-dummy-element-for-download"></a>'); | |
} | |
var dummy = $('#youtube-subtitle-downloader-dummy-element-for-download'); | |
// 判断 Chrome 版本来做事,Chrome 52 和 53 的文件下载方式不一样, 总不能为了兼顾 53 的让 52 的用户用不了 | |
if (version > 52){ | |
dummy.attr('download', fileName); | |
dummy.attr('href','data:Content-type: text/plain,' + content); | |
dummy[0].click(); | |
} else { | |
downloadViaBlob(fileName, content); | |
} | |
} | |
// 复制自: http://www.alloyteam.com/2014/01/use-js-file-download/ | |
// Chrome 53 之后这个函数失效。52有效。 | |
function downloadViaBlob(fileName, content){ | |
var aLink = document.createElement('a'); | |
var blob = new Blob([content]); | |
var evt = document.createEvent("HTMLEvents"); | |
evt.initEvent("click", false, false); | |
aLink.download = fileName; | |
aLink.href = URL.createObjectURL(blob); | |
aLink.dispatchEvent(evt); | |
} | |
//http://stackoverflow.com/questions/4900436/how-to-detect-the-installed-chrome-version | |
function getChromeVersion() { | |
var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); | |
return raw ? parseInt(raw[2], 10) : false; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can you make scripts that set a timer to automatically pause/stop on all Youtube embed videos? Like pause a video after X seconds!!?