Compare commits

...

100 Commits
0.2.2 ... 0.3.0

Author SHA1 Message Date
DYefremov
0b0d3ded8c IPTV elements translation 2018-04-01 09:54:50 +03:00
DYefremov
c05dd026fb Merge remote-tracking branch 'origin/experimental' into experimental 2018-04-01 09:39:29 +03:00
DYefremov
dca94271b0 checking data in IPTV dialog 2018-04-01 09:39:14 +03:00
Dmitriy Yefremov
8d7aa8736e Update README.md 2018-03-30 09:41:05 +03:00
Dmitriy Yefremov
1d693670f4 Merge branch 'experimental' into testing 2018-03-30 09:07:41 +03:00
Dmitriy Yefremov
52f50cdaf5 upd readme 2018-03-30 09:07:17 +03:00
DYefremov
d2ac5d5ac4 impl of checking the CAID field 2018-03-29 13:49:01 +03:00
Dmitriy Yefremov
fe579358f6 Merge branch 'experimental' into testing 2018-03-28 17:30:00 +03:00
Dmitriy Yefremov
db942ee10b set defaults for dvb-s2 after system change 2018-03-28 17:29:07 +03:00
DYefremov
b4648a6efd Merge remote-tracking branch 'origin/testing' into testing 2018-03-27 23:05:39 +03:00
DYefremov
647f468feb Merge branch 'experimental' into testing 2018-03-27 23:05:21 +03:00
DYefremov
ef608df76b picon name update after service edit 2018-03-27 23:03:56 +03:00
DYefremov
8e32373a99 picon name update after service edit 2018-03-27 23:03:01 +03:00
DYefremov
7f817944fa reference update in the service dialog 2018-03-26 17:33:20 +03:00
Dmitriy Yefremov
7752da92b1 Merge branch 'experimental' into testing 2018-03-25 10:47:33 +03:00
Dmitriy Yefremov
24023d438d ui changes for the service dialog 2018-03-25 10:46:30 +03:00
DYefremov
9e0d8840f3 init neutrino ui elms for service dialog 2018-03-23 21:42:54 +03:00
Dmitriy Yefremov
d762f097d0 state update for navigation buttons 2018-03-20 23:42:06 +03:00
DYefremov
336aa47177 added default init in service dialog 2018-03-20 00:33:00 +03:00
DYefremov
1eeccd654a added validation for non empty entries in service dialog 2018-03-18 18:11:16 +03:00
DYefremov
e37abef359 decoupling for [on save] in service dialog 2018-03-18 17:13:21 +03:00
Dmitriy Yefremov
c120f42ee1 skeleton of getting transponder data for neutrino 2018-03-17 22:18:10 +03:00
DYefremov
1531548e51 add impl for iptv 2018-03-16 00:10:33 +03:00
DYefremov
72ebdceb6e edit impl for iptv 2018-03-15 23:10:22 +03:00
Dmitriy Yefremov
791fa2b5f6 init neutrino data skeleton for iptv dialog 2018-03-13 23:27:55 +03:00
Dmitriy Yefremov
47df44c202 little optimisation 2018-03-13 10:42:56 +03:00
Dmitriy Yefremov
d7635370ba iptv dialog init data 2018-03-12 22:47:43 +03:00
DYefremov
1d577750c0 Iptv dialog skeleton 2018-03-11 21:52:10 +03:00
DYefremov
b56685edb1 Ui skeleton for IPTV dialog 2018-03-10 19:12:56 +03:00
DYefremov
f7f230f40e Separation of service creation and editing 2018-03-10 17:49:53 +03:00
Dmitriy Yefremov
88e19e2fd1 added warning message to the picons dialog 2018-03-09 17:40:26 +03:00
DYefremov
ae6f0e1ae2 pos fix for picons dialog 2018-03-07 23:56:21 +03:00
DYefremov
c3e880890e little picons dialog changes 2018-03-07 22:43:42 +03:00
Dmitriy Yefremov
a7edb6d0f6 Merge branch 'experimental' into testing 2018-03-06 20:46:25 +03:00
DYefremov
03e18401cc global search navigation skeleton 2018-03-06 19:06:16 +03:00
DYefremov
1d6b8c2558 Dialogs translation 2018-03-06 11:34:06 +03:00
Dmitriy Yefremov
ccd111cd94 added ui elements for search navigation 2018-03-05 22:45:21 +03:00
Dmitriy Yefremov
e8f3b5df8a convert to impl 2018-03-04 19:37:41 +03:00
DYefremov
c4c9c73809 neutrino data init skeleton for service dialog 2018-03-03 23:24:43 +03:00
DYefremov
a4514ebb2b fix tv counter 2018-03-03 20:55:08 +03:00
Dmitriy Yefremov
7b44df9afd added converter tab for picons dialog 2018-03-03 13:38:33 +03:00
DYefremov
3a018e9654 added local translation 2018-03-02 17:08:40 +03:00
DYefremov
9d4e571d89 added local translation 2018-03-02 17:06:53 +03:00
DYefremov
f62184c96c type entry for service dialog 2018-03-02 15:12:21 +03:00
Dmitriy Yefremov
320183554c status bar changes 2018-03-01 20:39:00 +03:00
Dmitriy Yefremov
a525816eca russian translation 2018-03-01 00:18:05 +03:00
DYefremov
0e11a223ad russian translate 2018-02-27 22:05:09 +03:00
Dmitriy Yefremov
6e4b992a79 russian translation skeleton 2018-02-27 14:55:03 +03:00
DYefremov
1164d38e5c e2 impl for set transponder 2018-02-26 16:26:53 +03:00
Dmitriy Yefremov
4288d62a53 set transponder for services 2018-02-25 22:59:36 +03:00
Dmitriy Yefremov
b17bd13fb5 revert transponder services dialog 2018-02-25 17:52:21 +03:00
Dmitriy Yefremov
7124fd6a92 new ui variant for services details dialog 2018-02-24 16:12:05 +03:00
DYefremov
81f354207d ServicesListDialog skeleton 2018-02-23 23:56:19 +03:00
Dmitriy Yefremov
15cb611764 fix flags 2018-02-23 17:36:49 +03:00
Dmitriy Yefremov
6115433aba skeleton of implementation of service editing for Enigma 2018-02-23 17:14:08 +03:00
Dmitriy Yefremov
0c5b9165ef intermediate impl of save in service dialog 2018-02-20 23:34:07 +03:00
DYefremov
c432646f30 on save skeleton for service details dialog 2018-02-20 00:20:32 +03:00
DYefremov
f70913832c showing flags in the service details dialog 2018-02-19 23:18:01 +03:00
DYefremov
25ee7f3538 added simple wait dialog 2018-02-18 17:14:02 +03:00
DYefremov
5a76601ae6 little refactoring in UI for edit/rename 2018-02-18 11:23:31 +03:00
Dmitriy Yefremov
4367fe6ead skeleton implementation of service details show 2018-02-17 17:07:32 +03:00
Dmitriy Yefremov
242642a7ed Update README.md 2018-02-17 16:40:40 +03:00
Dmitriy Yefremov
074fc960e5 skeleton implementation of service details show 2018-02-17 16:23:41 +03:00
DYefremov
e26d08ca8e filling pids in service dialog 2018-02-16 07:41:19 +03:00
DYefremov
8c433680a9 little refactoring ) 2018-02-16 01:58:43 +03:00
DYefremov
547046bddb service details dialog data filling 2018-02-16 01:56:28 +03:00
Dmitriy Yefremov
12983bb1a6 new variant of service details dialog 2018-02-15 18:05:13 +03:00
Dmitriy Yefremov
5dc20232ef Update README.md 2018-02-15 15:22:09 +03:00
Dmitriy Yefremov
0040ecee32 Update README.md 2018-02-15 15:21:08 +03:00
Dmitriy Yefremov
e8f30b667d service dialog elements update 2018-02-15 15:16:34 +03:00
DYefremov
99a9f081fa service details dialog ui changes 2018-02-14 19:06:08 +03:00
Dmitriy Yefremov
51605ae680 service details dialog skeleton 2018-02-14 00:00:52 +03:00
Dmitriy Yefremov
ca06400071 little ui changes 2018-02-13 17:21:25 +03:00
Dmitriy Yefremov
588df32b2f added IPTV icon 2018-02-12 14:27:21 +03:00
Dmitriy Yefremov
ab7f560b4f webtv download/upload 2018-02-12 13:34:00 +03:00
DYefremov
ffce103eae m3u gui elements for neutrino 2018-02-11 23:14:22 +03:00
Dmitriy Yefremov
90418f0e28 webtv parse for neutrino 2018-02-11 16:47:20 +03:00
Dmitriy Yefremov
0daaf6d1e5 neutrino save services fix 2018-02-11 10:50:21 +03:00
DYefremov
47f26b0f4c little fix 2018-02-10 15:49:44 +03:00
DYefremov
e67ce41667 copy/paste fix 2018-02-06 14:20:59 +03:00
DYefremov
0cff24486a insert marker to empty bouquet fix 2018-02-06 13:56:17 +03:00
DYefremov
850ba0d96a little ui changes 2018-02-06 09:49:51 +03:00
Dmitriy Yefremov
303c9a0267 write bouquet fix 2018-02-05 22:17:06 +03:00
Dmitriy Yefremov
0f95165088 set encoding for ftp, fix file names for bouquets 2018-02-05 18:24:49 +03:00
Dmitriy Yefremov
105cf9c90c new algorithm for picons retrieving 2018-02-05 14:44:42 +03:00
Dmitriy Yefremov
cb40a8d0de name space for picons 2018-02-04 18:09:37 +03:00
DYefremov
f19ab37bc8 sat position for picons dialog 2018-02-03 23:25:15 +03:00
DYefremov
d716bd6a86 Displaying picons in lists for Neutrino 2018-02-03 21:07:01 +03:00
Dmitriy Yefremov
5b13b22823 lamedb fix 2018-02-03 17:23:16 +03:00
Dmitriy Yefremov
49076fe477 bouquet names fix 2018-02-03 16:45:54 +03:00
DYefremov
50a517b6f1 simple skeleton for global search 2018-02-02 12:45:58 +03:00
Dmitriy Yefremov
184c3b18ba assign picon skeleton 2018-02-01 21:43:44 +03:00
Dmitriy Yefremov
2f8dcaf47b data backup before save 2018-02-01 14:02:59 +03:00
Dmitriy Yefremov
dbe18b345f skeleton for picon remove 2018-02-01 13:10:06 +03:00
Dmitriy Yefremov
5d68ec8176 added search bar 2018-01-31 16:02:26 +03:00
DYefremov
83e58f9375 little decoupling, filter bar 2018-01-31 00:13:42 +03:00
Dmitriy Yefremov
fe199d78a4 picon reference 2018-01-30 12:37:04 +03:00
DYefremov
d5f7acb019 popup menus for picons 2018-01-29 18:07:47 +03:00
DYefremov
a141c34ee7 Display picons in the fav list 2018-01-29 14:46:34 +03:00
Dmitriy Yefremov
25c9189e1a Display picons in the services list 2018-01-28 23:10:54 +03:00
34 changed files with 5919 additions and 970 deletions

View File

@@ -1,32 +1,34 @@
# DemonEditor
Enigma2 channel and satellites list editor for GNU/Linux.
## Enigma2 channel and satellites list editor for GNU/Linux.
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
Keyboard shortcuts:
Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
### Keyboard shortcuts:
Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
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 + S, T, E in Satellites edit tool for create and edit satellite or transponder.
Ctrl + E - edit.
Ctrl + R, F2 - rename.
Ctrl + S, T in Satellites edit tool for create satellite or transponder.
Ctrl + L - parental lock.
Ctrl + H - hide/skip.
Left/Right - remove selection.
Space - select/deselect.
Left/Right - remove selection.
Multiple selections in lists only with Space key (as in file managers)!
Extra:
Ability to import IPTV into bouquet from m3u files(Enigma2 only)!
### Extra:
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.
#### Note.
To create a simple debian package, you can use the build-deb.sh
Tests only in image based on OpenPLi or last BPanther(neutrino) images with GM 990 Spark Reloaded receiver
in my preferred linux distro (Last Linux Mint 18.* - MATE 64-bit)!
Minimum requirements: Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
#### Terrestrial and cable channels at the moment are not supported!
Terrestrial and cable channels at the moment are not supported!
Note. To create a simple debian package, you can use the build-deb.sh

View File

@@ -2,8 +2,8 @@
from collections import namedtuple
from enum import Enum
Service = namedtuple("Service", ["flags_cas", "transponder_type", "coded", "service", "locked", "hide",
"package", "service_type", "ssid", "freq", "rate", "pol", "fec",
Service = namedtuple("Service", ["flags_cas", "transponder_type", "coded", "service", "locked", "hide", "package",
"service_type", "picon", "picon_id", "ssid", "freq", "rate", "pol", "fec",
"system", "pos", "data_id", "fav_id", "transponder"])
@@ -34,8 +34,14 @@ class Type(Enum):
Cable = "c"
class FLAG(Enum):
""" Service flags """
class Flag(Enum):
""" Service flags
K - last bit (1)
H - second from end (10)
P - third (100)
N - sixth (100000)
"""
KEEP = 1 # Do not automatically update the services parameters.
HIDE = 2
PIDS = 4 # Always use the cached instead of current pids.
@@ -43,9 +49,48 @@ class FLAG(Enum):
NEW = 40 # Marked as new at the last scan
@staticmethod
def hide_values():
return 2, 3, 6, 7, 10, 42, 43, 46, 47
def is_hide(value: int):
return value & 1 << 1
@staticmethod
def is_keep(value: int):
return value & 1 << 0
@staticmethod
def is_pids(value: int):
return value & 1 << 2
@staticmethod
def is_new(value: int):
return value & 1 << 5
class Pids(Enum):
VIDEO = "c:00"
AUDIO = "c:01"
TELETEXT = "c:02"
PCR = "c:03"
AC3 = "c:04"
VIDEO_TYPE = "c:05"
AUDIO_CHANNEL = "c:06"
BIT_STREAM_DELAY = "c:07" # in ms
PCM_DELAY = "c:08" # in ms
SUBTITLE = "c:09"
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"}
@@ -56,13 +101,15 @@ FEC = {"0": "Auto", "1": "1/2", "2": "2/3", "3": "3/4", "4": "5/6", "5": "7/8",
"17": "4/5", "18": "9/10", "19": "1/2", "20": "2/3", "21": "3/4", "22": "5/6", "23": "7/8", "24": "8/9",
"25": "3/5", "26": "4/5", "27": "9/10", "28": "Auto"}
FEC_DEFAULT = {"0": "Auto", "1": "1/2", "2": "2/3", "3": "3/4", "4": "5/6", "5": "7/8", "6": "8/9", "7": "3/5",
"8": "4/5", "9": "9/10"}
SYSTEM = {"0": "DVB-S", "1": "DVB-S2"}
MODULATION = {"0": "Auto", "1": "QPSK", "2": "8PSK", "3": "16APSK", "5": "32APSK"}
SERVICE_TYPE = {"-2": "Unknown", "1": "TV", "2": "Radio", "3": "Data",
"10": "Radio", "12": "Data", "22": "TV", "25": "TV (HD)",
"136": "Data", "139": "Data"}
SERVICE_TYPE = {"-2": "Data", "1": "TV", "2": "Radio", "3": "Data", "10": "Radio", "22": "TV (H264)",
"25": "TV (HD)", "31": "TV (UHD)"}
CAS = {"C:2600": "BISS", "C:0b00": "Conax", "C:0b01": "Conax", "C:0b02": "Conax", "C:0baa": "Conax", "C:0602": "Irdeto",
"C:0604": "Irdeto", "C:0606": "Irdeto", "C:0608": "Irdeto", "C:0622": "Irdeto", "C:0626": "Irdeto",
@@ -71,3 +118,19 @@ CAS = {"C:2600": "BISS", "C:0b00": "Conax", "C:0b01": "Conax", "C:0b02": "Conax"
# 'on' attribute 0070(hex) = 112(int) = ONID(ONID-TID on www.lyngsat.com)
PROVIDER = {112: "HTB+", 253: "Tricolor TV"}
# ************* subsidiary functions ****************
def get_key_by_value(dc: dict, value):
""" Returns key from dict by value """
for k, v in dc.items():
if v == value:
return k
def get_value_by_name(en, name):
""" Returns value by name from enums """
for n in en:
if n.name == name:
return n.value

View File

@@ -18,10 +18,10 @@ def write_bouquets(path, bouquets):
line.append("#NAME {}\n".format(bqs.name))
for bq in bqs.bouquets:
line.append(srv_line.format(bq.name, bq.type))
line.append(srv_line.format(bq.name.replace(" ", "_"), bq.type))
write_bouquet(path, bq.name, bq.type, bq.services)
with open(path + "bouquets.{}".format(bqs.type), "w") as file:
with open(path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
file.writelines(line)
@@ -37,44 +37,40 @@ def write_bouquet(path, name, bq_type, channels):
else:
bouquet.append("#SERVICE {}\n".format(to_bouquet_id(ch)))
with open(path + "userbouquet.{}.{}".format(name, bq_type), "w") as file:
with open(path + "userbouquet.{}.{}".format(name.replace(" ", "_"), bq_type), "w", encoding="utf-8") as file:
file.writelines(bouquet)
def to_bouquet_id(ch):
""" Creates bouquet channel id """
data_type = ch.data_id
if data_type:
data_type = int(ch.data_id.split(":")[-2])
if data_type == 22:
data_type = 16
elif data_type == 25:
data_type = 19
service = "{}:0:{}:{}:0:0:0:".format(1, data_type, ch.fav_id)
if data_type and len(data_type) > 4:
data_type = int(ch.data_id.split(":")[4])
return service
return "{}:0:{:X}:{}:0:0:0:".format(1, data_type, ch.fav_id)
def get_bouquet(path, name, bq_type):
""" Parsing services ids from bouquet file """
with open(path + "userbouquet.{}.{}".format(name, bq_type)) as file:
with open(path + "userbouquet.{}.{}".format(name, bq_type), encoding="utf-8", errors="replace") as file:
chs_list = file.read()
services = []
for ch in list(filter(lambda x: len(x) > 1, chs_list.split("#SERVICE")[1:])): # filtering ['']
srvs = list(filter(None, chs_list.split("\n#SERVICE"))) # filtering ['']
for ch in srvs[1:]:
ch_data = ch.strip().split(":")
if ch_data[1] == "64":
services.append(BouquetService(ch_data[-1].split("\n")[0], BqServiceType.MARKER, ch, ch_data[2]))
elif "http" in ch:
services.append(BouquetService(ch_data[-1].split("\n")[0], BqServiceType.IPTV, ch, 0))
else:
services.append(BouquetService(None, BqServiceType.DEFAULT,
"{}:{}:{}:{}".format(ch_data[3], ch_data[4], ch_data[5], ch_data[6]), 0))
fav_id = "{}:{}:{}:{}".format(ch_data[3], ch_data[4], ch_data[5], ch_data[6])
services.append(BouquetService(None, BqServiceType.DEFAULT, fav_id, 0))
return services
return srvs[0].strip("#NAME").strip(), services
def parse_bouquets(path, bq_name, bq_type):
with open(path + bq_name) as file:
with open(path + bq_name, encoding="utf-8", errors="replace") as file:
lines = file.readlines()
bouquets = None
nm_sep = "#NAME"
@@ -84,10 +80,10 @@ def parse_bouquets(path, bq_name, bq_type):
_, _, name = line.partition(nm_sep)
bouquets = Bouquets(name.strip(), bq_type, [])
if bouquets and "#SERVICE" in line:
name = line.split(".")[1]
bouquets[2].append(Bouquet(name=name,
b_name, services = get_bouquet(path, line.split(".")[1], bq_type)
bouquets[2].append(Bouquet(name=b_name,
type=bq_type,
services=get_bouquet(path, name, bq_type),
services=services,
locked=None,
hidden=None))

View File

@@ -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
@@ -56,7 +56,7 @@ def parse(path):
log(msg)
raise SyntaxError(msg)
transponders, sep, services = services.partition("services") # 2 step
services, sep, _ = services.partition("end") # 3 step
services, sep, _ = services.partition("\nend") # 3 step
return parse_services(services.split("\n"), transponders.split("/"), path)
@@ -85,39 +85,50 @@ def parse_services(services, transponders, path):
for ch in srv:
data = str(ch[0]).split(_SEP)
sp = "0"
tid = data[2]
nid = data[3]
transponder_id = "{}:{}:{}".format(data[1], tid, nid)
transponder = transponders.get(transponder_id, None)
tid = tid.lstrip(sp).upper()
nid = nid.lstrip(sp).upper()
ssid = str(data[0]).lstrip(sp).upper()
onid = str(data[1]).lstrip(sp).upper()
# For comparison in bouquets. Needed in upper case!!!
fav_id = "{}:{}:{}:{}".format(str(data[0]).lstrip(sp), str(data[2]).lstrip(sp),
str(data[3]).lstrip(sp), str(data[1]).lstrip(sp)).upper()
fav_id = "{}:{}:{}:{}".format(ssid, tid, nid, onid)
picon_id = "1_0_{}_{}_{}_{}_{}_0_0_0.png".format(1, ssid, tid, nid, onid)
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 Flag.is_hide(int(flags[0][2:])) else None
locked = LOCKED_ICON if fav_id in blacklist else None
package = list(filter(lambda x: x.startswith("p:"), all_flags))
package = package[0][2:] if package else None
transponder_id = "{}:{}:{}".format(data[1], data[2], data[3])
transponder = transponders.get(transponder_id, None)
if transponder is not None:
tr_type, sp, tr = str(transponder).partition(" ")
tr = tr.split(_SEP)
service_type = SERVICE_TYPE.get(data[4], SERVICE_TYPE["-2"])
# removing all non printable symbols!
srv_name = "".join(c for c in ch[1] if c.isprintable())
channels.append(Service(flags_cas=ch[2],
transponder_type=tr_type,
coded=coded,
service=ch[1],
service=srv_name,
locked=locked,
hide=hide,
package=package,
service_type=service_type,
picon=None,
picon_id=picon_id,
ssid=data[0],
freq=tr[0],
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,

View File

@@ -1,21 +1,40 @@
""" Module for IPTV and streams support """
from enum import Enum
from app.properties import Profile
from app.ui import IPTV_ICON
from .ecommons import BqServiceType, Service
# url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group
NEUTRINO_FAV_ID_FORMAT = "{}::{}::{}::{}::{}::{}::{}::{}::{}::{}"
ENIGMA2_FAV_ID_FORMAT = " {}:0:{}:{}:{}:{}:{}:0:0:0:{}:{}\n#DESCRIPTION: {}\n"
def parse_m3u(path):
class StreamType(Enum):
DVB_TS = "1"
NONE_TS = "4097"
def parse_m3u(path, profile):
with open(path) as file:
aggr = [None] * 8
aggr = [None] * 10
channels = []
count = 0
name = None
fav_id = None
for line in file.readlines():
if line.startswith("#EXTINF"):
name = line[1 + line.index(","):].strip()
count += 1
elif count == 1:
count = 0
fav_id = " 1:0:1:0:0:0:0:0:0:0:{}:{}\n#DESCRIPTION: {}\n".format(
line.strip().replace(":", "%3a"), name, name, None)
channels.append(Service(*aggr[0:3], name, *aggr[0:3], BqServiceType.IPTV.name, *aggr, fav_id, None))
if profile is Profile.ENIGMA_2:
fav_id = ENIGMA2_FAV_ID_FORMAT.format(StreamType.DVB_TS.value, 1, 0, 0, 0, 0,
line.strip().replace(":", "%3a"), name, name, None)
elif profile is Profile.NEUTRINO_MP:
fav_id = NEUTRINO_FAV_ID_FORMAT.format(line.strip(), "", 0, None, None, None, None, "", "", 1)
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], BqServiceType.IPTV.name, *aggr, fav_id, None)
channels.append(srv)
return channels

View File

@@ -1,23 +1,28 @@
import os
from contextlib import suppress
from enum import Enum
from xml.dom.minidom import parse, Document
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT
from app.ui import LOCKED_ICON, HIDE_ICON
from ..ecommons import Bouquets, Bouquet, BouquetService, BqServiceType, PROVIDER
_FILE = "bouquets.xml"
_U_FILE = "ubouquets.xml"
_W_FILE = "webtv.xml"
_COMMENT = " File was created in DemonEditor. Enjoy watching! "
class BqType(Enum):
BOUQUET = "bouquet"
TV = "tv"
WEBTV = "webtv"
def get_bouquets(path):
return (parse_bouquets(path + _FILE, "Providers", BqType.BOUQUET.value),
parse_bouquets(path + _U_FILE, "FAV", BqType.TV.value))
parse_bouquets(path + _U_FILE, "FAV", BqType.TV.value),
parse_webtv(path + _W_FILE, "WEBTV", BqType.WEBTV.value))
def parse_bouquets(file, name, bq_type):
@@ -61,22 +66,62 @@ def parse_bouquets(file, name, bq_type):
return bouquets
def write_bouquets(path, bouquets):
if len(bouquets) < 2:
for f in path + _FILE, path + _U_FILE:
with suppress(FileNotFoundError):
os.remove(f)
def parse_webtv(path, name, bq_type):
bouquets = Bouquets(name=name, type=bq_type, bouquets=[])
if not os.path.exists(path):
return bouquets
dom = parse(path)
services = []
for elem in dom.getElementsByTagName("webtv"):
if elem.hasAttributes():
title = elem.attributes["title"].value
url = elem.attributes["url"].value
description = elem.attributes.get("description")
description = description.value if description else description
urlkey = elem.attributes.get("urlkey", None)
urlkey = urlkey.value if urlkey else urlkey
account = elem.attributes.get("account", None)
account = account.value if account else account
usrname = elem.attributes.get("usrname", None)
usrname = usrname.value if usrname else usrname
psw = elem.attributes.get("psw", None)
psw = psw.value if psw else psw
s_type = elem.attributes.get("type", None)
s_type = s_type.value if s_type else s_type
iconsrc = elem.attributes.get("iconsrc", None)
iconsrc = iconsrc.value if iconsrc else iconsrc
iconsrc_b = elem.attributes.get("iconsrc_b", None)
iconsrc_b = iconsrc_b.value if iconsrc_b else iconsrc_b
group = elem.attributes.get("group", None)
group = group.value if group else group
fav_id = NEUTRINO_FAV_ID_FORMAT.format(url, description, urlkey, account, usrname, psw, s_type, iconsrc,
iconsrc_b, group)
srv = BouquetService(name=title,
type=BqServiceType.IPTV,
data=fav_id,
num=0)
services.append(srv)
bouquet = Bouquet(name="default", type=bq_type, services=services, locked=None, hidden=None)
bouquets[2].append(bouquet)
return bouquets
def write_bouquets(path, bouquets):
for bq in bouquets:
bq_type = BqType(bq.type)
write_bouquet(path + (_FILE if bq_type is BqType.BOUQUET else _U_FILE), bq)
if bq_type is BqType.WEBTV:
write_webtv(path + _W_FILE, bq)
else:
write_bouquet(path + (_FILE if bq_type is BqType.BOUQUET else _U_FILE), bq)
def write_bouquet(file, bouquet):
doc = Document()
root = doc.createElement("zapit")
doc.appendChild(root)
comment = doc.createComment(" File was created in DemonEditor. Enjoy watching! ")
comment = doc.createComment(_COMMENT)
doc.appendChild(comment)
for bq in bouquet.bouquets:
@@ -102,5 +147,43 @@ def write_bouquet(file, bouquet):
doc.writexml(open(file, "w"), addindent=" ", newl="\n", encoding="UTF-8")
def write_webtv(file, bouquet):
doc = Document()
root = doc.createElement("webtvs")
doc.appendChild(root)
comment = doc.createComment(_COMMENT)
doc.appendChild(comment)
for bq in bouquet.bouquets:
for srv in bq.services:
url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group = srv.fav_id.split("::")
srv_elem = doc.createElement("webtv")
srv_elem.setAttribute("title", srv.service)
srv_elem.setAttribute("url", url)
if description != "None":
srv_elem.setAttribute("description", description)
if urlkey != "None":
srv_elem.setAttribute("urlkey", urlkey)
if account != "None":
srv_elem.setAttribute("account", account)
if usrname != "None":
srv_elem.setAttribute("usrname", usrname)
if psw != "None":
srv_elem.setAttribute("psw", psw)
if s_type != "None":
srv_elem.setAttribute("type", s_type)
if iconsrc != "None":
srv_elem.setAttribute("iconsrc", iconsrc)
if iconsrc_b != "None":
srv_elem.setAttribute("iconsrc_b", iconsrc_b)
if group != "None":
srv_elem.setAttribute("group", group)
root.appendChild(srv_elem)
doc.writexml(open(file, "w"), addindent=" ", newl="\n", encoding="UTF-8")
if __name__ == "__main__":
pass

View File

@@ -53,8 +53,8 @@ def write_services(path, services):
for srv in transponers.get(tr):
srv_elem = doc.createElement("S")
srv_elem.setAttribute("i", srv[8])
srv_elem.setAttribute("n", srv[3])
srv_elem.setAttribute("i", srv.ssid)
srv_elem.setAttribute("n", srv.service)
srv_attrs = srv.data_id.split(":")
api = srv_attrs.pop(0)
@@ -116,9 +116,7 @@ def parse_transponder(api, sat, sat_pos, services, tr_elem):
sys = sys.value if sys else sys
tr = "{}:{}:{}:{}:{}:{}:{}:{}:{}".format(tr_id, on, freq, inv, rate, fec, pol, mod, sys)
tr_id = tr_id.lstrip("0")
on = on.lstrip("0")
for srv_elem in tr_elem.getElementsByTagName("S"):
if srv_elem.hasAttributes():
@@ -141,7 +139,8 @@ def parse_transponder(api, sat, sat_pos, services, tr_elem):
vt = srv_elem.attributes["vt"].value
data_id = "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}".format(api, srv_type, sys, num, f, v, a, p, pmt, tx, vt)
fav_id = "{}:{}:{}".format(tr_id, on, ssid.lstrip("0"))
fav_id = "{}:{}:{}".format(tr_id, on.lstrip("0"), ssid.lstrip("0"))
picon_id = "{}{}{}.png".format(tr_id, on, ssid)
srv = Service(flags_cas=sat,
transponder_type=None,
@@ -151,6 +150,8 @@ def parse_transponder(api, sat, sat_pos, services, tr_elem):
hide=None,
package=PROVIDER.get(int(on, 16)),
service_type=SERVICE_TYPE.get(str(int(srv_type, 16))),
picon=None,
picon_id=picon_id,
ssid=ssid,
freq=freq,
rate=rate,

View File

@@ -2,9 +2,12 @@
For more info see __COMMENT
"""
from functools import lru_cache
from xml.dom.minidom import parse, Document
from .ecommons import POLARIZATION, FEC, SYSTEM, MODULATION, PLS_MODE, Transponder, Satellite
import os
from .ecommons import POLARIZATION, FEC, SYSTEM, MODULATION, PLS_MODE, Transponder, Satellite, get_key_by_value
__COMMENT = (" File was created in DemonEditor\n\n"
"usable flags are\n"
@@ -29,7 +32,7 @@ __COMMENT = (" File was created in DemonEditor\n\n"
def get_satellites(path):
return parse_satellites(path)
return parse_satellites(path, os.path.getsize(path))
def write_satellites(satellites, data_path):
@@ -98,7 +101,8 @@ def parse_sat(elem):
parse_transponders(elem))
def parse_satellites(path):
@lru_cache(maxsize=1)
def parse_satellites(path, file_size):
""" Parsing satellites from xml"""
dom = parse(path)
satellites = []
@@ -110,11 +114,5 @@ def parse_satellites(path):
return satellites
def get_key_by_value(dictionary, value):
for k, v in dictionary.items():
if v == value:
return k
if __name__ == "__main__":
pass

View File

@@ -11,17 +11,22 @@ from app.properties import Profile
__DATA_FILES_LIST = ("tv", "radio", "lamedb", "blacklist", "whitelist", # enigma 2
"services.xml", "myservices.xml", "bouquets.xml", "ubouquets.xml") # neutrino
_SATELLITES_XML_FILE = "satellites.xml"
_WEBTV_XML_FILE = "webtv.xml"
class DownloadDataType(Enum):
ALL = 0
BOUQUETS = 1
SATELLITES = 2
PICONS = 3
WEBTV = 4
def download_data(*, properties, download_type=DownloadDataType.ALL, callback=None):
with FTP(host=properties["host"]) as ftp:
ftp.login(user=properties["user"], passwd=properties["password"])
ftp.encoding = "utf-8"
save_path = properties["data_dir_path"]
os.makedirs(os.path.dirname(save_path), exist_ok=True)
files = []
@@ -34,20 +39,21 @@ def download_data(*, properties, download_type=DownloadDataType.ALL, callback=No
name = str(file).strip()
if name.endswith(__DATA_FILES_LIST):
name = name.split()[-1]
with open(save_path + name, "wb") as f:
ftp.retrbinary("RETR " + name, f.write)
# satellites.xml section
if download_type is DownloadDataType.ALL or download_type is DownloadDataType.SATELLITES:
download_file(ftp, name, save_path)
# satellites.xml and webtv section
if download_type in (DownloadDataType.ALL, DownloadDataType.SATELLITES, DownloadDataType.WEBTV):
ftp.cwd(properties["satellites_xml_path"])
files.clear()
ftp.dir(files.append)
for file in files:
name = str(file).strip()
xml_file = "satellites.xml"
if name.endswith(xml_file):
with open(save_path + xml_file, 'wb') as f:
ftp.retrbinary("RETR " + xml_file, f.write)
if download_type in (DownloadDataType.ALL, DownloadDataType.SATELLITES):
if name.endswith(_SATELLITES_XML_FILE):
download_file(ftp, _SATELLITES_XML_FILE, save_path)
elif download_type in (DownloadDataType.ALL, DownloadDataType.WEBTV):
if name.endswith(_WEBTV_XML_FILE):
download_file(ftp, _WEBTV_XML_FILE, save_path)
if callback is not None:
callback()
@@ -66,12 +72,24 @@ def upload_data(*, properties, download_type=DownloadDataType.ALL, remove_unused
with FTP(host=host) as ftp:
ftp.login(user=properties["user"], passwd=properties["password"])
ftp.encoding = "utf-8"
if download_type is DownloadDataType.ALL or download_type is DownloadDataType.SATELLITES:
ftp.cwd(properties["satellites_xml_path"])
file_name = "satellites.xml"
send = send_file(file_name, data_path, ftp)
if download_type == DownloadDataType.SATELLITES:
send = send_file(_SATELLITES_XML_FILE, data_path, ftp)
if download_type is DownloadDataType.SATELLITES:
tn.send("init 3" if profile is Profile.ENIGMA_2 else "init 6")
if callback is not None:
callback()
return send
if profile is Profile.NEUTRINO_MP and download_type in (DownloadDataType.ALL, DownloadDataType.WEBTV):
ftp.cwd(properties["satellites_xml_path"])
send = send_file(_WEBTV_XML_FILE, data_path, ftp)
if download_type is DownloadDataType.WEBTV:
tn.send("init 6")
if callback is not None:
callback()
return send
if download_type is DownloadDataType.ALL or download_type is DownloadDataType.BOUQUETS:
@@ -86,7 +104,7 @@ def upload_data(*, properties, download_type=DownloadDataType.ALL, remove_unused
ftp.delete(name)
for file_name in os.listdir(data_path):
if file_name == "satellites.xml":
if file_name == _SATELLITES_XML_FILE or file_name == _WEBTV_XML_FILE:
continue
if file_name.endswith(__DATA_FILES_LIST):
send_file(file_name, data_path, ftp)
@@ -121,6 +139,11 @@ def upload_data(*, properties, download_type=DownloadDataType.ALL, remove_unused
callback()
def download_file(ftp, name, save_path):
with open(save_path + name, "wb") as f:
ftp.retrbinary("RETR " + name, f.write)
def send_file(file_name, path, ftp):
""" Opens the file in binary mode and transfers into receiver """
with open(path + file_name, "rb") as f:

View File

@@ -1,12 +1,18 @@
import glob
import os
import shutil
from collections import namedtuple
from html.parser import HTMLParser
from app.commons import log
import re
from app.commons import log, run_task
from app.properties import Profile
Provider = namedtuple("Provider", ["logo", "name", "url", "on_id", "selected"])
_ENIGMA2_PICON_KEY = "{:X}:{:X}:{:X}0000"
_NEUTRINO_PICON_KEY = "{:x}{:04x}{:04x}.png"
Provider = namedtuple("Provider", ["logo", "name", "pos", "url", "on_id", "selected"])
Picon = namedtuple("Picon", ["ref", "ssid", "v_pid"])
@@ -68,7 +74,7 @@ class PiconsParser(HTMLParser):
pass
@staticmethod
def parse(open_path, picons_path, tmp_path, on_id, profile=Profile.ENIGMA_2):
def parse(open_path, picons_path, tmp_path, on_id, pos, picon_ids, profile=Profile.ENIGMA_2):
with open(open_path, encoding="utf-8", errors="replace") as f:
parser = PiconsParser()
parser.reset()
@@ -78,19 +84,20 @@ class PiconsParser(HTMLParser):
os.makedirs(picons_path, exist_ok=True)
for p in picons:
try:
picon_file_name = picons_path + PiconsParser.format(p.ssid, on_id, p.v_pid, profile)
shutil.copyfile(tmp_path + "www.lyngsat.com/" + p.ref.lstrip("."), picon_file_name)
name = PiconsParser.format(p.ssid, on_id, p.v_pid, pos, picon_ids, profile)
p_name = picons_path + (name if name else os.path.basename(p.ref))
shutil.copyfile(tmp_path + "www.lyngsat.com/" + p.ref.lstrip("."), p_name)
except (TypeError, ValueError) as e:
log("Picons format parse error: {} {} {}".format(p.ref, p.ssid, p.v_pid) + "\n" + str(e))
log("Picons format parse error: {}".format(p) + "\n" + str(e))
print(e)
@staticmethod
def format(ssid, on_id, v_pid, profile: Profile):
def format(ssid, on_id, v_pid, pos, picon_ids, profile: Profile):
tr_id = int(ssid[:-2] if len(ssid) < 4 else ssid[:2])
if profile is Profile.ENIGMA_2:
return "1_0_{}_{:X}_{:X}_{:X}_1680000_0_0_0.png".format(1 if v_pid else 2, int(ssid), tr_id, int(on_id))
return picon_ids.get(_ENIGMA2_PICON_KEY.format(int(ssid), int(on_id), int(pos)), None)
elif profile is Profile.NEUTRINO_MP:
return "{:x}{:04x}{:04x}.png".format(tr_id, int(on_id), int(ssid))
return _NEUTRINO_PICON_KEY.format(tr_id, int(on_id), int(ssid))
else:
return "{}.png".format(ssid)
@@ -98,6 +105,8 @@ class PiconsParser(HTMLParser):
class ProviderParser(HTMLParser):
""" Parser for satellite html page. (https://www.lyngsat.com/*sat-name*.html) """
_POSITION_PATTERN = re.compile("at\s\d+\..*(?:E|W)']")
def __init__(self, entities=False, separator=' '):
HTMLParser.__init__(self)
@@ -112,6 +121,7 @@ class ProviderParser(HTMLParser):
self._current_cell = []
self.rows = []
self._ids = set()
self._positon = None
def handle_starttag(self, tag, attrs):
if tag == 'td':
@@ -142,27 +152,56 @@ class ProviderParser(HTMLParser):
self._current_cell = []
elif tag == 'tr':
row = self._current_row
# Satellite position
if not self._positon:
pos = re.findall(self._POSITION_PATTERN, str(row))
if pos:
self._positon = "".join(c for c in str(pos) if c.isdigit() or c in ".EW")
if len(row) == 12:
on_id, sep, tid = str(row[-2]).partition("-")
if tid and on_id not in self._ON_ID_BLACK_LIST and on_id not in self._ids:
row[-2] = on_id
self.rows.append(row)
self._ids.add(on_id)
row[0] = self._positon
self._current_row = []
def error(self, message):
pass
def reset(self):
super().reset()
def parse_providers(open_path):
parser = ProviderParser()
parser.reset()
with open(open_path, encoding="utf-8", errors="replace") as f:
parser = ProviderParser()
parser.reset()
parser.feed(f.read())
rows = parser.rows
if rows:
return [Provider(logo=r[2], name=r[5], url=r[6], on_id=r[-2], selected=True) for r in rows]
return [Provider(logo=r[2], name=r[5], pos=r[0], url=r[6], on_id=r[-2], selected=True) for r in rows]
@run_task
def convert_to(src_path, dest_path, profile, callback, done_callback):
""" Converts names format of picons.
Copies resulting files from src to dest and writes state to callback.
"""
pattern = "/*_0_0_0.png" if profile is Profile.ENIGMA_2 else "/*.png"
for file in glob.glob(src_path + pattern):
base_name = os.path.basename(file)
pic_data = base_name.rstrip(".png").split("_")
dest_file = _NEUTRINO_PICON_KEY.format(int(pic_data[4], 16), int(pic_data[5], 16), int(pic_data[3], 16))
dest = "{}/{}".format(dest_path, dest_file)
callback('Converting "{}" to "{}"\n'.format(base_name, dest_file))
shutil.copyfile(file, dest)
done_callback()
if __name__ == "__main__":

View File

@@ -1,3 +1,4 @@
import locale
import gi
import os
@@ -7,6 +8,12 @@ from gi.repository import Gtk, Gdk
# path to *.glade files
UI_RESOURCES_PATH = "app/ui/" if os.path.exists("app/ui/") else "/usr/share/demoneditor/app/ui/"
# translation
TEXT_DOMAIN = "demon-editor"
if UI_RESOURCES_PATH == "app/ui/":
LANG_DIR = UI_RESOURCES_PATH + "lang"
locale.bindtextdomain(TEXT_DOMAIN, UI_RESOURCES_PATH + "lang")
theme = Gtk.IconTheme.get_default()
_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None
CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(
@@ -15,6 +22,7 @@ LOCKED_ICON = theme.load_icon("system-lock-screen", 16, 0) if theme.lookup_icon(
"system-lock-screen", 16, 0) else _IMAGE_MISSING
HIDE_ICON = theme.load_icon("go-jump", 16, 0) if theme.lookup_icon("go-jump", 16, 0) else _IMAGE_MISSING
TV_ICON = theme.load_icon("tv-symbolic", 16, 0) if theme.lookup_icon("tv-symbolic", 16, 0) else _IMAGE_MISSING
IPTV_ICON = theme.load_icon("emblem-shared", 16, 0) if theme.load_icon("emblem-shared", 16, 0) else None
if __name__ == "__main__":
pass

View File

@@ -9,8 +9,8 @@
<property name="icon_name">system-help</property>
<property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property>
<property name="version">0.2.2 Pre-alpha</property>
<property name="copyright" translatable="yes">2018 Dmitriy Yefremov
<property name="version">0.3.0 Pre-alpha</property>
<property name="copyright">2018 Dmitriy Yefremov
dmitry.v.yefremov@gmail.com
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellites list editor for GNU/Linux</property>
@@ -37,9 +37,6 @@ dmitry.v.yefremov@gmail.com
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
@@ -114,7 +111,7 @@ dmitry.v.yefremov@gmail.com
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="max_width_chars">10</property>
<property name="text" translatable="yes">127.0.0.1</property>
<property name="text">127.0.0.1</property>
<property name="caps_lock_warning">False</property>
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
</object>
@@ -140,7 +137,7 @@ dmitry.v.yefremov@gmail.com
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="text" translatable="yes">data/</property>
<property name="text">data/</property>
<property name="caps_lock_warning">False</property>
</object>
<packing>
@@ -223,7 +220,21 @@ dmitry.v.yefremov@gmail.com
</packing>
</child>
<child>
<placeholder/>
<object class="GtkRadioButton" id="webtv_radio_button">
<property name="label" translatable="yes">WebTV</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="yalign">0.52999997138977051</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">all_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
@@ -563,9 +574,6 @@ dmitry.v.yefremov@gmail.com
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<action-widgets>
@@ -605,6 +613,481 @@ dmitry.v.yefremov@gmail.com
</object>
</child>
</object>
<object class="GtkListStore" id="stream_type_liststore">
<columns>
<!-- column-name stream_type -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0">DVB/TS</col>
</row>
<row>
<col id="0">non-TS</col>
</row>
</data>
</object>
<object class="GtkDialog" id="iptv_dialog">
<property name="width_request">480</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Stream data</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<child internal-child="vbox">
<object class="GtkBox" id="iptv_dialog_vbox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="iptv_dialog_action_area">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="iptv_dialog_cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="iptv_dialog_add_button">
<property name="label">gtk-add</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_save" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="iptv_dialog_save_button">
<property name="label">gtk-save</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_save" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="iptv_dialog_main_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="label22">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Service data:</property>
<property name="xalign">0.0099999997764825821</property>
<style>
<class name="primary-toolbar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="iptv_dialog_main_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">2</property>
<property name="column_spacing">2</property>
<child>
<object class="GtkLabel" id="label23">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Name</property>
<property name="width_chars">7</property>
<property name="max_width_chars">7</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="name_entry">
<property name="width_request">-1</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="primary_icon_stock">gtk-edit</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="iptv_description_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Description</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="description_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="primary_icon_stock">gtk-edit</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="iptv_type_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Type</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="stream_type_combobox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">stream_type_liststore</property>
<property name="active">0</property>
<property name="id_column">0</property>
<signal name="changed" handler="on_stream_type_changed" swapped="no"/>
<child>
<object class="GtkCellRendererText" id="iptv_sream_type_cellrenderertext"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="iptv_reference_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Reference</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="reference_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="editable">False</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="iptv_url_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel" id="url_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Url:</property>
<property name="width_chars">7</property>
<property name="max_width_chars">7</property>
<property name="xalign">0.0099999997764825821</property>
<style>
<class name="primary-toolbar"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="url_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_url_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="iptv_data_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkSeparator" id="separator6">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">1</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ts_data_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">DVB/TS data:</property>
<property name="xalign">0.0099999997764825821</property>
<style>
<class name="primary-toolbar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="ts_data_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">2</property>
<property name="column_spacing">2</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel" id="label28">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Type</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label29">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">SID</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label30">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">TID</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label31">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">NID</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label32">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Namespace</property>
</object>
<packing>
<property name="left_attach">4</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="srv_type_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">1</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="sid_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">0</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="tr_id_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">0</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="net_id_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">0</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="namespace_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">0</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">4</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator7">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">7</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">iptv_dialog_cancel_button</action-widget>
</action-widgets>
</object>
<object class="GtkAdjustment" id="telnet_timeout_adjustment">
<property name="lower">1</property>
<property name="upper">11</property>
@@ -712,7 +1195,7 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="host_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">127.0.0.1</property>
<property name="text">127.0.0.1</property>
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
</object>
<packing>
@@ -746,7 +1229,7 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="port_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">21</property>
<property name="text">21</property>
<property name="primary_icon_name">network-workgroup-symbolic</property>
</object>
<packing>
@@ -769,7 +1252,7 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="login_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">root</property>
<property name="text">root</property>
<property name="primary_icon_name">avatar-default-symbolic</property>
<property name="primary_icon_activatable">False</property>
</object>
@@ -784,7 +1267,7 @@ dmitry.v.yefremov@gmail.com
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="invisible_char">●</property>
<property name="text" translatable="yes">root</property>
<property name="text">root</property>
<property name="primary_icon_name">emblem-readonly</property>
<property name="primary_icon_activatable">False</property>
<property name="input_purpose">password</property>
@@ -794,6 +1277,9 @@ dmitry.v.yefremov@gmail.com
<property name="top_attach">3</property>
</packing>
</child>
<style>
<class name="primary-toolbar"/>
</style>
</object>
</child>
<child type="tab">
@@ -849,7 +1335,7 @@ dmitry.v.yefremov@gmail.com
<object class="GtkLabel" id="label16">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Loggin:</property>
<property name="label" translatable="yes">Login:</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -906,6 +1392,9 @@ dmitry.v.yefremov@gmail.com
<property name="top_attach">1</property>
</packing>
</child>
<style>
<class name="primary-toolbar"/>
</style>
</object>
<packing>
<property name="position">1</property>
@@ -973,7 +1462,8 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="services_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/enigma2/</property>
<property name="text">/etc/enigma2/</property>
<property name="primary_icon_stock">gtk-edit</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -996,7 +1486,8 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="user_bouquet_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/enigma2/</property>
<property name="text">/etc/enigma2/</property>
<property name="primary_icon_stock">gtk-edit</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -1019,7 +1510,8 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="satellites_xml_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/tuxbox/</property>
<property name="text">/etc/tuxbox/</property>
<property name="primary_icon_stock">gtk-edit</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -1042,7 +1534,8 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="picons_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/usr/share/enigma2/picon</property>
<property name="text">/usr/share/enigma2/picon</property>
<property name="primary_icon_stock">gtk-edit</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -1081,7 +1574,7 @@ dmitry.v.yefremov@gmail.com
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Active profile:</property>
<property name="xalign">0</property>
<property name="xalign">0.20000000298023224</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1091,7 +1584,7 @@ dmitry.v.yefremov@gmail.com
</child>
<child>
<object class="GtkRadioButton" id="enigma_radio_button">
<property name="label" translatable="yes">Enigma2 </property>
<property name="label">Enigma2 </property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
@@ -1109,7 +1602,7 @@ dmitry.v.yefremov@gmail.com
</child>
<child>
<object class="GtkRadioButton" id="neutrino_radio_button">
<property name="label" translatable="yes">Neutrino-MP
<property name="label">Neutrino-MP
(experimental)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
@@ -1190,7 +1683,8 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="data_dir_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/data</property>
<property name="text">/data</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Select</property>
@@ -1209,18 +1703,6 @@ dmitry.v.yefremov@gmail.com
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator3">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label18">
<property name="visible">True</property>
@@ -1231,20 +1713,33 @@ dmitry.v.yefremov@gmail.com
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">8</property>
<property name="position">7</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="picons_dir_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/data/picons</property>
<property name="text">/data/picons</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<signal name="icon-press" handler="on_picons_dir_field_icon_press" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">8</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator3">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">9</property>
</packing>
</child>
@@ -1255,4 +1750,75 @@ dmitry.v.yefremov@gmail.com
<action-widget response="-5">ok_button</action-widget>
</action-widgets>
</object>
<object class="GtkDialog" id="wait_dialog">
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">splashscreen</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="decorated">False</property>
<child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox4">
<property name="width_request">118</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog-action_area4">
<property name="can_focus">False</property>
<property name="opacity">0</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkSpinner" id="spinner">
<property name="width_request">150</property>
<property name="height_request">45</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label21">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Loading data...</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<style>
<class name="primary-toolbar"/>
</style>
</object>
</child>
</object>
</interface>

View File

@@ -1,24 +1,42 @@
""" Common module for showing dialogs """
import locale
from enum import Enum
from . import Gtk, UI_RESOURCES_PATH
from app.commons import run_idle
from . import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
class Action(Enum):
EDIT = 0
ADD = 1
class DialogType(Enum):
INPUT = "input_dialog"
MESSAGE = ""
CHOOSER = "path_chooser_dialog"
ERROR = "error_dialog"
QUESTION = "question_dialog"
ABOUT = "about_dialog"
WAIT = "wait_dialog"
class WaitDialog:
def __init__(self, transient):
builder, dialog = get_dialog_from_xml(DialogType.WAIT, transient)
self._dialog = dialog
self._dialog.set_transient_for(transient)
def show(self):
self._dialog.show()
@run_idle
def hide(self):
self._dialog.hide()
def show_dialog(dialog_type: DialogType, transient, text=None, options=None, action_type=None, file_filter=None):
""" Shows dialogs by name """
builder = Gtk.Builder()
builder.add_from_file(UI_RESOURCES_PATH + "dialogs.glade")
dialog = builder.get_object(dialog_type.value)
dialog.set_transient_for(transient)
builder, dialog = get_dialog_from_xml(dialog_type, transient)
if dialog_type is DialogType.CHOOSER and options:
if action_type is not None:
@@ -51,12 +69,39 @@ def show_dialog(dialog_type: DialogType, transient, text=None, options=None, act
return txt if response == Gtk.ResponseType.OK else Gtk.ResponseType.CANCEL
if text:
dialog.set_markup(text)
dialog.set_markup(get_message(text))
response = dialog.run()
dialog.destroy()
return response
def get_dialog_from_xml(dialog_type, transient):
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", (dialog_type.value,))
dialog = builder.get_object(dialog_type.value)
dialog.set_transient_for(transient)
return builder, dialog
def get_chooser_dialog(transient, options, pattern, name):
file_filter = Gtk.FileFilter()
file_filter.add_pattern(pattern)
file_filter.set_name(name)
return show_dialog(dialog_type=DialogType.CHOOSER,
transient=transient,
options=options,
action_type=Gtk.FileChooserAction.OPEN,
file_filter=file_filter)
def get_message(message):
""" returns translated message """
return locale.dgettext(TEXT_DOMAIN, message)
if __name__ == "__main__":
pass

View File

@@ -1,8 +1,8 @@
from app.commons import run_idle, run_task
from app.ftp import download_data, DownloadDataType, upload_data
from app.properties import Profile
from . import Gtk, UI_RESOURCES_PATH
from .dialogs import show_dialog, DialogType
from . import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .dialogs import show_dialog, DialogType, get_message
def show_download_dialog(transient, options, open_data, profile=Profile.ENIGMA_2):
@@ -22,6 +22,7 @@ class DownloadDialog:
"on_info_bar_close": self.on_info_bar_close}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", ("download_dialog",))
builder.connect_signals(handlers)
@@ -35,16 +36,19 @@ class DownloadDialog:
self._all_radio_button = builder.get_object("all_radio_button")
self._bouquets_radio_button = builder.get_object("bouquets_radio_button")
self._satellites_radio_button = builder.get_object("satellites_radio_button")
self._webtv_radio_button = builder.get_object("webtv_radio_button")
if profile is Profile.NEUTRINO_MP:
self._webtv_radio_button.set_visible(True)
# self._dialog.get_content_area().set_border_width(0)
@run_idle
def on_receive(self, item):
self.download(True, d_type=self.get_download_type())
self.download(True, self.get_download_type())
@run_idle
def on_send(self, item):
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.CANCEL:
self.download(d_type=self.get_download_type())
self.download(False, self.get_download_type())
def get_download_type(self):
download_type = DownloadDataType.ALL
@@ -52,6 +56,8 @@ class DownloadDialog:
download_type = DownloadDataType.BOUQUETS
elif self._satellites_radio_button.get_active():
download_type = DownloadDataType.SATELLITES
elif self._webtv_radio_button.get_active():
download_type = DownloadDataType.WEBTV
return download_type
def run(self):
@@ -65,18 +71,18 @@ class DownloadDialog:
@run_idle
@run_task
def download(self, download=False, d_type=DownloadDataType.ALL):
def download(self, download, d_type):
""" Download/upload data from/to receiver """
try:
if download:
download_data(properties=self._properties, download_type=d_type)
else:
self.show_info_message("Please, wait...", Gtk.MessageType.INFO)
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
upload_data(properties=self._properties,
download_type=d_type,
remove_unused=self._remove_unused_check_button.get_active(),
profile=self._profile,
callback=lambda: self.show_info_message("Done!", Gtk.MessageType.INFO))
callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
except Exception as e:
message = str(getattr(e, "message", str(e)))
self.show_info_message(message, Gtk.MessageType.ERROR)

185
app/ui/iptv.py Normal file
View File

@@ -0,0 +1,185 @@
import re
from urllib.parse import urlparse
from app.eparser.ecommons import BqServiceType, Service
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT
from app.properties import Profile
from . import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON
from .dialogs import Action, show_dialog, DialogType
from .main_helper import get_base_model
class IptvDialog:
_DIGIT_ENTRY_NAME = "digit-entry"
def __init__(self, transient, view, services, bouquet, profile=Profile.ENIGMA_2, action=Action.ADD):
handlers = {"on_entry_changed": self.on_entry_changed,
"on_url_changed": self.on_url_changed,
"on_save": self.on_save,
"on_stream_type_changed": self.on_stream_type_changed}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", ("iptv_dialog", "stream_type_liststore"))
builder.connect_signals(handlers)
self._dialog = builder.get_object("iptv_dialog")
self._dialog.set_transient_for(transient)
self._name_entry = builder.get_object("name_entry")
self._description_entry = builder.get_object("description_entry")
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")
self._stream_type_combobox = builder.get_object("stream_type_combobox")
self._action = action
self._profile = profile
self._bouquet = bouquet
self._services = services
self._model, self._paths = view.get_selection().get_selected_rows()
self._PATTERN = re.compile("(?:^[\s]*$|\D)")
# style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
for el in (self._srv_type_entry, self._sid_entry, self._tr_id_entry, self._net_id_entry, self._namespace_entry):
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
if profile is Profile.NEUTRINO_MP:
builder.get_object("iptv_data_box").set_visible(False)
builder.get_object("iptv_type_label").set_visible(False)
builder.get_object("reference_entry").set_visible(False)
builder.get_object("iptv_reference_label").set_visible(False)
self._stream_type_combobox.set_visible(False)
else:
self._description_entry.set_visible(False)
builder.get_object("iptv_description_label").set_visible(False)
if self._action is Action.ADD:
self._save_button.set_visible(False)
self._add_button.set_visible(True)
if self._profile is Profile.ENIGMA_2:
self._update_reference_entry()
elif self._action is Action.EDIT:
self._current_srv = get_base_model(self._model)[self._paths][:]
self.init_data(self._current_srv)
def show(self):
self._dialog.run()
self._dialog.destroy()
def on_save(self, item):
if not self.is_data_correct():
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
self.save_enigma2_data() if self._profile is Profile.ENIGMA_2 else self.save_neutrino_data()
self._dialog.destroy()
def init_data(self, srv):
name, fav_id = srv[2], srv[7]
self._name_entry.set_text(name)
self.init_enigma2_data(fav_id) if self._profile is Profile.ENIGMA_2 else self.init_neutrino_data(fav_id)
def init_enigma2_data(self, fav_id):
data, sep, desc = fav_id.partition("#DESCRIPTION:")
self._description_entry.set_text(desc.strip())
data = data.split(":")
if len(data) < 12:
return
self._stream_type_combobox.set_active(0 if StreamType(data[0].strip()) is StreamType.DVB_TS else 1)
self._srv_type_entry.set_text(data[2])
self._sid_entry.set_text(data[3])
self._tr_id_entry.set_text(data[4])
self._net_id_entry.set_text(data[5])
self._namespace_entry.set_text(data[6])
self._url_entry.set_text(data[10].replace("%3a", ":"))
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])
def _update_reference_entry(self):
if self._profile is Profile.ENIGMA_2:
self._reference_entry.set_text("{}:0:{}:{}:{}:{}:{}:0:0:0".format(self.get_type(),
self._srv_type_entry.get_text(),
self._sid_entry.get_text(),
self._tr_id_entry.get_text(),
self._net_id_entry.get_text(),
self._namespace_entry.get_text()))
def get_type(self):
return 1 if self._stream_type_combobox.get_active() == 0 else 4097
def on_entry_changed(self, entry):
if self._PATTERN.search(entry.get_text()):
entry.set_name(self._DIGIT_ENTRY_NAME)
else:
entry.set_name("GtkEntry")
self._update_reference_entry()
def on_url_changed(self, entry):
url = urlparse(entry.get_text())
entry.set_name("GtkEntry" if all([url.scheme, url.netloc, url.path]) else self._DIGIT_ENTRY_NAME)
def on_stream_type_changed(self, item):
self._update_reference_entry()
def save_enigma2_data(self):
name = self._name_entry.get_text().strip()
fav_id = ENIGMA2_FAV_ID_FORMAT.format(self.get_type(),
self._srv_type_entry.get_text(),
self._sid_entry.get_text(),
self._tr_id_entry.get_text(),
self._net_id_entry.get_text(),
self._namespace_entry.get_text(),
self._url_entry.get_text().replace(":", "%3a"),
name, name)
self.update_bouquet_data(name, fav_id)
def save_neutrino_data(self):
if self._action is Action.EDIT:
id_data = self._current_srv[7].split("::")
else:
id_data = ["", "", "0", None, None, None, None, "", "", "1"]
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()
def update_bouquet_data(self, name, fav_id):
if self._action is Action.EDIT:
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
self._model.set(self._model.get_iter(self._paths), {2: name, 7: fav_id})
else:
aggr = [None] * 10
s_type = BqServiceType.IPTV.name
srv = (None, None, name, None, None, s_type, None, fav_id, None)
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)
def is_data_correct(self):
for elem in (self._srv_type_entry, self._sid_entry, self._tr_id_entry, self._net_id_entry,
self._namespace_entry, self._url_entry):
if elem.get_name() == self._DIGIT_ENTRY_NAME:
return False
return True
if __name__ == "__main__":
pass

Binary file not shown.

View File

@@ -2,29 +2,41 @@ import os
from contextlib import suppress
from functools import lru_cache
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
from . import Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON
from .dialogs import show_dialog, DialogType
from .iptv import IptvDialog
from .search import SearchProvider
from . import Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON
from .dialogs import show_dialog, DialogType, get_chooser_dialog, WaitDialog, get_message
from .download_dialog import show_download_dialog
from .main_helper import edit_marker, insert_marker, move_items, edit, ViewTarget, set_flags, locate_in_services, \
scroll_to, get_base_model
from .main_helper import edit_marker, insert_marker, move_items, rename, ViewTarget, set_flags, locate_in_services, \
scroll_to, get_base_model, update_picons, copy_picon_reference, assign_picon, remove_picon, \
is_only_one_item_selected
from .picons_dialog import PiconsDialog
from .satellites_dialog import show_satellites_dialog
from .settings_dialog import show_settings_dialog
from .service_details_dialog import ServiceDetailsDialog, Action
class MainAppWindow:
_TV_TYPES = ("TV", "TV (HD)", "TV (UHD)", "TV (H264)")
_SERVICE_LIST_NAME = "services_list_store"
_FAV_LIST_NAME = "fav_list_store"
_BOUQUETS_LIST_NAME = "bouquets_tree_store"
# dynamically active elements depending on the selected view
_SERVICE_ELEMENTS = ("copy_tool_button", "to_fav_tool_button", "copy_menu_item", "services_to_fav_move_popup_item",
"services_edit_popup_item", "services_copy_popup_item", "filter_entry")
"services_edit_popup_item", "services_copy_popup_item", "services_picon_popup_item")
_BOUQUET_ELEMENTS = ("edit_tool_button", "new_tool_button",
"bouquets_new_popup_item", "bouquets_edit_popup_item")
@@ -35,10 +47,11 @@ class MainAppWindow:
_FAV_ELEMENTS = ("cut_tool_button", "paste_tool_button", "cut_menu_item",
"paste_menu_item", "fav_cut_popup_item", "fav_paste_popup_item", "import_m3u_tool_button",
"fav_import_m3u_popup_item", "fav_insert_marker_popup_item", "fav_edit_popup_item",
"fav_locate_popup_item")
"fav_locate_popup_item", "fav_picon_popup_item", "fav_add_iptv_popup_item")
_FAV_ONLY_ELEMENTS = ("import_m3u_tool_button", "fav_import_m3u_popup_item", "fav_insert_marker_popup_item",
"fav_edit_marker_popup_item")
_FAV_ENIGMA_ELEMENTS = ("fav_insert_marker_popup_item", "fav_edit_marker_popup_item")
_FAV_M3U_ELEMENTS = ("import_m3u_tool_button", "fav_import_m3u_popup_item", "fav_add_iptv_popup_item")
_LOCK_HIDE_ELEMENTS = ("locked_tool_button", "hide_tool_button")
@@ -51,7 +64,8 @@ class MainAppWindow:
"bouquets_remove_popup_item", "fav_remove_popup_item", "hide_tool_button",
"import_m3u_tool_button", "fav_import_m3u_popup_item", "fav_insert_marker_popup_item",
"fav_edit_marker_popup_item", "fav_edit_popup_item", "fav_locate_popup_item",
"services_copy_popup_item", "filter_entry")
"services_copy_popup_item", "services_picon_popup_item", "fav_picon_popup_item",
"services_add_new_popup_item", "fav_add_iptv_popup_item")
def __init__(self):
handlers = {"on_close_main_window": self.on_quit,
@@ -71,7 +85,7 @@ class MainAppWindow:
"on_cut": self.on_cut,
"on_copy": self.on_copy,
"on_paste": self.on_paste,
"on_edit": self.on_edit,
"on_edit": self.on_rename,
"on_delete": self.on_delete,
"on_new_bouquet": self.on_new_bouquet,
"on_bouquets_edit": self.on_bouquets_edit,
@@ -91,7 +105,18 @@ class MainAppWindow:
"on_fav_popup": self.on_fav_popup,
"on_locate_in_services": self.on_locate_in_services,
"on_picons_loader_show": self.on_picons_loader_show,
"on_filter_changed": self.on_filter_changed}
"on_filter_changed": self.on_filter_changed,
"on_assign_picon": self.on_assign_picon,
"on_remove_picon": self.on_remove_picon,
"on_reference_picon": self.on_reference_picon,
"on_filter_toggled": self.on_filter_toggled,
"on_search_toggled": self.on_search_toggled,
"on_search_down": self.on_search_down,
"on_search_up": self.on_search_up,
"on_search": self.on_search,
"on_service_edit": self.on_service_edit,
"on_services_add_new": self.on_services_add_new,
"on_iptv": self.on_iptv}
self.__options = get_config()
self.__profile = self.__options.get("profile")
@@ -101,9 +126,11 @@ class MainAppWindow:
self.__rows_buffer = []
self.__services = {}
self.__bouquets = {}
self.__picons = {}
self.__blacklist = set()
builder = Gtk.Builder()
builder.set_translation_domain("demon-editor")
builder.add_from_file(UI_RESOURCES_PATH + "main_window.glade")
builder.connect_signals(handlers)
self.__main_window = builder.get_object("main_window")
@@ -119,10 +146,12 @@ class MainAppWindow:
self.__bouquets_model = builder.get_object("bouquets_tree_store")
self.__status_bar = builder.get_object("status_bar")
self.__profile_label = builder.get_object("profile_label")
self.__status_bar.push(0, "Current IP: " + self.__options.get(self.__profile).get("host"))
self.__ip_label = builder.get_object("ip_label")
self.__ip_label.set_text(self.__options.get(self.__profile).get("host"))
self.__profile_label.set_text("Enigma2 v.4" if Profile(self.__profile) is Profile.ENIGMA_2 else "Neutrino-MP")
# dynamically active elements depending on the selected view
self.__tool_elements = {k: builder.get_object(k) for k in self.__DYNAMIC_ELEMENTS}
self.__picons_download_tool_button = builder.get_object("picons_download_tool_button")
self.__cas_label = builder.get_object("cas_label")
self.__fav_count_label = builder.get_object("fav_count_label")
self.__bouquets_count_label = builder.get_object("bouquets_count_label")
@@ -130,14 +159,25 @@ class MainAppWindow:
self.__radio_count_label = builder.get_object("radio_count_label")
self.__data_count_label = builder.get_object("data_count_label")
self.__fav_edit_marker_popup_item = builder.get_object("fav_edit_marker_popup_item")
# Filter
self.__services_model_filter = builder.get_object("services_model_filter")
self.__services_model_filter.set_visible_func(self.services_filter_function)
self.__filter_entry = builder.get_object("filter_entry")
self.init_drag_and_drop() # drag and drop
# Force ctrl press event for view. Multiple selections in lists only with Space key(as in file managers)!!!
self.__services_view.connect("key-press-event", self.force_ctrl)
self.__fav_view.connect("key-press-event", self.force_ctrl)
# Clipboard
self.__clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
# Wait dialog
self.__wait_dialog = WaitDialog(self.__main_window)
# Filter
self.__services_model_filter = builder.get_object("services_model_filter")
self.__services_model_filter.set_visible_func(self.services_filter_function)
self.__filter_entry = builder.get_object("filter_entry")
self.__filter_info_bar = builder.get_object("filter_info_bar")
# Search
self.__search_info_bar = builder.get_object("search_info_bar")
self.__search_provider = SearchProvider(self.__services_view, self.__fav_view, self.__bouquets_view,
self.__services, self.__bouquets,
builder.get_object("search_down_button"),
builder.get_object("search_up_button"))
self.__main_window.show()
def init_drag_and_drop(self):
@@ -192,7 +232,7 @@ class MainAppWindow:
def on_copy(self, view):
model, paths = view.get_selection().get_selected_rows()
itrs = [model.get_iter(path) for path in paths]
rows = [(0, *model.get(in_itr, 2, 3, 4, 5, 7, 14, 16)) for in_itr in itrs]
rows = [(0, *model.get(in_itr, 2, 3, 4, 5, 7, 16, 18, 8)) for in_itr in itrs]
self.__rows_buffer.extend(rows)
def on_paste(self, view):
@@ -221,16 +261,17 @@ class MainAppWindow:
self.__rows_buffer.clear()
self.on_view_focus(view, None)
def on_edit(self, view):
def on_rename(self, view):
model = get_base_model(view.get_model())
name = model.get_name()
if name == self._BOUQUETS_LIST_NAME:
self.on_bouquets_edit(view)
# edit(view, self.__main_window, ViewTarget.BOUQUET)
elif name == self._FAV_LIST_NAME:
edit(view, self.__main_window, ViewTarget.FAV, service_view=self.__services_view, channels=self.__services)
rename(view, self.__main_window, ViewTarget.FAV, service_view=self.__services_view,
channels=self.__services)
elif name == self._SERVICE_LIST_NAME:
edit(view, self.__main_window, ViewTarget.SERVICES, fav_view=self.__fav_view, channels=self.__services)
rename(view, self.__main_window, ViewTarget.SERVICES, fav_view=self.__fav_view, channels=self.__services)
def on_delete(self, item):
""" Delete selected items from views
@@ -343,11 +384,11 @@ class MainAppWindow:
def on_tool_edit(self, item):
""" Edit tool bar button """
if self.__services_view.is_focus():
self.on_edit(self.__services_view)
self.on_service_edit(self.__services_view)
elif self.__fav_view.is_focus():
self.on_edit(self.__fav_view)
self.on_service_edit(self.__fav_view)
elif self.__bouquets_view.is_focus():
self.on_edit(self.__bouquets_view)
self.on_rename(self.__bouquets_view)
def on_bouquets_edit(self, view):
""" Rename bouquets """
@@ -378,8 +419,7 @@ class MainAppWindow:
def get_selection(self, view):
""" Creates a string from the iterators of the selected rows """
model, paths = view.get_selection().get_selected_rows()
if model.get_model(): # needs think about it !
model = model.get_model().get_model()
model = get_base_model(model)
if len(paths) > 0:
itrs = [model.get_iter(path) for path in paths]
@@ -415,17 +455,17 @@ class MainAppWindow:
dest_index -= 1
for ext_row in ext_rows:
dest_index += 1
fav_id = ext_row[-2]
channel = self.__services[fav_id]
model.insert(dest_index, (0, channel.coded, channel.service, channel.locked, channel.hide,
channel.service_type, channel.pos, channel.fav_id))
fav_bouquet.insert(dest_index, channel.fav_id)
fav_id = ext_row[18]
ch = self.__services[fav_id]
model.insert(dest_index, (0, ch.coded, ch.service, ch.locked, ch.hide,
ch.service_type, ch.pos, ch.fav_id, self.__picons.get(ch.picon_id, None)))
fav_bouquet.insert(dest_index, ch.fav_id)
elif source == self._FAV_LIST_NAME:
in_itrs = [model.get_iter_from_string(itr) for itr in itrs]
in_rows = [model[in_itr][:] for in_itr in in_itrs]
for row in in_rows:
model.insert(dest_index, row)
fav_bouquet.insert(dest_index, row[4])
fav_bouquet.insert(dest_index, row[7])
for in_itr in in_itrs:
del fav_bouquet[int(model.get_path(in_itr)[0])]
model.remove(in_itr)
@@ -477,6 +517,7 @@ class MainAppWindow:
@run_idle
def open_data(self, data_path=None):
""" Opening data and fill views. """
self.__wait_dialog.show()
self.clear_current_data()
data_path = self.__options.get(self.__profile).get("data_dir_path") if data_path is None else data_path
@@ -485,11 +526,14 @@ class MainAppWindow:
self.append_bouquets(data_path)
self.append_services(data_path)
self.update_services_counts(len(self.__services_model))
self.update_picons()
except FileNotFoundError as e:
show_dialog(DialogType.ERROR, self.__main_window, getattr(e, "message", str(e)) +
"\n\nPlease, download files from receiver or setup your path for read data!")
show_dialog(DialogType.ERROR, self.__main_window, getattr(e, "message", str(e)) + "\n\n" +
get_message("Please, download files from receiver or setup your path for read data!"))
except SyntaxError as e:
show_dialog(DialogType.ERROR, self.__main_window, str(e))
finally:
self.__wait_dialog.hide()
def append_blacklist(self, data_path):
black_list = get_blacklist(data_path)
@@ -503,14 +547,15 @@ class MainAppWindow:
name, bt_type, locked, hidden = bt.name, bt.type, bt.locked, bt.hidden
self.__bouquets_model.append(parent, [name, locked, hidden, bt_type])
services = []
agr = [None] * 7
agr = [None] * 9
for srv in bt.services:
fav_id = srv.data
# IPTV and MARKER services
s_type = srv.type
if s_type is BqServiceType.MARKER or s_type is BqServiceType.IPTV:
self.__services[fav_id] = Service(*agr[0:3], srv.name, *agr[0:3],
s_type.name, *agr, srv.num, fav_id, None)
icon = IPTV_ICON if s_type is BqServiceType.IPTV else None
srv = Service(*agr[0:2], icon, srv.name, *agr[0:3], s_type.name, *agr, srv.num, fav_id, None)
self.__services[fav_id] = srv
services.append(fav_id)
self.__bouquets["{}:{}".format(name, bt_type)] = services
@@ -520,7 +565,7 @@ class MainAppWindow:
except Exception as e:
print(e)
log("Append services error: " + str(e))
show_dialog(DialogType.ERROR, self.__main_window, "Error opening data!")
show_dialog(DialogType.ERROR, self.__main_window, "Reading data error!")
else:
if services:
for srv in services:
@@ -543,9 +588,11 @@ class MainAppWindow:
return
path = self.__options.get(self.__profile).get("data_dir_path")
# deleting files in data dir(skipping dirs) :)
list(map(os.unlink, (os.path.join(path, f) for f in filter(
lambda f: f != "satellites.xml" and os.path.isfile(os.path.join(path, f)), os.listdir(path)))))
backup_path = path + "backup/"
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
# backup files in data dir(skipping dirs and satellites.xml)
for file in filter(lambda f: f != "satellites.xml" and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
shutil.move(os.path.join(path, file), backup_path + file)
bouquets = []
services_model = self.__services_view.get_model()
@@ -572,7 +619,7 @@ class MainAppWindow:
services = [Service(*row[:]) for row in services_model]
write_services(path, services, profile)
# removing bouquet files
if profile is profile.ENIGMA_2:
if profile is Profile.ENIGMA_2:
# blacklist
write_blacklist(path, self.__blacklist)
@@ -616,7 +663,8 @@ class MainAppWindow:
channel = self.__services.get(ch_id, None)
if channel:
self.__fav_model.append((num + 1, channel.coded, channel.service, channel.locked,
channel.hide, channel.service_type, channel.pos, channel.fav_id))
channel.hide, channel.service_type, channel.pos, channel.fav_id,
self.__picons.get(channel.picon_id, None)))
def is_bouquet_selected(self):
""" Checks whether the bouquet is selected
@@ -641,8 +689,8 @@ class MainAppWindow:
response = show_settings_dialog(self.__main_window, self.__options)
if response != Gtk.ResponseType.CANCEL:
profile = self.__options.get("profile")
self.__ip_label.set_text(self.__options.get(profile).get("host"))
if profile != self.__profile:
self.__status_bar.push(0, "Current IP: " + self.__options.get(profile).get("host"))
self.__profile_label.set_text("Enigma 2 v.4" if Profile(profile) is Profile.ENIGMA_2 else "Neutrino-MP")
self.__profile = profile
self.clear_current_data()
@@ -684,10 +732,13 @@ 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:
self.on_edit(view)
elif key == Gdk.KEY_space and model_name == self._FAV_LIST_NAME:
pass
elif ctrl and key == Gdk.KEY_R or key == Gdk.KEY_r or key == Gdk.KEY_F2:
self.on_rename(view)
elif ctrl and key == Gdk.KEY_E or key == Gdk.KEY_e:
if model_name == self._BOUQUETS_LIST_NAME:
self.on_rename(view)
return
self.on_service_edit(view)
elif key == Gdk.KEY_Left or key == Gdk.KEY_Right:
view.do_unselect_all(view)
@@ -697,7 +748,6 @@ class MainAppWindow:
open_data=self.open_data,
profile=Profile(self.__profile))
@run_idle
def on_view_focus(self, view, focus_event):
profile = Profile(self.__profile)
model = get_base_model(view.get_model())
@@ -714,12 +764,21 @@ class MainAppWindow:
self.__tool_elements[elem].set_sensitive(not_empty)
else:
is_service = model_name == self._SERVICE_LIST_NAME
bq_selected = False
if model_name == self._FAV_LIST_NAME:
bq_selected = self.is_bouquet_selected()
if profile is Profile.NEUTRINO_MP and bq_selected:
name, bq_type = bq_selected.split(":")
bq_selected = BqType(bq_type) is BqType.WEBTV
for elem in self._FAV_ELEMENTS:
if elem in ("paste_tool_button", "paste_menu_item", "fav_paste_popup_item"):
self.__tool_elements[elem].set_sensitive(not is_service and self.__rows_buffer)
elif elem in self._FAV_ONLY_ELEMENTS:
elif elem in self._FAV_ENIGMA_ELEMENTS:
if profile is Profile.ENIGMA_2:
self.__tool_elements[elem].set_sensitive(self.is_bouquet_selected() and not is_service)
self.__tool_elements[elem].set_sensitive(bq_selected and not is_service)
elif elem in self._FAV_M3U_ELEMENTS:
self.__tool_elements[elem].set_sensitive(bq_selected and not is_service)
else:
self.__tool_elements[elem].set_sensitive(not_empty and not is_service)
for elem in self._SERVICE_ELEMENTS:
@@ -732,11 +791,13 @@ class MainAppWindow:
for elem in self._COMMONS_ELEMENTS:
self.__tool_elements[elem].set_sensitive(not_empty)
self.__tool_elements["services_add_new_popup_item"].set_sensitive(len(self.__bouquets_model))
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)
@@ -749,9 +810,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):
@@ -773,7 +834,7 @@ class MainAppWindow:
for ch in self.__services.values():
ch_type = ch.service_type
if ch_type in ("TV", "TV (HD)"):
if ch_type in self._TV_TYPES:
tv_count += 1
elif ch_type == "Radio":
radio_count += 1
@@ -786,14 +847,7 @@ class MainAppWindow:
def on_import_m3u(self, item):
""" Imports iptv from m3u files. """
file_filter = Gtk.FileFilter()
file_filter.add_pattern("*.m3u")
file_filter.set_name("m3u files")
response = show_dialog(dialog_type=DialogType.CHOOSER,
transient=self.__main_window,
options=self.__options.get(self.__profile),
action_type=Gtk.FileChooserAction.OPEN,
file_filter=file_filter)
response = get_chooser_dialog(self.__main_window, self.__options.get(self.__profile), "*.m3u", "m3u files")
if response == Gtk.ResponseType.CANCEL:
return
@@ -801,7 +855,7 @@ class MainAppWindow:
show_dialog(DialogType.ERROR, self.__main_window, text="No m3u file is selected!")
return
channels = parse_m3u(response)
channels = parse_m3u(response, Profile(self.__profile))
bq_selected = self.is_bouquet_selected()
if channels and bq_selected:
bq_services = self.__bouquets.get(bq_selected)
@@ -811,6 +865,16 @@ class MainAppWindow:
bq_services.append(ch.fav_id)
self.update_bouquet_channels(self.__fav_model, None, bq_selected)
def on_iptv(self, item):
response = IptvDialog(self.__main_window,
self.__fav_view,
self.__services,
self.__bouquets.get(self.is_bouquet_selected(), None),
Profile(self.__profile),
Action.ADD).show()
if response != Gtk.ResponseType.CANCEL:
self.update_fav_num_column(self.__fav_model)
def on_insert_marker(self, view):
""" Inserts marker into bouquet services list. """
insert_marker(view, self.__bouquets, self.is_bouquet_selected(), self.__services, self.__main_window)
@@ -828,9 +892,21 @@ class MainAppWindow:
def on_locate_in_services(self, view):
locate_in_services(view, self.__services_view, self.__main_window)
@run_idle
def on_picons_loader_show(self, item):
dialog = PiconsDialog(self.__main_window, self.__options.get(self.__profile), Profile(self.__profile))
ids = {}
if Profile(self.__profile) is Profile.ENIGMA_2:
for r in self.__services_model:
data = r[9].split("_")
ids["{}:{}:{}".format(data[3], data[5], data[6])] = r[9]
dialog = PiconsDialog(self.__main_window, self.__options, ids, Profile(self.__profile))
dialog.show()
self.update_picons()
@run_idle
def on_filter_toggled(self, toggle_button: Gtk.ToggleToolButton):
self.__filter_info_bar.set_visible(toggle_button.get_active())
@run_idle
def on_filter_changed(self, entry):
@@ -840,7 +916,80 @@ class MainAppWindow:
if self.__services_model_filter is None or self.__services_model_filter == "None":
return True
else:
return self.__filter_entry.get_text() in str(model.get(iter, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14))
return self.__filter_entry.get_text() in str(model.get(iter, 3, 6, 7, 10, 11, 12, 13, 14, 15, 16))
def on_search_toggled(self, toggle_button: Gtk.ToggleToolButton):
self.__search_info_bar.set_visible(toggle_button.get_active())
def on_search_down(self, item):
self.__search_provider.on_search_down()
def on_search_up(self, item):
self.__search_provider.on_search_up()
@run_idle
def on_search(self, entry):
self.__search_provider.search(entry.get_text())
@run_idle
def on_service_edit(self, view):
model, paths = view.get_selection().get_selected_rows()
if is_only_one_item_selected(paths, self.__main_window):
model_name = get_base_model(model).get_name()
if model_name == self._FAV_LIST_NAME:
srv_type = model.get_value(model.get_iter(paths), 5)
if srv_type == BqServiceType.MARKER.name:
return self.on_rename(view)
elif srv_type == BqServiceType.IPTV.name:
return IptvDialog(self.__main_window,
self.__fav_view,
self.__services,
self.__bouquets.get(self.is_bouquet_selected(), None),
Profile(self.__profile),
Action.EDIT).show()
self.on_locate_in_services(view)
dialog = ServiceDetailsDialog(self.__main_window,
self.__options,
self.__services_view,
self.__services,
self.__bouquets)
dialog.show()
def on_services_add_new(self, item):
dialog = ServiceDetailsDialog(self.__main_window,
self.__options,
self.__services_view,
self.__services,
self.__bouquets,
action=Action.ADD)
dialog.show()
@run_idle
def update_picons(self):
update_picons(self.__options.get(self.__profile).get("picons_dir_path"), self.__picons, self.__services_model)
def on_assign_picon(self, view):
assign_picon(self.get_target_view(view),
self.__services_view,
self.__fav_view,
self.__main_window,
self.__picons,
self.__options.get(self.__profile),
self.__services)
def on_remove_picon(self, view):
remove_picon(self.get_target_view(view),
self.__services_view,
self.__fav_view, self.__picons,
self.__options.get(self.__profile))
def on_reference_picon(self, view):
""" Copying picon id to clipboard """
copy_picon_reference(self.get_target_view(view), view, self.__services, self.__clipboard, self.__main_window)
def get_target_view(self, view):
return ViewTarget.SERVICES if Gtk.Buildable.get_name(view) == "services_tree_view" else ViewTarget.FAV
def start_app():

View File

@@ -1,11 +1,16 @@
""" This is helper module for ui """
from enum import Enum
import os
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
from .dialogs import show_dialog, DialogType, get_chooser_dialog
class ViewTarget(Enum):
@@ -28,15 +33,16 @@ def insert_marker(view, bouquets, selected_bouquet, channels, parent_window):
return
# Searching for max num value in all marker services (if empty default = 0)
max_num = max(map(lambda num: int(num.data_id, 16),
max_num = max(map(lambda num: int(num.data_id, 18),
filter(lambda ch: ch.service_type == BqServiceType.MARKER.name, channels.values())), default=0)
max_num = '{:x}'.format(max_num + 1)
fav_id = "1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n".format(max_num, response, response)
s_type = BqServiceType.MARKER.name
model, paths = view.get_selection().get_selected_rows()
itr = model.insert_before(model.get_iter(paths[0]), (None, None, response, None, None, s_type, None, fav_id))
channels[fav_id] = Service(None, None, None, response, None, None, None, s_type, *[None] * 7, max_num, fav_id, None)
marker = (None, None, response, None, None, s_type, None, fav_id, None)
itr = model.insert_before(model.get_iter(paths[0]), marker) if paths else model.insert(0, marker)
bouquets[selected_bouquet].insert(model.get_path(itr)[0], fav_id)
channels[fav_id] = Service(None, None, None, response, None, None, None, s_type, *[None] * 9, max_num, fav_id, None)
def edit_marker(view, bouquets, selected_bouquet, channels, parent_window):
@@ -53,7 +59,7 @@ def edit_marker(view, bouquets, selected_bouquet, channels, parent_window):
old_ch = channels.pop(fav_id, None)
new_fav_id = "{}::{}\n#DESCRIPTION {}\n".format(fav_id.split("::")[0], response, response)
model.set(itr, {2: response, 7: new_fav_id})
channels[new_fav_id] = Service(*old_ch[0:3], response, *old_ch[4:15], old_ch.data_id, new_fav_id, None)
channels[new_fav_id] = old_ch._replace(service=response, fav_id=new_fav_id)
bq_services.pop(index)
bq_services.insert(index, new_fav_id)
@@ -90,9 +96,9 @@ def move_items(key, view):
model.move_after(itr, down_itr)
# ***************** Edit *******************#
# ***************** Rename *******************#
def edit(view, parent_window, target, fav_view=None, service_view=None, channels=None):
def rename(view, parent_window, target, fav_view=None, service_view=None, channels=None):
model, paths = view.get_selection().get_selected_rows()
model = get_base_model(model)
@@ -107,7 +113,7 @@ def edit(view, parent_window, target, fav_view=None, service_view=None, channels
channel_name = None
if target is ViewTarget.SERVICES:
name, fav_id = model.get(itr, 3, 16)
name, fav_id = model.get(itr, 3, 18)
f_id = fav_id
response = show_dialog(DialogType.INPUT, parent_window, name)
if response == Gtk.ResponseType.CANCEL:
@@ -130,14 +136,14 @@ def edit(view, parent_window, target, fav_view=None, service_view=None, channels
model.set_value(itr, 2, response)
if service_view is not None:
for row in service_view.get_model():
if row[16] == fav_id:
for row in get_base_model(service_view.get_model()):
if row[18] == fav_id:
row[3] = response
break
old_ch = channels.get(f_id, None)
if old_ch:
channels[f_id] = Service(*old_ch[0:3], channel_name, *old_ch[4:])
channels[f_id] = old_ch._replace(service=channel_name)
# ***************** Flags *******************#
@@ -160,15 +166,15 @@ 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:
fav_ids = [model.get_value(model.get_iter(path), 7) for path in paths]
srv_model = get_base_model(services_view.get_model())
srv_paths = [row.path for row in srv_model if row[16] in fav_ids]
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
@@ -182,18 +188,20 @@ def set_lock(blacklist, channels, model, paths, target, services_model):
for path in paths:
itr = model.get_iter(path)
fav_id = model.get_value(itr, 16 if target is ViewTarget.SERVICES else 7)
fav_id = model.get_value(itr, 18 if target is ViewTarget.SERVICES else 7)
channel = channels.get(fav_id, None)
if channel:
bq_id = to_bouquet_id(channel)
if not bq_id:
continue
blacklist.discard(bq_id) if locked else blacklist.add(bq_id)
model.set_value(itr, col_num, None if locked else LOCKED_ICON)
channels[fav_id] = Service(*channel[:4], None if locked else LOCKED_ICON, *channel[5:])
channels[fav_id] = channel._replace(locked=None if locked else LOCKED_ICON)
ids.append(fav_id)
if target is ViewTarget.FAV and ids:
for ch in services_model:
if ch[16] in ids:
if ch[18] in ids:
ch[4] = None if locked else LOCKED_ICON
@@ -215,28 +223,28 @@ def set_hide(channels, model, paths):
value = int(flag[2:]) if flag else 0
if not hide:
if value in FLAG.hide_values():
if Flag.is_hide(value):
continue # skip if already hidden
value += FLAG.HIDE.value
value += Flag.HIDE.value
else:
if value not in FLAG.hide_values():
if not Flag.is_hide(value):
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]
else:
value = "f:{}".format(value) if value > 10 else "f:0{}".format(value)
value = "f:{:02d}".format(value)
if index is not None:
flags[index] = value
else:
flags.append(value)
model.set_value(itr, 0, (",".join(reversed(sorted(flags)))))
fav_id = model.get_value(itr, 16)
fav_id = model.get_value(itr, 18)
channel = channels.get(fav_id, None)
if channel:
channels[fav_id] = Service(*channel[:5], None if hide else HIDE_ICON, *channel[6:])
channels[fav_id] = channel._replace(hide=None if hide else HIDE_ICON)
def has_locked_hide(model, paths, col_num):
@@ -260,7 +268,7 @@ def locate_in_services(fav_view, services_view, parent_window):
fav_id = model.get_value(model.get_iter(paths[0]), 7)
for index, row in enumerate(services_view.get_model()):
if row[16] == fav_id:
if row[18] == fav_id:
scroll_to(index, services_view)
break
@@ -275,6 +283,134 @@ def scroll_to(index, view, paths=None):
selection.select_path(index)
# ***************** Picons *********************#
def update_picons(path, picons, model):
if not os.path.exists(path):
return
for file in os.listdir(path):
picons[file] = get_picon_pixbuf(path + file)
for r in model:
model.set_value(model.get_iter(r.path), 8, picons.get(r[9], None))
def assign_picon(target, srv_view, fav_view, transient, picons, options, services):
view = srv_view if target is ViewTarget.SERVICES else fav_view
model, paths = view.get_selection().get_selected_rows()
if not is_only_one_item_selected(paths, transient):
return
response = get_chooser_dialog(transient, options, "*.png", "png files")
if response == Gtk.ResponseType.CANCEL:
return
if not str(response).endswith(".png"):
show_dialog(DialogType.ERROR, transient, text="No png file is selected!")
return
picon_pos = 8
model = get_base_model(model)
itr = model.get_iter(paths)
fav_id = model.get_value(itr, 18 if target is ViewTarget.SERVICES else 7)
picon_id = services.get(fav_id)[9]
if picon_id:
picon_file = options.get("picons_dir_path") + picon_id
if os.path.isfile(response):
shutil.copy(response, picon_file)
picon = get_picon_pixbuf(picon_file)
picons[picon_id] = picon
model.set_value(itr, picon_pos, picon)
if target is ViewTarget.SERVICES:
set_picon(fav_id, fav_view.get_model(), picon, 7, picon_pos)
else:
set_picon(fav_id, get_base_model(srv_view.get_model()), picon, 18, picon_pos)
def set_picon(fav_id, model, picon, fav_id_pos, picon_pos):
for row in model:
if row[fav_id_pos] == fav_id:
row[picon_pos] = picon
break
def remove_picon(target, srv_view, fav_view, picons, options):
view = srv_view if target is ViewTarget.SERVICES else fav_view
model, paths = view.get_selection().get_selected_rows()
model = get_base_model(model)
fav_ids = []
picon_ids = []
picon_pos = 8 # picon position is equal for services and fav
for path in paths:
itr = model.get_iter(path)
model.set_value(itr, picon_pos, None)
if target is ViewTarget.SERVICES:
fav_ids.append(model.get_value(itr, 18))
picon_ids.append(model.get_value(itr, 9))
else:
fav_ids.append(model.get_value(itr, 7))
def remove(md, path, itr):
if md.get_value(itr, 7 if target is ViewTarget.SERVICES else 18) in fav_ids:
md.set_value(itr, picon_pos, None)
if target is ViewTarget.FAV:
picon_ids.append(md.get_value(itr, 9))
fav_view.get_model().foreach(remove) if target is ViewTarget.SERVICES else get_base_model(
srv_view.get_model()).foreach(remove)
pions_path = options.get("picons_dir_path")
backup_path = options.get("data_dir_path") + "backup/picons/"
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
for p_id in picon_ids:
picons[p_id] = None
src = pions_path + p_id
if os.path.isfile(src):
shutil.move(src, backup_path + p_id)
def copy_picon_reference(target, view, services, clipboard, transient):
""" Copying picon id to clipboard """
model, paths = view.get_selection().get_selected_rows()
if not is_only_one_item_selected(paths, transient):
return
if target is ViewTarget.SERVICES:
picon_id = model.get_value(model.get_iter(paths), 9)
if picon_id:
clipboard.set_text(picon_id.rstrip(".png"), -1)
else:
show_dialog(DialogType.ERROR, transient, "No reference is present!")
elif target is ViewTarget.FAV:
fav_id = model.get_value(model.get_iter(paths), 7)
srv = services.get(fav_id, None)
if srv and srv.picon_id:
clipboard.set_text(srv.picon_id.rstrip(".png"), -1)
else:
show_dialog(DialogType.ERROR, transient, "No reference is present!")
def is_only_one_item_selected(paths, transient):
if len(paths) > 1:
show_dialog(DialogType.ERROR, transient, "Please, select only one item!")
return False
if not paths:
show_dialog(DialogType.ERROR, transient, "No selected item!")
return False
return True
def get_picon_pixbuf(path):
return GdkPixbuf.Pixbuf.new_from_file_at_scale(filename=path, width=32, height=32, preserve_aspect_ratio=True)
# ***************** Others *********************#
def update_entry_data(entry, dialog, options):

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,20 +7,20 @@ from gi.repository import GLib, GdkPixbuf
from app.commons import run_idle, run_task
from app.ftp import upload_data, DownloadDataType
from app.picons.picons import PiconsParser, parse_providers, Provider
from app.picons.picons import PiconsParser, parse_providers, Provider, convert_to
from app.properties import Profile
from . import Gtk, Gdk, UI_RESOURCES_PATH
from .dialogs import show_dialog, DialogType
from . import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .dialogs import show_dialog, DialogType, get_message
from .main_helper import update_entry_data
class PiconsDialog:
def __init__(self, transient, options, profile=Profile.ENIGMA_2):
def __init__(self, transient, options, picon_ids, profile=Profile.ENIGMA_2):
self._picon_ids = picon_ids
self._TMP_DIR = tempfile.gettempdir() + "/"
self._BASE_URL = "www.lyngsat.com/packages/"
self._PATTERN = re.compile("^https://www\.lyngsat\.com/[\w-]+\.html$")
self._current_process = None
self._picons_path = options.get("picons_dir_path", "")
self._terminate = False
handlers = {"on_receive": self.on_receive,
@@ -31,9 +31,13 @@ class PiconsDialog:
"on_info_bar_close": self.on_info_bar_close,
"on_picons_dir_open": self.on_picons_dir_open,
"on_selected_toggled": self.on_selected_toggled,
"on_url_changed": self.on_url_changed}
"on_url_changed": self.on_url_changed,
"on_position_edited": self.on_position_edited,
"on_notebook_switch_page": self.on_notebook_switch_page,
"on_convert": self.on_convert}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "picons_dialog.glade",
("picons_dialog", "receive_image", "providers_list_store"))
builder.connect_signals(handlers)
@@ -52,6 +56,10 @@ class PiconsDialog:
self._message_label = builder.get_object("info_bar_message_label")
self._load_providers_tool_button = builder.get_object("load_providers_tool_button")
self._receive_tool_button = builder.get_object("receive_tool_button")
self._convert_tool_button = builder.get_object("convert_tool_button")
self._enigma2_path_button = builder.get_object("enigma2_path_button")
self._save_to_button = builder.get_object("save_to_button")
self._send_tool_button = builder.get_object("send_tool_button")
self._enigma2_radio_button = builder.get_object("enigma2_radio_button")
self._neutrino_mp_radio_button = builder.get_object("neutrino_mp_radio_button")
self._resize_no_radio_button = builder.get_object("resize_no_radio_button")
@@ -62,13 +70,19 @@ class PiconsDialog:
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)
self._properties = options
self._properties = options.get(profile.value)
self._profile = profile
self._ip_entry.set_text(options.get("host", ""))
self._picons_entry.set_text(options.get("picons_path", ""))
self._picons_path = options.get("picons_dir_path", "")
self._ip_entry.set_text(self._properties.get("host", ""))
self._picons_entry.set_text(self._properties.get("picons_path", ""))
self._picons_path = self._properties.get("picons_dir_path", "")
self._picons_dir_entry.set_text(self._picons_path)
self._enigma2_picons_path = self._picons_path
if profile is Profile.NEUTRINO_MP:
self._enigma2_picons_path = options.get(Profile.ENIGMA_2.value).get("picons_dir_path", "")
if not len(self._picon_ids) and self._profile is Profile.ENIGMA_2:
message = get_message("To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window.")
self.show_info_message(message, Gtk.MessageType.WARNING)
def show(self):
self._dialog.run()
@@ -83,18 +97,18 @@ class PiconsDialog:
stderr=subprocess.PIPE,
universal_newlines=True)
GLib.io_add_watch(self._current_process.stderr, GLib.IO_IN, self.write_to_buffer)
self.append_providers(url)
@run_task
def append_providers(self, url):
model = self._providers_tree_view.get_model()
model.clear()
self.update_receive_button_state()
self.append_providers(url, model)
@run_task
def append_providers(self, url, model):
self._current_process.wait()
providers = parse_providers(self._TMP_DIR + url[url.find("w"):])
if providers:
for p in providers:
logo = self.get_pixbuf(p[0])
model.append((logo, p.name, p.url, p.on_id, p.selected))
model.append((self.get_pixbuf(p[0]), p.name, p.pos, p.url, p.on_id, p.selected))
self.update_receive_button_state()
def get_pixbuf(self, img_url):
@@ -114,14 +128,21 @@ class PiconsDialog:
self._terminate = False
self._expander.set_expanded(True)
for prv in self.get_selected_providers():
providers = self.get_selected_providers()
for prv in providers:
if not prv[2] and prv[2][:-2].isdigit():
self.show_info_message(
get_message("Specify the correct position value for the provider!"), Gtk.MessageType.ERROR)
return
for prv in providers:
if self._terminate:
break
self.process_provider(Provider(*prv))
def process_provider(self, provider):
url = provider.url
self.show_info_message("Please, wait...", Gtk.MessageType.INFO)
def process_provider(self, prv):
url = prv.url
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
@@ -129,9 +150,11 @@ class PiconsDialog:
GLib.io_add_watch(self._current_process.stderr, GLib.IO_IN, self.write_to_buffer)
self._current_process.wait()
path = self._TMP_DIR + self._BASE_URL + url[url.rfind("/") + 1:]
PiconsParser.parse(path, self._picons_path, self._TMP_DIR, provider.on_id, self.get_picons_format())
pos = "".join(c for c in prv.pos if c.isdigit())
PiconsParser.parse(path, self._picons_path, self._TMP_DIR, prv.on_id, pos,
self._picon_ids, self.get_picons_format())
self.resize(self._picons_path)
self.show_info_message("Done", Gtk.MessageType.INFO)
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
def write_to_buffer(self, fd, condition):
if condition == GLib.IO_IN:
@@ -155,7 +178,7 @@ class PiconsDialog:
if self._resize_no_radio_button.get_active():
return
self.show_info_message("Resizing...", Gtk.MessageType.INFO)
self.show_info_message(get_message("Resizing..."), Gtk.MessageType.INFO)
command = "mogrify -resize {}! *.png".format(
"320x240" if self._resize_220_132_radio_button.get_active() else "100x60").split()
self._current_process = subprocess.Popen(command, universal_newlines=True, cwd=path)
@@ -177,7 +200,7 @@ class PiconsDialog:
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
self.show_info_message("Please, wait...", Gtk.MessageType.INFO)
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
self.upload_picons()
@run_task
@@ -189,7 +212,7 @@ class PiconsDialog:
upload_data(properties=self._properties,
download_type=DownloadDataType.PICONS,
profile=self._profile,
callback=lambda: self.show_info_message("Done!", Gtk.MessageType.INFO))
callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@@ -206,7 +229,7 @@ class PiconsDialog:
@run_idle
def on_selected_toggled(self, toggle, path):
model = self._providers_tree_view.get_model()
model.set_value(model.get_iter(path), 4, not toggle.get_active())
model.set_value(model.get_iter(path), 5, not toggle.get_active())
self.update_receive_button_state()
def on_url_changed(self, entry):
@@ -214,13 +237,45 @@ class PiconsDialog:
entry.set_name("GtkEntry" if suit else "digit-entry")
self._load_providers_tool_button.set_sensitive(suit if suit else False)
def on_position_edited(self, render, path, value):
model = self._providers_tree_view.get_model()
model.set_value(model.get_iter(path), 2, value)
@run_idle
def on_notebook_switch_page(self, nb, box, tab_num):
self._load_providers_tool_button.set_visible(not tab_num)
self._receive_tool_button.set_visible(not tab_num)
self._convert_tool_button.set_visible(tab_num)
self._send_tool_button.set_sensitive(not tab_num)
if self._enigma2_path_button.get_filename() is None:
self._enigma2_path_button.set_current_folder(self._enigma2_picons_path)
@run_idle
def on_convert(self, item):
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
picons_path = self._enigma2_path_button.get_filename()
save_path = self._save_to_button.get_filename()
if not picons_path or not save_path:
show_dialog(DialogType.ERROR, transient=self._dialog, text="Select paths!")
return
self._expander.set_expanded(True)
convert_to(src_path=picons_path,
dest_path=save_path,
profile=Profile.ENIGMA_2,
callback=self.append_output,
done_callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
@run_idle
def update_receive_button_state(self):
self._receive_tool_button.set_sensitive(len(self.get_selected_providers()) > 0)
def get_selected_providers(self):
""" returns selected providers """
return [r for r in self._providers_tree_view.get_model() if r[4]]
return [r for r in self._providers_tree_view.get_model() if r[5]]
@run_idle
def show_dialog(self, message, dialog_type):

View File

@@ -66,6 +66,9 @@
<row>
<col id="0" translatable="yes">9/10</col>
</row>
<row>
<col id="0" translatable="yes">Auto</col>
</row>
</data>
</object>
<object class="GtkListStore" id="mod_store">
@@ -243,6 +246,8 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="margin_right">5</property>
<property name="margin_bottom">2</property>
<property name="use_stock">True</property>
</object>
<packing>
@@ -585,7 +590,7 @@
<child>
<object class="GtkTreeViewColumn" id="freq_column">
<property name="resizable">True</property>
<property name="title" translatable="yes">Freq.</property>
<property name="title" translatable="yes">Freq</property>
<property name="expand">True</property>
<child>
<object class="GtkCellRendererText" id="frequency_cellrenderertext"/>
@@ -611,7 +616,7 @@
<child>
<object class="GtkTreeViewColumn" id="pol_column">
<property name="resizable">True</property>
<property name="title" translatable="yes">Pol.</property>
<property name="title" translatable="yes">Pol</property>
<property name="expand">True</property>
<child>
<object class="GtkCellRendererText" id="sat_pol_cellrenderertext"/>
@@ -624,7 +629,7 @@
<child>
<object class="GtkTreeViewColumn" id="fec_column">
<property name="resizable">True</property>
<property name="title" translatable="yes">Fec.</property>
<property name="title" translatable="yes">FEC</property>
<property name="expand">True</property>
<child>
<object class="GtkCellRendererText" id="set_fec_cellrenderertext"/>
@@ -650,7 +655,7 @@
<child>
<object class="GtkTreeViewColumn" id="mod_column">
<property name="resizable">True</property>
<property name="title" translatable="yes">Mod.</property>
<property name="title" translatable="yes">Mod</property>
<property name="expand">True</property>
<child>
<object class="GtkCellRendererText" id="mod_cellrenderertext"/>
@@ -741,6 +746,17 @@
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator5">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -1060,7 +1076,7 @@
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Freq.</property>
<property name="label" translatable="yes">Freq</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -1082,7 +1098,7 @@
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Pol.</property>
<property name="label" translatable="yes">Pol</property>
</object>
<packing>
<property name="left_attach">2</property>
@@ -1093,7 +1109,7 @@
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Fec.</property>
<property name="label" translatable="yes">FEC</property>
</object>
<packing>
<property name="left_attach">3</property>
@@ -1104,7 +1120,7 @@
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Sys.</property>
<property name="label" translatable="yes">System</property>
</object>
<packing>
<property name="left_attach">4</property>
@@ -1115,7 +1131,7 @@
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Mod.</property>
<property name="label" translatable="yes">Mod</property>
</object>
<packing>
<property name="left_attach">5</property>
@@ -1348,7 +1364,7 @@
<object class="GtkLabel" id="expander_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Extra</property>
<property name="label" translatable="yes">Extra:</property>
</object>
</child>
</object>

View File

@@ -3,8 +3,8 @@ from math import fabs
from app.commons import run_idle
from app.eparser import get_satellites, write_satellites, Satellite, Transponder
from . import Gtk, Gdk, UI_RESOURCES_PATH
from .dialogs import show_dialog, DialogType
from . import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .dialogs import show_dialog, DialogType, WaitDialog
from .main_helper import move_items, scroll_to
@@ -15,7 +15,7 @@ def show_satellites_dialog(transient, options):
class SatellitesDialog:
__slots__ = ["_dialog", "_data_path", "_stores", "_options", "_sat_view"]
__slots__ = ["_dialog", "_data_path", "_stores", "_options", "_sat_view", "_wait_dialog"]
_aggr = [None for x in range(9)] # aggregate
@@ -38,6 +38,7 @@ class SatellitesDialog:
"on_quit": self.on_quit}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("satellites_editor_dialog", "satellites_tree_store",
"popup_menu", "add_popup_menu", "add_menu_icon"))
@@ -50,6 +51,7 @@ class SatellitesDialog:
self._dialog.set_transient_for(transient)
self._dialog.get_content_area().set_border_width(0) # The width of the border around the app dialog area!
self._sat_view = builder.get_object("satellites_editor_tree_view")
self._wait_dialog = WaitDialog(self._dialog)
# Setting the last size of the dialog window if it was saved
window_size = self._options.get("sat_editor_window_size", None)
if window_size:
@@ -132,6 +134,7 @@ class SatellitesDialog:
def on_satellites_list_load(self, model):
""" Load satellites data into model """
try:
self._wait_dialog.show()
satellites = get_satellites(self._data_path)
except FileNotFoundError as e:
show_dialog(DialogType.ERROR, self._dialog, getattr(e, "message", str(e)) +
@@ -139,6 +142,8 @@ class SatellitesDialog:
else:
model.clear()
self.append_data(model, satellites)
finally:
self._wait_dialog.hide()
@run_idle
def append_data(self, model, satellites):
@@ -304,6 +309,7 @@ class TransponderDialog:
handlers = {"on_entry_changed": self.on_entry_changed}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("transponder_dialog",
"pol_store", "fec_store",
@@ -387,6 +393,7 @@ class SatelliteDialog:
def __init__(self, transient, satellite: Satellite = None):
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("satellite_dialog", "side_store", "pos_adjustment"))

59
app/ui/search.py Normal file
View File

@@ -0,0 +1,59 @@
""" This is helper module for search features """
class SearchProvider:
def __init__(self, srv_view, fav_view, bqs_view, services, bouquets, down_button, up_button):
self._paths = []
self._current_index = -1
self._max_indexes = 0
self._srv_view = srv_view
self._fav_view = fav_view
self._bqs_view = bqs_view
self._services = services
self._bouquets = bouquets
self._up_button = up_button
self._down_button = down_button
def search(self, text, ):
self._current_index = -1
self._paths.clear()
for view in self._srv_view, self._fav_view:
model = view.get_model()
selection = view.get_selection()
selection.unselect_all()
if not text:
continue
text = text.upper()
for r in model:
if text in str(r[:]).upper():
path = r.path
selection.select_path(r.path)
self._paths.append((view, path))
self._max_indexes = len(self._paths) - 1
if self._max_indexes > 0:
self.on_search_down()
def scroll_to(self, index):
view, path = self._paths[index]
view.scroll_to_cell(path, None)
self.update_navigation_buttons()
def on_search_down(self):
if self._current_index < self._max_indexes:
self._current_index += 1
self.scroll_to(self._current_index)
def on_search_up(self):
if self._current_index > -1:
self._current_index -= 1
self.scroll_to(self._current_index)
def update_navigation_buttons(self):
self._up_button.set_sensitive(self._current_index > 0)
self._down_button.set_sensitive(self._current_index < self._max_indexes)
if __name__ == "__main__":
pass

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,594 @@
import re
import os
from app.commons import run_idle
from app.eparser import Service
from app.eparser.ecommons import MODULATION, Inversion, ROLL_OFF, Pilot, Flag, Pids, POLARIZATION, \
get_key_by_value, get_value_by_name, FEC_DEFAULT, PLS_MODE, SERVICE_TYPE
from app.properties import Profile
from . import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, TEXT_DOMAIN
from .dialogs import show_dialog, DialogType, Action
from .main_helper import get_base_model
class ServiceDetailsDialog:
_ENIGMA2_DATA_ID = "{:04x}:{:08x}:{:04x}:{:04x}:{}:{}"
_ENIGMA2_FAV_ID = "{:X}:{:X}:{:X}:{:X}"
_ENIGMA2_TRANSPONDER_DATA = "{} {}:{}:{}:{}:{}:{}:{}"
_NEUTRINO_FAV_ID = "{:x}:{:x}:{:x}"
_NEUTRINO_TRANSPONDER_DATA = "{:04x}:{:04x}:{}:{}:{}:{}:{}:{}:{}"
_DIGIT_ENTRY_ELEMENTS = ("bitstream_entry", "pcm_entry", "video_pid_entry", "pcr_pid_entry", "srv_type_entry",
"ac3_pid_entry", "ac3plus_pid_entry", "acc_pid_entry", "he_acc_pid_entry",
"teletext_pid_entry", "pls_code_entry", "stream_id_entry", "tr_flag_entry",
"audio_pid_entry")
_NOT_EMPTY_DIGIT_ELEMENTS = ("sid_entry", "freq_entry", "rate_entry", "transponder_id_entry", "network_id_entry",
"namespace_entry", "srv_type_entry")
_DIGIT_ENTRY_NAME = "digit-entry"
def __init__(self, transient, options, view, services, bouquets, action=Action.EDIT):
handlers = {"on_system_changed": self.on_system_changed,
"on_save": self.on_save,
"on_create_new": self.on_create_new,
"on_tr_edit_toggled": self.on_tr_edit_toggled,
"update_reference": self.update_reference,
"on_cas_entry_changed": self.on_cas_entry_changed,
"on_digit_entry_changed": self.on_digit_entry_changed,
"on_non_empty_entry_changed": self.on_non_empty_entry_changed}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_from_file(UI_RESOURCES_PATH + "service_details_dialog.glade")
builder.connect_signals(handlers)
self._builder = builder
self._dialog = builder.get_object("service_details_dialog")
self._dialog.set_transient_for(transient)
self._profile = Profile(options["profile"])
self._satellites_xml_path = options.get(self._profile.value)["data_dir_path"] + "satellites.xml"
self._picons_dir_path = options.get(self._profile.value)["picons_dir_path"]
self._services_view = view
self._action = action
self._old_service = None
self._services = services
self._bouquets = bouquets
self._transponder_services_iters = None
self._current_model = None
self._current_itr = None
# Patterns
self._DIGIT_PATTERN = re.compile("\D")
self._NON_EMPTY_PATTERN = re.compile("(?:^[\s]*$|\D)")
self._CAID_PATTERN = re.compile("(?:^[\s]*$)|(C:[0-9a-z]{4})(,C:[0-9a-z]{4})*")
# Buttons
self._apply_button = builder.get_object("apply_button")
self._create_button = builder.get_object("create_button")
# style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
# initialization only digit elements
self._digit_elements = {k: builder.get_object(k) for k in self._DIGIT_ENTRY_ELEMENTS}
for elem in self._digit_elements.values():
elem.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
# initialization of non empty elements
self._non_empty_elements = {k: builder.get_object(k) for k in self._NOT_EMPTY_DIGIT_ELEMENTS}
for elem in self._non_empty_elements.values():
elem.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
self._sid_entry = self._non_empty_elements.get("sid_entry")
self._bitstream_entry = self._digit_elements.get("bitstream_entry")
self._pcm_entry = self._digit_elements.get("pcm_entry")
self._video_pid_entry = self._digit_elements.get("video_pid_entry")
self._pcr_pid_entry = self._digit_elements.get("pcr_pid_entry")
self._audio_pid_entry = self._digit_elements.get("audio_pid_entry")
self._ac3_pid_entry = self._digit_elements.get("ac3_pid_entry")
self._ac3plus_pid_entry = self._digit_elements.get("ac3plus_pid_entry")
self._acc_pid_entry = self._digit_elements.get("acc_pid_entry")
self._he_acc_pid_entry = self._digit_elements.get("he_acc_pid_entry")
self._teletext_pid_entry = self._digit_elements.get("teletext_pid_entry")
self._transponder_id_entry = self._non_empty_elements.get("transponder_id_entry")
self._network_id_entry = self._non_empty_elements.get("network_id_entry")
self._freq_entry = self._non_empty_elements.get("freq_entry")
self._rate_entry = self._non_empty_elements.get("rate_entry")
self._pls_code_entry = self._digit_elements.get("pls_code_entry")
self._stream_id_entry = self._digit_elements.get("stream_id_entry")
self._tr_flag_entry = self._digit_elements.get("tr_flag_entry")
self._namespace_entry = self._non_empty_elements.get("namespace_entry")
# Service elements
self._name_entry = builder.get_object("name_entry")
self._package_entry = builder.get_object("package_entry")
self._srv_type_entry = self._non_empty_elements.get("srv_type_entry")
self._service_type_combo_box = builder.get_object("service_type_combo_box")
self._cas_entry = builder.get_object("cas_entry")
self._reference_entry = builder.get_object("reference_entry")
self._keep_check_button = builder.get_object("keep_check_button")
self._hide_check_button = builder.get_object("hide_check_button")
self._use_pids_check_button = builder.get_object("use_pids_check_button")
self._new_check_button = builder.get_object("new_check_button")
self._pids_grid = builder.get_object("pids_grid")
# Transponder elements
self._sat_pos_button = builder.get_object("sat_pos_button")
self._pol_combo_box = builder.get_object("pol_combo_box")
self._fec_combo_box = builder.get_object("fec_combo_box")
self._sys_combo_box = builder.get_object("sys_combo_box")
self._mod_combo_box = builder.get_object("mod_combo_box")
self._invertion_combo_box = builder.get_object("invertion_combo_box")
self._rolloff_combo_box = builder.get_object("rolloff_combo_box")
self._pilot_combo_box = builder.get_object("pilot_combo_box")
self._pls_mode_combo_box = builder.get_object("pls_mode_combo_box")
self._tr_edit_switch = builder.get_object("tr_edit_switch")
self._tr_extra_expander = builder.get_object("tr_extra_expander")
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._TRANSPONDER_ELEMENTS = (self._sat_pos_button, self._pol_combo_box, self._invertion_combo_box,
self._sys_combo_box, self._freq_entry, self._transponder_id_entry,
self._network_id_entry, self._namespace_entry, self._fec_combo_box,
self._rate_entry)
if self._action is Action.EDIT:
self.update_data_elements()
elif self._action is Action.ADD:
self.init_default_data_elements()
def show(self):
response = self._dialog.run()
if response == Gtk.ResponseType.OK:
pass
self._dialog.destroy()
return response
@run_idle
def init_default_data_elements(self):
self._apply_button.set_visible(False)
self._create_button.set_visible(True)
self._tr_edit_switch.set_sensitive(False)
self.on_tr_edit_toggled(self._tr_edit_switch.set_active(True), True)
for elem in self._non_empty_elements.values():
elem.set_text(" ")
elem.set_text("")
self._new_check_button.set_active(True)
self._tr_extra_expander.activate()
self._service_type_combo_box.set_active(0)
self._pol_combo_box.set_active(0)
self._fec_combo_box.set_active(0)
self._sys_combo_box.set_active(0)
self._invertion_combo_box.set_active(2)
def update_data_elements(self):
model, paths = self._services_view.get_selection().get_selected_rows()
itr = model.get_iter(paths)
# Unpacking to search for an iterator for the base model
filter_model = model.get_model()
itr = filter_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(itr))
self._current_model = get_base_model(model)
srv = Service(*self._current_model[itr][:])
self._old_service = srv
self._current_itr = itr
# Service
self._name_entry.set_text(srv.service)
self._package_entry.set_text(srv.package)
self._sid_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)
if self._profile is Profile.ENIGMA_2:
self.init_enigma2_service_data(srv)
self.init_enigma2_transponder_data(srv)
elif self._profile is Profile.NEUTRINO_MP:
self.init_neutrino_data(srv)
self.init_neutrino_ui_elements()
# ***************** Init Enigma2 data *********************#
@run_idle
def init_enigma2_service_data(self, srv):
""" Service data initialisation """
flags = srv.flags_cas
if flags:
flags = flags.split(",")
self.init_enigma2_flags(flags)
self.init_enigma2_pids(flags)
self.init_enigma2_cas(flags)
def init_enigma2_flags(self, flags):
f_flags = list(filter(lambda x: x.startswith("f:"), flags))
if f_flags:
value = int(f_flags[0][2:])
self._keep_check_button.set_active(Flag.is_keep(value))
self._hide_check_button.set_active(Flag.is_hide(value))
self._use_pids_check_button.set_active(Flag.is_pids(value))
self._new_check_button.set_active(Flag.is_new(value))
def init_enigma2_cas(self, flags):
cas = list(filter(lambda x: x.startswith("C:"), flags))
if cas:
self._cas_entry.set_text(",".join(cas))
def init_enigma2_pids(self, flags):
pids = list(filter(lambda x: x.startswith("c:"), flags))
if pids:
for pid in pids:
if pid.startswith(Pids.VIDEO.value):
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[4:], 16)))
elif pid.startswith(Pids.TELETEXT.value):
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[4:], 16)))
elif pid.startswith(Pids.AC3.value):
self._ac3_pid_entry.set_text(str(int(pid[4:], 16)))
elif pid.startswith(Pids.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[4:], 16)))
elif pid.startswith(Pids.PCM_DELAY.value):
self._pcm_entry.set_text(str(int(pid[4:], 16)))
elif pid.startswith(Pids.SUBTITLE.value):
pass
def init_enigma2_transponder_data(self, srv):
""" Transponder data initialisation """
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._tr_flag_entry.set_text(tr_data[7])
if len(tr_data) > 12:
self._stream_id_entry.set_text(tr_data[11])
self._pls_code_entry.set_text(tr_data[12])
self.select_active_text(self._pls_mode_combo_box, PLS_MODE.get(tr_data[13]))
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)
# Should be called last to properly initialize the reference
self._srv_type_entry.set_text(data[4])
# ***************** Init Neutrino data *********************#
def init_neutrino_data(self, srv):
tr_data = srv.transponder.split(":")
self._transponder_id_entry.set_text(str(int(tr_data[0], 16)))
self._network_id_entry.set_text(str(int(tr_data[1], 16)))
self.select_active_text(self._invertion_combo_box, Inversion(tr_data[3]).name)
self.select_active_text(self._service_type_combo_box, srv.service_type)
self.update_reference_entry()
def init_neutrino_ui_elements(self):
self._builder.get_object("flags_box").set_visible(False)
self._builder.get_object("pids_grid").set_visible(False)
self._builder.get_object("tr_grid").remove_column(7)
self._builder.get_object("tr_extra_expander").set_visible(False)
self._builder.get_object("srv_separator").set_visible(False)
# ***************** Init Sat positions *********************#
def set_sat_positions(self, sat_pos):
""" Sat positions initialisation """
self._sat_pos_button.set_value(float(sat_pos))
def on_system_changed(self, box):
if not self._tr_edit_switch.get_active():
return
active = box.get_active()
self.update_dvb_s2_elements(active)
def update_dvb_s2_elements(self, active):
for elem in self._DVB_S2_ELEMENTS:
elem.set_sensitive(active)
self._pls_code_entry.set_name("GtkEntry")
self._stream_id_entry.set_name("GtkEntry")
if active:
if not self._mod_combo_box.get_active_id():
self._mod_combo_box.set_active_id(MODULATION["2"])
if not self._rolloff_combo_box.get_active_id():
self._rolloff_combo_box.set_active_id(ROLL_OFF["0"])
if not self._pilot_combo_box.get_active_id():
self._pilot_combo_box.set_active_id(Pilot.Auto.name)
if not self._pls_mode_combo_box.get_active_id():
self._pls_mode_combo_box.set_active_id(PLS_MODE["0"])
# ***************** Save data *********************#
def on_save(self, item):
self.save_data()
def on_create_new(self, item):
self.save_data()
def save_data(self):
if not self.is_data_correct():
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
self.on_edit() if self._action is Action.EDIT else self.on_new()
self._dialog.destroy()
def on_edit(self):
fav_id, data_id = self.get_srv_data()
# transponder
transponder = self._old_service.transponder
if self._tr_edit_switch.get_active():
transponder = self.get_transponder_data()
if self._transponder_services_iters:
self.update_transponder_services(transponder)
service = self.get_service(fav_id, data_id, transponder)
old_fav_id = self._old_service.fav_id
if old_fav_id != fav_id:
self.update_bouquets(fav_id, old_fav_id)
self._services[fav_id] = service
if self._old_service.picon_id != service.picon_id:
self.update_picon_name(self._old_service.picon_id, service.picon_id)
self._current_model.set(self._current_itr, {i: v for i, v in enumerate(service)})
self._old_service = service
def update_bouquets(self, fav_id, old_fav_id):
self._services.pop(old_fav_id, None)
for bq in self._bouquets.values():
indexes = []
for i, f_id in enumerate(bq):
if old_fav_id == f_id:
indexes.append(i)
for i in indexes:
bq[i] = fav_id
def update_picon_name(self, old_name, new_name):
for file_name in os.listdir(self._picons_dir_path):
if file_name == old_name:
old_file = os.path.join(self._picons_dir_path, old_name)
new_file = os.path.join(self._picons_dir_path, new_name)
os.rename(old_file, new_file)
break
def on_new(self):
service = self.get_service(*self.get_srv_data(), self.get_transponder_data())
print(service)
show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
def get_service(self, fav_id, data_id, transponder):
freq, rate, pol, fec, system, pos = self.get_transponder_values()
return Service(flags_cas=self.get_flags(),
transponder_type="s",
coded=self._old_service.coded,
service=self._name_entry.get_text(),
locked=self._old_service.locked,
hide=HIDE_ICON if self._hide_check_button.get_active() else None,
package=self._package_entry.get_text(),
service_type=SERVICE_TYPE.get(self._srv_type_entry.get_text(), SERVICE_TYPE["3"]),
picon=self._old_service.picon,
picon_id=self._reference_entry.get_text().replace(":", "_") + ".png",
ssid="{:04x}".format(int(self._sid_entry.get_text())),
freq=freq,
rate=rate,
pol=pol,
fec=fec,
system=system,
pos=pos,
data_id=data_id,
fav_id=fav_id,
transponder=transponder)
def get_flags(self):
if self._profile is Profile.ENIGMA_2:
return self.get_enigma2_flags()
elif self._profile is Profile.NEUTRINO_MP:
return self._old_service.flags_cas
def get_enigma2_flags(self):
flags = ["p:{}".format(self._package_entry.get_text())]
# cas
cas = self._cas_entry.get_text()
if cas:
flags.append(cas)
# pids
video_pid = self._video_pid_entry.get_text()
if video_pid:
flags.append("{}{:04x}".format(Pids.VIDEO.value, int(video_pid)))
audio_pid = self._audio_pid_entry.get_text()
if audio_pid:
flags.append("{}{:04x}".format(Pids.AUDIO.value, int(audio_pid)))
teletext_pid = self._teletext_pid_entry.get_text()
if teletext_pid:
flags.append("{}{:04x}".format(Pids.TELETEXT.value, int(teletext_pid)))
pcr_pid = self._pcr_pid_entry.get_text()
if pcr_pid:
flags.append("{}{:04x}".format(Pids.PCR.value, int(pcr_pid)))
ac3_pid = self._ac3_pid_entry.get_text()
if ac3_pid:
flags.append("{}{:04x}".format(Pids.AC3.value, int(ac3_pid)))
bitstream_pid = self._bitstream_entry.get_text()
if bitstream_pid:
flags.append("{}{:04x}".format(Pids.BIT_STREAM_DELAY.value, int(bitstream_pid)))
pcm_pid = self._pcm_entry.get_text()
if pcm_pid:
flags.append("{}{:04x}".format(Pids.PCM_DELAY.value, int(pcm_pid)))
# flags
f_flags = Flag.KEEP.value if self._keep_check_button.get_active() else 0
f_flags = f_flags + Flag.HIDE.value if self._hide_check_button.get_active() else f_flags
f_flags = f_flags + Flag.PIDS.value if self._use_pids_check_button.get_active() else f_flags
f_flags = f_flags + Flag.NEW.value if self._new_check_button.get_active() else f_flags
if f_flags:
flags.append("f:{:02d}".format(f_flags))
return ",".join(flags)
def get_srv_data(self):
ssid = int(self._sid_entry.get_text())
net_id, tr_id = int(self._network_id_entry.get_text()), int(self._transponder_id_entry.get_text())
service_type = self._srv_type_entry.get_text()
if self._profile is Profile.ENIGMA_2:
namespace = int(self._namespace_entry.get_text())
data_id = self._ENIGMA2_DATA_ID.format(ssid, namespace, tr_id, net_id, service_type, 0)
fav_id = self._ENIGMA2_FAV_ID.format(ssid, tr_id, net_id, namespace)
return fav_id, data_id
elif self._profile is Profile.NEUTRINO_MP:
fav_id = self._NEUTRINO_FAV_ID.format(tr_id, net_id, ssid)
return fav_id, self._old_service.data_id
def get_transponder_values(self):
freq = self._freq_entry.get_text()
rate = self._rate_entry.get_text()
pol = self._pol_combo_box.get_active_id()
fec = self._fec_combo_box.get_active_id()
system = self._sys_combo_box.get_active_id()
pos = str(round(self._sat_pos_button.get_value(), 1))
return freq, rate, pol, fec, system, pos
def get_transponder_data(self):
sys = self._sys_combo_box.get_active_id()
freq = self._freq_entry.get_text()
rate = self._rate_entry.get_text()
pol = self.get_value_from_combobox_id(self._pol_combo_box, POLARIZATION)
fec = self.get_value_from_combobox_id(self._fec_combo_box, FEC_DEFAULT)
sat_pos = str(round(self._sat_pos_button.get_value(), 1)).replace(".", "")
inv = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
srv_sys = "0" # !!!
if self._profile is Profile.ENIGMA_2:
dvb_s_tr = self._ENIGMA2_TRANSPONDER_DATA.format("s", freq, rate, pol, fec, sat_pos, inv, srv_sys)
if sys == "DVB-S":
return dvb_s_tr
if sys == "DVB-S2":
flag = self._tr_flag_entry.get_text()
mod = self.get_value_from_combobox_id(self._mod_combo_box, MODULATION)
roll_off = self.get_value_from_combobox_id(self._rolloff_combo_box, ROLL_OFF)
pilot = get_value_by_name(Pilot, self._pilot_combo_box.get_active_id())
pls_mode = self.get_value_from_combobox_id(self._pls_mode_combo_box, PLS_MODE)
pls_code = self._pls_code_entry.get_text()
st_id = self._stream_id_entry.get_text()
pls = ":{}:{}:{}".format(st_id, pls_code, pls_mode) if pls_mode and pls_code and st_id else ""
return "{}:{}:{}:{}:{}{}".format(dvb_s_tr, flag, mod, roll_off, pilot, pls)
elif self._profile is Profile.NEUTRINO_MP:
on_id, tr_id = int(self._network_id_entry.get_text()), int(self._transponder_id_entry.get_text())
mod = self.get_value_from_combobox_id(self._mod_combo_box, MODULATION) if sys == "DVB-S2" else None
srv_sys = None
return self._NEUTRINO_TRANSPONDER_DATA.format(tr_id, on_id, freq, inv, rate, fec, pol, mod, srv_sys)
def update_transponder_services(self, transponder):
for itr in self._transponder_services_iters:
srv = self._current_model[itr][:]
srv[-9], srv[-8], srv[-7], srv[-6], srv[-5], srv[-4] = self.get_transponder_values()
srv[-1] = transponder
srv = Service(*srv)
self._services[srv.fav_id] = self._services.pop(srv.fav_id)._replace(transponder=transponder)
self._current_model.set(itr, {i: v for i, v in enumerate(srv)})
# ***************** Others *********************#
def select_active_text(self, box: Gtk.ComboBox, text):
model = box.get_model()
for index, row in enumerate(model):
if row[0] == text:
box.set_active(index)
break
def on_digit_entry_changed(self, entry):
entry.set_name(self._DIGIT_ENTRY_NAME if self._DIGIT_PATTERN.search(entry.get_text()) else "GtkEntry")
def on_non_empty_entry_changed(self, entry):
entry.set_name(self._DIGIT_ENTRY_NAME if self._NON_EMPTY_PATTERN.search(entry.get_text()) else "GtkEntry")
def on_cas_entry_changed(self, entry):
entry.set_name("GtkEntry" if self._CAID_PATTERN.fullmatch(entry.get_text()) else self._DIGIT_ENTRY_NAME)
def get_value_from_combobox_id(self, box: Gtk.ComboBox, dc: dict):
cb_id = box.get_active_id()
return get_key_by_value(dc, cb_id)
@run_idle
def on_tr_edit_toggled(self, switch: Gtk.Switch, active):
if active and self._action is Action.EDIT:
self._transponder_services_iters = []
response = TransponderServicesDialog(self._dialog,
self._current_model,
self._old_service.transponder,
self._transponder_services_iters).show()
if response == Gtk.ResponseType.CANCEL or response == -4:
switch.set_active(False)
self._transponder_services_iters = None
return
self.update_dvb_s2_elements(active and self._sys_combo_box.get_active_id() == "DVB-S2")
for elem in self._TRANSPONDER_ELEMENTS:
elem.set_sensitive(active)
def is_data_correct(self):
for elem in self._digit_elements.values():
if elem.get_name() == self._DIGIT_ENTRY_NAME:
return False
for elem in self._non_empty_elements.values():
if elem.get_name() == self._DIGIT_ENTRY_NAME:
return False
if self._cas_entry.get_name() == self._DIGIT_ENTRY_NAME:
return False
return True
def update_reference(self, entry, event=None):
if not self.is_data_correct() or (event is None and self._profile is Profile.NEUTRINO_MP):
return
self.update_reference_entry()
def update_reference_entry(self):
srv_type = 0 if self._srv_type_entry.get_text() == "2" else 1
ssid = int(self._sid_entry.get_text())
tid = int(self._transponder_id_entry.get_text())
nid = int(self._network_id_entry.get_text())
if self._profile is Profile.ENIGMA_2:
on_id = int(self._namespace_entry.get_text())
ref = "1:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0".format(srv_type, ssid, tid, nid, on_id)
self._reference_entry.set_text(ref)
else:
self._reference_entry.set_text("{:x}{:04x}{:04x}".format(tid, nid, ssid))
class TransponderServicesDialog:
def __init__(self, transient, model, transponder, tr_iters):
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "service_details_dialog.glade",
("tr_services_dialog", "transponder_services_liststore"))
self._dialog = builder.get_object("tr_services_dialog")
self._dialog.set_transient_for(transient)
self._srv_model = builder.get_object("transponder_services_liststore")
self.append_services(model, transponder, tr_iters)
def append_services(self, model, transponder, tr_iters):
for row in model:
if row[-1] == transponder:
self._srv_model.append((row[3], row[6], row[7], row[10], row[11], row[16]))
tr_iters.append(model.get_iter(row.path))
def show(self):
response = self._dialog.run()
self._dialog.destroy()
return response
if __name__ == "__main__":
pass

View File

@@ -1,5 +1,5 @@
from app.properties import write_config, Profile, get_default_settings
from . import Gtk, UI_RESOURCES_PATH
from . import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .main_helper import update_entry_data
@@ -16,6 +16,7 @@ class SettingsDialog:
"apply_settings": self.apply_settings}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade",
("settings_dialog", "telnet_timeout_adjustment"))
builder.connect_signals(handlers)

View File

@@ -1,11 +1,11 @@
#!/bin/env bash
VER="0.2.2_Pre-alpha"
#!/bin/bash
VER="0.3.0_Pre-alpha"
B_PATH="dist/DemonEditor"
DEB_PATH="$B_PATH/usr/share/demoneditor"
mkdir -p $B_PATH
cp -TRv deb $B_PATH
cp -Rv app $DEB_PATH
rsync --exclude=app/ui/lang -arv app $DEB_PATH
cp -Rv start.py $DEB_PATH
cd dist

View File

@@ -1,5 +1,5 @@
Package: DemonEditor
Version: 0.2.2-Pre-alpha
Version: 0.3.0-Pre-alpha
Section: utils
Priority: optional
Architecture: all

View File

@@ -1,25 +1,33 @@
# DemonEditor
Enigma2 channel and satellites list editor for GNU/Linux.
## Enigma2 channel and satellites list editor for GNU/Linux.
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
Keyboard shortcuts:
Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
### Keyboard shortcuts:
Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
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 + S, T, E in Satellites edit tool for create and edit satellite or transponder.
Ctrl + L - parental lock.
Ctrl + H - hide/skip.
Clipboard is "rubber". There is an accumulation before the insertion!
Ctrl + E - edit.
Ctrl + R, F2 - rename.
Ctrl + S, T in Satellites edit tool for create satellite or transponder.
Ctrl + L - parental lock.
Ctrl + H - hide/skip.
Left/Right - remove selection.
Ability to import IPTV into bouquet from m3u files!
### Extra:
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.
#### Note.
To create a simple debian package, you can use the build-deb.sh
Tests only in image based on OpenPLi or last BPanther(neutrino) images with GM 990 Spark Reloaded receiver
in my preferred linux distro (Last Linux Mint 18.* - MATE 64-bit)!
Minimum requirements: Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
#### Terrestrial and cable channels at the moment are not supported!
Terrestrial and cable channels at the moment are not supported!

Binary file not shown.

3
po/build.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
#xgettext --keyword=translatable --sort-output -L Glade -o po/demon-editor.po app/ui/main_window.glade
#msgfmt demon-editor.po -o demon-editor.mo

BIN
po/ru/demon-editor.mo Normal file

Binary file not shown.

467
po/ru/demon-editor.po Normal file
View File

@@ -0,0 +1,467 @@
# Copyright (C) 2018 Dmitriy Yefremov
# This file is distributed under the MIT license.
# Dmitriy Yefremov , 2018.
msgid ""
msgstr ""
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
# Main
msgid "Service"
msgstr "Сервис"
msgid "Package"
msgstr "Пакет"
msgid "Type"
msgstr "Тип"
msgid "Picon"
msgstr "Пикон"
msgid "Freq"
msgstr "Частота"
msgid "Rate"
msgstr "Сим. скорость"
msgid "Pol"
msgstr "Пол."
msgid "System"
msgstr "Система"
msgid "Pos"
msgstr "Поз."
msgid "Num"
msgstr "№"
msgid "Current IP:"
msgstr "Текущий IP:"
msgid "Assign"
msgstr "Привязать"
msgid "Bouquet details"
msgstr "Сервисы букета"
msgid "Bouquets"
msgstr "Букеты"
msgid "Copy"
msgstr "Копировать"
msgid "Copy reference"
msgstr "Копировать ссылку"
msgid "Data"
msgstr ""
msgid "Download"
msgstr "Загрузить"
msgid "Edit"
msgstr "Изменить"
msgid "Edit "
msgstr "Изменить"
msgid "Edit mаrker text"
msgstr "Изменить текст маркера"
msgid "FTP-transfer"
msgstr "Передача установок по FTP"
msgid "Global search"
msgstr "Глобальный поиск"
msgid "Hide"
msgstr "Пропустить"
msgid "Hide/Skip On/Off Ctrl + H"
msgstr "Скрыть/Пропустить Вкл/Выкл Ctrl + H"
msgid "IPTV"
msgstr ""
msgid "Add IPTV or stream service"
msgstr "Добавить IPTV или поток"
msgid "Import m3u"
msgstr "Импортировать m3u"
msgid "Import m3u file"
msgstr "Импортировать файл m3u"
msgid "Insert marker"
msgstr "Вставить маркер"
msgid "Locate in services"
msgstr "Найти в списке сервисов"
msgid "Locked"
msgstr "Заблокирован"
msgid "Move"
msgstr "Переместить"
msgid "New"
msgstr "Новый"
msgid "New bouquet"
msgstr "Новый букет"
msgid "Open"
msgstr "Открыть"
msgid "Parent lock On/Off Ctrl + L"
msgstr "Родительский замок Вкл/Выкл Ctrl + L"
msgid "Paste"
msgstr ""
msgid "Picons"
msgstr "Пиконы"
msgid "Picons loader"
msgstr "Загрузчик пиконов"
msgid "Preferences"
msgstr "Настройки"
msgid "Profile:"
msgstr "Профиль:"
msgid "Radio"
msgstr ""
msgid "Remove"
msgstr "Удалить"
msgid "Satellites editor"
msgstr "Редактор спутников"
msgid "Save"
msgstr "Сохранить"
msgid "Search"
msgstr "Поиск"
msgid "Services"
msgstr "Сервисы"
msgid "Services filter"
msgstr "Фильтр сервисов"
msgid "Settings"
msgstr "Настройки"
msgid "TV"
msgstr ""
msgid "Up"
msgstr "Переместить вверх"
msgid "Down"
msgstr "Переместить вниз"
msgid "Active profile:"
msgstr "Активный профиль:"
msgid "All"
msgstr "Все"
msgid "Are you sure?"
msgstr "Вы уверены?"
msgid "Current data path:"
msgstr "Текущий путь к данным:"
msgid "Data dir:"
msgstr "Путь к данным:"
msgid "Data:"
msgstr "Данные:"
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
msgstr "Редактор списка каналов и спутников Enigma2\n для GNU/Linux"
msgid "FTP"
msgstr ""
msgid "Host:"
msgstr "Адрес ресивера:"
msgid "Loading data..."
msgstr "Загрузка данных..."
msgid "Login:"
msgstr "Логин:"
msgid "Options"
msgstr "Настройки"
msgid "Password:"
msgstr "Пароль:"
msgid "Picons dir:"
msgstr "Директория пиконов:"
msgid "Picons:"
msgstr "Пиконы:"
msgid "Port:"
msgstr "Порт:"
msgid "Receive"
msgstr "Получить"
msgid "Receive files from receiver"
msgstr "Получить файлы из ресивера"
msgid "Receiver IP:"
msgstr "IP адрес ресивера:"
msgid "Remove unused bouquets"
msgstr "Удалить не испрльзуемые букеты"
msgid "Reset profile"
msgstr "Сброс профиля"
msgid "Satellites"
msgstr "Спутники"
msgid "Satellites.xml file:"
msgstr "Файл satellites.xml:"
msgid "Select"
msgstr ""
msgid "Send"
msgstr "Отправить"
msgid "Send files to receiver"
msgstr "Отправить файлы в ресивер"
msgid "Services and Bouquets files:"
msgstr "Файлы сервисов и букетов:"
msgid "Telnet"
msgstr ""
msgid "Timeout between commands in seconds"
msgstr "Пауза между коммандами в сек."
msgid "Timeout:"
msgstr "Тайм-аут:"
msgid "User bouquet files:"
msgstr "Файлы букетов:"
msgid "WebTV"
msgstr ""
msgid "Extra:"
msgstr "Дополнительно:"
# Picons dialog
msgid "Load providers"
msgstr "Загрузить провайдеров"
msgid "Receive picons"
msgstr "Загрузить пиконы"
msgid "Picons name format:"
msgstr "Формат имени пиконов:"
msgid "Resize:"
msgstr "Обрезать:"
msgid "Current picons path:"
msgstr "Текущий путь к пиконам:"
msgid "Receiver picons path:"
msgstr "Путь к пиконам ресивера:"
msgid "Picons download tool"
msgstr "Загрузчик пиконов"
msgid "Transfer to receiver"
msgstr "Загрузить в ресивер"
msgid "Downloader"
msgstr "Загрузчик"
msgid "Converter"
msgstr "Конвертер"
msgid "Convert"
msgstr "Конвертировать"
msgid "Path to save:"
msgstr "Путь для сохранения:"
msgid "Path to Enigma2 picons:"
msgstr "Путь к пиконам формата Enigma2:"
msgid "Specify the correct position value for the provider!"
msgstr "Укажите правильное значение позиции для провайдера!"
msgid "Converter between name formats"
msgstr "Конвертер формата имен"
msgid "Receive picons for providers"
msgstr "Получение пиконов для провайдеров"
msgid "Load satellite providers."
msgstr "Загрузка провайдеров"
msgid "To automatically set the identifiers for picons,\nfirst load the required services list into the main application window."
msgstr "Для автоматического именования пиконов,\nзагрузите список необходимых сервисов в главное окно программы."
# Satellites editor
msgid "Satellites edit tool"
msgstr "Редактор спутников"
msgid "Add"
msgstr "Добавить"
msgid "Satellite"
msgstr "Спутник"
msgid "Transponder"
msgstr "Транспондер"
msgid "Satellite properties:"
msgstr "Параметры спутника:"
msgid "Transponder properties:"
msgstr "Параметры транспондера:"
msgid "Name"
msgstr "Имя"
msgid "Position"
msgstr "Позиция"
# Service details dialog
msgid "Service data:"
msgstr "Данные сервиса:"
msgid "Transponder data:"
msgstr "Данные транспондера:"
msgid "Service data"
msgstr "Данные сервиса"
msgid "Transponder details"
msgstr "Данные транспондера"
msgid "Changes will be applied to all services of this transponder!\nContinue?"
msgstr "Изменения будут применены ко всем сервисам данного транспондера!\nПродолжить?"
msgid "Reference"
msgstr "Ссылка"
msgid "Namespace"
msgstr "Пр. имен"
msgid "Flags:"
msgstr "Флаги:"
msgid "Delays (ms):"
msgstr "Задержки (mc)"
msgid "Bitstream"
msgstr "Поток"
msgid "Description"
msgstr "Описание"
# IPTV dialog
msgid "Stream data"
msgstr "Данные потока"
# Dialogs messages
msgid "Error. No bouquet is selected!"
msgstr "Ошибка. Не выбран букет!"
msgid "This item is not allowed to be removed!"
msgstr "Этот элемент не разрешен к удалению!"
msgid "This item is not allowed to edit!"
msgstr "Элемент не предназначен для редактирования!"
msgid "Please, download files from receiver or setup your path for read data!"
msgstr "Пожалуйста, загрузите файлы из приемника или настройте путь для чтения данных!"
msgid "Reading data error!"
msgstr "Ошибка чтения данных!"
msgid "No m3u file is selected!"
msgstr "Не выбран m3u файл!"
msgid "Not implemented yet!"
msgstr "Пока не реализовано!"
msgid "The text of marker is empty, please try again!"
msgstr "Текст маркера пуст, попробуйте еще!"
msgid "Please, select only one item!"
msgstr "Пожалуйста, выберите только один элемент!"
msgid "No png file is selected!"
msgstr "Не выбран png файл!"
msgid "No reference is present!"
msgstr "Ссылка не найдена!"
msgid "No selected item!"
msgstr "Не выбран элемент!"
msgid "The task is already running!"
msgstr "Задача уже запущена!"
msgid "Done!"
msgstr "Готово!"
msgid "Please, wait..."
msgstr "Пожалуйста, подождите..."
msgid "Resizing..."
msgstr "Изменение размера..."
msgid "Select paths!"
msgstr "Укажите пути!"
msgid "No satellite is selected!"
msgstr "Не выбран спутник!"
msgid "Please, select only one satellite!"
msgstr "Пожалуйста, выберите только один спутник!"
msgid "Please check your parameters and try again."
msgstr "Пожалуйста, проверте параметры и попробуйте снова!"
msgid "No satellites.xml file is selected!"
msgstr "Не выбран файл satellites.xml!"
msgid "Error. Verify the data!"
msgstr "Ошибка. Проверьте данные!"