华为官方网站进入,海口网站建设公司哪家好,天津关键词,QQ点钓鱼网站后怎么做项目介绍
https://github.com/incubated-geek-cc/FFmpegWasmLocalServer.git可将音频或视频文件转换为其他可选的多媒体格式#xff0c;并导出转码的结果
$ bash run.sh
FFmpeg App is listening on port 3000!运行效果 相关依赖
Error: Cannot find module ‘express’
…项目介绍
https://github.com/incubated-geek-cc/FFmpegWasmLocalServer.git可将音频或视频文件转换为其他可选的多媒体格式并导出转码的结果
$ bash run.sh
FFmpeg App is listening on port 3000!运行效果 相关依赖
Error: Cannot find module ‘express’
npm install express
$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: FFmpegWasmLocalServer1.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 consolidate0.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 changelogadded 70 packages, and audited 71 packages in 2m7 packages are looking for fundingrun npm fund for detailsfound 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) {// 设置响应头启用 SharedBufferres.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服务:
非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;// 1. 使用 Lets Encrypt 获取 SSL/TLS 证书并将证书文件放置在项目中
// or just openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt
const options {key: fs.readFileSync(path/to/server.key),cert: fs.readFileSync(path/to/server.crt),
};// 2. 配置 Express 应用程序以使用 HTTPS
const server https.createServer(options, app);// 3. 添加 HTTP 到 HTTPS 的重定向中间件
app.use(function (req, res, next) {if (!req.secure) {return res.redirect(https:// req.headers.host req.url);}next();
});// 4. 添加其他中间件和路由
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));// 启动 Express 应用程序
server.listen(PORT, () {console.log(FFmpeg App is listening on port ${PORT} with HTTPS!);
});
index.html
html langen classnotranslate translatenohead!-- 设置页面元数据 --meta namegoogle contentnotranslate / meta charsetUTF-8 meta namedescription contentAn Offline Multimedia File Conversion Tool. meta namekeywords contentffmpeg,wasm API,audio-conversion meta nameauthor contentCharmaine Chui / meta http-equivX-UA-Compatible contentIEedge,chrome1 meta nameviewport contentwidthdevice-width, user-scalableno, initial-scale1.0, maximum-scale1.0, minimum-scale1.0 / meta http-equivContent-Language contenten /titleMedia Transcoder | Built With FFmpeg for Audio Video Files/titlemeta namemsapplication-TileColor content#ffffff / meta nametheme-color content#ffffff / meta nameapple-mobile-web-app-status-bar-style contentblack-translucent / meta nameapple-mobile-web-app-capable contentyes / meta namemobile-web-app-capable contentyes / meta nameHandheldFriendly contentTrue / meta nameMobileOptimized content320 /!-- 设置网站图标 --link relapple-touch-icon sizes76x76 hrefimg/favicon-76.png link relapple-touch-icon sizes120x120 hrefimg/favicon-120.png link relapple-touch-icon sizes152x152 hrefimg/favicon-152.png link relicon sizes196x196 hrefimg/favicon-196.png link relicon typeimage/x-icon hrefimg/favicon.ico!-- 引入样式表 --link hrefcss/bootstrap-4.5.2.min.css relstylesheet typetext/css /link hrefcss/offcanvas.css relstylesheet typetext/css /link hrefcss/custom.css relstylesheet typetext/css //head!-- 在无法运行JavaScript的情况下显示提示信息 --noscriptYou need to enable JavaScript to run this app./noscriptbody!-- 网站导航栏 --nav idsite-header classnavbar navbar-expand-sm bg-light navbar-light border-bottom fixed-top pt-0 pb-0 text-muted small!-- 网站标志 --!-- 网站信息和链接 --/nav!-- 主体内容区域 --div classcontainer-full h-100 p-1div classrow no-gutters!-- 第一个列选择输出文件格式 --div classcol-sm-4 p-1!-- 卡片组件 --div classcard rounded-0div classcard-header p-1span classsymbol❶/span Select media format of output file/divdiv classcard-body p-1!-- 表格组件 --table classtable table-bordered small mb-0 w-100theadtrtd colspan2!-- 输入框和下拉列表 --/td/tr/thead!-- 输出文件详细信息 --!-- 重置按钮 --button idresetAllBtn typebutton classbtn btn-sm btn-outline-danger rounded-circle navBtn float-right text-center symbol↺/button/td/tr/tbody/table/div/div/div!-- 第二个列上传媒体文件 --!-- 上传文件按钮 --button iduploadMediaBtn typebutton classbtn btn-sm btn-light border border-primary text-primary rounded-0span classemoji/span smallUpload File/smallinput iduploadMedia typefile acceptaudio/*,video/* //button/theadtbody!-- 输入文件详细信息 --/tbodytrtd colspan2 valignmiddle!-- 保存按钮 --button idsaveOutput typebutton classbtn btn-sm btn-outline-success rounded-circle navBtn float-right text-center symbol/buttonspan classsymbol float-right mr-2 text-success ▷/span/td/tr/table/div/div/div!-- 第三个列服务器的 Cross-Origin Isolated 状态 --!-- 支持的文件格式信息 --!-- 媒体文件预览区域 --div idmediaWrapper classtext-center/div/div/div/div/div!-- 底部输出日志区域 --div classrow no-gutters/div!-- 引入JavaScript文件 --script srcjs/polyfill.js/scriptscript srcjs/ie10-viewport-bug-workaround.js/scriptscript srcjs/bootstrap-native-v4.js/scriptscript srcjs/ffmpeg/ffmpeg.min.js/scriptscript srcjs/mimeTypes.js/scriptscript srcjs/custom.js/script/body
/html
脚本标签作用script srcjs/polyfill.js/scriptJavaScript特性的兼容性支持确保在旧版本的浏览器正常运行script srcjs/ie10-viewport-bug-workaround.js/script解决在 Internet Explorer 10 (IE10) 浏览器中的一些视口viewport相关的问题script srcjs/bootstrap-native-v4.js/script引入 Bootstrap 框架的 JavaScript 部分提供页面布局、样式和交互的基本功能。script srcjs/ffmpeg/ffmpeg.min.js/script引入 FFmpeg 库script srcjs/mimeTypes.js/script定义和处理不同媒体类型MIME类型的脚本script srcjs/custom.js/script自定义的 JavaScript 代码
custom.js
// 检查文档是否完全加载如果是则立即执行回调否则等待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 classmb-0 smallspan classunicode text-dark mr-1 logType /spanspan classtext-white bg-darkspan classsymbol【/span getCurrentDatetimeStamp() span classsymbol】/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 classmb-0 smallspan classunicode text-dark mr-1 errNote /spanspan classtext-white bg-darkspan classsymbol【/span getCurrentDatetimeStamp() span classsymbol】/span/span span classtext-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);}}// Uint8Array 转为 Base64上传文件时调用const convertBitArrtoB64 (bitArr) (btoa(bitArr.reduce((data, byte) data String.fromCharCode(byte), )));// 读取文件为 Array Buffer上传文件时调用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;});// HTML5 兼容的媒体类型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 classsymbol/strong span classsymbol≈/span ${fileSizeInMB} strong classsymbol/strong;// 使用指定路径创建 FFmpeg 实例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.);// 将文件读取为数组缓冲区然后将数组缓冲区转换为 Uint8ArrayappendDataLog(Reading input file.);let arrBuffer await readFileAsArrayBuffer(file);let uInt8Array new Uint8Array(arrBuffer);appendDataLog(Writing to input file.);ffmpeg.FS(writeFile, fileName, uInt8Array);// https://emscripten.org/docs/api_reference/Filesystem-API.html // https://segmentfault.com/a/1190000039308144 // ffmpeg.FS(writeFile, input.avi, new Uint8Array(sourceBuffer, 0, sourceBuffer.byteLength));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}); // Uint8Array 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 classsymbol…/span;fileTypeDisplay.innerHTML span classsymbol…/span;fileSizeDisplay.innerHTML span classsymbol…/span;outputLogs.innerHTML ;saveOutputBtn.value ;saveOutputBtn.disabled true;}// 重置所有按钮点击事件绑定resetAllBtn.addEventListener(click, async () {resetAll();});});
}