From e599ea04c7fe29553d246656cd58612166f3215c Mon Sep 17 00:00:00 2001 From: DYefremov Date: Tue, 28 Dec 2021 15:17:39 +0300 Subject: [PATCH] improved bouquet export to *.m3u (#56) --- app/eparser/ecommons.py | 4 ++ app/eparser/iptv.py | 20 +++++----- app/ui/app_menu.ui | 2 +- app/ui/main.glade | 82 ++++++++++++++++++++++++++++++++++++----- app/ui/main.py | 51 ++++++++++++++++++++----- 5 files changed, 130 insertions(+), 29 deletions(-) diff --git a/app/eparser/ecommons.py b/app/eparser/ecommons.py index b3c17ef1..30118cab 100644 --- a/app/eparser/ecommons.py +++ b/app/eparser/ecommons.py @@ -47,6 +47,10 @@ class BqServiceType(Enum): ALT = "ALT" # Service with alternatives BOUQUET = "BOUQUET" # Sub bouquet. + @classmethod + def _missing_(cls, value): + return cls.DEFAULT + Bouquet = namedtuple("Bouquet", ["name", "type", "services", "locked", "hidden", "file"]) Bouquet.__new__.__defaults__ = (None, BqServiceType.DEFAULT, [], None, None, None) # For Python3 < 3.7 diff --git a/app/eparser/iptv.py b/app/eparser/iptv.py index 651d1fb5..1a6b82cb 100644 --- a/app/eparser/iptv.py +++ b/app/eparser/iptv.py @@ -112,12 +112,12 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None): srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], st, picon, p_id, *s_aggr, url, fav_id, None) services.append(srv) else: - log("*.m3u* parse error ['{}']: name[{}], url[{}], fav id[{}]".format(path, name, url, fav_id)) + log(f"*.m3u* parse error ['{path}']: name[{name}], url[{url}], fav id[{fav_id}]") return services -def export_to_m3u(path, bouquet, s_type): +def export_to_m3u(path, bouquet, s_type, url=None): pattern = re.compile(".*:(http.*):.*") if s_type is SettingsType.ENIGMA_2 else re.compile("(http.*?)::::.*") lines = ["#EXTM3U\n"] current_grp = None @@ -128,15 +128,17 @@ def export_to_m3u(path, bouquet, s_type): res = re.match(pattern, s.data) if not res: continue - data = res.group(1) - lines.append("#EXTINF:-1,{}\n".format(s.name)) - if current_grp: - lines.append(current_grp) - lines.append("{}\n".format(unquote(data.strip()))) + lines.append(f"#EXTINF:-1,{s.name}\n") + lines.append(current_grp) if current_grp else None + lines.append(f"{unquote(res.group(1).strip())}\n") elif s_type is BqServiceType.MARKER: - current_grp = "#EXTGRP:{}\n".format(s.name) + current_grp = f"#EXTGRP:{s.name}\n" + elif s_type is BqServiceType.DEFAULT and url: + lines.append(f"#EXTINF:-1,{s.name}\n") + lines.append(current_grp) if current_grp else None + lines.append(f"{url}{s.data}\n") - with open(path + "{}.m3u".format(bouquet.name), "w", encoding="utf-8") as file: + with open(f"{path}{bouquet.name}.m3u", "w", encoding="utf-8") as file: file.writelines(lines) diff --git a/app/ui/app_menu.ui b/app/ui/app_menu.ui index 3f9d12f8..55cf70aa 100644 --- a/app/ui/app_menu.ui +++ b/app/ui/app_menu.ui @@ -380,7 +380,7 @@ Export to m3u - app.on_export_to_m3u + app.on_export_iptv_to_m3u
diff --git a/app/ui/main.glade b/app/ui/main.glade index 0162afb3..6974bde7 100644 --- a/app/ui/main.glade +++ b/app/ui/main.glade @@ -138,11 +138,63 @@ Author: Dmitriy Yefremov False gtk-index + + True + False + document-save-as + True False gtk-save-as + + False + + + True + False + 10 + 10 + 5 + 5 + vertical + 2 + + + False + True + True + All + + + + False + True + 0 + + + + + True + True + True + app.on_export_iptv_to_m3u + IPTV services only + + + False + True + 1 + + + + + main + 1 + + + True False @@ -765,6 +817,16 @@ Author: Dmitriy Yefremov + + + gtk-save-as + True + False + True + True + + + Import @@ -778,13 +840,13 @@ Author: Dmitriy Yefremov - - gtk-save-as - True + + Export to m3u + False False - True - True - + export_bouquet_to_m3u_image + False + @@ -2455,16 +2517,16 @@ Author: Dmitriy Yefremov - + True False False False True Export to m3u - + export_to_m3u_menu - + True False document-save-as-symbolic @@ -3995,7 +4057,7 @@ Author: Dmitriy Yefremov False export_to_m3u_image False - + diff --git a/app/ui/main.py b/app/ui/main.py index 2b5fca77..a200becb 100644 --- a/app/ui/main.py +++ b/app/ui/main.py @@ -101,7 +101,7 @@ class Application(Gtk.Application): _FAV_ENIGMA_ELEMENTS = ("fav_insert_marker_popup_item", "fav_epg_configuration_popup_item") - _FAV_IPTV_ELEMENTS = ("fav_iptv_popup_item", "import_m3u_header_button", "export_to_m3u_header_button", + _FAV_IPTV_ELEMENTS = ("fav_iptv_popup_item", "import_m3u_header_button", "export_to_m3u_menu_button", "iptv_menu_button") _LOCK_HIDE_ELEMENTS = ("enigma_lock_hide_box", "bouquet_lock_hide_box") @@ -156,7 +156,8 @@ class Application(Gtk.Application): "on_import_yt_list": self.on_import_yt_list, "on_import_m3u": self.on_import_m3u, "on_bouquet_export": self.on_bouquet_export, - "on_export_to_m3u": self.on_export_to_m3u, + "on_bouquet_export_to_m3u": self.on_bouquet_export_to_m3u, + "on_export_iptv_to_m3u": self.on_export_iptv_to_m3u, "on_import_bouquet": self.on_import_bouquet, "on_insert_marker": self.on_insert_marker, "on_insert_space": self.on_insert_space, @@ -384,7 +385,14 @@ class Application(Gtk.Application): self.bind_property("is-enigma", self._tool_elements.get(self._LOCK_HIDE_ELEMENTS[0]), "visible") self.bind_property("is-enigma", self._tool_elements.get(self._LOCK_HIDE_ELEMENTS[1]), "visible", 4) # Sub-bouquets menu item. - self.bind_property("is_enigma", builder.get_object("bouquets_new_sub_popup_item"), "visible") + self.bind_property("is-enigma", builder.get_object("bouquets_new_sub_popup_item"), "visible") + # Export bouquet to m3u menu items. + export_to_m3u_item = builder.get_object("bouquet_export_to_m3u_item") + self.bind_property("is-enigma", export_to_m3u_item, "visible") + self._signal_box.bind_property("visible", export_to_m3u_item, "sensitive") + export_to_m3u_model_button = builder.get_object("export_all_to_m3u_model_button") + self.bind_property("is-enigma", export_to_m3u_model_button, "visible") + self._signal_box.bind_property("visible", export_to_m3u_model_button, "sensitive") # Stack page widgets. self._stack_services_frame = builder.get_object("services_frame") self._stack_satellite_box = builder.get_object("satellite_box") @@ -463,7 +471,7 @@ class Application(Gtk.Application): # IPTV menu. self._iptv_menu_button.set_menu_model(builder.get_object("iptv_menu")) iptv_elem = self._tool_elements.get("fav_iptv_popup_item") - for h in (self.on_iptv, self.on_import_yt_list, self.on_import_m3u, self.on_export_to_m3u, + for h in (self.on_iptv, self.on_import_yt_list, self.on_import_m3u, self.on_export_iptv_to_m3u, self.on_epg_list_configuration, self.on_iptv_list_configuration, self.on_remove_all_unavailable): iptv_elem.bind_property("sensitive", self.set_action(h.__name__, h, False), "enabled") @@ -2780,18 +2788,43 @@ class Application(Gtk.Application): else: show_dialog(DialogType.INFO, self._main_window, "Done!") + def on_bouquet_export_to_m3u(self, item): + """ Exports bouquet services to * .m3u file. + + Since the streaming port can be changed by the user, + we're getting base link to the stream -> http(s)://IP:PORT/ + """ + self._http_api.send(HttpAPI.Request.STREAM, "", lambda d: self.export_bouquet_to_m3u(self.get_url_from_m3u(d))) + @run_idle - def on_export_to_m3u(self, action, value=None): + def export_bouquet_to_m3u(self, url): + if not url: + return + + def get_service(name, s_type, fav_id, num): + if s_type is BqServiceType.DEFAULT: + srv = self._services.get(fav_id, None) + s_data = srv.picon_id.rstrip(".png").replace("_", ":") if srv.picon_id else None + return BouquetService(name, s_type, s_data, num) + return BouquetService(name, s_type, fav_id, num) + + self.save_bouquet_to_m3u((get_service(r[Column.FAV_SERVICE], BqServiceType(r[Column.FAV_TYPE]), + r[Column.FAV_ID], r[Column.FAV_NUM]) for r in self._fav_model), url) + + @run_idle + def on_export_iptv_to_m3u(self, action, value=None): i_types = (BqServiceType.IPTV.value, BqServiceType.MARKER.value) - bq_services = [BouquetService(r[Column.FAV_SERVICE], - BqServiceType(r[Column.FAV_TYPE]), - r[Column.FAV_ID], + bq_services = [BouquetService(r[Column.FAV_SERVICE], BqServiceType(r[Column.FAV_TYPE]), r[Column.FAV_ID], r[Column.FAV_NUM]) for r in self._fav_model if r[Column.FAV_TYPE] in i_types] if not any(s.type is BqServiceType.IPTV for s in bq_services): self.show_error_message("This list does not contains IPTV streams!") return + self.save_bouquet_to_m3u(bq_services) + + def save_bouquet_to_m3u(self, bq_services, url=None): + """ Saves bouquet services to *.m3u file. """ response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings, buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT): @@ -2799,7 +2832,7 @@ class Application(Gtk.Application): try: bq = Bouquet(self._current_bq_name, None, bq_services, None, None) - export_to_m3u(response, bq, self._s_type) + export_to_m3u(response, bq, self._s_type, url) except Exception as e: self.show_error_message(str(e)) else: