source: main/trunk/model-sites-dev/respooled/user-script/Download_YouTube_Videos_as_MP4.user.js@ 29984

Last change on this file since 29984 was 29984, checked in by davidb, 9 years ago

The Respooled User-script for transfering a selected YouTube video to the DL, along with the YouTube Downloader script it was based on

  • Property svn:executable set to *
File size: 34.5 KB
Line 
1// ==UserScript==
2// @name Download YouTube Videos as MP4
3// @description Adds a button that lets you download YouTube videos.
4// @homepageURL https://github.com/gantt/downloadyoutube
5// @author Gantt
6// @version 1.8.3
7// @date 2015-05-17
8// @namespace http://googlesystem.blogspot.com
9// @include http://www.youtube.com/*
10// @include https://www.youtube.com/*
11// @exclude http://www.youtube.com/embed/*
12// @exclude https://www.youtube.com/embed/*
13// @match http://www.youtube.com/*
14// @match https://www.youtube.com/*
15// @match http://s.ytimg.com/yts/jsbin/html5player*
16// @match https://s.ytimg.com/yts/jsbin/html5player*
17// @match http://manifest.googlevideo.com/*
18// @match https://manifest.googlevideo.com/*
19// @match http://*.googlevideo.com/videoplayback*
20// @match https://*.googlevideo.com/videoplayback*
21// @match http://*.youtube.com/videoplayback*
22// @match https://*.youtube.com/videoplayback*
23// @grant GM_xmlhttpRequest
24// @grant GM_getValue
25// @grant GM_setValue
26// @run-at document-end
27// @license MIT License
28// @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=
29// ==/UserScript==
30
31
32(function () {
33 var FORMAT_LABEL={'5':'FLV 240p','18':'MP4 360p','22':'MP4 720p','34':'FLV 360p','35':'FLV 480p','37':'MP4 1080p','38':'MP4 2160p','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','139':'M4A 48kbps - audio','140':'M4A 128kbps - audio','141':'M4A 256kbps - audio','264':'MP4 1440p - no audio','266':'MP4 2160p - no audio','298':'MP4 720p60 - no audio','299':'MP4 1080p60 - no audio'};
34 var FORMAT_TYPE={'5':'flv','18':'mp4','22':'mp4','34':'flv','35':'flv','37':'mp4','38':'mp4','43':'webm','44':'webm','45':'webm','46':'webm','135':'mp4','137':'mp4','138':'mp4','139':'m4a','140':'m4a','141':'m4a','264':'mp4','266':'mp4','298':'mp4','299':'mp4'};
35 var FORMAT_ORDER=['5','18','34','43','35','135','44','22','298','45','37','299','46','264','38','266','139','140','141'];
36 var FORMAT_RULE={'flv':'max','mp4':'all','webm':'none','m4a':'max'};
37 // all=display all versions, max=only highest quality version, none=no version
38 // the default settings show all MP4 videos, the highest quality FLV and no WebM
39 var SHOW_DASH_FORMATS=false;
40 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':'下載'};
41 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':'下載此影片'};
42 var DECODE_RULE=[];
43 var RANDOM=7489235179; // Math.floor(Math.random()*1234567890);
44 var CONTAINER_ID='download-youtube-video'+RANDOM;
45 var LISTITEM_ID='download-youtube-video-fmt'+RANDOM;
46 var BUTTON_ID='download-youtube-video-button'+RANDOM;
47 var DEBUG_ID='download-youtube-video-debug-info';
48 var STORAGE_URL='download-youtube-script-url';
49 var STORAGE_CODE='download-youtube-signature-code';
50 var STORAGE_DASH='download-youtube-dash-enabled';
51 var isDecodeRuleUpdated=false;
52
53 start();
54
55function start() {
56 var pagecontainer=document.getElementById('page-container');
57 if (!pagecontainer) return;
58 if (/^https?:\/\/www\.youtube.com\/watch\?/.test(window.location.href)) run();
59 var isAjax=/class[\w\s"'-=]+spf\-link/.test(pagecontainer.innerHTML);
60 var logocontainer=document.getElementById('logo-container');
61 if (logocontainer && !isAjax) { // fix for blocked videos
62 isAjax=(' '+logocontainer.className+' ').indexOf(' spf-link ')>=0;
63 }
64 var content=document.getElementById('content');
65 if (isAjax && content) { // Ajax UI
66 var mo=window.MutationObserver||window.WebKitMutationObserver;
67 if(typeof mo!=='undefined') {
68 var observer=new mo(function(mutations) {
69 mutations.forEach(function(mutation) {
70 if(mutation.addedNodes!==null) {
71 for (var i=0; i<mutation.addedNodes.length; i++) {
72 if (mutation.addedNodes[i].id=='watch7-container' ||
73 mutation.addedNodes[i].id=='watch7-main-container') { // old value: movie_player
74 run();
75 break;
76 }
77 }
78 }
79 });
80 });
81 observer.observe(content, {childList: true, subtree: true}); // old value: pagecontainer
82 } else { // MutationObserver fallback for old browsers
83 pagecontainer.addEventListener('DOMNodeInserted', onNodeInserted, false);
84 }
85 }
86}
87
88function onNodeInserted(e) {
89 if (e && e.target && (e.target.id=='watch7-container' ||
90 e.target.id=='watch7-main-container')) { // old value: movie_player
91 run();
92 }
93}
94
95function run() {
96 if (document.getElementById(CONTAINER_ID)) return; // check download container
97 if (document.getElementById('p') && document.getElementById('vo')) return; // Feather not supported
98
99 var videoID, videoFormats, videoAdaptFormats, videoManifestURL, scriptURL=null;
100 var isSignatureUpdatingStarted=false;
101 var operaTable=new Array();
102 var language=document.documentElement.getAttribute('lang');
103 var textDirection='left';
104 if (document.body.getAttribute('dir')=='rtl') {
105 textDirection='right';
106 }
107 if (document.getElementById('watch7-action-buttons')) { // old UI
108 fixTranslations(language, textDirection);
109 }
110
111 // obtain video ID, formats map
112
113 var args=null;
114 var usw=(typeof this.unsafeWindow !== 'undefined')?this.unsafeWindow:window; // Firefox, Opera<15
115 if (usw.ytplayer && usw.ytplayer.config && usw.ytplayer.config.args) {
116 args=usw.ytplayer.config.args;
117 }
118 if (args) {
119 videoID=args['video_id'];
120 videoFormats=args['url_encoded_fmt_stream_map'];
121 videoAdaptFormats=args['adaptive_fmts'];
122 videoManifestURL=args['dashmpd'];
123 debug('DYVAM - Info: Standard mode. videoID '+(videoID?videoID:'none')+'; ');
124 }
125 if (usw.ytplayer && usw.ytplayer.config && usw.ytplayer.config.assets) {
126 scriptURL=usw.ytplayer.config.assets.js;
127 }
128
129 if (videoID==null) { // unsafeWindow workaround (Chrome, Opera 15+)
130 var buffer=document.getElementById(DEBUG_ID+'2');
131 if (buffer) {
132 while (buffer.firstChild) {
133 buffer.removeChild(buffer.firstChild);
134 }
135 } else {
136 buffer=createHiddenElem('pre', DEBUG_ID+'2');
137 }
138 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+\'"\'));}');
139 var code=buffer.innerHTML;
140 if (code) {
141 videoID=findMatch(code, /\"video_id\":\s*\"([^\"]+)\"/);
142 videoFormats=findMatch(code, /\"url_encoded_fmt_stream_map\":\s*\"([^\"]+)\"/);
143 videoFormats=videoFormats.replace(/&amp;/g,'\\u0026');
144 videoAdaptFormats=findMatch(code, /\"adaptive_fmts\":\s*\"([^\"]+)\"/);
145 videoAdaptFormats=videoAdaptFormats.replace(/&amp;/g,'\\u0026');
146 videoManifestURL=findMatch(code, /\"dashmpd\":\s*\"([^\"]+)\"/);
147 scriptURL=findMatch(code, /\"js\":\s*\"([^\"]+)\"/);
148 }
149 debug('DYVAM - Info: Injection mode. videoID '+(videoID?videoID:'none')+'; ');
150 }
151
152 if (videoID==null) { // if all else fails
153 var bodyContent=document.body.innerHTML;
154 if (bodyContent!=null) {
155 videoID=findMatch(bodyContent, /\"video_id\":\s*\"([^\"]+)\"/);
156 videoFormats=findMatch(bodyContent, /\"url_encoded_fmt_stream_map\":\s*\"([^\"]+)\"/);
157 videoAdaptFormats=findMatch(bodyContent, /\"adaptive_fmts\":\s*\"([^\"]+)\"/);
158 videoManifestURL=findMatch(bodyContent, /\"dashmpd\":\s*\"([^\"]+)\"/);
159 if (scriptURL==null) {
160 scriptURL=findMatch(bodyContent, /\"js\":\s*\"([^\"]+)\"/);
161 if (scriptURL) {
162 scriptURL=scriptURL.replace(/\\/g,'');
163 }
164 }
165 }
166 debug('DYVAM - Info: Brute mode. videoID '+(videoID?videoID:'none')+'; ');
167 }
168
169 debug('DYVAM - Info: url '+window.location.href+'; useragent '+window.navigator.userAgent);
170
171 if (videoID==null || videoFormats==null || videoID.length==0 || videoFormats.length==0) {
172 debug('DYVAM - Error: No config information found. YouTube must have changed the code.');
173 return;
174 }
175
176 // Opera 12 extension message handler
177 if (typeof window.opera !== 'undefined' && window.opera && typeof opera.extension !== 'undefined') {
178 opera.extension.onmessage = function(event) {
179 var index=findMatch(event.data.action, /xhr\-([0-9]+)\-response/);
180 if (index && operaTable[parseInt(index,10)]) {
181 index=parseInt(index,10);
182 var trigger=(operaTable[index])['onload'];
183 if (typeof trigger === 'function' && event.data.readyState == 4) {
184 if (trigger) {
185 trigger(event.data);
186 }
187 }
188 }
189 }
190 }
191
192 if (!isDecodeRuleUpdated) {
193 DECODE_RULE=getDecodeRules(DECODE_RULE);
194 isDecodeRuleUpdated=true;
195 }
196 if (scriptURL) {
197 if (scriptURL.indexOf('//')==0) {
198 var protocol=(document.location.protocol=='http:')?'http:':'https:';
199 scriptURL=protocol+scriptURL;
200 }
201 fetchSignatureScript(scriptURL);
202 }
203
204 // video title
205 var videoTitle=document.title || 'video';
206 videoTitle=videoTitle.replace(/\s*\-\s*YouTube$/i,'').replace(/[#"\?:\*]/g,'').replace(/[&\|\\\/]/g,'_').replace(/'/g,'\'').replace(/^\s+|\s+$/g,'').replace(/\.+$/g,'');
207
208 // parse the formats map
209 var sep1='%2C', sep2='%26', sep3='%3D';
210 if (videoFormats.indexOf(',')>-1) {
211 sep1=',';
212 sep2=(videoFormats.indexOf('&')>-1)?'&':'\\u0026';
213 sep3='=';
214 }
215 var videoURL=new Array();
216 var videoSignature=new Array();
217 if (videoAdaptFormats) {
218 videoFormats=videoFormats+sep1+videoAdaptFormats;
219 }
220 var videoFormatsGroup=videoFormats.split(sep1);
221 for (var i=0;i<videoFormatsGroup.length;i++) {
222 var videoFormatsElem=videoFormatsGroup[i].split(sep2);
223 var videoFormatsPair=new Array();
224 for (var j=0;j<videoFormatsElem.length;j++) {
225 var pair=videoFormatsElem[j].split(sep3);
226 if (pair.length==2) {
227 videoFormatsPair[pair[0]]=pair[1];
228 }
229 }
230 if (videoFormatsPair['url']==null) continue;
231 var url=unescape(unescape(videoFormatsPair['url'])).replace(/\\\//g,'/').replace(/\\u0026/g,'&');
232 if (videoFormatsPair['itag']==null) continue;
233 var itag=videoFormatsPair['itag'];
234 var sig=videoFormatsPair['sig']||videoFormatsPair['signature'];
235 if (sig) {
236 url=url+'&signature='+sig;
237 videoSignature[itag]=null;
238 } else if (videoFormatsPair['s']) {
239 url=url+'&signature='+decryptSignature(videoFormatsPair['s']);
240 videoSignature[itag]=videoFormatsPair['s'];
241 }
242 if (url.toLowerCase().indexOf('ratebypass')==-1) { // speed up download for dash
243 url=url+'&ratebypass=yes';
244 }
245 if (url.toLowerCase().indexOf('http')==0) { // validate URL
246 videoURL[itag]=url+'&title='+videoTitle;
247 }
248 }
249
250 var showFormat=new Array();
251 for (var category in FORMAT_RULE) {
252 var rule=FORMAT_RULE[category];
253 for (var index in FORMAT_TYPE){
254 if (FORMAT_TYPE[index]==category) {
255 showFormat[index]=(rule=='all');
256 }
257 }
258 if (rule=='max') {
259 for (var i=FORMAT_ORDER.length-1;i>=0;i--) {
260 var format=FORMAT_ORDER[i];
261 if (FORMAT_TYPE[format]==category && videoURL[format]!=undefined) {
262 showFormat[format]=true;
263 break;
264 }
265 }
266 }
267 }
268
269 var dashPref=getPref(STORAGE_DASH);
270 if (dashPref=='1') {
271 SHOW_DASH_FORMATS=true;
272 } else if (dashPref!='0') {
273 setPref(STORAGE_DASH,'0');
274 }
275
276 var downloadCodeList=[];
277 for (var i=0;i<FORMAT_ORDER.length;i++) {
278 var format=FORMAT_ORDER[i];
279 if (format=='37' && videoURL[format]==undefined) { // hack for dash 1080p
280 if (videoURL['137']) {
281 format='137';
282 }
283 showFormat[format]=showFormat['37'];
284 } else if (format=='38' && videoURL[format]==undefined) { // hack for dash 4K
285 if (videoURL['138'] && !videoURL['266']) {
286 format='138';
287 }
288 showFormat[format]=showFormat['38'];
289 }
290 if (!SHOW_DASH_FORMATS && format.length>2) continue;
291 if (videoURL[format]!=undefined && FORMAT_LABEL[format]!=undefined && showFormat[format]) {
292 downloadCodeList.push({url:videoURL[format],sig:videoSignature[format],format:format,label:FORMAT_LABEL[format]});
293 debug('DYVAM - Info: itag'+format+' url:'+videoURL[format]);
294 }
295 }
296
297 if (downloadCodeList.length==0) {
298 debug('DYVAM - Error: No download URL found. Probably YouTube uses encrypted streams.');
299 return; // no format
300 }
301
302 // find parent container
303 var newWatchPage=false;
304 var parentElement=document.getElementById('watch7-action-buttons');
305 if (parentElement==null) {
306 parentElement=document.getElementById('watch8-secondary-actions');
307 if (parentElement==null) {
308 debug('DYVAM Error - No container for adding the download button. YouTube must have changed the code.');
309 return;
310 } else {
311 newWatchPage=true;
312 }
313 }
314
315 // get button labels
316 var buttonText=(BUTTON_TEXT[language])?BUTTON_TEXT[language]:BUTTON_TEXT['en'];
317 var buttonLabel=(BUTTON_TOOLTIP[language])?BUTTON_TOOLTIP[language]:BUTTON_TOOLTIP['en'];
318
319 // generate download code for regular interface
320 var mainSpan=document.createElement('span');
321
322 if (newWatchPage) {
323 var spanIcon=document.createElement('span');
324 spanIcon.setAttribute('class', 'yt-uix-button-icon-wrapper');
325 var imageIcon=document.createElement('img');
326 imageIcon.setAttribute('src', '//s.ytimg.com/yt/img/pixel-vfl3z5WfW.gif');
327 imageIcon.setAttribute('class', 'yt-uix-button-icon');
328 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==);');
329 spanIcon.appendChild(imageIcon);
330 mainSpan.appendChild(spanIcon);
331 }
332
333 var spanButton=document.createElement('span');
334 spanButton.setAttribute('class', 'yt-uix-button-content');
335 spanButton.appendChild(document.createTextNode(buttonText+' '));
336 mainSpan.appendChild(spanButton);
337
338 if (!newWatchPage) { // old UI
339 var imgButton=document.createElement('img');
340 imgButton.setAttribute('class', 'yt-uix-button-arrow');
341 imgButton.setAttribute('src', '//s.ytimg.com/yt/img/pixel-vfl3z5WfW.gif');
342 mainSpan.appendChild(imgButton);
343 }
344
345 var listItems=document.createElement('ol');
346 listItems.setAttribute('style', 'display:none;');
347 listItems.setAttribute('class', 'yt-uix-button-menu');
348 for (var i=0;i<downloadCodeList.length;i++) {
349 var listItem=document.createElement('li');
350 var listLink=document.createElement('a');
351 listLink.setAttribute('style', 'text-decoration:none;');
352 listLink.setAttribute('href', downloadCodeList[i].url);
353 listLink.setAttribute('download', videoTitle+'.'+FORMAT_TYPE[downloadCodeList[i].format]);
354 var listButton=document.createElement('span');
355 listButton.setAttribute('class', 'yt-uix-button-menu-item');
356 listButton.setAttribute('loop', i+'');
357 listButton.setAttribute('id', LISTITEM_ID+downloadCodeList[i].format);
358 listButton.appendChild(document.createTextNode(downloadCodeList[i].label));
359 listLink.appendChild(listButton);
360 listItem.appendChild(listLink);
361 listItems.appendChild(listItem);
362 }
363 mainSpan.appendChild(listItems);
364 var buttonElement=document.createElement('button');
365 buttonElement.setAttribute('id', BUTTON_ID);
366 if (newWatchPage) {
367 buttonElement.setAttribute('class', 'yt-uix-button yt-uix-button-size-default yt-uix-button-opacity yt-uix-tooltip');
368 } else { // old UI
369 buttonElement.setAttribute('class', 'yt-uix-button yt-uix-tooltip yt-uix-button-empty yt-uix-button-text');
370 buttonElement.setAttribute('style', 'margin-top:4px; margin-left:'+((textDirection=='left')?5:10)+'px;');
371 }
372 buttonElement.setAttribute('data-tooltip-text', buttonLabel);
373 buttonElement.setAttribute('type', 'button');
374 buttonElement.setAttribute('role', 'button');
375 buttonElement.addEventListener('click', function(){return false;}, false);
376 buttonElement.appendChild(mainSpan);
377 var containerSpan=document.createElement('span');
378 containerSpan.setAttribute('id', CONTAINER_ID);
379 containerSpan.appendChild(document.createTextNode(' '));
380 containerSpan.appendChild(buttonElement);
381
382 // add the button
383 if (!newWatchPage) { // watch7
384 parentElement.appendChild(containerSpan);
385 } else { // watch8
386 parentElement.insertBefore(containerSpan, parentElement.firstChild);
387 }
388
389 // REPLACEWITH if (!isSignatureUpdatingStarted) {
390 for (var i=0;i<downloadCodeList.length;i++) {
391 addFileSize(downloadCodeList[i].url, downloadCodeList[i].format);
392 }
393 // }
394
395 if (typeof GM_download !== 'undefined') {
396 for (var i=0;i<downloadCodeList.length;i++) {
397 var downloadFMT=document.getElementById(LISTITEM_ID+downloadCodeList[i].format);
398 var url=(downloadCodeList[i].url).toLowerCase();
399 if (url.indexOf('clen=')>0 && url.indexOf('dur=')>0 && url.indexOf('gir=')>0
400 && url.indexOf('lmt=')>0) {
401 downloadFMT.addEventListener('click', downloadVideoNatively, false);
402 }
403 }
404 }
405
406 addFromManifest('140', '141'); // replace fmt140 with fmt141 if found in manifest
407
408 function downloadVideoNatively(e) {
409 var elem=e.currentTarget;
410 e.returnValue=false;
411 if (e.preventDefault) {
412 e.preventDefault();
413 }
414 var loop=elem.getAttribute('loop');
415 if (loop) {
416 GM_download(downloadCodeList[loop].url, videoTitle+'.'+FORMAT_TYPE[downloadCodeList[loop].format]);
417 }
418 return false;
419 }
420
421 function addFromManifest(oldFormat, newFormat) { // find newFormat URL in manifest
422 if (videoManifestURL && videoURL[newFormat]==undefined && SHOW_DASH_FORMATS && FORMAT_RULE['m4a']=='max') {
423 var matchSig=findMatch(videoManifestURL, /\/s\/([a-zA-Z0-9\.]+)\//i);
424 if (matchSig) {
425 var decryptedSig=decryptSignature(matchSig);
426 if (decryptedSig) {
427 videoManifestURL=videoManifestURL.replace('/s/'+matchSig+'/','/signature/'+decryptedSig+'/');
428 }
429 }
430 if (videoManifestURL.indexOf('//')==0) {
431 var protocol=(document.location.protocol=='http:')?'http:':'https:';
432 videoManifestURL=protocol+videoManifestURL;
433 }
434 debug('DYVAM - Info: manifestURL '+videoManifestURL);
435 crossXmlHttpRequest({
436 method:'GET',
437 url:videoManifestURL, // check if URL exists
438 onload:function(response) {
439 if (response.readyState === 4 && response.status === 200 && response.responseText) {
440 var regexp = new RegExp('<BaseURL.+>(http[^<]+itag='+newFormat+'[^<]+)<\\/BaseURL>','i');
441 var matchURL=findMatch(response.responseText, regexp);
442 debug('DYVAM - Info: matchURL '+matchURL);
443 if (!matchURL) return;
444 matchURL=matchURL.replace(/&amp\;/g,'&');
445 for (var i=0;i<downloadCodeList.length;i++) {
446 if (downloadCodeList[i].format==oldFormat) {
447 downloadCodeList[i].format==newFormat;
448 var downloadFMT=document.getElementById(LISTITEM_ID+oldFormat);
449 downloadFMT.setAttribute('id', LISTITEM_ID+newFormat);
450 downloadFMT.parentNode.setAttribute('href', matchURL);
451 downloadCodeList[i].url=matchURL;
452 downloadFMT.firstChild.nodeValue=FORMAT_LABEL[newFormat];
453 addFileSize(matchURL, newFormat);
454 }
455 }
456 }
457 }
458 });
459 }
460 }
461
462 function injectStyle(code) {
463 var style=document.createElement('style');
464 style.type='text/css';
465 style.appendChild(document.createTextNode(code));
466 document.getElementsByTagName('head')[0].appendChild(style);
467 }
468
469 function injectScript(code) {
470 var script=document.createElement('script');
471 script.type='application/javascript';
472 script.textContent=code;
473 document.body.appendChild(script);
474 document.body.removeChild(script);
475 }
476
477 function debug(str) {
478 var debugElem=document.getElementById(DEBUG_ID);
479 if (!debugElem) {
480 debugElem=createHiddenElem('div', DEBUG_ID);
481 }
482 debugElem.appendChild(document.createTextNode(str+' '));
483 }
484
485 function createHiddenElem(tag, id) {
486 var elem=document.createElement(tag);
487 elem.setAttribute('id', id);
488 elem.setAttribute('style', 'display:none;');
489 document.body.appendChild(elem);
490 return elem;
491 }
492
493 function fixTranslations(language, textDirection) {
494 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
495 var likeButton=document.getElementById('watch-like');
496 if (likeButton) {
497 var spanElements=likeButton.getElementsByClassName('yt-uix-button-content');
498 if (spanElements) {
499 spanElements[0].style.display='none'; // hide like text
500 }
501 }
502 var marginPixels=10;
503 if (/^bg|ca|cs|el|eu|hr|it|ml|ms|pl|sl|sw|te$/.test(language)) {
504 marginPixels=1;
505 }
506 injectStyle('#watch7-secondary-actions .yt-uix-button{margin-'+textDirection+':'+marginPixels+'px!important}');
507 }
508 }
509
510 function findMatch(text, regexp) {
511 var matches=text.match(regexp);
512 return (matches)?matches[1]:null;
513 }
514
515 function isString(s) {
516 return (typeof s==='string' || s instanceof String);
517 }
518
519 function isInteger(n) {
520 return (typeof n==='number' && n%1==0);
521 }
522
523 function getPref(name) { // cross-browser GM_getValue
524 var a='', b='';
525 try {a=typeof GM_getValue.toString; b=GM_getValue.toString()} catch(e){}
526 if (typeof GM_getValue === 'function' &&
527 (a === 'undefined' || b.indexOf('not supported') === -1)) {
528 return GM_getValue(name, null); // Greasemonkey, Tampermonkey, Firefox extension
529 } else {
530 var ls=null;
531 try {ls=window.localStorage||null} catch(e){}
532 if (ls) {
533 return ls.getItem(name); // Chrome script, Opera extensions
534 }
535 }
536 return;
537 }
538
539 function setPref(name, value) { // cross-browser GM_setValue
540 var a='', b='';
541 try {a=typeof GM_setValue.toString; b=GM_setValue.toString()} catch(e){}
542 if (typeof GM_setValue === 'function' &&
543 (a === 'undefined' || b.indexOf('not supported') === -1)) {
544 GM_setValue(name, value); // Greasemonkey, Tampermonkey, Firefox extension
545 } else {
546 var ls=null;
547 try {ls=window.localStorage||null} catch(e){}
548 if (ls) {
549 return ls.setItem(name, value); // Chrome script, Opera extensions
550 }
551 }
552 }
553
554 function crossXmlHttpRequest(details) { // cross-browser GM_xmlhttpRequest
555 if (typeof GM_xmlhttpRequest === 'function') { // Greasemonkey, Tampermonkey, Firefox extension, Chrome script
556 GM_xmlhttpRequest(details);
557 } else if (typeof window.opera !== 'undefined' && window.opera && typeof opera.extension !== 'undefined' &&
558 typeof opera.extension.postMessage !== 'undefined') { // Opera 12 extension
559 var index=operaTable.length;
560 opera.extension.postMessage({'action':'xhr-'+index, 'url':details.url, 'method':details.method});
561 operaTable[index]=details;
562 } else if (typeof window.opera === 'undefined' && typeof XMLHttpRequest === 'function') { // Opera 15+ extension
563 var xhr=new XMLHttpRequest();
564 xhr.onreadystatechange = function() {
565 if (xhr.readyState == 4) {
566 if (details['onload']) {
567 details['onload'](xhr);
568 }
569 }
570 }
571 xhr.open(details.method, details.url, true);
572 xhr.send();
573 }
574 }
575
576 function addFileSize(url, format) {
577
578 function updateVideoLabel(size, format) {
579 var elem=document.getElementById(LISTITEM_ID+format);
580 if (elem) {
581 size=parseInt(size,10);
582 if (size>=1073741824) {
583 size=parseFloat((size/1073741824).toFixed(1))+' GB';
584 } else if (size>=1048576) {
585 size=parseFloat((size/1048576).toFixed(1))+' MB';
586 } else {
587 size=parseFloat((size/1024).toFixed(1))+' KB';
588 }
589 if (elem.childNodes.length>1) {
590 elem.lastChild.nodeValue=' ('+size+')';
591 } else if (elem.childNodes.length==1) {
592 elem.appendChild(document.createTextNode(' ('+size+')'));
593 }
594 }
595 }
596
597 var matchSize=findMatch(url, /[&\?]clen=([0-9]+)&/i);
598 if (matchSize) {
599 updateVideoLabel(matchSize, format);
600 } else {
601 try {
602 crossXmlHttpRequest({
603 method:'HEAD',
604 url:url,
605 onload:function(response) {
606 if (response.readyState == 4 && response.status == 200) { // add size
607 var size=0;
608 if (typeof response.getResponseHeader === 'function') {
609 size=response.getResponseHeader('Content-length');
610 } else if (response.responseHeaders) {
611 var regexp = new RegExp('^Content\-length: (.*)$','im');
612 var match = regexp.exec(response.responseHeaders);
613 if (match) {
614 size=match[1];
615 }
616 }
617 if (size) {
618 updateVideoLabel(size, format);
619 }
620 }
621 }
622 });
623 } catch(e) { }
624 }
625 }
626
627 function findSignatureCode(sourceCode) {
628 debug('DYVAM - Info: signature start '+getPref(STORAGE_CODE));
629 var signatureFunctionName =
630 findMatch(sourceCode,
631 /\.set\s*\("signature"\s*,\s*([a-zA-Z0-9_$][\w$]*)\(/)
632 || findMatch(sourceCode,
633 /\.sig\s*\|\|\s*([a-zA-Z0-9_$][\w$]*)\(/)
634 || findMatch(sourceCode,
635 /\.signature\s*=\s*([a-zA-Z_$][\w$]*)\([a-zA-Z_$][\w$]*\)/); //old
636 if (signatureFunctionName == null) return setPref(STORAGE_CODE, 'error');
637 signatureFunctionName=signatureFunctionName.replace('$','\\$');
638 var regCode = new RegExp('function \\s*' + signatureFunctionName +
639 '\\s*\\([\\w$]*\\)\\s*{[\\w$]*=[\\w$]*\\.split\\(""\\);(.+);return [\\w$]*\\.join');
640 var functionCode = findMatch(sourceCode, regCode);
641 debug('DYVAM - Info: signaturefunction ' + signatureFunctionName + ' -- ' + functionCode);
642 if (functionCode == null) return setPref(STORAGE_CODE, 'error');
643
644 var reverseFunctionName = findMatch(sourceCode,
645 /([\w$]*)\s*:\s*function\s*\(\s*[\w$]*\s*\)\s*{\s*(?:return\s*)?[\w$]*\.reverse\s*\(\s*\)\s*}/);
646 debug('DYVAM - Info: reversefunction ' + reverseFunctionName);
647 if (reverseFunctionName) reverseFunctionName=reverseFunctionName.replace('$','\\$');
648 var sliceFunctionName = findMatch(sourceCode,
649 /([\w$]*)\s*:\s*function\s*\(\s*[\w$]*\s*,\s*[\w$]*\s*\)\s*{\s*(?:return\s*)?[\w$]*\.(?:slice|splice)\(.+\)\s*}/);
650 debug('DYVAM - Info: slicefunction ' + sliceFunctionName);
651 if (sliceFunctionName) sliceFunctionName=sliceFunctionName.replace('$','\\$');
652
653 var regSlice = new RegExp('\\.(?:'+'slice'+(sliceFunctionName?'|'+sliceFunctionName:'')+
654 ')\\s*\\(\\s*(?:[a-zA-Z_$][\\w$]*\\s*,)?\\s*([0-9]+)\\s*\\)'); // .slice(5) sau .Hf(a,5)
655 var regReverse = new RegExp('\\.(?:'+'reverse'+(reverseFunctionName?'|'+reverseFunctionName:'')+
656 ')\\s*\\([^\\)]*\\)'); // .reverse() sau .Gf(a,45)
657 var regSwap = new RegExp('[\\w$]+\\s*\\(\\s*[\\w$]+\\s*,\\s*([0-9]+)\\s*\\)');
658 var regInline = new RegExp('[\\w$]+\\[0\\]\\s*=\\s*[\\w$]+\\[([0-9]+)\\s*%\\s*[\\w$]+\\.length\\]');
659 var functionCodePieces=functionCode.split(';');
660 var decodeArray=[];
661 for (var i=0; i<functionCodePieces.length; i++) {
662 functionCodePieces[i]=functionCodePieces[i].trim();
663 var codeLine=functionCodePieces[i];
664 if (codeLine.length>0) {
665 var arrSlice=codeLine.match(regSlice);
666 var arrReverse=codeLine.match(regReverse);
667 debug(i+': '+codeLine+' --'+(arrSlice?' slice length '+arrSlice.length:'') +' '+(arrReverse?'reverse':''));
668 if (arrSlice && arrSlice.length >= 2) { // slice
669 var slice=parseInt(arrSlice[1], 10);
670 if (isInteger(slice)){
671 decodeArray.push(-slice);
672 } else return setPref(STORAGE_CODE, 'error');
673 } else if (arrReverse && arrReverse.length >= 1) { // reverse
674 decodeArray.push(0);
675 } else if (codeLine.indexOf('[0]') >= 0) { // inline swap
676 if (i+2<functionCodePieces.length &&
677 functionCodePieces[i+1].indexOf('.length') >= 0 &&
678 functionCodePieces[i+1].indexOf('[0]') >= 0) {
679 var inline=findMatch(functionCodePieces[i+1], regInline);
680 inline=parseInt(inline, 10);
681 decodeArray.push(inline);
682 i+=2;
683 } else return setPref(STORAGE_CODE, 'error');
684 } else if (codeLine.indexOf(',') >= 0) { // swap
685 var swap=findMatch(codeLine, regSwap);
686 swap=parseInt(swap, 10);
687 if (isInteger(swap) && swap>0){
688 decodeArray.push(swap);
689 } else return setPref(STORAGE_CODE, 'error');
690 } else return setPref(STORAGE_CODE, 'error');
691 }
692 }
693
694 if (decodeArray) {
695 setPref(STORAGE_URL, scriptURL);
696 setPref(STORAGE_CODE, decodeArray.toString());
697 DECODE_RULE=decodeArray;
698 debug('DYVAM - Info: signature '+decodeArray.toString()+' '+scriptURL);
699 // update download links and add file sizes
700 for (var i=0;i<downloadCodeList.length;i++) {
701 var elem=document.getElementById(LISTITEM_ID+downloadCodeList[i].format);
702 var url=downloadCodeList[i].url;
703 var sig=downloadCodeList[i].sig;
704 if (elem && url && sig) {
705 url=url.replace(/\&signature=[\w\.]+/, '&signature='+decryptSignature(sig));
706 elem.parentNode.setAttribute('href', url);
707 addFileSize(url, downloadCodeList[i].format);
708 }
709 }
710 }
711 }
712
713 function isValidSignatureCode(arr) { // valid values: '5,-3,0,2,5', 'error'
714 if (!arr) return false;
715 if (arr=='error') return true;
716 arr=arr.split(',');
717 for (var i=0;i<arr.length;i++) {
718 if (!isInteger(parseInt(arr[i],10))) return false;
719 }
720 return true;
721 }
722
723 function fetchSignatureScript(scriptURL) {
724 var storageURL=getPref(STORAGE_URL);
725 var storageCode=getPref(STORAGE_CODE);
726 if (!(/,0,|^0,|,0$|\-/.test(storageCode))) storageCode=null; // hack for only positive items
727 if (storageCode && isValidSignatureCode(storageCode) && storageURL &&
728 scriptURL.replace(/^https?/i,'')==storageURL.replace(/^https?/i,'')) return;
729 try {
730 debug('DYVAM fetch '+scriptURL);
731 isSignatureUpdatingStarted=true;
732 crossXmlHttpRequest({
733 method:'GET',
734 url:scriptURL,
735 onload:function(response) {
736 debug('DYVAM fetch status '+response.status);
737 if (response.readyState === 4 && response.status === 200 && response.responseText) {
738 findSignatureCode(response.responseText);
739 }
740 }
741 });
742 } catch(e) { }
743 }
744
745 function getDecodeRules(rules) {
746 var storageCode=getPref(STORAGE_CODE);
747 if (storageCode && storageCode!='error' && isValidSignatureCode(storageCode)) {
748 var arr=storageCode.split(',');
749 for (var i=0; i<arr.length; i++) {
750 arr[i]=parseInt(arr[i], 10);
751 }
752 rules=arr;
753 debug('DYVAM - Info: signature '+arr.toString()+' '+scriptURL);
754 }
755 return rules;
756 }
757
758 function decryptSignature(sig) {
759 function swap(a,b){var c=a[0];a[0]=a[b%a.length];a[b]=c;return a};
760 function decode(sig, arr) { // encoded decryption
761 if (!isString(sig)) return null;
762 var sigA=sig.split('');
763 for (var i=0;i<arr.length;i++) {
764 var act=arr[i];
765 if (!isInteger(act)) return null;
766 sigA=(act>0)?swap(sigA, act):((act==0)?sigA.reverse():sigA.slice(-act));
767 }
768 var result=sigA.join('');
769 return result;
770 }
771
772 if (sig==null) return '';
773 var arr=DECODE_RULE;
774 if (arr) {
775 var sig2=decode(sig, arr);
776 if (sig2) return sig2;
777 } else {
778 setPref(STORAGE_URL, '');
779 setPref(STORAGE_CODE, '');
780 }
781 return sig;
782 }
783
784 }
785
786})();
Note: See TracBrowser for help on using the repository browser.