コード例 #1
0
ファイル: browser.py プロジェクト: nebulabroadcast/firefly
    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')
コード例 #2
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()
コード例 #3
0
    def load_callback(self, callback, response):
        self.beginResetModel()
        QApplication.setOverrideCursor(Qt.WaitCursor)

        if not response:
            logging.error(response.message)
        else:
            asset_cache.request(response.data)

        # Pagination

        current_page = self.parent().current_page

        if len(response.data) > RECORDS_PER_PAGE:
            page_count = current_page + 1
        elif len(response.data) == 0:
            page_count = max(1, current_page - 1)
        else:
            page_count = current_page

        if current_page > page_count:
            current_page = page_count

        # Replace object data

        if len(response.data) > RECORDS_PER_PAGE:
            response.data.pop(-1)
        self.object_data = [asset_cache.get(row[0]) for row in response.data]

        self.parent().set_page(current_page, page_count)
        self.endResetModel()
        QApplication.restoreOverrideCursor()

        callback()
コード例 #4
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()
コード例 #5
0
ファイル: browser.py プロジェクト: nebulabroadcast/firefly
 def redraw_tabs(self, *args, **kwargs):
     QApplication.processEvents()
     views = []
     for i, b in enumerate(self.browsers):
         id_view = b.id_view
         self.tabs.setTabText(i, b.title or config["views"][id_view]["title"])
         sq = copy.copy(b.search_query)
         if self.tabs.currentIndex() == i:
             sq["active"] = True
         if b.title:
             sq["title"] = b.title
         views.append(sq)
     self.app_state["browser_tabs"] = views
コード例 #6
0
ファイル: api.py プロジェクト: nebulabroadcast/firefly
    def run(self, method, callback, **kwargs):
        logging.debug("Executing {}{} query".format(
            "" if callback == -1 else "async ", method))
        kwargs["session_id"] = config["session_id"]
        kwargs["initiator"] = CLIENT_ID

        if method in ["ping", "login", "logout"]:
            method = "/" + method
            mime = QVariant("application/x-www-form-urlencoded")
            post_data = QUrlQuery()
            for key in kwargs:
                post_data.addQueryItem(key, kwargs[key])
            data = post_data.toString(QUrl.FullyEncoded).encode("ascii")
        else:
            method = "/api/" + method
            mime = QVariant("application/json")
            data = json.dumps(kwargs).encode("ascii")

        request = QNetworkRequest(QUrl(config["hub"] + method))
        request.setHeader(QNetworkRequest.ContentTypeHeader, mime)
        request.setHeader(
            QNetworkRequest.UserAgentHeader,
            QVariant(f"nebula-firefly/{FIREFLY_VERSION}"),
        )

        try:
            query = self.manager.post(request, data)
            if callback != -1:
                query.finished.connect(
                    functools.partial(self.handler, query, callback))
            self.queries.append(query)
        except Exception:
            log_traceback()
            if callback:
                r = NebulaResponse(400, "Unable to send request")
                if callback == -1:
                    return r
                else:
                    callback(r)
            return

        if callback == -1:
            while not query.isFinished():
                time.sleep(0.0001)
                QApplication.processEvents()
            return self.handler(query, -1)
コード例 #7
0
    def update_display(self):
        now = time.time()
        adv = now - self.local_request_time

        rtime = self.request_time + adv
        rpos = self.pos

        if not self.paused:
            rpos += adv

        frame = int(self.fps * (rtime - math.floor(rtime)))
        clock = time.strftime(f"%H:%M:%S:{frame:02d}", time.localtime(rtime))
        self.display_clock.set_text(clock)
        self.display_pos.set_text(s2tc(min(self.dur, rpos), self.fps))

        rem = self.dur - rpos
        t = s2tc(max(0, rem), self.fps)
        if rem < 10:
            self.display_rem.set_text(f"<font color='red'>{t}</font>")
        else:
            self.display_rem.set_text(t)

        if self.pos == self.dur == self.progress_bar.value() == 0:
            self.progress_bar.setValue(0)

        try:
            ppos = int((rpos / self.dur) * PROGRESS_BAR_RESOLUTION)
        except ZeroDivisionError:
            return
        else:
            oldval = self.progress_bar.value()
            if ppos > oldval or abs(oldval -
                                    ppos) > PROGRESS_BAR_RESOLUTION / self.dur:
                self.progress_bar.setValue(ppos)
                self.progress_bar.update()

        if self.request_display_resize:
            QApplication.processEvents()
            self.display_clock.setFixedSize(self.display_clock.size())
            self.display_pos.setFixedSize(self.display_clock.size())
            self.display_rem.setFixedSize(self.display_clock.size())
            self.display_dur.setFixedSize(self.display_clock.size())
            self.request_display_resize = False
コード例 #8
0
ファイル: browser.py プロジェクト: nebulabroadcast/firefly
 def on_copy_result(self):
     result = ""
     for obj in self.view.selected_objects:
         result += "{}\n".format(
             "\t".join(
                 [obj.format_display(key) or "" for key in self.model().header_data]
             )
         )
     clipboard = QApplication.clipboard()
     clipboard.setText(result)
コード例 #9
0
    def load(self, asset, **kwargs):
        id_folder = kwargs.get("id_folder", asset["id_folder"])
        if id_folder != self.id_folder or kwargs.get("force"):
            if not id_folder:
                self.keys = []
            else:
                self.keys = config["folders"][id_folder]["meta_set"]

            if self.form:
                # SRSLY. I've no idea what I'm doing here
                self.layout.removeWidget(self.form)
                self.form.deleteLater()
                QApplication.processEvents()
                self.form.destroy()
                QApplication.processEvents()
                self.form = None
            for i in reversed(range(self.layout.count())):
                self.layout.itemAt(i).widget().deleteLater()

            self.form = MetaEditor(self, self.keys)
            self.layout.addWidget(self.form)
            self.id_folder = id_folder
            self.status = asset["status"]

        if self.form:
            for key, conf in self.keys:
                if meta_types[key]["class"] in [
                        MetaClass.SELECT, MetaClass.LIST
                ]:
                    self.form.inputs[key].set_data(
                        asset.show(key, result="full"))
                self.form[key] = asset[key]
            self.form.set_defaults()

        if self.form:
            enabled = has_right("asset_edit", id_folder)
            self.form.setEnabled(enabled)
コード例 #10
0
    def load(self, ts=False):
        if not self.week_start_time and not ts:
            ts = time.time()

        if ts:
            dt = datetime.datetime.fromtimestamp(ts)
            week_start = dt - datetime.timedelta(days=dt.weekday())
            week_start = week_start.replace(hour=self.day_start[0],
                                            minute=self.day_start[1],
                                            second=0)
            self.week_start_time = time.mktime(week_start.timetuple())
            self.week_end_time = self.week_start_time + SECS_PER_WEEK

        QApplication.processEvents()
        self.setCursor(Qt.WaitCursor)

        response = api.schedule(
            id_channel=self.id_channel,
            start_time=self.week_start_time,
            end_time=self.week_end_time,
        )

        if response:
            self.clock_bar.day_start = self.day_start
            self.clock_bar.update()
            self.set_data(response.data)

            for i, widgets in enumerate(zip(self.days, self.headers)):
                day_widget, header_widget = widgets
                start_time = self.week_start_time + (i * SECS_PER_DAY)
                day_widget.set_time(start_time)
                header_widget.set_time(start_time)
        else:
            logging.error(response.message)
        self.setCursor(Qt.ArrowCursor)
        self.on_zoom()
コード例 #11
0
 def on_solve(self, solver):
     QApplication.processEvents()
     QApplication.setOverrideCursor(Qt.WaitCursor)
     response = api.solve(id_item=self.selected_objects[0]["id"],
                          solver=solver)
     QApplication.restoreOverrideCursor()
     if not response:
         logging.error(response.message)
     self.model().load()
     self.parent().main_window.scheduler.load()
コード例 #12
0
 def on_send(self, id_action):
     QApplication.processEvents()
     QApplication.setOverrideCursor(Qt.WaitCursor)
     response = api.send(
         id_action=id_action,
         objects=self.assets,
         restart_existing=self.restart_existing.isChecked(),
         restart_running=self.restart_running.isChecked(),
     )
     QApplication.restoreOverrideCursor()
     if not response:
         logging.error(response.message)
     else:
         self.close()
コード例 #13
0
 def on_accept(self):
     QApplication.processEvents()
     QApplication.setOverrideCursor(Qt.WaitCursor)
     response = api.set(
         object_type="item",
         objects=[self.item.id],
         data={
             "mark_in": self.form["mark_in"],
             "mark_out": self.form["mark_out"]
         },
     )
     QApplication.restoreOverrideCursor()
     if not response:
         logging.error(response.message)
     self.close()
コード例 #14
0
 def on_set_mode(self, mode):
     if not self.parent().can_edit:
         logging.error("You are not allowed to modify this rundown")
         return
     QApplication.processEvents()
     QApplication.setOverrideCursor(Qt.WaitCursor)
     response = api.set(
         object_type=self.selected_objects[0].object_type,
         objects=[obj.id for obj in self.selected_objects],
         data={"run_mode": mode},
     )
     QApplication.restoreOverrideCursor()
     if not response:
         logging.error(response.message)
         return
     self.model().load()
コード例 #15
0
 def on_close(self):
     self.detail.check_changed()
     self.main_window.listener.halt()
     QApplication.quit()
     logging.debug("[MAIN WINDOW] Window closed")
コード例 #16
0
 def set_data(self, data):
     self.events = []
     for meta in data:
         self.events.append(Event(meta=meta))
     QApplication.processEvents()
     self.update()
コード例 #17
0
ファイル: scheduler.py プロジェクト: nebulabroadcast/firefly
    def import_template(self, day_offset=0):
        try:
            if not os.path.exists("templates"):
                os.makedirs("templates")
        except Exception:
            log_traceback()
        file_path = QFileDialog.getOpenFileName(self, "Open template",
                                                os.path.abspath("templates"),
                                                "Templates (*.xml)")[0]

        if not file_path:
            return

        QApplication.setOverrideCursor(Qt.WaitCursor)
        try:
            feed = open(file_path, "rb").read().decode("utf-8")
            data = xml(feed)
        except Exception:
            QApplication.restoreOverrideCursor()
            log_traceback()
            return
        ch, cm = self.calendar.day_start
        events = []
        try:
            for day_index, day in enumerate(data.findall("day")):
                day_start = self.calendar.week_start_time + (3600 * 24 *
                                                             day_index)
                for event_data in day.findall("event"):
                    hh, mm = [
                        int(x) for x in event_data.attrib["time"].split(":")
                    ]

                    clock_offset = (hh * 3600) + (mm * 60) - (ch *
                                                              3600) - (cm * 60)
                    if (hh * 3600) + (mm * 60) < (ch * 3600) - (cm * 60):
                        clock_offset += 24 * 3600

                    start_time = day_start + clock_offset + (day_offset *
                                                             3600 * 24)

                    event = Event(meta={"start": start_time})
                    for m in event_data.findall("meta"):
                        key = m.attrib["key"]
                        value = m.text
                        if value:
                            event[key] = value

                    items_data = event_data.find("items")
                    if items_data is not None:
                        event.meta["_items"] = []
                        for ipos, item_data in enumerate(
                                items_data.findall("item")):
                            item = Item()
                            item["position"] = ipos + 1
                            for kv in item_data.findall("meta"):
                                item[kv.attrib["key"]] = kv.text
                            event.meta["_items"].append(item.meta)

                    events.append(event.meta)
                if day_offset:  # Importing single day.
                    break
        except Exception:
            QApplication.restoreOverrideCursor()
            log_traceback("Unable to parse template:")
            return
        if not events:
            QApplication.restoreOverrideCursor()
            return
        response = api.schedule(id_channel=self.id_channel, events=events)
        QApplication.restoreOverrideCursor()
        if not response:
            logging.error(response.message)
        else:
            logging.info(response.message)
        self.load()
コード例 #18
0
 def handle_query(self, msg):
     QApplication.processEvents()
コード例 #19
0
    def dropMimeData(self, data, action, row, column, parent):
        if action == Qt.IgnoreAction:
            return True

        if not self.parent().parent().edit_enabled:
            return True

        if row < 1:
            return False

        drop_objects = []
        if data.hasFormat("application/nx.item"):
            d = data.data("application/nx.item").data()
            items = json.loads(d.decode("ascii"))
            if not items or items[0].get("rundown_row", "") in [row, row - 1]:
                return False
            else:
                for obj in items:
                    if action == Qt.CopyAction:
                        obj["id"] = False
                    elif not obj.get("id", False):
                        item_role = obj.get("item_role", False)
                        if item_role in ["live", "placeholder"]:
                            dlg = PlaceholderDialog(self.parent(), obj)
                            dlg.exec_()
                            if not dlg.ok:
                                return False
                            for key in dlg.meta:
                                obj[key] = dlg.meta[key]
                        elif item_role in ["lead_in", "lead_out"]:
                            pass
                        else:
                            continue
                    drop_objects.append(Item(meta=obj))

        elif data.hasFormat("application/nx.asset"):
            d = data.data("application/nx.asset").data()
            items = json.loads(d.decode("ascii"))
            for asset_data in items:
                asset = Asset(meta=asset_data)
                drop_objects.append(asset)
        else:
            return False

        sorted_items = []
        i = row - 1
        to_bin = self.object_data[i]["id_bin"]

        # Apend heading items

        while i >= 1:
            current_object = self.object_data[i]
            if (current_object.object_type != "item"
                    or current_object["id_bin"] != to_bin):
                break
            p_item = current_object.id
            if p_item not in [item.id for item in drop_objects]:
                if p_item:
                    sorted_items.append({
                        "object_type": "item",
                        "id_object": p_item,
                        "meta": {}
                    })
            i -= 1
        sorted_items.reverse()

        # Append drop

        for obj in drop_objects:
            if data.hasFormat("application/nx.item"):
                sorted_items.append({
                    "object_type": "item",
                    "id_object": obj.id,
                    "meta": obj.meta
                })

            elif data.hasFormat("application/nx.asset"):
                if obj["subclips"]:
                    dlg = SubclipSelectDialog(self.parent(), obj)
                    dlg.exec_()
                    if dlg.ok:
                        for meta in dlg.result:
                            sorted_items.append({
                                "object_type": "asset",
                                "id_object": obj.id,
                                "meta": meta,
                            })

                else:  # Asset does not have subclips
                    meta = {}
                    if obj["mark_in"]:
                        meta["mark_in"] = obj["mark_in"]
                        meta["mark_out"] = obj["mark_out"]

                    sorted_items.append({
                        "object_type": "asset",
                        "id_object": obj.id,
                        "meta": meta
                    })

        # Append trailing items

        i = row
        while i < len(self.object_data):
            current_object = self.object_data[i]
            if (current_object.object_type != "item"
                    or current_object["id_bin"] != to_bin):
                break
            p_item = current_object.id
            if p_item not in [item.id for item in drop_objects]:
                if p_item:
                    sorted_items.append({
                        "object_type": "item",
                        "id_object": p_item,
                        "meta": {}
                    })
            i += 1

        #
        # Send order query
        #

        sorted_items = [item for item in sorted_items]  # if item["id_object"]]

        if not sorted_items:
            return
        self.parent().setCursor(Qt.BusyCursor)
        QApplication.processEvents()
        api.order(
            self.order_callback,
            id_channel=self.id_channel,
            id_bin=to_bin,
            order=sorted_items,
        )
        return False
コード例 #20
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)