Download YouTube Videos

By panzi Last update Dec 7, 2012 — Installed 74,047 times.

There are 15 previous versions of this script.

// ==UserScript==
// @name           Download YouTube Videos
// @namespace      http://userscripts.org/users/90251
// @description    Adds a download menu to YouTube videos.
// @include        http://*.youtube.com/*
// @include        http://youtube.com/*
// @include        https://*.youtube.com/*
// @include        https://youtube.com/*
// ==/UserScript==

(function () {
"use strict";

function clickButton (event) {
	var els = this.parentNode.getElementsByClassName("yt-uix-button-toggled");
	for (var i = 0; i < els.length; ++ i) {
		els[i].classList.remove("yt-uix-button-toggled");
	}
	this.classList.add("yt-uix-button-toggled");
	event.preventDefault();
}

function insertForm (videos) {
	var actions = document.getElementById("watch7-secondary-actions");
	if (!actions) return;
	var after = document.getElementById("watch-actions-share");

	if (!after || after.parentNode != actions) {
		if (actions.getElementsByClassName) {
			after = actions.getElementsByClassName("clear");
			after = after[after.length-1];
		}
	}

	if (!after) {
		after = actions.lastElementChild;
	}

	var lists = [];
	var types = {};
	for (var i = 0; i < videos.videos.length; ++ i) {
		var video = videos.videos[i];
		var res = video.size ? video.size.height+'p' : 'Unknown Size';
		var type = video.type.split(';')[0].split('/')[1].replace(/^x-/,'');
		var url = video.url;
		if (video.sig) url += "&signature="+video.sig;
		var item = [video.size ? video.size.height : 0, res, url];
		if (type in types) {
			types[type].push(item);
		}
		else {
			lists.push([type, (types[type] = [item])]);
		}
	}
	lists.sort(function (lhs,rhs) {
		lhs = lhs[0];
		rhs = rhs[0];
		return lhs < rhs ? -1 : (lhs > rhs ? 1 : 0);
	});

	var filename = (videos.title + (videos.user ? " by " + videos.user : "")).
		replace(/["\?]/g,'').
		replace(/[:;<>\*\|\/\\]/g,' ').
		replace(/\s\s+/g,' ').
		replace(/^\s+/,'').
		replace(/\s+$/,'')+".";

	var menu = document.createElement('div');
	menu.style.display = 'none';
	menu.id = "action-panel-download";
	menu.className = "action-panel-content hid";
	menu.setAttribute("data-panel-loaded","true");
	menu.style.lineHeight = "1.5";

	for (var i = 0; i < lists.length; ++ i) {
		var type = lists[i];
		var downloads = type[1];
		var div = document.createElement('div');
		var ul = document.createElement('ul');
		var title = document.createElement('strong');
		type = type[0];
		title.appendChild(document.createTextNode(type));
		ul.style.textAlign = "right";
		div.style.marginRight = "20px";
		div.style.display = "inline-block";
		div.style.verticalAlign = "top";
		downloads.sort(function (lhs,rhs) { return lhs[0] - rhs[0]; });

		for (var j = 0; j < downloads.length; ++ j) {
			var download = downloads[j];
			var li = document.createElement('li');
			var a = document.createElement('a');
			a.href = download[2];
			a.setAttribute("download", filename+type);
			a.appendChild(document.createTextNode(download[1]));
			li.appendChild(a);
			ul.appendChild(li);
		}

		div.appendChild(title);
		div.appendChild(ul);
		menu.appendChild(div);
	}
	
	var btn = document.createElement('button');
	var span = document.createElement('span');
	btn.type = 'button';
	span.className = 'yt-uix-button-content';
	span.appendChild(document.createTextNode('Download'));
	btn.appendChild(span);
	btn.className = 'action-panel-trigger yt-uix-button yt-uix-button-text yt-uix-button-download';
	btn.addEventListener('click', clickButton, false);
	btn.setAttribute('data-trigger-for','action-panel-download');
	btn.setAttribute('role','button');

	if (after) {
		actions.insertBefore(btn, after);
	}
	else {
		actions.appendChild(btn);
	}

	document.getElementById('watch7-action-panels').appendChild(menu);
}

function parseVars (vars) {
	vars = vars.split("&");
	var map = {};
	for (var i = 0; i < vars.length; ++ i) {
		var v = vars[i];
		var j = v.search("=");
		if (j < 0) j = v.length;
		var key = decodeURIComponent(v.slice(0,j).replace(/\+/g, ' '));
		var value = decodeURIComponent(v.slice(j+1).replace(/\+/g, ' ') || "");

		if (key in map) {
			var old = map[key];
			if (typeof(old) === "string") {
				map[key] = [old, value];
			}
			else {
				old.push(value);
			}
		}
		else {
			map[key] = value;
		}
	}
	return map;
}

function findJSConfig () {
	if (window.yt && window.yt.playerConfig) {
		return window.yt.playerConfig.args;
	}
	var scripts = document.getElementsByTagName("script");
	for (var i = 0; i < scripts.length; ++ i) {
		var script = scripts[i];
		var m = /^\s*yt\.playerConfig\s*=\s*(.*);\s*$/m.exec(script.textContent || script.text);
		if (m) {
			try {
				return JSON.parse(m[1]).args;
			}
			catch (e) {}
		}
	}

	return null;
}

function findFlashConfig () {
	var movie_player = document.getElementById("movie_player");
	if (!movie_player) {	
		return null;
	}

	var flashvars = movie_player.getAttribute('flashvars');
	if (!flashvars) {
		return null;
	}

	return parseVars(flashvars);
}

function getVideos () {
	var config = findFlashConfig();

	if (!config) {
		config = findJSConfig();
	}

	if (!config || !config.url_encoded_fmt_stream_map) {
		return null;
	}

	var fmt_stream_map = config.url_encoded_fmt_stream_map;
	var fmt_list = config.fmt_list ? config.fmt_list.split(',') : [];
	var sizes = {};
	for (var i = 0; i < fmt_list.length; ++ i) {
		var v = fmt_list[i].split('/');
		var size = v[1].split('x');
		sizes[v[0]] = {
			width:  parseInt(size[0], 10),
			height: parseInt(size[1], 10)
		};
	}

	fmt_stream_map = fmt_stream_map.split(',');
	var videos = [];
	for (var i = 0; i < fmt_stream_map.length; ++ i) {
		var video = parseVars(fmt_stream_map[i]);
		video.size = sizes[video.itag];
		videos.push(video);
	}

	var title = config.title;
	
	if (!title) {
		title = document.querySelector("*[itemscope] *[itemprop='name'], head meta[name='title']");
	
		if (title) {
			title = title.getAttribute("content");
		}
		else {
			title = config.video_id;
		}
	}
	
	var user = document.querySelector("a[rel='author']");

	if (user) {
		user = (user.getAttribute("title") || user.textContent || user.text || '').replace(/^\s+/,'').replace(/\s+$/,'') || null;
	}

	if (!user) {
		user = document.querySelector("*[itemscope][itemprop='author'] link[itemprop='url']");
		if (user) {
			user = user.getAttribute("href").split("/").pop();
		}
	}

	return {title: title, user: user, videos: videos};
}

var retry_count = 0;
function retryLater () {
	setTimeout(function () {
		var videos = getVideos();
		if (videos) {
			insertForm(videos);
		}
		else if (retry_count < 5) {
			++ retry_count;
			retryLater();
		}
	}, 1000);
}

var videos = getVideos();
if (videos) {
	insertForm(videos);
}
else if (!document.body) {
	window.addEventListener("load",function () {
		var videos = getVideos();
		if (videos) {
			insertForm(videos);
		}
		else {
			retryLater();
		}
	},false);
}
else {
	retryLater();
}

})();