diff --git a/app/eparser/__init__.py b/app/eparser/__init__.py
index dad92073..7f9057f1 100644
--- a/app/eparser/__init__.py
+++ b/app/eparser/__init__.py
@@ -1,3 +1,30 @@
+# -*- coding: utf-8 -*-
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2018-2021 Dmitriy Yefremov
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# Author: Dmitriy Yefremov
+#
+
from app.commons import run_task
from app.settings import SettingsType
from .ecommons import Service, Satellite, Transponder, Bouquet, Bouquets, is_transponder_valid
@@ -32,6 +59,15 @@ def get_bouquets(path, s_type):
return get_neutrino_bouquets(path)
+def write_bouquet(path, bq, s_type):
+ if s_type is SettingsType.ENIGMA_2:
+ writer = BouquetsWriter(path, None)
+ writer.write_bouquet(path + "userbouquet.{}.{}".format(bq.name, bq.type), bq.name, bq.services)
+ elif s_type is SettingsType.NEUTRINO_MP:
+ from .neutrino.bouquets import write_bouquet
+ write_bouquet(path, bq)
+
+
@run_task
def write_bouquets(path, bouquets, s_type, force_bq_names=False):
if s_type is SettingsType.ENIGMA_2:
diff --git a/app/ui/main_app_window.py b/app/ui/main_app_window.py
index 6ff8b4f9..3180b72f 100644
--- a/app/ui/main_app_window.py
+++ b/app/ui/main_app_window.py
@@ -39,7 +39,7 @@ from gi.repository import GLib, Gio
from app.commons import run_idle, log, run_task, run_with_delay, init_logger
from app.connections import (HttpAPI, download_data, DownloadType, upload_data, test_http, TestException,
HttpApiException, STC_XML_FILE)
-from app.eparser import get_blacklist, write_blacklist
+from app.eparser import get_blacklist, write_blacklist, write_bouquet
from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service
from app.eparser.ecommons import CAS, Flag, BouquetService
from app.eparser.enigma.bouquets import BqServiceType
@@ -154,6 +154,7 @@ class Application(Gtk.Application):
"on_model_changed": self.on_model_changed,
"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_import_bouquet": self.on_import_bouquet,
"on_import_bouquets": self.on_import_bouquets,
@@ -1710,19 +1711,7 @@ class Application(Gtk.Application):
bqs = []
num_of_children = model.iter_n_children(itr)
for num in range(num_of_children):
- bq_itr = model.iter_nth_child(itr, num)
- bq_name, locked, hidden, bq_type = model.get(bq_itr, Column.BQ_NAME, Column.BQ_LOCKED,
- Column.BQ_HIDDEN, Column.BQ_TYPE)
- bq_id = "{}:{}".format(bq_name, bq_type)
- favs = self._bouquets[bq_id]
- ex_s = self._extra_bouquets.get(bq_id, None)
- bq_s = list(filter(None, [self._services.get(f_id, None) for f_id in favs]))
-
- if profile is SettingsType.ENIGMA_2:
- bq_s = self.get_enigma_bq_services(bq_s, ex_s)
-
- bq = Bouquet(bq_name, bq_type, bq_s, locked, hidden, self._bq_file.get(bq_id, None))
- bqs.append(bq)
+ bqs.append(self.get_bouquet(model.iter_nth_child(itr, num), model))
if len(b_path) == 1:
bouquets.append(Bouquets(*model.get(itr, Column.BQ_NAME, Column.BQ_TYPE), bqs if bqs else []))
@@ -1747,6 +1736,20 @@ class Application(Gtk.Application):
if callback:
callback()
+ def get_bouquet(self, itr, model):
+ """ Constructs and returns Bouquet class instance. """
+ bq_name, locked, hidden, bq_type = model.get(itr, Column.BQ_NAME, Column.BQ_LOCKED, Column.BQ_HIDDEN,
+ Column.BQ_TYPE)
+ bq_id = "{}:{}".format(bq_name, bq_type)
+ favs = self._bouquets[bq_id]
+ ex_s = self._extra_bouquets.get(bq_id, None)
+ bq_s = list(filter(None, [self._services.get(f_id, None) for f_id in favs]))
+
+ if self._s_type is SettingsType.ENIGMA_2:
+ bq_s = self.get_enigma_bq_services(bq_s, ex_s)
+
+ return Bouquet(bq_name, bq_type, bq_s, locked, hidden, self._bq_file.get(bq_id, None))
+
def get_enigma_bq_services(self, services, ext_services):
""" Preparing a list of services for the Enigma2 bouquet. """
s_list = []
@@ -2224,7 +2227,7 @@ class Application(Gtk.Application):
bq = self._bouquets.get(self._bq_selected)
EpgDialog(self._main_window, self._settings, self._services, bq, self._fav_model, self._current_bq_name).show()
- # ***************** Import ********************#
+ # ***************** Import ******************** #
def on_import_yt_list(self, action, value=None):
""" Import playlist from YouTube """
@@ -2256,30 +2259,6 @@ class Application(Gtk.Application):
gen = self.update_bouquet_services(self._fav_model, None, self._bq_selected)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
- @run_idle
- def on_export_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],
- 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_dialog("This list does not contains IPTV streams!")
- return
-
- response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings)
- if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
- return
-
- try:
- bq = Bouquet(self._current_bq_name, None, bq_services, None, None)
- export_to_m3u(response, bq, self._s_type)
- except Exception as e:
- self.show_error_dialog(str(e))
- else:
- show_dialog(DialogType.INFO, self._main_window, "Done!")
-
def on_import_data(self, path):
msg = "Combine with the current data?"
if len(self._services_model) > 0 and show_dialog(DialogType.QUESTION, self._main_window,
@@ -2361,6 +2340,61 @@ class Application(Gtk.Application):
yield True
self._wait_dialog.hide()
+ # ***************** Export ******************** #
+
+ def on_bouquet_export(self, item=None):
+ """ Exports single bouquet to file. """
+ bq_selected = self.check_bouquet_selection()
+ if not bq_selected:
+ return
+
+ model, paths = self._bouquets_view.get_selection().get_selected_rows()
+ if len(paths) > 1:
+ self.show_error_dialog("Please, select only one bouquet!")
+ return
+
+ 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):
+ return
+
+ try:
+ itr = model.get_iter(paths)
+ bq = self.get_bouquet(itr, model)
+ if self._s_type is SettingsType.NEUTRINO_MP:
+ bq = Bouquets(*model.get(itr, Column.BQ_NAME, Column.BQ_TYPE), [bq])
+ response += bq.name
+ write_bouquet(response, bq, self._s_type)
+ except OSError as e:
+ self.show_error_dialog(str(e))
+ else:
+ show_dialog(DialogType.INFO, self._main_window, "Done!")
+
+ @run_idle
+ def on_export_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],
+ 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_dialog("This list does not contains IPTV streams!")
+ return
+
+ 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):
+ return
+
+ try:
+ bq = Bouquet(self._current_bq_name, None, bq_services, None, None)
+ export_to_m3u(response, bq, self._s_type)
+ except Exception as e:
+ self.show_error_dialog(str(e))
+ else:
+ show_dialog(DialogType.INFO, self._main_window, "Done!")
+
# ***************** Backup ******************** #
def on_backup_tool_show(self, action, value=None):
diff --git a/app/ui/main_window.glade b/app/ui/main_window.glade
index 8484e6ed..73fc031d 100644
--- a/app/ui/main_window.glade
+++ b/app/ui/main_window.glade
@@ -358,6 +358,16 @@ Author: Dmitriy Yefremov
+
+
+