【新项目开发】vue3+ts+elementPlus+ffmpegjs开发纯web端的视频编辑器

2023-10-27

新项目开发的流程

当在项目中使用新技术时,我们应该首先进行调研,了解其特点和使用方法。在实现功能时,我们可以采用最简单的方式,而不必过于关注项目的设计和结构。一旦掌握了新技术,我们可以根据其API属性进行代码设计,以便更好地开发。以开发一个纯web端的视频编辑处理器为例,我们打算采用vite、vue3、elementPlus、pinia和ffmpegjs等技术。虽然第一次接触ffmpegjs库可能会感到陌生,但我们需要先了解其API属性,并能够实现所需的功能。在此基础上,我们可以考虑如何将ffmpegjs集成到项目中,并设计好项目架构和代码结构,以便更好地使用和维护。

视频编辑器项目操作介绍:

web视频编辑器

1.功能列表:

  1. 项目运行
  • pnpm i安装依赖
  • pnpm dev 项目本地运行
  • pnpm build 项目打包

ffmpegjs是什么

ffmpegjs是一个基于JavaScript的开源库,它提供了一个在浏览器中运行FFmpeg的解决方案。FFmpeg是一个广泛使用的开源跨平台音视频处理工具,可以用于音视频的编解码、转码、剪辑、合并等操作。ffmpegjs库通过将FFmpeg编译为WebAssembly格式,并使用JavaScript封装其API,使得我们可以在浏览器中使用FFmpeg进行音视频处理,而无需安装任何本地软件。ffmpegjs库可以用于开发各种基于Web的音视频应用,如视频编辑器、音视频转换器、在线直播等。

安装ffmpegjs

需要安装 @ffmpeg/ffmpeg @ffmpeg/core 这两个npm包
pnpm install @ffmpeg/ffmpeg @ffmpeg/core

在项目中引入

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

项目核心模块

import { fetchFile, createFFmpeg } from '@ffmpeg/ffmpeg'
import { processData, tickCounts } from '../store'
// @ts-ignore
// const { fetchFile, createFFmpeg } = FFmpeg

const ffmpeg = createFFmpeg({
  corePath: '/plugin/ffmpeg-core.js'
})
ffmpeg.load()
ffmpeg.setProgress(progress => {
  processData.value = progress.ratio
})

const videoInfo = {
  duration: '',
  bitRate: ''
}
ffmpeg.setLogger(logs => {
  if (logs.message.includes('Duration')) {
    videoInfo.duration = logs.message.slice(
      logs.message.indexOf('Duration:') + 'Duration: '.length,
      logs.message.indexOf(',')
    )
    videoInfo.bitRate = logs.message.slice(
      logs.message.indexOf('bitrate:') + 'bitrate: '.length
    )
    console.log(videoInfo)
  }
})

let videoName = 'initVideo'
/**ffmpeg导入视频 */
export const initVideo = async (video: Blob) => {
  ffmpeg.FS('writeFile', videoName, await fetchFile(video))
  await ffmpeg.run('-i', videoName)
}

let fontName = 'font1'
/** ffmpeg导入字体 */
export const writeFontFile = async (font: Blob) => {
  ffmpeg.FS('writeFile', fontName, await fetchFile(font))
}

let imageName = 'imageMark'
/**ffmpeg导入贴图 */
export const writeImage = async (image: Blob) => {
  ffmpeg.FS('writeFile', imageName, await fetchFile(image))
}

let subTitle = 'subtitle.srt'
/**ffmpeg导入字幕文件 */
export const writeSubTitle = async (subtitle: Blob) => {
  ffmpeg.FS('writeFile', subTitle, await fetchFile(subtitle))
}

/**通过url获取文件blob数据 */
export const urlGetData = async (fileUrl: string, type = 'video/mp4') => {
  const tmp = 'tmpFile'
  ffmpeg.FS('writeFile', tmp, await fetchFile(fileUrl))
  const outputData = ffmpeg.FS('readFile', tmp)
  return new Blob([outputData.buffer], { type })
}

/** 切分视频 */
export const ffmpegSliceVideo = async (
  fileUrl: string,
  fileName: string,
  middleTime: string
) => {
  ffmpeg.FS('writeFile', fileName, await fetchFile(fileUrl))
  // 将视频分割为两个部分
  const command = `-i ${fileName} -t ${middleTime} -c copy output1.mp4 -ss ${middleTime} -c copy output2.mp4`
  await ffmpeg.run(...command.split(' '))

  const video1 = ffmpeg.FS('readFile', 'output1.mp4')
  const video1Url = URL.createObjectURL(
    new Blob([video1.buffer], { type: 'video/mp4' })
  )
  const video2 = ffmpeg.FS('readFile', 'output2.mp4')
  const video2Url = URL.createObjectURL(
    new Blob([video2.buffer], { type: 'video/mp4' })
  )

  return [video1Url, video2Url]
}

/**给视频添加字幕 */
export const addSubTitle = async (fileUrl: string, fileName: string) => {
  ffmpeg.FS('writeFile', fileName, await fetchFile(fileUrl))
  const cmd = `-i ${fileName} -vf subtitles=${subTitle} output.mp4`

  await ffmpeg.run(...cmd.split(' '))

  const outputData = ffmpeg.FS('readFile', 'output.mp4')

  const outputBlob = new Blob([outputData.buffer], { type: 'video/mp4' })
  return URL.createObjectURL(outputBlob)
}

/** 获取视频的每一秒帧 */
export const gVideoFrame = async (
  fileUrl: string,
  timeArr: number[],
  videoName: string = 'initVideo'
) => {
  const frameDir = videoName
  ffmpeg.FS('writeFile', videoName, await fetchFile(fileUrl))
  ffmpeg.FS('mkdir', frameDir + 'Frame')

  const second = tickCounts.value / timeArr[timeArr.length - 1]
  let cmd = `-i ${videoName} -vf fps=${second} -q:v 5 -s 320x240 -an -preset fast /${frameDir}Frame/%3d.jpeg -hide_banner`
  let args = cmd.split(' ')
  await ffmpeg.run(...args)
  const fileList = ffmpeg.FS('readdir', '/' + frameDir + 'Frame')
  let urls: { url: string }[] = []
  fileList.forEach(v => {
    if (v !== '.' && v !== '..') {
      const path = frameDir + 'Frame' + '/' + v
      const img = ffmpeg.FS('readFile', path)
      let url = URL.createObjectURL(
        new Blob([img.buffer], { type: 'image/jpeg' })
      )
      urls.push({
        url
      })
    }
  })
  return urls
}

/** 给视频添加文字 */
export const addText = async (
  fileUrl: string,
  videoName: string = 'initVideo',
  text: string = 'hello',
  startT: number = 5,
  endT: number = 7
) => {
  ffmpeg.FS('writeFile', videoName, await fetchFile(fileUrl))
  const cmd = `-re -i ${videoName} -vf`
  const textT =
    `drawtext=fontfile=font1:text=${text}` +
    `:fontcolor=white:fontsize=80:x=100:y=10:box=1:boxcolor=#0000007d:enable='between(t,${startT},${endT})'`
  let args = cmd.split(' ')
  await ffmpeg.run(...args, textT, 'outfile.mp4')
  const data = ffmpeg.FS('readFile', 'outfile.mp4')
  return URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }))
}

/** 给视频添加贴图 */
export const addImage = async (
  fileUrl: string,
  videoName: string = 'initVideo',
  startT: number = 5,
  endT: number = 7
) => {
  ffmpeg.FS('writeFile', videoName, await fetchFile(fileUrl))
  const cmd = `-i ${videoName} -i ${imageName} -filter_complex overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2:enable='between(t,${startT},${endT})' outfile.mp4`
  let args = cmd.split(' ')
  await ffmpeg.run(...args, '-hide_banner')
  const data = ffmpeg.FS('readFile', 'outfile.mp4')
  return URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }))
}

/** 获取视频的第一帧图片 */
export const getFirstFrame = async (
  fileUrl: string,
  fileName: string,
  initTime = '00:00:00.001'
) => {
  ffmpeg.FS('writeFile', fileName, await fetchFile(fileUrl))
  console.log('视频的第一帧图片')
  await ffmpeg.run(
    '-hwaccel',
    'auto',
    '-i',
    fileName,
    '-ss',
    initTime,
    '-vframes',
    '1',
    '-s',
    '640x480',
    '-an',
    '-threads',
    '4',
    '-preset',
    'fast',
    'output.jpg'
  )
  const data = ffmpeg.FS('readFile', 'output.jpg')
  const url = URL.createObjectURL(
    new Blob([data.buffer], { type: 'image/jpeg' })
  )
  return {
    url,
    videoInfo: JSON.stringify(videoInfo)
  }
}

/**导出视频,降帧 */
export const videoLower = async (fileUrl: string, fileName: string) => {
  ffmpeg.FS('writeFile', fileName, await fetchFile(fileUrl))
  const cmd = `-i ${fileName} -b:v 2000k -q:v 2 -r 24 -s 1240x960 output.mp4`
  let args = cmd.split(' ')
  await ffmpeg.run(...args)
  const data = ffmpeg.FS('readFile', 'output.mp4')
  const url = URL.createObjectURL(
    new Blob([data.buffer], { type: 'video/mp4' })
  )
  return url
}

项目常见的问题:

1、shareArrayBuffer问题,shareArrayBuffer not defined
给服务响应头加入'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp'这两个字段,引发这个问题主要是因为浏览器厂商因为内存安全问题而对shareArrayBuffer这个api做了限制,只需要在服务器响应头中设置这个字段即可解决。

 server: {
      open: true,
      host: '0.0.0.0',
      headers: {
        'Cross-Origin-Opener-Policy': 'same-origin',
        'Cross-Origin-Embedder-Policy': 'require-corp'
      },
      proxy: {}
    },
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【新项目开发】vue3+ts+elementPlus+ffmpegjs开发纯web端的视频编辑器 的相关文章

随机推荐

  • (简单课设)前端小白刚做的一个简单的移动端项目的分享和总结

    前端小白刚做的一个简单的移动端项目的分享和总结 所用技术为简单的div css 直接上图片 代码部分 小滴服务 接下来是企业项目部分 接下来是我的小滴部分 另外两个部分内容非常简单 没必要粘贴代码了 我遇到的问题和总结 所用技术为简单的di
  • 【TypeScript】Object、object和{}类型

    文章目录 Object object和 对象类型 Object object 空类型 总结 比较 Object object和 对象类型 Object Object类型是所有 Object 类的实例的类型 它由以下两个接口来定义 Objec
  • vue中使用swiper实现页面内容滑动以及导航栏之间的联动效果

    思路 1 实现页面 2 点击导航栏实现页面切换 3 滑动页面实现导航栏激活 效果演示 一 引入swiper 安装 npm install swiper vue awesome swiper save 在项目main js中进行如下设置 二
  • shell多行注释详解

    我们写shell脚本时 经常需要注释多行 但在每一行前输入 有些麻烦 基于 shell命令的灵活性 我们可以使用下面的方法 1 采用HERE DOCUMENT特性 格式 lt lt COMMENT COMMENT 例子 bin bash e
  • ora-01861:文字与格式字符串不匹配问题

    刚开始用oracle 今天把字符串数据传递到mapper文件中执行的时候遇到了文字与格式字符串不匹配这个错误 这是由于给的字符串和要转换的日期格式不一样导致的 例如 select to date 2018 3 9 0 00 00 yy mm
  • MAX SDK 开发学习(1) 开发环境

    一 前言 之前用maxscript写了一些功能 小文件情况下还好 文件越大时间越久 大文件 5G以上的max文件时 卡死 慢死 因为3dmax有 net c 的二次开发 接下来打算边学习边整理资料 最终目的 自动模型简化 之前处理Revit
  • 【数据结构】C语言实现栈(详细解读)

    前言 个人主页 Dream Chaser 专栏 http t csdn cn oXkBa 本篇内容 c语言数据结构 C语言实现栈 目录 什么是栈 栈的概念及结构 实现栈的方式 链表的优缺点 顺序表的优缺点 栈的实现 a 头文件的包含 b 栈
  • 【kafka】服务器命令行查询kafka信息消费情况

    大家好 我是好学的小师弟 kafka tool出问题的情况下 可以用命令行来查看kafka信息 1 找到kafka所在的安装目录 find name kafka 2 列出有哪些用户组来消费 kafka consumer groups sh
  • windows下OpenCV安装教程以及vs2019配置opencv教程

    文章目录 一 OpenCV下载 二 OpenCV安装及配置 三 VS2019项目配置OpenCV 一 OpenCV下载 官网地址 Home OpenCV 下载地址 OpenCV download SourceForge net 二 Open
  • Zabbix通过SNMPv2监控DELL服务器的硬件信息

    一 zabbix监控DELL服务器 1 简述 监控DELL服务器硬件一般有两种途径 1 操作系统上安装OMSA 编写脚本调用omreport命令进行监控 需要在操作系统上安装比较麻烦 2 使用iDRAC Integrated Dell Re
  • 神经网络与深度学习三:编写单隐层神经网络

    三 编写单隐层神经网络 1 神经网络概述 这篇文章你会学到如何实现一个神经网络 在我们深入学习技术细节之前 现在先大概快速的了解一下如何实现神经网络 如果你对某些内容不甚理解 后面的文章中会深入其中的细节 上周我们讨论了logistic回归
  • 流程引擎(flowable)之流程相关

    代码示例 流程部署 发布 InputStream inputStream new DefaultResourceLoader getResource classpath BusinessProcessBeanTest test bpmn20
  • 学习笔记(106):R语言入门基础-lines函数

    立即学习 https edu csdn net course play 24913 285850 utm source blogtoedu lines函数 在已有图上加线 功能相当于plot x y type l x lt 1 20 y1
  • 数据库产品如何选型:

    author skate time 2014 06 26 数据库产品如何选型 一 软件功能对比 二 成本考虑 三 满足业务场景 四 平衡各种资源 oracle mysql nosql选型 一 是否满足业务场景 各DB系统软件功能对比 1 功
  • *【思路】程序员怎么快速接手一个项目

    可能不管新手老手有些程序员 接手一个项目之后都会多少有些迷惘 以下是本人总结出来的一点小心得 如果错误希望大家给我留言 一起讨论 最重要的事儿 如果你总是看见代码多就发愁 看见代码脏乱差就诅咒埋怨 看见代码逻辑复杂就头疼 搞不清调用关系就放
  • 用Python做一个无限弹窗

    首先 我们需要对程序进行分析 1 导入GUI库 2 死循环 3 跳出弹窗 理清了思路 我们一起来愉快地写代码吧 第一步 导入GUI库 相信很多人都会自己导入吧 小编这里不在多噜嗦了 直接上代码 import easygui 导入GUI库 这
  • 小梅哥Xilinx FPGA学习笔记3——时序逻辑电路设计之计数器

    计数器 功能介绍 1 功能描述 一 代码编写 1 设计文件 2 激励文件 3 仿真图 二 总结 功能介绍 1 功能描述 时序逻辑电路是指电路任何时刻的稳态输出不仅取决于当前的输入 还与前一时刻输入形成的状态有关 这跟组合逻辑电路相反 组合逻
  • 锐捷商通v6数据库服务器位置,热烈庆祝我校开通IPv6资源

    当前 云计算风起云涌 物联网方兴未艾 而IP地址的枯竭 网络安全性和管理 维护 运营要求与日俱增 互联网面临更严峻的挑战 下一代互联网的发展和应用 将成为提高我校未来竞争力的突破口和制高点 我校在IPv6建设相对取得了阶段性成果 下一步以I
  • 晶体振荡器与晶体谐振器的区别

    晶体振荡器与晶体谐振器的区别 2012 02 17 10 43 112311440yan 等2人 分类 工程技术科学 浏览506次 网友采纳 2007 04 05 12 42 yueyezhe858 十四级 晶体谐振器跟晶体振荡器有区别 晶
  • 【新项目开发】vue3+ts+elementPlus+ffmpegjs开发纯web端的视频编辑器

    新项目开发的流程 当在项目中使用新技术时 我们应该首先进行调研 了解其特点和使用方法 在实现功能时 我们可以采用最简单的方式 而不必过于关注项目的设计和结构 一旦掌握了新技术 我们可以根据其API属性进行代码设计 以便更好地开发 以开发一个