def api_solve(**kwargs): id_item = kwargs.get("id_item", False) solver_name = kwargs.get("solver", False) items = kwargs.get("items", []) user = kwargs.get("user", anonymous) if not user.has_right("rundown_edit", anyval=True): return NebulaResponse(403) if id_item: items.append(id_item) if not (items and solver_name): return NebulaResponse( 400, "You must specify placeholder item ID and a solver name" ) Solver = get_solver(solver_name) if Solver is None: return NebulaResponse(500, "Unable to load the solver. Check logs for details") db = DB() for id_item in items: solver = Solver(Item(id_item, db=db), db=db) response = solver.main() if response.is_error: return response return response
def retake(self, **kwargs): layer = kwargs.get("layer", self.caspar_feed_layer) if self.parent.current_live: return NebulaResponse(409, "Unable to retake live item") seekparam = str(int(self.current_item.mark_in() * self.fps)) if self.current_item.mark_out(): seekparam += " LENGTH {}".format( int( (self.current_item.mark_out() - self.current_item.mark_in()) * self.parser.seek_fps ) ) q = "PLAY {}-{} {} SEEK {}".format( self.caspar_channel, layer, self.current_fname, seekparam ) self.paused = False result = self.query(q) if result.is_success: message = "Retake OK" self.stalled = False self.paused = False self.parent.cue_next() else: message = "Take command failed: " + result.data return NebulaResponse(result.response, message)
def freeze(self, **kwargs): layer = kwargs.get("layer", self.caspar_feed_layer) if self.parent.current_live: return NebulaResponse(409, "Unable to freeze live item") if not self.paused: q = "PAUSE {}-{}".format(self.caspar_channel, layer) message = "Playback paused" new_val = True else: if self.parser.protocol >= 2.07: q = "RESUME {}-{}".format(self.caspar_channel, layer) else: length = "LENGTH {}".format( int((self.current_out or self.fdur) - self.fpos) ) q = "PLAY {}-{} {} SEEK {} {}".format( self.caspar_channel, layer, self.current_fname, self.fpos, length ) message = "Playback resumed" new_val = False result = self.query(q) if result.is_success: self.paused = new_val if self.parser.protocol < 2.07 and not new_val: self.force_cue = True else: message = result.data return NebulaResponse(result.response, message)
def api_rundown(**kwargs): """Rundown API endpoint.""" user = kwargs.get("user", anonymous) id_channel = int(kwargs.get("id_channel", -1)) start_time = kwargs.get("start_time", 0) if not (user.has_right("rundown_view", id_channel) or user.has_right("rundown_edit", id_channel)): return NebulaResponse(401) process_start_time = time.time() if id_channel not in config["playout_channels"]: return NebulaResponse(400, "Invalid playout channel specified") rows = [] i = 0 for event in get_rundown(id_channel, start_time): row = event.meta row["object_type"] = "event" row["is_empty"] = len(event.items) == 0 row["id_bin"] = event["id_magic"] rows.append(row) i += 1 for item in event.items: row = item.meta row["object_type"] = "item" rows.append(row) i += 1 process_time = time.time() - process_start_time return NebulaResponse(200, f"Rundown loaded in {process_time:.02f} seconds", data=rows)
def api_actions(**kwargs): objects = kwargs.get("objects") or kwargs.get("ids", []) db = kwargs.get("db", DB()) user = kwargs.get("user", anonymous) if not user: return NebulaResponse(401, "You are not allowed to execute any actions") if not objects: return NebulaResponse(400, "No asset selected") result = [] db.query("SELECT id, title, settings FROM actions ORDER BY id ASC") for id, title, settings in db.fetchall(): allow = False # noqa try: cond = xml(settings).find("allow_if").text except Exception: log_traceback() continue for id_asset in objects: asset = Asset(id_asset, db=db) # noqa if not eval(cond): break else: if user.has_right("job_control", id): result.append((id, title)) return NebulaResponse(200, data=result)
def api_settings(**kwargs): """Return system settings.""" if not kwargs.get("user", None): return NebulaResponse(401) data = {} for key in [ "actions", "cs", "folders", "ingest_channels", "meta_types", "playout_channels", "proxy_url", "services", "seismic_addr", "seismic_port", "site_name", "views", "language", ]: if key in config: data[key] = config[key] data["storages"] = {} for k in config["storages"]: data["storages"][k] = {"title": storages[k].title} return NebulaResponse(200, data=data)
def retake(self, **kwargs): if self.parent.current_live: return NebulaResponse(409, "Unable to retake live item") if not self.current: return NebulaResponse(400, "Unable to retake. No item is playing.") self.media_player.set_time(self.current.mark_in_ms) message = "Retake OK" self.parent.cue_next() return NebulaResponse(200, message)
def freeze(self, **kwargs): if self.parent.current_live: return NebulaResponse(409, "Unable to freeze live item") if not self.paused: self.media_player.set_pause(True) message = "Playback paused" else: self.media_player.set_pause(False) message = "Playback resumed" return NebulaResponse(200, message)
def freeze(self, **kwargs): layer = kwargs.get("layer", self.caspar_feed_layer) if self.parent.current_live: return NebulaResponse(409, "Unable to freeze live item") if self.paused: query = f"RESUME {self.caspar_channel}-{layer}" message = "Playback resumed" else: query = f"PAUSE {self.caspar_channel}-{layer}" message = "Playback paused" result = self.query(query) return NebulaResponse(result.response, message)
def take(self, **kwargs): layer = kwargs.get("layer", self.caspar_feed_layer) if not self.cued_item or self.cueing: return NebulaResponse(400, "Unable to take. No item is cued.") result = self.query(f"PLAY {self.caspar_channel}-{layer}") if result.is_success: if self.parent.current_live: self.parent.on_live_leave() message = "Take OK" self.stalled = False else: message = "Take failed: {result.data}" return NebulaResponse(result.response, message)
def set(self, **kwargs): """Set a controller property. This is controller specific. Args: key (str): Name of the property value: Value to be set """ key = kwargs.get("key", None) value = kwargs.get("value", None) if (key is None) or (value is None): return NebulaResponse(400) if hasattr(self.controller, "set"): return self.controller.set(key, value) return NebulaResponse(501)
def take(self, **kwargs): layer = kwargs.get("layer", self.caspar_feed_layer) if not self.cued_item or self.cueing: return NebulaResponse(400, "Unable to take. No item is cued.") self.paused = False result = self.query("PLAY {}-{}".format(self.caspar_channel, layer)) if result.is_success: if self.parent.current_live: self.parent.on_live_leave() message = "Take OK" self.stalled = False self.paused = False else: message = "Take command failed: " + result.data return NebulaResponse(result.response, message)
def abort(self, **kwargs): layer = kwargs.get("layer", self.caspar_feed_layer) if not self.cued_item: return NebulaResponse(400, "Unable to abort. No item is cued.") query = f"LOAD {self.caspar_channel}-{layer} {self.cued_fname}" if self.cued_item.mark_in(): seek = int(self.cued_item.mark_in() * self.channel_fps) query += f" SEEK {seek}" if self.cued_item.mark_out(): length = int( (self.cued_item.mark_out() - self.cued_item.mark_in()) * self.channel_fps) query += f" LENGTH {length}" result = self.query(query) return NebulaResponse(result.response, result.data)
def set(self, key, value): if key == "loop": do_loop = int(str(value) in ["1", "True", "true"]) result = self.query( f"CALL {self.caspar_channel}-{self.caspar_feed_layer} LOOP {do_loop}" ) if self.current_item and bool( self.current_item["loop"] != bool(do_loop)): self.current_item["loop"] = bool(do_loop) self.current_item.save(notify=False) bin_refresh([self.current_item["id_bin"]], db=self.current_item.db) return NebulaResponse(result.response, f"SET LOOP: {result.data}") else: return NebulaResponse(400)
def do_POST(self): ctype = self.headers.get("content-type") if ctype != "application/x-www-form-urlencoded": self.error(400, "Play service received a bad request.") return length = int(self.headers.get("content-length")) postvars = urllib.parse.parse_qs(self.rfile.read(length), keep_blank_values=1) method = self.path.lstrip("/").split("/")[0] params = {} for key in postvars: params[key.decode("utf-8")] = postvars[key][0].decode("utf-8") if method not in self.server.methods: self.error(501) return try: result = self.server.methods[method](**params) if result.is_error: logging.error(result.message) elif result["message"]: logging.info(result.message) self.result(result.dict) except Exception: msg = log_traceback() self.result(NebulaResponse(500, msg).dict)
def cue_backward(self, **kwargs): cc = self.controller.cued_item if not cc: return NebulaResponse(204) db = DB() nc = get_next_item(cc.id, db=db, force="prev") return self.cue(item=nc, db=db, level=5)
def plugin_exec(self, **kwargs): action = kwargs.get("action_name", False) data = json.loads(kwargs.get("data", "{}")) id_plugin = int(kwargs["id_plugin"]) logging.debug("Executing playout plugin:", action, id_plugin, data) if not action: return NebulaResponse(400, "No plugin action requested") try: plugin = self.plugins[id_plugin] except (KeyError, IndexError): log_traceback() return NebulaResponse(400, "No such action") if plugin.on_command(action, **data): return NebulaResponse(200) else: return NebulaResponse(500, "Playout plugin failed")
def cue(self, fname, item, layer=None, play=False, auto=True, loop=False, **kwargs): layer = layer or self.caspar_feed_layer query_list = ["PLAY" if play else "LOADBG"] query_list.append(f"{self.caspar_channel}-{layer}") query_list.append(fname) if auto: query_list.append("AUTO") if loop: query_list.append("LOOP") if item.mark_in(): query_list.append(f"SEEK {int(item.mark_in() * self.channel_fps)}") if item.mark_out(): query_list.append( f"LENGTH {int(item.duration * self.channel_fps)}") query = " ".join(query_list) self.cueing = fname self.cueing_item = item self.cueing_time = time.time() result = self.query(query) if result.is_error: message = f'Unable to cue "{fname}" {result.data}' self.cued_item = Item() self.cued_fname = False self.cueing = False self.cueing_item = False self.cueing_time = 0 return NebulaResponse(result.response, message) if play: self.cueing = False self.cueing_item = False self.cueing_time = 0 self.current_item = item self.current_fname = fname return NebulaResponse(200)
def abort(self, **kwargs): layer = kwargs.get("layer", self.caspar_feed_layer) if not self.cued_item: return NebulaResponse(400, "Unable to abort. No item is cued.") q = "LOAD {}-{} {}".format(self.caspar_channel, layer, self.cued_fname) if self.cued_item.mark_in(): q += " SEEK {}".format(int(self.cued_item.mark_in() * self.parser.seek_fps)) if self.cued_item.mark_out(): q += " LENGTH {}".format( int( (self.cued_item.mark_out() - self.cued_item.mark_in()) * self.parser.seek_fps ) ) result = self.query(q) if result.is_success: self.paused = True return NebulaResponse(result.response, result.data)
def cue(self, item, full_path, **kwargs): self.cued = VlcMedia(self.instance, full_path, item, **kwargs) play = kwargs.get("play", False) if play: return self.take() message = "Cued item {} ({})".format(self.cued_item, full_path) return NebulaResponse(200, message)
def main(self, debug=False, counter=0): logging.info("Solving {}".format(self.placeholder)) message = "Solver returned no items. Keeping placeholder." try: for new_item in self.solve(): self.new_items.append(new_item) if debug: logging.debug("Appending {}".format(new_item.asset)) except Exception: message = log_traceback("Error occured during solving") return NebulaResponse(501, message) if debug: return NebulaResponse(202) if not self.new_items: return NebulaResponse(204, message) i = 0 for item in self.bin.items: i += 1 if item.id == self.placeholder.id: item.delete() for new_item in self.new_items: i += 1 new_item["id_bin"] = self.bin.id new_item["position"] = i new_item.save(notify=False) if item["position"] != i: item["position"] = i item.save(notify=False) if self.bin.id not in self.affected_bins: self.affected_bins.append(self.bin.id) if self.solve_next: self.init_solver(self.solve_next) return self.main(debug=debug, counter=len(self.new_items) + counter) bin_refresh(self.affected_bins, db=self.db) return NebulaResponse( 200, "Created {} new items".format(len(self.new_items) + counter) )
def retake(self, **kwargs): layer = kwargs.get("layer", self.caspar_feed_layer) if self.parent.current_live: return NebulaResponse(409, "Unable to retake live item") seekparams = "SEEK " + str( int(self.current_item.mark_in() * self.channel_fps)) if self.current_item.mark_out(): seekparams += " LENGTH " + str( int((self.current_item.mark_out() - self.current_item.mark_in()) * self.channel_fps)) query = f"PLAY {self.caspar_channel}-{layer} {self.current_fname} {seekparams}" result = self.query(query) if result.is_success: message = "Retake OK" self.stalled = False self.parent.cue_next() else: message = "Take command failed: " + result.data return NebulaResponse(result.response, message)
def api_send(**kwargs): objects = kwargs.get("objects") or kwargs.get( "ids", []) # TODO: ids is deprecated. use objects instead id_action = kwargs.get("id_action", False) settings = kwargs.get("settings", {}) db = kwargs.get("db", DB()) user = kwargs.get("user", anonymous) restart_existing = kwargs.get("restart_existing", True) restart_running = kwargs.get("restart_running", False) if not user.has_right("job_control", anyval=True): return NebulaResponse(403, "You are not allowed to execute this action") # TODO: Better ACL if not id_action: return NebulaResponse(400, "No valid action selected") if not objects: return NebulaResponse(400, "No asset selected") if not user.has_right("job_control", id_action): return NebulaResponse(400, "You are not allowed to start this action") logging.info( "{} is starting action {} for the following assets: {}".format( user, id_action, ", ".join([str(i) for i in objects]))) for id_object in objects: send_to( id_object, id_action, settings=settings, id_user=user.id, restart_existing=restart_existing, restart_running=restart_running, db=db, ) return NebulaResponse( 200, "Starting {} job{}".format(len(objects), "s" if len(objects) > 1 else ""))
def plugin_list(self, **kwargs): result = [] for id_plugin, plugin in enumerate(self.plugins): if not plugin.slots: continue result.append({ "id": id_plugin, "title": plugin.title, "slots": plugin.slot_manifest, }) return NebulaResponse(200, data=result)
def finalize(self): if self.proc.return_code > 0: logging.error(self.proc.stderr.read()) return NebulaResponse( 500, message=f"Encoding failed\n{self.proc.error_log}" ) for temp_path in self.files: target_path = self.files[temp_path] try: logging.debug(f"Moving {temp_path} to {target_path}") os.rename(temp_path, target_path) except IOError: return NebulaResponse( 500, message=f"""Unable to move output file Source: {temp_path} Target: {target_path}""", ) return NebulaResponse(200, message="Task finished")
def take(self, **kwargs): if not self.cued_item: return NebulaResponse(400, "Unable to take. No item is cued.") self.media_player.set_media(self.cued.media) self.media_player.play() self.current = self.cued self.cued = None self.parent.on_change() # TODO: Check if media actually starts playing if True: if self.parent.current_live: self.parent.on_live_leave() code = 200 message = "Take OK" else: code = 500 message = "Take command failed" return NebulaResponse(code, message)
def build(self, *args, **kwargs): mode = "active" if len(args) > 1: if args[1] in ["finished", "failed"]: mode = args[1] id_asset = kwargs.get("id_asset", 0) id_action = kwargs.get("id_action", 0) try: id_asset = int(id_asset) except ValueError: id_asset = 0 try: id_action = int(id_action) except ValueError: id_action = 0 query = kwargs.get("q", "") if cherrypy.request.method == "POST": if id_asset and id_action: # TODO: how to select restert_existing/running? response = api_send(ids=[id_asset], id_action=id_action, user=self["user"]) if response.is_error: self.context.message(response.message, level="error") else: self.context.message(response.message) # do not use filter: show all active jobs to see queue id_asset = id_action = 0 if id_asset: db = DB() asset = Asset(id_asset, db=db) actions = api_actions(user=self["user"], db=db, ids=[id_asset]) else: actions = NebulaResponse(404) asset = False self["name"] = "jobs" self["js"] = ["/static/js/jobs.js"] self["title"] = mode.capitalize() + " jobs" self["mode"] = mode self["id_asset"] = id_asset self["asset"] = asset self["actions"] = actions.data if actions.is_success else [] self["id_action"] = id_asset self["query"] = query
def cue(self, item, full_path, **kwargs): kwargs["item"] = item kwargs["meta"] = item.asset.meta self.cued = NebulaContiSource(self.conti, full_path, **kwargs) # TODO: add per-source filters here self.cued.open() self.cueing = False if not self.cued: return NebulaResponse(500) if len(self.conti.playlist) > 1: del self.conti.playlist[1:] self.conti.playlist.append(self.cued) if not self.conti.started: logging.info("Starting Conti") self.conti.start() if kwargs.get("play", False): return self.take() message = "Cued item {} ({})".format(self.cued_item, full_path) return NebulaResponse(200, message)
def cue(self, fname, item, **kwargs): auto = kwargs.get("auto", True) layer = kwargs.get("layer", self.caspar_feed_layer) play = kwargs.get("play", False) loop = kwargs.get("loop", False) mark_in = item.mark_in() mark_out = item.mark_out() marks = "" if loop: marks += " LOOP" if mark_in: marks += " SEEK {}".format(int(mark_in * self.parser.seek_fps)) if mark_out and mark_out < item["duration"] and mark_out > mark_in: marks += " LENGTH {}".format( int((mark_out - mark_in) * self.parser.seek_fps) ) if play: q = "PLAY {}-{} {}{}".format(self.caspar_channel, layer, fname, marks) else: q = "LOADBG {}-{} {} {} {}".format( self.caspar_channel, layer, fname, ["", "AUTO"][auto], marks ) self.cueing = fname result = self.query(q) if result.is_error: message = 'Unable to cue "{}" {} - args: {}'.format( fname, result.data, str(kwargs) ) self.cued_item = Item() self.cued_fname = False self.cueing = False else: self.cued_item = item self.cued_fname = fname self.cued_in = mark_in * self.fps self.cued_out = mark_out * self.fps message = "Cued item {} ({})".format(self.cued_item, fname) return NebulaResponse(result.response, message)
def api_delete(**kwargs): object_type = kwargs.get("object_type", "asset") # TODO: ids is deprecated. use objects instead objects = kwargs.get("objects") or kwargs.get("ids", []) db = kwargs.get("db", DB()) user = kwargs.get("user", anonymous) initiator = kwargs.get("initiator", None) if not user: return NebulaResponse(401) if not (objects): return NebulaResponse(200, "No object deleted") object_type_class = { "asset": Asset, "item": Item, "bin": Bin, "event": Event, }[object_type] num = 0 affected_bins = [] for id_object in objects: obj = object_type_class(id_object, db=db) if object_type == "item": if not user.has_right("rundown_edit", anyval=True): return NebulaResponse(403) try: obj.delete() except psycopg2.IntegrityError: return NebulaResponse( 423, f"Unable to delete {obj}. Already aired.") if obj["id_bin"] not in affected_bins: affected_bins.append(obj["id_bin"]) else: return NebulaResponse( 501, f"{object_type} deletion is not implemented") num += 1 if affected_bins: bin_refresh(affected_bins, db=db, initiator=initiator) return NebulaResponse(200, f"{num} objects deleted")