国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

120 行代碼實現純 Web 剪輯視頻

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

[[422785]]

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

前言

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

什么是 webasembly?

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

WebAssembly 的優勢

網上對于這個相關的介紹應該有很多了,WebAssembly 優勢性能好,運行速度遠高于 Js,對于需要高計算量、對性能要求高的應用場景如圖像/視頻解碼、圖像處理、3D/WebVR/AR 等,優勢非常明顯,們可以將現有的用 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? 以下根據維基百科的目錄解釋:

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

簡單的說這個就是由 C 語言編寫的視頻處理軟件,它的用法也是相當滴簡單

我主要將這次需要用到的命令給調了出來,如果你還可能用到別的命令,可以根據他的官方文檔查看 ,還可以了解下阮一峰大佬的文章 (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 存在較多的環境問題,所以我們這次直接使用在線已經編譯好的 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 方式去拉取資源

最小的功能實現

前置功能實現: 在我們本地需要實現一個 node 服務,因為使用 ffmpeg 這個模塊會出現如果沒在服務器端設置響應頭, 會報錯 SharedArrayBuffer is not defined,這個是因為系統的安全漏洞,瀏覽器默認禁用了該 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 來實現下這個剪輯功能,剪輯視頻的前一秒鐘 新建一個 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 去創建一個實例,然后通過 ffmpeg.load()方法去加載對應的 wasm 和 worker 資源 沒有進行優化的 wasm 的資源是相當滴大,本地文件竟有 23MB,這個若是需要投入生產的可是必須通過 emcc 調節打包參數的方式去掉無用模塊。然后通 fetchFile 方法將選中的 input file 加載到內存中去,接下來就可以通過 ffmpeg.run 運行和 本地命令行一樣的 ffmpeg 命令行參數了參數基本一致。

這時我們的核心功能已經實現完畢了。

做一點小小的優化

剪輯的話最好是可以選擇時間段,我這為了方便直接把 element 的以 cdn 方式引入使用 通過 slider 來截取視頻區間,我這邊就只貼 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. }); 

這樣文章開頭的效果就這樣實現啦

小結

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

參考資料

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

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

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

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

 

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

2021-06-15 07:20:47

Webpack 機制HMR

2019-06-05 15:00:28

Java代碼區塊鏈

2017-02-24 12:00:35

iOS代碼AutoLayout

2020-02-19 15:02:23

代碼開發工具

2019-11-18 10:14:19

AI 數據人工智能

2019-12-23 09:27:43

Python短視頻視頻

2021-07-20 09:45:58

PythonEV短視頻

2022-01-21 09:31:37

PythonLinux視頻

2022-04-09 09:11:33

Python

2022-03-26 22:28:06

加密通信Python

2018-01-23 09:17:22

Python人臉識別

2020-12-17 08:06:33

CSS 日歷界面

2018-11-09 15:47:07

剪輯工具

2020-03-04 09:35:55

開源技術 軟件

2018-06-06 16:17:41

視頻剪輯

2014-06-19 10:02:32

Haskell代碼

2022-04-15 08:07:21

ReactDiff算法

2021-12-16 06:21:16

React組件前端

2022-02-08 12:30:30

React事件系統React事件系統

2020-08-19 10:30:25

代碼Python多線程
點贊
收藏

51CTO技術棧公眾號

青青青免费视频在线2| 久久久久久久久久久9不雅视频| 番号集在线观看| 精品视频一区二区三区| 蜜臀精品久久久久久蜜臀| 日韩一级片在线观看| 韩日视频在线观看| 国产麻豆一精品一av一免费| 日韩美女免费视频| 暧暧视频在线免费观看| 亚洲欧美日本国产| 精品一区二区在线免费观看| 免费观看日韩电影| 一本大道久久a久久精二百| 在线播放国产一区二区三区| 欧美亚洲激情在线| 欧美va在线| 精品久久国产老人久久综合| 99亚洲国产精品| 在线观看的av网站| 黄毛片在线观看| 日韩电影网站| 成a人片亚洲日本久久| 国产精品视频永久免费播放| 国产视频一区二区三区在线播放 | 国产精品白丝jk白祙喷水网站| 人人爱人人干婷婷丁香亚洲| 久久久久高清精品| 成人精品一区二区三区电影黑人| 秋霞蜜臀av久久电影网免费| 色伦专区97中文字幕| crdy在线观看欧美| 欧美大片免费观看| 综合国产视频| 国产a∨精品一区二区三区不卡| 红桃视频在线观看一区二区| 欧美一区二区三区精品电影| 麻豆精品av| 欧美一级bbbbb性bbbb喷潮片| 久久精品色综合| 国产成人拍精品视频午夜网站| 岛国精品一区| 555www成人网| 国产欧美日韩影院| 成人免费淫片aa视频免费| 一区在线视频| 日韩一区不卡| 国产精品欧美一级免费| 亚洲精品套图| 亚洲欧美国产一本综合首页| 日韩电影免费看| 在线精品国产成人综合| 蜜乳av综合| 黄色99视频| 国产成人亚洲综合a∨婷婷图片 | 欧洲在线视频一区| 久久久久欧美精品| 国产精品亚洲a| 91久久精品一区二区三区| 欧美激情喷水| 国产精品视频xxx| 国精产品一区一区三区mba桃花| 麻豆传媒网站在线观看| 亚洲欧美日韩成人高清在线一区| 日韩一区av| 亚洲女成人图区| 一本色道久久综合亚洲精品酒店 | 日韩另类在线| 97婷婷涩涩精品一区| 少妇精品久久久| 乱子伦视频在线看| 亚洲国产第一页| 亚洲一级毛片| 亚洲高清在线免费观看| 99在线国产| 成人av午夜影院| 二区在线视频| 97久久超碰福利国产精品…| 视频在线观看91| 中文在线a√在线8| 欧美国产亚洲精品久久久8v| 日韩精品国产精品| 日本福利在线观看| 国产一区红桃视频| 亚洲精品大片www| 成人性教育视频在线观看| 免费成人你懂的| xxxx视频在线| 欧美在线一二三区| 日韩一级欧美一级| 国产精品v亚洲精品v日韩精品| 成人3d漫画免费无遮挡软件| 一区二区欧美亚洲| 国产精品一区在线观看你懂的| 美女羞羞视频在线观看| 久久久久综合一区二区三区| 欧美精品一级二级三级| 伊人色**天天综合婷婷| 九色视频在线观看免费播放| 国产亚洲欧美一区二区三区| 亚洲高清免费一级二级三级| 日韩欧美亚洲日产国| 色八戒一区二区三区| 久久一区二区中文字幕| 中文字幕第5页| 91福利视频导航| 欧美一区二区三区不卡| 国产一区二区三区在线看麻豆| 丁香久久综合| 免费高清特黄a大片| 蜜桃传媒视频麻豆第一区免费观看| 欧美剧在线免费观看网站| 丁香天五香天堂综合| 亚洲一区二区伦理| 色综合久久网| 无码一区二区三区视频| 成人av资源电影网站| 婷婷成人在线| 欧美调教在线| 日韩欧美影院| 欧美日韩在线网站| 亚洲欧美成人vr| 女人抽搐喷水高潮国产精品| 日韩三区四区| 国产日韩一区二区三免费高清 | 欧美午夜精品久久久久久超碰 | 秋霞a级毛片在线看| avav在线播放| 国产精品手机视频| 国自在线精品视频| 亚洲精品国产欧美| 色哟哟国产精品| 99久久精品国产观看| 日本不卡视频在线观看| 欧美日韩在线二区| 秋霞午夜一区二区三区视频| shkd中文字幕久久在线观看| 日本xxxxwwww| 久久婷婷国产91天堂综合精品| 国产精品波多野结衣| 欧美深深色噜噜狠狠yyy| 国产精品草莓在线免费观看| 亚洲精品大尺度| 欧美在线观看一区| 午夜影院久久久| 亚洲美腿欧美偷拍| 久久新电视剧免费观看| 韩国自拍一区| 国产亚洲电影| 6080亚洲理论片在线观看| 国产盗摄——sm在线视频| 可以在线观看的av网站| 1pon在线| 最新版sss视频在线| 国产九色porn网址| 在线观看免费国产小视频| 五月综合激情在线| 在线视频99| 日韩精品123| 国产在线精彩视频| 中文不卡1区2区3区| 美女扒开腿让男人桶爽久久软| 超碰在线caoporn| 国产社区精品视频| 桃花岛tv亚洲品质| 色999久久久精品人人澡69| 人人九九精品视频| 成人黄色av| 麻豆精品视频在线观看免费| 污片在线观看一区二区| 欧美精品久久久久久久免费观看| 中文字幕在线乱| а√在线天堂官网| 久久一区精品| 日韩精品一区二区三区视频在线观看| 99www免费人成精品| 深夜福利在线看| 日本久久综合| 亚洲福利一二三区| 清纯唯美亚洲激情| 日本福利一区二区| 97在线精品国自产拍中文| japanese在线视频| 男女在线观看视频| 日韩中文字幕亚洲一区二区va在线| 日本中文一区二区三区| 亚洲欧洲成人av每日更新| 色婷婷综合久久久久中文 | 中文在线视频| 国产精品久久乐| 激情文学一区| 久久久蜜桃精品| 欧美日本在线看| 国产成人精品久久二区二区| 香蕉久久夜色| 99re在线视频| 好看的日韩av电影| 在线精品视频一区二区三四 | 日韩在线成人| 久久99久久99精品免视看婷婷 |