├── preview └── .gitkeep ├── src ├── chrome.manifest ├── .jpmignore ├── data │ ├── shared │ │ └── connect.jsm │ ├── report │ │ ├── 16.png │ │ ├── 38.png │ │ ├── open.png │ │ ├── tabs.png │ │ ├── links.png │ │ ├── download.png │ │ ├── segoeui.woff │ │ ├── no-download.png │ │ ├── preferences.png │ │ ├── google-font.woff │ │ ├── report.js │ │ └── report.css │ ├── toolbar-16.png │ ├── toolbar-32.png │ ├── formats │ │ ├── fetch.png │ │ ├── font.woff │ │ ├── images.png │ │ ├── downloadm5.png │ │ ├── injected-button.png │ │ ├── permanent.js │ │ └── permanent.css │ ├── notification.png │ ├── info │ │ ├── jsoneditor │ │ │ ├── img │ │ │ │ └── jsoneditor-icons.png │ │ │ └── jsoneditor.css │ │ └── info.js │ ├── info.html │ ├── chrome │ │ └── content │ │ │ ├── options.css │ │ │ └── options.js │ ├── overlay.css │ └── report.html ├── icon.png ├── icon64.png ├── webextension │ ├── data │ │ ├── helper │ │ ├── icons │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ ├── 64.png │ │ │ ├── 128.png │ │ │ ├── 256.png │ │ │ └── 512.png │ │ ├── inject │ │ │ ├── panel │ │ │ │ ├── fetch.gif │ │ │ │ ├── font.woff │ │ │ │ ├── dot.svg │ │ │ │ ├── audio-only.svg │ │ │ │ ├── download.svg │ │ │ │ ├── close.svg │ │ │ │ ├── video-only.svg │ │ │ │ ├── action.js │ │ │ │ ├── index.html │ │ │ │ ├── fetch.svg │ │ │ │ ├── ui.js │ │ │ │ └── index.css │ │ │ ├── download.svg │ │ │ ├── styles.css │ │ │ ├── panel.js │ │ │ ├── id.js │ │ │ └── button.js │ │ ├── popup │ │ │ ├── list.svg │ │ │ ├── unchecked.svg │ │ │ ├── checked.svg │ │ │ ├── download.svg │ │ │ ├── more.svg │ │ │ ├── convert.svg │ │ │ ├── settings.svg │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── locale.js │ │ └── options │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── ffmpeg.js │ │ │ └── index.html │ ├── manifest.json │ ├── ffmpeg.js │ ├── _locales │ │ └── en │ │ │ └── messages.json │ ├── download.js │ └── common.js ├── lib │ ├── userstyles.js │ ├── toolbarbutton │ │ ├── new.js │ │ └── old.js │ ├── subtitle.js │ ├── ffmpeg.js │ ├── misc.js │ ├── download.js │ ├── external.js │ └── extract.js ├── run.js ├── package.json └── locale │ └── en-US.properties ├── .gitignore └── README.md /preview/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/chrome.manifest: -------------------------------------------------------------------------------- 1 | content iaextractor data/chrome/content/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | addon-sdk* 2 | node_modules/ 3 | test/ 4 | .DS_Store 5 | Thumbs.db -------------------------------------------------------------------------------- /src/.jpmignore: -------------------------------------------------------------------------------- 1 | .jpmignore 2 | builds/* 3 | .git 4 | *.xpi 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /src/data/shared/connect.jsm: -------------------------------------------------------------------------------- 1 | var EXPORTED_SYMBOLS = ["glow"]; 2 | 3 | var glow = {}; -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/icon.png -------------------------------------------------------------------------------- /src/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/icon64.png -------------------------------------------------------------------------------- /src/webextension/data/helper: -------------------------------------------------------------------------------- 1 | ../../../../external-application-button/data/helper/ -------------------------------------------------------------------------------- /src/data/report/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/report/16.png -------------------------------------------------------------------------------- /src/data/report/38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/report/38.png -------------------------------------------------------------------------------- /src/data/report/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/report/open.png -------------------------------------------------------------------------------- /src/data/report/tabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/report/tabs.png -------------------------------------------------------------------------------- /src/data/toolbar-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/toolbar-16.png -------------------------------------------------------------------------------- /src/data/toolbar-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/toolbar-32.png -------------------------------------------------------------------------------- /src/data/formats/fetch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/formats/fetch.png -------------------------------------------------------------------------------- /src/data/formats/font.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/formats/font.woff -------------------------------------------------------------------------------- /src/data/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/notification.png -------------------------------------------------------------------------------- /src/data/report/links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/report/links.png -------------------------------------------------------------------------------- /src/data/formats/images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/formats/images.png -------------------------------------------------------------------------------- /src/data/report/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/report/download.png -------------------------------------------------------------------------------- /src/data/report/segoeui.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/report/segoeui.woff -------------------------------------------------------------------------------- /src/data/formats/downloadm5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/formats/downloadm5.png -------------------------------------------------------------------------------- /src/data/report/no-download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/report/no-download.png -------------------------------------------------------------------------------- /src/data/report/preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/report/preferences.png -------------------------------------------------------------------------------- /src/data/report/google-font.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/report/google-font.woff -------------------------------------------------------------------------------- /src/webextension/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/webextension/data/icons/16.png -------------------------------------------------------------------------------- /src/webextension/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/webextension/data/icons/32.png -------------------------------------------------------------------------------- /src/webextension/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/webextension/data/icons/48.png -------------------------------------------------------------------------------- /src/webextension/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/webextension/data/icons/64.png -------------------------------------------------------------------------------- /src/data/formats/injected-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/formats/injected-button.png -------------------------------------------------------------------------------- /src/webextension/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/webextension/data/icons/128.png -------------------------------------------------------------------------------- /src/webextension/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/webextension/data/icons/256.png -------------------------------------------------------------------------------- /src/webextension/data/icons/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/webextension/data/icons/512.png -------------------------------------------------------------------------------- /src/webextension/data/inject/panel/fetch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/webextension/data/inject/panel/fetch.gif -------------------------------------------------------------------------------- /src/webextension/data/inject/panel/font.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/webextension/data/inject/panel/font.woff -------------------------------------------------------------------------------- /src/data/info/jsoneditor/img/jsoneditor-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iaextractor/HEAD/src/data/info/jsoneditor/img/jsoneditor-icons.png -------------------------------------------------------------------------------- /src/data/info/info.js: -------------------------------------------------------------------------------- 1 | var container = document.getElementById("jsoneditor"); 2 | var editor = new JSONEditor(container, { 3 | mode: "viewer", 4 | name: "Video info", 5 | history: false 6 | }); 7 | 8 | self.port.on("info", function(json) { 9 | editor.set(json); 10 | }); -------------------------------------------------------------------------------- /src/data/info.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /src/webextension/data/popup/list.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/webextension/data/locale.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | document.addEventListener('DOMContentLoaded', () => { 4 | [...document.querySelectorAll('[data-i18n]')].forEach(e => { 5 | e[e.dataset.mtd || 'textContent'] = chrome.i18n.getMessage(e.dataset.i18n); 6 | }); 7 | }); 8 | 9 | var locale = { 10 | get: (name) => chrome.i18n.getMessage(name) || name 11 | }; 12 | -------------------------------------------------------------------------------- /src/webextension/data/popup/unchecked.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/data/chrome/content/options.css: -------------------------------------------------------------------------------- 1 | description { 2 | font-size: 90.9%; 3 | color: graytext; 4 | margin-top: -2px; 5 | -moz-margin-start: 2em; 6 | white-space: pre-wrap; 7 | } 8 | label { 9 | margin: 0; 10 | padding: 0; 11 | } 12 | .abbr { 13 | text-decoration-line: underline; 14 | text-decoration-style: dotted; 15 | margin: 0 4px; 16 | } 17 | .text-link { 18 | padding: 0 4px; 19 | -moz-user-focus: ignore; 20 | } 21 | -------------------------------------------------------------------------------- /src/webextension/data/inject/panel/dot.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/webextension/data/popup/checked.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/webextension/data/options/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 10px; 3 | font-family: "Helvetica Neue",Helvetica,sans-serif; 4 | font-size: 13px; 5 | min-width: 500px; 6 | } 7 | table { 8 | width: 100%; 9 | } 10 | td:not(:first-child) { 11 | text-align: right; 12 | } 13 | input[type=text] { 14 | width: 100%; 15 | box-sizing: border-box; 16 | } 17 | fieldset { 18 | margin-bottom: 10px; 19 | } 20 | select { 21 | margin-left: 10px; 22 | } 23 | .commands td:last-child{ 24 | width: 10px; 25 | white-space: nowrap; 26 | } 27 | -------------------------------------------------------------------------------- /src/webextension/data/inject/panel/audio-only.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/webextension/data/inject/download.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/webextension/data/popup/download.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/webextension/data/inject/panel/download.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/webextension/data/popup/more.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/webextension/data/inject/panel/close.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/webextension/data/inject/panel/video-only.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/webextension/data/inject/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --iax-background: #FF6C09; 3 | --iax-color: #FFF; 4 | } 5 | 6 | .iaextractor-webx-button>span { 7 | color: var(--iax-background); 8 | } 9 | .iaextractor-webx-button:before { 10 | background-image: url('moz-extension://__MSG_@@extension_id__/data/inject/download.svg'), url('chrome-extension://__MSG_@@extension_id__/data/inject/download.svg') !important; 11 | background-size: 16px 16px !important; 12 | background-position: 0 !important; 13 | } 14 | .iaextractor-new-button { 15 | background-image: url('moz-extension://__MSG_@@extension_id__/data/inject/download.svg'), url('chrome-extension://__MSG_@@extension_id__/data/inject/download.svg'); 16 | background-position: center center; 17 | background-repeat: no-repeat; 18 | background-size: 16px 16px; 19 | width: 36px; 20 | cursor: pointer; 21 | } 22 | 23 | .iaextractor-webx-iframe { 24 | position: relative; 25 | background: transparent; 26 | border: none; 27 | z-index: 1999999998; 28 | overflow: hidden; 29 | left: 50%; 30 | width: 50%; 31 | height: 100%; 32 | } 33 | -------------------------------------------------------------------------------- /src/webextension/data/inject/panel/action.js: -------------------------------------------------------------------------------- 1 | /* globals youtube, build, items, info */ 2 | 'use strict'; 3 | 4 | var id = document.location.search.split('id=').pop(); 5 | youtube.perform(id).then( 6 | info => build(info), 7 | e => { 8 | document.body.dataset.loading = false; 9 | items.setAttribute('pack', 'center'); 10 | items.setAttribute('align', 'center'); 11 | items.textContent = e.message; 12 | } 13 | ); 14 | 15 | document.addEventListener('click', e => { 16 | const cmd = e.target.dataset.cmd; 17 | if (cmd === 'toggle-toolbar') { 18 | let item = e.target.parentNode; 19 | [...items.querySelectorAll('a')] 20 | .filter(a => a !== item) 21 | .forEach(a => a.dataset.toolbar = 'false'); 22 | item.dataset.toolbar = 23 | document.body.dataset.integration = 24 | item.dataset.toolbar === 'false'; 25 | return; 26 | } 27 | else if (cmd === 'close-me') { 28 | chrome.runtime.sendMessage({ 29 | method: 'close-panel' 30 | }); 31 | } 32 | // do not load audio or video files inside the iframe 33 | const a = e.target.closest('a'); 34 | if (a) { 35 | if (a.dataset.itag) { 36 | chrome.runtime.sendMessage({ 37 | method: 'download', 38 | info, 39 | itag: a.dataset.itag 40 | }); 41 | } 42 | e.preventDefault(); 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##FLV Audio Extractor (iaextractor) 2 | A pure JavaScript AAC extractor for FLV format. 3 | 4 | ###General information 5 | To compile iaextractor project you need to have these packages and libraries installed: 6 | * [python](http://www.python.org/getit/) 7 | * [nodejs](http://nodejs.org/) 8 | * [Mozilla addon-sdk](https://addons.mozilla.org/en-US/developers/builder) 9 | 10 | Folders description: 11 | * src: source code 12 | * compile: nodejs compiler 13 | * ../addon-sdk-*: latest version of [Mozilla addon-sdk](https://addons.mozilla.org/en-US/developers/builder). 14 | * preview: screenshots 15 | * template: bootstrap folder 16 | 17 | > By default, the addon-sdk folder is assumed to be one directory above the project. This can be modified using the ``--sdk`` parameter. 18 | 19 | ###How to compile this project: 20 | 1. Open a new terminal in the root dir (directory contains src, preview, template, and compile folders) 21 | 2. Run ``npm install`` to acquire the necessary nodejs packages 22 | 3. Run ``node compile/install.js`` to run iaextractor in a new Firefox profile 23 | To make the xpi run ``node compile/install.js --xpi`` 24 | For more options use ``node compile/install.js --help`` 25 | 26 | ###How to try the pre-compiled latest version: 27 | 1. Select the right branch 28 | 2. Browse the src directory 29 | 3. Download the raw *.xpi file 30 | 4. Drag and drop it into Firefox 31 | 32 | ###iaextractor contains codes from the following projects: 33 | 1. http://www.moitah.net/ 34 | 2. https://github.com/fent/node-ytdl 35 | 3. https://github.com/josdejong/jsoneditoronline 36 | -------------------------------------------------------------------------------- /src/webextension/data/inject/panel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 18 | 24 | 25 |
26 | Download Links 27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Integration with external download managers is in progress! 35 |
36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/webextension/data/inject/panel/fetch.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/webextension/data/inject/panel.js: -------------------------------------------------------------------------------- 1 | /* globals id */ 2 | 'use strict'; 3 | 4 | (function (detect) { 5 | // remove old panels 6 | [...document.querySelectorAll('.iaextractor-webx-iframe')].forEach(p => p.parentNode.removeChild(p)); 7 | chrome.runtime.sendMessage({ 8 | method: 'close-panel' 9 | }, () => { 10 | // add the new panel 11 | const d = detect(); 12 | if (d) { 13 | if (id) { 14 | const iframe = document.createElement('iframe'); 15 | iframe.classList.add('iaextractor-webx-iframe'); 16 | iframe.src = chrome.runtime.getURL('/data/inject/panel/index.html?id=' + id); 17 | d.player.appendChild(iframe); 18 | } 19 | else { 20 | chrome.runtime.sendMessage({ 21 | method: 'error', 22 | message: 'error_8' 23 | }); 24 | } 25 | } 26 | else { 27 | chrome.runtime.sendMessage({ 28 | method: 'error', 29 | message: 'error_9' 30 | }); 31 | } 32 | }); 33 | })(function () { 34 | const players = [ 35 | ...document.getElementsByTagName('embed'), 36 | ...document.getElementsByTagName('video') 37 | ] 38 | .sort((a, b) => b.getBoundingClientRect().width - a.getBoundingClientRect().width); 39 | 40 | if (players.length) { 41 | if (players[0].localName === 'embed') { //Flash player 42 | return { 43 | player: players[0], 44 | method: 'insertAdjacentHTML' 45 | }; 46 | } 47 | else { //HTML5 player 48 | return { 49 | player: players[0].parentNode.parentNode, 50 | method: 'insertAdjacentHTML' 51 | }; 52 | } 53 | } 54 | else { 55 | const parentDiv = document.getElementById('player-api'); 56 | if (parentDiv) { 57 | return { 58 | player: parentDiv, 59 | method: 'innerHTML' 60 | }; 61 | } 62 | } 63 | }); 64 | -------------------------------------------------------------------------------- /src/lib/userstyles.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | "use strict"; 5 | 6 | const { Cc, Ci } = require("chrome"); 7 | const unload = require("sdk/system/unload").when; 8 | 9 | const sss = Cc["@mozilla.org/content/style-sheet-service;1"] 10 | .getService(Ci.nsIStyleSheetService); 11 | 12 | function getURI(aURL) Cc["@mozilla.org/network/io-service;1"] 13 | .getService(Ci.nsIIOService).newURI(aURL, null, null); 14 | 15 | function setOptions(url, options) { 16 | let newOptions = {}; 17 | options = options || {}; 18 | 19 | newOptions.uri = getURI(url); 20 | newOptions.type = (options.type || 'user').toLowerCase(); 21 | newOptions.type = (newOptions.type == 'agent') ? sss.AGENT_SHEET : sss.USER_SHEET; 22 | 23 | return newOptions; 24 | }; 25 | 26 | /** 27 | * Load various packaged styles for the add-on and undo on unload 28 | * 29 | * @usage load(aURL): Load specified style 30 | * @param [string] aURL: Style file to load 31 | * @param [object] options: 32 | */ 33 | const loadSS = exports.load = function loadSS(url, options) { 34 | let { uri, type } = setOptions(url, options); 35 | 36 | // load the stylesheet 37 | sss.loadAndRegisterSheet(uri, type); 38 | 39 | // unload the stylesheet on unload 40 | unload(unregisterSS.bind(null, url, options)); 41 | }; 42 | 43 | const registeredSS = exports.registered = function registeredSS(url, options) { 44 | let { uri, type } = setOptions(url, options); 45 | 46 | // check that the stylesheet is registered 47 | return !!sss.sheetRegistered(uri, type); 48 | }; 49 | 50 | const unregisterSS = exports.unload = function unregisterSS(url, options) { 51 | let { uri, type } = setOptions(url, options); 52 | 53 | // unregister the stylesheet 54 | sss.unregisterSheet(uri, type); 55 | }; 56 | -------------------------------------------------------------------------------- /src/webextension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "YouTube Video and Audio Downloader (Dev Edt.)", 3 | "short_name": "iaextractor", 4 | "description": "__MSG_appDesc__", 5 | "author": "InBasic", 6 | "version": "0.7.1", 7 | "manifest_version": 2, 8 | "default_locale": "en", 9 | "permissions": [ 10 | "tabs", 11 | "storage", 12 | "nativeMessaging", 13 | "downloads", 14 | "notifications", 15 | "*://www.youtube.com/*", 16 | "*://*.googlevideo.com/*", 17 | "https://api.github.com/repos/andy-portmen/native-client/releases/latest", 18 | "https://api.github.com/repos/inbasic/ffmpeg/releases/latest" 19 | ], 20 | "background": { 21 | "persistent": false, 22 | "scripts": [ 23 | "/data/locale.js", 24 | "download.js", 25 | "ffmpeg.js", 26 | "common.js" 27 | ] 28 | }, 29 | "browser_action": { 30 | "default_icon": { 31 | "16": "/data/icons/16.png", 32 | "32": "/data/icons/512.png", 33 | "64": "/data/icons/64.png", 34 | "128": "/data/icons/128.png", 35 | "256": "/data/icons/256.png", 36 | "512": "/data/icons/512.png" 37 | }, 38 | "default_popup": "/data/popup/index.html" 39 | }, 40 | "content_scripts": [{ 41 | "matches": [ 42 | "*://www.youtube.com/*" 43 | ], 44 | "js": [ 45 | "data/inject/id.js", 46 | "data/inject/button.js" 47 | ], 48 | "css": [ 49 | "data/inject/styles.css" 50 | ], 51 | "run_at": "document_start", 52 | "all_frames": false 53 | }], 54 | "homepage_url": "https://github.com/inbasic/iaextractor/", 55 | "icons": { 56 | "16": "/data/icons/16.png", 57 | "32": "/data/icons/512.png", 58 | "64": "/data/icons/64.png", 59 | "128": "/data/icons/128.png", 60 | "256": "/data/icons/256.png", 61 | "512": "/data/icons/512.png" 62 | }, 63 | "web_accessible_resources": [ 64 | "/data/inject/download.svg", 65 | "/data/inject/panel/index.html" 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /src/webextension/data/popup/convert.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sp = require('sdk/simple-prefs'); 4 | var utils = require('sdk/window/utils'); 5 | var self = require('sdk/self'); 6 | 7 | function loadSDK () { 8 | require('./lib/main.js'); 9 | sp.on('openOptions', () => { 10 | utils.openDialog({ 11 | url: 'chrome://iaextractor/content/options.xul', 12 | name: 'iaextractor-options', 13 | features: 'chrome,titlebar,toolbar,centerscreen,dialog=no,resizable' 14 | }).focus(); 15 | }); 16 | } 17 | 18 | function loadWebE () { 19 | try { 20 | return require('sdk/webextension').startup(); 21 | } 22 | catch (e) { 23 | return Promise.reject(e); 24 | } 25 | } 26 | 27 | if (sp.prefs.experiment) { 28 | loadWebE().then(api => { 29 | const {browser} = api; 30 | browser.runtime.onConnect.addListener(channel => { 31 | sp.on('openOptions', () => { 32 | channel.postMessage({ 33 | method: 'open-options' 34 | }); 35 | }); 36 | if (sp.prefs.ffmpegPath) { 37 | channel.postMessage({ 38 | method: 'prefs', 39 | prefs: { 40 | ffmpeg: sp.prefs.ffmpegPath, 41 | version: self.version 42 | } 43 | }); 44 | } 45 | }); 46 | }).catch(e => { 47 | loadSDK(); 48 | console.error('WebExtension Error', e); 49 | sp.prefs.experiment = false; 50 | }); 51 | } 52 | else { 53 | loadSDK(); 54 | } 55 | 56 | sp.on('experiment', () => { 57 | if (sp.prefs.experiment) { 58 | utils.getMostRecentBrowserWindow().alert( 59 | `Please disable and re-enable the extension to switch to the experimental WebExtension version 60 | 61 | Make sure to refresh this page afterward and visit the new options page to install a minimal native client for the extension to recognize FFmpeg media converter. 62 | ` 63 | ); 64 | } 65 | else { 66 | utils.getMostRecentBrowserWindow().alert( 67 | `Please disable and re-enable the extension to switch back to the SDK version 68 | 69 | Note that the WebExtension version will replace the SDK version on Firefox 57 release. So please help us fix the WebExtension bugs by opening new bug reports. 70 | ` 71 | ); 72 | } 73 | }); 74 | -------------------------------------------------------------------------------- /src/webextension/data/inject/id.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var id; 4 | 5 | document.addEventListener('iaextractor', e => { 6 | if (e.detail.id) { 7 | console.error('ID', e.detail); 8 | id = e.detail.id; 9 | } 10 | }); 11 | 12 | // Finding id 13 | function findID () { 14 | const url = document.location.href; 15 | const tmp = /v\=([^\=\&]*)|embed\/([^\=\&]*)/.exec(url); 16 | if (tmp && tmp.length) { 17 | document.dispatchEvent(new CustomEvent('iaextractor', { 18 | detail: { 19 | id: tmp[1] 20 | } 21 | })); 22 | } 23 | else { 24 | const parent = document.documentElement; 25 | parent.appendChild( 26 | Object.assign(document.createElement('script'), { 27 | textContent: ` 28 | var yttools = yttools || []; 29 | yttools.push(function (e) { 30 | document.dispatchEvent(new CustomEvent('iaextractor', { 31 | detail: { 32 | id: e.getVideoData()['video_id'] 33 | } 34 | })); 35 | }); 36 | function onYouTubePlayerReady (e) { 37 | yttools.forEach(c => c(e)); 38 | } 39 | 40 | (function (observe) { 41 | observe(window, 'ytplayer', (ytplayer) => { 42 | observe(ytplayer, 'config', (config) => { 43 | if (config && config.args) { 44 | config.args.jsapicallback = 'onYouTubePlayerReady'; 45 | } 46 | }); 47 | }); 48 | })(function (object, property, callback) { 49 | let value; 50 | let descriptor = Object.getOwnPropertyDescriptor(object, property); 51 | Object.defineProperty(object, property, { 52 | enumerable: true, 53 | configurable: true, 54 | get: () => value, 55 | set: (v) => { 56 | callback(v); 57 | if (descriptor && descriptor.set) { 58 | descriptor.set(v); 59 | } 60 | value = v; 61 | return value; 62 | } 63 | }); 64 | }); 65 | ` 66 | }) 67 | ); 68 | } 69 | } 70 | 71 | findID(); 72 | document.addEventListener('spfdone', findID, false); 73 | window.addEventListener('yt-navigate-finish', findID, false); 74 | -------------------------------------------------------------------------------- /src/webextension/ffmpeg.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ffmpeg = { 4 | sep: navigator.platform.startsWith('Win') ? '\\' : '/' 5 | }; 6 | ffmpeg.convert = function (command, args, dictionary) { 7 | return new Promise((resolve, reject) => { 8 | args = args.split(/\s/).map(s => { 9 | Object.keys(dictionary).forEach(key => s = s.replace(key, dictionary[key])); 10 | return s; 11 | }); 12 | chrome.runtime.sendNativeMessage('com.add0n.node', { 13 | cmd: 'exec', 14 | kill: true, 15 | command, 16 | arguments: args 17 | }, obj => { 18 | console.error(obj); 19 | if (!obj || obj.error || (obj.code && obj.code !== 0)) { 20 | reject(new Error(obj ? obj.error || obj.stderr : 'error_12')); 21 | } 22 | else { 23 | resolve(); 24 | } 25 | }); 26 | }); 27 | }; 28 | 29 | ffmpeg.parent = (path) => { 30 | return path.split(ffmpeg.sep).slice(0, -1).join(ffmpeg.sep); 31 | }; 32 | 33 | ffmpeg.extract = (path) => { 34 | return path.split(ffmpeg.sep).pop().split('.'); 35 | }; 36 | 37 | ffmpeg.resolve = (path, name, extension) => { 38 | function check (files, name, extension, index = 0) { 39 | let leafname = name.replace(/\-\d+$/, '') + (index ? '-' + index : '') + '.' + extension; 40 | for (let n of files) { 41 | if (n.endsWith(leafname)) { 42 | return check(files, name, extension, index + 1); 43 | } 44 | } 45 | return leafname; 46 | } 47 | 48 | return new Promise((resolve, reject) => { 49 | chrome.runtime.sendNativeMessage('com.add0n.node', { 50 | cmd: 'dir', 51 | path 52 | }, obj => { 53 | if (!obj || obj.error || (obj.code && obj.code !== 0)) { 54 | reject(new Error(obj ? obj.error || obj.stderr : 'error_10')); 55 | } 56 | else { 57 | resolve(path + ffmpeg.sep + check(obj.files, name, extension)); 58 | } 59 | }); 60 | }); 61 | }; 62 | 63 | ffmpeg.remove = files => { 64 | return new Promise((resolve, reject) => { 65 | chrome.runtime.sendNativeMessage('com.add0n.node', { 66 | cmd: 'remove', 67 | files 68 | }, obj => { 69 | if (!obj || obj.error || (obj.code && obj.code !== 0)) { 70 | reject(new Error(obj ? obj.error || obj.stderr : 'error_11')); 71 | } 72 | else { 73 | resolve(); 74 | } 75 | }); 76 | }); 77 | }; 78 | -------------------------------------------------------------------------------- /src/webextension/data/inject/panel/ui.js: -------------------------------------------------------------------------------- 1 | /* globals youtube */ 2 | 'use strict'; 3 | 4 | var tItem = document.getElementById('template-item'); 5 | var tPage = document.getElementById('template-page'); 6 | var items = document.getElementById('items'); 7 | var toolbar = document.getElementById('toolbar'); 8 | var info; 9 | 10 | function page () { 11 | toolbar.textContent = ''; 12 | let a = items.querySelector('a:last-of-type'); 13 | if (a) { 14 | const pages = Math.floor( 15 | a.getBoundingClientRect().left / window.innerWidth 16 | ) + 1; 17 | for (let i = 1; i <= pages; i += 1) { 18 | let item = document.importNode(tPage.content, true); 19 | item.querySelector('label').textContent = i; 20 | item.querySelector('label').setAttribute('for', 'page-' + i); 21 | item.querySelector('input').id = 'page-' + i; 22 | item.querySelector('input').checked = i === 1; 23 | toolbar.appendChild(item); 24 | } 25 | } 26 | } 27 | window.addEventListener('resize', page); 28 | 29 | function build (o) { 30 | info = o; 31 | document.body.dataset.loading = false; 32 | info.formats.forEach(format => { 33 | const textContent = [].concat.apply([], [ 34 | format.container.toUpperCase(), 35 | format.dash === 'a' ? 'audio-only' : format.resolution || format.quality, 36 | '-', 37 | format.dash === 'v' ? 'video-only' : [ 38 | format.audioEncoding.toUpperCase(), 39 | format.audioBitrate + 'K', 40 | ] 41 | ]).join(' '); 42 | let item = document.importNode(tItem.content, true); 43 | item.querySelector('span:nth-child(2)').textContent = textContent; 44 | const title = format.name; 45 | item.querySelector('a').href = format.url + '&title=' + 46 | encodeURIComponent(title); 47 | item.querySelector('a').download = title; 48 | item.querySelector('a').dataset.itag = format.itag; 49 | if (format.dash) { 50 | item.querySelector('img').src = (format.dash === 'v' ? 'video' : 'audio') + '-only.svg'; 51 | } 52 | const size = item.querySelector('span:last-of-type'); 53 | youtube.size(format.url, true).then(s => { 54 | size.textContent = s; 55 | }); 56 | item = items.appendChild(item); 57 | }); 58 | page(); 59 | } 60 | 61 | document.addEventListener('change', e => { 62 | const id = /\d+/.exec(e.target.id); 63 | if (id) { 64 | items.style = `transform: translate(-${100 * (id[0] - 1)}%, 0)`; 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /src/webextension/data/inject/button.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* close panel on reload */ 4 | (function (callback) { 5 | document.addEventListener('spfrequest', callback); 6 | window.addEventListener('yt-navigate-start', callback); 7 | })(function () { 8 | chrome.runtime.sendMessage({ 9 | method: 'close-panel' 10 | }); 11 | }); 12 | 13 | /* appeding button */ 14 | function prepare (button, cname) { 15 | button.dataset.tooltipText = 'Detect all possible download links'; 16 | button.addEventListener('click', e => { 17 | e.preventDefault(); 18 | e.stopPropagation(); 19 | chrome.runtime.sendMessage({ 20 | method: 'display-panel' 21 | }); 22 | }); 23 | button.classList.add(cname); 24 | button.classList.remove('yt-uix-clickcard-target', 'pause-resume-autoplay'); 25 | } 26 | 27 | // old UI 28 | (function (callback) { 29 | document.addEventListener('DOMContentLoaded', callback); 30 | document.addEventListener('spfdone', callback); 31 | })(function () { 32 | const parent = document.getElementById('watch8-secondary-actions'); 33 | if (parent) { 34 | // find class names; use a top level button 35 | const button = parent.querySelector('#watch8-secondary-actions>button').cloneNode(true); 36 | if (button) { 37 | prepare(button, 'iaextractor-webx-button'); 38 | const span = button.querySelector('span'); 39 | if (span) { 40 | span.textContent = 'Download'; 41 | parent.appendChild(button); 42 | } 43 | else { 44 | console.error('Cannot find span element on the sample button element', button); 45 | } 46 | } 47 | else { 48 | console.error('Cannot find a sample button in the parent element', parent); 49 | } 50 | } 51 | }); 52 | // new UI 53 | (function () { 54 | function observe () { 55 | const top = document.querySelector('ytd-video-primary-info-renderer #top-level-buttons'); 56 | if (top) { 57 | let button = top.querySelector('ytd-button-renderer'); 58 | if (button) { 59 | button = button.cloneNode(false); 60 | let a = top.querySelector('ytd-button-renderer a'); 61 | if (a) { 62 | a = a.cloneNode(true); 63 | button.appendChild(a); 64 | } 65 | prepare(button, 'iaextractor-new-button'); 66 | //top.parentNode.appendChild(button); 67 | top.parentNode.insertBefore(button, [...top.parentNode.children].pop()); 68 | } 69 | window.removeEventListener('yt-visibility-refresh', observe); 70 | } 71 | } 72 | window.addEventListener('yt-visibility-refresh', observe); 73 | })(); 74 | -------------------------------------------------------------------------------- /src/webextension/data/popup/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/webextension/data/popup/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --bg1: #FF6C09; 3 | --bg2: #FFA05A; 4 | --color: #FFF; 5 | --spacing: 20px; 6 | } 7 | html,body { 8 | height: 100%; 9 | } 10 | body { 11 | font-family: sans-serif,Roboto,arial; 12 | font-size: 13px; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | margin: 0; 16 | color: var(--color); 17 | background-color: var(--bg1); 18 | overflow: hidden; 19 | user-select: none; 20 | width: 300px; 21 | height: 200px; 22 | } 23 | body>div:first-child { 24 | margin-bottom: calc(-1 * var(--spacing)); 25 | } 26 | body>div>div { 27 | background-color: var(--bg2); 28 | box-sizing: border-box; 29 | margin: var(--spacing); 30 | cursor: pointer; 31 | } 32 | body>div>div:first-child { 33 | margin-right: 0; 34 | } 35 | span { 36 | height: 20px; 37 | } 38 | ul { 39 | list-style: none; 40 | padding: 0 0 0 10px; 41 | margin: 0; 42 | } 43 | li { 44 | text-align: left; 45 | margin-bottom: 3px; 46 | } 47 | 48 | input[type=radio] { 49 | display: none; 50 | } 51 | label { 52 | background: url('unchecked.svg') left center no-repeat; 53 | background-size: 16px; 54 | padding-left: 20px; 55 | cursor: pointer; 56 | } 57 | input:checked ~ label { 58 | background-image: url('checked.svg'); 59 | } 60 | img { 61 | max-width: 12vw; 62 | } 63 | 64 | [data-cmd="quick-download"] { 65 | position: relative; 66 | } 67 | #more { 68 | position: absolute; 69 | top: 7px; 70 | right: 10px; 71 | opacity: 0.5; 72 | cursor: pointer; 73 | } 74 | #more:hover { 75 | opacity: 1; 76 | } 77 | #settings { 78 | background-color: var(--bg1); 79 | border-top: 1px solid #fff; 80 | position: absolute; 81 | bottom: -70vh; 82 | right: 0; 83 | left: 0; 84 | height: 70vh; 85 | box-sizing: border-box; 86 | transition: transform 200ms; 87 | } 88 | body[data-settings=true] #settings{ 89 | transform: translate(0, -70vh); 90 | } 91 | #settings>div { 92 | background-color: transparent; 93 | } 94 | body[data-youtube=true] [youtube=false] { 95 | display: none; 96 | } 97 | body[data-youtube=false] [youtube=true] { 98 | display: none; 99 | } 100 | body[data-youtube=false] [data-cmd=display-panel], 101 | [data-working=true] { 102 | opacity: 0.4; 103 | pointer-events: none; 104 | } 105 | 106 | [hbox] { 107 | display: flex; 108 | flex-direction: row; 109 | } 110 | [vbox] { 111 | display: flex; 112 | flex-direction: column; 113 | } 114 | [flex="1"] { 115 | flex: 1; 116 | } 117 | [pack=center] { 118 | justify-content: center; 119 | } 120 | [align=center] { 121 | align-items: center; 122 | } 123 | [data-cmd]:active { 124 | opacity: 0.7; 125 | } 126 | -------------------------------------------------------------------------------- /src/webextension/data/inject/panel/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --bg1: #FF6C09; 3 | --bg2: #FFA05A; 4 | --color: #FFF; 5 | } 6 | html,body { 7 | height: 100%; 8 | } 9 | body { 10 | font-family: sans-serif,Roboto,arial; 11 | font-size: 13px; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | margin: 0; 15 | color: var(--color); 16 | background-color: var(--bg1); 17 | overflow: hidden; 18 | user-select: none; 19 | } 20 | body[data-loading=true] { 21 | background: url('fetch.svg') center center no-repeat; 22 | background-color: var(--bg1); 23 | } 24 | 25 | .item { 26 | background-color: var(--bg2); 27 | margin: 20px 28px 0 28px; 28 | height: 30px; 29 | padding: 0 10px; 30 | text-decoration: none; 31 | color: var(--color); 32 | width: calc(100vw - 76px); 33 | } 34 | .item>span:nth-child(2) { 35 | white-space: nowrap; 36 | overflow: hidden; 37 | text-overflow: ellipsis; 38 | margin: 0 5px; 39 | text-align: left; 40 | } 41 | .item>span:first-child { 42 | height: 16px; 43 | width: 16px; 44 | overflow: hidden; 45 | } 46 | .item>span:first-child>img{ 47 | transition: transform 200ms; 48 | } 49 | .item[data-toolbar=true]>span:first-child>img{ 50 | transform: translate(0, -16px); 51 | } 52 | .item img { 53 | min-width: 16px; 54 | min-height: 16px; 55 | pointer-events: none; 56 | } 57 | 58 | #titlebar { 59 | min-height: 30px; 60 | } 61 | #content { 62 | overflow: hidden; 63 | height: 100%; 64 | } 65 | #items { 66 | flex-wrap: wrap; 67 | transition: transform 500ms; 68 | } 69 | #toolbar { 70 | min-height: 32px; 71 | padding-top: 10px; 72 | } 73 | #close { 74 | position: absolute; 75 | top: 0; 76 | right: 0; 77 | height: 20px; 78 | width: 40px; 79 | background: url('close.svg') center center no-repeat; 80 | background-color: var(--bg2); 81 | background-size: 16px; 82 | cursor: pointer; 83 | } 84 | #toolbar input { 85 | display: none; 86 | } 87 | #toolbar div { 88 | flex: 1; 89 | } 90 | #toolbar label { 91 | cursor: pointer; 92 | border: none; 93 | border-top: 2px solid transparent; 94 | } 95 | #toolbar input:checked + label { 96 | border-color: #fff; 97 | } 98 | #externals { 99 | position: absolute; 100 | height: 32px; 101 | bottom: 0; 102 | left: 0; 103 | right: 0; 104 | padding: 0 10px; 105 | transition: transform 300ms; 106 | transform: translate(0, 32px); 107 | background-color: var(--bg2); 108 | } 109 | body[data-integration=true] #externals { 110 | transform: translate(0, 0); 111 | } 112 | 113 | [hbox] { 114 | display: flex; 115 | flex-direction: row; 116 | } 117 | [vbox] { 118 | display: flex; 119 | flex-direction: column; 120 | } 121 | [flex="1"] { 122 | flex: 1; 123 | } 124 | [pack=center] { 125 | justify-content: center; 126 | } 127 | [align=center] { 128 | align-items: center; 129 | } 130 | -------------------------------------------------------------------------------- /src/webextension/data/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 19 |
20 |
21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 29 |
30 |
31 |
32 |
33 | 34 | 40 |
41 |
42 | 43 | 51 |
52 |
53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/data/chrome/content/options.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var {require} = Components.utils.import('resource://gre/modules/commonjs/toolkit/require.js', {}); 4 | 5 | function emit (msg) { 6 | Components.classes['@mozilla.org/observer-service;1'] 7 | .getService(Components.interfaces.nsIObserverService) 8 | .notifyObservers(null, 'iaextractor', msg); 9 | } 10 | 11 | document.addEventListener('click', function (e) { 12 | let target = e.target; 13 | if (target.localName === 'label') { 14 | let checkbox = target.parentNode.querySelector('checkbox'); 15 | if (checkbox) { 16 | checkbox.click(); 17 | } 18 | } 19 | }, false); 20 | 21 | var myPrefObserver = { 22 | branch: Components.classes['@mozilla.org/preferences-service;1'] 23 | .getService(Components.interfaces.nsIPrefService) 24 | .getBranch('extensions.feca4b87-3be4-43da-a1b1-137c24220968@jetpack.'), 25 | register: function () { 26 | if (!('addObserver' in this.branch)) { 27 | this.branch.QueryInterface(Components.interfaces.nsIPrefBranch2); 28 | } 29 | this.branch.addObserver('', this, false); 30 | document.querySelector('[preference="userFolder"]').disabled = this.branch.getIntPref('dFolder') !== 5; 31 | }, 32 | unregister: function () { 33 | this.branch.removeObserver('', this); 34 | }, 35 | observe: function (aSubject, aTopic, aData) { 36 | switch (aData) { 37 | case 'dFolder': 38 | let id = aSubject.getIntPref(aData); 39 | document.querySelector('[preference="userFolder"]').disabled = id !== 5; 40 | break; 41 | } 42 | }, 43 | }; 44 | myPrefObserver.register(); 45 | 46 | document.getElementById('namePattern-placeholders').addEventListener('command', function (e) { 47 | let label = e.target.label; 48 | let textbox = document.querySelector('[preference="namePattern"]'); 49 | let value = textbox.value; 50 | if (label) { 51 | let position = textbox.selectionStart + label.length; 52 | textbox.value = value.substr(0, textbox.selectionStart) + label + value.substr(textbox.selectionStart); 53 | textbox.selectionStart = textbox.selectionEnd = position; 54 | textbox.focus(); 55 | } 56 | }, false); 57 | 58 | (function (textbox) { 59 | textbox.addEventListener('keydown', function (e) { 60 | if (e.keyCode === 9 || (e.ctrlKey && e.keyCode === 87) || (e.altKey && e.keyCode === 115)) { 61 | return; 62 | } 63 | e.stopPropagation(); 64 | e.preventDefault(); 65 | let comb = []; 66 | if ((e.ctrlKey || (e.shiftKey && e.altKey) || (e.shiftKey && e.ctrlKey) || e.altKey) && (e.keyCode >= 65 && e.keyCode <= 90)) { 67 | if (e.ctrlKey) { 68 | comb.push('Accel'); 69 | } 70 | if (e.shiftKey) { 71 | comb.push('Shift'); 72 | } 73 | if (e.altKey) { 74 | comb.push('Alt'); 75 | } 76 | comb.push(String.fromCharCode(e.keyCode)); 77 | myPrefObserver.branch.setCharPref('downloadHKey', comb.join(' + ')); 78 | } 79 | else { 80 | myPrefObserver.branch.setCharPref('downloadHKey', ''); 81 | } 82 | }, true); 83 | })(document.querySelector('[preference="downloadHKey"]')); 84 | -------------------------------------------------------------------------------- /src/lib/toolbarbutton/new.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.ToolbarButton = function (options) { 4 | var {Cu} = require('chrome'), 5 | utils = require('sdk/window/utils'); 6 | var {CustomizableUI} = Cu.import('resource:///modules/CustomizableUI.jsm'); 7 | 8 | var listen = { 9 | onWidgetBeforeDOMChange: function (tbb) { 10 | if (tbb.id.indexOf('youtube-audio-converter') === -1) { 11 | return; 12 | } 13 | if (tbb.isInstalled) { 14 | return; 15 | } 16 | tbb.isInstalled = true; 17 | 18 | tbb.addEventListener('command', function (e) { 19 | if (e.ctrlKey) { 20 | return; 21 | } 22 | if (e.originalTarget.localName === 'menu' || e.originalTarget.localName === 'menuitem') { 23 | return; 24 | } 25 | 26 | if (options.onCommand) { 27 | options.onCommand(e, tbb); 28 | } 29 | }, true); 30 | if (options.onClick) { 31 | tbb.addEventListener('click', function (e) { 32 | options.onClick(e, tbb); 33 | return true; 34 | }); 35 | } 36 | if (options.panel) { 37 | tbb.addEventListener('contextmenu', function (e) { 38 | e.stopPropagation(); 39 | e.preventDefault(); 40 | try { 41 | options.panel.show(tbb); 42 | } 43 | catch (e) { 44 | options.panel.show(null, tbb); 45 | } 46 | }, true); 47 | } 48 | } 49 | }; 50 | CustomizableUI.addListener(listen); 51 | 52 | var getButton = () => utils.getMostRecentBrowserWindow().document.getElementById(options.id); 53 | var button = CustomizableUI.createWidget({ 54 | id : options.id, 55 | defaultArea : CustomizableUI.AREA_NAVBAR, 56 | label : options.label, 57 | tooltiptext : options.tooltiptext 58 | }); 59 | 60 | //Destroy on unload 61 | require('sdk/system/unload').when(function () { 62 | CustomizableUI.removeListener(listen); 63 | CustomizableUI.destroyWidget(options.id); 64 | }); 65 | 66 | return { 67 | destroy: function () { 68 | CustomizableUI.destroyWidget(options.id); 69 | }, 70 | moveTo: function () {}, 71 | get label() { 72 | return button.label; 73 | }, 74 | set label(value) { 75 | button.instances.forEach(function (i) { 76 | var tbb = i.anchor.ownerDocument.defaultView.document.getElementById(options.id); 77 | tbb.setAttribute('label', value); 78 | }); 79 | }, 80 | get tooltiptext() { 81 | return button.tooltiptext; 82 | }, 83 | set tooltiptext(value) { 84 | button.instances.forEach(function (i) { 85 | var tbb = i.anchor.ownerDocument.defaultView.document.getElementById(options.id); 86 | tbb.setAttribute('tooltiptext', value); 87 | }); 88 | }, 89 | get saturate() { 90 | return options.saturate; 91 | }, 92 | set saturate(value) { 93 | options.saturate = value; 94 | button.instances.forEach(function (i) { 95 | var tbb = i.anchor.ownerDocument.defaultView.document.getElementById(options.id); 96 | 97 | if (!value) { 98 | tbb.setAttribute('type', 'gray'); 99 | } 100 | else { 101 | tbb.removeAttribute('type'); 102 | } 103 | }); 104 | }, 105 | set progress (value) { //jshint ignore:line 106 | button.instances.forEach(function (i) { 107 | var tbb = i.anchor.ownerDocument.defaultView.document.getElementById(options.id); 108 | if (!value) { 109 | tbb.removeAttribute('progress'); 110 | } 111 | else { 112 | tbb.setAttribute('progress', (value * 8).toFixed(0)); 113 | } 114 | }); 115 | }, 116 | get object () { 117 | return getButton(); 118 | } 119 | }; 120 | }; 121 | -------------------------------------------------------------------------------- /src/data/formats/permanent.js: -------------------------------------------------------------------------------- 1 | /* globals self */ 2 | 'use strict' 3 | 4 | function $ (id) { 5 | try { 6 | return window.content.document.getElementById(id); 7 | } 8 | catch (e) { 9 | return null; 10 | } 11 | } 12 | 13 | function html (tag, atts, parent) { 14 | var elem = document.createElement(tag); 15 | for (var name in atts) { 16 | elem.setAttribute(name, atts[name]); 17 | } 18 | if (parent) { 19 | parent.appendChild(elem); 20 | } 21 | return elem; 22 | } 23 | 24 | function remove () { 25 | var button = $('formats-button-small'); 26 | if (button && button.parentNode) { 27 | button.parentNode.removeChild(button); 28 | } 29 | var menu = $('iaextractor-menu'); 30 | if (menu && menu.parentNode) { 31 | menu.parentNode.removeChild(menu); 32 | } 33 | } 34 | 35 | function init () { 36 | // Remove old button 37 | remove(); 38 | // old UI 39 | var parent = $('watch8-secondary-actions') || $('vo'); 40 | if (parent) { 41 | // Add new button 42 | var button = html('button', { 43 | 'id': 'formats-button-small', 44 | 'title': 'Detect all possible download links', 45 | 'class': 'yt-uix-button yt-uix-button-size-default yt-uix-button-opacity yt-uix-button-has-icon yt-uix-tooltip' 46 | }, parent); 47 | button.addEventListener('click', function () { 48 | this.blur(); 49 | self.port.emit('formats'); 50 | }); 51 | 52 | var title = html('span', { 53 | 'class': 'yt-uix-button-content' 54 | }); 55 | title.textContent = 'Download'; 56 | var imgContainer = html('span', { 57 | 'class': 'yt-uix-button-icon-wrapper' 58 | }); 59 | html('img', { 60 | 'class': 'yt-uix-button-icon yt-sprite', 61 | 'src': 'resource://feca4b87-3be4-43da-a1b1-137c24220968-at-jetpack/data/formats/injected-button.png' 62 | }, imgContainer); 63 | button.appendChild(imgContainer); 64 | button.appendChild(title); 65 | } 66 | } 67 | 68 | // new UI 69 | (function () { 70 | function observe () { 71 | const top = document.querySelector('ytd-video-primary-info-renderer #top-level-buttons'); 72 | if (top) { 73 | let button = top.querySelector('ytd-button-renderer'); 74 | if (button) { 75 | button = button.cloneNode(false); 76 | let a = top.querySelector('ytd-button-renderer a'); 77 | if (a) { 78 | a = a.cloneNode(true); 79 | button.appendChild(a); 80 | } 81 | button.id = 'formats-button-small-2'; 82 | button.addEventListener('click', () => self.port.emit('formats')); 83 | const lastChild = [...top.parentNode.children].pop(); 84 | if (lastChild) { 85 | top.parentNode.insertBefore(button, lastChild); 86 | } 87 | else { 88 | top.parentNode.appendChild(button); 89 | } 90 | } 91 | window.removeEventListener('yt-visibility-refresh', observe); 92 | } 93 | } 94 | window.addEventListener('yt-visibility-refresh', observe); 95 | })(); 96 | 97 | // init 98 | document.addEventListener('DOMContentLoaded', init, false); 99 | if (document.readyState !== 'loading') { 100 | init(); 101 | } 102 | function loader () { 103 | init(); 104 | self.port.emit('page-update'); 105 | } 106 | // Update toolbar button (HTML5 History API) 107 | self.port.emit('page-update'); 108 | document.addEventListener('spfdone', loader); 109 | document.addEventListener('yt-navigate-start', loader); 110 | self.port.on('detach', function () { 111 | remove(); 112 | document.removeEventListener('spfdone', loader); 113 | document.removeEventListener('yt-navigate-start', loader); 114 | }); 115 | 116 | // Clean up 117 | function resize () { 118 | var menu = $('iaextractor-menu'); 119 | if (menu && menu.parentNode) { 120 | menu.parentNode.removeChild(menu); 121 | } 122 | } 123 | window.addEventListener('resize', resize, false); 124 | self.port.on('detach', function () { 125 | remove(); 126 | window.removeEventListener('resize', resize); 127 | }); 128 | -------------------------------------------------------------------------------- /src/webextension/data/options/index.js: -------------------------------------------------------------------------------- 1 | /* globals locale */ 2 | 'use strict'; 3 | 4 | document.addEventListener('click', e => { 5 | let cmd = e.target.dataset.cmd; 6 | if (cmd === 'reset') { 7 | e.target.parentNode.parentNode.querySelector('input').value = e.target.dataset.value; 8 | } 9 | }); 10 | document.addEventListener('change', e => { 11 | if (e.target.dataset.cmd === 'insert') { 12 | let patern = document.getElementById('pattern'); 13 | let {value, selectionStart, selectionEnd} = patern; 14 | patern.value = value.substr(0, selectionStart) + e.target.value + value.substr(selectionEnd); 15 | patern.focus(); 16 | } 17 | }); 18 | 19 | function save() { 20 | let ffmpeg = document.getElementById('ffmpeg').value; 21 | let doMerge = document.getElementById('doMerge').checked; 22 | let pretendHD = document.getElementById('pretendHD').checked; 23 | let remove = document.getElementById('remove').checked; 24 | let toAudio = document.getElementById('post-process').value === 'audio'; 25 | let toMP3 = document.getElementById('post-process').value === 'mp3'; 26 | let opusmixing = document.getElementById('opusmixing').checked; 27 | let pattern = document.getElementById('pattern').value; 28 | let savein = document.getElementById('savein').value; 29 | let saveAs = document.getElementById('saveAs').checked; 30 | let faqs = document.getElementById('faqs').checked; 31 | let notification = document.getElementById('notification').checked; 32 | let commands = { 33 | toAudio: document.getElementById('toAudio').value, 34 | toMP3: document.getElementById('toMP3').value, 35 | muxing: document.getElementById('muxing').value 36 | }; 37 | 38 | chrome.storage.local.set({ 39 | ffmpeg, 40 | doMerge, 41 | pretendHD, 42 | remove, 43 | toAudio, 44 | toMP3, 45 | opusmixing, 46 | pattern, 47 | savein, 48 | saveAs, 49 | notification, 50 | faqs, 51 | commands 52 | }, () => { 53 | const status = document.getElementById('status'); 54 | status.textContent = locale.get('opt_013'); 55 | setTimeout(() => status.textContent = '', 750); 56 | }); 57 | } 58 | 59 | // Restores select box and checkbox state using the preferences 60 | // stored in chrome.storage. 61 | function restore() { 62 | // Use default value color = 'red' and likesColor = true. 63 | chrome.storage.local.get({ 64 | ffmpeg: '', 65 | doMerge: true, 66 | toAudio: true, 67 | toMP3: false, 68 | pretendHD: true, 69 | remove: true, 70 | opusmixing: false, 71 | pattern: '[file_name].[extension]', 72 | savein: '', 73 | saveAs: false, 74 | notification: true, 75 | faqs: true, 76 | commands: { 77 | toMP3: '-loglevel error -i %input -q:a 0 %output', 78 | toAudio: '-loglevel error -i %input -acodec copy -vn %output', 79 | muxing: '-loglevel error -i %audio -i %video -acodec copy -vcodec copy %output' 80 | } 81 | }, prefs => { 82 | document.getElementById('ffmpeg').value = prefs.ffmpeg; 83 | document.getElementById('doMerge').checked = prefs.doMerge; 84 | document.getElementById('pretendHD').checked = prefs.pretendHD; 85 | document.getElementById('remove').checked = prefs.remove; 86 | document.getElementById('post-process').value = prefs.toAudio ? 'audio' : (prefs.toMP3 ? 'mp3' : 'nothing'); 87 | document.getElementById('opusmixing').checked = prefs.opusmixing; 88 | document.getElementById('pattern').value = prefs.pattern; 89 | document.getElementById('savein').value = prefs.savein; 90 | document.getElementById('saveAs').checked = prefs.saveAs; 91 | document.getElementById('toAudio').value = prefs.commands.toAudio; 92 | document.getElementById('toMP3').value = prefs.commands.toMP3; 93 | document.getElementById('muxing').value = prefs.commands.muxing; 94 | document.getElementById('notification').checked = prefs.notification; 95 | document.getElementById('faqs').checked = prefs.faqs; 96 | }); 97 | } 98 | document.addEventListener('DOMContentLoaded', restore); 99 | document.getElementById('save').addEventListener('click', save); 100 | -------------------------------------------------------------------------------- /src/lib/subtitle.js: -------------------------------------------------------------------------------- 1 | /* Test cases: 2 | * 1. http://www.youtube.com/watch?v=XraeBDMm2PM (Non English) [okay] 3 | * 2. http://www.youtube.com/watch?v=0VJqrlH9cdI (English) [okay] 4 | * 3. http://www.youtube.com/watch?v=9kmUf3fflrA [Fail] 5 | */ 6 | 7 | var {Cc, Ci, Cu, components} = require('chrome'), 8 | _ = require("sdk/l10n").get, 9 | Request = require("sdk/request").Request, 10 | youtube = require("./youtube"), 11 | windows = { 12 | get active () { // Chrome window 13 | return require('sdk/window/utils').getMostRecentBrowserWindow() 14 | } 15 | }; 16 | 17 | Cu.import("resource://gre/modules/NetUtil.jsm"); 18 | Cu.import("resource://gre/modules/FileUtils.jsm"); 19 | 20 | function xmlToSrt (str) { 21 | var parser = new windows.active.DOMParser(); 22 | 23 | var getTimes = function (node) { 24 | var start = parseFloat(node.getAttribute("start")); 25 | var end = start + parseFloat(node.getAttribute("dur")); 26 | function toTime (secs) { 27 | var mil = (secs % 1).toFixed(3).replace("0.", ""); 28 | var sec_numb = parseInt(secs, 10); 29 | var hours = Math.floor(sec_numb / 3600); 30 | var minutes = Math.floor((sec_numb - (hours * 3600)) / 60); 31 | var seconds = sec_numb - (hours * 3600) - (minutes * 60); 32 | if (hours < 10) {hours = "0" + hours;} 33 | if (minutes < 10) {minutes = "0" + minutes;} 34 | if (seconds < 10) {seconds = "0" + seconds;} 35 | 36 | return hours + ":" + minutes + ":" + seconds + "," + mil; 37 | } 38 | 39 | return {start: toTime(start), end: toTime(end)} 40 | } 41 | 42 | var dom = parser.parseFromString(str, "text/html"); 43 | var texts = dom.getElementsByTagName("text"); 44 | var srt = ""; 45 | for (var i = 0; i < texts.length; i++) { 46 | var times = getTimes(texts[i]); 47 | srt += (i+1) + "\n" + times.start + " --> " + times.end + "\n" + texts[i].textContent + "\n\n"; 48 | } 49 | return srt; 50 | } 51 | 52 | var subtitle = function (video_id, langID, oFile, callback, pointer) { 53 | var lang = "en", name = ""; 54 | switch (langID) { 55 | case 0: lang = "en"; name = "English (en)"; break; 56 | case 1: lang = "fr"; name = "French (fr)"; break; 57 | case 2: lang = "de"; name = "German (de)"; break; 58 | case 3: lang = "it"; name = "Italian (it)"; break; 59 | case 4: lang = "ja"; name = "Japanese"; break; 60 | case 5: lang = "es"; name = "Spanish (es)"; break; 61 | case 6: lang = "ru"; name = "Russian (ru)"; break; 62 | } 63 | youtube.getInfo(video_id, function (vInfo) { 64 | if (vInfo.ttsurl) { 65 | var url = vInfo.ttsurl + "&kind=" + (vInfo.cc_asr ? "asr" : "") + "&lang=" + lang + "&name="; 66 | Request({ 67 | url: url, 68 | onComplete: function (response) { 69 | analyse (response.text); 70 | } 71 | }).get(); 72 | url += name; 73 | Request({ 74 | url: url, 75 | onComplete: function (response) { 76 | analyse (response.text); 77 | } 78 | }).get(); 79 | 80 | var responses = []; 81 | function analyse (txt) { 82 | responses.push(txt); 83 | if (responses.length == 2) { 84 | var tmp = ""; 85 | if (responses[0].length && responses[0].indexOf("Error 404") === -1) { 86 | tmp = responses[0]; 87 | } 88 | else if (responses[1].length && responses[1].indexOf("Error 404") === -1) { 89 | tmp = responses[1]; 90 | } 91 | else { 92 | if (callback) callback.apply(pointer, [_('err11')]); 93 | return; 94 | } 95 | var ostream = FileUtils.openSafeFileOutputStream(oFile) 96 | var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. 97 | createInstance(Ci.nsIScriptableUnicodeConverter); 98 | converter.charset = "UTF-8"; 99 | var istream = converter.convertToInputStream(xmlToSrt(tmp)); 100 | NetUtil.asyncCopy(istream, ostream, function(status) { 101 | if (!components.isSuccessCode(status)) { 102 | if (callback) callback.apply(pointer, [_('err10')]); 103 | return; 104 | } 105 | callback.apply(pointer); 106 | }); 107 | } 108 | } 109 | } 110 | else { 111 | if (callback) callback.apply(pointer, [_('err11')]); 112 | } 113 | }); 114 | } 115 | exports.get = subtitle; -------------------------------------------------------------------------------- /src/webextension/data/options/ffmpeg.js: -------------------------------------------------------------------------------- 1 | /* globals download, locale */ 2 | 'use strict'; 3 | 4 | function notify (message) { 5 | chrome.runtime.sendMessage({ 6 | method: 'message', 7 | message: message.message || message 8 | }); 9 | } 10 | 11 | function url () { 12 | let os = /windows|mac|linux/i.exec(navigator.userAgent); 13 | if (os) { 14 | os = os[0].toLowerCase(); 15 | } 16 | else { 17 | return Promise.reject(new Error('error_1')); 18 | } 19 | 20 | return new Promise((resolve, reject) => { 21 | let req = new window.XMLHttpRequest(); 22 | req.open('GET', 'https://api.github.com/repos/inbasic/ffmpeg/releases/latest'); 23 | req.responseType = 'json'; 24 | req.onload = () => { 25 | try { 26 | let name = 'ffmpeg-'; 27 | let platform = navigator.platform; 28 | if (platform === 'Win32') { 29 | name += 'win32-ia32.exe'; 30 | } 31 | else if (platform === 'Win64') { 32 | name += 'win32-x64.exe'; 33 | } 34 | else if (platform === 'MacIntel') { 35 | name += 'darwin-x64'; 36 | } 37 | else if (platform.indexOf('64')) { 38 | name += 'linux-x64'; 39 | } 40 | else { 41 | name += 'linux-ia32'; 42 | } 43 | 44 | resolve({ 45 | url: req.response.assets.filter(a => a.name === name)[0].browser_download_url, 46 | name, 47 | container: '' 48 | }); 49 | } 50 | catch (e) {} 51 | }; 52 | req.onerror = () => { 53 | reject(new Error('error_2')); 54 | }; 55 | req.send(); 56 | }); 57 | } 58 | 59 | function move (source, target) { 60 | return new Promise((resolve, reject) => { 61 | chrome.runtime.sendNativeMessage('com.add0n.node', { 62 | cmd: 'copy', 63 | delete: false, 64 | source, 65 | target, 66 | chmod: '0777' 67 | }, obj => { 68 | if (obj.error) { 69 | reject(new Error(obj.error)); 70 | } 71 | else { 72 | resolve(target); 73 | } 74 | }); 75 | }); 76 | } 77 | 78 | function ffmpeg () { 79 | return new Promise((resolve, reject) => { 80 | chrome.runtime.sendNativeMessage('com.add0n.node', { 81 | cmd: 'spec' 82 | }, response => { 83 | let button = document.querySelector('[data-cmd=install-native]'); 84 | if (response) { 85 | button.value = locale.get('opt_014'); 86 | url().then(format => { 87 | return download.get({ 88 | info: { 89 | formats: [Object.assign(format, { 90 | itag: 1, 91 | name: format.name 92 | })] 93 | }, 94 | itag: 1 95 | }, {}).then(([d]) => { 96 | const name = navigator.platform.startsWith('Win') ? 'ffmpeg.exe' : 'ffmpeg'; 97 | return move(d.filename, (response.env.APPDATA || response.env.HOME) + response.separator + name); 98 | }); 99 | }).then(resolve).catch(e => reject(e)); 100 | } 101 | else { 102 | button.value = locale.get('opt_015'); 103 | reject(new Error('error_3')); 104 | } 105 | }); 106 | }); 107 | } 108 | 109 | document.addEventListener('click', e => { 110 | const cmd = e.target.dataset.cmd; 111 | if (cmd === 'install-native') { 112 | chrome.tabs.create({ 113 | url: '/data/helper/index.html' 114 | }); 115 | } 116 | else if (cmd === 'download-ffmpeg') { 117 | let button = document.querySelector('[data-cmd=download-ffmpeg]'); 118 | button.value = locale.get('opt_018'); 119 | button.disabled = true; 120 | ffmpeg().then( 121 | target => { 122 | chrome.storage.local.set({ 123 | ffmpeg: target, 124 | doMerge: true 125 | }, () => { 126 | notify('message_1'); 127 | button.value = locale.get('opt_014'); 128 | button.disabled = false; 129 | }); 130 | }, 131 | e => { 132 | notify(e); 133 | button.value = locale.get('opt_016'); 134 | button.disabled = false; 135 | } 136 | ); 137 | } 138 | }); 139 | 140 | // init 141 | chrome.runtime.sendNativeMessage('com.add0n.node', { 142 | cmd: 'version' 143 | }, response => { 144 | if (response) { 145 | document.querySelector('[data-cmd=install-native]').value = locale.get('opt_014'); 146 | } 147 | }); 148 | chrome.storage.local.get({ 149 | ffmpeg: '' 150 | }, prefs => { 151 | if (prefs.ffmpeg) { 152 | document.querySelector('[data-cmd=download-ffmpeg]').value = locale.get('opt_014'); 153 | } 154 | }); 155 | -------------------------------------------------------------------------------- /src/lib/ffmpeg.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var {Ci, Cu} = require('chrome'), 4 | _ = require('sdk/l10n').get, 5 | sp = require('sdk/simple-prefs'), 6 | prefs = sp.prefs, 7 | notify = require('./misc').notify, 8 | childProcess = require('sdk/system/child_process'); 9 | 10 | var {FileUtils} = Cu.import('resource://gre/modules/FileUtils.jsm'); 11 | /* 12 | * inputs: array of input files, for video and audio combiner the second input is the video file 13 | * options: mode (combine, extract) 14 | * options: doRemux overwrite '- DASH' filename requirement 15 | * options: deleteInputs delete input files 16 | */ 17 | 18 | function isError(str) { 19 | return str.indexOf('Error') !== -1 || 20 | str.indexOf('incorrect codec parameters') !== -1; 21 | } 22 | 23 | exports.ffmpeg = function (inputs, options, callback, pointer) { 24 | var extensions = [], outputLocation; 25 | // Is FFmpeg available? 26 | var ffmpegPath = prefs.ffmpegPath; 27 | if (!ffmpegPath) { 28 | throw _('err12'); 29 | } 30 | // 31 | if (!options.mode) { 32 | options.mode = inputs.length === 2 ? 'combine' : 'extract'; 33 | } 34 | // Converting inputs to array 35 | if (typeof (inputs) === 'string') { 36 | inputs = [inputs]; 37 | } 38 | // Converting inputs to nsiFile 39 | inputs.forEach (function (input, index) { 40 | if (typeof (input) === 'string') { 41 | inputs[index] = new FileUtils.File(input); 42 | } 43 | var extension = /([^\.]*)$/.exec(inputs[index].leafName); 44 | extensions[index] = extension ? extension[1] : ''; 45 | }); 46 | // FFmpeg command 47 | var cmd; 48 | if (options.mode === 'combine') { 49 | cmd = prefs.ffmpegInputs4; 50 | } 51 | else if ((inputs[0].leafName.indexOf(' - DASH') !== -1 && prefs.doRemux) || options.doRemux) { 52 | cmd = prefs.ffmpegInputs3; 53 | } 54 | else { 55 | cmd = prefs.ffmpegInputs; 56 | } 57 | var tmp = /\-output\-location \"(.*)\"/.exec (cmd); 58 | if (tmp && tmp.length) { 59 | outputLocation = new FileUtils.File(tmp[1]); 60 | cmd = cmd.replace(/\-output\-location \".*\"/, ''); 61 | } 62 | // Determining output file extension 63 | if (options.mode === 'combine') { // video and audio combiner 64 | extensions[2] = extensions[1]; 65 | } 66 | else { 67 | if (extensions[0] === 'mp4') { 68 | extensions[2] = 'm4a'; 69 | } 70 | else if (extensions[0] === 'webm') { 71 | extensions[2] = options.extension ? options.extension : 'ogg'; 72 | } 73 | else { 74 | extensions[2] = extensions[0]; 75 | } 76 | } 77 | // has user determined the output extension? 78 | var tmp2 = /\%output\.(\S+)/.exec(cmd); 79 | if (tmp2 && tmp2.length) { 80 | extensions[2] = tmp2[1]; 81 | cmd = cmd.replace(/\%output\.\S+/, '%output'); 82 | } 83 | 84 | // Creating a temporary folder and copying input files 85 | var output = (function () { 86 | let parent = outputLocation || inputs[0].parent; 87 | let a = new FileUtils.File(parent.path); 88 | let name = (inputs[1] || inputs[0]).leafName.replace(' - DASH', '').replace(/\.+[^\.]*$/, ''); 89 | a.append(name + '.' + extensions[2]); 90 | a.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); 91 | let path = a.path; 92 | a.remove(false); 93 | return path; 94 | })(); 95 | 96 | // Preparing FFmpeg command for execution 97 | var args = cmd.trim().replace(/\s\s+/g, ' ').split(' '); 98 | args.forEach(function (arg, index) { 99 | args[index] = arg 100 | .replace('%audio', inputs[0].path) 101 | .replace('%input', inputs[0].path) 102 | .replace('%output', output); 103 | 104 | if (options.mode === 'combine') { 105 | args[index] = args[index].replace('%video', inputs[1].path); 106 | } 107 | }); 108 | // FFmpeg execution 109 | var ffmpeg = new FileUtils.File(ffmpegPath); 110 | if (!ffmpeg.exists()) { 111 | throw _('err13') + ' ' + ffmpeg.path; 112 | } 113 | var ww = childProcess.spawn(ffmpeg, args); 114 | var strout = '', stderr = ''; 115 | ww.stdout.on('data', function (data) { 116 | strout += data; 117 | }); 118 | ww.stderr.on('data', function (data) { 119 | stderr += data; 120 | }); 121 | ww.on('close', function (code) { 122 | if (code === 0) { 123 | if (options.deleteInputs && !isError(stderr)) { 124 | inputs.forEach(function (input) { 125 | input.remove(false); 126 | }); 127 | } 128 | if (isError(stderr)) { 129 | notify(_('name'), _('err24') + '\n\n' + stderr.substr(stderr.length - 500)); 130 | } 131 | 132 | if (callback) { 133 | callback.apply(pointer, []); 134 | } 135 | } 136 | else { 137 | notify(_('name'), _('err25') + ' ' + code); 138 | } 139 | }); 140 | }; 141 | -------------------------------------------------------------------------------- /src/lib/misc.js: -------------------------------------------------------------------------------- 1 | var {Cc, Ci, Cu} = require("chrome"), 2 | Request = require("sdk/request").Request, 3 | prefs = require("sdk/simple-prefs").prefs, 4 | data = require("sdk/self").data, 5 | windows = { 6 | get active () { // Chrome window 7 | return require('sdk/window/utils').getMostRecentBrowserWindow() 8 | } 9 | }; 10 | 11 | Cu.import("resource://gre/modules/DownloadUtils.jsm"); 12 | 13 | function format (size) { 14 | if (!size || size == "0") return NaN; 15 | return DownloadUtils.convertByteUnits(size).join(" "); 16 | } 17 | exports.format = format; 18 | 19 | /** Low level prefs **/ 20 | var _prefs = (function () { 21 | var pservice = Cc["@mozilla.org/preferences-service;1"]. 22 | getService(Ci.nsIPrefService). 23 | getBranch("extensions.feca4b87-3be4-43da-a1b1-137c24220968@jetpack."); 24 | return { 25 | getIntPref: pservice.getIntPref, 26 | setIntPref: pservice.setIntPref, 27 | getCharPref: pservice.getCharPref, 28 | setCharPref: pservice.setCharPref, 29 | getBoolPref: pservice.getBoolPref, 30 | setBoolPref: pservice.setBoolPref, 31 | getComplexValue: pservice.getComplexValue, 32 | clearUserPref: pservice.clearUserPref, 33 | setComplexValue: function (id, val) { 34 | var str = Cc["@mozilla.org/supports-string;1"] 35 | .createInstance(Ci.nsISupportsString); 36 | str.data = val; 37 | pservice.setComplexValue(id, Ci.nsISupportsString, str); 38 | }, 39 | setComplexFile: function (id, file) { 40 | pservice.setComplexValue(id, Ci.nsIFile, file); 41 | }, 42 | reset: function () { 43 | pservice.getChildList('',{}) 44 | .filter(n => n !== 'version' && n.indexOf('sdk') === -1 && n !== 'ffmpegPath') 45 | .forEach(n => pservice.clearUserPref(n)); 46 | } 47 | } 48 | })(); 49 | exports.prefs = _prefs; 50 | 51 | /** Prompt **/ 52 | var prompts = (function () { 53 | let prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"]. 54 | getService(Ci.nsIPromptService); 55 | return function (title, content, items) { 56 | var selected = {}; 57 | var result = prompts.select(null, title, content, items.length, items, selected); 58 | return [result, selected.value]; 59 | } 60 | })(); 61 | exports.prompts = prompts; 62 | 63 | /** Prompt2 **/ 64 | var prompts2 = (function () { 65 | let prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"] 66 | .getService(Ci.nsIPromptService); 67 | 68 | return function (title, content, strButton0, strButton1, strCheck, checked) { 69 | var check = {value: checked}; 70 | var flags = prompts.BUTTON_POS_0 * (strButton0 ? prompts.BUTTON_TITLE_IS_STRING : prompts.BUTTON_TITLE_YES) + 71 | prompts.BUTTON_POS_1 * (strButton1 ? prompts.BUTTON_TITLE_IS_STRING : prompts.BUTTON_TITLE_NO); 72 | var button = prompts.confirmEx(null, title, content, flags, strButton0 || "", strButton1 || "", "", strCheck, check); 73 | return { 74 | button: button, 75 | check: check 76 | } 77 | } 78 | })(); 79 | exports.prompts2 = prompts2; 80 | 81 | /** Calculate file size **/ 82 | var cache = {}; 83 | var calculate = function (url, callback, pointer) { 84 | if (cache[url]) { 85 | callback.apply(pointer, [url, format(cache[url])]); 86 | return; 87 | } 88 | var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] 89 | .createInstance(Ci.nsIXMLHttpRequest); 90 | req.onreadystatechange = function () { 91 | if (req.readyState === 4) { 92 | if (callback && typeof callback === 'function') { 93 | var size = null; 94 | try { 95 | size = req.getResponseHeader("Content-Length"); 96 | } 97 | catch (e){} 98 | if (size) { 99 | cache[url] = size; 100 | callback.apply(pointer, [url, format(size)]); 101 | } 102 | } 103 | } 104 | } 105 | req.open('HEAD', url, true); 106 | req.setRequestHeader('Cache-Control', 'no-cache'); 107 | req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; 108 | req.send(null); 109 | } 110 | exports.fileSize = calculate; 111 | 112 | /** Notifier **/ 113 | exports.notify = function (title, text) { 114 | if (!prefs.showNotifications) return; 115 | 116 | try { 117 | let alertServ = Cc["@mozilla.org/alerts-service;1"]. 118 | getService(Ci.nsIAlertsService); 119 | alertServ.showAlertNotification(data.url("report/open.png"), title, text); 120 | } 121 | catch (e) { 122 | let browser = windows.active.gBrowser, 123 | notificationBox = browser.getNotificationBox(); 124 | 125 | notification = notificationBox.appendNotification( 126 | text, 127 | 'jetpack-notification-box', 128 | data.url("report/open.png"), 129 | notificationBox.PRIORITY_INFO_MEDIUM, 130 | [] 131 | ); 132 | timer.setTimeout(function() { 133 | notification.close(); 134 | }, config.desktopNotification * 1000); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/webextension/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appDesc": { 3 | "message": "Download YouTube videos in all available formats and extract the original audio files" 4 | }, 5 | "title": { 6 | "message": "YouTube Video and Audio Downloader" 7 | }, 8 | "opt_legend_1": { 9 | "message": "FFmpeg Integration" 10 | }, 11 | "opt_legend_2": { 12 | "message": "Download Options" 13 | }, 14 | "opt_legend_3": { 15 | "message": "Saving Options" 16 | }, 17 | "opt_legend_4": { 18 | "message": "FFmpeg Commands" 19 | }, 20 | "opt_legend_5": { 21 | "message": "Misc" 22 | }, 23 | "opt_011": { 24 | "message": "(if FFmpeg is installed)" 25 | }, 26 | "opt_012": { 27 | "message": "Save" 28 | }, 29 | "opt_013": { 30 | "message": "Options saved." 31 | }, 32 | "opt_014": { 33 | "message": "Installed" 34 | }, 35 | "opt_017": { 36 | "message": "Install" 37 | }, 38 | "opt_018": { 39 | "message": "Wait..." 40 | }, 41 | "opt_015": { 42 | "message": "Required" 43 | }, 44 | "opt_016": { 45 | "message": "Error" 46 | }, 47 | "opt_l11": { 48 | "message": "Install native client (1/2)" 49 | }, 50 | "opt_l12": { 51 | "message": "Install FFmpeg (2/2)" 52 | }, 53 | "opt_l13": { 54 | "message": "FFmpeg path" 55 | }, 56 | "opt_211": { 57 | "message": "Combine video-only and audio-only files" 58 | }, 59 | "opt_212": { 60 | "message": "Always download HD quality audio for video-only files" 61 | }, 62 | "opt_213": { 63 | "message": "Remove downloaded file(s) after successful conversion" 64 | }, 65 | "opt_214": { 66 | "message": "Always ask for download location" 67 | }, 68 | "opt_215": { 69 | "message": "Post processing" 70 | }, 71 | "opt_2151": { 72 | "message": "Do nothing" 73 | }, 74 | "opt_2152": { 75 | "message": "Extract audio" 76 | }, 77 | "opt_2153": { 78 | "message": "Convert to MP3" 79 | }, 80 | "opt_216": { 81 | "message": "Consider \"OPUS\" audio-only streams when muxing with WebM video-only stream" 82 | }, 83 | "opt_311": { 84 | "message": "File-name pattern" 85 | }, 86 | "opt_312": { 87 | "message": "Save merged files in" 88 | }, 89 | "opt_411": { 90 | "message": "Audio Extraction" 91 | }, 92 | "opt_412": { 93 | "message": "Audio and Video combining" 94 | }, 95 | "opt_413": { 96 | "message": "MP3 Conversion" 97 | }, 98 | "opt_511": { 99 | "message": "Allow extension to use desktop notifications" 100 | }, 101 | "opt_512": { 102 | "message": "Display FAQs page on updates" 103 | }, 104 | "error_1": { 105 | "message": "Your OS is not yet supported" 106 | }, 107 | "error_2": { 108 | "message": "Cannot connect to api.github.com server" 109 | }, 110 | "error_3": { 111 | "message": "Cannot find the native client. Follow the 3 steps to install the native client" 112 | }, 113 | "error_4": { 114 | "message": "No DASH audio is detected" 115 | }, 116 | "error_5": { 117 | "message": "Cannot find the downloaded file!" 118 | }, 119 | "error_6": { 120 | "message": "Batch downloading is interrupted" 121 | }, 122 | "error_7": { 123 | "message": "Cannot find any streams in this format. Select another format" 124 | }, 125 | "error_8": { 126 | "message": "Cannot detect video ID. Refresh the tab might help!" 127 | }, 128 | "error_9": { 129 | "message": "Cannot detect the parent player or the parent player is too small" 130 | }, 131 | "error_10": { 132 | "message": "something went wrong/2; Are you sure the native client is installed? If not visit options page to install it." 133 | }, 134 | "error_11": { 135 | "message": "something went wrong/3; Are you sure the native client is installed? If not visit options page to install it." 136 | }, 137 | "error_12": { 138 | "message": "something went wrong/1; Are you sure the native client is installed? If not visit options page to install it." 139 | }, 140 | "message_1": { 141 | "message": "FFmpeg is installed successfully" 142 | }, 143 | "message_2": { 144 | "message": "For merging audio- and video-only files FFmpeg is required" 145 | }, 146 | "message_3": { 147 | "message": "Media Converter integration will be available soon!" 148 | }, 149 | "pp_1": { 150 | "message": "Quick Download" 151 | }, 152 | "pp_2": { 153 | "message": "Open YouTube" 154 | }, 155 | "pp_3": { 156 | "message": "Download Panel" 157 | }, 158 | "pp_4": { 159 | "message": "Conversion Tool" 160 | }, 161 | "pp_5": { 162 | "message": "Settings" 163 | }, 164 | "pp_6": { 165 | "message": "Format:" 166 | }, 167 | "pp_7": { 168 | "message": "Please wait" 169 | }, 170 | "pp_8": { 171 | "message": "Quality:" 172 | }, 173 | "pp_21": { 174 | "message": "Highest" 175 | }, 176 | "pp_22": { 177 | "message": "HD1080p" 178 | }, 179 | "pp_23": { 180 | "message": "HD720p" 181 | }, 182 | "pp_24": { 183 | "message": "High" 184 | }, 185 | "pp_25": { 186 | "message": "Medium" 187 | }, 188 | "pp_26": { 189 | "message": "Small" 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/webextension/data/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | YouTube Video and Audio Downloader :: Options 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 65 | 66 | 67 | 68 | 69 | 70 |
59 | 64 |
71 |
72 | 73 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 96 | 97 | 98 | 99 | 100 | 101 |
83 |
84 | 85 | 94 |
95 |
102 |
103 | 104 |
105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 |
127 |
128 | 129 |
130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
145 |
146 | 147 |
148 |
149 | 150 | 151 |
152 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/lib/download.js: -------------------------------------------------------------------------------- 1 | var {Cc, Ci, Cu} = require('chrome'); 2 | Cu.import("resource://gre/modules/Downloads.jsm"); 3 | 4 | var get, cancel; 5 | if (Downloads.getList) { // use Downloads.jsm 6 | Cu.import("resource://gre/modules/Promise.jsm"); 7 | var cache = []; 8 | get = function (callback, pointer) { 9 | return function (url, file, aPrivacyContext, aIsPrivate, callback2, pointer2) { 10 | Promise.all([ 11 | Downloads.createDownload({ 12 | source: { 13 | url: url, 14 | isPrivate: aIsPrivate 15 | }, 16 | target: file 17 | }), 18 | Downloads.getList(Downloads.PUBLIC) 19 | ]).then(function([dl, list]) { 20 | // Adapting to the old download object 21 | dl.id = Math.floor(Math.random()*100000); 22 | Object.defineProperty(dl, "amountTransferred", {get: function (){return dl.currentBytes}}); 23 | Object.defineProperty(dl, "size", {get: function (){return dl.totalBytes}}); 24 | // Observe progress 25 | list.add(dl); 26 | var view = { 27 | onDownloadChanged: function (d) { 28 | if (d != dl) return; 29 | if (callback && callback.progress) { 30 | callback.progress.apply(pointer, [dl]); 31 | } 32 | if (d.succeeded && d.stopped) { 33 | if (callback && callback.done) callback.done.apply(pointer, [dl]); 34 | } 35 | if (d.stopped && !(d.canceled || d.succeeded) && d.error) { 36 | if (callback && callback.error) callback.error.apply(pointer, [dl, d.error.message]); 37 | } 38 | if (d.stopped && !(d.canceled || d.succeeded) && !d.error) { 39 | if (callback && callback.paused) callback.paused.apply(pointer, [dl]); 40 | } 41 | if (d.stopped && d.canceled) { 42 | if (callback && callback.error) callback.error.apply(pointer, [dl]); 43 | } 44 | if (d.stopped) list.removeView(view); 45 | } 46 | }; 47 | list.addView(view); 48 | dl.start(); 49 | cache.push({id: dl.id, dl: dl}); 50 | if (callback2) callback2.apply(pointer2, [dl]); 51 | }, function (err) {throw err}); 52 | } 53 | } 54 | cancel = function (id) { 55 | cache.forEach (function (obj) { 56 | if (obj.id == id) obj.dl.cancel(); 57 | }); 58 | } 59 | } 60 | else { // Old 61 | var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService), 62 | dm = Cc["@mozilla.org/download-manager;1"].createInstance(Ci.nsIDownloadManager); 63 | get = function (callback, pointer) { 64 | var dl, 65 | persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(Ci.nsIWebBrowserPersist); 66 | persist.persistFlags = persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; 67 | 68 | var listener; 69 | var mListener = function (download) { 70 | this.download = download; 71 | } 72 | mListener.prototype = { 73 | download: null, 74 | onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage, aDownload) {}, 75 | onSecurityChange: function(prog, req, state, dl) { }, 76 | onProgressChange: function(prog, req, prog, progMax, tProg, tProgMax, dl) { 77 | if (dl.id != this.download.id) return; 78 | if (callback && callback.progress) callback.progress.apply(pointer, [dl]); 79 | }, 80 | onStateChange: function(prog, req, flags, status, dl) {}, 81 | onDownloadStateChange : function(state, dl) { 82 | if (dl.id != this.download.id) return; 83 | 84 | if (dl.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) { 85 | dm.removeListener(this); 86 | if (callback && callback.done) callback.done.apply(pointer, [dl]); 87 | } 88 | else if (dl.state == Ci.nsIDownloadManager.DOWNLOAD_PAUSED) { 89 | if (callback && callback.paused) callback.paused.apply(pointer, [dl]); 90 | } 91 | else if (dl.state == Ci.nsIDownloadManager.DOWNLOAD_FAILED || 92 | dl.state == Ci.nsIDownloadManager.DOWNLOAD_CANCELED || 93 | dl.state == Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL || 94 | dl.state == Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY || 95 | dl.state == Ci.nsIDownloadManager.DOWNLOAD_DIRTY) { 96 | dm.removeListener(this); 97 | if (callback && callback.error) callback.error.apply(pointer, [dl]); 98 | } 99 | } 100 | } 101 | 102 | return function (url, file, aPrivacyContext, aIsPrivate, callback2, pointer2) { 103 | // Create URI 104 | var urlURI = ioService.newURI(url, null, null), 105 | fileURI = ioService.newFileURI(file); 106 | // Start download, Currently the extension is not available on private mode due to panel module incompatibility 107 | dl = dm.addDownload(dm.DOWNLOAD_TYPE_DOWNLOAD, urlURI, fileURI, null, null, null, null, persist, aIsPrivate || false); 108 | listener = new mListener(dl); 109 | dm.addListener(listener); 110 | persist.progressListener = dl.QueryInterface(Ci.nsIWebProgressListener); 111 | persist.saveURI(dl.source, null, null, null, null, file, aPrivacyContext); 112 | 113 | if (callback2) callback2.apply(pointer2, [dl]); 114 | } 115 | }; 116 | cancel = function (id) { 117 | dm.cancelDownload(id); 118 | }; 119 | } 120 | exports.get = get; 121 | exports.cancel = cancel; 122 | 123 | exports.show = function () { 124 | Cc["@mozilla.org/download-manager-ui;1"] 125 | .createInstance(Ci.nsIDownloadManagerUI).show(); 126 | } -------------------------------------------------------------------------------- /src/lib/external.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var {Cc, Ci, Cu} = require('chrome'), 4 | os = Cc['@mozilla.org/xre/app-info;1'].getService(Ci.nsIXULRuntime).OS, 5 | arch = Cc['@mozilla.org/xre/app-info;1'].getService(Ci.nsIXULRuntime).is64Bit ? 'x64' : 'ia32', 6 | _ = require('sdk/l10n').get, 7 | timer = require('sdk/timers'), 8 | Request = require('sdk/request').Request, 9 | tools = require('./misc'), 10 | tools = require('./misc'), 11 | prefs = require('sdk/simple-prefs').prefs, 12 | childProcess = require('sdk/system/child_process'), 13 | {defer, resolve, reject} = require('sdk/core/promise'), 14 | _prefs = tools.prefs, 15 | notify = tools.notify, 16 | prompts2 = tools.prompts2, 17 | download = require('./download'), 18 | windows = { 19 | get active () { // Chrome window 20 | return require('sdk/window/utils').getMostRecentBrowserWindow(); 21 | } 22 | }; 23 | 24 | var {FileUtils} = Cu.import('resource://gre/modules/FileUtils.jsm'); 25 | 26 | function inWindows () { 27 | var d = defer(); 28 | try { 29 | var file = FileUtils.getFile('WinD', ['System32', 'where.exe']), stderr = '', stdout = ''; 30 | 31 | var win = childProcess.spawn(file, ['FFmpeg.exe']); 32 | win.stdout.on('data', function (data) { 33 | stdout += data; 34 | }); 35 | win.stderr.on('data', function (data) { 36 | stderr += data; 37 | }); 38 | win.on('close', function (code) { 39 | if (code === 0) { 40 | var path = /\w\:.*FFmpeg\.exe/i.exec(stdout); 41 | if (path) { 42 | d.resolve(path[0].replace(/\\/g, '\\')); 43 | } 44 | else { 45 | d.reject(Error(_('err23') + ' ' + stdout + ' ' + stderr)); 46 | } 47 | } 48 | else { 49 | d.reject(Error(_('err22') + ' ' + code)); 50 | } 51 | }); 52 | } 53 | catch (e) { 54 | d.reject(Error(_('err21') + ' ' + e)); 55 | } 56 | return d.promise; 57 | } 58 | function inLinux () { 59 | var {env} = require('sdk/system/environment'); 60 | var locs = env.PATH.split(':'); 61 | locs.push('/usr/local/bin'); 62 | locs = locs.filter((o, i, l) => l.indexOf(o) === i); 63 | for (var i = 0; i < locs.length; i++) { 64 | var file = FileUtils.File(locs[i]); 65 | file.append('ffmpeg'); 66 | if (file.exists()) { 67 | return resolve(file.path); 68 | } 69 | } 70 | return reject(Error(_('err21') + ' Not Found')); 71 | } 72 | 73 | var where = (os === 'WINNT') ? inWindows : inLinux; 74 | 75 | /** Install FFmpeg **/ 76 | function installFFmpeg () { 77 | notify(_('name'), _('msg28')); 78 | 79 | var file = FileUtils.getFile('ProfD', ['ffmpeg' + ((os === 'WINNT') ? '.exe' : '')]); 80 | if (file.exists() && file.fileSize > 10485760) { 81 | if (!windows.active.confirm(_('msg29'))) { 82 | return; 83 | } 84 | file.remove(false); 85 | } 86 | file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 755); 87 | 88 | new Request({ 89 | url: 'https://api.github.com/repos/inbasic/ffmpeg/releases/latest', 90 | onComplete: function (response) { 91 | if (response.status === 200) { 92 | let json = response.json; 93 | if (json) { 94 | let assets = json.assets; 95 | if (assets) { 96 | let entry = assets.filter(obj => obj.name.indexOf(os === 'WINNT' ? 'win32' : os.toLowerCase()) !== -1 && obj.name.indexOf(arch) !== -1); 97 | if (entry.length) { 98 | var dr = new download.get({ 99 | done: function () { 100 | if (file.fileSize > 10485760) { 101 | _prefs.setComplexFile('ffmpegPath', file); 102 | prefs.extension = 2; 103 | //make sure file has executable permission 104 | timer.setTimeout(() => file.permissions = 755, 500); 105 | notify(_('name'), _('msg26')); 106 | } 107 | else { 108 | notify(_('name'), _('err19')); 109 | } 110 | }, 111 | error: () => notify(_('name'), _('err17')) 112 | }); 113 | dr(entry[0].browser_download_url, file); 114 | } 115 | else { 116 | notify(_('name'), 'externals.js -> install -> cannot find a suitable FFmpeg for your OS'); 117 | } 118 | } 119 | else { 120 | notify(_('name'), 'externals.js -> install -> response does not have any asset'); 121 | } 122 | } 123 | else { 124 | notify(_('name'), 'externals.js -> install -> response is not a JSON object'); 125 | } 126 | } 127 | else { 128 | notify(_('name'), `install -> cannot connect to server; code: ${response.status}`); 129 | } 130 | } 131 | }).get(); 132 | } 133 | exports.installFFmpeg = installFFmpeg; 134 | 135 | exports.checkFFmpeg = function () { 136 | function runFFmpegInstaller() { 137 | var tmp = prompts2(_('msg27'), _('msg24'), '', '', _('msg21'), true); 138 | prefs.showFFmpegInstall = tmp.check.value; 139 | if (tmp.button === 0) { 140 | installFFmpeg(); 141 | } 142 | } 143 | timer.setTimeout(function () { 144 | where().then( 145 | function (path) { 146 | try { 147 | var file = new FileUtils.File(path); 148 | _prefs.setComplexFile('ffmpegPath', file); 149 | prefs.extension = 2; 150 | } 151 | catch (e) { 152 | runFFmpegInstaller(); 153 | } 154 | }, 155 | runFFmpegInstaller 156 | ); 157 | }, 4000); 158 | }; 159 | -------------------------------------------------------------------------------- /src/webextension/data/popup/index.js: -------------------------------------------------------------------------------- 1 | /* globals youtube, locale */ 2 | 'use strict'; 3 | var tab; 4 | 5 | function quickDownload () { 6 | return new Promise((resolve, reject) => { 7 | chrome.tabs.executeScript(tab.id, { 8 | allFrames: false, 9 | code: `id` 10 | }, ([id]) => { 11 | if (id) { 12 | chrome.storage.local.get({ 13 | doMerge: true, 14 | ffmpeg: '' 15 | }, prefs => { 16 | youtube.perform(id).then(info => { 17 | let format = document.querySelector('[name=format]:checked').id; 18 | let formats = info.formats.filter(o => o.container === format) 19 | .filter(o => o.dash !== 'a') 20 | .filter(o => !o.dash || (prefs.ffmpeg && prefs.doMerge)) 21 | .sort((a, b) => parseInt(b.resolution) - parseInt(a.resolution)); 22 | if (formats.length) { 23 | let quality = document.querySelector('[name=quality]:checked').id; 24 | if (quality === '1080p') { 25 | formats = [ 26 | formats.filter(o => o.resolution === '1080p').shift(), 27 | ...formats 28 | ].filter(o => o); 29 | } 30 | else if (quality === '720p') { 31 | formats = [ 32 | formats.filter(o => o.resolution === '720p').shift(), 33 | formats.filter(o => o.resolution === '1080p').shift(), 34 | ...formats 35 | ].filter(o => o); 36 | } 37 | else if (quality === 'high') { 38 | formats = [ 39 | formats.filter(o => o.resolution === '480p').shift(), 40 | formats.filter(o => o.resolution === '360p').shift(), 41 | formats.filter(o => o.resolution === '720p').shift(), 42 | formats.filter(o => o.resolution === '1080p').shift(), 43 | ...formats 44 | ].filter(o => o); 45 | } 46 | else if (quality === 'medium') { 47 | formats = [ 48 | formats.filter(o => o.resolution === '240p').shift(), 49 | formats.filter(o => o.resolution === '360p').shift(), 50 | formats.filter(o => o.resolution === '480p').shift(), 51 | formats.filter(o => o.resolution === '720p').shift(), 52 | formats.filter(o => o.resolution === '1080p').shift(), 53 | ...formats 54 | ].filter(o => o); 55 | } 56 | else if (quality === 'small') { 57 | formats = formats.reverse(); 58 | } 59 | chrome.runtime.sendMessage({ 60 | method: 'download', 61 | info, 62 | itag: formats[0].itag 63 | }); 64 | resolve(); 65 | } 66 | else { 67 | reject(new Error('error_7')); 68 | } 69 | }).catch(reject); 70 | }); 71 | } 72 | else { 73 | reject(new Error('error_8')); 74 | } 75 | }); 76 | }); 77 | } 78 | 79 | document.addEventListener('click', e => { 80 | const cmd = e.target.dataset.cmd; 81 | if (cmd === 'more') { 82 | document.body.dataset.settings = document.body.dataset.settings === 'false'; 83 | e.preventDefault(); 84 | } 85 | else { 86 | if (!e.target.closest('#settings')) { 87 | document.body.dataset.settings = false; 88 | } 89 | } 90 | }); 91 | document.addEventListener('click', e => { 92 | const cmd = e.target.dataset.cmd || e.target.closest('div').dataset.cmd; 93 | if (cmd === 'display-panel') { 94 | chrome.tabs.query({ 95 | active: true, 96 | currentWindow: true 97 | }, ([tab]) => { 98 | chrome.runtime.sendMessage({ 99 | method: 'display-panel', 100 | tabId: tab.id 101 | }, () => window.close()); 102 | }); 103 | } 104 | else if (cmd === 'open-converter') { 105 | chrome.runtime.sendMessage({ 106 | method: 'message', 107 | message: 'message_3' 108 | }, () => window.close()); 109 | } 110 | else if (cmd === 'open-options') { 111 | chrome.runtime.openOptionsPage(() => { 112 | if (chrome.runtime.lastError) { // TO-DO: Remove this after FF 57 release 113 | chrome.tabs.create({ 114 | url: '/data/options/index.html' 115 | }, window.close); 116 | } 117 | }); 118 | } 119 | else if (cmd === 'open-youtube') { 120 | chrome.tabs.create({ 121 | url: 'https://www.youtube.com/' 122 | }, () => window.close()); 123 | } 124 | else if (cmd === 'quick-download') { 125 | let div = document.querySelector('[data-cmd="quick-download"]'); 126 | div.dataset.working = true; 127 | div.querySelector('span').textContent = locale.get('pp_7'); 128 | quickDownload().then( 129 | window.close, 130 | error => { 131 | chrome.runtime.sendMessage({ 132 | method: 'error', 133 | error: error.message || error 134 | }); 135 | div.dataset.working = false; 136 | div.querySelector('span').textContent = locale.get('pp_1'); 137 | } 138 | ); 139 | } 140 | }); 141 | 142 | // persist 143 | document.addEventListener('change', e => { 144 | const target = e.target; 145 | if (target.name === 'format' || target.name === 'quality') { 146 | chrome.storage.local.set({ 147 | [target.name]: target.id 148 | }); 149 | } 150 | }); 151 | // init 152 | chrome.tabs.query({ 153 | active: true, 154 | currentWindow: true 155 | }, ([t]) => { 156 | tab = t; 157 | const isYouTube = tab.url && tab.url.indexOf('www.youtube.com') !== -1; 158 | document.body.dataset.youtube = !!isYouTube; 159 | if (isYouTube) { 160 | document.querySelector('[data-cmd="open-youtube"]').dataset.cmd = 'quick-download'; 161 | } 162 | }); 163 | 164 | chrome.storage.local.get({ 165 | quality: 'highest', 166 | format: 'mp4' 167 | }, prefs => { 168 | document.getElementById(prefs.quality).checked = true; 169 | document.getElementById(prefs.format).checked = true; 170 | }); 171 | 172 | -------------------------------------------------------------------------------- /src/lib/extract.js: -------------------------------------------------------------------------------- 1 | var _ = require("sdk/l10n").get, 2 | timer = require("sdk/timers"), 3 | {Cc, Ci, Cu} = require('chrome'); 4 | 5 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 6 | Cu.import("resource://gre/modules/NetUtil.jsm"); 7 | Cu.import("resource://gre/modules/FileUtils.jsm"); 8 | 9 | /** Read stream **/ 10 | function read(iFile, callback, pointer) { 11 | var data = "", 12 | uri = NetUtil.ioService.newFileURI(iFile); 13 | channel = NetUtil.ioService.newChannelFromURI(uri); 14 | 15 | channel.asyncOpen({ 16 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver, Ci.nsIStreamListener]), 17 | onStartRequest: function(request, context) {}, 18 | onDataAvailable: function(request, context, stream, offset, count) { 19 | data += NetUtil.readInputStreamToString(stream, count); 20 | }, 21 | onStopRequest: function(request, context, result) { 22 | callback.apply(pointer, [data]) 23 | } 24 | }, null); 25 | } 26 | 27 | /** Write stream **/ 28 | function write (oFile, data) { 29 | var fileStream = FileUtils.openFileOutputStream(oFile, 30 | FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE); 31 | 32 | fileStream.write(data, data.length); 33 | if (fileStream instanceof Ci.nsISafeOutputStream) { 34 | fileStream.finish(); 35 | } else { 36 | fileStream.close(); 37 | } 38 | } 39 | 40 | /** Extract Audio **/ 41 | function extract(stream, callback, pointer) { 42 | var pointer = 0, audio = "", oldPercent = 0, stack = 0; 43 | 44 | if (stream.slice(0,3) != "FLV") { 45 | throw Error(_("err1")); 46 | } 47 | 48 | var readInt = function () { return stream[++pointer].charCodeAt(0);} 49 | var readByte = function () { return readInt().toString(16); } 50 | var read3Bytes = function () { return readInt() * 65536 + readInt() * 256 + readInt();} 51 | 52 | pointer = 2; 53 | var version = readByte(); 54 | var exists = readByte(); 55 | 56 | if (exists != 5 && exists != 4) { 57 | throw Error(_("err2")); 58 | } 59 | 60 | pointer = 8 61 | var _aacProfile, _sampleRateIndex, _channelConfig; 62 | function oneChunk () { 63 | pointer += 4; // PreviousTagSize0 skipping 64 | 65 | if (pointer >= stream.length - 1) { 66 | if (callback && callback.done) { 67 | callback.done.apply(pointer, [audio]); 68 | } 69 | return; 70 | } 71 | 72 | var tagType = readByte(); 73 | while (tagType != 8) { 74 | var skip = read3Bytes() + 11; 75 | pointer += skip; 76 | if (pointer >= stream.length - 1) { 77 | if (callback && callback.done) { 78 | callback.done.apply(pointer, [audio]); 79 | } 80 | return; 81 | } 82 | tagType = readByte(); 83 | } 84 | 85 | var dataSize = read3Bytes(); 86 | read3Bytes(); //skip timestamps 87 | readByte(); //skip TimestampExtended 88 | read3Bytes(); //skip streamID 89 | 90 | var mediaInfo = readInt(), 91 | format = mediaInfo >> 4, 92 | rate = (mediaInfo >> 2) & 0x3, 93 | bits = (mediaInfo >> 1) & 0x1, 94 | chans = mediaInfo & 0x1; 95 | dataSize -= 1; 96 | 97 | var chunk = stream.slice(pointer+2, pointer + dataSize + 1); 98 | // AAC [http://wiki.multimedia.cx/index.php?title=Understanding_AAC] 99 | if (format == 10) { 100 | if(stream[pointer+1].charCodeAt(0).toString(16) == 0) { //Header 101 | var byte1 = stream[pointer+2].charCodeAt(0), 102 | byte2 = stream[pointer+3].charCodeAt(0); 103 | 104 | _aacProfile = ((byte1 & 0xF8) >> 3) - 1; //11111000 105 | _sampleRateIndex = ((byte1 & 0x7) << 1) + //00000111 106 | ((byte2 & 0x80) >> 7); //10000000 107 | _channelConfig = (byte2 & 0x78) >> 3; //01111000 108 | 109 | if ((_aacProfile < 0) || (_aacProfile > 3)) { 110 | throw Error(_("err3")); 111 | } 112 | if (_sampleRateIndex > 12) { 113 | throw Error(_("err4")); 114 | } 115 | if (_channelConfig > 6) { 116 | throw Error(_("err5")); 117 | } 118 | } 119 | else { //Audio data 120 | var buffer = new ArrayBuffer(8), 121 | bytes8 = new Uint8Array(buffer), 122 | bytes32 = new Uint32Array(buffer); 123 | bytes8[6] = 0xFF; 124 | bytes8[5] = 0xF1; 125 | bytes8[4] = (_aacProfile << 6) + (_sampleRateIndex << 2) + ((_channelConfig & 8) << 2); 126 | bytes32[0] = (_channelConfig & 3) << 30 127 | bytes32[0] += (chunk.length + 7) << 13; 128 | bytes32[0] += 0x7FF << 2; 129 | audio += String.fromCharCode.apply(null, bytes8.subarray(0,7)).split("").reverse().join(""); 130 | audio += chunk; 131 | } 132 | pointer += dataSize; 133 | } 134 | else { 135 | throw Error(_("err6")); 136 | } 137 | if (callback && callback.progress) { 138 | var percent = pointer/stream.length * 100; 139 | if (percent > oldPercent + 5) { 140 | oldPercent = percent; 141 | callback.progress.apply(pointer, [pointer/stream.length * 100]); 142 | } 143 | } 144 | //async call to prevent stack overflow for long extractions 145 | stack += 1; 146 | if (stack == 1000) { 147 | stack = 0; 148 | timer.setTimeout(function () {oneChunk();}, 0); 149 | } 150 | else { 151 | oneChunk(); 152 | } 153 | } 154 | oneChunk(); 155 | } 156 | 157 | exports.perform = function (id, iFile, oFile, done, progress, pointer) { 158 | read(iFile, function (stream) { 159 | try { 160 | extract(stream, { 161 | done: function (audio) { 162 | write(oFile, audio); 163 | done.apply(pointer, [id]); 164 | }, 165 | progress: function (percent) { 166 | if (progress) { 167 | progress.apply(pointer, [id, percent]); 168 | } 169 | } 170 | }); 171 | } 172 | catch(e) { 173 | write(oFile, e.toString()); 174 | oFile.moveTo(null, oFile.leafName + ".error.log"); 175 | done.apply(pointer, [id, e]); 176 | } 177 | }); 178 | } -------------------------------------------------------------------------------- /src/webextension/download.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var download = {}; 4 | 5 | download.tagInfo = (opusmixing = false) => { 6 | const audio = { 7 | m4a: [141, 140, 139], 8 | get ogg () { 9 | return opusmixing ? [172, 251, 171, 250, 249] : [172, 171]; // ogg or opus 10 | }, 11 | opus: [251, 250, 249] 12 | }; 13 | const video = { 14 | mp4: { 15 | low: [134, 133, 160], 16 | medium: [135], 17 | high: [138, 266, 264, 299, 137, 298, 136] 18 | }, 19 | webm: { 20 | low: [243, 242, 278], 21 | medium: [246, 245, 244], 22 | high: [315, 272, 313, 308, 271, 303, 248, 302, 247] 23 | } 24 | }; 25 | return { 26 | list: { 27 | audio: [ 28 | ...audio.m4a, 29 | ...audio.ogg 30 | ], 31 | video: [ 32 | ...video.mp4.low, 33 | ...video.mp4.medium, 34 | ...video.mp4.high, 35 | ...video.webm.low, 36 | ...video.webm.medium, 37 | ...video.webm.high 38 | ] 39 | }, 40 | audio, 41 | video 42 | }; 43 | }; 44 | 45 | // select a proper audio track 46 | download.guessAudio = (vInfo, info, pretendHD = false, opusmixing = false) => { 47 | function sort (tmp, toArr) { 48 | return tmp.sort((a, b) => { 49 | const i = toArr.indexOf(a.itag), 50 | j = toArr.indexOf(b.itag); 51 | if (i === -1 && j === -1) { 52 | return 0; 53 | } 54 | if (i !== -1 && j === -1) { 55 | return -1; 56 | } 57 | if (i === -1 && j !== -1) { 58 | return +1; 59 | } 60 | return i > j; 61 | }); 62 | } 63 | let tmp = info.formats.filter((a) => a.dash === 'a'); 64 | let tagInfo = download.tagInfo(opusmixing); 65 | if (tmp && tmp.length) { 66 | if (tagInfo.video.mp4.low.indexOf(vInfo.itag) !== -1 && !pretendHD) { // Low quality (mp4) 67 | tmp = sort(tmp, [140, 171, 139, 172, 141]); 68 | } 69 | else if (tagInfo.video.webm.low.indexOf(vInfo.itag) !== -1 && !pretendHD) { // Low quality (webm) 70 | tmp = sort(tmp, tagInfo.audio.ogg.reverse()); 71 | } 72 | else if (tagInfo.video.mp4.medium.indexOf(vInfo.itag) !== -1 && !pretendHD) { // Medium quality (mp4) 73 | tmp = sort(tmp, [140, 171, 172, 141, 139]); 74 | } 75 | else if (tagInfo.video.webm.medium.indexOf(vInfo.itag) !== -1 && !pretendHD) { // Medium quality (webm) 76 | tmp = sort(tmp, tagInfo.audio.ogg.reverse()); 77 | } 78 | else { 79 | if ([ 80 | ...tagInfo.video.webm.low, 81 | ...tagInfo.video.webm.medium, 82 | ...tagInfo.video.webm.high 83 | ].indexOf(vInfo.itag) !== -1) { //High quality (webm) 84 | tmp = sort(tmp, tagInfo.audio.ogg); 85 | } 86 | else { //High quality (mp4) 87 | if (opusmixing) { 88 | // if opus is selected, make sure to use mkv container 89 | tmp = sort(tmp, [141, 172, 251, 140, 171, 250, 249, 139]); 90 | } 91 | else { 92 | tmp = sort(tmp, [141, 172, 140, 171, 139]); 93 | } 94 | } 95 | } 96 | return tmp[0]; 97 | } 98 | throw Error('error_4'); 99 | }; 100 | 101 | download.get = ({info, itag}, {doMerge, pretendHD, opusmixing, saveAs}) => { 102 | let vInfo = info.formats.filter(f => f.itag === +itag).pop(); 103 | let ds = [vInfo]; 104 | if (doMerge && vInfo.dash === 'v') { 105 | let aInfo = download.guessAudio(vInfo, info, pretendHD, opusmixing); 106 | ds.push(aInfo); 107 | } 108 | let ids = []; 109 | 110 | function search (id) { 111 | return new Promise((resolve, reject) => { 112 | chrome.downloads.search({id}, ([d]) => { 113 | if (d) { 114 | resolve(d); 115 | } 116 | else { 117 | reject(new Error('error_5')); 118 | } 119 | }); 120 | }); 121 | } 122 | 123 | function cancel (id) { 124 | return new Promise(resolve => { 125 | chrome.downloads.cancel(id, resolve); 126 | }); 127 | } 128 | 129 | function dl (obj) { 130 | if (navigator.userAgent.indexOf('Firefox') === -1) { 131 | return new Promise((resolve, reject) => { 132 | chrome.downloads.download(obj, (id) => { 133 | let error = chrome.runtime.lastError; 134 | return error ? reject(error) : resolve(id); 135 | }); 136 | }); 137 | } 138 | else { 139 | // in Firefox "chrome.downloads.download" does not return id! 140 | return browser.downloads.download(obj); //jshint ignore:line 141 | } 142 | } 143 | 144 | return new Promise((resolve, reject) => { 145 | function observe (d) { 146 | if (!d.state || d.state.current === 'in_progress') { 147 | return; 148 | } 149 | const index = ids.indexOf(d.id); 150 | if (index !== -1) { 151 | ids.splice(index, 1); 152 | } 153 | else { 154 | return; 155 | } 156 | if (ids.length === 0 || d.state.current === 'interrupted') { 157 | chrome.downloads.onChanged.removeListener(observe); 158 | } 159 | if (d.state.current === 'interrupted' && ids.length) { 160 | Promise.all(ids.map(id => cancel(id))).then(download.badge); 161 | ids = []; 162 | } 163 | else { 164 | download.badge(); 165 | } 166 | if (ids.length === 0) { 167 | if (d.state.current === 'complete') { 168 | Promise.all(ds.map(d => { 169 | return search(d.id).then(o => Object.assign(d, o)); 170 | })).then( 171 | ds => resolve(ds), 172 | e => reject(e) 173 | ); 174 | } 175 | else { 176 | reject(new Error('error_6')); 177 | } 178 | } 179 | } 180 | Promise.all(ds.map(o => { 181 | return dl({ 182 | url: o.url, 183 | saveAs, 184 | filename: o.name 185 | }); 186 | })).then(os => { 187 | ids = os; 188 | ids.forEach((id, i) => ds[i].id = id); 189 | chrome.downloads.onChanged.addListener(observe); 190 | download.badge(); 191 | }).catch(e => reject(e)); 192 | }); 193 | }; 194 | 195 | download.badge = () => { 196 | chrome.downloads.search({ 197 | state: 'in_progress' 198 | }, ds => { 199 | ds = ds.filter(d => d.byExtensionId === chrome.runtime.id); 200 | chrome.browserAction.setBadgeText({ 201 | text: ds.length ? ds.length + '' : '' 202 | }); 203 | }); 204 | }; 205 | -------------------------------------------------------------------------------- /src/data/overlay.css: -------------------------------------------------------------------------------- 1 | #youtube-audio-converter, 2 | #youtube-audio-converter[cui-areatype="toolbar"] { 3 | list-style-image: url('toolbar-16.png') !important; 4 | -moz-image-region: rect(0, 16px, 16px, 0); 5 | } 6 | #youtube-audio-converter[progress="0"] { 7 | -moz-image-region: rect(0, 32px, 16px, 16px); 8 | } 9 | #youtube-audio-converter[progress="1"] { 10 | -moz-image-region: rect(0, 48px, 16px, 32px); 11 | } 12 | #youtube-audio-converter[progress="2"] { 13 | -moz-image-region: rect(0, 64px, 16px, 48px); 14 | } 15 | #youtube-audio-converter[progress="3"] { 16 | -moz-image-region: rect(0, 80px, 16px, 64px); 17 | } 18 | #youtube-audio-converter[progress="4"] { 19 | -moz-image-region: rect(0, 96px, 16px, 80px); 20 | } 21 | #youtube-audio-converter[progress="5"] { 22 | -moz-image-region: rect(0, 112px, 16px, 96px); 23 | } 24 | #youtube-audio-converter[progress="6"] { 25 | -moz-image-region: rect(0, 128px, 16px, 112px); 26 | } 27 | #youtube-audio-converter[progress="7"] { 28 | -moz-image-region: rect(0, 144px, 16px, 128px); 29 | } 30 | #youtube-audio-converter[progress="8"] { 31 | -moz-image-region: rect(0, 160px, 16px, 144px); 32 | } 33 | 34 | #youtube-audio-converter[type="gray"] { 35 | -moz-image-region: rect(16px, 16px, 32px, 0); 36 | } 37 | #youtube-audio-converter[type="gray"][progress="0"] { 38 | -moz-image-region: rect(16px, 32px, 32px, 16px); 39 | } 40 | #youtube-audio-converter[type="gray"][progress="1"] { 41 | -moz-image-region: rect(16px, 48px, 32px, 32px); 42 | } 43 | #youtube-audio-converter[type="gray"][progress="2"] { 44 | -moz-image-region: rect(16px, 64px, 32px, 48px); 45 | } 46 | #youtube-audio-converter[type="gray"][progress="3"] { 47 | -moz-image-region: rect(16px, 80px, 32px, 64px); 48 | } 49 | #youtube-audio-converter[type="gray"][progress="4"] { 50 | -moz-image-region: rect(16px, 96px, 32px, 80px); 51 | } 52 | #youtube-audio-converter[type="gray"][progress="5"] { 53 | -moz-image-region: rect(16px, 112px, 32px, 96px); 54 | } 55 | #youtube-audio-converter[type="gray"][progress="6"] { 56 | -moz-image-region: rect(16px, 128px, 32px, 112px); 57 | } 58 | #youtube-audio-converter[type="gray"][progress="7"] { 59 | -moz-image-region: rect(16px, 144px, 32px, 128px); 60 | } 61 | #youtube-audio-converter[type="gray"][progress="8"] { 62 | -moz-image-region: rect(16px, 160px, 32px, 144px); 63 | } 64 | 65 | #youtube-audio-converter[cui-areatype="menu-panel"], 66 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter { 67 | list-style-image: url('toolbar-32.png') !important; 68 | -moz-image-region: rect(0, 32px, 32px, 0); 69 | } 70 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="0"], 71 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="0"] { 72 | -moz-image-region: rect(0, 64px, 32px, 32px); 73 | } 74 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="1"], 75 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="1"] { 76 | -moz-image-region: rect(0, 96px, 32px, 64px); 77 | } 78 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="2"], 79 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="2"] { 80 | -moz-image-region: rect(0, 128px, 32px, 96px); 81 | } 82 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="3"], 83 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="3"] { 84 | -moz-image-region: rect(0, 160px, 32px, 128px); 85 | } 86 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="4"], 87 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="4"] { 88 | -moz-image-region: rect(0, 192px, 32px, 160px); 89 | } 90 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="5"], 91 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="5"] { 92 | -moz-image-region: rect(0, 224px, 32px, 192px); 93 | } 94 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="6"], 95 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="6"] { 96 | -moz-image-region: rect(0, 256px, 32px, 224px); 97 | } 98 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="7"], 99 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="7"] { 100 | -moz-image-region: rect(0, 288px, 32px, 256px); 101 | } 102 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="8"], 103 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="8"] { 104 | -moz-image-region: rect(0, 320px, 32px, 288px); 105 | } 106 | 107 | 108 | #youtube-audio-converter[cui-areatype="menu-panel"][type="gray"], 109 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[type="gray"] { 110 | list-style-image: url('toolbar-32.png') !important; 111 | -moz-image-region: rect(32px, 32px, 64px, 0); 112 | } 113 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="0"][type="gray"], 114 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="0"][type="gray"] { 115 | -moz-image-region: rect(32px, 64px, 64px, 32px); 116 | } 117 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="1"][type="gray"], 118 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="1"][type="gray"] { 119 | -moz-image-region: rect(32px, 96px, 64px, 64px); 120 | } 121 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="2"][type="gray"], 122 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="2"][type="gray"] { 123 | -moz-image-region: rect(32px, 128px, 64px, 96px); 124 | } 125 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="3"][type="gray"], 126 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="3"][type="gray"] { 127 | -moz-image-region: rect(32px, 160px, 64px, 128px); 128 | } 129 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="4"][type="gray"], 130 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="4"][type="gray"] { 131 | -moz-image-region: rect(32px, 192px, 64px, 160px); 132 | } 133 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="5"][type="gray"], 134 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="5"][type="gray"] { 135 | -moz-image-region: rect(32px, 224px, 64px, 192px); 136 | } 137 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="6"][type="gray"], 138 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="6"][type="gray"] { 139 | -moz-image-region: rect(32px, 256px, 64px, 224px); 140 | } 141 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="7"][type="gray"], 142 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="7"][type="gray"] { 143 | -moz-image-region: rect(32px, 288px, 64px, 256px); 144 | } 145 | #youtube-audio-converter[cui-areatype="menu-panel"][progress="8"][type="gray"], 146 | toolbarpaletteitem[place="palette"] > #youtube-audio-converter[progress="8"][type="gray"] { 147 | -moz-image-region: rect(32px, 320px, 64px, 288px); 148 | } -------------------------------------------------------------------------------- /src/webextension/common.js: -------------------------------------------------------------------------------- 1 | /* globals download, ffmpeg, locale */ 2 | 'use strict'; 3 | 4 | function close (id) { 5 | chrome.tabs.executeScript(id, { 6 | 'runAt': 'document_start', 7 | 'code': ` 8 | [...document.querySelectorAll('.iaextractor-webx-iframe')].forEach(p => p.parentNode.removeChild(p)); 9 | ` 10 | }); 11 | } 12 | 13 | function notify (request) { 14 | chrome.storage.local.get({ 15 | notification: true 16 | }, prefs => { 17 | if (prefs.notification) { 18 | let message = request.error || request.message || request; 19 | message = locale.get(message); 20 | let optns = Object.assign({ 21 | type: 'basic', 22 | iconUrl: '/data/icons/48.png', 23 | title: locale.get('title'), 24 | message 25 | }, { 26 | //requireInteraction: request.requireInteraction, 27 | isClickable: request.isClickable 28 | }); 29 | chrome.notifications.create(optns); 30 | } 31 | }); 32 | } 33 | 34 | chrome.runtime.onMessage.addListener((request, sender, response) => { 35 | if (request.method === 'error' || request.method === 'message') { 36 | notify(request); 37 | } 38 | else if (request.method === 'display-panel') { 39 | let id = request.tabId || sender.tab.id; 40 | chrome.tabs.executeScript(id, { 41 | 'runAt': 'document_start', 42 | 'code': `!!document.querySelector('.iaextractor-webx-iframe')` 43 | }, rs => { 44 | const error = chrome.runtime.lastError; 45 | let r = rs && rs[0]; 46 | if (error) { 47 | notify(error); 48 | } 49 | else if (r) { 50 | close(id); 51 | } 52 | else { 53 | chrome.tabs.executeScript(id, { 54 | 'runAt': 'document_start', 55 | 'file': '/data/inject/panel.js' 56 | }); 57 | } 58 | }); 59 | } 60 | else if (request.method === 'download') { 61 | chrome.storage.local.get({ 62 | pretendHD: false, 63 | opusmixing: false, 64 | doMerge: true, 65 | toAudio: true, 66 | toMP3: false, 67 | remove: true, 68 | ffmpeg: '', 69 | savein: '', 70 | saveAs: false, 71 | commands: { 72 | toMP3: '-loglevel error -i %input -q:a 0 %output', 73 | toAudio: '-loglevel error -i %input -acodec copy -vn %output', 74 | muxing: '-loglevel error -i %audio -i %video -acodec copy -vcodec copy %output' 75 | } 76 | }, prefs => { 77 | download.get(request, prefs).then( 78 | ds => { 79 | let vInfo = ds.filter(d => d.dash !== 'a').shift() || ds[0]; 80 | let aInfo = ds.filter(d => d !== vInfo).shift(); 81 | let root = prefs.savein || ffmpeg.parent(vInfo.filename); 82 | let [leafname, extension] = ffmpeg.extract(vInfo.filename); 83 | 84 | // audio and video muxing 85 | if (ds.length === 2 && prefs.doMerge && !prefs.ffmpeg) { 86 | notify({ 87 | message: 'message_2', 88 | isClickable: true, 89 | requireInteraction: true 90 | }); 91 | window.setTimeout(() => chrome.runtime.openOptionsPage(), 2000); 92 | } 93 | else if (ds.length === 2 && prefs.doMerge) { 94 | // we now allow OPUS for muxing; if vInfo === 'mp4', change container to MKV 95 | if (extension === 'mp4' && prefs.opusmixing) { 96 | extension = 'mkv'; 97 | } 98 | leafname = leafname.replace(' - DASH', ''); 99 | ffmpeg.resolve(root, leafname, extension).then(output => { 100 | return ffmpeg.convert(prefs.ffmpeg, prefs.commands.muxing, { 101 | '%audio': aInfo.filename, 102 | '%video': vInfo.filename, 103 | '%output': output 104 | }); 105 | }).then(() => { 106 | if (prefs.remove) { 107 | return ffmpeg.remove([aInfo.filename, vInfo.filename]); 108 | } 109 | }).catch(e => notify(e)); 110 | } 111 | // extract audio 112 | else if (ds.length === 1 && vInfo.dash !== 'v' && prefs.toAudio && prefs.ffmpeg) { 113 | switch (extension) { 114 | case 'weba': 115 | case 'webm': 116 | case 'ogg': 117 | case 'vorbis': 118 | extension = 'ogg'; 119 | break; 120 | case 'opus': 121 | break; 122 | case 'aac': 123 | break; 124 | case 'mp4': 125 | case 'm4a': 126 | extension = 'm4a'; 127 | break; 128 | default: 129 | extension = 'mka'; // "MKA" container format can store a huge number of audio codecs. 130 | } 131 | leafname = leafname.replace(' - DASH', ''); 132 | ffmpeg.resolve(root, leafname, extension).then(output => { 133 | return ffmpeg.convert(prefs.ffmpeg, prefs.commands.toAudio, { 134 | '%input': vInfo.filename, 135 | '%output': output 136 | }); 137 | }).then(() => { 138 | if (prefs.remove) { 139 | return ffmpeg.remove([vInfo.filename]); 140 | } 141 | }).catch(e => notify(e)); 142 | } 143 | // convert to mp3 144 | else if (ds.length === 1 && vInfo.dash !== 'v' && prefs.toMP3 && prefs.ffmpeg) { 145 | ffmpeg.resolve(root, leafname, 'mp3').then(output => { 146 | return ffmpeg.convert(prefs.ffmpeg, prefs.commands.toMP3, { 147 | '%input': vInfo.filename, 148 | '%output': output 149 | }); 150 | }).catch(e =>notify(e)); 151 | } 152 | }, 153 | e => notify(e) 154 | ); 155 | }); 156 | } 157 | else if (request.method === 'fetch') { 158 | const req = new XMLHttpRequest(); 159 | req.open(request.type, request.url); 160 | req.onload = () => response({ 161 | req 162 | }); 163 | req.onerror = (error) => response({ 164 | error 165 | }); 166 | req.send(); 167 | return true; 168 | } 169 | // 170 | if (request.method === 'close-panel') { 171 | let id = request.tabId || sender.tab.id; 172 | close(id); 173 | } 174 | }); 175 | 176 | // FAQs 177 | window.setTimeout(() => { 178 | chrome.storage.local.get({ 179 | 'version': null, 180 | 'faqs': true 181 | }, prefs => { 182 | let version = chrome.runtime.getManifest().version; 183 | 184 | if (prefs.version ? (prefs.faqs && prefs.version !== version) : true) { 185 | chrome.storage.local.set({version}, () => { 186 | chrome.tabs.create({ 187 | url: 'http://technologyto.com/extractor.html?version=' + version + 188 | '&type=' + (prefs.version ? ('upgrade&p=' + prefs.version) : 'install') 189 | }); 190 | }); 191 | } 192 | }); 193 | }, 3000); 194 | 195 | // FF connect 196 | if (navigator.userAgent.indexOf('Firefox') !== -1) { // TO-DO; remove this on FF57 release 197 | const channel = chrome.runtime.connect({ 198 | name: 'sdk-channel' 199 | }); 200 | channel.onMessage.addListener(request => { 201 | if (request.method === 'open-options') { 202 | chrome.tabs.create({ 203 | url: '/data/options/index.html' 204 | }); 205 | } 206 | else if (request.method === 'prefs') { 207 | chrome.storage.local.set(request.prefs); 208 | } 209 | }); 210 | } 211 | -------------------------------------------------------------------------------- /src/data/formats/permanent.css: -------------------------------------------------------------------------------- 1 | 2 | /** Menu **/ 3 | #iaextractor-menu { 4 | position: relative; 5 | font-family: sans-serif,Roboto,arial; 6 | background: #FF6C09!important; 7 | color: #FFF!important; 8 | -moz-user-select: none; 9 | z-index: 1999999998; 10 | overflow: hidden; 11 | word-spacing: 0!important; 12 | letter-spacing: 0!important; 13 | } 14 | #iaextractor-menu a { 15 | color: #FFF!important; 16 | } 17 | #iaextractor-menu span[type=title] { 18 | display: block; 19 | height: 30px; 20 | line-height: 36px; 21 | font-size: 16px!important; 22 | text-align: center; 23 | color: #FFF!important; 24 | } 25 | #iaextractor-tabs { 26 | display: none; 27 | overflow: hidden; 28 | position: absolute; 29 | bottom: 0; 30 | font-size: 16px!important; 31 | text-align: center; 32 | line-height: 30px; 33 | } 34 | #iaextractor-tabs span { 35 | float: left; 36 | cursor: pointer; 37 | color: #FFF!important; 38 | } 39 | #iaextractor-tabs span:hover:active { 40 | opacity: 0.5; 41 | } 42 | #iaextractor-selected { 43 | z-index: 1; 44 | position: absolute!important; 45 | bottom: 30px!important; 46 | height: 2px!important; 47 | background-color: #FFF!important; 48 | transition: transform 300ms; 49 | } 50 | 51 | /** Loading Video Info **/ 52 | 53 | #iaextractor-load { 54 | position: absolute; 55 | display: block; 56 | width: 64px; 57 | height: 64px; 58 | background: url(./fetch.png) no-repeat center center !important; 59 | } 60 | 61 | /** Buttons **/ 62 | 63 | .iaextractor-button { 64 | position: absolute; 65 | display: inline-block; 66 | width: 40px; 67 | height: 20px; 68 | line-height: 36px; 69 | text-align: center; 70 | } 71 | .iaextractor-button:not([disabled=true]) { 72 | cursor: pointer; 73 | } 74 | #iaextractor-close { 75 | top: 0; 76 | right: 0; 77 | transition: background 100ms!important; 78 | background: #F79646 -moz-image-rect(url(./images.png), 0, 32, 16, 16) no-repeat center!important; 79 | } 80 | #iaextractor-close:hover { 81 | background-color: #FFA05A!important; 82 | } 83 | #iaextractor-close:hover:active { 84 | background-color: #FF8830!important; 85 | } 86 | .iaextractor-dropdown { 87 | position: relative; 88 | transform: translate(10px, -23px); 89 | width: 16px!important; 90 | height: 43px!important; 91 | transition: opacity 100ms, transform 200ms!important; 92 | background: -moz-image-rect(url(./downloadm5.png), 0, 16, 16, 0) no-repeat !important; 93 | } 94 | .iaextractor-dropdown[dash="a"] { 95 | background: -moz-image-rect(url(./downloadm5.png), 0, 80, 16, 64) no-repeat !important; 96 | } 97 | .iaextractor-dropdown[dash="v"] { 98 | background: -moz-image-rect(url(./downloadm5.png), 0, 96, 16, 80) no-repeat !important; 99 | } 100 | .iaextractor-dropdown[dash="v"][fps="60"] { 101 | background: -moz-image-rect(url(./downloadm5.png), 0, 112, 16, 96) no-repeat !important; 102 | } 103 | .iaextractor-dropdown[dash="v"][fps="50"] { 104 | background: -moz-image-rect(url(./downloadm5.png), 0, 144, 16, 128) no-repeat !important; 105 | } 106 | .iaextractor-dropdown:hover:active { 107 | opacity: 0.5; 108 | } 109 | .iaextractor-dropdown i { 110 | position: absolute; 111 | display: block!important; 112 | width: 12px; 113 | height: 12px; 114 | border-radius: 200px; 115 | bottom: 0; 116 | border: 2px solid #FFF!important; 117 | pointer-events: none; 118 | } 119 | .iaextractor-dropdown i:after { 120 | display: block; 121 | content: ""; 122 | width: 8px; 123 | height: 8px; 124 | margin-top: 2px; 125 | margin-left: 2px; 126 | border-radius: 200px; 127 | background-color: #FFF; 128 | } 129 | 130 | /** Menu Items **/ 131 | 132 | #iaextractor-items div { 133 | position: absolute; 134 | opacity: 0; 135 | transition: transform 300ms; 136 | } 137 | .iaextractor-item { 138 | font-size: 13px; 139 | display: block; 140 | line-height: 30px; 141 | height: 30px; 142 | margin: 20px 28px 0 28px!important; 143 | text-decoration: none!important; 144 | outline: 0!important; 145 | overflow: hidden; 146 | transition: background-color 100ms!important; 147 | } 148 | .iaextractor-item span:first-child { 149 | display: inline-block; 150 | height: 100%; 151 | width: 200%!important; 152 | color: #FFF!important; 153 | background-color: #F79646!important; 154 | transition: background-color 100ms!important; 155 | padding-left: 35px!important; 156 | -moz-box-sizing: border-box!important; 157 | } 158 | .iaextractor-item:hover span:first-child { 159 | background-color: #FFA05A!important; 160 | } 161 | .iaextractor-item span:first-child:hover:active { 162 | background-color: #FF8830!important; 163 | } 164 | .iaextractor-item[selected] .iaextractor-dropdown { 165 | transform: translate(10px, -50px); 166 | } 167 | 168 | /** Downloader **/ 169 | 170 | #iaextractor-downloader { 171 | z-index: 1; 172 | position: absolute; 173 | bottom: -32px; 174 | width: 100%!important; 175 | height: 32px!important; 176 | transition: bottom 200ms!important; 177 | background-color: #F79646!important; 178 | -moz-user-select: none; 179 | padding: 0; /** embeded **/ 180 | margin: 0; 181 | } 182 | #iaextractor-downloader li { 183 | display: inline-block!important; 184 | height: 100%!important; 185 | line-height: 32px!important; 186 | text-align: center!important; 187 | margin-right: -3px!important; 188 | background: no-repeat center!important; 189 | transition: background-color 100ms!important; 190 | } 191 | #iaextractor-downloader li:hover { 192 | background-color: #FFA05A!important; 193 | cursor: pointer; 194 | } 195 | #iaextractor-downloader li:hover:active { 196 | background-color: #F79646!important; 197 | } 198 | #iaextractor-downloader li:nth-child(1) { 199 | background-image: -moz-image-rect(url(./downloadm5.png), 0, 32, 16, 16)!important; 200 | } 201 | #iaextractor-downloader li:nth-child(2) { 202 | background-image: -moz-image-rect(url(./downloadm5.png), 0, 128, 16, 112)!important; 203 | } 204 | #iaextractor-downloader li:nth-child(3) { 205 | background-image: -moz-image-rect(url(./downloadm5.png), 0, 48, 16, 32)!important; 206 | } 207 | #iaextractor-downloader li:nth-child(4) { 208 | background-image: -moz-image-rect(url(./downloadm5.png), 0, 64, 16, 48)!important; 209 | } 210 | 211 | #iaextractor-downloader li span { 212 | position: relative!important; 213 | background: white!important; 214 | top: -33px; 215 | margin: 0 -50%; 216 | padding: 5px!important; 217 | pointer-events: none; 218 | color: #000!important; 219 | opacity: 0; 220 | white-space: nowrap; 221 | transition: opacity 200ms; 222 | } 223 | #iaextractor-downloader li span:after { 224 | position: absolute!important; 225 | top: 100%!important; 226 | left: 50%!important; 227 | content: ""!important; 228 | border: solid transparent!important; 229 | border-color: rgba(255, 255, 255, 0)!important; 230 | border-top-color: white!important; 231 | border-width: 6px!important; 232 | margin-left: -6px!important; 233 | } 234 | #iaextractor-downloader li:nth-child(1):hover span, 235 | #iaextractor-downloader li:nth-child(2):hover span, 236 | #iaextractor-downloader li:nth-child(3):hover span, 237 | #iaextractor-downloader li:nth-child(4):hover span { 238 | opacity: 1; 239 | } 240 | 241 | #formats-button-small { 242 | color: #F79646!important; 243 | opacity: 0.8; 244 | } 245 | #formats-button-small:hover{ 246 | opacity: 1; 247 | } 248 | 249 | #formats-button-small-2 { 250 | background: url('./injected-button.png') center center no-repeat; 251 | background-size: 16px 16px; 252 | width: 36px; 253 | cursor: pointer; 254 | } 255 | -------------------------------------------------------------------------------- /src/data/report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 |
59 |
60 |

61 |
62 |
63 | 72 |
73 |
74 |
75 |
76 |
77 | 78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
95 |
96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
116 |
117 | 118 | 119 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "YouTube Video and Audio Downloader", 3 | "name": "iaextractor", 4 | "description": "", 5 | 6 | "license": "MPLv2.0", 7 | 8 | "author": "InBasic (@inbasic)", 9 | "contributors": ["Alejandro Rodriguez (@alopepeo), UI design and beta tester"], 10 | 11 | "url": "", 12 | "version": "0.7.1", 13 | "main": "./run.js", 14 | 15 | "id": "feca4b87-3be4-43da-a1b1-137c24220968@jetpack", 16 | 17 | "hasEmbeddedWebExtension": true, 18 | 19 | "permissions": { 20 | "private-browsing": true, 21 | "multiprocess": true 22 | }, 23 | 24 | "preferences": [{ 25 | "name": "getConverter", 26 | "type": "control", 27 | "label": ".", 28 | "title": ".", 29 | "hidden": true 30 | }, 31 | { 32 | "name": "extension", 33 | "title": ".", 34 | "description": "", 35 | "type": "menulist", 36 | "value": 0, 37 | "options": [ 38 | {"value": "0", "label": "FLV"}, 39 | {"value": "1", "label": "3GP"}, 40 | {"value": "2", "label": "MP4"}, 41 | {"value": "3", "label": "WebM"} 42 | ], 43 | "hidden": true 44 | }, 45 | { 46 | "name": "quality", 47 | "title": ".", 48 | "type": "menulist", 49 | "value": 2, 50 | "options": [ 51 | {"value": "0", "label": "HD1080p"}, 52 | {"value": "1", "label": "HD720p"}, 53 | {"value": "2", "label": "High"}, 54 | {"value": "3", "label": "Medium"}, 55 | {"value": "4", "label": "Small"} 56 | ], 57 | "hidden": true 58 | }, 59 | { 60 | "name": "doExtract", 61 | "title": ".", 62 | "type": "bool", 63 | "value": true, 64 | "hidden": true 65 | }, 66 | { 67 | "name": "namePattern", 68 | "title": ".", 69 | "description": "", 70 | "type": "string", 71 | "value": "[file_name].[extension]", 72 | "hidden": true 73 | }, 74 | { 75 | "name": "dFolder", 76 | "title": ".", 77 | "type": "menulist", 78 | "value": 3, 79 | "options": [ 80 | {"value": "0", "label": "dft_dir"}, 81 | {"value": "1", "label": "hm_dir"}, 82 | {"value": "2", "label": "tmp_dir"}, 83 | {"value": "3", "label": "dsk_dir"}, 84 | {"value": "4", "label": "slt_fdr"}, 85 | {"value": "5", "label": "urs_fdr"} 86 | ], 87 | "hidden": true 88 | }, 89 | { 90 | "name": "userFolder", 91 | "type": "directory", 92 | "title": ".", 93 | "description": "", 94 | "hidden": true 95 | }, 96 | { 97 | "name": "getFileSize", 98 | "title": ".", 99 | "type": "bool", 100 | "value": true, 101 | "hidden": true 102 | }, 103 | { 104 | "name": "open", 105 | "title": ".", 106 | "type": "bool", 107 | "value": false, 108 | "hidden": true 109 | }, 110 | { 111 | "type": "string", 112 | "name": "downloadHKey", 113 | "title": ".", 114 | "description": "", 115 | "value": "Accel + Shift + Q", 116 | "hidden": true 117 | }, 118 | { 119 | "name": "inject", 120 | "title": ".", 121 | "description": "", 122 | "type": "bool", 123 | "value": true, 124 | "hidden": true 125 | }, 126 | { 127 | "name": "oneClickDownload", 128 | "title": ".", 129 | "description": "", 130 | "type": "bool", 131 | "value": false, 132 | "hidden": true 133 | }, 134 | { 135 | "name": "silentOneClickDownload", 136 | "title": ".", 137 | "description": "", 138 | "type": "bool", 139 | "value": true, 140 | "hidden": true 141 | }, 142 | { 143 | "name": "ffmpegPath", 144 | "type": "file", 145 | "title": ".", 146 | "description": "", 147 | "hidden": true 148 | }, 149 | { 150 | "name": "ffmpegPath-manual", 151 | "type": "string", 152 | "title": ".", 153 | "description": "", 154 | "hidden": true 155 | }, 156 | { 157 | "name": "ffmpegInputs", 158 | "title": ".", 159 | "description": "", 160 | "type": "string", 161 | "value": "-loglevel error -i %input -q:a 0 %output.mp3", 162 | "hidden": true 163 | }, 164 | { 165 | "name": "ffmpegInputs4", 166 | "title": ".", 167 | "description": "", 168 | "type": "string", 169 | "value": "-loglevel error -i %audio -i %video -acodec copy -vcodec copy %output", 170 | "hidden": true 171 | }, 172 | { 173 | "name": "doBatchMode", 174 | "title": ".", 175 | "description": "", 176 | "type": "bool", 177 | "value": true, 178 | "hidden": true 179 | }, 180 | { 181 | "name": "pretendHD", 182 | "title": ".", 183 | "description": "", 184 | "type": "bool", 185 | "value": true, 186 | "hidden": true 187 | }, 188 | { 189 | "name": "opusmixing", 190 | "title": ".", 191 | "description": "", 192 | "type": "bool", 193 | "value": false, 194 | "hidden": true 195 | }, 196 | { 197 | "name": "ffmpegInputs3", 198 | "title": ".", 199 | "description": "", 200 | "type": "string", 201 | "value": "-loglevel error -i %input -acodec copy -vn %output", 202 | "hidden": true 203 | }, 204 | { 205 | "name": "doRemux", 206 | "title": ".", 207 | "description": "", 208 | "type": "bool", 209 | "value": true, 210 | "hidden": true 211 | }, 212 | { 213 | "name": "deleteInputs", 214 | "title": ".", 215 | "description": "", 216 | "type": "bool", 217 | "value": true, 218 | "hidden": true 219 | }, 220 | { 221 | "name": "showNotifications", 222 | "title": ".", 223 | "description": "", 224 | "type": "bool", 225 | "value": true, 226 | "hidden": true 227 | }, 228 | { 229 | "name": "showWEBM", 230 | "title": ".", 231 | "description": "", 232 | "type": "bool", 233 | "value": true, 234 | "hidden": true 235 | }, 236 | { 237 | "name": "showMP4", 238 | "title": ".", 239 | "description": "", 240 | "type": "bool", 241 | "value": true, 242 | "hidden": true 243 | }, 244 | { 245 | "name": "showFLV", 246 | "title": ".", 247 | "description": "", 248 | "type": "bool", 249 | "value": true, 250 | "hidden": true 251 | }, 252 | { 253 | "name": "show3GP", 254 | "title": ".", 255 | "description": "", 256 | "type": "bool", 257 | "value": true, 258 | "hidden": true 259 | }, 260 | { 261 | "name": "protocol", 262 | "title": ".", 263 | "description": "", 264 | "type": "menulist", 265 | "value": 0, 266 | "options": [ 267 | {"value": "0", "label": "default"}, 268 | {"value": "1", "label": "http"}, 269 | {"value": "2", "label": "https"} 270 | ], 271 | "hidden": true 272 | }, 273 | { 274 | "name": "customUA", 275 | "title": ".", 276 | "description": "", 277 | "type": "string", 278 | "value": "", 279 | "hidden": true 280 | }, 281 | { 282 | "name": "welcome", 283 | "title": ".", 284 | "type": "bool", 285 | "value": true, 286 | "hidden": true 287 | }, 288 | { 289 | "name": "dnotifiy", 290 | "title": ".", 291 | "type": "bool", 292 | "value": true, 293 | "hidden": true 294 | }, 295 | { 296 | "name": "forceVisible", 297 | "title": ".", 298 | "description": "", 299 | "type": "bool", 300 | "value": true, 301 | "hidden": true 302 | }, 303 | { 304 | "name": "installFFmpeg", 305 | "type": "control", 306 | "label": ".", 307 | "title": ".", 308 | "hidden": true 309 | }, 310 | { 311 | "name": "reset", 312 | "type": "control", 313 | "label": ".", 314 | "title": ".", 315 | "hidden": true 316 | }, 317 | { 318 | "name": "openOptions", 319 | "type": "control", 320 | "label": "Options", 321 | "title": "Open Options Window" 322 | }, 323 | { 324 | "name": "experiment", 325 | "type": "bool", 326 | "label": "Switch", 327 | "description": "Switch to the experimental WebExtension version (Firefox >= 53.0)", 328 | "title": "Try the WebExtension version" 329 | }] 330 | } 331 | -------------------------------------------------------------------------------- /src/data/info/jsoneditor/jsoneditor.css: -------------------------------------------------------------------------------- 1 | 2 | .jsoneditor-field, .jsoneditor-value, .jsoneditor-field-readonly, .jsoneditor-readonly { 3 | border: 1px solid transparent; 4 | min-height: 16px; 5 | min-width: 24px; 6 | padding: 2px; 7 | margin: 1px; 8 | outline: none; 9 | word-wrap: break-word; 10 | float: left; 11 | } 12 | 13 | /* adjust margin of p elements inside editable divs, needed for Opera, IE */ 14 | .jsoneditor-field p, .jsoneditor-value p { 15 | margin: 0; 16 | } 17 | 18 | .jsoneditor-value { 19 | word-break: normal; 20 | } 21 | 22 | .jsoneditor-empty { 23 | background-color: #E5E5E5; 24 | border-radius: 2px; 25 | } 26 | 27 | .jsoneditor-separator { 28 | padding: 3px 0; 29 | vertical-align: top; 30 | } 31 | 32 | .jsoneditor-value:focus, .jsoneditor-field:focus, 33 | .jsoneditor-value:hover, .jsoneditor-field:hover, 34 | .jsoneditor-search-highlight { 35 | background-color: #FFFFAB; 36 | border: 1px solid yellow; 37 | border-radius: 2px; 38 | } 39 | 40 | .jsoneditor-search-highlight-active, 41 | .jsoneditor-search-highlight-active:focus, 42 | .jsoneditor-search-highlight-active:hover { 43 | background-color: #ffee00; 44 | border: 1px solid #ffc700; 45 | border-radius: 2px; 46 | } 47 | 48 | .jsoneditor-field-readonly:hover { 49 | border: 1px solid white; 50 | } 51 | 52 | .jsoneditor-readonly { 53 | color: gray; 54 | } 55 | 56 | button.jsoneditor-remove, button.jsoneditor-append, button.jsoneditor-duplicate, 57 | button.jsoneditor-collapsed, button.jsoneditor-expanded, 58 | button.jsoneditor-invisible, button.jsoneditor-dragarea, 59 | button.jsoneditor-type-auto, button.jsoneditor-type-string, 60 | button.jsoneditor-type-array, button.jsoneditor-type-object { 61 | width: 24px; 62 | height: 24px; 63 | padding: 0; 64 | margin: 0; 65 | border: none; 66 | cursor: pointer; 67 | background: url('img/jsoneditor-icons.png'); 68 | } 69 | 70 | button:disabled { 71 | color: #808080; 72 | } 73 | 74 | button.jsoneditor-collapsed { 75 | background-position: -168px 0; 76 | } 77 | 78 | button.jsoneditor-expanded { 79 | background-position: -168px -24px; 80 | } 81 | 82 | button.jsoneditor-invisible { 83 | visibility: hidden; 84 | background: none; 85 | } 86 | 87 | button.jsoneditor-collapsed, button.jsoneditor-expanded, 88 | button.jsoneditor-invisible { 89 | float: left; 90 | } 91 | 92 | button.jsoneditor-remove { 93 | background-position: -24px -24px; 94 | } 95 | button.jsoneditor-remove:hover { 96 | background-position: -24px 0; 97 | } 98 | 99 | button.jsoneditor-append { 100 | background-position: 0 -24px; 101 | } 102 | button.jsoneditor-append:hover { 103 | background-position: 0 0; 104 | } 105 | 106 | button.jsoneditor-duplicate { 107 | background-position: -48px -24px; 108 | } 109 | button.jsoneditor-duplicate:hover { 110 | background-position: -48px 0; 111 | } 112 | 113 | button.jsoneditor-type-string { 114 | background-position: -144px -24px; 115 | } 116 | button.jsoneditor-type-string:hover { 117 | background-position: -144px 0; 118 | } 119 | 120 | button.jsoneditor-type-auto { 121 | background-position: -120px -24px; 122 | } 123 | button.jsoneditor-type-auto:hover { 124 | background-position: -120px 0; 125 | } 126 | 127 | button.jsoneditor-type-object { 128 | background-position: -72px -24px; 129 | } 130 | button.jsoneditor-type-object:hover { 131 | background-position: -72px 0; 132 | } 133 | 134 | button.jsoneditor-type-array { 135 | background-position: -96px -24px; 136 | } 137 | button.jsoneditor-type-array:hover { 138 | background-position: -96px 0; 139 | } 140 | 141 | div.jsoneditor-select { 142 | border: 1px solid gray; 143 | background-color: white; 144 | box-shadow: 4px 4px 10px rgba(128, 128, 128, 0.5); 145 | } 146 | 147 | div.jsoneditor-option { 148 | color: #4D4D4D; 149 | background-color: white; 150 | 151 | border: none; 152 | margin: 0; 153 | display: block; 154 | text-align: left; 155 | cursor: pointer; 156 | } 157 | div.jsoneditor-option:hover { 158 | background-color: #FFFFAB; 159 | color: #1A1A1A; 160 | } 161 | div.jsoneditor-option-selected { 162 | background-color: #D5DDF6; 163 | } 164 | div.jsoneditor-option-text { 165 | height: 24px; 166 | line-height: 24px; 167 | padding: 0 12px 0 0; 168 | display: inline-block; 169 | } 170 | 171 | div.jsoneditor-option-string, div.jsoneditor-option-auto, 172 | div.jsoneditor-option-object, div.jsoneditor-option-array { 173 | float: left; 174 | width: 24px; 175 | height: 24px; 176 | display: inline-block; 177 | background: url('img/jsoneditor-icons.png'); 178 | } 179 | div.jsoneditor-option-string { 180 | background-position: -144px 0; 181 | } 182 | div.jsoneditor-option-auto { 183 | background-position: -120px 0; 184 | } 185 | div.jsoneditor-option-object { 186 | background-position: -72px 0; 187 | } 188 | div.jsoneditor-option-array { 189 | background-position: -96px 0; 190 | } 191 | 192 | 193 | div.jsoneditor-frame { 194 | color: #1A1A1A; 195 | border: 1px solid #97B0F8; 196 | width: 100%; 197 | height: 100%; 198 | overflow: auto; 199 | position: relative; 200 | padding: 0; 201 | } 202 | 203 | table.jsoneditor-table { 204 | border-collapse: collapse; 205 | border-spacing: 0; 206 | width: 100%; 207 | margin: 0; 208 | } 209 | 210 | div.jsoneditor-content-outer, div.jsonformatter-content { 211 | width: 100%; 212 | height: 100%; 213 | margin: -35px 0 0 0; 214 | padding: 35px 0 0 0; 215 | 216 | -moz-box-sizing: border-box; 217 | -webkit-box-sizing: border-box; 218 | 219 | overflow: hidden; 220 | } 221 | 222 | div.jsoneditor-content { 223 | width: 100%; 224 | height: 100%; 225 | position: relative; 226 | overflow: auto; 227 | } 228 | 229 | textarea.jsonformatter-textarea { 230 | width: 100%; 231 | height: 100%; 232 | margin: 0; 233 | 234 | -moz-box-sizing: border-box; 235 | -webkit-box-sizing: border-box; 236 | 237 | border: none; 238 | background-color: white; 239 | resize: none; 240 | } 241 | 242 | tr.jsoneditor-tr-highlight { 243 | background-color: #FFFFAB; 244 | } 245 | 246 | button.jsoneditor-dragarea { 247 | width: 16px; 248 | height: 24px; 249 | /* 250 | margin: 3px 0; 251 | background: url('img/dots_gray.gif') top center; 252 | background-repeat: repeat-y; 253 | */ 254 | background: url('img/jsoneditor-icons.png') -220px 0; 255 | 256 | display: block; 257 | cursor: move; 258 | } 259 | 260 | div.jsoneditor-menu { 261 | width: 100%; 262 | height: 35px; 263 | padding: 2px; 264 | margin: 0; 265 | overflow: hidden; 266 | -moz-box-sizing: border-box; 267 | -webkit-box-sizing: border-box; 268 | 269 | color: #1A1A1A; 270 | background-color: #D5DDF6; 271 | border-bottom: 1px solid #97B0F8; 272 | } 273 | 274 | table.jsoneditor-search { 275 | position: absolute; 276 | right: 2px; 277 | top: 2px; 278 | } 279 | 280 | table.jsoneditor-search-input { 281 | border-collapse: collapse; 282 | } 283 | 284 | div.jsoneditor-search { 285 | border: 1px solid #97B0F8; 286 | background-color: white; 287 | padding: 0 2px; 288 | margin: 0; 289 | } 290 | 291 | input.jsoneditor-search { 292 | width: 120px; 293 | border: none; 294 | outline: none; 295 | margin: 1px; 296 | } 297 | 298 | div.jsoneditor-search-results { 299 | color: #4d4d4d; 300 | padding-right: 5px; 301 | } 302 | 303 | button.jsoneditor-search-refresh, button.jsoneditor-search-next, 304 | button.jsoneditor-search-previous { 305 | width: 16px; 306 | height: 24px; 307 | padding: 0; 308 | margin: 0; 309 | border: none; 310 | background: url('img/jsoneditor-icons.png'); 311 | vertical-align: top; 312 | } 313 | 314 | button.jsoneditor-search-refresh { 315 | width: 18px; 316 | background-position: -243px -25px; 317 | } 318 | 319 | button.jsoneditor-search-next { 320 | cursor: pointer; 321 | background-position: -268px -25px; 322 | } 323 | button.jsoneditor-search-next:hover { 324 | background-position: -268px -1px; 325 | } 326 | 327 | button.jsoneditor-search-previous { 328 | cursor: pointer; 329 | background-position: -292px -25px; 330 | margin-right: 2px; 331 | } 332 | button.jsoneditor-search-previous:hover { 333 | background-position: -292px -1px; 334 | } 335 | 336 | 337 | button.jsoneditor-menu { 338 | width: 26px; 339 | height: 26px; 340 | margin: 2px; 341 | padding: 2px; 342 | border-radius: 2px; 343 | border: 1px solid #aec0f8; 344 | background: #e3eaf6 url('../jsoneditor/img/jsoneditor-icons.png'); 345 | } 346 | 347 | button.jsoneditor-menu:hover { 348 | background-color: #f0f2f5; 349 | } 350 | button.jsoneditor-menu:active { 351 | background-color: #ffffff; 352 | } 353 | button.jsoneditor-menu:disabled { 354 | background-color: #e3eaf6; 355 | } 356 | 357 | button.jsoneditor-collapse-all { 358 | background-position: -312px 0; 359 | } 360 | button.jsoneditor-expand-all { 361 | background-position: -312px -24px; 362 | } 363 | button.jsoneditor-undo { 364 | background-position: -336px 0; 365 | } 366 | button.jsoneditor-redo { 367 | background-position: -360px 0; 368 | } 369 | button.jsoneditor-undo:disabled { 370 | background-position: -336px -24px; 371 | } 372 | button.jsoneditor-redo:disabled { 373 | background-position: -360px -24px; 374 | } 375 | /* TODO: css for button:disabled is not supported by IE8 */ 376 | button.jsoneditor-compact { 377 | background-position: -384px 0; 378 | } 379 | button.jsoneditor-format { 380 | background-position: -384px -24px; 381 | } 382 | 383 | 384 | tr, th, td { 385 | padding: 0; 386 | margin: 0; 387 | } 388 | 389 | td.jsoneditor-td { 390 | vertical-align: top; 391 | } 392 | 393 | td.jsoneditor-td { 394 | padding: 0 3px; 395 | } 396 | 397 | td.jsoneditor-td-edit { 398 | background-color: #F5F5F5; 399 | padding: 0; 400 | } 401 | 402 | td.jsoneditor-td-tree { 403 | vertical-align: top; 404 | } 405 | 406 | td.jsoneditor-droparea { 407 | height: 24px; 408 | 409 | border-top: 1px dashed gray; 410 | border-bottom: 1px dashed gray; 411 | background-color: #FFFF80; 412 | } 413 | 414 | .jsoneditor-field, .jsoneditor-value, .jsoneditor-td, .jsoneditor-th, 415 | .jsoneditor-type, 416 | .jsonformatter-textarea { 417 | font-family: droid sans mono, monospace, courier new, courier, sans-serif; 418 | font-size: 10pt; 419 | color: #1A1A1A; 420 | } 421 | 422 | .jsoneditor-hidden-focus { 423 | position: absolute; 424 | left: -1000px; 425 | top: -1000px; 426 | border: none; 427 | outline: none; 428 | } 429 | -------------------------------------------------------------------------------- /src/lib/toolbarbutton/old.js: -------------------------------------------------------------------------------- 1 | const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 2 | 3 | var prefs = require("sdk/simple-prefs").prefs, 4 | winUtils = require("sdk/deprecated/window-utils"), 5 | utils = require('sdk/window/utils'); 6 | 7 | const browserURL = "chrome://browser/content/browser.xul"; 8 | 9 | /** unload+.js [start] **/ 10 | var unload = (function () { 11 | var unloaders = []; 12 | 13 | function unloadersUnlaod() { 14 | unloaders.slice().forEach(function(unloader) unloader()); 15 | unloaders.length = 0; 16 | } 17 | 18 | require("sdk/system/unload").when(unloadersUnlaod); 19 | 20 | function removeUnloader(unloader) { 21 | let index = unloaders.indexOf(unloader); 22 | if (index != -1) 23 | unloaders.splice(index, 1); 24 | } 25 | 26 | return { 27 | unload: function unload(callback, container) { 28 | // Calling with no arguments runs all the unloader callbacks 29 | if (callback == null) { 30 | unloadersUnlaod(); 31 | return null; 32 | } 33 | 34 | var remover = removeUnloader.bind(null, unloader); 35 | 36 | // The callback is bound to the lifetime of the container if we have one 37 | if (container != null) { 38 | // Remove the unloader when the container unloads 39 | container.addEventListener("unload", remover, false); 40 | 41 | // Wrap the callback to additionally remove the unload listener 42 | let origCallback = callback; 43 | callback = function() { 44 | container.removeEventListener("unload", remover, false); 45 | origCallback(); 46 | } 47 | } 48 | 49 | // Wrap the callback in a function that ignores failures 50 | function unloader() { 51 | try { 52 | callback(); 53 | } 54 | catch(ex) {} 55 | } 56 | unloaders.push(unloader); 57 | 58 | // Provide a way to remove the unloader 59 | return remover; 60 | } 61 | }; 62 | })().unload; 63 | /** unload+.js [end] **/ 64 | /** listen.js [start] **/ 65 | var listen = function listen(window, node, event, func, capture) { 66 | // Default to use capture 67 | if (capture == null) 68 | capture = true; 69 | 70 | node.addEventListener(event, func, capture); 71 | function undoListen() { 72 | node.removeEventListener(event, func, capture); 73 | } 74 | 75 | // Undo the listener on unload and provide a way to undo everything 76 | let undoUnload = unload(undoListen, window); 77 | return function() { 78 | undoListen(); 79 | undoUnload(); 80 | }; 81 | } 82 | /** listen.js [end] **/ 83 | 84 | exports.ToolbarButton = function ToolbarButton(options) { 85 | var unloaders = [], 86 | toolbarID = prefs.toolbarID || "", 87 | insertbefore = prefs.nextSibling || "", 88 | destroyed = false, 89 | destoryFuncs = []; 90 | 91 | var delegate = { 92 | onTrack: function (window) { 93 | if ("chrome://browser/content/browser.xul" != window.location || destroyed) 94 | return; 95 | 96 | let doc = window.document; 97 | let $ = function(id) doc.getElementById(id); 98 | options.tooltiptext = options.tooltiptext || ''; 99 | // create toolbar button 100 | let tbb = doc.createElementNS(NS_XUL, "toolbarbutton"); 101 | tbb.setAttribute("id", options.id); 102 | tbb.setAttribute("value", ""); 103 | tbb.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional"); 104 | tbb.setAttribute("label", options.label); 105 | tbb.setAttribute('tooltiptext', options.tooltiptext); 106 | tbb.addEventListener("command", function(e) { 107 | if (e.ctrlKey) return; 108 | if (e.originalTarget.localName == "menu" || e.originalTarget.localName == "menuitem") return; 109 | 110 | if (options.onCommand) { 111 | options.onCommand(e, tbb); 112 | } 113 | }, true); 114 | if (options.onClick) { 115 | tbb.addEventListener("click", function (e) { 116 | options.onClick(e, tbb); 117 | }, true); 118 | } 119 | if (options.panel) { 120 | tbb.addEventListener("contextmenu", function (e) { 121 | e.stopPropagation(); 122 | e.preventDefault(); 123 | try { 124 | options.panel.show(tbb); 125 | } 126 | catch (e) { 127 | options.panel.show(null, tbb); 128 | } 129 | }, true); 130 | } 131 | if (options.onContext) { 132 | let menupopup = doc.createElementNS(NS_XUL, "menupopup"); 133 | let menuitem = doc.createElementNS(NS_XUL, "menuitem"); 134 | let menuseparator = doc.createElementNS(NS_XUL, "menuseparator"); 135 | tbb.addEventListener("contextmenu", function (e) { 136 | e.stopPropagation(); //Prevent Firefox context menu 137 | e.preventDefault(); 138 | options.onContext(e, menupopup, menuitem, menuseparator); 139 | menupopup.openPopup(tbb , "after_end", 0, 0, false); 140 | }, true); 141 | tbb.appendChild(menupopup); 142 | } 143 | // add toolbarbutton to palette 144 | ($("navigator-toolbox") || $("mail-toolbox")).palette.appendChild(tbb); 145 | 146 | // find a toolbar to insert the toolbarbutton into 147 | if (toolbarID) { 148 | var tb = $(toolbarID); 149 | } 150 | if (!tb) { 151 | var tb = toolbarbuttonExists(doc, options.id); 152 | } 153 | 154 | // found a toolbar to use? 155 | if (tb) { 156 | let b4; 157 | 158 | // find the toolbarbutton to insert before 159 | if (insertbefore) { 160 | b4 = $(insertbefore); 161 | } 162 | if (!b4) { 163 | let currentset = tb.getAttribute("currentset").split(","); 164 | let i = currentset.indexOf(options.id) + 1; 165 | 166 | // was the toolbarbutton id found in the curent set? 167 | if (i > 0) { 168 | let len = currentset.length; 169 | // find a toolbarbutton to the right which actually exists 170 | for (; i < len; i++) { 171 | b4 = $(currentset[i]); 172 | if (b4) break; 173 | } 174 | } 175 | if (!b4) b4 = $("home-button"); 176 | } 177 | try { 178 | tb.insertItem(options.id, b4, null, false); 179 | } 180 | catch(e) { 181 | tb.insertItem(options.id, null, null, false); 182 | } 183 | } 184 | 185 | var saveTBNodeInfo = function(e) { 186 | toolbarID = tbb.parentNode.getAttribute("id") || ""; 187 | insertbefore = (tbb.nextSibling || "") 188 | && tbb.nextSibling.getAttribute("id").replace(/^wrapper-/i, ""); 189 | 190 | prefs.nextSibling = insertbefore; 191 | prefs.toolbarID = toolbarID; 192 | }; 193 | 194 | window.addEventListener("aftercustomization", saveTBNodeInfo, false); 195 | 196 | // add unloader to unload+'s queue 197 | var unloadFunc = function() { 198 | tbb.parentNode.removeChild(tbb); 199 | window.removeEventListener("aftercustomization", saveTBNodeInfo, false); 200 | }; 201 | var index = destoryFuncs.push(unloadFunc) - 1; 202 | listen(window, window, "unload", function() { 203 | destoryFuncs[index] = null; 204 | }, false); 205 | unloaders.push(unload(unloadFunc, window)); 206 | }, 207 | onUntrack: function (window) {} 208 | }; 209 | var tracker = winUtils.WindowTracker(delegate); 210 | 211 | function setProgress(aOptions) { 212 | getToolbarButtons(function(tbb) { 213 | if (!aOptions.progress) { 214 | tbb.removeAttribute("progress"); 215 | } 216 | else { 217 | tbb.setAttribute("progress", (aOptions.progress * 8).toFixed(0)); 218 | } 219 | }, options.id); 220 | return aOptions.progress; 221 | } 222 | function setSaturate(aOptions) { 223 | getToolbarButtons(function(tbb) { 224 | if (!aOptions.value) { 225 | tbb.setAttribute("type", "gray"); 226 | } 227 | else { 228 | tbb.removeAttribute("type"); 229 | } 230 | }, options.id); 231 | options.saturate = aOptions.value; 232 | return aOptions.value; 233 | } 234 | 235 | 236 | 237 | return { 238 | destroy: function() { 239 | if (destroyed) return; 240 | destroyed = true; 241 | 242 | if (options.panel) 243 | options.panel.destroy(); 244 | 245 | // run unload functions 246 | destoryFuncs.forEach(function(f) f && f()); 247 | destoryFuncs.length = 0; 248 | 249 | // remove unload functions from unload+'s queue 250 | unloaders.forEach(function(f) f()); 251 | unloaders.length = 0; 252 | }, 253 | moveTo: function(pos) { 254 | if (destroyed) return; 255 | 256 | // record the new position for future windows 257 | toolbarID = prefs.toolbarID || pos.toolbarID; 258 | insertbefore = prefs.nextSibling || pos.insertbefore; 259 | 260 | if (toolbarID == "BrowserToolbarPalette") { 261 | toolbarID = "nav-bar"; 262 | insertbefore = "home-button"; 263 | } 264 | 265 | // change the current position for open windows 266 | for (let window of utils.windows()) { 267 | if (browserURL != window.location) return; 268 | 269 | let doc = window.document; 270 | let $ = function (id) doc.getElementById(id); 271 | 272 | // if the move isn't being forced and it is already in the window, abort 273 | if (!pos.forceMove && $(options.id)) return; 274 | 275 | var tb = $(toolbarID); 276 | var b4 = $(insertbefore); 277 | 278 | if (tb) { 279 | try { 280 | tb.insertItem(options.id, b4, null, false); 281 | } 282 | catch(e) { 283 | tb.insertItem(options.id, null, null, false); 284 | } 285 | tb.setAttribute("currentset", tb.currentSet); 286 | doc.persist(tb.id, "currentset"); 287 | } 288 | } 289 | }, 290 | get label() options.label, 291 | set label(value) { 292 | options.label = value; 293 | getToolbarButtons(function(tbb) { 294 | tbb.label = value; 295 | }, options.id); 296 | return value; 297 | }, 298 | set progress(value) setProgress({progress: value}), 299 | set saturate(value) setSaturate({value: value}), 300 | get saturate() options.saturate, 301 | get tooltiptext() options.tooltiptext, 302 | set tooltiptext(value) { 303 | options.tooltiptext = value; 304 | getToolbarButtons(function(tbb) { 305 | tbb.setAttribute('tooltiptext', value); 306 | }, options.id); 307 | }, 308 | get object () { 309 | return utils.getMostRecentBrowserWindow().document.getElementById(options.id); 310 | } 311 | }; 312 | }; 313 | 314 | function getToolbarButtons(callback, id) { 315 | let buttons = []; 316 | for (let window of utils.windows()) { 317 | if (browserURL != window.location) continue; 318 | let tbb = window.document.getElementById(id); 319 | if (tbb) buttons.push(tbb); 320 | } 321 | if (callback) buttons.forEach(callback); 322 | return buttons; 323 | } 324 | 325 | function toolbarbuttonExists(doc, id) { 326 | var toolbars = doc.getElementsByTagNameNS(NS_XUL, "toolbar"); 327 | for (var i = toolbars.length - 1; ~i; i--) { 328 | if ((new RegExp("(?:^|,)" + id + "(?:,|$)")).test(toolbars[i].getAttribute("currentset"))) 329 | return toolbars[i]; 330 | } 331 | return false; 332 | } 333 | -------------------------------------------------------------------------------- /src/data/report/report.js: -------------------------------------------------------------------------------- 1 | var $ = function (id) { 2 | return document.getElementById(id); 3 | } 4 | 5 | var _ = function (id) { 6 | var items = $("locale").getElementsByTagName("span"); 7 | for (var i = 0; i < items.length; i++) { 8 | if (items[i].getAttribute("data-l10n-id") == id) { 9 | return items[i].textContent; 10 | } 11 | } 12 | return id; 13 | } 14 | 15 | var downloadButton = $("download"), 16 | formatsButton = $("links"), 17 | aCheckbox = $("audio-checkbox"), 18 | subCheckbox = $("subtitle-checkbox"), 19 | sCheckbox = $("resolve-size-checkbox"), 20 | dmanager = $("download-manager"), 21 | videop = $("video-preferences"), 22 | folderp = $("folder-preferences"), 23 | handler = $("click-handler"), 24 | qdtoggle = $("qd-toggle"), 25 | fbtoggle = $("folder-button"), 26 | tabs = $("tabs"); 27 | 28 | // e10s compatibility 29 | window.addEventListener("load", function () { 30 | folderp.style.transform = "translate(0, 230px)"; 31 | videop.style.transform = "translate(0, 230px)"; 32 | $("selected").style.transform = "translate(100px)"; 33 | $("tabpanels").style.transform = "translate(-300px)"; 34 | }, false); 35 | 36 | var cleanup = function() { 37 | if (handler.style.display == "block") handler.style.display = "none"; 38 | if (folderp.style.transform == "translate(0px, 129px)") folderp.style.transform = "translate(0, " + 230 + "px)"; 39 | if (videop.style.transform == "translate(0px, 119px)") videop.style.transform = "translate(0, " + 230 + "px)"; 40 | } 41 | 42 | var slidePanel = function(el, value1, value2) { 43 | if (el.style.transform == "translate(0px, " + value1 + "px)") { 44 | handler.style.display = "block"; 45 | el.style.transform = "translate(0, " + value2 + "px)"; 46 | } 47 | 48 | else if (el.style.transform == "translate(0px, " + value2 + "px)") { 49 | handler.style.display = "none"; 50 | el.style.transform = "translate(0, " + value1 + "px)"; 51 | } 52 | } 53 | 54 | handler.addEventListener("click", function () { 55 | cleanup(); 56 | }, false); 57 | fbtoggle.addEventListener("click", function () { 58 | slidePanel(folderp, 230, 129); 59 | }, false); 60 | qdtoggle.addEventListener("click", function () { 61 | slidePanel(videop, 230, 119); 62 | }, false); 63 | qdtoggle.addEventListener("mouseover", function () { 64 | downloadButton.style.backgroundColor = "#FFA05A"; 65 | }, false); 66 | qdtoggle.addEventListener("mouseleave", function () { 67 | downloadButton.style.backgroundColor = "#F79646"; 68 | }, false); 69 | 70 | var mList = function (name, value, func) { 71 | var radios = document.getElementsByName(name); 72 | 73 | function set (soft) { 74 | radios[parseInt(value || '0')].checked = true; 75 | if (!soft) func(value); 76 | } 77 | set(true); 78 | 79 | window.addEventListener("click", function (e) { 80 | if (e.target.className == "r") cleanup(); 81 | if (e.button != 0) return; 82 | if (e.originalTarget.localName != "input") 83 | return; 84 | for (var i = 0; i < radios.length; i++) { 85 | if (radios[i].checked) { 86 | value = radios[i].value; 87 | set(); 88 | } 89 | } 90 | }, false); 91 | 92 | return { 93 | get value () {return value}, 94 | set value (v) { 95 | value = v; 96 | set(); 97 | } 98 | } 99 | } 100 | 101 | var download = new mList ( 102 | "dinput", 103 | 0, 104 | function (value) { 105 | self.port.emit("cmd", "destination", value); 106 | } 107 | ); 108 | var quality = new mList ( 109 | "vinput", 110 | 0, 111 | function (value) { 112 | self.port.emit("cmd", "quality", value); 113 | } 114 | ); 115 | var format = new mList ( 116 | "finput", 117 | 0, 118 | function (value) { 119 | self.port.emit("cmd", "format", value); 120 | } 121 | ); 122 | 123 | function tabSelector (e) { 124 | var n; 125 | if (typeof(e) == "number") { 126 | n = e; 127 | } 128 | else { 129 | n = 0; 130 | var child = e.originalTarget 131 | if (child.localName != "span") { 132 | return; 133 | } 134 | while ((child = child.previousSibling)) { 135 | if(child.localName) { 136 | n += 1; 137 | } 138 | } 139 | } 140 | //Select a new tab 141 | var tabs = $("tabs").getElementsByClassName("tab"); 142 | var tabpanels = $("tabpanels").getElementsByClassName("tabpanel"); 143 | for (var i = 0; i < tabs.length; i++) { 144 | var tab = tabs[i], 145 | tabpanel = tabpanels[i]; 146 | if (i == n) { 147 | tab.setAttribute("selected", true); 148 | tabpanel.setAttribute("selected", true); 149 | } 150 | else { 151 | tab.removeAttribute("selected"); 152 | tabpanel.removeAttribute("selected"); 153 | } 154 | if ($("home").hasAttribute("selected")) { 155 | $("selected").style.transform = "translate(" + 100 + "px)"; 156 | $("tabpanels").style.transform = "translate(-" + 300 + "px)"; 157 | } 158 | if ($("downloads").hasAttribute("selected")) { 159 | $("selected").style.transform = "translate(" + 200 + "px)"; 160 | $("tabpanels").style.transform = "translate(-" + 600 + "px)"; 161 | } 162 | if ($("preferences").hasAttribute("selected")) { 163 | $("selected").style.transform = "translate(0)"; 164 | $("tabpanels").style.transform = "translate(0)"; 165 | } 166 | } 167 | } 168 | $("tabs").addEventListener("click", tabSelector, false); 169 | 170 | 171 | var dm = {}, inList = []; 172 | var dmUI = { 173 | set: function (id, item) { 174 | if (id && item) { 175 | dm[id] = item; 176 | } 177 | else if (item) { 178 | inList.push(item); 179 | } 180 | else { 181 | dm[id] = inList.shift(); 182 | } 183 | }, 184 | get: (function () { 185 | var cache = []; 186 | return function (item) { 187 | if (typeof (item) == "number") { 188 | item = dm[item]; 189 | } 190 | if (!item) return; 191 | if (item.hasAttribute("cache")) { 192 | return cache[item.getAttribute("cache")]; 193 | } 194 | var span = item.getElementsByTagName("span"); 195 | var progress = item.getElementsByClassName("progress-inner"); 196 | var image = item.getElementsByClassName("cancel"); 197 | 198 | var rtn = { 199 | set name(value) {span[0].textContent = value}, 200 | set description(value) {span[1].textContent = value}, 201 | get progress() {return progress[0]}, 202 | set progress(value) {progress[0].style.width = value}, 203 | get close() {return image[0]} 204 | }; 205 | item.setAttribute("cache", cache.push(rtn) - 1); 206 | return rtn; 207 | } 208 | })(), 209 | get count () { 210 | return Object.getOwnPropertyNames(dm).length; 211 | }, 212 | isEmpty: function () { 213 | return dmUI.count == 0; 214 | }, 215 | add: function () { 216 | //Show download manager 217 | $("no-download-manager").style.display = "none"; 218 | $("download-manager").style.display = "block"; 219 | //Add new download item 220 | var _item = $("download-item"); 221 | var item = _item.cloneNode(true); 222 | $("download-manager").appendChild(item); 223 | item.style.display = "inline-block"; 224 | this.get(item).close.addEventListener("click", function (e) { 225 | var id = e.originalTarget.getAttribute("dlID"); 226 | if (id) { 227 | self.port.emit("cmd", "cancel", id); 228 | } 229 | }, true); 230 | return item; 231 | }, 232 | remove: function (id) { 233 | $("download-manager").removeChild(dm[id]); 234 | delete dm[id]; 235 | 236 | if (dmUI.isEmpty()) { 237 | $("no-download-manager").style.display = "block"; 238 | $("download-manager").style.display = "none"; 239 | } 240 | } 241 | } 242 | 243 | self.port.on("detect", function(msg) { 244 | var item = dmUI.add(); 245 | //Update fields 246 | dmUI.get(item).description = msg; 247 | //Switch to progress tab 248 | window.setTimeout(function(){tabSelector(2);}, 200); 249 | dmanager.scrollTop = dmanager.scrollHeight; 250 | // 251 | dmUI.set(null, item); 252 | }); 253 | self.port.on("download-start", function(id, name, msg) { 254 | if (id == -1) return; 255 | //Finding a free slot 256 | dmUI.set(id, null); 257 | dmUI.get(id).close.setAttribute("dlID", id); 258 | try { 259 | var reg = /(.*)\.([^\.]*)$/.exec(name); 260 | dmUI.get(id).name = reg[1] + " (" + reg[2] + ")"; 261 | } 262 | catch (e) { 263 | dmUI.get(id).name = name; 264 | } 265 | dmUI.get(id).description = msg; 266 | }); 267 | self.port.on("download-update", function(id, percent, msg, amountTransferred, size, speed) { 268 | if (id == -1) return; 269 | // 270 | if (!dmUI.get(id)) return; 271 | dmUI.get(id).progress = percent + "%"; 272 | dmUI.get(id).progress.removeAttribute("type"); 273 | dmUI.get(id).description = msg 274 | .replace("%f1", amountTransferred) 275 | .replace("%f2", size) 276 | .replace("%f3", speed); 277 | }); 278 | self.port.on("download-paused", function(id, msg) { 279 | dmUI.get(id).description = msg; 280 | dmUI.get(id).progress.setAttribute("type", "inactive"); 281 | }); 282 | self.port.on("download-done", function(id, msg, rm) { 283 | dmUI.get(id).progress = "100%"; 284 | dmUI.get(id).description = msg; 285 | if (rm) { 286 | dmUI.remove(id); 287 | } 288 | }); 289 | self.port.on("extract", function(id, msg, rm) { 290 | dmUI.get(id).description = msg; 291 | if (rm) { 292 | dmUI.remove(id); 293 | } 294 | }); 295 | 296 | downloadButton.addEventListener("click", function () { 297 | self.port.emit("cmd", "download"); 298 | }, true); 299 | formatsButton.addEventListener("click", function () { 300 | self.port.emit("cmd", "formats"); 301 | }, true); 302 | $("tools").addEventListener("click", function () { 303 | self.port.emit("cmd", "tools"); 304 | }, true); 305 | $("embed").addEventListener("click", function () { 306 | self.port.emit("cmd", "embed"); 307 | }, true); 308 | $("settings-button").addEventListener("click", function () { 309 | self.port.emit("cmd", "settings"); 310 | }, true); 311 | $("bug-button").addEventListener("click", function () { 312 | self.port.emit("cmd", "bug"); 313 | }, true); 314 | $("faqs-button").addEventListener("click", function () { 315 | self.port.emit("cmd", "faqs"); 316 | }, true); 317 | aCheckbox.addEventListener("change", function () { 318 | self.port.emit("cmd", "do-extract", aCheckbox.checked); 319 | }); 320 | subCheckbox.addEventListener("change", function () { 321 | self.port.emit("cmd", "do-subtitle", subCheckbox.checked); 322 | }); 323 | sCheckbox.addEventListener("change", function () { 324 | self.port.emit("cmd", "do-size", sCheckbox.checked); 325 | }); 326 | 327 | 328 | //Update UI 329 | self.port.on("update", function(doExtract, doSubtitle, doFileSize, dIndex, vIndex, fIndex, isRed) { 330 | aCheckbox.checked = doExtract; 331 | subCheckbox.checked = doSubtitle; 332 | sCheckbox.checked = doFileSize; 333 | download.value = dIndex; 334 | quality.value = vIndex; 335 | format.value = fIndex; 336 | if (isRed) { 337 | downloadButton.setAttribute("type", "active"); 338 | formatsButton.setAttribute("type", "active"); 339 | downloadButton.textContent = _("quick-download"); 340 | qdtoggle.style.display = "block"; 341 | } 342 | else { 343 | downloadButton.removeAttribute("type"); 344 | formatsButton.removeAttribute("type"); 345 | downloadButton.textContent = _("open-youtube"); 346 | qdtoggle.style.display = "none"; 347 | } 348 | downloadButton[isRed ? "setAttribute" : "removeAttribute"]("type", "active"); 349 | // If there is no download switch to download tab 350 | if (dmUI.isEmpty()) { 351 | tabSelector(1); 352 | } 353 | // Restore sliding panels to default state 354 | cleanup(); 355 | }); 356 | -------------------------------------------------------------------------------- /src/data/report/report.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url(google-font.woff) format('woff'); 6 | } 7 | @font-face { 8 | font-family: 'Segoe UI'; 9 | src: local("Segoe UI"), 10 | local("Segoe"), 11 | local("Segoe WP"), 12 | url('segoeui.woff') format('woff'); 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* General Code */ 18 | 19 | body { 20 | font-family: "Open Sans",sans-serif,Roboto,arial; 21 | font-size: 15px; 22 | background-color: white; 23 | -moz-user-select: none; 24 | margin: 0; 25 | overflow: hidden; 26 | color: white; 27 | cursor: default; 28 | } 29 | table { 30 | font-size: 14px; 31 | } 32 | #global-container { 33 | width: 100%; 34 | height: 100%; 35 | } 36 | .tabs { 37 | position: fixed; 38 | bottom: 0; 39 | width: 300px; 40 | height: 30px; 41 | background-color: #FF6C09; 42 | -moz-user-select: none; 43 | } 44 | .tab { 45 | display: inline-block; 46 | width: 100px; 47 | height: 100%; 48 | cursor: pointer; 49 | -moz-user-select: none; 50 | margin-right: -4px; 51 | padding-top: 2px; 52 | } 53 | .tab:hover:active { 54 | opacity: 0.5; 55 | } 56 | #selected { 57 | position: absolute; 58 | top: 0; 59 | height: 2px; 60 | width: 100px; 61 | background-color: white; 62 | transition: transform 300ms; 63 | } 64 | #home, 65 | #downloads, 66 | #preferences { 67 | background-repeat: no-repeat; 68 | background-position: center; 69 | } 70 | #home { 71 | background-image: -moz-image-rect(url("tabs.png"), 0, 16, 16, 0); 72 | } 73 | #downloads { 74 | background-image: -moz-image-rect(url("tabs.png"), 0, 48, 16, 32); 75 | } 76 | #preferences { 77 | background-image: -moz-image-rect(url("tabs.png"), 0, 32, 16, 16); 78 | } 79 | #tabpanels { 80 | width: 900px; 81 | height: 200px; 82 | transition: transform 300ms; 83 | position: absolute; 84 | font-size: 0; /* preventing extra margin between child elements (MAC issue) */ 85 | } 86 | .tabpanel { 87 | display: inline-block; 88 | width: 300px; 89 | height: 200px; 90 | background-color: #FF6C09; 91 | position: relative; 92 | margin: 0; /* preventing extra margin between siblings*/ 93 | } 94 | #click-handler { 95 | display: none; 96 | position: fixed; 97 | top: 0; 98 | width: 600px; 99 | height: 200px; 100 | -moz-user-select: none; 101 | } 102 | #home-panel, 103 | #downloads-panel, 104 | #preferences-panel { 105 | -moz-user-select: none; 106 | } 107 | label { 108 | -moz-user-select: none; 109 | } 110 | input[type="radio"] { 111 | display: none; 112 | } 113 | input[type="radio"] + div { 114 | display: inline-block; 115 | border: 2px solid white; 116 | width: 10px; 117 | height: 10px; 118 | border-radius: 100px; 119 | margin-right: 5px; 120 | margin-left: 4px; 121 | margin-top: 1px; 122 | } 123 | .radio { 124 | cursor: pointer; 125 | } 126 | .radio:hover > div { 127 | background-color: rgba(255, 255, 255, 0.4); 128 | } 129 | input[type="radio"]:checked + div .checked { 130 | display: block; 131 | } 132 | input[type="radio"]:checked + div { 133 | background-color: transparent; 134 | } 135 | .checked { 136 | width: 6px; 137 | height: 6px; 138 | margin-left: 2px; 139 | margin-top: 2px; 140 | border-radius: 100px; 141 | background-color: white; 142 | display: none; 143 | pointer-events: none; 144 | } 145 | .text { 146 | display: inline-block; 147 | position: relative; 148 | top: -2px; 149 | } 150 | label { 151 | display: inline-block; 152 | width: 100%; 153 | text-align: left; 154 | } 155 | 156 | /* Home Tab */ 157 | 158 | #buttons { 159 | position: absolute; 160 | width: 272px; 161 | margin-left: 16px; 162 | margin-top: 16px; 163 | } 164 | .mbutton { 165 | display: inline-block; 166 | text-align: center; 167 | background-repeat: no-repeat; 168 | background-position: center 12px; 169 | background-color: #F79646; 170 | font-size: 13px; 171 | cursor: pointer; 172 | line-height: 128px; 173 | color: white; 174 | width: 128px; 175 | height: 78px; 176 | transition: background-color 150ms; 177 | } 178 | .mbutton:not(#links):hover { 179 | background-color: #FFA05A!important; 180 | } 181 | .mbutton:not(#links):hover:active { 182 | background-color: #FF8830!important; 183 | } 184 | #qd-container { 185 | margin-right: 8px; 186 | margin-bottom: 12px; 187 | } 188 | #download { 189 | background-image: -moz-image-rect(url("38.png"), 0, 76, 38, 38); 190 | background-position: center 13px!important; 191 | } 192 | #download[type="active"] { 193 | background-image: -moz-image-rect(url("38.png"), 0, 38, 38, 0); 194 | } 195 | #links { 196 | background: #F79646 url("links.png") no-repeat center 10px; 197 | opacity: 0.5; 198 | cursor: default; 199 | } 200 | #links[type="active"] { 201 | opacity: 1; 202 | cursor: pointer; 203 | } 204 | #links[type="active"]:hover { 205 | background-color: #FFA05A!important; 206 | cursor: pointer; 207 | } 208 | #tools { 209 | background-image: -moz-image-rect(url("38.png"), 0, 152, 38, 114); 210 | margin-right: 8px; 211 | } 212 | #embed { 213 | background-image: -moz-image-rect(url("38.png"), 0, 114, 38, 76); 214 | } 215 | #qd-toggle { 216 | background-image: -moz-image-rect(url("16.png"), 0, 32, 16, 16); 217 | } 218 | 219 | #video-preferences { 220 | z-index: 1; 221 | width: 300px; 222 | height: 110px; 223 | cursor: default; 224 | position: absolute; 225 | background-color: #FF6C09; 226 | transition: transform 280ms; 227 | border-top: 1px solid white; 228 | -moz-user-select: none; 229 | } 230 | #format-addition, 231 | #quality-addition { 232 | position: absolute; 233 | top: 5px; 234 | font-size: 13px; 235 | } 236 | #format-addition { 237 | width: 140px; 238 | height: 100px; 239 | left: 5px; 240 | } 241 | #quality-addition { 242 | width: 142px; 243 | height: 100px; 244 | left: 145px; 245 | } 246 | .format-title, 247 | .quality-title { 248 | font-size: 13px; 249 | text-align: center; 250 | display: block; 251 | height: 24px; 252 | position: absolute; 253 | margin-top: 38px; 254 | } 255 | .format-title { 256 | width: 72px; 257 | } 258 | .fcontainer, 259 | .qcontainer { 260 | display: block; 261 | position: absolute; 262 | right: 0; 263 | padding-left: 5px; 264 | } 265 | .fcontainer { 266 | width: 70px; 267 | height: 76px; 268 | margin-top: 14px; 269 | } 270 | .qcontainer { 271 | width: 87px; 272 | height: 95px; 273 | margin-top: 4px; 274 | } 275 | #qd-toggle { 276 | background-repeat: no-repeat; 277 | background-position: center; 278 | position: absolute; 279 | top: 6px; 280 | left: 106px; 281 | opacity: 0.5; 282 | height: 16px; 283 | width: 16px; 284 | transition: opacity 180ms; 285 | } 286 | #qd-toggle:hover { 287 | opacity: 1; 288 | cursor: pointer; 289 | } 290 | #qd-toggle:hover:active { 291 | opacity: 0.5; 292 | } 293 | 294 | /* Downloads Tab */ 295 | 296 | #wrapper { 297 | position: absolute; 298 | left: 16px; 299 | top: 13px; 300 | width: 268px; 301 | height: 176px; 302 | overflow: hidden; 303 | } 304 | .d-content { 305 | display: none; 306 | position: absolute; 307 | width: 285px!important; 308 | height: 176px!important; 309 | overflow-x: hidden; 310 | overflow-y: scroll; 311 | } 312 | .download-item { 313 | width: 100%; 314 | background: url("download.png") 10px center no-repeat; 315 | margin-bottom: 4px; 316 | cursor: pointer; 317 | font-family: "Segoe UI",sans-serif,Roboto,arial; 318 | background-color: #F79646; 319 | transition: background-color 180ms; 320 | } 321 | .download-item:hover { 322 | background-color: #FFA050; 323 | } 324 | .download-item:last-child { 325 | margin-bottom: 0; 326 | } 327 | .download-item span { 328 | display: block; 329 | width: 192px; 330 | text-overflow: " .... "; 331 | overflow: hidden; 332 | white-space: nowrap; 333 | line-height: 16px; 334 | font-size: 12px; 335 | } 336 | .body { 337 | display: inline-block; 338 | width: 192px; 339 | height: 55px; 340 | margin-left: 42px; 341 | } 342 | #top-text { 343 | margin-bottom: -5px; 344 | margin-top: 4px; 345 | } 346 | .progress-outer { 347 | display: inline-block; 348 | height: 5px; 349 | width: 100%; 350 | margin: 10px 0 4px 0; 351 | background: none; 352 | } 353 | .progress-inner { 354 | background: white; 355 | width: 0%; 356 | height: 100%; 357 | } 358 | .progress-inner[type="inactive"] { 359 | background: #E3FF23; 360 | } 361 | .cancel { 362 | display: inline-block; 363 | background: -moz-image-rect(url("16.png"), 0, 16, 16, 0) center no-repeat; 364 | width: 34px; 365 | height: 55px; 366 | position: absolute; 367 | } 368 | .cancel:hover:active { 369 | opacity: 0.5; 370 | } 371 | .nd-content { 372 | width: 300px; 373 | height: 200px; 374 | padding-top: 110px; 375 | position: absolute; 376 | font-size: 14px; 377 | text-align: center; 378 | background: url("no-download.png") center 45% no-repeat; 379 | -moz-box-sizing: border-box; 380 | } 381 | 382 | /* Preferences Tab */ 383 | 384 | #container { 385 | font-size: 13px; 386 | position: absolute; 387 | width: 300px; 388 | height: 200px; 389 | padding-top: 14px; 390 | padding-left: 10px; 391 | -moz-box-sizing: border-box; 392 | } 393 | .preference { 394 | display: block; 395 | } 396 | .title { 397 | display: inline-block; 398 | position: absolute; 399 | margin-top: 3px; 400 | padding-left: 8px; 401 | } 402 | .checkBox { 403 | width: 25px; 404 | height: 22px; 405 | margin-bottom: 7px; 406 | display: inline-block; 407 | } 408 | .checkbox span { 409 | width: 22px; 410 | height: 22px; 411 | display: block; 412 | cursor: pointer; 413 | border: 1px solid white; 414 | transition: background-color 110ms; 415 | } 416 | .checkbox span:hover { 417 | background-color: rgba(255, 255, 255, 0.4); 418 | } 419 | .checkbox span:active { 420 | background-color: rgba(255, 255, 255, 0.2); 421 | } 422 | .checkbox input { 423 | display: none; 424 | } 425 | .checkbox input:checked + span { 426 | background-position: center; 427 | background-repeat: no-repeat; 428 | background-image: -moz-image-rect(url("preferences.png"), 0, 32, 16, 16); 429 | } 430 | .button { 431 | width: 22px; 432 | height: 22px; 433 | cursor: pointer; 434 | text-align: center; 435 | margin-bottom: 5px; 436 | display: inline-block; 437 | border: 1px solid white; 438 | background-position: center; 439 | background-repeat: no-repeat; 440 | transition: background-color 100ms; 441 | } 442 | .button:hover { 443 | background-color: rgba(255, 255, 255, 0.4); 444 | } 445 | .button:hover:active { 446 | background-color: rgba(255, 255, 255, 0.2); 447 | } 448 | #folder-button { 449 | background-image: -moz-image-rect(url("preferences.png"), 0, 16, 16, 0); 450 | } 451 | #settings-button { 452 | background-image: -moz-image-rect(url("preferences.png"), 0, 48, 16, 32); 453 | } 454 | #faqs-button { 455 | background-image: -moz-image-rect(url("preferences.png"), 0, 80, 16, 64); 456 | } 457 | #bug-button { 458 | background-image: -moz-image-rect(url("preferences.png"), 0, 64, 16, 48); 459 | } 460 | 461 | #folder-preferences { 462 | z-index: 1; 463 | width: 300px; 464 | height: 100px; 465 | font-size: 13px; 466 | position: absolute; 467 | transition: transform 280ms; 468 | background-color: #FF6C09; 469 | border-top: 1px solid white; 470 | -moz-user-select: none; 471 | } 472 | #download-addition { 473 | width: 300px; 474 | display: block; 475 | position: absolute; 476 | text-align: center; 477 | padding-left: 5px; 478 | } 479 | #folder-title { 480 | height: 33px; 481 | display: block; 482 | text-align: center; 483 | padding-top: 5px; 484 | -moz-box-sizing: border-box; 485 | } 486 | .dlabel { 487 | max-width: 100px; 488 | display: inline-block; 489 | } 490 | 491 | 492 | -------------------------------------------------------------------------------- /src/locale/en-US.properties: -------------------------------------------------------------------------------- 1 | # Brand 2 | name = YouTube Video and Audio Downloader 3 | toolbar = YouTube Video and Audio Downloader 4 | iaextractor_description = a Download YouTube videos in all available formats and extract the original audio file. 5 | # Toolbar tooltip 6 | tooltip1 = Left click: Open download panel 7 | tooltip2 = Middle click (Red icon): Get video information 8 | tooltip4 = Size: %1Mb 9 | tooltip5 = Progress: %1% 10 | # main.js messages 11 | msg2 = No video in FLV format found for this video 12 | msg3 = Downloading Video: Quality: %1 - Format: %2 [%3] - Audio: %6 [%7] 13 | msg4 = No embedded video detected 14 | msg5 = For audio extraction form this video format, you need to install and configure FFmpeg from addon's settings. 15 | msg6 = Downloading... 16 | msg7 = Sending Download Request 17 | msg8 = Download Finished 18 | msg9 = Extracting Audio File 19 | msg10 = Audio Extraction Done 20 | msg11 = %f1 of %f2 (%f3) 21 | msg12 = Download Paused 22 | msg13 = Select user defined folder 23 | msg14 = Video File 24 | msg15 = Sent from YouTube Video and Audio Downloader 25 | msg16 = Size of video player is very small! 26 | msg17 = Download with a Download Manager 27 | msg18 = This extension supports file downloading by other download managers. Would you like to see the instruction? 28 | msg19 = Close and continue 29 | msg20 = Show instruction 30 | msg21 = Do not show this message again 31 | msg22 = Download Audio File 32 | msg23 = The selected file does not have any audio track. Would you like to download a proper audio track as well? Note: only if FFmpeg is installed, the extension will automatically combine the video and audio files for you. 33 | msg24 = Thank you for trying YouTube Video and Audio Downloader. Do you want the extension to automatically download and configure FFmpeg audio converter for you? This program will improve the conversion capability of the extension. 34 | msg25 = This action will reset your current settings back to the factory defaults! Are you sure? 35 | msg26 = FFmpeg has been installed and configured successfully. 36 | msg27 = FFmpeg installation 37 | msg28 = Start downloading FFmpeg. Please wait... 38 | msg29 = You already have FFmpeg installed. Would you like to remove the old installation and download it again? 39 | msg30 = "Conversion Tools" is now maintained as a separate project. Please first install it. 40 | msg31 = Select FFmpeg executable 41 | msg32 = Media Converter and Muxer is ready. Open the UI from toolbar panel 42 | msg33 = Media Converter and Muxer installation is cancelled 43 | msg34 = Media Converter and Muxer installation is failed 44 | msg35 = Downloading Media Converter and Muxer from "https://addons.mozilla.org/" 45 | msg36 = Media Converter and Muxer is not responding. Make sure it is functional 46 | # main.js errors 47 | err = Error 48 | err1 = The file is not a valid "FLV" format. Audio extraction is not possible. To extract audio file, please change video format to "FLV" or alternatively, use the offline conversion tool. 49 | err2 = No Audio Stream detected. 50 | err3 = Unsupported AAC profile. 51 | err4 = Invalid AAC sample rate index. 52 | err5 = Invalid AAC channel configuration. 53 | err6 = For audio extraction form this video format, you need to install and configure FFmpeg (https://www.ffmpeg.org/download.html) from addon's settings. 54 | err7 = To save files in "User defined folder", set a folder in Settings first. 55 | err8 = Not a valid combination. Keyboard shortcut is disabled! 56 | err9 = subtitle:: Cannot connect to YouTube server. 57 | err10 = subtitle::asyncCopy could not write to disk. 58 | err11 = No subtitle in the selected language is available for this video! 59 | err12 = FFmpeg audio converter is not available. Please download and install it. 60 | err13 = FFmpeg audio converter is not available at 61 | err14 = Flashgot extension is not installed. To activate this option install the extension. 62 | err15 = DownThemAll extension is not installed. To activate this option install the extension. 63 | err26 = Turbo Download Manager extension is not installed. To activate this option install the extension. 64 | err16 = Error during VEVO signature update 65 | err17 = FFmpeg installation failed: Download problem. 66 | err18 = FFmpeg installation failed: Link detection problem. 67 | err19 = FFmpeg installation failed: Downloaded file is too small! 68 | err20 = Cannot write to the destination folder! 69 | err21 = Internal error 70 | err22 = Process exited with an error code 71 | err23 = FFmpeg path cannot be resolved from 72 | err24 = FFmpeg conversion error 73 | err25 = FFmpeg converter exited with error code 74 | # main.js prompts 75 | prompt1 = All available video IDs 76 | prompt2 = Select a video ID from the list to open 77 | prompt3 = Save video as 78 | # report.html 79 | format-title = Format: 80 | quality-title = Quality: 81 | download = Download 82 | progress = Progress 83 | tools = Tools 84 | download-links = Download Links 85 | video-quality = Video Quality: 86 | no-download = No active downloads yet! 87 | conversion-tool = Conversion Tools 88 | settings = Show all preferences 89 | resolve-links = Resolve links size in the injected panel 90 | extract-embed = Embed Videos 91 | extract-audio = Extract or convert audio files 92 | extract-subtitle = Extract subtitle (if possible) 93 | download-folder = Download Folder 94 | quick-download = Quick Download 95 | open-youtube = Open YouTube 96 | bug-button = Open bug reporter 97 | faqs-button = Open FAQs page 98 | # package.json 99 | extension_title = Preferred file format: 100 | extension_description = Audio extraction is only supported for "FLV" video format. You can extend audio extraction capability (remuxing) by installing FFmpeg (Download and Configure FFmpeg section below). In case FFmpeg is installed, the preferred format is "MP4". 101 | extension_options.FLV = FLV 102 | extension_options.3GP = 3GP 103 | extension_options.MP4 = MP4 104 | extension_options.WebM = WebM 105 | quality_title = Select video quality: 106 | quality_options.HD1080p = HD1080p 107 | quality_options.HD720p = HD720p 108 | quality_options.High = High 109 | quality_options.Medium = Medium 110 | quality_options.Small = Small 111 | dFolder_title = Select download folder: 112 | dFolder_description = "Downloads" is equal to "%UserProfile%\Downloads" in Windows OS. 113 | dFolder_options.dft_dir = Downloads 114 | dFolder_options.hm_dir = Home 115 | dFolder_options.tmp_dir = Temp 116 | dFolder_options.dsk_dir = Desktop 117 | dFolder_options.slt_fdr = Always ask 118 | dFolder_options.urs_fdr = Custom 119 | doExtract_title = Extract audio from video file: 120 | userFolder_title = Custom download folder: 121 | userFolder_description = To enable this option, you need to set "Select download folder" to "Custom". 122 | open_title = Open download folder on completion: 123 | progressColor_title = Progressbar color: 124 | getFileSize_title = Calculate file-size of download links in the injected menu: 125 | namePattern_title = File-naming pattern: 126 | namePattern_description = Available options: [file_name], [extension], [author], [video_id], [video_resolution], [audio_bitrate], and [published_date] 127 | downloadHKey_title = Download video files using keyboard shortcut: 128 | downloadHKey_description = First click in the box and then enter a combination of A-Z keys with one or more modifiers. "Accel" is "Ctrl" or "Command" key based on your operation system and "Alt" is similar to "Option" key. 129 | inject_title = Place download button beneath YouTube players: 130 | inject_description = Restart is required. 131 | oneClickDownload_title = One click mode: 132 | oneClickDownload_description = (Gray icon) Open YouTube homepage. (Red icon) Detect and start downloading video. 133 | silentOneClickDownload_title = Silent download: 134 | silentOneClickDownload_description = Do not open download panel when "One click mode" is activated. 135 | forceVisible_title = Try to keep toolbar button visible: 136 | doSubtitle_title = Extract subtitle (if possible): 137 | subtitleLang_title = Video subtitle language: 138 | subtitleLang_description = We will only look for subtitles in this language. 139 | subtitleLang_options.en = English 140 | subtitleLang_options.ar = Arabic 141 | subtitleLang_options.zh = Chinese 142 | subtitleLang_options.nl = Dutch 143 | subtitleLang_options.fr = French 144 | subtitleLang_options.de = German 145 | subtitleLang_options.it = Italian 146 | subtitleLang_options.ja = Japanese 147 | subtitleLang_options.ko = Korean 148 | subtitleLang_options.pl = Polish 149 | subtitleLang_options.ru = Russian 150 | subtitleLang_options.es = Spanish 151 | subtitleLang_options.tr = Turkish 152 | ffmpegPath_title = FFmpeg(binary) location: 153 | ffmpegPath_description = You can download FFmpeg(binary) from https://ffmpeg.org. Note: if FFmpeg(binary) location contains non-English characters in the path, use the "FFmpeg(binary) location (for experts)" option. 154 | ffmpegPath-manual_title = FFmpeg(binary) location (for experts) 155 | ffmpegPath-manual_description = If FFmpeg(binary) cannot be set using the above browse button, or if the symlink is not resolved correctly, you can insert the correct path here. Please do not use this option if you are not sure how it works. 156 | ffmpegInputs_title = Input arguments for FFmpeg MP3 converter: 157 | ffmpegInputs_description = Optional argument: -output-location "path", To place the converted file in another directory. "path" is the path of a directory enclosed with double quotation marks (e.g. -output-location "d:\\new folder") 158 | ffmpegInputs3_title = Input arguments for FFmpeg audio remuxing: 159 | ffmpegInputs3_description = This option is only available for audio-only streams. Optional argument: -output-location "path" 160 | ffmpegInputs4_title = Input arguments for FFmpeg audio and video combiner: 161 | ffmpegInputs4_description = Optional argument: -output-location "path" 162 | welcome_title = Show welcome page on upgrade: 163 | doBatchMode_title = Download a proper audio track when video-only file is selected (Batch-mode): 164 | doBatchMode_description = If FFmpeg is installed, audio and video files will be automatically combined together. 165 | pretendHD_title = Always download the highest audio quality available in the batch-mode: 166 | doRemux_title = Remux audio-only streams (instead of MP3 conversion): 167 | doRemux_description = Only available for audio-only streams provided FFmpeg is installed. 168 | installFFmpeg_title = Download and configure FFmpeg audio converter (before clicking on the Proceed button, make sure you can visit https://sourceforge.net/projects/iaextractor/ webpage): 169 | installFFmpeg_label = Proceed 170 | reset_title = Reset all settings: 171 | reset_label = Reset 172 | deleteInputs_title = Delete original DASH (video-only and audio-only) streams after conversion 173 | deleteInputs_description = Only if FFmpeg is installed. 174 | showNotifications_title = Show desktop notifications: 175 | customUA_title = Set custom user-agent for the downloader: 176 | customUA_description = You can use custom user-agent to change your browser's identification to force getting a certain server-side content. 177 | protocol_title = Download protocol is: 178 | protocol_description = Warning: It is highly recommended to select "default" value to let the extension decide the best protocol. 179 | showFLV_title = Display "FLV" format in the injected panel: 180 | showWEBM_title = Display "WEBM" format in the injected panel: 181 | show3GP_title = Display "3GP" format in the injected panel: 182 | showMP4_title = Display "MP4" format in the injected panel: 183 | opusmixing_title = Consider OPUS format as the audio track of video and audio combiner when a "video-only" item with WEBM format is selected: 184 | opusmixing_description = If a WEBM "video-only" item is selected for download, by default only an OGG format "audio-only" item is used as the input of the combiner. By checking this option, either OPUS format "audio-only" or OGG format "audio-only" will be used (based on their bitrates). Before checking this option, make sure your player can play OPUS audio formats. 185 | getConverter_label = Media Converter 186 | getConverter_title = Convert to MP3, adjust volume, or Scale video: 187 | getConverter_description = Media converter allows you to perform conversion operations on media files. It is integrated with YouTube Video and Audio downloader. You can access it from the toolbar button 188 | --------------------------------------------------------------------------------