mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2025-12-21 07:59:40 +01:00
improved bouquet export to *.m3u (#56)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -380,7 +380,7 @@
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Export to m3u</attribute>
|
||||
<attribute name="action">app.on_export_to_m3u</attribute>
|
||||
<attribute name="action">app.on_export_iptv_to_m3u</attribute>
|
||||
</item>
|
||||
<section>
|
||||
<item>
|
||||
|
||||
@@ -138,11 +138,63 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-index</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="export_bouquet_to_m3u_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-save-as</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="export_to_m3u_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-save-as</property>
|
||||
</object>
|
||||
<object class="GtkPopoverMenu" id="export_to_m3u_menu">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="export_to_m3u_menu_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="export_all_to_m3u_model_button">
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes">All</property>
|
||||
<signal name="clicked" handler="on_bouquet_export_to_m3u" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="export_to_m3u_model_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">app.on_export_iptv_to_m3u</property>
|
||||
<property name="text" translatable="yes">IPTV services only</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="submenu">main</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="extra_edit_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
@@ -765,6 +817,16 @@ Author: Dmitriy Yefremov
|
||||
<signal name="activate" handler="on_new_sub_bouquet" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="bouquet_export_popup_item">
|
||||
<property name="label">gtk-save-as</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_bouquet_export" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="bouquet_import_popup_item">
|
||||
<property name="label" translatable="yes">Import</property>
|
||||
@@ -778,13 +840,13 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="bouquet_export_popup_item">
|
||||
<property name="label">gtk-save-as</property>
|
||||
<property name="visible">True</property>
|
||||
<object class="GtkImageMenuItem" id="bouquet_export_to_m3u_item">
|
||||
<property name="label" translatable="yes">Export to m3u</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_bouquet_export" swapped="no"/>
|
||||
<property name="image">export_bouquet_to_m3u_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_bouquet_export_to_m3u" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
@@ -2455,16 +2517,16 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="export_to_m3u_header_button">
|
||||
<object class="GtkMenuButton" id="export_to_m3u_menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Export to m3u</property>
|
||||
<signal name="clicked" handler="on_export_to_m3u" swapped="no"/>
|
||||
<property name="popover">export_to_m3u_menu</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="export_to_m3u_header_button_image">
|
||||
<object class="GtkImage" id="export_to_m3u_menu_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-save-as-symbolic</property>
|
||||
@@ -3995,7 +4057,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">export_to_m3u_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_export_to_m3u" swapped="no"/>
|
||||
<signal name="activate" handler="on_export_iptv_to_m3u" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user