废话不多,先上视频。
ffmpeg编辑器
用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",
创建页面和路由,用的vue-router,简单的添加一下。
router.js
{path: "/ffmpeg/app",name: "ffmpeg-app",component: () => import("../view/ffmpeg/app.vue")},
主要项目结构
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 }}/{{ unt }}</div><div class="percent" :style="progress()">{{ props.number }}/{{ unt }}</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 / untpercent = percent > 1 ? 1 : percentpercent = 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>
resource-item.vue
<template><div class="line" :title=String()"><div class="icon"><img v-if=ver" :src=ver"/><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= === 'mp4'">mp4</div><div class="mp3" v-if= === '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>
time-item.vue
<template><div class="line"draggable="true":title="props.name":style="{width:props.width + 'px','margin-left':props.left + 'px',lor}">{{ 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>
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>
file.js
import dayjs from 'dayjs'
export default class ResourceFile {constructor(file) {this.file = filethis.key = dayjs().unix() + '_' +file.namethis.name = file.namethis.size = file.sizethis.sizeStr = pe = pethis.lastModified = file.lastModifiedthis.lastModifiedDate = file.lastModifiedDatethis.lastModifiedDateStr = file.lastModifiedDatethis.webkitRelativePath = file.webkitRelativePath// 外加// 扩展名 = ''// this.baseName = dayjs().format('YYYYMMDDHHmmss') + '_' + file.namethis.baseName = this.keythis.fileType = ''this.mime = 'ver = ''this.url = ''this.durationStr = ''this.duration = ''this.bitRate = ''this.majorBrand = 'der = 'solution = ''this.fps = ''this.videoInfo = ''this.audioType = ''this.audioRate = ''this.audioInfo = ''this.setDate()}setUrl(url) {this.url = url}setCover(url){ver = url}isVideo() {return this.mime.indexOf('video') !== -1}isAudio() {return this.mime.indexOf('audio') !== -1}setMedia() {this.fileType ='media'this.mime = pe.split(',')[ = this.name.split('.')[this.name.split('.').length - 1]}setFont() {this.fileType ='font'this.mime = 'font = 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.durationStrthis.duration = info.durationthis.bitRate = info.bitRatethis.majorBrand = der = solution = solutionthis.fps = info.fpsthis.videoInfo = info.videoInfothis.audioType = info.audioTypethis.audioRate = info.audioRatethis.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/1024let M = this.size/1024/1024let K = this.size/1024console.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'}breakcase 'B':str = this.size + 'B'breakcase 'KB':str = (this.size/1024).toFixed(2) + 'KB'breakcase 'MB':str = (this.size/1024/1024).toFixed(2) + 'MB'breakcase 'GB':str = (this.size/1024/1024/1024).toFixed(2) + 'GB'breakdefault:str = this.size + 'B'}this.sizeStr = str}toString() {let str = ''str += '文件名:' + this.name +'rn'str += '时长:' + this.durationStr +'rn'str += '时长:' + this.duration +'rn'str += '比特率:' + this.bitRate +'rn'str += '格式:' + this.majorBrand +'rn'str += '编码器:' + der +'rn'str += '分辨率:' + solution +'rn'str += '帧率:' + this.fps +'rn'str += '视频信息:' + this.videoInfo +'rn'str += '音频类型:' + this.audioType +'rn'str += '采样率:' + this.audioRate +'rn'str += '音频信息:' + this.audioInfo +'rn'str += '文件唯一标识:' + this.key +'rn'str += '文件大小:' + this.size +'rn'str += '文件大小:' + this.sizeStr +'rn'str += '文件类型:' + pe +'rn'str += '最后修改:' + this.lastModified +'rn'str += '最后修改时间:' + this.lastModifiedDate +'rn'str += '最后修改时间:' + this.lastModifiedDateStr +'rn'str += 'webkit路径:' + this.webkitRelativePath +'rn'// 外加str += '基本名:' + this.baseName +'rn'str += '文件类型:' + this.fileType +'rn'str += 'mime信息:' + this.mime +'rn'str += '扩展名:' + +'rn'str += '封面:' + ver +'rn'return str}
}
line.js
import { randColor } from '@/utils/color.js'
import { uuid } from '@/utils/key.js'
/*** 时间揍单个数据*/
export default class Line{leftTime = 2constructor(file) {// 时间轴唯一this.key = uuid()this.name = pe = ''this.duration = file.durationthis.left = 0this.width = file.duration * lor = randColor()// 原始资源文件名this.fileKey = file.keythis.font = ''}setMedia() {pe = 'media'}setText() {pe = 'text'}setFont(path) {this.font = path}getFont() {return this.font}getLeftSecond() {return parseInt((this.left/this.leftTime))}getFile() {return '/'+this.fileKey}
}
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',sourceDir)this.ffmpeg.FS('mkdir',pDir)// 设置日志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') {ssage.push(clearEmpty(message))}});// 设置进度this.ffmpeg.setProgress((progress) => {this.progress.ratio = progress.ratio * 100this.progress.time = progress.timeconsole.log('进度',progress);console.log('进度',this.progress);this.updateProgress(this.progress)})}static updateProgress(progress) {console.log('进度更新了',progress)}static clearMessage() {ssage = []}static loadFile(file){// console.log('加载的文件',file)return new Promise(async (resolve) => {const filePath = '/' + sourceDir + '/' + FSName()const fileData = await File())console.log('fileData',fileData)this.ffmpeg.FS( 'writeFile' , filePath , fileData );if(file.mime){let url = ateObjectURL( new Blob( [fileData.buffer] , { type: file.mime } ) );file.setUrl(url)}if(file.isVideo()) {adCover(filePath).then(url => {file.setCover(url)// console.log('file',file)console.log('全部日志',ssage)file.setInfo(this.ssage))this.clearMessage()resolve()})}else if(file.isAudio()) {adInfo(filePath).then(() => {console.log('全部日志',ssage)file.setInfo(this.ssage))this.clearMessage()resolve()})}else{resolve()}})}static readFile(filePath) {return new Promise(async (resolve) => {const data = this.ffmpeg.FS( 'readFile' , filePath );let url = ateObjectURL( 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 = '/'pDir +'/'+ fileNamelet cmd = '-i ' + path + ' -ss 1 -f image2 ' + tmpPathlet args = cmd.split(' ')console.log('args',args)this.ffmpeg.run(...args).then(() => {// console.pDir))const data = this.ffmpeg.FS( 'readFile' , tmpPath );// console.log("文件数据",data)const fileUrl = ateObjectURL( 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 ' + pathlet 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)place(':','')}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 = durationdata.duration = parseInt(time[0])*120 + parseInt(time[1]) *60 +parseFloat(time[2])}if(message.indexOf('Duration') !== -1 && message.indexOf('bitrate') !== -1) {let bitRate = ssageGetDataCutLastR(message,'bitrate')console.log("比特率",bitRate)data.bitRate = bitRate}if(message.indexOf('major_brand') !== -1) {let majorBrand = ssageGetDataCutLastR(message,'major_brand')console.log("格式",majorBrand)data.majorBrand = majorBrand}if(message.indexOf('encoder') !== -1) {let encoder = ssageGetDataCutLastR(message,'encoder')console.log("编码器",der = 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('[')))solution=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",adDir())console.log("文件2",sourceDir))let textCmdList = []timelineList.forEach(time => {console.log('time',LeftSecond())pe === 'media') {cmd.push('-i /' + sourceDir + File())}pe === 'text') {// 阶段切换// cmd.push('-vf drawtext=fontsize=60:fontfile='/' + sourceDir +'/' Font() + '':text=' + time.name + ':fontcolor=green:enable=lt(mod(t\,3)\,1):box=1:boxcolor=yellow')// 显示cmd.push('-vf drawtext=fontsize=60:fontfile='/' + sourceDir +'/' Font() + '':text=' + time.name + ':fontcolor=green:enable='between(t,' + LeftSecond() +','+(LeftSecond() + 6)+')':box=1:boxcolor=yellow ')// 多条// textCmdList.push('drawtext=fontsize=60:fontfile='/' + sourceDir +'/' Font() + '':text=' + time.name + ':fontcolor=green:enable='between(t,' + LeftSecond() +','+(LeftSecond() + 6)+')':box=1:boxcolor=yellow')}})// const textCmd = '-vf "' + textCmdList.join(',') + '"'// console.log('文字命令',textCmd)// cmd.push(textCmd)// 添加最后输出文明cmd.derFileName)// 命令生成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)}
}
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);
}
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-itemdraggable="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"><divclass="line"draggable="true"v-for="(file,index) in timeLineList":title="file.name":style="{width:file.width + 'px','margin-left':file.left + 'px',lor}":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 = typeuploadInput.value.click()
}
// 选择文件
const changeFile = function (e) {const files = e.target.filesconst 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 = 0for ( 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 = 0for ( 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 = trueprogressCount.value = countprogressNumber.value = 0progressTitle.value = title
}
// 设置进度条值
const setLoadProgressNumber = (val) => {progressNumber.value = val
}
// 关闭进度条
const closeLoadProgress = () => {progressVisible.value = falseprogressCount.value = 0progressNumber.value = 0progressTitle.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 = filelet width = nowFile.value.duration * 2moveBlock.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.valueif(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 = indexmoveStartPosition.value.x = $event.pageXmoveStartPosition.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 - veStartPosition.value.x = 0moveStartPosition.value.y = 0moveIndex = ''
}
/*** 时间轴内容进入* @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.derFileName).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>
util.js
const filterType = ['audio','video','image']
const fontExt = ['ttc','ttf','fon']
export function checkMediaFile(file) {let status = falsefilterType.forEach(type => {pe.toLowerCase().indexOf(type) !== -1) {status = true}})return status
}export function checkFontFile(file) {pe){return false}let status = falselet nameSplit = file.name.split('.')let fileExt = nameSplit[nameSplit.length-1].toLowerCase()fontExt.forEach(type => {if(fileExt.indexOf(type) !== -1) {status = true}})return status
}
string.js
/*** ======================================* 说明:string处理* 作者:SKY* 文件:string.js* 日期:2022/11/22 16:30* ======================================*/
export function clearEmpty(val) {val = place(' ','')if(val.indexOf(' ') !== -1) {return clearEmpty( val )}else{return val}
}
key.js
/*** 生成UUID* @return {string}*/
export function uuid() {return +new Date() + Math.random()*10+ Math.random()*10+ Math.random()*10+ Math.random()*10 + 'a'
}
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})`
}
本文发布于:2024-01-31 01:33:59,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170663604224384.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |