Download YouTube Videos


SUBMITTED BY: AnD888

DATE: Oct. 7, 2016, 6:01 p.m.

FORMAT: Text only

SIZE: 11.1 kB

HITS: 1448

  1. // ==UserScript==
  2. // @name Download YouTube Videos
  3. // @namespace http://userscripts.org/users/90251
  4. // @description Adds a download menu to YouTube videos.
  5. // @include http://*.youtube.com/*
  6. // @include http://youtube.com/*
  7. // @include https://*.youtube.com/*
  8. // @include https://youtube.com/*
  9. // ==/UserScript==
  10. ((function (code) {
  11. "use strict";
  12. // run script inside of the document so it can access JavaScript objects created by YouTube
  13. var s = document.createElement("script");
  14. s.appendChild(document.createTextNode("("+code.toString()+")();"));
  15. document.head.appendChild(s);
  16. })(function () {
  17. "use strict";
  18. function clickButton (event) {
  19. var els = this.parentNode.getElementsByClassName("yt-uix-button-toggled");
  20. for (var i = 0; i < els.length; ++ i) {
  21. els[i].classList.remove("yt-uix-button-toggled");
  22. }
  23. this.classList.add("yt-uix-button-toggled");
  24. event.preventDefault();
  25. }
  26. function createMenu (videos) {
  27. var lists = [];
  28. var types = {};
  29. for (var i = 0; i < videos.videos.length; ++ i) {
  30. var video = videos.videos[i];
  31. var res = video.size ? video.size.height+'p' : 'Unknown Size';
  32. var type = video.type.split(';')[0].split('/')[1].replace(/^x-/,'');
  33. var url = video.url;
  34. if (video.sig) url += "&signature="+video.sig;
  35. var item = [video.size ? video.size.height : 0, res, url];
  36. if (type in types) {
  37. types[type].push(item);
  38. }
  39. else {
  40. lists.push([type, (types[type] = [item])]);
  41. }
  42. }
  43. lists.sort(function (lhs,rhs) {
  44. lhs = lhs[0];
  45. rhs = rhs[0];
  46. return lhs < rhs ? -1 : (lhs > rhs ? 1 : 0);
  47. });
  48. var filename = (videos.title + (videos.user ? " by " + videos.user : "")).
  49. replace(/["\?]/g,'').
  50. replace(/[:;<>\*\|\/\\]/g,' ').
  51. replace(/\s\s+/g,' ').
  52. replace(/^\s+/,'').
  53. replace(/\s+$/,'')+".";
  54. var menu = document.createElement('div');
  55. menu.id = "action-panel-download";
  56. menu.className = "action-panel-content hid";
  57. menu.setAttribute("data-panel-loaded","true");
  58. menu.style.lineHeight = "1.5";
  59. for (var i = 0; i < lists.length; ++ i) {
  60. var type = lists[i];
  61. var downloads = type[1];
  62. var div = document.createElement('div');
  63. var ul = document.createElement('ul');
  64. var title = document.createElement('strong');
  65. type = type[0];
  66. title.appendChild(document.createTextNode(type));
  67. ul.style.textAlign = "right";
  68. ul.style.listStyle = "none";
  69. ul.style.margin = "0";
  70. ul.style.padding = "0";
  71. div.style.marginRight = "20px";
  72. div.style.display = "inline-block";
  73. div.style.verticalAlign = "top";
  74. downloads.sort(function (lhs,rhs) { return lhs[0] - rhs[0]; });
  75. for (var j = 0; j < downloads.length; ++ j) {
  76. var download = downloads[j];
  77. var li = document.createElement('li');
  78. var a = document.createElement('a');
  79. a.href = download[2];
  80. a.setAttribute("download", filename+type);
  81. a.appendChild(document.createTextNode(download[1]));
  82. li.appendChild(a);
  83. ul.appendChild(li);
  84. }
  85. div.appendChild(title);
  86. div.appendChild(ul);
  87. menu.appendChild(div);
  88. }
  89. return menu;
  90. }
  91. function createMenuButton () {
  92. var btn = document.createElement('button');
  93. var span = document.createElement('span');
  94. btn.type = 'button';
  95. span.className = 'yt-uix-button-content';
  96. span.appendChild(document.createTextNode('Download'));
  97. btn.appendChild(span);
  98. btn.className = 'action-panel-trigger yt-uix-button yt-uix-button-text yt-uix-button-download';
  99. btn.addEventListener('click', clickButton, false);
  100. btn.setAttribute('data-trigger-for','action-panel-download');
  101. btn.setAttribute('role','button');
  102. return btn;
  103. }
  104. function createSidebar (videos) {
  105. var toolbar = document.getElementById("docs-toolbar-wrapper");
  106. var sidebar = document.createElement('div');
  107. var hdr = document.createElement('div');
  108. var close = document.createElement('div');
  109. var icon = document.createElement('div');
  110. var img = document.createElement('div');
  111. var details = document.createElement('div');
  112. var menu = createMenu(videos);
  113. sidebar.id = 'docs-details-sidebar';
  114. sidebar.style.top = toolbar ? (toolbar.offsetTop + toolbar.offsetParent.offsetTop)+'px' : '0px';
  115. sidebar.style.bottom = '0px';
  116. hdr.id = 'docs-details-sidebar-header';
  117. close.id = 'docs-details-sidebar-close';
  118. close.tabindex = '0';
  119. close.title = 'Close';
  120. close.setAttribute('onclick', 'this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode);');
  121. icon.className = 'docs-icon goog-inline-block ';
  122. img.className = 'docs-icon-img-container docs-icon-img docs-icon-close-white';
  123. details.className = 'docs-details-sidebar-details jfk-sidebar';
  124. menu.style.marginTop = '10px';
  125. menu.style.textAlign = 'center';
  126. img.appendChild(document.createTextNode('\u00A0'));
  127. icon.appendChild(img);
  128. close.appendChild(icon);
  129. hdr.appendChild(document.createTextNode('Download'));
  130. hdr.appendChild(close);
  131. sidebar.appendChild(hdr);
  132. details.appendChild(menu);
  133. sidebar.appendChild(details);
  134. return sidebar;
  135. }
  136. function insertForm (videos) {
  137. var actions = document.getElementById("watch7-secondary-actions");
  138. if (!actions) {
  139. var sidebar = document.getElementById("docs-details-sidebar");
  140. if (sidebar) {
  141. sidebar.parentNode.removeChild(sidebar);
  142. }
  143. document.body.appendChild(createSidebar(videos));
  144. return;
  145. }
  146. var after = document.getElementById("watch-actions-share");
  147. if (!after || after.parentNode != actions) {
  148. if (actions.getElementsByClassName) {
  149. after = actions.getElementsByClassName("clear");
  150. after = after[after.length-1];
  151. }
  152. }
  153. if (!after) {
  154. after = actions.lastElementChild;
  155. }
  156. var menu = createMenu(videos);
  157. var btn = createMenuButton();
  158. menu.style.display = 'none';
  159. if (after) {
  160. actions.insertBefore(btn, after);
  161. }
  162. else {
  163. actions.appendChild(btn);
  164. }
  165. document.getElementById('watch7-action-panels').appendChild(menu);
  166. }
  167. function parseVars (vars) {
  168. vars = vars.split("&");
  169. var map = {};
  170. for (var i = 0; i < vars.length; ++ i) {
  171. var v = vars[i];
  172. var j = v.search("=");
  173. if (j < 0) j = v.length;
  174. var key = decodeURIComponent(v.slice(0,j).replace(/\+/g, ' '));
  175. var value = decodeURIComponent(v.slice(j+1).replace(/\+/g, ' ') || "");
  176. if (key in map) {
  177. var old = map[key];
  178. if (typeof(old) === "string") {
  179. map[key] = [old, value];
  180. }
  181. else {
  182. old.push(value);
  183. }
  184. }
  185. else {
  186. map[key] = value;
  187. }
  188. }
  189. return map;
  190. }
  191. function findJSConfig () {
  192. if (window.ytplayer && window.ytplayer.config) {
  193. return window.ytplayer.config.args;
  194. }
  195. if (window.yt && window.yt.playerConfig) {
  196. return window.yt.playerConfig.args;
  197. }
  198. var scripts = document.getElementsByTagName("script");
  199. for (var i = scripts.length - 1; i >= 0; -- i) {
  200. var script = scripts[i];
  201. var code = script.textContent || script.text;
  202. var m = /;\s*ytplayer\.config\s*=\s*(.*);\(function\(\).*\(\)\);\s*$/m.exec(code) || /\byt\.playerConfig\s*=\s*(.*);\s*$/m.exec(code);
  203. if (m) {
  204. try {
  205. return JSON.parse(m[1]).args;
  206. }
  207. catch (e) {}
  208. }
  209. }
  210. return null;
  211. }
  212. function findFlashConfig () {
  213. var movie_player = document.getElementById("movie_player") || document.getElementById("vpl1");
  214. if (!movie_player) {
  215. return null;
  216. }
  217. var flashvars;
  218. if (movie_player.nodeName === 'OBJECT') {
  219. flashvars = movie_player.querySelector('param[name=flashvars]');
  220. if (!flashvars) {
  221. return null;
  222. }
  223. flashvars = flashvars.getAttribute('value');
  224. }
  225. else {
  226. flashvars = movie_player.getAttribute('flashvars');
  227. }
  228. if (!flashvars) {
  229. return null;
  230. }
  231. return parseVars(flashvars);
  232. }
  233. function parseFmtStreamMap (map, sizes) {
  234. var videos = [];
  235. map = map.split(',');
  236. for (var i = 0; i < map.length; ++ i) {
  237. var video = parseVars(map[i]);
  238. var size = video.size;
  239. if (size) {
  240. size = size.split('x');
  241. video.size = {
  242. width: parseInt(size[0], 10),
  243. height: parseInt(size[1], 10)
  244. };
  245. }
  246. else {
  247. video.size = sizes[video.itag];
  248. }
  249. videos.push(video);
  250. }
  251. return videos;
  252. }
  253. function getVideos () {
  254. var config = findFlashConfig();
  255. if (!config) {
  256. config = findJSConfig();
  257. }
  258. if (!config || !config.url_encoded_fmt_stream_map) {
  259. return null;
  260. }
  261. var fmt_list = config.fmt_list ? config.fmt_list.split(',') : [];
  262. var sizes = {};
  263. for (var i = 0; i < fmt_list.length; ++ i) {
  264. var v = fmt_list[i].split('/');
  265. var size = v[1].split('x');
  266. sizes[v[0]] = {
  267. width: parseInt(size[0], 10),
  268. height: parseInt(size[1], 10)
  269. };
  270. }
  271. var videos = parseFmtStreamMap(config.url_encoded_fmt_stream_map, sizes);
  272. // these aren't really files but streams in some obscure binary format that separates audio and video
  273. // if (config.adaptive_fmts) {
  274. // videos.push.apply(videos, parseFmtStreamMap(config.adaptive_fmts, sizes));
  275. // }
  276. var title = config.title;
  277. if (!title) {
  278. title = document.querySelector("*[itemscope] *[itemprop='name'], head meta[name='title']");
  279. if (title) {
  280. title = title.getAttribute("content");
  281. }
  282. else {
  283. title = config.video_id;
  284. }
  285. }
  286. var user = document.querySelector("a[rel='author']");
  287. if (user) {
  288. user = (user.getAttribute("title") || user.textContent || user.text || '').replace(/^\s+/,'').replace(/\s+$/,'') || null;
  289. }
  290. if (!user) {
  291. user = document.querySelector("*[itemscope][itemprop='author'] link[itemprop='url']");
  292. if (user) {
  293. user = user.getAttribute("href").split("/").pop();
  294. }
  295. }
  296. return {title: title, user: user, videos: videos};
  297. }
  298. var retry_count = 0;
  299. function retryLater () {
  300. setTimeout(function () {
  301. var videos = getVideos();
  302. if (videos) {
  303. insertForm(videos);
  304. }
  305. else if (retry_count < 5) {
  306. ++ retry_count;
  307. retryLater();
  308. }
  309. }, 1000);
  310. }
  311. var videos = getVideos();
  312. if (videos) {
  313. insertForm(videos);
  314. handleDynamicUpdates();
  315. }
  316. else if (!document.body) {
  317. window.addEventListener("load",function () {
  318. var videos = getVideos();
  319. if (videos) {
  320. insertForm(videos);
  321. }
  322. else {
  323. retryLater();
  324. }
  325. handleDynamicUpdates();
  326. },false);
  327. }
  328. else {
  329. retryLater();
  330. handleDynamicUpdates();
  331. }
  332. function handleDynamicUpdates () {
  333. var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  334. if (!MutationObserver) return;
  335. var observer = new MutationObserver(function (mutations) {
  336. for (var i = 0; i < mutations.length; ++ i) {
  337. var mutation = mutations[i];
  338. if (mutation.type === "childList") {
  339. for (var j = 0; j < mutation.addedNodes.length; ++ j) {
  340. var node = mutation.addedNodes[j];
  341. if (node.nodeType === 1 && (node.id === "watch7-container" || node.querySelector("#watch7-container"))) {
  342. var videos = getVideos();
  343. if (videos) {
  344. insertForm(videos);
  345. }
  346. }
  347. }
  348. }
  349. }
  350. });
  351. observer.observe(document.body, {childList: true, subtree: true});
  352. }
  353. }));

comments powered by Disqus