From 074fc960e556c201be89545eee213e73e8d5a17e Mon Sep 17 00:00:00 2001 From: Dmitriy Yefremov Date: Sat, 17 Feb 2018 16:23:41 +0300 Subject: [PATCH] skeleton implementation of service details show --- README.md | 7 +- app/eparser/ecommons.py | 16 +- app/eparser/enigma/lamedb.py | 6 +- app/ui/main_app_window.py | 23 +- app/ui/main_helper.py | 14 +- app/ui/main_window.glade | 4 +- app/ui/service_details_dialog.glade | 323 ++++++++++++++++++---------- app/ui/service_details_dialog.py | 94 +++++--- 8 files changed, 325 insertions(+), 162 deletions(-) diff --git a/README.md b/README.md index c0ca408d..632309e0 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,16 @@ Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet. Ctrl + X - only in bouquet list. Ctrl + C - only in services list. Clipboard is "rubber". There is an accumulation before the insertion! -Ctrl + E, F2 - edit/rename. +Ctrl + E, F2 - edit. +Ctrl + R - rename. Ctrl + S, T, E in Satellites edit tool for create and edit satellite or transponder. Ctrl + L - parental lock. Ctrl + H - hide/skip. Left/Right - remove selection. ### Extra: -Multiple selections in lists only with Space key (as in file managers)! -Ability to import IPTV into bouquet (Neutrino WEBTV) from m3u files! +Multiple selections in lists only with Space key (as in file managers). +Ability to import IPTV into bouquet (Neutrino WEBTV) from m3u files. Tool for downloading picons from lyngsat.com. ### Minimum requirements: Python >= 3.5.2 and GTK+ 3 with PyGObject bindings. diff --git a/app/eparser/ecommons.py b/app/eparser/ecommons.py index a593d947..0755c1c5 100644 --- a/app/eparser/ecommons.py +++ b/app/eparser/ecommons.py @@ -34,7 +34,7 @@ class Type(Enum): Cable = "c" -class FLAG(Enum): +class Flag(Enum): """ Service flags """ KEEP = 1 # Do not automatically update the services parameters. HIDE = 2 @@ -47,6 +47,20 @@ class FLAG(Enum): return 2, 3, 6, 7, 10, 42, 43, 46, 47 +class Inversion(Enum): + Off = "0" + On = "1" + Auto = "2" + + +class Pilot(Enum): + Off = "0" + On = "1" + Auto = "2" + + +ROLL_OFF = {"0": "35%", "1": "25%", "2": "20%", "3": "Auto"} + POLARIZATION = {"0": "H", "1": "V", "2": "L", "3": "R"} PLS_MODE = {"0": "Root", "1": "Gold", "2": "Combo"} diff --git a/app/eparser/enigma/lamedb.py b/app/eparser/enigma/lamedb.py index 2ff88f0c..a4054c2c 100644 --- a/app/eparser/enigma/lamedb.py +++ b/app/eparser/enigma/lamedb.py @@ -6,7 +6,7 @@ from app.commons import log from app.ui import CODED_ICON, LOCKED_ICON, HIDE_ICON from .blacklist import get_blacklist -from ..ecommons import Service, POLARIZATION, SYSTEM, FEC, SERVICE_TYPE, FLAG +from ..ecommons import Service, POLARIZATION, SYSTEM, FEC, SERVICE_TYPE, Flag _HEADER = "eDVB services /4/" _SEP = ":" # separator @@ -101,7 +101,7 @@ def parse_services(services, transponders, path): all_flags = ch[2].split(",") coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None flags = list(filter(lambda x: x.startswith("f:"), all_flags)) - hide = HIDE_ICON if flags and int(flags[0][2:]) in FLAG.hide_values() else None + hide = HIDE_ICON if flags and int(flags[0][2:]) in Flag.hide_values() else None locked = LOCKED_ICON if fav_id in blacklist else None package = list(filter(lambda x: x.startswith("p:"), all_flags)) @@ -128,7 +128,7 @@ def parse_services(services, transponders, path): rate=tr[1], pol=POLARIZATION[tr[2]], fec=FEC[tr[3]], - system=SYSTEM[tr[6]], + system="DVB-S2" if len(tr) > 7 else "DVB-S", pos="{}.{}".format(tr[4][:-1], tr[4][-1:]), data_id=ch[0], fav_id=fav_id, diff --git a/app/ui/main_app_window.py b/app/ui/main_app_window.py index addf7a60..c71947f5 100644 --- a/app/ui/main_app_window.py +++ b/app/ui/main_app_window.py @@ -7,7 +7,7 @@ import shutil from app.commons import run_idle, log from app.eparser import get_blacklist, write_blacklist, parse_m3u from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service -from app.eparser.ecommons import CAS, FLAG +from app.eparser.ecommons import CAS, Flag from app.eparser.enigma.bouquets import BqServiceType from app.eparser.neutrino.bouquets import BqType from app.properties import get_config, write_config, Profile @@ -708,8 +708,15 @@ class MainAppWindow: self.on_locked(None) elif ctrl and key == Gdk.KEY_h or key == Gdk.KEY_H: self.on_hide(None) - elif ctrl and key == Gdk.KEY_E or key == Gdk.KEY_e or key == Gdk.KEY_F2: + elif ctrl and key == Gdk.KEY_R or key == Gdk.KEY_r: self.on_edit(view) + elif ctrl and key == Gdk.KEY_E or key == Gdk.KEY_e or key == Gdk.KEY_F2: + if model_name == self._BOUQUETS_LIST_NAME: + self.on_edit(view) + return + elif model_name == self._FAV_LIST_NAME: + self.on_locate_in_services(view) + self.on_services_data_edit(view) elif key == Gdk.KEY_Left or key == Gdk.KEY_Right: view.do_unselect_all(view) @@ -763,10 +770,10 @@ class MainAppWindow: self.__tool_elements[elem].set_sensitive(not_empty) def on_hide(self, item): - self.set_service_flags(FLAG.HIDE) + self.set_service_flags(Flag.HIDE) def on_locked(self, item): - self.set_service_flags(FLAG.LOCK) + self.set_service_flags(Flag.LOCK) def set_service_flags(self, flag): profile = Profile(self.__profile) @@ -779,9 +786,9 @@ class MainAppWindow: elif profile is Profile.NEUTRINO_MP: if bq_selected: model, path = self.__bouquets_view.get_selection().get_selected() - value = model.get_value(path, 1 if flag is FLAG.LOCK else 2) - value = None if value else LOCKED_ICON if flag is FLAG.LOCK else HIDE_ICON - model.set_value(path, 1 if flag is FLAG.LOCK else 2, value) + value = model.get_value(path, 1 if flag is Flag.LOCK else 2) + value = None if value else LOCKED_ICON if flag is Flag.LOCK else HIDE_ICON + model.set_value(path, 1 if flag is Flag.LOCK else 2, value) @run_idle def on_model_changed(self, model, path, itr=None): @@ -891,7 +898,7 @@ class MainAppWindow: @run_idle def on_services_data_edit(self, item): - dialog = ServiceDetailsDialog(self.__main_window, Profile(self.__profile), self.__services_view) + dialog = ServiceDetailsDialog(self.__main_window, self.__options, self.__services_view) dialog.show() @run_idle diff --git a/app/ui/main_helper.py b/app/ui/main_helper.py index a7cf51a2..c4dd2546 100644 --- a/app/ui/main_helper.py +++ b/app/ui/main_helper.py @@ -7,7 +7,7 @@ import shutil from gi.repository import GdkPixbuf from app.eparser import Service -from app.eparser.ecommons import FLAG +from app.eparser.ecommons import Flag from app.eparser.enigma.bouquets import BqServiceType, to_bouquet_id from . import Gtk, Gdk, HIDE_ICON, LOCKED_ICON from .dialogs import show_dialog, DialogType, get_chooser_dialog @@ -166,7 +166,7 @@ def set_flags(flag, services_view, fav_view, channels, blacklist): model = get_base_model(model) - if flag is FLAG.HIDE: + if flag is Flag.HIDE: if target is ViewTarget.SERVICES: set_hide(channels, model, paths) else: @@ -174,7 +174,7 @@ def set_flags(flag, services_view, fav_view, channels, blacklist): srv_model = get_base_model(services_view.get_model()) srv_paths = [row.path for row in srv_model if row[18] in fav_ids] set_hide(channels, srv_model, srv_paths) - elif flag is FLAG.LOCK: + elif flag is Flag.LOCK: set_lock(blacklist, channels, model, paths, target, services_model=get_base_model(services_view.get_model())) return True @@ -223,13 +223,13 @@ def set_hide(channels, model, paths): value = int(flag[2:]) if flag else 0 if not hide: - if value in FLAG.hide_values(): + if value in Flag.hide_values(): continue # skip if already hidden - value += FLAG.HIDE.value + value += Flag.HIDE.value else: - if value not in FLAG.hide_values(): + if value not in Flag.hide_values(): continue # skip if already allowed to show - value -= FLAG.HIDE.value + value -= Flag.HIDE.value if value == 0 and index is not None: del flags[index] diff --git a/app/ui/main_window.glade b/app/ui/main_window.glade index fbb278d6..4ee4efb4 100644 --- a/app/ui/main_window.glade +++ b/app/ui/main_window.glade @@ -380,12 +380,12 @@ - Edit data/new service + Show details/edit True False image17 False - + diff --git a/app/ui/service_details_dialog.glade b/app/ui/service_details_dialog.glade index 671a9a45..6822f3eb 100644 --- a/app/ui/service_details_dialog.glade +++ b/app/ui/service_details_dialog.glade @@ -45,6 +45,17 @@ + + + Off + + + On + + + Auto + + @@ -74,6 +85,17 @@ + + + Off + + + On + + + Auto + + @@ -117,6 +139,20 @@ + + + 35% + + + 25% + + + 20% + + + Auto + + @@ -730,6 +766,7 @@ True True + False 30 @@ -963,6 +1000,7 @@ True False sys_list_store + @@ -989,7 +1027,9 @@ True + False False + 0.98999999999999999 mod_list_store @@ -1040,116 +1080,6 @@ True False 2 - - - True - False - Flags - - - 6 - 0 - - - - - True - True - 5 - 10 - - - 6 - 1 - - - - - True - False - Pilot - 1 - - - 5 - 0 - - - - - True - False - pilot_list_store - - - - 0 - - - - - 5 - 1 - - - - - True - False - R.off - 1 - - - 4 - 0 - - - - - True - False - Inv. - 1 - - - 3 - 0 - - - - - True - False - rolloff_list_store - - - - 0 - - - - - 4 - 1 - - - - - True - False - invertion_list_store - - - - 0 - - - - - 3 - 1 - - True @@ -1197,10 +1127,10 @@ - + True False - Stream ID + Inversion 2 @@ -1208,14 +1138,179 @@ - + True + False + Rolloff + 0.02 + + + 3 + 0 + + + + + True + False + Pilot + + + 4 + 0 + + + + + True + False + invertion_list_store + + + + 0 + + + + + 2 + 1 + + + + + True + False + False + rolloff_list_store + + + + 0 + + + + + 3 + 1 + + + + + True + False + False + pilot_list_store + + + + 0 + + + + + 4 + 1 + + + + + True + False + PLS mode + + + 5 + 0 + + + + + True + False + False + pls_mode_list_store + + + + 0 + + + + + 5 + 1 + + + + + True + False + Flags + + + 8 + 0 + + + + + True + False True 5 10 - 2 + 8 + 1 + + + + + True + False + PLS code + + + 6 + 0 + + + + + True + False + True + 7 + 10 + + + 6 + 1 + + + + + True + False + Stream ID + + + 7 + 0 + + + + + True + False + True + 5 + 10 + + + 7 1 diff --git a/app/ui/service_details_dialog.py b/app/ui/service_details_dialog.py index e7bd3d4c..766c0679 100644 --- a/app/ui/service_details_dialog.py +++ b/app/ui/service_details_dialog.py @@ -1,9 +1,12 @@ from enum import Enum +from functools import lru_cache from app.commons import run_idle -from app.eparser import Service -from app.ui.main_helper import get_base_model, is_only_one_item_selected +from app.eparser import Service, get_satellites +from app.eparser.ecommons import MODULATION, Inversion, ROLL_OFF, Pilot +from app.properties import Profile from . import Gtk, UI_RESOURCES_PATH +from .main_helper import is_only_one_item_selected class Pids(Enum): @@ -19,14 +22,23 @@ class Pids(Enum): SUBTITLE = "c:09" +@lru_cache(maxsize=1) +def get_sat_positions(path): + return ["{:.1f}".format(float(x.position) / 10) for x in get_satellites(path)] + + class ServiceDetailsDialog: def __init__(self, transient, options, view): + handlers = {"on_system_changed": self.on_system_changed} + builder = Gtk.Builder() builder.add_from_file(UI_RESOURCES_PATH + "service_details_dialog.glade") + builder.connect_signals(handlers) self._dialog = builder.get_object("service_details_dialog") self._dialog.set_transient_for(transient) - self._options = options + self._profile = Profile(options["profile"]) + self._satellites_xml_path = options.get(self._profile.value)["data_dir_path"] + "satellites.xml" self._services_view = view # Service elements self._name_entry = builder.get_object("name_entry") @@ -67,6 +79,8 @@ class ServiceDetailsDialog: self._stream_id_entry = builder.get_object("stream_id_entry") self._flags_entry = builder.get_object("flags_entry") self._namespace_entry = builder.get_object("namespace_entry") + self._DVB_S2_ELEMENTS = (self._mod_combo_box, self._rolloff_combo_box, self._pilot_combo_box, + self._pls_mode_combo_box, self._pls_code_entry, self._stream_id_entry) self.update_data_elements() @run_idle @@ -74,16 +88,28 @@ class ServiceDetailsDialog: model, paths = self._services_view.get_selection().get_selected_rows() if is_only_one_item_selected(paths, self._dialog): srv = Service(*model[paths][:]) - self.init_service_data(srv) - self.init_transponder_data(srv) + # Service + self._name_entry.set_text(srv.service) + self._package_entry.set_text(srv.package) + self.select_active_text(self._service_type_combo_box, srv.service_type) + self._id_entry.set_text(str(int(srv.ssid, 16))) + # Transponder + self._freq_entry.set_text(srv.freq) + self._rate_entry.set_text(srv.rate) + self.select_active_text(self._pol_combo_box, srv.pol) + self.select_active_text(self._fec_combo_box, srv.fec) + self.select_active_text(self._sys_combo_box, srv.system) + self.set_sat_positions(srv.pos) - def init_service_data(self, srv): + if self._profile is Profile.ENIGMA_2: + self.init_enigma2_service_data(srv) + self.init_enigma2_transponder_data(srv) + + @run_idle + def init_enigma2_service_data(self, srv): """ Service data initialisation """ - self._name_entry.set_text(srv.service) - self._package_entry.set_text(srv.package) - self.select_active_text(self._service_type_combo_box, srv.service_type) - self._id_entry.set_text(str(int(srv.ssid, 16))) flags = srv.flags_cas.split(",") + cas = list(filter(lambda x: x.startswith("C:"), flags)) if cas: self._cas_entry.set_text(",".join(cas)) @@ -92,36 +118,44 @@ class ServiceDetailsDialog: if pids: for pid in pids: if pid.startswith(Pids.VIDEO.value): - self._video_pid_entry.set_text(str(int(pid.lstrip(Pids.VIDEO.value), 16))) + self._video_pid_entry.set_text(str(int(pid[4:], 16))) elif pid.startswith(Pids.AUDIO.value): - self._audio_pid_entry.set_text(str(int(pid.lstrip(Pids.AUDIO.value), 16))) + self._audio_pid_entry.set_text(str(int(pid[4:], 16))) elif pid.startswith(Pids.TELETEXT.value): - self._teletext_pid_entry.set_text(str(int(pid.lstrip(Pids.TELETEXT.value), 16))) + self._teletext_pid_entry.set_text(str(int(pid[4:], 16))) elif pid.startswith(Pids.PCR.value): - self._pcr_pid_entry.set_text(str(int(pid.lstrip(Pids.PCR.value), 16))) + self._pcr_pid_entry.set_text(str(int(pid[4:], 16))) elif pid.startswith(Pids.AC3.value): - self._ac3_pid_entry.set_text(str(int(pid.lstrip(Pids.AC3.value), 16))) + self._ac3_pid_entry.set_text(str(int(pid[4:], 16))) elif pid.startswith(Pids.VIDEO_TYPE.value): - # self._type_entry.set_text(pid.strip(Pisd.VIDEO_TYPE.value)) pass elif pid.startswith(Pids.AUDIO_CHANNEL.value): pass elif pid.startswith(Pids.BIT_STREAM_DELAY.value): - self._bitstream_entry.set_text(str(int(pid.lstrip(Pids.BIT_STREAM_DELAY.value), 16))) + self._bitstream_entry.set_text(str(int(pid[4:], 16))) elif pid.startswith(Pids.PCM_DELAY.value): - self._pcm_entry.set_text(str(int(pid.lstrip(Pids.PCM_DELAY.value), 16))) + self._pcm_entry.set_text(str(int(pid[4:], 16))) elif pid.startswith(Pids.SUBTITLE.value): pass self._reference_entry.set_text(srv.picon_id.replace("_", ":").rstrip(".png")) - def init_transponder_data(self, srv): + @run_idle + def init_enigma2_transponder_data(self, srv): """ Transponder data initialisation """ - self._freq_entry.set_text(srv.freq) - self._rate_entry.set_text(srv.rate) - self.select_active_text(self._pol_combo_box, srv.pol) - self.select_active_text(self._fec_combo_box, srv.fec) - self.select_active_text(self._sys_combo_box, srv.system) + data = srv.data_id.split(":") + tr_data = srv.transponder.split(":") + + if srv.system == "DVB-S2": + self.select_active_text(self._mod_combo_box, MODULATION.get(tr_data[8])) + self.select_active_text(self._rolloff_combo_box, ROLL_OFF.get(tr_data[9])) + self.select_active_text(self._pilot_combo_box, Pilot(tr_data[10]).name) + + self._namespace_entry.set_text(str(int(data[1], 16))) + self._transponder_id_entry.set_text(str(int(data[2], 16))) + self._network_id_entry.set_text(str(int(data[3], 16))) + self.select_active_text(self._invertion_combo_box, Inversion(tr_data[5]).name) + self._flags_entry.set_text(tr_data[6]) def select_active_text(self, box: Gtk.ComboBox, text): model = box.get_model() @@ -130,6 +164,18 @@ class ServiceDetailsDialog: box.set_active(index) break + @run_idle + def set_sat_positions(self, sat_pos): + model = self._sat_pos_combo_box.get_model() + positions = get_sat_positions(self._satellites_xml_path) + for pos in positions: + model.append((pos,)) + self.select_active_text(self._sat_pos_combo_box, sat_pos) + + def on_system_changed(self, box): + for elem in self._DVB_S2_ELEMENTS: + elem.set_sensitive(box.get_active()) + def show(self): response = self._dialog.run() if response == Gtk.ResponseType.OK: