123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- <html>
- <head>
- <meta charset="UTF-8">
- <meta name="description" content="Online Media File Converter">
- <meta name="author" content="tobychui">
- <title>Universal Format Converter | imuslab</title>
- <link rel="icon" type="image/png" href="/favicon.png" />
- <meta name="apple-mobile-web-app-capable" content="yes" />
- <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.3/semantic.min.js" integrity="sha512-gnoBksrDbaMnlE0rhhkcx3iwzvgBGz6mOEj4/Y5ZY09n55dYddx6+WYc72A55qEesV8VX2iMomteIwobeGK1BQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.3/semantic.min.css" integrity="sha512-3quBdRGJyLy79hzhDDcBzANW+mVqPctrGCfIPosHQtMKb3rKsCxfyslzwlz2wj1dT8A7UX+sEvDjaUv+WExQrA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@100;200;300;400&display=swap" rel="stylesheet">
- <style>
- .main{
- display: flex;
- align-items: center;
- justify-content: center;
- height: calc(100vh);
- margin: 0;
- }
-
- .item.disabled{
- opacity: 0.5;
- }
-
- .ui.header,button,input{
- font-family: 'Noto Sans TC', sans-serif !important;
- }
-
- @media screen and (min-width: 768px) {
- #title{
- border-right: 1px solid #dedede;
- padding-right: 5em;
- min-width: 300px;
- }
-
- #title .ui.header{
- width: 300px;
- }
-
- #qrgrid{
- padding-left: 5em;
- }
- }
- </style>
- </head>
- <body>
- <div class="main">
- <div class="ui stackable grid">
- <div id="title" class="eight wide column">
- <h2 class="ui header">
- Universal Format Converter
- <div class="sub header">通用多媒體格式轉換工具</div>
- </h2>
- <div class="ui divider"></div>
- <p>Select a file | 選擇檔案</p>
- <div class="ui file input">
- <input type="file" id="uploader">
- </div>
- <br><br>
- <p>Convert selected file to | 轉換檔案格式至</p>
- <div class="ui fluid selection dropdown">
- <div class="default text">Select format | 選擇格式</div>
- <i class="dropdown icon"></i>
- <div class="menu">
- <div class="header">
- <i class="play icon"></i>
- Video | 影片
- </div>
- <div class="item format fVideo disabled">MP4</div>
- <div class="item format fVideo disabled">MKV</div>
- <div class="item format fVideo disabled">AVI</div>
- <div class="header">
- <i class="music icon"></i>
- Audio | 音訊
- </div>
- <div class="item format fAudio disabled">MP3</div>
- <div class="item format fAudio disabled">WAV</div>
- <div class="item format fAudio disabled">FLAC</div>
- <div class="header">
- <i class="image icon"></i>
- Image | 圖片
- </div>
- <div class="item format fImage disabled">PNG</div>
- <div class="item format fImage disabled">JPEG</div>
- <div class="item format fImage disabled">WebP</div>
- <div class="item format fImage fVideo disabled">GIF</div>
- </div>
- </div>
- <button id="startconv" class="ui basic fluid button" style="margin-top: 1em;">Start Convert | 轉換</button>
- <div class="ui divider"></div>
- <p>Powered by <a href="/">imuslab</a></p>
- </div>
- <div id="conv" class="eight wide column" align="center">
- <div style="width: 300px">
- <video id="output-video" style="display:none; max-width: 100%;" controls></video><br/>
- <img id="output-image" class="ui fluid image" style="display:none;"></img>
-
- <p id="message" style="margin-top: 1em;"><i class="ui arrow left icon"></i> Select a file to start conversion</p>
- <a id="downloadbtn" href="#" download="">Download</a>
- </div>
- </div>
- </div>
- </div>
- <script>
- $(".dropdown").dropdown();
- </script>
-
- <script type="module">
- import { FFmpeg } from "./assets/ffmpeg/package/dist/esm/index.js";
- import { fetchFile } from "./assets/util/package/dist/esm/index.js";
- let ffmpeg = null;
-
- function getMimeFromExt(ext){
- if (ext == "mp4" || ext == "webm"){
- return "video/" + ext;
- }else if (ext == "mp3" || ext == "wav" || ext == "ogg" || ext == "flac"){
- return "audio/" + ext;
- }else if (ext == "png" || ext == "jpeg" || ext == "webp" || ext == "gif"){
- return "image/" + ext;
- }else{
- return "application/" + ext;
- }
- }
- const transcode = async ({ target: { files } }) => {
- const message = document.getElementById('message');
- if (ffmpeg === null) {
- ffmpeg = new FFmpeg();
- ffmpeg.on("log", ({ message }) => {
- console.log(message);
- })
- ffmpeg.on("progress", ({ progress }) => {
- message.innerHTML = `${(progress * 100).toFixed(3)} %`;
- });
- await ffmpeg.load({
- coreURL: "../../../../../assets/core-mt/package/dist/esm/ffmpeg-core.js",
- });
- }
- const { name } = files[0];
- let format = $(".dropdown").dropdown("get value")[0];
- await ffmpeg.writeFile(name, await fetchFile(files[0]));
- message.innerHTML = 'Start transcoding';
- await ffmpeg.exec(['-i', name, '-threads', '4', 'output.' + format]);
- message.innerHTML = 'Complete transcoding';
- const data = await ffmpeg.readFile('output.' + format);
-
- let mime = getMimeFromExt(format);
- let datablob = URL.createObjectURL(new Blob([data.buffer], { type: mime }));
- if (mime.includes("image")){
- $("#output-image").show();
- $("#output-video").hide();
- $("#output-image").attr('src', datablob);
- }else{
- $("#output-image").hide();
- $("#output-video").show();
- const video = document.getElementById('output-video');
- video.src = datablob;
- }
-
- //Generate a download button
- let outputFilename = name.split(".");
- outputFilename.pop();
- outputFilename = outputFilename + "." + format;
-
- $("#downloadbtn").attr('href', datablob);
- $("#downloadbtn").attr("download",outputFilename);
-
- }
-
- function disableInvalidOptions(input){
- let selectedFile = input.target.files;
- window.selectedFilelist = selectedFile;
- $(".dropdown").dropdown("clear");
- const message = document.getElementById('message');
- if (selectedFile.length > 0){
- selectedFile = selectedFile[0];
- let formats = document.getElementsByClassName("format");
- console.log(formats);
- for (var i = 0; i < formats.length; i++){
- let format = formats[i];
- format.classList.add("disabled");
- }
- let formatsToEnable;
- if (selectedFile.type.includes("video/")){
- formatsToEnable = document.querySelectorAll(".fVideo,.fAudio");
- }else if (selectedFile.type.includes("audio/")){
- formatsToEnable = document.getElementsByClassName("fAudio");
- }else if (selectedFile.type.includes("image/")){
- formatsToEnable = document.getElementsByClassName("fImage");
- }else{
- message.innerHTML = 'Input format not supported';
- return;
- }
-
- for (let item of formatsToEnable) {
- item.classList.remove("disabled");
- }
- }
- }
-
- function startConv(){
- let format = $(".dropdown").dropdown("get value")[0];
- if (format == ""){
- const message = document.getElementById('message');
- message.innerHTML = 'No format selected';
- return;
- }
-
- let selectedFile = {target: {
- files: window.selectedFilelist
- }
- };
-
- const message = document.getElementById('message');
- message.innerHTML = "Waiting server response"
- console.log(selectedFile);
- transcode(selectedFile);
- }
-
- const elm = document.getElementById('uploader');
- //elm.addEventListener('change', transcode);
- elm.addEventListener('change', disableInvalidOptions);
-
- const sconv = document.getElementById('startconv');
- sconv.addEventListener('click', startConv);
- </script>
- </body>
- </html>
|