提取视频中的帧画面

demo仅测试了以file对象做为源,格式为mp4

有几点可以考虑做成配置项:画布宽高;转 base64 或 blob 时,图片的格式以及质量;是否需要返回 base64 或 blob 的数组以及视频时长;

我在实际应用中仅使用了base64的数组,选择某一个图片时,再将单个的 base64转blob

 

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>提取视频帧</title>
        <style type="text/css">
            #imgBox img {
                width: 50%;
            }
        </style>
    </head>

    <body>
        <input type="file" id="input" accept="video/*" />
        <br>
        <br>
        <div id="imgBox"></div>
    </body>

    <script type="text/javascript">
        /**
         * 在视频中提取帧画面
         * @param { File | String } videoSource
         * @param { String } [interval = 1 / 30] - 以30fps提取每一帧,或传入指定间隔(s)
         * @returns { Object } obj: { base64Frames, blobFrames, duration }
         */
        function extractFramesFromVideo(videoSource, interval = 1 / 30) {
            return new Promise(async (resolve) => {
                let videoBlob;

                // 如果视频源是视频文件对象,直接赋值
                if (typeof videoSource === "object") {
                    videoBlob = videoSource
                } else {
                    // 如果是url路径,要先完全下载(无缓冲)
                    videoBlob = await fetch(videoSource).then(r => r.blob());
                }

                const videoObjectUrl = URL.createObjectURL(videoBlob);
                const video = document.createElement("video");

                let seekResolve;
                video.addEventListener('seeked', async function() {
                    // 音视频移动/跳跃到新的位置,并寻址完成后执行此函数
                    if (seekResolve) seekResolve();
                });

                // 当前帧的数据可用时执行
                video.addEventListener('loadeddata', async function() {
                    const canvas = document.createElement('canvas');
                    const context = canvas.getContext('2d');

                    // 画布宽高为视频原始宽高(考虑要不要做成配置项)
                    const [w, h] = [video.videoWidth, video.videoHeight]
                    canvas.width = w;
                    canvas.height = h;

                    // base64格式与blob对象格式的帧数组
                    const base64Frames = [],
                        blobFrames = [];

                    let currentTime = 0;
                    const duration = video.duration;

                    while (currentTime < duration) {
                        video.currentTime = currentTime;
                        // 设置完时间点后等待寻址完成
                        await new Promise(r => seekResolve = r);

                        context.drawImage(video, 0, 0, w, h);
                        let base64ImageData = canvas.toDataURL();
                        base64Frames.push(base64ImageData);

                        canvas.toBlob((blob) => {
                            blobFrames.push(blob)
                        })

                        // 提取画面的时间步进(间隔)
                        currentTime += interval;
                    }
                    resolve({
                        base64Frames, // base64格式的字符串数组
                        blobFrames, // blob对象格式的文件对象数组
                        duration // 视频总时长
                    });
                });

                // 在设置视频路径前先注册好监听事件,防止资源加载太快,事件发生在注册监听之前
                video.src = videoObjectUrl;
            });
        }


        const input = document.getElementById("input")
        input.onchange = function(e) {
            const file = input.files[0]

            console.log("视频处理中……");
            console.time("耗时")


            // 以10秒的间隔取帧(取每一帧非常耗显卡算力)
            extractFramesFromVideo(file, 10).then(data => {
                console.timeEnd("耗时");
                console.log(data);

                const box = document.getElementById("imgBox")
                data.base64Frames.forEach(item => {
                    const img = new Image()
                    img.src = item
                    box.appendChild(img)
                })
            })
        }
    </script>
</html>

 

posted @ 2021-05-27 13:39  Web初心  阅读(858)  评论(0编辑  收藏  举报