mirror of
https://github.com/Geniusay/ChopperBot.git
synced 2026-09-03 06:25:04 +08:00
视频转换工具栏页面 #32
This commit is contained in:
6687
console-ui/package-lock.json
generated
6687
console-ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,9 @@
|
||||
"coverage": "vitest run --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ffmpeg/core": "^0.12.4",
|
||||
"@ffmpeg/ffmpeg": "^0.12.7",
|
||||
"@ffmpeg/util": "^0.12.1",
|
||||
"@mdi/font": "7.2.96",
|
||||
"@tiptap/pm": "^2.0.2",
|
||||
"@tiptap/starter-kit": "^2.0.2",
|
||||
|
||||
16
console-ui/public/ffmpeg-core/ffmpeg-core.js
Normal file
16
console-ui/public/ffmpeg-core/ffmpeg-core.js
Normal file
File diff suppressed because one or more lines are too long
BIN
console-ui/public/ffmpeg-core/ffmpeg-core.wasm
Normal file
BIN
console-ui/public/ffmpeg-core/ffmpeg-core.wasm
Normal file
Binary file not shown.
1
console-ui/public/ffmpeg-core/ffmpeg-core.worker.js
Normal file
1
console-ui/public/ffmpeg-core/ffmpeg-core.worker.js
Normal file
@@ -0,0 +1 @@
|
||||
"use strict";var Module={};var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";if(ENVIRONMENT_IS_NODE){var nodeWorkerThreads=require("worker_threads");var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",data=>onmessage({data:data}));var fs=require("fs");Object.assign(global,{self:global,require:require,Module:Module,location:{href:__filename},Worker:nodeWorkerThreads.Worker,importScripts:function(f){(0,eval)(fs.readFileSync(f,"utf8")+"//# sourceURL="+f)},postMessage:function(msg){parentPort.postMessage(msg)},performance:global.performance||{now:function(){return Date.now()}}})}var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");if(ENVIRONMENT_IS_NODE){fs.writeSync(2,text+"\n");return}console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=(info,receiveInstance)=>{var module=Module["wasmModule"];Module["wasmModule"]=null;var instance=new WebAssembly.Instance(module,info);return receiveInstance(instance)};self.onunhandledrejection=e=>{throw e.reason??e};function handleMessage(e){try{if(e.data.cmd==="load"){let messageQueue=[];self.onmessage=e=>messageQueue.push(e);self.startWorker=instance=>{Module=instance;postMessage({"cmd":"loaded"});for(let msg of messageQueue){handleMessage(msg)}self.onmessage=handleMessage};Module["wasmModule"]=e.data.wasmModule;for(const handler of e.data.handlers){Module[handler]=function(){postMessage({cmd:"callHandler",handler:handler,args:[...arguments]})}}Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob=="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}createFFmpegCore(Module)}else if(e.data.cmd==="run"){Module["__emscripten_thread_init"](e.data.pthread_ptr,0,0,1);Module["__emscripten_thread_mailbox_await"](e.data.pthread_ptr);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInitTLS();if(!initializedJS){initializedJS=true}try{Module["invokeEntryPoint"](e.data.start_routine,e.data.arg)}catch(ex){if(ex!="unwind"){throw ex}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="checkMailbox"){if(initializedJS){Module["checkMailbox"]()}}else if(e.data.cmd){err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){if(Module["__emscripten_thread_crashed"]){Module["__emscripten_thread_crashed"]()}throw ex}}self.onmessage=handleMessage;
|
||||
@@ -1,5 +1,5 @@
|
||||
import unsplashRoutes from "@/views/app/unsplash/UnsplashRoutes";
|
||||
|
||||
import toolRoutes from "@/views/app/tool/toolRoutes";
|
||||
import emailRoutes from "@/views/app/email/emailRoutes";
|
||||
import taskRoutes from "@/views/app/taskcenter/taskRoutes";
|
||||
import hotGuardRoutes from "@/views/app/hot/hot_guard/hotGuardRoutes";
|
||||
@@ -190,11 +190,12 @@ export default [
|
||||
},
|
||||
{
|
||||
path: "/apps/tool",
|
||||
name: "tools",
|
||||
name: "app-tool",
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "utility-board" */ "@/views/app/tool/ToolApp.vue"
|
||||
),
|
||||
children: [...toolRoutes],
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: "Tools",
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
|
||||
<script>
|
||||
import ToolMenu from "./component/ToolMenu";
|
||||
export default {
|
||||
name: "ToolApp.vue"
|
||||
name: "ToolApp.vue",
|
||||
components: {ToolMenu}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!-- Side Bar -->
|
||||
<!-- ---------------------------------------------- -->
|
||||
<div class="d-none d-md-block sidebar">
|
||||
|
||||
<ToolMenu></ToolMenu>
|
||||
</div>
|
||||
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!-- List User-->
|
||||
<!-- ---------------------------------------------- -->
|
||||
<div class="main">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="fade">
|
||||
@@ -23,8 +19,6 @@ export default {
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
38
console-ui/src/views/app/tool/component/ToolMenu.vue
Normal file
38
console-ui/src/views/app/tool/component/ToolMenu.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<v-card class="pa-3" height="100%">
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!-- Write Email -->
|
||||
<!-- ---------------------------------------------- -->
|
||||
|
||||
<EmailCompose />
|
||||
|
||||
<v-list class="mt-2 pa-0" nav>
|
||||
<v-list-item
|
||||
active-class="text-primary"
|
||||
link
|
||||
prepend-icon="mdi-inbox"
|
||||
title="VideoTool"
|
||||
to="/apps/tool/video"
|
||||
>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
active-class="text-primary"
|
||||
link
|
||||
prepend-icon="mdi-send-outline"
|
||||
title="balabala"
|
||||
to="/apps/email/send"
|
||||
>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ToolMenu"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
220
console-ui/src/views/app/tool/pages/VideoTool.vue
Normal file
220
console-ui/src/views/app/tool/pages/VideoTool.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<div ref="VideoTool" class="VideoTool">
|
||||
<el-upload
|
||||
:before-upload="beforeUpload"
|
||||
:file-list="fileList"
|
||||
:on-change="handleChange"
|
||||
:on-remove="handleRemove"
|
||||
action=""
|
||||
class="upload"
|
||||
drag
|
||||
multiple>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
</el-upload>
|
||||
<div class="SelectContainer">
|
||||
<div class="select">
|
||||
<el-select v-model="CurrentType">
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<span style="margin:0 20px 0 20px;display: inline-block;font-family: 'Hiragino Sans GB';font-weight: 700;line-height: 32px">
|
||||
转成
|
||||
</span>
|
||||
<el-select v-model="TargetType">
|
||||
<el-option
|
||||
v-for="item in options2"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="button" @click="transform">
|
||||
<el-button style="width: 80px;position: relative;background-color: rgb(255,44,85);color: white">开始转换</el-button>
|
||||
</div>
|
||||
<p ref="messageRef"></p>
|
||||
<video ref="video" controls>
|
||||
|
||||
</video>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { FFmpeg } from '@ffmpeg/ffmpeg';
|
||||
import {fetchFile, toBlobURL} from '@ffmpeg/util';
|
||||
let ffmpegRef = new FFmpeg()
|
||||
let ffmpeg = ffmpegRef;
|
||||
export default {
|
||||
name: "VideoTool",
|
||||
data () {
|
||||
return {
|
||||
ffmpeg:[],
|
||||
options:[
|
||||
{
|
||||
label: 'flv',
|
||||
value: 'flv'
|
||||
},
|
||||
{
|
||||
label: 'm3u8',
|
||||
value: 'm3u8'
|
||||
},
|
||||
{
|
||||
label: 'webm',
|
||||
value: 'webm'
|
||||
},
|
||||
{
|
||||
label: 'avi',
|
||||
value: 'avi'
|
||||
},
|
||||
{
|
||||
label: 'mp4',
|
||||
value: 'mp4'
|
||||
}
|
||||
],
|
||||
options2:[
|
||||
{
|
||||
label: 'flv',
|
||||
value: 'flv'
|
||||
},
|
||||
{
|
||||
label: 'm3u8',
|
||||
value: 'm3u8'
|
||||
},
|
||||
{
|
||||
label: 'webm',
|
||||
value: 'webm'
|
||||
},
|
||||
{
|
||||
label: 'avi',
|
||||
value: 'avi'
|
||||
},
|
||||
{
|
||||
label: 'mp4',
|
||||
value: 'mp4'
|
||||
}
|
||||
],
|
||||
CurrentType: '',
|
||||
TargetType: '',
|
||||
file: null,
|
||||
fileList: [
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
beforeUpload(file){
|
||||
this.file = file
|
||||
return false
|
||||
},
|
||||
handleChange(file, fileList) {
|
||||
this.fileList = [{name:file.name}];
|
||||
},
|
||||
handleRemove(file){
|
||||
this.fileList = []
|
||||
this.file = null
|
||||
},
|
||||
async transform() {
|
||||
if(!this.file){
|
||||
this.$message.warning("请选择文件")
|
||||
return
|
||||
}
|
||||
if(!this.CurrentType||!this.TargetType){
|
||||
this.$message.warning("请选择格式")
|
||||
return
|
||||
}
|
||||
if(!this.CurrentType === this.TargetType){
|
||||
this.$message.warning("格式相同")
|
||||
return
|
||||
}
|
||||
let file = this.file
|
||||
let that = this
|
||||
let reader = new FileReader()
|
||||
reader.addEventListener(
|
||||
"load",
|
||||
function () {
|
||||
(async () => {
|
||||
await ffmpeg.writeFile(`input.${that.CurrentType}`,await fetchFile(reader.result));
|
||||
await ffmpeg.exec(['-i', `input.${that.CurrentType}`,'-c','copy', `output.${that.TargetType}`]);
|
||||
const data = await ffmpeg.readFile(`output.${that.TargetType}`);
|
||||
let downloadFile =new Blob([data.buffer], {type: `video/${that.TargetType}`})
|
||||
const downloadA = document.createElement("a");
|
||||
downloadA.href = URL.createObjectURL(downloadFile);
|
||||
downloadA.download = `output.${that.TargetType}`
|
||||
document.body.appendChild(downloadA);
|
||||
downloadA.click()
|
||||
document.body.removeChild(downloadA);
|
||||
that.$refs.video.setAttribute('src',URL.createObjectURL(new Blob([data.buffer], {type: `video/${that.TargetType}`})))
|
||||
})()
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
if (file) {
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
},
|
||||
init(){
|
||||
let that = this;
|
||||
(async () => {
|
||||
let messageRef = that.$refs.messageRef
|
||||
ffmpeg.on('progress', ({ progress, time }) => {
|
||||
messageRef.innerHTML = `${(progress * 100).toFixed(5)} % `;
|
||||
});
|
||||
// toBlobURL is used to bypass CORS issue, urls with the same
|
||||
// domain can be used directly.
|
||||
const baseURL = '/ffmpeg-core'
|
||||
await ffmpeg.load({
|
||||
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
|
||||
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
|
||||
workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript'),
|
||||
});
|
||||
})()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.VideoTool{
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
.upload{
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
width: 80%;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
.SelectContainer{
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
.select{
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 500px;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
.button{
|
||||
margin-top: 10px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 110px;
|
||||
left: 50%;
|
||||
transform: translate(-50%)
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
15
console-ui/src/views/app/tool/toolRoutes.ts
Normal file
15
console-ui/src/views/app/tool/toolRoutes.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export default [
|
||||
{
|
||||
path: "",
|
||||
redirect: "/apps/tool/video",
|
||||
},
|
||||
{
|
||||
path: "video",
|
||||
name: "apps-tool-video",
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "apps-tool-video" */ "@/views/app/tool/pages/VideoTool.vue"
|
||||
),
|
||||
},
|
||||
|
||||
];
|
||||
@@ -35,6 +35,9 @@ export default defineConfig({
|
||||
},
|
||||
extensions: [".js", ".json", ".jsx", ".mjs", ".ts", ".tsx", ".vue"],
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util']
|
||||
},
|
||||
server: {
|
||||
port: 4399,
|
||||
proxy: {
|
||||
|
||||
Reference in New Issue
Block a user