def update_title(self): title = "{} [{}]" if self._s_type is SettingsType.ENIGMA_2: self._dialog.set_title( title.format(get_message("Options"), self._enigma_radio_button.get_label())) elif self._s_type is SettingsType.NEUTRINO_MP: self._dialog.set_title( title.format(get_message("Options"), self._neutrino_radio_button.get_label()))
def __init__(self, transient, s_type, m3_path, app): super().__init__(transient, s_type) self._app = app self._picons = app.picons self._pic_path = app._settings.profile_picons_path self._services = None self._url_count = 0 self._errors_count = 0 self._max_count = 0 self._is_download = False self._cancellable = Gio.Cancellable() self._dialog.set_title(get_message("Playlist import")) self._dialog.connect("delete-event", self.on_close) self._apply_button.set_label(get_message("Import")) # Progress self._progress_bar = Gtk.ProgressBar(visible=False, valign="center") self._spinner = Gtk.Spinner(active=False) self._info_label = Gtk.Label(visible=True, ellipsize="end", max_width_chars=30) load_label = Gtk.Label(label=get_message("Loading data...")) self._spinner.bind_property("active", self._spinner, "visible") self._spinner.bind_property("visible", load_label, "visible") self._spinner.bind_property("active", self._start_values_grid, "sensitive", 4) progress_box = Gtk.HBox(visible=True, spacing=2) progress_box.add(self._progress_bar) progress_box.pack_end(self._spinner, False, False, 0) progress_box.pack_start(load_label, False, False, 0) # Picons self._picons_switch = Gtk.Switch(visible=True) self._picon_box = Gtk.HBox(visible=True, sensitive=False, spacing=2) self._picon_box.pack_end(self._picons_switch, False, False, 0) self._picon_box.pack_end( Gtk.Label(visible=True, label=get_message("Download picons")), False, False, 0) # Extra box extra_box = Gtk.HBox(visible=True, spacing=2, margin_bottom=5, margin_top=5) extra_box.set_center_widget(progress_box) extra_box.pack_start(self._info_label, False, False, 5) extra_box.pack_end(self._picon_box, True, True, 5) frame = Gtk.Frame(visible=True, margin_bottom=5) frame.add(extra_box) self._data_box.add(frame) self.get_m3u(m3_path, s_type)
def on_auto_configuration(self, item): """ Simple mapping of services by name. """ use_cyrillic = locale.getdefaultlocale()[0] in ("ru_RU", "be_BY", "uk_UA", "sr_RS") tr = None if use_cyrillic: # may be not entirely correct symbols = (u"АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯІÏҐЎЈЂЉЊЋЏTB", u"ABVGDEEJZIJKLMNOPRSTUFHZCSS_Y_EUAIEGUEDLNCJTV") tr = {ord(k): ord(v) for k, v in zip(*symbols)} source = {} for row in self._services_model: name = re.sub("\\W+", "", str(row[0])).upper() name = name.translate(tr) if use_cyrillic else name source[name] = row[1] success_count = 0 not_founded = {} for r in self._bouquet_model: if r[Column.FAV_TYPE] != BqServiceType.IPTV.value: continue name = re.sub("\\W+", "", str(r[Column.FAV_SERVICE])).upper() if use_cyrillic: name = name.translate(tr) ref = source.get( name, None ) # Not [pop], because the list may contain duplicates or similar names! if ref: self.assign_data(r, ref, True) success_count += 1 else: not_founded[name] = r # Additional attempt to search in the remaining elements for n in not_founded: for k in source: if k.startswith(n): self.assign_data(not_founded[n], source[k], True) success_count += 1 break self.update_epg_count() self.show_info_message( "{} {} {}".format( get_message("Done!"), get_message("Count of successfully configured services:"), success_count), Gtk.MessageType.INFO)
def update_source_count_info(self): source_count = len(self._services_model) self._source_count_label.set_text(str(source_count)) if self._enable_dat_filter and source_count == 0: msg = get_message( "Current epg.dat file does not contains references for the services of this bouquet!" ) self.show_info_message(msg, Gtk.MessageType.WARNING)
def on_drag_data_get(self, view, drag_context, data, info, time): model, paths = view.get_selection().get_selected_rows() if paths: s_data = model[paths][:] if all(s_data): data.set_text("::::".join(s_data), -1) else: self.show_info_message(get_message("Source error!"), Gtk.MessageType.ERROR)
def on_import(self, item): if not any(r[-1] for r in self._main_model): self.show_info_message(get_message("No selected item!"), Gtk.MessageType.ERROR) return if not self._bouquets or show_dialog( DialogType.QUESTION, self._dialog_window) == Gtk.ResponseType.CANCEL: return self.import_data()
def set_yt_url(self, entry, video_id): try: if not self._yt_dl: def callback(message, error=True): msg_type = Gtk.MessageType.ERROR if error else Gtk.MessageType.INFO self.show_info_message(message, msg_type) self._yt_dl = YouTube.get_instance(self._settings, callback=callback) yield True links, title = self._yt_dl.get_yt_link(video_id, entry.get_text()) yield True except urllib.error.URLError as e: self.show_info_message( get_message("Getting link error:") + (str(e)), Gtk.MessageType.ERROR) return except YouTubeException as e: self.show_info_message((str(e)), Gtk.MessageType.ERROR) return else: if self._action is Action.ADD: self._name_entry.set_text(title) if links: if len(links) > 1: self._yt_quality_box.set_visible(True) entry.set_text(links[sorted(links, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]) self._yt_links = links else: msg = get_message("Getting link error:" ) + " No link received for id: {}".format( video_id) self.show_info_message(msg, Gtk.MessageType.ERROR) finally: entry.set_sensitive(True) yield True
def get_m3u(self, path, s_type): try: GLib.idle_add(self._spinner.set_property, "active", True) self._services = parse_m3u(path, s_type) for s in self._services: if s.picon: GLib.idle_add(self._picon_box.set_sensitive, True) break finally: msg = "{} {}.".format(get_message("Streams detected:"), len(self._services) if self._services else 0) GLib.idle_add(self._info_label.set_text, msg) GLib.idle_add(self._spinner.set_property, "active", False)
def update_progress(self): self._url_count -= 1 frac = 1 - self._url_count / self._max_count self._progress_bar.set_fraction(frac) if self._url_count == 0: self._progress_bar.set_visible(False) self._progress_bar.set_fraction(0.0) self._apply_button.set_sensitive(True) self._info_label.set_text("{} {}.".format(get_message("Errors:"), self._errors_count)) self._is_download = False gen = self.update_fav_model() GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def assign_data(self, row, ref, show_error=False): if row[Column.FAV_TYPE] != BqServiceType.IPTV.value: if not show_error: self.show_info_message( get_message("Not allowed in this context!"), Gtk.MessageType.ERROR) return fav_id = row[Column.FAV_ID] fav_id_data = fav_id.split(":") fav_id_data[3:7] = ref.split(":") new_fav_id = ":".join(fav_id_data) service = self._services.pop(fav_id, None) if service: self._services[new_fav_id] = service row[Column.FAV_ID] = new_fav_id row[Column.FAV_LOCKED] = EPG_ICON row[Column.FAV_TOOLTIP] = ":".join( fav_id_data[:10]) if self._show_tooltips else None
def on_save(self, item): if self._action is Action.ADD: self.on_url_changed(self._url_entry) if not is_data_correct(self._digit_elems) or self._url_entry.get_name( ) == _DIGIT_ENTRY_NAME: self.show_info_message(get_message("Error. Verify the data!"), Gtk.MessageType.ERROR) return if show_dialog(DialogType.QUESTION, self._dialog) in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT): return self.save_enigma2_data( ) if self._s_type is SettingsType.ENIGMA_2 else self.save_neutrino_data( ) self._dialog.destroy()
def on_import(self, item): self.on_info_bar_close() self.update_active_elements(False) self._download_task = True try: with concurrent.futures.ThreadPoolExecutor( max_workers=4) as executor: done_links = {} rows = list(filter(lambda r: r[2], self._model)) if not self._yt: self._yt = YouTube.get_instance(self._settings) futures = { executor.submit(self._yt.get_yt_link, r[1], YouTube.VIDEO_LINK.format(r[1]), True): r for r in rows } size = len(futures) counter = 0 for future in concurrent.futures.as_completed(futures): if not self._download_task: executor.shutdown() return done_links[futures[future]] = future.result() counter += 1 self.update_progress_bar(counter / size) except YouTubeException as e: self.show_info_message(str(e), Gtk.MessageType.ERROR) except Exception as e: self.show_info_message(str(e), Gtk.MessageType.ERROR) else: if self._download_task: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO) self.append_services([done_links[r] for r in rows]) finally: self._download_task = False self.update_active_elements(True)
def assign_data(self, row, data, show_error=False): if row[Column.FAV_TYPE] != BqServiceType.IPTV.value: if not show_error: self.show_info_message( get_message("Not allowed in this context!"), Gtk.MessageType.ERROR) return fav_id = row[Column.FAV_ID] fav_id_data = fav_id.split(":") fav_id_data[3:7] = data[-1].split(":") new_fav_id = ":".join(fav_id_data) service = self._services.pop(fav_id, None) if service: self._services[new_fav_id] = service row[Column.FAV_ID] = new_fav_id row[Column.FAV_LOCKED] = EPG_ICON pos = f"({data[1] if self._refs_source is RefsSource.SERVICES else 'XML'})" src = f"{get_message('EPG source')}: {(GLib.markup_escape_text(data[0] or ''))} {pos}" row[Column. FAV_TOOLTIP] = f"{get_message('Service reference')}: {':'.join(fav_id_data[:10])}\n{src}"
def on_save_to_xml(self, item): response = show_dialog(DialogType.CHOOSER, self._dialog, settings=self._settings) if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT): return services = [] iptv_types = (BqServiceType.IPTV.value, BqServiceType.MARKER.value) for r in self._bouquet_model: srv_type = r[Column.FAV_TYPE] if srv_type in iptv_types: srv = BouquetService(name=r[Column.FAV_SERVICE], type=BqServiceType(srv_type), data=r[Column.FAV_ID], num=r[Column.FAV_NUM]) services.append(srv) ChannelsParser.write_refs_to_xml( "{}{}.xml".format(response, self._bouquet_name), services) self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
def init_xml_source(self, refs): path = self._epg_dat_path_entry.get_text( ) if self._use_web_source else self._xml_chooser_button.get_filename() if not path: self.show_info_message("The path to the xml file is not set!", Gtk.MessageType.ERROR) return if self._use_web_source: # Downloading gzipped xml file that contains services names with references from the web. self._download_xml_is_active = True self.update_active_header_elements(False) url = self._url_to_xml_entry.get_text() try: with urllib.request.urlopen(url, timeout=2) as fp: headers = fp.info() content_type = headers.get("Content-Type", "") if content_type != "application/gzip": self._download_xml_is_active = False raise ValueError("{} {} {}".format( get_message("Download XML file error."), get_message("Unsupported file type:"), content_type)) file_name = os.path.basename(url) data_path = self._epg_dat_path_entry.get_text() with open(data_path + file_name, "wb") as tfp: bs = 1024 * 8 size = -1 read = 0 b_num = 0 if "content-length" in headers: size = int(headers["Content-Length"]) while self._download_xml_is_active: block = fp.read(bs) if not block: break read += len(block) tfp.write(block) b_num += 1 self.update_download_progress(b_num * bs / size) yield True path = tfp.name.rstrip(".gz") except (HTTPError, URLError) as e: raise ValueError("{} {}".format( get_message("Download XML file error."), e)) else: try: with open(path, "wb") as f_out: with gzip.open(tfp.name, "rb") as f: shutil.copyfileobj(f, f_out) os.remove(tfp.name) except Exception as e: raise ValueError("{} {}".format( get_message("Unpacking data error."), e)) finally: self._download_xml_is_active = False self.update_active_header_elements(True) try: s_refs, info = ChannelsParser.get_refs_from_xml(path) yield True except Exception as e: raise ValueError("{} {}".format(get_message("XML parsing error:"), e)) else: if refs: s_refs = filter(lambda x: x.num in refs, s_refs) list( map(lambda s: self._services_model.append((s.name, s.data)), s_refs)) self.update_source_info(info) self.update_source_count_info() yield True
def __init__(self, app, bouquet, bouquet_name): handlers = { "on_close_dialog": self.on_close_dialog, "on_apply": self.on_apply, "on_update": self.on_update, "on_save_to_xml": self.on_save_to_xml, "on_auto_configuration": self.on_auto_configuration, "on_filter_toggled": self.on_filter_toggled, "on_filter_changed": self.on_filter_changed, "on_info_bar_close": self.on_info_bar_close, "on_popup_menu": on_popup_menu, "on_bouquet_popup_menu": self.on_bouquet_popup_menu, "on_copy_ref": self.on_copy_ref, "on_assign_ref": self.on_assign_ref, "on_reset": self.on_reset, "on_list_reset": self.on_list_reset, "on_drag_begin": self.on_drag_begin, "on_drag_data_get": self.on_drag_data_get, "on_drag_data_received": self.on_drag_data_received, "on_resize": self.on_resize, "on_names_source_changed": self.on_names_source_changed, "on_options_save": self.on_options_save, "on_use_web_source_switch": self.on_use_web_source_switch, "on_enable_filtering_switch": self.on_enable_filtering_switch, "on_update_on_start_switch": self.on_update_on_start_switch, "on_field_icon_press": self.on_field_icon_press, "on_key_press": self.on_key_press, "on_bq_cursor_changed": self.on_bq_cursor_changed } self._app = app self._services = {} self._ex_services = self._app.current_services self._ex_fav_model = self._app.fav_view.get_model() self._settings = self._app.app_settings self._bouquet = bouquet self._bouquet_name = bouquet_name self._current_ref = [] self._enable_dat_filter = False self._use_web_source = False self._update_epg_data_on_start = False self._refs_source = RefsSource.SERVICES self._download_xml_is_active = False builder = get_builder(UI_RESOURCES_PATH + "epg.glade", handlers) self._dialog = builder.get_object("epg_dialog_window") self._dialog.set_transient_for(self._app.app_window) self._source_view = builder.get_object("source_view") self._bouquet_view = builder.get_object("bouquet_view") self._bouquet_model = builder.get_object("bouquet_list_store") self._services_model = builder.get_object("services_list_store") self._info_bar = builder.get_object("info_bar") self._message_label = builder.get_object("info_bar_message_label") self._assign_ref_popup_item = builder.get_object( "bouquet_assign_ref_popup_item") self._left_action_box = builder.get_object("left_action_box") self._xml_download_progress_bar = builder.get_object( "xml_download_progress_bar") # Filter self._filter_bar = builder.get_object("filter_bar") self._filter_entry = builder.get_object("filter_entry") self._filter_auto_switch = builder.get_object("filter_auto_switch") self._services_filter_model = builder.get_object( "services_filter_model") self._services_filter_model.set_visible_func( self.services_filter_function) # Info self._source_count_label = builder.get_object("source_count_label") self._source_info_label = builder.get_object("source_info_label") self._bouquet_count_label = builder.get_object("bouquet_count_label") self._bouquet_epg_count_label = builder.get_object( "bouquet_epg_count_label") # Options self._xml_radiobutton = builder.get_object("xml_radiobutton") self._xml_chooser_button = builder.get_object("xml_chooser_button") self._names_source_box = builder.get_object("names_source_box") self._web_source_box = builder.get_object("web_source_box") self._use_web_source_switch = builder.get_object( "use_web_source_switch") self._url_to_xml_entry = builder.get_object("url_to_xml_entry") self._enable_filtering_switch = builder.get_object( "enable_filtering_switch") self._epg_dat_path_entry = builder.get_object("epg_dat_path_entry") self._epg_dat_stb_path_entry = builder.get_object( "epg_dat_stb_path_entry") self._update_on_start_switch = builder.get_object( "update_on_start_switch") self._epg_dat_source_box = builder.get_object("epg_dat_source_box") if IS_GNOME_SESSION: header_bar = Gtk.HeaderBar( visible=True, show_close_button=True, title="EPG", subtitle=get_message("List configuration")) self._dialog.set_titlebar(header_bar) builder.get_object("left_action_box").reparent(header_bar) right_box = builder.get_object("right_action_box") builder.get_object("main_actions_box").remove(right_box) header_bar.pack_end(right_box) builder.get_object("toolbar_box").set_visible(False) self._app.connect("epg-dat-downloaded", self.on_epg_dat_downloaded) # Setting the last size of the dialog window window_size = self._settings.get("epg_tool_window_size") if window_size: self._dialog.resize(*window_size) self.init_drag_and_drop() self.on_update()
def show_info_message(self, text, message_type): self._info_bar.set_visible(False) self._info_bar.set_message_type(message_type) self._message_label.set_text(get_message(text)) self._info_bar.set_visible(True)