upload
@@ -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]
|
||||
@@ -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;
|
||||
}
|
||||
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 367 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
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();
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -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() } } })();
|
||||
@@ -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> 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>
|
||||