前端實(shí)現(xiàn)視頻文件動(dòng)畫幀圖片提取完整教程
1. 前言
相信很多小伙伴在一些短視頻平臺(tái)上傳視頻的時(shí)候,系統(tǒng)會(huì)自動(dòng)幫我們生成一些視頻中的畫面幀的圖片,讓我們作為視頻封面的功能,那么這些短視頻平臺(tái)是如何從視頻中抽取關(guān)鍵幀來作為封面、生成縮略圖,或用于制作動(dòng)圖預(yù)覽的?
今天博主就帶著大家一起來探討這個(gè)問題,當(dāng)然實(shí)現(xiàn)畫面幀的方式前端和后端均可實(shí)現(xiàn),具體要看小伙伴們的應(yīng)用場(chǎng)景,這里我們先介紹前端的實(shí)現(xiàn)方式,后續(xù)博主再出一篇 基于JAVA實(shí)現(xiàn)的視頻畫面幀教程~
2. 場(chǎng)景分析
上面我們提到了獲取畫面幀用于視頻封面,實(shí)際上還會(huì)有很多的使用場(chǎng)景,比如:
- 用戶上傳視頻文件后生成首幀預(yù)覽
- 創(chuàng)建視頻縮略圖時(shí)間軸
- 逐幀分析視頻內(nèi)容
- 制作GIF動(dòng)畫的素材提取
前端實(shí)現(xiàn)的視頻文件動(dòng)畫幀也有它的優(yōu)缺點(diǎn):
前端實(shí)現(xiàn)的優(yōu)點(diǎn)
實(shí)現(xiàn)簡(jiǎn)單,依賴瀏覽器內(nèi)置 API,無需額外庫,適合常見 MP4/WebM/OGG 等格式
前端實(shí)現(xiàn)的缺點(diǎn)
- 瀏覽器對(duì)視頻格式支持有限,無法處理非標(biāo)準(zhǔn)格式;
- 精確的幀定位依賴瀏覽器的
currentTime
跳轉(zhuǎn)和loadeddata
事件,可能產(chǎn)生誤差或丟幀;- 對(duì)大批量幀抽取性能較差,主線程阻塞風(fēng)險(xiǎn)高
3. 實(shí)現(xiàn)原理
利用 HTML5 的 canvas 元素可以直接對(duì)視頻進(jìn)行像素級(jí)操作,無需后端處理即可完成簡(jiǎn)易的幀截圖功能。
將 <video> 元素加載視頻資源,通過 canvas.drawImage(video, …) 將當(dāng)前幀繪制到畫布上,再調(diào)用 canvas.toDataURL() 或 canvas.toBlob() 獲取圖片數(shù)據(jù)
要實(shí)現(xiàn)上述思路,我們需要以下幾個(gè)步驟:
- 1、通過
<input type="file">
獲取視頻文件- 2、使用
URL.createObjectURL
創(chuàng)建視頻源- 3、監(jiān)聽視頻元數(shù)據(jù)加載完成
- 4、通過
Canvas
繪制當(dāng)前幀- 5、將
Canvas
轉(zhuǎn)換為圖片數(shù)據(jù)
4. 完整代碼演示
下面以 HTML5 Canvas
方案為例,演示一個(gè)完整的幀提取與下載流程。
設(shè)置了兩個(gè)功能按鈕,一個(gè)是自動(dòng)獲取所有幀,一個(gè)是獲取視頻當(dāng)前播放幀,小伙伴們可以自行根據(jù)自己需求進(jìn)行代碼修改~
HTML 結(jié)構(gòu)
<!DOCTYPE html> <html> <head> <title>視頻幀提取工具 Demo</title> <style> .container { max-width: 800px; margin: 20px auto; } #preview { display: flex; gap: 10px; flex-wrap: wrap; } .frame-img { width: 150px; border: 1px solid #ccc; } #video { width: 300px; margin-top: 20px; } #all img { width: 50px; margin-bottom: 20px; } </style> <script src="extractFrames.js"></script> </head> <body> <h1>視頻幀提取工具 Demo</h1> <div class="container"> <input type="file" id="videoInput" accept="video/*"> <button onclick="captureFrame()">截取當(dāng)前幀</button> <button id="captureAll">獲取所有幀</button> <div> <video id="video" controls muted playsinline></video> </div> <canvas id="canvas" style="display: none;"></canvas> <div id="all"></div> <div id="preview"></div> </div> </body> </html>
JavaScript 邏輯
前端引入的視頻幀處理 extractFrames.js
代碼
// extractFrames.js 代碼 const video = document.getElementById('video'); const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); // 文件選擇處理 document.getElementById('videoInput').addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; const videoURL = URL.createObjectURL(file); video.src = videoURL; video.play().catch(() => video.pause()); // 確保加載 metadata video.play(); }); // 視頻元數(shù)據(jù)加載 video.addEventListener('loadedmetadata', () => { canvas.width = video.videoWidth; canvas.height = video.videoHeight; }); // 幀捕獲函數(shù) function captureFrame() { if (!video.src) return; ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const dataURL = canvas.toDataURL('image/png'); const img = new Image(); img.src = dataURL; img.className = 'frame-img'; document.getElementById('preview').appendChild(img); } //獲取所有幀 // 主流程:讀取文件并批量截幀 document.getElementById('captureAll').addEventListener('click', async () => { const duration = video.duration; const interval = 1; // 每 1 秒抽一幀 for (let t = 1; t < duration; t += interval) { const blob = await captureAll(t); const imgURL = URL.createObjectURL(blob); const img = document.createElement('img'); img.src = imgURL; img.width = 160; document.getElementById('all').appendChild(img); } URL.revokeObjectURL(url); }); // 獲取指定時(shí)間點(diǎn)的幀 function captureAll(time) { return new Promise((resolve) => { video.currentTime = time; video.addEventListener('seeked', function onSeeked() { ctx.drawImage(video, 0, 0,canvas.width, canvas.height); canvas.toBlob(blob => { resolve(blob); }, 'image/png'); video.removeEventListener('seeked', onSeeked); }); }); }
代碼解析
文件上傳處理
- 通過 獲取用戶上傳的視頻文件
- 使用 URL.createObjectURL 創(chuàng)建臨時(shí)視頻源地址
視頻初始化
- 監(jiān)聽
loadedmetadata
事件獲取視頻原始尺寸- 根據(jù)視頻尺寸初始化 Canvas 畫布
當(dāng)前幀捕獲
- 通過 drawImage 將當(dāng)前視頻幀繪制到 Canvas
- 使用 toDataURL 將 Canvas 內(nèi)容轉(zhuǎn)換為 Base64 圖片
- 動(dòng)態(tài)創(chuàng)建 Image 元素展示捕獲結(jié)果
所有幀幀捕獲
- 通過監(jiān)聽 seeked 事件,確保視頻跳轉(zhuǎn)已完成后再繪制幀
- 使用
canvas.toBlob()
可以獲得原生 Blob 對(duì)象- 按秒為單位根據(jù)視頻時(shí)間循環(huán)生產(chǎn)幀圖片
5. 結(jié)語
至此在純前端環(huán)境下基于 HTML5 Canvas API
的視頻幀提取方案已經(jīng)演示完畢了,在處理一些格式簡(jiǎn)單、幀數(shù)較少的場(chǎng)景,直接使用 Canvas
即可快速實(shí)現(xiàn),但需要更多格式或大規(guī)模自動(dòng)化截幀,推薦引入 FFmpeg Wasm
庫(如 ffmpeg.wasm
)進(jìn)行處理。
以上就是前端實(shí)現(xiàn)視頻文件動(dòng)畫幀圖片提取完整教程的詳細(xì)內(nèi)容,更多關(guān)于前端視頻文件動(dòng)畫幀提取的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS+CSS實(shí)現(xiàn)簡(jiǎn)易實(shí)用的滑動(dòng)門菜單效果
這篇文章主要介紹了JS+CSS實(shí)現(xiàn)簡(jiǎn)易實(shí)用的滑動(dòng)門菜單效果,涉及JavaScript鼠標(biāo)事件及頁面元素遍歷的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果
本文要探討的是,當(dāng)這兩個(gè)容器元素的內(nèi)容都超出了容器高度,即都出現(xiàn)了滾動(dòng)框的時(shí)候,如何在其中一個(gè)容器元素滾動(dòng)時(shí),讓另外一個(gè)元素也隨之滾動(dòng)2017-12-12兼容IE與firefox火狐的回車事件(js與jquery)
今天看了網(wǎng)上的朋友說了,很多網(wǎng)站提供的回車事件代碼都是不兼容firefox的,其實(shí)腳本之家提供的代碼,一直以來都是盡量的兼容多瀏覽器。2010-10-10JavaScript實(shí)現(xiàn)下拉菜單的顯示和隱藏
這篇文章主要介紹了JavaScript實(shí)現(xiàn)下拉菜單的顯示和隱藏的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-01-01JS實(shí)現(xiàn)帶有抽屜效果的產(chǎn)品類網(wǎng)站多級(jí)導(dǎo)航菜單代碼
這篇文章主要介紹了JS實(shí)現(xiàn)帶有抽屜效果的產(chǎn)品類網(wǎng)站多級(jí)導(dǎo)航菜單代碼,涉及JavaScript動(dòng)態(tài)操作頁面元素屬性的技巧,整體界面效果美觀大方,具有極強(qiáng)的立體感,需要的朋友可以參考下2015-09-09