記錄---前端Vue使用ffmpeg壓縮影片再上傳

林恒發表於2024-11-12

🧑‍💻 寫在開頭

點贊 + 收藏 === 學會🤣🤣🤣

保姆級操作步驟,從我實際執行中的專案中摘取的所有相關程式碼展示如下:

1.Vue專案中安裝外掛ffmpeg

1.1 外掛版本依賴配置

兩個外掛的版本 "@ffmpeg/core": "^0.10.0", "@ffmpeg/ffmpeg": "^0.10.1"
package.json 和 package-lock.json 都加入如下ffmpeg的版本配置:

1.2 把ffmpeg安裝(下載)到專案依賴目錄裡

terminal執行命令:

npm i
或直接執行命令安裝ffmpeg:
npm install @ffmpeg/ffmpeg @ffmpeg/core -S
安裝後package-lock.json會自動寫入如下配置:

1.2.1 報錯處理

如果出現安裝問題:

①先在npm i命令後加--legacy-peer-deps 或者 --force執行

npm i --force
②如果上步不行,嘗試刪除這個安裝依賴目錄node_modules

和package-lock.json檔案,重試npm i

請參考:

npm ERR! code ERESOLVEnpm ERR! ERESOLVE could not resolve 報錯,版本衝突,最全解決步驟(#^.^#)_npm err! code eresolve npm err! eresolve could not-CSDN部落格

1.2.2 映象過期

安裝ffmpeg可能提示映象證書過期

你使用的映象地址可能還是這個過期的淘寶映象:https://registry.npm.taobao.org/

按如下步驟重設映象地址:

①檢視映象:npm config list

②強制清理映象快取:npm cache clean --force

③設定映象:npm config set registry https://registry.npmmirror.com/(國內推薦淘寶新映象)

也可:npm config set registry https://registry.npmjs.org/

1.3 把ffmpeg安裝到專案裡

在專案裡的ffmpeg外掛目錄下找到:

ffmpeg-core.js

ffmpeg-core.wasm

ffmpeg-core-worker.js

複製到專案程式碼的public目錄裡

至此,專案裡安裝ffmpeg完畢。

接下來就是在程式碼塊裡使用ffmpeg

2 專案裡引用並封裝ffmpeg

在util目錄下封裝ffmpeg.js以便專案全域性引用

封裝的工具透過:

import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg';

把ffmpeg外掛引入到專案裡使用。

完整ffmpeg.js程式碼:

import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg';
let ffmpeg = {};
 
ffmpeg.squeezVideo = async function(file, filename, filetype, width, height, msg) {
    console.log('file', file);
    console.log('filename', filename);
    console.log('filetype', filetype);
    console.log('width', width);
    console.log('height', height);
 
    // 解析度
    const resolution = `${width}x${height}`;
    // 例項化ffmpeg
    const ffmpegObj = createFFmpeg({
        // ffmpeg路徑
        corePath: 'ffmpeg-core.js',
        // 日誌
        log: true,
        // 進度
        progress: ({ ratio }) => {
            msg = `完成率: ${(ratio * 100.0).toFixed(1)}%`;
        }
    })
 
    var { name } = file;
    // msg = '正在載入 ffmpeg-core.js'
    // 開始載入
    await ffmpegObj.load();
    // msg = '開始壓縮'
    // 把檔案加到ffmpeg   寫檔案
    ffmpegObj.FS('writeFile', name, await fetchFile(file));
    // await ffmpeg.run('-i', name, '-b', '2000000', '-fs', '4194304', '-preset medium', 'superfast', 'output.mp4')
    // 開始壓縮影片
    const compressedFileSize = this.computeFileSize(file);
    console.log("After compression,this file size is " + compressedFileSize + " Bytes.");
    await ffmpegObj.run('-i', name, '-b', '2000000', '-crf', '18', '-fs', compressedFileSize, '-s', resolution, 'output.mp4');
    // msg = '壓縮完成'
    // 壓縮所完成,   讀檔案  壓縮後的檔名稱為 output.mp4
    const data = ffmpegObj.FS('readFile', 'output.mp4');
 
    // 轉換bolb型別
    const blob = new Blob([data], { type: 'text/plain;charset=utf-8' });
 
    return new Promise((resolve, reject) => {
        const file = new window.File([blob], filename, { type: filetype });
        resolve(file);
    })
}
 
ffmpeg.computeFileSize = function(file) {
    if(!file){
        return '0';
    }
    if(file.size / 1024 / 1024 > 60){
        //30M
        return '31457280';
    }else if(file.size / 1024 / 1024 <= 60 && file.size / 1024 / 1024 > 30){
        return file.size / 2;
    }else{
        return file.size;
    }
}
 
// 獲取上傳影片的url
ffmpeg.getObjectURL = function(file) {
    let url = null;
    window.URL = window.URL || window.webkitURL;
    if (window.URL) {
        url = window.URL.createObjectURL(file);
    } else {
        url = URL.createObjectURL(file);
    }
    return url;
}
 
// 獲取影片的寬高解析度
ffmpeg.getVideoData = function() {
    return new Promise((resolve, reject) => {
        const videoElement = document.getElementById('video');
        videoElement.addEventListener('loadedmetadata', function () {
            resolve({
                width: this.videoWidth,
                height: this.videoHeight,
                duration: this.duration
            })
        });
    })
}
 
export default ffmpeg;

2.1 ffmpeg壓縮引數配置

-b:指定影片位元率

-crf:恆定速率因子,控制輸出影片質量的引數。

這個引數的取值範圍為0~51,其中0為無損模式。數值越大,畫質越差,生成的檔案卻越小。

從主觀上講,18~28是一個合理的範圍。18被認為是視覺無損的(從技術角度上看當然還是有損的),它的輸出影片質量和輸入影片相當。

-fs:壓縮到指定大小,單位Byte

-s:解析度

控制壓縮後影片質量的最重要的是後面三個引數:crf、fs、s

3.影片上傳元素

<template>
    <el-upload
        ref='operationVideoUpload'
        :limit="1"
        list-type='text'
        :class="{disabled:addModelParam.attachments.operationVideo.length>0}"
        :action='actionUrl'
        :on-success="(res,file)=>handleVideoSuccess(res,file,'operationVideo')"
        :before-upload='beforeAvatarUploadVideo'
        :on-remove="(file,fileList)=>handleRemove(file,fileList,'operationVideo')"
        :auto-upload='true'
        :on-exceed="handelFileExceed"
        accept='.mp4,.mov,.wmv,.flv,.mvi,.mkv'>
      <el-button style="position: relative; margin: -5px"><i  class="el-icon-circle-plus-outline" style="color: #66b1ff;">上傳附件</i></el-button>
      <br/><br/>
      <p>{{ msg }}</p>
    </el-upload>
    <video id="video" hidden controls object-fill="fill"></video>
  </template>

如果不想要展示壓縮影片,可以去掉video標籤。

4.上傳壓縮指令碼

把封裝的ffmpeg.js匯入到頁面使用:

import ffmpeg from "@/utils/ffmpeg";

完整js指令碼:

<script>
import ffmpeg from "@/utils/ffmpeg";
 
export default {
    data() {
        return {
            msg: '',
            videoWidth: '',
            videoHeight: '',
            duration: '',
            actionUrl: '',
            addModelParam: {
                attachments: {
                    operationVideo: []
                }
            },
        }
    },
    created() {
        this.actionUrl = "你的後端上傳檔案介面地址URL";
    },
    methods: {
        handleVideoSuccess(res, file, code) {
          this.msg = "已完成影片壓縮後上傳!";
          file.url = res.data.url;
          file.fileId = res.data.fileId;
          this.addModelParam.attachments[code].push(file.fileId);
        },
 
        handleAvatarSuccess(res, file, code) {
            file.url = res.data.url;
            file.fileId = res.data.fileId;
            this.addModelParam.attachments[code].push(file.fileId);
        },
 
        handleRemove(file, fileList, code) {
            this.addModelParam.attachments[code].splice(this.addModelParam.attachments[code].indexOf(file.fileId),1)
        },
        beforeAvatarUploadVideo(file) {
         const isLarge = file.size / 1024 / 1024 > 30;
         if (isLarge) {
           this.msg = "請稍等,過大的影片正在壓縮上傳中...";
           //壓縮影片
           return this.uploadCompressVideo(file);
         }
        },
        handelFileExceed(){
            this.$message('檔案數量超出限制!');
        },
        
        // 上傳影片檔案壓縮後再上傳
        uploadCompressVideo(file) {
            if (file) {
                let filename = file.name;
                let filetype = file.type;
 
                const videoUrl = ffmpeg.getObjectURL(file);
                const video = document.getElementById('video');
                video.src = videoUrl;
                return ffmpeg.getVideoData().then((videoObj) => {
                    const {width, height} = videoObj;
                    return ffmpeg.squeezVideo(file, filename, filetype, width, height, this.msg);
                })
            }
        },
},
 
}
</script>

注意非同步處理:非同步壓縮,再上傳

可使用new Promise();

5.其他配置:

5.1 vue專案根目錄下的vue.config.js里加配置

headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp'
}

module.exports = {
    publicPath: './',
    devServer: {
        client: {
            overlay: false,
        },
        port: 9002,
        headers: {
            'Cross-Origin-Opener-Policy': 'same-origin',
            'Cross-Origin-Embedder-Policy': 'require-corp'
        }
    },
    transpileDependencies: []
  }

  以免出現如下SharedArrayBuffer的報錯:

6.其他實現方案

外掛video-conversion.js

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文件,大家一起討論學習,一起進步。

相關文章