一、运行环境
linux18.04,python3.6
二、主体代码
1.简易的前端代码
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>抽帧小网站</title>
</head>
<body>
<form method="post" action="/login">
用户名:<input type="text" name="name"/><br/>
密 码:<input type="password" name="password"/><br/>
<input type="submit" value="登陆" />
</form>
</body>
</html>
index.html
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>抽帧小网站</title>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<h2>用户:{{username}}</h2>
<form enctype="multipart/form-data" id="form_example">
<input type="file" id="files" multiple="multiple"/><br/><br/>
<input type="submit" value="上传"/>
标签名:<input type="text" name="tag_name" id="tag_name" required="required"/>
<label id="upload_st">待上传</label>
<label id="finish_c">已上传数:0</label>
</form>
<div id='file-list-display'></div>
<button id="extract" type="button">开始抽帧</button>
<label id="start_e">待抽帧</label>
<label id="finish_e">已抽帧数:0</label>
<div>
<button id="download" type="button">下载图片</button>
</div>
<div id="wrapper">
<!-- 进度条容器 -->
<div id="progressbar">
<!-- 进度条 -->
<div id="fill"></div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function () {
var fileList = [];
var fileCatcher = document.getElementById('form_example');
var files = document.getElementById("files"), renderFileList;
var fileListDisplay = document.getElementById('file-list-display'), sendFile;
var label=document.getElementById("upload_st");
var tag_text=document.getElementById("tag_name");
var finish = document.getElementById("finish_c");
var start_e = document.getElementById("start_e");
var finish_e = document.getElementById("finish_e");
var count = 0;
var count_e = 0;
var extract = document.getElementById("extract");
var download = document.getElementById("download");
extract.addEventListener("click", function (event) {
event.preventDefault();
// 开始抽帧
extract_func();
});
download.addEventListener("click", function (event) {
event.preventDefault();
// 下载图片
console.log('下载图片')
download_func();
console.log('weishenme')
});
download_func = function() {
var req = new XMLHttpRequest();
req.open('POST', '/tag_name', true);
req.responseType = 'blob';
console.log('zaihuishi')
req.onload = function() {
var data = req.response;
var blob = new Blob([data], {type: 'application/x-zip-compressed'});
var blobUrl = window.URL.createObjectURL(blob);
console.log(blobUrl)
var a = document.createElement('a');
a.download = tag_text.value;
a.href = blobUrl;
a.click();
};
var formData = new FormData();
if(tag_text.value == '') {
alert('标签名不能为空!');
}
else
{
formData.append("tag_name", tag_text.value);
req.send(formData);
}
};
extract_func = function() {
//循环添加到formData中
start_e.innerText="正在抽帧";
fileList.forEach(function (file) {
var formData = new FormData();
var request = new XMLHttpRequest();
formData.append("tag_name", tag_text.value);
formData.append('filename', file.name);
request.open("POST", "/frame");
request.send(formData);
request.onreadystatechange = function () {//请求后的回调接口,可将请求成功后要执行的程序写在其中
if (request.readyState == 4 && request.status == 200) {//验证请求是否发送成功
var json = request.responseText;//获取到服务端返回的数据
console.log(json);
var obj =JSON.parse(json);
console.log(obj); //控制台返回 Object
count_e += obj.code;
finish_e.innerText="已抽帧数" + ":" + count_e;
if (count == fileList.length){
start_e.innerText="完成抽帧";
}
}
};
})
};
fileCatcher.addEventListener("submit", function (event) {
event.preventDefault();
//上传文件
sendFile();
});
files.addEventListener("change", function (event) {
for (var i = 0; i < files.files.length; i++) {
fileList.push(files.files[i]);
}
// renderFileList();
});
renderFileList = function () {
fileListDisplay.innerHTML = '';
fileList.forEach(function (file, index) {
var fileDisplayEl = document.createElement("p");
fileDisplayEl.innerHTML = (index + 1) + ":" + file.name;
fileListDisplay.appendChild(fileDisplayEl);
})
};
sendFile = function () {
//循环添加到formData中
label.innerText="上传中";
fileList.forEach(function (file) {
var formData = new FormData();
var request = new XMLHttpRequest();
formData.append("tag_name", tag_text.value);
formData.append('files', file, file.name);
request.open("POST", "/index");
request.send(formData);
request.onreadystatechange = function () {//请求后的回调接口,可将请求成功后要执行的程序写在其中
if (request.readyState == 4 && request.status == 200) {//验证请求是否发送成功
var json = request.responseText;//获取到服务端返回的数据
console.log(json);
count += 1;
finish.innerText="已上传数" + ":" + count;
if (count == fileList.length){
label.innerText="完成上传";
}
}
};
})
}
})
</script>
</body>
</html>
2.tornado后端代码
import zipfile
import tornado.ioloop
import tornado.web
import json
from uuid import uuid4
import os
import time
from subprocess import PIPE, Popen
import traceback
import shutil
BASEDIR = os.path.abspath(os.path.dirname(__file__))
BASENAME = os.path.basename(BASEDIR)
class LoginHandler(tornado.web.RequestHandler):
def get(self):
self.render('login.html')
def post(self, *args, **kwargs):
user_name = self.get_argument('name', 'None')
password = self.get_argument('name', 'None')
self.render('index.html',
username=user_name
)
class UpFileHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
def post(self, *args, **kwargs):
file_metas = self.request.files["files"]
tag_name = self.get_argument('tag_name', str(uuid4()))
# save folder
folder = os.path.join(BASEDIR, tag_name)
os.makedirs(folder, exist_ok=True)
for meta in file_metas:
file_name = os.path.join(folder, meta['filename'])
print(file_name)
with open(file_name, 'wb') as up:
up.write(meta['body'])
self.write(json.dumps({'info': 'success!'}))
class ExtractHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
def post(self, *args, **kwargs):
tag_name = self.get_argument('tag_name', 'None')
filename = self.get_argument('filename', 'None')
folder = os.path.join(BASEDIR, tag_name)
filename = os.path.join(folder, filename)
code = self.extract_frame(folder, filename)
self.write(json.dumps({'code': code}))
def extract_frame(self, folder, file):
new_f = file.replace(' ', '').replace('(', '').replace(')', '').strip()
print(f'new_f: {new_f}')
try:
shutil.copyfile(file, new_f)
except shutil.SameFileError:
print('no copy!')
picture_id = str(uuid4())
folder = os.path.join(folder, 'picture')
os.makedirs(folder, exist_ok=True)
command = f"ffmpeg -i {new_f} -vf fps=2 {folder}/frame{picture_id}_xxx.png"
command = command.replace('xxx', '%d')
proc = Popen(command, shell=True,
stdout=PIPE,
stderr=PIPE)
return_code = None
while return_code is None:
return_code = proc.poll()
time.sleep(0.01)
out, err = proc.communicate()
proc.kill()
if return_code == 0:
return 1
else:
print(f'[{new_f}]出错结束')
print(f"err: \n{err}")
return 0
class DownLoadHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
def post(self, *args, **kwargs):
tag_name = self.get_argument('tag_name', 'None')
folder = os.path.join(BASEDIR, tag_name)
if os.path.exists(folder):
# zip
try:
zip_file = self.zippictures(folder)
print(f"zip_file: {zip_file}")
self.set_header("Content-Type", "application/x-zip-compressed")
# -----<或者这么写> ----- #
# self.set_header("Content-Type", "application/octet-stream")
self.set_header("Content-Disposition",
f"attachment; filename={zip_file.split('/')[-1]}")
f = open(zip_file, 'rb')
while True:
b = f.read(8096)
if not b:
break
else:
self.write(b)
self.flush()
f.close()
except FileNotFoundError as e:
print(traceback.format_exc())
self.write(json.dumps({'info': 0}))
except Exception as e:
print(traceback.format_exc())
self.write(json.dumps({'info': 0}))
self.finish()
else:
self.write(json.dumps({'info': 0}))
def zippictures(self, folder):
# zip
target = os.path.join(folder, time.strftime('%Y%m%d')+'.zip')
f = zipfile.ZipFile(target, 'w', zipfile.ZIP_DEFLATED)
pictures = os.path.join(folder, 'picture')
if not os.path.exists(pictures):
raise FileNotFoundError(f'{pictures} not found!')
for i in os.listdir(pictures):
file = os.path.join(pictures, i)
f.write(file, i)
# 这个file是文件名,意思是直接把文件添加到zip没有文件夹层级,
# f.write(i)这种写法,则会出现上面路径的层级
f.close()
return target
settings = {
'template_path': 'templates',
'debug': True
}
application = tornado.web.Application([
(r"/login", LoginHandler),
(r"/index", UpFileHandler),
(r"/frame", ExtractHandler),
(r"/tag_name", DownLoadHandler),
], **settings)
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.current().start()
三、运行效果
ps.非常简陋,凑合能用。