Files
DemonEditor/app/ui/iptv.py

985 lines
42 KiB
Python
Raw Normal View History

2019-06-24 00:36:54 +03:00
import concurrent.futures
import re
2019-01-27 23:20:07 +03:00
import urllib
from urllib.error import HTTPError
2020-06-10 11:10:41 +03:00
from urllib.parse import urlparse, unquote, quote
from urllib.request import Request, urlopen
2021-01-31 16:27:35 +03:00
from gi.repository import GLib, Gio, GdkPixbuf
2021-01-31 16:27:35 +03:00
from app.commons import run_idle, run_task, log
2018-03-16 00:10:33 +03:00
from app.eparser.ecommons import BqServiceType, Service
2021-01-31 16:27:35 +03:00
from app.eparser.iptv import (NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT, get_fav_id, MARKER_FORMAT,
parse_m3u)
2019-12-22 20:42:29 +03:00
from app.settings import SettingsType
from app.tools.yt import YouTubeException, YouTube
2021-01-31 16:27:35 +03:00
from app.ui.dialogs import Action, show_dialog, DialogType, get_dialogs_string, get_message
from app.ui.main_helper import get_base_model, get_iptv_url, on_popup_menu
from app.ui.uicommons import (Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON, Column, IS_GNOME_SESSION,
KeyboardKey, get_yt_icon)
2018-03-11 21:52:10 +03:00
_DIGIT_ENTRY_NAME = "digit-entry"
_ENIGMA2_REFERENCE = "{}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
2019-05-09 14:48:29 +03:00
_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
_UI_PATH = UI_RESOURCES_PATH + "iptv.glade"
def is_data_correct(elems):
for elem in elems:
if elem.get_name() == _DIGIT_ENTRY_NAME:
return False
return True
2018-03-11 21:52:10 +03:00
2019-04-14 00:03:52 +03:00
def get_stream_type(box):
active = box.get_active()
if active == 0:
return StreamType.DVB_TS.value
elif active == 1:
return StreamType.NONE_TS.value
elif active == 2:
return StreamType.NONE_REC_1.value
2020-06-10 11:10:41 +03:00
elif active == 3:
return StreamType.NONE_REC_2.value
2021-01-11 12:30:44 +03:00
elif active == 4:
return StreamType.E_SERVICE_URI.value
return StreamType.E_SERVICE_HLS.value
2019-04-14 00:03:52 +03:00
2018-03-11 21:52:10 +03:00
class IptvDialog:
2018-04-01 09:39:14 +03:00
2020-06-10 11:10:41 +03:00
def __init__(self, transient, view, services, bouquet, settings, action=Action.ADD):
handlers = {"on_response": self.on_response,
"on_entry_changed": self.on_entry_changed,
2018-04-01 09:39:14 +03:00
"on_url_changed": self.on_url_changed,
2018-03-15 23:10:22 +03:00
"on_save": self.on_save,
"on_stream_type_changed": self.on_stream_type_changed,
2019-08-18 00:06:44 +03:00
"on_yt_quality_changed": self.on_yt_quality_changed,
"on_info_bar_close": self.on_info_bar_close}
2018-03-15 23:10:22 +03:00
2020-06-10 11:10:41 +03:00
self._action = action
self._s_type = settings.setting_type
self._settings = settings
self._bouquet = bouquet
self._services = services
self._yt_links = None
self._yt_dl = None
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
2019-05-09 14:48:29 +03:00
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
2019-08-18 00:06:44 +03:00
("iptv_dialog", "stream_type_liststore", "yt_quality_liststore"))
builder.connect_signals(handlers)
self._dialog = builder.get_object("iptv_dialog")
2018-03-11 21:52:10 +03:00
self._dialog.set_transient_for(transient)
self._name_entry = builder.get_object("name_entry")
2018-03-12 22:47:43 +03:00
self._description_entry = builder.get_object("description_entry")
2018-03-11 21:52:10 +03:00
self._url_entry = builder.get_object("url_entry")
self._reference_entry = builder.get_object("reference_entry")
self._srv_type_entry = builder.get_object("srv_type_entry")
self._sid_entry = builder.get_object("sid_entry")
self._tr_id_entry = builder.get_object("tr_id_entry")
self._net_id_entry = builder.get_object("net_id_entry")
self._namespace_entry = builder.get_object("namespace_entry")
self._stream_type_combobox = builder.get_object("stream_type_combobox")
self._add_button = builder.get_object("iptv_dialog_add_button")
self._save_button = builder.get_object("iptv_dialog_save_button")
2018-03-12 22:47:43 +03:00
self._stream_type_combobox = builder.get_object("stream_type_combobox")
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
2019-08-18 00:06:44 +03:00
self._yt_quality_box = builder.get_object("yt_iptv_quality_combobox")
self._model, self._paths = view.get_selection().get_selected_rows()
# style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._digit_elems = (self._srv_type_entry, self._sid_entry, self._tr_id_entry, self._net_id_entry,
self._namespace_entry)
for el in self._digit_elems:
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
2020-06-10 11:10:41 +03:00
if self._s_type is SettingsType.NEUTRINO_MP:
2018-10-15 12:36:03 +03:00
builder.get_object("iptv_dialog_ts_data_frame").set_visible(False)
builder.get_object("iptv_type_label").set_visible(False)
2018-03-15 23:10:22 +03:00
builder.get_object("reference_entry").set_visible(False)
builder.get_object("iptv_reference_label").set_visible(False)
self._stream_type_combobox.set_visible(False)
2018-03-15 23:10:22 +03:00
else:
self._description_entry.set_visible(False)
builder.get_object("iptv_description_label").set_visible(False)
2018-03-11 21:52:10 +03:00
if self._action is Action.ADD:
self._save_button.set_visible(False)
self._add_button.set_visible(True)
2020-06-10 11:10:41 +03:00
if self._s_type is SettingsType.ENIGMA_2:
self.update_reference_entry()
2019-06-15 23:50:42 +03:00
self._stream_type_combobox.set_active(1)
2018-03-11 21:52:10 +03:00
elif self._action is Action.EDIT:
2018-03-16 00:10:33 +03:00
self._current_srv = get_base_model(self._model)[self._paths][:]
self.init_data(self._current_srv)
2018-03-11 21:52:10 +03:00
def show(self):
self._dialog.run()
2018-09-07 23:42:59 +03:00
def on_response(self, dialog, response):
2020-07-24 11:37:27 +03:00
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
self._dialog.destroy()
2018-03-11 21:52:10 +03:00
def on_save(self, item):
2019-06-15 23:50:42 +03:00
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)
2018-04-01 09:39:14 +03:00
return
2020-07-24 11:37:27 +03:00
if show_dialog(DialogType.QUESTION, self._dialog) in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
2020-06-10 11:10:41 +03:00
self.save_enigma2_data() if self._s_type is SettingsType.ENIGMA_2 else self.save_neutrino_data()
self._dialog.destroy()
2018-03-11 21:52:10 +03:00
2018-03-12 22:47:43 +03:00
def init_data(self, srv):
name, fav_id = srv[2], srv[7]
self._name_entry.set_text(name)
2020-06-10 11:10:41 +03:00
self.init_enigma2_data(fav_id) if self._s_type is SettingsType.ENIGMA_2 else self.init_neutrino_data(fav_id)
def init_enigma2_data(self, fav_id):
2019-01-27 23:20:07 +03:00
data, sep, desc = fav_id.partition("#DESCRIPTION")
self._description_entry.set_text(desc.strip())
data = data.split(":")
2019-01-27 23:20:07 +03:00
if len(data) < 11:
return
2019-04-14 00:03:52 +03:00
s_type = data[0].strip()
try:
stream_type = StreamType(s_type)
if stream_type is StreamType.DVB_TS:
self._stream_type_combobox.set_active(0)
elif stream_type is StreamType.NONE_TS:
self._stream_type_combobox.set_active(1)
elif stream_type is StreamType.NONE_REC_1:
self._stream_type_combobox.set_active(2)
elif stream_type is StreamType.NONE_REC_2:
self._stream_type_combobox.set_active(3)
2020-06-10 11:10:41 +03:00
elif stream_type is StreamType.E_SERVICE_URI:
self._stream_type_combobox.set_active(4)
2021-01-11 12:30:44 +03:00
elif stream_type is StreamType.E_SERVICE_HLS:
self._stream_type_combobox.set_active(5)
2019-04-14 00:03:52 +03:00
except ValueError:
self.show_info_message("Unknown stream type {}".format(s_type), Gtk.MessageType.ERROR)
2019-04-14 00:03:52 +03:00
self._srv_type_entry.set_text(data[2])
2018-04-10 16:03:36 +03:00
self._sid_entry.set_text(str(int(data[3], 16)))
self._tr_id_entry.set_text(str(int(data[4], 16)))
self._net_id_entry.set_text(str(int(data[5], 16)))
self._namespace_entry.set_text(str(int(data[6], 16)))
2020-06-10 11:10:41 +03:00
self._url_entry.set_text(unquote(data[10].strip()))
self.update_reference_entry()
def init_neutrino_data(self, fav_id):
data = fav_id.split("::")
self._url_entry.set_text(data[0])
self._description_entry.set_text(data[1])
2018-03-12 22:47:43 +03:00
2020-06-10 11:10:41 +03:00
def update_reference_entry(self):
2020-07-24 11:37:27 +03:00
if self._s_type is SettingsType.ENIGMA_2 and is_data_correct(self._digit_elems):
2020-06-10 11:10:41 +03:00
self.on_url_changed(self._url_entry)
self._reference_entry.set_text(_ENIGMA2_REFERENCE.format(self.get_type(),
self._srv_type_entry.get_text(),
int(self._sid_entry.get_text()),
int(self._tr_id_entry.get_text()),
int(self._net_id_entry.get_text()),
int(self._namespace_entry.get_text())))
2018-03-12 22:47:43 +03:00
def get_type(self):
2019-04-14 00:03:52 +03:00
return get_stream_type(self._stream_type_combobox)
2018-03-12 22:47:43 +03:00
def on_entry_changed(self, entry):
if _PATTERN.search(entry.get_text()):
entry.set_name(_DIGIT_ENTRY_NAME)
else:
entry.set_name("GtkEntry")
2020-06-10 11:10:41 +03:00
self.update_reference_entry()
2018-04-01 09:39:14 +03:00
def on_url_changed(self, entry):
2019-06-15 23:50:42 +03:00
url_str = entry.get_text()
url = urlparse(url_str)
2021-01-11 12:30:44 +03:00
e_types = (StreamType.E_SERVICE_URI.value, StreamType.E_SERVICE_HLS.value)
cond = all([url.scheme, url.netloc, url.path]) or self.get_type() in e_types
2020-06-10 11:10:41 +03:00
entry.set_name("GtkEntry" if cond else _DIGIT_ENTRY_NAME)
yt_id = YouTube.get_yt_id(url_str)
if yt_id:
2019-06-15 23:50:42 +03:00
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
text = "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
if show_dialog(DialogType.QUESTION, self._dialog, text=text) == Gtk.ResponseType.OK:
entry.set_sensitive(False)
gen = self.set_yt_url(entry, yt_id)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
elif YouTube.is_yt_video_link(url_str):
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
2019-06-15 23:50:42 +03:00
else:
entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
2019-08-18 00:06:44 +03:00
self._yt_quality_box.set_visible(False)
2018-04-01 09:39:14 +03:00
def set_yt_url(self, entry, video_id):
try:
2020-06-13 20:57:37 +03:00
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:
2019-10-06 09:50:16 +03:00
self.show_info_message(get_message("Getting link error:") + (str(e)), Gtk.MessageType.ERROR)
return
2020-06-13 20:57:37 +03:00
except YouTubeException as e:
2020-06-10 11:10:41 +03:00
self.show_info_message((str(e)), Gtk.MessageType.ERROR)
return
else:
if self._action is Action.ADD:
self._name_entry.set_text(title)
2019-08-18 00:06:44 +03:00
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:
2019-10-06 09:50:16 +03:00
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
2018-03-15 23:10:22 +03:00
def on_stream_type_changed(self, item):
2021-01-11 12:30:44 +03:00
if self.get_type() in (StreamType.E_SERVICE_URI.value, StreamType.E_SERVICE_HLS.value):
2020-06-10 11:10:41 +03:00
self.show_info_message("DreamOS only!", Gtk.MessageType.WARNING)
self.update_reference_entry()
2018-03-15 23:10:22 +03:00
2019-08-18 00:06:44 +03:00
def on_yt_quality_changed(self, box):
model = box.get_model()
active = model.get_value(box.get_active_iter(), 0)
if self._yt_links and active in self._yt_links:
self._url_entry.set_text(self._yt_links[active])
def save_enigma2_data(self):
2018-03-15 23:10:22 +03:00
name = self._name_entry.get_text().strip()
fav_id = ENIGMA2_FAV_ID_FORMAT.format(self.get_type(),
self._srv_type_entry.get_text(),
2018-04-10 16:03:36 +03:00
int(self._sid_entry.get_text()),
int(self._tr_id_entry.get_text()),
int(self._net_id_entry.get_text()),
int(self._namespace_entry.get_text()),
2020-06-10 11:10:41 +03:00
quote(self._url_entry.get_text()),
2018-03-15 23:10:22 +03:00
name, name)
self.update_bouquet_data(name, fav_id)
def save_neutrino_data(self):
2018-03-16 00:10:33 +03:00
if self._action is Action.EDIT:
id_data = self._current_srv[7].split("::")
else:
id_data = ["", "", "0", None, None, None, None, "", "", "1"]
2018-03-15 23:10:22 +03:00
id_data[0] = self._url_entry.get_text()
id_data[1] = self._description_entry.get_text()
self.update_bouquet_data(self._name_entry.get_text(), NEUTRINO_FAV_ID_FORMAT.format(*id_data))
self._dialog.destroy()
2018-03-15 23:10:22 +03:00
def update_bouquet_data(self, name, fav_id):
if self._action is Action.EDIT:
2018-03-15 23:10:22 +03:00
old_srv = self._services.pop(self._current_srv[7])
self._services[fav_id] = old_srv._replace(service=name, fav_id=fav_id)
self._bouquet[self._paths[0][0]] = fav_id
2019-03-14 13:43:13 +03:00
self._model.set(self._model.get_iter(self._paths), {Column.FAV_SERVICE: name, Column.FAV_ID: fav_id})
2018-03-16 00:10:33 +03:00
else:
aggr = [None] * 10
s_type = BqServiceType.IPTV.name
2019-03-14 13:43:13 +03:00
srv = (None, None, name, None, None, s_type, None, fav_id, *aggr[0:3])
2018-03-16 00:10:33 +03:00
itr = self._model.insert_after(self._model.get_iter(self._paths[0]),
srv) if self._paths else self._model.insert(0, srv)
self._model.set_value(itr, 1, IPTV_ICON)
self._bouquet.insert(self._model.get_path(itr)[0], fav_id)
self._services[fav_id] = Service(None, None, IPTV_ICON, name, *aggr[0:3], s_type, *aggr, fav_id, None)
@run_idle
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@run_idle
def show_info_message(self, text, message_type):
self._info_bar.set_visible(True)
self._info_bar.set_message_type(message_type)
self._message_label.set_text(text)
2018-03-11 21:52:10 +03:00
class SearchUnavailableDialog:
2020-06-10 11:10:41 +03:00
def __init__(self, transient, model, fav_bouquet, iptv_rows, s_type):
2018-11-12 13:43:05 +03:00
handlers = {"on_response": self.on_response}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "iptv.glade", ("search_unavailable_streams_dialog",))
builder.connect_signals(handlers)
self._dialog = builder.get_object("search_unavailable_streams_dialog")
self._dialog.set_transient_for(transient)
self._model = model
self._counter_label = builder.get_object("streams_rows_counter_label")
self._level_bar = builder.get_object("unavailable_streams_level_bar")
self._bouquet = fav_bouquet
2020-06-10 11:10:41 +03:00
self._s_type = s_type
self._iptv_rows = iptv_rows
self._counter = -1
self._max_rows = len(self._iptv_rows)
self._level_bar.set_max_value(self._max_rows)
self._download_task = True
self._to_delete = []
self.update_counter()
self.do_search()
@run_task
def do_search(self):
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
futures = {executor.submit(self.get_unavailable, row): row for row in self._iptv_rows}
for future in concurrent.futures.as_completed(futures):
if not self._download_task:
executor.shutdown()
return
future.result()
self._download_task = False
self.on_close()
def get_unavailable(self, row):
if not self._download_task:
return
try:
2020-06-10 11:10:41 +03:00
req = Request(get_iptv_url(row, self._s_type))
self.update_bar()
urlopen(req, timeout=2)
except HTTPError as e:
if e.code != 403:
self.append_data(row)
except Exception:
self.append_data(row)
def append_data(self, row):
self._to_delete.append(self._model.get_iter(row.path))
self.update_counter()
@run_idle
def update_bar(self):
self._max_rows -= 1
self._level_bar.set_value(self._max_rows)
@run_idle
def update_counter(self):
self._counter += 1
self._counter_label.set_text(str(self._counter))
def show(self):
response = self._dialog.run()
return self._to_delete if response not in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT) else False
2018-11-12 13:43:05 +03:00
def on_response(self, dialog, response):
if response == Gtk.ResponseType.CANCEL:
self.on_close()
@run_idle
2018-11-12 13:43:05 +03:00
def on_close(self):
if self._download_task and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
self._download_task = False
self._dialog.destroy()
2021-01-31 16:27:35 +03:00
class IptvListDialog:
""" Base class for working with iptv lists. """
2021-01-31 16:27:35 +03:00
def __init__(self, transient, s_type):
handlers = {"on_apply": self.on_apply,
"on_response": self.on_response,
"on_stream_type_default_togged": self.on_stream_type_default_togged,
"on_stream_type_changed": self.on_stream_type_changed,
"on_default_type_toggled": self.on_default_type_toggled,
"on_auto_sid_toggled": self.on_auto_sid_toggled,
2018-08-22 00:09:52 +03:00
"on_default_tid_toggled": self.on_default_tid_toggled,
"on_default_nid_toggled": self.on_default_nid_toggled,
"on_default_namespace_toggled": self.on_default_namespace_toggled,
"on_reset_to_default": self.on_reset_to_default,
"on_entry_changed": self.on_entry_changed,
"on_info_bar_close": self.on_info_bar_close}
2020-06-10 11:10:41 +03:00
self._s_type = s_type
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
2019-05-09 14:48:29 +03:00
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("iptv_list_configuration_dialog", "stream_type_liststore"))
builder.connect_signals(handlers)
self._dialog = builder.get_object("iptv_list_configuration_dialog")
self._dialog.set_transient_for(transient)
2021-01-31 16:27:35 +03:00
self._data_box = builder.get_object("iptv_list_data_box")
self._start_values_grid = builder.get_object("start_values_grid")
self._info_bar = builder.get_object("list_configuration_info_bar")
self._reference_label = builder.get_object("reference_label")
self._stream_type_check_button = builder.get_object("stream_type_default_check_button")
self._type_check_button = builder.get_object("type_default_check_button")
self._sid_auto_check_button = builder.get_object("sid_auto_check_button")
self._tid_check_button = builder.get_object("tid_default_check_button")
self._nid_check_button = builder.get_object("nid_default_check_button")
self._namespace_check_button = builder.get_object("namespace_default_check_button")
self._stream_type_combobox = builder.get_object("stream_type_list_combobox")
self._list_srv_type_entry = builder.get_object("list_srv_type_entry")
self._list_sid_entry = builder.get_object("list_sid_entry")
2018-08-22 00:09:52 +03:00
self._list_tid_entry = builder.get_object("list_tid_entry")
self._list_nid_entry = builder.get_object("list_nid_entry")
self._list_namespace_entry = builder.get_object("list_namespace_entry")
self._reset_to_default_switch = builder.get_object("reset_to_default_lists_switch")
2021-01-31 16:27:35 +03:00
# Style
style_provider = Gtk.CssProvider()
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._default_elems = (self._stream_type_check_button, self._type_check_button, self._sid_auto_check_button,
self._tid_check_button, self._nid_check_button, self._namespace_check_button)
self._digit_elems = (self._list_srv_type_entry, self._list_sid_entry, self._list_tid_entry,
self._list_nid_entry, self._list_namespace_entry)
for el in self._digit_elems:
2021-01-31 16:27:35 +03:00
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
def show(self):
2018-08-22 00:09:52 +03:00
self._dialog.run()
def on_response(self, dialog, response):
if response == Gtk.ResponseType.CANCEL:
self._dialog.destroy()
def on_stream_type_changed(self, box):
self.update_reference()
def on_stream_type_default_togged(self, button):
if button.get_active():
self._stream_type_combobox.set_active(1)
self._stream_type_combobox.set_sensitive(not button.get_active())
def on_default_type_toggled(self, button):
if button.get_active():
self._list_srv_type_entry.set_text("1")
self._list_srv_type_entry.set_sensitive(not button.get_active())
def on_auto_sid_toggled(self, button):
if button.get_active():
self._list_sid_entry.set_text("0")
self._list_sid_entry.set_sensitive(not button.get_active())
2018-08-22 00:09:52 +03:00
def on_default_tid_toggled(self, button):
if button.get_active():
self._list_tid_entry.set_text("0")
2018-08-22 00:09:52 +03:00
self._list_tid_entry.set_sensitive(not button.get_active())
def on_default_nid_toggled(self, button):
if button.get_active():
self._list_nid_entry.set_text("0")
2018-08-22 00:09:52 +03:00
self._list_nid_entry.set_sensitive(not button.get_active())
def on_default_namespace_toggled(self, button):
if button.get_active():
self._list_namespace_entry.set_text("0")
self._list_namespace_entry.set_sensitive(not button.get_active())
@run_idle
2021-01-31 16:27:35 +03:00
def on_reset_to_default(self, item):
self._stream_type_combobox.set_active(1)
self._list_srv_type_entry.set_text("1")
2021-01-31 16:27:35 +03:00
for el in self._digit_elems[1:]:
el.set_text("0")
2021-01-31 16:27:35 +03:00
for el in self._default_elems:
el.set_active(True)
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
2021-01-31 16:27:35 +03:00
def on_apply(self, item):
pass
@run_idle
def update_reference(self):
if is_data_correct(self._digit_elems):
stream_type = get_stream_type(self._stream_type_combobox)
self._reference_label.set_text(
_ENIGMA2_REFERENCE.format(stream_type, *[int(elem.get_text()) for elem in self._digit_elems]))
def on_entry_changed(self, entry):
if _PATTERN.search(entry.get_text()):
entry.set_name(_DIGIT_ENTRY_NAME)
else:
entry.set_name("GtkEntry")
self.update_reference()
def is_default_values(self):
return any(el.get_text() == "0" for el in self._digit_elems[2:])
def is_all_data_default(self):
return all(el.get_active() for el in self._default_elems)
class IptvListConfigurationDialog(IptvListDialog):
def __init__(self, transient, services, iptv_rows, bouquet, fav_model, s_type):
super().__init__(transient, s_type)
self._rows = iptv_rows
self._bouquet = bouquet
self._fav_model = fav_model
self._services = services
apply_button = Gtk.Button(visible=True,
image=Gtk.Image(icon_name="gtk-apply"),
always_show_image=True,
label=get_message("Apply"))
apply_button.connect("clicked", self.on_apply)
self._dialog.add_action_widget(apply_button, Gtk.ResponseType.APPLY)
@run_idle
def on_apply(self, item):
if not is_data_correct(self._digit_elems):
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return
2020-06-10 11:10:41 +03:00
if self._s_type is SettingsType.ENIGMA_2:
type_default = self._type_check_button.get_active()
tid_default = self._tid_check_button.get_active()
sid_auto = self._sid_auto_check_button.get_active()
nid_default = self._nid_check_button.get_active()
namespace_default = self._namespace_check_button.get_active()
2021-01-31 16:27:35 +03:00
stream_type = get_stream_type(self._stream_type_combobox)
srv_type = "1" if type_default else self._list_srv_type_entry.get_text()
tid = "0" if tid_default else "{:X}".format(int(self._list_tid_entry.get_text()))
nid = "0" if nid_default else "{:X}".format(int(self._list_nid_entry.get_text()))
namespace = "0" if namespace_default else "{:X}".format(int(self._list_namespace_entry.get_text()))
for index, row in enumerate(self._rows):
fav_id = row[Column.FAV_ID]
data, sep, desc = fav_id.partition("http")
data = data.split(":")
2021-01-31 16:27:35 +03:00
if self.is_all_data_default():
2018-08-22 00:09:52 +03:00
data[2], data[3], data[4], data[5], data[6] = "10000"
else:
data[0], data[2], data[4], data[5], data[6] = stream_type, srv_type, tid, nid, namespace
data[3] = "{:X}".format(index) if sid_auto else "0"
data = ":".join(data)
new_fav_id = "{}{}{}".format(data, sep, desc)
row[Column.FAV_ID] = new_fav_id
srv = self._services.pop(fav_id, None)
if srv:
self._services[new_fav_id] = srv._replace(fav_id=new_fav_id)
self._bouquet.clear()
list(map(lambda r: self._bouquet.append(r[Column.FAV_ID]), self._fav_model))
self._info_bar.set_visible(True)
2021-01-31 16:27:35 +03:00
class M3uImportDialog(IptvListDialog):
""" Import dialog for *.m3u* playlists. """
def __init__(self, transient, s_type, path, callback):
super().__init__(transient, s_type)
self._callback = callback
self._services = None
self._url_count = 0
self._max_count = 0
self._is_download = False
self._cancellable = Gio.Cancellable()
self._dialog.set_title(get_message("Playlist 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=False, 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)
frame.add(extra_box)
self._data_box.add(frame)
self._apply_button = Gtk.Button(visible=True,
image=Gtk.Image(icon_name="insert-link"),
always_show_image=True,
label=get_message("Import"))
self._apply_button.connect("clicked", self.on_apply)
self._dialog.add_action_widget(self._apply_button, Gtk.ResponseType.APPLY)
self._dialog.connect("delete-event", self.on_close)
self.get_m3u(path, s_type)
@run_task
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 on_apply(self, item):
if not is_data_correct(self._digit_elems):
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return
picons = {}
services = self._services
if not self.is_all_data_default():
services = []
params = [int(el.get_text()) for el in self._digit_elems]
s_type = params[0]
params = params[1:]
2019-04-14 00:03:52 +03:00
stream_type = get_stream_type(self._stream_type_combobox)
2021-01-31 16:27:35 +03:00
for i, s in enumerate(self._services, start=params[0]):
# Skipping markers.
if not s.data_id:
services.append(s)
continue
params[0] = i
picon_id = "1_0_{:X}_{:X}_{:X}_{:X}_{:04X}0000_0_0_0.png".format(s_type, *params)
fav_id = get_fav_id(url=s.data_id,
service_name=s.service,
settings_type=self._s_type,
params=params,
stream_type=stream_type,
s_type=s_type)
picons[s.picon] = picon_id
services.append(s._replace(picon_id=picon_id, data_id=None, fav_id=fav_id))
if self._picons_switch.get_active():
if self.is_default_values():
show_dialog(DialogType.ERROR, self._dialog,
"Set values for TID, NID and Namespace for correct naming of the picons!")
return
self.download_picons(picons)
self._callback(services)
@run_task
def download_picons(self, picons):
self._is_download = True
GLib.idle_add(self._apply_button.set_sensitive, False)
GLib.idle_add(self._progress_bar.set_visible, True)
self._url_count = len(picons)
self._max_count = self._url_count
self._cancellable.reset()
for p in filter(None, picons):
if not self._is_download:
return
f = Gio.File.new_for_uri(p)
try:
GdkPixbuf.Pixbuf.new_from_stream_at_scale_async(f.read(cancellable=self._cancellable), 220, 132, False,
self._cancellable,
self.on_picon_load_done,
picons.get(p, None))
except GLib.GError as e:
self.update_progress()
if e.code != Gio.IOErrorEnum.CANCELLED:
log(str("Picon download error:{} {}").format(p, e))
def on_picon_load_done(self, file, result, user_data):
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_stream_finish(result)
except GLib.GError as e:
if e.code != Gio.IOErrorEnum.CANCELLED:
log("Loading picon [{}] data error: {}".format(user_data, e))
finally:
self._info_label.set_text("Processing: {}".format(user_data))
self.update_progress()
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(get_message("Done!"))
self._is_download = False
def on_response(self, dialog, response):
if response == Gtk.ResponseType.APPLY:
return True
if response == Gtk.ResponseType.CANCEL and not self._is_download or not self.on_close():
self._dialog.destroy()
def on_close(self, window=None, event=None):
if self._is_download:
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.OK:
self._is_download = False
self._cancellable.cancel()
return False
return True
return False
2019-06-24 00:36:54 +03:00
class YtListImportDialog:
2020-06-10 11:10:41 +03:00
def __init__(self, transient, settings, appender):
2019-06-24 00:36:54 +03:00
handlers = {"on_import": self.on_import,
"on_receive": self.on_receive,
"on_yt_url_entry_changed": self.on_url_entry_changed,
"on_yt_info_bar_close": self.on_info_bar_close,
2019-06-28 00:02:34 +03:00
"on_popup_menu": on_popup_menu,
2019-06-24 00:36:54 +03:00
"on_selected_toggled": self.on_selected_toggled,
2019-06-28 00:02:34 +03:00
"on_select_all": self.on_select_all,
"on_unselect_all": self.on_unselect_all,
"on_key_press": self.on_key_press,
2019-06-24 00:36:54 +03:00
"on_close": self.on_close}
2020-06-10 11:10:41 +03:00
self.appender = appender
self._s_type = settings.setting_type
self._download_task = False
self._yt_list_id = None
self._yt_list_title = None
self._settings = settings
2020-06-13 20:57:37 +03:00
self._yt = None
2020-06-10 11:10:41 +03:00
2019-06-24 00:36:54 +03:00
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
2019-06-26 15:57:22 +03:00
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("yt_import_dialog_window", "yt_liststore", "yt_quality_liststore",
"yt_popup_menu", "remove_selection_image"))
2019-06-24 00:36:54 +03:00
builder.connect_signals(handlers)
self._dialog = builder.get_object("yt_import_dialog_window")
self._dialog.set_transient_for(transient)
self._list_view_scrolled_window = builder.get_object("yt_list_view_scrolled_window")
self._model = builder.get_object("yt_liststore")
self._progress_bar = builder.get_object("yt_progress_bar")
self._info_bar_box = builder.get_object("yt_info_bar_box")
self._message_label = builder.get_object("yt_info_bar_message_label")
self._info_bar = builder.get_object("yt_info_bar")
2019-06-30 22:13:26 +03:00
self._yt_count_label = builder.get_object("yt_count_label")
2019-06-24 00:36:54 +03:00
self._url_entry = builder.get_object("yt_url_entry")
self._receive_button = builder.get_object("yt_receive_button")
self._import_button = builder.get_object("yt_import_button")
self._quality_box = builder.get_object("yt_quality_combobox")
self._quality_model = builder.get_object("yt_quality_liststore")
self._import_button.bind_property("visible", self._quality_box, "visible")
self._import_button.bind_property("sensitive", self._quality_box, "sensitive")
self._receive_button.bind_property("sensitive", self._import_button, "sensitive")
2019-06-24 00:36:54 +03:00
# style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
def show(self):
self._dialog.show()
@run_task
def on_import(self, item):
2019-06-26 15:57:22 +03:00
self.on_info_bar_close()
2019-06-24 00:36:54 +03:00
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))
2020-06-13 20:57:37 +03:00
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}
2019-06-24 00:36:54 +03:00
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()
2019-06-24 00:36:54 +03:00
counter += 1
self.update_progress_bar(counter / size)
2020-06-13 20:57:37 +03:00
except YouTubeException as e:
2020-06-10 11:10:41 +03:00
self.show_info_message(str(e), Gtk.MessageType.ERROR)
2019-06-24 00:36:54 +03:00
except Exception as e:
2019-06-27 22:10:44 +03:00
self.show_info_message(str(e), Gtk.MessageType.ERROR)
2019-06-24 00:36:54 +03:00
else:
2019-06-27 22:10:44 +03:00
if self._download_task:
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
self.append_services([done_links[r] for r in rows])
2019-06-24 00:36:54 +03:00
finally:
self._download_task = False
self.update_active_elements(True)
def on_receive(self, item):
2019-06-30 22:13:26 +03:00
self.show_invisible_elements()
2019-06-24 00:36:54 +03:00
self.update_active_elements(False)
self._model.clear()
2019-06-30 22:13:26 +03:00
self._yt_count_label.set_text("0")
2019-06-24 00:36:54 +03:00
self.on_info_bar_close()
self.update_refs_list()
@run_task
def update_refs_list(self):
if self._yt_list_id:
try:
if not self._yt:
self._yt = YouTube.get_instance(self._settings)
self._yt_list_title, links = self._yt.get_yt_playlist(self._yt_list_id, self._url_entry.get_text())
2019-06-24 00:36:54 +03:00
except Exception as e:
2019-06-27 22:10:44 +03:00
self.show_info_message(str(e), Gtk.MessageType.ERROR)
2019-06-24 00:36:54 +03:00
return
else:
gen = self.update_links(links)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
finally:
self.update_active_elements(True)
def update_links(self, links):
2020-06-10 11:10:41 +03:00
for link in links:
yield self._model.append((link[0], link[1], True, None))
2019-06-24 00:36:54 +03:00
size = len(self._model)
self._yt_count_label.set_text(str(size))
self._import_button.set_visible(size)
2019-06-30 22:13:26 +03:00
yield True
2019-06-24 00:36:54 +03:00
@run_idle
def append_services(self, links):
2019-06-27 22:10:44 +03:00
aggr = [None] * 9
2019-06-26 15:57:22 +03:00
srvs = []
if self._yt_list_title:
title = self._yt_list_title
2019-09-04 10:39:46 +03:00
fav_id = MARKER_FORMAT.format(0, title, title)
mk = Service(None, None, None, title, *aggr[0:3], BqServiceType.MARKER.name, *aggr, 0, fav_id, None)
2019-06-27 22:10:44 +03:00
srvs.append(mk)
act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0)
for link in links:
2020-06-10 11:10:41 +03:00
lnk, title = link or (None, None)
2019-08-18 17:02:32 +03:00
if not lnk:
continue
ln = lnk.get(act) if act in lnk else lnk[sorted(lnk, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
2020-06-10 11:10:41 +03:00
fav_id = get_fav_id(ln, title, self._s_type)
srv = Service(None, None, IPTV_ICON, title, *aggr[0:3], BqServiceType.IPTV.name, *aggr, None, fav_id, None)
2019-06-28 00:02:34 +03:00
srvs.append(srv)
2019-06-26 15:57:22 +03:00
self.appender(srvs)
2019-06-24 00:36:54 +03:00
@run_idle
def update_active_elements(self, sensitive):
self._url_entry.set_sensitive(sensitive)
self._receive_button.set_sensitive(sensitive)
2019-06-30 22:13:26 +03:00
def show_invisible_elements(self):
self._list_view_scrolled_window.set_visible(True)
self._info_bar_box.set_visible(True)
self._dialog.set_resizable(True)
2019-06-24 00:36:54 +03:00
def on_url_entry_changed(self, entry):
url_str = entry.get_text()
yt_id = YouTube.get_yt_list_id(url_str)
entry.set_name("GtkEntry" if yt_id else _DIGIT_ENTRY_NAME)
self._receive_button.set_sensitive(bool(yt_id))
self._import_button.set_sensitive(bool(yt_id))
2019-06-24 00:36:54 +03:00
self._yt_list_id = yt_id
if yt_id:
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
else:
entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
@run_idle
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@run_idle
def update_progress_bar(self, value):
2019-06-30 22:13:26 +03:00
self._progress_bar.set_visible(value < 1)
2019-06-24 00:36:54 +03:00
self._progress_bar.set_fraction(value)
@run_idle
def show_info_message(self, text, message_type):
self._info_bar.set_visible(True)
self._info_bar.set_message_type(message_type)
self._message_label.set_text(text)
def on_selected_toggled(self, toggle, path):
self._model.set_value(self._model.get_iter(path), 2, not toggle.get_active())
2019-06-28 00:02:34 +03:00
def on_select_all(self, view):
self.update_selection(view, True)
def on_unselect_all(self, view):
self.update_selection(view, False)
def update_selection(self, view, select):
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 2, select))
def on_key_press(self, view, event):
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):
return
key = KeyboardKey(key_code)
if key is KeyboardKey.SPACE:
path, column = view.get_cursor()
itr = self._model.get_iter(path)
selected = self._model.get_value(itr, 2)
self._model.set_value(itr, 2, not selected)
2019-06-24 00:36:54 +03:00
def on_close(self, window, event):
if self._download_task and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
2019-06-27 22:10:44 +03:00
return True
2019-06-24 00:36:54 +03:00
self._download_task = False
2018-03-11 21:52:10 +03:00
if __name__ == "__main__":
pass