├── .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 |
8 |
9 |
10 |
11 |
12 | "推荐一波我的公众号,想要学习爬虫,大数据的可以关注一下,绝对满满的干货哦!" - 一个诈尸的人,哈哈
13 |
14 |
15 |
16 |
17 |
18 |
20 |
21 |
22 |
24 |
25 |
26 |
28 |
29 |
30 |
31 |
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 | 
107 |
108 | - 抽取了代码,进行了优化,就是这样目录看起来会很多
109 |
110 | - Added a beautiful login interface
111 | 
112 |
113 | - Optimization homepage
114 |
115 | 
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 |
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 |
--------------------------------------------------------------------------------