日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
120行代碼實(shí)現(xiàn)純Web剪輯視頻

[[422785]]

本文轉(zhuǎn)載自微信公眾號(hào)「微醫(yī)大前端技術(shù)」,作者翁佳瑞 。轉(zhuǎn)載本文請(qǐng)聯(lián)系微醫(yī)大前端技術(shù)公眾號(hào)。

成都創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都網(wǎng)站設(shè)計(jì)、做網(wǎng)站、濱海網(wǎng)絡(luò)推廣、重慶小程序開發(fā)、濱海網(wǎng)絡(luò)營(yíng)銷、濱海企業(yè)策劃、濱海品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);成都創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供濱海建站搭建服務(wù),24小時(shí)服務(wù)熱線:028-86922220,官方網(wǎng)址:www.cdcxhl.com

前言

前幾天偶爾看到一篇 webassembly 的相關(guān)文章,對(duì)這個(gè)技術(shù)還是挺感興趣的,在了解一些相關(guān)知識(shí)的基礎(chǔ)上,看下自己能否小小的實(shí)踐下。

什么是 webasembly?

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

WebAssembly 的優(yōu)勢(shì)

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

WebAssembly 最簡(jiǎn)單的實(shí)踐調(diào)用

我們編寫一個(gè)最簡(jiǎn)單的 c 文件

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

然后安裝對(duì)于的 Emscripten 編譯器Emscripten 安裝指南

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

然后我們?cè)?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. }); 

這時(shí)我們即可在控制臺(tái)看到對(duì)應(yīng)的打印日志,成功調(diào)用我們編譯的代碼啦

正式開動(dòng)

既然我們已經(jīng)知道如何能快速的調(diào)用到一些已經(jīng)成熟的 C,C++的類庫,那我們離在線剪輯視頻預(yù)期目標(biāo)更進(jìn)一步了。

最終 demo 演示

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

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

FFmpeg

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

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

簡(jiǎn)單的說這個(gè)就是由 C 語言編寫的視頻處理軟件,它的用法也是相當(dāng)?shù)魏?jiǎn)單

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

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

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

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

獲取相關(guān)的FFmpeg的wasm

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

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

為了本地調(diào)試方便,我把其相關(guān)的資源都下了下來 一共 4 個(gè)資源文件

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

我們使用的時(shí)候只需引入第一個(gè)文件即可,其它文件會(huì)在調(diào)用時(shí)通過 fetch 方式去拉取資源

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

前置功能實(shí)現(xiàn): 在我們本地需要實(shí)現(xiàn)一個(gè) node 服務(wù),因?yàn)槭褂?ffmpeg 這個(gè)模塊會(huì)出現(xiàn)如果沒在服務(wù)器端設(shè)置響應(yīng)頭, 會(huì)報(bào)錯(cuò) SharedArrayBuffer is not defined,這個(gè)是因?yàn)橄到y(tǒng)的安全漏洞,瀏覽器默認(rèn)禁用了該 api,若要啟用則需要在 header 頭上設(shè)置

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

我們啟動(dòng)一個(gè)簡(jiǎn)易的 node 服務(wù)

 
 
 
 
  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 = '

    Index

    '; 
  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...'); 

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

 
 
 
 
  1.  
  2.  
  3.  
  4.  
  5.    
  6.     選擇原始視頻文件: 
  7.      
  8.     開始剪輯視頻 
  9.   
 
  •    
  •     原視頻
  •  
  •      
  •   
  •  
  •    
  •     處理后的視頻
  •  
  •      
  •   
  •  
  •  
     
     
     
     
    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. }); 

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

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

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

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

     
     
     
     
    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. }); 

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

    小結(jié)

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

    參考資料

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

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

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

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


    當(dāng)前文章:120行代碼實(shí)現(xiàn)純Web剪輯視頻
    文章網(wǎng)址:http://www.5511xx.com/article/cdeeicd.html