Пример #1
0
    def channel_recover(self):
        logging.warning("Performing recovery")

        db = DB()
        db.query(
            """
            SELECT id_item, start FROM asrun
            WHERE id_channel = %s ORDER BY id DESC LIMIT 1
            """,
            [self.id_channel],
        )
        try:
            last_id_item, last_start = db.fetchall()[0]
        except IndexError:
            logging.error("Unable to perform recovery.")
        last_item = Item(last_id_item, db=db)
        last_item.asset

        self.controller.current_item = last_item
        self.controller.cued_item = False
        self.controller.cued_fname = False

        if last_start + last_item.duration <= time.time():
            logging.info(f"Last {last_item} has been broadcasted.")
            new_item = self.cue_next(item=last_item, db=db, play=True)
        else:
            logging.info(f"Last {last_item} has not been fully broadcasted.")
            new_item = self.cue_next(item=last_item, db=db)

        if not new_item:
            logging.error("Recovery failed. Unable to cue")
            return

        self.on_change()
Пример #2
0
    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)
Пример #3
0
 def kill_service(self, pid=False, id_service=False):
     if id_service in self.services:
         pid = self.services[id_service][0].pid
     if pid == os.getpid() or pid == 0:
         return
     logging.info(f"Attempting to kill PID {pid}")
     os.system(os.path.join(config["nebula_root"], "support", f"kill_tree.sh {pid}"))
Пример #4
0
    def listen_rabbit(self):
        try:
            import pika
        except ModuleNotFoundError:
            critical_error("'pika' module is not installed")

        host = config.get("rabbitmq_host", "rabbitmq")
        conparams = pika.ConnectionParameters(host=host)

        while True:
            try:
                connection = pika.BlockingConnection(conparams)
                channel = connection.channel()

                result = channel.queue_declare(
                    queue=config["site_name"],
                    arguments={"x-message-ttl": 1000})
                queue_name = result.method.queue

                logging.info("Listening on", queue_name)

                channel.basic_consume(
                    queue=queue_name,
                    on_message_callback=lambda c, m, p, b: self.handle_data(b),
                    auto_ack=True,
                )

                channel.start_consuming()
            except pika.exceptions.AMQPConnectionError:
                logging.error("RabbitMQ connection error", handlers=[])
            except Exception:
                log_traceback()
            time.sleep(2)
Пример #5
0
    def selectionChanged(self, selected, deselected):
        rows = []
        self.selected_objects = []
        tot_dur = 0

        for idx in self.selectionModel().selectedIndexes():
            row = idx.row()
            if row in rows:
                continue
            rows.append(row)
            obj = self.model().object_data[row]
            self.selected_objects.append(obj)
            if obj.object_type in ["asset", "item"]:
                tot_dur += obj.duration

        if self.selected_objects and self.focus_enabled:
            self.parent().main_window.focus(self.selected_objects[0])
            if (len(self.selected_objects) == 1
                    and self.selected_objects[0].object_type == "item"
                    and self.selected_objects[0]["id_asset"]):
                asset = self.selected_objects[0].asset
                times = len([
                    obj for obj in self.model().object_data if
                    obj.object_type == "item" and obj["id_asset"] == asset.id
                ])
                logging.info("{} is scheduled {}x in this rundown".format(
                    asset, times))
            if len(self.selected_objects) > 1 and tot_dur:
                logging.info("{} objects selected. Total duration {}".format(
                    len(self.selected_objects), s2time(tot_dur)))

        super(FireflyView, self).selectionChanged(selected, deselected)
Пример #6
0
    def on_delete_event(self):
        if not self.calendar.selected_event:
            return
        cursor_event = self.calendar.selected_event
        if not has_right("scheduler_edit", self.id_channel):
            logging.error(
                "You are not allowed to modify schedule of this channel.")
            return

        ret = QMessageBox.question(
            self,
            "Delete event",
            f"Do you really want to delete {cursor_event}?"
            "\nThis operation cannot be undone.",
            QMessageBox.Yes | QMessageBox.No,
        )
        if ret == QMessageBox.Yes:
            QApplication.processEvents()
            self.calendar.setCursor(Qt.WaitCursor)
            response = api.schedule(
                id_channel=self.id_channel,
                start_time=self.calendar.week_start_time,
                end_time=self.calendar.week_end_time,
                delete=[cursor_event.id],
            )
            self.calendar.setCursor(Qt.ArrowCursor)
            if response:
                logging.info(f"{cursor_event} deleted")
                self.calendar.set_data(response.data)
            else:
                logging.error(response.message)
                self.calendar.load()
Пример #7
0
def cg_download(target_path, method, timeout=10, verbose=True, **kwargs):
    start_time = time.monotonic()
    target_dir = os.path.dirname(os.path.abspath(target_path))
    cg_server = config.get("cg_server", "https://cg.immstudios.org")
    cg_site = config.get("cg_site", config["site_name"])
    if not os.path.isdir(target_dir):
        try:
            os.makedirs(target_dir)
        except Exception:
            logging.error(f"Unable to create output directory {target_dir}")
            return False
    url = f"{cg_server}/render/{cg_site}/{method}"
    try:
        response = requests.get(url, params=kwargs, timeout=timeout)
    except Exception:
        log_traceback("Unable to download CG item")
        return False
    if response.status_code != 200:
        logging.error(f"CG Download failed with code {response.status_code}")
        return False
    try:
        temp_path = target_path + ".creating"
        with open(temp_path, "wb") as f:
            f.write(response.content)
        os.rename(temp_path, target_path)
    except Exception:
        log_traceback(f"Unable to write CG item to {target_path}")
        return False
    if verbose:
        elapsed = time.monotonic() - start_time
        logging.info(f"CG {method} downloaded in {elapsed:.02f}s")
    return True
Пример #8
0
    def load(self):
        self.plugins = []
        bpath = get_plugin_path("playout")
        if not bpath:
            logging.warning("Playout plugins directory does not exist")
            return

        for plugin_name in self.service.channel_config.get("plugins", []):
            plugin_file = plugin_name + ".py"
            plugin_path = os.path.join(bpath, plugin_file)

            if not os.path.exists(plugin_path):
                logging.error(f"Plugin {plugin_name} does not exist")
                continue

            try:
                py_mod = imp.load_source(plugin_name, plugin_path)
            except Exception:
                log_traceback(f"Unable to load plugin {plugin_name}")
                continue

            if "Plugin" not in dir(py_mod):
                logging.error(f"No plugin class found in {plugin_file}")
                continue

            logging.info("Initializing plugin {}".format(plugin_name))
            self.plugins.append(py_mod.Plugin(self.service))
            self.plugins[
                -1].title = self.plugins[-1].title or plugin_name.capitalize()
        logging.info("All plugins initialized")
Пример #9
0
    def on_activate(self, mi):
        obj = self.model().object_data[mi.row()]
        key = self.model().header_data[mi.column()]
        val = obj.show(key)

        QApplication.clipboard().setText(str(val))
        logging.info(f'Copied "{val}" to clipboard')
Пример #10
0
    def on_accept(self):
        reply = QMessageBox.question(
            self,
            "Save changes?",
            "{}".format("\n".join(" - {}".format(meta_types[k].alias(
                config.get("language", "en"))) for k in self.form.changed)),
            QMessageBox.Yes | QMessageBox.No,
        )

        if reply == QMessageBox.Yes:
            pass
        else:
            logging.info("Save aborted")
            return

        response = api.set(
            objects=[a.id for a in self.objects],
            data={k: self.form[k]
                  for k in self.form.changed},
        )

        if not response:
            logging.error(response.message)

        self.response = True
        self.close()
Пример #11
0
    def on_init(self):
        if not config["playout_channels"]:
            logging.error("No playout channel configured")
            self.shutdown(no_restart=True)
            return

        try:
            self.id_channel = int(self.settings.find("id_channel").text)
            self.channel_config = config["playout_channels"][self.id_channel]
        except Exception:
            logging.error("Invalid channel specified")
            self.shutdown(no_restart=True)
            return

        self.fps = float(self.channel_config.get("fps", 25.0))

        self.current_asset = Asset()
        self.current_event = Event()

        self.last_run = False
        self.last_info = 0
        self.current_live = False
        self.cued_live = False
        self.auto_event = 0

        self.status_key = f"playout_status/{self.id_channel}"

        self.plugins = PlayoutPlugins(self)
        self.controller = create_controller(self)
        if not self.controller:
            logging.error("Invalid controller specified")
            self.shutdown(no_restart=True)
            return

        port = int(self.channel_config.get("controller_port", 42100))
        logging.info(f"Using port {port} for the HTTP interface.")

        self.server = HTTPServer(("", port), PlayoutRequestHandler)
        self.server.service = self
        self.server.methods = {
            "take": self.take,
            "cue": self.cue,
            "cue_forward": self.cue_forward,
            "cue_backward": self.cue_backward,
            "freeze": self.freeze,
            "set": self.set,
            "retake": self.retake,
            "abort": self.abort,
            "stat": self.stat,
            "plugin_list": self.plugin_list,
            "plugin_exec": self.plugin_exec,
            "recover": self.channel_recover,
        }
        self.server_thread = threading.Thread(target=self.server.serve_forever,
                                              args=(),
                                              daemon=True)
        self.server_thread.start()
        self.plugins.load()
        self.on_progress()
Пример #12
0
 def work(self):
     while self.should_run:
         try:
             self.main()
         except Exception:
             log_traceback()
         time.sleep(1 / self.fps)
     logging.info("Controller work thread shutdown")
Пример #13
0
 def start(self):
     self.splash.hide()
     try:
         self.exec_()
     except Exception:
         log_traceback()
     logging.info("Shutting down")
     self.on_exit()
Пример #14
0
    def on_delete(self):
        items = list(
            set([
                obj.id for obj in self.selected_objects
                if obj.object_type == "item"
            ]))
        events = list(
            set([
                obj.id for obj in self.selected_objects
                if obj.object_type == "event"
            ]))

        if items and not self.parent().can_edit:
            logging.error("You are not allowed to modify this rundown items")
            return
        elif events and not self.parent().can_schedule:
            logging.error("You are not allowed to modify this rundown blocks")
            return

        if events or len(items) > 10:
            ret = QMessageBox.question(
                self,
                "Delete",
                "Do you REALLY want to delete "
                f"{len(items)} items and {len(events)} events?\n"
                "This operation CANNOT be undone",
                QMessageBox.Yes | QMessageBox.No,
            )

            if ret != QMessageBox.Yes:
                return

        if items:
            QApplication.processEvents()
            QApplication.setOverrideCursor(Qt.WaitCursor)
            response = api.delete(object_type="item", objects=items)
            QApplication.restoreOverrideCursor()
            if not response:
                logging.error(response.message)
                return
            else:
                logging.info("Item deleted: {}".format(response.message))

        if events:
            QApplication.processEvents()
            QApplication.setOverrideCursor(Qt.WaitCursor)
            response = api.schedule(delete=events,
                                    id_channel=self.parent().id_channel)
            QApplication.restoreOverrideCursor()
            if not response:
                logging.error(response.message)
                return
            else:
                logging.info("Event deleted: {}".format(response.message))

        self.selectionModel().clear()
        self.model().load()
        self.parent().main_window.scheduler.refresh_events(events)
Пример #15
0
 def run_forever(self):
     try:
         logging.info("Listening on port %d for clients.." % self.port)
         self.serve_forever()
     except KeyboardInterrupt:
         self.server_close()
         logging.info("Server terminated.")
     except Exception as e:
         log_traceback("ERROR: WebSocketsServer: " + str(e))
Пример #16
0
 def shutdown(self, no_restart=False):
     logging.info("Shutting down")
     if no_restart:
         db = DB()
         db.query("UPDATE services SET autostart=FALSE WHERE id=%s",
                  [self.id_service])
         db.commit()
     self.on_shutdown()
     sys.exit(0)
Пример #17
0
 def __init__(self, osc_port=5253):
     self.osc_port = osc_port
     self.channels = {}
     self.last_osc = time.time()
     logging.info(f"Starting OSC listener on port {self.osc_port}")
     self.osc_server = OSCServer("", self.osc_port, self.handle_osc)
     self.osc_thread = threading.Thread(target=self.serve_forever, args=())
     self.osc_thread.name = "OSC Server"
     self.osc_thread.start()
Пример #18
0
    def load_callback(self, response):
        self.parent().setCursor(Qt.ArrowCursor)
        if not response:
            logging.error(response.message)
            return

        QApplication.processEvents()
        self.parent().setCursor(Qt.WaitCursor)
        self.beginResetModel()
        logging.info("Loading rundown. Please wait...")

        required_assets = []

        self.header_data = config["playout_channels"][self.id_channel].get(
            "rundown_columns", DEFAULT_COLUMNS)
        self.object_data = []
        self.event_ids = []

        i = 0
        for row in response.data:
            row["rundown_row"] = i
            if row["object_type"] == "event":
                self.object_data.append(Event(meta=row))
                i += 1
                self.event_ids.append(row["id"])
                if row["is_empty"]:
                    self.object_data.append(
                        Item(meta={
                            "title": "(Empty event)",
                            "id_bin": row["id_bin"]
                        }))
                    i += 1
            elif row["object_type"] == "item":
                item = Item(meta=row)
                item.id_channel = self.id_channel
                if row["id_asset"]:
                    item._asset = asset_cache.get(row["id_asset"])
                    required_assets.append(
                        [row["id_asset"], row["asset_mtime"]])
                else:
                    item._asset = False
                self.object_data.append(item)
                i += 1
            else:
                continue

        asset_cache.request(required_assets)

        self.endResetModel()
        self.parent().setCursor(Qt.ArrowCursor)
        logging.goodnews(
            "Rundown loaded in {:.03f}s".format(time.time() -
                                                self.load_start_time))

        if self.current_callback:
            self.current_callback()
Пример #19
0
 def __init__(self, parent, **kwargs):
     super(FireflyInteger, self).__init__(parent)
     self.setFocusPolicy(Qt.StrongFocus)
     self.setMinimum(kwargs.get("min", 0))
     self.setMaximum(kwargs.get("max", 99999))
     if kwargs.get("hide_null"):
         logging.info("HIDE NULL")
         self.setMinimum(0)
         self.setSpecialValueText(" ")
     self.setSingleStep(1)
     self.default = self.get_value()
Пример #20
0
 def __init__(self, parent, **kwargs):
     super(FireflyNumeric, self).__init__(parent)
     self.setFocusPolicy(Qt.StrongFocus)
     self.setMinimum(kwargs.get("min", -99999))
     self.setMaximum(kwargs.get("max", 99999))
     if kwargs.get("hide_null"):
         logging.info("HIDE NULL")
         self.setMinimum(0)
         self.setSpecialValueText(" ")
     # TODO: custom step (default 1, allow floats)
     self.default = self.get_value()
Пример #21
0
 def run(self):
     self.is_running = True
     logging.info(f"Starting {self.__class__.__name__}")
     while self.should_run:
         try:
             self.main()
         except Exception:
             log_traceback()
         self.first_run = False
         time.sleep(2)
     self.on_shutdown()
     self.is_running = False
Пример #22
0
 def delete(self):
     if not self.id:
         return
     logging.info(f"Deleting {self}")
     cache.delete(self.cache_key)
     self.delete_children()
     self.db.query("DELETE FROM {} WHERE id=%s".format(self.table_name),
                   [self.id])
     self.db.query(
         "DELETE FROM ft WHERE object_type=%s AND id=%s",
         [self.object_type_id, self.id],
     )
     self.db.commit()
Пример #23
0
    def load(self):
        self.clear()
        self.update({
            "get": api_get,
            "set": api_set,
            "browse": api_browse,
            "delete": api_delete,
            "settings": api_settings,
            "rundown": api_rundown,
            "order": api_order,
            "schedule": api_schedule,
            "jobs": api_jobs,
            "playout": api_playout,
            "actions": api_actions,
            "send": api_send,
            "solve": api_solve,
            "system": api_system,
        })
        logging.info("Reloading API methods")
        apidir = get_plugin_path("api")
        if not apidir:
            return

        for plugin_entry in os.listdir(apidir):
            entry_path = os.path.join(apidir, plugin_entry)
            if os.path.isdir(entry_path):
                plugin_module_path = os.path.join(entry_path,
                                                  plugin_entry + ".py")
                if not os.path.exists(plugin_module_path):
                    continue
            elif not os.path.splitext(plugin_entry)[1] == ".py":
                continue
            else:
                plugin_module_path = os.path.join(apidir, plugin_entry)

            plugin_module_path = FileObject(plugin_module_path)
            plugin_name = plugin_module_path.base_name
            try:
                py_mod = imp.load_source(plugin_name, plugin_module_path.path)
            except Exception:
                log_traceback(f"Unable to load plugin {plugin_name}")
                continue

            if "Plugin" not in dir(py_mod):
                logging.error(f"No plugin class found in {plugin_name}")
                continue

            plugin = py_mod.Plugin()
            logging.info(f"Loaded plugin {plugin_name} ({plugin_module_path})")
            self[plugin_name] = plugin
Пример #24
0
    def save(self):
        if len(self.data) > CACHE_LIMIT:
            to_rm = list(self.data.keys())
            to_rm.sort(key=lambda x: self.data[x].meta.get("_last_access", 0))
            for t in to_rm[:-CACHE_LIMIT]:
                del self.data[t]

        logging.info("Saving {} assets to local cache".format(len(self.data)))
        start_time = time.time()
        data = [asset.meta for asset in self.data.values()]
        with open(self.cache_path, "w") as f:
            json.dump(data, f)
        logging.debug("Cache updated in {:.03f}s".format(time.time() -
                                                         start_time))
Пример #25
0
    def start_service(self, id_service, title, db=False):
        proc_cmd = [
            os.path.join(config["nebula_root"], "manage.py"),
            "run",
            str(id_service),
            '"{}"'.format(title),
        ]
        if config.get("daemon_mode"):
            proc_cmd.append("--daemon")

        logging.info(f"Starting service ID {id_service} ({title})")

        self.services[id_service] = [
            subprocess.Popen(proc_cmd, cwd=config["nebula_root"]),
            title,
        ]
Пример #26
0
    def block_split(self, tc):
        if tc <= self.event["start"] or tc >= self.next_event["start"]:
            logging.error(
                "Timecode of block split must be between "
                "the current and next event start times"
            )
            return False

        logging.info(f"Splitting {self.event} at {format_time(tc)}")
        logging.info(
            "Next event is {} at {}".format(
                self.next_event, self.next_event.show("start")
            )
        )
        new_bin = Bin(db=self.db)
        new_bin.save(notify=False)

        new_placeholder = Item(db=self.db)
        new_placeholder["id_bin"] = new_bin.id
        new_placeholder["position"] = 0

        for key in self.placeholder.meta.keys():
            if key not in ["id_bin", "position", "id_asset", "id"]:
                new_placeholder[key] = self.placeholder[key]

        new_placeholder.save(notify=False)
        new_bin.append(new_placeholder)
        new_bin.save(notify=False)

        new_event = Event(db=self.db)
        new_event["id_channel"] = self.event["id_channel"]
        new_event["title"] = "Split block"
        new_event["start"] = tc
        new_event["id_magic"] = new_bin.id

        new_event.save(notify=False)

        self._needed_duration = None
        self._next_event = None
        self.solve_next = new_placeholder

        if new_bin.id not in self.affected_bins:
            self.affected_bins.append(new_bin.id)

        return True
Пример #27
0
    def execute(self, name):
        data = {}
        for slot in self.slots:
            data[slot] = self.slots[slot].get_value()

        response = api.playout(
            action="plugin_exec",
            id_channel=self.id_channel,
            id_plugin=self.id_plugin,
            action_name=name,
            data=json.dumps(data),
        )
        if response:
            logging.info(f"{self.title} action '{name}' executed succesfully.")
        else:
            logging.error(
                f"[PLUGINS] Plugin error {response.response}\n\n{response.message}"
            )
Пример #28
0
    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)
        )
Пример #29
0
def listen():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    addr = config.get("seismic_addr", "224.168.1.1")
    port = int(config.get("seismic_port", 42005))

    try:
        firstoctet = int(addr.split(".")[0])
        is_multicast = firstoctet >= 224
    except ValueError:
        is_multicast = False

    if is_multicast:
        logging.info(f"Starting multicast listener {addr}:{port}")
        sock.bind(("0.0.0.0", port))
        sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
        sock.setsockopt(
            socket.IPPROTO_IP,
            socket.IP_ADD_MEMBERSHIP,
            socket.inet_aton(addr) + socket.inet_aton("0.0.0.0"),
        )
    else:
        logging.info(f"Starting unicast listener {addr}:{port}")
        sock.bind((addr, port))

    sock.settimeout(1)

    while True:
        try:
            data, _ = sock.recvfrom(4092)
        except (socket.error):
            continue
        try:
            message = SeismicMessage(json.loads(data.decode()))
        except Exception:
            continue

        if message.site_name != config["site_name"]:
            continue

        if message.method == "log":
            log_message(message)
Пример #30
0
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 ""))