Пример #1
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()
Пример #2
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
Пример #3
0
 def next_event(self):
     if not self._next_event:
         self.db.query(
             """
             SELECT meta FROM events
             WHERE id_channel = %s AND start > %s
             ORDER BY start ASC LIMIT 1
             """,
             [self.event["id_channel"], self.event["start"]],
         )
         try:
             self._next_event = Event(meta=self.db.fetchall()[0][0], db=self.db)
         except IndexError:
             self._next_event = Event(
                 meta={
                     "id_channel": self.event["id_channel"],
                     "start": self.event["start"] + 3600,
                 }
             )
     return self._next_event
Пример #4
0
    def on_init(self):
        self.id_channel = int(self.settings.find("channel").text)
        self.current_task = None

        self.capture = Capture(self)
        """
                self,
                ingest_mode = self.settings.find("ingest_mode").text, # PRIMARY - Nahrava se do assetu, BACKUP - Nahrava se lokalne
                cache_dir   = self.settings.find("cache_dir").text,
                bmd_device  = self.settings.find("device").text,
                bmd_mode    = self.settings.find("mode").text,
                bmd_input   = self.settings.find("input").text
            )
        """

        # DEMO EVENT
        db = DB()
        e = Event(5529, db=db)
        e["start"] = time.time() + 3
        e["stop"] = time.time() + 30
        e.save()
Пример #5
0
def get_item_event(id_item, **kwargs):
    db = kwargs.get("db", DB())
    db.query("""
        SELECT e.id, e.meta
        FROM items AS i, events AS e
        WHERE e.id_magic = i.id_bin
        AND i.id = {}
        AND e.id_channel in ({})
        """.format(
        id_item,
        ", ".join([str(f) for f in config["playout_channels"].keys()])))
    for _, meta in db.fetchall():
        return Event(meta=meta, db=db)
    return False
Пример #6
0
def get_day_events(id_channel, date, num_days=1):
    chconfig = config["playout_channels"][id_channel]
    start_time = datestr2ts(date, *chconfig.get("day_start", [6, 0]))
    end_time = start_time + (3600 * 24 * num_days)
    db = DB()
    db.query(
        """
        SELECT id, meta
        FROM events
        WHERE id_channel=%s
        AND start > %s
        AND start < %s
        """,
        (id_channel, start_time, end_time),
    )
    for _, meta in db.fetchall():
        yield Event(meta=meta)
Пример #7
0
def bin_refresh(bins, **kwargs):
    bins = [b for b in bins if b]
    if not bins:
        return True
    db = kwargs.get("db", DB())
    sender = kwargs.get("sender", False)
    for id_bin in bins:
        b = Bin(id_bin, db=db)
        b.save(notify=False)
    bq = ", ".join([str(b) for b in bins if b])
    changed_events = []
    db.query(f"""
        SELECT e.meta FROM events as e, channels AS c
        WHERE
            c.channel_type = 0 AND
            c.id = e.id_channel AND
            e.id_magic IN ({bq})
        """)
    for (meta, ) in db.fetchall():
        event = Event(meta=meta, db=db)
        if event.id not in changed_events:
            changed_events.append(event.id)
    logging.debug(f"Bins changed {bins}.",
                  f"Initiator {kwargs.get('initiator', logging.user)}")
    messaging.send(
        "objects_changed",
        sender=sender,
        objects=bins,
        object_type="bin",
        initiator=kwargs.get("initiator", None),
    )
    if changed_events:
        logging.debug(f"Events changed {bins}."
                      f"Initiator {kwargs.get('initiator', logging.user)}")
        messaging.send(
            "objects_changed",
            sender=sender,
            objects=changed_events,
            object_type="event",
            initiator=kwargs.get("initiator", None),
        )
    return True
Пример #8
0
    def on_change(self):
        if not self.controller.current_item:
            return

        item = self.controller.current_item
        db = DB()

        self.current_asset = item.asset or Asset()
        self.current_event = item.event or Event()

        logging.info(f"Advanced to {item}")

        if self.last_run:
            db.query(
                """
                UPDATE asrun SET stop = %s
                WHERE id = %s""",
                [int(time.time()), self.last_run],
            )
            db.commit()

        if self.current_item:
            db.query(
                """
                INSERT INTO asrun (id_channel, id_item, start)
                VALUES (%s, %s, %s)
                """,
                [self.id_channel, item.id,
                 time.time()],
            )
            self.last_run = db.lastid()
            db.commit()
        else:
            self.last_run = False

        for plugin in self.plugins:
            try:
                plugin.on_change()
            except Exception:
                log_traceback("Plugin on-change failed")
Пример #9
0
    def on_main(self):
        now = int(time.time())

        db = DB()
        db.query(
            "SELECT id_object, start, stop, id_magic FROM nx_events WHERE id_channel = %s AND start < %s AND stop > %s ORDER BY start LIMIT 1",
            (self.id_channel, now, now))
        for id_event, start, stop, id_asset in db.fetchall():
            event = Event(id_event, db=db)

            if not self.current_task:
                task = CaptureTask(event)
                self.current_task = task
                break

            elif self.current_task.id_event != id_event:
                logging.warning(
                    "Another event should be ingested right now....")
                continue
            break
        else:
            self.current_task = None

        self.capture(self.current_task)
Пример #10
0
def get_next_item(item, **kwargs):
    db = kwargs.get("db", DB())
    force = kwargs.get("force", False)
    if type(item) == int and item > 0:
        current_item = Item(item, db=db)
    elif isinstance(item, Item):
        current_item = item
    else:
        logging.error(f"Unexpected get_next_item argument {item}")
        return False

    logging.debug(f"Looking for an item following {current_item}")
    current_bin = Bin(current_item["id_bin"], db=db)

    items = current_bin.items
    if force == "prev":
        items.reverse()

    for item in items:
        if (force == "prev" and item["position"] < current_item["position"]
            ) or (force != "prev"
                  and item["position"] > current_item["position"]):
            if item["item_role"] == "lead_out" and not force:
                logging.info("Cueing Lead In")
                for i, r in enumerate(current_bin.items):
                    if r["item_role"] == "lead_in":
                        return r
                else:
                    next_item = current_bin.items[0]
                    next_item.asset
                    return next_item
            if item["run_mode"] == RunMode.RUN_SKIP:
                continue
            item.asset
            return item
    else:
        current_event = get_item_event(item.id, db=db)
        direction = ">"
        order = "ASC"
        if force == "prev":
            direction = "<"
            order = "DESC"
        db.query(
            f"""
            SELECT meta FROM events
            WHERE id_channel = %s and start {direction} %s
            ORDER BY start {order} LIMIT 1
            """,
            [current_event["id_channel"], current_event["start"]],
        )
        try:
            next_event = Event(meta=db.fetchall()[0][0], db=db)
            if not next_event.bin.items:
                logging.debug("Next playlist is empty")
                raise Exception
            if next_event["run_mode"] and not kwargs.get(
                    "force_next_event", False):
                logging.debug("Next playlist run mode is not auto")
                raise Exception
            if force == "prev":
                next_item = next_event.bin.items[-1]
            else:
                next_item = next_event.bin.items[0]
            next_item.asset
            return next_item
        except Exception:
            logging.info("Looping current playlist")
            next_item = current_bin.items[0]
            next_item.asset
            return next_item
Пример #11
0
    def on_main(self):
        """
        This method checks if the following event
        should start automatically at given time.
        It does not handle AUTO playlist advancing
        """

        if not hasattr(self, "controller"):
            return

        if hasattr(self.controller, "on_main"):
            self.controller.on_main()

        current_item = self.controller.current_item  # YES. CURRENT
        if not current_item:
            return

        db = DB()

        current_event = get_item_event(current_item.id, db=db)

        if not current_event:
            logging.warning("Unable to fetch the current event")
            return

        db.query(
            """
            SELECT DISTINCT(e.id), e.meta, e.start FROM events AS e, items AS i
                WHERE e.id_channel = %s
                AND e.start > %s
                AND e.start <= %s
                AND i.id_bin = e.id_magic
            ORDER BY e.start ASC LIMIT 1
            """,
            [self.id_channel, current_event["start"],
             time.time()],
        )

        try:
            next_event = Event(meta=db.fetchall()[0][1], db=db)
        except IndexError:
            self.auto_event = False
            return

        if self.auto_event == next_event.id:
            return

        run_mode = int(next_event["run_mode"]) or RunMode.RUN_AUTO

        if not run_mode:
            return

        elif not next_event.bin.items:
            return

        elif run_mode == RunMode.RUN_MANUAL:
            pass  # ?????

        elif run_mode == RunMode.RUN_SOFT:
            logging.info("Soft cue", next_event)
            # if current item is live, take next block/lead out automatically
            play = self.current_live
            for i, r in enumerate(current_event.bin.items):
                if r["item_role"] == "lead_out":
                    try:
                        self.cue(
                            id_channel=self.id_channel,
                            id_item=current_event.bin.items[i + 1].id,
                            db=db,
                            play=play,
                        )
                        self.auto_event = next_event.id
                        break
                    except IndexError:
                        pass
            else:
                try:
                    id_item = next_event.bin.items[0].id
                except KeyError:
                    id_item = 0
                if not self.controller.cued_item:
                    return
                if id_item != self.controller.cued_item.id:
                    self.cue(id_channel=self.id_channel,
                             id_item=id_item,
                             db=db)
                    self.auto_event = next_event.id
                return

        elif run_mode == RunMode.RUN_HARD:
            logging.info("Hard cue", next_event)
            id_item = next_event.bin.items[0].id
            self.cue(id_channel=self.id_channel,
                     id_item=id_item,
                     play=True,
                     db=db)
            self.auto_event = next_event.id
            return
Пример #12
0
def get_rundown(id_channel, start_time=False, end_time=False, db=False):
    """Get a rundown."""
    db = db or DB()
    channel_config = config["playout_channels"][id_channel]
    if not start_time:
        # default today
        sh, sm = channel_config.get("day_start", [6, 0])
        rundown_date = time.strftime("%Y-%m-%d", time.localtime(time.time()))
        start_time = datestr2ts(rundown_date, hh=sh, mm=sm)

    end_time = end_time or start_time + (3600 * 24)

    item_runs = get_item_runs(id_channel, start_time, end_time, db=db)

    if channel_config.get("send_action", False):
        db.query(
            """SELECT id_asset FROM jobs
            WHERE id_action=%s AND status in (0, 5)
            """,
            [channel_config["send_action"]],
        )
        pending_assets = [r[0] for r in db.fetchall()]
    else:
        pending_assets = []

    db.query(
        """
        SELECT
            e.id,
            e.meta,
            i.meta,
            a.meta
        FROM
            events AS e

        LEFT JOIN
            items AS i
        ON
            e.id_magic = i.id_bin

        LEFT JOIN
            assets AS a
        ON
            i.id_asset = a.id

        WHERE
            e.id_channel = %s AND e.start >= %s AND e.start < %s

        ORDER BY
            e.start ASC,
            i.position ASC,
            i.id ASC
        """,
        (id_channel, start_time, end_time),
    )

    current_event_id = None
    event = None

    ts_broadcast = ts_scheduled = 0
    pskey = "playout_status/{}".format(id_channel)
    for id_event, emeta, imeta, ameta in db.fetchall() + [
        (-1, None, None, None)
    ]:
        if id_event != current_event_id:
            if event:
                yield event
                if not event.items:
                    ts_broadcast = 0
            if id_event == -1:
                break

            event = Event(meta=emeta)
            event.items = []
            current_event_id = id_event
            rundown_event_asset = event.meta.get("id_asset", False)

            if event["run_mode"]:
                ts_broadcast = 0
            event.meta["rundown_scheduled"] = ts_scheduled = event["start"]
            event.meta["rundown_broadcast"] = ts_broadcast = (ts_broadcast
                                                              or ts_scheduled)

        if imeta:
            item = Item(meta=imeta, db=db)
            if ameta:
                asset = Asset(meta=ameta, db=db) if ameta else False
                item._asset = asset
            else:
                asset = False

            as_start, as_stop = item_runs.get(item.id, (0, 0))
            airstatus = 0
            if as_start:
                ts_broadcast = as_start
                if as_stop:
                    airstatus = ObjectStatus.AIRED
                else:
                    airstatus = ObjectStatus.ONAIR

            item.meta["asset_mtime"] = asset["mtime"] if asset else 0
            item.meta["rundown_scheduled"] = ts_scheduled
            item.meta["rundown_broadcast"] = ts_broadcast
            item.meta["rundown_difference"] = ts_broadcast - ts_scheduled
            if rundown_event_asset:
                item.meta["rundown_event_asset"] = rundown_event_asset

            istatus = 0
            if not asset:
                istatus = ObjectStatus.ONLINE
            elif airstatus:
                istatus = airstatus
            elif asset["status"] == ObjectStatus.OFFLINE:
                istatus = ObjectStatus.OFFLINE
            elif pskey not in asset.meta:
                istatus = ObjectStatus.REMOTE
            elif asset[pskey]["status"] == ObjectStatus.OFFLINE:
                istatus = ObjectStatus.REMOTE
            elif asset[pskey]["status"] == ObjectStatus.ONLINE:
                istatus = ObjectStatus.ONLINE
            elif asset[pskey]["status"] == ObjectStatus.CORRUPTED:
                istatus = ObjectStatus.CORRUPTED
            else:
                istatus = ObjectStatus.UNKNOWN

            item.meta["status"] = istatus
            if asset and asset.id in pending_assets:
                item.meta["transfer_progress"] = -1

            if item["run_mode"] != RunMode.RUN_SKIP:
                ts_scheduled += item.duration
                ts_broadcast += item.duration

            event.items.append(item)
Пример #13
0
def api_schedule(**kwargs):
    id_channel = kwargs.get("id_channel", 0)
    start_time = kwargs.get("start_time", 0)
    end_time = kwargs.get("end_time", 0)
    events = kwargs.get("events", [])  # Events to add/update
    delete = kwargs.get("delete", [])  # Event ids to delete
    db = kwargs.get("db", DB())
    user = kwargs.get("user", anonymous)
    initiator = kwargs.get("initiator", None)

    try:
        id_channel = int(id_channel)
    except ValueError:
        return NebulaResponse(400, "id_channel must be an integer")

    try:
        start_time = int(start_time)
    except ValueError:
        return NebulaResponse(400, "start_time must be an integer")

    try:
        end_time = int(end_time)
    except ValueError:
        return NebulaResponse(400, "end_time must be an integer")

    if not id_channel or id_channel not in config["playout_channels"]:
        return NebulaResponse(400, f"Unknown playout channel ID {id_channel}")

    changed_event_ids = []

    #
    # Delete events
    #

    for id_event in delete:
        if not user.has_right("scheduler_edit", id_channel):
            return NebulaResponse(403, "You are not allowed to edit this channel")
        event = Event(id_event, db=db)
        if not event:
            logging.warning(f"Unable to delete non existent event ID {id_event}")
            continue
        try:
            event.bin.delete()
        except psycopg2.IntegrityError:
            return NebulaResponse(423, f"Unable to delete {event}. Already aired.")
        else:
            event.delete()
        changed_event_ids.append(event.id)

    #
    # Create / update events
    #

    for event_data in events:
        if not user.has_right("scheduler_edit", id_channel):
            return NebulaResponse(423, "You are not allowed to edit this channel")
        id_event = event_data.get("id", False)

        db.query(
            "SELECT meta FROM events WHERE id_channel=%s and start=%s",
            [id_channel, event_data["start"]],
        )
        try:
            event_at_pos_meta = db.fetchall()[0][0]
            event_at_pos = Event(meta=event_at_pos_meta, db=db)
        except IndexError:
            event_at_pos = False

        if id_event:
            logging.debug(f"Updating event ID {id_event}")
            event = Event(id_event, db=db)
            if not event:
                logging.warning(f"No such event ID {id_event}")
                continue
            pbin = event.bin
        elif event_at_pos:
            event = event_at_pos
            pbin = event.bin
        else:
            logging.debug("Creating new event")
            event = Event(db=db)
            pbin = Bin(db=db)
            pbin.save()
            logging.debug("Saved", pbin)
            event["id_magic"] = pbin.id
            event["id_channel"] = id_channel

        id_asset = event_data.get("id_asset", False)
        if id_asset and id_asset != event["id_asset"]:
            asset = Asset(id_asset, db=db)
            if asset:
                logging.info(f"Replacing event primary asset with {asset}")
                pbin.delete_children()
                pbin.items = []

                item = Item(db=db)
                item["id_asset"] = asset.id
                item["position"] = 0
                item["id_bin"] = pbin.id
                item._asset = asset
                item.save()
                pbin.append(item)
                pbin.save()

                event["id_asset"] = asset.id
                for key in meta_types:
                    if meta_types[key]["ns"] != "m":
                        continue
                    if key in asset.meta:
                        event[key] = asset[key]

        for key in event_data:
            if key == "id_magic" and not event_data[key]:
                continue

            if key == "_items":
                for item_data in event_data["_items"]:
                    if not pbin.items:
                        start_pos = 0
                    else:
                        start_pos = pbin.items[-1]["position"]
                    try:
                        pos = int(item_data["position"])
                    except KeyError:
                        pos = 0
                    item = Item(meta=item_data, db=db)
                    item["position"] = start_pos + pos
                    item["id_bin"] = pbin.id
                    item.save()
                continue
            event[key] = event_data[key]

        changed_event_ids.append(event.id)
        event.save(notify=False)

    if changed_event_ids:
        messaging.send(
            "objects_changed",
            objects=changed_event_ids,
            object_type="event",
            initiator=initiator,
        )

    #
    # Return existing events
    #

    # TODO: ACL scheduler view

    result = []
    if start_time and end_time:
        logging.debug(
            f"Requested events of channel {id_channel} "
            f"from {format_time(start_time)} to {format_time(end_time)}"
        )

        db.query(
            """
                SELECT e.meta, o.meta FROM events AS e, bins AS o
                WHERE
                    e.id_channel=%s
                    AND e.start > %s
                    AND e.start < %s
                    AND e.id_magic = o.id
                ORDER BY start ASC""",
            [id_channel, start_time, end_time],
        )
        res = db.fetchall()
        db.query(
            """
                SELECT e.meta, o.meta FROM events AS e, bins AS o
                WHERE
                    e.id_channel=%s
                    AND start <= %s
                    AND e.id_magic = o.id
                ORDER BY start DESC LIMIT 1""",
            [id_channel, start_time],
        )
        res = db.fetchall() + res

        for event_meta, alt_meta in res:
            ebin = Bin(meta=alt_meta, db=db)
            if "duration" in alt_meta.keys():
                event_meta["duration"] = ebin.duration
            result.append(event_meta)

    return NebulaResponse(200, data=result)