from concurrent.futures import ThreadPoolExecutor from tornado.concurrent import run_on_executor from API.handlers import APIHandler from API.schema import api from utils.settings_manager import SETTINGS import errors class StitcherAPI(APIHandler): """REST interface to the stitcher and related actions. """ executor = ThreadPoolExecutor(1) reset_executor = ThreadPoolExecutor(1) stitcher_executor = ThreadPoolExecutor(1) def __init__(self, extra): """Init """ super(StitcherAPI, self).__init__(extra) self.server = extra["server"] self.stitcher = extra["video_stitcher"] self.output_manager = extra["output_manager"] self.preset_manager = extra["preset_manager"] self.project_manager = extra["project_manager"] @api(name="StartRecording", endpoint="stitcher.start_recording", description="Start recording Inputs, Output or InputsOutput in the listed drives", parameters={ "type": "object", "properties": { "output_preset": { "type": "object", "properties": { "drive_path": { "type": "string" } }, "description": "The output recording preset."}, "inputs_preset": { "type": "object", "properties": { "drive_path": { "type": "string" } }, "description": "The inputs recording preset."}, } }, errors=[errors.RecordingError, errors.StitcherError, errors.CameraError] ) @run_on_executor(executor='stitcher_executor') def start_recording(self, parameters): inputs_preset = parameters.get("inputs_preset") output_preset = parameters.get("output_preset") if inputs_preset: self.output_manager.start_output("input_recorder", inputs_preset) if output_preset: self.output_manager.start_output("output_recorder", output_preset) @api(name="StopRecording", endpoint="stitcher.stop_recording", description="Stop the inputs and/or output recording", errors=[errors.RecordingError, errors.StitcherError, errors.CameraError] ) @run_on_executor def stop_recording(self, parameters=None): self.output_manager.stop_output("input_recorder") self.output_manager.stop_output("output_recorder") @api(name="StartStream", endpoint="stitcher.start_stream", description="Starts the RTMP stream", parameters={ "type": ["object", "null"], "properties": { "preset": { "type": "string", "description": "A streaming preset"}, }, "required": ["preset"] }, errors=[errors.PresetDoesNotExist, errors.StreamingError, errors.StitcherError] ) @run_on_executor(executor='stitcher_executor') def start_stream(self, parameters): preset = self.preset_manager.get(parameters.get("preset")) if (preset is None): raise errors.PresetDoesNotExist("preset {} not found".format(parameters.get("preset"))) self.output_manager.start_output("stream", preset) SETTINGS.last_preset_streaming = preset["name"] @api(name="StopStream", endpoint="stitcher.stop_stream", description="Stops the RTMP stream", errors=[errors.StreamingError, errors.StitcherError] ) @run_on_executor def stop_stream(self, parameters=None): self.output_manager.stop_output("stream") @run_on_executor(executor='stitcher_executor') def start_preview(self, parameters=None): self.output_manager.start_output("preview", parameters) @run_on_executor def stop_preview(self, parameters=None): self.output_manager.stop_output("preview") @run_on_executor def get_record_config(self, parameters=None): """Get the current project recording configuration. Returns: - ``ConfigurationError`` - Otherwise: the recording configuration. """ from output import Output return Output.get_output_config(self.output_manager.record_conf) @api(name="GetStreamPresets", endpoint="stitcher.get_stream_presets", description="Returns a list of streaming presets", result={ "type": "object", "properties": { "entries": { "type": "array", "items": { "$ref": "StreamPreset" } } } }, errors=[errors.PresetError] ) @run_on_executor def get_stream_presets(self, parameters=None): return {"entries": self.preset_manager.list()} @api(name="CreateStreamPreset", endpoint="stitcher.create_stream_preset", description="Create a streaming preset", parameters={ "$ref": "StreamPreset" }, errors=[errors.PresetError] ) @run_on_executor def create_stream_preset(self, parameters): self.preset_manager.create(parameters) @api(name="RemoveStreamPreset", endpoint="stitcher.remove_stream_preset", description="Remove a streaming preset", parameters={ "type": "object", "properties": { "name": { "type": "string", "description": "The streaming preset name"}, }, "minProperties": 1 }, errors=[errors.PresetError] ) @run_on_executor def remove_stream_preset(self, parameters): name = parameters.get("name") self.preset_manager.remove(name) @api(name="GetInfo", endpoint="stitcher.get_info", description="Get the current project information", result={ "type": "object", "properties": { "pano_size": {"type": "string"}, "preview_size": {"type": "string"}, "inputs": {"type": "integer"}, "inputs_size": {"type": "string"}, "has_audio": {"type": "boolean"}, } } ) @run_on_executor def get_info(self, parameters=None): panorama_size = self.project_manager.get_panorama_size() inputs_size = self.project_manager.get_input_size() preview_size = self.project_manager.get_preview_size() config = {"pano_size": "{}x{}".format(panorama_size[0], panorama_size[1]), "preview_size": "{}x{}".format(preview_size[0], preview_size[1]), "inputs": self.project_manager.get_num_inputs(), "inputs_size": "{}x{}".format(inputs_size[0], inputs_size[1]), "has_audio": bool(self.stitcher.has_audio)} return config @api(name="ListVideoModes", endpoint="stitcher.list_video_modes", description="Get the list of supported video modes and associated encoding capabilities", result={ "type": "object", "properties": { "video_modes": { "type": "object", "patternProperties": { "4K DCI|4K UHD|2.8K|2K|HD": { "type": "object", "patternProperties": { "baseline|extended|main|high": { "type": "object", "properties": { "min_bitrate": {"type": "integer"}, "max_bitrate": {"type": "integer"} } } } } } } } } ) @run_on_executor def list_video_modes(self, parameters=None): return {"video_modes": self.project_manager.get_video_modes()} @api(name="SetVideoMode", endpoint="stitcher.set_video_mode", description="Set the current video mode", parameters={ "type": "object", "properties": { "name": { "type": "string", "enum": ["4K DCI", "4K UHD", "2.8K", "2K", "HD"], "description": "The video mode name" }, }, "minProperties": 1 }, errors=[errors.StitcherVideoModeInvalidError, errors.StitcherVideoModeUnsupportedError] ) @run_on_executor(executor='reset_executor') def set_video_mode(self, parameters): # setting the video mode is forbidden while broadcasting or recording if self.output_manager.has_ongoing_critical_output(): raise errors.StitcherVideoModeChangeForbiddenError( "video mode cannot be changed (critical output is ongoing)") self.project_manager.set_resolution(parameters.get("name")) return self.server.reset()