def connect(self): """Create connection to CasparCG Server""" try: self.connection = telnetlib.Telnet(self.host, self.port, timeout=self.timeout) except Exception: log_traceback() return False return True
def get_plugin_path(group=False): try: plugin_path = os.path.join( storages[int(config.get("plugin_storage", 1))].local_path, config.get("plugin_root", ".nx/scripts/v5"), ) except Exception: log_traceback() return "" if group: plugin_path = os.path.join(plugin_path, group) if not os.path.exists(plugin_path): return "" return plugin_path
def load(self): if not os.path.exists(self.cache_path): return start_time = time.time() try: data = json.load(open(self.cache_path)) except Exception: log_traceback("Corrupted cache file '{}'".format(self.cache_path)) return for meta in data: self.data[int(meta["id"])] = Asset(meta=meta) logging.debug("Loaded {} assets from cache in {:.03f}s".format( len(self.data), time.time() - start_time))
def query(self, query, **kwargs): """Send AMCP command""" if not self.connection: if not self.connect(): return CasparResponse(500, "Unable to connect CasparCG server") query = query.strip() if kwargs.get("verbose", True): if not query.startswith("INFO"): logging.debug("Executing AMCP: {}".format(query)) query += "\r\n" if PYTHON_VERSION >= 3: query = bytes(query.encode("utf-8")) delim = bytes("\r\n".encode("utf-8")) else: delim = "\r\n" try: self.connection.write(query) result = self.connection.read_until(delim).strip() except Exception: log_traceback() return CasparResponse(500, "Query failed") if PYTHON_VERSION >= 3: result = result.decode("UTF-8") if not result: return CasparResponse(500, "No result") try: if result[0:3] == "202": return CasparResponse(202, "No result") elif result[0:3] in ["201", "200"]: stat = int(result[0:3]) result = decode_if_py3( self.connection.read_until(delim)).strip() return CasparResponse(stat, result) elif int(result[0:1]) > 3: stat = int(result[0:3]) return CasparResponse(stat, result) except Exception: log_traceback() return CasparResponse(500, "Malformed result: {}".format(result)) return CasparResponse(500, "Unexpected result: {}".format(result))
def save(self, key, value): if config.get("mc_thread_safe", False): return self.threaded_save(key, value) key = self.site + "-" + key for i in range(MAX_RETRIES): try: self.conn.set(str(key), str(value)) break except Exception: log_traceback(f"Cache save failed ({key})") time.sleep(0.1) self.connect() else: critical_error("Memcache save failed. This should never happen.") return True
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 restore_state(self): state = get_app_state(self.app.app_state_path) if "main_window/geometry" in state.allKeys(): self.restoreGeometry(state.value("main_window/geometry")) self.restoreState(state.value("main_window/state")) else: self.resize(800, 600) qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) if "main_window/app" in state.allKeys(): try: self.app_state = state.value("main_window/app") except Exception: log_traceback()
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 query(self, query, **kwargs): """Send AMCP command""" if not self.connection: if not self.connect(): return CasparResponse(500, "Unable to connect CasparCG server") query = query.strip() if kwargs.get("verbose", True): if not query.startswith("INFO"): logging.debug("Executing AMCP: {}".format(query)) query += "\r\n" if PYTHON_VERSION >= 3: query = bytes(query.encode("utf-8")) delim = bytes("\r\n".encode("utf-8")) else: delim = "\r\n" try: self.connection.write(query) result = self.connection.read_until(delim).strip() except Exception: log_traceback() return CasparResponse(500, "Query failed") if PYTHON_VERSION >= 3: result = result.decode("UTF-8") if not result: return CasparResponse(500, "No result") try: if result[0:3] == "202": return CasparResponse(202, "No result") elif result[0:3] in ["201", "200"]: stat = int(result[0:3]) result = decode_if_py3(self.connection.read_until(delim)).strip() return CasparResponse(stat, result) elif int(result[0:1]) > 3: stat = int(result[0:3]) return CasparResponse(stat, result) except Exception: log_traceback() return CasparResponse(500, "Malformed result: {}".format(result)) return CasparResponse(500, "Unexpected result: {}".format(result))
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)
def __init__(self): """Load initial config.""" super(Config, self).__init__() self["site_name"] = "Unnamed" self["user"] = "******" self["host"] = socket.gethostname() self["storages"] = {} self["rights"] = {} self["folders"] = {} self["playout_channels"] = {} self["ingest_channels"] = {} self["cs"] = {} self["views"] = {} self["meta_types"] = {} self["actions"] = {} self["services"] = {} if len(sys.argv) > 1 and os.path.exists(sys.argv[1]): local_settings_path = sys.argv[1] else: local_settings_path = "settings.json" settings_files = ["/etc/nebula.json", local_settings_path] settings = {} if "--daemon" in sys.argv: logging.file = os.devnull settings["daemon_mode"] = True for settings_file in settings_files: if os.path.exists(settings_file): try: settings.update(json.load(open(settings_file))) break except Exception: log_traceback(handlers=False) for key, value in dict(os.environ).items(): if key.lower().startswith("nebula_"): key = key.lower().replace("nebula_", "", 1) settings[key] = value if not settings: critical_error("Unable to open site settings") self.update(settings)
def parse_request(**kwargs): data = kwargs if cherrypy.request.method == "POST": try: raw_body = cherrypy.request.body.read().decode("utf-8") if raw_body.strip(): data.update(json.loads(raw_body)) except Exception: pass if not data.get("session_id", None): try: data["session_id"] = cherrypy.request.cookie["session_id"].value except KeyError: pass except Exception: log_traceback() return data
def threaded_save(self, key, value): if not self.pool: self.pool = pylibmc.ThreadMappedPool(self.conn) key = self.site + "-" + key with self.pool.reserve() as mc: for i in range(MAX_RETRIES): try: mc.set(str(key), str(value)) break except Exception: log_traceback(f"Cache save failed ({key})") time.sleep(0.3) self.connect() else: critical_error( "Memcache save failed. This should never happen.") self.pool.relinquish() return True
def event(self): if not hasattr(self, "_event"): self.db.query( """ SELECT meta FROM events WHERE id_magic=%s """, [self.id], ) # TODO: playout only try: self._event = Event(meta=self.db.fetchall()[0][0]) except IndexError: logging.error(f"Unable to get {self} event") self._event = False except Exception: log_traceback() self._event = False return self._event
def __init__(self, parent): super(BrowserModule, self).__init__(parent) self.tabs = QTabWidget(self) self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.close_tab) self.tabs.currentChanged.connect(self.on_tab_switch) self.layout = QVBoxLayout(self) self.layout.setSpacing(0) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.tabs) self.setLayout(self.layout) tabscfg = self.app_state.get("browser_tabs", []) created_tabs = 0 current_index = 0 for tabcfg in tabscfg: try: if tabcfg["id_view"] not in config["views"]: continue if tabcfg.get("active"): current_index = self.tabs.count() try: del tabcfg["active"] except KeyError: pass title = False if tabcfg.get("title"): title = tabcfg.get("title") try: del tabcfg["title"] except KeyError: pass self.new_tab(title, **tabcfg) created_tabs += 1 except Exception: log_traceback() logging.warning("Unable to restore tab") if not created_tabs: self.new_tab() self.tabs.setCurrentIndex(current_index)
def format_log_message(message): try: log = "{}\t{}\t{}@{}\t{}\n".format( time.strftime("%H:%M:%S"), { 0: "DEBUG ", 1: "INFO ", 2: "WARNING ", 3: "ERROR ", 4: "GOOD NEWS", }[message.data["message_type"]], message.data["user"], message.host, message.data["message"], ) except Exception: log_traceback() return None return log
def get_validator(object_type, **kwargs): plugin_path = get_plugin_path("validator") if not plugin_path: return f = FileObject(plugin_path, object_type + ".py") if f.exists: try: py_mod = imp.load_source(object_type, f.path) except Exception: log_traceback(f"Unable to load plugin {f.base_name}") return else: return if "Plugin" not in dir(py_mod): logging.error(f"No plugin class found in {f.base_name}") return return py_mod.Plugin(**kwargs)
def export_template(self): data = dump_template(self.calendar) try: if not os.path.exists("templates"): os.makedirs("templates") except Exception: log_traceback() save_file_path = QFileDialog.getSaveFileName( self, "Save scheduler template", os.path.abspath("templates"), "Templates (*.xml)", )[0] if os.path.splitext(save_file_path)[1].lower() != ".xml": save_file_path += ".xml" try: with open(save_file_path, "wb") as save_file: save_file.write(data.encode("utf-8")) except Exception: log_traceback()
def t(*args): tools_dir = get_plugin_path("tools") if not tools_dir: return plugin_name = args[0] try: fp, pathname, description = imp.find_module(plugin_name, [tools_dir]) except ImportError: critical_error("unable to locate module: " + plugin_name) try: module = imp.load_module(plugin_name, fp, pathname, description) except Exception: log_traceback() critical_error("Unable ot open tool " + plugin_name) logging.user = plugin_name margs = args[1:] if len(args) > 1 else [] module.Plugin(*margs)
def __init__(self, id_service, settings=False): logging.debug(f"Initializing service ID {id_service}") self.id_service = id_service self.settings = settings config["id_service"] = id_service try: self.on_init() except SystemExit: sys.exit(1) except Exception: log_traceback(f"Unable to initialize service ID {id_service}") self.shutdown() else: db = DB() db.query( "UPDATE services SET last_seen = %s, state=1 WHERE id=%s", [time.time(), self.id_service], ) db.commit() logging.goodnews("Service started")
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")
def version_backup(asset): target_dir = os.path.join( storages[asset["id_storage"]].local_path, ".nx", "versions", f"{int(asset.id/1000):04d}", f"{asset.id:d}", ) ext = os.path.splitext(asset.file_path)[1] target_fname = f"{asset['mtime']:d}{ext}" if not os.path.isdir(target_dir): try: os.makedirs(target_dir) except IOError: pass try: os.rename(asset.file_path, os.path.join(target_dir, target_fname)) except IOError: log_traceback() logging.warning(f"Unable to create version backup of {asset}")
def on_message(self, *args): data = args[-1] if not self.active: logging.goodnews("[LISTENER] connected", handlers=False) self.active = True try: message = SeismicMessage(json.loads(data)) except Exception: log_traceback(handlers=False) logging.debug(f"[LISTENER] Malformed message: {data}", handlers=False) return if message.site_name != self.site_name: return self.last_msg = time.time() if message.data and message.data.get("initiator", None) == CLIENT_ID: return self.queue.put(message)
def send_message(self, method, **data): if not (self.connection and self.channel): if not self.connect(): time.sleep(1) return message = json.dumps( [time.time(), config["site_name"], config["host"], method, data] ) try: self.channel.basic_publish( exchange="", routing_key=config["site_name"], body=message ) except pika.exceptions.ChannelWrongStateError: logging.warning("RabbitMQ: nobody's listening", handlers=[]) return except pika.exceptions.StreamLostError: logging.error("RabbitMQ connection lost", handlers=[]) self.connection = self.channel = None except Exception: log_traceback("RabbitMQ error", handlers=[]) logging.debug("Unable to send message", message, handlers=[]) self.connection = self.channel = None
def load(self): logging.info("Reloading webtools") self.tools = {} tooldir = get_plugin_path("webtools") if not tooldir: return for plugin_entry in os.listdir(tooldir): entry_path = os.path.join(tooldir, 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(tooldir, 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_module_path}") continue Plugin = py_mod.Plugin if hasattr(Plugin, "title"): title = Plugin.title else: title = plugin_name.capitalize() logging.info(f"Loaded plugin {plugin_name} ({plugin_module_path})") self.tools[plugin_name] = [Plugin, title]
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 main_thread(self): try: self.on_main() except Exception: log_traceback() self.busy = False
def build(self, *args, **kwargs): self["name"] = "passreset" self["title"] = "Password reset" self["mode"] = "email-entry" # # REQUEST EMAIL # if "email" in kwargs: email = kwargs["email"].strip() if not re.match(EMAIL_REGEXP, email): self.context.message("Invalid e-mail address specified", "error") return db = DB() db.query( """ SELECT meta FROM users where LOWER(meta->>'email') = LOWER(%s) """, [email], ) try: user = User(meta=db.fetchall()[0][0], db=db) except IndexError: self.context.message("No such user", "error") return if time.time() - user.meta.get("pass_reset_time", 0) < 3600: self.context.message( "Only one password reset request per hour is allowed", "error" ) return token = get_guid() user["pass_reset_time"] = time.time() user["pass_reset_code"] = token mailvars = { "name": user["full_name"] or user["login"], "site_name": config["site_name"], "hub_url": config.get( "hub_url", f"https://{config['site_name']}.nbla.cloud" ), "token": token, } body = MAIL_BODY.format(**mailvars) try: send_mail(email, "Nebula password reset", body) except Exception: log_traceback() self.context.message( """Unable to send password reset email. Please contact your system administrator""", "error", ) return user.save() self["mode"] = "mail-sent" return # # GOT TOKEN # elif "token" in kwargs: token = kwargs["token"].strip() self["mode"] = False self["token"] = token if not re.match(GUID_REGEXP, token): self.context.message("Invalid token specified", "error") return db = DB() db.query( "SELECT meta FROM users WHERE meta->>'pass_reset_code' = %s", [token] ) try: user = User(meta=db.fetchall()[0][0], db=db) except IndexError: self.context.message("No such token", "error") return if user["pass_reset_time"] < time.time() - 3600: self.context.message("Token expired.", "error") self["mode"] = "email-entry" return pass1 = kwargs.get("pass1", False) pass2 = kwargs.get("pass2", False) if pass1 and pass2: if pass1 != pass2: self["mode"] = "pass-entry" self.context.message("Passwords don't match", "error") return if len(pass1) < 8: self["mode"] = "pass-entry" self.context.message( "The password is weak. Must be at least 8 characters", "error" ) return user.set_password(pass1) del user.meta["pass_reset_code"] del user.meta["pass_reset_time"] user.save() self["mode"] = "finished" return self["mode"] = "pass-entry" return
def send_to( id_asset, id_action, settings=None, id_user=None, priority=3, restart_existing=True, restart_running=False, db=False, ): db = db or DB() if not id_asset: NebulaResponse(401, message="You must specify existing object") if settings is None: settings = {} db.query( """ SELECT id FROM jobs WHERE id_asset=%s AND id_action=%s AND settings=%s """, [id_asset, id_action, json.dumps(settings)], ) res = db.fetchall() if res: if restart_existing: conds = "0,5" if not restart_running: conds += ",1" db.query( f""" UPDATE jobs SET id_user=%s, id_service=NULL, message='Restart requested', status=5, retries=0, creation_time=%s, start_time=NULL, end_time=NULL WHERE id=%s AND status NOT IN ({conds}) RETURNING id """, [id_user, time.time(), res[0][0]], ) db.commit() if db.fetchall(): messaging.send( "job_progress", id=res[0][0], id_asset=id_asset, id_action=id_action, progress=0, ) return NebulaResponse(201, message="Job restarted") return NebulaResponse(200, message="Job exists. Not restarting") else: return NebulaResponse(200, message="Job exists. Not restarting") # # Create a new job # db.query( """INSERT INTO jobs ( id_asset, id_action, id_user, settings, priority, message, creation_time ) VALUES ( %s, %s, %s, %s, %s, 'Pending', %s ) RETURNING id """, [ id_asset, id_action, id_user, json.dumps(settings), priority, time.time() ], ) try: id_job = db.fetchall()[0][0] db.commit() except Exception: log_traceback() return NebulaResponse(500, "Unable to create job") messaging.send( "job_progress", id=id_job, id_asset=id_asset, id_action=id_action, progress=0, message="Job created", ) return NebulaResponse(201, message="Job created")
def get_job(id_service, action_ids, db=False): assert type(action_ids) == list, "action_ids must be list of integers" if not action_ids: return False db = db or DB() now = time.time() running_jobs_count = {} db.query(""" select id_action, count(id) from jobs where status=1 group by id_action """) for id_action, cnt in db.fetchall(): running_jobs_count[id_action] = cnt q = """ SELECT id, id_action, id_asset, id_user, settings, priority, retries, status FROM jobs WHERE status IN (0,3,5) AND id_action IN %s AND id_service IS NULL AND retries < %s ORDER BY priority DESC, creation_time DESC """ db.query(q, [tuple(action_ids), MAX_RETRIES]) for ( id_job, id_action, id_asset, id_user, settings, priority, retries, status, ) in db.fetchall(): asset = Asset(id_asset, db=db) action = actions[id_action] job = Job(id_job, db=db) job._asset = asset job._settings = settings job.priority = priority job.retries = retries job.id_user = id_user max_running_jobs = action.settings.attrib.get("max_jobs", 0) try: max_running_jobs = int(max_running_jobs) except ValueError: max_running_jobs = 0 if max_running_jobs: running_jobs = running_jobs_count.get(id_action, 0) if running_jobs >= max_running_jobs: continue # Maximum allowed jobs already running. skip for pre in action.settings.findall("pre"): if pre.text: try: exec(pre.text) except Exception: log_traceback() continue if not action: logging.warning( f"Unable to get job. No such action ID {id_action}") continue if status != 5 and action.should_skip(asset): logging.info(f"Skipping {job}") db.query( """ UPDATE jobs SET status=6, message='Skipped', start_time=%s, end_time=%s WHERE id=%s """, [now, now, id_job], ) db.commit() continue if action.should_start(asset): if job.take(id_service): return job else: logging.warning(f"Unable to take {job}") continue else: db.query("UPDATE jobs SET message='Waiting' WHERE id=%s", [id_job]) messaging.send( "job_progress", id=id_job, id_asset=id_asset, id_action=id_action, status=status, progress=0, message="Waiting", ) db.commit() return False
def api_set(**kwargs): object_type = kwargs.get("object_type", "asset") objects = kwargs.get("objects", []) data = kwargs.get("data", {}) user = kwargs.get("user", anonymous) db = kwargs.get("db", DB()) initiator = kwargs.get("initiator", None) if not user: return NebulaResponse(401) if not (data and objects): return NebulaResponse(200, "No object created or modified") object_type_class = { "asset": Asset, "item": Item, "bin": Bin, "event": Event, "user": User, }.get(object_type, None) if object_type_class is None: return NebulaResponse(400, f"Unsupported object type {object_type}") changed_objects = [] affected_bins = [] if "_password" in data: hpass = get_hash(data["_password"]) del data["_password"] data["password"] = hpass for id_object in objects: obj = object_type_class(id_object, db=db) changed = False if object_type == "asset": id_folder = data.get("id_folder", False) or obj["id_folder"] if not user.has_right("asset_edit", id_folder): folder_title = config["folders"][id_folder]["title"] return NebulaResponse( 403, f"{user} is not allowed to edit {folder_title} folder") elif object_type == "user": if obj.id: if not user.has_right("user_edit"): return NebulaResponse( 403, f"{user} is not allowed to edit users data") else: if not user.has_right("user_create"): return NebulaResponse( 403, f"{user} is not allowed to add new users") changed = False for key in data: value = data[key] old_value = obj[key] obj[key] = value if obj[key] != old_value: changed = True validator = get_validator(object_type, db=db) if changed and validator: logging.debug("Executing validation script") tt = obj.__repr__() try: obj = validator.validate(obj) except Exception: return NebulaResponse( 500, log_traceback("Unable to validate object changes.")) if not isinstance(obj, BaseObject): # TODO: use 409-conflict? return NebulaResponse(400, f"Unable to save {tt}:\n\n{obj}") if changed: obj.save(notify=False) changed_objects.append(obj.id) if object_type == "item" and obj["id_bin"] not in affected_bins: affected_bins.append(obj["id_bin"]) if changed_objects: messaging.send( "objects_changed", objects=changed_objects, object_type=object_type, user="******".format(user), initiator=initiator, ) if affected_bins: bin_refresh(affected_bins, db=db, initiator=initiator) return NebulaResponse(200, data=changed_objects)
def main(self): info = self.parser.get_info(self.caspar_feed_layer) if not info: logging.warning("Channel {} update stat failed".format(self.id_channel)) self.bad_requests += 1 if self.bad_requests > 10: logging.error("Connection lost. Reconnecting...") if self.connect(): logging.goodnews("Connection estabilished") else: logging.error("Connection call failed") time.sleep(2) time.sleep(0.3) return else: self.request_time = time.time() self.bad_requests = 0 current_fname = info["current"] cued_fname = info["cued"] # # # Auto recovery # # if not current_fname and time.time() - self.recovery_time > 20: # self.parent.channel_recover() # return # self.recovery_time = time.time() if ( cued_fname and (not self.paused) and (info["pos"] == self.fpos) and not self.parent.current_live and self.cued_item and (not self.cued_item["run_mode"]) ): if self.stalled and self.stalled < time.time() - 5: logging.warning("Stalled for a long time") logging.warning("Taking stalled clip (pos: {})".format(self.fpos)) self.take() elif not self.stalled: logging.debug("Playback is stalled") self.stalled = time.time() elif self.stalled: logging.debug("No longer stalled") self.stalled = False self.fpos = info["pos"] self.fdur = info["dur"] # # Playlist advancing # advanced = False if ( self.cueing and self.cueing == current_fname and not cued_fname and not self.parent.cued_live ): logging.warning("Using short clip workaround") self.current_item = self.cued_item self.current_fname = current_fname self.current_in = self.cued_in self.current_out = self.cued_out self.cued_in = self.cued_out = 0 advanced = True self.cued_item = False self.cueing = False self.cued_fname = False elif (not cued_fname) and (current_fname) and not self.parent.cued_live: if current_fname == self.cued_fname: self.current_item = self.cued_item self.current_fname = self.cued_fname self.current_in = self.cued_in self.current_out = self.cued_out self.cued_in = self.cued_out = 0 advanced = True self.cued_item = False elif (not current_fname) and (not cued_fname) and self.parent.cued_live: self.current_item = self.cued_item self.current_fname = "LIVE" self.current_in = 0 self.current_out = 0 self.cued_in = self.cued_out = 0 advanced = True self.cued_item = False self.parent.on_live_enter() if advanced: try: self.parent.on_change() except Exception: log_traceback("Playout on_change failed") if self.current_item and not self.cued_item and not self.cueing: self.parent.cue_next() elif self.force_cue: logging.info("Forcing cue next") self.parent.cue_next() self.force_cue = False if self.cueing: if cued_fname == self.cued_fname: logging.debug("Cued", self.cueing) self.cueing = False else: logging.debug("Cueing", self.cueing) if ( self.cued_item and cued_fname and cued_fname != self.cued_fname and not self.cueing ): logging.warning( "Cue mismatch: IS: {} SHOULDBE: {}".format(cued_fname, self.cued_fname) ) self.cued_item = False try: self.parent.on_progress() except Exception: log_traceback("Playout on_main failed") self.current_fname = current_fname self.cued_fname = cued_fname