当前位置:   article > 正文

【JS】纯web端使用ffmpeg实现的视频编辑器_ffmpeg.js

ffmpeg.js

【JS】纯web端使用ffmpeg实现的视频编辑器

废话不多,先上视频。

ffmpeg编辑器


这是一个纯前端实现的视频编辑器,用的ffmpeg的wasm,web框架用的vue3。界面手撸。

界面效果

在这里插入图片描述

开发过程

初始化vue3框架

vite的vue3模板创建一个就可以。

安装的依赖

package.json


    "@ffmpeg/core": "^0.11.0",
    "@ffmpeg/ffmpeg": "^0.11.5",
    "dayjs": "^1.11.6",
    "less": "^4.1.2",
    "less-loader": "^11.1.0",
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

创建页面和路由,用的vue-router,简单的添加一下。
router.js

{
     path: "/ffmpeg/app",
       name: "ffmpeg-app",
       component: () => import("../view/ffmpeg/app.vue")
   },
  • 1
  • 2
  • 3
  • 4
  • 5

开发编辑器

主要项目结构
在这里插入图片描述

组件代码

progress-dialog.vue

<template>
<div class="dialog-content">
  <div class="dialog-box">
    <div class="header">
      {{ props.title }}
    </div>
    <div class="content">
      <div class="progress">
        <div class="box">
          <div class="value" >
            {{ props.number }}/{{ props.count }}
          </div>
          <div class="percent" :style="progress()">
            {{ props.number }}/{{ props.count }}
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
</template>

<script setup>
const props = defineProps({
  title: { type: String, required: false,default:'加载中' },
  number:{type:Number,required:true,default:0},
  count:{type:Number,required:true,default:100},
})
const progress = () => {
  let percent = props.number / props.count
  percent = percent > 1 ? 1 : percent
  percent = percent * 460
  // console.log('百分比',percent)
  const style = {
    clip:'rect(0px, ' + percent  + 'px, 20px, 0px)'
  }
  // console.log('style',style)
  return style
}
</script>

<style lang="less" scoped>
.dialog-content{
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: rgba(0,0,0,0.1);
  backdrop-filter: blur(10px);
  display: flex;
  justify-content: center;
  align-items: center;
}
.dialog-box{
  width: 500px;
  //height: 100px;
  background-color: #fff;
  box-shadow: 0 0 10px #222222;
  border-radius: 5px;
  .header{
    height: 30px;
    line-height: 30px;
    text-align: center;
    font-size: 14px;
    font-weight: bold;
    border-bottom: 1px solid #999;
  }
  .content{
    display: flex;
    align-items: center;
    justify-content: center;
    height: 50px;
  }
}
.progress{
  height: 20px;
  margin-left: 20px;
  margin-right: 20px;
  border:1px solid #222;
  width: 100%;
  position: relative;
  .box{
    position: relative;
  }
  .percent{
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    background-color: #dc3562;
    color:#fff;
    height: 20px;
    transition: all 0.1s;
    width: 100%;
    text-align: center;
    clip:rect(0px,0px,20px,0px);
  }
  .value{
    position: absolute;
    top: 1px;
    left: 0;
    right: 0;
    text-align: center;
    height: 20px;
    line-height: 20px;
    color:#000;
  }
}
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111

resource-item.vue

<template>
  <div class="line" :title="file.toString()">
    <div class="icon">
      <img v-if="file.cover" :src="file.cover"/>
      <span v-else>无图</span>
    </div>
    <div class="info">
      <div class="play" @click="handlePlay">播</div>
      <div class="del" @click="handleDel">删</div>
      <div class="file-type">
        <div class="mp4" v-if="file.ext === 'mp4'">mp4</div>
        <div class="mp3" v-if="file.ext === 'mp3'">mp3</div>
      </div>
      <div class="filename" :title="file.name">{{ file.name }}</div>
      <div class="k-box-flex">
        <div class="size k-flex-1">{{ file.durationStr }}</div>
<!--        <div class="size k-flex-1">{{ file.sizeStr }}</div>-->
        <div class="date">{{ file.sizeStr }}</div>
<!--        <div class="date">{{ file.lastModifiedDateStr }}</div>-->
      </div>
    </div>
  </div>
</template>

<script setup>
import ResourceFile from '@/view/ffmpeg/app/type/file.js'
import { toRef } from 'vue'
const emit = defineEmits(['del','play'])
const props = defineProps({
  file: { type: Object, required: true,default:() => new ResourceFile() }
})
const file = toRef(props, 'file')
const handleDel = () => {
  console.log("点删除")
  emit('del')
}
const handlePlay = () => {
  console.log("点播放")
  emit('play')
}
</script>

<style lang="less" scoped>
@import "../index.less";
.line{
  user-select: none;
  box-sizing: border-box;
  border: @resource-border-color solid 1px;
  height: 50px;
  display: flex;
  .icon{
    width: 50px;
    box-sizing: border-box;
    border: 1px dashed #999;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-right: 10px;
    img{
      max-width: 100%;
      max-height: 100%;
    }
  }
  .info{
    flex: 1;
    position: relative;
    padding-top: 5px;
    .del{
      position: absolute;
      right: 5px;
      top: 5px;
      width: 15px;
      height: 15px;
      line-height: 15px;
      text-align: center;
      background-color: palevioletred;
      font-size: 10px;
      color:#fff;
      cursor: pointer;
      border-radius: 2px;
    }
    .play{
      position: absolute;
      right: 25px;
      top: 5px;
      width: 15px;
      height: 15px;
      line-height: 15px;
      text-align: center;
      background-color: palevioletred;
      font-size: 10px;
      color:#fff;
      cursor: pointer;
      border-radius: 2px;
    }
    .file-type{
      position: absolute;
      right: 40px;
      top: 5px;
      height: 15px;
      line-height: 15px;
      text-align: center;
      font-size: 10px;
      border-radius: 2px;
      padding:0 5px;
      .mp4{
        color:#fff;
        background-color: #07b3c9;
      }
      .mp3{
        color:#fff;
        background-color: #d9b608;
      }
    }
    .filename{
      font-size: 6px;
      width: 220px;
      height: 20px;
      overflow: hidden;
      white-space:nowrap;/*不显示的地方用省略号...代替*/
      text-overflow:ellipsis;/* 支持 IE */
    }
    .size{
      font-size: 6px;
      text-align: left;
    }
    .date{
      font-size: 6px;
      text-align: right;
      margin-right: 5px;
    }
  }
}
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135

time-item.vue

<template>
  <div class="line"
       draggable="true"
       :title="props.name"
       :style="{
      width:props.width + 'px',
      'margin-left':props.left + 'px',
      background:props.color
  }">
    {{ props.name }}
  </div>
</template>

<script setup>
const props = defineProps({
    name: { type: String, required: false,default:'文件名' },
    color: { type: String, required: false,default:'' },
    left:{type:Number,required:true,default:0},
    width:{type:Number,required:true,default:10},
})
</script>

<style lang="less" scoped>
@import "../index.less";
.line{
  cursor: move;
  height: 20px;
  white-space: nowrap; /*不显示的地方用省略号...代替*/
  text-overflow: ellipsis; /* 支持 IE */
  line-height: 20px;
  padding-left: 10px;
  box-sizing: border-box;
  border-bottom: @border-color 1px solid;
  background-color: rgba(248, 235, 174, 0.78);
  user-select: none;
  overflow: hidden;
  &:last-child{
    border-bottom: none;
  }
}
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

tool-tab.vue

<template>
  <div class="tab">
    字母
  </div>
  <div class="tool-content">
    <input type="text" v-model="text">
    <button @click="handleCreate">添加</button>
    <button @click="handleRender">渲染</button>
  </div>
</template>

<script setup>
import {ref} from 'vue'
const text = ref('')
const emit = defineEmits(['create','render'])
const handleCreate = () => {
    console.log("添加",text.value)
    emit('create', text.value)
    text.value = ''
}
const handleRender = () =>{
    console.log('渲染')
    emit('render')
}
</script>

<style scoped>

</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

class代码

file.js

import dayjs from 'dayjs'
export default class ResourceFile {
    constructor(file) {
        this.file = file
        this.key = dayjs().unix() + '_' +file.name
        this.name = file.name
        this.size = file.size
        this.sizeStr = file.size
        this.type = file.type
        this.lastModified = file.lastModified
        this.lastModifiedDate = file.lastModifiedDate
        this.lastModifiedDateStr = file.lastModifiedDate
        this.webkitRelativePath = file.webkitRelativePath
        // 外加
        // 扩展名
        this.ext = ''
        // this.baseName = dayjs().format('YYYYMMDDHHmmss') + '_' + file.name
        this.baseName = this.key
        this.fileType = ''
        this.mime = ''
        this.cover = ''
        this.url = ''
        this.durationStr = ''
        this.duration = ''
        this.bitRate = ''
        this.majorBrand = ''
        this.encoder = ''
        this.resolution = ''
        this.fps = ''
        this.videoInfo = ''
        this.audioType = ''
        this.audioRate = ''
        this.audioInfo = ''
        this.setDate()
    }

    setUrl(url) {
        this.url = url
    }
    setCover(url){
        this.cover = url
    }
    isVideo() {
        return this.mime.indexOf('video') !== -1
    }
    isAudio() {
        return this.mime.indexOf('audio') !== -1
    }
    setMedia() {
        this.fileType ='media'
        this.mime = this.file.type.split(',')[0]
        this.ext = this.name.split('.')[this.name.split('.').length - 1]
    }
    setFont() {
        this.fileType ='font'
        this.mime = 'font'
        this.ext = this.name.split('.')[this.name.split('.').length - 1]
    }
    getFile() {
        return this.file
    }
    getFSName() {
        return this.baseName
    }
    setDate() {
        this.lastModifiedDateStr = dayjs(this.lastModifiedDate).format('YYYY-MM-DD HH:mm:ss')
    }
    setInfo(info) {
        this.durationStr = info.durationStr
        this.duration = info.duration
        this.bitRate = info.bitRate
        this.majorBrand = info.majorBrand
        this.encoder = info.encoder
        this.resolution = info.resolution
        this.fps = info.fps
        this.videoInfo = info.videoInfo
        this.audioType = info.audioType
        this.audioRate = info.audioRate
        this.audioInfo = info.audioInfo
    }
    setSize(type = ''){
        let str = ''
        console.log('size',type,this.size,this.size/1024)
        switch (type) {
            case 'AUTO':
                let G = this.size/1024/1024/1024
                let M = this.size/1024/1024
                let K = this.size/1024
                console.log(G,M,K)
                if(G > 1){
                    str = G.toFixed(2) + 'GB'
                }else if(M >1) {
                    str = M.toFixed(2) + 'MB'
                }else if(K > 1) {
                    str = K.toFixed(2) + 'KB'
                }else{
                    str = this.size + 'B'
                }
                break
            case 'B':
                str = this.size + 'B'
                break
            case 'KB':
                str = (this.size/1024).toFixed(2) + 'KB'
                break
            case 'MB':
                str = (this.size/1024/1024).toFixed(2) + 'MB'
                break
            case 'GB':
                str = (this.size/1024/1024/1024).toFixed(2) + 'GB'
                break
            default:
                str = this.size + 'B'
        }
        this.sizeStr = str
    }

    toString() {
        let str = ''
        str += '文件名:' + this.name +'\r\n'
        str += '时长:' + this.durationStr +'\r\n'
        str += '时长:' + this.duration +'\r\n'
        str += '比特率:' + this.bitRate +'\r\n'
        str += '格式:' + this.majorBrand +'\r\n'
        str += '编码器:' + this.encoder +'\r\n'
        str += '分辨率:' + this.resolution +'\r\n'
        str += '帧率:' + this.fps +'\r\n'
        str += '视频信息:' + this.videoInfo +'\r\n'
        str += '音频类型:' + this.audioType +'\r\n'
        str += '采样率:' + this.audioRate +'\r\n'
        str += '音频信息:' + this.audioInfo +'\r\n'
        str += '文件唯一标识:' + this.key +'\r\n'
        str += '文件大小:' + this.size +'\r\n'
        str += '文件大小:' + this.sizeStr +'\r\n'
        str += '文件类型:' + this.type +'\r\n'
        str += '最后修改:' + this.lastModified +'\r\n'
        str += '最后修改时间:' + this.lastModifiedDate +'\r\n'
        str += '最后修改时间:' + this.lastModifiedDateStr +'\r\n'
        str += 'webkit路径:' + this.webkitRelativePath +'\r\n'
        // 外加
        str += '基本名:' + this.baseName +'\r\n'
        str += '文件类型:' + this.fileType +'\r\n'
        str += 'mime信息:' + this.mime +'\r\n'
        str += '扩展名:' + this.ext +'\r\n'
        str += '封面:' + this.cover +'\r\n'
        return str
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149

line.js

import { randColor } from '@/utils/color.js'
import { uuid } from '@/utils/key.js'
/**
 * 时间揍单个数据
 */
export default class Line{
    leftTime = 2
    constructor(file) {
        // 时间轴唯一
        this.key = uuid()
        this.name = file.name
        this.type = ''
        this.duration = file.duration
        this.left = 0
        this.width = file.duration * this.leftTime
        this.color = randColor()
        // 原始资源文件名
        this.fileKey = file.key
        this.font = ''
    }

    setMedia() {
        this.type = 'media'
    }
    setText() {
        this.type = 'text'
    }
    setFont(path) {
        this.font = path
    }
    getFont() {
        return this.font
    }
    getLeftSecond() {
        return parseInt((this.left/this.leftTime))
    }
    getFile() {
        return '/'+this.fileKey
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

主要的代码

ffmpeg.js

import { clearEmpty } from '@/utils/string.js'
import { createFFmpeg , fetchFile } from '@ffmpeg/ffmpeg'
import dayjs from 'dayjs'
/**
 * ======================================
 * 说明:需要用到的ffmpeg操作封装一下
 * 作者: YYDS
 * 文件: ffmpeg.js
 * 日期: 2023/3/29 11:08
 * ======================================
 */

export default class Ffmpeg {
    static ffmpeg = ''
    // 进度输出
    static progress = {
        /*
         * ratio is a float number between 0 to 1.
         */
        ratio:0,
        time:0
    }
    // 日志输出
    static message = []
    // 资源目录
    static resourceDir = 'resource'
    // 缓存目录
    static tmpDir = 'mediaTmp'
    // 渲染完的文件名
    static renderFileName = 'render.mp4'
    static async instance  () {
        this.ffmpeg = createFFmpeg( {
            log: true
        })
        await this.ffmpeg.load();
        this.ffmpeg.FS('mkdir',this.resourceDir)
        this.ffmpeg.FS('mkdir',this.tmpDir)
        // 设置日志
        this.ffmpeg.setLogger(({ type, message }) => {
            // console.log('日志',type, message);
            /*
             * type can be one of following:
             *
             * info: internal workflow debug messages
             * fferr: ffmpeg native stderr output
             * ffout: ffmpeg native stdout output
             */
            if(type === 'fferr') {
                this.message.push(clearEmpty(message))
            }
        });
        // 设置进度
        this.ffmpeg.setProgress((progress) => {
            this.progress.ratio = progress.ratio * 100
            this.progress.time = progress.time
            console.log('进度',progress);
            console.log('进度',this.progress);
            this.updateProgress(this.progress)
        })
    }
    static updateProgress(progress) {
        console.log('进度更新了',progress)
    }
    static clearMessage() {
        this.message = []
    }
    static  loadFile(file){
        // console.log('加载的文件',file)
        return new Promise(async (resolve) => {
            const filePath = '/' + this.resourceDir + '/' + file.getFSName()
            const fileData = await fetchFile(file.getFile())
            console.log('fileData',fileData)
            this.ffmpeg.FS( 'writeFile' , filePath , fileData );
            if(file.mime){
                let url = URL.createObjectURL( new Blob( [fileData.buffer] , { type: file.mime } ) );
                file.setUrl(url)
            }
            if(file.isVideo()) {
                this.readCover(filePath).then(url => {
                    file.setCover(url)
                    // console.log('file',file)
                    console.log('全部日志',this.message)
                    file.setInfo(this.fileInfoFilter(this.message))
                    this.clearMessage()
                    resolve()
                })
            }else if(file.isAudio()) {
                this.readInfo(filePath).then(() => {
                    console.log('全部日志',this.message)
                    file.setInfo(this.fileInfoFilter(this.message))
                    this.clearMessage()
                    resolve()
                })
            }else{
                resolve()
            }
        })

    }

    static readFile(filePath) {
        return new Promise(async (resolve) => {
            const data = this.ffmpeg.FS( 'readFile' , filePath );
            let url = URL.createObjectURL( new Blob( [data.buffer] , { type: 'video/mp4' } ) );
            resolve(url)
        })
    }

    static async readCover (path)  {
        return new Promise(async (resolve, reject) => {
            const fileName = dayjs().valueOf()+'.jpg'
            const tmpPath = '/'+this.tmpDir +'/'+ fileName
            let cmd = '-i ' + path + ' -ss 1 -f image2 ' + tmpPath
            let args = cmd.split(' ')
            console.log('args',args)
            this.ffmpeg.run(...args).then(() => {
                // console.log(this.readDir(this.tmpDir))
                const data = this.ffmpeg.FS( 'readFile' , tmpPath );
                // console.log("文件数据",data)
                const fileUrl = URL.createObjectURL( new Blob( [data.buffer] , { type: 'image/jpeg' } ) );
                // console.log('文件url',fileUrl)
                resolve(fileUrl)
            })
        })
    }
    static async readInfo (path)  {
        return new Promise(async (resolve, reject) => {
            const fileName = dayjs().valueOf()+'.jpg'
            let cmd = '-i ' + path
            let args = cmd.split(' ')
            console.log('args',args)
            this.ffmpeg.run(...args).then(() => {
                resolve()
            })
        })
    }
    static  readDir (path = '')  {
        let list = this.ffmpeg.FS( 'readdir' , '/' + path )
        console.log('list',list)
        return list
    }
    static messageGetDataCutLastR(message,key) {
        let str = message.substring(message.indexOf(key) + key.length)
        return str.replace(':','')
    }
    static  fileInfoFilter (messageList) {
        const data = {
            durationStr:'',
            duration:'',
            bitRate:'',
            majorBrand:'',
            encoder:'',
            resolution:'',
            fps:'',
            videoInfo:'',
            audioType:'',
            audioRate:'',
            audioInfo:''
        }
        messageList.forEach(message => {
            if(message.indexOf('Duration') !== -1) {
                let duration = message.substring(message.indexOf('Duration:') + 'Duration:'.length ,message.indexOf('Duration:')+ 'Duration:'.length + '00:00:20.48'.length)
                console.log("时长",duration)
                let time = duration.split(':')
                console.log('time',time)
                data.durationStr = duration
                data.duration = parseInt(time[0])*120 + parseInt(time[1]) *60 +parseFloat(time[2])
            }
            if(message.indexOf('Duration') !== -1 && message.indexOf('bitrate') !== -1) {
                let bitRate = this.messageGetDataCutLastR(message,'bitrate')
                console.log("比特率",bitRate)
                data.bitRate = bitRate
            }
            if(message.indexOf('major_brand') !== -1) {
                let majorBrand = this.messageGetDataCutLastR(message,'major_brand')
                console.log("格式",majorBrand)
                data.majorBrand = majorBrand
            }
            if(message.indexOf('encoder') !== -1) {
                let encoder = this.messageGetDataCutLastR(message,'encoder')
                console.log("编码器",encoder)
                data.encoder = encoder
            }
            if(message.indexOf('Video:') !== -1) {
                let key = 'Video:'
                let arr = message.substring(message.indexOf(key) + key.length)
                let arrList =  arr.split(',')
                console.log("视频信息",arr)
                console.log("分辨率",arrList[2].substring(0,arrList[2].indexOf('[')))
                data.resolution=arrList[2].substring(0,arrList[2].indexOf('['))
                arrList.forEach(v=>{
                    if(v.indexOf('fps') !== -1) {
                        console.log("帧率",v)
                        data.fps=v
                    }
                })
                data.videoInfo=arr
            }
            if(message.indexOf('Audio:') !== -1) {
                let key = 'Audio:'
                let arr = message.substring(message.indexOf(key) + key.length)
                let arrList =  arr.split(',')
                console.log("音频信息",arr,)
                console.log("音频格式",arrList[0])
                console.log("音频采样率",arrList[1])
                data.audioType=arrList[0]
                data.audioRate=arrList[1]
                data.audioInfo=arr
            }
        })
        console.log('信息',data)
        return data
    }
    static generateArgs(timelineList) {
        const cmd = []
        console.log('时间轴数据',timelineList)
        console.log("文件1",this.readDir())
        console.log("文件2",this.readDir(this.resourceDir))
        let textCmdList = []
        timelineList.forEach(time => {
            console.log('time',time,time.getLeftSecond())
            if(time.type === 'media') {
                cmd.push('-i /' + this.resourceDir  + time.getFile())
            }
            if(time.type === 'text') {
                // 阶段切换
                // cmd.push('-vf drawtext=fontsize=60:fontfile=\'/' + this.resourceDir +'/' +time.getFont() + '\':text=' + time.name + ':fontcolor=green:enable=lt(mod(t\\,3)\\,1):box=1:boxcolor=yellow')
                // 显示
                cmd.push('-vf drawtext=fontsize=60:fontfile=\'/' + this.resourceDir +'/' +time.getFont() + '\':text=' + time.name + ':fontcolor=green:enable=\'between(t,' + time.getLeftSecond() +','+(time.getLeftSecond() + 6)+')\':box=1:boxcolor=yellow ')
                // 多条
                // textCmdList.push('drawtext=fontsize=60:fontfile=\'/' + this.resourceDir +'/' +time.getFont() + '\':text=' + time.name + ':fontcolor=green:enable=\'between(t,' + time.getLeftSecond() +','+(time.getLeftSecond() + 6)+')\':box=1:boxcolor=yellow')
            }
        })
        // const textCmd = '-vf "' + textCmdList.join(',') + '"'
        // console.log('文字命令',textCmd)
        // cmd.push(textCmd)
        // 添加最后输出文明
        cmd.push(this.renderFileName)
        // 命令生成
        let args = cmd.join(' ')
        args = args.split(' ')
        console.log('命令',args)
        // const cmd = '-i infile -vf movie=watermark.png,colorkey=white:0.01:1.0[wm];[in][wm]overlay=30:10[out] outfile.mp4'
        // const cmd = '-re -i infile -vf drawtext=fontsize=60:fontfile=\'font\':text=\'%{localtime\\:%Y\\-%m\\-%d%H-%M-%S}\':fontcolor=green:box=1:boxcolor=yellow outfile.mp4'
        // let args = cmd.split(' ')
        // console.log('args',args)
        return args
    }

    static async run(args) {
        console.log("运行命令",args)
        await this.ffmpeg.run(...args)
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255

index.less

@border-color:#222;
@resource-border-color:#999;
@resource-width:300px;
::-webkit-scrollbar {
  width: 5px;
  height: 10px;
  background-color: #ebeef5;
}
::-webkit-scrollbar-thumb {
  box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
  background-color: #ccc;
}
::-webkit-scrollbar-track{
  box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  border-radius: 3px;
  background: rgba(255, 255, 255, 1);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

index.vue

<template>
  <div class="app-container">
    <div class="resource-list">
      <div class="btn-bar">
        <input ref="uploadInput" v-show="false" type="file" multiple @change="changeFile"/>
        <button @click="addFile('media')">添加媒体</button>
        <button @click="addFile('font')">添加字体</button>
        <button @click="addDirectory">添加文件夹</button>
      </div>
      <div class="file-list">
        <resource-item
                draggable="true"
                v-for="(item,index) in mediaList"
                :file="item"
                :key="item.key"
                @play="handlePlay(item)"
                @del="handleDel(index)"
                @dragstart.private="fileDragStart($event,item)"
                @dragend.private="fileDragEnd($event,item)"
                @dblclick="appendFile(item)"
        />
      </div>
    </div>
    <div class="view">
      <div class="window">
        <div class="screen">
          <video :src="previewSrc" controls autoplay></video>
        </div>
        <div class="screen">
          <video :src="renderSrc" controls autoplay></video>
        </div>
      </div>
      <div class="time-line"
           @dragenter="lineDragEnter"
           @dragleave="lineDragLeave"
           @dragover="lineDragOver"
           @drop="lineDropFile">
        <div
          class="line"
          draggable="true"
          v-for="(file,index) in timeLineList"
          :title="file.name"
          :style="{
              width:file.width + 'px',
              'margin-left':file.left + 'px',
              background:file.color
          }"
          :key="'timeLine' + file.key"
          @dragstart="lineDragStart($event,index)"
          @dragend="lineDragEnd"
          @dragenter="lineItemDragEnter($event,index)"
          @dragleave="lineItemDragLeave"
          @dragover="lineItemDragMove"
          @drop.prevent.stop="lineItemDropFile(index)"
        >{{ file.name }}
        </div>
      </div>
      <div class="tool-bar">
        <tool-tab @create="handleCreateText" @render="handleRender"/>
      </div>
    </div>
    <!--    加载弹窗-->
    <progress-dialog :title="progressTitle" v-if="progressVisible" :number="progressNumber" :count="progressCount"/>
    <!--    时间轴示例-->
    <div class="hidden">
        <div id="move" ref="moveBlock">
            {{ nowFile.name }}
        </div>
    </div>
  </div>
</template>

<script setup>
import ResourceFile from '@/view/ffmpeg/app/type/file.js'
import { checkFontFile , checkMediaFile } from '@/view/ffmpeg/app/util.js'
import ft from './ffmpeg.js'
import { reactive , ref } from 'vue'
import ResourceItem from './component/resource-item.vue'
import ToolTab from './component/tool-tab.vue'
import ProgressDialog from '@/view/ffmpeg/app/component/progress-dialog.vue'
ft.instance()
const uploadInput = ref(null)
const previewSrc = ref('')
const renderSrc = ref('')
// 进度条
const progressTitle = ref('')
const progressNumber = ref(0)
const progressCount = ref(100)
const progressVisible = ref(false)
// 媒体资源 图片 视频 音频
const mediaList = reactive([])
// 字体资源
const fontList = reactive([])
// 添加文件
let addType = ''

const addDirectory = () => {
    console.log("未实现")
    alert('未实现')
}
const addFile = (type) => {
  addType = type
  uploadInput.value.click()
}
// 选择文件
const changeFile = function (e) {
  const files = e.target.files
  const mediaLoadList = []
  const fontLoadList = []
  console.log('文件列表',files)
  for ( let i = 0 ; i < files.length ; i++ ) {
    const file = new ResourceFile(files[i])
    console.log('文件',file)
    file.setSize('AUTO')
    if(addType === 'media'){
      if(checkMediaFile(file)){
        file.setMedia()
        mediaLoadList.push(file)
        // mediaList.push(file)
        continue
      }
    }
    if(addType === 'font'){
      if(checkFontFile(file)){
        file.setFont()
        fontLoadList.push(file)
        // fontList.push(file)
      }
    }
  }
    if(addType === 'media'){
        loadMediaFile(mediaLoadList)
    }
    if(addType === 'font'){
        loadFontFile(fontLoadList)
    }


}
// 加载文件
const loadMediaFile = async (list) => {
  console.log('加载文件',list)
  openLoadProgress(list.length,'加载资源文件')
  let i = 0
  for ( const file of list ) {
    await ft.loadFile(file)
    mediaList.push(file)
    i++
    setLoadProgressNumber(i)
  }
  setTimeout(() => {
    closeLoadProgress()
  },100)
}
const loadFontFile = async (list) => {
  console.log('加载文件',list)
  openLoadProgress(list.length,'加载字体文件')
  let i = 0
  for ( const file of list ) {
    await ft.loadFile(file)
    fontList.push(file)
    i++
    setLoadProgressNumber(i)
  }
  setTimeout(() => {
    closeLoadProgress()
  },100)
}

const handlePlay = (file) => {
  console.log("播放文件",file)
  previewSrc.value=file.url
}
const handleDel = (index) => {
  console.log("删除文件",index)
  mediaList.splice(index,1)
}
// 打开进度条
const openLoadProgress = (count,title = '加载中') => {
  progressVisible.value = true
  progressCount.value = count
  progressNumber.value = 0
  progressTitle.value = title
}
// 设置进度条值
const setLoadProgressNumber = (val) => {
  progressNumber.value = val
}
// 关闭进度条
const closeLoadProgress = () => {
  progressVisible.value = false
  progressCount.value = 0
  progressNumber.value = 0
  progressTitle.value = ''
}

import Line from './type/line.js'
// 时间轴
const timeLineList = ref([])
const moveStartPosition = ref({x:0,y:0})
let moveIndex = ''
let moveIn = ''
// 拖动类型
let dragType = 'create'
// 拖动的当前文件
const nowFile = ref({})
// 拖动的dom
const moveBlock = ref( null )
// 是否拖入时间揍
const lineIn = ref( false )
/**
 * 文件列表拖拽开始
 * @param $event
 * @param file
 */
const fileDragStart = ( $event , file ) => {
    console.log( '文件列表拖拽开始' , $event , file )
    dragType = 'create'
    nowFile.value = file
    let width = nowFile.value.duration * 2
    moveBlock.value.style.width = (width > 270 ? 270 : width) + 'px'
    $event.dataTransfer.setDragImage( moveBlock.value , 0 , 0 )
}
/**
 * 文件列表拖拽结束
 * @param $event
 * @param file
 */
const fileDragEnd = ( $event , file ) => {
    console.log( '文件列表拖拽结束' , $event , file )
    lineIn.value = false
}
/**
 * 添加到时间轴最后
 * @param item
 */
const appendFile = (item)=> {
    console.log('双击添加',item)
    const file = new Line(item)
    file.setMedia()
    console.log( 'file' , file )
    timeLineList.value.push( file )
}

/**
 * 时间轴放入
 * @param index
 */
const lineItemDropFile = ( index ) => {
    console.log( '时间轴放入' , index )
    // 放在某个轴上
    if ( dragType === 'create' ) {
        const file = new Line(mediaList[index])
        file.setMedia()
        console.log( 'file' , file )
        timeLineList.value.splice(index,0,file)
    }
    // 移动
    if ( dragType === 'move' ) {
        console.log( '移动' ,moveIndex,index)
        let list = timeLineList.value
        if(moveIndex > index){
            const item = timeLineList.value[moveIndex]
            list.splice(moveIndex,1)
            list.splice(index,0,item)
        }else{
            const item = timeLineList.value[moveIndex]
            console.log('移动',item,list)
            list.splice(moveIndex,1)
            list.splice(index,0,item)
            console.log("移动到后面",list)
        }
        timeLineList.value = list
    }
}
const lineDragStart = ( $event , index ) => {
    console.log( '时间轴拖动开始' , index,$event )
    dragType = 'move'
    moveIndex = index
    moveStartPosition.value.x = $event.pageX
    moveStartPosition.value.y = $event.pageY
}
/**
 * 时间轴拖动结束
 * @param $event
 * @constructor
 */
const lineDragEnd = ( $event ) => {
    console.log( '时间轴拖动结束' , $event )
    console.log('移动了',$event.pageX - moveStartPosition.value.x,$event.pageY-moveStartPosition.value.y)
    timeLineList.value[moveIndex].left+=$event.pageX - moveStartPosition.value.x
    moveStartPosition.value.x = 0
    moveStartPosition.value.y = 0
    moveIndex = ''
}
/**
 * 时间轴内容进入
 * @param $event
 * @constructor
 */
const lineItemDragEnter = ( $event,index ) => {
    console.log( '时间轴进入' , $event )
    $event.preventDefault(); //阻止默认事件
    moveIn = index
}
/**
 * 时间轴内容离开
 * @param $event
 * @constructor
 */
const lineItemDragLeave = ( $event  ) => {
    console.log( '时间轴离开' , $event )
    $event.preventDefault(); //阻止默认事件
    moveIn = ''
}
/**
 * 时间轴内容移动
 * @param $event
 */
const lineItemDragMove = ($event) => {
    console.log("拖拽移动",$event)
    console.log('移动了',$event.pageX - moveStartPosition.value.x,$event.pageY-moveStartPosition.value.y)
}
/**
 * 时间轴进入
 * @param $event
 * @constructor
 */
const lineDragEnter = ( $event ) => {
    console.log( '时间列表进入' , $event )
    $event.preventDefault(); //阻止默认事件
    lineIn.value = true
}

/**
 * 时间轴离开
 * @param $event
 * @param file
 * @constructor
 */
const lineDragLeave = ( $event , file ) => {
    console.log( '时间列表离开' , $event )
    $event.preventDefault(); //阻止默认事件
    lineIn.value = false
}

/**
 * 时间轴阻止默认
 * @param $event
 * @constructor
 */
const lineDragOver = ( $event ) => {
    $event.preventDefault(); //阻止默认事件
}

/**
 * 时间轴放入
 * @param $event
 */
const lineDropFile = ( $event ) => {
    console.log( '时间列表放入' ,dragType, $event )
    // 放在空的地方
    if ( dragType === 'create' ) {
        let file = new Line(nowFile.value)
        file.setMedia()
        console.log( 'file' , file )
        timeLineList.value.push( file )
    }
}

const handleCreateText = (text) => {
    let data = {
        key : '',
        name : text,
        duration : 30,
        left : 0
    }
    const item = new Line(data)
    item.setText()
    item.setFont(fontList[0].getFSName())
    console.log('添加',item,fontList[0].getFSName())
    timeLineList.value.push(item)
}
const handleRender = () => {
    console.log("渲染视频")
    let args = ft.generateArgs(timeLineList.value)
    ft.run( args )
    console.log('ft.progress',ft.progress)
    openLoadProgress(100,'渲染中')
    ft.updateProgress = updateRender
}
const updateRender = (progress) => {
    setLoadProgressNumber(parseInt(progress.ratio))
    if(progress.ratio >= 100) {
        setTimeout(() => {
            closeLoadProgress()
            previewRender()
        },1000)

    }
}
const previewRender = () => {
    ft.readFile(ft.renderFileName).then(res => {
        console.log("文件",res)
        renderSrc.value = res
    })
}
window.ft = ft
</script>

<style lang="less" scoped>
@import "index.less";
.app-container{
  width: 100vw;
  height: 100vh;
  display: flex;
  position: relative;
}
.resource-list{
  display: flex;
  flex-direction:column;
  width: @resource-width;
  height: 100%;
  box-sizing: border-box;
  border-right:  @border-color 1px solid;
  .btn-bar{
    display: flex;
    border-bottom: @border-color 1px solid;
    button{
      flex: 1;
      margin:5px;
    }
  }
  .file-list{
    width: 300px;
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
  }
}
.view{
  flex: 1;
  display: flex;
  flex-direction:column;
  .window{
    flex:1;
    box-sizing: border-box;
    border-bottom: @border-color 1px solid;
    display: flex;
    .screen{
      flex: 1;
      box-sizing: border-box;
      display: flex;
      justify-content: center;
      align-items: center;
      &:first-child{
        border-right: @border-color 1px solid;
      }
      video{
        width: 100%;
        max-height: 100%;
        //max-width: 1024px;
        //max-height: 768px;
      }
    }
  }
  .time-line{
    width: calc(100vw - @resource-width);
    height: 300px;
    box-sizing: border-box;
    border-bottom: @border-color 1px solid;
    overflow-x: scroll;
    .line{
      cursor: move;
      height: 20px;
      white-space: nowrap; /*不显示的地方用省略号...代替*/
      text-overflow: ellipsis; /* 支持 IE */
      line-height: 20px;
      padding-left: 10px;
      box-sizing: border-box;
      border-bottom: @border-color 1px solid;
      background-color: rgba(248, 235, 174, 0.78);
      user-select: none;
      overflow: hidden;
      &:last-child{
        border-bottom: none;
      }
    }
  }
  .tool-bar{
    height: 100px;
    box-sizing: border-box;
  }
}

.hidden {
  position: fixed;
  left: 0;
  top: -100px;

  #move {
    min-width: 20px;
    height: 20px;
    background: red;
    border: 1px solid #07b3c9;
    overflow: hidden;
  }
}
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510

util.js

const filterType = ['audio','video','image']
const fontExt = ['ttc','ttf','fon']
export function checkMediaFile(file) {
    let status = false
    filterType.forEach(type => {
        if(file.type.toLowerCase().indexOf(type) !== -1) {
            status = true
        }
    })
    return status
}

export function checkFontFile(file) {
    if(file.type){
        return false
    }
    let status = false
    let nameSplit = file.name.split('.')
    let fileExt = nameSplit[nameSplit.length-1].toLowerCase()
    fontExt.forEach(type => {
        if(fileExt.indexOf(type) !== -1) {
            status = true
        }
    })
    return status
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

string.js

/**
 * ======================================
 * 说明:string处理
 * 作者:SKY
 * 文件:string.js
 * 日期:2022/11/22 16:30
 * ======================================
 */
export function clearEmpty(val) {
    val = val.replace(' ','')
    if(val.indexOf(' ') !== -1) {
        return clearEmpty( val )
    }else{
        return val
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

key.js

/**
 * 生成UUID
 * @return {string}
 */
export function uuid() {
    return +new Date() + Math.random()*10+ Math.random()*10+ Math.random()*10+ Math.random()*10 + 'a'
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

color.js

/**
 * 随机生成颜色
 */
export function randColor() {
    const r = parseInt(Math.random() * 255)
    const g = parseInt(Math.random() * 255)
    const b = parseInt(Math.random() * 255)
    return `rgb(${r},${g},${b})`
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/229156
推荐阅读
相关标签
  

闽ICP备14008679号