vue+flask实现视频合成功能(拖拽上传)


Posted in Vue.js onMarch 04, 2021

vue+flask实现视频合成
效果如下

vue+flask实现视频合成功能(拖拽上传)

拖拽上传我们之前一个文章有写过

原理就是 监听drop事件 来获取拖拽的文件列表

vue+flask实现视频合成功能(拖拽上传)
vue+flask实现视频合成功能(拖拽上传)

上传文件

通过axios 上传文件

this,.fileList就是我们的文件列表

let files = this.fileList;
let formd = new FormData();
let i = 1;

//添加上传列表
files.forEach(item => {
	formd.append(i + "", item, item.name)
	i++;
})
formd.append("type", i)
let config = {
	headers: {
		"Content-Type": "multipart/form-data"
	}
}

//上传文件请求
axios.post("/qwe", formd, config).then(res => {
	console.log(res.data)
})

flask处理文件

完整代码见最底部

逻辑如下
接收文件
为每次合成请求随机生成一个文件夹 临时保存文件
拼接视频
返回文件路径

@app.route("/file",methods=['POST'])
def test():

 #获取文件
 files = request.files
 #合成队列
 videoL = []
 #随机字符串
 dirs = sjs()
 #生成文件夹
 os.mkdir(dirs)
 #保存文件并添加至合成队列
 for file in files.values():
  print(file)
  dst = dirs + "/" + file.name + ".mp4"
  file.save(dst)
  video = VideoFileClip(dirs + "/" + file.name + ".mp4")
  videoL.append(video)
 
 #拼接视频
 final = concatenate_videoclips(videoL)
 #文件路径
 fileName = dirs + "/" +"{}.mp4".format(sjs())
 #生成视频
 final.to_videofile(fileName)
 
 #销毁文件夹
 def sc():
  shutil.rmtree(dirs)
 
 #30秒后销毁文件夹
 timer = threading.Timer(30, sc)
 timer.start()

 # 返回文件路径
 return fileName

拼接获取文件路径

首先我们看flask

逻辑如下
通过文件名 获取文件 返回文件

app.route("/getvoi",methods=['GET'])
def getImg():
 #获取文件名
 ss = request.args['name']
 #文件加至返回响应
 response = make_response(
  send_file(ss))

 #删除文件
 def sc():
  os.remove(ss)
 
 #30秒后删除文件
 timer = threading.Timer(30, sc)
 timer.start()
 
 return response

前端获取

通过a标签下载

<a s :href="herfs" rel="external nofollow" rel="external nofollow" :download="fileName">下载</a>

herfs如下

vue+flask实现视频合成功能(拖拽上传)

我们上传文件后 通过falsk处理返回文件路径 拼接后获取文件地址

a标签添加download属性可以给下载的文件命名

如果你对/qwe /voi有疑惑 请看下面的配置代理说明

配置代理说明

配置代理是为了解决跨域问题 开发环境可在vue.config.js配置即可使用
生产环境需要额外配置nginx

vue+flask实现视频合成功能(拖拽上传)

/qwe实际上就是 http://127.0.0.1:8087/file
/voi实际上就是 http://127.0.0.1:8087/getvoi
对应我们flask中的

vue+flask实现视频合成功能(拖拽上传)

额外说明(如果你使用uni-app)

如果你使用uni-app 可参照文档使用api
上传文件api https://uniapp.dcloud.io/api/request/network-file?id=uploadfile
下载文件api https://uniapp.dcloud.io/api/request/network-file?id=downloadfile
或者直接使用别人封装好的 插件毕竟比较方便

完整代码

如果你不想一个一个复制可以去下载
下载途径1: https://download.csdn.net/download/qq_42027681/15561897
下载途径2:https://github.com/dmhsq/vue-flask-videoSynthesis

flask代码

md5random.py 用于随机字符串生成

import random
import hashlib
def sjs():
 a = random.randint(0, 100)
 a = "a" + str(a);
 b = random.randint(100, 10000);
 b = "b" + str(b);
 c = hashlib.md5(a.encode(encoding='UTF-8')).hexdigest() + hashlib.md5(b.encode(encoding='UTF-8')).hexdigest();
 c = "c" + str(c);
 d = random.randint(10, 100);
 d = "d" + str(d);
 e = hashlib.md5(c.encode(encoding='UTF-8')).hexdigest() + hashlib.md5(d.encode(encoding='UTF-8')).hexdigest();
 e = hashlib.md5(e.encode(encoding='UTF-8')).hexdigest()
 return e;

app_service.py 服务代码

from flask import Flask,request,send_file,make_response
import os,json,threading,shutil
from moviepy.editor import *
from md5random import sjs

app = Flask(__name__)

@app.route("/file",methods=['POST'])
def test():

 #获取文件
 files = request.files
 #合成队列
 videoL = []
 #随机字符串
 dirs = sjs()
 #生成文件夹
 os.mkdir(dirs)
 #保存文件并添加至合成队列
 for file in files.values():
  print(file)
  dst = dirs + "/" + file.name + ".mp4"
  file.save(dst)
  video = VideoFileClip(dirs + "/" + file.name + ".mp4")
  videoL.append(video)

 #拼接视频
 final = concatenate_videoclips(videoL)
 #文件路径
 fileName = dirs + "/" +"{}.mp4".format(sjs())
 #生成视频
 final.to_videofile(fileName)

 #销毁文件夹
 def sc():
  shutil.rmtree(dirs)

 #30秒后销毁文件夹
 timer = threading.Timer(30, sc)
 timer.start()

 # 返回文件路径
 return fileName


@app.route("/getvoi",methods=['GET'])
def getImg():
 #获取文件名
 ss = request.args['name']
 #文件加至返回响应
 response = make_response(
  send_file(ss))

 #删除文件
 def sc():
  os.remove(ss)

 #30秒后删除文件
 timer = threading.Timer(30, sc)
 timer.start()

 return response

if __name__ == '__main__':
 app.run(host='0.0.0.0',port=8087)

vue代码

演示文件代码

<template>
 <div>
 <div
  v-on:dragover="tts"
  v-on:drop="ttrs"
  style="width: 800px;height: 200px;border: 1px solid black;font-size: 40px;line-height: 200px"
 >
  {{ dt }}
 </div>
 <div
  v-for="(item, index) in fileList"
  :key="index"
  style="width: 800px;height: 200px;border: 1px solid black;font-size: 40px;position: relative;top:10px"
 >
  <p
  style="font-size: 20px;float: left;position: relative;left: 20pxword-wrap:break-word;word-break:normal;"
  >
  {{ item.name }}
  </p>
  <h5 style="float:right;position: absolute;top: 80px;right: 20px">
  {{ item.type }}
  </h5>
  <h6 style="position: absolute;top: 80px;float: left;left: 20px">
  {{ item.size | sizeType }}
  </h6>
  <button style="float: right" @click="del(index)">删除</button>
 </div>
 <!-- 此处为展示最后一个上传的文件 -->
<!-- <div style="position:relative;top: 100px">-->
<!--  <img v-if="isImage" :src="srcs" style="width: 800px" />-->
<!--  <video v-if="isVideo" controls :src="srcs" style="width: 800px"></video>-->
<!--  <audio v-if="isAudio" controls :src="srcs" style="width: 800px"></audio>-->
<!-- </div>-->

 <el-button style="position: relative;top: 50px" type="success" @click="ups()" :disabled="!isCan">合成</el-button>
 <el-button style="position: relative;top: 50px" v-loading="loading" type="success" >。。。</el-button>
 <a style="position: relative;top: 50px;left: 15px;" type="success" :href="herfs" rel="external nofollow" rel="external nofollow" :download="fileName"><el-button :disabled="isCans"><span style="color: black">下载</span></el-button></a>
 <div style="position: relative;top: 100px">文件下载有效时间{{times}}s</div>
 </div>
</template>

<script>
import axios from "axios";

export default {
 name: "trs",
 data() {
 return {
  dt: "",//上传提醒 "拖动到此处上传文件“或者"上传完成,可继续上传"
  fileList: [],//文件列表
  loading:false,
  srcs: "",//图片/视频/音频 base64
  isImage: false,//是否是图片
  isAudio: false,//是否是音频
  isVideo: false,//是否是视频
  isCan: true,//是否能合成
  isCans:true,//是否能下载
  herfs: "",//下载地址
  fileName: "",//文件名
  times: 25//下载有效时间
 };
 },
 filters: {
 //格式化文件大小
 sizeType(val) {
  let kbs = val / 1024;
  let mbs = 0;
  let gbs = 0;
  if (kbs >= 1024) {
  mbs = kbs / 1024;
  }
  if (mbs >= 1024) {
  gbs = mbs / 1024;
  return gbs.toFixed(2) + "GB";
  } else if (mbs >= 1) {
  return mbs.toFixed(2) + "MB";
  } else {
  return kbs.toFixed(2) + "KB";
  }
 }
 },
 mounted() {
 let vm = this;
 window.addEventListener("dragdrop", this.testfunc, false);

 //全局监听 当页面内有文件拖动 提醒拖动到此处
 document.addEventListener("dragover", function() {
  console.log(111);
  vm.dt = "拖动到此处上传文件";
  console.log(vm.dt);
 });
 },
 methods: {
 //展示文件 主要为三个类型 图片/视频/音频
 readFile(file) {
  let vm = this;
  let reader = new FileReader();
  reader.readAsDataURL(file);
  reader.onload = function() {
  let type = file.type.substr(0, 5);
  if (type == "image") {
   vm.isImage = true;
   vm.isAudio = false;
   vm.isVideo = false;
  } else if (type == "audio") {
   vm.isImage = false;
   vm.isAudio = true;
   vm.isVideo = false;
  } else if (type == "video") {
   vm.isImage = false;
   vm.isAudio = false;
   vm.isVideo = true;
  } else {
   alert("不是图片/视频/音频");
  }
  vm.srcs = reader.result;
  // this.$nextTick(()=>{
  //
  // })
  };
 },
 //全局监听drop的触发事件 取消drop弹窗显示资源
 testfunc(event) {
  alert("dragdrop!");

  //取消drop弹窗显示资源
  event.stopPropagation();
  event.preventDefault();
 },
 del(index) {
  this.fileList.splice(index, 1);
  if (this.fileList.length === 0) {
  this.dt = "";
  }
 },
 //监听div上传框 当有文件拖动时 显示"拖动到此处上传文件"
 tts(e) {
  console.log(e);
  this.dt = "拖动到此处上传文件";
 },
 //监听div上传框 drop事件触发
 ttrs(e) {
  console.log(e);
  console.log(e.dataTransfer.files);

  //获取文件
  let datas = e.dataTransfer.files;

  //取消drop弹窗显示资源
  e.stopPropagation();
  e.preventDefault();
  datas.forEach(item => {
  if(item.type=="video/mp4"){
   this.fileList.push(item);
  }
  });

  //读取文件 如果不想展示图片/视频/音频可忽略
  this.readFile(this.fileList[this.fileList.length - 1]);



  this.dt = "上传完成,可继续上传";
 },

 //上传文件到服务器
 ups(){
  if(this.fileList.length==0){
  this.$message('文件列表为空');
  return ;
  }
  this.loading = true;
  this.isCan = false;
  this.isCans = true;
  let files = this.fileList;
  let formd = new FormData();
  let i = 1;

  //添加上传列表
  files.forEach(item=>{
  formd.append(i+"",item,item.name)
  i++;
  })
  formd.append("type",i)
  let config={
  headers:{"Content-Type":"multipart/form-data"}
  }

  //上传文件请求
  axios.post("/qwe",formd,config).then(res=>{
  console.log(res.data)
  this.loading = false
  //合成下载路径
  this.herfs = "/voi?name="+res.data

  this.fileName = res.data.split('/')[1]
  //禁止合成
  this.isCan = false

  this.isCans = false

  //设置下载有效时间 时间到后无法下载但可以继续合成
  let timer = setInterval(()=>{
   this.times--;
  },1000)
  this.setCans(timer)
  })
 },
 setCans(timer){
  setTimeout(()=>{
  this.isCans = true
  this.isCan = true
  this.fileName =""
  clearInterval(timer)
  this.times = 25
  },25000)
 }
 }
};
</script>

<style scoped></style>

vue.config.js

module.exports = {
 devServer: {
 // assetsSubDirectory: 'static',
 // assetsPublicPath: '/',
 proxy: {
  "/qwe": {
  target: "http://127.0.0.1:8087/file",
  changeOrigin: true,
  pathRewrite: {
   "^/qwe": ""
  }
  },
  "/voi": {
  target: "http://127.0.0.1:8087/getvoi",
  changeOrigin: true,
  pathRewrite: {
   "^/voi": ""
  }
  }
 }
 }
};

到此这篇关于vue+flask实现视频合成功能(拖拽上传)的文章就介绍到这了,更多相关vue视频合成内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Vue.js 相关文章推荐
Vue使用Element实现增删改查+打包的步骤
Nov 25 Vue.js
Vue-router中hash模式与history模式的区别详解
Dec 15 Vue.js
vue keep-alive的简单总结
Jan 25 Vue.js
vue实现禁止浏览器记住密码功能的示例代码
Feb 03 Vue.js
vue仿携程轮播图效果(滑动轮播,下方高度自适应)
Feb 11 Vue.js
vue前端和Django后端如何查询一定时间段内的数据
Feb 28 Vue.js
vue3.0封装轮播图组件的步骤
Mar 04 Vue.js
vue实现倒计时功能
Mar 24 Vue.js
Vue全局事件总线你了解吗
Feb 24 Vue.js
vue中使用mockjs配置和使用方式
Apr 06 Vue.js
vue3.0 数字翻牌组件的使用方法详解
Apr 20 Vue.js
vue修饰符.capture和.self的区别
Apr 22 Vue.js
vue打开新窗口并实现传参的图文实例
Mar 04 #Vue.js
Vue-router编程式导航的两种实现代码
Mar 04 #Vue.js
手写Vue2.0 数据劫持的示例
Mar 04 #Vue.js
vue3.0封装轮播图组件的步骤
Mar 04 #Vue.js
vue3.0 项目搭建和使用流程
Mar 04 #Vue.js
vue 数据双向绑定的实现方法
Mar 04 #Vue.js
vue3.0中使用element的完整步骤
Mar 04 #Vue.js
You might like
php引用传值实例详解学习
2013/11/06 PHP
php简单计算页面加载时间的方法
2015/06/19 PHP
yii用户注册表单验证实例
2015/12/26 PHP
php7性能提升的原因详解
2019/10/13 PHP
jQuery EasyUI API 中文文档 - Documentation 文档
2011/09/29 Javascript
JavaScript字符串对象split方法入门实例(用于把字符串分割成数组)
2014/10/16 Javascript
基于javascript实现表格的简单操作
2016/05/21 Javascript
JS+CSS3实现超炫的散列画廊特效
2016/07/16 Javascript
jquery拼接ajax 的json和字符串拼接的方法
2017/03/11 Javascript
Bootstrap弹出框之自定义悬停框标题、内容和样式示例代码
2017/07/11 Javascript
nodejs socket服务端和客户端简单通信功能
2017/09/14 NodeJs
JavaScript实现简单动态进度条效果
2018/04/06 Javascript
vue 的点击事件获取当前点击的元素方法
2018/09/15 Javascript
vue-music 使用better-scroll遇到轮播图不能自动轮播问题
2018/12/03 Javascript
layui树形菜单动态遍历的例子
2019/09/23 Javascript
vue.js实现左边导航切换右边内容
2019/10/21 Javascript
使用原生JS实现火锅点餐小程序(面向对象思想)
2019/12/10 Javascript
Django1.3添加app提示模块不存在的解决方法
2014/08/26 Python
python中管道用法入门实例
2015/06/04 Python
Python中有趣在__call__函数
2015/06/21 Python
python判断字符串是否是json格式方法分享
2017/11/07 Python
深入浅析Python中的yield关键字
2018/01/24 Python
深入理解Python 关于supper 的 用法和原理
2018/02/28 Python
tensorflow 用矩阵运算替换for循环 用tf.tile而不写for的方法
2018/07/27 Python
python散点图实例之随机漫步
2018/08/27 Python
Python操作Mongodb数据库的方法小结
2019/09/10 Python
Django实现文件上传下载
2019/10/06 Python
numpy数组做图片拼接的实现(concatenate、vstack、hstack)
2019/11/08 Python
python从zip中删除指定后缀文件(推荐)
2019/12/05 Python
租房协议书
2014/04/10 职场文书
2014年学校办公室工作总结
2014/12/19 职场文书
老乡会致辞
2015/07/28 职场文书
小学体育教学随笔
2015/08/14 职场文书
合作协议书格式范本
2016/03/21 职场文书
年中了,该如何写好个人述职报告?
2019/07/02 职场文书
Python anaconda安装库命令详解
2021/10/16 Python