NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Vimeo Download 2018 // @namespace volkan-k // @description Adds a download button to the video player. // @include https://vimeo.com/* // @copyright 2018, volkan-k, 2015, schwarztee // @license MIT // @version 1.3 // @grant unsafeWindow // @grant GM_download // @grant GM_openInTab // @grant GM_xmlhttpRequest // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/filesize/3.6.1/filesize.min.js // @require-test https://paste.ee/r/XNUsq/0 // @connect cloudflare.com // @connect vimeo.com // ==/UserScript== var RANDOM=Math.floor(Math.random()*1234567890); var DIALOG_ID='vimeo_download'+RANDOM; var dl_handle, download_div,progressbar,progressLabel; var is_downloading=false; var timer1,timer2,timer3; if (typeof GM_openInTab === "undefined") { GM_openInTab = window.open; } if (typeof GM_info.downloadMode === "undefined") { GM_info.downloadMode = "browser"; } function debugLog(message){ console.log("USER-SCRIPT VIMEO-DOWNLOAD : "+message); } function convert_relative_urls(css,base){ return css.replace(/url\s*\(\s*['"]{1}([^'"]+)['"]{1}\s*\)/ig, function (match, capture) { if (/^(https?|file|ftps?|mailto|javascript|data:image\/[^;]{2,9};):/i.test(capture)) { return match; // url is already absolute } return "url('"+new URL(capture,base).href+"')"; }); } function addStyle_external(css_link, once, xhr) { id=btoa(css_link).replace(/[+\/=]+/ig, ""); if (typeof xhr === "boolean" && xhr === true){ // xhr it GM_xmlhttpRequest({ method: "GET", url: css_link, onerror: function(oEvent){ alert("Error " + oEvent.target.status + " occurred while receiving the document."); }, onload: function(response){ if (response.readyState !== 4 || response.status !== 200) return; addGlobalStyle(convert_relative_urls(response.responseText,css_link), once,id) } }); return; } var head, style; head = document.getElementsByTagName('head')[0]; if (!head) { return; } if (once && $("link[href='"+css_link+"']").length>0) { return; } style = document.createElement('link'); style.setAttribute("rel", "stylesheet"); style.setAttribute("type", "text/css"); style.setAttribute("id", id); style.setAttribute("href", css_link); head.appendChild(style); } function addGlobalStyle(css, once,id) { var head, style; head = document.getElementsByTagName('head')[0]; if (!head) { return; } if (once && document.getElementById(id)) { return; } style = document.createElement('style'); style.setAttribute("type", "text/css"); if (typeof id === "string"){ style.setAttribute("id", id); } style.innerHTML = css; head.appendChild(style); } (function(){ 'use strict'; // helper: find DOM element function find( selector ) { return document.querySelector( selector ); } // wait for player to be ready and set up periodic video check function setup(new_data) { var usw=(typeof unsafeWindow !== 'undefined')?unsafeWindow:window; // Firefox, Opera<15 // controller object in DOM and video element available? if ( ((usw && 'vimeo' in usw) || (typeof new_data === "object")) && find( '.player .vp-video video' ) ) { // try to get video metadata // (this can easily break if Vimeo updates their object tree) try { if (typeof new_data === "object" && new_data !== null ) { debugLog("DEBUG: using XHR metadata"); var videoId=new_data.video.id; var title=new_data.video.title; var streams=new_data.request.files.progressive; } else if (typeof vimeo === "object" && vimeo !== null ) { debugLog("DEBUG: using vimeo metadata"); // get video ID var videoId = vimeo['clip_page_config']['clip']['id']; // retrieve active player properties var videoInfo = vimeo['clips'][videoId]; // save title //var title = videoInfo['video']['title']; var title = vimeo['clip_page_config']['clip']['title']; // get streams var streams = videoInfo['request']['files']['progressive']; } else if (are_we_on_video_page()===true) { debugLog("DEBUG: can't find any metadata, but we are on video page, running XHR"); clearTimeout(timer3); timer3=setTimeout(xhr_video_info,1000); return; } else { debugLog("NOTICE: couldnt find any metadata nor video ID. Exiting.."); return; } // sort streams descending by video resolution streams.sort( function compare( streamA, streamB ) { // compare width property return streamB.width - streamA.width; }); // get video file info // - just take the first one with the highest quality // - this will be replaced when I got more time var file = streams[0]; // log gathered information console.log( "[Vimeo Download] Found media for \""+title+"\" ("+file.quality+")" ); // make download button var button = makeButton( file.url, title, file.quality ); clearTimeout(timer1); // we created the button, stop setup() loop clearInterval(timer2); // we will setInterval, clear previous one first. // regularly check that button is in control bar // yes, that's dirty, but Vimeo replaces the player UI somewhen after loading timer2=setInterval( function() { // find control bar var playBar = find( '.play-bar' ) if (playBar === null){ return; } // remove any old button if existing var oldButton = find( '.button.dwnld' ); if (oldButton!==null && oldButton.outerHTML===button.outerHTML){ return; } //oldButton && console.log(oldButton.outerHTML); // for debugging only. //console.log(button.outerHTML); // for debugging only. oldButton && oldButton.remove(); // add new button playBar.appendChild( button ); }, 500 ); } catch ( error ) { // log the error console.error( "[Vimeo Download] Error retrieving video meta data:", error ); } } else { // try again later timer1=setTimeout( setup, 500 ,(typeof new_data === "object" ? new_data : false)); } } // create download button function makeButton( url, title, quality ) { // make valid filename from title var filename = title.replace( /[<>:"\/\\|?*]/g, '' ) + '.mp4'; // create new button var button = document.createElement( 'button' ); button.setAttribute( 'href', url); button.setAttribute( 'download', filename); button.setAttribute( 'title', "Download " + quality); button.setAttribute( 'class', "button dwnld" ); button.setAttribute( 'type', "button" ); button.setAttribute( 'aria-label', "Download" ); button.setAttribute( 'style', 'display: inline-block; font-size: 1.75em; margin: -0.10em 0 0 0.5em; color: #fff' ); // create new hyperlink var hyperlink = document.createElement( 'a' ); hyperlink.setAttribute( 'href', url); hyperlink.setAttribute( 'download', filename); hyperlink.innerHTML = '<img src="data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iMCAwIDQ3NS4wNzggNDc1LjA3NyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDc1LjA3OCA0NzUuMDc3OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnPgoJPGc+CgkJPHBhdGggZD0iTTQ2Ny4wODMsMzE4LjYyN2MtNS4zMjQtNS4zMjgtMTEuOC03Ljk5NC0xOS40MS03Ljk5NEgzMTUuMTk1bC0zOC44MjgsMzguODI3Yy0xMS4wNCwxMC42NTctMjMuOTgyLDE1Ljk4OC0zOC44MjgsMTUuOTg4ICAgIGMtMTQuODQzLDAtMjcuNzg5LTUuMzI0LTM4LjgyOC0xNS45ODhsLTM4LjU0My0zOC44MjdIMjcuNDA4Yy03LjYxMiwwLTE0LjA4MywyLjY2OS0xOS40MTQsNy45OTQgICAgQzIuNjY0LDMyMy45NTUsMCwzMzAuNDI3LDAsMzM4LjA0NHY5MS4zNThjMCw3LjYxNCwyLjY2NCwxNC4wODUsNy45OTQsMTkuNDE0YzUuMzMsNS4zMjgsMTEuODAxLDcuOTksMTkuNDE0LDcuOTloNDIwLjI2NiAgICBjNy42MSwwLDE0LjA4Ni0yLjY2MiwxOS40MS03Ljk5YzUuMzMyLTUuMzI5LDcuOTk0LTExLjgsNy45OTQtMTkuNDE0di05MS4zNThDNDc1LjA3OCwzMzAuNDI3LDQ3Mi40MTYsMzIzLjk1NSw0NjcuMDgzLDMxOC42Mjd6ICAgICBNMzYwLjAyNSw0MTQuODQxYy0zLjYyMSwzLjYxNy03LjkwNSw1LjQyNC0xMi44NTQsNS40MjRzLTkuMjI3LTEuODA3LTEyLjg0Ny01LjQyNGMtMy42MTQtMy42MTctNS40MjEtNy44OTgtNS40MjEtMTIuODQ0ICAgIGMwLTQuOTQ4LDEuODA3LTkuMjM2LDUuNDIxLTEyLjg0N2MzLjYyLTMuNjIsNy44OTgtNS40MzEsMTIuODQ3LTUuNDMxczkuMjMyLDEuODExLDEyLjg1NCw1LjQzMSAgICBjMy42MTMsMy42MSw1LjQyMSw3Ljg5OCw1LjQyMSwxMi44NDdDMzY1LjQ0Niw0MDYuOTQyLDM2My42MzgsNDExLjIyNCwzNjAuMDI1LDQxNC44NDF6IE00MzMuMTA5LDQxNC44NDEgICAgYy0zLjYxNCwzLjYxNy03Ljg5OCw1LjQyNC0xMi44NDgsNS40MjRjLTQuOTQ4LDAtOS4yMjktMS44MDctMTIuODQ3LTUuNDI0Yy0zLjYxMy0zLjYxNy01LjQyLTcuODk4LTUuNDItMTIuODQ0ICAgIGMwLTQuOTQ4LDEuODA3LTkuMjM2LDUuNDItMTIuODQ3YzMuNjE3LTMuNjIsNy44OTgtNS40MzEsMTIuODQ3LTUuNDMxYzQuOTQ5LDAsOS4yMzMsMS44MTEsMTIuODQ4LDUuNDMxICAgIGMzLjYxNywzLjYxLDUuNDI3LDcuODk4LDUuNDI3LDEyLjg0N0M0MzguNTM2LDQwNi45NDIsNDM2LjcyOSw0MTEuMjI0LDQzMy4xMDksNDE0Ljg0MXoiIGZpbGw9IiNGRkZGRkYiLz4KCQk8cGF0aCBkPSJNMjI0LjY5MiwzMjMuNDc5YzMuNDI4LDMuNjEzLDcuNzEsNS40MjEsMTIuODQ3LDUuNDIxYzUuMTQxLDAsOS40MTgtMS44MDgsMTIuODQ3LTUuNDIxbDEyNy45MDctMTI3LjkwOCAgICBjNS44OTktNS41MTksNy4yMzQtMTIuMTgyLDMuOTk3LTE5Ljk4NmMtMy4yMy03LjQyMS04Ljg0Ny0xMS4xMzItMTYuODQ0LTExLjEzNmgtNzMuMDkxVjM2LjU0M2MwLTQuOTQ4LTEuODExLTkuMjMxLTUuNDIxLTEyLjg0NyAgICBjLTMuNjItMy42MTctNy45MDEtNS40MjYtMTIuODQ3LTUuNDI2aC03My4wOTZjLTQuOTQ2LDAtOS4yMjksMS44MDktMTIuODQ3LDUuNDI2Yy0zLjYxNSwzLjYxNi01LjQyNCw3Ljg5OC01LjQyNCwxMi44NDdWMTY0LjQ1ICAgIGgtNzMuMDg5Yy03Ljk5OCwwLTEzLjYxLDMuNzE1LTE2Ljg0NiwxMS4xMzZjLTMuMjM0LDcuODAxLTEuOTAzLDE0LjQ2NywzLjk5OSwxOS45ODZMMjI0LjY5MiwzMjMuNDc5eiIgZmlsbD0iI0ZGRkZGRiIvPgoJPC9nPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPgo=" />'; // append hyperlink to button button.appendChild( hyperlink ); // listen to click event button.addEventListener('click', downloadVideoNatively, false); // return DOM object return button; } function downloadVideoNatively(e) { if ($.inArray(GM_info.downloadMode, ["native","browser"]) === -1){ return ; } var elem=e.currentTarget; e.returnValue=false; if (e.preventDefault) { e.preventDefault(); } var link=$(elem).attr('href'); var name=$(elem).attr('download'); name = name.replace(/[\\<>:"\/|?*]*/g, ""); if (link && name) { if (typeof GM_download !== 'undefined') { if (GM_info.downloadMode==="native"){ open_download_dialog(); if (is_downloading===false){ dl_handle=GM_download({ url: link, name: name, onerror: gm_dl_error, onload: gm_dl_load, onprogress: gm_dl_progress, ontimeout: gm_dl_timeout }); dl_handle.my_filename=name; is_downloading=true; } } else { GM_download(link, name); // browser handles } debugLog("Downloading should start now.. File Name = "+name+" ; File URL = "+link); } else { GM_openInTab(link); debugLog("Download link should be opened in new tab now.. File Name = "+name+" ; File URL = "+link); } } return false; } function open_download_dialog() { if ($('#' + DIALOG_ID).length > 0 && $('#' + DIALOG_ID).dialog('isOpen')) { $('#' + DIALOG_ID).dialog('close').remove(); } var dialogButtons = [{ text: "Cancel Download", click: cancel_download }]; addStyle_external('https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.css', true, true); addGlobalStyle("#progressbar"+RANDOM+"{margin-top: 20px;}.progress-label"+RANDOM+"{font-weight: bold; text-shadow: 1px 1px 0 #fff;}", true,'gm_added_style_vimeodl'); download_div = $("<div title='File Download' id='" + DIALOG_ID + "'></div>").dialog({ resizable: false, closeText: "Continue in background", buttons: dialogButtons, close: function(event, ui) { $(this).dialog('destroy').remove(); } }); $("button.ui-dialog-titlebar-close").tooltip(); $('#' + DIALOG_ID).append('<div class="progress-label'+RANDOM+'">Starting download...</div>' +'<div id="extrainfo'+RANDOM+'"></div>' +'<div id="progressbar'+RANDOM+'"></div>'); /* $("#master" + RANDOM).slider({ value: volume, create: function() { $("#handle" + RANDOM).text(' = %' + $(this).slider("value")); }, slide: function(event, ui) { $("#handle" + RANDOM).text(' = %' + ui.value); SetVolume(ui.value); } });*/ progressbar = $( "#progressbar"+RANDOM ); progressLabel = $( ".progress-label"+RANDOM ); progressbar.progressbar({ value: false, change: function() { progressLabel.text("Current Progress: " + progressbar.progressbar("value") + "%"); }, complete: function() { progressLabel.text("Complete!"); change_button_to_close(); } }); } function gm_dl_progress(response) { if ($('#' + DIALOG_ID).length === 0 || !$('#' + DIALOG_ID).dialog('isOpen')) { return; } if (response.lengthComputable===false){ return; } //console.log(response); var fs_html="<p>File size: "+filesize(response.total)+"</p>"; if (typeof dl_handle.my_filename === "string"){ fs_html+="<p>File name: "+dl_handle.my_filename+"</p>"; } if ($("#extrainfo"+RANDOM).html()!==fs_html){ $("#extrainfo"+RANDOM).html(fs_html); } var val = Math.floor(response.done/response.total*100); progressbar.progressbar("value", val); } function gm_dl_load(response) { is_downloading=false; if ($('#' + DIALOG_ID).length === 0 || !$('#' + DIALOG_ID).dialog('isOpen')) { return; } progressbar.progressbar( "value", 100 ); change_button_to_close(); } function gm_dl_timeout(response) { is_downloading=false; if ($('#' + DIALOG_ID).length === 0 || !$('#' + DIALOG_ID).dialog('isOpen')) { return; } progressbar.progressbar( "value", false ); progressLabel.text( "Download time-out!" ); change_button_to_close(); } function gm_dl_error(response) { is_downloading=false; if ($('#' + DIALOG_ID).length === 0 || !$('#' + DIALOG_ID).dialog('isOpen')) { return; } progressbar.progressbar( "value", false ); progressLabel.text( "Download error!" ); change_button_to_close(); } function change_button_to_close() { download_div.dialog("option", "buttons", [{ text: "Close", click: function(event, ui) { $(this).dialog('close'); } }]); download_div.dialog( "option", "closeText", "Close" ); } function cancel_download() { is_downloading=false; dl_handle.abort(); if ($('#' + DIALOG_ID).length === 0 || !$('#' + DIALOG_ID).dialog('isOpen')) { return; } download_div.dialog("close"); } function vimeoRegex () { var regex = /(http|https)?:\/\/(www\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|)(\d+)(?:|\/\?)/i; return regex; } function are_we_on_video_page(){ var url = window.location.href; return vimeoRegex().test(url); } function get_vimeo_id(url){ if (are_we_on_video_page()===false){ return null; } //console.log(vimeoRegex().exec(url)); // for debug. var id_match=vimeoRegex().exec(url); return id_match[4]; } function xhr_video_info() { var url = window.location.href; var video_id = get_vimeo_id(url); if (typeof video_id === "undefined" || video_id === null){ debugLog("DEBUG: xhr_video_info() couldnt find video id"); return; } GM_xmlhttpRequest({ method: "GET", url: "https://player.vimeo.com/video/"+video_id+"/config", onerror: function(oEvent){ console.log("Error " + oEvent.status + " occurred while receiving the document."); }, onload: function(response){ if (response.readyState !== 4 || response.status !== 200) return; setup(JSON.parse(response.responseText)); } }); } // start looking for video player setup(); // f*cking vimeo does not update window.vimeo object. $(window).bind('popstate', function() { debugLog("DEBUG: popstate event: calling xhr_video_info()"); xhr_video_info(); }); var window_history_pushState = window.history.pushState; window.history.pushState = function () { window_history_pushState.apply(window.history, arguments); debugLog("DEBUG: pushstate event: calling xhr_video_info()"); xhr_video_info(); } })();