├── .gitignore ├── LICENSE ├── README.md ├── preview ├── icon.cdr ├── media-converter-big.png └── media-converter-small.png └── v3 ├── data ├── converter │ ├── element.js │ ├── ffmpeg.js │ ├── file.js │ ├── images │ │ ├── arrow.svg │ │ ├── busy.svg │ │ └── working.svg │ ├── index.css │ ├── index.html │ ├── log.js │ ├── matched │ │ ├── matched.js │ │ └── matched.json │ ├── misc.js │ ├── native │ │ ├── core.js │ │ └── ffmpeg.js │ ├── restore.js │ ├── ui.js │ ├── utils.js │ └── wasm │ │ ├── ffmpeg.js │ │ ├── ffmpeg │ │ ├── README │ │ ├── ffmpeg-core.js │ │ ├── ffmpeg-core.wasm │ │ └── ffmpeg-core.worker.js │ │ ├── instance.html │ │ └── instance.js └── icons │ ├── 128.png │ ├── 16.png │ ├── 19.png │ ├── 256.png │ ├── 32.png │ ├── 38.png │ ├── 48.png │ └── 64.png ├── manifest.json └── worker.js /.gitignore: -------------------------------------------------------------------------------- 1 | addon-sdk* 2 | node_modules/ 3 | test/ 4 | .DS_Store 5 | Thumbs.db -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MOZILLA PUBLIC LICENSE 2 | Version 1.1 3 | 4 | --------------- 5 | 6 | 1. Definitions. 7 | 8 | 1.0.1. "Commercial Use" means distribution or otherwise making the 9 | Covered Code available to a third party. 10 | 11 | 1.1. "Contributor" means each entity that creates or contributes to 12 | the creation of Modifications. 13 | 14 | 1.2. "Contributor Version" means the combination of the Original 15 | Code, prior Modifications used by a Contributor, and the Modifications 16 | made by that particular Contributor. 17 | 18 | 1.3. "Covered Code" means the Original Code or Modifications or the 19 | combination of the Original Code and Modifications, in each case 20 | including portions thereof. 21 | 22 | 1.4. "Electronic Distribution Mechanism" means a mechanism generally 23 | accepted in the software development community for the electronic 24 | transfer of data. 25 | 26 | 1.5. "Executable" means Covered Code in any form other than Source 27 | Code. 28 | 29 | 1.6. "Initial Developer" means the individual or entity identified 30 | as the Initial Developer in the Source Code notice required by Exhibit 31 | A. 32 | 33 | 1.7. "Larger Work" means a work which combines Covered Code or 34 | portions thereof with code not governed by the terms of this License. 35 | 36 | 1.8. "License" means this document. 37 | 38 | 1.8.1. "Licensable" means having the right to grant, to the maximum 39 | extent possible, whether at the time of the initial grant or 40 | subsequently acquired, any and all of the rights conveyed herein. 41 | 42 | 1.9. "Modifications" means any addition to or deletion from the 43 | substance or structure of either the Original Code or any previous 44 | Modifications. When Covered Code is released as a series of files, a 45 | Modification is: 46 | A. Any addition to or deletion from the contents of a file 47 | containing Original Code or previous Modifications. 48 | 49 | B. Any new file that contains any part of the Original Code or 50 | previous Modifications. 51 | 52 | 1.10. "Original Code" means Source Code of computer software code 53 | which is described in the Source Code notice required by Exhibit A as 54 | Original Code, and which, at the time of its release under this 55 | License is not already Covered Code governed by this License. 56 | 57 | 1.10.1. "Patent Claims" means any patent claim(s), now owned or 58 | hereafter acquired, including without limitation, method, process, 59 | and apparatus claims, in any patent Licensable by grantor. 60 | 61 | 1.11. "Source Code" means the preferred form of the Covered Code for 62 | making modifications to it, including all modules it contains, plus 63 | any associated interface definition files, scripts used to control 64 | compilation and installation of an Executable, or source code 65 | differential comparisons against either the Original Code or another 66 | well known, available Covered Code of the Contributor's choice. The 67 | Source Code can be in a compressed or archival form, provided the 68 | appropriate decompression or de-archiving software is widely available 69 | for no charge. 70 | 71 | 1.12. "You" (or "Your") means an individual or a legal entity 72 | exercising rights under, and complying with all of the terms of, this 73 | License or a future version of this License issued under Section 6.1. 74 | For legal entities, "You" includes any entity which controls, is 75 | controlled by, or is under common control with You. For purposes of 76 | this definition, "control" means (a) the power, direct or indirect, 77 | to cause the direction or management of such entity, whether by 78 | contract or otherwise, or (b) ownership of more than fifty percent 79 | (50%) of the outstanding shares or beneficial ownership of such 80 | entity. 81 | 82 | 2. Source Code License. 83 | 84 | 2.1. The Initial Developer Grant. 85 | The Initial Developer hereby grants You a world-wide, royalty-free, 86 | non-exclusive license, subject to third party intellectual property 87 | claims: 88 | (a) under intellectual property rights (other than patent or 89 | trademark) Licensable by Initial Developer to use, reproduce, 90 | modify, display, perform, sublicense and distribute the Original 91 | Code (or portions thereof) with or without Modifications, and/or 92 | as part of a Larger Work; and 93 | 94 | (b) under Patents Claims infringed by the making, using or 95 | selling of Original Code, to make, have made, use, practice, 96 | sell, and offer for sale, and/or otherwise dispose of the 97 | Original Code (or portions thereof). 98 | 99 | (c) the licenses granted in this Section 2.1(a) and (b) are 100 | effective on the date Initial Developer first distributes 101 | Original Code under the terms of this License. 102 | 103 | (d) Notwithstanding Section 2.1(b) above, no patent license is 104 | granted: 1) for code that You delete from the Original Code; 2) 105 | separate from the Original Code; or 3) for infringements caused 106 | by: i) the modification of the Original Code or ii) the 107 | combination of the Original Code with other software or devices. 108 | 109 | 2.2. Contributor Grant. 110 | Subject to third party intellectual property claims, each Contributor 111 | hereby grants You a world-wide, royalty-free, non-exclusive license 112 | 113 | (a) under intellectual property rights (other than patent or 114 | trademark) Licensable by Contributor, to use, reproduce, modify, 115 | display, perform, sublicense and distribute the Modifications 116 | created by such Contributor (or portions thereof) either on an 117 | unmodified basis, with other Modifications, as Covered Code 118 | and/or as part of a Larger Work; and 119 | 120 | (b) under Patent Claims infringed by the making, using, or 121 | selling of Modifications made by that Contributor either alone 122 | and/or in combination with its Contributor Version (or portions 123 | of such combination), to make, use, sell, offer for sale, have 124 | made, and/or otherwise dispose of: 1) Modifications made by that 125 | Contributor (or portions thereof); and 2) the combination of 126 | Modifications made by that Contributor with its Contributor 127 | Version (or portions of such combination). 128 | 129 | (c) the licenses granted in Sections 2.2(a) and 2.2(b) are 130 | effective on the date Contributor first makes Commercial Use of 131 | the Covered Code. 132 | 133 | (d) Notwithstanding Section 2.2(b) above, no patent license is 134 | granted: 1) for any code that Contributor has deleted from the 135 | Contributor Version; 2) separate from the Contributor Version; 136 | 3) for infringements caused by: i) third party modifications of 137 | Contributor Version or ii) the combination of Modifications made 138 | by that Contributor with other software (except as part of the 139 | Contributor Version) or other devices; or 4) under Patent Claims 140 | infringed by Covered Code in the absence of Modifications made by 141 | that Contributor. 142 | 143 | 3. Distribution Obligations. 144 | 145 | 3.1. Application of License. 146 | The Modifications which You create or to which You contribute are 147 | governed by the terms of this License, including without limitation 148 | Section 2.2. The Source Code version of Covered Code may be 149 | distributed only under the terms of this License or a future version 150 | of this License released under Section 6.1, and You must include a 151 | copy of this License with every copy of the Source Code You 152 | distribute. You may not offer or impose any terms on any Source Code 153 | version that alters or restricts the applicable version of this 154 | License or the recipients' rights hereunder. However, You may include 155 | an additional document offering the additional rights described in 156 | Section 3.5. 157 | 158 | 3.2. Availability of Source Code. 159 | Any Modification which You create or to which You contribute must be 160 | made available in Source Code form under the terms of this License 161 | either on the same media as an Executable version or via an accepted 162 | Electronic Distribution Mechanism to anyone to whom you made an 163 | Executable version available; and if made available via Electronic 164 | Distribution Mechanism, must remain available for at least twelve (12) 165 | months after the date it initially became available, or at least six 166 | (6) months after a subsequent version of that particular Modification 167 | has been made available to such recipients. You are responsible for 168 | ensuring that the Source Code version remains available even if the 169 | Electronic Distribution Mechanism is maintained by a third party. 170 | 171 | 3.3. Description of Modifications. 172 | You must cause all Covered Code to which You contribute to contain a 173 | file documenting the changes You made to create that Covered Code and 174 | the date of any change. You must include a prominent statement that 175 | the Modification is derived, directly or indirectly, from Original 176 | Code provided by the Initial Developer and including the name of the 177 | Initial Developer in (a) the Source Code, and (b) in any notice in an 178 | Executable version or related documentation in which You describe the 179 | origin or ownership of the Covered Code. 180 | 181 | 3.4. Intellectual Property Matters 182 | (a) Third Party Claims. 183 | If Contributor has knowledge that a license under a third party's 184 | intellectual property rights is required to exercise the rights 185 | granted by such Contributor under Sections 2.1 or 2.2, 186 | Contributor must include a text file with the Source Code 187 | distribution titled "LEGAL" which describes the claim and the 188 | party making the claim in sufficient detail that a recipient will 189 | know whom to contact. If Contributor obtains such knowledge after 190 | the Modification is made available as described in Section 3.2, 191 | Contributor shall promptly modify the LEGAL file in all copies 192 | Contributor makes available thereafter and shall take other steps 193 | (such as notifying appropriate mailing lists or newsgroups) 194 | reasonably calculated to inform those who received the Covered 195 | Code that new knowledge has been obtained. 196 | 197 | (b) Contributor APIs. 198 | If Contributor's Modifications include an application programming 199 | interface and Contributor has knowledge of patent licenses which 200 | are reasonably necessary to implement that API, Contributor must 201 | also include this information in the LEGAL file. 202 | 203 | (c) Representations. 204 | Contributor represents that, except as disclosed pursuant to 205 | Section 3.4(a) above, Contributor believes that Contributor's 206 | Modifications are Contributor's original creation(s) and/or 207 | Contributor has sufficient rights to grant the rights conveyed by 208 | this License. 209 | 210 | 3.5. Required Notices. 211 | You must duplicate the notice in Exhibit A in each file of the Source 212 | Code. If it is not possible to put such notice in a particular Source 213 | Code file due to its structure, then You must include such notice in a 214 | location (such as a relevant directory) where a user would be likely 215 | to look for such a notice. If You created one or more Modification(s) 216 | You may add your name as a Contributor to the notice described in 217 | Exhibit A. You must also duplicate this License in any documentation 218 | for the Source Code where You describe recipients' rights or ownership 219 | rights relating to Covered Code. You may choose to offer, and to 220 | charge a fee for, warranty, support, indemnity or liability 221 | obligations to one or more recipients of Covered Code. However, You 222 | may do so only on Your own behalf, and not on behalf of the Initial 223 | Developer or any Contributor. You must make it absolutely clear than 224 | any such warranty, support, indemnity or liability obligation is 225 | offered by You alone, and You hereby agree to indemnify the Initial 226 | Developer and every Contributor for any liability incurred by the 227 | Initial Developer or such Contributor as a result of warranty, 228 | support, indemnity or liability terms You offer. 229 | 230 | 3.6. Distribution of Executable Versions. 231 | You may distribute Covered Code in Executable form only if the 232 | requirements of Section 3.1-3.5 have been met for that Covered Code, 233 | and if You include a notice stating that the Source Code version of 234 | the Covered Code is available under the terms of this License, 235 | including a description of how and where You have fulfilled the 236 | obligations of Section 3.2. The notice must be conspicuously included 237 | in any notice in an Executable version, related documentation or 238 | collateral in which You describe recipients' rights relating to the 239 | Covered Code. You may distribute the Executable version of Covered 240 | Code or ownership rights under a license of Your choice, which may 241 | contain terms different from this License, provided that You are in 242 | compliance with the terms of this License and that the license for the 243 | Executable version does not attempt to limit or alter the recipient's 244 | rights in the Source Code version from the rights set forth in this 245 | License. If You distribute the Executable version under a different 246 | license You must make it absolutely clear that any terms which differ 247 | from this License are offered by You alone, not by the Initial 248 | Developer or any Contributor. You hereby agree to indemnify the 249 | Initial Developer and every Contributor for any liability incurred by 250 | the Initial Developer or such Contributor as a result of any such 251 | terms You offer. 252 | 253 | 3.7. Larger Works. 254 | You may create a Larger Work by combining Covered Code with other code 255 | not governed by the terms of this License and distribute the Larger 256 | Work as a single product. In such a case, You must make sure the 257 | requirements of this License are fulfilled for the Covered Code. 258 | 259 | 4. Inability to Comply Due to Statute or Regulation. 260 | 261 | If it is impossible for You to comply with any of the terms of this 262 | License with respect to some or all of the Covered Code due to 263 | statute, judicial order, or regulation then You must: (a) comply with 264 | the terms of this License to the maximum extent possible; and (b) 265 | describe the limitations and the code they affect. Such description 266 | must be included in the LEGAL file described in Section 3.4 and must 267 | be included with all distributions of the Source Code. Except to the 268 | extent prohibited by statute or regulation, such description must be 269 | sufficiently detailed for a recipient of ordinary skill to be able to 270 | understand it. 271 | 272 | 5. Application of this License. 273 | 274 | This License applies to code to which the Initial Developer has 275 | attached the notice in Exhibit A and to related Covered Code. 276 | 277 | 6. Versions of the License. 278 | 279 | 6.1. New Versions. 280 | Netscape Communications Corporation ("Netscape") may publish revised 281 | and/or new versions of the License from time to time. Each version 282 | will be given a distinguishing version number. 283 | 284 | 6.2. Effect of New Versions. 285 | Once Covered Code has been published under a particular version of the 286 | License, You may always continue to use it under the terms of that 287 | version. You may also choose to use such Covered Code under the terms 288 | of any subsequent version of the License published by Netscape. No one 289 | other than Netscape has the right to modify the terms applicable to 290 | Covered Code created under this License. 291 | 292 | 6.3. Derivative Works. 293 | If You create or use a modified version of this License (which you may 294 | only do in order to apply it to code which is not already Covered Code 295 | governed by this License), You must (a) rename Your license so that 296 | the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", 297 | "MPL", "NPL" or any confusingly similar phrase do not appear in your 298 | license (except to note that your license differs from this License) 299 | and (b) otherwise make it clear that Your version of the license 300 | contains terms which differ from the Mozilla Public License and 301 | Netscape Public License. (Filling in the name of the Initial 302 | Developer, Original Code or Contributor in the notice described in 303 | Exhibit A shall not of themselves be deemed to be modifications of 304 | this License.) 305 | 306 | 7. DISCLAIMER OF WARRANTY. 307 | 308 | COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, 309 | WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, 310 | WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF 311 | DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. 312 | THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE 313 | IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, 314 | YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE 315 | COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER 316 | OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF 317 | ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. 318 | 319 | 8. TERMINATION. 320 | 321 | 8.1. This License and the rights granted hereunder will terminate 322 | automatically if You fail to comply with terms herein and fail to cure 323 | such breach within 30 days of becoming aware of the breach. All 324 | sublicenses to the Covered Code which are properly granted shall 325 | survive any termination of this License. Provisions which, by their 326 | nature, must remain in effect beyond the termination of this License 327 | shall survive. 328 | 329 | 8.2. If You initiate litigation by asserting a patent infringement 330 | claim (excluding declatory judgment actions) against Initial Developer 331 | or a Contributor (the Initial Developer or Contributor against whom 332 | You file such action is referred to as "Participant") alleging that: 333 | 334 | (a) such Participant's Contributor Version directly or indirectly 335 | infringes any patent, then any and all rights granted by such 336 | Participant to You under Sections 2.1 and/or 2.2 of this License 337 | shall, upon 60 days notice from Participant terminate prospectively, 338 | unless if within 60 days after receipt of notice You either: (i) 339 | agree in writing to pay Participant a mutually agreeable reasonable 340 | royalty for Your past and future use of Modifications made by such 341 | Participant, or (ii) withdraw Your litigation claim with respect to 342 | the Contributor Version against such Participant. If within 60 days 343 | of notice, a reasonable royalty and payment arrangement are not 344 | mutually agreed upon in writing by the parties or the litigation claim 345 | is not withdrawn, the rights granted by Participant to You under 346 | Sections 2.1 and/or 2.2 automatically terminate at the expiration of 347 | the 60 day notice period specified above. 348 | 349 | (b) any software, hardware, or device, other than such Participant's 350 | Contributor Version, directly or indirectly infringes any patent, then 351 | any rights granted to You by such Participant under Sections 2.1(b) 352 | and 2.2(b) are revoked effective as of the date You first made, used, 353 | sold, distributed, or had made, Modifications made by that 354 | Participant. 355 | 356 | 8.3. If You assert a patent infringement claim against Participant 357 | alleging that such Participant's Contributor Version directly or 358 | indirectly infringes any patent where such claim is resolved (such as 359 | by license or settlement) prior to the initiation of patent 360 | infringement litigation, then the reasonable value of the licenses 361 | granted by such Participant under Sections 2.1 or 2.2 shall be taken 362 | into account in determining the amount or value of any payment or 363 | license. 364 | 365 | 8.4. In the event of termination under Sections 8.1 or 8.2 above, 366 | all end user license agreements (excluding distributors and resellers) 367 | which have been validly granted by You or any distributor hereunder 368 | prior to termination shall survive termination. 369 | 370 | 9. LIMITATION OF LIABILITY. 371 | 372 | UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT 373 | (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL 374 | DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, 375 | OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR 376 | ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY 377 | CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, 378 | WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER 379 | COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN 380 | INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF 381 | LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY 382 | RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW 383 | PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE 384 | EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO 385 | THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. 386 | 387 | 10. U.S. GOVERNMENT END USERS. 388 | 389 | The Covered Code is a "commercial item," as that term is defined in 390 | 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer 391 | software" and "commercial computer software documentation," as such 392 | terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 393 | C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), 394 | all U.S. Government End Users acquire Covered Code with only those 395 | rights set forth herein. 396 | 397 | 11. MISCELLANEOUS. 398 | 399 | This License represents the complete agreement concerning subject 400 | matter hereof. If any provision of this License is held to be 401 | unenforceable, such provision shall be reformed only to the extent 402 | necessary to make it enforceable. This License shall be governed by 403 | California law provisions (except to the extent applicable law, if 404 | any, provides otherwise), excluding its conflict-of-law provisions. 405 | With respect to disputes in which at least one party is a citizen of, 406 | or an entity chartered or registered to do business in the United 407 | States of America, any litigation relating to this License shall be 408 | subject to the jurisdiction of the Federal Courts of the Northern 409 | District of California, with venue lying in Santa Clara County, 410 | California, with the losing party responsible for costs, including 411 | without limitation, court costs and reasonable attorneys' fees and 412 | expenses. The application of the United Nations Convention on 413 | Contracts for the International Sale of Goods is expressly excluded. 414 | Any law or regulation which provides that the language of a contract 415 | shall be construed against the drafter shall not apply to this 416 | License. 417 | 418 | 12. RESPONSIBILITY FOR CLAIMS. 419 | 420 | As between Initial Developer and the Contributors, each party is 421 | responsible for claims and damages arising, directly or indirectly, 422 | out of its utilization of rights under this License and You agree to 423 | work with Initial Developer and Contributors to distribute such 424 | responsibility on an equitable basis. Nothing herein is intended or 425 | shall be deemed to constitute any admission of liability. 426 | 427 | 13. MULTIPLE-LICENSED CODE. 428 | 429 | Initial Developer may designate portions of the Covered Code as 430 | "Multiple-Licensed". "Multiple-Licensed" means that the Initial 431 | Developer permits you to utilize portions of the Covered Code under 432 | Your choice of the NPL or the alternative licenses, if any, specified 433 | by the Initial Developer in the file described in Exhibit A. 434 | 435 | EXHIBIT A -Mozilla Public License. 436 | 437 | ``The contents of this file are subject to the Mozilla Public License 438 | Version 1.1 (the "License"); you may not use this file except in 439 | compliance with the License. You may obtain a copy of the License at 440 | http://www.mozilla.org/MPL/ 441 | 442 | Software distributed under the License is distributed on an "AS IS" 443 | basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 444 | License for the specific language governing rights and limitations 445 | under the License. 446 | 447 | The Original Code is ibus-avro. 448 | 449 | The Initial Developer of the Original Code is 450 | Sarim Khan 451 | 452 | Copyright (C) Sarim Khan, unless otherwise stated. All Rights Reserved. 453 | 454 | 455 | Contributor(s): Mehdi Hasan , Rifat Nabi " 456 | 457 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Media Converter (imconverter) 2 | 3 | Media Converter is a complete cross-platform solution to convert audio and video files built on top of the FFmpeg command-line tool. 4 | 5 | ![screen shot](https://user-images.githubusercontent.com/351062/32877581-714fd738-cab7-11e7-903e-db0110201015.png) 6 | 7 | Download Links: 8 | * Firefox: https://addons.mozilla.org/firefox/addon/media-conversion-tool/ 9 | * Chrome: https://chrome.google.com/webstore/detail/media-converter-and-muxer/ocnfecjfebnllnapjjoncgjnnkfmobjc 10 | * Opera: https://addons.opera.com/en/extensions/details/media-converter-and-muxer/ 11 | 12 | FAQs page: http://add0n.com/media-converter.html 13 | -------------------------------------------------------------------------------- /preview/icon.cdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/media-converter/ac991fdd89473ae1d432457521c81dd9f402f9a0/preview/icon.cdr -------------------------------------------------------------------------------- /preview/media-converter-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/media-converter/ac991fdd89473ae1d432457521c81dd9f402f9a0/preview/media-converter-big.png -------------------------------------------------------------------------------- /preview/media-converter-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/media-converter/ac991fdd89473ae1d432457521c81dd9f402f9a0/preview/media-converter-small.png -------------------------------------------------------------------------------- /v3/data/converter/element.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var element = {}; 4 | 5 | element.tabs = { 6 | get list() { 7 | return [...document.querySelectorAll('#tabs input[type=radio]')]; 8 | }, 9 | get active() { 10 | return document.querySelector('#tabs :checked'); 11 | } 12 | }; 13 | element.panels = { 14 | get list() { 15 | return [...document.querySelectorAll('#panels>div')].filter(e => e.id !== 'upload'); 16 | } 17 | }; 18 | element.mp3 = { 19 | get quality() { 20 | return document.querySelector('#mp3 [name=mp3]:checked').value; 21 | }, 22 | bitrate: { 23 | get constant() { 24 | return document.querySelector('#mp3 [data-restore="mp3-cs"]').value; 25 | }, 26 | get variable() { 27 | return document.querySelector('#mp3 [data-restore="mp3-sv"]').value; 28 | } 29 | } 30 | }; 31 | element.volume = { 32 | get percent() { 33 | return document.querySelector('#volume [data-restore]').value; 34 | } 35 | }; 36 | element.scale = { 37 | get divide() { 38 | return document.querySelector('#scale [data-restore="scale-id"]').value; 39 | }, 40 | get multiply() { 41 | return document.querySelector('#scale [data-restore="scale-im"]').value; 42 | } 43 | }; 44 | element.rotate = { 45 | get selected() { 46 | return document.querySelector('#rotate :checked').value; 47 | } 48 | }; 49 | element.shift = { 50 | get type() { 51 | return document.querySelector('#shift :checked').value; 52 | }, 53 | get time() { 54 | return document.querySelector('#shift [type=text]').value; 55 | } 56 | }; 57 | element.concat = { 58 | get type() { 59 | return document.querySelector('#concat :checked').value; 60 | } 61 | }; 62 | element.cut = { 63 | get start() { 64 | return document.querySelector('#cut [data-restore="cut-if"]').value; 65 | }, 66 | get end() { 67 | return document.querySelector('#cut [data-restore="cut-it"]').value; 68 | } 69 | }; 70 | element.custom = { 71 | input: { 72 | audio: { 73 | get rate() { 74 | return document.querySelector('[data-restore="custom-iar"]').value; 75 | }, 76 | set rate(val) { 77 | const elem = document.querySelector('[data-restore="custom-iar"]'); 78 | elem.value = val; 79 | elem.dispatchEvent(new Event('change', {bubbles: true})); 80 | }, 81 | get channels() { 82 | return document.querySelector('[data-restore="custom-iac"]').value; 83 | }, 84 | set channels(val) { 85 | const elem = document.querySelector('[data-restore="custom-iac"]'); 86 | elem.value = val; 87 | elem.dispatchEvent(new Event('change', {bubbles: true})); 88 | }, 89 | }, 90 | video: { 91 | get rate() { 92 | return document.querySelector('[data-restore="custom-ivr"]').value; 93 | }, 94 | set rate(val) { 95 | const elem = document.querySelector('[data-restore="custom-ivr"]'); 96 | elem.value = val; 97 | elem.dispatchEvent(new Event('change', {bubbles: true})); 98 | } 99 | } 100 | }, 101 | output: { 102 | get format() { 103 | return document.querySelector('[data-restore="custom-of"]').value; 104 | }, 105 | set format(val) { 106 | const elem = document.querySelector('[data-restore="custom-of"]'); 107 | elem.value = val; 108 | elem.dispatchEvent(new Event('change', {bubbles: true})); 109 | }, 110 | audio: { 111 | get rate() { 112 | return document.querySelector('[data-restore="custom-oab"]').value; 113 | }, 114 | set rate(val) { 115 | const elem = document.querySelector('[data-restore="custom-oab"]'); 116 | elem.value = val; 117 | elem.dispatchEvent(new Event('change', {bubbles: true})); 118 | } 119 | }, 120 | video: { 121 | get rate() { 122 | return document.querySelector('[data-restore="custom-ovb"]').value; 123 | }, 124 | set rate(val) { 125 | const elem = document.querySelector('[data-restore="custom-ovb"]'); 126 | elem.value = val; 127 | elem.dispatchEvent(new Event('change', {bubbles: true})); 128 | }, 129 | get tolerance() { 130 | return document.querySelector('[data-restore="custom-ovt"]').value; 131 | }, 132 | set tolerance(val) { 133 | const elem = document.querySelector('[data-restore="custom-ovt"]'); 134 | elem.value = val; 135 | elem.dispatchEvent(new Event('change', {bubbles: true})); 136 | }, 137 | get size() { 138 | const mode = document.querySelector('[name="screen-size"]:checked').value; 139 | if (mode === 'predefined') { 140 | return document.querySelector('[data-restore="custom-sspi"]').value; 141 | } 142 | else { 143 | return (document.querySelector('[data-restore="custom-sscw"]').value || '800') + 144 | 'x' + 145 | (document.querySelector('[data-restore="custom-ssch"]').value || '600'); 146 | } 147 | }, 148 | set size(val) { 149 | const elem1 = document.querySelector('[name="screen-size"]'); 150 | elem1.checked = true; 151 | elem1.dispatchEvent(new Event('change', {bubbles: true})); 152 | const elem2 = document.querySelector('[data-restore="custom-sspi"]'); 153 | elem2.value = val; 154 | elem2.dispatchEvent(new Event('change', {bubbles: true})); 155 | } 156 | } 157 | } 158 | }; 159 | 160 | element.settings = { 161 | get mode() { 162 | return document.getElementById('mode'); 163 | }, 164 | get version() { 165 | return document.getElementById('client-version'); 166 | }, 167 | get ffmpeg() { 168 | return document.getElementById('ffmpeg-path'); 169 | }, 170 | get savein() { 171 | return document.getElementById('save-in'); 172 | }, 173 | get tmpdir() { 174 | return document.getElementById('tmpdir'); 175 | }, 176 | get port() { 177 | return document.getElementById('port'); 178 | } 179 | }; 180 | element.drag = { 181 | get div() { 182 | return document.getElementById('upload'); 183 | }, 184 | get input() { 185 | return document.querySelector('#upload input'); 186 | }, 187 | }; 188 | element.log = { 189 | get parent() { 190 | return document.getElementById('console'); 191 | } 192 | }; 193 | element.instruction = { 194 | get parent() { 195 | return document.getElementById('instruction'); 196 | }, 197 | get step1() { 198 | return document.querySelector('#instruction li:first-child'); 199 | } 200 | }; 201 | element.busy = { 202 | get parent() { 203 | return document.getElementById('busy'); 204 | } 205 | }; 206 | -------------------------------------------------------------------------------- /v3/data/converter/ffmpeg.js: -------------------------------------------------------------------------------- 1 | /* globals WFFmpeg, NFFmpeg, Native, element, restore, log */ 2 | 'use strict'; 3 | 4 | let ffmpeg = { 5 | emit: function() {} 6 | }; 7 | 8 | function unhandledrejection(msg) { 9 | if (msg.indexOf('code=EPERM') !== -1) { 10 | msg += ' (operation not permitted)'; 11 | } 12 | ffmpeg.log('error', msg); 13 | ffmpeg.busy = false; 14 | ffmpeg.counter(); 15 | } 16 | window.addEventListener('unhandledrejection', e => { 17 | unhandledrejection(e.reason.message); 18 | }); 19 | 20 | chrome.storage.local.get({ 21 | 'ffmpeg-path': null, 22 | 'savein-path': null, 23 | 'tmpdir-path': null, 24 | 'server-port': 3002, 25 | 'mode': 'native' 26 | }, prefs => { 27 | element.settings.mode.value = prefs.mode; 28 | 29 | let isNative = false; 30 | 31 | if (prefs.mode === 'wasm') { 32 | Native.prototype.spawn = function(path, args) { 33 | this.run(...args); 34 | }; 35 | Native.prototype['clean-tmp'] = function() {}; 36 | } 37 | const FFmpeg = prefs.mode === 'wasm' ? WFFmpeg : NFFmpeg; 38 | 39 | ffmpeg = window.ffmpeg = new FFmpeg({ 40 | ffmpeg: prefs['ffmpeg-path'], 41 | savein: prefs['savein-path'], 42 | tmpdir: prefs['tmpdir-path'], 43 | port: prefs['server-port'] 44 | }); 45 | ffmpeg.log = function(type, e = '-') { 46 | log.emit(type, e.message || e); 47 | }; 48 | ffmpeg.counter = function() { 49 | const queue = ffmpeg.jobs.length + (this.busy ? 1 : 0); 50 | if (queue) { 51 | document.title = 'Media Converter (' + queue + ')'; 52 | } 53 | else { 54 | document.title = 'Media Converter'; 55 | } 56 | }; 57 | ffmpeg.onDisconnect(() => { 58 | if (isNative) { 59 | document.body.dataset.crashed = true; 60 | } 61 | else { 62 | document.body.dataset.instruction = true; 63 | } 64 | document.body.dataset.working = false; 65 | }); 66 | ffmpeg.on('job', jobs => { 67 | ffmpeg.jobs = ffmpeg.jobs.concat(jobs); 68 | ffmpeg.convert().catch(e => unhandledrejection(e.message)); 69 | ffmpeg.counter(); 70 | }); 71 | ffmpeg.on('ffmpeg-path-changed', path => { 72 | if (path) { 73 | ffmpeg.config.ffmpeg.path = path; 74 | } 75 | else { 76 | ffmpeg.find().catch(e => ffmpeg.log(e)); 77 | } 78 | }); 79 | ffmpeg.on('savein-path-changed', path => { 80 | ffmpeg.config.user.savein = path || ffmpeg.config.user._savein; 81 | }); 82 | ffmpeg.on('tmpdir-path-changed', path => { 83 | ffmpeg.config.user.tmpdir = path || ffmpeg.config.user._tmpdir; 84 | }); 85 | 86 | ffmpeg.init().then(obj => { 87 | isNative = true; 88 | element.settings.version.textContent = obj.version; 89 | element.settings.savein.value = element.settings.savein.dataset.value = obj.savein; 90 | element.settings.tmpdir.value = element.settings.tmpdir.dataset.value = obj.tmpdir; 91 | element.settings.port.value = element.settings.port.dataset.value = obj.port; 92 | 93 | element.settings.savein.disabled = 94 | element.settings.tmpdir.disabled = 95 | element.settings.port.disabled = prefs.mode === 'wasm'; 96 | 97 | if (prefs['ffmpeg-path']) { 98 | return prefs['ffmpeg-path']; 99 | } 100 | else { 101 | return ffmpeg.find().catch(e => { 102 | if (window.confirm('For the converter to work, FFmpeg command-line tool is needed.\nShould I get it now?')) { 103 | element.busy.parent.dataset.working = true; 104 | return ffmpeg.download().then(path => { 105 | element.busy.parent.dataset.working = false; 106 | return path; 107 | }); 108 | } 109 | throw e; 110 | }); 111 | } 112 | }) 113 | // setting FFmpeg path 114 | .then(path => element.settings.ffmpeg.value = element.settings.ffmpeg.dataset.value = path) 115 | // creating server 116 | .then(() => ffmpeg.server()) 117 | // enabling conversion tabs 118 | .then(() => element.tabs.list.forEach(tab => tab.disabled = false)) 119 | // restoring user settings 120 | .then(() => restore.emit('restore', () => document.body.dataset.working = false)) 121 | // welcome message 122 | .then(() => ffmpeg.log('info', 'Select a tab then drag one or multiple media files into the designated box. All the media files will be downloaded to the directory that is set in the settings tab (default directory is your Home directory).')) 123 | // catching errors 124 | .catch(e => { 125 | console.error(e); 126 | ffmpeg.log('error', e); 127 | document.body.dataset.working = false; 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /v3/data/converter/file.js: -------------------------------------------------------------------------------- 1 | /* globals element, ffmpeg, log */ 2 | 'use strict'; 3 | 4 | document.addEventListener('dragover', e => { 5 | e.preventDefault(); 6 | e.stopPropagation(); 7 | e.dataTransfer.dropEffect = 'none'; 8 | }, false); 9 | 10 | function drop(e) { 11 | e.stopPropagation(); 12 | e.preventDefault(); 13 | const files = e.target.files || e.dataTransfer.files; // FileList object 14 | const mode = element.tabs.active.dataset.for; 15 | const recipe = (function() { 16 | const options = {}; 17 | if (mode === 'mp3') { 18 | options.quality = element.mp3.quality; 19 | options.bitrate = element.mp3.bitrate[options.quality]; 20 | } 21 | else if (mode === 'volume') { 22 | options.volume = element.volume.percent; 23 | } 24 | else if (mode === 'scale') { 25 | options.divide = element.scale.divide; 26 | options.multiply = element.scale.multiply; 27 | } 28 | else if (mode === 'rotate') { 29 | options.angle = element.rotate.selected; 30 | } 31 | else if (mode === 'shift') { 32 | options.type = element.shift.type; 33 | options.time = element.shift.time; 34 | } 35 | else if (mode === 'cut') { 36 | options.start = element.cut.start; 37 | options.end = element.cut.end; 38 | } 39 | else if (mode === 'concat') { 40 | options.type = element.concat.type; 41 | } 42 | else if (mode === 'custom') { 43 | options.input = { 44 | audio: { 45 | rate: element.custom.input.audio.rate, 46 | channels: element.custom.input.audio.channels 47 | }, 48 | video: { 49 | rate: element.custom.input.video.rate 50 | } 51 | }; 52 | options.output = { 53 | format: element.custom.output.format, 54 | audio: { 55 | rate: element.custom.output.audio.rate 56 | }, 57 | video: { 58 | size: element.custom.output.video.size, 59 | rate: element.custom.output.video.rate, 60 | torerance: element.custom.output.video.torerance 61 | } 62 | }; 63 | } 64 | return options; 65 | })(); 66 | // 67 | if (mode === 'muxer' || mode === 'concat') { 68 | ffmpeg.emit('job', { 69 | files: [...files], 70 | mode, 71 | recipe 72 | }); 73 | } 74 | else { 75 | log.emit('clean'); 76 | ffmpeg.emit('job', [...files].map(file => ({ 77 | files: [file], 78 | mode, 79 | recipe 80 | }))); 81 | } 82 | } 83 | element.drag.input.addEventListener('change', drop); 84 | element.drag.div.addEventListener('dragover', e => { 85 | if (e.dataTransfer.types.indexOf('Files') !== -1) { 86 | e.preventDefault(); 87 | e.stopPropagation(); 88 | e.dataTransfer.dropEffect = 'link'; 89 | } 90 | else { 91 | e.dataTransfer.dropEffect = 'none'; 92 | } 93 | }, false); 94 | element.drag.div.addEventListener('drop', drop, false); 95 | -------------------------------------------------------------------------------- /v3/data/converter/images/arrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /v3/data/converter/images/busy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /v3/data/converter/images/working.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /v3/data/converter/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | display: flex; 6 | flex-direction: column; 7 | font-family: "Helvetica Neue",Helvetica,sans-serif; 8 | font-size: 13px; 9 | font-weight: 400; 10 | padding: 0; 11 | margin: 5px 5px 0 5px; 12 | height: calc(100% - 5px); 13 | } 14 | body:not([data-working=true]) #busy { 15 | display: none; 16 | } 17 | #busy { 18 | position: absolute; 19 | top: 0; 20 | left: 0; 21 | width: 100%; 22 | height: 100%; 23 | background: rgba(0, 0, 0, 0.8) url('images/busy.svg') no-repeat center center; 24 | background-size: 128px; 25 | z-index: 2; 26 | } 27 | body:not([data-crashed=true]) #crash { 28 | display: none; 29 | } 30 | body:not([data-instruction=true]) #instruction { 31 | display: none; 32 | } 33 | #instruction, 34 | #crash { 35 | position: absolute; 36 | top: 0; 37 | left: 0; 38 | width: 100%; 39 | height: 100%; 40 | z-index: 3; 41 | background-color: rgba(0, 0, 0, 0.9); 42 | color: #fff; 43 | padding: 50px; 44 | box-sizing: border-box; 45 | display: flex; 46 | flex-direction: column; 47 | justify-content: center; 48 | align-items: center; 49 | } 50 | #busy[data-working=true], 51 | #instruction[data-working=true] { 52 | background-image: url('images/working.svg'); 53 | background-position: right 10px top 10px; 54 | background-repeat: no-repeat; 55 | } 56 | #instruction { 57 | line-height: 16px; 58 | } 59 | #instruction a { 60 | text-decoration: none; 61 | color: #07c !important; 62 | } 63 | #busy[data-working=true]::after { 64 | content: 'Downloading...'; 65 | color: #fff; 66 | position: absolute; 67 | width: 100%; 68 | height: 100%; 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | font-size: 150%; 73 | } 74 | #instruction a, 75 | #crash a { 76 | color: #fff; 77 | } 78 | 79 | #tabs { 80 | overflow: auto; 81 | } 82 | #tabs label { 83 | display: block; 84 | } 85 | #tabs input { 86 | display: none; 87 | } 88 | #content { 89 | flex: 1; 90 | display: flex; 91 | margin-top: 0; 92 | width: 100%; 93 | overflow-y: hidden; 94 | } 95 | #console { 96 | border: solid 1px #dedede; 97 | margin-top: -1px; 98 | height: 100px; 99 | } 100 | #panels { 101 | border: solid 1px #dedede; 102 | flex: 1; 103 | padding: 0 10px 0 20px; 104 | margin-left: -1px; 105 | display: flex; 106 | flex-direction: column; 107 | } 108 | #panels > div { 109 | flex: 1; 110 | } 111 | #panels > div:not([data-selected=true]) { 112 | display: none; 113 | } 114 | #panels p { 115 | background-color: #f5f5f5; 116 | padding: 10px; 117 | } 118 | #panels #cut input[type=text] { 119 | width: 100px; 120 | } 121 | #settings { 122 | overflow: auto; 123 | } 124 | #settings[data-selected=false] ~ #upload { 125 | display: flex; 126 | } 127 | #upload { 128 | height: 100px; 129 | flex: initial !important; 130 | align-items: center; 131 | justify-content: center; 132 | padding: 0 5px; 133 | margin-bottom: 10px; 134 | border: dashed 2px #5f5aa6; 135 | color: #5f5aa6; 136 | font-size: 120%; 137 | } 138 | #upload input { 139 | display: none; 140 | } 141 | #upload label { 142 | cursor: pointer; 143 | } 144 | #console { 145 | overflow-y: auto; 146 | font: 10px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; 147 | } 148 | #console > div { 149 | border-left: solid 8px; 150 | display: flex; 151 | } 152 | #console > div span:first-child { 153 | margin-right: 10px; 154 | padding-left: 10px; 155 | white-space: nowrap; 156 | } 157 | #console > div[data-type=error] { 158 | border-color: #ff0000; 159 | } 160 | #console > div[data-type=warning] { 161 | border-color: #fb9500; 162 | } 163 | #console > div[data-type=info] { 164 | border-color: #808080; 165 | } 166 | #custom { 167 | flex-direction: column; 168 | } 169 | #custom select { 170 | flex: 1; 171 | } 172 | #custom input[type=text] { 173 | flex: 1; 174 | } 175 | #custom label { 176 | width: 120px; 177 | } 178 | #custom table { 179 | table-layout: fixed; 180 | height: 100%; 181 | } 182 | #custom td { 183 | vertical-align: top; 184 | } 185 | #custom td:first-child { 186 | padding-right: 10px; 187 | } 188 | #custom td:last-child { 189 | padding-left: 10px; 190 | border-left: dashed 1px #dedede; 191 | } 192 | #custom ul { 193 | list-style: none; 194 | margin: 0; 195 | padding: 0; 196 | } 197 | #custom li { 198 | padding: 2px 0; 199 | } 200 | #custom > div:last-child { 201 | justify-content: flex-start; 202 | width: 100%; 203 | padding: 10px; 204 | } 205 | #samples { 206 | position: relative; 207 | } 208 | #samples select { 209 | height: 100%; 210 | border: none; 211 | background-color: transparent; 212 | position: absolute; 213 | right: 0; 214 | width: 80px; 215 | } 216 | #tabs { 217 | margin: 0; 218 | padding: 0; 219 | user-select: none; 220 | } 221 | #tabs li { 222 | width: 160px; 223 | height: 40px; 224 | border: solid 1px #dedede; 225 | display: flex; 226 | flex-direction: column; 227 | align-items: center; 228 | justify-content: center; 229 | cursor: pointer; 230 | margin-bottom: -1px; 231 | } 232 | 233 | html[data-platform=windows] .linux { 234 | display: none; 235 | } 236 | html:not([data-platform=windows]) .windows { 237 | display: none; 238 | } 239 | 240 | table { 241 | width: 100%; 242 | } 243 | input { 244 | border: solid 1px #dedede; 245 | border-radius: 0; 246 | height: 26px; 247 | box-sizing: border-box; 248 | background-color: transparent; 249 | outline: none; 250 | } 251 | input:disabled { 252 | background-color: #dedede; 253 | } 254 | input[type=text] { 255 | width: 100%; 256 | text-indent: 5px; 257 | } 258 | input[type=button] { 259 | cursor: pointer; 260 | } 261 | input[type=range], 262 | input[type=radio] { 263 | margin-right: 10px; 264 | } 265 | input[type=radio] { 266 | height: auto; 267 | } 268 | input[type=button]:active { 269 | opacity: 0.5; 270 | } 271 | input:disabled ~ li { 272 | opacity: 0.5; 273 | cursor: default; 274 | } 275 | input:checked ~ li { 276 | background-color: #5f5aa6; 277 | border: solid 1px #5f5aa6; 278 | color: #fff; 279 | position: relative; 280 | background-color: #5f5aa6; 281 | } 282 | input:checked ~ li:after { 283 | left: 100%; 284 | top: calc(50% - 12px); 285 | position: absolute; 286 | content: " "; 287 | width: 0; 288 | height: 0; 289 | border-style: solid; 290 | border-width: 12.5px 0 12.5px 10px; 291 | border-color: transparent transparent transparent #5f5aa6; 292 | } 293 | select { 294 | outline: none; 295 | border: solid 1px #dedede; 296 | border-radius: 0; 297 | -webkit-appearance: none; 298 | height: 26px; 299 | text-indent: 5px; 300 | background: url('images/arrow.svg') no-repeat center right 5px; 301 | padding-right: 20px; 302 | background-size: 16px; 303 | width: 150px; 304 | } 305 | label { 306 | display: inline-flex; 307 | align-items: center; 308 | } 309 | h2 { 310 | text-align: center; 311 | font-weight: 400; 312 | } 313 | h3 { 314 | margin: 8px 0; 315 | padding: 3px; 316 | font-size: 100%; 317 | background-color: rgba(0, 0, 0, 0.05); 318 | } 319 | hr { 320 | border: none; 321 | border-bottom: 1px #ececec dotted; 322 | margin-bottom: 20px; 323 | margin-top: 20px; 324 | } 325 | .flex { 326 | display: flex; 327 | align-items: center; 328 | } 329 | [flex="1"] { 330 | flex: 1; 331 | } 332 | [data-cmd="save-preference"]:not(:disabled) { 333 | color: #fff; 334 | background-color: #4eb14d; 335 | border-color: #3fa63e; 336 | } 337 | [data-cmd="save-preference"]:disabled { 338 | cursor: default; 339 | } 340 | [data-cmd="support"] { 341 | background-color: #439c43; 342 | color: #fff; 343 | border-color: #327332; 344 | } 345 | [data-for="settings"] { 346 | margin-top: 10px; 347 | } 348 | 349 | #explore[data-loaded=true] { 350 | margin-bottom: 8px !important; 351 | } 352 | -------------------------------------------------------------------------------- /v3/data/converter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Media Converter 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

Oops! The native client just crashed!

13 |

Please open a bug report here and describe the steps to regenerate this crash.

14 |

To restart the converter click here

15 |
16 |
17 |

Welcome to Media Converter

18 |
19 |

This converter uses the well-known FFmpeg command-line tool to convert your media files. You can either use the experimental embedded FFmpeg mode or for better performance you can install the native client version.

20 | 21 |

Click here to use embedded FFmpeg mode or follow the instruction to enable native mode:

22 |
    23 |
  • Click here to allow native access
  • 24 |
  • Download this minimal NodeJS native client
  • 25 |
  • Unzip the downloaded package
  • 26 |
  • Install the native client: 27 |
    28 |

    ↝ Open a terminal at the root directory of the downloaded package and run ./install.sh

    29 |

    ↝ Double-click on Install.bash

    30 |
  • 31 |
  • Click here to validate the native integration
  • 32 |
33 |

34 | 35 | Tutorial: 36 |

40 | 41 |

42 |
43 |
44 |
45 |
46 |
    47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
59 |
60 |
61 |

MP3 Converter

62 |

Convert a video or audio file into the MP3 format. Note that this conversion is lossy. It is always recommended to use the "variable" bit-rate option. Adjust the following settings and drop a media file into the box to start the conversion. Lower bitrate will result in smaller file size.

63 | 64 | Select quality: 65 | 66 | 67 | 68 | 69 | 70 | 71 | 85 | 86 | 87 | 88 | 108 | 109 |
72 | 84 |
89 | 107 |
110 |
111 |
112 |
113 | 114 | 115 | 159 | 367 | 368 |
116 |

Muxing Settings

117 |
118 |

Input Sampling Rate

119 |
    120 |
  • 121 | 131 |
  • 132 |
  • 133 | 143 |
  • 144 |
145 |
146 |
147 |

Audio

148 |
149 | 150 | 156 |
157 |
158 |
160 |

Output Settings

161 |
162 | 163 | 315 |
316 |
317 |

Screen Size

318 |
    319 |
  • 320 | 321 | 322 | 343 |
  • 344 |
  • 345 | 346 |  x  347 |
  • 348 |
349 |
350 |
351 |
352 |

Bit-Rate (BR)

353 | 359 |
360 |
    361 |
  • 362 |
  • 363 |
  • 364 |
365 |
366 |
369 |
370 |
371 | 372 |
373 |
374 |
375 |

Audio Extractor

376 |

EExtract the original audio stream from a video file. The output format will be based on the audio codec that is used in the input stream. There is no setting for this conversion, just select or drop one or more video files to extract the audio tracks from. You can use the MP3 tab later to convert this audio file to the MP3 format only if you need to use it on a device that does not support the extracted audio format.

377 |
378 |
379 |

Video and Audio Combiner

380 |

Replace the audio track of a video file with a new audio track. If the video file already has an audio track, this track will be overwritten by the new one. There is no setting for this conversion, just select or drop a pair of a video file and an audio track. This conversion can only convert a single video and audio files at a time. To convert multiple files, drop pairs individually.

381 |
382 |
383 |

Concatenating media files

384 |

Drop multiple media files to combine them into a single media file. Note that the media files need to be in the correct order. If they are not, make sure to rename them in a sequential numbering order first. This tool can be used to combine segmented tracks that are downloaded from popular websites. Choose the right method first, then drop the files. If the input files have the same codec and the same codec parameters, the first method is recommended to prevent re-encoding.

385 | 386 | 387 |
388 | 389 |
390 | 391 |
392 |
393 |

Audio Volume Adjuster

394 |

Reduce or increase the audio volume of a video or audio file. Adjust the range and drop the files in the designated box. To increase the volume more than two times, drop the converted file one more time. Note that this may cause your audio quality to drop.

395 | 396 | Change volume in percent: 397 |
398 | 399 | 100% 400 |
401 |
402 |
403 |

Video Size Scaler

404 |

Scale the video screen size. This option can be used to reduce video file size by reducing its screen dimension for instance. Final width of the video will be (original size) * (m) / (d). Video height is calculated such that width to height ratio is intact. Adjust settings and drop the files into the designated box.

405 | 406 | Divide video screen by: 407 |
408 | 409 | 1 410 |
411 | Multiply video screen by: 412 |
413 | 414 | 1 415 |
416 |
417 |
418 |

Video Rotator

419 |

You can rotate a video file here. This can be helpful to watch a video on a mobile device for instance. Select the rotation value and drop files into the designated box.

420 | 421 | 422 |
423 | 424 |
425 | 426 |
427 | 428 |
429 | 430 |
431 |
432 |

Audio Time Shifter

433 |

Sometimes, video and audio tracks are not synced. This tab can shift either audio or video file forward with a defined value to fix the synchronization problem.

434 | 435 | 436 | 437 | 438 | 439 | 440 | 445 | 446 | 447 |
441 | 442 |
443 | 444 |
448 |
449 |
450 |

Video Cutting Tool

451 |

If you want to cut part of a video or an audio file specify "from" time-frame and "to" time-frame and drop the file into the box. Leave the "from" box empty to start from the beginning of the track. Leave the "to" box empty to cut to the last frame. You can use the time offset in seconds from the beginning of the track or you can specify the times in hh:mm:ss.m format.

452 | 453 | Cut the video or audio from 454 | to 455 | 456 |
457 |
458 |

Settings

459 |

To save a preference, click on the green button next to it. Place your mouse over each item to see the description.

460 | 461 | 462 | 463 | 464 | 465 | 471 | 472 | 473 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 487 | 488 | 489 | 490 | 494 | 495 | 496 | 497 | 501 | 502 | 503 | 504 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 |
Mode 466 | 470 |
474 |

WASM mode (experimental): In this mode, there is no need for the native FFmpeg. The extension uses the embedded FFmpeg compiled into JS. This mode does not require any permission to operate but it is only recommended for light conversions. If the conversion window crashed, please switch to the native mode.

475 |

Native Client: In this mode, you need to permit the extension to use the native client also you need to install the native client. The extension uses the native FFmpeg binary to perform your conversions. This mode is recommended if you plan to do the conversion on large files.

476 |
Native client...
FFmpeg Location 484 | 485 | 486 |
Save Files In 491 | 492 | 493 |
Temporary Directory 498 | 499 | 500 |
Local communication port 505 | 506 | 507 |
Update native client
Update FFmpeg converter
Terminate all jobs
Support development
Reset all settings
530 |
531 |
532 |   or drag them here 533 |
534 |
535 |
536 |
537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | -------------------------------------------------------------------------------- /v3/data/converter/log.js: -------------------------------------------------------------------------------- 1 | /* globals element, EventEmitter */ 2 | 'use strict'; 3 | 4 | var log = new EventEmitter(); 5 | log.generate = (type, msg) => { 6 | const div = document.createElement('div'); 7 | div.dataset.type = type; 8 | const time = document.createElement('span'); 9 | time.textContent = (new Date()).toLocaleTimeString(); 10 | div.appendChild(time); 11 | const body = document.createElement('span'); 12 | body.textContent = msg; 13 | div.appendChild(body); 14 | element.log.parent.appendChild(div); 15 | div.scrollIntoView(false); 16 | }; 17 | log.clean = () => { 18 | element.log.parent.textContent = ''; 19 | }; 20 | log.on('clean', log.clean.bind(log)); 21 | log.on('info', log.generate.bind(log, 'info')); 22 | log.on('warning', log.generate.bind(log, 'warning')); 23 | log.on('error', msg => { 24 | log.generate('error', msg); 25 | if (msg.indexOf('ENOENT') !== -1 && msg.indexOf('spawn') !== -1) { 26 | window.alert(`FFmpeg executable is damaged. Please replace the "FFmpeg Location" with a new executable path. 27 | 28 | You can update FFmpeg executable either from the "Settings" tab or manually from https://www.ffmpeg.org/download.html`); 29 | element.tabs.list.pop().click(); 30 | } 31 | else if (msg.indexOf('ENOENT') !== -1) { 32 | window.alert('The destination directory is unreachable. Please use another path for "Save Files In" field'); 33 | element.tabs.list.pop().click(); 34 | } 35 | else if (msg.indexOf('Error in transferring data') !== -1) { 36 | window.alert(`There is a problem storing your media file in a temporary directory. 37 | 38 | Is "Temporary Directory" field set to a reachable local directory?`); 39 | element.tabs.list.pop().click(); 40 | } 41 | else if (msg.indexOf('Output file #0 does not contain any stream') !== -1) { 42 | window.alert('The input file does not seem to be a media file. Please double check the input file.'); 43 | element.tabs.list.pop().click(); 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /v3/data/converter/matched/matched.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | { 4 | const shuffle = array => { 5 | for (let i = array.length - 1; i > 0; i -= 1) { 6 | const j = Math.floor(Math.random() * (i + 1)); 7 | [array[i], array[j]] = [array[j], array[i]]; 8 | } 9 | 10 | return array; 11 | }; 12 | 13 | const root = document.getElementById('explore'); 14 | 15 | const INC = Number(root.dataset.inc || 100); 16 | const count = Number(localStorage.getItem('explore-count') || INC - 5); 17 | 18 | const style = document.createElement('style'); 19 | style.textContent = ` 20 | #explore { 21 | background-color: #fff; 22 | position: relative; 23 | color: #969696; 24 | user-select: none; 25 | } 26 | #explore[data-loaded=true] { 27 | margin: 4px; 28 | padding: 5px; 29 | box-shadow: 0 0 1px #ccc; 30 | border: solid 1px #ccc; 31 | } 32 | #explore .close { 33 | position: absolute; 34 | right: 6px; 35 | top: 4px; 36 | cursor: pointer; 37 | } 38 | #explore>table { 39 | margin-top: 10px; 40 | table-layout: fixed; 41 | width: 100%; 42 | border-collapse: collapse; 43 | } 44 | #explore a { 45 | text-decoration: none; 46 | color: #000; 47 | display: flex; 48 | align-items: center; 49 | justify-content: center; 50 | } 51 | #explore td:first-child a { 52 | justify-content: flex-start; 53 | } 54 | #explore td:last-child a { 55 | justify-content: flex-end; 56 | } 57 | #explore .title { 58 | border-left: solid 1px #ccc; 59 | display: inline-block; 60 | align-items: center; 61 | overflow: hidden; 62 | text-overflow: ellipsis; 63 | white-space: nowrap; 64 | padding-left: 5px; 65 | } 66 | #explore .icon { 67 | min-width: 28px; 68 | height: 28px; 69 | display: inline-flex; 70 | align-items: center; 71 | justify-content: center; 72 | border-radius: 50%; 73 | color: #fff; 74 | margin-right: 5px; 75 | font-size: 10px; 76 | font-weight: 100; 77 | } 78 | #explore .explore { 79 | position: absolute; 80 | right: 10px; 81 | z-index: 1000000; 82 | cursor: pointer; 83 | font-size: 15px; 84 | }`; 85 | document.documentElement.appendChild(style); 86 | 87 | const cload = () => fetch('matched/matched.json').then(r => r.json()).then(build); 88 | const explore = () => { 89 | const span = document.createElement('span'); 90 | span.textContent = '↯'; 91 | span.title = 'Explore more'; 92 | span.classList.add('explore'); 93 | root.appendChild(span); 94 | span.onclick = () => { 95 | root.textContent = ''; 96 | localStorage.setItem('explore-count', INC); 97 | cload(); 98 | }; 99 | }; 100 | const build = json => { 101 | if (json.length === 0) { 102 | return; 103 | } 104 | root.dataset.loaded = true; 105 | root.textContent = 'Explore more'; 106 | const table = document.createElement('table'); 107 | const tr = document.createElement('tr'); 108 | const span = document.createElement('span'); 109 | span.classList.add('close'); 110 | span.textContent = '✕'; 111 | span.onclick = () => { 112 | root.textContent = ''; 113 | root.dataset.loaded = false; 114 | localStorage.setItem('explore-count', 0); 115 | explore(); 116 | }; 117 | root.appendChild(span); 118 | 119 | const {homepage_url} = chrome.runtime.getManifest(); 120 | const origin = homepage_url.split('/').slice(0, -1).join('/'); 121 | const colors = shuffle( 122 | ['524c84', '606470', '755da3', 'c06c84', '393e46', '446e5c', '693e52', '1d566e', '693e52', 'd95858', 'f27370'] 123 | ); 124 | shuffle(Object.entries(json)).slice(0, 4).forEach(([id, {name}], i) => { 125 | const td = document.createElement('td'); 126 | const a = Object.assign(document.createElement('a'), { 127 | target: '_blank', 128 | title: 'Click to browse', 129 | href: origin + '/' + id + '.html?context=explore' 130 | }); 131 | 132 | const icon = document.createElement('span'); 133 | icon.textContent = name.split(' ').slice(0, 2).map(s => s[0]).join('').toUpperCase(); 134 | icon.classList.add('icon'); 135 | icon.style['background-color'] = '#' + colors[i]; 136 | a.appendChild(icon); 137 | 138 | const span = document.createElement('span'); 139 | span.classList.add('title'); 140 | span.textContent = name; 141 | a.appendChild(span); 142 | td.appendChild(a); 143 | tr.appendChild(td); 144 | }); 145 | table.appendChild(tr); 146 | root.appendChild(table); 147 | }; 148 | const init = () => { 149 | if (count >= INC) { 150 | if (count < INC + 3) { 151 | cload(); 152 | } 153 | else { 154 | explore(); 155 | } 156 | if (count > INC + 5) { 157 | localStorage.setItem('explore-count', INC - 6); 158 | } 159 | else { 160 | localStorage.setItem('explore-count', count + 1); 161 | } 162 | } 163 | else { 164 | explore(); 165 | localStorage.setItem('explore-count', count + 1); 166 | } 167 | }; 168 | init(); 169 | } 170 | -------------------------------------------------------------------------------- /v3/data/converter/matched/matched.json: -------------------------------------------------------------------------------- 1 | { 2 | "country-flags": { 3 | "name": "Country Flags & IP WHOIS" 4 | }, 5 | "useragent-switcher": { 6 | "name": "User Agent Switcher" 7 | }, 8 | "work-offline": { 9 | "name": "Work Offline" 10 | }, 11 | "local-cdn": { 12 | "name": "Local CDN" 13 | }, 14 | "media-converter": { 15 | "name": "Tor Control (Anonymity Layer)" 16 | }, 17 | "save-images": { 18 | "name": "Download All Images" 19 | }, 20 | "privacy-settings": { 21 | "name": "Privacy Settings" 22 | }, 23 | "media-player": { 24 | "name": "YouTube Media Player" 25 | }, 26 | "tab-discard": { 27 | "name": "Auto Tab Discard" 28 | }, 29 | "send-to": { 30 | "name": "Send to VLC" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /v3/data/converter/misc.js: -------------------------------------------------------------------------------- 1 | document.getElementById('mode').addEventListener('change', e => chrome.storage.local.set({ 2 | mode: e.target.value 3 | }, () => location.reload())); 4 | 5 | document.getElementById('wasm').onclick = () => chrome.storage.local.set({ 6 | mode: 'wasm' 7 | }, () => location.reload()); 8 | 9 | document.getElementById('permission').onclick = () => chrome.permissions.request({ 10 | permissions: ['nativeMessaging', 'downloads'], 11 | origins: ['http://127.0.0.1/'] 12 | }, () => { 13 | const e = chrome.runtime.lastError; 14 | 15 | if (e) { 16 | alert(e.message); 17 | } 18 | location.reload(); 19 | }); 20 | 21 | chrome.permissions.contains({ 22 | permissions: ['nativeMessaging', 'downloads'] 23 | }, granted => { 24 | if (granted) { 25 | document.getElementById('permission').parentElement.remove(); 26 | } 27 | }); 28 | 29 | chrome.runtime.onMessage.addListener((request, sender, response) => { 30 | if (request.method === 'is-open') { 31 | response(true); 32 | chrome.runtime.sendMessage({ 33 | method: 'focus' 34 | }); 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /v3/data/converter/native/core.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Native = function() { 4 | if (chrome.runtime.connectNative) { 5 | this.channel = chrome.runtime.connectNative('com.add0n.node'); 6 | } 7 | this.platform = navigator.platform.startsWith('Win') ? 'Windows' : navigator.platform; 8 | }; 9 | // Events 10 | Native.prototype.onDisconnect = function(onDisconnect) { 11 | if (this.channel) { 12 | this.channel.onDisconnect.addListener(onDisconnect); 13 | } 14 | else { 15 | onDisconnect(); 16 | } 17 | }; 18 | Native.prototype.onMessage = function(onMessage) { 19 | if (this.channel) { 20 | this.channel.onMessage.addListener(onMessage); 21 | } 22 | }; 23 | // Actions 24 | Native.prototype.toString = function(msg) { 25 | if (msg.data && msg.type === 'Buffer') { 26 | return String.fromCharCode(...msg.data); 27 | } 28 | return msg.data; 29 | }; 30 | Native.prototype.disconnect = function() { 31 | this.channel.disconnect(); 32 | }; 33 | Native.prototype.spec = function() { 34 | if (this.channel) { 35 | this.channel.postMessage({ 36 | cmd: 'spec' 37 | }); 38 | } 39 | }; 40 | Native.prototype.dir = function(path) { 41 | this.channel.postMessage({ 42 | cmd: 'dir', 43 | path 44 | }); 45 | }; 46 | Native.prototype.ifup = function(port, key) { 47 | this.channel.postMessage({ 48 | cmd: 'ifup', 49 | port, 50 | key 51 | }); 52 | }; 53 | Native.prototype.exec = function(command, args, env = []) { 54 | this.channel.postMessage({ 55 | cmd: 'exec', 56 | command, 57 | arguments: args, 58 | env 59 | }); 60 | }; 61 | Native.prototype.spawn = function(command, args, env = []) { 62 | this.channel.postMessage({ 63 | cmd: 'spawn', 64 | command, 65 | arguments: args, 66 | env 67 | }); 68 | }; 69 | Native.prototype['clean-tmp'] = function() { 70 | this.channel.postMessage({ 71 | cmd: 'clean-tmp' 72 | }); 73 | }; 74 | Native.prototype.cut = function(obj) { 75 | this.channel.postMessage(Object.assign(obj, { 76 | cmd: 'copy' 77 | })); 78 | }; 79 | -------------------------------------------------------------------------------- /v3/data/converter/native/ffmpeg.js: -------------------------------------------------------------------------------- 1 | /* globals Native, vCompare, ffmpeg */ 2 | 'use strict'; 3 | 4 | const NFFmpeg = function(path) { 5 | Native.call(this); 6 | this.callbacks = []; 7 | this.jobs = []; 8 | this.busy = false; 9 | this.callback = null; 10 | this.key = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { 11 | const r = Math.random() * 16 | 0; 12 | const v = c === 'x' ? r : r & 0x3 | 0x8; 13 | return v.toString(16); 14 | }); 15 | this.config = { 16 | port: path.port, 17 | native: {}, 18 | ffmpeg: { 19 | path: path.ffmpeg 20 | }, 21 | user: { 22 | savein: path.savein, 23 | tmpdir: path.tmpdir 24 | } 25 | }; 26 | this.log = function() { 27 | console.log('number of active jobs', this.config.queue); 28 | }; 29 | this.onMessage(response => { 30 | if (this.callback) { 31 | this.callback(response); 32 | this.callback = null; 33 | } 34 | else { 35 | if (response.stdout && response.stdout.type === 'Buffer') { 36 | const msg = String.fromCharCode(...response.stdout.data); 37 | this.log('info', msg); 38 | } 39 | else if (response.stderr && response.stderr.type === 'Buffer') { 40 | const msg = String.fromCharCode(...response.stderr.data); 41 | this.log('error', msg); 42 | } 43 | else if (response.code === 0) { 44 | this.log('warning', 'Done!'); 45 | if (this.output) { 46 | this.log('warning', 'Output file is ' + this.output); 47 | } 48 | } 49 | else if (response.code) { 50 | this.log('error', 'Something went wrong; ' + JSON.stringify(response)); 51 | } 52 | else { 53 | this.log('warning', JSON.stringify(response)); 54 | } 55 | } 56 | // 57 | if ('code' in response) { 58 | if (this.busy && response.cmd === 'spawn') { 59 | this.cleanup().then(() => { 60 | this.busy = false; 61 | this.counter(); 62 | this.convert(); 63 | }); 64 | } 65 | else { 66 | this.convert(); 67 | } 68 | } 69 | }); 70 | }; 71 | NFFmpeg.prototype = Object.create(Native.prototype); 72 | 73 | NFFmpeg.prototype.on = function(id, callback) { 74 | this.callbacks[id] = this.callbacks[id] || []; 75 | this.callbacks[id].push(callback); 76 | }; 77 | NFFmpeg.prototype.emit = function(id, value) { 78 | (this.callbacks[id] || []).forEach(c => c(value)); 79 | }; 80 | 81 | NFFmpeg.prototype.convert = function() { 82 | return new Promise((resolve, reject) => { 83 | if (this.jobs.length && !this.busy) { 84 | this.busy = true; 85 | this.counter(); 86 | const job = this.jobs.shift(); 87 | let name = job.files[0].name; 88 | let extension = name.split('.').pop(); 89 | name = name.replace('.' + extension, ''); 90 | name = name.replace('- DASH', ''); 91 | 92 | const args = []; 93 | if (job.mode === 'mp3' && job.recipe.quality === 'variable') { 94 | args.push('-qscale:a', job.recipe.bitrate); 95 | extension = 'mp3'; 96 | } 97 | else if (job.mode === 'mp3' && job.recipe.quality === 'constant') { 98 | args.push('-b:a', job.recipe.bitrate); 99 | extension = 'mp3'; 100 | } 101 | else if (job.mode === 'extract') { 102 | args.push('-acodec', 'copy', '-vn'); 103 | switch (extension) { 104 | case 'weba': 105 | case 'webm': 106 | case 'ogg': 107 | case 'vorbis': 108 | extension = 'ogg'; 109 | break; 110 | case 'mp4': 111 | case 'm4a': 112 | extension = 'm4a'; 113 | break; 114 | default: 115 | extension = 'mka'; // "MKA" container format can store a huge number of audio codecs. 116 | } 117 | } 118 | else if (job.mode === 'muxer') { 119 | /* find video's extension */ 120 | extension = job.files 121 | .map(f => f.name.split('.').pop()) 122 | .filter(e => e === 'mp4' || e === 'webm') 123 | .pop() || extension; 124 | args.push('-c', 'copy'); 125 | } 126 | else if (job.mode === 'volume') { 127 | args.push('-af', `volume=${job.recipe.volume / 100}`); 128 | } 129 | else if (job.mode === 'scale') { 130 | args.push('-vf', `scale=iw*${job.recipe.multiply}/${job.recipe.divide}:-1`); 131 | } 132 | else if (job.mode === 'rotate') { 133 | if (job.recipe.angle === '-1') { 134 | args.push('-vf', 'transpose=2,transpose=2'); 135 | } 136 | else { 137 | args.push('-vf', 'transpose=' + job.recipe.angle); 138 | } 139 | } 140 | else if (job.mode === 'shift') { 141 | if (job.type === 'video') { 142 | args.push('-map', '0:a', '-map', '1:v', '-c', 'copy'); 143 | } 144 | else { 145 | args.push('-map', '0:v', '-map', '1:a', '-c', 'copy'); 146 | } 147 | } 148 | else if (job.mode === 'cut') { 149 | if (job.recipe.start) { 150 | args.push('-ss', job.recipe.start); 151 | } 152 | if (job.recipe.end) { 153 | args.push('-to', job.recipe.end); 154 | } 155 | args.push('-c', 'copy'); 156 | } 157 | else if (job.mode === 'custom') { 158 | if (job.recipe.input.audio.channels) { 159 | args.push('-ac', job.recipe.input.audio.channels); 160 | } 161 | if (job.recipe.input.audio.rate) { 162 | args.push('-ar', job.recipe.input.audio.rate); 163 | } 164 | if (job.recipe.input.video.rate) { 165 | args.push('-r', job.recipe.input.video.rate); 166 | } 167 | extension = job.recipe.output.format; 168 | if (job.recipe.output.audio.rate) { 169 | args.push('-ab', job.recipe.output.audio.rate); 170 | } 171 | if (job.recipe.output.video.rate) { 172 | args.push('-b', job.recipe.output.video.rate); 173 | } 174 | if (job.recipe.output.video.tolerance) { 175 | args.push('-bt', job.recipe.output.video.tolerance); 176 | } 177 | if (job.recipe.output.video.size) { 178 | args.push('-s', job.recipe.output.video.size); 179 | const [width, height] = job.recipe.output.video.size.split('x'); 180 | args.push('-aspect', width + ':' + height); 181 | } 182 | } 183 | 184 | return this.createUnique(this.config.user.savein, name, extension, job).then(output => { 185 | args.push(output); 186 | this.output = output; 187 | 188 | return Promise.all(job.files.map(file => this.send(file))).then(async files => { 189 | if (job.mode === 'shift') { 190 | args.unshift('-itsoffset', job.recipe.time, '-i', files[0]); 191 | } 192 | if (job.mode === 'concat' && job.recipe.type === 'concat-protocol') { 193 | args.unshift('-c', 'copy'); 194 | args.unshift('concat:' + files.join('|')); 195 | args.unshift('-i'); 196 | } 197 | else if (job.mode === 'concat' && job.recipe.type === 'concat-demuxer') { 198 | const blob = new Blob([files.map(file => `file '${file}'`).join('\n')]); 199 | blob.name = 'files.txt'; 200 | const txt = await this.send(blob); 201 | args.unshift( 202 | '-safe', '0', 203 | '-f', 'concat', 204 | '-i', txt, 205 | '-c', 'copy' 206 | ); 207 | } 208 | else if (job.mode === 'concat' && job.recipe.type === 'concat-filter') { 209 | args.unshift('-map', '[a]', '-map', '[v]'); 210 | args.unshift( 211 | '-filter_complex', 212 | files.reverse().map((f, i) => `[${i}:v][${i}:a]`).join(' ') + ` concat=n=${files.length}:v=1:a=1 [v] [a]` 213 | ); 214 | files.forEach(f => args.unshift('-i', f)); 215 | } 216 | else { 217 | files.forEach(file => args.unshift('-i', file)); 218 | } 219 | 220 | args.unshift('-hide_banner', '-nostdin'); 221 | 222 | this.log('warning', 'Arguments: ' + args.join(', ')); 223 | this.spawn(this.config.ffmpeg.path, args); 224 | }); 225 | }).catch(reject); 226 | } 227 | else { 228 | resolve(); 229 | } 230 | }); 231 | }; 232 | 233 | NFFmpeg.prototype.find = function() { 234 | return new Promise((resolve, reject) => { 235 | this.callback = function(r) { 236 | if (r && r.code === 0) { 237 | const path = r.stdout.trim(); 238 | this.config.ffmpeg.path = path; 239 | resolve(path); 240 | } 241 | else { 242 | reject(new Error(r.stderr || `I am not able to find FFmpeg executable; code=${r.code}`)); 243 | } 244 | }; 245 | if (this.platform === 'Windows') { 246 | this.exec('where', ['ffmpeg.exe'], [this.config.user.home]); 247 | } 248 | else { 249 | this.exec('which', ['ffmpeg'], ['/usr/local/bin', this.config.user.home]); 250 | } 251 | }); 252 | }; 253 | 254 | NFFmpeg.prototype.createUnique = function(root, name, extension) { 255 | return new Promise((resolve, reject) => { 256 | function check(files, name, extension, index = 0) { 257 | const leafname = name.replace(/-\d+$/, '') + (index ? '-' + index : '') + '.' + extension; 258 | for (const n of files) { 259 | if (n.endsWith(leafname)) { 260 | return check(files, name, extension, index + 1); 261 | } 262 | } 263 | return leafname; 264 | } 265 | 266 | this.callback = function(obj) { 267 | if (obj.error) { 268 | reject(new Error(obj.error)); 269 | } 270 | else { 271 | resolve(root + obj.separator + check(obj.files, name, extension)); 272 | } 273 | }; 274 | this.dir(root); 275 | }); 276 | }; 277 | 278 | NFFmpeg.prototype.init = function() { 279 | return new Promise((resolve, reject) => { 280 | const me = this; 281 | this.callback = function(response) { 282 | if (response) { 283 | me.config.native.version = response.version; 284 | me.config.user.home = (response.env.HOME || response.env.USERPROFILE); 285 | me.config.user.separator = response.separator; 286 | let savein = me.config.user.home; 287 | me.config.user._savein = savein; 288 | savein = me.config.user.savein || savein; 289 | me.config.user.savein = savein; 290 | let tmpdir = response.tmpdir; 291 | me.config.user._tmpdir = tmpdir; 292 | tmpdir = me.config.user.tmpdir || tmpdir; 293 | me.config.user.tmpdir = tmpdir; 294 | 295 | resolve({ 296 | version: response.version, 297 | savein, 298 | tmpdir, 299 | port: me.config.port 300 | }); 301 | } 302 | else { 303 | reject(new Error('Cannot communicate with the native client')); 304 | } 305 | }; 306 | this.spec(); 307 | }); 308 | }; 309 | 310 | NFFmpeg.prototype.download = function() { 311 | function download(filename, url) { 312 | return new Promise((resolve, reject) => { 313 | chrome.downloads.download({url, filename}, id => { 314 | function observe(d) { 315 | if (d.id === id && d.state) { 316 | if (d.state.current === 'complete' || d.state.current === 'interrupted') { 317 | chrome.downloads.onChanged.removeListener(observe); 318 | if (d.state.current === 'complete') { 319 | chrome.downloads.search({id}, ([d]) => { 320 | if (d) { 321 | resolve(d); 322 | } 323 | else { 324 | reject('I am not able to find the downloaded file!'); 325 | } 326 | }); 327 | } 328 | else { 329 | reject('The downloading job got interrupted'); 330 | } 331 | } 332 | } 333 | } 334 | chrome.downloads.onChanged.addListener(observe); 335 | }); 336 | }); 337 | } 338 | 339 | return new Promise((resolve, reject) => { 340 | this.callback = function(obj) { 341 | if (obj.code === 0) { 342 | this.config.ffmpeg.path = obj.target; 343 | resolve(obj.target); 344 | } 345 | else { 346 | reject(obj.error); 347 | } 348 | }; 349 | let name = 'ffmpeg-'; 350 | const platform = navigator.platform; 351 | if (platform === 'Win32') { 352 | name += 'win32-ia32.exe'; 353 | } 354 | else if (platform === 'Win64') { 355 | name += 'win32-x64.exe'; 356 | } 357 | else if (platform === 'MacIntel') { 358 | name += 'darwin-x64'; 359 | } 360 | else if (platform.indexOf('64')) { 361 | name += 'linux-x64'; 362 | } 363 | else { 364 | name += 'linux-ia32'; 365 | } 366 | const req = new window.XMLHttpRequest(); 367 | req.open('GET', 'https://api.github.com/repos/inbasic/ffmpeg/releases/latest'); 368 | req.responseType = 'json'; 369 | req.onload = () => { 370 | const assests = req.response.assets.filter(o => o.name === name); 371 | if (assests.length) { 372 | const filename = navigator.platform.startsWith('Win') ? 'ffmpeg.exe' : 'ffmpeg'; 373 | const url = assests[0].browser_download_url; 374 | download(filename, url).then(d => this.cut({ 375 | source: d.filename, 376 | target: this.config.user.home + 377 | this.config.user.separator + filename, 378 | chmod: '0777', 379 | delete: true 380 | })).catch(reject); 381 | } 382 | else { 383 | reject('Cannot find ' + name + ' in the list. Please download and install FFmpeg manually.'); 384 | } 385 | }; 386 | req.onerror = reject; 387 | req.send(); 388 | }); 389 | }; 390 | 391 | NFFmpeg.prototype.server = function() { 392 | return new Promise((resolve, reject) => { 393 | this.callback = function(res) { 394 | if (res.code === 0) { 395 | resolve(); 396 | } 397 | else { 398 | reject(new Error('Cannot create server at this port; ' + res.error)); 399 | } 400 | }; 401 | this.ifup(this.config.port, this.key); 402 | }); 403 | }; 404 | 405 | NFFmpeg.prototype.send = function(file) { 406 | const path = this.config.user.tmpdir + this.config.user.separator + parseInt(Math.random() * 1000) + '_' + file.name; 407 | this.log('info', 'Saving a temp file in ' + path); 408 | 409 | // check for non-ASCII characters 410 | if (/^[ -~]+$/.test(path) === false && vCompare('0.3.1', ffmpeg.config.native.version) > 0) { 411 | return Promise.reject(new Error( 412 | 'Support for non-ASCII characters in file-name is introduced in native-client v0.3.1. ' + 413 | 'Please update your native client or rename the files and try again.' 414 | )); 415 | } 416 | const epath = /^[ -~]+$/.test(path) ? path : 'enc:' + window.encodeURIComponent(path); 417 | // PUT 418 | 419 | return fetch('http://127.0.0.1:' + this.config.port, { 420 | method: 'PUT', 421 | headers: { 422 | 'file-path': epath, 423 | 'api-key': this.key 424 | }, 425 | body: file // This is your file object 426 | }).then(() => path); 427 | }; 428 | 429 | NFFmpeg.prototype.cleanup = function() { 430 | return new Promise((resolve, reject) => { 431 | this.callback = function(res) { 432 | if (res.code === 0) { 433 | resolve(); 434 | } 435 | else { 436 | reject(new Error(res.error)); 437 | } 438 | }; 439 | this['clean-tmp'](); 440 | }); 441 | }; 442 | -------------------------------------------------------------------------------- /v3/data/converter/restore.js: -------------------------------------------------------------------------------- 1 | /* globals EventEmitter */ 2 | 'use strict'; 3 | 4 | var restore = new EventEmitter(); 5 | 6 | document.addEventListener('change', e => { 7 | const target = e.target; 8 | if (target.dataset.restore) { 9 | chrome.storage.local.set({ 10 | 'session-restore': [...document.querySelectorAll('[data-restore]')].map(e => { 11 | if (e.type === 'checkbox' || e.type === 'radio') { 12 | return { 13 | id: e.dataset.restore, 14 | value: e.checked 15 | }; 16 | } 17 | return { 18 | id: e.dataset.restore, 19 | value: e.value 20 | }; 21 | }) 22 | }); 23 | } 24 | }); 25 | 26 | restore.on('restore', callback => { 27 | chrome.storage.local.get({ 28 | 'session-restore': [] 29 | }, prefs => { 30 | prefs['session-restore'].forEach(o => { 31 | const element = document.querySelector(`[data-restore="${o.id}"]`); 32 | if (element) { 33 | element[typeof o.value === 'boolean' ? 'checked' : 'value'] = o.value; 34 | element.dispatchEvent(new Event('change', {bubbles: true})); 35 | } 36 | }); 37 | callback(); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /v3/data/converter/ui.js: -------------------------------------------------------------------------------- 1 | /* globals element, ffmpeg, os */ 2 | 'use strict'; 3 | 4 | var os = navigator.userAgent.indexOf('Mac') !== -1 ? 'mac' : ( 5 | navigator.userAgent.indexOf('Linux') !== -1 ? 'linux' : 'windows' 6 | ); 7 | 8 | document.documentElement.dataset.platform = os; 9 | 10 | document.addEventListener('change', e => { 11 | const target = e.target; 12 | const id = target.dataset.for; 13 | // tabs 14 | if (id && !target.disabled && target.checked) { 15 | const panel = document.getElementById(id); 16 | element.panels.list.filter(e => e !== panel).forEach(e => e.dataset.selected = false); 17 | panel.dataset.selected = true; 18 | } 19 | // range 20 | if (target.type === 'range') { 21 | target.parentNode.querySelector('span').textContent = target.value + (target.dataset.append || ''); 22 | } 23 | // samples 24 | if (target.dataset.cmd === 'fill-sample-values') { 25 | const [vbr = '', vtr = '', abr = ''] = target.value.split(','); 26 | element.custom.output.audio.rate = abr; 27 | element.custom.output.video.rate = vbr; 28 | element.custom.output.video.tolerance = vtr; 29 | } 30 | }); 31 | // user preferences 32 | document.addEventListener('keyup', ({target}) => { 33 | const pref = target.dataset.pref; 34 | if (pref) { 35 | target.parentNode.querySelector('[type=button]').disabled = target.value === target.dataset.value; 36 | } 37 | }); 38 | document.addEventListener('click', ({target}) => { 39 | const cmd = target.dataset.cmd; 40 | if (cmd === 'save-preference') { 41 | const input = target.parentNode.querySelector('[type=text]'); 42 | const pref = input.dataset.pref; 43 | chrome.storage.local.set({ 44 | [pref]: input.value 45 | }, () => { 46 | input.dataset.value = input.value; 47 | input.dispatchEvent(new Event('keyup', {bubbles: true})); 48 | ffmpeg.emit(pref + '-changed', input.value); 49 | }); 50 | } 51 | else if (cmd === 'restart') { 52 | window.location.reload(); 53 | } 54 | else if (cmd === 'support') { 55 | chrome.tabs.create({ 56 | url: chrome.runtime.getManifest().homepage_url + '?rd=donate' 57 | }); 58 | } 59 | else if (cmd === 'reset') { 60 | if (window.confirm('Are you sure you want to reset all the settings to the factory defaults?')) { 61 | localStorage.clear(); 62 | chrome.storage.local.clear(() => { 63 | chrome.runtime.reload(); 64 | window.close(); 65 | }); 66 | } 67 | } 68 | else if (cmd === 'download-native') { 69 | element.instruction.parent.dataset.working = true; 70 | const req = new window.XMLHttpRequest(); 71 | req.open('GET', 'https://api.github.com/repos/andy-portmen/native-client/releases/latest'); 72 | req.responseType = 'json'; 73 | req.onload = () => { 74 | try { 75 | chrome.downloads.download({ 76 | url: req.response.assets.filter(a => a.name === os + '.zip')[0].browser_download_url, 77 | filename: os + '.zip' 78 | }, () => { 79 | element.instruction.step1.style = 'text-decoration: line-through;'; 80 | element.instruction.parent.dataset.working = false; 81 | }); 82 | } 83 | catch (e) { 84 | window.alert(e.message || e); 85 | } 86 | }; 87 | req.onerror = () => { 88 | window.alert('Something went wrong! Please download the package manually'); 89 | window.setTimeout(() => { 90 | window.open('https://github.com/andy-portmen/native-client/releases'); 91 | }, 5000); 92 | }; 93 | req.send(); 94 | } 95 | else if (cmd === 'update-native-client') { 96 | document.body.dataset.instruction = true; 97 | } 98 | else if (cmd === 'update-ffmpeg') { 99 | document.body.dataset.working = true; 100 | element.busy.parent.dataset.working = true; 101 | ffmpeg.download().then(path => { 102 | element.busy.parent.dataset.working = false; 103 | document.body.dataset.working = false; 104 | return path; 105 | }); 106 | } 107 | else if (cmd === 'reset-custom') { 108 | element.custom.input.audio.rate = ''; 109 | element.custom.input.audio.channels = ''; 110 | element.custom.input.video.rate = ''; 111 | element.custom.output.format = 'mp4'; 112 | element.custom.output.video.size = ''; 113 | element.custom.output.video.rate = ''; 114 | element.custom.output.video.tolerance = ''; 115 | element.custom.output.audio.rate = ''; 116 | } 117 | }); 118 | //resize 119 | (function(id) { 120 | function resize() { 121 | chrome.storage.local.set({ 122 | width: window.outerWidth, 123 | height: window.outerHeight 124 | }); 125 | } 126 | function check() { 127 | window.clearTimeout(id); 128 | id = window.setTimeout(resize, 1000); 129 | } 130 | 131 | window.addEventListener('resize', check); 132 | })(); 133 | -------------------------------------------------------------------------------- /v3/data/converter/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = function() { 4 | this.callbacks = {}; 5 | }; 6 | EventEmitter.prototype.on = function(id, callback) { 7 | this.callbacks[id] = this.callbacks[id] || []; 8 | this.callbacks[id].push(callback); 9 | }; 10 | EventEmitter.prototype.emit = function(id, value) { 11 | (this.callbacks[id] || []).forEach(c => c(value)); 12 | }; 13 | 14 | function vCompare(v1, v2) { 15 | const toNumber = v => v.split('.') 16 | .slice(0, 3) 17 | .reverse() 18 | .map((v, i) => Number(v) * Math.pow(10, i)).reduce((p, c) => c + p, 0); 19 | return toNumber(v1) - toNumber(v2); 20 | } 21 | -------------------------------------------------------------------------------- /v3/data/converter/wasm/ffmpeg.js: -------------------------------------------------------------------------------- 1 | /* global NFFmpeg, ffmpeg */ 2 | 3 | class WFFmpeg extends NFFmpeg { 4 | async run(...args) { 5 | this.iframe.contentWindow.postMessage({ 6 | method: 'run', 7 | args 8 | }); 9 | } 10 | read(file) { 11 | return new Promise(resolve => { 12 | const r = new FileReader(); 13 | r.onload = () => resolve(r.result); 14 | r.readAsArrayBuffer(file); 15 | }); 16 | } 17 | async init() { 18 | return { 19 | version: 'v.0.10.0 (WASM)', 20 | savein: '', 21 | tmpdir: '', 22 | port: '' 23 | }; 24 | } 25 | async createUnique(root, name, extension, job) { 26 | let filename = name + '.' + extension; 27 | const filenames = job.files.map(f => f.name); 28 | 29 | let index = 1; 30 | while (filenames.indexOf(filename) !== -1) { 31 | filename = name + '-' + index + '.' + extension; 32 | index += 1; 33 | } 34 | return filename; 35 | } 36 | async cleanup() { 37 | this.iframe.remove(); 38 | delete this.iframe; 39 | } 40 | async send(file) { 41 | if (!this.iframe) { 42 | await new Promise(resolve => { 43 | const iframe = this.iframe = document.createElement('iframe'); 44 | iframe.src = 'wasm/instance.html'; 45 | iframe.onload = resolve; 46 | iframe.style.display = 'none'; 47 | document.body.appendChild(iframe); 48 | }); 49 | } 50 | this.iframe.contentWindow.postMessage({ 51 | method: 'input-file', 52 | name: file.name, 53 | buffer: await this.read(file) 54 | }, '*'); 55 | 56 | await new Promise(resolve => { 57 | this.send.resolve = resolve; 58 | }); 59 | 60 | return file.name; 61 | } 62 | async find() { 63 | return ''; 64 | } 65 | async server() { 66 | return; 67 | } 68 | onDisconnect() {} 69 | } 70 | 71 | window.addEventListener('message', async e => { 72 | const request = e.data; 73 | 74 | if (request.method === 'terminate') { 75 | await ffmpeg.cleanup(); 76 | await new Promise(resolve => setTimeout(resolve, 2000)); 77 | ffmpeg.busy = false; 78 | ffmpeg.counter(); 79 | ffmpeg.convert(); 80 | ffmpeg.log('warning', 'Done!'); 81 | } 82 | else if (request.method === 'stdout') { 83 | ffmpeg.log('info', request.msg); 84 | } 85 | else if (request.method === 'stderr') { 86 | ffmpeg.log('error', request.msg); 87 | } 88 | }); 89 | -------------------------------------------------------------------------------- /v3/data/converter/wasm/ffmpeg/README: -------------------------------------------------------------------------------- 1 | https://unpkg.com/@ffmpeg/core@0.10.0/dist/ffmpeg-core.js 2 | https://unpkg.com/@ffmpeg/core@0.10.0/dist/ffmpeg-core.wasm 3 | https://unpkg.com/@ffmpeg/core@0.10.0/dist/ffmpeg-core.worker.js 4 | -------------------------------------------------------------------------------- /v3/data/converter/wasm/ffmpeg/ffmpeg-core.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/media-converter/ac991fdd89473ae1d432457521c81dd9f402f9a0/v3/data/converter/wasm/ffmpeg/ffmpeg-core.wasm -------------------------------------------------------------------------------- /v3/data/converter/wasm/ffmpeg/ffmpeg-core.worker.js: -------------------------------------------------------------------------------- 1 | var threadInfoStruct = 0; 2 | var selfThreadId = 0; 3 | var parentThreadId = 0; 4 | var Module = {}; 5 | 6 | function threadPrintErr() { 7 | var text = Array.prototype.slice.call(arguments).join(" "); 8 | console.error(text) 9 | } 10 | 11 | function threadAlert() { 12 | var text = Array.prototype.slice.call(arguments).join(" "); 13 | postMessage({ 14 | cmd: "alert", 15 | text: text, 16 | threadId: selfThreadId 17 | }) 18 | } 19 | var err = threadPrintErr; 20 | this.alert = threadAlert; 21 | Module["instantiateWasm"] = function(info, receiveInstance) { 22 | var instance = new WebAssembly.Instance(Module["wasmModule"], info); 23 | Module["wasmModule"] = null; 24 | receiveInstance(instance); 25 | return instance.exports 26 | }; 27 | this.onmessage = function(e) { 28 | try { 29 | if (e.data.cmd === "load") { 30 | Module["wasmModule"] = e.data.wasmModule; 31 | Module["wasmMemory"] = e.data.wasmMemory; 32 | Module["buffer"] = Module["wasmMemory"].buffer; 33 | Module["ENVIRONMENT_IS_PTHREAD"] = true; 34 | if (typeof e.data.urlOrBlob === "string") { 35 | importScripts(e.data.urlOrBlob) 36 | } else { 37 | var objectUrl = URL.createObjectURL(e.data.urlOrBlob); 38 | importScripts(objectUrl); 39 | URL.revokeObjectURL(objectUrl) 40 | } 41 | createFFmpegCore(Module).then(function(instance) { 42 | Module = instance; 43 | postMessage({ 44 | "cmd": "loaded" 45 | }) 46 | }) 47 | } else if (e.data.cmd === "objectTransfer") { 48 | Module["PThread"].receiveObjectTransfer(e.data) 49 | } else if (e.data.cmd === "run") { 50 | Module["__performance_now_clock_drift"] = performance.now() - e.data.time; 51 | threadInfoStruct = e.data.threadInfoStruct; 52 | Module["registerPthreadPtr"](threadInfoStruct, /*isMainBrowserThread=*/ 0, /*isMainRuntimeThread=*/ 0); 53 | selfThreadId = e.data.selfThreadId; 54 | parentThreadId = e.data.parentThreadId; 55 | var max = e.data.stackBase; 56 | var top = e.data.stackBase + e.data.stackSize; 57 | Module["establishStackSpace"](top, max); 58 | Module["_emscripten_tls_init"](); 59 | Module["PThread"].receiveObjectTransfer(e.data); 60 | Module["PThread"].setThreadStatus(Module["_pthread_self"](), 1); /*EM_THREAD_STATUS_RUNNING*/ 61 | try { 62 | var result = Module["dynCall"]("ii", e.data.start_routine, [e.data.arg]); 63 | if (!Module["getNoExitRuntime"]()) Module["PThread"].threadExit(result) 64 | } catch (ex) { 65 | if (ex === "Canceled!") { 66 | Module["PThread"].threadCancel() 67 | } else if (ex != "unwind") { 68 | Atomics.store(Module["HEAPU32"], (threadInfoStruct + 4) >> /*C_STRUCTS.pthread.threadExitCode*/ 2, (ex instanceof Module["ExitStatus"]) ? ex.status : -2); /*A custom entry specific to Emscripten denoting that the thread crashed.*/ 69 | Atomics.store(Module["HEAPU32"], (threadInfoStruct + 0) >> /*C_STRUCTS.pthread.threadStatus*/ 2, 1); 70 | Module["_emscripten_futex_wake"](threadInfoStruct + 0, /*C_STRUCTS.pthread.threadStatus*/ 2147483647); 71 | if (!(ex instanceof Module["ExitStatus"])) throw ex 72 | } 73 | } 74 | } else if (e.data.cmd === "cancel") { 75 | if (threadInfoStruct) { 76 | Module["PThread"].threadCancel() 77 | } 78 | } else if (e.data.target === "setimmediate") {} else if (e.data.cmd === "processThreadQueue") { 79 | if (threadInfoStruct) { 80 | Module["_emscripten_current_thread_process_queued_calls"]() 81 | } 82 | } else { 83 | err("worker.js received unknown command " + e.data.cmd); 84 | err(e.data) 85 | } 86 | } catch (ex) { 87 | err("worker.js onmessage() captured an uncaught exception: " + ex); 88 | if (ex && ex.stack) err(ex.stack); 89 | throw ex 90 | } 91 | }; 92 | if (typeof process === "object" && typeof process.versions === "object" && typeof process.versions.node === "string") { 93 | self = { 94 | location: { 95 | href: __filename 96 | } 97 | }; 98 | var onmessage = this.onmessage; 99 | var nodeWorkerThreads = require("worker_threads"); 100 | global.Worker = nodeWorkerThreads.Worker; 101 | var parentPort = nodeWorkerThreads.parentPort; 102 | parentPort.on("message", function(data) { 103 | onmessage({ 104 | data: data 105 | }) 106 | }); 107 | var nodeFS = require("fs"); 108 | var nodeRead = function(filename) { 109 | return nodeFS.readFileSync(filename, "utf8") 110 | }; 111 | 112 | function globalEval(x) { 113 | global.require = require; 114 | global.Module = Module; 115 | eval.call(null, x) 116 | } 117 | importScripts = function(f) { 118 | globalEval(nodeRead(f)) 119 | }; 120 | postMessage = function(msg) { 121 | parentPort.postMessage(msg) 122 | }; 123 | if (typeof performance === "undefined") { 124 | performance = { 125 | now: function() { 126 | return Date.now() 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /v3/data/converter/wasm/instance.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /v3/data/converter/wasm/instance.js: -------------------------------------------------------------------------------- 1 | /* global createFFmpegCore */ 2 | let Core; 3 | let ffmpeg; 4 | let filename; 5 | 6 | const parse = args => { 7 | const argsPtr = Core._malloc(args.length * Uint32Array.BYTES_PER_ELEMENT); 8 | args.forEach((s, idx) => { 9 | const buf = Core._malloc(s.length + 1); 10 | Core.writeAsciiToMemory(s, buf); 11 | Core.setValue(argsPtr + (Uint32Array.BYTES_PER_ELEMENT * idx), buf, 'i32'); 12 | }); 13 | return [args.length, argsPtr]; 14 | }; 15 | 16 | const s = createFFmpegCore({ 17 | mainScriptUrlOrBlob: 'ffmpeg-core.js', 18 | printErr(msg) { 19 | parent.postMessage({ 20 | method: 'stderr', 21 | msg 22 | }, '*'); 23 | }, 24 | print(msg) { 25 | if (msg === 'FFMPEG_END') { 26 | const output = Core.FS.readFile(filename); 27 | const blob = new Blob([output], { 28 | type: 'audio/mp3' 29 | }); 30 | const href = URL.createObjectURL(blob); 31 | const a = document.createElement('a'); 32 | a.href = href; 33 | a.download = filename; 34 | a.click(); 35 | 36 | try { 37 | Core.exit(0); 38 | } 39 | catch (e) {} 40 | Core = null; 41 | ffmpeg = null; 42 | 43 | parent.postMessage({ 44 | method: 'terminate' 45 | }, '*'); 46 | } 47 | if (msg !== 'FFMPEG_ENDFFMPEG_END' && msg !== 'FFMPEG_END') { 48 | parent.postMessage({ 49 | method: 'stdout', 50 | msg 51 | }); 52 | } 53 | }, 54 | locateFile(path, prefix) { 55 | return prefix + path; 56 | } 57 | }).then(C => { 58 | Core = C; 59 | }); 60 | 61 | 62 | window.addEventListener('message', async e => { 63 | const request = e.data; 64 | if (request.method === 'run') { 65 | filename = request.args[request.args.length - 1]; 66 | ffmpeg = Core.cwrap('proxy_main', 'number', ['number', 'number']); 67 | ffmpeg(...parse(request.args)); 68 | } 69 | else if (request.method === 'input-file') { 70 | await s; 71 | Core.FS.writeFile(request.name, new Uint8Array(request.buffer)); 72 | parent.ffmpeg.send.resolve(); 73 | } 74 | }); 75 | -------------------------------------------------------------------------------- /v3/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/media-converter/ac991fdd89473ae1d432457521c81dd9f402f9a0/v3/data/icons/128.png -------------------------------------------------------------------------------- /v3/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/media-converter/ac991fdd89473ae1d432457521c81dd9f402f9a0/v3/data/icons/16.png -------------------------------------------------------------------------------- /v3/data/icons/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/media-converter/ac991fdd89473ae1d432457521c81dd9f402f9a0/v3/data/icons/19.png -------------------------------------------------------------------------------- /v3/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/media-converter/ac991fdd89473ae1d432457521c81dd9f402f9a0/v3/data/icons/256.png -------------------------------------------------------------------------------- /v3/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/media-converter/ac991fdd89473ae1d432457521c81dd9f402f9a0/v3/data/icons/32.png -------------------------------------------------------------------------------- /v3/data/icons/38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/media-converter/ac991fdd89473ae1d432457521c81dd9f402f9a0/v3/data/icons/38.png -------------------------------------------------------------------------------- /v3/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/media-converter/ac991fdd89473ae1d432457521c81dd9f402f9a0/v3/data/icons/48.png -------------------------------------------------------------------------------- /v3/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/media-converter/ac991fdd89473ae1d432457521c81dd9f402f9a0/v3/data/icons/64.png -------------------------------------------------------------------------------- /v3/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Media Converter and Muxer - Audio Tools", 3 | "description": "Easy to use video and audio converter and muxer based on FFmpeg multimedia command-line tool.", 4 | "version": "0.4.0", 5 | "manifest_version": 3, 6 | "permissions": [ 7 | "storage" 8 | ], 9 | "optional_permissions": [ 10 | "nativeMessaging", 11 | "downloads" 12 | ], 13 | "optional_host_permissions": [ 14 | "http://127.0.0.1/" 15 | ], 16 | "homepage_url": "https://add0n.com/media-converter.html", 17 | "icons": { 18 | "16": "data/icons/16.png", 19 | "19": "data/icons/19.png", 20 | "32": "data/icons/32.png", 21 | "38": "data/icons/38.png", 22 | "48": "data/icons/48.png", 23 | "64": "data/icons/64.png", 24 | "128": "data/icons/128.png", 25 | "256": "data/icons/256.png" 26 | }, 27 | "background": { 28 | "service_worker": "worker.js" 29 | }, 30 | "action": {}, 31 | "content_security_policy": { 32 | "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" 33 | }, 34 | "commands": { 35 | "_execute_action": {} 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /v3/worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const open = () => chrome.runtime.sendMessage({ 4 | method: 'is-open' 5 | }, r => { 6 | chrome.runtime.lastError; 7 | if (r !== true) { 8 | chrome.storage.local.get({ 9 | width: 800, 10 | height: 800 11 | }, async prefs => { 12 | const win = await chrome.windows.getCurrent(); 13 | chrome.windows.create({ 14 | url: '/data/converter/index.html', 15 | width: prefs.width, 16 | height: prefs.height, 17 | left: Math.round((win.width - prefs.width) / 2), 18 | top: Math.round((win.height - prefs.height) / 2), 19 | type: 'popup' 20 | }); 21 | }); 22 | } 23 | }); 24 | chrome.action.onClicked.addListener(open); 25 | 26 | chrome.runtime.onMessageExternal.addListener((request, sender, response) => { 27 | if (request.method === 'open') { 28 | open(); 29 | response(true); 30 | } 31 | else if (request.method === 'focus') { 32 | chrome.windows.update(sender.tab.windowId, { 33 | focused: true 34 | }); 35 | } 36 | }); 37 | 38 | /* FAQs & Feedback */ 39 | { 40 | const {management, runtime: {onInstalled, setUninstallURL, getManifest}, storage, tabs} = chrome; 41 | if (navigator.webdriver !== true) { 42 | const page = getManifest().homepage_url; 43 | const {name, version} = getManifest(); 44 | onInstalled.addListener(({reason, previousVersion}) => { 45 | management.getSelf(({installType}) => installType === 'normal' && storage.local.get({ 46 | 'faqs': true, 47 | 'last-update': 0 48 | }, prefs => { 49 | if (reason === 'install' || (prefs.faqs && reason === 'update')) { 50 | const doUpdate = (Date.now() - prefs['last-update']) / 1000 / 60 / 60 / 24 > 45; 51 | if (doUpdate && previousVersion !== version) { 52 | tabs.query({active: true, currentWindow: true}, tbs => tabs.create({ 53 | url: page + '?version=' + version + (previousVersion ? '&p=' + previousVersion : '') + '&type=' + reason, 54 | active: reason === 'install', 55 | ...(tbs && tbs.length && {index: tbs[0].index + 1}) 56 | })); 57 | storage.local.set({'last-update': Date.now()}); 58 | } 59 | } 60 | })); 61 | }); 62 | setUninstallURL(page + '?rd=feedback&name=' + encodeURIComponent(name) + '&version=' + version); 63 | } 64 | } 65 | --------------------------------------------------------------------------------