-
-
Save wangsrGit119/46380e8bedc48157037c4ca622b7f30e to your computer and use it in GitHub Desktop.
lav-tools
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div class="container"> | |
<el-row style="border: 1px black solid;"> | |
<el-col :span="12" style="position: relative;"> | |
<div style="position: absolute;left: 5px;top: 5px;z-index: 99;display: flex;justify-content: space-between;width: 95%;"> | |
<el-tag class="ml-2" size="small" type="success">预览</el-tag> | |
<el-button class="ml-2" size="small" type="danger" @click="downloadVideo">录屏/像下载</el-button> | |
</div> | |
<video | |
id="player" | |
controls | |
autoplay | |
muted | |
/> | |
</el-col> | |
<el-col :span="12" style="position: relative;"> | |
<div style="position: absolute;left: 5px;top: 5px;z-index: 99;display: flex;justify-content: space-between;width: 95%;"> | |
<el-tag class="ml-2" size="small" type="success">预览</el-tag> | |
<el-button class="ml-2" size="small" type="danger" @click="downloadGif()">GIF下载</el-button> | |
</div> | |
<img id="imagePre" /> | |
</el-col> | |
</el-row> | |
<el-row style="border: 1px black solid;"> | |
<el-col :span="12" style="position: relative;"> | |
<div style="position: absolute;left: 5px;top: 5px;z-index: 99;display: flex;justify-content: space-between;width: 95%;"> | |
<el-tag class="ml-2" size="small" type="success">转码预览</el-tag> | |
<el-button class="ml-2" size="small" type="danger" @click="transFileDown">转码文件下载</el-button> | |
</div> | |
<video | |
id="playerForTransf" | |
controls | |
autoplay | |
muted | |
/> | |
</el-col> | |
</el-row> | |
<el-row class="bottom-btn" style="width: 100%; display: flex;flex-direction: row;justify-content: center;"> | |
<!-- <el-upload | |
ref="uploadRef" | |
class="upload-demo" | |
action="no" | |
:on-change="(file) => playVideoFile(file)" | |
:auto-upload="false" | |
> | |
<template #trigger> | |
<el-button type="primary">本地文件</el-button> | |
</template> | |
</el-upload> --> | |
<el-button type="primary" @click="transcode"> | |
转码 | |
</el-button> | |
<el-button v-if="recordStatus==='inactive'" type="primary" @click="screenRecord"> | |
录屏 | |
</el-button> | |
<el-button v-if="recordStatus==='inactive'" type="primary" @click="localCamRecord"> | |
录像 | |
</el-button> | |
<!-- <el-button v-if="recordStatus==='recording'" type="danger" @click="pauseRecord"> | |
暂停录屏/录像 | |
</el-button> | |
<el-button v-if="recordStatus==='paused'" type="danger" @click="resumeRecord"> | |
继续录屏/录像 | |
</el-button> --> | |
<el-button v-if="recordStatus==='recording'" type="danger" @click="stopRecord"> | |
结束录屏/录像 | |
</el-button> | |
<el-button v-if="chunks" type="primary" @click="playRec"> | |
录像回放 | |
</el-button> | |
<el-button type="primary" @click="showGifParams"> | |
转换GIF | |
</el-button> | |
</el-row> | |
<el-dialog v-model="dialogTableVisibleForGifParams" title="GIF参数设置"> | |
<el-form | |
label-position="left" | |
label-width="100px" | |
:model="formForGifParams" | |
style="max-width: 400px" | |
> | |
<el-form-item label="时长"> | |
<el-slider :min="3" :max="50" v-model="formForGifParams.time" /> | |
</el-form-item> | |
<el-form-item label="清晰度"> | |
<el-select v-model="formForGifParams.scale" class="m-2" placeholder="请选择画质" size="small"> | |
<el-option | |
v-for="item in [1080,720,480]" | |
:key="item" | |
:label="item" | |
:value="item" | |
/> | |
</el-select> | |
</el-form-item> | |
<el-form-item label="FPS"> | |
<el-slider :min="10" :max="25" v-model="formForGifParams.fps" /> | |
</el-form-item> | |
</el-form> | |
<template #footer> | |
<span class="dialog-footer"> | |
<el-button type="primary" @click="startToGif"> | |
开始转换 | |
</el-button> | |
</span> | |
</template> | |
</el-dialog> | |
</div> | |
</template> | |
<script> | |
import { createFFmpeg, fetchFile } from "@ffmpeg/ffmpeg"; | |
import { ElNotification } from 'element-plus' | |
import {ref, reactive} from 'vue' | |
import getBlobDuration from 'get-blob-duration' | |
export default { | |
name: 'WasmFfmpeg', | |
components:{ | |
}, | |
data() { | |
return { | |
dialogTableVisibleForGifParams:false,//gif | |
formForGifParams:{ | |
time:5, | |
scale:720,//1080p 720p | |
fps:15,//FPS | |
}, | |
maxTime:100, | |
} | |
}, | |
setup(){ | |
var video = undefined; | |
var mediaRecorder = undefined; | |
var recordMediaType = 'video/webm'; | |
var stream; | |
var recordStatus = ref('inactive'); | |
var chunks = ref('') | |
var mediaConstraints = { | |
audio: true, | |
video: { | |
width: 1920, | |
height: 1080 | |
} | |
}; | |
const ffmpeg = createFFmpeg({ | |
log: true, | |
progress:p=>{console.log(p)}, | |
corePath: new URL('assets/f-core/ffmpeg-core.js', document.location).href, | |
}); | |
let file = 'http://localhost:8080/assets/190318231014076505.mp4'; | |
video = file; | |
async function init(){ | |
await ffmpeg.load(); | |
console.log("ffmpeg loaded") | |
} | |
//屏幕录制 | |
async function screenRecord(){ | |
ElNotification({ | |
title: '温馨提示', | |
message: '开始录屏,请选择要录制的窗口', | |
type: 'success', | |
}) | |
stream = await navigator.mediaDevices.getDisplayMedia(mediaConstraints); | |
var options = {mimeType: recordMediaType}; | |
mediaRecorder = new MediaRecorder(stream, options); | |
mediaRecorder.start(); | |
//停止录屏后触发保存 | |
mediaRecorder.ondataavailable = async function(e) { | |
console.log("data available", e.data); | |
chunks.value = e.data | |
} | |
recordStatus.value = mediaRecorder.state | |
} | |
//摄像头画面录 | |
async function localCamRecord(){ | |
ElNotification({ | |
title: '温馨提示', | |
message: '开始摄像头画面录制', | |
type: 'success', | |
}) | |
stream = await navigator.mediaDevices.getUserMedia(mediaConstraints); | |
var options = {mimeType: recordMediaType}; | |
mediaRecorder = new MediaRecorder(stream, options); | |
mediaRecorder.start(); | |
//停止录屏后触发保存 | |
mediaRecorder.ondataavailable = async function(e) { | |
console.log("data available", e.data); | |
chunks.value = e.data | |
} | |
recordStatus.value = mediaRecorder.state | |
} | |
//结束录制 | |
function stopRecord(){ | |
mediaRecorder.stop() | |
stream.getTracks().forEach(e=>{e.stop()}) | |
recordStatus.value = mediaRecorder.state | |
} | |
//暂停 | |
function pauseRecord(){ | |
mediaRecorder.pause() | |
recordStatus.value = mediaRecorder.state | |
} | |
//继续录 | |
function resumeRecord(){ | |
mediaRecorder.resume() | |
recordStatus.value = mediaRecorder.state | |
} | |
//https://superuser.com/questions/556029/how-do-i-convert-a-video-to-gif-using-ffmpeg-with-reasonable-quality | |
return{ | |
video, | |
init, | |
recordStatus, | |
screenRecord, | |
localCamRecord, | |
pauseRecord, | |
resumeRecord, | |
stopRecord, | |
stream, | |
ffmpeg, | |
chunks,//录制数据 | |
}; | |
}, | |
created() { | |
this.init() | |
}, | |
methods:{ | |
async transcode () { | |
if(!this.chunks){ | |
ElNotification({ | |
title: '警告', | |
message: '请选择点击录像/屏,完成录制后再转换', | |
type: 'warning', | |
}) | |
return | |
} | |
this.ffmpeg.FS("writeFile", "suke.avi", await fetchFile(this.chunks)); | |
await this.ffmpeg.run("-i", "suke.avi", "suke.mp4"); | |
const data = this.ffmpeg.FS("readFile", "suke.mp4"); | |
const video = document.getElementById('playerForTransf'); | |
this.transcodeUrl = URL.createObjectURL( | |
new Blob([data.buffer], { type: "video/mp4" }) | |
); | |
video.src = this.transcodeUrl | |
}, | |
async transFileDown(){ | |
if(this.transcodeUrl){ | |
this.downloadFile("转码视频-"+Date.now()+'.avi',this.transcodeUrl) | |
} | |
}, | |
//本地视频预览 | |
async playVideoFile(f){ | |
console.log(f) | |
if(f.raw.type.indexOf('video') >0){ | |
const video = document.getElementById('player'); | |
video.src = URL.createObjectURL( | |
f.raw | |
); | |
}else{ | |
ElNotification({ | |
title: '警告', | |
message: '请选择视频文件', | |
type: 'warning', | |
}) | |
} | |
}, | |
//录制文件预览 | |
async playRec(){ | |
const video = document.getElementById('player'); | |
video.src = URL.createObjectURL( | |
new Blob([this.chunks], { type: "video/mp4" }) | |
); | |
}, | |
async showGifParams(){ | |
if(!this.chunks){ | |
ElNotification({ | |
title: '警告', | |
message: '请先录制再点击转换GIF', | |
type: 'warning', | |
}) | |
return | |
} | |
this.videoTotalTime = await getBlobDuration(new Blob([this.chunks], { type: "video/mp4" })) | |
this.videoTotalTime = Math.floor(this.videoTotalTime) | |
console.log("时长",this.videoTotalTime) | |
if(this.videoTotalTime <= 2){ | |
ElNotification({ | |
title: '温馨提示', | |
message: '您录制的视频时间太短,请至少录制3秒', | |
type: 'warning', | |
}) | |
return | |
} | |
this.dialogTableVisibleForGifParams = true | |
ElNotification({ | |
title: '温馨提示', | |
message: '时常取决于您当前录制的视频,最大限制为 100秒', | |
type: 'success', | |
}) | |
}, | |
async startToGif(){ | |
if(this.formForGifParams.time > this.videoTotalTime ){ | |
ElNotification({ | |
title: '温馨提示', | |
message: '时间不能超过视频本身,视频时长:'+this.videoTotalTime +"秒", | |
type: 'warning', | |
}) | |
return | |
} | |
let time = this.formForGifParams.time+"" | |
let fps = this.formForGifParams.fps | |
let vh = this.formForGifParams.scale | |
let params = `fps=${fps},scale=${vh}:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse` | |
this.ffmpeg.FS("writeFile", "out.mp4", await fetchFile((this.chunks))); | |
await this.ffmpeg.run("-t", time, "-i", "out.mp4", "-vf",params, "-loop", "0" ,"output.gif"); | |
const data = this.ffmpeg.FS("readFile", "output.gif"); | |
const image = document.getElementById('imagePre'); | |
image.src = URL.createObjectURL( | |
new Blob([data.buffer], { type: "image/gif" }) | |
); | |
this.ffmpeg.FS('unlink','out.mp4') | |
}, | |
downloadVideo(){ | |
if(!this.chunks){ | |
ElNotification({ | |
title: '警告', | |
message: '请先录制再点击下载', | |
type: 'warning', | |
}) | |
return | |
} | |
let blobUrl = URL.createObjectURL( | |
new Blob([this.chunks], { type: "video/mp4" }) | |
); | |
this.downloadFile('录屏-'+Date.now()+'.mp4',blobUrl) | |
}, | |
downloadGif(){ | |
if(!this.chunks){ | |
ElNotification({ | |
title: '警告', | |
message: '请先点击转换为GIF再下载', | |
type: 'warning', | |
}) | |
return | |
} | |
const data = this.ffmpeg.FS("readFile", "output.gif"); | |
let blobUrl = URL.createObjectURL( | |
new Blob([data.buffer], { type: "image/gif" }) | |
); | |
this.downloadFile('GIF-'+Date.now()+'.gif',blobUrl) | |
}, | |
downloadFile(fileName,blobUrl){ | |
const a = document.createElement('a') | |
a.download = fileName | |
a.style.display = 'none' | |
a.href = blobUrl | |
// 触发点击 | |
document.body.appendChild(a) | |
a.click() | |
// 然后移除 | |
document.body.removeChild(a) | |
}, | |
}, | |
beforeDestroy() { | |
this.ffmpeg.exit() | |
} | |
} | |
</script> | |
<style scoped> | |
.container{ | |
width: 100%; | |
height: 99vh; | |
background-color: #333333; | |
} | |
#player{ | |
width: 100%; | |
height: 300px; | |
object-fit: fill; | |
} | |
#playerForTransf{ | |
width: 100%; | |
height: 300px; | |
object-fit: fill; | |
} | |
#imagePre{ | |
width: 100%; | |
height: 300px; | |
object-fit: fill; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment