Skip to content

Instantly share code, notes, and snippets.

@wangsrGit119
Created January 13, 2023 09:23
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wangsrGit119/46380e8bedc48157037c4ca622b7f30e to your computer and use it in GitHub Desktop.
Save wangsrGit119/46380e8bedc48157037c4ca622b7f30e to your computer and use it in GitHub Desktop.
lav-tools
<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