如何利用纯前端技术,实现一个网页版视频编辑器?

纯网页版视频编辑器

一、前言

介绍:本篇文章打算利用纯前端的技术,来实现一个网页版的视频编辑器。为什么突然想做一个这么项目来呢,主要是最近一直在利用手机剪映来剪辑一些照片或者视频之类的,在剪辑的过程中,突然想到,有没有一种纯网页版的视频剪辑网站呢?于是搜了下,大多为 sass 成熟版(需要花钱的那种),然后再加上最近一直在看前端技术,于是就打算利用现学的前端技术,来实现一个纯前端的纯网页版的视频编辑器demo。

先给大家看下整体效果图:

tips:整体看上去像模像样的。

二、功能实现

这里就先简单列下具体的功能包括哪些:

  • 支持深色模式(白天/黑夜)

  • 支持云素材 (暂为mock模拟)以及本地上传素材

  • 支持拖拽添加资源

  • 支持多轨道

  • 支持表单调整资源位置、属性

  • 支持音视频裁剪

  • 支持手动添加贴图、文字

  • 支持时间轴缩放(ctrl+滚轮),最多显示30帧

  • 支持播放预览

  • 支持导出

  • 支持操作撤销、重做功能

  • 支持持久化存储功能

三、所需技术

这里也先简单列下项目中具体用到的技术包括哪些:

  • axios(^1.4.0)
  • element-plus(^2.3.4)
  • mockjs (^1.1.0)
  • pinia (^2.1.3)
  • vue(^3.2.47)
  • typescript(^5.0.2)
  • vite(^4.3.2)

插件包括:

  • commitlint(^17.6.3)
  • ffmpeg(^0.11.6)核心插件
  • cross-env(^7.0.3)
  • eslint(^8.40.0)
  • husky(^8.0.3)
  • postcss(^8.4.23)
  • prettier(^2.8.8)
  • stylelint(^15.6.1)
  • types/node(^20.1.4)
  • element-plus(^2.1.0)

四、部分功能实现

4.1 素材预设

素材预设功能,我们这里是利用了 mock 技术,来代替后端传输的数据。

先利用mock 来模拟一些素材或者进行预设,比如:

js 复制代码
const mockMethods: MockMethod[] = [
  {
    url: '/api/getResources',
    method: 'get',
    response: ({ query }) => {
      const type = query.type
      let data: ResourcesList = []
      if (type === 'video') {
        data = [
          {
            title: '转场',
            type: 'video',
            items: [
              {
                name: '故障雪花屏.mp4',
                format: 'mp4',
                cover: '/image/video/故障雪花屏.jpg',
                source: '/video/故障雪花屏.mp4',
                width: 1920,
                height: 1080,
                fps: 30,
                frameCount: 30,
                time: 1000
              }
            ]
          }
        ]
      } else if (type === 'audio') {
        data = [
          {
            title: '旋律',
            type: 'audio',
            items: [
              {
                cover: '/image/audio/Charms.jpg',
                time: 244000,
                format: 'mp3',
                name: 'Charms.mp3',
                singer: 'Abel Korzeniowski',
                source: '/audio/Abel Korzeniowski - Charms.mp3'
              }
             
            ]
          }
        ]
      } else if (type === 'text') {
        data = [
          {
            title: '热门',
            type: 'text',
            items: [
              {
                name: 'CherryBombOne.ttf',
                templateId: 0,
                source: '/text/CherryBombOne-Regular.ttf',
                format: 'truetype'
              }
          }
        ]
      } else if (type === 'image') {
        data = [
          {
            title: '热门',
            type: 'image',
            items: [
              {
                name: '666.gif',
                cover: '/image/image/666.gif',
                source: '/image/image/666.gif',
                format: 'gif',
                width: 199,
                height: 200,
                sourceFrame: 8
              }
          },
          {
            title: '经典',
            type: 'image',
            items: [
              {
                name: '喇叭.gif',
                cover: '/image/image/喇叭.gif',
                source: '/image/image/喇叭.gif',
                format: 'gif',
                width: 199,
                height: 200,
                sourceFrame: 6
              },
              {
                name: '马赛克.gif',
                cover: '/image/image/马赛克.gif',
                source: '/image/image/马赛克.gif',
                format: 'gif',
                width: 199,
                height: 200,
                sourceFrame: 6
              },
              {
                name: '马赛克小人.gif',
                cover: '/image/image/马赛克小人.gif',
                source: '/image/image/马赛克小人.gif',
                format: 'gif',
                width: 199,
                height: 200,
                sourceFrame: 6
              },
              {
                name: '闪光.gif',
                cover: '/image/image/闪光.gif',
                source: '/image/image/闪光.gif',
                format: 'gif',
                width: 199,
                height: 200,
                sourceFrame: 6
              }
        ]
      }
      return {
        code: 200,
        data
      }
    }
  }
]

export default mockMethods

代码写完后,不要忘记把素材也要放到项目文件夹里

4.2 多轨道剪辑

什么是多轨道剪辑?

多轨道编辑即是将不同的素材放置在不同的轨道上,通过调整它们在时间线上的位置和长度,达到叠加、剪辑和混合的效果。 我们可以通过拖拽素材到时间线上的不同轨道来进行多轨道编辑。

通常情况下,视频素材放置在视频轨道上,音频素材放置在音频轨道上。这样,我们可以通过调整素材在时间线上的位置和长度来控制视频和音频的播放顺序、时长和重叠关系

从技术角度来实现的话,这里就通过用 ffmpeg 技术,来实现 多轨道剪辑功能。

  1. 首先创建一个任务队列对象,来存储多轨道的数据,比如视频、音乐、文本等等素材轨道。
js 复制代码
  private ffmpeg: FFmpeg
  private taskQueue = reactive<Task[]>([]) // 任务队列
  private running = ref(false) // 运行状态
  public showLog = true
  public playTimeCache = new Map()
  public audioCache: string[] = []
  public baseCommand = new Command()
  1. 然后我们可以对其创建任务,并判断任务队列中是否有执行任务的命令,如果有则返回任务存在,如果没有则返回 undefined。
js 复制代码
 createTask(commands: string[]) {
    const task = this.existTask(commands)
    if (task) {
      return task.instance
    } else {
      const callbacks = {}
      const instance = new Promise((resolve, reject) => {
        Object.assign(callbacks, {
          resolve,
          reject
        })
      })
      this.taskQueue.push({
        instance,
        commands,
        ...callbacks
      } as Task)
      return instance
    }
  }
  1. 用户把素材资源从本地拖拽到页面内,需要获取到文件内容
js 复制代码
 // 获取文件Blob
  getFileBlob(filePath: string, fileName: string, format: string) {
    const fileBuffer = this.getFileBuffer(filePath, fileName, format)
    return new Blob([fileBuffer], {
      type: FileTypeMap[format as keyof typeof FileTypeMap]
    })
  }
  1. 最重要最核心的音频合成功能:
js 复制代码
// 音频合成
  async mergeAudio(
    start: number,
    itemList: TrackItem[],
    fileName: string,
    filePath: string
  ) {
    const { commands } = this.baseCommand.mergeAudio(
      this.pathConfig,
      start,
      itemList
    )
    if (this.audioCache.indexOf(commands.join('')) > -1) return false
    this.audioCache = [commands.join('')]
    if (this.fileExist(filePath, fileName)) this.rmFile(filePath)
    return this.createTask(commands)
  }
  1. 获取视频每一帧
js 复制代码
 // 获取视频帧图片
  getFrame(videoName: string, frameIndex: number) {
    const framePath = `${this.pathConfig.framePath}${videoName}`
    const fileName = `/pic-${frameIndex}`
    // return this.getFileBlob(framePath, fileName, 'jpg')
    return this.getFileBuffer(framePath, fileName, 'jpg')
  }

目前只是一个简易的demo,如果有需要的话,可以私戳后台,谢谢。

相关推荐
轻口味12 分钟前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami14 分钟前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
Kai HVZ23 分钟前
python爬虫----爬取视频实战
爬虫·python·音视频
吃杠碰小鸡1 小时前
lodash常用函数
前端·javascript
丰云1 小时前
一个简单封装的的nodejs缓存对象
缓存·node.js
emoji1111111 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼1 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
m0_748250031 小时前
Web 第一次作业 初探html 使用VSCode工具开发
前端·html
一个处女座的程序猿O(∩_∩)O1 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
m0_748235951 小时前
web复习(三)
前端