This commit is contained in:
ascendhuawei
2020-09-16 11:50:53 -07:00
commit a61dda4612
97 changed files with 15410 additions and 0 deletions
@@ -0,0 +1,10 @@
[baseconf]
# A socket server address to communicate with presenter agent
# Please ensure that the port does not conflict, only support Ipv4
presenter_server_ip=192.168.1.223
presenter_server_port=7006
# A http server address, you can visit the website by "http//web_server_ip:web_server_port".
# Only support Chrome now.
web_server_ip=192.168.1.223
web_server_port=7007
@@ -0,0 +1,27 @@
[loggers]
keys=root,body_pose
[handlers]
keys=rotatingFileHandler
[formatters]
keys=simpleFmt
[logger_root]
level=DEBUG
handlers=rotatingFileHandler
[logger_body_pose]
level=DEBUG
handlers=rotatingFileHandler
qualname=body_pose
propagate=0
[handler_rotatingFileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFmt
args=("body_pose.log", "a", 10*1024*1024, 2)
[formatter_simpleFmt]
format=%(asctime)s-%(levelname)s-%(filename)s:%(lineno)s %(message)s
@@ -0,0 +1,225 @@
# =======================================================================
#
# Copyright (C) 2018, Hisilicon Technologies Co., Ltd. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2 Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3 Neither the names of the copyright holders nor the names of the
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# =======================================================================
#
"""presenter socket server module"""
import os
import logging
from logging.config import fileConfig
from google.protobuf.message import DecodeError
import common.presenter_message_pb2 as pb2
from common.channel_manager import ChannelManager
from common.presenter_socket_server import PresenterSocketServer
from body_pose.src.config_parser import ConfigParser
class BodyPoseServer(PresenterSocketServer):
'''A server forbody pose'''
def __init__(self, server_address):
'''init func'''
self.channel_manager = ChannelManager(["image", "video"])
super(BodyPoseServer, self).__init__(server_address)
def _clean_connect(self, sock_fileno, epoll, conns, msgs):
"""
close socket, and clean local variables
Args:
sock_fileno: a socket fileno, return value of socket.fileno()
epoll: a set of select.epoll.
conns: all socket connections registered in epoll
msgs: msg read from a socket
"""
logging.info("clean fd:%s, conns:%s", sock_fileno, conns)
self.channel_manager.clean_channel_resource_by_fd(sock_fileno)
epoll.unregister(sock_fileno)
conns[sock_fileno].close()
del conns[sock_fileno]
del msgs[sock_fileno]
def _process_msg(self, conn, msg_name, msg_data):
"""
Total entrance to process protobuf msg
Args:
conn: a socket connection
msg_name: name of a msg.
msg_data: msg body, serialized by protobuf
Returns:
False:somme error occured
True:succeed
"""
# process open channel request
if msg_name == pb2._OPENCHANNELREQUEST.full_name:
ret = self._process_open_channel(conn, msg_data)
# process image request, receive an image data from presenter agent
elif msg_name == pb2._PRESENTIMAGEREQUEST.full_name:
ret = self._process_image_request(conn, msg_data)
# process heartbeat request, it used to keepalive a channel path
elif msg_name == pb2._HEARTBEATMESSAGE.full_name:
ret = self._process_heartbeat(conn)
else:
logging.error("Not recognized msg type %s", msg_name)
ret = False
return ret
def _response_image_request(self, conn, response, err_code):
"""
Assemble protobuf to response image_request
Message structure like this:
--------------------------------------------------------------------
|total message len | int | 4 bytes |
|-------------------------------------------------------------------
|message name len | byte | 1 byte |
|-------------------------------------------------------------------
|message name | string | xx bytes |
|-------------------------------------------------------------------
|message body | protobuf | xx bytes |
--------------------------------------------------------------------
protobuf structure like this:
--------------------------------------------------------------------
|error_code | enum | PresentDataErrorCode |
|-------------------------------------------------------------------
|error_message | string | xx bytes |
|-------------------------------------------------------------------
enum PresentDataErrorCode {
kPresentDataErrorNone = 0;
kPresentDataErrorUnsupportedType = 1;
kPresentDataErrorUnsupportedFormat = 2;
kPresentDataErrorOther = -1;
}
"""
response.error_code = err_code
ret_code = True
if err_code == pb2.kPresentDataErrorUnsupportedFormat:
response.error_message = "Present data not support format."
logging.error("Present data not support format.")
ret_code = False
elif err_code == pb2.kPresentDataErrorNone:
response.error_message = "Present data ok"
ret_code = True
else:
response.error_message = "Present data not known error."
logging.error("Present data not known error.")
ret_code = False
self.send_message(conn, response, pb2._PRESENTIMAGERESPONSE.full_name)
return ret_code
def _process_image_request(self, conn, msg_data):
"""
Deserialization protobuf and process image_request
Args:
conn: a socket connection
msg_data: a protobuf struct, include image request.
Returns:
protobuf structure like this:
------------------------------------
|format | ImageFormat |
|------------------------------------
|width | uint32 |
|------------------------------------
|height | uint32 |
|------------------------------------
|data | bytes |
------------------------------------
enum ImageFormat {
kImageFormatJpeg = 0;
}
"""
request = pb2.PresentImageRequest()
response = pb2.PresentImageResponse()
# Parse msg_data from protobuf
try:
request.ParseFromString(msg_data)
except DecodeError:
logging.error("ParseFromString exception: Error parsing message")
err_code = pb2.kPresentDataErrorOther
return self._response_image_request(conn, response, err_code)
sock_fileno = conn.fileno()
handler = self.channel_manager.get_channel_handler_by_fd(sock_fileno)
if handler is None:
logging.error("get channel handler failed")
err_code = pb2.kPresentDataErrorOther
return self._response_image_request(conn, response, err_code)
# Currently, image format only support jpeg
if request.format != pb2.kImageFormatJpeg:
logging.error("image format %s not support", request.format)
err_code = pb2.kPresentDataErrorUnsupportedFormat
return self._response_image_request(conn, response, err_code)
rectangle_list = []
if request.rectangle_list:
for one_rectangle in request.rectangle_list:
rectangle = []
rectangle.append(one_rectangle.left_top.x)
rectangle.append(one_rectangle.left_top.y)
rectangle.append(one_rectangle.right_bottom.x)
rectangle.append(one_rectangle.right_bottom.y)
rectangle.append(one_rectangle.label_text)
# add the detection result to list
rectangle_list.append(rectangle)
handler.save_image(request.data, request.width, request.height, rectangle_list)
return self._response_image_request(conn, response,
pb2.kPresentDataErrorNone)
def stop_thread(self):
channel_manager = ChannelManager([])
channel_manager.close_all_thread()
self.set_exit_switch()
def run():
'''Entrance function of Body Pose Server '''
# read config file
config = ConfigParser()
# config log
log_file_path = os.path.join(ConfigParser.root_path, "config/logging.conf")
fileConfig(log_file_path)
logging.getLogger('body_pose')
if not config.config_verify():
return None
logging.info("presenter server is starting...")
server_address = (config.presenter_server_ip,
int(config.presenter_server_port))
return BodyPoseServer(server_address)
@@ -0,0 +1,83 @@
# =======================================================================
#
# Copyright (C) 2018, Hisilicon Technologies Co., Ltd. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2 Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3 Neither the names of the copyright holders nor the names of the
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# =======================================================================
#
"""pose detection config parser module"""
import os
import configparser
import common.parameter_validation as validate
class ConfigParser():
""" parse configuration from the config.conf"""
__instance = None
def __init__(self):
"""init"""
def __new__(cls):
"""ensure class object is a single instance"""
if cls.__instance is None:
cls.__instance = object.__new__(cls)
cls.config_parser()
return cls.__instance
def config_verify(self):
'''Verify configuration Parameters '''
if not validate.validate_ip(ConfigParser.web_server_ip) or \
not validate.validate_ip(ConfigParser.presenter_server_ip) or \
not validate.validate_port(ConfigParser.web_server_port) or \
not validate.validate_port(ConfigParser.presenter_server_port):
return False
return True
@classmethod
def config_parser(cls):
"""parser config from config.conf"""
config_parser = configparser.ConfigParser()
cls.root_path = ConfigParser.get_rootpath()
config_file = os.path.join(cls.root_path, "config/config.conf")
config_parser.read(config_file)
cls.web_server_ip = config_parser.get('baseconf', 'web_server_ip')
cls.presenter_server_ip = \
config_parser.get('baseconf', 'presenter_server_ip')
cls.web_server_port = config_parser.get('baseconf', 'web_server_port')
cls.presenter_server_port = \
config_parser.get('baseconf', 'presenter_server_port')
@staticmethod
def get_rootpath():
"""get presenter server's root directory."""
path = __file__
idx = path.rfind("src")
return path[0:idx]
+474
View File
@@ -0,0 +1,474 @@
# -*- coding: UTF-8 -*-
#
# =======================================================================
#
# Copyright (C) 2018, Hisilicon Technologies Co., Ltd. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2 Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3 Neither the names of the copyright holders nor the names of the
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# =======================================================================
#
"""
web application for presenter server.
"""
import os
import re
import random
import base64
import threading
import time
import logging
import tornado.ioloop
import tornado.web
import tornado.gen
import tornado.websocket
import body_pose.src.config_parser as config_parser
from common.channel_manager import ChannelManager
class WebApp:
"""
web application
"""
__instance = None
def __init__(self):
"""
init method
"""
self.channel_mgr = ChannelManager(["image", "video"])
self.request_list = set()
self.lock = threading.Lock()
def __new__(cls, *args, **kwargs):
# if instance is None than create one
if cls.__instance is None:
cls.__instance = object.__new__(cls, *args, **kwargs)
return cls.__instance
def add_channel(self, channel_name):
"""
add channel
@param channel_name name of channel
@return: return add status and message (for error status)
"""
ret = {"ret":"error", "msg":""}
# check channel_name validate,
# channel_name can not be None or length = 0
if channel_name is None:
logging.info("Channel name is None , add channel failed")
ret["msg"] = "Channel name can not be empty"
return ret
# strip channel name
channel_name = channel_name.strip()
# check channel_name emtpy or not
if channel_name == "":
logging.info("Channel name is emtpy , add channel failed")
ret["msg"] = "Channel name can not be empty"
return ret
# length of channel name can not over 25
if len(channel_name) > 25:
logging.info("Length of channel name %s > 25 , add channel failed", channel_name)
ret["msg"] = "Length of channel name should less than 25"
return ret
# define pattern support a-z A-Z and /
pattern = re.compile(r"[a-z]|[A-Z]|[0-9]|/")
tmp = pattern.findall(channel_name)
# check reuslt changed or not
if len(tmp) != len(channel_name):
logging.info("%s contain invalidate character, add channel failed", channel_name)
ret["msg"] = "Channel name only support 0-9, a-z, A-Z /"
return ret
# register channel
flag = self.channel_mgr.register_one_channel(channel_name)
# check register result
if self.channel_mgr.err_code_too_many_channel == flag:
logging.info("Only supports up to 10 channels, add channel failed")
ret["msg"] = "Only supports up to 10 channels"
elif self.channel_mgr.err_code_repeat_channel == flag:
logging.info("%s already exist, add channel failed", channel_name)
ret["msg"] = "Channel %s already exist" % channel_name
else:
logging.info("add channel %s succeed", channel_name)
ret["ret"] = "success"
return ret
def del_channel(self, names):
"""
delete channel
@param names: channel name to be deleted, separated by ','
@return: return add status and message (for error status)
"""
# init ret for return
ret = {"ret":"error", "msg":""}
# check length of names
if names.strip() == "":
logging.info("Channel name is empty, delete channel failed")
ret["msg"] = "Channel name should not be empty"
return ret
# split name for multi name
listname = names.split(",")
# unregister name
for item in listname:
item = item.strip()
# if name is emtpy continu
if item == "":
continue
self.channel_mgr.unregister_one_channel(item)
logging.info("delete channel %s succeed", item)
ret["ret"] = "success"
return ret
def list_channels(self):
"""
list all channels information
"""
# list register channels
ret = self.channel_mgr.list_channels()
# id for every channel item , start with 1
idx = 1
# set id for channel
for item in ret:
item['id'] = idx
idx = idx + 1
return ret
def is_channel_exists(self, name):
"""
view channel content via browser.
@param name : channel name
@return return True if exists. otherwise return False.
"""
return self.channel_mgr.is_channel_exist(name)
def add_requst(self, request):
"""
add request
@param requst: request item to be stored
@note: request can not be same with other request.
request is identified by (channel name ,random number)
so this method do not return value.
"""
with self.lock:
self.request_list.add(request)
def has_request(self, request):
"""
whether request exist or not
@param request: request to be checked.
@return: return True if exists, otherwise return False.
"""
with self.lock:
for item in self.request_list:
# check request equal
if item[0] == request[0] and item[1] == request[1]:
return True
return False
def get_media_data(self, channel_name):
"""
get media data by channel name
@param channel_name: channel to be quest data.
@return return dictionary which have for item
type: identify channel type, for image or video.
image: data to be returned.
fps: just for video type
status: can be error, ok, or loading.
"""
# channel exists or not
if self.is_channel_exists(channel_name) is False:
return {'type': 'unkown', 'image':'', 'fps':0, 'status':'error'}
image_data = self.channel_mgr.get_channel_image(channel_name)
# only for image type.
if image_data is not None:
image_data = base64.b64encode(image_data).decode('utf-8')
return {'type': 'image', 'image':image_data, 'fps':0, 'status':'ok'}
fps = 0 # fps for video
image = None # image for video & image
rectangle_list = None
handler = self.channel_mgr.get_channel_handler_by_name(channel_name)
if handler is not None:
media_type = handler.get_media_type()
# if type is image then get image data
if media_type == "image":
image = handler.get_image_data()
# for video
else:
frame_info = handler.get_frame()
image = frame_info[0]
fps = frame_info[1]
rectangle_list = frame_info[4]
status = "loading"
# decode binary to utf-8 when image is not None
if image is not None:
status = "ok"
image = base64.b64encode(image).decode('utf-8')
return {'type': media_type, 'image':image, 'fps':fps, 'status':status, 'rectangle_list':rectangle_list}
else:
return {'type': 'unkown', 'image':None, 'fps':0, 'status':'loading'}
# pylint: disable=abstract-method
class BaseHandler(tornado.web.RequestHandler):
"""
base handler.
"""
# pylint: disable=abstract-method
class HomeHandler(BaseHandler):
"""
handler index request
"""
@tornado.web.asynchronous
def get(self, *args, **kwargs):
"""
handle home or index request only for get
"""
self.render("home.html", listret=G_WEBAPP.list_channels())
# pylint: disable=abstract-method
class AddHandler(BaseHandler):
"""
handler add request
"""
@tornado.web.asynchronous
def post(self, *args, **kwargs):
"""
handle reqeust for add channel
"""
channel_name = self.get_argument('name', '')
self.finish(G_WEBAPP.add_channel(channel_name))
# pylint: disable=abstract-method
class DelHandler(BaseHandler):
"""
handler delete request
"""
@tornado.web.asynchronous
def post(self, *args, **kwargs):
"""
handel requst for delete channel
"""
channel_name = self.get_argument('name', '')
self.finish(G_WEBAPP.del_channel(channel_name))
# pylint: disable=abstract-method
class ViewHandler(BaseHandler):
"""
handler view request
"""
@tornado.web.asynchronous
def get(self, *args, **kwargs):
"""
handler request for view channel
"""
channel_name = self.get_argument('name', '')
if G_WEBAPP.is_channel_exists(channel_name):
req_id = str(random.random())
G_WEBAPP.add_requst((req_id, channel_name))
self.render('view.html', channel_name=channel_name, req=req_id)
else:
raise tornado.web.HTTPError(404)
class WebSocket(tornado.websocket.WebSocketHandler):
"""
web socket for web page socket quest
"""
def open(self, *args, **kwargs):
"""
called when client request by ws or wss
"""
self.req_id = self.get_argument("req", '', True)
self.channel_name = self.get_argument("name", '', True)
# check request valid or not.
if not G_WEBAPP.has_request((self.req_id, self.channel_name)):
self.close()
@staticmethod
def send_message(obj, message, binary=False):
"""
send message to client.
"""
# check socket exist or not
if not obj.ws_connection or not obj.ws_connection.stream.socket:
return False
ret = False
try:
obj.write_message(message, binary)
ret = True
except tornado.websocket.WebSocketClosedError:
ret = False
return ret
def on_close(self):
"""
called when closed web socket
"""
@tornado.web.asynchronous
@tornado.gen.coroutine
def on_message(self, message):
"""
On recv message from client.
"""
if message == "next":
self.run_task()
def run_task(self):
"""
send image to client
"""
# check channel valid
if not G_WEBAPP.is_channel_exists(self.channel_name) or \
not G_WEBAPP.has_request((self.req_id, self.channel_name)):
self.close()
return
result = G_WEBAPP.get_media_data(self.channel_name)
# sleep 100ms if status not ok for frequently query
if result['status'] != 'ok':
time.sleep(0.1)
# if channel not exist close websocket.
if result['status'] == "error":
self.close()
# send message to client
else:
# close websoket when send failed or for image channel.
ret = WebSocket.send_message(self, result)
if not ret or result['type'] == "image":
self.close()
def get_webapp():
"""
start web applicatioin
"""
# get template file and static file path.
templatepath = os.path.join(config_parser.ConfigParser.get_rootpath(), "ui/templates")
staticfilepath = os.path.join(config_parser.ConfigParser.get_rootpath(), "ui/static")
# create application object.
app = tornado.web.Application(handlers=[(r"/", HomeHandler),
(r"/index", HomeHandler),
(r"/add", AddHandler),
(r"/del", DelHandler),
(r"/view", ViewHandler),
(r"/static/(.*)",
tornado.web.StaticFileHandler,
{"path": staticfilepath}),
(r"/websocket", WebSocket)],
template_path=templatepath)
# create server
http_server = tornado.httpserver.HTTPServer(app)
return http_server
def start_webapp():
"""
start webapp
"""
http_server = get_webapp()
config = config_parser.ConfigParser()
http_server.listen(config.web_server_port, address=config.web_server_ip)
print("Please visit http://" + config.web_server_ip + ":" +
str(config.web_server_port) + " for body pose")
tornado.ioloop.IOLoop.instance().start()
def stop_webapp():
"""
stop web app
"""
tornado.ioloop.IOLoop.instance().stop()
global G_WEBAPP
G_WEBAPP = WebApp()
@@ -0,0 +1,39 @@
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote,
dl, dt, dd, ul, ol, li,
pre,
form, fieldset, legend, button, input, textarea,
th, td {
margin: 0;
padding: 0;
}
body,
button, input, select, textarea /* for ie */ {
font: 14px/1.5 tahoma, \5b8b\4f53, sans-serif;
}
h1, h2, h3, h4, h5, h6 { font-size: 100%; font-weight: normal;}
address, cite, dfn, em, var { font-style: normal; }
code, kbd, pre, samp { font-family: courier new, courier, monospace; }
small { font-size: 12px; }
ul, ol { list-style: none; }
a { text-decoration: none; }
a:hover { text-decoration: underline; }
sup { vertical-align: text-top; }
sub { vertical-align: text-bottom; }
legend { color: #000; } /* for ie6 */
fieldset, img { border: 0; }
button, input, select, textarea { font-size: 100%; }
table { border-collapse: collapse; border-spacing: 0; }
article, aside, details, figcaption, figure, footer,header, hgroup, menu, nav, section,
summary, time, mark, audio, video {
display: block;
margin: 0;
padding: 0;
}
mark { background: #ff0; }
@@ -0,0 +1,77 @@
#dlg-mask {
display: none;
position: fixed;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 10000;
background-color: rgba(0,0,0,0.75)/*rgba(140,140,140,0.4)*/;
}
#dlg-mask * {
color: #000;
box-sizing: border-box;
-moz-box-sizing: border-box; /* Firefox */
-webkit-box-sizing: border-box; /* Safari */
}
#dlg-box {
position: absolute;
width: 350px;
z-index: 200001;
padding: 25px;
background-color: rgba(255, 255, 255, 0.88);
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border-radius: 6px;
}
#dlg-box h1 {
width: 100%;
height: 20px;
line-height: 20px;
margin: 0;
font-weight: bold;
font-size: 18px;
}
#dlg-box p, #dlg-box input {
width: 100%;
margin: 20px 0;
font-size: 15px;
}
#dlg-box p {
width: 100%;
margin: 20px 0;
font-size: 15px;
line-height: 1.2;
word-wrap: break-word;
word-break: normal;
white-space: normal;
}
#dlg-box input {
height: 29px;
cursor: text;
}
#dlg-box div {
height: 30px;
}
#dlg-box div span {
display: inline-block;
float: right;
width: 64px;
height: 30px;
font-size: 15px;
color: #187be1;
text-align: center;
line-height: 30px;
cursor: pointer;
}
#dlg-box div span:hover {
background: #eee;
}
.dot {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@@ -0,0 +1,132 @@
body{
background-color: #edf7f9;
font-family: Arial, , Tahoma;
}
.nav{
width: 100%;
height: 80px;
background-color: #155070;
padding-left: 50px;
padding-right: 50px;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
box-sizing: border-box;
}
.nav_left{
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
}
.nav_left img{
width: 60px;
height: 60px;
margin-right: 20px;
}
.nav_left p span{
font-size: 24px;
font-weight: bold;
color: #ffffff;
}
.box_top{
width: 1024px;
margin: 0 auto;
height: 50px;
line-height: 50px;
font-weight: bold;
}
.box_content{
width: 1024px;
margin: 0 auto;
background-color: #ffffff;
}
.content_top{
height: 50px;
padding-top: 10px;
box-sizing: border-box;
-webkit-box-sizing: border-box;
}
.content_top li{
width: 82px;
height: 32px;
float: left;
border: 1px solid #51a8da;
margin-right: 5px;
color: #51a8da;
display: flex;
align-items: center;
justify-content: space-around;
padding-left: 7px;
padding-right: 7px;
box-sizing: border-box;
-webkit-box-sizing: border-box;
cursor: pointer;
}
.content_top li:nth-child(3){
background-color: #51a8da;
color: #ffffff;
}
.content_mid{
border-top:2px solid #51a8da;
padding-top: 15px;
height: 0;
display: none;
}
.mid_list{
width: 100%;
height: 40px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-right: 0;
box-sizing: border-box;
-webkit-box-sizing: border-box;
}
.mid_list li{
float: left;
height: 100%;
width: 25%;
line-height: 40px;
padding-left: 20px;
padding-right: 20px;
box-sizing: border-box;
-webkit-box-sizing: border-box;
border-right: 1px solid #ccc;
}
.mid_list li:nth-child(odd){
background-color: #edf7f9;
}
.mid_list li:nth-child(odd) span:nth-child(2){
float: right;
}
.mid_add{
width: 82px;
height: 32px;
background-color: #51a8da;
color: #ffffff;
display: flex;
align-items: center;
justify-content: space-around;
cursor: pointer;
}
.content_bot{
border-top:2px solid #51a8da;
}
#mytable th,td{
height: 40px;
text-align: center;
}
@@ -0,0 +1,71 @@
body{
background-color: #edf7f9;
font-family: Arial, , Tahoma;
}
.nav{
width: 100%;
height: 80px;
background-color: #155070;
padding-left: 50px;
padding-right: 50px;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
box-sizing: border-box;
}
.nav_left{
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
}
.nav_left img{
width: 60px;
height: 60px;
margin-right: 20px;
}
.nav_left p span{
font-size: 24px;
font-weight: bold;
color: #ffffff;
}
.refresh{
position: absolute;
left: 50%;
top: 50%;
margin: -6px 0 0 -6px ;
}
.video_top{
width: 1024px;
margin: 0 auto;
height: 50px;
line-height: 50px;
font-weight: bold;
}
.video_content{
width: 1024px;
margin: 0 auto;
}
.video_fps{
text-align: right;
padding-right: 100px;
}
.video_inner{
text-align: center;
position: relative;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

@@ -0,0 +1,97 @@
var dialog = (function Dialog() {
var $mask = $("<div id='dlg-mask'></div>");
//title--
//content--object{type[1 for input, 0 for text], text[display in content]}
//btnFlag--1 for OK+Cancel, 0 for OK
function initHtml(title, content, btnFlag, ok, cancel) {
var div = "";
div += "<div id='dlg-box' class='dot'>";
div += "<h1 class='dot' style=\"margin-bottom:5px\">" + title + "</h1>"
div += "<div style=\"width:100%;height:1px;background-color:#ccc;\"></div>";
if (content.type == 0) {
div += "<p>" + content.text + "</p>"
} else if (content.type == 1) {
div += "<input type='text' value='" + content.text + "' placeholder='" + content.placeholder + "' autocomplete='off'/>";
}
if (btnFlag == 0) {
div += "<div><span class='ok'>OK</span></div>";
} else if (btnFlag == 1) {
div += "<div><span class='ok'>OK</span><span class='cancel'>Cancel</span></div>";
}
div += "</div>";
$mask.html(div);
$mask.find("input").val($mask.find("input").val());
if (content.type == 1) {
$mask.find(".ok").on("click", function() {
var retText = $mask.find("input").val();
hide();
if (ok) {
ok(retText);
}
});
$mask.find(".cancel").on("click", function() {
var retText = $mask.find("input").val();
hide();
if (cancel) {
cancel(retText);
}
});
} else {
$mask.find(".ok").on("click", function() {
hide();
if (ok) {
ok();
}
});
$mask.find(".cancel").on("click", function() {
hide();
if (cancel) {
cancel();
}
});
}
}
function calcBoxPos() {
var $box = $mask.find("#dlg-box");
var mask_w = $mask.outerWidth();
var mask_h = $mask.outerHeight();
var box_w = $box.outerWidth();
var box_h = $box.outerHeight();
var pos_left = (mask_w - box_w) / 2 + "px";
var pos_top = (mask_h - box_h) / 2 + "px";
$box.css("left", pos_left).css("top", pos_top);
}
function show() {
$("body").prepend($mask);
$mask.css("display", "block");
if ($mask.find("input") && $mask.find("input")[0]) {
$mask.find("input")[0].focus();
}
calcBoxPos();
$(window).resize(calcBoxPos);
}
function hide() {
$mask.css("display", "none");
$mask.remove();
}
return {
hide: hide,
tip: function(title, text, ok) {
initHtml(title, { type: 0, text: text }, 0, ok, null);
show();
},
input: function(title, text, placeholder, ok, cancel) {
initHtml(title, { type: 1, text: text, placeholder: placeholder }, 1, ok, cancel);
show();
},
confirm: function(title, text, ok, cancel) {
initHtml(title, { type: 0, text: text }, 1, ok, cancel);
show();
}
}
})();
+1
View File
@@ -0,0 +1 @@
var dialog = (function Dialog() { var e = $("<div id='dlg-mask'></div>"); function d(j, h, i, f, g) { var k = ""; k += "<div id='dlg-box' class='dot'>"; k += "<h1 class='dot'>" + j + "</h1>"; if (h.type == 0) { k += "<p>" + h.text + "</p>" } else { if (h.type == 1) { k += "<input type='text' value='" + h.text + "' placeholder='" + h.placeholder + "' autocomplete='off'/>" } } if (i == 0) { k += "<div><span class='ok'>OK</span></div>" } else { if (i == 1) { k += "<div><span class='ok'>OK</span><span class='cancel'>Cancel</span></div>" } } k += "</div>"; e.html(k); e.find("input").val(e.find("input").val()); if (h.type == 1) { e.find(".ok").on("click", function() { var l = e.find("input").val(); b(); if (f) { f(l) } }); e.find(".cancel").on("click", function() { var l = e.find("input").val(); b(); if (g) { g(l) } }) } else { e.find(".ok").on("click", function() { b(); if (f) { f() } }); e.find(".cancel").on("click", function() { b(); if (g) { g() } }) } } function c() { var k = e.find("#dlg-box"); var h = e.outerWidth(); var j = e.outerHeight(); var l = k.outerWidth(); var g = k.outerHeight(); var f = (h - l) / 2 + "px"; var i = (j - g) / 2 + "px"; k.css("left", f).css("top", i) } function a() { $("body").prepend(e); e.css("display", "block"); if (e.find("input") && e.find("input")[0]) { e.find("input")[0].focus() } c(); $(window).resize(c) } function b() { e.css("display", "none"); e.remove() } return { hide: b, tip: function(h, g, f) { d(h, { type: 0, text: g }, 0, f, null); a() }, input: function(j, i, h, f, g) { d(j, { type: 1, text: i, placeholder: h }, 1, f, g); a() }, confirm: function(i, h, f, g) { d(i, { type: 0, text: h }, 1, f, g); a() } } })();
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,183 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Channels</title>
<link rel="stylesheet" href="/static/css/base.css">
<link rel="stylesheet" href="/static/css/list.css">
<link rel="stylesheet" href="/static/css/dialog.css">
</head>
<body>
<div class="box">
<div class="nav">
<div class="nav_left">
<img src="/static/images/logo.png" alt="">
<p><span>Presenter Server</span></p>
</div>
<div class="nav_right">
</div>
</div>
<div class="box_top">
<span> >View List</span>
</div>
<div style="width:100%;height:2px;background-color:#ccc;"></div>
<div class="box_content">
<ul class="content_top">
<li class="top_refresh"><img src="/static/images/u1.png" alt=""><span>Refresh</span></li>
<li class="top_del"><img src="/static/images/u2.png" alt=""><span>Delete</span></li>
</ul>
<div class="content_bot">
<form name="myForm">
<table id="mytable" border="1" width="100%">
<tr>
<th></th>
<th><input type="checkbox" id="checkAll"></th>
<th>Status</th>
<th>View Name</th>
</tr>
{% for item in listret %}
<tr>
<td>{{item['id']}}</td>
<td><input type="checkbox" name="selectFlag" value="1"></td>
{% if item['status'] == 1 %}
<td><img src="/static/images/u5.png" alt=""></td>
{% else %}
<td><img src="/static/images/u6.png" alt=""></td>
{% end %}
<td ><a class="view_channel" style="cursor:pointer">{{item['name']}}</a></td>
</tr>
{% end %}
</table>
</form>
</div>
</div>
</div>
</body>
<script src="/static/js/jquery-1.10.2.min.js"></script>
<script src="/static/js/dialog.js"></script>
<script>
$("#checkAll").click(function() {
if (this.checked) {
$("input[name='selectFlag']:checkbox").each(function() {
$(this).attr("checked", true);
})
} else { //反之 取消全选
$("input[name='selectFlag']:checkbox").each(function() {
$(this).attr("checked", false);
})
}
});
$(".view_channel").click(function(){
var url = "/view?name=" + encodeURIComponent($(this).text());
window.open(url);
}
);
</script>
<script>
function checkNameValidate()
{
var name = $("#cname").val().trim();
if (name.length == 0)
{
dialog.tip("Tips","Channel name can not be empty");
return false;
}
if (name.length > 25)
{
dialog.tip("Tips", "Length of channel name should less than 25" ,function(){});
return false;
}
for (var i = 0; i < name.length; i++)
{
var c = name.charAt(i).charCodeAt();
var flag = ((c >= 48 && c <= 57) || (c >= 97 && c<= 122) || (c>= 65 && c <= 90) || (c == 47));
if (false == flag)
{
dialog.tip("Tips", "Channel name only support 0-9, a-z, A-Z /" ,function(){});
return false;
}
}
return true;
}
$(".top_create").click(function () {
if($(".content_mid").css("display")=="none"){
$(".content_mid").css("display","block").animate({height:"100px"});
}else{
$(".content_mid").animate({height:"0"}).css("display","none");
}
});
$(".mid_add").click(function () {
var rowlen = $("#mytable").find("tr").length;
if (rowlen >= 11)
{
dialog.tip("Tips", "Presenter supports up to 10 channels" ,function(){});
return;
}
//check
if (true == checkNameValidate())
{
var url = "/add?"+"name=" + encodeURIComponent($("#cname").val().trim()) +"&time="+(new Date().getTime());
$.ajax({
type: "POST",
url: url,
dataType: "json",
success: function(data)
{
if (data["ret"] == "success")
{
window.location.reload()
}
else
{
dialog.tip("Tips", data["msg"], function(){});
}
},
});
}
});
$(".top_refresh").click(function () {
window.location.reload();
});
$(".top_del").click(function () {
if($("input[name='selectFlag']").is(":checked")){
var msg = "";
$("input[name='selectFlag']:checked").each(function() {
// 遍历选中的checkbox
tr = $(this).parents("tr");
td = $(tr).find("td");
msg += encodeURIComponent($(td).eq(3).text());
msg += ",";
});
dialog.confirm("Tips", "Are you sure to delete ", function(){
var url = "/del?"+"time="+(new Date().getTime())+"&name="+msg;
$.ajax({
type: "POST",
url: url,
dataType: "json",
success: function(data)
{
if (data["ret"] == "success")
{
window.location.reload()
}
else
{
dialog.tip("Tips", data["msg"], function(){});
}
},
});
}, function(){});
}else{
dialog.tip("Tips", "Please select one item at least", function(){});
}
});
</script>
</html>
@@ -0,0 +1,155 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>view</title>
<link rel="stylesheet" href="/static/css/base.css">
<link rel="stylesheet" href="/static/css/testvideo.css">
</head>
<body>
<div class="video_box">
<div class="nav">
<div class="nav_left">
<img src="/static/images/logo.png" alt="">
<p><span>Presenter Server</span></p>
</div>
<div class="nav_right">
</div>
</div>
<div class="video_top">
<span> >view</span>
</div>
<div style="width:100%;height:2px;background-color:#ccc;"></div>
<div class="video_content">
<div class="video_fps" id='fpswapper' hidden><p><span> channel name: {{ channel_name }} </span> <span>&nbsp;&nbsp;&nbsp;&nbsp;fps:</span><span id='fpsval'></span></p></div>
<div class="video_inner">
<img src="/static/images/loading.gif" id = "loading" board = "1" alt=""/>
<!-- <img id = "load_media" hidden width = "1024px" board = "1" alt=""/> -->
<canvas id="canvas"></canvas>
</div>
</div>
</div>
</body>
<script src="/static/js/jquery-1.10.2.min.js"></script>
<script>
var canvas=document.getElementById("canvas")
var ctx=canvas.getContext("2d")
$('#fpswapper').hide()
$('#loading').hide()
function startViewVideo(){
$('#loading').show()
$('#canvas').hide()
var image = new Image();
var wsProtocol = "ws://";
if (window.location.protocol == "https:"){
wsProtocol = "wss://";
}
var wsUrl = wsProtocol + window.location.host+"/websocket?req={{req}}&name={{channel_name}}";
var ws = new WebSocket(wsUrl);
var onmessageflag = false;
ws.onopen = function() {
ws.send('next');
};
var count = 0;
var timestart = 0;
ws.onmessage = function (evt) {
$('#loading').hide()
$('#canvas').show()
var data = JSON.parse(evt.data)
var rectangles = []
if ('ok' == data['status']){
$('#fpsval').text(data.fps);
$('#loading').hide();
// $('#load_media').show();
var src = "data:image/jpeg;base64," + data['image'];
// $('#load_media').attr('src', src);
if (data['type'] == 'video'){
$('#fpswapper').show();
rectangles = data['rectangle_list']
}
var wantedWidth = 1024
var img = new Image()
img.src = src
img.onload=function(){
scale_factor = wantedWidth/img.width
canvas.setAttribute("width",1024)
canvas.setAttribute("height",img.height*scale_factor)
ctx.strokeStyle="yellow"
ctx.font="30px serif"
ctx.fillStyle="yellow"
ctx.strokeStyle="yellow"
ctx.font="30px serif"
ctx.fillStyle="yellow"
ctx.drawImage(img,0,0, wantedWidth, img.height*scale_factor)
for (var index in rectangles){
var pos= rectangles[index].slice(0,4) //
for (var i in pos){
pos[i] = pos[i]*scale_factor
}
var msg = rectangles[index].slice(4,5)
//add space between msg and face
//if upper space is not enough show the msg at the bottom
if(50>pos[1]){
// ctx.fillText(msg,pos[0],pos[3]+50)
}
else{
// ctx.fillText(msg,pos[0],pos[1]-10)
}
ctx.beginPath()
// 1/3 space draw line
if(msg == 'points'){
ctx.strokeStyle = "#0F4F00"
ctx.arc(pos[0],pos[1],6,0,2*Math.PI)
ctx.fillStyle = "#6CFF33"
ctx.fill()
}else if(msg == 'hand_box'){
ctx.strokeStyle = "#FF0000"
ctx.lineWidth = 5
ctx.moveTo(pos[0],pos[1])
ctx.lineTo(pos[2],pos[1])
ctx.moveTo(pos[2],pos[1])
ctx.lineTo(pos[2],pos[3])
ctx.moveTo(pos[0],pos[1])
ctx.lineTo(pos[0],pos[3])
ctx.moveTo(pos[0],pos[3])
ctx.lineTo(pos[2],pos[3])
// }else if(msg == 'hand_point'){
// ctx.strokeStyle = "#33FAFF"
// ctx.arc(pos[0],pos[1],3,0,2*Math.PI)
// ctx.fillStyle = "#33FAFF"
// ctx.fill()
// }else if(msg == 'hand'){
// ctx.strokeStyle = "#FC33FF"
// ctx.lineWidth = 3
// ctx.moveTo(pos[0],pos[1])
// ctx.lineTo(pos[2],pos[3])
}else if(msg == 'body'){
ctx.strokeStyle = "#FFF700"
ctx.lineWidth = 5
ctx.moveTo(pos[0],pos[1])
ctx.lineTo(pos[2],pos[3])
}else{
ctx.font = "30px Arial";
ctx.lineWidth = 5
ctx.fillStyle = "#33FAFF"
ctx.fillText(msg, 200, 200);
}
ctx.stroke()
}
}
}
ws.send('next');
}
}
startViewVideo();
</script>
</html>