UC01–远程查看孩子的实时电脑桌面
US01-日志记录:
- 日志对象:MyLogger
- 简单的日志格式:2022-05-02 20:09:14.399::MainProcess:: INFO:: 主进程启动,主进程pid:18016
- 复杂的日志格式:2022-05-02 20:10:27.318 +0800|MainProcess|MainThread:18024-12804|capture_uc01.py:103|INFO|主程序运行中
- 日志支持控制台和文件双输出
- 日志文件为循环日志文件,每个5M,备份3个文件,utf-8编码
- 日志文件名:日志名称.log, 备份文件为 日志文件名.1.log,例如:MainProcess.log, MainProcess.1.log, MainProcess.2.log
- 主进程对象:MainProcess
import logging
from logging.handlers import RotatingFileHandler
import os
import signal
import sys
import time
__all__ = ("MyLogger", "MainProcess")
def __dir__():
return __all__
class MyLogger:
log_path = os.path.join(os.path.dirname(__file__), "logs", "my.log")
@classmethod
def get(cls, log_name=__name__, level=logging.INFO,
fmt_simple=True, logger=None):
if not logger:
logger = logging.getLogger(log_name)
logger.setLevel(level=level)
fmt_str = ("%(asctime)s.%(msecs)03d {}|%(name)s|"
"%(threadName)s:%(process)d-%(thread)d|"
"%(filename)s:%(lineno)s|"
"%(levelname)s|%(message)s").format(time.strftime("%z"))
if fmt_simple:
fmt_str = ("%(asctime)s.%(msecs)03d::%(name)s::"
"%(levelname)5s:: %(message)s")
date_fmt = "%Y-%m-%d %H:%M:%S"
log_format = logging.Formatter(fmt_str, datefmt=date_fmt)
log_file_name = os.path.join(os.path.dirname(cls.log_path),
f"{log_name}.log")
handle = RotatingFileHandler(log_file_name, maxBytes=5 * 1024 * 1024,
backupCount=3, encoding="utf-8")
handle.setLevel(level)
handle.setFormatter(log_format)
handle.namer = cls.log_file_name
logger.addHandler(handle)
console = logging.StreamHandler()
console.setLevel(level)
console.setFormatter(log_format)
logger.addHandler(console)
return logger
@staticmethod
def log_file_name(default_filename):
dir_name = os.path.dirname(default_filename)
file_name = os.path.basename(default_filename)
fields = file_name.split(".")
if len(fields) < 3:
return default_filename
fields[-1], fields[-2] = fields[-2], fields[-1]
file_name = ".".join(fields)
file_name = os.path.join(dir_name, file_name)
return file_name
class MainProcess:
logger = MyLogger.get("MainProcess", level=logging.INFO)
@classmethod
def break_signal_func(cls, signal_value, frame):
cls.logger.info("信号:%s %s", str(signal_value), str(frame))
time.sleep(1)
sys.exit(0)
@classmethod
def signal_proc(cls):
signal.signal(signal.SIGINT, cls.break_signal_func)
signal.signal(signal.SIGTERM, cls.break_signal_func)
signal.signal(signal.SIGILL, cls.break_signal_func)
@classmethod
def main_run(cls):
cls.logger.info("主进程启动,主进程pid:%d", os.getpid())
cls.signal_proc()
while True:
cls.logger.info("主程序运行中")
time.sleep(30)
if __name__ == '__main__':
MainProcess.main_run()
US02-多进程处理:
- 主进程对象:MainProcess
- capture() 抓屏进程处理函数
- web() web服务进程处理函数
- Web服务对象:MonitorPageWebService
- 抓屏对象:Capture
- capture() 抓屏处理函数
- del_his_file() 删除三天前的历史图片文件函数
class MonitorPageWebService:
logger = None
save_capture_pic_path = os.path.join(
os.path.dirname(__file__), "static", "capture_pic")
@classmethod
def main_run(cls):
cls.logger = MyLogger.get("WebService", level=logging.INFO)
while True:
cls.logger.info("程序运行中")
time.sleep(30)
class Capture:
sampling_times_minute = 1
save_pic_path = os.path.join(os.path.dirname(__file__),
"static", "capture_pic")
logger = MyLogger.get("Capture", level=logging.DEBUG)
@classmethod
def capture(cls):
while True:
cls.logger.info("程序运行中")
time.sleep(30)
@classmethod
def del_his_file(cls, del_secs=3 * 24 * 3600):
""""""
class MainProcess:
logger = MyLogger.get("MainProcess", level=logging.INFO)
child_process = []
@classmethod
def break_signal_func(cls, signal_value, frame):
cls.logger.info("信号:%s %s", str(signal_value), str(frame))
for child in cls.child_process:
child.terminate()
cls.logger.info("终止:%s", str(child))
cls.logger.info("等待子进程退出")
time.sleep(1)
sys.exit(0)
@classmethod
def signal_proc(cls):
signal.signal(signal.SIGINT, cls.break_signal_func)
signal.signal(signal.SIGTERM, cls.break_signal_func)
signal.signal(signal.SIGILL, cls.break_signal_func)
@classmethod
def main_run(cls):
cls.logger.info("主进程启动,主进程pid:%d", os.getpid())
capture_proc = mp.Process(target=cls.capture, name="抓屏进程")
cls.child_process.append(capture_proc)
capture_proc.start()
cls.logger.info("已启动进程:%s", str(capture_proc))
web_proc = mp.Process(target=cls.web, name="Web服务进程")
cls.child_process.append(web_proc)
web_proc.start()
cls.logger.info("已启动进程:%s", str(web_proc))
cls.signal_proc()
while True:
cls.logger.info("主程序运行中")
time.sleep(30)
@classmethod
def capture(cls):
Capture.sampling_times_minute = 2
Capture.del_his_file()
Capture.capture()
@classmethod
def web(cls):
MonitorPageWebService.main_run()
US03-提供Web服务:
- 界面设计:
- 上面一排操作单选框:实时屏幕画面、当前屏幕图片、历史屏幕图片、实时摄像头画面
- 下面是图片或者视频窗口
- Web服务对象:MonitorPageWebService
- main_run() web服务主函数
- get_html() 主界面Html模板代码
- camera_gen(camera) 生成摄像头视频流一帧
- camera_video_feed() 返给前台视频流一帧
- screen_gen() 生成屏幕视频流一帧
- screen_video_feed():返给前台屏幕视频流一帧
- new_capture_pic() 获取抓屏最新一张图片
- his_capture_pic(filename): 返回前台一张图片,使用send_file, mimetype=‘image/png’
- main_page(): 返给前台主页面
- args_proc(): 命令行参数处理,获取允许的接入ip地址和服务的端口
- main_run函数分析:
- main_page(): 主页面, url路由:/
- screen_video_feed(): 屏幕视频流地址, url路由:/screen_video_feed
- new_capture_pic(): 最新图片, url路由:/new_capture_pic
- his_capture_pic(): 历史图片, url路由:/his_capture_pic//
- camera_video_feed(): 摄像头视频流地址, url路由:/camera_video_feed
- limit_remote_addr(): 限定访问的IP地址
class MonitorPageWebService:
logger = None
save_capture_pic_path = os.path.join(
os.path.dirname(__file__), "static", "capture_pic")
def __init__(self):
""""""
@classmethod
def get_html(cls, form_field):
html = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>学习屏幕监控器</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
</head>
<body>
<form id=main_from action="." method="POST">
<table border = 0 cellpadding="20">
<tr>
<td></td>
{% for handle in handlers.values() %}
<td>
<input type="radio" name="operator"
value="{{handle['value']}}"
οnclick="main_from.submit();"
{{handle['checked']}}>{{handle['text']}}
</td>
{% endfor %}
</tr>
</table>
<table border = 0>
<tr>
<td colspan="2">
<div id="displayFileName" style="text-align:center">
</div>
</td>
</tr>
<tr>
<td>
<div style="height:600px; width:100%; overflow:auto">
<div>
{% for filename in his_filename_list %}
<a href="javascript:void(0);" οnclick="fa_click(this)">
{{ filename }} </a> <BR>
{% endfor %}
</div>
</div>
</td>
<td>
<img id="displayPic" src="{{ cur_handler.imgSrc }}"
width="100%" height="100%" alt="monitor capture"
οnclick="img_display_pic_onclick(this)">
</td>
</tr>
</table>
</form>
<script type="text/javascript">
function img_display_pic_onclick(elem){
var src = elem.src.split('?')[0];
src = src + '?' + (new Date().getTime());
elem.src = src;
}
</script>
</body>
</html>
"""
handlers = {
"realTimeScreen": {
"value": "realTimeScreen", "text": "实时屏幕画面",
"checked": "checked", "imgSrc": url_for('screen_video_feed')
},
"curPic": {"value": "curPic", "text": "当前屏幕图片",
"checked": "", "imgSrc": url_for('new_capture_pic')},
"hisPic": {"value": "hisPic", "text": "历史屏幕图片",
"checked": "", "imgSrc": url_for('new_capture_pic')},
"realTimeCamera": {
"value": "realTimeCamera", "text": "实时摄像头画面",
"checked": "", "imgSrc": url_for('camera_video_feed')},
}
cur_operator = ""
if form_field.get("operator", None):
for item in handlers.values():
if item["value"] == form_field["operator"]:
cur_operator = form_field["operator"]
item["checked"] = "checked"
else:
item["checked"] = ""
his_filename_list = []
if form_field["operator"] == "hisPic":
cur_operator = "hisPic"
his_filename_list = cls.get_capture_his_pic()
handlers["hisPic"]["imgSrc"] = url_for('new_capture_pic')
return render_template_string(html, form_field=form_field,
handlers=handlers,
cur_handler=handlers[cur_operator],
his_filename_list=his_filename_list)
@staticmethod
def camera_gen(camera):
return ''
@classmethod
def camera_video_feed(cls):
"""视频流的路线。将其放在img标记的src属性中。"""
return ""
@staticmethod
def screen_gen():
return ''
@classmethod
def screen_video_feed(cls):
"""视频流的路线。将其放在img标记的src属性中。"""
return ''
@classmethod
def new_capture_pic(cls):
return ''
@classmethod
def his_capture_pic(cls, filename):
if not filename:
filename_list = os.listdir(cls.save_capture_pic_path)
if filename_list:
filename = filename_list[0]
else:
filename = os.path.join(cls.save_capture_pic_path,
"..", "favicon.ico")
new_filename = os.path.join(cls.save_capture_pic_path, filename)
return send_file(new_filename, mimetype='image/png')
@classmethod
def main_page(cls):
"""主页面"""
if request.method not in ('GET', 'POST'):
return jsonify({'code': 500, 'msg': '不支持该请求'})
form_field = {"operator": "realTimeScreen"}
if request.method == 'POST':
form_field = request.form
return cls.get_html(form_field)
@staticmethod
def args_proc():
allowed_remote_address = ""
port = 22430
if len(sys.argv) > 1:
port = sys.argv[1]
if len(sys.argv) > 2:
allowed_remote_address = f";{sys.argv[2]};"
return allowed_remote_address, port
@classmethod
def main_run(cls):
app = Flask(__name__)
cls.logger = MyLogger.get("WebService", level=logging.INFO,
logger=app.logger)
allowed_remote_address, port = cls.args_proc()
@app.route('/', methods=['GET', 'POST'])
def main_page():
return cls.main_page()
@app.route('/screen_video_feed')
def screen_video_feed():
return cls.screen_video_feed()
@app.route('/new_capture_pic')
def new_capture_pic():
return cls.new_capture_pic()
@app.route('/his_capture_pic/<filename>/')
def his_capture_pic(filename=""):
return cls.his_capture_pic(filename)
@app.route('/camera_video_feed')
def camera_video_feed():
return cls.camera_video_feed()
@app.before_request
def limit_remote_addr():
if not allowed_remote_address:
return
if allowed_remote_address.find(request.remote_addr) < 0:
abort(403)
app.run(host="0.0.0.0", port=port)
US04-抓屏:
- 抓屏对象Capture:
- sampling_times_minute 每分钟采样次数
- save_pic_path 保存图片的路径
- logger 日志对象
- capture() 实时不停的抓拍
- get_filename() 获取截图文件名
- del_his_file() 删除del_secs=3 * 24 * 3600秒前的历史文件
class Capture:
sampling_times_minute = 1
save_pic_path = os.path.join(os.path.dirname(__file__),
"static", "capture_pic")
logger = MyLogger.get("Capture", level=logging.DEBUG)
@classmethod
def capture(cls):
while True:
filename = cls.get_filename()
im = ImageGrab.grab()
im.save(filename)
cls.logger.info("生成文件:%s", filename)
time.sleep(0.4)
@classmethod
def get_filename(cls):
now_str = time.strftime("%Y%m%d_%H%M%S")
filename = os.path.join(cls.save_pic_path, f"capture_{now_str}.png")
return filename
@classmethod
def del_his_file(cls, del_secs=3 * 24 * 3600):
""""""
for filename in os.listdir(cls.save_pic_path):
filename = os.path.join(cls.save_pic_path, filename)
if not os.path.isfile(filename):
continue
file_stat = os.stat(filename)
if file_stat.st_ctime + del_secs < time.time():
cls.logger.info("删除文件:%s", filename)
US05-视频流:
- WEb服务对象MonitorPageWebService:
- screen_gen 产生一帧图片
- screen_video_feed 返回给前台
- main_run.screen_video_feed 路由
- img的src为screen_video_feed,从url获取图片
class MonitorPageWebService:
......
@classmethod
def get_html(cls, form_field):
html = """
......
<img id="displayPic" src="/screen_video_feed"
width="100%" height="100%" alt="monitor capture" >
......
"""
......
@staticmethod
def screen_gen():
while True:
pil_image = ImageGrab.grab()
image = cv2.cvtColor(np.asarray(pil_image), cv2.COLOR_RGB2BGR)
ret, jpeg = cv2.imencode('.jpg', image)
frame = jpeg.tobytes()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
@classmethod
def screen_video_feed(cls):
"""视频流的路线。将其放在img标记的src属性中。"""
return Response(cls.screen_gen(),
mimetype='multipart/x-mixed-replace; boundary=frame')
......
@classmethod
def main_run(cls):
......
@app.route('/screen_video_feed')
def screen_video_feed():
return cls.screen_video_feed()
......