ffmpeg+tornado实现在线视频抽帧小网站

一、运行环境

        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.非常简陋,凑合能用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值