成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

120 行代碼實現(xiàn)純 Web 剪輯視頻

開發(fā) 前端
WebAssembly(wasm)就是一個可移植、體積小、加載快并且兼容 Web 的全新格式。可以將 C,C++等語言編寫的模塊通過編譯器來創(chuàng)建 wasm 格式的文件,此模塊通過二進制的方式發(fā)給瀏覽器,然后 js 可以通過 wasm 調用其中的方法功能。

[[422785]]

本文轉載自微信公眾號「微醫(yī)大前端技術」,作者翁佳瑞 。轉載本文請聯(lián)系微醫(yī)大前端技術公眾號。

前言

前幾天偶爾看到一篇 webassembly 的相關文章,對這個技術還是挺感興趣的,在了解一些相關知識的基礎上,看下自己能否小小的實踐下。

什么是 webasembly?

WebAssembly(wasm)就是一個可移植、體積小、加載快并且兼容 Web 的全新格式。可以將 C,C++等語言編寫的模塊通過編譯器來創(chuàng)建 wasm 格式的文件,此模塊通過二進制的方式發(fā)給瀏覽器,然后 js 可以通過 wasm 調用其中的方法功能。

WebAssembly 的優(yōu)勢

網(wǎng)上對于這個相關的介紹應該有很多了,WebAssembly 優(yōu)勢性能好,運行速度遠高于 Js,對于需要高計算量、對性能要求高的應用場景如圖像/視頻解碼、圖像處理、3D/WebVR/AR 等,優(yōu)勢非常明顯,們可以將現(xiàn)有的用 C、C++等語言編寫的庫直接編譯成 WebAssembly 運行到瀏覽器上,并且可以作為庫被 JavaScript 引用。那就意味著我們可以將很多后端的工作轉移到前端,減輕服務器的壓力。

WebAssembly 最簡單的實踐調用

我們編寫一個最簡單的 c 文件

  1. int add(int a,int b) { 
  2.   return a + b; 

然后安裝對于的 Emscripten 編譯器Emscripten 安裝指南

  1. emcc test.c -Os -s WASM=1 -s SIDE_MODULE=1 -o test.wasm 

然后我們在 html 中引入使用即可

  1. fetch('./test.wasm').then(response => 
  2.   response.arrayBuffer() 
  3. ).then(bytes => 
  4.   WebAssembly.instantiate(bytes) 
  5. ).then(results => { 
  6.   const add = results.instance.exports.add 
  7.   console.log(add(11,33)) 
  8. }); 

這時我們即可在控制臺看到對應的打印日志,成功調用我們編譯的代碼啦

正式開動

既然我們已經知道如何能快速的調用到一些已經成熟的 C,C++的類庫,那我們離在線剪輯視頻預期目標更進一步了。

最終 demo 演示

由于錄制操作的電腦 cpu 不太行,所以可能耗時比較久,但整體的效果還是能看的到滴

demo 倉庫地址(https://github.com/Dseekers/clip-video-by-webassembly)

FFmpeg

在這個之前你得稍微的了解下啥是 FFmpeg? 以下根據(jù)維基百科的目錄解釋:

FFmpeg 是一個開放源代碼的自由軟件,可以運行音頻和視頻多種格式的錄影、轉換、流功能[1],包含了 libavcodec——這是一個用于多個項目中音頻和視頻的解碼器庫,以及 libavformat——一個音頻與視頻格式轉換庫。

簡單的說這個就是由 C 語言編寫的視頻處理軟件,它的用法也是相當?shù)魏唵?/p>

我主要將這次需要用到的命令給調了出來,如果你還可能用到別的命令,可以根據(jù)他的官方文檔查看 ,還可以了解下阮一峰大佬的文章 (https://www.ruanyifeng.com/blog/2020/01/ffmpeg.html)

  1. ffmpeg -ss [start] -i [input] -to [end] -c copy [output

start 為開始時間 end 為結束時間 input 為需要操作的視頻源文件 output 為輸出文件的位置名稱

這一行代碼就是我們需要用到的剪輯視頻的命令了

獲取相關的FFmpeg的wasm

由于通過 Emscripten 編譯 ffmpeg 成 wasm 存在較多的環(huán)境問題,所以我們這次直接使用在線已經編譯好的 CDN 資源

這邊就直接使用了這個比較成熟的庫 https://github.com/ffmpegwasm/ffmpeg.wasm

為了本地調試方便,我把其相關的資源都下了下來 一共 4 個資源文件

  1. ffmpeg.min.js 
  2. ffmpeg-core.js 
  3. ffmpeg-core.wasm 
  4. ffmpeg-core.worker.js 

我們使用的時候只需引入第一個文件即可,其它文件會在調用時通過 fetch 方式去拉取資源

最小的功能實現(xiàn)

前置功能實現(xiàn): 在我們本地需要實現(xiàn)一個 node 服務,因為使用 ffmpeg 這個模塊會出現(xiàn)如果沒在服務器端設置響應頭, 會報錯 SharedArrayBuffer is not defined,這個是因為系統(tǒng)的安全漏洞,瀏覽器默認禁用了該 api,若要啟用則需要在 header 頭上設置

  1. Cross-Origin-Opener-Policy: same-origin 
  2. Cross-Origin-Embedder-Policy: require-corp 

我們啟動一個簡易的 node 服務

  1. const Koa = require('koa'); 
  2. const path = require('path'
  3. const fs = require('fs'
  4. const router = require('koa-router')(); 
  5. const static = require('koa-static'
  6. const staticPath = './static' 
  7. const app = new Koa(); 
  8. app.use(static
  9.     path.join(__dirname, staticPath) 
  10. )) 
  11. // log request URL: 
  12. app.use(async (ctx, next) => { 
  13.     console.log(`Process ${ctx.request.method} ${ctx.request.url}...`); 
  14.     ctx.set('Cross-Origin-Opener-Policy''same-origin'
  15.     ctx.set('Cross-Origin-Embedder-Policy''require-corp'
  16.     await next(); 
  17. }); 
  18.  
  19. router.get('/', async (ctx, next) => { 
  20.     ctx.response.body = '<h1>Index</h1>'
  21. }); 
  22. router.get('/:filename', async (ctx, next) => { 
  23.     console.log(ctx.request.url) 
  24.     const filePath = path.join(__dirname, ctx.request.url); 
  25.     console.log(filePath) 
  26.     const htmlContent = fs.readFileSync(filePath); 
  27.     ctx.type = "html"
  28.     ctx.body = htmlContent; 
  29. }); 
  30. app.use(router.routes()); 
  31. app.listen(3000); 
  32. console.log('app started at port 3000...'); 

我們做一個最小化的 demo 來實現(xiàn)下這個剪輯功能,剪輯視頻的前一秒鐘 新建一個 demo.html 文件,引入相關資源

  1. <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script> 
  2. <script src="./assets/ffmpeg.min.js"></script> 
  3.  
  4. <div class="container"
  5.   <div class="operate"
  6.     選擇原始視頻文件: 
  7.     <input type="file" id="select_origin_file"
  8.     <button id="start_clip">開始剪輯視頻</button> 
  9.   </div> 
  10.   <div class="video-container"
  11.     <div class="label">原視頻</div> 
  12.     <video class="my-video" id="origin-video" controls></video> 
  13.   </div> 
  14.   <div class="video-container"
  15.     <div class="label">處理后的視頻</div> 
  16.     <video class="my-video" id="handle-video" controls></video> 
  17.   </div> 
  18. </div> 
  1. let originFile 
  2. $(document).ready(function () { 
  3.   $('#select_origin_file').on('change', (e) => { 
  4.     const file = e.target.files[0] 
  5.     originFile = file 
  6.     const url = window.webkitURL.createObjectURL(file) 
  7.     $('#origin-video').attr('src', url) 
  8.   }) 
  9.   $('#start_clip').on('click', async function () { 
  10.     const { fetchFile, createFFmpeg } = FFmpeg; 
  11.     ffmpeg = createFFmpeg({ 
  12.       log: true
  13.       corePath: './assets/ffmpeg-core.js'
  14.     }); 
  15.     const file = originFile 
  16.     const { name } = file; 
  17.     if (!ffmpeg.isLoaded()) { 
  18.       await ffmpeg.load(); 
  19.     } 
  20.     ffmpeg.FS('writeFile'name, await fetchFile(file)); 
  21.     await ffmpeg.run('-i'name'-ss''00:00:00''-to''00:00:01''output.mp4'); 
  22.     const data = ffmpeg.FS('readFile''output.mp4'); 
  23.     const tempURL = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' })); 
  24.     $('#handle-video').attr('src', tempURL) 
  25.   }) 
  26. }); 

其代碼的含義也是相當簡單,通過引入的 FFmpeg 去創(chuàng)建一個實例,然后通過 ffmpeg.load()方法去加載對應的 wasm 和 worker 資源 沒有進行優(yōu)化的 wasm 的資源是相當?shù)未螅镜匚募褂?23MB,這個若是需要投入生產的可是必須通過 emcc 調節(jié)打包參數(shù)的方式去掉無用模塊。然后通 fetchFile 方法將選中的 input file 加載到內存中去,接下來就可以通過 ffmpeg.run 運行和 本地命令行一樣的 ffmpeg 命令行參數(shù)了參數(shù)基本一致。

這時我們的核心功能已經實現(xiàn)完畢了。

做一點小小的優(yōu)化

剪輯的話最好是可以選擇時間段,我這為了方便直接把 element 的以 cdn 方式引入使用 通過 slider 來截取視頻區(qū)間,我這邊就只貼 js 相關的代碼了,具體代碼可以去 github 倉庫里面仔細看下:

  1. class ClipVideo { 
  2.     constructor() { 
  3.         this.ffmpeg = null 
  4.         this.originFile = null 
  5.         this.handleFile = null 
  6.         this.vueInstance = null 
  7.         this.currentSliderValue = [0, 0] 
  8.         this.init() 
  9.     } 
  10.     init() { 
  11.         console.log('init'
  12.         this.initFfmpeg() 
  13.         this.bindSelectOriginFile() 
  14.         this.bindOriginVideoLoad() 
  15.         this.bindClipBtn() 
  16.         this.initVueSlider() 
  17.     } 
  18.     initVueSlider(maxSliderValue = 100) { 
  19.         console.log(`maxSliderValue ${maxSliderValue}`) 
  20.         if (!this.vueInstance) { 
  21.             const _this = this 
  22.             const Main = { 
  23.                 data() { 
  24.                     return { 
  25.                         value: [0, 0], 
  26.                         maxSliderValue: maxSliderValue 
  27.                     } 
  28.                 }, 
  29.                 watch: { 
  30.                     value() { 
  31.                         _this.currentSliderValue = this.value 
  32.                     } 
  33.                 }, 
  34.                 methods: { 
  35.                     formatTooltip(val) { 
  36.                         return _this.transformSecondToVideoFormat(val); 
  37.                     } 
  38.                 } 
  39.             } 
  40.             const Ctor = Vue.extend(Main) 
  41.             this.vueInstance = new Ctor().$mount('#app'
  42.         } else { 
  43.             this.vueInstance.maxSliderValue = maxSliderValue 
  44.             this.vueInstance.value = [0, 0] 
  45.         } 
  46.     } 
  47.     transformSecondToVideoFormat(value = 0) { 
  48.         const totalSecond = Number(value) 
  49.         let hours = Math.floor(totalSecond / (60 * 60)) 
  50.         let minutes = Math.floor(totalSecond / 60) % 60 
  51.         let second = totalSecond % 60 
  52.         let hoursText = '' 
  53.         let minutesText = '' 
  54.         let secondText = '' 
  55.         if (hours < 10) { 
  56.             hoursText = `0${hours}` 
  57.         } else { 
  58.             hoursText = `${hours}` 
  59.         } 
  60.         if (minutes < 10) { 
  61.             minutesText = `0${minutes}` 
  62.         } else { 
  63.             minutesText = `${minutes}` 
  64.         } 
  65.         if (second < 10) { 
  66.             secondText = `0${second}` 
  67.         } else { 
  68.             secondText = `${second}` 
  69.         } 
  70.         return `${hoursText}:${minutesText}:${secondText}` 
  71.     } 
  72.     initFfmpeg() { 
  73.         const { createFFmpeg } = FFmpeg; 
  74.         this.ffmpeg = createFFmpeg({ 
  75.             log: true
  76.             corePath: './assets/ffmpeg-core.js'
  77.         }); 
  78.     } 
  79.     bindSelectOriginFile() { 
  80.         $('#select_origin_file').on('change', (e) => { 
  81.             const file = e.target.files[0] 
  82.             this.originFile = file 
  83.             const url = window.webkitURL.createObjectURL(file) 
  84.             $('#origin-video').attr('src', url) 
  85.  
  86.         }) 
  87.     } 
  88.     bindOriginVideoLoad() { 
  89.         $('#origin-video').on('loadedmetadata', (e) => { 
  90.             const duration = Math.floor(e.target.duration) 
  91.             this.initVueSlider(duration) 
  92.         }) 
  93.     } 
  94.     bindClipBtn() { 
  95.         $('#start_clip').on('click', () => { 
  96.             console.log('start clip'
  97.             this.clipFile(this.originFile) 
  98.         }) 
  99.     } 
  100.     async clipFile(file) { 
  101.         const { ffmpeg, currentSliderValue } = this 
  102.         const { fetchFile } = FFmpeg; 
  103.         const { name } = file; 
  104.         const startTime = this.transformSecondToVideoFormat(currentSliderValue[0]) 
  105.         const endTime = this.transformSecondToVideoFormat(currentSliderValue[1]) 
  106.         console.log('clipRange', startTime, endTime) 
  107.         if (!ffmpeg.isLoaded()) { 
  108.             await ffmpeg.load(); 
  109.         } 
  110.         ffmpeg.FS('writeFile'name, await fetchFile(file)); 
  111.         await ffmpeg.run('-i'name'-ss', startTime, '-to', endTime, 'output.mp4'); 
  112.         const data = ffmpeg.FS('readFile''output.mp4'); 
  113.         const tempURL = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' })); 
  114.         $('#handle-video').attr('src', tempURL) 
  115.     } 
  116. $(document).ready(function () { 
  117.     const instance = new ClipVideo() 
  118. }); 

這樣文章開頭的效果就這樣實現(xiàn)啦

小結

webassbembly 還是比較新的一項技術,我這邊只是應用了其中一小部分功能,值得我們探索的地方還有很多,歡迎大家多多交流哈

參考資料

WebAssembly 完全入門——了解 wasm 的前世今生

(https://juejin.cn/post/6844903709806182413)

使用 FFmpeg 與 WebAssembly 實現(xiàn)純前端視頻截幀 (https://toutiao.io/posts/7as4kva/preview) 

前端視頻幀提取 ffmpeg + Webassembly (https://juejin.cn/post/6854573219454844935)

 

責任編輯:武曉燕 來源: 微醫(yī)大前端技術
相關推薦

2021-06-15 07:20:47

Webpack 機制HMR

2019-06-05 15:00:28

Java代碼區(qū)塊鏈

2017-02-24 12:00:35

iOS代碼AutoLayout

2020-02-19 15:02:23

代碼開發(fā)工具

2021-07-20 09:45:58

PythonEV短視頻

2022-01-21 09:31:37

PythonLinux視頻

2019-12-23 09:27:43

Python短視頻視頻

2019-11-18 10:14:19

AI 數(shù)據(jù)人工智能

2020-12-17 08:06:33

CSS 日歷界面

2018-01-23 09:17:22

Python人臉識別

2022-03-26 22:28:06

加密通信Python

2022-04-09 09:11:33

Python

2018-11-09 15:47:07

剪輯工具

2018-06-06 16:17:41

視頻剪輯

2020-03-04 09:35:55

開源技術 軟件

2020-08-19 10:30:25

代碼Python多線程

2021-09-09 05:57:57

JS模塊打包器前端

2022-02-08 12:30:30

React事件系統(tǒng)React事件系統(tǒng)

2014-06-19 10:02:32

Haskell代碼

2022-04-15 08:07:21

ReactDiff算法
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人免费在线观看 | 黑人巨大精品欧美一区二区一视频 | 中文字幕av一区 | 国产精品一区二区久久 | 美女视频h| 欧美在线网站 | 亚洲成人自拍网 | 亚洲免费成人 | 一区二区视频在线观看 | 日韩在线精品 | 精品1区| 成人高潮片免费视频欧美 | 免费一区 | 成人网在线观看 | 久久网一区二区三区 | 成人免费大片黄在线播放 | 免费视频一区 | 国产午夜精品久久久久免费视高清 | www.久久久久久久久久久 | 国产美女一区二区 | 日韩精品一区二区三区中文在线 | 精品视频一区二区三区 | 黄色一级视频免费 | 日日操视频 | 成人国产精品色哟哟 | h视频在线免费 | 国产成人网| 欧美中文在线 | 日韩国产中文字幕 | 国产精品久久久久久久久婷婷 | 亚洲精品视频免费观看 | 又黄又色 | 精品一区二区久久久久久久网站 | 亚洲精品自在在线观看 | 国内精品视频 | 中文在线一区二区 | 女同久久另类99精品国产 | 亚洲第一视频网站 | 99reav| 国产精品视频久久 | 国产成人99久久亚洲综合精品 |