diff --git a/Pipfile b/Pipfile index af32e1a..eea11f7 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,6 @@ name = "pypi" ffmpeg-python = "*" flask = "*" flask-httpauth = "*" -flask-restplus-patched = "*" flask-sqlalchemy = "*" flask-login = "*" pyjwt = "*" diff --git a/Pipfile.lock b/Pipfile.lock index f65be34..0d96969 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "381ef185977217922c32d4e8e182c1f885842e963576323cad0ffd6a59f1a5f8" + "sha256": "a58778e12f8a5bcff82fd344f97bf22ed1917412e66289d13be718fd88cfc64a" }, "pipfile-spec": 6, "requires": { @@ -29,13 +29,6 @@ ], "version": "==8.0.0" }, - "apispec": { - "hashes": [ - "sha256:419d0564b899e182c2af50483ea074db8cb05fee60838be58bb4542095d5c08d", - "sha256:9bf4e51d56c9067c60668b78210ae213894f060f85593dc2ad8805eb7d875a2a" - ], - "version": "==3.3.0" - }, "apscheduler": { "hashes": [ "sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244", @@ -281,13 +274,6 @@ "index": "pypi", "version": "==0.5.0" }, - "flask-marshmallow": { - "hashes": [ - "sha256:01520ef1851ccb64d4ffb33196cddff895cc1302ae1585bff1abf58684a8111a", - "sha256:28b969193958d9602ab5d6add6d280e0e360c8e373d3492c2f73b024ecd36374" - ], - "version": "==0.11.0" - }, "flask-migrate": { "hashes": [ "sha256:6fb038be63d4c60727d5dfa5f581a6189af5b4e2925bc378697b4f0a40cfb4e1", @@ -303,20 +289,6 @@ "index": "pypi", "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": { "hashes": [ "sha256:641759fe7cba1cb073d15b4258b1b15840af8cffe6edbd0da3e6b61eae0a67a6", @@ -489,13 +461,6 @@ ], "version": "==1.1.1" }, - "marshmallow": { - "hashes": [ - "sha256:3a94945a7461f2ab4df9576e51c97d66bee2c86155d3d3933fab752b31effab8", - "sha256:4b95c7735f93eb781dfdc4dded028108998cad759dda8dd9d4b5b4ac574cbf13" - ], - "version": "==3.5.0" - }, "monotonic": { "hashes": [ "sha256:23953d55076df038541e648a53676fb24980f7a1be290cdda21300b3bc21dfb0", @@ -843,14 +808,6 @@ ], "version": "==1.25.8" }, - "webargs": { - "hashes": [ - "sha256:4f04918864c7602886335d8099f9b8960ee698b6b914f022736ed50be6b71235", - "sha256:871642a2e0c62f21d5b78f357750ac7a87e6bc734c972f633aa5fb6204fbf29a", - "sha256:fc81c9f9d391acfbce406a319217319fd8b2fd862f7fdb5319ad06944f36ed25" - ], - "version": "==5.5.3" - }, "webencodings": { "hashes": [ "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", diff --git a/backend/api/__init__.py b/backend/api/__init__.py index 3487203..81fce38 100644 --- a/backend/api/__init__.py +++ b/backend/api/__init__.py @@ -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_room = Namespace('room', description="Room 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", authorizations=api_authorizations) 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_room) api_v1.add_namespace(api_recorder) +api_v1.add_namespace(api_state) api_v1.add_namespace(api_virtual_command) api_v1.add_namespace(api_cron_job) api_v1.add_namespace(api_control) @@ -64,6 +66,7 @@ from .permission_api import * from .group_api import * from .room_api import * from .recorder_api import * +from .state_api import * from .control_api import * from .virtual_command_api import * diff --git a/backend/api/models.py b/backend/api/models.py index c7bc8e3..c87996b 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -1,5 +1,5 @@ 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.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.'), 'commands': fields.List(fields.Nested(recorder_command_model), attribute="recorder_commands") }) + +state_model = api_state.model('Recorder', { +}) diff --git a/backend/api/recorder_api.py b/backend/api/recorder_api.py index 986eee5..a130fe9 100644 --- a/backend/api/recorder_api.py +++ b/backend/api/recorder_api.py @@ -1,9 +1,6 @@ # Copyright (c) 2019. Tobias Kurze """ -This module provides functions related to authentication 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. +This module provides functions related to recorders through the API. """ from datetime import datetime from pprint import pprint diff --git a/backend/api/state_api.py b/backend/api/state_api.py new file mode 100644 index 0000000..bbab7a8 --- /dev/null +++ b/backend/api/state_api.py @@ -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('/') +@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() diff --git a/backend/cron/__init__.py b/backend/cron/__init__.py index 7cd658e..665c0d6 100644 --- a/backend/cron/__init__.py +++ b/backend/cron/__init__.py @@ -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, id="check_recorder_state_job") + """ + Job regularly sending the state to "frontend recorders" through websocket + """ send_update_state_to_recorder_job = sched.add_job( lambda: send_state_update_to_recorders(recorder_checker.get_current_state()), 'interval', minutes=1, id="send_update_state_to_recorder_job") diff --git a/backend/cron/cron_state_checker.py b/backend/cron/cron_state_checker.py index dc34af1..6d0cedb 100644 --- a/backend/cron/cron_state_checker.py +++ b/backend/cron/cron_state_checker.py @@ -22,6 +22,11 @@ T = TypeVar('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, threads=NUM_THREADS): self.num_threads = threads @@ -135,7 +140,6 @@ class StateChecker(Generic[T]): 'previous': {'state_ok': self.state_results[o_s]['state_ok'], 'msg': self.state_results[o_s].get('msg', None), 'time_stamp': self.state_results[o_s].get('time_stamp', None)}} - pass else: self.state_results[o_s] = object_states[o_s] diff --git a/backend/tools/recorder_state_checker.py b/backend/tools/recorder_state_checker.py index d696f64..a821cbc 100644 --- a/backend/tools/recorder_state_checker.py +++ b/backend/tools/recorder_state_checker.py @@ -38,8 +38,6 @@ rec_err_state_log_stream_handler.setLevel(logging.WARNING) logger.addHandler(rec_err_state_log_stream_handler) #logger.addHandler(mem_handler) -base_url = "https://opencast.bibliothek.kit.edu" - session = requests.session() 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']: return config['service_urls'][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) if res.ok: 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): do_checks = False + if do_checks: return True else: @@ -224,7 +223,6 @@ if __name__ == '__main__': for a in agents: agent_states[a.get('name')] = 'PROBLEMATIC - unknown' - # pool = ThreadPool(5) # pool.map(check_capture_agent_state, agents) @@ -239,7 +237,6 @@ if __name__ == '__main__': except TimeoutError as e: logger.error("Timeout while pinging capture agent! {}".format(e)) - with ThreadPool(NUM_THREADS) as pool: results = [pool.apply_async(check_capture_agent_state, (agent,)) for agent in agents] try: diff --git a/backend/tools/recorder_streams_sanity_checks.py b/backend/tools/recorder_streams_sanity_checks.py index fe30817..9ae6ded 100644 --- a/backend/tools/recorder_streams_sanity_checks.py +++ b/backend/tools/recorder_streams_sanity_checks.py @@ -9,29 +9,6 @@ from pydub import AudioSegment 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): single_color_image = True 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") # print(sound.dBFS) - play(sound) + #play(sound) if sound.max_dBFS == -float('inf'): return False, "No active audio signal detected!" 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: return False, msg + 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): audio, _ = ( ffmpeg @@ -130,3 +112,4 @@ def check_if_audio_is_valid_stream(stream_url, raise_errors=True): play(sound) # check_if_audio_is_valid('rtsp://172.22.246.207/extron1') +"""