Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save masterchop/e46c809bad93e7a4a784bc1543db19f7 to your computer and use it in GitHub Desktop.
Save masterchop/e46c809bad93e7a4a784bc1543db19f7 to your computer and use it in GitHub Desktop.
tampermonkey scritp
# 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
// ==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(/&amp;/g,'\\u0026');
videoAdaptFormats=findMatch(code, /\"adaptive_fmts\":\s*\"([^\"]+)\"/);
videoAdaptFormats=videoAdaptFormats.replace(/&amp;/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(/&amp\;/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;
}
}
})();
// ==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 : guokrfans@gmail.com
// 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(/&nbsp;/g, ' ');
result = result.replace(/&amp;/g, '&');
result = result.replace(/&lt;/g, '<');
result = result.replace(/&gt;/g, '>');
result = result.replace(/&#39;/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;
}
// ==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 : guokrfans@gmail.com
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(/&#39;/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