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

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

在这里插入图片描述

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

//www.jb51.net/article/206543.htm

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

在这里插入图片描述
在这里插入图片描述

上传文件

通过axios 上传文件

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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处理文件

完整代码见最底部

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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标签下载

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

herfs如下

在这里插入图片描述

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

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

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

配置代理说明

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

在这里插入图片描述

/qwe实际上就是 http://127.0.0.1:8087/file
/voi实际上就是 http://127.0.0.1:8087/getvoi
对应我们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 用于随机字符串生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 服务代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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代码

演示文件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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": ""
    }
   }
  }
 }
};

林小贱

文章作者信息...

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐