1.ffmpeg.wasm
github.com/ffmpegwasm/…
ffmpeg & wasm 是什么
ffmpeg是功能非常强大的视频处理开源软件,很多视频播放器就是使用它来做为内核。
webassembly 是 Binary Code, 是编译目标。WebAssembly将很多编程语言带到了Web中。
wasm解决了性能问题,将各种耗性能的app从Desktop搬到Web上。
想用ffmpeg纯web端实现处理视频。就要用到wasm提高操作性能,就是ffmpeg.wasm做的事情。
2.前端实现
不使用node, 纯前端项目,实现在browser上处理视频。
上图是git的文档, 只需要在本地引入ffmpeg.min.js (文件很小22KB)就可以了。
2.1 获取ffmpeg.min.js文件。
install 后,会看到文件ffmpeg/dist/ffmpeg.min.js。
然后,直接将ffmpeg.min.js文件放到public下,打包后,直接在根目录。
2.2 index页面引入
这里引入后,使用时就很方便,直接获取 const { createFFmpeg, fetchFile } = FFmpeg;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite App</title> <script src="/ffmpeg.min.js"></script> </head> <body> <div id="root"></div> <script type="module" src="/src/main.jsx"></script> </body> </html> 复制代码
注意: 纯前端不需要install @ffmpeg/ffmpeg @ffmpeg/core。上面install,就为了方便拿ffmpeg.min.js
按照上面2步,到这里都比较简单,按理可以直接使用了。但是会遇到比较麻烦的跨域隔离问题。当给网站设置好跨域隔离后,又会发现网站上其他资源获取不了。哎,所以在使用前,要先处理一下跨域隔离。
先会发现报错信息,SharedArrayBuffer is not defined。因为shareArrayBuffer 要求跨域隔离same-origin。
3.跨域隔离(本地&部署)
git上有一句提醒:SharedArrayBuffer is only available to pages that are cross-origin isolated.So you need to host your own server with Cross-Origin-Embedder-Policy: require-corp and Cross-Origin-Opener-Policy: same-origin headers to use ffmpeg.wasm.
因为用了sharedArrayBuffer共享内存。然后shareArrayBuffer要求host设置跨域隔离same-origin。
3.1本地处理
这里demo用了vite+react,直接在vite.config.js中设置。其他框架类似设置就可以。
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], server: { headers: { "Cross-Origin-Opener-Policy": "same-origin", "Cross-Origin-Embedder-Policy": "require-corp", }, }, }) 复制代码
3.2部署处理
响应用户的HTTP请求时,增加HTTP header, coop + coep 这两个响应头。
Response Headers
cross-origin-embedder-policy: require-corp
cross-origin-opener-policy: same-origin
到这里,可以正常使用ffmpeg.wasm了。但是会发现网站上其他的图片视频等资源获取不到了。 会看到新的报错信息。接下来处理一下网站上的其他资源获取问题。
Because your site has the Cross-Origin Embedder Policy (COEP) enabled, each resource must specify a suitable Cross-Origin Resource Policy (CORP). This behavior prevents a document from loading cross-origin resources which don’t explicitly grant permission to be loaded.
3.3跨域隔离后,加载其他资源(图片视频等)
因为host加了两个响应头启用了跨域隔离。那这个host上的其他资源,所有跨域资源都需要明确被允许加载。比如图片和视频。
明确被允许加载,有两种实现方式,一种是CORP,另一种是 CORS。
CORS
<img crossOrigin="anonymous" alt="图片" src={value} /> <video crossOrigin="anonymous" controls width={200} src={URL.createObjectURL(video)} /> 复制代码
CORP
对应的图片或者视频域名配置加header。Cross-Origin-Resource- Policy:cross-origin
4.各种视频操作
完成上面引入和跨域隔离两步,就可以愉快地各种操作使用ffmpeg了。
4.1视频剪切
如图可以看到 fetchFile 接受的入参格式,下面代码尝试了传file,和直接用图片地址都可以。
import { useState } from "react"; import { InputNumber, Button, Card } from "antd"; // import { createFFmpeg, fetchFile } from "@ffmpeg/ffmpeg"; // const ffmpeg = createFFmpeg({ // corePath: CONFIG.IS_TEST // ? "http://localhost:3000/ffmpeg-core.js" // : "ffmpeg-core.js", // log: true, // }); // eslint-disable-next-line no-undef const { createFFmpeg, fetchFile } = FFmpeg; const ffmpeg = createFFmpeg({ log: true, }); const fakeUrl = "https://*****.mp4"; function VideoSlice() { const [video, setVideo] = useState(); const [start, setStart] = useState(0); const [length, setLength] = useState(0); const [partVideo, setPartVideo] = useState(); const sliceVideo = async (source) => { if (!ffmpeg.isLoaded()) { await ffmpeg.load(); } ffmpeg.FS("writeFile", "vid.mp4", await fetchFile(source)); await ffmpeg.run( "-i", "vid.mp4", "-s", "480x320", "-r", "3", "-t", String(length), "-ss", String(start), "-f", "mp4", "out.mp4" ); const data = ffmpeg.FS("readFile", "out.mp4"); const url = URL.createObjectURL( new Blob([data.buffer], { type: "video/mp4" }) ); setPartVideo(url); }; return ( <Card title="视频切片"> {video && <video controls width={250} src={URL.createObjectURL(video)} />} <input type="file" onChange={(e) => { setVideo(e.target.files?.item(0)); }} /> <div>Url: {fakeUrl}</div> <div> <span>开始时间: </span> <InputNumber onChange={(value) => { setStart(value); }} /> <span>结束时间: </span> <InputNumber onChange={(value) => { setLength(value - start); }} /> <Button type="primary" style={{ marginLeft: 30 }} onClick={() => { sliceVideo(video); }} > 截取 </Button> <Button type="primary" style={{ marginLeft: 30 }} onClick={() => { sliceVideo(fakeUrl); }} > Url截取 </Button> </div> {partVideo && <video controls src={partVideo} width={250} />} </Card> ); } export default VideoSlice; 复制代码
4.2视频合并
import { useState } from "react"; import { Button, Card } from "antd"; const { createFFmpeg, fetchFile } = FFmpeg; const ffmpeg = createFFmpeg({ log: true, }); function VideoSlice() { const [videoList, setVideoList] = useState([]); const [mergerVideo, setMergerVideo] = useState(); const handleMergerVideo = async (source) => { if (!ffmpeg.isLoaded()) { await ffmpeg.load(); } ffmpeg.FS("writeFile", "vid1.mp4", await fetchFile(videoList[0])); ffmpeg.FS("writeFile", "vid2.mp4", await fetchFile(videoList[1])); // concat协议 适合视频MPEG格式,其他格式文件,先转码再合并 await ffmpeg.run("-i", "vid1.mp4", "-c", "copy", "-bsf:v", "h264_mp4toannexb", "-f", "mpegts", "vid1.ts"); await ffmpeg.run("-i", "vid2.mp4", "-c", "copy", "-bsf:v", "h264_mp4toannexb", "-f", "mpegts", "vid2.ts"); await ffmpeg.run("-i", "concat:vid1.ts|vid2.ts", "-c", "copy", "-bsf:a", "aac_adtstoasc", "-movflags", "+faststart", "out.mp4" ); const data = ffmpeg.FS("readFile", "out.mp4"); const url = URL.createObjectURL( new Blob([data.buffer], { type: "video/mp4" }) ); setMergerVideo(url); }; return ( <Card title="视频合并"> {videoList[0] && <video controls width={250} src={URL.createObjectURL(videoList[0])} />} {videoList[1] && <video controls width={250} src={URL.createObjectURL(videoList[1])} />} <input type="file" onChange={(e) => { const newList = [...videoList, e.target.files?.item(0)]; setVideoList(newList); }} /> <div> <Button type="primary" style={{ marginLeft: 30 }} onClick={() => { handleMergerVideo(videoList); }} > 合并 </Button> </div> {mergerVideo && <video controls src={mergerVideo} width={250} />} </Card> ); } export default VideoSlice; 复制代码
4.3图片转视频
import { useState } from "react"; import { Card, Button } from "antd"; const { createFFmpeg, fetchFile } = FFmpeg; const ffmpeg = createFFmpeg({ log: true, }); export default function ImgToVideo () { const [imgs, setImgs] = useState([]); const [videoUrl, setVideoUrl] = useState(); const imgToVideo = async () => { if (!ffmpeg.isLoaded()) { await ffmpeg.load(); } console.log(imgs[0]); for (let i in imgs) { ffmpeg.FS('writeFile', `${i}.png`, await fetchFile(imgs[i])) } await ffmpeg.run('-r', "1", '-f', 'image2', '-i', '%d.png', 'video.mp4') const data = ffmpeg.FS('readFile', 'video.mp4') const url = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' })) setVideoUrl(url); } return ( <Card title="图片转视频"> { imgs.length > 0 && imgs.map((item, index) => <p key={index}>{item.name}</p>) } <input type="file" onChange={(e) => { const list = [...imgs, e.target.files?.item(0)]; setImgs(list); }} /> <Button onClick={imgToVideo}>转视频</Button> {videoUrl && <video controls src={videoUrl} width={250} />} </Card> ) }; 复制代码
5.注意点
实际项目实现该功能,考虑到用户机器性能情况比较差,不采用这种纯web端的方式。
下载的话,ffmpeg-core.wasm 是8.5M,就还好。
但是运行时,用Core i7, 内存8G电脑测试剪切一个3秒的视频。浏览器的任务管理器可以看到cpu占比就挺高的。
原文链接:ffmpeg.wasm处理视频 - 掘金
★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。文章来源:https://www.toymoban.com/news/detail-649176.html
见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓文章来源地址https://www.toymoban.com/news/detail-649176.html
到了这里,关于ffmpeg.wasm处理视频的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!