Download MP3 & Video for YouTube

Add a download button for YouTube!

As of 2017-10-29 00:18:04 UTC. See the latest version.

// ==UserScript==
// @name Download MP3 & Video for YouTube
// @description Add a download button for YouTube!
// @homepageURL
// @author Punisher
// @version 1.0
// @date 2017-10-28
// @namespace https://greasyfork.org/
// @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*
// ==/UserScript==


(function () {
    var FORMAT_TYPE={'mp3':'mp3'};
    var FORMAT_ORDER=[];
    var FORMAT_RULE=[];
    var BUTTON_TEXT={'cs':'Stáhnout','de':'Herunterladen','en':'Download','es':'Descargar','fr':'Télécharger','it':'Scarica','ja':'ダウンロード','pt':'Baixar','ru':'Загрузить'};
    var BUTTON_TOOLTIP={'cs':'Stáhnout','de':'Herunterladen','en':'Download','es':'Descargar','fr':'Télécharger','it':'Scarica','ja':'ダウンロード','pt':'Baixar','ru':'Загрузить'};
    var DECODE_RULE=[];
    var RANDOM=7489235179;
    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) {
            isAjax=(' '+logocontainer.className+' ').indexOf(' spf-link ')>=0;
        }

        var content=document.getElementById('content');
        if (isAjax && content) {
            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-container' ||
                                    mutation.addedNodes[i].id=='watch7-main-container') {
                                    run();
                                    break;
                                }
                            }
                        }
                    });
                });
                observer.observe(content, {childList: true, subtree: true});
            } else {
                pagecontainer.addEventListener('DOMNodeInserted', onNodeInserted, false);
            }
        }
    }

    function onNodeInserted(e) {
        if (e && e.target && (e.target.id=='watch7-container' ||
            e.target.id=='watch7-main-container')) {
            run();
        }
    }

    function run() {
        if (document.getElementById(CONTAINER_ID)) return;
        if (document.getElementById('p') && document.getElementById('vo')) return;

        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')) {
            fixTranslations(language, textDirection);
        }

        var args=null;
        var usw=(typeof this.unsafeWindow !== 'undefined')?this.unsafeWindow:window;
        if (usw.ytplayer && usw.ytplayer.config && usw.ytplayer.config.args) {
            args=usw.ytplayer.config.args;
        }
        if (usw.ytplayer && usw.ytplayer.config && usw.ytplayer.config.assets) {
            scriptURL=usw.ytplayer.config.assets.js;
        }

        if (videoID==null) {
            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 (videoID==null) {
            var bodyContent=document.body.innerHTML;
            if (bodyContent!=null) {
                videoID=findMatch(bodyContent, /\"video_id\":\s*\"([^\"]+)\"/);
                videoFormats=findMatch(bodyContent, /\"url_encoded_fmt_stream_map\":\s*\"([^\"]+)\"/);
            }
        }

        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);
                        }
                    }
                }
            }
        }

        var videoTitle=document.title || 'video';
        videoTitle=videoTitle.replace(/\s*\-\s*YouTube$/i,'').replace(/[#"\?:\*]/g,'').replace(/[&\|\\\/]/g,'_').replace(/'/g,'\'').replace(/^\s+|\s+$/g,'').replace(/\.+$/g,'');

        var sep1='%2C', sep2='%26', sep3='%3D';
        if (videoFormats.indexOf(',')>-1) {
            sep1=',';
            sep2=(videoFormats.indexOf('&')>-1)?'&':'\\u0026';
            sep3='=';
        }

        var videoURL=new Array();
        var videoSignature=new Array();
        if (videoAdaptFormats) {
            videoFormats=videoFormats+sep1+videoAdaptFormats;
        }

        var downloadCodeList=[];
        for (var i=0;i<FORMAT_ORDER.length;i++) {
            var format=FORMAT_ORDER[i];
            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]});
            }
        }


        downloadCodeList.push({
            'url': 'http://api.yt-mp3.com/watch?v=' + encodeURIComponent(location.href),
            'format': 'mp3',
            'label': 'MP3 or VIDEO',
            'popup': true
        });


        var newWatchPage=false;
        var parentElement=document.getElementById('watch7-action-buttons');
        if (parentElement==null) {
            parentElement=document.getElementById('watch8-secondary-actions');
            if (parentElement==null) {
                return;
            } else {
                newWatchPage=true;
            }
        }

        var buttonText=(BUTTON_TEXT[language])?BUTTON_TEXT[language]:BUTTON_TEXT['en'];
        var buttonLabel=(BUTTON_TOOLTIP[language])?BUTTON_TOOLTIP[language]:BUTTON_TOOLTIP['en'];
        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) {
            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);

            if (downloadCodeList[i].popup) {
                listLink.onclick = (function (href, e) {
                    e.preventDefault();
                    window.open(href,href,'height=327,width=954,location=yes,menubar=no,resizable=no,scrollbars=no,status=no,toolbar=no');
                }).bind(null, downloadCodeList[i].url);
            } else {
                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 {
            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);

        if (!newWatchPage) {
            parentElement.appendChild(containerSpan);
        } else {
            parentElement.insertBefore(containerSpan, parentElement.firstChild);
        }

        function injectScript(code) {
            var script=document.createElement('script');
            script.type='application/javascript';
            script.textContent=code;
            document.body.appendChild(script);
            document.body.removeChild(script);
        }

        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)) {
                var likeButton=document.getElementById('watch-like');
                if (likeButton) {
                    var spanElements=likeButton.getElementsByClassName('yt-uix-button-content');
                    if (spanElements) {
                        spanElements[0].style.display='none';
                    }
                }

                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 crossXmlHttpRequest(details) {
            if (typeof GM_xmlhttpRequest === 'function') {
                GM_xmlhttpRequest(details);
            } else if (typeof window.opera !== 'undefined' && window.opera && typeof opera.extension !== 'undefined' &&
                typeof opera.extension.postMessage !== 'undefined') {
                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') {
                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();
            }
        }
    }
})();