// ==UserScript== // @name YouTube Video Download // @namespace sooaweso.me // @description Download videos from YouTube. Simple, lightweight and supports all formats, including WebM. // @version 4.1.2 // @author rossy // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACC0lEQVR4Xu3TT2sTURQF8LtR88eMTgU/gOA3UNwoaLtw6yJYcaeCC3Er7hT3VlGhCMaC4CIoFAKKtqJYQ8XguOgEk0WCk8AsMsUsEmigSeF4Lg9KCMTORGm6yIXfu8Od+w6BJDKpiT2jMJW8SR/JIVUn/Cf1vtxFOi399Y1Dwi7LitbXqWSaMCZpydvJLGFMsrJiJwqEMSnIJzvhE8bElw92YosQVq/XQ1d1u+h1TTfMnDtRbMn7wwlE0el0BmxoNzY6iJonb3lE0W610Wq32Vumt1vKzDmLmic5HlE0m80hfmtH1DxZ5DGUncTn9AWsXEpvC4IGgkaARtAg0wP2wPTtvS+XL+pdzRieT/L6UBzDvLJiyJ6fge/7I+FdzdCsoSTL428epw7g7sw5eLUaPE95qNVUzfAMzviOzAx3eEfv7pQvL3ns5AGDbp06iZ9ra6hWKqgMqppeJe5w94TeQZhsecEjjIepGG4cP4bV5SWUS2WUy6qEknYDq0vLuqO7CJsrC1Z8kxDGEwZfP3oEuecZuEUXrltE0XUVcgsZfac7uhvWpjyz4nVCWPP6IeL7cP/aVfxwvsNxHD5f0Zm+050ofslTK54nRHU7wd/F2TNKn3U2irzMW7EMYRT3Du5XOhtVRvidTRPGZFq0HqVic4RdNif9xb/OLL0hp8864R+tD2S+o1nZKzUxqT/sC28zrWX8pAAAAABJRU5ErkJggg== // @license MIT License // @grant none // @updateURL https://github.com/rossy2401/youtube-video-download/raw/master/youtube-video-download.user.js // @include http://www.youtube.com/watch?* // @include https://www.youtube.com/watch?* // @include http://*.c.youtube.com/videoplayback?* // ==/UserScript== /* * This file is a part of YouTube Video Download, which has been placed under * the MIT/Expat license. * * Copyright (c) 2012-2013, James Ross-Gowan and YouTube Video Download * contributors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ (function() { "use strict"; function inject(str) { var elem = document.createElement("script"); elem.setAttribute("type", "application/javascript"); elem.textContent = "(function() {\"use strict\"; (" + str + ")();})();"; document.body.appendChild(elem); } function formatSize(bytes) { if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KiB"; else return (bytes / 1048576).toFixed(1) + " MiB"; } document.addEventListener("ytd-update-link", function(event) { if (window.chrome) { var xhr = new XMLHttpRequest(); var data = JSON.parse(event.data); var set = false; xhr.open("HEAD", data.href, true); xhr.onreadystatechange = function(e) { if (xhr.readyState >= 2) { if (!set) { set = true; var length = xhr.getResponseHeader("Content-length"); var target = document.getElementById(data.target); target.setAttribute("title", target.getAttribute("title") + ", " + formatSize(Number(length))); } xhr.abort(); } }; xhr.send(null); } }, false); function script() { var version = "4.1.2", hash = "8e8a5e7"; // -- Object tools -- // has(obj, key) - Does the object contain the given key? var has = Function.call.bind(Object.prototype.hasOwnProperty); // extend(obj, a, b, c, ...) - Add the properties of other objects to this // object function extend(obj) { for (var i = 1, max = arguments.length; i < max; i ++) for (var key in arguments[i]) if (has(arguments[i], key)) obj[key] = arguments[i][key]; return obj; } // merge(a, b, c, ...) - Create an object with the merged properties of other // objects function merge() { return extend.bind(null, {}).apply(null, arguments); } var copy = merge; // -- Array tools -- // arrayify(a) - Turn an array-like object into an array var slice = Function.call.bind(Array.prototype.slice), arrayify = slice; // index(on, a) - Index an array of objects by a key function index(on, a) { var obj = {}; for (var i = 0, max = a.length; i < max; i ++) if (a[i].has(on)) obj[a[i][on]] = a[i]; return obj; } // pluck(on, a) - Return a list of property values function pluck(on, a) { return a.map(function(o) { return o[on]; }); } // indexpluck(on, a) - Index and pluck function indexpluck(key, value, a) { var obj = {}; for (var i = 0, max = a.length; i < max; i ++) if (a[i].has(key)) obj[a[i][key]] = a[i][value]; return obj; } // equi(on, a, b, c, ...) - Performs a equijoin on all the objects in the given // arrays function equi(on) { var obj = {}, ret = []; for (var i = 1, imax = arguments.length; i < imax; i ++) for (var j = 0, jmax = arguments[i].length; j < jmax; j ++) if (has(arguments[i][j], on)) obj[arguments[i][j][on]] = merge(obj[arguments[i][j][on]] || {}, arguments[i][j]) for (var prop in obj) if (has(obj, prop)) ret.push(obj[prop]); return ret; } // -- URI tools -- // decodeURIPlus(str) - Decode a URI component, including conversion from '+' // to ' ' function decodeURIPlus(str) { return decodeURIComponent(str.replace(/\+/g, " ")); } // encodeURIPlus(str) - Encode a URI component, including conversion from ' ' // to '+' function encodeURIPlus(str) { return encodeURIComponent(str).replace(/ /g, "%20"); } // decodeQuery(query) - Convert a query string to an object function decodeQuery(query) { var obj = {}; query.split("&").forEach(function(str) { var m = str.match(/^([^=]*)=(.*)$/); if (m) obj[decodeURIPlus(m[1])] = decodeURIPlus(m[2]); else obj[decodeURIPlus(str)] = ""; }); return obj; } // encodeQuery(query) - Convert a query string back into a URI function encodeQuery(query) { var components = []; for (var name in query) if (has(query, name)) components.push(encodeURIPlus(name) + "=" + encodeURIPlus(query[name])); return components.join("&"); } // URI(uri) - Convert a URI to a mutable object function URI(uri) { if (!(this instanceof URI)) return new URI(uri); var m = uri.match(/([^\/]+)\/\/([^\/]+)([^?]*)(?:\?(.+))?/); if (m) { this.protocol = m[1]; this.host = m[2]; this.pathname = m[3]; this.query = m[4] ? decodeQuery(m[4]) : {}; } else { this.href = uri; this.query = {}; } } URI.prototype.toString = function() { if (this.href) return this.href; var encq = this.query && encodeQuery(this.query); return (this.protocol || "http") + "//" + this.host + this.pathname + (encq ? "?" + encq : ""); }; // -- Function tools -- function identity(a) { return a; } // runWith - Run JavaScript code with an object's properties as local variables function runWith(str, obj) { var names = [], values = []; for (var name in obj) if (has(obj, name)) { names.push(name); values.push(obj[name]); } var func = Function.apply(null, names.concat(str)); return func.apply(null, values); } // -- String tools -- // format(str, obj) - Formats a string with a syntax similar to Python template // strings, except the identifiers are executed as JavaScript code. function format(str, obj) { return str.replace(/\${([^}]+)}/g, function(match, name) { try { return runWith("return (" + name + ");", obj); } catch (e) { return match; } }); } // trim(str) - Trims whitespace from the start and end of the string. function trim(str) { return str.replace(/^\s+/, "").replace(/\s+$/, ""); } // formatFileName(str) - Formats a file name (sans extension) to obey certain // restrictions on certain platforms. Makes room for a 4 character extension, // ie. ".webm". function formatFileName(str) { return str .replace(/[\\/<>:"\?\*\|]/g, "-") .replace(/[\x00-\x1f]/g, "-") .replace(/^\./g, "-") .replace(/^\s+/, "") .substr(0, 250); } // Try - Do things or do other things if they don't work var Try = (function() { var self = { all: all, }; function all() { var args = Array.prototype.slice(arguments), arg; for (var i = 0, imax = arguments.length; i < imax; i ++) if (arguments[i] instanceof Array) for (var j = 0, jmax = arguments[i].length; j < jmax; j ++) try { return arguments[i][j](); } catch (e) {} else try { return arguments[i](); } catch (e) {} } return self; })(); // VideoInfo - Get global video metadata var VideoInfo = (function() { var self = { init: init, }; // init() - Populates the VideoInfo object with video metadata function init() { self.title = Try.all( function() { return ytplayer.config.args.title; }, function() { return document.querySelector("meta[name=title]").getAttribute("content"); }, function() { return document.querySelector("meta[property=\"og:title\"]").getAttribute("content"); }, function() { return document.querySelector("#watch-headline-title > .yt-uix-expander-head").getAttribute("title"); }, function() { return document.title.match(/^(.*) - YouTube$/)[1]; } ); self.author = Try.all( function() { return document.querySelector("#watch7-user-header > .yt-user-name").textContent; }, function() { return document.querySelector("#watch7-user-header .yt-thumb-clip img").getAttribute("alt"); }, function() { return document.querySelector("span[itemprop=author] > link[itemprop=url]").getAttribute("href").match(/www.youtube.com\/user\/([^\/]+)/)[1]; } ); self.date = Try.all( function() { return new Date(document.getElementById("eow-date").textContent); } ); self.video_id = Try.all( function() { return new URI(document.location.href).query.v; } ); self.seconds = Try.all( function() { return Math.floor(Number(ytplayer.config.args.length_seconds)); } ); if (self.date) { self.day = ("0" + self.date.getDate()).match(/..$/)[0]; self.month = ("0" + (self.date.getMonth() + 1)).match(/..$/)[0]; self.year = self.date.getFullYear().toString(); self.date.toString = function() { return self.year + "-" + self.month + "-" + self.day; }; } if (self.seconds) self.duration = Math.floor(self.seconds / 60) + ":" + self.seconds % 60; } return self; })(); var Languages = { "ar": {"language": "Arabic","credit0-name": "Anas Abu-Haimed","credit0-url": "http://www.3thical.org","download-button-tip": "تحميل هذا الفيديو","download-button-text": "تحميل","menu-button-tip": "اختر الصيغة","format-tip": "الصيغة ","group-options": "الخيارات","group-high-definition": "جودة عالية","group-standard-definition": "جودة متوسطة","group-mobile": "للجوال","group-unknown": "صيغة غير معروفه","group-update": "يوجد تحديثات","option-check": "التأكد من وجود تحديثات","option-webm": "افضل WebM","option-sizes": "الحصول على حجم ملف الفيديو","option-format": "صيغة الفيديو","option-itags": "الصيغ المفضلة","button-options": "خيارات","button-options-close": "اغلاق","button-update": "اضغط هنا لتحديث YouTube Video Download","error-no-downloads": "لايوجد محتوى قابل للتحميل"}, "cs": {"language": "Czech","credit0-name": "janwatzek","credit0-url": "http://userscripts.org/users/janwatzek","download-button-tip": "Uložit video na pevný disk","download-button-text": "Stáhnout","menu-button-tip": "Vyberte formát ke stažení","group-options": "Nastavení","group-high-definition": "Vysoké rozlišení","group-standard-definition": "Standardní rozlišení","group-mobile": "Mobile","group-unknown": "Neznámý formát","group-update": "Nová verze skriptu YouTube Video Download je dostupná ke stažení!","option-check": "Kontrolovat aktualizace","option-format": "Formát názvu videa","button-options": "nastavení","button-options-close": "close","button-update": "Klikněte sem pro aktualizaci","error-no-downloads": "Žádné formáty nejsou dostupné ke stažení"}, "de": {"language": "German","credit0-name": "QuHno","credit0-url": "http://userscripts.org/users/348658","download-button-tip": "Video auf der Festplatte speichern","download-button-text": "Download","menu-button-tip": "Weitere Formate wählen","group-options": "Einstellungen","group-high-definition": "Hohe Auflösung","group-standard-definition": "Standard Auflösung","group-mobile": "Mobil","group-unknown": "Unbekanntes Format","group-update": "Eine neue Version steht zur Verfügung","option-check": "Auf neue Version überprüfen","option-webm": "WebM bevorzugen","option-sizes": "Video Dateigröße ermitteln","option-format": "Titel Format","button-options": "Einstellungen","button-options-close": "Schließen","button-update": "Hier klicken, um YouTube Video Download zu aktualisieren","error-no-downloads": "Keine herunterladbaren Streams gefunden"}, "en": {"language": "English","download-button-tip": "Download this video","download-button-text": "Download","menu-button-tip": "Choose from additional formats","format-tip": "Format ","group-options": "Options","group-high-definition": "High definition","group-standard-definition": "Standard definition","group-mobile": "Mobile","group-unknown": "Unknown formats","group-update": "An update is available","option-check": "Check for updates","option-webm": "Prefer WebM","option-sizes": "Get video filesize","option-format": "Title format","option-itags": "Favourite formats","button-options": "options","button-options-close": "close","button-update": "Click here to update YouTube Video Download","error-no-downloads": "No downloadable streams found"}, "fr": {"language": "French","credit0-name": "jok-r","credit0-url": "http://userscripts.org/users/87056","download-button-tip": "Télécharger cette vidéo","download-button-text": "Télécharger","menu-button-tip": "Choisissez le format à télécharger","group-options": "Options","group-high-definition": "Haute définition","group-standard-definition": "Définition standard","group-mobile": "Mobile","group-unknown": "Format inconnu","group-update": "Une nouvelle version de YouTube Video Download est disponible","option-check": "Vérifier les mises à jour","option-format": "Format du nom de fichier","button-options": "options","button-options-close": "close","button-update": "Cliquer ici pour mettre à jour maintenant","error-no-downloads": "Pas de formats de téléchargement disponible"}, "id": {"language": "Indonesian","credit0-name": "Bayu Aditya H","credit0-url": "http://ba.yuah.web.id/?asal=gmytdlfl","download-button-tip": "Unduh video ini","download-button-text": "Unduh","menu-button-tip": "Pilih dari format tambahan","format-tip": "Format ","group-options": "Opsi","group-high-definition": "Definisi tinggi","group-standard-definition": "Definisi standar","group-mobile": "Perangkat bergerak","group-unknown": "Format tak dikenal","group-update": "Perbaruan tersedia","option-check": "Periksa pembaruan","option-webm": "Utamakan WebM","option-sizes": "Dapatkan ukuran video","option-format": "Format judul","option-itags": "Format kesukaan","button-options": "opsi","button-options-close": "tutup","button-update": "Klik di sini untuk memperbarui YouTube Video Download","error-no-downloads": "Tidak ada aliran yang dapat diunduh ditemukan"}, "it": {"language": "Italian","credit0-name": "Kharg","credit0-url": "http://userscripts.org/users/kharg","download-button-tip": "Salva il video nell'HD","download-button-text": "Scarica","menu-button-tip": "Scegli un formato da scaricare","group-options": "Opzioni","group-high-definition": "Alta definizione","group-standard-definition": "Qualità standard","group-mobile": "Mobile","option-check": "Controlla la disponibilità di aggiornamenti","option-format": "Formato titolo","button-options": "opzioni","button-options-close": "close","error-no-downloads": "Nessun formato da scaricare disponibile"}, "ja-JP": {"language": "Japanese","credit0-name": "K-M","credit0-url": "http://userscripts.org/users/184613","download-button-tip": "ハードディスクにビデオを保存","download-button-text": "ダウンロード","menu-button-tip": "ダウンロードする形式を選択","group-options": "オプション","group-high-definition": "高画質","group-standard-definition": "普通の画質","group-mobile": "Mobile","group-unknown": "不明な形式","group-update": "YouTube Video Downloadの更新があります","option-check": "更新を確認","option-format": "タイトルの形式","button-options": "オプション","button-options-close": "close","button-update": "ここをクリックすると更新します","error-no-downloads": "ダウンロードできません"}, "ko": {"language": "Korean","credit0-name": "Joonhyuk Song","credit0-url": "http://blog.naver.com/fprhqkrtk303","download-button-tip": "이 비디오를 다운로드 합니다","download-button-text": "다운로드","menu-button-tip": "파일 형식 고르기","group-options": "설정","group-high-definition": "고화질 (HD)","group-standard-definition": "일반화질 (SD)","group-mobile": "휴대기기용","group-unknown": "알 수 없는 파일 형식","group-update": "업데이트 가능","option-check": "업데이트 확인","option-webm": "WebM 사용하기","option-restrict": "선호하는 파일 형식만 사용","option-sizes": "파일크기 보기","option-format": "제목 형식 설정","button-options": "설정","button-options-close": "닫기","button-update": "YouTube Video Download를 업데이트 하려면 여기를 누르세요","error-no-downloads": "받을 수 있는 스트림이 없습니다"}, "pl": {"language": "Polish","credit0-name": "look997","credit0-url": "http://userscripts.org/users/123591","download-button-tip": "Zapisz film na twardym dysku","download-button-text": "Pobierz","menu-button-tip": "Wybierz format do pobrania","group-options": "Opcje","group-high-definition": "Wysoka rozdzielczość","group-standard-definition": "Standardowa rozdzielczość","group-mobile": "Mobile","group-unknown": "Nieznany format","group-update": "Nowa wersja YouTube Video Download jest dostępna","option-check": "Sprawdzaj aktualizacje","option-format": "Format tytułu","button-options": "opcje","button-options-close": "close","button-update": "Kliknij tutaj, aby zaktualizować","error-no-downloads": "Nie dostępne formaty"}, "pt-BR": {"language": "Portuguese (Brazil)","credit0-name": "Gandalf","credit0-url": "http://userscripts.org/users/73303","download-button-tip": "Salvar vídeo","download-button-text": "Salvar","menu-button-tip": "Escolha outros formatos","group-options": "Opções","group-high-definition": "Definição alta","group-standard-definition": "Definição padrão","group-mobile": "Definição celular","group-unknown": "Formatos desconhecidos","group-update": "Nova versão disponível","option-check": "Procurar atualizações","option-webm": "WebM como formato padrão","option-restrict": "Mostrar apenas formato padrão","option-sizes": "Mostrar tamanho do arquivo","option-format": "Formato do Título","button-options": "opções","button-options-close": "fechar","button-update": "Clique aqui para atualizar YouTube Video Download","error-no-downloads": "Nenhum formato disponível para salvar"}, "ru": {"language": "Russian","credit0-name": "lmiol","credit0-url": "http://userscripts.org/users/121962","credit1-name": "Ареопагит","credit1-url": "http://userscripts.org/users/155252","download-button-tip": "Скачать видео","download-button-text": "Скачать","menu-button-tip": "Выбрать формат для загрузки","format-tip": "Формат ","group-options": "Настройки","group-high-definition": "Высокое разрешение","group-standard-definition": "Стандартное разрешение","group-mobile": "Для телефона","group-unknown": "Неизвестный формат","group-update": "Доступна новая версия YouTube Video Download","option-check": "Проверять обновления","option-webm": "Предпочитать WebM","option-sizes": "Получать размер видеофайла","option-format": "Шаблон имени файла:","option-itags": "Любимые форматы:","button-options": "Настройки","button-options-close": "Скрыть","button-update": "Нажмите здесь для обновления","error-no-downloads": "Нет доступных форматов для загрузки"}, "sr": {"language": "Serbian","credit0-name": "titanicus","credit0-url": "http://userscripts.org/users/26334","download-button-tip": "Преузми овај видео","download-button-text": "Преузми","menu-button-tip": "Изаберите остале доступне формате","group-options": "Опције","group-high-definition": "Висока резолуција","group-standard-definition": "Стандардна резолуција","group-mobile": "Мобилни","group-unknown": "Непознати формати","group-update": "Надоградња је доступна","option-check": "Провери надоградње","option-webm": "Преферирај WebM","option-sizes": "Добави величину фајла","option-format": "Формат наслова","button-options": "опције","button-options-close": "затвори","button-update": "Кликните овде да ажурирате YouTube Видео Преузимач","error-no-downloads": "Нема доступних формата"}, "sv": {"language": "Swedish","credit0-name": "eson","credit0-url": "http://userscripts.org/users/367569","download-button-tip": "Ladda ner den här videon","download-button-text": "Ladda ner","menu-button-tip": "Välj mellan olika format","group-options": "Alternativ","group-high-definition": "Högupplöst","group-standard-definition": "Standardupplösning","group-mobile": "Mobilt","group-unknown": "Okända format","group-update": "Det finns en uppdatering tillgänglig","option-check": "Sök efter uppdateringar","option-webm": "Prioritera WebM","option-sizes": "Visa filstorlek","option-format": "Titelformat","option-itags": "Favoritformat","button-options": "Alternativ","button-options-close": "Stäng","button-update": "Klicka här för att uppdatera YouTube Video Download","error-no-downloads": "Det går inte att hitta några nedladdningsbara strömmar"}, "tr": {"language": "Turkish","credit0-name": "Kenterte","credit0-url": "http://userscripts.org/users/Kenterte","download-button-tip": "Videoyu Farklı Kaydet","download-button-text": "İndir","menu-button-tip": "Bir İndirme Formatı Seçin","group-options": "Ayarlar","group-high-definition": "Yüksek Çözünürlük","group-standard-definition": "Standart Çözünürlük","group-mobile": "Mobile","group-unknown": "Bilinmeyen Format","group-update": "YouTube Video Downloader'ın yeni bir versiyonu var","option-check": "Güncelleştirmeleri Kontrol Et","option-format": "Başlık Türü","button-options": "Ayarlar","button-options-close": "close","button-update": "Güncelleştirmek için tıklayınız","error-no-downloads": "Format Bulunamadı"}, "zh-TW": {"language": "Chinese (Traditional)","credit0-name": "Wang Zheng","credit0-url": "http://userscripts.org/users/381783","download-button-tip": "儲存到硬碟","download-button-text": "下載","menu-button-tip": "選擇要下載的格式","group-options": "選項","group-high-definition": "HD","group-standard-definition": "標準畫質","group-mobile": "Mobile","group-unknown": "不明的格式","group-update": "有新的YouTube Video Download可供更新","option-check": "檢查更新","option-format": "標題格式","button-options": "選項","button-options-close": "close","button-update": "請點選此處更新","error-no-downloads": "沒有可用的下載格式"}, "zh": {"language": "Chinese (Simplified)","credit0-name": "Louiz","credit0-url": "http://userscripts.org/users/349372","download-button-tip": "保存到本地","download-button-text": "下载","menu-button-tip": "选择下载格式","group-options": "选项","group-high-definition": "标准分辨率","group-standard-definition": "较高分辨率","group-mobile": "Mobile","group-unknown": "未知格式","group-update": "已推出新版YouTube下载插件!","option-check": "检查更新","option-format": "标题格式","button-options": "选项","button-options-close": "close","button-update": "更新请点击这里","error-no-downloads": "下载格式不可用"}, }; function T(item) { return Languages.current[item] || Languages.en[item]; } Languages.current = (yt && yt.config_ && yt.config_.HL_LOCALE && Languages[yt.config_.HL_LOCALE]) || Languages[document.documentElement.getAttribute("lang")] || Languages.en; // StreamMap - Get and convert format maps var StreamMap = (function() { var self = { getStreams: getStreams, getURL: getURL, sortFunc: sortFunc, getExtension: getExtension, }; // Just in case the auto format detection code breaks, fall back on these // defaults for determining what is in the streams var defaultStreams = [ { itag: 5 , width: 320, height: 240, container: "FLV" , acodec:"MP3" , vcodec: "H.263" }, { itag: 17 , width: 176, height: 144, container: "3GPP", acodec:"AAC" , vcodec: "MPEG-4", vprofile: "Simple" }, { itag: 18 , width: 640, height: 360, container: "MP4" , acodec:"AAC" , vcodec: "H.264" , vprofile: "Baseline", level: 3.0 }, { itag: 22 , width: 1280, height: 720, container: "MP4" , acodec:"AAC" , vcodec: "H.264" , vprofile: "High" , level: 3.1 }, { itag: 34 , width: 640, height: 360, container: "FLV" , acodec:"AAC" , vcodec: "H.264" , vprofile: "Main" , level: 3.0 }, { itag: 35 , width: 854, height: 480, container: "FLV" , acodec:"AAC" , vcodec: "H.264" , vprofile: "Main" , level: 3.0 }, { itag: 36 , width: 320, height: 240, container: "3GPP", acodec:"AAC" , vcodec: "MPEG-4", vprofile: "Simple" }, { itag: 37 , width: 1920, height: 1080, container: "MP4" , acodec:"AAC" , vcodec: "H.264" , vprofile: "High" , level: 3.1 }, { itag: 38 , width: 2048, height: 1536, container: "MP4" , acodec:"AAC" , vcodec: "H.264" , vprofile: "High" , level: 3.1 }, { itag: 43 , width: 640, height: 360, container: "WebM", acodec:"Vorbis", vcodec: "VP8" }, { itag: 44 , width: 854, height: 480, container: "WebM", acodec:"Vorbis", vcodec: "VP8" }, { itag: 45 , width: 1280, height: 720, container: "WebM", acodec:"Vorbis", vcodec: "VP8" }, { itag: 46 , width: 1920, height: 1080, container: "WebM", acodec:"Vorbis", vcodec: "VP8" }, { itag: 82 , width: 640, height: 360, container: "MP4" , acodec:"AAC" , vcodec: "H.264" , vprofile: "Baseline", level: 3.0, stereo3d: true }, { itag: 83 , width: 854, height: 480, container: "MP4" , acodec:"AAC" , vcodec: "H.264" , vprofile: "Baseline", level: 3.1, stereo3d: true }, { itag: 84 , width: 1280, height: 720, container: "MP4" , acodec:"AAC" , vcodec: "H.264" , vprofile: "High", level: 3.1, stereo3d: true }, { itag: 85 , width: 1920, height: 1080, container: "MP4" , acodec:"AAC" , vcodec: "H.264" , vprofile: "High", level: 3.1, stereo3d: true }, { itag: 100, width: 640, height: 360, container: "WebM", acodec:"Vorbis", vcodec: "VP8" , stereo3d: true }, { itag: 101, width: 854, height: 480, container: "WebM", acodec:"Vorbis", vcodec: "VP8" , stereo3d: true }, { itag: 102, width: 1280, height: 720, container: "WebM", acodec:"Vorbis", vcodec: "VP8" , stereo3d: true }, ]; // Map containers to the order they sort in function containerToNum(container) { if (String(localStorage["ytd-prefer-webm"]) == "true") return { "WebM": 1, "MP4": 2, "FLV": 3, "3GPP": 4, }[container] || 5; else return { "MP4": 1, "FLV": 2, "WebM": 3, "3GPP": 4, }[container] || 5; } // sortFunc(a, b) - Sort streams from best to worst function sortFunc(a, b) { if (a.height && b.height && a.height != b.height) return b.height - a.height; if (a.stereo3d && !b.stereo3d) return 1; else if (!a.stereo3d && b.stereo3d) return -1; if (a.container && b.container && a.container != b.container) return containerToNum(a.container) - containerToNum(b.container); return (Number(b.itag) - Number(a.itag)) || 0; } // decodeType(type) - Decode the mime type of the video function decodeType(type) { var m = type.match(/^[^ ;]*/)[0], ret = { container: "Unknown" }; if (m == "video/mp4") { ret.container = "MP4"; ret.vcodec = "H.264"; ret.acodec = "AAC"; var m = type.match(/avc1\.(....)(..)/) if (m) { ret.level = parseInt(m[2], 16) / 10; if (m[1] == "58A0") ret.vprofile = "Extended"; else if (m[1] == "6400") ret.vprofile = "High"; else if (m[1] == "4D40") ret.vprofile = "Main"; else if (m[1] == "42E0") ret.vprofile = "Baseline"; else if (m[1] == "4200") ret.vprofile = "Baseline"; } } else if (m == "video/webm") { ret.container = "WebM"; ret.vcodec = "VP8"; ret.acodec = "Vorbis"; } else if (m == "video/x-flv") { ret.container = "FLV"; } else if (m == "video/3gpp") { ret.container = "3GPP"; ret.vcodec = "MPEG-4"; ret.acodec = "AAC"; } return ret; } // processStream(stream) - Add some format information to the stream function processStream(stream) { if (stream.type) { stream = merge(stream, decodeType(stream.type)); if (stream.container == "FLV") if (stream.flashMajor == 7) { stream.vcodec = "H.263"; stream.acodec = "MP3"; } else { stream.vcodec = "H.264"; stream.acodec = "AAC"; } } return stream; } // decodeFormat(format) - Decode an element of the fmt_list array function decodeFormat(format) { format = format.split("/"); var size = format[1].split("x"); return { itag: format[0], width: Number(size[0]), height: Number(size[1]), flashMajor: Number(format[2]), flashMinor: Number(format[3]), flashPatch: Number(format[4]), }; } // getFlashArgs() - Get the flashvars from the page function getFlashArgs() { return Try.all( function() { return ytplayer.config.args; }, function() { return decodeQuery(document.getElementById("movie_player").getAttribute("flashvars")); } ); } // getStreams() - Get the streams from the page function getStreams() { try { var flashArgs = getFlashArgs(), streams = equi("itag", defaultStreams, flashArgs.url_encoded_fmt_stream_map.split(",").map(decodeQuery)); try { streams = equi("itag", streams, flashArgs.fmt_list.split(",").map(decodeFormat)); } catch (e) {} } catch (e) {} return streams.map(processStream); } // getURL(stream) - Get a URL from a stream function getURL(stream, title) { if (stream.url) { var uri = new URI(stream.url); if (!uri.query.signature && stream.sig) uri.query.signature = stream.sig; if (title) uri.query.title = formatFileName(title); return uri.toString(); } } // getExtension(stream) - Get the file extension associated with the // container type of the specified stream function getExtension(stream) { return { "MP4": ".mp4", "WebM": ".webm", "3GPP": ".3gp", "FLV": ".flv", }[stream.container] || ""; } return self; })(); var Styles = (function() { var self = { injectStyle: injectStyle, }; // injectStyle(text) - Add a stylesheet to the page's head function injectStyle(text) { var style = document.createElement("style"); style.setAttribute("type", "text/css"); style.textContent = text; document.head.appendChild(style); } return self; })(); Styles["styles"] = "/* Download buttons */#watch7-sentiment-actions .yt-uix-button {margin-right: 2px;}#watch7-sentiment-actions > .yt-uix-button-group:last-child > button:last-child, #watch7-sentiment-actions > button:last-child {margin-right: 0px;}#watch7-secondary-actions .yt-uix-button {margin-left: 7px;}#watch7-secondary-actions > span:first-child > .yt-uix-button:first-child, #watch7-secondary-actions > .yt-uix-button:first-child {margin-left: 0px;}#watch7-sentiment-actions #ytd-dl-button {margin-right: -1px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;}#watch7-sentiment-actions #ytd-menu-button {border-top-left-radius: 0px;border-bottom-left-radius: 0px;}#watch7-sentiment-actions #ytd-menu-button .yt-uix-button-arrow {margin: 0px;}/* Menu */#ytd-menu {font-size: 12px;box-shadow: 0 3px 3px rgba(0, 0, 0, 0.1);max-height: 100%;overflow-x: hidden;}#ytd-options-button {position: absolute;right: 8px;top: 8px;}.ytd-header {padding: 2px 13px;font-weight: bold;padding-top: 5px;border-bottom: 1px solid #999;}/* Menu items */#ytd-menu .ytd-item-group {position: relative;}#ytd-menu .ytd-item-group:hover {background-color: #777;}#ytd-menu .ytd-item-group:hover .yt-uix-button-menu-item {color: #fff;}#ytd-menu .ytd-item-group .ytd-item-size {position: absolute;left: 0px;top: 0px;width: 55px;padding: 8px 0px;text-align: right;color: inherit;}#ytd-menu .ytd-item-group .ytd-item-main {display: block;padding: 8px 0px 8px 55px;}#ytd-menu .ytd-item-group .ytd-item-sub {display: block;position: absolute;top: 0px;width: 53px;border-left: 1px solid #ddd;padding: 8px 5px;}#ytd-menu .ytd-item-group:hover .ytd-item-sub {border-left: 1px solid #666;}#ytd-menu .ytd-item-update {padding: 8px 20px;}/* uix checkboxes */.ytd-checkbox-container {margin: 6px 6px 6px 13px;}.ytd-checkbox-label {display: block;padding-right: 13px;}.ytd-textbox-container {margin: 6px 13px;}/* uix textboxes */.ytd-textbox-container .yt-uix-form-input-text {display: block;box-sizing: border-box;-moz-box-sizing: border-box;width: 100%;}.ytd-textbox-label {display: block;padding: 3px 6px;}"; Styles["styles-rtl"] = "/* Download buttons */#watch7-sentiment-actions .yt-uix-button {margin-left: 2px;}#watch7-sentiment-actions > .yt-uix-button-group:last-child > button:last-child, #watch7-sentiment-actions > button:last-child {margin-left: 0px;}#watch7-secondary-actions .yt-uix-button {margin-right: 7px;}#watch7-secondary-actions > span:first-child > .yt-uix-button:first-child, #watch7-secondary-actions > .yt-uix-button:first-child {margin-right: 0px;}#watch7-sentiment-actions #ytd-dl-button {margin-left: -1px;border-top-left-radius: 0px;border-bottom-left-radius: 0px;}#watch7-sentiment-actions #ytd-menu-button {border-top-right-radius: 0px;border-bottom-right-radius: 0px;}#watch7-sentiment-actions #ytd-menu-button .yt-uix-button-arrow {margin: 0px;}/* Menu */#ytd-menu {font-size: 12px;box-shadow: 0 3px 3px rgba(0, 0, 0, 0.1);max-height: 100%;overflow-x: hidden;}#ytd-options-button {position: absolute;left: 8px;top: 8px;}.ytd-header {padding: 2px 13px;font-weight: bold;padding-top: 5px;border-bottom: 1px solid #999;}/* Menu items */#ytd-menu .ytd-item-group {position: relative;}#ytd-menu .ytd-item-group:hover {background-color: #777;}#ytd-menu .ytd-item-group:hover .yt-uix-button-menu-item {color: #fff;}#ytd-menu .ytd-item-group .ytd-item-size {position: absolute;right: 0px;top: 0px;width: 55px;padding: 8px 0px;text-align: left;color: inherit;}#ytd-menu .ytd-item-group .ytd-item-main {display: block;padding: 8px 55px 8px 0px;}#ytd-menu .ytd-item-group .ytd-item-sub {display: block;position: absolute;top: 0px;width: 53px;border-right: 1px solid #ddd;padding: 8px 5px;}#ytd-menu .ytd-item-group:hover .ytd-item-sub {border-right: 1px solid #666;}#ytd-menu .ytd-item-update {padding: 8px 20px;}/* uix checkboxes */.ytd-checkbox-container {margin: 6px 6px 6px 13px;}.ytd-checkbox-label {display: block;padding-right: 13px;}.ytd-textbox-container {margin: 6px 13px;}/* uix textboxes */.ytd-textbox-container .yt-uix-form-input-text {display: block;box-sizing: border-box;-moz-box-sizing: border-box;width: 100%;}.ytd-textbox-label {display: block;padding: 3px 6px;}"; // Interface - Handles the user interface for the watch page var Interface = (function() { var self = { init: init, update: update, notifyUpdate: notifyUpdate, }; var rtl = document.body.getAttribute("dir") == "rtl", groups, lastStreams, links = [], nextId = 0; // createOptionsButton() - Creates the button that opens the options menu function createOptionsButton() { var elem = document.createElement("a"), optionsOpen = false; elem.setAttribute("id", "ytd-options-button"); elem.setAttribute("href", "javascript:;"); elem.innerHTML = T("button-options"); elem.addEventListener("click", function() { optionsOpen = !optionsOpen; self.options.style.display = optionsOpen ? "" : "none"; elem.innerHTML = optionsOpen ? T("button-options-close") : T("button-options"); }); return elem } // createHeader(text) - Creates a menu section header function createHeader(text) { var elem = document.createElement("div"); elem.className = "ytd-header"; elem.appendChild(document.createTextNode(text)); return elem; } // createCheckbox(text) - Creates a YouTube uix checkbox function createCheckbox(labelText, checked, callback) { var label = document.createElement("label"), span = document.createElement("span"), checkbox = document.createElement("input"), elem = document.createElement("span"); label.className = "ytd-checkbox-label"; span.className = "ytd-checkbox-container yt-uix-form-input-checkbox-container" + (checked ? " checked" : ""); checkbox.className = "yt-uix-form-input-checkbox"; checkbox.setAttribute("type", "checkbox"); checkbox.checked = !!checked; checkbox.addEventListener("change", function() { callback(checkbox.checked); }, true); elem.className = "yt-uix-form-input-checkbox-element"; span.appendChild(checkbox); span.appendChild(elem); label.appendChild(span); label.appendChild(document.createTextNode(labelText)); return label; } // createTextbox(text) - Creates a YouTube uix textbox function createTextbox(labelText, text, ltr, callback) { var label = document.createElement("label"), container = document.createElement("div"), box = document.createElement("input"); container.className = "ytd-textbox-container"; box.className = "yt-uix-form-input-text"; box.value = text; if (rtl && ltr) box.setAttribute("dir", "ltr"); box.addEventListener("input", function() { callback(box.value); }); label.className = "ytd-textbox-label"; label.appendChild(document.createTextNode(labelText)); label.appendChild(document.createElement("br")); label.appendChild(container); container.appendChild(box); return label; } // createOptions() - Creates the options menu function createOptions() { var elem = document.createElement("div"); elem.setAttribute("id", "ytd-options"); elem.appendChild(createHeader(T("group-options"))); // Determine whether to check GitHub for updates every two days elem.appendChild(createCheckbox(T("option-check"), String(localStorage["ytd-check-updates"]) == "true", function (checked) { localStorage["ytd-check-updates"] = checked; })); // Prefer WebM over MP4 elem.appendChild(createCheckbox(T("option-webm"), String(localStorage["ytd-prefer-webm"]) == "true", function (checked) { localStorage["ytd-prefer-webm"] = checked; update(lastStreams); })); // Determine whether to get video file sizes (Chrome only) if (window.chrome) elem.appendChild(createCheckbox(T("option-sizes"), String(localStorage["ytd-get-sizes"]) == "true", function (checked) { localStorage["ytd-get-sizes"] = checked; })); // Title format elem.appendChild(createTextbox(T("option-format"), localStorage["ytd-title-format"], true, function (text) { localStorage["ytd-title-format"] = text; updateLinks(); })); // Favourite itags elem.appendChild(createTextbox(T("option-itags"), localStorage["ytd-itags"], false, function (text) { localStorage["ytd-itags"] = text.split(",").map(Number).filter(identity).map(Math.floor).join(", "); update(lastStreams); })); elem.style.display = "none"; return elem; } // createDlButton() - Creates the instant download button function createDlButton() { var link = document.createElement("a"), elem = document.createElement("button"); link.setAttribute("href", "javascript:;"); elem.className = "start yt-uix-button yt-uix-button-text yt-uix-tooltip"; elem.setAttribute("id", "ytd-dl-button"); elem.setAttribute("title", T("download-button-tip")); elem.setAttribute("type", "button"); elem.setAttribute("role", "button"); elem.innerHTML = "" + T("download-button-text") + ""; link.appendChild(elem); return link; } // createMenuButton() - Creates the download menu button function createMenuButton() { var elem = document.createElement("button"); elem.className = "end yt-uix-button yt-uix-button-text yt-uix-button-empty yt-uix-tooltip"; elem.setAttribute("id", "ytd-menu-button"); elem.setAttribute("title", T("menu-button-tip")); elem.setAttribute("type", "button"); elem.setAttribute("role", "button"); elem.setAttribute("onclick", "; return false;"); elem.innerHTML = "\"\""; return elem; } // createMenu() - Creates the downloads menu function createMenu() { var elem = document.createElement("div"); elem.className = "yt-uix-button-menu"; elem.setAttribute("id", "ytd-menu"); elem.style.display = "none"; return elem; } // formatTitle(stream) - Format stream information for the tooltips function formatTitle(stream) { return T("format-tip") + stream.itag + ", " + (stream.vcodec ? stream.vcodec + "/" + stream.acodec : "") + (stream.vprofile ? " (" + stream.vprofile + (stream.level ? "@L" + stream.level.toFixed(1) : "") + ")" : ""); } // updateLink(href, target) - Informs the privileged extension code that a // new link has been added function updateLink(href, target) { if (!window.chrome || String(localStorage["ytd-get-sizes"]) != "true") return; var data = { "href": href, target: target }; var event = document.createEvent("MessageEvent"); event.initMessageEvent("ytd-update-link", true, true, JSON.stringify(data), document.location.origin, "", window); document.dispatchEvent(event); } // createMenuItemGroup() - Creates a sub-group for a set of related streams function createMenuItemGroup(streams) { // Create the button group and the size label ("360p", "480p", etc.) var itemGroup = document.createElement("div"), size = document.createElement("div"), mainLink = document.createElement("a"), mainId = nextId ++; itemGroup.className = "ytd-item-group"; itemGroup.style.minWidth = streams.length * 64 + 48 + "px"; size.className = "ytd-item-size yt-uix-button-menu-item"; // Create the main video link mainLink.className = "ytd-item ytd-item-main yt-uix-button-menu-item"; mainLink.setAttribute("id", "ytd-" + mainId); mainLink.setAttribute("title", formatTitle(streams[0])); links.push({ stream: streams[0], anchor: mainLink }); updateLink(StreamMap.getURL(streams[0]), "ytd-" + mainId); if (rtl) mainLink.style.marginLeft = (streams.length - 1) * 64 + "px"; else mainLink.style.marginRight = (streams.length - 1) * 64 + "px"; mainLink.addEventListener("contextmenu", function(e) { // Prevent right-click closing the menu in Chrome e.stopPropagation(); }, false); // Append the main link to the button group size.appendChild(document.createTextNode(streams[0].height + "p\u00a0")); mainLink.appendChild(size); mainLink.appendChild(document.createTextNode((streams[0].stereo3d ? "3D " : "") + streams[0].container)); itemGroup.appendChild(mainLink); // Create each sublink for (var i = 1, max = streams.length; i < max; i ++) { var subLink = document.createElement("a"), subId = nextId ++; subLink.className = "ytd-item-sub yt-uix-button-menu-item"; subLink.setAttribute("id", "ytd-" + subId); subLink.setAttribute("title", formatTitle(streams[i])); if (streams[i].audio) Audio.updateLink(streams[i], subLink); else { links.push({ stream: streams[i], anchor: subLink }); updateLink(StreamMap.getURL(streams[i]), "ytd-" + subId); } if (rtl) subLink.style.left = (streams.length - i - 1) * 64 + "px"; else subLink.style.right = (streams.length - i - 1) * 64 + "px"; subLink.addEventListener("contextmenu", function(e) { // Prevent right-click closing the menu in Chrome e.stopPropagation(); }, false); // Append the sublink to the button group subLink.appendChild(document.createTextNode( (streams[i].audio ? streams[i].acodec : (streams[i].stereo3d ? "3D " : "") + streams[i].container) )); itemGroup.appendChild(subLink); } return itemGroup; } // createGroup(title, streams) - Creates a new menu group function createGroup(title, flat, streams) { var elem = document.createElement("div"); elem.appendChild(createHeader(title)); if (flat) for (var i = 0, max = streams.length; i < max; i ++) elem.appendChild(createMenuItemGroup([streams[i]])); else { var resolutions = [], resGroups = {}; for (var i = 0, max = streams.length; i < max; i ++) { if (!resGroups[streams[i].height]) { resolutions.push(streams[i].height); resGroups[streams[i].height] = []; } resGroups[streams[i].height].push(streams[i]); } for (var i = 0, max = resolutions.length; i < max; i ++) elem.appendChild(createMenuItemGroup(resGroups[resolutions[i]])); } return elem; } // createUpdate() - Creates the updates button function createUpdate() { var elem = document.createElement("div"); elem.appendChild(createHeader(T("group-update"))); var a = document.createElement("a"); a.className = "ytd-item-update yt-uix-button-menu-item"; a.setAttribute("href", "https://github.com/rossy2401/youtube-video-download/raw/master/youtube-video-download.user.js"); a.appendChild(document.createTextNode(T("button-update"))); elem.appendChild(a); return elem; } // setDlButton(stream) - Sets the default stream to download function setDlButton(stream) { self.dlButton.getElementsByTagName("button")[0] .setAttribute("title", T("download-button-tip") + " (" + stream.height + "p " + stream.container + ")"); links.push({ stream: stream, anchor: self.dlButton }); } // updateLinks() - Set the href and download attributes of all video // download links function updateLinks() { for (var i = 0, max = links.length; i < max; i ++) { var title = formatFileName(format(localStorage["ytd-title-format"], merge(links[i].stream, VideoInfo))); links[i].anchor.setAttribute("download", title + StreamMap.getExtension(links[i].stream)); links[i].anchor.setAttribute("href", StreamMap.getURL(links[i].stream, title)); } } // update(streams) - Adds streams to the menu function update(streams) { lastStreams = streams; streams = streams .filter(function(obj) { return obj.url; }) .sort(StreamMap.sortFunc); links = []; var favouriteItags = localStorage["ytd-itags"].split(",").map(Number); var favouriteStreams = streams .filter(function(obj) { return (obj.favouriteIndex = favouriteItags.indexOf(Number(obj.itag))) + 1; }) .sort(function(a, b) { return a.favouriteIndex - b.favouriteIndex; }); if (favouriteStreams.length) setDlButton(favouriteStreams[0]); else if (streams.length) setDlButton(streams[0]); else { var button = self.dlButton.getElementsByTagName("button")[0]; self.menuButton.disabled = true; self.menuButton.setAttribute("title", ""); button.setAttribute("title", T("error-no-downloads")); } self.downloads.innerHTML = ""; for (var i = 0, max = groups.length; i < max; i ++) { var groupStreams = streams.filter(groups[i].predicate); if (groupStreams.length) self.downloads.appendChild(createGroup(groups[i].title, groups[i].flat, groupStreams)); } updateLinks(); } // init() - Initalises the user interface function init() { // Get the flag button from the actions menu var buttonGroup = document.createElement("span"), watchSentimentActions = document.getElementById("watch7-sentiment-actions"), watchLike = document.getElementById("watch-like"), watchDislike = document.getElementById("watch-dislike"); // Inject stylesheet(s) if (rtl) Styles.injectStyle(Styles["styles-rtl"]); else Styles.injectStyle(Styles["styles"]); groups = [ { title: T("group-high-definition"), predicate: function(stream) { return stream.height && stream.container && stream.container != "3GPP" && stream.height > 576; } }, { title: T("group-standard-definition"), predicate: function(stream) { return stream.height && stream.container && stream.container != "3GPP" && stream.height <= 576; } }, { title: T("group-mobile"), predicate: function(stream) { return stream.height && stream.container && stream.container == "3GPP"; } }, { title: T("group-unknown"), flat: true, predicate: function(stream) { return !stream.height || !stream.container; } }, ]; buttonGroup.className = "yt-uix-button-group"; // Create the buttons self.dlButton = createDlButton(); self.menuButton = createMenuButton(); // Create the dropdown menu self.menu = createMenu(); self.menu.appendChild(createOptionsButton()); self.menu.appendChild(self.options = createOptions()); self.menu.appendChild(self.downloads = document.createElement("div")); self.menuButton.appendChild(self.menu); // Populate the button group buttonGroup.appendChild(self.dlButton); buttonGroup.appendChild(self.menuButton); if (watchLike) { // If the like button is disabled, all the controls should be // disabled self.dlButton.disabled = self.menuButton.disabled = watchLike.disabled; // Add a space between the Like and Dislike buttons to make them // consistent with the download button in Chrome watchDislike.parentNode.insertBefore(document.createTextNode(" "), watchDislike); } watchSentimentActions.appendChild(buttonGroup); } // notifyUpdate() - Notify the user of an available update function notifyUpdate() { self.menu.appendChild(createUpdate()); } return self; })(); // Update - Check GitHub for updates var Update = (function() { var self = { check: check, }; // apiRequest(path, callback) - Perform a JSON API request for path, // calling the callback(json, error) function on completion function apiRequest(path, callback) { var xhr = new XMLHttpRequest(); xhr.open("GET", path); xhr.onload = function() { var json; try { json = JSON.parse(xhr.responseText); } catch (e) { callback(null, true); } if (json) callback(json); }; xhr.onerror = function() { callback(null, true); }; xhr.send(); } // check() - Query GitHub for changes to // "youtube-video-download.user.js.sha1sum". If there is, inform the // Interface module. function check() { delete localStorage["ytd-update-sha1sum"]; delete localStorage["ytd-last-update"]; apiRequest("https://api.github.com/repos/rossy2401/youtube-video-download/git/refs/heads/master", function(json) { if (!json) return; apiRequest(json.object.url, function (json) { if (!json) return; apiRequest(json.tree.url, function (json) { if (!json) return; apiRequest(json.tree.filter(function(a) { return a.path == "youtube-video-download.user.js.sha1sum"; })[0].url, function (json) { if (!json) return; var sha1sum = atob(json.content.replace(/\n/g, "")); localStorage["ytd-update-sha1sum"] = sha1sum; localStorage["ytd-last-update"] = Date.now(); if (sha1sum.substr(0, 7) != hash) Interface.notifyUpdate(); }); }); }); }); } return self; })(); function main() { if (localStorage.getItem("ytd-check-updates") === null) localStorage["ytd-check-updates"] = true; if (localStorage.getItem("ytd-prefer-webm") === null) localStorage["ytd-prefer-webm"] = false; if (localStorage.getItem("ytd-get-sizes") === null) localStorage["ytd-get-sizes"] = false; if (localStorage.getItem("ytd-title-format") === null) localStorage["ytd-title-format"] = "${title}"; if (localStorage.getItem("ytd-itags") === null) localStorage["ytd-itags"] = "37, 22, 18"; VideoInfo.init(); Interface.init(); Interface.update(StreamMap.getStreams()); if ((String(localStorage["ytd-check-updates"]) == "true")) if (localStorage["ytd-current-sha1sum"] != hash || !localStorage["ytd-last-update"] || Number(localStorage["ytd-last-update"]) < Date.now() - 2 * 24 * 60 * 60 * 1000) Update.check(); else if (localStorage["ytd-update-sha1sum"] && localStorage["ytd-update-sha1sum"].substr(0, 7) != hash) Interface.notifyUpdate(); localStorage["ytd-current-sha1sum"] = hash; } main(); } inject(script); })();