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
|
ALT = "ALT" # Service with alternatives
|
||||||
BOUQUET = "BOUQUET" # Sub bouquet.
|
BOUQUET = "BOUQUET" # Sub bouquet.
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _missing_(cls, value):
|
||||||
|
return cls.DEFAULT
|
||||||
|
|
||||||
|
|
||||||
Bouquet = namedtuple("Bouquet", ["name", "type", "services", "locked", "hidden", "file"])
|
Bouquet = namedtuple("Bouquet", ["name", "type", "services", "locked", "hidden", "file"])
|
||||||
Bouquet.__new__.__defaults__ = (None, BqServiceType.DEFAULT, [], None, None, None) # For Python3 < 3.7
|
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)
|
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], st, picon, p_id, *s_aggr, url, fav_id, None)
|
||||||
services.append(srv)
|
services.append(srv)
|
||||||
else:
|
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
|
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.*?)::::.*")
|
pattern = re.compile(".*:(http.*):.*") if s_type is SettingsType.ENIGMA_2 else re.compile("(http.*?)::::.*")
|
||||||
lines = ["#EXTM3U\n"]
|
lines = ["#EXTM3U\n"]
|
||||||
current_grp = None
|
current_grp = None
|
||||||
@@ -128,15 +128,17 @@ def export_to_m3u(path, bouquet, s_type):
|
|||||||
res = re.match(pattern, s.data)
|
res = re.match(pattern, s.data)
|
||||||
if not res:
|
if not res:
|
||||||
continue
|
continue
|
||||||
data = res.group(1)
|
lines.append(f"#EXTINF:-1,{s.name}\n")
|
||||||
lines.append("#EXTINF:-1,{}\n".format(s.name))
|
lines.append(current_grp) if current_grp else None
|
||||||
if current_grp:
|
lines.append(f"{unquote(res.group(1).strip())}\n")
|
||||||
lines.append(current_grp)
|
|
||||||
lines.append("{}\n".format(unquote(data.strip())))
|
|
||||||
elif s_type is BqServiceType.MARKER:
|
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)
|
file.writelines(lines)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -380,7 +380,7 @@
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes">Export to m3u</attribute>
|
<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>
|
</item>
|
||||||
<section>
|
<section>
|
||||||
<item>
|
<item>
|
||||||
|
|||||||
@@ -138,11 +138,63 @@ Author: Dmitriy Yefremov
|
|||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="stock">gtk-index</property>
|
<property name="stock">gtk-index</property>
|
||||||
</object>
|
</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">
|
<object class="GtkImage" id="export_to_m3u_image">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="stock">gtk-save-as</property>
|
<property name="stock">gtk-save-as</property>
|
||||||
</object>
|
</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">
|
<object class="GtkImage" id="extra_edit_image">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
@@ -765,6 +817,16 @@ Author: Dmitriy Yefremov
|
|||||||
<signal name="activate" handler="on_new_sub_bouquet" swapped="no"/>
|
<signal name="activate" handler="on_new_sub_bouquet" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</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>
|
<child>
|
||||||
<object class="GtkImageMenuItem" id="bouquet_import_popup_item">
|
<object class="GtkImageMenuItem" id="bouquet_import_popup_item">
|
||||||
<property name="label" translatable="yes">Import</property>
|
<property name="label" translatable="yes">Import</property>
|
||||||
@@ -778,13 +840,13 @@ Author: Dmitriy Yefremov
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImageMenuItem" id="bouquet_export_popup_item">
|
<object class="GtkImageMenuItem" id="bouquet_export_to_m3u_item">
|
||||||
<property name="label">gtk-save-as</property>
|
<property name="label" translatable="yes">Export to m3u</property>
|
||||||
<property name="visible">True</property>
|
<property name="sensitive">False</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="use_underline">True</property>
|
<property name="image">export_bouquet_to_m3u_image</property>
|
||||||
<property name="use_stock">True</property>
|
<property name="use_stock">False</property>
|
||||||
<signal name="activate" handler="on_bouquet_export" swapped="no"/>
|
<signal name="activate" handler="on_bouquet_export_to_m3u" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
@@ -2455,16 +2517,16 @@ Author: Dmitriy Yefremov
|
|||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<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="visible">True</property>
|
||||||
<property name="sensitive">False</property>
|
<property name="sensitive">False</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="focus_on_click">False</property>
|
<property name="focus_on_click">False</property>
|
||||||
<property name="receives_default">True</property>
|
<property name="receives_default">True</property>
|
||||||
<property name="tooltip_text" translatable="yes">Export to m3u</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>
|
<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="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="icon_name">document-save-as-symbolic</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="can_focus">False</property>
|
||||||
<property name="image">export_to_m3u_image</property>
|
<property name="image">export_to_m3u_image</property>
|
||||||
<property name="use_stock">False</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>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class Application(Gtk.Application):
|
|||||||
|
|
||||||
_FAV_ENIGMA_ELEMENTS = ("fav_insert_marker_popup_item", "fav_epg_configuration_popup_item")
|
_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")
|
"iptv_menu_button")
|
||||||
|
|
||||||
_LOCK_HIDE_ELEMENTS = ("enigma_lock_hide_box", "bouquet_lock_hide_box")
|
_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_yt_list": self.on_import_yt_list,
|
||||||
"on_import_m3u": self.on_import_m3u,
|
"on_import_m3u": self.on_import_m3u,
|
||||||
"on_bouquet_export": self.on_bouquet_export,
|
"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_import_bouquet": self.on_import_bouquet,
|
||||||
"on_insert_marker": self.on_insert_marker,
|
"on_insert_marker": self.on_insert_marker,
|
||||||
"on_insert_space": self.on_insert_space,
|
"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[0]), "visible")
|
||||||
self.bind_property("is-enigma", self._tool_elements.get(self._LOCK_HIDE_ELEMENTS[1]), "visible", 4)
|
self.bind_property("is-enigma", self._tool_elements.get(self._LOCK_HIDE_ELEMENTS[1]), "visible", 4)
|
||||||
# Sub-bouquets menu item.
|
# 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.
|
# Stack page widgets.
|
||||||
self._stack_services_frame = builder.get_object("services_frame")
|
self._stack_services_frame = builder.get_object("services_frame")
|
||||||
self._stack_satellite_box = builder.get_object("satellite_box")
|
self._stack_satellite_box = builder.get_object("satellite_box")
|
||||||
@@ -463,7 +471,7 @@ class Application(Gtk.Application):
|
|||||||
# IPTV menu.
|
# IPTV menu.
|
||||||
self._iptv_menu_button.set_menu_model(builder.get_object("iptv_menu"))
|
self._iptv_menu_button.set_menu_model(builder.get_object("iptv_menu"))
|
||||||
iptv_elem = self._tool_elements.get("fav_iptv_popup_item")
|
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):
|
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")
|
iptv_elem.bind_property("sensitive", self.set_action(h.__name__, h, False), "enabled")
|
||||||
|
|
||||||
@@ -2780,18 +2788,43 @@ class Application(Gtk.Application):
|
|||||||
else:
|
else:
|
||||||
show_dialog(DialogType.INFO, self._main_window, "Done!")
|
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
|
@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)
|
i_types = (BqServiceType.IPTV.value, BqServiceType.MARKER.value)
|
||||||
bq_services = [BouquetService(r[Column.FAV_SERVICE],
|
bq_services = [BouquetService(r[Column.FAV_SERVICE], BqServiceType(r[Column.FAV_TYPE]), r[Column.FAV_ID],
|
||||||
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]
|
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):
|
if not any(s.type is BqServiceType.IPTV for s in bq_services):
|
||||||
self.show_error_message("This list does not contains IPTV streams!")
|
self.show_error_message("This list does not contains IPTV streams!")
|
||||||
return
|
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,
|
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings,
|
||||||
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
|
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
|
||||||
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||||
@@ -2799,7 +2832,7 @@ class Application(Gtk.Application):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
bq = Bouquet(self._current_bq_name, None, bq_services, None, None)
|
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:
|
except Exception as e:
|
||||||
self.show_error_message(str(e))
|
self.show_error_message(str(e))
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user