├── .gitattributes ├── .gitignore ├── README.md ├── config.py ├── controller ├── __init__.py ├── modules │ ├── __init__.py │ ├── home │ │ ├── __init__.py │ │ └── views.py │ └── user │ │ ├── __init__.py │ │ └── views.py ├── static │ ├── css │ │ └── style.css │ ├── images │ │ ├── Thumbs.db │ │ ├── adm.png │ │ ├── avtar.png │ │ ├── bg1.jpg │ │ ├── close.png │ │ ├── key.png │ │ └── pass.png │ └── recorder.js ├── templates │ ├── index.html │ └── login.html └── utils │ ├── __init__.py │ └── camera.py ├── img ├── hha.jpeg ├── index.jpg ├── login.png ├── main.jpg └── tree.png ├── logs └── .keepgit └── main.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=python 2 | *.css linguist-language=python 3 | *.html linguist-language=python 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *py[cod] 3 | logs/log* 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Raspberry Pi flask+opencv Surveillance System

2 | 3 |
4 | 5 |

6 | Master 8 |

9 | 10 |
11 | 12 |

"推荐一波我的公众号,想要学习爬虫,大数据的可以关注一下,绝对满满的干货哦!" - 一个诈尸的人,哈哈

13 | 14 |
15 | 16 |

17 | 18 | Branch 20 | 21 | 22 | Pull Requests 24 | 25 | 26 | License 28 | 29 |

30 | 31 |
32 | Created by 33 | CriseLYJ 34 |
35 | 36 |
37 | 38 | **** 39 | 40 | # Installing 41 | ### 🐍First you should install ``Python3.x`` on your Raspberry Pi 42 | 43 | > $ sudo apt-get update 44 | > $ sudo apt-get upgrade 45 | 46 | 47 | - Install python``dependent environment`` 48 | - install python ``Dependent environment`` 49 | 50 | > $ sudo apt-get install build-essential libsqlite3-dev sqlite3 bzip2 libbz2-dev 51 | 52 | 53 | - Download the python3.6 version source and extract it 54 | - Download the python version 3.6 source code and decompress it 55 | 56 | > $ wget https://www.python.org/ftp/python/3.6.1/Python-3.6.1.tgz 57 | > $ tar zxvf Python-3.6.1.tgz 58 | 59 | - Compilation and installation 60 | 61 | > $ cd Python-3.6.1 62 | > $ sudo ./configure 63 | > $ sudo make 64 | > $ sudo make install 65 | 66 | - Check installation 67 | 68 | > $ ls -al /usr/local/bin/python* 69 | 70 | 71 | ### Next install the module 72 | 73 | - Install flask 74 | 75 | > $ pip3 install flask==0.10.1 76 | 77 | - Install opencv 78 | - install opencv 79 | 80 | > $ pip3 install opencv_python 81 | 82 | # Running the tests 83 | 84 | - Download all files to run 85 | - run main.py 86 | 87 | > $ python3 main.py -p 0.0.0.0 88 | > 当然你也可以使用Gunicorn来当做你的多线程服务器 89 | 90 | - 2019.2.21 update 91 | 92 | - Increased login, a simple login interface, does not need a database 93 | 94 | - Test account 95 | ``` 96 | Username: admin 97 | Password: admin 98 | ``` 99 | - 2019.3.4 update 100 | - Add multi-threading and recording downloads 101 | - Support multi-device access, logout login is normal 102 | 103 | - 2019.3.14 update 104 | - 现在的目录结构是这个样子 105 | 106 | ![](./img/tree.png) 107 | 108 | - 抽取了代码,进行了优化,就是这样目录看起来会很多 109 | 110 | - Added a beautiful login interface 111 | ![Alt text](./img/login.png) 112 | 113 | - Optimization homepage 114 | 115 | ![Alt text](./img/index.jpg) 116 | 117 | - Add video recording and download capabilities 118 | - Realized the ``high performance``, using the yield generator, and multi-threading, silky smooth! 119 | 120 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from datetime import timedelta 3 | 4 | 5 | class Config: 6 | # 调试模式 7 | DEBUG = True 8 | # session加密秘钥 9 | SECRET_KEY = "fM3PEZwSRcbLkk2Ew82yZFffdAYsNgOddWoANdQo/U3VLZ/qNsOKLsQPYXDPon2t" 10 | # session过期时间 11 | PERMANENT_SESSION_LIFETIME = timedelta(days=7) 12 | 13 | 14 | class DevelopmentConfig(Config): 15 | # 开发模式配置 16 | DEBUG = True 17 | LOG_LEVEL = logging.DEBUG 18 | 19 | 20 | class ProductionConfig(Config): 21 | # 上线配置 22 | # 关闭调试 23 | DEBUG = False 24 | LOG_LEVEL = logging.ERROR # 日志级别 25 | 26 | 27 | # 配置字典,键:配置 28 | config_dict = { 29 | 'dev': DevelopmentConfig, 30 | 'pro': ProductionConfig 31 | } 32 | -------------------------------------------------------------------------------- /controller/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from logging.handlers import RotatingFileHandler 3 | from flask import Flask 4 | from config import config_dict 5 | 6 | 7 | # 设置日志(目的是将flask默认的日志和自定义的日志保存到文件中) 8 | def setup_log(log_level): 9 | # 设置日志的记录等级 10 | logging.basicConfig(level=log_level) # 根据配置类型设置日志等级 11 | 12 | # 创建日志记录器,指明日志保存的路径、每个日志文件的最大大小、保存的日志文件个数上限 13 | file_log_handler = RotatingFileHandler("logs/log", maxBytes=1024 * 1024 * 100, backupCount=10) 14 | # 创建日志记录的格式 日志等级 输入日志信息的文件名 行数 日志信息 15 | formatter = logging.Formatter('%(levelname)s %(pathname)s:%(lineno)d %(message)s') 16 | # 为刚创建的日志记录器设置日志记录格式 17 | file_log_handler.setFormatter(formatter) 18 | # 为全局的日志工具对象(flask app使用的)添加日志记录器 19 | logging.getLogger().addHandler(file_log_handler) 20 | 21 | 22 | # 工厂函数: 由外界提供物料, 在函数内部封装对象的创建过程 23 | def create_app(config_type): # 封装web应用的创建过程 24 | # 根据类型取出对应的配置子类 25 | config_class = config_dict[config_type] 26 | app = Flask(__name__) 27 | app.config.from_object(config_class) 28 | 29 | # 注册蓝图对象 如果内容只在文件中使用一次, 最好在使用前才导入, 可以有效避免导入错误 30 | from controller.modules.home import home_blu 31 | app.register_blueprint(home_blu) 32 | from controller.modules.user import user_blu 33 | app.register_blueprint(user_blu) 34 | 35 | # 设置日志 36 | setup_log(config_class.LOG_LEVEL) 37 | 38 | return app 39 | -------------------------------------------------------------------------------- /controller/modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/controller/modules/__init__.py -------------------------------------------------------------------------------- /controller/modules/home/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | # 创建蓝图对象 4 | home_blu = Blueprint("home", __name__) 5 | 6 | # 让视图函数和主程序建立关联 7 | from controller.modules.home.views import * 8 | -------------------------------------------------------------------------------- /controller/modules/home/views.py: -------------------------------------------------------------------------------- 1 | from flask import session, render_template, redirect, url_for, Response 2 | from controller.modules.home import home_blu 3 | from controller.utils.camera import VideoCamera 4 | 5 | video_camera = None 6 | global_frame = None 7 | 8 | 9 | # 主页 10 | @home_blu.route('/') 11 | def index(): 12 | # 模板渲染 13 | username = session.get("username") 14 | if not username: 15 | return redirect(url_for("user.login")) 16 | return render_template("index.html") 17 | 18 | 19 | # 获取视频流 20 | def video_stream(): 21 | global video_camera 22 | global global_frame 23 | 24 | if video_camera is None: 25 | video_camera = VideoCamera() 26 | 27 | while True: 28 | frame = video_camera.get_frame() 29 | 30 | if frame is not None: 31 | global_frame = frame 32 | yield (b'--frame\r\n' 33 | b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n') 34 | else: 35 | yield (b'--frame\r\n' 36 | b'Content-Type: image/jpeg\r\n\r\n' + global_frame + b'\r\n\r\n') 37 | 38 | 39 | # 视频流 40 | @home_blu.route('/video_viewer') 41 | def video_viewer(): 42 | # 模板渲染 43 | username = session.get("username") 44 | if not username: 45 | return redirect(url_for("user.login")) 46 | return Response(video_stream(), 47 | mimetype='multipart/x-mixed-replace; boundary=frame') 48 | -------------------------------------------------------------------------------- /controller/modules/user/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | # 创建蓝图对象 4 | user_blu = Blueprint("user", __name__) 5 | 6 | # 让视图函数和主程序建立关联 7 | from controller.modules.user.views import * 8 | -------------------------------------------------------------------------------- /controller/modules/user/views.py: -------------------------------------------------------------------------------- 1 | from flask import session, redirect, url_for, request, render_template, jsonify 2 | 3 | from controller.modules.user import user_blu 4 | from controller.utils.camera import VideoCamera 5 | 6 | 7 | # 登录 8 | @user_blu.route("/login", methods=["GET", "POST"]) 9 | def login(): 10 | username = session.get("username") 11 | 12 | if username: 13 | return redirect(url_for("home.index")) 14 | 15 | if request.method == "GET": 16 | return render_template("login.html") 17 | # 获取参数 18 | username = request.form.get("username") 19 | password = request.form.get("password") 20 | # 校验参数 21 | if not all([username, password]): 22 | return render_template("login.html", errmsg="参数不足") 23 | 24 | # 校验对应的管理员用户数据 25 | if username == "admin" and password == "admin": 26 | # 验证通过 27 | session["username"] = username 28 | return redirect(url_for("home.index")) 29 | 30 | return render_template("login.html", errmsg="用户名或密码错误") 31 | 32 | 33 | # 退出登录 34 | @user_blu.route("/logout") 35 | def logout(): 36 | # 删除session数据 37 | session.pop("username", None) 38 | # 返回登录页面 39 | return redirect(url_for("user.login")) 40 | 41 | 42 | # 录制状态 43 | @user_blu.route('/record_status', methods=['POST']) 44 | def record_status(): 45 | global video_camera 46 | if video_camera is None: 47 | video_camera = VideoCamera() 48 | 49 | json = request.get_json() 50 | 51 | status = json['status'] 52 | 53 | if status == "true": 54 | video_camera.start_record() 55 | return jsonify(result="started") 56 | else: 57 | video_camera.stop_record() 58 | return jsonify(result="stopped") -------------------------------------------------------------------------------- /controller/static/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | /* reset */ 3 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,dl,dt,dd,ol,nav ul,nav li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;} 4 | article, aside, details, figcaption, figure,footer, header, hgroup, menu, nav, section {display: block;} 5 | ol,ul{list-style:none;margin:0px;padding:0px;} 6 | blockquote,q{quotes:none;} 7 | blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;} 8 | table{border-collapse:collapse;border-spacing:0;} 9 | /* start editing from here */ 10 | a{text-decoration:none;} 11 | .txt-rt{text-align:right;}/* text align right */ 12 | .txt-lt{text-align:left;}/* text align left */ 13 | .txt-center{text-align:center;}/* text align center */ 14 | .float-rt{float:right;}/* float right */ 15 | .float-lt{float:left;}/* float left */ 16 | .clear{clear:both;}/* clear float */ 17 | .pos-relative{position:relative;}/* Position Relative */ 18 | .pos-absolute{position:absolute;}/* Position Absolute */ 19 | .vertical-base{ vertical-align:baseline;}/* vertical align baseline */ 20 | .vertical-top{ vertical-align:top;}/* vertical align top */ 21 | nav.vertical ul li{ display:block;}/* vertical menu */ 22 | nav.horizontal ul li{ display: inline-block;}/* horizontal menu */ 23 | img{max-width:100%;} 24 | /*end reset*/ 25 | /****-----start-body----****/ 26 | body{ 27 | background: url(../images/bg1.jpg) no-repeat 0px 0px; 28 | font-family: 'Open Sans', sans-serif; 29 | background-size:cover; 30 | -webkit-background-size:cover; 31 | -moz-background-size:cover; 32 | -o-background-size:cover; 33 | min-height:1050px; 34 | } 35 | .wrap{ 36 | margin: 0 auto; 37 | width: 80%; 38 | } 39 | body a,form li,input[type="submit"],input[type="text"],.sixth-login input[type="submit"],.third-login input[type="submit"]{ 40 | transition: 0.1s all; 41 | -webkit-transition: 0.1s all; 42 | -moz-transition: 0.1s all; 43 | -o-transition: 0.1s all; 44 | } 45 | /*--close--*/ 46 | .close{ 47 | background: url('../images/close.png') no-repeat 0px 0px; 48 | cursor: pointer; 49 | width: 20px; 50 | height: 20px; 51 | position: absolute; 52 | left: 20px; 53 | top: 20px; 54 | -webkit-transition: color 0.2s ease-in-out; 55 | -moz-transition: color 0.2s ease-in-out; 56 | -o-transition: color 0.2s ease-in-out; 57 | transition: color 0.2s ease-in-out; 58 | } 59 | /*--/close--*/ 60 | h1 { 61 | font-family: 'Exo 2', sans-serif; 62 | text-align: center; 63 | padding-top: 4em; 64 | font-weight: 400; 65 | color: #2B2B36; 66 | font-size: 2em; 67 | } 68 | .login-form { 69 | background: #2b2b36; 70 | position: relative; 71 | width: 30%; 72 | margin: 3% auto 0 auto; 73 | text-align: center; 74 | border-radius: 15px; 75 | -webkit-border-radius: 15px; 76 | -moz-border-radius: 15px; 77 | -o-border-radius: 15px; 78 | } 79 | .head img { 80 | width: 100%; 81 | } 82 | .avtar img { 83 | margin: 2em 0 0; 84 | } 85 | .head-info { 86 | padding: 5px 0; 87 | text-align: center; 88 | font-weight: 600; 89 | font-size: 2em; 90 | color: #fff; 91 | background: #23232e; 92 | height: 50px; 93 | border-top-left-radius: 10px; 94 | -webkit-border-top-left-radius: 10px; 95 | -moz-border-top-left-radius: 10px; 96 | -o-border-top-left-radius: 10px; 97 | border-top-right-radius: 10px; 98 | -webkit-border-top-right-radius: 10px; 99 | -moz-border-top-right-radius: 10px; 100 | -o-border-top-right-radius: 10p 101 | } 102 | input[type="text"] { 103 | width: 70%; 104 | padding: 1em 2em 1em 3em; 105 | color: #9199aa; 106 | font-size: 18px; 107 | outline: none; 108 | background: url(../images/adm.png) no-repeat 10px 15px; 109 | border: none; 110 | font-weight: 100; 111 | border-bottom: 1px solid#484856; 112 | margin-top: 2em; 113 | } 114 | input[type="password"]{ 115 | width: 70%; 116 | padding: 1em 2em 1em 3em; 117 | color: #dd3e3e; 118 | font-size: 18px; 119 | outline: none; 120 | background: url(../images/key.png) no-repeat 10px 23px; 121 | border: none; 122 | font-weight: 100; 123 | border-bottom: 1px solid#484856; 124 | margin-bottom: 3em; 125 | } 126 | .key { 127 | background: url(../images/pass.png) no-repeat 447px 17px; 128 | } 129 | input[type="submit"]{ 130 | font-size: 30px; 131 | color: #fff; 132 | outline: none; 133 | border: none; 134 | background: #3ea751; 135 | width: 100%; 136 | padding: 18px 0; 137 | border-bottom-left-radius: 15px; 138 | -webkit-border-bottom-left-radius: 15px; 139 | -moz-border-bottom-left-radius: 15px; 140 | -o-border-bottom-left-radius: 15px; 141 | border-bottom-right-radius: 15px; 142 | -webkit-border-bottom-right-radius: 15px; 143 | -moz-border-bottom-right-radius: 15px; 144 | -o-border-bottom-right-radius: 15px; 145 | cursor: pointer; 146 | } 147 | input[type="submit"]:hover { 148 | background: #ff2775; 149 | border-bottom-left-radius: 15px; 150 | -webkit-border-bottom-left-radius: 15px; 151 | -moz-border-bottom-left-radius: 15px; 152 | -o-border-bottom-left-radius: 15px; 153 | border-bottom-right-radius: 15px; 154 | -webkit-border-bottom-right-radius: 15px; 155 | -moz-border-bottom-right-radius: 15px; 156 | -o-border-bottom-right-radius: 15px; 157 | transition: 1s all; 158 | -webkit-transition: 1s all; 159 | -moz-transition: 1s all; 160 | -o-transition: 1s all; 161 | } 162 | label.lbl-1 { 163 | background: #6756ea; 164 | width: 20px; 165 | height: 20px; 166 | display: block; 167 | float: right; 168 | border-radius: 50%; 169 | margin: 16px 10px 0px 0px; 170 | } 171 | label.lbl-2 { 172 | background: #ea569a; 173 | width: 20px; 174 | height: 20px; 175 | display: block; 176 | float: right; 177 | border-radius: 50%; 178 | margin: 16px 10px 0px 0px; 179 | } 180 | label.lbl-3 { 181 | background: #f1c85f; 182 | width: 20px; 183 | height: 20px; 184 | display: block; 185 | float: right; 186 | border-radius: 50%; 187 | margin: 16px 10px 0px 0px; 188 | } 189 | /*--copyrights--*/ 190 | .copy-rights{ 191 | text-align: center; 192 | margin-top: 8em; 193 | } 194 | .copy-rights p{ 195 | color:#FFF; 196 | font-size: 1em; 197 | line-height:1.8em; 198 | } 199 | .copy-rights p a{ 200 | color:#ff2a75; 201 | -webkit-transition: all 0.3s ease-out; 202 | -moz-transition: all 0.3s ease-out; 203 | -ms-transition: all 0.3s ease-out; 204 | -o-transition: all 0.3s ease-out; 205 | transition: all 0.3s ease-out; 206 | text-decoration:none; 207 | } 208 | .copy-rights p a:hover{ 209 | color:#3faa53; 210 | text-decoration:none; 211 | transition: 0.1s all; 212 | -webkit-transition: 0.1s all; 213 | -moz-transition: 0.1s all; 214 | -o-transition: 0.1s all; 215 | } 216 | /*--/copyrights--*/ 217 | /*--start-responsive-design--*/ 218 | @media (max-width:1440px){ 219 | .key { 220 | background: url(../images/pass.png) no-repeat 376px 17px; 221 | } 222 | 223 | body { 224 | min-height: 811px; 225 | } 226 | } 227 | @media (max-width:1366px){ 228 | .key { 229 | background: url(../images/pass.png) no-repeat 358px 19px; 230 | } 231 | .copy-rights { 232 | margin-top: 3em; 233 | } 234 | body { 235 | min-height: 768px; 236 | } 237 | } 238 | @media (max-width:1280px){ 239 | .key { 240 | background: url(../images/pass.png) no-repeat 336px 18px; 241 | } 242 | body { 243 | min-height: 711px; 244 | } 245 | .copy-rights { 246 | margin-top: 0.5em; 247 | } 248 | } 249 | @media (max-width:1024px){ 250 | .login-form { 251 | width: 37%; 252 | } 253 | .key { 254 | background: url(../images/pass.png) no-repeat 339px 18px; 255 | } 256 | .copy-rights { 257 | margin-top: 3em; 258 | } 259 | h1 { 260 | padding-top: 2em; 261 | } 262 | body { 263 | min-height: 675px; 264 | } 265 | } 266 | @media (max-width:768px){ 267 | .login-form { 268 | width: 50%; 269 | margin: 12% auto 0 auto; 270 | } 271 | .key { 272 | background: url(../images/pass.png) no-repeat 342px 18px; 273 | } 274 | body { 275 | min-height: 929px; 276 | } 277 | } 278 | @media (max-width:640px){ 279 | .login-form { 280 | width: 60%; 281 | margin: 20% auto 0 auto; 282 | } 283 | .key { 284 | background: url(../images/pass.png) no-repeat 342px 19px; 285 | } 286 | } 287 | @media (max-width:480px){ 288 | .login-form { 289 | width: 80%; 290 | } 291 | } 292 | @media (max-width:320px){ 293 | h1 { 294 | padding-top: 1em; 295 | font-size: 1.5em; 296 | } 297 | .login-form { 298 | width: 90%; 299 | margin: 10% auto 0 auto; 300 | } 301 | input[type="text"] { 302 | width: 62%; 303 | padding: 1.2em 2em .5em 2.5em; 304 | font-size: 17px; 305 | margin-top: .5em; 306 | } 307 | input[type="password"] { 308 | width: 62%; 309 | padding: 1.2em 2em .5em 2.5em; 310 | font-size: 17px; 311 | margin-top: .5em; 312 | margin-bottom: 2em; 313 | background: url(../images/key.png) no-repeat 8px 23px; 314 | } 315 | .key { 316 | background: url(../images/pass.png) no-repeat 235px 27px; 317 | } 318 | .avtar img { 319 | margin: 1.1em 0 0; 320 | } 321 | .head-info { 322 | height: 35px; 323 | } 324 | label.lbl-1 { 325 | margin: 8px 10px 0px 0px; 326 | } 327 | label.lbl-2 { 328 | margin: 8px 10px 0px 0px; 329 | } 330 | label.lbl-3 { 331 | margin: 8px 10px 0px 0px; 332 | } 333 | .close { 334 | left: 16px; 335 | top: 13px; 336 | } 337 | .copy-rights { 338 | margin-top: 2em; 339 | } 340 | body { 341 | min-height: 504px; 342 | } 343 | input[type="submit"] { 344 | font-size: 28px; 345 | padding: 10px 0; 346 | } 347 | } 348 | /*--end-responsive-design--*/ -------------------------------------------------------------------------------- /controller/static/images/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/controller/static/images/Thumbs.db -------------------------------------------------------------------------------- /controller/static/images/adm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/controller/static/images/adm.png -------------------------------------------------------------------------------- /controller/static/images/avtar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/controller/static/images/avtar.png -------------------------------------------------------------------------------- /controller/static/images/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/controller/static/images/bg1.jpg -------------------------------------------------------------------------------- /controller/static/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/controller/static/images/close.png -------------------------------------------------------------------------------- /controller/static/images/key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/controller/static/images/key.png -------------------------------------------------------------------------------- /controller/static/images/pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/controller/static/images/pass.png -------------------------------------------------------------------------------- /controller/static/recorder.js: -------------------------------------------------------------------------------- 1 | var buttonRecord = document.getElementById("record"); 2 | var buttonStop = document.getElementById("stop"); 3 | 4 | buttonStop.disabled = true; 5 | 6 | buttonRecord.onclick = function () { 7 | // var url = window.location.href + "record_status"; 8 | buttonRecord.disabled = true; 9 | buttonStop.disabled = false; 10 | 11 | // 禁用下载链接 12 | var downloadLink = document.getElementById("download"); 13 | downloadLink.text = ""; 14 | downloadLink.href = ""; 15 | 16 | // XMLHttpRequest 17 | var xhr = new XMLHttpRequest(); 18 | xhr.onreadystatechange = function () { 19 | if (xhr.readyState == 4 && xhr.status == 200) { 20 | alert(xhr.responseText); 21 | } 22 | } 23 | xhr.open("POST", "/record_status"); 24 | xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 25 | xhr.send(JSON.stringify({status: "true"})); 26 | }; 27 | 28 | buttonStop.onclick = function () { 29 | buttonRecord.disabled = false; 30 | buttonStop.disabled = true; 31 | 32 | // XMLHttpRequest 33 | var xhr = new XMLHttpRequest(); 34 | xhr.onreadystatechange = function () { 35 | if (xhr.readyState == 4 && xhr.status == 200) { 36 | alert(xhr.responseText); 37 | 38 | // 设置下载链接 39 | var downloadLink = document.getElementById("download"); 40 | downloadLink.text = "下载视频"; 41 | downloadLink.href = "/static/video.avi"; 42 | } 43 | } 44 | xhr.open("POST", "/record_status"); 45 | xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 46 | xhr.send(JSON.stringify({status: "false"})); 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /controller/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 监控系统 9 | 15 | 16 | 17 |

视频流媒体直播

18 |
19 |
20 | 21 | 22 | 24 | 25 | 26 |
27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /controller/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Login 6 | 7 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 31 | 32 |

家庭监控系统

33 |
34 |
35 |
36 | 37 | 38 | 39 |
40 |
41 |
42 | 43 |
44 |
45 | 47 |
48 | 50 |
51 | 59 |
60 | 61 |
62 |
63 |

Copyright@2019 可以在我的Github上找到我 Github 64 | 如果喜欢记得star Blog

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /controller/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/controller/utils/__init__.py -------------------------------------------------------------------------------- /controller/utils/camera.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import threading 3 | 4 | 5 | class RecordingThread(threading.Thread): 6 | def __init__(self, name, camera): 7 | threading.Thread.__init__(self) 8 | self.name = name 9 | self.isRunning = True 10 | 11 | self.cap = camera 12 | fourcc = cv2.VideoWriter_fourcc(*'MJPG') 13 | self.out = cv2.VideoWriter('./static/video.avi', fourcc, 20.0, (640, 480)) 14 | 15 | def run(self): 16 | while self.isRunning: 17 | ret, frame = self.cap.read() 18 | if ret: 19 | self.out.write(frame) 20 | 21 | self.out.release() 22 | 23 | def stop(self): 24 | self.isRunning = False 25 | 26 | def __del__(self): 27 | self.out.release() 28 | 29 | 30 | class VideoCamera(object): 31 | def __init__(self): 32 | # 打开摄像头, 0代表笔记本内置摄像头 33 | self.cap = cv2.VideoCapture(0) 34 | 35 | # 初始化视频录制环境 36 | self.is_record = False 37 | self.out = None 38 | 39 | # 视频录制线程 40 | self.recordingThread = None 41 | 42 | # 退出程序释放摄像头 43 | def __del__(self): 44 | self.cap.release() 45 | 46 | def get_frame(self): 47 | ret, frame = self.cap.read() 48 | 49 | if ret: 50 | ret, jpeg = cv2.imencode('.jpg', frame) 51 | 52 | # 视频录制 53 | if self.is_record: 54 | if self.out == None: 55 | fourcc = cv2.VideoWriter_fourcc(*'MJPG') 56 | self.out = cv2.VideoWriter('./static/video.avi', fourcc, 20.0, (640, 480)) 57 | 58 | ret, frame = self.cap.read() 59 | if ret: 60 | self.out.write(frame) 61 | else: 62 | if self.out != None: 63 | self.out.release() 64 | self.out = None 65 | 66 | return jpeg.tobytes() 67 | 68 | else: 69 | return None 70 | 71 | def start_record(self): 72 | self.is_record = True 73 | self.recordingThread = RecordingThread("Video Recording Thread", self.cap) 74 | self.recordingThread.start() 75 | 76 | def stop_record(self): 77 | self.is_record = False 78 | 79 | if self.recordingThread != None: 80 | self.recordingThread.stop() 81 | -------------------------------------------------------------------------------- /img/hha.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/img/hha.jpeg -------------------------------------------------------------------------------- /img/index.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/img/index.jpg -------------------------------------------------------------------------------- /img/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/img/login.png -------------------------------------------------------------------------------- /img/main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/img/main.jpg -------------------------------------------------------------------------------- /img/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/img/tree.png -------------------------------------------------------------------------------- /logs/.keepgit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr1s77/flask-video-streaming-recorder/eb93162bf3b5a09a3193a38f89a7675b482bdacf/logs/.keepgit -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # from flask_script import Manager 2 | from controller import create_app 3 | 4 | # 创建APP对象 5 | app = create_app('dev') 6 | # # 创建脚本管理 7 | # mgr = Manager(app) 8 | 9 | 10 | if __name__ == '__main__': 11 | # mgr.run() 12 | app.run(threaded=True, host="0.0.0.0") 13 | 14 | --------------------------------------------------------------------------------