// ==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.3 // @date 2015-05-17 // @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/html5player* // @match https://s.ytimg.com/yts/jsbin/html5player* // @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* // @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={'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'}; 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'}; var FORMAT_ORDER=['5','18','34','43','35','135','44','22','298','45','37','299','46','264','38','266','139','140','141']; var FORMAT_RULE={'flv':'max','mp4':'all','webm':'none','m4a':'max'}; // all=display all versions, max=only highest quality version, none=no version // the default settings show all MP4 videos, the highest quality FLV and no WebM 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-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=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;i2) 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(http[^<]+itag='+newFormat+'[^<]+)<\\/BaseURL>','i'); var matchURL=findMatch(response.responseText, regexp); debug('DYVAM - Info: matchURL '+matchURL); if (!matchURL) return; matchURL=matchURL.replace(/&\;/g,'&'); for (var i=0;i=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('function \\s*' + signatureFunctionName + '\\s*\\([\\w$]*\\)\\s*{[\\w$]*=[\\w$]*\\.split\\(""\\);(.+);return [\\w$]*\\.join'); var functionCode = findMatch(sourceCode, regCode); 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; i0) { 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= 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;i0)?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; } } })();