added state api
This commit is contained in:
1
Pipfile
1
Pipfile
@@ -7,7 +7,6 @@ name = "pypi"
|
|||||||
ffmpeg-python = "*"
|
ffmpeg-python = "*"
|
||||||
flask = "*"
|
flask = "*"
|
||||||
flask-httpauth = "*"
|
flask-httpauth = "*"
|
||||||
flask-restplus-patched = "*"
|
|
||||||
flask-sqlalchemy = "*"
|
flask-sqlalchemy = "*"
|
||||||
flask-login = "*"
|
flask-login = "*"
|
||||||
pyjwt = "*"
|
pyjwt = "*"
|
||||||
|
|||||||
45
Pipfile.lock
generated
45
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "381ef185977217922c32d4e8e182c1f885842e963576323cad0ffd6a59f1a5f8"
|
"sha256": "a58778e12f8a5bcff82fd344f97bf22ed1917412e66289d13be718fd88cfc64a"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@@ -29,13 +29,6 @@
|
|||||||
],
|
],
|
||||||
"version": "==8.0.0"
|
"version": "==8.0.0"
|
||||||
},
|
},
|
||||||
"apispec": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:419d0564b899e182c2af50483ea074db8cb05fee60838be58bb4542095d5c08d",
|
|
||||||
"sha256:9bf4e51d56c9067c60668b78210ae213894f060f85593dc2ad8805eb7d875a2a"
|
|
||||||
],
|
|
||||||
"version": "==3.3.0"
|
|
||||||
},
|
|
||||||
"apscheduler": {
|
"apscheduler": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244",
|
"sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244",
|
||||||
@@ -281,13 +274,6 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.5.0"
|
"version": "==0.5.0"
|
||||||
},
|
},
|
||||||
"flask-marshmallow": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:01520ef1851ccb64d4ffb33196cddff895cc1302ae1585bff1abf58684a8111a",
|
|
||||||
"sha256:28b969193958d9602ab5d6add6d280e0e360c8e373d3492c2f73b024ecd36374"
|
|
||||||
],
|
|
||||||
"version": "==0.11.0"
|
|
||||||
},
|
|
||||||
"flask-migrate": {
|
"flask-migrate": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6fb038be63d4c60727d5dfa5f581a6189af5b4e2925bc378697b4f0a40cfb4e1",
|
"sha256:6fb038be63d4c60727d5dfa5f581a6189af5b4e2925bc378697b4f0a40cfb4e1",
|
||||||
@@ -303,20 +289,6 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.2.0"
|
"version": "==3.2.0"
|
||||||
},
|
},
|
||||||
"flask-restplus": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:a15d251923a8feb09a5d805c2f4d188555910a42c64d58f7dd281b8cac095f1b",
|
|
||||||
"sha256:a66e442d0bca08f389fc3d07b4d808fc89961285d12fb8013f7cf15516fa9f5c"
|
|
||||||
],
|
|
||||||
"version": "==0.13.0"
|
|
||||||
},
|
|
||||||
"flask-restplus-patched": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:36342775f9e0990dfc000dbe61133dfe56f9ef32c9b4c6293ba7f2c128d16efc"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==0.1.10"
|
|
||||||
},
|
|
||||||
"flask-restx": {
|
"flask-restx": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:641759fe7cba1cb073d15b4258b1b15840af8cffe6edbd0da3e6b61eae0a67a6",
|
"sha256:641759fe7cba1cb073d15b4258b1b15840af8cffe6edbd0da3e6b61eae0a67a6",
|
||||||
@@ -489,13 +461,6 @@
|
|||||||
],
|
],
|
||||||
"version": "==1.1.1"
|
"version": "==1.1.1"
|
||||||
},
|
},
|
||||||
"marshmallow": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3a94945a7461f2ab4df9576e51c97d66bee2c86155d3d3933fab752b31effab8",
|
|
||||||
"sha256:4b95c7735f93eb781dfdc4dded028108998cad759dda8dd9d4b5b4ac574cbf13"
|
|
||||||
],
|
|
||||||
"version": "==3.5.0"
|
|
||||||
},
|
|
||||||
"monotonic": {
|
"monotonic": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:23953d55076df038541e648a53676fb24980f7a1be290cdda21300b3bc21dfb0",
|
"sha256:23953d55076df038541e648a53676fb24980f7a1be290cdda21300b3bc21dfb0",
|
||||||
@@ -843,14 +808,6 @@
|
|||||||
],
|
],
|
||||||
"version": "==1.25.8"
|
"version": "==1.25.8"
|
||||||
},
|
},
|
||||||
"webargs": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:4f04918864c7602886335d8099f9b8960ee698b6b914f022736ed50be6b71235",
|
|
||||||
"sha256:871642a2e0c62f21d5b78f357750ac7a87e6bc734c972f633aa5fb6204fbf29a",
|
|
||||||
"sha256:fc81c9f9d391acfbce406a319217319fd8b2fd862f7fdb5319ad06944f36ed25"
|
|
||||||
],
|
|
||||||
"version": "==5.5.3"
|
|
||||||
},
|
|
||||||
"webencodings": {
|
"webencodings": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
|
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ api_group = Namespace('group', description="Group management namespace", authori
|
|||||||
api_permissions = Namespace('permissions', description="Permissions management namespace", authorizations=api_authorizations)
|
api_permissions = Namespace('permissions', description="Permissions management namespace", authorizations=api_authorizations)
|
||||||
api_room = Namespace('room', description="Room management namespace", authorizations=api_authorizations)
|
api_room = Namespace('room', description="Room management namespace", authorizations=api_authorizations)
|
||||||
api_recorder = Namespace('recorder', description="Recorder management namespace", authorizations=api_authorizations)
|
api_recorder = Namespace('recorder', description="Recorder management namespace", authorizations=api_authorizations)
|
||||||
|
api_state = Namespace('state', description="Object state management namespace", authorizations=api_authorizations)
|
||||||
api_virtual_command = Namespace('virtual_command', description="Virtual command namespace",
|
api_virtual_command = Namespace('virtual_command', description="Virtual command namespace",
|
||||||
authorizations=api_authorizations)
|
authorizations=api_authorizations)
|
||||||
api_cron_job = Namespace('cron_job', description="Cron job namespace",
|
api_cron_job = Namespace('cron_job', description="Cron job namespace",
|
||||||
@@ -42,6 +43,7 @@ api_v1.add_namespace(api_group)
|
|||||||
api_v1.add_namespace(api_permissions)
|
api_v1.add_namespace(api_permissions)
|
||||||
api_v1.add_namespace(api_room)
|
api_v1.add_namespace(api_room)
|
||||||
api_v1.add_namespace(api_recorder)
|
api_v1.add_namespace(api_recorder)
|
||||||
|
api_v1.add_namespace(api_state)
|
||||||
api_v1.add_namespace(api_virtual_command)
|
api_v1.add_namespace(api_virtual_command)
|
||||||
api_v1.add_namespace(api_cron_job)
|
api_v1.add_namespace(api_cron_job)
|
||||||
api_v1.add_namespace(api_control)
|
api_v1.add_namespace(api_control)
|
||||||
@@ -64,6 +66,7 @@ from .permission_api import *
|
|||||||
from .group_api import *
|
from .group_api import *
|
||||||
from .room_api import *
|
from .room_api import *
|
||||||
from .recorder_api import *
|
from .recorder_api import *
|
||||||
|
from .state_api import *
|
||||||
from .control_api import *
|
from .control_api import *
|
||||||
from .virtual_command_api import *
|
from .virtual_command_api import *
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from flask_restx import fields
|
from flask_restx import fields
|
||||||
from backend.api import api_user, api_recorder, api_v1
|
from backend.api import api_user, api_recorder, api_v1, api_state
|
||||||
|
|
||||||
generic_id_parser = api_v1.parser()
|
generic_id_parser = api_v1.parser()
|
||||||
generic_id_parser.add_argument('id', type=str, required=True, store_missing=False)
|
generic_id_parser.add_argument('id', type=str, required=True, store_missing=False)
|
||||||
@@ -102,3 +102,6 @@ recorder_model_model = api_recorder.model('Recorder Model', {
|
|||||||
description='Model of the recorder.'),
|
description='Model of the recorder.'),
|
||||||
'commands': fields.List(fields.Nested(recorder_command_model), attribute="recorder_commands")
|
'commands': fields.List(fields.Nested(recorder_command_model), attribute="recorder_commands")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
state_model = api_state.model('Recorder', {
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
# Copyright (c) 2019. Tobias Kurze
|
# Copyright (c) 2019. Tobias Kurze
|
||||||
"""
|
"""
|
||||||
This module provides functions related to authentication through the API.
|
This module provides functions related to recorders through the API.
|
||||||
For example: listing of available auth providers or registration of users.
|
|
||||||
|
|
||||||
Login through API does not start a new session, but instead returns JWT.
|
|
||||||
"""
|
"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|||||||
48
backend/api/state_api.py
Normal file
48
backend/api/state_api.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Copyright (c) 2019. Tobias Kurze
|
||||||
|
"""
|
||||||
|
This module provides functions related to object states through the API.
|
||||||
|
"""
|
||||||
|
from datetime import datetime
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
from flask_jwt_extended import jwt_required
|
||||||
|
from flask_restx import fields, Resource, inputs
|
||||||
|
|
||||||
|
from backend import db, app
|
||||||
|
from backend.api import api_state
|
||||||
|
from backend.api.models import recorder_model, recorder_model_model, recorder_command_model, state_model
|
||||||
|
from backend.models.recorder_model import Recorder, RecorderModel, RecorderCommand
|
||||||
|
from backend.models.room_model import Room
|
||||||
|
import backend.recorder_adapters as r_a
|
||||||
|
|
||||||
|
|
||||||
|
# ==
|
||||||
|
|
||||||
|
@api_state.route('/<int:id>')
|
||||||
|
@api_state.response(404, 'Recorder not found')
|
||||||
|
@api_state.param('id', 'The recorder identifier')
|
||||||
|
class RecorderStateResource(Resource):
|
||||||
|
@jwt_required
|
||||||
|
@api_state.doc('get_recorder_state')
|
||||||
|
@api_state.marshal_with(state_model, skip_none=False)
|
||||||
|
def get(self, id):
|
||||||
|
"""Fetch a recorder given its identifier"""
|
||||||
|
recorder = Recorder.query.get(id)
|
||||||
|
if recorder is None:
|
||||||
|
api_state.abort(404)
|
||||||
|
return recorder
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@api_state.route('')
|
||||||
|
class RecorderStateList(Resource):
|
||||||
|
@jwt_required
|
||||||
|
@api_state.doc('recorders')
|
||||||
|
@api_state.marshal_list_with(recorder_model, skip_none=False)
|
||||||
|
def get(self):
|
||||||
|
"""
|
||||||
|
List all recorders
|
||||||
|
:return: recorders
|
||||||
|
"""
|
||||||
|
return Recorder.get_all()
|
||||||
@@ -47,6 +47,9 @@ def add_default_jobs(sched=None, testing=False):
|
|||||||
check_recorder_state_job = sched.add_job(recorder_checker.check_object_state, 'interval', minutes=2,
|
check_recorder_state_job = sched.add_job(recorder_checker.check_object_state, 'interval', minutes=2,
|
||||||
id="check_recorder_state_job")
|
id="check_recorder_state_job")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Job regularly sending the state to "frontend recorders" through websocket
|
||||||
|
"""
|
||||||
send_update_state_to_recorder_job = sched.add_job(
|
send_update_state_to_recorder_job = sched.add_job(
|
||||||
lambda: send_state_update_to_recorders(recorder_checker.get_current_state()), 'interval', minutes=1,
|
lambda: send_state_update_to_recorders(recorder_checker.get_current_state()), 'interval', minutes=1,
|
||||||
id="send_update_state_to_recorder_job")
|
id="send_update_state_to_recorder_job")
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ T = TypeVar('T')
|
|||||||
|
|
||||||
|
|
||||||
class StateChecker(Generic[T]):
|
class StateChecker(Generic[T]):
|
||||||
|
"""
|
||||||
|
This class is designed generically to regularly check the state of objects with given function(s).
|
||||||
|
The determined state is stored "locally" in the state checker object and NOT reflected back to the checked objects!
|
||||||
|
It can be retrieved by calling get_current_state.
|
||||||
|
"""
|
||||||
def __init__(self, state_checker_func: Union[Callable, List[Callable]], type_to_check: T, type_name=None,
|
def __init__(self, state_checker_func: Union[Callable, List[Callable]], type_to_check: T, type_name=None,
|
||||||
threads=NUM_THREADS):
|
threads=NUM_THREADS):
|
||||||
self.num_threads = threads
|
self.num_threads = threads
|
||||||
@@ -135,7 +140,6 @@ class StateChecker(Generic[T]):
|
|||||||
'previous': {'state_ok': self.state_results[o_s]['state_ok'],
|
'previous': {'state_ok': self.state_results[o_s]['state_ok'],
|
||||||
'msg': self.state_results[o_s].get('msg', None),
|
'msg': self.state_results[o_s].get('msg', None),
|
||||||
'time_stamp': self.state_results[o_s].get('time_stamp', None)}}
|
'time_stamp': self.state_results[o_s].get('time_stamp', None)}}
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
self.state_results[o_s] = object_states[o_s]
|
self.state_results[o_s] = object_states[o_s]
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ rec_err_state_log_stream_handler.setLevel(logging.WARNING)
|
|||||||
logger.addHandler(rec_err_state_log_stream_handler)
|
logger.addHandler(rec_err_state_log_stream_handler)
|
||||||
#logger.addHandler(mem_handler)
|
#logger.addHandler(mem_handler)
|
||||||
|
|
||||||
base_url = "https://opencast.bibliothek.kit.edu"
|
|
||||||
|
|
||||||
session = requests.session()
|
session = requests.session()
|
||||||
session.auth = HTTPBasicAuth(Config.OPENCAST_USER, Config.OPENCAST_PW)
|
session.auth = HTTPBasicAuth(Config.OPENCAST_USER, Config.OPENCAST_PW)
|
||||||
|
|
||||||
@@ -55,7 +53,7 @@ def get_service_url(service_type: str):
|
|||||||
if service_type in config['service_urls']:
|
if service_type in config['service_urls']:
|
||||||
return config['service_urls'][service_type]
|
return config['service_urls'][service_type]
|
||||||
params = {'serviceType': service_type}
|
params = {'serviceType': service_type}
|
||||||
url = base_url + "/services/available.json"
|
url = Config.OPENCAST_URL + "/services/available.json"
|
||||||
res = session.get(url, params=params)
|
res = session.get(url, params=params)
|
||||||
if res.ok:
|
if res.ok:
|
||||||
service = res.json()["services"]["service"]
|
service = res.json()["services"]["service"]
|
||||||
@@ -135,6 +133,7 @@ def check_stream_sanity(recorder: Recorder, recorder_adapter: RecorderAdapter):
|
|||||||
|
|
||||||
for i in range(0, Config.STREAM_SANITY_CHECK_RETRIES):
|
for i in range(0, Config.STREAM_SANITY_CHECK_RETRIES):
|
||||||
do_checks = False
|
do_checks = False
|
||||||
|
|
||||||
if do_checks:
|
if do_checks:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@@ -224,7 +223,6 @@ if __name__ == '__main__':
|
|||||||
for a in agents:
|
for a in agents:
|
||||||
agent_states[a.get('name')] = 'PROBLEMATIC - unknown'
|
agent_states[a.get('name')] = 'PROBLEMATIC - unknown'
|
||||||
|
|
||||||
|
|
||||||
# pool = ThreadPool(5)
|
# pool = ThreadPool(5)
|
||||||
# pool.map(check_capture_agent_state, agents)
|
# pool.map(check_capture_agent_state, agents)
|
||||||
|
|
||||||
@@ -239,7 +237,6 @@ if __name__ == '__main__':
|
|||||||
except TimeoutError as e:
|
except TimeoutError as e:
|
||||||
logger.error("Timeout while pinging capture agent! {}".format(e))
|
logger.error("Timeout while pinging capture agent! {}".format(e))
|
||||||
|
|
||||||
|
|
||||||
with ThreadPool(NUM_THREADS) as pool:
|
with ThreadPool(NUM_THREADS) as pool:
|
||||||
results = [pool.apply_async(check_capture_agent_state, (agent,)) for agent in agents]
|
results = [pool.apply_async(check_capture_agent_state, (agent,)) for agent in agents]
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -9,29 +9,6 @@ from pydub import AudioSegment
|
|||||||
from pydub.playback import play
|
from pydub.playback import play
|
||||||
|
|
||||||
|
|
||||||
def old_test():
|
|
||||||
file_name = tempfile.gettempdir() + os.path.sep + "test.jpg"
|
|
||||||
print(file_name)
|
|
||||||
if os.path.exists(file_name):
|
|
||||||
os.remove(file_name)
|
|
||||||
process = (
|
|
||||||
ffmpeg
|
|
||||||
.input('rtsp://172.22.246.207/extron1')
|
|
||||||
# .input('rtsp://172.22.246.207/extron3')
|
|
||||||
.output(file_name, vframes=1)
|
|
||||||
# .output('-', format='h264')
|
|
||||||
.run(capture_stdout=True)
|
|
||||||
)
|
|
||||||
image = Image.open(file_name)
|
|
||||||
r, g, b = image.split()
|
|
||||||
print(r.histogram())
|
|
||||||
print(g.histogram())
|
|
||||||
print(b.histogram())
|
|
||||||
image.show()
|
|
||||||
|
|
||||||
|
|
||||||
# old_test()
|
|
||||||
|
|
||||||
def is_single_color_image(image):
|
def is_single_color_image(image):
|
||||||
single_color_image = True
|
single_color_image = True
|
||||||
color = {}
|
color = {}
|
||||||
@@ -97,7 +74,7 @@ def check_if_audio_is_valid(stream_url, sample_length_sec=3, lower_alert_limit_d
|
|||||||
|
|
||||||
sound = AudioSegment.from_file(file_name, "aac")
|
sound = AudioSegment.from_file(file_name, "aac")
|
||||||
# print(sound.dBFS)
|
# print(sound.dBFS)
|
||||||
play(sound)
|
#play(sound)
|
||||||
if sound.max_dBFS == -float('inf'):
|
if sound.max_dBFS == -float('inf'):
|
||||||
return False, "No active audio signal detected!"
|
return False, "No active audio signal detected!"
|
||||||
elif sound.max_dBFS < lower_alert_limit_dBFS:
|
elif sound.max_dBFS < lower_alert_limit_dBFS:
|
||||||
@@ -115,9 +92,14 @@ def check_if_audio_is_valid(stream_url, sample_length_sec=3, lower_alert_limit_d
|
|||||||
else:
|
else:
|
||||||
return False, msg
|
return False, msg
|
||||||
|
|
||||||
|
|
||||||
print(check_if_audio_is_valid('rtsp://172.22.246.207/extron1'))
|
print(check_if_audio_is_valid('rtsp://172.22.246.207/extron1'))
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Following code is not working correctly - ffmpeg parameters are wrong.
|
||||||
|
"""
|
||||||
|
"""
|
||||||
def check_if_audio_is_valid_stream(stream_url, raise_errors=True):
|
def check_if_audio_is_valid_stream(stream_url, raise_errors=True):
|
||||||
audio, _ = (
|
audio, _ = (
|
||||||
ffmpeg
|
ffmpeg
|
||||||
@@ -130,3 +112,4 @@ def check_if_audio_is_valid_stream(stream_url, raise_errors=True):
|
|||||||
play(sound)
|
play(sound)
|
||||||
|
|
||||||
# check_if_audio_is_valid('rtsp://172.22.246.207/extron1')
|
# check_if_audio_is_valid('rtsp://172.22.246.207/extron1')
|
||||||
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user