https://github.com/incubated-geek-cc/FFmpegWasmLocalServer.git可将音频或视频文件转换为其他可选的多媒体格式,并导出转码的结果 $ bash run.sh 
FFmpeg App is listening on port 3000!
 
 
 
$npm install express
npm WARN old lockfile 
npm WARN old lockfile The package-lock.json file was created with an old version of npm,
npm WARN old lockfile so supplemental metadata must be fetched from the registry.
npm WARN old lockfile 
npm WARN old lockfile This is a one-time fix-up, please be patient...
npm WARN old lockfile 
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE   package: 'FFmpegWasmLocalServer@1.0.0',
npm WARN EBADENGINE   required: { node: '12.13.0' },
npm WARN EBADENGINE   current: { node: 'v16.20.0', npm: '8.19.4' }
npm WARN EBADENGINE }
npm WARN deprecated consolidate@0.16.0: Please upgrade to consolidate v1.0.0+ as it has been modernized with several long-awaited fixes implemented. Maintenance is supported by Forward Email at https://forwardemail.net ; follow/watch https://github.com/ladjs/consolidate for updates and release changelog
added 70 packages, and audited 71 packages in 2m
7 packages are looking for funding
  run `npm fund` for details
found 0 vulnerabilities
// 引入 Express 库
const express = require('express');
// 创建一个 Express 应用程序实例
const app = express();
// 设置应用程序的端口号,使用环境变量 PORT 或默认值 3000
const PORT = process.env.PORT || 3000;
// 引入文件系统和路径处理模块
const fs = require('fs');
const path = require('path');
// 引入 Consolidate 模块用于模板引擎支持
const engine = require("consolidate");
// 引入 compression 模块用于启用响应压缩
const compression = require('compression');
// 使用 compression 中间件,对所有响应进行压缩
app.use(compression());
// 中间件,启用 SharedBuffer
app.use(function(req, res, next) {
  // 设置响应头,启用 SharedBuffer
  res.header("Cross-Origin-Embedder-Policy", "require-corp");
  res.header("Cross-Origin-Opener-Policy", "same-origin");
  // 继续执行下一个中间件或路由处理函数
  next();
});
// 静态文件中间件,将 public 目录设置为静态文件目录
app.use(express.static(path.join(__dirname, "public")))
// 设置视图目录为 views
.set("views", path.join(__dirname, "views"))
// 使用 Mustache 模板引擎
.engine("html", engine.mustache)
// 设置视图引擎为 Mustache
.set("view engine", "html")
// 处理根路径的 GET 请求,渲染 index.html 页面
.get("/", (req, res) => res.render("index.html"))
// 处理 /index.html 路径的 GET 请求,同样渲染 index.html 页面
.get("/index.html", (req, res) => res.render("index.html"))
// 监听指定的端口号,当应用程序启动时打印日志
.listen(PORT, () => {
  console.log(`FFmpeg App is listening on port ${PORT}!`);
});
非https访问可能存在如下问题:The Cross-Origin-Opener-Policy header has been ignored, because the URL’s origin was untrustworthy. It was defined either in the final response or a redirect. Please deliver the response using the HTTPS protocol. You can also use the ‘localhost’ origin instead. See https://www.w3.org/TR/powerful-features/#potentially-trustworthy-origin and https://html.spec.whatwg.org/#the-cross-origin-opener-policy-header. const  express =  require ( 'express') ; 
const  https =  require ( 'https') ; 
const  fs =  require ( 'fs') ; 
const  compression =  require ( 'compression') ; 
const  engine =  require ( 'consolidate') ; 
const  app =  express ( ) ; 
const  PORT =  process. env. PORT ||  3000 ; 
const  options =  { 
  key:  fs. readFileSync ( 'path/ to/ server. key') , 
  cert :  fs. readFileSync ( 'path/ to/ server. crt') , 
} ; 
const  server =  https. createServer ( options,  app) ; 
app. use ( function ( req,  res,  next)  { 
  if  ( ! req. secure)  { 
    return  res. redirect ( 'https: / / ' +  req. headers. host +  req. url) ; 
  } 
  next ( ) ; 
} ) ; 
app. use ( compression ( ) ) ; 
app. use ( function ( req,  res,  next)  { 
  res. header ( 'Cross- Origin- Embedder- Policy',  'require- corp') ; 
  res. header ( 'Cross- Origin- Opener- Policy',  'same- origin') ; 
  next ( ) ; 
} ) ; 
app. use ( express. static ( __dirname +  '/ public ') ) 
  . set ( 'views',  __dirname +  '/ views') 
  . engine ( 'html',  engine. mustache) 
  . set ( 'view engine',  'html') 
  . get ( '/' ,  ( req,  res)  =>  res. render ( 'index. html') ) 
  . get ( '/ index. html',  ( req,  res)  =>  res. render ( 'index. html') ) ; 
server. listen ( PORT,  ( )  =>  { 
  console. log ( `FFmpeg App is  listening  on  port ${ PORT}  with HTTPS! `) ; 
} ) ; 
<html lang='en' class='notranslate' translate='no'>
  <head>
      <!-- 设置页面元数据 -->
      <meta name='google' content='notranslate' />      <meta charset='UTF-8'>      <meta name='description' content='An Offline Multimedia File Conversion Tool.'>      <meta name='keywords' content='ffmpeg,wasm API,audio-conversion'>      <meta name="author" content="Charmaine Chui" />      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">      <meta name='viewport' content='width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' />     <meta http-equiv='Content-Language' content='en' />
      <title>Media Transcoder | Built With FFmpeg for Audio & Video Files</title>
      <meta name='msapplication-TileColor' content='#ffffff' />      <meta name='theme-color' content='#ffffff' />      <meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />      <meta name='apple-mobile-web-app-capable' content='yes' />      <meta name='mobile-web-app-capable' content='yes' />      <meta name='HandheldFriendly' content='True' />      <meta name='MobileOptimized' content='320' />
      <!-- 设置网站图标 -->
      <link rel="apple-touch-icon" sizes="76x76" href="img/favicon-76.png">      <link rel="apple-touch-icon" sizes="120x120" href="img/favicon-120.png">      <link rel="apple-touch-icon" sizes="152x152" href="img/favicon-152.png">      <link rel="icon" sizes="196x196" href="img/favicon-196.png">      <link rel="icon" type="image/x-icon" href="img/favicon.ico">
      <!-- 引入样式表 -->
      <link href='css/bootstrap-4.5.2.min.css' rel='stylesheet' type='text/css' />
      <link href='css/offcanvas.css' rel='stylesheet' type='text/css' />
      <link href='css/custom.css' rel='stylesheet' type='text/css' />
  </head>
  <!-- 在无法运行JavaScript的情况下显示提示信息 -->
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <body>
    <!-- 网站导航栏 -->
    <nav id='site-header' class="navbar navbar-expand-sm bg-light navbar-light border-bottom fixed-top pt-0 pb-0 text-muted small">
        <!-- 网站标志 -->
        <!-- 网站信息和链接 -->
    </nav>
    <!-- 主体内容区域 -->
    <div class='container-full h-100 p-1'>
      <div class='row no-gutters'>
        <!-- 第一个列:选择输出文件格式 -->
        <div class='col-sm-4 p-1'>
          <!-- 卡片组件 -->
          <div class="card rounded-0">
            <div class="card-header p-1">
              <span class='symbol'>❶</span> Select media format of output file
            </div>
            <div class="card-body p-1">
              <!-- 表格组件 -->
              <table class='table table-bordered small mb-0 w-100'>
                <thead>
                  <tr>
                    <td colspan='2'>
                      <!-- 输入框和下拉列表 -->
                    </td>
                  </tr>
                </thead>
                <!-- 输出文件详细信息 -->
                      <!-- 重置按钮 -->
                      <button id='resetAllBtn' type='button' class='btn btn-sm btn-outline-danger rounded-circle navBtn float-right text-center symbol'>↺</button>
                    </td>
                  </tr>
                </tbody>
              </table>
            </div>
          </div>
        </div>
        <!-- 第二个列:上传媒体文件 -->
                      <!-- 上传文件按钮 -->
                      <button id='uploadMediaBtn' type='button' class='btn btn-sm btn-light border border-primary text-primary rounded-0'>
                        <span class='emoji'>📂</span> <small>Upload File</small><input id='uploadMedia' type='file' accept='audio/*,video/*' />
                      </button>
                </thead>
              
                <tbody>
				 <!-- 输入文件详细信息 -->
                </tbody>
                <tr>
                  <td colspan='2' valign='middle'>
                    <!-- 保存按钮 -->
                    <button id='saveOutput' type='button' class='btn btn-sm btn-outline-success rounded-circle navBtn float-right text-center symbol'>💾</button>
                    <span class='symbol float-right mr-2 text-success'>𝙴𝚡𝚙𝚘𝚛𝚝 𝙿𝚛𝚘𝚌𝚎𝚜𝚜𝚎𝚍 𝙾𝚞𝚝𝚙𝚞𝚝 ▷</span>
                  </td>
                </tr>
              </table>
            </div>
          </div>
        </div>
        <!-- 第三个列:服务器的 Cross-Origin Isolated 状态 -->
        <!-- 支持的文件格式信息 -->
             
              <!-- 媒体文件预览区域 -->
              <div id='mediaWrapper' class='text-center'></div>
            </div>
            
          </div>
        </div>
      </div>
      <!-- 底部输出日志区域 -->
      <div class='row no-gutters'>
    </div>
    <!-- 引入JavaScript文件 -->
    <script src='js/polyfill.js'></script>
    <script src='js/ie10-viewport-bug-workaround.js'></script>
    <script src='js/bootstrap-native-v4.js'></script>
    <script src="js/ffmpeg/ffmpeg.min.js"></script>
    <script src="js/mimeTypes.js"></script>
    <script src="js/custom.js"></script>
  </body>
</html>
脚本标签 作用 <script src='js/polyfill.js'></script>JavaScript特性的兼容性支持,确保在旧版本的浏览器正常运行 <script src='js/ie10-viewport-bug-workaround.js'></script>解决在 Internet Explorer 10 (IE10) 浏览器中的一些视口(viewport)相关的问题 <script src='js/bootstrap-native-v4.js'></script>引入 Bootstrap 框架的 JavaScript 部分,提供页面布局、样式和交互的基本功能。 <script src="js/ffmpeg/ffmpeg.min.js"></script>引入 FFmpeg 库 <script src="js/mimeTypes.js"></script>定义和处理不同媒体类型(MIME类型)的脚本 <script src="js/custom.js"></script>自定义的 JavaScript 代码 
 
// 检查文档是否完全加载,如果是则立即执行回调,否则等待DOMContentLoaded事件
if (document.readyState === "complete" || document.readyState !== "loading" && !document.documentElement.doScroll) {
    callback();
} else {
    // 在DOMContentLoaded事件触发时执行
    document.addEventListener('DOMContentLoaded', async () => {
        console.log('DOMContentLoaded');
        // 获取所有类名为 'card' 的元素
        const cards = document.querySelectorAll('.card');
        let maxHeight;
        // 计算所有 'card' 元素的最大高度
        for (let card of cards) {
            if (typeof maxHeight === 'undefined' || card.clientHeight > maxHeight) {
                maxHeight = card.clientHeight;
            }
        }
        // 设置所有 'card' 元素的高度和溢出样式
        for (let card of cards) {
            card['style']['height'] = `${maxHeight}px`;
            card['style']['overflow-y'] = 'auto';
        }
        // 设置 logsOutput 元素的高度
        const logsOutput = document.getElementById('logsOutput');
        logsOutput['style']['height'] = `calc(100vh - 50px - 0.25rem - 0.25rem - 0.25rem - 0.25rem - 0.25rem - ${maxHeight}px)`;
        // 显示当前年份
        const yearDisplay = document.getElementById('yearDisplay');
        yearDisplay.innerHTML = new Date().getFullYear();
        // 获取 outputLogs 元素
        const outputLogs = document.getElementById('outputLogs');
        // 获取当前日期时间的字符串表示
        function getCurrentDatetimeStamp() {
            const d = new Date();
            let datestamp = d.getFullYear() + '-' + ((d.getMonth() + 1 < 10) ? ('0' + (d.getMonth() + 1)) : (d.getMonth() + 1)) + '-' + ((d.getDate() < 10) ? ('0' + d.getDate()) : (d.getDate()));
            let timestamp = ((d.getHours() < 10) ? ('0' + d.getHours()) : (d.getHours())) + ':' + ((d.getMinutes() < 10) ? ('0' + d.getMinutes()) : (d.getMinutes())) + ':' + ((d.getSeconds() < 10) ? ('0' + d.getSeconds()) : (d.getSeconds()));
            let datetimeStr = datestamp + ' ' + timestamp;
            return datetimeStr;
        }
        // 日志类型常量
        const infoNote = 'ɪɴғᴏ ';
        const errNote = 'ᴇʀʀᴏʀ';
        // 添加数据日志到页面
        function appendDataLog(logMsg) {
            if (typeof logMsg === 'string') {
                let logType = infoNote;
                let textClass = 'text-light bg-dark';
                // 根据日志内容判断日志类型,并设置样式
                if (logMsg.toLowerCase().includes('fail')) {
                    logType = errNote;
                    textClass = 'text-light bg-danger';
                    logMsg = `${logMsg}`;
                } else if (logMsg.indexOf('[fferr] size= ') === 0 || logMsg.indexOf('[fferr] frame= ') === 0) {
                    textClass = 'text-white bg-primary'; // 重要的操作需求
                    logMsg = `${logMsg}`;
                } else if (logMsg.indexOf('[fferr]') === 0 && logMsg.includes(':') && !logMsg.toLowerCase().includes('config')) {
                    textClass = 'text-primary bg-light'; // 文件信息
                    logMsg = `${logMsg}`;
                } else if (logMsg.indexOf('[info]') === 0) {
                    textClass = 'text-dark'; // 比填充更好
                    logMsg = `${logMsg}`;
                } else if (logMsg.indexOf('[fferr]') === 0) {
                    textClass = 'text-secondary'; // 填充日志
                    logMsg = `${logMsg}`;
                } else if (logMsg.indexOf('[ffout]') === 0) {
                    textClass = 'text-white bg-success'; // 重要通知,处理结束
                    logMsg = `${logMsg}`;
                } else {
                    logMsg = `${logMsg}`;
                }
                // 插入日志到页面
                outputLogs.insertAdjacentHTML('beforeend', '<p class="mb-0 small"><span class="unicode text-dark mr-1">' + logType + '</span><span class="text-white bg-dark"><span class="symbol">【</span>' + getCurrentDatetimeStamp() + '<span class="symbol">】</span></span> <span class="' + textClass + '"> ' + logMsg.trim() + ' </span></p>');
                // 滚动到日志底部
                let scrollTopVal = outputLogs.scrollHeight - outputLogs.clientHeight;
                outputLogs.scroll(0, scrollTopVal);
            }
        }
        // 添加错误日志到页面
        function appendErrorLog(errMsg) {
            if (typeof errMsg === 'string') {
                outputLogs.insertAdjacentHTML('beforeend', '<p class="mb-0 small"><span class="unicode text-dark mr-1">' + errNote + '</span><span class="text-white bg-dark"><span class="symbol">【</span>' + getCurrentDatetimeStamp() + '<span class="symbol">】</span></span> <span class="text-light bg-danger"> ' + errMsg.trim() + ' </span></p>');
                // 滚动到日志底部
                let scrollTopVal = outputLogs.scrollHeight - outputLogs.clientHeight;
                outputLogs.scroll(0, scrollTopVal);
            }
        }
        // 重写 console.log 和 console.error,将输出信息显示在页面中的 outputLogs 元素中
        console.logs = console.log.bind(console);
        console.log = function () {
            console.logs.apply(console, arguments);
            if (Array.from(arguments).length === 1 && typeof (Array.from(arguments)[0]) === 'string') {
                appendDataLog(Array.from(arguments)[0]);
            }
        };
        console.errors = console.error.bind(console);
        console.error = function () {
            console.errors.apply(console, arguments);
            if (Array.from(arguments).length === 1 && typeof Array.from(arguments) === 'object') {
                appendErrorLog(Array.from(arguments)[0].path[0].error.message);
            }
        };
        
        const  isCrossOriginIsolated =  document. getElementById ( 'isCrossOriginIsolated' ) ; 
        if  ( crossOriginIsolated)  { 
            isCrossOriginIsolated. innerHTML =  '🟢' ;  
        }  else  { 
            isCrossOriginIsolated. innerHTML =  '🔴' ;  
        } 
        
        const  uploadMediaBtn =  document. getElementById ( 'uploadMediaBtn' ) ; 
        const  uploadMedia =  document. getElementById ( 'uploadMedia' ) ; 
        
        const  fileNameDisplay =  document. getElementById ( 'FileName' ) ; 
        const  fileTypeDisplay =  document. getElementById ( 'FileType' ) ; 
        const  fileSizeDisplay =  document. getElementById ( 'FileSize' ) ; 
        
        const  outputFileExtension =  document. getElementById ( 'outputFileExtension' ) ; 
        const  FileExtDisplay =  document. getElementById ( 'FileExt' ) ; 
        const  MimeTypeDisplay =  document. getElementById ( 'MimeType' ) ; 
        const  MimeDescriptionDisplay =  document. getElementById ( 'MimeDescription' ) ; 
        
        const  resetAllBtn =  document. getElementById ( 'resetAllBtn' ) ; 
        const  saveOutputBtn =  document. getElementById ( 'saveOutput' ) ; 
        saveOutputBtn. disabled =  true ; 
        
        function  triggerEvent ( el,  type )  { 
            let  e =  ( ( 'createEvent'  in  document)  ?  document. createEvent ( 'HTMLEvents' )  :  document. createEventObject ( ) ) ; 
            if  ( 'createEvent'  in  document)  { 
                e. initEvent ( type,  false ,  true ) ; 
                el. dispatchEvent ( e) ; 
            }  else  { 
                e. eventType =  type; 
                el. fireEvent ( 'on'  +  e. eventType,  e) ; 
            } 
        } 
        
        const  convertBitArrtoB64  =  ( bitArr )  =>  ( btoa ( bitArr. reduce ( ( data,  byte )  =>  data +  String. fromCharCode ( byte) ,  '' ) ) ) ; 
        
        function  readFileAsArrayBuffer ( file )  { 
            return  new  Promise ( ( resolve,  reject )  =>  { 
                let  fileredr =  new  FileReader ( ) ; 
                fileredr. onload  =  ( )  =>  resolve ( fileredr. result) ; 
                fileredr. onerror  =  ( )  =>  reject ( fileredr) ; 
                fileredr. readAsArrayBuffer ( file) ; 
            } ) ; 
        } 
        let  isSelected =  false ; 
        let  counter =  0 ; 
        
        for  ( let  mimeTypeObj of  mimeTypes)  { 
            let  fileExt =  mimeTypeObj[ 'Extension' ] ; 
            let  fileDescription =  mimeTypeObj[ 'Description' ] ; 
            let  fileMimeType =  mimeTypeObj[ 'MIME_Types' ] [ 0 ] ; 
            let  conversionWorks =  mimeTypeObj[ 'Works' ] ; 
            let  oOption =  document. createElement ( 'option' ) ; 
            oOption. value =  fileMimeType; 
            oOption. text =  ` ${ fileDescription}  [ ${ fileExt} ] ` ; 
            if  ( ! isSelected)  { 
                oOption. setAttribute ( 'selected' ,  true ) ; 
                MimeTypeDisplay. innerHTML =  fileMimeType; 
                FileExtDisplay. innerHTML =  fileExt; 
                MimeDescriptionDisplay. innerHTML =  fileDescription; 
                isSelected =  true ; 
            } 
            outputFileExtension. add ( oOption,  counter++ ) ; 
        } 
        
        await  new  Promise ( ( resolve,  reject )  =>  setTimeout ( resolve,  50 ) ) ; 
        
        outputFileExtension. addEventListener ( 'change' ,  async  ( e )  =>  { 
            let  allOptions =  e. currentTarget. options; 
            let  optionSelectedIndex =  e. currentTarget. selectedIndex; 
            let  mimeType =  allOptions[ optionSelectedIndex] . value; 
            let  fileExtStr =  ( ( e. currentTarget. options[ optionSelectedIndex] . textContent) . split ( '[' ) [ 1 ] ) ; 
            fileExtStr =  fileExtStr. replaceAll ( ']' ,  '' ) ; 
            let  mimeDescriptionStr =  ( ( e. currentTarget. options[ optionSelectedIndex] . textContent) . split ( '[' ) [ 0 ] ) ; 
            mimeDescriptionStr =  mimeDescriptionStr. trim ( ) ; 
            MimeTypeDisplay. innerHTML =  mimeType; 
            FileExtDisplay. innerHTML =  fileExtStr; 
            MimeDescriptionDisplay. innerHTML =  mimeDescriptionStr; 
        } ) ; 
        
        const  HTML5MediaTypes =  { 
            '.mp4' :  true , 
            '.mp3' :  true , 
            '.wav' :  true , 
            '.ogg' :  true 
        } ; 
        const  mediaWrapper =  document. getElementById ( 'mediaWrapper' ) ; 
        const  displayedHeightVal =  150 ; 
        
        const  loadMedia  =  ( url,  type )  =>  new  Promise ( ( resolve,  reject )  =>  { 
            var  mediaObj =  document. createElement ( type) ; 
            mediaObj. addEventListener ( 'canplay' ,  ( )  =>  resolve ( mediaObj) ) ; 
            mediaObj. addEventListener ( 'error' ,  ( err )  =>  reject ( err) ) ; 
            mediaObj. src =  url; 
        } ) ; 
        
        async  function  renderProcessedOutput ( encodedData,  mediaType,  outputFileExt )  { 
            if  ( typeof  HTML5MediaTypes[ outputFileExt. toLowerCase ( ) ]  !==  'undefined' )  { 
                try  { 
                    let  loadedMediaObj =  await  loadMedia ( encodedData,  mediaType) ; 
                    loadedMediaObj. setAttribute ( 'controls' ,  '' ) ; 
                    await  new  Promise ( ( resolve,  reject )  =>  setTimeout ( resolve,  50 ) ) ; 
                    if  ( mediaType ==  'video' )  { 
                        let  mediaObjHeight =  loadedMediaObj. videoHeight; 
                        let  mediaObjWidth =  loadedMediaObj. videoWidth; 
                        let  scaleRatio =  parseFloat ( displayedHeightVal /  mediaObjHeight) ; 
                        let  displayedHeight =  scaleRatio *  mediaObjHeight; 
                        let  displayedWidth =  scaleRatio *  mediaObjWidth; 
                        loadedMediaObj[ 'style' ] [ 'height' ]  =  ` ${ displayedHeight} px ` ; 
                        loadedMediaObj[ 'style' ] [ 'width' ]  =  ` ${ displayedWidth} px ` ; 
                        loadedMediaObj[ 'style' ] [ 'margin' ]  =  '0 auto' ; 
                        await  new  Promise ( ( resolve,  reject )  =>  setTimeout ( resolve,  50 ) ) ; 
                    } 
                    mediaWrapper. appendChild ( loadedMediaObj) ; 
                }  catch  ( errMsg)  { 
                    console. error ( errMsg) ; 
                } 
            }  else  { 
                let  fillerDIV =  document. createElement ( 'div' ) ; 
                fillerDIV. className =  'border' ; 
                fillerDIV[ 'style' ] [ 'height' ]  =  ` ${ displayedHeightVal} px ` ; 
                fillerDIV[ 'style' ] [ 'width' ]  =  ` ${ displayedHeightVal} px ` ; 
                fillerDIV[ 'style' ] [ 'margin' ]  =  '0 auto' ; 
                fillerDIV. innerHTML =  'Content is not HTML5 compatible for display.' ; 
                mediaWrapper. appendChild ( fillerDIV) ; 
            } 
            return  Promise. resolve ( 'Conversion Success!' ) ; 
        } 
        
        uploadMedia. addEventListener ( 'change' ,  async  ( evt )  =>  { 
            outputFileExtension. disabled =  true ; 
            uploadMediaBtn. disabled =  true ; 
            const  outputFileMimeType =  MimeTypeDisplay. innerHTML; 
            const  outputFileExt =  FileExtDisplay. innerHTML; 
            const  file =  evt. target. files[ 0 ] ; 
            if  ( ! file)  return ; 
            let  fileName =  file. name; 
            let  fileType =  file. type; 
            let  fileSizeInKB =  parseInt ( file. size /  1024 ) ; 
            let  fileSizeInMB =  ( ( file. size /  1024 )  /  1024 ) . toFixed ( 2 ) ; 
            fileNameDisplay. innerHTML =  fileName; 
            fileTypeDisplay. innerHTML =  fileType; 
            fileSizeDisplay. innerHTML =  ` ${ fileSizeInKB}  <strong class="symbol">𝚔𝙱</strong> <span class="symbol">≈</span>  ${ fileSizeInMB}  <strong class="symbol">𝙼𝙱</strong> ` ; 
			
            appendDataLog ( 'Initialising FFmpeg.' ) ; 
            const  ffmpeg =  FFmpeg. createFFmpeg ( { 
                corePath :  new  URL ( 'js/ffmpeg/ffmpeg-core.js' ,  document. location) . href, 
                workerPath :  new  URL ( 'js/ffmpeg/ffmpeg-core.worker.js' ,  document. location) . href, 
                wasmPath :  new  URL ( 'js/ffmpeg/ffmpeg-core.wasm' ,  document. location) . href, 
                log :  true 
            } ) ; 
            await  ffmpeg. load ( ) ; 
            appendDataLog ( 'FFmpeg has loaded.' ) ; 
			
            appendDataLog ( 'Reading input file.' ) ; 
            let  arrBuffer =  await  readFileAsArrayBuffer ( file) ; 
            let  uInt8Array =  new  Uint8Array ( arrBuffer) ; 
            appendDataLog ( 'Writing to input file.' ) ; 
            ffmpeg. FS ( 'writeFile' ,  fileName,  uInt8Array) ; 
            appendDataLog ( 'Transcoding input file to output file.' ) ; 
            await  ffmpeg. run ( '-i' ,  fileName,  ` output ${ outputFileExt} ` ) ; 
            appendDataLog ( 'Retrieving output file from virtual files system.' ) ; 
            const  data =  ffmpeg. FS ( 'readFile' ,  ` output ${ outputFileExt} ` ) ;  
            let  b64Str =  convertBitArrtoB64 ( data) ; 
            let  encodedData =  ` data: ${ outputFileMimeType} ;base64, ${ b64Str} ` ; 
            appendDataLog ( 'File conversion has been successfully completed.' ) ; 
            saveOutputBtn. disabled =  false ; 
            saveOutputBtn. value =  encodedData; 
            let  mediaType =  'audio' ; 
            if  ( ! outputFileMimeType. includes ( mediaType) )  { 
                mediaType =  'video' ; 
            } 
            let  status =  await  renderProcessedOutput ( encodedData,  mediaType,  outputFileExt) ; 
            appendDataLog ( status) ; 
            ffmpeg. FS ( 'unlink' ,  ` output ${ outputFileExt} ` ) ; 
            await  new  Promise ( ( resolve,  reject )  =>  setTimeout ( resolve,  50 ) ) ; 
            ffmpeg. exit ( ) ; 
        } ) ; 
        
        saveOutputBtn. addEventListener ( 'click' ,  async  ( )  =>  { 
            let  dwnlnk =  document. createElement ( 'a' ) ; 
            let  fileName =  fileNameDisplay. innerHTML; 
            let  outputFileExt =  FileExtDisplay. innerHTML; 
            let  saveFilename =  fileName. substr ( 0 ,  fileName. lastIndexOf ( '.' ) ) ; 
            dwnlnk. download =  ` ${ saveFilename} ${ outputFileExt} ` ; 
            dwnlnk. href =  saveOutputBtn. value; 
            dwnlnk. click ( ) ; 
        } ) ; 
        
        function  resetAll ( )  { 
            if  ( mediaWrapper. children. length >  0 )  { 
                mediaWrapper. removeChild ( mediaWrapper. children[ 0 ] ) ; 
            } 
            outputFileExtension. disabled =  false ; 
            outputFileExtension. selectedIndex =  0 ; 
            triggerEvent ( outputFileExtension,  'change' ) ; 
            uploadMediaBtn. disabled =  false ; 
            uploadMedia. value =  '' ; 
            fileNameDisplay. innerHTML =  '<span class="symbol">…</span>' ; 
            fileTypeDisplay. innerHTML =  '<span class="symbol">…</span>' ; 
            fileSizeDisplay. innerHTML =  '<span class="symbol">…</span>' ; 
            outputLogs. innerHTML =  '' ; 
            saveOutputBtn. value =  '' ; 
            saveOutputBtn. disabled =  true ; 
        } 
        
        resetAllBtn. addEventListener ( 'click' ,  async  ( )  =>  { 
            resetAll ( ) ; 
        } ) ; 
    } ) ; 
}