├── 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 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | --
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Download Links
27 |
28 |
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 |
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 |
32 |
33 |
72 |
73 |
103 |
104 |
128 |
129 |
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 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
48 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
-
67 |
68 |
...
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
84 |
95 |
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 |
--------------------------------------------------------------------------------