mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-08 12:25:59 +02:00
Compare commits
220 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
408a4cfa32 | ||
|
|
fe3fb1fefe | ||
|
|
95b5c7aa74 | ||
|
|
d2e80c6d44 | ||
|
|
57f157ef9b | ||
|
|
f62f104c8d | ||
|
|
8d6b1303dc | ||
|
|
de770169fa | ||
|
|
7b2e467111 | ||
|
|
1c1dff2497 | ||
|
|
309f960e1e | ||
|
|
dadb73280c | ||
|
|
81260211a4 | ||
|
|
d4d1dd397d | ||
|
|
fe3e1ef30a | ||
|
|
1f46eae6be | ||
|
|
a398b0c6e9 | ||
|
|
272b6af3ad | ||
|
|
d976e02cf6 | ||
|
|
fb6951896f | ||
|
|
9c62a49968 | ||
|
|
99bb508912 | ||
|
|
e382a51f81 | ||
|
|
fdd30b2ac9 | ||
|
|
f2b99f9eea | ||
|
|
dea5723bb7 | ||
|
|
236a7a15d0 | ||
|
|
cfe281116a | ||
|
|
984e8ca088 | ||
|
|
62eae7f029 | ||
|
|
204962b531 | ||
|
|
767c06a0ff | ||
|
|
d4ec28e9cd | ||
|
|
8fee65cabb | ||
|
|
95069bbf24 | ||
|
|
60f106bc2a | ||
|
|
4becdf1d6e | ||
|
|
f2f027c6f4 | ||
|
|
edeab12b50 | ||
|
|
896aa7f66e | ||
|
|
6b6220a3ac | ||
|
|
3fa46c6c6c | ||
|
|
a0b322b188 | ||
|
|
3550f58603 | ||
|
|
729c85be77 | ||
|
|
f8aee1b807 | ||
|
|
75d93f6a19 | ||
|
|
4581cc7d4f | ||
|
|
2f8ea069e1 | ||
|
|
8afcec6b7e | ||
|
|
b5a9321c5c | ||
|
|
7ee781c39b | ||
|
|
48f3c1a4d6 | ||
|
|
717bac6446 | ||
|
|
fe1323f8cf | ||
|
|
0f30d74edc | ||
|
|
0686c91a5d | ||
|
|
a84090cda7 | ||
|
|
291b3aa289 | ||
|
|
dd92ffc9b1 | ||
|
|
97a8f793c3 | ||
|
|
3ad2e3d6b6 | ||
|
|
7d6763ffb5 | ||
|
|
6582be7a0d | ||
|
|
1e45621bd8 | ||
|
|
61bcb85bbc | ||
|
|
9eee9ac424 | ||
|
|
3f720afedc | ||
|
|
cd19c5fd9c | ||
|
|
61ca2f3e8b | ||
|
|
75fc7adc88 | ||
|
|
3678a9d29d | ||
|
|
a5927dd2b6 | ||
|
|
34e0ed4748 | ||
|
|
d7a214b445 | ||
|
|
e194827af7 | ||
|
|
e9e53da5cc | ||
|
|
822497317d | ||
|
|
c2047bd7b5 | ||
|
|
3636da60d6 | ||
|
|
2eebd55b77 | ||
|
|
12f76f8e28 | ||
|
|
28e6cca919 | ||
|
|
9b53538da6 | ||
|
|
994541bad5 | ||
|
|
3cbb16febe | ||
|
|
2b61fa07b9 | ||
|
|
406f4bd0f0 | ||
|
|
1ec6b817e9 | ||
|
|
7c55692c99 | ||
|
|
3aa29a788d | ||
|
|
55b0dccc80 | ||
|
|
edb97cbf8c | ||
|
|
7620f03e2b | ||
|
|
cced856297 | ||
|
|
3bcfd66971 | ||
|
|
e7e7c667e9 | ||
|
|
6de0bc4201 | ||
|
|
878520b7f9 | ||
|
|
63ac413982 | ||
|
|
171c58c546 | ||
|
|
6758ae3d16 | ||
|
|
329513d2a7 | ||
|
|
be195e9001 | ||
|
|
635a3fb966 | ||
|
|
281f7a28f3 | ||
|
|
507f5817c2 | ||
|
|
d3822474ba | ||
|
|
e1ce9f3006 | ||
|
|
c2b0768857 | ||
|
|
283d85ef8e | ||
|
|
f5656d8d5f | ||
|
|
5dd5a09bfc | ||
|
|
1b5f3372b4 | ||
|
|
974e964f42 | ||
|
|
8cb6ed02d2 | ||
|
|
8bb3b780d1 | ||
|
|
3000c8830c | ||
|
|
ac550e016d | ||
|
|
7420751806 | ||
|
|
f35889e8e4 | ||
|
|
857b252f4c | ||
|
|
572584a14f | ||
|
|
7e4ac3e69c | ||
|
|
0d73ffa79d | ||
|
|
5e2f1ddb84 | ||
|
|
6c4040901f | ||
|
|
103e09b900 | ||
|
|
8ddc517ab7 | ||
|
|
b26d982db4 | ||
|
|
3733bc395b | ||
|
|
26bfbafc0e | ||
|
|
84d1a18111 | ||
|
|
1cdacd5276 | ||
|
|
354715558c | ||
|
|
bca1613bff | ||
|
|
36b533b890 | ||
|
|
2eabccc1a9 | ||
|
|
75cd78277e | ||
|
|
5181b732ed | ||
|
|
513c0e8d3d | ||
|
|
f932feb305 | ||
|
|
6a2fda5ec0 | ||
|
|
86c30dd2c1 | ||
|
|
0ed41c473d | ||
|
|
5078a854d2 | ||
|
|
353bf04924 | ||
|
|
474ff8e303 | ||
|
|
5834bd4a0b | ||
|
|
81f31e5d8d | ||
|
|
267f645c16 | ||
|
|
8c10d7d6a5 | ||
|
|
bbdb47ee7a | ||
|
|
7f6856e6aa | ||
|
|
3439a3ad0a | ||
|
|
ee2e2ac49d | ||
|
|
a745167fb7 | ||
|
|
8551bc2459 | ||
|
|
38ceb6f65d | ||
|
|
33fe78911a | ||
|
|
3552415054 | ||
|
|
a2ec6f1e1f | ||
|
|
5f90d07853 | ||
|
|
d88548eece | ||
|
|
9ead5b3918 | ||
|
|
1a376e6922 | ||
|
|
eb29f2e1b2 | ||
|
|
bfaab7b2fb | ||
|
|
3e6a7f8a42 | ||
|
|
aff34d7627 | ||
|
|
8ef0a451ff | ||
|
|
8678f02fd6 | ||
|
|
a0a64606cd | ||
|
|
6574296278 | ||
|
|
d8414f56ee | ||
|
|
bb9499392b | ||
|
|
069ea9348f | ||
|
|
8c932c8913 | ||
|
|
562501dda8 | ||
|
|
79583869e4 | ||
|
|
5c9abcee21 | ||
|
|
e8377ed174 | ||
|
|
16f8df0238 | ||
|
|
dab772e25c | ||
|
|
37791a5537 | ||
|
|
eb55cc76be | ||
|
|
f728a01963 | ||
|
|
8aae503e35 | ||
|
|
fe499d6c94 | ||
|
|
cd2c820324 | ||
|
|
b4c5af4c04 | ||
|
|
f239957ca2 | ||
|
|
3b835e6f34 | ||
|
|
4ab3d2d0e1 | ||
|
|
3781686213 | ||
|
|
0247aeed52 | ||
|
|
3d34539c16 | ||
|
|
0a06b36f60 | ||
|
|
ea1d536c6c | ||
|
|
5087266693 | ||
|
|
3e617e329c | ||
|
|
62bcb64640 | ||
|
|
300ea3b6d9 | ||
|
|
3ae1901757 | ||
|
|
2b63a59c91 | ||
|
|
ec69bae2a6 | ||
|
|
c07e0b606b | ||
|
|
61745df2a7 | ||
|
|
146c59e0db | ||
|
|
c7c0a4055b | ||
|
|
6c7d39889d | ||
|
|
9d9626d065 | ||
|
|
9bf7a10bf1 | ||
|
|
9659514feb | ||
|
|
f39ddd4315 | ||
|
|
90bd9e0211 | ||
|
|
b993eba2df | ||
|
|
dbab024778 | ||
|
|
7999fc6893 | ||
|
|
3fc5a3fd68 |
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2019 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
29
README.md
29
README.md
@@ -20,31 +20,38 @@ Clipboard is **"rubber"**. There is an accumulation before the insertion!
|
||||
* **Space** - select/deselect.
|
||||
* **Left/Right** - remove selection.
|
||||
* **Ctrl + Up, Down, PageUp, PageDown, Home, End** - move selected items in the list.
|
||||
* **Ctrl + O** - (re)load user data from current dir.
|
||||
* **Ctrl + D** - load data from receiver.
|
||||
* **Ctrl + U/B** upload data/bouquets to receiver.
|
||||
|
||||
### Extra:
|
||||
* Multiple selections in lists only with Space key (as in file managers).
|
||||
* Ability to import IPTV into bouquet (Neutrino WEBTV) from m3u files.
|
||||
* Ability to download picons and update satellites (transponders) from web.
|
||||
* Preview (playing) IPTV or other streams directly from the bouquet list(should be installed VLC).
|
||||
* Import feature.
|
||||
* Multiple selections in lists only with Space key (as in file managers).
|
||||
* Ability to download picons and update satellites (transponders) from web.
|
||||
* Ability to import into bouquet (Neutrino WEB TV) from m3u.
|
||||
* Ability to export bouquets with IPTV services to m3u.
|
||||
* Assignment EPG from DVB or XML for IPTV services (Enigma2 only).
|
||||
* Preview (playing) IPTV or other streams directly from the bouquet list (should be installed VLC).
|
||||
|
||||
### Minimum requirements:
|
||||
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings.
|
||||
|
||||
### Launching
|
||||
### Launching:
|
||||
To start the program, in most cases it is enough to download the archive, unpack and run it by
|
||||
double clicking on DemonEditor.desktop in the root directory, or launching from the console
|
||||
with the command: ```python3 ./start.py```
|
||||
with the command: ```./start.py```
|
||||
Extra folders can be deleted, excluding the *app* folder and root files like *DemonEditor.desktop* and *start.py*!
|
||||
|
||||
### Note.
|
||||
To create a simple **debian package**, you can use the *build-deb.sh.*
|
||||
|
||||
Tests only with openATV image and Formuler F1 receiver in my preferred Linux distros
|
||||
(latest Linux Mint 18.* and 19 MATE 64-bit)!
|
||||
The program is tested only with openATV image and Formuler F1 receiver in my favourite Linux distributions.
|
||||
(the latest versions of Linux Mint 18.* and 19* MATE 64-bit)!
|
||||
|
||||
**Terrestrial(DVB-T/T2) and cable channels are supported(Enigma2 only) with limitation!**
|
||||
**Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2!**
|
||||
|
||||
Main supported **lamedb** format is version **4**. Versions **3** and **5** has only experimental support!
|
||||
For version **3** is only read mode available. When saving, version **4** format is used instead!
|
||||
**Important:**
|
||||
Main supported **lamedb** format is version **4**. Versions **3** and **5** has only experimental support!
|
||||
For version **3** is only read mode available. When saving, version **4** format is used instead!
|
||||
|
||||
|
||||
|
||||
@@ -7,11 +7,19 @@ from gi.repository import GLib
|
||||
_LOG_FILE = "demon-editor.log"
|
||||
_DATE_FORMAT = "%d-%m-%y %H:%M:%S"
|
||||
_LOGGER_NAME = "main_logger"
|
||||
logging.Logger(_LOGGER_NAME)
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
filename=_LOG_FILE,
|
||||
format="%(asctime)s %(message)s",
|
||||
datefmt=_DATE_FORMAT)
|
||||
_USE_LOG = False
|
||||
|
||||
|
||||
def init_logger():
|
||||
global _USE_LOG
|
||||
_USE_LOG = True
|
||||
logging.Logger(_LOGGER_NAME)
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format="%(asctime)s %(message)s",
|
||||
datefmt=_DATE_FORMAT,
|
||||
handlers=[logging.FileHandler(_LOG_FILE),
|
||||
logging.StreamHandler()])
|
||||
log("Logging is enabled.", level=logging.INFO)
|
||||
|
||||
|
||||
def get_logger():
|
||||
@@ -19,7 +27,7 @@ def get_logger():
|
||||
|
||||
|
||||
def log(message, level=logging.ERROR):
|
||||
get_logger().log(level, message)
|
||||
get_logger().log(level, message) if _USE_LOG else print(message)
|
||||
|
||||
|
||||
def run_idle(func):
|
||||
|
||||
@@ -28,6 +28,7 @@ class DownloadType(Enum):
|
||||
SATELLITES = 2
|
||||
PICONS = 3
|
||||
WEBTV = 4
|
||||
EPG = 5
|
||||
|
||||
|
||||
class HttpRequestType(Enum):
|
||||
@@ -41,14 +42,14 @@ class TestException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def download_data(*, properties, download_type=DownloadType.ALL, callback=None):
|
||||
def download_data(*, properties, download_type=DownloadType.ALL, callback=print):
|
||||
with FTP(host=properties["host"], user=properties["user"], passwd=properties["password"]) as ftp:
|
||||
ftp.encoding = "utf-8"
|
||||
callback("FTP OK.\n")
|
||||
save_path = properties["data_dir_path"]
|
||||
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
||||
files = []
|
||||
# bouquets section
|
||||
# bouquets
|
||||
if download_type is DownloadType.ALL or download_type is DownloadType.BOUQUETS:
|
||||
ftp.cwd(properties["services_path"])
|
||||
ftp.dir(files.append)
|
||||
@@ -58,7 +59,7 @@ def download_data(*, properties, download_type=DownloadType.ALL, callback=None):
|
||||
if name.endswith(file_list):
|
||||
name = name.split()[-1]
|
||||
download_file(ftp, name, save_path, callback)
|
||||
# satellites.xml and webtv section
|
||||
# satellites.xml and webtv
|
||||
if download_type in (DownloadType.ALL, DownloadType.SATELLITES, DownloadType.WEBTV):
|
||||
ftp.cwd(properties["satellites_xml_path"])
|
||||
files.clear()
|
||||
@@ -70,13 +71,26 @@ def download_data(*, properties, download_type=DownloadType.ALL, callback=None):
|
||||
download_file(ftp, _SAT_XML_FILE, save_path, callback)
|
||||
if download_type in (DownloadType.ALL, DownloadType.WEBTV) and name.endswith(_WEBTV_XML_FILE):
|
||||
download_file(ftp, _WEBTV_XML_FILE, save_path, callback)
|
||||
# epg.dat
|
||||
if download_type is DownloadType.EPG:
|
||||
stb_path = properties["services_path"]
|
||||
epg_options = properties.get("epg_options", None)
|
||||
if epg_options:
|
||||
stb_path = epg_options.get("epg_dat_stb_path", stb_path)
|
||||
save_path = epg_options.get("epg_dat_path", save_path)
|
||||
ftp.cwd(stb_path)
|
||||
ftp.dir(files.append)
|
||||
for file in files:
|
||||
name = str(file).strip()
|
||||
if name.endswith("epg.dat"):
|
||||
name = name.split()[-1]
|
||||
download_file(ftp, name, save_path, callback)
|
||||
|
||||
if callback is not None:
|
||||
callback("\nDone.\n")
|
||||
callback("\nDone.\n")
|
||||
|
||||
|
||||
def upload_data(*, properties, download_type=DownloadType.ALL, remove_unused=False, profile=Profile.ENIGMA_2,
|
||||
callback=None, done_callback=None, use_http=False):
|
||||
callback=print, done_callback=None, use_http=False):
|
||||
data_path = properties["data_dir_path"]
|
||||
host = properties["host"]
|
||||
base_url = "http://{}:{}/api/".format(host, properties.get("http_port", "80"))
|
||||
@@ -137,7 +151,7 @@ def upload_data(*, properties, download_type=DownloadType.ALL, remove_unused=Fal
|
||||
upload_files(ftp, data_path, _DATA_FILES_LIST, callback)
|
||||
|
||||
if download_type is DownloadType.PICONS:
|
||||
upload_picons(ftp, properties.get("picons_dir_path"), properties.get("picons_path"))
|
||||
upload_picons(ftp, properties.get("picons_dir_path"), properties.get("picons_path"), callback)
|
||||
|
||||
if tn and not use_http:
|
||||
# resume enigma or restart neutrino
|
||||
@@ -188,7 +202,7 @@ def upload_xml(ftp, data_path, xml_path, xml_file, callback):
|
||||
send_file(xml_file, data_path, ftp, callback)
|
||||
|
||||
|
||||
def upload_picons(ftp, src, dest):
|
||||
def upload_picons(ftp, src, dest, callback):
|
||||
try:
|
||||
ftp.cwd(dest)
|
||||
except error_perm as e:
|
||||
@@ -205,7 +219,7 @@ def upload_picons(ftp, src, dest):
|
||||
ftp.delete(name)
|
||||
for file_name in os.listdir(src):
|
||||
if file_name.endswith(picons_suf):
|
||||
send_file(file_name, src, ftp)
|
||||
send_file(file_name, src, ftp, callback)
|
||||
|
||||
|
||||
def download_file(ftp, name, save_path, callback):
|
||||
@@ -259,6 +273,7 @@ def telnet(host, port=23, user="", password="", timeout=5):
|
||||
def http_request(host, port, user, password):
|
||||
base_url = "http://{}:{}/api/".format(host, port)
|
||||
init_auth(user, password, base_url)
|
||||
|
||||
while True:
|
||||
req_type, ref = yield
|
||||
url = base_url
|
||||
@@ -271,19 +286,22 @@ def http_request(host, port, user, password):
|
||||
elif req_type is HttpRequestType.STREAM:
|
||||
url = base_url + HttpRequestType.STREAM.value
|
||||
|
||||
try:
|
||||
with urlopen(url, timeout=5) as f:
|
||||
if req_type is HttpRequestType.STREAM:
|
||||
yield f.read().decode("utf-8")
|
||||
else:
|
||||
yield json.loads(f.read().decode("utf-8"))
|
||||
except (URLError, HTTPError):
|
||||
yield None
|
||||
yield from get_json(req_type, url)
|
||||
|
||||
|
||||
def get_json(req_type, url):
|
||||
try:
|
||||
with urlopen(url, timeout=5) as f:
|
||||
if req_type is HttpRequestType.STREAM:
|
||||
yield f.read().decode("utf-8")
|
||||
else:
|
||||
yield json.loads(f.read().decode("utf-8"))
|
||||
except (URLError, HTTPError):
|
||||
yield None
|
||||
|
||||
|
||||
# ***************** Connections testing *******************#
|
||||
|
||||
|
||||
def test_ftp(host, port, user, password, timeout=5):
|
||||
try:
|
||||
with FTP(host=host, user=user, passwd=password, timeout=timeout) as ftp:
|
||||
@@ -292,10 +310,11 @@ def test_ftp(host, port, user, password, timeout=5):
|
||||
raise TestException(e)
|
||||
|
||||
|
||||
def test_http(host, port, user, password, timeout=5):
|
||||
def test_http(host, port, user, password, timeout=5, skip_message=False):
|
||||
try:
|
||||
params = urlencode({"text": "Connection test", "type": 2, "timeout": timeout})
|
||||
url = "http://{}:{}/api/message?{}".format(host, port, params)
|
||||
params = "statusinfo" if skip_message else "message?{}".format(params)
|
||||
url = "http://{}:{}/api/{}".format(host, port, params)
|
||||
# authentication
|
||||
init_auth(user, password, url)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ Transponder = namedtuple("Transponder", ["frequency", "symbol_rate", "polarizati
|
||||
class TrType(Enum):
|
||||
""" Transponders type """
|
||||
Satellite = "s"
|
||||
Terestrial = "t"
|
||||
Terrestrial = "t"
|
||||
Cable = "c"
|
||||
|
||||
|
||||
@@ -98,6 +98,12 @@ class Pilot(Enum):
|
||||
Auto = "2"
|
||||
|
||||
|
||||
class SystemCable(Enum):
|
||||
""" System of cable service """
|
||||
ANNEX_A = "0"
|
||||
ANNEX_C = "1"
|
||||
|
||||
|
||||
ROLL_OFF = {"0": "35%", "1": "25%", "2": "20%", "3": "Auto"}
|
||||
|
||||
POLARIZATION = {"0": "H", "1": "V", "2": "L", "3": "R"}
|
||||
@@ -110,7 +116,7 @@ FEC = {"0": "Auto", "1": "1/2", "2": "2/3", "3": "3/4", "4": "5/6", "5": "7/8",
|
||||
"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"}
|
||||
"8": "4/5", "9": "9/10", "10": "6/7", "15": "None"}
|
||||
|
||||
SYSTEM = {"0": "DVB-S", "1": "DVB-S2"}
|
||||
|
||||
@@ -119,6 +125,26 @@ MODULATION = {"0": "Auto", "1": "QPSK", "2": "8PSK", "4": "16APSK", "5": "32APSK
|
||||
SERVICE_TYPE = {"-2": "Data", "1": "TV", "2": "Radio", "3": "Data", "10": "Radio", "22": "TV (H264)",
|
||||
"25": "TV (HD)", "31": "TV (UHD)"}
|
||||
|
||||
# Terrestrial
|
||||
BANDWIDTH = {"0": "8MHz", "1": "7MHz", "2": "6MHz", "3": "Auto", "4": "5MHz", "5": "1/712MHz", "6": "10MHz"}
|
||||
|
||||
T_MODULATION = {"0": "QPSK", "1": "QAM16", "2": "QAM64", "3": "Auto", "4": "QAM256"}
|
||||
|
||||
TRANSMISSION_MODE = {"0": "2k", "1": "8k", "2": "Auto", "3": "4k", "4": "1k", "5": "16k", "6": "32k"}
|
||||
|
||||
GUARD_INTERVAL = {"0": "1/32", "1": "1/16", "2": "1/8", "3": "1/4", "4": "Auto", "5": "1/128", "6": "19/128",
|
||||
"7": "19/256"}
|
||||
|
||||
HIERARCHY = {"0": "None", "1": "1", "2": "2", "3": "4", "4": "Auto"}
|
||||
|
||||
T_FEC = {"0": "1/2", "1": "2/3", "2": "3/4", "3": "5/6", "4": "7/8", "5": "Auto", "6": "6/7", "7": "8/9"}
|
||||
|
||||
T_SYSTEM = {"0": "DVB-T", "1": "DVB-T2", "-1": "DVB-T/T2"}
|
||||
|
||||
# Cable
|
||||
C_MODULATION = {"0": "Auto", "1": "QAM16", "2": "QAM32", "3": "QAM64", "4": "QAM128", "5": "QAM256"}
|
||||
|
||||
# CAS
|
||||
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",
|
||||
"C:0664": "Irdeto", "C:0614": "Irdeto", "C:0692": "Irdeto", "C:1801": "Nagravision", "C:0500": "Viaccess",
|
||||
|
||||
@@ -14,9 +14,10 @@ def get_bouquets(path):
|
||||
|
||||
|
||||
def write_bouquets(path, bouquets):
|
||||
srv_line = '#SERVICE 1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
|
||||
srv_line = '#SERVICE 1:7:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
|
||||
line = []
|
||||
pattern = re.compile("[^\w_()]+")
|
||||
pattern = re.compile("[^\\w_()]+")
|
||||
current_marker = [0]
|
||||
|
||||
for bqs in bouquets:
|
||||
line.clear()
|
||||
@@ -28,23 +29,29 @@ def write_bouquets(path, bouquets):
|
||||
bq_name = _DEFAULT_BOUQUET_NAME
|
||||
else:
|
||||
bq_name = re.sub(pattern, "_", bq.name)
|
||||
line.append(srv_line.format(bq_name, bq.type))
|
||||
write_bouquet(path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services)
|
||||
line.append(srv_line.format(2 if bq.type == BqType.RADIO.value else 1, bq_name, bq.type))
|
||||
write_bouquet(path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services, current_marker)
|
||||
|
||||
with open(path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
|
||||
file.writelines(line)
|
||||
|
||||
|
||||
def write_bouquet(path, name, channels):
|
||||
def write_bouquet(path, name, services, current_marker):
|
||||
bouquet = ["#NAME {}\n".format(name)]
|
||||
marker = "#SERVICE 1:64:{:X}:0:0:0:0:0:0:0::{}\n"
|
||||
|
||||
for ch in channels:
|
||||
if ch.service_type == BqServiceType.IPTV.name or ch.service_type == BqServiceType.MARKER.name:
|
||||
bouquet.append("#SERVICE {}\n".format(ch.fav_id.strip()))
|
||||
for srv in services:
|
||||
if srv.service_type == BqServiceType.IPTV.name:
|
||||
bouquet.append("#SERVICE {}\n".format(srv.fav_id.strip()))
|
||||
elif srv.service_type == BqServiceType.MARKER.name:
|
||||
m_data = srv.fav_id.strip().split(":")
|
||||
m_data[2] = current_marker[0]
|
||||
current_marker[0] += 1
|
||||
bouquet.append(marker.format(m_data[2], m_data[-1]))
|
||||
else:
|
||||
data = to_bouquet_id(ch)
|
||||
if ch.service:
|
||||
bouquet.append("#SERVICE {}:{}\n#DESCRIPTION {}\n".format(data, ch.service, ch.service))
|
||||
data = to_bouquet_id(srv)
|
||||
if srv.service:
|
||||
bouquet.append("#SERVICE {}:{}\n#DESCRIPTION {}\n".format(data, srv.service, srv.service))
|
||||
else:
|
||||
bouquet.append("#SERVICE {}\n".format(data))
|
||||
|
||||
@@ -52,13 +59,13 @@ def write_bouquet(path, name, channels):
|
||||
file.writelines(bouquet)
|
||||
|
||||
|
||||
def to_bouquet_id(ch):
|
||||
def to_bouquet_id(srv):
|
||||
""" Creates bouquet channel id """
|
||||
data_type = ch.data_id
|
||||
data_type = srv.data_id
|
||||
if data_type and len(data_type) > 4:
|
||||
data_type = int(ch.data_id.split(":")[4])
|
||||
data_type = int(srv.data_id.split(":")[4])
|
||||
|
||||
return "{}:0:{:X}:{}:0:0:0:".format(1, data_type, ch.fav_id)
|
||||
return "{}:0:{:X}:{}:0:0:0:".format(1, data_type, srv.fav_id)
|
||||
|
||||
|
||||
def get_bouquet(path, name, bq_type):
|
||||
@@ -70,15 +77,17 @@ def get_bouquet(path, name, bq_type):
|
||||
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]))
|
||||
marker_data = ch.split("#DESCRIPTION", 1)
|
||||
services.append(BouquetService(marker_data[1].strip(), BqServiceType.MARKER, ch, ch_data[2]))
|
||||
elif "http" in ch:
|
||||
services.append(BouquetService(ch_data[-1].split("\n")[0], BqServiceType.IPTV, ch, 0))
|
||||
stream_data = ch.split("#DESCRIPTION", 1)
|
||||
services.append(BouquetService(stream_data[-1].strip(":").strip(), BqServiceType.IPTV, ch, 0))
|
||||
else:
|
||||
fav_id = "{}:{}:{}:{}".format(ch_data[3], ch_data[4], ch_data[5], ch_data[6])
|
||||
name = None
|
||||
if len(ch_data) == 12:
|
||||
name, desc = str(ch_data[-1]).split("\n#DESCRIPTION")
|
||||
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id, 0))
|
||||
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id.upper(), 0))
|
||||
|
||||
return srvs[0].lstrip("#NAME").strip(), services
|
||||
|
||||
@@ -88,18 +97,23 @@ def parse_bouquets(path, bq_name, bq_type):
|
||||
lines = file.readlines()
|
||||
bouquets = None
|
||||
nm_sep = "#NAME"
|
||||
bq_pattern = re.compile(".*userbouquet\\.+(.*)\\.+[tv|radio].*")
|
||||
|
||||
for line in lines:
|
||||
if nm_sep in line:
|
||||
_, _, name = line.partition(nm_sep)
|
||||
bouquets = Bouquets(name.strip(), bq_type, [])
|
||||
if bouquets and "#SERVICE" in line:
|
||||
b_name, services = get_bouquet(path, line.split(".")[1], bq_type)
|
||||
bouquets[2].append(Bouquet(name=b_name,
|
||||
type=bq_type,
|
||||
services=services,
|
||||
locked=None,
|
||||
hidden=None))
|
||||
name = re.match(bq_pattern, line)
|
||||
if name:
|
||||
b_name, services = get_bouquet(path, name.group(1), bq_type)
|
||||
bouquets[2].append(Bouquet(name=b_name,
|
||||
type=bq_type,
|
||||
services=services,
|
||||
locked=None,
|
||||
hidden=None))
|
||||
else:
|
||||
raise ValueError("No bouquet name found for: {}".format(line))
|
||||
|
||||
return bouquets
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import re
|
||||
from app.commons import log
|
||||
from app.ui.uicommons import CODED_ICON, LOCKED_ICON, HIDE_ICON
|
||||
from .blacklist import get_blacklist
|
||||
from ..ecommons import Service, POLARIZATION, FEC, SERVICE_TYPE, Flag
|
||||
from ..ecommons import Service, POLARIZATION, FEC, SERVICE_TYPE, Flag, T_FEC, TrType, FEC_DEFAULT, T_SYSTEM
|
||||
|
||||
_HEADER = "eDVB services /{}/"
|
||||
_SEP = ":" # separator
|
||||
@@ -170,15 +170,15 @@ def parse_transponders(arg):
|
||||
|
||||
|
||||
def parse_services(services, transponders, path):
|
||||
""" Parsing channels """
|
||||
channels = []
|
||||
""" Parsing services """
|
||||
services_list = []
|
||||
blacklist = str(get_blacklist(path))
|
||||
srv = split(services, 3)
|
||||
if srv[0][0] == "": # remove first empty element
|
||||
srv.remove(srv[0])
|
||||
srvs = split(services, 3)
|
||||
if srvs[0][0] == "": # remove first empty element
|
||||
srvs.remove(srvs[0])
|
||||
|
||||
for ch in srv:
|
||||
data_id = str(ch[0]).lower() # lower is for lamedb ver.3
|
||||
for srv in srvs:
|
||||
data_id = str(srv[0]).lower() # lower is for lamedb ver.3
|
||||
data = data_id.split(_SEP)
|
||||
sp = "0"
|
||||
tid = data[2]
|
||||
@@ -209,7 +209,7 @@ def parse_services(services, transponders, path):
|
||||
fav_id = "{}:{}:{}:{}".format(ssid, tid, nid, onid)
|
||||
picon_id = "1_0_{:X}_{}_{}_{}_{}_0_0_0.png".format(srv_type, ssid, tid, nid, onid)
|
||||
|
||||
all_flags = ch[2].split(",")
|
||||
all_flags = srv[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 Flag.is_hide(int(flags[0][2:])) else None
|
||||
@@ -220,42 +220,51 @@ def parse_services(services, transponders, path):
|
||||
|
||||
if transponder is not None:
|
||||
tr_type, sp, tr = str(transponder).partition(" ")
|
||||
tr_type = TrType(tr_type)
|
||||
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())
|
||||
srv_name = "".join(c for c in srv[1] if c.isprintable())
|
||||
pol = None
|
||||
fec = None
|
||||
system = None
|
||||
pos = None
|
||||
|
||||
if tr_type == "t":
|
||||
system = "DVB-T"
|
||||
pos = "T"
|
||||
elif tr_type == "c":
|
||||
system = "CABLE"
|
||||
pos = "C"
|
||||
else:
|
||||
if tr_type is TrType.Satellite:
|
||||
pol = POLARIZATION.get(tr[2], None)
|
||||
fec = FEC.get(tr[3], None)
|
||||
system = "DVB-S2" if len(tr) > 7 else "DVB-S"
|
||||
pos = "{}.{}".format(tr[4][:-1], tr[4][-1:])
|
||||
if tr_type is TrType.Terrestrial:
|
||||
system = T_SYSTEM.get(tr[9], None)
|
||||
pos = "T"
|
||||
fec = T_FEC.get(tr[3], None)
|
||||
elif tr_type is TrType.Cable:
|
||||
system = "DVB-C"
|
||||
pos = "C"
|
||||
fec = FEC_DEFAULT.get(tr[4])
|
||||
|
||||
channels.append(Service(flags_cas=ch[2],
|
||||
transponder_type=tr_type,
|
||||
coded=coded,
|
||||
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.get(tr[2], None),
|
||||
fec=FEC[tr[3]],
|
||||
system=system,
|
||||
pos=pos,
|
||||
data_id=data_id,
|
||||
fav_id=fav_id,
|
||||
transponder=transponder))
|
||||
return channels
|
||||
services_list.append(Service(flags_cas=srv[2],
|
||||
transponder_type=tr_type.value,
|
||||
coded=coded,
|
||||
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=pol,
|
||||
fec=fec,
|
||||
system=system,
|
||||
pos=pos,
|
||||
data_id=data_id,
|
||||
fav_id=fav_id,
|
||||
transponder=transponder))
|
||||
return services_list
|
||||
|
||||
|
||||
def split(itr, size):
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
""" Module for IPTV and streams support """
|
||||
import re
|
||||
import urllib.request
|
||||
from enum import Enum
|
||||
|
||||
from app.properties import Profile
|
||||
@@ -14,6 +16,8 @@ MARKER_FORMAT = " 1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n"
|
||||
class StreamType(Enum):
|
||||
DVB_TS = "1"
|
||||
NONE_TS = "4097"
|
||||
NONE_REC_1 = "5001"
|
||||
NONE_REC_2 = "5002"
|
||||
|
||||
|
||||
def parse_m3u(path, profile):
|
||||
@@ -23,7 +27,7 @@ def parse_m3u(path, profile):
|
||||
groups = set()
|
||||
counter = 0
|
||||
name = None
|
||||
fav_id = None
|
||||
|
||||
for line in file.readlines():
|
||||
if line.startswith("#EXTINF"):
|
||||
name = line[1 + line.index(","):].strip()
|
||||
@@ -36,16 +40,47 @@ def parse_m3u(path, profile):
|
||||
mr = Service(None, None, None, grp_name, *aggr[0:3], BqServiceType.MARKER.name, *aggr, fav_id, None)
|
||||
services.append(mr)
|
||||
elif not line.startswith("#"):
|
||||
if profile is Profile.ENIGMA_2:
|
||||
fav_id = ENIGMA2_FAV_ID_FORMAT.format(StreamType.NONE_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)
|
||||
services.append(srv)
|
||||
url = line.strip()
|
||||
fav_id = get_fav_id(url, name, profile)
|
||||
if name and url:
|
||||
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], BqServiceType.IPTV.name, *aggr, fav_id, None)
|
||||
services.append(srv)
|
||||
|
||||
return services
|
||||
|
||||
|
||||
def export_to_m3u(path, bouquet, profile):
|
||||
pattern = re.compile(".*:(http.*):.*") if profile is Profile.ENIGMA_2 else re.compile("(http.*?)::::.*")
|
||||
lines = ["#EXTM3U\n"]
|
||||
current_grp = None
|
||||
|
||||
for s in bouquet.services:
|
||||
s_type = s.type
|
||||
if s_type is BqServiceType.IPTV:
|
||||
res = re.match(pattern, s.data)
|
||||
if not res:
|
||||
continue
|
||||
data = res.group(1)
|
||||
lines.append("#EXTINF:-1,{}\n".format(s.name))
|
||||
if current_grp:
|
||||
lines.append(current_grp)
|
||||
lines.append("{}\n".format(urllib.request.unquote(data.strip())))
|
||||
elif s_type is BqServiceType.MARKER:
|
||||
current_grp = "#EXTGRP:{}\n".format(s.name)
|
||||
|
||||
with open(path + "{}.m3u".format(bouquet.name), "w", encoding="utf-8") as file:
|
||||
file.writelines(lines)
|
||||
|
||||
|
||||
def get_fav_id(url, service_name, profile):
|
||||
""" Returns fav id depending on the profile. """
|
||||
if profile is Profile.ENIGMA_2:
|
||||
url = urllib.request.quote(url)
|
||||
stream_type = StreamType.NONE_TS.value
|
||||
return ENIGMA2_FAV_ID_FORMAT.format(stream_type, 1, 0, 0, 0, 0, url, service_name, service_name, None)
|
||||
elif profile is Profile.NEUTRINO_MP:
|
||||
return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -50,10 +50,9 @@ def parse_bouquets(file, name, bq_type):
|
||||
if BqType(bq_type) is BqType.BOUQUET:
|
||||
for bq in bouquets.bouquets:
|
||||
if bq.services:
|
||||
name = bq.name
|
||||
name = name[name.index("]") + 1:]
|
||||
key = int(bq.services[0].data.split(":")[1], 16)
|
||||
if key not in PROVIDER:
|
||||
pos, sep, name = bq.name.partition("]")
|
||||
PROVIDER[key] = name
|
||||
|
||||
return bouquets
|
||||
|
||||
@@ -2,13 +2,10 @@
|
||||
|
||||
For more info see __COMMENT
|
||||
"""
|
||||
from functools import lru_cache
|
||||
from xml.dom.minidom import parse, Document
|
||||
|
||||
import os
|
||||
|
||||
from app.commons import log
|
||||
from .ecommons import POLARIZATION, FEC, SYSTEM, MODULATION, PLS_MODE, Transponder, Satellite, get_key_by_value
|
||||
from .ecommons import POLARIZATION, FEC, SYSTEM, MODULATION, Transponder, Satellite, get_key_by_value
|
||||
|
||||
__COMMENT = (" File was created in DemonEditor\n\n"
|
||||
"usable flags are\n"
|
||||
@@ -33,7 +30,7 @@ __COMMENT = (" File was created in DemonEditor\n\n"
|
||||
|
||||
|
||||
def get_satellites(path):
|
||||
return parse_satellites(path, os.path.getsize(path))
|
||||
return parse_satellites(path)
|
||||
|
||||
|
||||
def write_satellites(satellites, data_path):
|
||||
@@ -60,7 +57,7 @@ def write_satellites(satellites, data_path):
|
||||
transponder_child.setAttribute("system", get_key_by_value(SYSTEM, tr.system))
|
||||
transponder_child.setAttribute("modulation", get_key_by_value(MODULATION, tr.modulation))
|
||||
if tr.pls_mode:
|
||||
transponder_child.setAttribute("pls_mode", get_key_by_value(PLS_MODE, tr.pls_mode))
|
||||
transponder_child.setAttribute("pls_mode", tr.pls_mode)
|
||||
if tr.pls_code:
|
||||
transponder_child.setAttribute("pls_code", tr.pls_code)
|
||||
if tr.is_id:
|
||||
@@ -88,7 +85,7 @@ def parse_transponders(elem, sat_name):
|
||||
FEC[atr["fec_inner"].value],
|
||||
SYSTEM[atr["system"].value],
|
||||
MODULATION[atr["modulation"].value],
|
||||
PLS_MODE[atr["pls_mode"].value] if "pls_mode" in atr else None,
|
||||
atr["pls_mode"].value if "pls_mode" in atr else None,
|
||||
atr["pls_code"].value if "pls_code" in atr else None,
|
||||
atr["is_id"].value if "is_id" in atr else None)
|
||||
except Exception as e:
|
||||
@@ -109,8 +106,7 @@ def parse_sat(elem):
|
||||
parse_transponders(elem, sat_name))
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def parse_satellites(path, file_size):
|
||||
def parse_satellites(path):
|
||||
""" Parsing satellites from xml"""
|
||||
dom = parse(path)
|
||||
satellites = []
|
||||
|
||||
@@ -43,16 +43,23 @@ def get_default_settings():
|
||||
"http_user": "root", "http_password": "", "http_port": "80", "http_timeout": 5,
|
||||
"telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 5,
|
||||
"services_path": "/etc/enigma2/", "user_bouquet_path": "/etc/enigma2/",
|
||||
"satellites_xml_path": "/etc/tuxbox/", "data_dir_path": DATA_PATH + "enigma2/",
|
||||
"satellites_xml_path": "/etc/tuxbox/", "data_dir_path": DATA_PATH + "enigma2/",
|
||||
"picons_path": "/usr/share/enigma2/picon", "picons_dir_path": DATA_PATH + "enigma2/picons/",
|
||||
"v5_support": False, "http_api_support": False},
|
||||
"backup_dir_path": DATA_PATH + "enigma2/backup/",
|
||||
"backup_before_save": True, "backup_before_downloading": True,
|
||||
"v5_support": False, "http_api_support": False,
|
||||
"use_colors": True, "new_color": "rgb(255,230,204)", "extra_color": "rgb(179,230,204)",
|
||||
"fav_click_mode": 0},
|
||||
Profile.NEUTRINO_MP.value: {
|
||||
"host": "127.0.0.1", "port": "21", "user": "root", "password": "root",
|
||||
"http_user": "", "http_password": "", "http_port": "80", "http_timeout": 2,
|
||||
"telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 1,
|
||||
"services_path": "/var/tuxbox/config/zapit/", "user_bouquet_path": "/var/tuxbox/config/zapit/",
|
||||
"satellites_xml_path": "/var/tuxbox/config/", "data_dir_path": DATA_PATH + "neutrino/",
|
||||
"picons_path": "/usr/share/tuxbox/neutrino/icons/logo/", "picons_dir_path": DATA_PATH + "neutrino/picons/"},
|
||||
"picons_path": "/usr/share/tuxbox/neutrino/icons/logo/", "picons_dir_path": DATA_PATH + "neutrino/picons/",
|
||||
"backup_dir_path": DATA_PATH + "neutrino/backup/",
|
||||
"backup_before_save": True, "backup_before_downloading": True,
|
||||
"fav_click_mode": 0},
|
||||
"profile": Profile.ENIGMA_2.value}
|
||||
|
||||
|
||||
|
||||
110
app/tools/epg.py
Normal file
110
app/tools/epg.py
Normal file
@@ -0,0 +1,110 @@
|
||||
""" Module for working with epg.dat file """
|
||||
import struct
|
||||
from datetime import datetime
|
||||
from xml.dom.minidom import parse, Node, Document
|
||||
|
||||
from app.eparser.ecommons import BqServiceType, BouquetService
|
||||
|
||||
|
||||
class EPG:
|
||||
|
||||
@staticmethod
|
||||
def get_epg_refs(path):
|
||||
""" The read algorithm was taken from the eEPGCache::load() function from this source:
|
||||
https://github.com/OpenPLi/enigma2/blob/44d9b92f5260c7de1b3b3a1b9a9cbe0f70ca4bf0/lib/dvb/epgcache.cpp#L1300
|
||||
"""
|
||||
refs = set()
|
||||
|
||||
with open(path, mode="rb") as f:
|
||||
crc = struct.unpack("<I", f.read(4))[0]
|
||||
if crc != int(0x98765432):
|
||||
raise ValueError("Epg file has incorrect byte order!")
|
||||
|
||||
header = f.read(13).decode()
|
||||
if header != "ENIGMA_EPG_V7":
|
||||
raise ValueError("Unsupported format of epd.dat file!")
|
||||
|
||||
channels_count = struct.unpack("<I", f.read(4))[0]
|
||||
|
||||
for i in range(channels_count):
|
||||
sid, nid, tsid, events_size = struct.unpack("<IIII", f.read(16))
|
||||
service_id = "{:X}:{:X}:{:X}".format(sid, tsid, nid)
|
||||
|
||||
for j in range(events_size):
|
||||
_type, _len = struct.unpack("<BB", f.read(2))
|
||||
f.read(10)
|
||||
n_crc = (_len - 10) // 4
|
||||
if n_crc > 0:
|
||||
[f.read(4) for n in range(n_crc)]
|
||||
|
||||
refs.add(service_id)
|
||||
|
||||
return refs
|
||||
|
||||
|
||||
class ChannelsParser:
|
||||
_COMMENT = "File was created in DemonEditor"
|
||||
|
||||
@staticmethod
|
||||
def get_refs_from_xml(path):
|
||||
""" Returns tuple from references and description. """
|
||||
refs = []
|
||||
dom = parse(path)
|
||||
description = "".join(n.data + "\n" for n in dom.childNodes if n.nodeType == Node.COMMENT_NODE)
|
||||
|
||||
for elem in dom.getElementsByTagName("channels"):
|
||||
c_count = 0
|
||||
comment_count = 0
|
||||
current_data = ""
|
||||
|
||||
if elem.hasChildNodes():
|
||||
for n in elem.childNodes:
|
||||
if n.nodeType == Node.COMMENT_NODE:
|
||||
c_count += 1
|
||||
comment_count += 1
|
||||
txt = n.data.strip()
|
||||
if comment_count:
|
||||
comment_count -= 1
|
||||
else:
|
||||
ref_data = current_data.split(":")
|
||||
refs.append(BouquetService(name=txt,
|
||||
type=BqServiceType.DEFAULT,
|
||||
data="{}:{}:{}:{}".format(*ref_data[3:7]).upper(),
|
||||
num="{}:{}:{}".format(*ref_data[3:6]).upper()))
|
||||
|
||||
if n.hasChildNodes():
|
||||
for s_node in n.childNodes:
|
||||
if s_node.nodeType == Node.TEXT_NODE:
|
||||
comment_count -= 1
|
||||
current_data = s_node.data
|
||||
return refs, description
|
||||
|
||||
@staticmethod
|
||||
def write_refs_to_xml(path, services):
|
||||
header = '<?xml version="1.0" encoding="utf-8"?>\n<!-- {} -->\n<!-- {} -->\n<channels>\n'.format(
|
||||
"Created in DemonEditor.", datetime.now().strftime("%d.%m.%Y %H:%M:%S"))
|
||||
doc = Document()
|
||||
lines = [header]
|
||||
|
||||
for srv in services:
|
||||
srv_type = srv.type
|
||||
if srv_type is BqServiceType.IPTV:
|
||||
channel_child = doc.createElement("channel")
|
||||
channel_child.setAttribute("id", str(srv.num))
|
||||
data = srv.data.strip().split(":")
|
||||
channel_child.appendChild(doc.createTextNode(":".join(data[:10])))
|
||||
comment = doc.createComment(srv.name)
|
||||
lines.append("{} {}\n".format(str(channel_child.toxml()), str(comment.toxml())))
|
||||
elif srv_type is BqServiceType.MARKER:
|
||||
comment = doc.createComment(srv.name)
|
||||
lines.append("{}\n".format(str(comment.toxml())))
|
||||
|
||||
lines.append("</channels>")
|
||||
doc.unlink()
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -1,13 +1,25 @@
|
||||
from app.commons import run_task
|
||||
from app.tools import vlc
|
||||
from app.tools.vlc import EventType
|
||||
|
||||
|
||||
class Player:
|
||||
_VLC_INSTANCE = None
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, rewind_callback=None, position_callback=None):
|
||||
self._is_playing = False
|
||||
self._player = self.get_vlc_instance()
|
||||
ev_mgr = self._player.event_manager()
|
||||
|
||||
if rewind_callback:
|
||||
# TODO look other EventType options
|
||||
ev_mgr.event_attach(EventType.MediaPlayerBuffering,
|
||||
lambda e, p: rewind_callback(p.get_media().get_duration()),
|
||||
self._player)
|
||||
if position_callback:
|
||||
ev_mgr.event_attach(EventType.MediaPlayerTimeChanged,
|
||||
lambda e, p: position_callback(p.get_time()),
|
||||
self._player)
|
||||
|
||||
@staticmethod
|
||||
def get_vlc_instance():
|
||||
@@ -32,6 +44,9 @@ class Player:
|
||||
def pause(self):
|
||||
self._player.pause()
|
||||
|
||||
def set_time(self, time):
|
||||
self._player.set_time(time)
|
||||
|
||||
@run_task
|
||||
def release(self):
|
||||
if self._player:
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
for replace or update current satellites.xml file.
|
||||
"""
|
||||
import re
|
||||
|
||||
import requests
|
||||
from enum import Enum
|
||||
from html.parser import HTMLParser
|
||||
|
||||
from app.commons import log
|
||||
from app.eparser import Satellite, Transponder, is_transponder_valid
|
||||
from app.eparser.ecommons import PLS_MODE
|
||||
|
||||
|
||||
class SatelliteSource(Enum):
|
||||
@@ -79,14 +82,14 @@ class SatellitesParser(HTMLParser):
|
||||
try:
|
||||
request = requests.get(url=src, headers=self._HEADERS)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
print(repr(e))
|
||||
log(repr(e))
|
||||
return []
|
||||
else:
|
||||
reason = request.reason
|
||||
if reason == "OK":
|
||||
self.feed(request.text)
|
||||
else:
|
||||
print(reason)
|
||||
log(reason)
|
||||
|
||||
if self._rows:
|
||||
if self._source is SatelliteSource.FLYSAT:
|
||||
@@ -102,7 +105,9 @@ class SatellitesParser(HTMLParser):
|
||||
r_len = len(row)
|
||||
if r_len == 7:
|
||||
current_pos = self.parse_position(row[2])
|
||||
sats.append((row[4], current_pos, row[5], row[1], False))
|
||||
name = row[1].rsplit("/")[-1].rstrip(".html").replace("-", " ")
|
||||
sats.append((name, current_pos, row[5], row[1], False)) # coupled [all in one] satellites
|
||||
sats.append((row[4], current_pos, row[5], row[3], False))
|
||||
if r_len == 8: # for a very limited number of satellites
|
||||
data = list(filter(None, row))
|
||||
urls = set()
|
||||
@@ -123,7 +128,7 @@ class SatellitesParser(HTMLParser):
|
||||
|
||||
def get_satellite(self, sat):
|
||||
pos = sat[1]
|
||||
return Satellite(name=sat[0] + " ({})".format(pos),
|
||||
return Satellite(name="{} {}".format(pos, sat[0]),
|
||||
flags="0",
|
||||
position=self.get_position(pos.replace(".", "")),
|
||||
transponders=self.get_transponders(sat[3]))
|
||||
@@ -137,6 +142,7 @@ class SatellitesParser(HTMLParser):
|
||||
return "{}{}".format("-" if pos[-1] == "W" else "", pos[:-1])
|
||||
|
||||
def get_transponders(self, sat_url):
|
||||
""" Getting transponders(sorted by frequency). """
|
||||
self._rows.clear()
|
||||
url = "https://www.flysat.com/" + sat_url if self._source is SatelliteSource.FLYSAT else sat_url
|
||||
request = requests.get(url=url, headers=self._HEADERS)
|
||||
@@ -148,13 +154,23 @@ class SatellitesParser(HTMLParser):
|
||||
self.get_transponders_for_fly_sat(trs)
|
||||
elif self._source is SatelliteSource.LYNGSAT:
|
||||
self.get_transponders_for_lyng_sat(trs)
|
||||
return trs
|
||||
|
||||
return sorted(trs, key=lambda x: int(x.frequency))
|
||||
|
||||
def get_transponders_for_fly_sat(self, trs):
|
||||
""" Parsing transponders for FlySat """
|
||||
pls_pattern = re.compile("(PLS:)+ (Root|Gold|Combo)+ (\\d+)?")
|
||||
is_id_pattern = re.compile("(Stream) (\\d+)")
|
||||
pls_modes = {v: k for k, v in PLS_MODE.items()}
|
||||
n_trs = []
|
||||
|
||||
if self._rows:
|
||||
zeros = "000"
|
||||
is_ids = []
|
||||
for r in self._rows:
|
||||
if len(r) == 1:
|
||||
is_ids.extend(re.findall(is_id_pattern, r[0]))
|
||||
continue
|
||||
if len(r) < 3:
|
||||
continue
|
||||
data = r[2].split(" ")
|
||||
@@ -171,18 +187,41 @@ class SatellitesParser(HTMLParser):
|
||||
sys, mod = sys
|
||||
mod = "QPSK" if sys == "DVB-S" else mod
|
||||
|
||||
tr = Transponder(freq + zeros, sr + zeros, pol, fec, sys, mod, None, None, None)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
pls = re.findall(pls_pattern, r[1])
|
||||
pls_code = None
|
||||
pls_mode = None
|
||||
|
||||
if pls:
|
||||
pls_code = pls[0][2]
|
||||
pls_mode = pls_modes.get(pls[0][1], None)
|
||||
|
||||
if is_ids:
|
||||
tr = trs.pop()
|
||||
for index, is_id in enumerate(is_ids):
|
||||
tr = tr._replace(is_id=is_id[1])
|
||||
if is_transponder_valid(tr):
|
||||
n_trs.append(tr)
|
||||
else:
|
||||
tr = Transponder(freq + zeros, sr + zeros, pol, fec, sys, mod, pls_mode, pls_code, None)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
is_ids.clear()
|
||||
trs.extend(n_trs)
|
||||
|
||||
def get_transponders_for_lyng_sat(self, trs):
|
||||
""" Parsing transponders for LyngSat """
|
||||
frq_pol_pattern = re.compile("(\d{4,5}).*([RLHV])(.*\d$)")
|
||||
sr_fec_pattern = re.compile("^(\d{4,5})-(\d/\d)(.+PSK)?(.*)?$")
|
||||
sys_pattern = re.compile("(DVB-S[2]?)(.*)?")
|
||||
frq_pol_pattern = re.compile("(\\d{4,5})\\s+([RLHV]).*")
|
||||
sr_fec_pattern = re.compile("^(\\d{4,5})-(\\d/\\d)(.+PSK)?(.*)?$")
|
||||
sys_pattern = re.compile("(DVB-S[2]?) ?(PLS+ (Root|Gold|Combo)+ (\\d+))* ?(multistream stream (\\d+))?",
|
||||
re.IGNORECASE)
|
||||
zeros = "000"
|
||||
pls_modes = {v: k for k, v in PLS_MODE.items()}
|
||||
|
||||
for r in filter(lambda x: len(x) > 8, self._rows):
|
||||
freq = re.match(frq_pol_pattern, r[2])
|
||||
for frq in r[1], r[2], r[3]:
|
||||
freq = re.match(frq_pol_pattern, frq)
|
||||
if freq:
|
||||
break
|
||||
if not freq:
|
||||
continue
|
||||
frq, pol = freq.group(1), freq.group(2)
|
||||
@@ -191,12 +230,18 @@ class SatellitesParser(HTMLParser):
|
||||
continue
|
||||
sr, fec, mod = sr_fec.group(1), sr_fec.group(2), sr_fec.group(3)
|
||||
mod = mod.strip() if mod else "Auto"
|
||||
sys = re.match(sys_pattern, r[-4])
|
||||
if not sys:
|
||||
continue
|
||||
sys = sys.group(1)
|
||||
|
||||
tr = Transponder(frq + zeros, sr + zeros, pol, fec, sys, mod, None, None, None)
|
||||
res = re.match(sys_pattern, r[-4])
|
||||
if not res:
|
||||
continue
|
||||
|
||||
sys = res.group(1)
|
||||
pls_mode = res.group(3)
|
||||
pls_mode = pls_modes.get(pls_mode.capitalize(), None) if pls_mode else pls_mode
|
||||
pls_code = res.group(4)
|
||||
pls_id = res.group(6)
|
||||
|
||||
tr = Transponder(frq + zeros, sr + zeros, pol, fec, sys, mod, pls_mode, pls_code, pls_id)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
|
||||
|
||||
167
app/tools/yt.py
Normal file
167
app/tools/yt.py
Normal file
@@ -0,0 +1,167 @@
|
||||
""" Module for working with YouTube service """
|
||||
import gzip
|
||||
import json
|
||||
import re
|
||||
import urllib
|
||||
from html.parser import HTMLParser
|
||||
from json import JSONDecodeError
|
||||
from urllib.request import Request
|
||||
|
||||
from app.commons import log
|
||||
|
||||
_YT_PATTERN = re.compile(r"https://www.youtube.com/.+(?:v=)([\w-]{11}).*")
|
||||
_YT_LIST_PATTERN = re.compile(r"https://www.youtube.com/.+?(?:list=)([\w-]{23,})?.*")
|
||||
_YT_VIDEO_PATTERN = re.compile(r"https://r\d+---sn-[\w]{10}-[\w]{3,5}.googlevideo.com/videoplayback?.*")
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/69.0",
|
||||
"DNT": "1",
|
||||
"Accept-Encoding": "gzip, deflate"}
|
||||
|
||||
Quality = {137: "1080p", 136: "720p", 135: "480p", 134: "360p",
|
||||
133: "240p", 160: "144p", 0: "0p", 18: "360p", 22: "720p"}
|
||||
|
||||
|
||||
class YouTube:
|
||||
""" Helper class for working with YouTube service. """
|
||||
|
||||
@staticmethod
|
||||
def is_yt_video_link(url):
|
||||
return re.match(_YT_VIDEO_PATTERN, url)
|
||||
|
||||
@staticmethod
|
||||
def get_yt_id(url):
|
||||
""" Returns video id or None """
|
||||
yt = re.search(_YT_PATTERN, url)
|
||||
if yt:
|
||||
return yt.group(1)
|
||||
|
||||
@staticmethod
|
||||
def get_yt_list_id(url):
|
||||
""" Returns playlist id or None """
|
||||
yt = re.search(_YT_LIST_PATTERN, url)
|
||||
if yt:
|
||||
return yt.group(1)
|
||||
|
||||
@staticmethod
|
||||
def get_yt_link(video_id):
|
||||
""" Getting link to YouTube video by id.
|
||||
|
||||
returns tuple from the video links dict and title
|
||||
"""
|
||||
req = Request("https://youtube.com/get_video_info?video_id={}&hl=en".format(video_id), headers=_HEADERS)
|
||||
|
||||
with urllib.request.urlopen(req, timeout=2) as resp:
|
||||
data = urllib.request.unquote(gzip.decompress(resp.read()).decode("utf-8")).split("&")
|
||||
out = {k: v for k, sep, v in (str(d).partition("=") for d in map(urllib.request.unquote, data))}
|
||||
player_resp = out.get("player_response", None)
|
||||
|
||||
if player_resp:
|
||||
try:
|
||||
resp = json.loads(player_resp)
|
||||
except JSONDecodeError as e:
|
||||
log("{}: Parsing player response error: {}".format(__class__.__name__, e))
|
||||
else:
|
||||
det = resp.get("videoDetails", None)
|
||||
title = det.get("title", None) if det else None
|
||||
streaming_data = resp.get("streamingData", None)
|
||||
fmts = streaming_data.get("formats", None) if streaming_data else None
|
||||
|
||||
if fmts:
|
||||
urls = {Quality[i["itag"]]: i["url"] for i in
|
||||
filter(lambda i: i.get("itag", -1) in Quality, fmts)}
|
||||
|
||||
if urls and title:
|
||||
return urls, title.replace("+", " ")
|
||||
|
||||
stream_map = out.get("url_encoded_fmt_stream_map", None)
|
||||
if stream_map:
|
||||
s_map = {k: v for k, sep, v in (str(d).partition("=") for d in stream_map.split("&"))}
|
||||
url, title = s_map.get("url", None), out.get("title", None)
|
||||
url, title = urllib.request.unquote(url) if url else "", title.replace("+", " ") if title else ""
|
||||
if url and title:
|
||||
return {Quality[0]: url}, title.replace("+", " ")
|
||||
|
||||
rsn = out.get("reason", None)
|
||||
rsn = rsn.replace("+", " ") if rsn else ""
|
||||
log("{}: Getting link to video with id {} filed! Cause: {}".format(__class__.__name__, video_id, rsn))
|
||||
|
||||
return None, rsn
|
||||
|
||||
|
||||
class PlayListParser(HTMLParser):
|
||||
""" Very simple parser to handle YouTube playlist pages. """
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._is_header = False
|
||||
self._header = ""
|
||||
self._playlist = []
|
||||
self._is_script = False
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "script":
|
||||
self._is_script = True
|
||||
|
||||
def handle_data(self, data):
|
||||
if self._is_script:
|
||||
data = data.lstrip()
|
||||
if data.startswith('window["ytInitialData"] = '):
|
||||
data = data.split(";")[0].lstrip('window["ytInitialData"] = ')
|
||||
try:
|
||||
resp = json.loads(data)
|
||||
except JSONDecodeError as e:
|
||||
log("{}: Parsing data error: {}".format(__class__.__name__, e))
|
||||
else:
|
||||
sb = resp.get("sidebar", None)
|
||||
if sb:
|
||||
for t in [t["runs"][0] for t in flat("title", sb) if "runs" in t]:
|
||||
txt = t.get("text", None)
|
||||
if txt:
|
||||
self._header = txt
|
||||
break
|
||||
|
||||
ct = resp.get("contents", None)
|
||||
if ct:
|
||||
for d in [(d["title"]["simpleText"], d["videoId"]) for d in flat("playlistVideoRenderer", ct)]:
|
||||
self._playlist.append(d)
|
||||
self._is_script = False
|
||||
|
||||
def error(self, message):
|
||||
log("{} Parsing error: {}".format(__class__.__name__, message))
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return self._header
|
||||
|
||||
@property
|
||||
def playlist(self):
|
||||
return self._playlist
|
||||
|
||||
@staticmethod
|
||||
def get_yt_playlist(play_list_id):
|
||||
""" Getting YouTube playlist by id.
|
||||
|
||||
returns tuple from the playlist header and list of tuples (title, video id)
|
||||
"""
|
||||
request = Request("https://www.youtube.com/playlist?list={}&hl=en".format(play_list_id), headers=_HEADERS)
|
||||
|
||||
with urllib.request.urlopen(request, timeout=2) as resp:
|
||||
data = gzip.decompress(resp.read()).decode("utf-8")
|
||||
parser = PlayListParser()
|
||||
parser.feed(data)
|
||||
return parser.header, parser.playlist
|
||||
|
||||
|
||||
def flat(key, d):
|
||||
for k, v in d.items():
|
||||
if k == key:
|
||||
yield v
|
||||
elif isinstance(v, dict):
|
||||
yield from flat(key, v)
|
||||
elif isinstance(v, list):
|
||||
for el in v:
|
||||
if isinstance(el, dict):
|
||||
yield from flat(key, el)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
219
app/ui/backup.py
Normal file
219
app/ui/backup.py
Normal file
@@ -0,0 +1,219 @@
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from app.commons import run_idle
|
||||
from app.properties import Profile
|
||||
from app.ui.dialogs import show_dialog, DialogType
|
||||
from app.ui.main_helper import append_text_to_tview
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey
|
||||
|
||||
|
||||
class RestoreType(Enum):
|
||||
BOUQUETS = 0
|
||||
ALL = 1
|
||||
|
||||
|
||||
class BackupDialog:
|
||||
def __init__(self, transient, options, profile, callback):
|
||||
handlers = {"on_restore_bouquets": self.on_restore_bouquets,
|
||||
"on_restore_all": self.on_restore_all,
|
||||
"on_remove": self.on_remove,
|
||||
"on_view_popup_menu": self.on_view_popup_menu,
|
||||
"on_info_button_toggled": self.on_info_button_toggled,
|
||||
"on_info_bar_close": self.on_info_bar_close,
|
||||
"on_cursor_changed": self.on_cursor_changed,
|
||||
"on_resize": self.on_resize,
|
||||
"on_key_release": self.on_key_release}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain("demon-editor")
|
||||
builder.add_from_file(UI_RESOURCES_PATH + "backup_dialog.glade")
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._options = options.get(profile.value)
|
||||
self._data_path = self._options.get("data_dir_path", "")
|
||||
self._backup_path = self._options.get("backup_dir_path", self._data_path + "backup/")
|
||||
self._profile = profile
|
||||
self._open_data_callback = callback
|
||||
self._dialog_window = builder.get_object("dialog_window")
|
||||
self._dialog_window.set_transient_for(transient)
|
||||
self._model = builder.get_object("main_list_store")
|
||||
self._main_view = builder.get_object("main_view")
|
||||
self._text_view = builder.get_object("text_view")
|
||||
self._text_view_scrolled_window = builder.get_object("text_view_scrolled_window")
|
||||
self._info_check_button = builder.get_object("info_check_button")
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._message_label = builder.get_object("message_label")
|
||||
# Setting the last size of the dialog window if it was saved
|
||||
window_size = self._options.get("backup_tool_window_size", None)
|
||||
if window_size:
|
||||
self._dialog_window.resize(*window_size)
|
||||
|
||||
self.init_data()
|
||||
|
||||
def show(self):
|
||||
self._dialog_window.show()
|
||||
|
||||
def init_data(self):
|
||||
try:
|
||||
files = os.listdir(self._backup_path)
|
||||
except FileNotFoundError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
for file in filter(lambda x: x.endswith(".zip"), files):
|
||||
self._model.append((file.rstrip(".zip"), False))
|
||||
|
||||
def on_restore_bouquets(self, item):
|
||||
self.restore(RestoreType.BOUQUETS)
|
||||
|
||||
def on_restore_all(self, item):
|
||||
self.restore(RestoreType.ALL)
|
||||
|
||||
def on_remove(self, item):
|
||||
model, paths = self._main_view.get_selection().get_selected_rows()
|
||||
if not paths:
|
||||
show_dialog(DialogType.ERROR, self._dialog_window, "No selected item!")
|
||||
return
|
||||
|
||||
if show_dialog(DialogType.QUESTION, self._dialog_window) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
itrs_to_delete = []
|
||||
try:
|
||||
for itr in map(model.get_iter, paths):
|
||||
file_name = model.get_value(itr, 0)
|
||||
os.remove("{}{}{}".format(self._backup_path, file_name, ".zip"))
|
||||
itrs_to_delete.append(itr)
|
||||
except FileNotFoundError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
list(map(model.remove, itrs_to_delete))
|
||||
|
||||
def on_view_popup_menu(self, menu, event):
|
||||
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_SECONDARY:
|
||||
menu.popup(None, None, None, None, event.button, event.time)
|
||||
|
||||
def on_info_button_toggled(self, button):
|
||||
active = button.get_active()
|
||||
self._text_view_scrolled_window.set_visible(active)
|
||||
if active:
|
||||
self.on_cursor_changed(self._main_view)
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(True)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._message_label.set_text(text)
|
||||
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
|
||||
def on_cursor_changed(self, view):
|
||||
if not self._info_check_button.get_active():
|
||||
return
|
||||
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
if paths:
|
||||
try:
|
||||
file_name = self._backup_path + model.get_value(model.get_iter(paths[0]), 0) + ".zip"
|
||||
created = time.ctime(os.path.getctime(file_name))
|
||||
self._text_view.get_buffer().set_text(
|
||||
"Created: {}\n********** Files: **********\n".format(created))
|
||||
with zipfile.ZipFile(file_name) as zip_file:
|
||||
for name in zip_file.namelist():
|
||||
append_text_to_tview(name + "\n", self._text_view)
|
||||
except FileNotFoundError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
|
||||
def restore(self, restore_type):
|
||||
model, paths = self._main_view.get_selection().get_selected_rows()
|
||||
if not paths:
|
||||
show_dialog(DialogType.ERROR, self._dialog_window, "No selected item!")
|
||||
return
|
||||
|
||||
if len(paths) > 1:
|
||||
show_dialog(DialogType.ERROR, self._dialog_window, "Please, select only one item!")
|
||||
return
|
||||
|
||||
if show_dialog(DialogType.QUESTION, self._dialog_window) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
file_name = model.get_value(model.get_iter(paths[0]), 0)
|
||||
full_file_name = self._backup_path + file_name + ".zip"
|
||||
|
||||
try:
|
||||
if restore_type is RestoreType.ALL:
|
||||
clear_data_path(self._data_path)
|
||||
shutil.unpack_archive(full_file_name, self._data_path)
|
||||
elif restore_type is RestoreType.BOUQUETS:
|
||||
tmp_dir = tempfile.gettempdir() + "/" + file_name
|
||||
cond = (".tv", ".radio") if self._profile is Profile.ENIGMA_2 else "bouquets.xml"
|
||||
shutil.unpack_archive(full_file_name, tmp_dir)
|
||||
for file in filter(lambda f: f.endswith(cond), os.listdir(self._data_path)):
|
||||
os.remove(os.path.join(self._data_path, file))
|
||||
for file in filter(lambda f: f.endswith(cond), os.listdir(tmp_dir)):
|
||||
shutil.move(os.path.join(tmp_dir, file), self._data_path + file)
|
||||
shutil.rmtree(tmp_dir)
|
||||
except FileNotFoundError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
self.show_info_message("Done!", Gtk.MessageType.INFO)
|
||||
self._open_data_callback(self._data_path)
|
||||
|
||||
def on_resize(self, window):
|
||||
if self._options:
|
||||
self._options["backup_tool_window_size"] = window.get_size()
|
||||
|
||||
def on_key_release(self, view, event):
|
||||
""" Handling keystrokes """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
|
||||
|
||||
if key is KeyboardKey.DELETE:
|
||||
self.on_remove(view)
|
||||
elif ctrl and key is KeyboardKey.E:
|
||||
self.restore(RestoreType.ALL)
|
||||
elif ctrl and key is KeyboardKey.R:
|
||||
self.restore(RestoreType.BOUQUETS)
|
||||
|
||||
|
||||
def backup_data(path, backup_path, move=True):
|
||||
""" Creating data backup from a folder at the specified path
|
||||
|
||||
Returns full path to the compressed file.
|
||||
"""
|
||||
backup_path = "{}{}/".format(backup_path, datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
|
||||
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)):
|
||||
src, dst = os.path.join(path, file), backup_path + file
|
||||
shutil.move(src, dst) if move else shutil.copy(src, dst)
|
||||
# compressing to zip and delete remaining files
|
||||
zip_file = shutil.make_archive(backup_path, "zip", backup_path)
|
||||
shutil.rmtree(backup_path)
|
||||
|
||||
return zip_file
|
||||
|
||||
|
||||
def restore_data(src, dst):
|
||||
""" Unpacks backup data. """
|
||||
clear_data_path(dst)
|
||||
shutil.unpack_archive(src, dst)
|
||||
|
||||
|
||||
def clear_data_path(path):
|
||||
""" Clearing data at the specified path excluding satellites.xml file """
|
||||
for file in filter(lambda f: f != "satellites.xml" and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
|
||||
os.remove(os.path.join(path, file))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
355
app/ui/backup_dialog.glade
Normal file
355
app/ui/backup_dialog.glade
Normal file
@@ -0,0 +1,355 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkListStore" id="main_list_store">
|
||||
<columns>
|
||||
<!-- column-name date -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name selected -->
|
||||
<column type="gboolean"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkWindow" id="dialog_window">
|
||||
<property name="width_request">560</property>
|
||||
<property name="height_request">320</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="icon_name">document-revert</property>
|
||||
<property name="gravity">center</property>
|
||||
<signal name="check-resize" handler="on_resize" swapped="no"/>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Backups</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="header_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="restore_bouquets_header_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Restore bouquets</property>
|
||||
<signal name="clicked" handler="on_restore_bouquets" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="restore_bouquets_header_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-revert</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="restore_all_header_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Restore all</property>
|
||||
<signal name="clicked" handler="on_restore_all" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="restore_all_header_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-select-all</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<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">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_header_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Remove</property>
|
||||
<signal name="clicked" handler="on_remove" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="remove_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">user-trash</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="info_check_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="info_check_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-dialog-info</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkPaned" id="main_paned">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="wide_handle">True</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="main_view_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="main_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="model">main_list_store</property>
|
||||
<property name="headers_visible">False</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<signal name="button-press-event" handler="on_view_popup_menu" object="popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
|
||||
<signal name="key-release-event" handler="on_key_release" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="main_view_selection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="backup_date_column">
|
||||
<property name="title" translatable="yes">Backup</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="date_render">
|
||||
<property name="xpad">10</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="text_view_scrolled_window">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="pixels_above_lines">5</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="left_margin">10</property>
|
||||
<property name="right_margin">10</property>
|
||||
<property name="indent">10</property>
|
||||
<property name="cursor_visible">False</property>
|
||||
<property name="accepts_tab">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="info_bar">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<signal name="response" handler="on_info_bar_close" swapped="no"/>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">6</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 internal-child="content_area">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">16</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="message_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">message</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_popup_menu_item_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-revert-to-saved</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_popup_menu_item_image2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-select-all</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="restore_bouquets_popup_menu_item">
|
||||
<property name="label" translatable="yes">Restore bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">restore_popup_menu_item_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_restore_bouquets" swapped="no"/>
|
||||
<accelerator key="r" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="restore_all_popup_menu_item">
|
||||
<property name="label" translatable="yes">Restore all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">restore_popup_menu_item_image2</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_restore_all" swapped="no"/>
|
||||
<accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="popup_menu_separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="remove_popup_menu_item">
|
||||
<property name="label">gtk-remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
@@ -40,8 +40,8 @@ Author: Dmitriy Yefremov
|
||||
<property name="icon_name">system-help</property>
|
||||
<property name="type_hint">normal</property>
|
||||
<property name="program_name">DemonEditor</property>
|
||||
<property name="version">0.4.2 Pre-alpha</property>
|
||||
<property name="copyright">2018 Dmitriy Yefremov
|
||||
<property name="version">0.4.6 Pre-alpha</property>
|
||||
<property name="copyright">2018-2019 Dmitriy Yefremov
|
||||
</property>
|
||||
<property name="comments" translatable="yes">Enigma2 channel and satellites list editor for GNU/Linux</property>
|
||||
<property name="website">https://dyefremov.github.io/DemonEditor/</property>
|
||||
@@ -75,121 +75,57 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkMessageDialog" id="error_dialog">
|
||||
<property name="width_request">320</property>
|
||||
<object class="GtkDialog" id="input_dialog">
|
||||
<property name="use-header-bar">{use_header}</property>
|
||||
<property name="title" translatable="yes">Transponder</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">{title}</property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center</property>
|
||||
<property name="default_width">320</property>
|
||||
<property name="default_height">240</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="icon_name">accessories-text-editor</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="message_type">error</property>
|
||||
<property name="buttons">ok</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="error_dialog_vbox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="resize_mode">immediate</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox" id="messagedialog-action_area8">
|
||||
<property name="can_focus">False</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>
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<property name="gravity">center</property>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="input_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>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkDialog" id="input_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title"> </property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="icon_name">gtk-edit</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="input_dialog_ok_button">
|
||||
<property name="label">gtk-ok</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>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="input_dialog_vbox">
|
||||
<property name="width_request">320</property>
|
||||
<object class="GtkBox">
|
||||
<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="dialog-action_area2">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="button3">
|
||||
<property name="label">gtk-undo</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">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="button4">
|
||||
<property name="label">gtk-ok</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</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="input_dialog_box">
|
||||
<object class="GtkEntry" id="input_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="input_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="secondary_icon_activatable">False</property>
|
||||
<property name="secondary_icon_sensitive">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="margin_left">2</property>
|
||||
<property name="margin_right">2</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="secondary_icon_activatable">False</property>
|
||||
<property name="secondary_icon_sensitive">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -200,108 +136,10 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<action-widgets>
|
||||
<action-widget response="-6">button3</action-widget>
|
||||
<action-widget response="-5">button4</action-widget>
|
||||
<action-widget response="cancel">input_dialog_cancel_button</action-widget>
|
||||
<action-widget response="ok">input_dialog_ok_button</action-widget>
|
||||
</action-widgets>
|
||||
</object>
|
||||
<object class="GtkFileChooserDialog" id="path_chooser_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes"> </property>
|
||||
<property name="modal">True</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="icon_name">document-open</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="action">select-folder</property>
|
||||
<property name="do_overwrite_confirmation">True</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="filechooser_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="filechooser_dialog_action_area">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="button2">
|
||||
<property name="label">gtk-undo</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">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="button1">
|
||||
<property name="label">gtk-ok</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<action-widgets>
|
||||
<action-widget response="-6">button2</action-widget>
|
||||
<action-widget response="-12">button1</action-widget>
|
||||
</action-widgets>
|
||||
</object>
|
||||
<object class="GtkMessageDialog" id="question_dialog">
|
||||
<property name="width_request">320</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="default_width">320</property>
|
||||
<property name="default_height">240</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="message_type">question</property>
|
||||
<property name="buttons">ok-cancel</property>
|
||||
<property name="text" translatable="yes">Are you sure?</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="question_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="messagedialog-action_area">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="layout_style">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkDialog" id="wait_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="resizable">False</property>
|
||||
@@ -316,8 +154,8 @@ Author: Dmitriy Yefremov
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="dialog-vbox4">
|
||||
<property name="width_request">118</property>
|
||||
<object class="GtkBox" id="wait_dialog_vbox">
|
||||
<property name="width_request">120</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child internal-child="action_area">
|
||||
|
||||
@@ -1,9 +1,32 @@
|
||||
""" Common module for showing dialogs """
|
||||
import locale
|
||||
from enum import Enum
|
||||
from functools import lru_cache
|
||||
|
||||
from app.commons import run_idle
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN, IS_GNOME_SESSION
|
||||
|
||||
|
||||
class Dialog(Enum):
|
||||
MESSAGE = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<object class="GtkMessageDialog" id="message_dialog">
|
||||
<property name="use-header-bar">{use_header}</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="default_width">320</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>
|
||||
<property name="gravity">center</property>
|
||||
<property name="message_type">{message_type}</property>
|
||||
<property name="buttons">{buttons_type}</property>
|
||||
</object>
|
||||
</interface>
|
||||
"""
|
||||
|
||||
|
||||
class Action(Enum):
|
||||
@@ -12,12 +35,16 @@ class Action(Enum):
|
||||
|
||||
|
||||
class DialogType(Enum):
|
||||
INPUT = "input_dialog"
|
||||
CHOOSER = "path_chooser_dialog"
|
||||
ERROR = "error_dialog"
|
||||
QUESTION = "question_dialog"
|
||||
ABOUT = "about_dialog"
|
||||
WAIT = "wait_dialog"
|
||||
INPUT = "input"
|
||||
CHOOSER = "chooser"
|
||||
ERROR = "error"
|
||||
QUESTION = "question"
|
||||
INFO = "info"
|
||||
ABOUT = "about"
|
||||
WAIT = "wait"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class WaitDialog:
|
||||
@@ -42,54 +69,16 @@ class WaitDialog:
|
||||
|
||||
def show_dialog(dialog_type: DialogType, transient, text=None, options=None, action_type=None, file_filter=None):
|
||||
""" Shows dialogs by name """
|
||||
builder, dialog = get_dialog_from_xml(dialog_type, transient)
|
||||
|
||||
if dialog_type is DialogType.CHOOSER and options:
|
||||
if action_type is not None:
|
||||
dialog.set_action(action_type)
|
||||
if file_filter is not None:
|
||||
dialog.add_filter(file_filter)
|
||||
|
||||
path = options.get("data_dir_path")
|
||||
dialog.set_current_folder(path)
|
||||
|
||||
response = dialog.run()
|
||||
if response == -12: # -12 for fix assertion 'gtk_widget_get_can_default (widget)' failed
|
||||
if dialog.get_filename():
|
||||
path = dialog.get_filename()
|
||||
if action_type is not Gtk.FileChooserAction.OPEN:
|
||||
path = path + "/"
|
||||
|
||||
response = path
|
||||
dialog.destroy()
|
||||
|
||||
return response
|
||||
|
||||
if dialog_type is DialogType.INPUT:
|
||||
entry = builder.get_object("input_entry")
|
||||
entry.set_text(text if text else "")
|
||||
response = dialog.run()
|
||||
txt = entry.get_text()
|
||||
dialog.destroy()
|
||||
|
||||
return txt if response == Gtk.ResponseType.OK else Gtk.ResponseType.CANCEL
|
||||
|
||||
if 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
|
||||
if dialog_type in (DialogType.INFO, DialogType.ERROR):
|
||||
return get_message_dialog(transient, dialog_type, Gtk.ButtonsType.OK, text)
|
||||
elif dialog_type is DialogType.CHOOSER and options:
|
||||
return get_file_chooser_dialog(transient, text, options, action_type, file_filter)
|
||||
elif dialog_type is DialogType.INPUT:
|
||||
return get_input_dialog(transient, text)
|
||||
elif dialog_type is DialogType.QUESTION:
|
||||
return get_message_dialog(transient, DialogType.QUESTION, Gtk.ButtonsType.OK_CANCEL, text or "Are you sure?")
|
||||
elif dialog_type is DialogType.ABOUT:
|
||||
return get_about_dialog(transient)
|
||||
|
||||
|
||||
def get_chooser_dialog(transient, options, pattern, name):
|
||||
@@ -104,10 +93,84 @@ def get_chooser_dialog(transient, options, pattern, name):
|
||||
file_filter=file_filter)
|
||||
|
||||
|
||||
def get_file_chooser_dialog(transient, text, options, action_type, file_filter):
|
||||
dialog = Gtk.FileChooserDialog(get_message(text) if text else "", transient,
|
||||
action_type if action_type is not None else Gtk.FileChooserAction.SELECT_FOLDER,
|
||||
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK),
|
||||
use_header_bar=IS_GNOME_SESSION)
|
||||
if file_filter is not None:
|
||||
dialog.add_filter(file_filter)
|
||||
|
||||
path = options.get("data_dir_path")
|
||||
dialog.set_current_folder(path)
|
||||
response = dialog.run()
|
||||
if response == Gtk.ResponseType.OK:
|
||||
if dialog.get_filename():
|
||||
path = dialog.get_filename()
|
||||
if action_type is not Gtk.FileChooserAction.OPEN:
|
||||
path = path + "/"
|
||||
response = path
|
||||
dialog.destroy()
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def get_input_dialog(transient, text):
|
||||
builder, dialog = get_dialog_from_xml(DialogType.INPUT, transient, use_header=IS_GNOME_SESSION)
|
||||
entry = builder.get_object("input_entry")
|
||||
entry.set_text(text if text else "")
|
||||
response = dialog.run()
|
||||
txt = entry.get_text()
|
||||
dialog.destroy()
|
||||
|
||||
return txt if response == Gtk.ResponseType.OK else Gtk.ResponseType.CANCEL
|
||||
|
||||
|
||||
def get_message_dialog(transient, message_type, buttons_type, text):
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
dialog_str = Dialog.MESSAGE.value.format(use_header=0, message_type=message_type, buttons_type=int(buttons_type))
|
||||
builder.add_from_string(dialog_str)
|
||||
dialog = builder.get_object("message_dialog")
|
||||
dialog.set_transient_for(transient)
|
||||
dialog.set_markup(get_message(text))
|
||||
response = dialog.run()
|
||||
dialog.destroy()
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def get_about_dialog(transient):
|
||||
builder, dialog = get_dialog_from_xml(DialogType.ABOUT, transient)
|
||||
dialog.set_transient_for(transient)
|
||||
response = dialog.run()
|
||||
dialog.destroy()
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def get_dialog_from_xml(dialog_type, transient, use_header=0, title=""):
|
||||
dialog_name = dialog_type.value + "_dialog"
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
dialog_str = get_dialogs_string(UI_RESOURCES_PATH + "dialogs.glade").format(use_header=use_header, title=title)
|
||||
builder.add_objects_from_string(dialog_str, (dialog_name,))
|
||||
dialog = builder.get_object(dialog_name)
|
||||
dialog.set_transient_for(transient)
|
||||
|
||||
return builder, dialog
|
||||
|
||||
|
||||
def get_message(message):
|
||||
""" returns translated message """
|
||||
return locale.dgettext(TEXT_DOMAIN, message)
|
||||
|
||||
|
||||
@lru_cache(maxsize=5)
|
||||
def get_dialogs_string(path):
|
||||
with open(path, "r") as f:
|
||||
return "".join(f)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -3,6 +3,7 @@ from gi.repository import GLib
|
||||
from app.commons import run_idle, run_task
|
||||
from app.connections import download_data, DownloadType, upload_data
|
||||
from app.properties import Profile, get_config
|
||||
from app.ui.backup import backup_data, restore_data
|
||||
from app.ui.main_helper import append_text_to_tview
|
||||
from app.ui.settings_dialog import show_settings_dialog
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
|
||||
@@ -10,10 +11,11 @@ from .dialogs import show_dialog, DialogType, get_message
|
||||
|
||||
|
||||
class DownloadDialog:
|
||||
def __init__(self, transient, properties, open_data_callback, profile=Profile.ENIGMA_2):
|
||||
def __init__(self, transient, properties, open_data_callback, update_settings_callback, profile=Profile.ENIGMA_2):
|
||||
self._profile_properties = properties.get(profile.value)
|
||||
self._properties = properties
|
||||
self._open_data_callback = open_data_callback
|
||||
self._update_settings_callback = update_settings_callback
|
||||
self._profile = profile
|
||||
|
||||
handlers = {"on_receive": self.on_receive,
|
||||
@@ -49,20 +51,21 @@ class DownloadDialog:
|
||||
self._timeout_entry = builder.get_object("timeout_entry")
|
||||
self._settings_buttons_box = builder.get_object("settings_buttons_box")
|
||||
self._use_http_switch = builder.get_object("use_http_switch")
|
||||
self._http_radio_button = builder.get_object("http_radio_button")
|
||||
self._use_http_box = builder.get_object("use_http_box")
|
||||
self.init_properties()
|
||||
|
||||
if profile is Profile.NEUTRINO_MP:
|
||||
self._webtv_radio_button.set_visible(True)
|
||||
builder.get_object("http_radio_button").set_visible(False)
|
||||
builder.get_object("use_http_box").set_visible(False)
|
||||
self._use_http_switch.set_active(False)
|
||||
|
||||
def show(self):
|
||||
self._dialog_window.show()
|
||||
|
||||
def init_properties(self):
|
||||
self._host_entry.set_text(self._profile_properties["host"])
|
||||
self._data_path_entry.set_text(self._profile_properties["data_dir_path"])
|
||||
is_enigma = self._profile is Profile.ENIGMA_2
|
||||
self._webtv_radio_button.set_visible(not is_enigma)
|
||||
self._http_radio_button.set_visible(is_enigma)
|
||||
self._use_http_box.set_visible(is_enigma)
|
||||
self._use_http_switch.set_active(is_enigma)
|
||||
|
||||
@run_idle
|
||||
def on_receive(self, item):
|
||||
@@ -108,12 +111,14 @@ class DownloadDialog:
|
||||
|
||||
def on_preferences(self, item):
|
||||
show_settings_dialog(self._dialog_window, self._properties)
|
||||
self._profile = Profile(self._properties.get("profile", Profile.ENIGMA_2.value))
|
||||
self._profile_properties = get_config().get(self._profile.value)
|
||||
self.init_properties()
|
||||
self._update_settings_callback()
|
||||
|
||||
for button in self._settings_buttons_box.get_children():
|
||||
if button.get_active():
|
||||
self.on_settings_button(button)
|
||||
self.init_properties()
|
||||
break
|
||||
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
@@ -122,11 +127,16 @@ class DownloadDialog:
|
||||
@run_task
|
||||
def download(self, download, d_type):
|
||||
""" Download/upload data from/to receiver """
|
||||
try:
|
||||
self._expander.set_expanded(True)
|
||||
self.clear_output()
|
||||
self._expander.set_expanded(True)
|
||||
self.clear_output()
|
||||
backup, backup_src, data_path = self._profile_properties.get("backup_before_downloading", True), None, None
|
||||
|
||||
try:
|
||||
if download:
|
||||
if backup and d_type is not DownloadType.SATELLITES:
|
||||
data_path = self._profile_properties.get("data_dir_path", self._data_path_entry.get_text())
|
||||
backup_path = self._profile_properties.get("backup_dir_path", data_path + "backup/")
|
||||
backup_src = backup_data(data_path, backup_path, d_type is DownloadType.ALL)
|
||||
download_data(properties=self._profile_properties, download_type=d_type, callback=self.append_output)
|
||||
else:
|
||||
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
|
||||
@@ -140,6 +150,8 @@ class DownloadDialog:
|
||||
except Exception as e:
|
||||
message = str(getattr(e, "message", str(e)))
|
||||
self.show_info_message(message, Gtk.MessageType.ERROR)
|
||||
if all((download, backup, data_path)):
|
||||
restore_data(backup_src, data_path)
|
||||
else:
|
||||
if download and d_type is not DownloadType.SATELLITES:
|
||||
GLib.idle_add(self._open_data_callback)
|
||||
|
||||
1283
app/ui/epg_dialog.glade
Normal file
1283
app/ui/epg_dialog.glade
Normal file
File diff suppressed because it is too large
Load Diff
549
app/ui/epg_dialog.py
Normal file
549
app/ui/epg_dialog.py
Normal file
@@ -0,0 +1,549 @@
|
||||
import gzip
|
||||
import locale
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import urllib.request
|
||||
from enum import Enum
|
||||
from urllib.error import HTTPError, URLError
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from app.commons import run_idle
|
||||
from app.connections import download_data, DownloadType
|
||||
from app.eparser.ecommons import BouquetService, BqServiceType
|
||||
from app.tools.epg import EPG, ChannelsParser
|
||||
from app.ui.dialogs import get_message, show_dialog, DialogType
|
||||
from .main_helper import on_popup_menu, update_entry_data
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, Column, EPG_ICON, KeyboardKey
|
||||
|
||||
|
||||
class RefsSource(Enum):
|
||||
SERVICES = 0
|
||||
XML = 1
|
||||
|
||||
|
||||
class EpgDialog:
|
||||
|
||||
def __init__(self, transient, options, services, bouquet, fav_model, bouquet_name):
|
||||
|
||||
handlers = {"on_close_dialog": self.on_close_dialog,
|
||||
"on_apply": self.on_apply,
|
||||
"on_update": self.on_update,
|
||||
"on_save_to_xml": self.on_save_to_xml,
|
||||
"on_auto_configuration": self.on_auto_configuration,
|
||||
"on_filter_toggled": self.on_filter_toggled,
|
||||
"on_filter_changed": self.on_filter_changed,
|
||||
"on_info_bar_close": self.on_info_bar_close,
|
||||
"on_popup_menu": on_popup_menu,
|
||||
"on_bouquet_popup_menu": self.on_bouquet_popup_menu,
|
||||
"on_copy_ref": self.on_copy_ref,
|
||||
"on_assign_ref": self.on_assign_ref,
|
||||
"on_reset": self.on_reset,
|
||||
"on_list_reset": self.on_list_reset,
|
||||
"on_drag_begin": self.on_drag_begin,
|
||||
"on_drag_data_get": self.on_drag_data_get,
|
||||
"on_drag_data_received": self.on_drag_data_received,
|
||||
"on_resize": self.on_resize,
|
||||
"on_names_source_changed": self.on_names_source_changed,
|
||||
"on_options_save": self.on_options_save,
|
||||
"on_use_web_source_switch": self.on_use_web_source_switch,
|
||||
"on_enable_filtering_switch": self.on_enable_filtering_switch,
|
||||
"on_update_on_start_switch": self.on_update_on_start_switch,
|
||||
"on_field_icon_press": self.on_field_icon_press,
|
||||
"on_key_release": self.on_key_release}
|
||||
|
||||
self._services = {}
|
||||
self._ex_services = services
|
||||
self._ex_fav_model = fav_model
|
||||
self._options = options
|
||||
self._bouquet = bouquet
|
||||
self._bouquet_name = bouquet_name
|
||||
self._current_ref = []
|
||||
self._enable_dat_filter = False
|
||||
self._use_web_source = False
|
||||
self._update_epg_data_on_start = False
|
||||
self._refs_source = RefsSource.SERVICES
|
||||
self._show_tooltips = True
|
||||
self._download_xml_is_active = False
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_from_file(UI_RESOURCES_PATH + "epg_dialog.glade")
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._dialog = builder.get_object("epg_dialog_window")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._source_view = builder.get_object("source_view")
|
||||
self._bouquet_view = builder.get_object("bouquet_view")
|
||||
self._bouquet_model = builder.get_object("bouquet_list_store")
|
||||
self._services_model = builder.get_object("services_list_store")
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._message_label = builder.get_object("info_bar_message_label")
|
||||
self._assign_ref_popup_item = builder.get_object("bouquet_assign_ref_popup_item")
|
||||
self._left_header_box = builder.get_object("left_header_box")
|
||||
self._xml_download_progress_bar = builder.get_object("xml_download_progress_bar")
|
||||
# Filter
|
||||
self._filter_bar = builder.get_object("filter_bar")
|
||||
self._filter_entry = builder.get_object("filter_entry")
|
||||
self._services_filter_model = builder.get_object("services_filter_model")
|
||||
self._services_filter_model.set_visible_func(self.services_filter_function)
|
||||
# Info
|
||||
self._source_count_label = builder.get_object("source_count_label")
|
||||
self._source_info_label = builder.get_object("source_info_label")
|
||||
self._bouquet_count_label = builder.get_object("bouquet_count_label")
|
||||
self._bouquet_epg_count_label = builder.get_object("bouquet_epg_count_label")
|
||||
# Options
|
||||
self._xml_radiobutton = builder.get_object("xml_radiobutton")
|
||||
self._xml_chooser_button = builder.get_object("xml_chooser_button")
|
||||
self._names_source_box = builder.get_object("names_source_box")
|
||||
self._web_source_box = builder.get_object("web_source_box")
|
||||
self._use_web_source_switch = builder.get_object("use_web_source_switch")
|
||||
self._url_to_xml_entry = builder.get_object("url_to_xml_entry")
|
||||
self._enable_filtering_switch = builder.get_object("enable_filtering_switch")
|
||||
self._epg_dat_path_entry = builder.get_object("epg_dat_path_entry")
|
||||
self._epg_dat_stb_path_entry = builder.get_object("epg_dat_stb_path_entry")
|
||||
self._update_on_start_switch = builder.get_object("update_on_start_switch")
|
||||
self._epg_dat_source_box = builder.get_object("epg_dat_source_box")
|
||||
# Setting the last size of the dialog window
|
||||
window_size = self._options.get("epg_tool_window_size", None)
|
||||
if window_size:
|
||||
self._dialog.resize(*window_size)
|
||||
|
||||
self.init_drag_and_drop()
|
||||
self.on_update()
|
||||
|
||||
def show(self):
|
||||
self._dialog.show()
|
||||
|
||||
def on_close_dialog(self, window, event):
|
||||
self._download_xml_is_active = False
|
||||
|
||||
@run_idle
|
||||
def on_apply(self, item):
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
self._bouquet.clear()
|
||||
list(map(self._bouquet.append, [r[Column.FAV_ID] for r in self._bouquet_model]))
|
||||
for index, row in enumerate(self._ex_fav_model):
|
||||
fav_id = self._bouquet[index]
|
||||
row[Column.FAV_ID] = fav_id
|
||||
if row[Column.FAV_TYPE] == BqServiceType.IPTV.name:
|
||||
old_fav_id = self._services[fav_id]
|
||||
srv = self._ex_services.pop(old_fav_id, None)
|
||||
if srv:
|
||||
self._ex_services[fav_id] = srv._replace(fav_id=fav_id)
|
||||
self._dialog.destroy()
|
||||
|
||||
@run_idle
|
||||
def on_update(self, item=None):
|
||||
self.clear_data()
|
||||
self.init_options()
|
||||
gen = self.init_data()
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def clear_data(self):
|
||||
self._services_model.clear()
|
||||
self._bouquet_model.clear()
|
||||
self._services.clear()
|
||||
self._source_info_label.set_text("")
|
||||
self._bouquet_epg_count_label.set_text("")
|
||||
self.on_info_bar_close()
|
||||
|
||||
def init_data(self):
|
||||
gen = self.init_bouquet_data()
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
refs = None
|
||||
if self._enable_dat_filter:
|
||||
if self._update_epg_data_on_start:
|
||||
try:
|
||||
self.download_epg_from_stb()
|
||||
except OSError as e:
|
||||
self.show_info_message("Download epg.dat file error: {}".format(e), Gtk.MessageType.ERROR)
|
||||
return
|
||||
yield True
|
||||
|
||||
try:
|
||||
refs = EPG.get_epg_refs(self._epg_dat_path_entry.get_text() + "epg.dat")
|
||||
except FileNotFoundError as e:
|
||||
self.show_info_message("Read data error: {}".format(e), Gtk.MessageType.ERROR)
|
||||
return
|
||||
yield True
|
||||
|
||||
if self._refs_source is RefsSource.SERVICES:
|
||||
self.init_lamedb_source(refs)
|
||||
elif self._refs_source is RefsSource.XML:
|
||||
xml_gen = self.init_xml_source(refs)
|
||||
try:
|
||||
yield from xml_gen
|
||||
except ValueError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
self.show_info_message("Unknown names source!", Gtk.MessageType.ERROR)
|
||||
yield True
|
||||
|
||||
def init_bouquet_data(self):
|
||||
for r in self._ex_fav_model:
|
||||
row = [*r[:]]
|
||||
fav_id = r[Column.FAV_ID]
|
||||
self._services[fav_id] = self._ex_services[fav_id].fav_id
|
||||
yield self._bouquet_model.append(row)
|
||||
self._bouquet_count_label.set_text(str(len(self._bouquet_model)))
|
||||
yield True
|
||||
|
||||
def init_lamedb_source(self, refs):
|
||||
srvs = {k[:k.rfind(":")]: v for k, v in self._ex_services.items()}
|
||||
s_types = (BqServiceType.MARKER.value, BqServiceType.IPTV.value)
|
||||
filtered = filter(None, [srvs.get(ref) for ref in refs]) if refs else filter(
|
||||
lambda s: s.service_type not in s_types, self._ex_services.values())
|
||||
list(map(self._services_model.append, map(lambda s: (s.service, s.fav_id), filtered)))
|
||||
self.update_source_count_info()
|
||||
|
||||
def init_xml_source(self, refs):
|
||||
path = self._epg_dat_path_entry.get_text() if self._use_web_source else self._xml_chooser_button.get_filename()
|
||||
if not path:
|
||||
self.show_info_message("The path to the xml file is not set!", Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
if self._use_web_source:
|
||||
# Downloading gzipped xml file that contains services names with references from the web.
|
||||
self._download_xml_is_active = True
|
||||
self.update_active_header_elements(False)
|
||||
url = self._url_to_xml_entry.get_text()
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=2) as fp:
|
||||
headers = fp.info()
|
||||
content_type = headers.get("Content-Type", "")
|
||||
|
||||
if content_type != "application/gzip":
|
||||
self._download_xml_is_active = False
|
||||
raise ValueError("{} {} {}".format(get_message("Download XML file error."),
|
||||
get_message("Unsupported file type:"),
|
||||
content_type))
|
||||
|
||||
file_name = os.path.basename(url)
|
||||
data_path = self._epg_dat_path_entry.get_text()
|
||||
|
||||
with open(data_path + file_name, "wb") as tfp:
|
||||
bs = 1024 * 8
|
||||
size = -1
|
||||
read = 0
|
||||
b_num = 0
|
||||
if "content-length" in headers:
|
||||
size = int(headers["Content-Length"])
|
||||
|
||||
while self._download_xml_is_active:
|
||||
block = fp.read(bs)
|
||||
if not block:
|
||||
break
|
||||
read += len(block)
|
||||
tfp.write(block)
|
||||
b_num += 1
|
||||
self.update_download_progress(b_num * bs / size)
|
||||
yield True
|
||||
|
||||
path = tfp.name.rstrip(".gz")
|
||||
except (HTTPError, URLError) as e:
|
||||
raise ValueError("{} {}".format(get_message("Download XML file error."), e))
|
||||
else:
|
||||
try:
|
||||
with open(path, "wb") as f_out:
|
||||
with gzip.open(tfp.name, "rb") as f:
|
||||
shutil.copyfileobj(f, f_out)
|
||||
os.remove(tfp.name)
|
||||
except Exception as e:
|
||||
raise ValueError("{} {}".format(get_message("Unpacking data error."), e))
|
||||
finally:
|
||||
self._download_xml_is_active = False
|
||||
self.update_active_header_elements(True)
|
||||
|
||||
try:
|
||||
s_refs, info = ChannelsParser.get_refs_from_xml(path)
|
||||
yield True
|
||||
except Exception as e:
|
||||
raise ValueError("{} {}".format(get_message("XML parsing error:"), e))
|
||||
else:
|
||||
if refs:
|
||||
s_refs = filter(lambda x: x.num in refs, s_refs)
|
||||
list(map(lambda s: self._services_model.append((s.name, s.data)), s_refs))
|
||||
self.update_source_info(info)
|
||||
self.update_source_count_info()
|
||||
yield True
|
||||
|
||||
def on_key_release(self, view, event):
|
||||
""" Handling keystrokes """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
|
||||
|
||||
if ctrl and key is KeyboardKey.C:
|
||||
self.on_copy_ref()
|
||||
elif ctrl and key is KeyboardKey.V:
|
||||
self.on_assign_ref()
|
||||
|
||||
@run_idle
|
||||
def on_save_to_xml(self, item):
|
||||
response = show_dialog(DialogType.CHOOSER, self._dialog, options=self._options)
|
||||
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
return
|
||||
|
||||
services = []
|
||||
iptv_types = (BqServiceType.IPTV.value, BqServiceType.MARKER.value)
|
||||
for r in self._bouquet_model:
|
||||
srv_type = r[Column.FAV_TYPE]
|
||||
if srv_type in iptv_types:
|
||||
srv = BouquetService(name=r[Column.FAV_SERVICE],
|
||||
type=BqServiceType(srv_type),
|
||||
data=r[Column.FAV_ID],
|
||||
num=r[Column.FAV_NUM])
|
||||
services.append(srv)
|
||||
|
||||
ChannelsParser.write_refs_to_xml("{}{}.xml".format(response, self._bouquet_name), services)
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
|
||||
@run_idle
|
||||
def on_auto_configuration(self, item):
|
||||
""" Simple mapping of services by name. """
|
||||
use_cyrillic = locale.getdefaultlocale()[0] in ("ru_RU", "be_BY", "uk_UA", "sr_RS")
|
||||
tr = None
|
||||
if use_cyrillic:
|
||||
# may be not entirely correct
|
||||
symbols = (u"АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯІÏҐЎЈЂЉЊЋЏTB",
|
||||
u"ABVGDEEJZIJKLMNOPRSTUFHZCSS_Y_EUAIEGUEDLNCJTV")
|
||||
tr = {ord(k): ord(v) for k, v in zip(*symbols)}
|
||||
|
||||
source = {}
|
||||
for row in self._services_model:
|
||||
name = re.sub("\\W+", "", str(row[0])).upper()
|
||||
name = name.translate(tr) if use_cyrillic else name
|
||||
source[name] = row[1]
|
||||
|
||||
success_count = 0
|
||||
not_founded = {}
|
||||
|
||||
for r in self._bouquet_model:
|
||||
if r[Column.FAV_TYPE] != BqServiceType.IPTV.value:
|
||||
continue
|
||||
name = re.sub("\\W+", "", str(r[Column.FAV_SERVICE])).upper()
|
||||
if use_cyrillic:
|
||||
name = name.translate(tr)
|
||||
ref = source.get(name, None) # Not [pop], because the list may contain duplicates or similar names!
|
||||
if ref:
|
||||
self.assign_data(r, ref, True)
|
||||
success_count += 1
|
||||
else:
|
||||
not_founded[name] = r
|
||||
# Additional attempt to search in the remaining elements
|
||||
for n in not_founded:
|
||||
for k in source:
|
||||
if k.startswith(n):
|
||||
self.assign_data(not_founded[n], source[k], True)
|
||||
success_count += 1
|
||||
break
|
||||
|
||||
self.update_epg_count()
|
||||
self.show_info_message("{} {} {}".format(get_message("Done!"),
|
||||
get_message("Count of successfully configured services:"),
|
||||
success_count), Gtk.MessageType.INFO)
|
||||
|
||||
def assign_data(self, row, ref, show_error=False):
|
||||
if row[Column.FAV_TYPE] != BqServiceType.IPTV.value:
|
||||
if not show_error:
|
||||
self.show_info_message(get_message("Not allowed in this context!"), Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
fav_id = row[Column.FAV_ID]
|
||||
fav_id_data = fav_id.split(":")
|
||||
fav_id_data[3:7] = ref.split(":")
|
||||
new_fav_id = ":".join(fav_id_data)
|
||||
service = self._services.pop(fav_id, None)
|
||||
if service:
|
||||
self._services[new_fav_id] = service
|
||||
row[Column.FAV_ID] = new_fav_id
|
||||
row[Column.FAV_LOCKED] = EPG_ICON
|
||||
row[Column.FAV_TOOLTIP] = ":".join(fav_id_data[:10]) if self._show_tooltips else None
|
||||
|
||||
def on_filter_toggled(self, button: Gtk.ToggleButton):
|
||||
self._filter_bar.set_search_mode(button.get_active())
|
||||
|
||||
def on_filter_changed(self, entry):
|
||||
self._services_filter_model.refilter()
|
||||
|
||||
def services_filter_function(self, model, itr, data):
|
||||
txt = self._filter_entry.get_text().upper()
|
||||
return model is None or model == "None" or txt in model.get_value(itr, 0).upper()
|
||||
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
|
||||
def on_copy_ref(self, item=None):
|
||||
model, paths = self._source_view.get_selection().get_selected_rows()
|
||||
self._current_ref.clear()
|
||||
if paths:
|
||||
self._current_ref.append(model[paths][1])
|
||||
|
||||
def on_assign_ref(self, item=None):
|
||||
if self._current_ref:
|
||||
model, paths = self._bouquet_view.get_selection().get_selected_rows()
|
||||
self.assign_data(model[paths], self._current_ref.pop())
|
||||
self.update_epg_count()
|
||||
|
||||
@run_idle
|
||||
def on_reset(self, item):
|
||||
model, paths = self._bouquet_view.get_selection().get_selected_rows()
|
||||
if paths:
|
||||
row = self._bouquet_model[paths]
|
||||
self.reset_row_data(row)
|
||||
self.update_epg_count()
|
||||
|
||||
@run_idle
|
||||
def on_list_reset(self, item):
|
||||
list(map(self.reset_row_data, self._bouquet_model))
|
||||
self.update_epg_count()
|
||||
|
||||
def reset_row_data(self, row):
|
||||
default_fav_id = self._services.pop(row[Column.FAV_ID], None)
|
||||
if default_fav_id:
|
||||
self._services[default_fav_id] = default_fav_id
|
||||
row[Column.FAV_ID], row[Column.FAV_LOCKED], row[Column.FAV_TOOLTIP] = default_fav_id, None, None
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(True)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._message_label.set_text(text)
|
||||
|
||||
@run_idle
|
||||
def update_source_info(self, info):
|
||||
lines = info.split("\n")
|
||||
self._source_info_label.set_text(lines[0] if lines else "")
|
||||
self._source_view.set_tooltip_text(info)
|
||||
|
||||
@run_idle
|
||||
def update_source_count_info(self):
|
||||
source_count = len(self._services_model)
|
||||
self._source_count_label.set_text(str(source_count))
|
||||
if self._enable_dat_filter and source_count == 0:
|
||||
msg = get_message("Current epg.dat file does not contains references for the services of this bouquet!")
|
||||
self.show_info_message(msg, Gtk.MessageType.WARNING)
|
||||
|
||||
@run_idle
|
||||
def update_epg_count(self):
|
||||
count = len(list((filter(None, [r[Column.FAV_LOCKED] for r in self._bouquet_model]))))
|
||||
self._bouquet_epg_count_label.set_text(str(count))
|
||||
|
||||
@run_idle
|
||||
def update_active_header_elements(self, state):
|
||||
self._left_header_box.set_sensitive(state)
|
||||
self._xml_download_progress_bar.set_visible(not state)
|
||||
self._source_info_label.set_text("" if state else "Downloading XML:")
|
||||
|
||||
@run_idle
|
||||
def update_download_progress(self, value):
|
||||
self._xml_download_progress_bar.set_fraction(value)
|
||||
|
||||
def on_bouquet_popup_menu(self, menu, event):
|
||||
self._assign_ref_popup_item.set_sensitive(self._current_ref)
|
||||
on_popup_menu(menu, event)
|
||||
|
||||
# ***************** Drag-and-drop *********************#
|
||||
|
||||
def init_drag_and_drop(self):
|
||||
""" Enable drag-and-drop """
|
||||
target = []
|
||||
self._source_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, target, Gdk.DragAction.COPY)
|
||||
self._source_view.drag_source_add_text_targets()
|
||||
self._bouquet_view.enable_model_drag_dest(target, Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
|
||||
self._bouquet_view.drag_dest_add_text_targets()
|
||||
|
||||
def on_drag_begin(self, view, context):
|
||||
""" Selects a row under the cursor in the view at the dragging beginning. """
|
||||
selection = view.get_selection()
|
||||
if selection.count_selected_rows() > 1:
|
||||
view.do_toggle_cursor_row(view)
|
||||
|
||||
def on_drag_data_get(self, view: Gtk.TreeView, drag_context, data, info, time):
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
if paths:
|
||||
val = model.get_value(model.get_iter(paths), 1)
|
||||
data.set_text(val, -1)
|
||||
|
||||
def on_drag_data_received(self, view: Gtk.TreeView, drag_context, x, y, data, info, time):
|
||||
path, pos = view.get_dest_row_at_pos(x, y)
|
||||
model = view.get_model()
|
||||
self.assign_data(model[path], data.get_text())
|
||||
self.update_epg_count()
|
||||
return False
|
||||
|
||||
# ***************** Options *********************#
|
||||
|
||||
def init_options(self):
|
||||
epg_dat_path = self._options.get("data_dir_path", "") + "epg/"
|
||||
self._epg_dat_path_entry.set_text(epg_dat_path)
|
||||
default_epg_data_stb_path = "/etc/enigma2"
|
||||
epg_options = self._options.get("epg_options", None)
|
||||
if epg_options:
|
||||
self._refs_source = RefsSource.XML if epg_options.get("xml_source", False) else RefsSource.SERVICES
|
||||
self._xml_radiobutton.set_active(self._refs_source is RefsSource.XML)
|
||||
self._use_web_source = epg_options.get("use_web_source", False)
|
||||
self._use_web_source_switch.set_active(self._use_web_source)
|
||||
self._url_to_xml_entry.set_text(epg_options.get("url_to_xml", ""))
|
||||
self._enable_dat_filter = epg_options.get("enable_filtering", False)
|
||||
self._enable_filtering_switch.set_active(self._enable_dat_filter)
|
||||
epg_dat_path = epg_options.get("epg_dat_path", epg_dat_path)
|
||||
self._epg_dat_path_entry.set_text(epg_dat_path)
|
||||
self._epg_dat_stb_path_entry.set_text(epg_options.get("epg_dat_stb_path", default_epg_data_stb_path))
|
||||
self._update_epg_data_on_start = epg_options.get("epg_data_update_on_start", False)
|
||||
self._update_on_start_switch.set_active(self._update_epg_data_on_start)
|
||||
local_xml_path = epg_options.get("local_path_to_xml", None)
|
||||
if local_xml_path:
|
||||
self._xml_chooser_button.set_filename(local_xml_path)
|
||||
os.makedirs(os.path.dirname(self._epg_dat_path_entry.get_text()), exist_ok=True)
|
||||
|
||||
def on_options_save(self, item=None):
|
||||
epg_options = {"xml_source": self._xml_radiobutton.get_active(),
|
||||
"use_web_source": self._use_web_source_switch.get_active(),
|
||||
"local_path_to_xml": self._xml_chooser_button.get_filename(),
|
||||
"url_to_xml": self._url_to_xml_entry.get_text(),
|
||||
"enable_filtering": self._enable_filtering_switch.get_active(),
|
||||
"epg_dat_path": self._epg_dat_path_entry.get_text(),
|
||||
"epg_dat_stb_path": self._epg_dat_stb_path_entry.get_text(),
|
||||
"epg_data_update_on_start": self._update_on_start_switch.get_active()}
|
||||
self._options["epg_options"] = epg_options
|
||||
|
||||
def on_resize(self, window):
|
||||
if self._options:
|
||||
self._options["epg_tool_window_size"] = window.get_size()
|
||||
|
||||
def on_names_source_changed(self, button):
|
||||
self._refs_source = RefsSource.XML if button.get_active() else RefsSource.SERVICES
|
||||
self._names_source_box.set_sensitive(button.get_active())
|
||||
|
||||
def on_enable_filtering_switch(self, switch, state):
|
||||
self._epg_dat_source_box.set_sensitive(state)
|
||||
self._update_on_start_switch.set_active(False if not state else self._update_epg_data_on_start)
|
||||
|
||||
def on_update_on_start_switch(self, switch, state):
|
||||
pass
|
||||
|
||||
def on_use_web_source_switch(self, switch, state):
|
||||
self._web_source_box.set_sensitive(state)
|
||||
self._xml_chooser_button.set_sensitive(not state)
|
||||
|
||||
def on_field_icon_press(self, entry, icon, event_button):
|
||||
update_entry_data(entry, self._dialog, self._options)
|
||||
|
||||
# ***************** Downloads *********************#
|
||||
|
||||
def download_epg_from_stb(self):
|
||||
""" Download the epg.dat file via ftp from the receiver. """
|
||||
download_data(properties=self._options, download_type=DownloadType.EPG, callback=print)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
388
app/ui/import_dialog.glade
Normal file
388
app/ui/import_dialog.glade
Normal file
@@ -0,0 +1,388 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2019 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkListStore" id="main_list_store">
|
||||
<columns>
|
||||
<!-- column-name name -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name type -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name selected -->
|
||||
<column type="gboolean"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_selection_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-undo</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="select_all_popup_item">
|
||||
<property name="label">gtk-select-all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_select_all" object="main_view" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="unselect_all_popup_item">
|
||||
<property name="label" translatable="yes">Remove selection</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">remove_selection_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_unselect_all" object="main_view" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkListStore" id="services_list_store">
|
||||
<columns>
|
||||
<!-- column-name name -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name type -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkWindow" id="dialog_window">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="default_width">480</property>
|
||||
<property name="default_height">320</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="gravity">center</property>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Import</property>
|
||||
<property name="subtitle" translatable="yes">Bouquets and services</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="import_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Import</property>
|
||||
<signal name="clicked" handler="on_import" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="import_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-revert-to-saved</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="info_check_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="info_check_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-dialog-info</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="width_request">480</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">1</property>
|
||||
<property name="margin_right">1</property>
|
||||
<property name="margin_bottom">1</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkPaned" id="main_paned">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="wide_handle">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="bouquets_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="label" translatable="yes">Bouquets</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="bouquets_screlled_window">
|
||||
<property name="width_request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="main_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">main_list_store</property>
|
||||
<property name="headers_clickable">False</property>
|
||||
<property name="search_column">0</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
<signal name="select-all" handler="on_select_all" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="bouquet_name_column">
|
||||
<property name="title" translatable="yes">Name</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="bq_name_renderer">
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="bouquet_type_column">
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="bq_type_renderer">
|
||||
<property name="xalign">0.50999999046325684</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="bouquet_selected_column">
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="bq_selected_renderer">
|
||||
<property name="xalign">0.50999999046325684</property>
|
||||
<signal name="toggled" handler="on_selected_toggled" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="services_box">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="label" translatable="yes">Bouquet details</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="services_view_scrolled_window">
|
||||
<property name="width_request">150</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="services_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">services_list_store</property>
|
||||
<property name="headers_clickable">False</property>
|
||||
<property name="search_column">0</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="service_name_column">
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="info_name_renderer">
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="service_type_column">
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="info_type_renderer">
|
||||
<property name="xalign">0.50999999046325684</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="info_bar">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<signal name="response" handler="on_info_bar_close" swapped="no"/>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">6</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 internal-child="content_area">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">16</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="message_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">message</property>
|
||||
<property name="wrap">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
224
app/ui/imports.py
Normal file
224
app/ui/imports.py
Normal file
@@ -0,0 +1,224 @@
|
||||
from contextlib import suppress
|
||||
from pathlib import Path
|
||||
|
||||
from app.commons import run_idle
|
||||
from app.eparser import get_bouquets, get_services
|
||||
from app.eparser.ecommons import BqType, BqServiceType, Bouquet
|
||||
from app.eparser.enigma.bouquets import get_bouquet
|
||||
from app.eparser.neutrino.bouquets import parse_webtv, parse_bouquets as get_neutrino_bouquets
|
||||
from app.properties import Profile
|
||||
from app.ui.dialogs import show_dialog, DialogType, get_chooser_dialog, get_message
|
||||
from app.ui.main_helper import on_popup_menu
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, KeyboardKey, Column
|
||||
|
||||
|
||||
def import_bouquet(transient, profile, model, path, options, services, appender):
|
||||
""" Import of single bouquet """
|
||||
itr = model.get_iter(path)
|
||||
bq_type = BqType(model.get(itr, Column.BQ_TYPE)[0])
|
||||
pattern, f_pattern = None, None
|
||||
|
||||
if profile is Profile.ENIGMA_2:
|
||||
pattern = ".{}".format(bq_type.value)
|
||||
f_pattern = "userbouquet.*{}".format(pattern)
|
||||
elif profile is Profile.NEUTRINO_MP:
|
||||
pattern = "webtv.xml" if bq_type is BqType.WEBTV else "bouquets.xml"
|
||||
f_pattern = "bouquets.xml"
|
||||
if bq_type is BqType.TV:
|
||||
f_pattern = "ubouquets.xml"
|
||||
elif bq_type is BqType.WEBTV:
|
||||
f_pattern = "webtv.xml"
|
||||
|
||||
file_path = get_chooser_dialog(transient, options, f_pattern, "bouquet files")
|
||||
if file_path == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
if not str(file_path).endswith(pattern):
|
||||
show_dialog(DialogType.ERROR, transient, text="No bouquet file is selected!")
|
||||
return
|
||||
|
||||
if profile is Profile.ENIGMA_2:
|
||||
bq = get_enigma2_bouquet(file_path)
|
||||
imported = list(filter(lambda x: x.data in services or x.type is BqServiceType.IPTV, bq.services))
|
||||
|
||||
if len(imported) == 0:
|
||||
show_dialog(DialogType.ERROR, transient, text="The main list does not contain services for this bouquet!")
|
||||
return
|
||||
|
||||
if model.iter_n_children(itr):
|
||||
appender(bq, itr)
|
||||
else:
|
||||
p_itr = model.iter_parent(itr)
|
||||
appender(bq, p_itr) if p_itr else appender(bq, itr)
|
||||
elif profile is Profile.NEUTRINO_MP:
|
||||
if bq_type is BqType.WEBTV:
|
||||
bqs = parse_webtv(file_path, "WEBTV", bq_type.value)
|
||||
else:
|
||||
bqs = get_neutrino_bouquets(file_path, "", bq_type.value)
|
||||
file_path = "{}/".format(Path(file_path).parent)
|
||||
ImportDialog(transient, file_path, profile, services.keys(), lambda b, s: appender(b), (bqs,)).show()
|
||||
|
||||
|
||||
def get_enigma2_bouquet(path):
|
||||
path, sep, f_name = path.rpartition("userbouquet.")
|
||||
name, sep, suf = f_name.rpartition(".")
|
||||
bq = get_bouquet(path, name, suf)
|
||||
bouquet = Bouquet(name=bq[0], type=BqType(suf).value, services=bq[1], locked=None, hidden=None)
|
||||
return bouquet
|
||||
|
||||
|
||||
class ImportDialog:
|
||||
def __init__(self, transient, path, profile, service_ids, appender, bouquets=None):
|
||||
handlers = {"on_import": self.on_import,
|
||||
"on_cursor_changed": self.on_cursor_changed,
|
||||
"on_info_button_toggled": self.on_info_button_toggled,
|
||||
"on_selected_toggled": self.on_selected_toggled,
|
||||
"on_info_bar_close": self.on_info_bar_close,
|
||||
"on_select_all": self.on_select_all,
|
||||
"on_unselect_all": self.on_unselect_all,
|
||||
"on_popup_menu": on_popup_menu,
|
||||
"on_key_press": self.on_key_press}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain("demon-editor")
|
||||
builder.add_from_file(UI_RESOURCES_PATH + "import_dialog.glade")
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._bq_services = {}
|
||||
self._services = {}
|
||||
self._service_ids = service_ids
|
||||
self._append = appender
|
||||
self._profile = profile
|
||||
self._bouquets = bouquets
|
||||
|
||||
self._dialog_window = builder.get_object("dialog_window")
|
||||
self._dialog_window.set_transient_for(transient)
|
||||
self._main_model = builder.get_object("main_list_store")
|
||||
self._main_view = builder.get_object("main_view")
|
||||
self._services_view = builder.get_object("services_view")
|
||||
self._services_model = builder.get_object("services_list_store")
|
||||
self._services_box = builder.get_object("services_box")
|
||||
self._info_check_button = builder.get_object("info_check_button")
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._message_label = builder.get_object("message_label")
|
||||
|
||||
self.init_data(path)
|
||||
|
||||
def show(self):
|
||||
self._dialog_window.show()
|
||||
|
||||
@run_idle
|
||||
def init_data(self, path):
|
||||
self._main_model.clear()
|
||||
self._services_model.clear()
|
||||
try:
|
||||
if not self._bouquets:
|
||||
self._bouquets = get_bouquets(path, self._profile)
|
||||
for bqs in self._bouquets:
|
||||
for bq in bqs.bouquets:
|
||||
self._main_model.append((bq.name, bq.type, True))
|
||||
self._bq_services[(bq.name, bq.type)] = bq.services
|
||||
# Note! Getting default format ver. 4
|
||||
services = get_services(path, self._profile, 4 if self._profile is Profile.ENIGMA_2 else 0)
|
||||
for srv in services:
|
||||
self._services[srv.fav_id] = srv
|
||||
except FileNotFoundError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
|
||||
def on_import(self, item):
|
||||
if not any(r[-1] for r in self._main_model):
|
||||
self.show_info_message(get_message("No selected item!"), Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
if not self._bouquets or show_dialog(DialogType.QUESTION, self._dialog_window) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
services = set()
|
||||
to_delete = set()
|
||||
|
||||
for row in self._main_model:
|
||||
bq = (row[0], row[1])
|
||||
if row[-1]:
|
||||
for bq_srv in self._bq_services.get(bq, []):
|
||||
srv = self._services.get(bq_srv.data, None)
|
||||
if srv:
|
||||
services.add(srv)
|
||||
else:
|
||||
to_delete.add(bq)
|
||||
|
||||
bqs_to_delete = []
|
||||
for bqs in self._bouquets:
|
||||
for bq in bqs.bouquets:
|
||||
if (bq.name, bq.type) in to_delete:
|
||||
bqs_to_delete.append(bq)
|
||||
|
||||
for bqs in self._bouquets:
|
||||
bq = bqs.bouquets
|
||||
for b in bqs_to_delete:
|
||||
with suppress(ValueError):
|
||||
bq.remove(b)
|
||||
|
||||
self._append(self._bouquets, list(filter(lambda s: s.fav_id not in self._service_ids, services)))
|
||||
self._dialog_window.destroy()
|
||||
|
||||
@run_idle
|
||||
def on_cursor_changed(self, view):
|
||||
if not self._info_check_button.get_active():
|
||||
return
|
||||
|
||||
self._services_model.clear()
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
if not paths:
|
||||
return
|
||||
|
||||
bq_services = self._bq_services.get(model.get(model.get_iter(paths[0]), 0, 1))
|
||||
for bq_srv in bq_services:
|
||||
if bq_srv.type is BqServiceType.DEFAULT:
|
||||
srv = self._services.get(bq_srv.data, None)
|
||||
if srv:
|
||||
self._services_model.append((srv.service, srv.service_type))
|
||||
else:
|
||||
self._services_model.append((bq_srv.name, bq_srv.type.value))
|
||||
|
||||
def on_info_button_toggled(self, button):
|
||||
active = button.get_active()
|
||||
self._services_box.set_visible(active)
|
||||
|
||||
def on_selected_toggled(self, toggle, path):
|
||||
self._main_model.set_value(self._main_model.get_iter(path), 2, not toggle.get_active())
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(True)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._message_label.set_text(text)
|
||||
|
||||
@run_idle
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
|
||||
def on_select_all(self, view):
|
||||
self.update_selection(view, True)
|
||||
|
||||
def on_unselect_all(self, view):
|
||||
self.update_selection(view, False)
|
||||
|
||||
def update_selection(self, view, select):
|
||||
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 2, select))
|
||||
|
||||
def on_key_press(self, view, event):
|
||||
""" Handling keystrokes """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
|
||||
if key is KeyboardKey.SPACE:
|
||||
path, column = view.get_cursor()
|
||||
itr = self._main_model.get_iter(path)
|
||||
selected = self._main_model.get_value(itr, 2)
|
||||
self._main_model.set_value(itr, 2, not selected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2019 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkDialog" id="search_unavailable_streams_dialog">
|
||||
<property name="use-header-bar">1</property>
|
||||
@@ -45,18 +45,9 @@ Author: Dmitriy Yefremov
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<property name="decorated">False</property>
|
||||
<property name="gravity">center</property>
|
||||
<signal name="response" handler="on_response" swapped="no"/>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="search_unavailable_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>
|
||||
</child>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
@@ -65,6 +56,16 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">1</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="search_unavailable_box_frame">
|
||||
<property name="visible">True</property>
|
||||
@@ -74,42 +75,17 @@ Author: Dmitriy Yefremov
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="label_yalign">1</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="search_unavailable_main_box">
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Please wait, streams testing in progress...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLevelBar" id="unavailable_streams_level_bar">
|
||||
<property name="height_request">10</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="inverted">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<property name="column_spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
@@ -157,11 +133,59 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLevelBar" id="unavailable_streams_level_bar">
|
||||
<property name="height_request">10</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="inverted">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="search_unavailable_cancel_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Cancel</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="cancel_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-cancel</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Please wait, streams testing in progress...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
@@ -192,10 +216,16 @@ Author: Dmitriy Yefremov
|
||||
<row>
|
||||
<col id="0">non-TS</col>
|
||||
</row>
|
||||
<row>
|
||||
<col id="0">none-REC1</col>
|
||||
</row>
|
||||
<row>
|
||||
<col id="0">none-REC2</col>
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkDialog" id="iptv_dialog">
|
||||
<property name="use-header-bar">1</property>
|
||||
<property name="use-header-bar">{use_header}</property>
|
||||
<property name="width_request">480</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Stream data</property>
|
||||
@@ -400,14 +430,49 @@ Author: Dmitriy Yefremov
|
||||
<property name="label_xalign">0.019999999552965164</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="url_entry">
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<signal name="changed" handler="on_url_changed" swapped="no"/>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="url_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<property name="secondary_icon_tooltip_text" translatable="yes">Link to YouTube resource.</property>
|
||||
<signal name="changed" handler="on_url_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="yt_iptv_quality_combobox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Desired video quality</property>
|
||||
<property name="model">yt_quality_liststore</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<signal name="changed" handler="on_yt_quality_changed" swapped="no"/>
|
||||
<property name="active">0</property>
|
||||
<property name="id_column">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="yt_quality_renderer"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
@@ -592,6 +657,54 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="info_bar">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<signal name="response" handler="on_info_bar_close" swapped="no"/>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">6</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 internal-child="content_area">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">16</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="info_bar_message_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">label</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -606,10 +719,10 @@ Author: Dmitriy Yefremov
|
||||
</action-widgets>
|
||||
</object>
|
||||
<object class="GtkDialog" id="iptv_list_configuration_dialog">
|
||||
<property name="use-header-bar">1</property>
|
||||
<property name="use-header-bar">{use_header}</property>
|
||||
<property name="width_request">400</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title"> IPTV streams list configuration</property>
|
||||
<property name="title" translatable="yes">IPTV streams list configuration</property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center</property>
|
||||
@@ -1108,4 +1221,379 @@ Author: Dmitriy Yefremov
|
||||
<action-widget response="-6">close_config_list_button</action-widget>
|
||||
</action-widgets>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_selection_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-undo</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="yt_popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="select_all_popup_item">
|
||||
<property name="label">gtk-select-all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_select_all" object="yt_list_view" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="unselect_all_popup_item">
|
||||
<property name="label" translatable="yes">Remove selection</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">remove_selection_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_unselect_all" object="yt_list_view" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkListStore" id="yt_liststore">
|
||||
<columns>
|
||||
<!-- column-name title -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name id -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name selected -->
|
||||
<column type="gboolean"/>
|
||||
<!-- column-name tooltip -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkListStore" id="yt_quality_liststore">
|
||||
<columns>
|
||||
<!-- column-name quality -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
<data>
|
||||
<row>
|
||||
<col id="0" translatable="yes">Auto</col>
|
||||
</row>
|
||||
<row>
|
||||
<col id="0">720p</col>
|
||||
</row>
|
||||
<row>
|
||||
<col id="0">360p</col>
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkWindow" id="yt_import_dialog_window">
|
||||
<property name="width_request">480</property>
|
||||
<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="default_width">480</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<property name="gravity">center</property>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="yt_header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="title" translatable="yes">YouTube</property>
|
||||
<property name="subtitle" translatable="yes">Playlist import</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="yt_receive_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Receive</property>
|
||||
<signal name="clicked" handler="on_receive" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="yt_receive_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-goto-bottom</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="d" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="yt_import_button">
|
||||
<property name="visible">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Import</property>
|
||||
<signal name="clicked" handler="on_import" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="yt_import_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">insert-link</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="yt_quality_combobox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Desired video quality</property>
|
||||
<property name="model">yt_quality_liststore</property>
|
||||
<property name="active">0</property>
|
||||
<property name="id_column">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="yt_quality_renderer"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="yt_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="yt_url_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<property name="secondary_icon_tooltip_text" translatable="yes">Link to YouTube resource.</property>
|
||||
<property name="placeholder_text" translatable="yes">YouTube playlist URL:</property>
|
||||
<signal name="changed" handler="on_yt_url_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="yt_list_view_scrolled_window">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="min_content_height">150</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="yt_list_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">yt_liststore</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="enable_grid_lines">horizontal</property>
|
||||
<property name="tooltip_column">3</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="yt_popup_menu" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
<signal name="select-all" handler="on_select_all" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="yt_title_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">50</property>
|
||||
<property name="title" translatable="yes">Title</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="yt_title_renderer">
|
||||
<property name="xpad">5</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="yt_id_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title" translatable="yes">ID</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="yt_id_renderer"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="yt_selected_column">
|
||||
<property name="min_width">50</property>
|
||||
<property name="max_width">100</property>
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">2</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="yt_selected_renderer">
|
||||
<property name="width">50</property>
|
||||
<signal name="toggled" handler="on_selected_toggled" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="yt_tooltip_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title" translatable="yes">Tooltip</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="yt_tooltip_renderer"/>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="yt_info_bar_box">
|
||||
<property name="height_request">24</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="yt_cout_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="yt_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-properties</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="yt_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label">0</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">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkProgressBar" id="yt_progress_bar">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="pulse_step">0.01</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</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="GtkInfoBar" id="yt_info_bar">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<signal name="response" handler="on_yt_info_bar_close" swapped="no"/>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child internal-child="content_area">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">16</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="yt_info_bar_message_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">info</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
403
app/ui/iptv.py
403
app/ui/iptv.py
@@ -1,20 +1,28 @@
|
||||
import concurrent.futures
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import urllib
|
||||
from functools import lru_cache
|
||||
from urllib.error import HTTPError
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
from app.commons import run_idle, run_task
|
||||
from gi.repository import GLib
|
||||
|
||||
from app.commons import run_idle, run_task, log
|
||||
from app.eparser.ecommons import BqServiceType, Service
|
||||
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT
|
||||
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT, get_fav_id, MARKER_FORMAT
|
||||
from app.properties import Profile
|
||||
from .uicommons import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON
|
||||
from .dialogs import Action, show_dialog, DialogType
|
||||
from .main_helper import get_base_model, get_iptv_url
|
||||
from app.tools.yt import YouTube, PlayListParser
|
||||
from .dialogs import Action, show_dialog, DialogType, get_dialogs_string, get_message
|
||||
from .main_helper import get_base_model, get_iptv_url, on_popup_menu
|
||||
from .uicommons import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON, Column, IS_GNOME_SESSION, KeyboardKey
|
||||
|
||||
_DIGIT_ENTRY_NAME = "digit-entry"
|
||||
_ENIGMA2_REFERENCE = "{}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
|
||||
_PATTERN = re.compile("(?:^[\s]*$|\D)")
|
||||
_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
|
||||
_UI_PATH = UI_RESOURCES_PATH + "iptv.glade"
|
||||
|
||||
|
||||
def is_data_correct(elems):
|
||||
@@ -24,6 +32,33 @@ def is_data_correct(elems):
|
||||
return True
|
||||
|
||||
|
||||
def get_stream_type(box):
|
||||
active = box.get_active()
|
||||
if active == 0:
|
||||
return StreamType.DVB_TS.value
|
||||
elif active == 1:
|
||||
return StreamType.NONE_TS.value
|
||||
elif active == 2:
|
||||
return StreamType.NONE_REC_1.value
|
||||
return StreamType.NONE_REC_2.value
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_yt_icon(icon_name, size=24):
|
||||
""" Getting YouTube icon. If the icon is not found in the icon themes, the "Info" icon is returned by default! """
|
||||
default_theme = Gtk.IconTheme.get_default()
|
||||
if default_theme.has_icon(icon_name):
|
||||
return default_theme.load_icon(icon_name, size, 0)
|
||||
|
||||
theme = Gtk.IconTheme.new()
|
||||
for theme_name in map(os.path.basename, filter(os.path.isdir, glob.glob("/usr/share/icons/*"))):
|
||||
theme.set_custom_theme(theme_name)
|
||||
if theme.has_icon(icon_name):
|
||||
return theme.load_icon(icon_name, size, 0)
|
||||
|
||||
return default_theme.load_icon("info", size, 0)
|
||||
|
||||
|
||||
class IptvDialog:
|
||||
|
||||
def __init__(self, transient, view, services, bouquet, profile=Profile.ENIGMA_2, action=Action.ADD):
|
||||
@@ -31,13 +66,22 @@ class IptvDialog:
|
||||
"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}
|
||||
"on_stream_type_changed": self.on_stream_type_changed,
|
||||
"on_yt_quality_changed": self.on_yt_quality_changed,
|
||||
"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 + "iptv.glade", ("iptv_dialog", "stream_type_liststore"))
|
||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
||||
("iptv_dialog", "stream_type_liststore", "yt_quality_liststore"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._action = action
|
||||
self._profile = profile
|
||||
self._bouquet = bouquet
|
||||
self._services = services
|
||||
self._yt_links = None
|
||||
|
||||
self._dialog = builder.get_object("iptv_dialog")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._name_entry = builder.get_object("name_entry")
|
||||
@@ -53,10 +97,9 @@ class IptvDialog:
|
||||
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._info_bar = builder.get_object("info_bar")
|
||||
self._message_label = builder.get_object("info_bar_message_label")
|
||||
self._yt_quality_box = builder.get_object("yt_iptv_quality_combobox")
|
||||
self._model, self._paths = view.get_selection().get_selected_rows()
|
||||
# style
|
||||
self._style_provider = Gtk.CssProvider()
|
||||
@@ -81,6 +124,7 @@ class IptvDialog:
|
||||
self._add_button.set_visible(True)
|
||||
if self._profile is Profile.ENIGMA_2:
|
||||
self._update_reference_entry()
|
||||
self._stream_type_combobox.set_active(1)
|
||||
elif self._action is Action.EDIT:
|
||||
self._current_srv = get_base_model(self._model)[self._paths][:]
|
||||
self.init_data(self._current_srv)
|
||||
@@ -93,9 +137,11 @@ class IptvDialog:
|
||||
self._dialog.destroy()
|
||||
|
||||
def on_save(self, item):
|
||||
self.on_url_changed(self._url_entry)
|
||||
if self._action is Action.ADD:
|
||||
self.on_url_changed(self._url_entry)
|
||||
|
||||
if not is_data_correct(self._digit_elems) or self._url_entry.get_name() == _DIGIT_ENTRY_NAME:
|
||||
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
|
||||
self.show_info_message(get_message("Error. Verify the data!"), Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
@@ -110,18 +156,32 @@ class IptvDialog:
|
||||
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:")
|
||||
data, sep, desc = fav_id.partition("#DESCRIPTION")
|
||||
self._description_entry.set_text(desc.strip())
|
||||
data = data.split(":")
|
||||
if len(data) < 12:
|
||||
if len(data) < 11:
|
||||
return
|
||||
self._stream_type_combobox.set_active(0 if StreamType(data[0].strip()) is StreamType.DVB_TS else 1)
|
||||
|
||||
s_type = data[0].strip()
|
||||
try:
|
||||
stream_type = StreamType(s_type)
|
||||
if stream_type is StreamType.DVB_TS:
|
||||
self._stream_type_combobox.set_active(0)
|
||||
elif stream_type is StreamType.NONE_TS:
|
||||
self._stream_type_combobox.set_active(1)
|
||||
elif stream_type is StreamType.NONE_REC_1:
|
||||
self._stream_type_combobox.set_active(2)
|
||||
elif stream_type is StreamType.NONE_REC_2:
|
||||
self._stream_type_combobox.set_active(3)
|
||||
except ValueError:
|
||||
self.show_info_message("Unknown stream type {}".format(s_type), Gtk.MessageType.ERROR)
|
||||
|
||||
self._srv_type_entry.set_text(data[2])
|
||||
self._sid_entry.set_text(str(int(data[3], 16)))
|
||||
self._tr_id_entry.set_text(str(int(data[4], 16)))
|
||||
self._net_id_entry.set_text(str(int(data[5], 16)))
|
||||
self._namespace_entry.set_text(str(int(data[6], 16)))
|
||||
self._url_entry.set_text(data[10].replace("%3a", ":"))
|
||||
self._url_entry.set_text(urllib.request.unquote(data[10].strip()))
|
||||
self._update_reference_entry()
|
||||
|
||||
def init_neutrino_data(self, fav_id):
|
||||
@@ -139,7 +199,7 @@ class IptvDialog:
|
||||
int(self._namespace_entry.get_text())))
|
||||
|
||||
def get_type(self):
|
||||
return 1 if self._stream_type_combobox.get_active() == 0 else 4097
|
||||
return get_stream_type(self._stream_type_combobox)
|
||||
|
||||
def on_entry_changed(self, entry):
|
||||
if _PATTERN.search(entry.get_text()):
|
||||
@@ -149,12 +209,55 @@ class IptvDialog:
|
||||
self._update_reference_entry()
|
||||
|
||||
def on_url_changed(self, entry):
|
||||
url = urlparse(entry.get_text())
|
||||
url_str = entry.get_text()
|
||||
url = urlparse(url_str)
|
||||
entry.set_name("GtkEntry" if all([url.scheme, url.netloc, url.path]) else _DIGIT_ENTRY_NAME)
|
||||
|
||||
yt_id = YouTube.get_yt_id(url_str)
|
||||
if yt_id:
|
||||
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
|
||||
text = "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
|
||||
if show_dialog(DialogType.QUESTION, self._dialog, text=text) == Gtk.ResponseType.OK:
|
||||
entry.set_sensitive(False)
|
||||
gen = self.set_yt_url(entry, yt_id)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
elif YouTube.is_yt_video_link(url_str):
|
||||
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
|
||||
else:
|
||||
entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
|
||||
self._yt_quality_box.set_visible(False)
|
||||
|
||||
def set_yt_url(self, entry, video_id):
|
||||
try:
|
||||
links, title = YouTube.get_yt_link(video_id)
|
||||
except urllib.error.URLError as e:
|
||||
self.show_info_message(get_message("Getting link error:") + (str(e)), Gtk.MessageType.ERROR)
|
||||
return
|
||||
else:
|
||||
if self._action is Action.ADD:
|
||||
self._name_entry.set_text(title)
|
||||
|
||||
if links:
|
||||
if len(links) > 1:
|
||||
self._yt_quality_box.set_visible(True)
|
||||
entry.set_text(links[sorted(links, key=lambda x: int(x.rstrip("p")), reverse=True)[0]])
|
||||
self._yt_links = links
|
||||
else:
|
||||
msg = get_message("Getting link error:") + " No link received for id: {}".format(video_id)
|
||||
self.show_info_message(msg, Gtk.MessageType.ERROR)
|
||||
finally:
|
||||
entry.set_sensitive(True)
|
||||
yield True
|
||||
|
||||
def on_stream_type_changed(self, item):
|
||||
self._update_reference_entry()
|
||||
|
||||
def on_yt_quality_changed(self, box):
|
||||
model = box.get_model()
|
||||
active = model.get_value(box.get_active_iter(), 0)
|
||||
if self._yt_links and active in self._yt_links:
|
||||
self._url_entry.set_text(self._yt_links[active])
|
||||
|
||||
def save_enigma2_data(self):
|
||||
name = self._name_entry.get_text().strip()
|
||||
fav_id = ENIGMA2_FAV_ID_FORMAT.format(self.get_type(),
|
||||
@@ -163,7 +266,7 @@ class IptvDialog:
|
||||
int(self._tr_id_entry.get_text()),
|
||||
int(self._net_id_entry.get_text()),
|
||||
int(self._namespace_entry.get_text()),
|
||||
self._url_entry.get_text().replace(":", "%3a"),
|
||||
urllib.request.quote(self._url_entry.get_text()),
|
||||
name, name)
|
||||
self.update_bouquet_data(name, fav_id)
|
||||
|
||||
@@ -182,17 +285,27 @@ class IptvDialog:
|
||||
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})
|
||||
self._model.set(self._model.get_iter(self._paths), {Column.FAV_SERVICE: name, Column.FAV_ID: fav_id})
|
||||
else:
|
||||
aggr = [None] * 10
|
||||
s_type = BqServiceType.IPTV.name
|
||||
srv = (None, None, name, None, None, s_type, None, fav_id, None)
|
||||
srv = (None, None, name, None, None, s_type, None, fav_id, *aggr[0:3])
|
||||
itr = self._model.insert_after(self._model.get_iter(self._paths[0]),
|
||||
srv) if self._paths else self._model.insert(0, srv)
|
||||
self._model.set_value(itr, 1, IPTV_ICON)
|
||||
self._bouquet.insert(self._model.get_path(itr)[0], fav_id)
|
||||
self._services[fav_id] = Service(None, None, IPTV_ICON, name, *aggr[0:3], s_type, *aggr, fav_id, None)
|
||||
|
||||
@run_idle
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(True)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._message_label.set_text(text)
|
||||
|
||||
|
||||
class SearchUnavailableDialog:
|
||||
|
||||
@@ -280,7 +393,7 @@ class SearchUnavailableDialog:
|
||||
|
||||
class IptvListConfigurationDialog:
|
||||
|
||||
def __init__(self, transient, services, iptv_rows, bouquet, profile):
|
||||
def __init__(self, transient, services, iptv_rows, bouquet, fav_model, profile):
|
||||
handlers = {"on_apply": self.on_apply,
|
||||
"on_response": self.on_response,
|
||||
"on_stream_type_default_togged": self.on_stream_type_default_togged,
|
||||
@@ -296,13 +409,14 @@ class IptvListConfigurationDialog:
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_file(UI_RESOURCES_PATH + "iptv.glade",
|
||||
("iptv_list_configuration_dialog", "stream_type_liststore"))
|
||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
||||
("iptv_list_configuration_dialog", "stream_type_liststore"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._rows = iptv_rows
|
||||
self._services = services
|
||||
self._bouquet = bouquet
|
||||
self._fav_model = fav_model
|
||||
self._profile = profile
|
||||
|
||||
self._dialog = builder.get_object("iptv_list_configuration_dialog")
|
||||
@@ -391,9 +505,6 @@ class IptvListConfigurationDialog:
|
||||
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
|
||||
return
|
||||
|
||||
if len(self._bouquet) != len(self._rows):
|
||||
return
|
||||
|
||||
if self._profile is Profile.ENIGMA_2:
|
||||
reset = self._reset_to_default_switch.get_active()
|
||||
type_default = self._type_check_button.get_active()
|
||||
@@ -402,35 +513,40 @@ class IptvListConfigurationDialog:
|
||||
nid_default = self._nid_check_button.get_active()
|
||||
namespace_default = self._namespace_check_button.get_active()
|
||||
|
||||
stream_type = StreamType.NONE_TS.value if reset else get_stream_type(self._stream_type_combobox)
|
||||
srv_type = "1" if type_default else self._list_srv_type_entry.get_text()
|
||||
tid = "0" if tid_default else "{:X}".format(int(self._list_tid_entry.get_text()))
|
||||
nid = "0" if nid_default else "{:X}".format(int(self._list_nid_entry.get_text()))
|
||||
namespace = "0" if namespace_default else "{:X}".format(int(self._list_namespace_entry.get_text()))
|
||||
|
||||
for index, row in enumerate(self._rows):
|
||||
fav_id = row[7]
|
||||
fav_id = row[Column.FAV_ID]
|
||||
data, sep, desc = fav_id.partition("http")
|
||||
data = data.split(":")
|
||||
|
||||
if reset:
|
||||
data[0] = " 4097"
|
||||
data[2], data[3], data[4], data[5], data[6] = "10000"
|
||||
else:
|
||||
data[0] = " 4097" if self._stream_type_combobox.get_active() == 1 else "1"
|
||||
data[2] = "1" if type_default else self._list_srv_type_entry.get_text()
|
||||
data[0], data[2], data[4], data[5], data[6] = stream_type, srv_type, tid, nid, namespace
|
||||
data[3] = "{:X}".format(index) if sid_auto else "0"
|
||||
data[4] = "0" if tid_default else "{:X}".format(int(self._list_tid_entry.get_text()))
|
||||
data[5] = "0" if nid_default else "{:X}".format(int(self._list_nid_entry.get_text()))
|
||||
data[6] = "0" if namespace_default else "{:X}".format(int(self._list_namespace_entry.get_text()))
|
||||
|
||||
data = ":".join(data)
|
||||
new_fav_id = "{}{}{}".format(data, sep, desc)
|
||||
row[7] = new_fav_id
|
||||
self._bouquet[index] = new_fav_id
|
||||
row[Column.FAV_ID] = new_fav_id
|
||||
srv = self._services.pop(fav_id, None)
|
||||
self._services[new_fav_id] = srv._replace(fav_id=new_fav_id)
|
||||
|
||||
if srv:
|
||||
self._services[new_fav_id] = srv._replace(fav_id=new_fav_id)
|
||||
|
||||
self._bouquet.clear()
|
||||
list(map(lambda r: self._bouquet.append(r[Column.FAV_ID]), self._fav_model))
|
||||
|
||||
self._info_bar.set_visible(True)
|
||||
|
||||
@run_idle
|
||||
def update_reference(self):
|
||||
if is_data_correct(self._digit_elems):
|
||||
stream_type = "4097" if self._stream_type_combobox.get_active() == 1 else "1"
|
||||
stream_type = get_stream_type(self._stream_type_combobox)
|
||||
self._reference_label.set_text(
|
||||
_ENIGMA2_REFERENCE.format(stream_type, *[int(elem.get_text()) for elem in self._digit_elems]))
|
||||
|
||||
@@ -442,5 +558,210 @@ class IptvListConfigurationDialog:
|
||||
self.update_reference()
|
||||
|
||||
|
||||
class YtListImportDialog:
|
||||
def __init__(self, transient, profile, appender):
|
||||
handlers = {"on_import": self.on_import,
|
||||
"on_receive": self.on_receive,
|
||||
"on_yt_url_entry_changed": self.on_url_entry_changed,
|
||||
"on_yt_info_bar_close": self.on_info_bar_close,
|
||||
"on_popup_menu": on_popup_menu,
|
||||
"on_selected_toggled": self.on_selected_toggled,
|
||||
"on_select_all": self.on_select_all,
|
||||
"on_unselect_all": self.on_unselect_all,
|
||||
"on_key_press": self.on_key_press,
|
||||
"on_close": self.on_close}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
||||
("yt_import_dialog_window", "yt_liststore", "yt_quality_liststore",
|
||||
"yt_popup_menu", "remove_selection_image"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._dialog = builder.get_object("yt_import_dialog_window")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._list_view_scrolled_window = builder.get_object("yt_list_view_scrolled_window")
|
||||
self._model = builder.get_object("yt_liststore")
|
||||
self._progress_bar = builder.get_object("yt_progress_bar")
|
||||
self._info_bar_box = builder.get_object("yt_info_bar_box")
|
||||
self._message_label = builder.get_object("yt_info_bar_message_label")
|
||||
self._info_bar = builder.get_object("yt_info_bar")
|
||||
self._yt_count_label = builder.get_object("yt_count_label")
|
||||
self._url_entry = builder.get_object("yt_url_entry")
|
||||
self._receive_button = builder.get_object("yt_receive_button")
|
||||
self._import_button = builder.get_object("yt_import_button")
|
||||
self._quality_box = builder.get_object("yt_quality_combobox")
|
||||
self._quality_model = builder.get_object("yt_quality_liststore")
|
||||
self._import_button.bind_property("visible", self._quality_box, "visible")
|
||||
self._import_button.bind_property("sensitive", self._quality_box, "sensitive")
|
||||
self._receive_button.bind_property("sensitive", self._import_button, "sensitive")
|
||||
# style
|
||||
self._style_provider = Gtk.CssProvider()
|
||||
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
|
||||
self.appender = appender
|
||||
self._profile = profile
|
||||
self._download_task = False
|
||||
self._yt_list_id = None
|
||||
self._yt_list_title = None
|
||||
|
||||
def show(self):
|
||||
self._dialog.show()
|
||||
|
||||
@run_task
|
||||
def on_import(self, item):
|
||||
self.on_info_bar_close()
|
||||
self.update_active_elements(False)
|
||||
self._download_task = True
|
||||
|
||||
try:
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
||||
done_links = {}
|
||||
rows = list(filter(lambda r: r[2], self._model))
|
||||
futures = {executor.submit(YouTube.get_yt_link, r[1]): r for r in rows}
|
||||
size = len(futures)
|
||||
counter = 0
|
||||
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
if not self._download_task:
|
||||
executor.shutdown()
|
||||
return
|
||||
|
||||
done_links[futures[future]] = future.result()
|
||||
counter += 1
|
||||
self.update_progress_bar(counter / size)
|
||||
except Exception as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
if self._download_task:
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
self.append_services([done_links[r] for r in rows])
|
||||
finally:
|
||||
self._download_task = False
|
||||
self.update_active_elements(True)
|
||||
|
||||
def on_receive(self, item):
|
||||
self.show_invisible_elements()
|
||||
self.update_active_elements(False)
|
||||
self._model.clear()
|
||||
self._yt_count_label.set_text("0")
|
||||
self.on_info_bar_close()
|
||||
self.update_refs_list()
|
||||
|
||||
@run_task
|
||||
def update_refs_list(self):
|
||||
if self._yt_list_id:
|
||||
try:
|
||||
self._yt_list_title, links = PlayListParser.get_yt_playlist(self._yt_list_id)
|
||||
except Exception as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
return
|
||||
else:
|
||||
gen = self.update_links(links)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
finally:
|
||||
self.update_active_elements(True)
|
||||
|
||||
def update_links(self, links):
|
||||
for l in links:
|
||||
yield self._model.append((l[0], l[1], True, None))
|
||||
|
||||
size = len(self._model)
|
||||
self._yt_count_label.set_text(str(size))
|
||||
self._import_button.set_visible(size)
|
||||
yield True
|
||||
|
||||
@run_idle
|
||||
def append_services(self, links):
|
||||
aggr = [None] * 9
|
||||
srvs = []
|
||||
|
||||
if self._yt_list_title:
|
||||
title = self._yt_list_title
|
||||
fav_id = MARKER_FORMAT.format(0, title, title)
|
||||
mk = Service(None, None, None, title, *aggr[0:3], BqServiceType.MARKER.name, *aggr, 0, fav_id, None)
|
||||
srvs.append(mk)
|
||||
|
||||
act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0)
|
||||
for link in links:
|
||||
lnk, title = link
|
||||
if not lnk:
|
||||
continue
|
||||
ln = lnk.get(act) if act in lnk else lnk[sorted(lnk, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
|
||||
fav_id = get_fav_id(ln, title, self._profile)
|
||||
srv = Service(None, None, IPTV_ICON, title, *aggr[0:3], BqServiceType.IPTV.name, *aggr, None, fav_id, None)
|
||||
srvs.append(srv)
|
||||
self.appender(srvs)
|
||||
|
||||
@run_idle
|
||||
def update_active_elements(self, sensitive):
|
||||
self._url_entry.set_sensitive(sensitive)
|
||||
self._receive_button.set_sensitive(sensitive)
|
||||
|
||||
def show_invisible_elements(self):
|
||||
self._list_view_scrolled_window.set_visible(True)
|
||||
self._info_bar_box.set_visible(True)
|
||||
self._dialog.set_resizable(True)
|
||||
|
||||
def on_url_entry_changed(self, entry):
|
||||
url_str = entry.get_text()
|
||||
yt_id = YouTube.get_yt_list_id(url_str)
|
||||
entry.set_name("GtkEntry" if yt_id else _DIGIT_ENTRY_NAME)
|
||||
self._receive_button.set_sensitive(bool(yt_id))
|
||||
self._import_button.set_sensitive(bool(yt_id))
|
||||
self._yt_list_id = yt_id
|
||||
|
||||
if yt_id:
|
||||
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
|
||||
else:
|
||||
entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
|
||||
|
||||
@run_idle
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
|
||||
@run_idle
|
||||
def update_progress_bar(self, value):
|
||||
self._progress_bar.set_visible(value < 1)
|
||||
self._progress_bar.set_fraction(value)
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(True)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._message_label.set_text(text)
|
||||
|
||||
def on_selected_toggled(self, toggle, path):
|
||||
self._model.set_value(self._model.get_iter(path), 2, not toggle.get_active())
|
||||
|
||||
def on_select_all(self, view):
|
||||
self.update_selection(view, True)
|
||||
|
||||
def on_unselect_all(self, view):
|
||||
self.update_selection(view, False)
|
||||
|
||||
def update_selection(self, view, select):
|
||||
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 2, select))
|
||||
|
||||
def on_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
|
||||
if key is KeyboardKey.SPACE:
|
||||
path, column = view.get_cursor()
|
||||
itr = self._model.get_iter(path)
|
||||
selected = self._model.get_value(itr, 2)
|
||||
self._model.set_value(itr, 2, not selected)
|
||||
|
||||
def on_close(self, window, event):
|
||||
if self._download_task and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return True
|
||||
self._download_task = False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
app/ui/lang/pt/LC_MESSAGES/demon-editor.mo
Normal file
BIN
app/ui/lang/pt/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,8 @@
|
||||
""" This is helper module for ui """
|
||||
import os
|
||||
import shutil
|
||||
import urllib.request
|
||||
|
||||
from gi.repository import GdkPixbuf, GLib
|
||||
|
||||
from app.commons import run_task
|
||||
@@ -8,13 +10,13 @@ from app.eparser import Service
|
||||
from app.eparser.ecommons import Flag, BouquetService, Bouquet, BqType
|
||||
from app.eparser.enigma.bouquets import BqServiceType, to_bouquet_id
|
||||
from app.properties import Profile
|
||||
from .uicommons import ViewTarget, BqGenType, Gtk, Gdk, HIDE_ICON, LOCKED_ICON, KeyboardKey
|
||||
from .uicommons import ViewTarget, BqGenType, Gtk, Gdk, HIDE_ICON, LOCKED_ICON, KeyboardKey, Column
|
||||
from .dialogs import show_dialog, DialogType, get_chooser_dialog, WaitDialog
|
||||
|
||||
|
||||
# ***************** Markers *******************#
|
||||
|
||||
def insert_marker(view, bouquets, selected_bouquet, channels, parent_window):
|
||||
def insert_marker(view, bouquets, selected_bouquet, services, parent_window):
|
||||
"""" Inserts marker into bouquet services list. """
|
||||
response = show_dialog(DialogType.INPUT, parent_window)
|
||||
if response == Gtk.ResponseType.CANCEL:
|
||||
@@ -24,17 +26,13 @@ def insert_marker(view, bouquets, selected_bouquet, channels, parent_window):
|
||||
show_dialog(DialogType.ERROR, parent_window, "The text of marker is empty, please try again!")
|
||||
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),
|
||||
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)
|
||||
fav_id = "1:64:0:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n".format(response, response)
|
||||
s_type = BqServiceType.MARKER.name
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
marker = (None, None, response, None, None, s_type, None, fav_id, None)
|
||||
marker = (None, None, response, None, None, s_type, None, fav_id, None, None, 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)
|
||||
services[fav_id] = Service(None, None, None, response, None, None, None, s_type, *[None] * 9, 0, fav_id, None)
|
||||
|
||||
|
||||
# ***************** Movement *******************#
|
||||
@@ -75,10 +73,12 @@ def move_items(key, view: Gtk.TreeView):
|
||||
|
||||
if key is KeyboardKey.UP:
|
||||
top_path = Gtk.TreePath(paths[0])
|
||||
set_cursor(top_path, paths, selection, view)
|
||||
top_path.prev()
|
||||
move_up(top_path, model, paths)
|
||||
elif key is KeyboardKey.DOWN:
|
||||
down_path = Gtk.TreePath(paths[-1])
|
||||
set_cursor(down_path, paths, selection, view)
|
||||
down_path.next()
|
||||
if down_path < max_path:
|
||||
move_down(down_path, model, paths)
|
||||
@@ -118,6 +118,12 @@ def is_some_level(paths):
|
||||
return True
|
||||
|
||||
|
||||
def set_cursor(dest_path, paths, selection, view):
|
||||
view.set_cursor(dest_path, view.get_column(0), False)
|
||||
for p in paths:
|
||||
selection.select_path(p)
|
||||
|
||||
|
||||
# ***************** Rename *******************#
|
||||
|
||||
def rename(view, parent_window, target, fav_view=None, service_view=None, services=None):
|
||||
@@ -130,32 +136,32 @@ def rename(view, parent_window, target, fav_view=None, service_view=None, servic
|
||||
f_id, srv_name, srv_type = None, None, None
|
||||
|
||||
if target is ViewTarget.SERVICES:
|
||||
name, fav_id = model.get(itr, 3, 18)
|
||||
name, fav_id = model.get(itr, Column.SRV_SERVICE, Column.SRV_FAV_ID)
|
||||
f_id = fav_id
|
||||
response = show_dialog(DialogType.INPUT, parent_window, name)
|
||||
if response == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
srv_name = response
|
||||
model.set_value(itr, 3, response)
|
||||
model.set_value(itr, Column.SRV_SERVICE, response)
|
||||
if fav_view is not None:
|
||||
for row in fav_view.get_model():
|
||||
if row[7] == fav_id:
|
||||
row[2] = response
|
||||
if row[Column.FAV_ID] == fav_id:
|
||||
row[Column.FAV_SERVICE] = response
|
||||
break
|
||||
elif target is ViewTarget.FAV:
|
||||
name, srv_type, fav_id = model.get(itr, 2, 5, 7)
|
||||
name, srv_type, fav_id = model.get(itr, Column.FAV_SERVICE, Column.FAV_TYPE, Column.FAV_ID)
|
||||
f_id = fav_id
|
||||
response = show_dialog(DialogType.INPUT, parent_window, name)
|
||||
if response == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
srv_name = response
|
||||
model.set_value(itr, 2, response)
|
||||
model.set_value(itr, Column.FAV_SERVICE, response)
|
||||
|
||||
if service_view is not None:
|
||||
for row in get_base_model(service_view.get_model()):
|
||||
if row[18] == fav_id:
|
||||
row[3] = response
|
||||
if row[Column.SRV_FAV_ID] == fav_id:
|
||||
row[Column.SRV_SERVICE] = response
|
||||
break
|
||||
|
||||
old_srv = services.get(f_id, None)
|
||||
@@ -208,9 +214,9 @@ def set_flags(flag, services_view, fav_view, services, blacklist):
|
||||
if target is ViewTarget.SERVICES:
|
||||
set_hide(services, model, paths)
|
||||
else:
|
||||
fav_ids = [model.get_value(model.get_iter(path), 7) for path in paths]
|
||||
fav_ids = [model.get_value(model.get_iter(path), Column.FAV_ID) for path in paths]
|
||||
srv_model = get_base_model(services_view.get_model())
|
||||
srv_paths = [row.path for row in srv_model if row[18] in fav_ids]
|
||||
srv_paths = [row.path for row in srv_model if row[Column.SRV_FAV_ID] in fav_ids]
|
||||
set_hide(services, srv_model, srv_paths)
|
||||
elif flag is Flag.LOCK:
|
||||
set_lock(blacklist, services, model, paths, target, services_model=get_base_model(services_view.get_model()))
|
||||
@@ -220,20 +226,20 @@ def set_flags(flag, services_view, fav_view, services, blacklist):
|
||||
|
||||
def update_fav_model(fav_view, services):
|
||||
for row in get_base_model(fav_view.get_model()):
|
||||
srv = services.get(row[7], None)
|
||||
srv = services.get(row[Column.FAV_ID], None)
|
||||
if srv:
|
||||
row[3], row[4] = srv.locked, srv.hide
|
||||
row[Column.FAV_LOCKED], row[Column.FAV_HIDE] = srv.locked, srv.hide
|
||||
|
||||
|
||||
def set_lock(blacklist, services, model, paths, target, services_model):
|
||||
col_num = 4 if target is ViewTarget.SERVICES else 3
|
||||
col_num = Column.SRV_LOCKED if target is ViewTarget.SERVICES else Column.FAV_LOCKED
|
||||
locked = has_locked_hide(model, paths, col_num)
|
||||
|
||||
ids = []
|
||||
|
||||
for path in paths:
|
||||
itr = model.get_iter(path)
|
||||
fav_id = model.get_value(itr, 18 if target is ViewTarget.SERVICES else 7)
|
||||
fav_id = model.get_value(itr, Column.SRV_FAV_ID if target is ViewTarget.SERVICES else Column.FAV_ID)
|
||||
srv = services.get(fav_id, None)
|
||||
if srv:
|
||||
bq_id = to_bouquet_id(srv)
|
||||
@@ -251,13 +257,13 @@ def set_lock(blacklist, services, model, paths, target, services_model):
|
||||
|
||||
def update_services_model(ids, locked, services_model):
|
||||
for srv in services_model:
|
||||
if srv[18] in ids:
|
||||
srv[4] = None if locked else LOCKED_ICON
|
||||
if srv[Column.SRV_FAV_ID] in ids:
|
||||
srv[Column.SRV_LOCKED] = None if locked else LOCKED_ICON
|
||||
yield True
|
||||
|
||||
|
||||
def set_hide(services, model, paths):
|
||||
col_num = 5
|
||||
col_num = Column.SRV_HIDE
|
||||
hide = has_locked_hide(model, paths, col_num)
|
||||
|
||||
for path in paths:
|
||||
@@ -292,7 +298,7 @@ def set_hide(services, model, paths):
|
||||
flags.append(value)
|
||||
|
||||
model.set_value(itr, 0, (",".join(reversed(sorted(flags)))))
|
||||
fav_id = model.get_value(itr, 18)
|
||||
fav_id = model.get_value(itr, Column.SRV_FAV_ID)
|
||||
srv = services.get(fav_id, None)
|
||||
if srv:
|
||||
services[fav_id] = srv._replace(hide=None if hide else HIDE_ICON)
|
||||
@@ -317,9 +323,9 @@ def locate_in_services(fav_view, services_view, parent_window):
|
||||
show_dialog(DialogType.ERROR, parent_window, "Please, select only one item!")
|
||||
return
|
||||
|
||||
fav_id = model.get_value(model.get_iter(paths[0]), 7)
|
||||
fav_id = model.get_value(model.get_iter(paths[0]), Column.FAV_ID)
|
||||
for index, row in enumerate(services_view.get_model()):
|
||||
if row[18] == fav_id:
|
||||
if row[Column.SRV_FAV_ID] == fav_id:
|
||||
scroll_to(index, services_view)
|
||||
break
|
||||
|
||||
@@ -347,7 +353,7 @@ def update_picons_data(path, picons):
|
||||
def append_picons(picons, model):
|
||||
def append_picons_data(pcs, mod):
|
||||
for r in mod:
|
||||
mod.set_value(mod.get_iter(r.path), 8, pcs.get(r[9], None))
|
||||
mod.set_value(mod.get_iter(r.path), Column.SRV_PICON, pcs.get(r[Column.SRV_PICON_ID], None))
|
||||
yield True
|
||||
|
||||
app = append_picons_data(picons, model)
|
||||
@@ -368,11 +374,11 @@ def assign_picon(target, srv_view, fav_view, transient, picons, options, service
|
||||
show_dialog(DialogType.ERROR, transient, text="No png file is selected!")
|
||||
return
|
||||
|
||||
picon_pos = 8
|
||||
picon_pos = Column.SRV_PICON
|
||||
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]
|
||||
fav_id = model.get_value(itr, Column.SRV_FAV_ID if target is ViewTarget.SERVICES else Column.FAV_ID)
|
||||
picon_id = services.get(fav_id)[Column.SRV_PICON_ID]
|
||||
|
||||
if picon_id:
|
||||
picon_file = options.get("picons_dir_path") + picon_id
|
||||
@@ -382,9 +388,9 @@ def assign_picon(target, srv_view, fav_view, transient, picons, options, service
|
||||
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)
|
||||
set_picon(fav_id, fav_view.get_model(), picon, Column.FAV_ID, picon_pos)
|
||||
else:
|
||||
set_picon(fav_id, get_base_model(srv_view.get_model()), picon, 18, picon_pos)
|
||||
set_picon(fav_id, get_base_model(srv_view.get_model()), picon, Column.SRV_FAV_ID, picon_pos)
|
||||
|
||||
|
||||
def set_picon(fav_id, model, picon, fav_id_pos, picon_pos):
|
||||
@@ -401,39 +407,31 @@ def remove_picon(target, srv_view, fav_view, picons, options):
|
||||
|
||||
fav_ids = []
|
||||
picon_ids = []
|
||||
picon_pos = 8 # picon position is equal for services and fav
|
||||
picon_pos = Column.SRV_PICON # 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))
|
||||
fav_ids.append(model.get_value(itr, Column.SRV_FAV_ID))
|
||||
picon_ids.append(model.get_value(itr, Column.SRV_PICON_ID))
|
||||
else:
|
||||
srv_type, fav_id = model.get(itr, 5, 7)
|
||||
srv_type, fav_id = model.get(itr, Column.FAV_TYPE, Column.FAV_ID)
|
||||
if srv_type == BqServiceType.IPTV.name:
|
||||
picon_ids.append("{}_{}_{}_{}_{}_{}_{}_{}_{}_{}.png".format(*fav_id.split(":")[0:10]).strip())
|
||||
else:
|
||||
fav_ids.append(fav_id)
|
||||
|
||||
def remove(md, path, it):
|
||||
if md.get_value(it, 7 if target is ViewTarget.SERVICES else 18) in fav_ids:
|
||||
if md.get_value(it, Column.FAV_ID if target is ViewTarget.SERVICES else Column.SRV_FAV_ID) in fav_ids:
|
||||
md.set_value(it, picon_pos, None)
|
||||
if target is ViewTarget.FAV:
|
||||
picon_ids.append(md.get_value(it, 9))
|
||||
picon_ids.append(md.get_value(it, Column.SRV_PICON_ID))
|
||||
|
||||
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)
|
||||
remove_picons(options, picon_ids, picons)
|
||||
|
||||
|
||||
def copy_picon_reference(target, view, services, clipboard, transient):
|
||||
@@ -443,13 +441,13 @@ def copy_picon_reference(target, view, services, clipboard, transient):
|
||||
return
|
||||
|
||||
if target is ViewTarget.SERVICES:
|
||||
picon_id = model.get_value(model.get_iter(paths), 9)
|
||||
picon_id = model.get_value(model.get_iter(paths), Column.SRV_PICON_ID)
|
||||
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)
|
||||
fav_id = model.get_value(model.get_iter(paths), Column.FAV_ID)
|
||||
srv = services.get(fav_id, None)
|
||||
if srv and srv.picon_id:
|
||||
clipboard.set_text(srv.picon_id.rstrip(".png"), -1)
|
||||
@@ -457,6 +455,23 @@ def copy_picon_reference(target, view, services, clipboard, transient):
|
||||
show_dialog(DialogType.ERROR, transient, "No reference is present!")
|
||||
|
||||
|
||||
def remove_all_unused_picons(options, picons, services):
|
||||
ids = {s.picon_id for s in services}
|
||||
pcs = list(filter(lambda x: x not in ids, picons))
|
||||
remove_picons(options, pcs, picons)
|
||||
|
||||
|
||||
def remove_picons(options, picon_ids, picons):
|
||||
pions_path = options.get("picons_dir_path")
|
||||
backup_path = options.get("backup_dir_path") + "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 is_only_one_item_selected(paths, transient):
|
||||
if len(paths) > 1:
|
||||
show_dialog(DialogType.ERROR, transient, "Please, select only one item!")
|
||||
@@ -477,15 +492,19 @@ def get_picon_pixbuf(path):
|
||||
|
||||
def gen_bouquets(view, bq_view, transient, gen_type, tv_types, profile, callback):
|
||||
""" Auto-generate and append list of bouquets """
|
||||
fav_id_index = 18
|
||||
index = 6 if gen_type in (BqGenType.PACKAGE, BqGenType.EACH_PACKAGE) else 16 if gen_type in (
|
||||
BqGenType.SAT, BqGenType.EACH_SAT) else 7
|
||||
fav_id_index = Column.SRV_FAV_ID
|
||||
index = Column.SRV_TYPE
|
||||
if gen_type in (BqGenType.PACKAGE, BqGenType.EACH_PACKAGE):
|
||||
index = Column.SRV_PACKAGE
|
||||
elif gen_type in (BqGenType.SAT, BqGenType.EACH_SAT):
|
||||
index = Column.SRV_POS
|
||||
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
bq_type = BqType.BOUQUET.value if profile is Profile.NEUTRINO_MP else BqType.TV.value
|
||||
if gen_type in (BqGenType.SAT, BqGenType.PACKAGE, BqGenType.TYPE):
|
||||
if not is_only_one_item_selected(paths, transient):
|
||||
return
|
||||
service = Service(*model[paths][:])
|
||||
service = Service(*model[paths][:Column.SRV_TOOLTIP])
|
||||
if service.service_type not in tv_types:
|
||||
bq_type = BqType.RADIO.value
|
||||
append_bouquets(bq_type, bq_view, callback, fav_id_index, index, model,
|
||||
@@ -564,12 +583,12 @@ def append_text_to_tview(char, view):
|
||||
|
||||
def get_iptv_url(row, profile):
|
||||
""" Returns url from iptv type row """
|
||||
data = row[7].split(":" if profile is Profile.ENIGMA_2 else "::")
|
||||
data = row[Column.FAV_ID].split(":" if profile is Profile.ENIGMA_2 else "::")
|
||||
if profile is Profile.ENIGMA_2:
|
||||
data = list(filter(lambda x: "http" in x, data))
|
||||
if data:
|
||||
url = data[0]
|
||||
return url.replace("%3a", ":") if profile is Profile.ENIGMA_2 else url
|
||||
return urllib.request.unquote(url) if profile is Profile.ENIGMA_2 else url
|
||||
|
||||
|
||||
def on_popup_menu(menu, event):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2019 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,28 +31,8 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-copyright 2018 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="image1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-goto-bottom</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="image2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">network-server-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="image3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-goto-top</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="image4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-execute</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="providers_list_store">
|
||||
<columns>
|
||||
<!-- column-name logo -->
|
||||
@@ -119,157 +99,76 @@ Author: Dmitriy Yefremov
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="icon_name">emblem-photos</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<signal name="destroy" handler="on_close" swapped="no"/>
|
||||
<signal name="delete-event" handler="on_close" swapped="no"/>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Picons download tool</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child type="title">
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="cancel_button">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Cancel</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_cancel" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<object class="GtkImage" id="cancel_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="label" translatable="yes">Picons download tool</property>
|
||||
<property name="xalign">0.52999997138977051</property>
|
||||
<property name="stock">gtk-cancel</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="z" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="header_download_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="load_providers_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Load providers</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_load_providers" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="load_providers_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">network-server-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<object class="GtkButton" id="receive_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Receive picons</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_receive" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<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="tooltip_text" translatable="yes">Cancel</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_cancel" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAlignment">
|
||||
<object class="GtkImage" id="receive_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<property name="stock">gtk-goto-bottom</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="receive_button">
|
||||
<property name="label" translatable="yes">Receive picons</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Receive picons for providers</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="image">image1</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_receive" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="convert_button">
|
||||
<property name="label" translatable="yes">Convert</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="image">image4</property>
|
||||
<property name="always_show_image">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="load_providers_button">
|
||||
<property name="label" translatable="yes">Load providers</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Load satellite providers.</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="image">image2</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_load_providers" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="send_button">
|
||||
<property name="label" translatable="yes">Send</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Transfer to receiver</property>
|
||||
<property name="image">image3</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_send" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAlignment">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -278,7 +177,64 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<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">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="send_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Transfer to receiver</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_send" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="send_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-goto-top</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="convert_button">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Convert</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_convert" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="convert_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-execute</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
@@ -286,15 +242,11 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkBox" id="picons_dialog_vbox">
|
||||
<property name="width_request">640</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">2</property>
|
||||
<property name="margin_right">2</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox" id="dialog_action_area">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">2</property>
|
||||
<property name="margin_right">2</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="layout_style">spread</property>
|
||||
</object>
|
||||
@@ -304,17 +256,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="separator1">
|
||||
<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">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkNotebook" id="notebook">
|
||||
<property name="visible">True</property>
|
||||
@@ -326,8 +267,8 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
@@ -404,10 +345,47 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel">
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Satellite</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="satellite_label">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Satellite</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="loading_data_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">2</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>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="loading_data_spinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_right">2</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -658,7 +636,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="secondary_icon_name">folder-open-symbolic</property>
|
||||
<property name="secondary_icon_name">folder-open</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<signal name="icon-press" handler="on_picons_dir_open" swapped="no"/>
|
||||
</object>
|
||||
@@ -988,7 +966,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">8</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -1025,7 +1003,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">13</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -1079,7 +1057,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">14</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -3,7 +3,6 @@ import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from gi.repository import GLib, GdkPixbuf
|
||||
|
||||
@@ -23,8 +22,8 @@ class PiconsDialog:
|
||||
self._sat_positions = sat_positions
|
||||
self._TMP_DIR = tempfile.gettempdir() + "/"
|
||||
self._BASE_URL = "www.lyngsat.com/packages/"
|
||||
self._PATTERN = re.compile("^https://www\.lyngsat\.com/[\w-]+\.html$")
|
||||
self._POS_PATTERN = re.compile("^\d+\.\d+[EW]?$")
|
||||
self._PATTERN = re.compile(r"^https://www\.lyngsat\.com/[\w-]+\.html$")
|
||||
self._POS_PATTERN = re.compile(r"^\d+\.\d+[EW]?$")
|
||||
self._current_process = None
|
||||
self._terminate = False
|
||||
|
||||
@@ -71,11 +70,16 @@ class PiconsDialog:
|
||||
self._enigma2_path_button = builder.get_object("enigma2_path_button")
|
||||
self._save_to_button = builder.get_object("save_to_button")
|
||||
self._send_button = builder.get_object("send_button")
|
||||
self._cancel_button = builder.get_object("cancel_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")
|
||||
self._resize_220_132_radio_button = builder.get_object("resize_220_132_radio_button")
|
||||
self._resize_100_60_radio_button = builder.get_object("resize_100_60_radio_button")
|
||||
self._satellite_label = builder.get_object("satellite_label")
|
||||
self._satellite_label.bind_property("visible", builder.get_object("loading_data_label"), "visible", 4)
|
||||
self._satellite_label.bind_property("visible", builder.get_object("loading_data_spinner"), "visible", 4)
|
||||
self._cancel_button.bind_property("visible", builder.get_object("header_download_box"), "visible", 4)
|
||||
# style
|
||||
self._style_provider = Gtk.CssProvider()
|
||||
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
@@ -88,16 +92,17 @@ class PiconsDialog:
|
||||
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)
|
||||
self._satellite_label.show()
|
||||
|
||||
def show(self):
|
||||
self._dialog.run()
|
||||
self._dialog.destroy()
|
||||
|
||||
def on_satellites_view_realize(self, view):
|
||||
self.get_satellites(view)
|
||||
@@ -105,18 +110,22 @@ class PiconsDialog:
|
||||
@run_task
|
||||
def get_satellites(self, view):
|
||||
sats = SatellitesParser().get_satellites_list(SatelliteSource.LYNGSAT)
|
||||
if not sats:
|
||||
self.show_info_message("Getting satellites list error!", Gtk.MessageType.ERROR)
|
||||
gen = self.append_satellites(view.get_model(), sats)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def append_satellites(self, model, sats):
|
||||
for sat in sats:
|
||||
pos = sat[1]
|
||||
name = "{} ({})".format(sat[0], pos)
|
||||
pos = "{}{}".format("-" if pos[-1] == "W" else "", pos[:-1])
|
||||
if not self._terminate and model:
|
||||
if pos in self._sat_positions:
|
||||
model.append((name, sat[3], pos))
|
||||
yield True
|
||||
try:
|
||||
for sat in sats:
|
||||
pos = sat[1]
|
||||
name, pos = "{} ({})".format(sat[0], pos), "{}{}".format("-" if pos[-1] == "W" else "", pos[:-1])
|
||||
|
||||
if not self._terminate and model:
|
||||
if pos in self._sat_positions:
|
||||
yield model.append((name, sat[3], pos))
|
||||
finally:
|
||||
self._satellite_label.show()
|
||||
|
||||
def on_satellite_selection(self, view, path, column):
|
||||
model = view.get_model()
|
||||
@@ -125,7 +134,10 @@ class PiconsDialog:
|
||||
@run_idle
|
||||
def on_load_providers(self, item):
|
||||
self._expander.set_expanded(True)
|
||||
self.on_info_bar_close()
|
||||
self._cancel_button.show()
|
||||
url = self._url_entry.get_text()
|
||||
|
||||
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
@@ -133,24 +145,33 @@ class PiconsDialog:
|
||||
GLib.io_add_watch(self._current_process.stderr, GLib.IO_IN, self.write_to_buffer)
|
||||
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:
|
||||
model.append((self.get_pixbuf(p[0]) if p[0] else TV_ICON, *p[1:]))
|
||||
self.update_receive_button_state()
|
||||
try:
|
||||
self._terminate = False
|
||||
providers = parse_providers(self._TMP_DIR + url[url.find("w"):])
|
||||
except FileNotFoundError:
|
||||
pass # NOP
|
||||
else:
|
||||
if providers:
|
||||
for p in providers:
|
||||
if self._terminate:
|
||||
return
|
||||
model.append((self.get_pixbuf(p[0]) if p[0] else TV_ICON, *p[1:]))
|
||||
self.update_receive_button_state()
|
||||
finally:
|
||||
GLib.idle_add(self._cancel_button.hide)
|
||||
self._terminate = False
|
||||
|
||||
def get_pixbuf(self, img_url):
|
||||
return GdkPixbuf.Pixbuf.new_from_file_at_scale(filename=self._TMP_DIR + "www.lyngsat.com/" + img_url,
|
||||
width=48, height=48, preserve_aspect_ratio=True)
|
||||
|
||||
@run_idle
|
||||
def on_receive(self, item):
|
||||
self._cancel_button.show()
|
||||
self.start_download()
|
||||
|
||||
@run_task
|
||||
@@ -170,13 +191,19 @@ class PiconsDialog:
|
||||
scroll_to(prv.path, self._providers_tree_view)
|
||||
return
|
||||
|
||||
for prv in providers:
|
||||
if self._terminate:
|
||||
break
|
||||
self.process_provider(Provider(*prv))
|
||||
self.resize(self._picons_path)
|
||||
if not self._terminate:
|
||||
try:
|
||||
for prv in providers:
|
||||
if self._terminate:
|
||||
return
|
||||
self.process_provider(Provider(*prv))
|
||||
|
||||
if self._resize_no_radio_button.get_active():
|
||||
self.resize(self._picons_path)
|
||||
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
finally:
|
||||
GLib.idle_add(self._cancel_button.hide)
|
||||
self._terminate = False
|
||||
|
||||
def process_provider(self, prv):
|
||||
url = prv.url
|
||||
@@ -195,17 +222,13 @@ class PiconsDialog:
|
||||
char = fd.read(1)
|
||||
self.append_output(char)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
@run_idle
|
||||
def append_output(self, char):
|
||||
append_text_to_tview(char, self._text_view)
|
||||
|
||||
def resize(self, path):
|
||||
if self._resize_no_radio_button.get_active():
|
||||
return
|
||||
|
||||
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()
|
||||
@@ -214,18 +237,30 @@ class PiconsDialog:
|
||||
self._current_process.wait()
|
||||
except FileNotFoundError as e:
|
||||
self.show_info_message("Conversion error. " + str(e), Gtk.MessageType.ERROR)
|
||||
self.on_cancel()
|
||||
|
||||
def on_cancel(self, item=None):
|
||||
if self.is_task_running() and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return True
|
||||
|
||||
self.terminate_task()
|
||||
|
||||
@run_task
|
||||
def on_cancel(self, item=None):
|
||||
if self._current_process:
|
||||
self._terminate = True
|
||||
self._current_process.terminate()
|
||||
time.sleep(1)
|
||||
def terminate_task(self):
|
||||
self._terminate = True
|
||||
|
||||
@run_idle
|
||||
def on_close(self, item):
|
||||
self.on_cancel(item)
|
||||
if self._current_process:
|
||||
self._current_process.terminate()
|
||||
self.show_info_message(get_message("The task is canceled!"), Gtk.MessageType.WARNING)
|
||||
|
||||
def on_close(self, window, event):
|
||||
if self.on_cancel():
|
||||
return True
|
||||
|
||||
self.clean_data()
|
||||
GLib.idle_add(self._dialog.destroy)
|
||||
|
||||
@run_task
|
||||
def clean_data(self):
|
||||
path = self._TMP_DIR + "www.lyngsat.com"
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
@@ -239,15 +274,17 @@ class PiconsDialog:
|
||||
|
||||
@run_task
|
||||
def upload_picons(self):
|
||||
if self._current_process is not None and self._current_process.poll() is None:
|
||||
if self.is_task_running():
|
||||
self.show_dialog("The task is already running!", DialogType.ERROR)
|
||||
return
|
||||
|
||||
try:
|
||||
GLib.idle_add(self._expander.set_expanded, True)
|
||||
upload_data(properties=self._properties,
|
||||
download_type=DownloadType.PICONS,
|
||||
profile=self._profile,
|
||||
callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
|
||||
callback=self.append_output,
|
||||
done_callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
|
||||
except OSError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
|
||||
@@ -276,7 +313,8 @@ class PiconsDialog:
|
||||
self.update_selection(view, False)
|
||||
|
||||
def update_selection(self, view, select):
|
||||
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 7, select))
|
||||
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 7, select))
|
||||
self.update_receive_button_state()
|
||||
|
||||
def on_url_changed(self, entry):
|
||||
suit = self._PATTERN.search(entry.get_text())
|
||||
@@ -317,7 +355,10 @@ class PiconsDialog:
|
||||
|
||||
@run_idle
|
||||
def update_receive_button_state(self):
|
||||
self._receive_button.set_sensitive(len(self.get_selected_providers()) > 0)
|
||||
try:
|
||||
self._receive_button.set_sensitive(len(self.get_selected_providers()) > 0)
|
||||
except TypeError:
|
||||
pass # NOP
|
||||
|
||||
def get_selected_providers(self):
|
||||
""" returns selected providers """
|
||||
@@ -335,6 +376,9 @@ class PiconsDialog:
|
||||
|
||||
return picon_format
|
||||
|
||||
def is_task_running(self):
|
||||
return self._current_process and self._current_process.poll() is None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -1,10 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<!-- Generated with glade 3.22.1
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2019 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<!--
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2019 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,7 +57,7 @@ Author: Dmitriy Yefremov
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-copyright 2018 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkListStore" id="fec_store">
|
||||
<columns>
|
||||
@@ -76,7 +102,6 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
<object class="GtkPopoverMenu" id="left_header_menu">
|
||||
<property name="can_focus">False</property>
|
||||
<signal name="button-release-event" handler="on_popover_release" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkBox" id="left_popover_munu_box">
|
||||
<property name="visible">True</property>
|
||||
@@ -89,16 +114,52 @@ Author: Dmitriy Yefremov
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="open_left_popover_munu_button">
|
||||
<property name="label">gtk-open</property>
|
||||
<object class="GtkModelButton" id="open_popover_munu_button">
|
||||
<property name="width_request">85</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<property name="text" translatable="yes">Open</property>
|
||||
<signal name="clicked" handler="on_open" object="satellites_tree_store" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="save_popover_munu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes">Save</property>
|
||||
<signal name="clicked" handler="on_save" object="satellites_editor_tree_view" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="save_as_popover_munu_button">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes">Save as...</property>
|
||||
<signal name="clicked" handler="on_save_as" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
@@ -106,15 +167,12 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="save_left_popover_munu_button">
|
||||
<property name="label">gtk-save</property>
|
||||
<object class="GtkModelButton" id="update_popover_munu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_save" object="satellites_editor_tree_view" swapped="no"/>
|
||||
<property name="text" translatable="yes">Update</property>
|
||||
<signal name="clicked" handler="on_update" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -130,69 +188,22 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">2</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="save_as_left_popover_munu_button">
|
||||
<property name="label">gtk-save-as</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_save_as" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="update_left_popover_munu_button">
|
||||
<property name="label">gtk-refresh</property>
|
||||
<object class="GtkModelButton" id="exit_popover_menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_update" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<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">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="exit_left_popover_menu_button">
|
||||
<property name="label">gtk-quit</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<property name="text" translatable="yes">Exit</property>
|
||||
<signal name="clicked" handler="on_quit" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">9</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
@@ -414,6 +425,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkHeaderBar" id="satellites_editor_header">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Satellites edit tool</property>
|
||||
<property name="spacing">1</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
@@ -477,6 +489,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Update</property>
|
||||
<signal name="clicked" handler="on_update" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="update_header_button_img">
|
||||
@@ -494,13 +507,6 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="title">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Satellites edit tool</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
@@ -535,6 +541,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="satellite_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="title" translatable="yes">Satellite</property>
|
||||
<property name="expand">True</property>
|
||||
<child>
|
||||
@@ -548,6 +555,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="freq_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="title" translatable="yes">Freq</property>
|
||||
<property name="expand">True</property>
|
||||
<child>
|
||||
@@ -561,6 +569,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rate_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="title" translatable="yes">Rate</property>
|
||||
<property name="expand">True</property>
|
||||
<child>
|
||||
@@ -574,6 +583,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="pol_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="title" translatable="yes">Pol</property>
|
||||
<property name="expand">True</property>
|
||||
<child>
|
||||
@@ -587,6 +597,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fec_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="title" translatable="yes">FEC</property>
|
||||
<property name="expand">True</property>
|
||||
<child>
|
||||
@@ -600,6 +611,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="sys_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="title" translatable="yes">System</property>
|
||||
<property name="expand">True</property>
|
||||
<child>
|
||||
@@ -613,6 +625,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="mod_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="title" translatable="yes">Mod</property>
|
||||
<property name="expand">True</property>
|
||||
<child>
|
||||
@@ -711,7 +724,8 @@ Author: Dmitriy Yefremov
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkDialog" id="satellite_dialog">
|
||||
<property name="use-header-bar">1</property>
|
||||
<property name="use-header-bar">{use_header}</property>
|
||||
<property name="title" translatable="yes">Satellite</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
@@ -720,41 +734,29 @@ Author: Dmitriy Yefremov
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<property name="gravity">center</property>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="satellites_dialog_header">
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="sat_cancel_button">
|
||||
<property name="label">gtk-cancel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Satellite</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="sat_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>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="sat_ok_button">
|
||||
<property name="label">gtk-ok</property>
|
||||
<property name="width_request">90</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="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<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>
|
||||
</child>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="sat_ok_button">
|
||||
<property name="label">gtk-ok</property>
|
||||
<property name="width_request">90</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>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="dialog-vbox3">
|
||||
<object class="GtkBox" id="satelitte_dialog_vbox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
@@ -780,7 +782,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="label_xalign">0.019999999552965164</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkGrid" id="grid3">
|
||||
<object class="GtkGrid" id="satellite_dialog_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
@@ -829,6 +831,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkSpinButton" id="sat_position_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="width_chars">5</property>
|
||||
<property name="secondary_icon_activatable">False</property>
|
||||
<property name="secondary_icon_sensitive">False</property>
|
||||
<property name="input_purpose">number</property>
|
||||
@@ -901,7 +904,8 @@ Author: Dmitriy Yefremov
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkDialog" id="transponder_dialog">
|
||||
<property name="use-header-bar">1</property>
|
||||
<property name="use-header-bar">{use_header}</property>
|
||||
<property name="title" translatable="yes">Transponder</property>
|
||||
<property name="width_request">320</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="resizable">False</property>
|
||||
@@ -913,37 +917,25 @@ Author: Dmitriy Yefremov
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<property name="gravity">center</property>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="transponder_dialog_header">
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="tr_cancel_button">
|
||||
<property name="label">gtk-cancel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Transponder</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="tr_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>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="tr_ok_button">
|
||||
<property name="label">gtk-ok</property>
|
||||
<property name="width_request">90</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="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<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>
|
||||
</child>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="tr_ok_button">
|
||||
<property name="label">gtk-ok</property>
|
||||
<property name="width_request">90</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>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
|
||||
@@ -3,14 +3,19 @@ import time
|
||||
import concurrent.futures
|
||||
from math import fabs
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from app.commons import run_idle, run_task
|
||||
from app.eparser import get_satellites, write_satellites, Satellite, Transponder
|
||||
from app.eparser.ecommons import PLS_MODE, get_key_by_value
|
||||
from app.tools.satellites import SatellitesParser, SatelliteSource
|
||||
from .search import SearchProvider
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, MOVE_KEYS, KeyboardKey
|
||||
from .dialogs import show_dialog, DialogType, WaitDialog
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, MOVE_KEYS, KeyboardKey, IS_GNOME_SESSION
|
||||
from .dialogs import show_dialog, DialogType, get_dialogs_string
|
||||
from .main_helper import move_items, scroll_to, append_text_to_tview, get_base_model, on_popup_menu
|
||||
|
||||
_UI_PATH = UI_RESOURCES_PATH + "satellites_dialog.glade"
|
||||
|
||||
|
||||
def show_satellites_dialog(transient, options):
|
||||
SatellitesDialog(transient, options).show()
|
||||
@@ -35,22 +40,20 @@ class SatellitesDialog:
|
||||
"on_transponder_add": self.on_transponder_add,
|
||||
"on_edit": self.on_edit,
|
||||
"on_key_release": self.on_key_release,
|
||||
"on_popover_release": self.on_popover_release,
|
||||
"on_row_activated": self.on_row_activated,
|
||||
"on_resize": self.on_resize,
|
||||
"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_window", "satellites_tree_store", "popup_menu",
|
||||
"left_header_menu", "popup_menu_add_image", "popup_menu_add_image_2"))
|
||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH),
|
||||
("satellites_editor_window", "satellites_tree_store", "popup_menu",
|
||||
"left_header_menu", "popup_menu_add_image", "popup_menu_add_image_2"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._window = builder.get_object("satellites_editor_window")
|
||||
self._window.set_transient_for(transient)
|
||||
self._sat_view = builder.get_object("satellites_editor_tree_view")
|
||||
self._wait_dialog = WaitDialog(self._window)
|
||||
# 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:
|
||||
@@ -60,7 +63,12 @@ class SatellitesDialog:
|
||||
4: builder.get_object("fec_store"),
|
||||
5: builder.get_object("system_store"),
|
||||
6: builder.get_object("mod_store")}
|
||||
self.on_satellites_list_load(self._sat_view.get_model())
|
||||
|
||||
self.load_satellites_list(self._sat_view.get_model())
|
||||
|
||||
def load_satellites_list(self, model):
|
||||
gen = self.on_satellites_list_load(model)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def show(self):
|
||||
self._window.show()
|
||||
@@ -84,7 +92,7 @@ class SatellitesDialog:
|
||||
show_dialog(DialogType.ERROR, self._window, text="No satellites.xml file is selected!")
|
||||
return
|
||||
self._data_path = response
|
||||
self.on_satellites_list_load(model)
|
||||
self.load_satellites_list(model)
|
||||
|
||||
def get_file_dialog_response(self, action: Gtk.FileChooserAction):
|
||||
file_filter = Gtk.FileFilter()
|
||||
@@ -133,28 +141,20 @@ class SatellitesDialog:
|
||||
elif key is KeyboardKey.LEFT or key is KeyboardKey.RIGHT:
|
||||
view.do_unselect_all(view)
|
||||
|
||||
def on_popover_release(self, menu, event):
|
||||
menu.hide()
|
||||
|
||||
@run_idle
|
||||
def on_satellites_list_load(self, model):
|
||||
""" Load satellites data into model """
|
||||
try:
|
||||
self._wait_dialog.show()
|
||||
satellites = get_satellites(self._data_path)
|
||||
yield True
|
||||
except FileNotFoundError as e:
|
||||
show_dialog(DialogType.ERROR, self._window, getattr(e, "message", str(e)) +
|
||||
"\n\nPlease, download files from receiver or setup your path for read data!")
|
||||
return
|
||||
else:
|
||||
model.clear()
|
||||
self.append_data(model, satellites)
|
||||
finally:
|
||||
self._wait_dialog.hide()
|
||||
|
||||
@run_idle
|
||||
def append_data(self, model, satellites):
|
||||
for sat in satellites:
|
||||
append_satellite(model, sat)
|
||||
for sat in satellites:
|
||||
append_satellite(model, sat)
|
||||
yield True
|
||||
|
||||
def on_add(self, view):
|
||||
""" Common adding """
|
||||
@@ -319,9 +319,9 @@ class TransponderDialog:
|
||||
|
||||
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", "mod_store", "system_store",
|
||||
"pls_mode_store"))
|
||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
||||
("transponder_dialog", "pol_store", "fec_store", "mod_store", "system_store",
|
||||
"pls_mode_store"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._dialog = builder.get_object("transponder_dialog")
|
||||
@@ -364,7 +364,7 @@ class TransponderDialog:
|
||||
self._fec_box.set_active_id(transponder.fec_inner)
|
||||
self._sys_box.set_active_id(transponder.system)
|
||||
self._mod_box.set_active_id(transponder.modulation)
|
||||
self._pls_mode_box.set_active_id(transponder.pls_mode)
|
||||
self._pls_mode_box.set_active_id(PLS_MODE.get(transponder.pls_mode, None))
|
||||
self._is_id_entry.set_text(transponder.is_id if transponder.is_id else "")
|
||||
self._pls_code_entry.set_text(transponder.pls_code if transponder.pls_code else "")
|
||||
|
||||
@@ -375,7 +375,7 @@ class TransponderDialog:
|
||||
fec_inner=self._fec_box.get_active_id(),
|
||||
system=self._sys_box.get_active_id(),
|
||||
modulation=self._mod_box.get_active_id(),
|
||||
pls_mode=self._pls_mode_box.get_active_id(),
|
||||
pls_mode=get_key_by_value(PLS_MODE, self._pls_mode_box.get_active_id()),
|
||||
pls_code=self._pls_code_entry.get_text(),
|
||||
is_id=self._is_id_entry.get_text())
|
||||
|
||||
@@ -403,8 +403,8 @@ 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"))
|
||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
||||
("satellite_dialog", "side_store", "pos_adjustment"))
|
||||
|
||||
self._dialog = builder.get_object("satellite_dialog")
|
||||
self._dialog.set_transient_for(transient)
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
<row>
|
||||
<col id="0" translatable="yes">4/5</col>
|
||||
</row>
|
||||
<row>
|
||||
<col id="0" translatable="yes">6/7</col>
|
||||
</row>
|
||||
<row>
|
||||
<col id="0" translatable="yes">9/10</col>
|
||||
</row>
|
||||
@@ -214,8 +217,21 @@
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkComboBox" id="rate_lp_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="model">fec_list_store</property>
|
||||
<property name="id_column">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rate_lp_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkDialog" id="service_details_dialog">
|
||||
<property name="use-header-bar">1</property>
|
||||
<property name="use-header-bar">{use_header}</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Service data</property>
|
||||
<property name="resizable">False</property>
|
||||
@@ -271,6 +287,16 @@
|
||||
<property name="margin_right">2</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="service_data_frame">
|
||||
<property name="visible">True</property>
|
||||
@@ -913,7 +939,7 @@
|
||||
<property name="can_focus">False</property>
|
||||
<property name="column_spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label1">
|
||||
<object class="GtkLabel" id="position_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Position</property>
|
||||
@@ -924,7 +950,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label16">
|
||||
<object class="GtkLabel" id="freq_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Freq</property>
|
||||
@@ -950,7 +976,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label17">
|
||||
<object class="GtkLabel" id="rate_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Rate</property>
|
||||
@@ -976,9 +1002,11 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label18">
|
||||
<object class="GtkLabel" id="pol_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="label" translatable="yes">Pol</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -1006,9 +1034,11 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label19">
|
||||
<object class="GtkLabel" id="fec_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="label" translatable="yes">FEC</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -1052,7 +1082,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label30">
|
||||
<object class="GtkLabel" id="namespace_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Namespace</property>
|
||||
@@ -1078,7 +1108,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label4">
|
||||
<object class="GtkLabel" id="tid_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">TID</property>
|
||||
@@ -1105,7 +1135,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label8">
|
||||
<object class="GtkLabel" id="nid_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">NID</property>
|
||||
@@ -1149,7 +1179,7 @@
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="column_spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label25">
|
||||
<object class="GtkLabel" id="inversion_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Inversion</property>
|
||||
@@ -1160,7 +1190,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label23">
|
||||
<object class="GtkLabel" id="rolloff_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Rolloff</property>
|
||||
@@ -1172,7 +1202,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label24">
|
||||
<object class="GtkLabel" id="pilot_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Pilot</property>
|
||||
@@ -1240,7 +1270,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label10">
|
||||
<object class="GtkLabel" id="pls_mode_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">PLS mode</property>
|
||||
@@ -1270,7 +1300,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label29">
|
||||
<object class="GtkLabel" id="tr_flag_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Flag</property>
|
||||
@@ -1296,7 +1326,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label9">
|
||||
<object class="GtkLabel" id="pls_code_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">PLS code</property>
|
||||
@@ -1322,7 +1352,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label28">
|
||||
<object class="GtkLabel" id="stream_id_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Stream ID</property>
|
||||
@@ -1348,7 +1378,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label21">
|
||||
<object class="GtkLabel" id="system_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">System</property>
|
||||
@@ -1380,7 +1410,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label22">
|
||||
<object class="GtkLabel" id="mod_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Mod</property>
|
||||
@@ -1517,7 +1547,7 @@
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkDialog" id="tr_services_dialog">
|
||||
<property name="use-header-bar">1</property>
|
||||
<property name="use-header-bar">{use_header}</property>
|
||||
<property name="width_request">480</property>
|
||||
<property name="height_request">300</property>
|
||||
<property name="can_focus">False</property>
|
||||
@@ -1557,23 +1587,69 @@
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="srv_list_dialog_hdr_label">
|
||||
<object class="GtkInfoBar" id="srv_list_dialog_info_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">20</property>
|
||||
<property name="margin_right">20</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="label" translatable="yes">Changes will be applied to all services of this transponder!
|
||||
<property name="message_type">warning</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="srv_list_dialog_hdr_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">20</property>
|
||||
<property name="margin_right">20</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="label" translatable="yes">Changes will be applied to all services of this transponder!
|
||||
Continue?</property>
|
||||
<property name="justify">center</property>
|
||||
<property name="lines">2</property>
|
||||
<property name="justify">center</property>
|
||||
<property name="lines">2</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child internal-child="content_area">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">16</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -1582,9 +1658,6 @@ Continue?</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="margin_left">1</property>
|
||||
<property name="margin_right">1</property>
|
||||
<property name="margin_bottom">1</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="tr_services_list_treeview">
|
||||
<property name="visible">True</property>
|
||||
|
||||
@@ -4,12 +4,15 @@ 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
|
||||
get_key_by_value, get_value_by_name, FEC_DEFAULT, PLS_MODE, SERVICE_TYPE, T_MODULATION, C_MODULATION, TrType, \
|
||||
SystemCable, T_SYSTEM, BANDWIDTH, TRANSMISSION_MODE, GUARD_INTERVAL, HIERARCHY, T_FEC
|
||||
from app.properties import Profile
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, TEXT_DOMAIN, CODED_ICON
|
||||
from .dialogs import show_dialog, DialogType, Action
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, TEXT_DOMAIN, CODED_ICON, Column, IS_GNOME_SESSION
|
||||
from .dialogs import show_dialog, DialogType, Action, get_dialogs_string
|
||||
from .main_helper import get_base_model
|
||||
|
||||
_UI_PATH = UI_RESOURCES_PATH + "service_details_dialog.glade"
|
||||
|
||||
|
||||
class ServiceDetailsDialog:
|
||||
_ENIGMA2_DATA_ID = "{:04x}:{:08x}:{:04x}:{:04x}:{}:{}"
|
||||
@@ -31,7 +34,7 @@ class ServiceDetailsDialog:
|
||||
|
||||
_DIGIT_ENTRY_NAME = "digit-entry"
|
||||
|
||||
def __init__(self, transient, options, srv_view, fav_view, services, bouquets, action=Action.EDIT):
|
||||
def __init__(self, transient, options, srv_view, fav_view, services, bouquets, new_color, action=Action.EDIT):
|
||||
handlers = {"on_system_changed": self.on_system_changed,
|
||||
"on_save": self.on_save,
|
||||
"on_create_new": self.on_create_new,
|
||||
@@ -43,13 +46,14 @@ class ServiceDetailsDialog:
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_from_file(UI_RESOURCES_PATH + "service_details_dialog.glade")
|
||||
builder.add_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION))
|
||||
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._tr_type = None
|
||||
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 = srv_view
|
||||
@@ -58,13 +62,14 @@ class ServiceDetailsDialog:
|
||||
self._old_service = None
|
||||
self._services = services
|
||||
self._bouquets = bouquets
|
||||
self._new_color = new_color
|
||||
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})*")
|
||||
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")
|
||||
@@ -116,6 +121,7 @@ class ServiceDetailsDialog:
|
||||
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._rate_lp_combo_box = builder.get_object("rate_lp_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")
|
||||
@@ -130,7 +136,7 @@ class ServiceDetailsDialog:
|
||||
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)
|
||||
self._rate_entry, self._rate_lp_combo_box)
|
||||
|
||||
if self._action is Action.EDIT:
|
||||
self.update_data_elements()
|
||||
@@ -183,7 +189,7 @@ class ServiceDetailsDialog:
|
||||
if not itr:
|
||||
return
|
||||
|
||||
srv = Service(*self._current_model[itr][:])
|
||||
srv = Service(*self._current_model[itr][: Column.SRV_TOOLTIP])
|
||||
self._old_service = srv
|
||||
self._current_itr = itr
|
||||
# Service
|
||||
@@ -191,14 +197,17 @@ class ServiceDetailsDialog:
|
||||
self._package_entry.set_text(srv.package)
|
||||
self._sid_entry.set_text(str(int(srv.ssid, 16)))
|
||||
# Transponder
|
||||
tr_type = srv.transponder_type
|
||||
if self._profile is Profile.ENIGMA_2:
|
||||
self._tr_type = TrType(srv.transponder_type)
|
||||
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)
|
||||
if tr_type in "tc" and self._profile is Profile.ENIGMA_2:
|
||||
if self._tr_type is TrType.Terrestrial:
|
||||
self.update_ui_for_terrestrial()
|
||||
elif self._tr_type is TrType.Cable:
|
||||
self.update_ui_for_cable()
|
||||
else:
|
||||
self.set_sat_positions(srv.pos)
|
||||
|
||||
@@ -264,21 +273,42 @@ class ServiceDetailsDialog:
|
||||
""" 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]))
|
||||
tr_type = TrType(srv.transponder_type)
|
||||
|
||||
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)
|
||||
|
||||
if tr_type is TrType.Satellite:
|
||||
self.select_active_text(self._invertion_combo_box, Inversion(tr_data[5]).name)
|
||||
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]))
|
||||
elif tr_type is TrType.Cable:
|
||||
self.select_active_text(self._invertion_combo_box, Inversion(tr_data[2]).name)
|
||||
self.select_active_text(self._mod_combo_box, C_MODULATION.get(tr_data[3]))
|
||||
self.select_active_text(self._fec_combo_box, FEC_DEFAULT.get(tr_data[4]))
|
||||
self.select_active_text(self._sys_combo_box, SystemCable(tr_data[5]).name)
|
||||
elif tr_type is TrType.Terrestrial:
|
||||
self.select_active_text(self._fec_combo_box, T_FEC.get(tr_data[2]))
|
||||
self.select_active_text(self._rate_lp_combo_box, T_FEC.get(tr_data[3]))
|
||||
# Pol -> Bandwidth
|
||||
self.select_active_text(self._pol_combo_box, BANDWIDTH.get(tr_data[1]))
|
||||
self.select_active_text(self._mod_combo_box, T_MODULATION.get(tr_data[4]))
|
||||
# Transmission Mode -> Roll off
|
||||
self.select_active_text(self._rolloff_combo_box, TRANSMISSION_MODE.get(tr_data[5]))
|
||||
# GuardInterval -> Pilot
|
||||
self.select_active_text(self._pilot_combo_box, GUARD_INTERVAL.get(tr_data[6]))
|
||||
# Hierarchy -> Pls Mode
|
||||
self.select_active_text(self._pls_mode_combo_box, HIERARCHY.get(tr_data[7]))
|
||||
self.select_active_text(self._invertion_combo_box, Inversion(tr_data[8]).name)
|
||||
self.select_active_text(self._sys_combo_box, T_SYSTEM.get(tr_data[9]))
|
||||
# Should be called last to properly initialize the reference
|
||||
self._srv_type_entry.set_text(data[4])
|
||||
|
||||
@@ -295,7 +325,9 @@ class ServiceDetailsDialog:
|
||||
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)
|
||||
tr_grid = self._builder.get_object("tr_grid")
|
||||
tr_grid.remove_column(7)
|
||||
tr_grid.set_margin_bottom(5)
|
||||
self._builder.get_object("tr_extra_expander").set_visible(False)
|
||||
self._builder.get_object("srv_separator").set_visible(False)
|
||||
|
||||
@@ -346,17 +378,32 @@ class ServiceDetailsDialog:
|
||||
self.on_edit() if self._action is Action.EDIT else self.on_new()
|
||||
self._dialog.destroy()
|
||||
|
||||
def on_new(self):
|
||||
""" Create new service. """
|
||||
service = self.get_service(*self.get_srv_data(), self.get_satellite_transponder_data())
|
||||
show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
|
||||
|
||||
def on_edit(self):
|
||||
""" Edit current service. """
|
||||
fav_id, data_id = self.get_srv_data()
|
||||
# transponder
|
||||
transponder = self._old_service.transponder
|
||||
tr_type = self._old_service.transponder_type
|
||||
if self._tr_edit_switch.get_active():
|
||||
transponder = self.get_transponder_data(tr_type)
|
||||
if self._transponder_services_iters:
|
||||
self.update_transponder_services(transponder, tr_type)
|
||||
try:
|
||||
if self._tr_type is TrType.Satellite:
|
||||
transponder = self.get_satellite_transponder_data()
|
||||
elif self._tr_type is TrType.Terrestrial:
|
||||
transponder = self.get_terrestrial_transponder_data()
|
||||
elif self._tr_type is TrType.Cable:
|
||||
transponder = self.get_cable_transponder_data()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
show_dialog(DialogType.ERROR, transient=self._dialog, text="Error getting transponder parameters!")
|
||||
else:
|
||||
if self._transponder_services_iters:
|
||||
self.update_transponder_services(transponder)
|
||||
# service
|
||||
service = self.get_service(fav_id, data_id, transponder, tr_type)
|
||||
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)
|
||||
@@ -365,6 +412,14 @@ class ServiceDetailsDialog:
|
||||
if self._old_service.picon_id != service.picon_id:
|
||||
self.update_picon_name(self._old_service.picon_id, service.picon_id)
|
||||
|
||||
flags = service.flags_cas
|
||||
extra_data = {Column.SRV_TOOLTIP: None, Column.SRV_BACKGROUND: None}
|
||||
if flags:
|
||||
f_flags = list(filter(lambda x: x.startswith("f:"), flags.split(",")))
|
||||
if f_flags and Flag.is_new(int(f_flags[0][2:])):
|
||||
extra_data[Column.SRV_BACKGROUND] = self._new_color
|
||||
|
||||
self._current_model.set(self._current_itr, extra_data)
|
||||
self._current_model.set(self._current_itr, {i: v for i, v in enumerate(service)})
|
||||
self.update_fav_view(self._old_service, service)
|
||||
self._old_service = service
|
||||
@@ -403,14 +458,12 @@ class ServiceDetailsDialog:
|
||||
os.rename(old_file, new_file)
|
||||
break
|
||||
|
||||
def on_new(self):
|
||||
service = self.get_service(*self.get_srv_data(), self.get_transponder_data())
|
||||
show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
|
||||
# ***************** Service ********************* #
|
||||
|
||||
def get_service(self, fav_id, data_id, transponder, tr_type):
|
||||
freq, rate, pol, fec, system, pos = self.get_transponder_values(tr_type)
|
||||
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=tr_type,
|
||||
transponder_type=self._old_service.transponder_type,
|
||||
coded=CODED_ICON if self._cas_entry.get_text() else None,
|
||||
service=self._name_entry.get_text(),
|
||||
locked=self._old_service.locked,
|
||||
@@ -488,20 +541,27 @@ class ServiceDetailsDialog:
|
||||
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, tr_type):
|
||||
if tr_type == "s":
|
||||
# ***************** Transponder ********************* #
|
||||
|
||||
def get_transponder_values(self):
|
||||
freq = self._freq_entry.get_text()
|
||||
fec = self._fec_combo_box.get_active_id()
|
||||
system = self._sys_combo_box.get_active_id()
|
||||
|
||||
if self._tr_type is TrType.Satellite or self._profile is Profile.NEUTRINO_MP:
|
||||
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
|
||||
elif tr_type in "tc":
|
||||
elif self._tr_type is TrType.Terrestrial:
|
||||
o_srv = self._old_service
|
||||
return o_srv.freq, o_srv.rate, o_srv.pol, o_srv.fec, o_srv.system, o_srv.pos
|
||||
return freq, o_srv.rate, o_srv.pol, fec, system, o_srv.pos
|
||||
elif self._tr_type is TrType.Cable:
|
||||
o_srv = self._old_service
|
||||
return freq, self._rate_entry.get_text(), o_srv.pol, fec, o_srv.system, o_srv.pos
|
||||
|
||||
def get_transponder_data(self, tr_type):
|
||||
def get_satellite_transponder_data(self):
|
||||
sys = self._sys_combo_box.get_active_id()
|
||||
freq = self._freq_entry.get_text()
|
||||
rate = self._rate_entry.get_text()
|
||||
@@ -531,18 +591,47 @@ class ServiceDetailsDialog:
|
||||
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, tr_type):
|
||||
def get_terrestrial_transponder_data(self):
|
||||
tr_data = re.split("\s|:", self._old_service.transponder)
|
||||
# frequency, bandwidth, code rate HP, code rate LP, modulation, transmission mode, guard interval, hierarchy,
|
||||
# inversion, system, plp_id
|
||||
# Bandwidth -> Pol, Rate HP -> FEC, TransmissionMode -> Roll off, GuardInterval -> Pilot, Hierarchy -> Pls Mode
|
||||
tr_data[1] = self._freq_entry.get_text()
|
||||
tr_data[2] = self.get_value_from_combobox_id(self._pol_combo_box, BANDWIDTH)
|
||||
tr_data[3] = self.get_value_from_combobox_id(self._fec_combo_box, T_FEC)
|
||||
tr_data[4] = self.get_value_from_combobox_id(self._rate_lp_combo_box, T_FEC)
|
||||
tr_data[5] = self.get_value_from_combobox_id(self._mod_combo_box, T_MODULATION)
|
||||
tr_data[6] = self.get_value_from_combobox_id(self._rolloff_combo_box, TRANSMISSION_MODE)
|
||||
tr_data[7] = self.get_value_from_combobox_id(self._pilot_combo_box, GUARD_INTERVAL)
|
||||
tr_data[8] = self.get_value_from_combobox_id(self._pls_mode_combo_box, HIERARCHY)
|
||||
tr_data[9] = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
|
||||
tr_data[10] = self.get_value_from_combobox_id(self._sys_combo_box, T_SYSTEM)
|
||||
return "{} {}".format(tr_data[0], ":".join(tr_data[1:]))
|
||||
|
||||
def get_cable_transponder_data(self):
|
||||
tr_data = re.split("\s|:", self._old_service.transponder)
|
||||
# frequency, symbol_rate, modulation, inversion, fec_inner, system;
|
||||
tr_data[1] = self._freq_entry.get_text()
|
||||
tr_data[2] = self._rate_entry.get_text()
|
||||
tr_data[3] = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
|
||||
tr_data[4] = self.get_value_from_combobox_id(self._mod_combo_box, C_MODULATION)
|
||||
tr_data[5] = self.get_value_from_combobox_id(self._fec_combo_box, FEC_DEFAULT)
|
||||
tr_data[6] = get_value_by_name(SystemCable, self._sys_combo_box.get_active_id())
|
||||
return "{} {}".format(tr_data[0], ":".join(tr_data[1:]))
|
||||
|
||||
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(tr_type)
|
||||
srv[-1] = transponder
|
||||
srv = self._current_model[itr][:Column.SRV_TOOLTIP]
|
||||
srv[Column.SRV_FREQ], srv[Column.SRV_RATE], srv[Column.SRV_POL], srv[Column.SRV_FEC], srv[
|
||||
Column.SRV_SYSTEM], srv[Column.SRV_POS] = self.get_transponder_values()
|
||||
srv[Column.SRV_TRANSPONDER] = 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):
|
||||
def select_active_text(self, box, text):
|
||||
model = box.get_model()
|
||||
for index, row in enumerate(model):
|
||||
if row[0] == text:
|
||||
@@ -563,13 +652,8 @@ class ServiceDetailsDialog:
|
||||
return get_key_by_value(dc, cb_id)
|
||||
|
||||
@run_idle
|
||||
def on_tr_edit_toggled(self, switch: Gtk.Switch, active):
|
||||
def on_tr_edit_toggled(self, switch, active):
|
||||
if active and self._action is Action.EDIT:
|
||||
if self._old_service.transponder_type == "t":
|
||||
show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
|
||||
switch.set_active(False)
|
||||
return
|
||||
|
||||
self._transponder_services_iters = []
|
||||
response = TransponderServicesDialog(self._dialog,
|
||||
self._current_model,
|
||||
@@ -580,7 +664,8 @@ class ServiceDetailsDialog:
|
||||
self._transponder_services_iters = None
|
||||
return
|
||||
|
||||
self.update_dvb_s2_elements(active and self._sys_combo_box.get_active_id() == "DVB-S2")
|
||||
self.update_dvb_s2_elements(active and (self._sys_combo_box.get_active_id() == "DVB-S2"
|
||||
or self._old_service.transponder_type in "tc"))
|
||||
|
||||
for elem in self._TRANSPONDER_ELEMENTS:
|
||||
elem.set_sensitive(active)
|
||||
@@ -614,28 +699,136 @@ class ServiceDetailsDialog:
|
||||
self._reference_entry.set_text("{:x}{:04x}{:04x}".format(tid, nid, ssid))
|
||||
|
||||
def update_ui_for_terrestrial(self):
|
||||
tr_grid = self.get_transponder_grid_for_non_satellite()
|
||||
tr_grid.remove_column(1)
|
||||
tr_grid.insert_column(1)
|
||||
extra_tr_grid = self._builder.get_object("extra_transponder_grid")
|
||||
for i in range(4):
|
||||
extra_tr_grid.remove_column(6)
|
||||
# Bandwidth -> Pol
|
||||
pol_label = self._builder.get_object("pol_label")
|
||||
pol_label.set_text("Bandwidth")
|
||||
tr_grid.attach(pol_label, 1, 0, 1, 1)
|
||||
tr_grid.attach(self._pol_combo_box, 1, 1, 1, 1)
|
||||
# Rate HP -> FEC
|
||||
self._builder.get_object("fec_label").set_text("Rate HP")
|
||||
# Rate LP
|
||||
tr_grid.insert_column(3)
|
||||
rate_lp_label = self._builder.get_object("pls_code_label")
|
||||
rate_lp_label.set_text("Rate LP")
|
||||
tr_grid.attach(rate_lp_label, 3, 0, 1, 1)
|
||||
tr_grid.attach(self._rate_lp_combo_box, 3, 1, 1, 1)
|
||||
# Modulation
|
||||
tr_grid.insert_column(4)
|
||||
extra_tr_grid.remove_column(1)
|
||||
tr_grid.attach(self._builder.get_object("mod_label"), 4, 0, 1, 1)
|
||||
tr_grid.attach(self._mod_combo_box, 4, 1, 1, 1)
|
||||
# TransmissionMode -> Roll off
|
||||
rolloff_label = self._builder.get_object("rolloff_label")
|
||||
rolloff_label.set_text("T mode")
|
||||
# GuardInterval -> Pilot
|
||||
pilot_label = self._builder.get_object("pilot_label")
|
||||
pilot_label.set_text("Guard Interval")
|
||||
# Hierarchy -> Pls Mode
|
||||
pls_mode_label = self._builder.get_object("pls_mode_label")
|
||||
pls_mode_label.set_text("Hierarchy")
|
||||
# Models
|
||||
fec_model, modulation_model, sys_model = self.get_models_for_non_satellite()
|
||||
pol_model = self._pol_combo_box.get_model()
|
||||
roll_off_model = self._rolloff_combo_box.get_model()
|
||||
pilot_model = self._pilot_combo_box.get_model()
|
||||
pls_model = self._pls_mode_combo_box.get_model()
|
||||
# Models clearing
|
||||
for m in pol_model, roll_off_model, pilot_model, pls_model:
|
||||
m.clear()
|
||||
|
||||
self.init_terrestrial_models((pol_model, modulation_model, roll_off_model, pilot_model, pls_model, sys_model),
|
||||
(BANDWIDTH, T_MODULATION, TRANSMISSION_MODE, GUARD_INTERVAL, HIERARCHY, T_SYSTEM))
|
||||
|
||||
# Removing the latest FEC elements from the model
|
||||
for itr in [fec_model.get_iter(Gtk.TreePath.new_from_string(str(i))) for i in range(7, 11)]:
|
||||
fec_model.remove(itr)
|
||||
# Extra
|
||||
self._namespace_entry.set_max_width_chars(15)
|
||||
self._sys_combo_box.set_hexpand(False)
|
||||
|
||||
def init_terrestrial_models(self, models, properties):
|
||||
for index, model in enumerate(models):
|
||||
for v in properties[index].values():
|
||||
model.append((v,))
|
||||
|
||||
def update_ui_for_cable(self):
|
||||
tr_grid = self.get_transponder_grid_for_non_satellite()
|
||||
tr_box = self._builder.get_object("tr_box")
|
||||
# Models
|
||||
fec_model, modulation_model, system_model = self.get_models_for_non_satellite()
|
||||
|
||||
extra_tr_grid = self._builder.get_object("extra_transponder_grid")
|
||||
for child in extra_tr_grid.get_children():
|
||||
extra_tr_grid.remove(child)
|
||||
tr_grid.remove(extra_tr_grid)
|
||||
|
||||
tr_grid.insert_column(3)
|
||||
tr_grid.insert_column(4)
|
||||
tr_grid.insert_column(5)
|
||||
# Modulation
|
||||
tr_grid.attach(self._builder.get_object("mod_label"), 3, 0, 1, 1)
|
||||
tr_grid.attach(self._mod_combo_box, 3, 1, 1, 1)
|
||||
for v in C_MODULATION.values():
|
||||
modulation_model.append((v,))
|
||||
# Inversion
|
||||
tr_grid.attach(self._builder.get_object("inversion_label"), 4, 0, 1, 1)
|
||||
tr_grid.attach(self._invertion_combo_box, 4, 1, 1, 1)
|
||||
# System
|
||||
tr_grid.attach(self._builder.get_object("system_label"), 5, 0, 1, 1)
|
||||
tr_grid.attach(self._sys_combo_box, 5, 1, 1, 1)
|
||||
system_model.append((SystemCable.ANNEX_A.name,))
|
||||
system_model.append((SystemCable.ANNEX_C.name,))
|
||||
# FEC
|
||||
fec_model.append(("None",))
|
||||
# Extra
|
||||
tr_box.remove(self._tr_extra_expander)
|
||||
tr_grid.set_margin_bottom(5)
|
||||
self._freq_entry.set_width_chars(10)
|
||||
self._freq_entry.set_max_width_chars(10)
|
||||
self._rate_entry.set_width_chars(10)
|
||||
self._rate_entry.set_max_width_chars(10)
|
||||
self._transponder_id_entry.set_max_width_chars(8)
|
||||
self._network_id_entry.set_max_width_chars(8)
|
||||
|
||||
def get_transponder_grid_for_non_satellite(self):
|
||||
self._pids_grid.set_visible(False)
|
||||
tr_frame = self._builder.get_object("transponder_data_frame")
|
||||
tr_frame.set_visible(False)
|
||||
self._builder.get_object("srv_separator").set_visible(False)
|
||||
self._reference_entry.set_max_width_chars(22)
|
||||
tr_grid = self._builder.get_object("tr_grid")
|
||||
tr_grid.remove_column(0)
|
||||
tr_grid.remove_column(2)
|
||||
return tr_grid
|
||||
|
||||
def get_models_for_non_satellite(self):
|
||||
fec_model = self._fec_combo_box.get_model()
|
||||
modulation_model = self._mod_combo_box.get_model()
|
||||
modulation_model.clear()
|
||||
system_model = self._sys_combo_box.get_model()
|
||||
system_model.clear()
|
||||
return fec_model, modulation_model, system_model
|
||||
|
||||
|
||||
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"))
|
||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
||||
("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)
|
||||
builder.get_object("srv_list_dialog_info_bar").connect("response", lambda bar, resp: bar.hide())
|
||||
|
||||
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]))
|
||||
if row[Column.SRV_TRANSPONDER] == transponder:
|
||||
self._srv_model.append((row[Column.SRV_SERVICE], row[Column.SRV_PACKAGE], row[Column.SRV_TYPE],
|
||||
row[Column.SRV_SSID], row[Column.SRV_FREQ], row[Column.SRV_POS]))
|
||||
tr_iters.append(model.get_iter(row.path))
|
||||
|
||||
def show(self):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ from enum import Enum
|
||||
from app.commons import run_task, run_idle
|
||||
from app.connections import test_telnet, test_ftp, TestException, test_http
|
||||
from app.properties import write_config, Profile, get_default_settings
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, NEW_COLOR, EXTRA_COLOR, FavClickMode
|
||||
from .main_helper import update_entry_data
|
||||
|
||||
|
||||
@@ -20,13 +20,14 @@ class Property(Enum):
|
||||
class SettingsDialog:
|
||||
|
||||
def __init__(self, transient, options):
|
||||
handlers = {"on_data_dir_field_icon_press": self.on_data_dir_field_icon_press,
|
||||
"on_picons_dir_field_icon_press": self.on_picons_dir_field_icon_press,
|
||||
handlers = {"on_field_icon_press": self.on_field_icon_press,
|
||||
"on_profile_changed": self.on_profile_changed,
|
||||
"on_reset": self.on_reset,
|
||||
"apply_settings": self.apply_settings,
|
||||
"on_connection_test": self.on_connection_test,
|
||||
"on_info_bar_close": self.on_info_bar_close}
|
||||
"on_info_bar_close": self.on_info_bar_close,
|
||||
"on_set_color_switch_state": self.on_set_color_switch_state,
|
||||
"on_http_mode_switch_state": self.on_http_mode_switch_state}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
@@ -35,6 +36,8 @@ class SettingsDialog:
|
||||
|
||||
self._dialog = builder.get_object("settings_dialog")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._header_bar = builder.get_object("header_bar")
|
||||
# Network
|
||||
self._host_field = builder.get_object("host_field")
|
||||
self._port_field = builder.get_object("port_field")
|
||||
self._login_field = builder.get_object("login_field")
|
||||
@@ -46,28 +49,52 @@ class SettingsDialog:
|
||||
self._telnet_password_field = builder.get_object("telnet_password_field")
|
||||
self._telnet_port_field = builder.get_object("telnet_port_field")
|
||||
self._telnet_timeout_spin_button = builder.get_object("telnet_timeout_spin_button")
|
||||
self._settings_stack = builder.get_object("settings_stack")
|
||||
# Paths
|
||||
self._services_field = builder.get_object("services_field")
|
||||
self._user_bouquet_field = builder.get_object("user_bouquet_field")
|
||||
self._satellites_xml_field = builder.get_object("satellites_xml_field")
|
||||
self._data_dir_field = builder.get_object("data_dir_field")
|
||||
self._picons_field = builder.get_object("picons_field")
|
||||
self._picons_dir_field = builder.get_object("picons_dir_field")
|
||||
self._backup_dir_field = builder.get_object("backup_dir_field")
|
||||
# Info bar
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._message_label = builder.get_object("info_bar_message_label")
|
||||
self._test_spinner = builder.get_object("test_spinner")
|
||||
# Profile
|
||||
self._enigma_radio_button = builder.get_object("enigma_radio_button")
|
||||
self._neutrino_radio_button = builder.get_object("neutrino_radio_button")
|
||||
self._support_ver5_check_button = builder.get_object("support_ver5_check_button")
|
||||
self._support_http_api_check_button = builder.get_object("support_http_api_check_button")
|
||||
self._settings_stack = builder.get_object("settings_stack")
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._message_label = builder.get_object("info_bar_message_label")
|
||||
self._test_spinner = builder.get_object("test_spinner")
|
||||
# Program
|
||||
self._before_save_switch = builder.get_object("before_save_switch")
|
||||
self._before_downloading_switch = builder.get_object("before_downloading_switch")
|
||||
self._program_frame = builder.get_object("program_frame")
|
||||
self._extra_support_grid = builder.get_object("extra_support_grid")
|
||||
self._colors_grid = builder.get_object("colors_grid")
|
||||
self._set_color_switch = builder.get_object("set_color_switch")
|
||||
self._new_color_button = builder.get_object("new_color_button")
|
||||
self._extra_color_button = builder.get_object("extra_color_button")
|
||||
self._click_mode_disabled_button = builder.get_object("click_mode_disabled_button")
|
||||
self._click_mode_stream_button = builder.get_object("click_mode_stream_button")
|
||||
self._click_mode_play_button = builder.get_object("click_mode_play_button")
|
||||
self._click_mode_zap_button = builder.get_object("click_mode_zap_button")
|
||||
# Options
|
||||
self._options = options
|
||||
self._active_profile = options.get("profile")
|
||||
self.set_settings()
|
||||
profile = Profile(self._active_profile)
|
||||
self.init_ui_elements(Profile(self._active_profile))
|
||||
|
||||
def init_ui_elements(self, profile):
|
||||
is_enigma_profile = profile is Profile.ENIGMA_2
|
||||
self._neutrino_radio_button.set_active(profile is Profile.NEUTRINO_MP)
|
||||
self._support_ver5_check_button.set_sensitive(profile is not Profile.NEUTRINO_MP)
|
||||
self._support_http_api_check_button.set_sensitive(profile is not Profile.NEUTRINO_MP)
|
||||
self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(profile is not Profile.NEUTRINO_MP)
|
||||
self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(is_enigma_profile)
|
||||
self._program_frame.set_sensitive(is_enigma_profile)
|
||||
self._extra_support_grid.set_sensitive(is_enigma_profile)
|
||||
http_active = self._support_http_api_check_button.get_active()
|
||||
self._click_mode_zap_button.set_sensitive(is_enigma_profile and http_active)
|
||||
self._click_mode_play_button.set_sensitive(is_enigma_profile and http_active)
|
||||
|
||||
def show(self):
|
||||
response = self._dialog.run()
|
||||
@@ -77,18 +104,14 @@ class SettingsDialog:
|
||||
|
||||
return response
|
||||
|
||||
def on_data_dir_field_icon_press(self, entry, icon, event_button):
|
||||
update_entry_data(entry, self._dialog, self._options.get(self._options.get("profile")))
|
||||
|
||||
def on_picons_dir_field_icon_press(self, entry, icon, event_button):
|
||||
def on_field_icon_press(self, entry, icon, event_button):
|
||||
update_entry_data(entry, self._dialog, self._options.get(self._options.get("profile")))
|
||||
|
||||
def on_profile_changed(self, item):
|
||||
profile = Profile.ENIGMA_2 if self._enigma_radio_button.get_active() else Profile.NEUTRINO_MP
|
||||
self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(profile is not Profile.NEUTRINO_MP)
|
||||
self.set_profile(profile)
|
||||
self._support_ver5_check_button.set_sensitive(profile is Profile.ENIGMA_2)
|
||||
self._support_http_api_check_button.set_sensitive(profile is Profile.ENIGMA_2)
|
||||
self._active_profile = profile.value
|
||||
self.set_settings()
|
||||
self.init_ui_elements(profile)
|
||||
|
||||
def set_profile(self, profile):
|
||||
self._active_profile = profile.value
|
||||
@@ -106,27 +129,42 @@ class SettingsDialog:
|
||||
self.set_settings()
|
||||
|
||||
def set_settings(self):
|
||||
def_settings = get_default_settings().get(self._active_profile)
|
||||
options = self._options.get(self._active_profile)
|
||||
self._host_field.set_text(options.get("host", ""))
|
||||
self._port_field.set_text(options.get("port", ""))
|
||||
self._login_field.set_text(options.get("user", ""))
|
||||
self._password_field.set_text(options.get("password", ""))
|
||||
self._http_login_field.set_text(options.get("http_user", ""))
|
||||
self._http_password_field.set_text(options.get("http_password", ""))
|
||||
self._http_port_field.set_text(options.get("http_port", "80"))
|
||||
self._telnet_login_field.set_text(options.get("telnet_user", ""))
|
||||
self._telnet_password_field.set_text(options.get("telnet_password", ""))
|
||||
self._telnet_port_field.set_text(options.get("telnet_port", ""))
|
||||
self._telnet_timeout_spin_button.set_value(options.get("telnet_timeout", 5))
|
||||
self._services_field.set_text(options.get("services_path", ""))
|
||||
self._user_bouquet_field.set_text(options.get("user_bouquet_path", ""))
|
||||
self._satellites_xml_field.set_text(options.get("satellites_xml_path", ""))
|
||||
self._picons_field.set_text(options.get("picons_path", ""))
|
||||
self._data_dir_field.set_text(options.get("data_dir_path", ""))
|
||||
self._picons_dir_field.set_text(options.get("picons_dir_path", ""))
|
||||
|
||||
self._host_field.set_text(options.get("host", def_settings["host"]))
|
||||
self._port_field.set_text(options.get("port", def_settings["port"]))
|
||||
self._login_field.set_text(options.get("user", def_settings["user"]))
|
||||
self._password_field.set_text(options.get("password", def_settings["password"]))
|
||||
self._http_login_field.set_text(options.get("http_user", def_settings["http_user"]))
|
||||
self._http_password_field.set_text(options.get("http_password", def_settings["http_password"]))
|
||||
self._http_port_field.set_text(options.get("http_port", def_settings["http_port"]))
|
||||
self._telnet_login_field.set_text(options.get("telnet_user", def_settings["telnet_user"]))
|
||||
self._telnet_password_field.set_text(options.get("telnet_password", def_settings["telnet_password"]))
|
||||
self._telnet_port_field.set_text(options.get("telnet_port", def_settings["telnet_port"]))
|
||||
self._telnet_timeout_spin_button.set_value(options.get("telnet_timeout", def_settings["telnet_timeout"]))
|
||||
self._services_field.set_text(options.get("services_path", def_settings["services_path"]))
|
||||
self._user_bouquet_field.set_text(options.get("user_bouquet_path", def_settings["user_bouquet_path"]))
|
||||
self._satellites_xml_field.set_text(options.get("satellites_xml_path", def_settings["satellites_xml_path"]))
|
||||
self._picons_field.set_text(options.get("picons_path", def_settings["picons_path"]))
|
||||
self._data_dir_field.set_text(options.get("data_dir_path", def_settings["data_dir_path"]))
|
||||
self._picons_dir_field.set_text(options.get("picons_dir_path", def_settings["picons_dir_path"]))
|
||||
self._backup_dir_field.set_text(options.get("backup_dir_path", def_settings["backup_dir_path"]))
|
||||
self._before_save_switch.set_active(options.get("backup_before_save", def_settings["backup_before_save"]))
|
||||
self._before_downloading_switch.set_active(options.get("backup_before_downloading",
|
||||
def_settings["backup_before_downloading"]))
|
||||
self.set_fav_click_mode(options.get("fav_click_mode", def_settings["fav_click_mode"]))
|
||||
|
||||
if Profile(self._active_profile) is Profile.ENIGMA_2:
|
||||
self._support_ver5_check_button.set_active(options.get("v5_support", False))
|
||||
self._support_http_api_check_button.set_active(options.get("http_api_support", False))
|
||||
self._set_color_switch.set_active(options.get("use_colors", False))
|
||||
new_rgb = Gdk.RGBA()
|
||||
new_rgb.parse(options.get("new_color", NEW_COLOR))
|
||||
extra_rgb = Gdk.RGBA()
|
||||
extra_rgb.parse(options.get("extra_color", EXTRA_COLOR))
|
||||
self._new_color_button.set_rgba(new_rgb)
|
||||
self._extra_color_button.set_rgba(extra_rgb)
|
||||
|
||||
def apply_settings(self, item=None):
|
||||
profile = Profile.ENIGMA_2 if self._enigma_radio_button.get_active() else Profile.NEUTRINO_MP
|
||||
@@ -150,9 +188,17 @@ class SettingsDialog:
|
||||
options["picons_path"] = self._picons_field.get_text()
|
||||
options["data_dir_path"] = self._data_dir_field.get_text()
|
||||
options["picons_dir_path"] = self._picons_dir_field.get_text()
|
||||
options["backup_dir_path"] = self._backup_dir_field.get_text()
|
||||
options["backup_before_save"] = self._before_save_switch.get_active()
|
||||
options["backup_before_downloading"] = self._before_downloading_switch.get_active()
|
||||
options["fav_click_mode"] = self.get_fav_click_mode()
|
||||
|
||||
if profile is Profile.ENIGMA_2:
|
||||
options["v5_support"] = self._support_ver5_check_button.get_active()
|
||||
options["http_api_support"] = self._support_http_api_check_button.get_active()
|
||||
options["use_colors"] = self._set_color_switch.get_active()
|
||||
options["new_color"] = self._new_color_button.get_rgba().to_string()
|
||||
options["extra_color"] = self._extra_color_button.get_rgba().to_string()
|
||||
|
||||
write_config(self._options)
|
||||
|
||||
@@ -214,6 +260,33 @@ class SettingsDialog:
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
|
||||
def on_set_color_switch_state(self, switch, state):
|
||||
self._colors_grid.set_sensitive(state)
|
||||
|
||||
def on_http_mode_switch_state(self, switch, state):
|
||||
self._click_mode_play_button.set_sensitive(state)
|
||||
self._click_mode_zap_button.set_sensitive(state)
|
||||
if self._click_mode_play_button.get_active() or self._click_mode_zap_button.get_active():
|
||||
self._click_mode_disabled_button.set_active(True)
|
||||
|
||||
@run_idle
|
||||
def set_fav_click_mode(self, mode):
|
||||
mode = FavClickMode(mode)
|
||||
self._click_mode_disabled_button.set_active(mode is FavClickMode.DISABLED)
|
||||
self._click_mode_stream_button.set_active(mode is FavClickMode.STREAM)
|
||||
self._click_mode_play_button.set_active(mode is FavClickMode.PLAY)
|
||||
self._click_mode_zap_button.set_active(mode is FavClickMode.ZAP)
|
||||
|
||||
def get_fav_click_mode(self):
|
||||
if self._click_mode_zap_button.get_active():
|
||||
return FavClickMode.ZAP
|
||||
if self._click_mode_play_button.get_active():
|
||||
return FavClickMode.PLAY
|
||||
if self._click_mode_stream_button.get_active():
|
||||
return FavClickMode.STREAM
|
||||
|
||||
return FavClickMode.DISABLED
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -2,7 +2,7 @@ import locale
|
||||
import os
|
||||
|
||||
import gi
|
||||
from enum import Enum
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk, Gdk
|
||||
@@ -10,6 +10,8 @@ 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/"
|
||||
|
||||
IS_GNOME_SESSION = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
|
||||
|
||||
# translation
|
||||
TEXT_DOMAIN = "demon-editor"
|
||||
if UI_RESOURCES_PATH == "app/ui/":
|
||||
@@ -24,21 +26,31 @@ LOCKED_ICON = theme.load_icon("changes-prevent-symbolic", 16, 0) if theme.lookup
|
||||
"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
|
||||
IPTV_ICON = theme.load_icon("emblem-shared", 16, 0) if theme.lookup_icon("emblem-shared", 16, 0) else None
|
||||
EPG_ICON = theme.load_icon("gtk-index", 16, 0) if theme.lookup_icon("gtk-index", 16, 0) else None
|
||||
|
||||
# Colors
|
||||
NEW_COLOR = "rgb(255,230,204)" # Color for new services in the main list
|
||||
EXTRA_COLOR = "rgb(179,230,204)" # Color for services with a extra name for the bouquet
|
||||
|
||||
|
||||
class KeyboardKey(Enum):
|
||||
""" The raw(hardware) codes of the keyboard keys """
|
||||
""" The raw(hardware) codes of the keyboard keys. """
|
||||
Q = 24
|
||||
E = 26
|
||||
R = 27
|
||||
T = 28
|
||||
U = 30
|
||||
O = 32
|
||||
P = 33
|
||||
S = 39
|
||||
D = 40
|
||||
H = 43
|
||||
L = 46
|
||||
X = 53
|
||||
C = 54
|
||||
V = 55
|
||||
B = 56
|
||||
W = 25
|
||||
Z = 52
|
||||
INSERT = 118
|
||||
@@ -51,6 +63,7 @@ class KeyboardKey(Enum):
|
||||
LEFT = 113
|
||||
RIGHT = 114
|
||||
F2 = 68
|
||||
SPACE = 65
|
||||
DELETE = 119
|
||||
BACK_SPACE = 22
|
||||
CTRL_L = 37
|
||||
@@ -71,15 +84,23 @@ MOVE_KEYS = (KeyboardKey.UP, KeyboardKey.PAGE_UP, KeyboardKey.DOWN, KeyboardKey.
|
||||
KeyboardKey.END, KeyboardKey.HOME_KP, KeyboardKey.END_KP, KeyboardKey.PAGE_UP_KP, KeyboardKey.PAGE_DOWN_KP)
|
||||
|
||||
|
||||
class FavClickMode(IntEnum):
|
||||
""" Double click mode on the service in the bouquet(FAV) list. """
|
||||
DISABLED = 0
|
||||
STREAM = 1
|
||||
PLAY = 2
|
||||
ZAP = 3
|
||||
|
||||
|
||||
class ViewTarget(Enum):
|
||||
""" Used for set target view """
|
||||
""" Used for set target view. """
|
||||
BOUQUET = 0
|
||||
FAV = 1
|
||||
SERVICES = 2
|
||||
|
||||
|
||||
class BqGenType(Enum):
|
||||
""" Bouquet generation type """
|
||||
""" Bouquet generation type. """
|
||||
SAT = 0
|
||||
EACH_SAT = 1
|
||||
PACKAGE = 2
|
||||
@@ -88,5 +109,53 @@ class BqGenType(Enum):
|
||||
EACH_TYPE = 5
|
||||
|
||||
|
||||
class Column(IntEnum):
|
||||
""" Column nums in the views """
|
||||
# main view
|
||||
SRV_CAS_FLAGS = 0
|
||||
SRV_STANDARD = 1
|
||||
SRV_CODED = 2
|
||||
SRV_SERVICE = 3
|
||||
SRV_LOCKED = 4
|
||||
SRV_HIDE = 5
|
||||
SRV_PACKAGE = 6
|
||||
SRV_TYPE = 7
|
||||
SRV_PICON = 8
|
||||
SRV_PICON_ID = 9
|
||||
SRV_SSID = 10
|
||||
SRV_FREQ = 11
|
||||
SRV_RATE = 12
|
||||
SRV_POL = 13
|
||||
SRV_FEC = 14
|
||||
SRV_SYSTEM = 15
|
||||
SRV_POS = 16
|
||||
SRV_DATA_ID = 17
|
||||
SRV_FAV_ID = 18
|
||||
SRV_TRANSPONDER = 19
|
||||
SRV_TOOLTIP = 20
|
||||
SRV_BACKGROUND = 21
|
||||
# fav view
|
||||
FAV_NUM = 0
|
||||
FAV_CODED = 1
|
||||
FAV_SERVICE = 2
|
||||
FAV_LOCKED = 3
|
||||
FAV_HIDE = 4
|
||||
FAV_TYPE = 5
|
||||
FAV_POS = 6
|
||||
FAV_ID = 7
|
||||
FAV_PICON = 8
|
||||
FAV_TOOLTIP = 9
|
||||
FAV_BACKGROUND = 10
|
||||
# bouquets view
|
||||
BQ_NAME = 0
|
||||
BQ_LOCKED = 1
|
||||
BQ_HIDDEN = 2
|
||||
BQ_TYPE = 3
|
||||
|
||||
def __index__(self):
|
||||
""" Overridden to get the index in slices directly """
|
||||
return self.value
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
VER="0.4.2_Pre-alpha"
|
||||
VER="0.4.6_Pre-alpha"
|
||||
B_PATH="dist/DemonEditor"
|
||||
DEB_PATH="$B_PATH/usr/share/demoneditor"
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
demon-editor for Debian
|
||||
----------------------
|
||||
|
||||
DemonEditor
|
||||
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 + Insert - copies the selected channels from the main list to the the bouquet beginning
|
||||
or inserts (creates) a new bouquet.
|
||||
Keyboard shortcuts:
|
||||
Ctrl + Insert - copies the selected channels from the main list to the the bouquet beginning or inserts (creates) a new bouquet.
|
||||
Ctrl + BackSpace - copies the selected channels from the main list to the bouquet end.
|
||||
Ctrl + X - only in bouquet list. Ctrl + C - only in services list.
|
||||
Clipboard is "rubber". There is an accumulation before the insertion!
|
||||
@@ -19,28 +17,32 @@ Keyboard shortcuts:
|
||||
Ctrl + L - parental lock.
|
||||
Ctrl + H - hide/skip.
|
||||
Ctrl + P - start play IPTV or other stream in the bouquet list.
|
||||
Ctrl + Z - switch(zap) the channel(works when the HTTP API is enabled, Enigma2 only).
|
||||
Ctrl + Z - switch (zap) the channel (works when the HTTP API is enabled, Enigma2 only).
|
||||
Ctrl + W - switch to the channel and watch in the program.
|
||||
Space - select/deselect.
|
||||
Left/Right - remove selection.
|
||||
Ctrl + Up, Down, PageUp, PageDown, Home, End - move selected items in the list.
|
||||
Ctrl + O - (re)load user data from current dir.
|
||||
Ctrl + D - load data from receiver.
|
||||
Ctrl + U/B upload data/bouquets to receiver.
|
||||
|
||||
Extra:
|
||||
|
||||
Import feature.
|
||||
Multiple selections in lists only with Space key (as in file managers).
|
||||
Ability to import IPTV into bouquet (Neutrino WEBTV) from m3u files.
|
||||
Ability to download picons and update satellites (transponders) from web.
|
||||
Preview (playing) IPTV or other streams directly from the bouquet list(should be installed VLC).
|
||||
Ability to import into bouquet (Neutrino WEB TV) from m3u.
|
||||
Ability to export bouquets with IPTV services to m3u.
|
||||
Assignment EPG from DVB or XML for IPTV services(Enigma2 only).
|
||||
Preview (playing) IPTV or other streams directly from the bouquet list (should be installed VLC).
|
||||
|
||||
Minimum requirements:
|
||||
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings.
|
||||
|
||||
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings.
|
||||
Launching
|
||||
Note.
|
||||
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2!
|
||||
|
||||
Terrestrial(DVB-T/T2) and cable channels are supported(Enigma2 only) with limitation!
|
||||
Important:
|
||||
Main supported lamedb format is version 4. Versions 3 and 5 has only experimental support!
|
||||
For version **3** is only read mode available. When saving, version **4** format is used instead!
|
||||
|
||||
Main supported lamedb format is version 4. Versions 3 and 5 has only experimental support!
|
||||
For version 3 is only read mode available. When saving, version 4 format is used instead!
|
||||
|
||||
-- Dmitriy Yefremov <dmitry.v.yefremov@gmail.com> Thu, 06 Dec 2018 11:27:21 +0300
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: DemonEditor
|
||||
Version: 0.4.2-Pre-alpha
|
||||
Version: 0.4.6-Pre-alpha
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
|
||||
@@ -5,7 +5,7 @@ Source: https://github.com/DYefremov/DemonEditor
|
||||
Files: *
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2019 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/bin/bash
|
||||
python3 /usr/share/demoneditor/start.py
|
||||
python3 /usr/share/demoneditor/start.py $1
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
deb/usr/share/locale/pt/LC_MESSAGES/demon-editor.mo
Normal file
BIN
deb/usr/share/locale/pt/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
# Copyright (C) 2018 Frank Neirynck
|
||||
# Copyright (C) 2018-2019 Frank Neirynck
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#Frank Neirynck <frank@insink.be>, 2018.
|
||||
# Frank Neirynck <frank@insink.be>, 2018-2019.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -10,6 +10,11 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Language-Team: \n"
|
||||
"X-Generator: Poedit 2.2.1\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "Frank Neirynck <frank@insink.be>"
|
||||
@@ -31,7 +36,7 @@ msgid "Freq"
|
||||
msgstr "Frec."
|
||||
|
||||
msgid "Rate"
|
||||
msgstr "Rate"
|
||||
msgstr "Ratio"
|
||||
|
||||
msgid "Pol"
|
||||
msgstr "Pol."
|
||||
@@ -52,7 +57,7 @@ msgid "Assign"
|
||||
msgstr "Assignar"
|
||||
|
||||
msgid "Bouquet details"
|
||||
msgstr "Detailes Ramo"
|
||||
msgstr "Detalles Ramo"
|
||||
|
||||
msgid "Bouquets"
|
||||
msgstr "Ramos"
|
||||
@@ -69,11 +74,8 @@ msgstr "Descargar"
|
||||
msgid "Edit"
|
||||
msgstr "Editar"
|
||||
|
||||
msgid "Edit "
|
||||
msgstr "Editar "
|
||||
|
||||
msgid "Edit mаrker text"
|
||||
msgstr "Editar testo del mаrcador"
|
||||
msgstr "Editar texto del mаrcador"
|
||||
|
||||
msgid "FTP-transfer"
|
||||
msgstr "Transferencia-FTP"
|
||||
@@ -106,7 +108,7 @@ msgid "Set default name"
|
||||
msgstr "Establecer nombre predeterminado"
|
||||
|
||||
msgid "Insert marker"
|
||||
msgstr "Inserter marcador"
|
||||
msgstr "Insertar marcador"
|
||||
|
||||
msgid "Locate in services"
|
||||
msgstr "Buscar en servicios"
|
||||
@@ -163,7 +165,7 @@ msgid "Remove"
|
||||
msgstr "Remover"
|
||||
|
||||
msgid "Remove all unavailable"
|
||||
msgstr "Remover toto lo indisponible"
|
||||
msgstr "Remover todo lo indisponible"
|
||||
|
||||
msgid "Satellites editor"
|
||||
msgstr "Editor Satélites"
|
||||
@@ -260,7 +262,7 @@ msgid "All positions"
|
||||
msgstr "Todas las posiciones"
|
||||
|
||||
msgid "All types"
|
||||
msgstr "Todas los tipos"
|
||||
msgstr "Todos los tipos"
|
||||
|
||||
# Streams player
|
||||
msgid "Play"
|
||||
@@ -298,7 +300,7 @@ msgid "Resize:"
|
||||
msgstr "Redimensionar:"
|
||||
|
||||
msgid "Current picons path:"
|
||||
msgstr "Ruta acual Picons:"
|
||||
msgstr "Ruta actual Picons:"
|
||||
|
||||
msgid "Receiver picons path:"
|
||||
msgstr "Ruta picons receptor:"
|
||||
@@ -319,7 +321,7 @@ msgid "Convert"
|
||||
msgstr "Convertir"
|
||||
|
||||
msgid "Path to save:"
|
||||
msgstr "Ruto para guardar:"
|
||||
msgstr "Ruta para guardar:"
|
||||
|
||||
msgid "Path to Enigma2 picons:"
|
||||
msgstr "Ruta a picons Enigma2:"
|
||||
@@ -336,12 +338,17 @@ msgstr "Recibir picons para proovedor"
|
||||
msgid "Load satellite providers."
|
||||
msgstr "Cargar proovedores Satélite."
|
||||
|
||||
msgid "To automatically set the identifiers for picons,\nfirst load the required services list into the main application window."
|
||||
msgstr "Para configurar automáticamente los identificadores para picons, \nprimero cargue la lista de servicios requeridos en la ventana principal."
|
||||
msgid ""
|
||||
"To automatically set the identifiers for picons,\n"
|
||||
"first load the required services list into the main application window."
|
||||
|
||||
msgstr ""
|
||||
"Para configurar automáticamente los identificadores para picons, \n"
|
||||
"primero cargue la lista de serviços requeridos en la ventana principal."
|
||||
|
||||
# Satellites editor
|
||||
msgid "Satellites edit tool"
|
||||
msgstr "Edito de Satélites"
|
||||
msgstr "Editor de Satélites"
|
||||
|
||||
msgid "Add"
|
||||
msgstr "Añadir"
|
||||
@@ -382,10 +389,14 @@ msgid "Service data"
|
||||
msgstr "Datos servicio"
|
||||
|
||||
msgid "Transponder details"
|
||||
msgstr "Detailes Transpondedor"
|
||||
msgstr "Detalles Transpondedor"
|
||||
|
||||
msgid "Changes will be applied to all services of this transponder!\nContinue?"
|
||||
msgstr "Los cambios se aplicarán a todos los servicios de este transpondedor!\nContinuar?"
|
||||
msgid ""
|
||||
"Changes will be applied to all services of this transponder!\n"
|
||||
"Continue?"
|
||||
msgstr ""
|
||||
"Los cambios se aplicarán a todos los servicios de este transpondedor!\n"
|
||||
"Continuar?"
|
||||
|
||||
msgid "Reference"
|
||||
msgstr "Referencia"
|
||||
@@ -426,7 +437,7 @@ msgstr "Datos de la Secuencia"
|
||||
|
||||
# IPTV list configuration dialog
|
||||
msgid "Starting values"
|
||||
msgstr "Volores iniciales"
|
||||
msgstr "Valores iniciales"
|
||||
|
||||
msgid "Reset to default"
|
||||
msgstr "Restablecer a predeterminado"
|
||||
@@ -434,7 +445,7 @@ msgstr "Restablecer a predeterminado"
|
||||
msgid "IPTV streams list configuration"
|
||||
msgstr "Configurar lista de Secuencias IPTV"
|
||||
|
||||
#Settings dialog
|
||||
# Settings dialog
|
||||
msgid "Preferences"
|
||||
msgstr "Preferencias"
|
||||
|
||||
@@ -466,7 +477,7 @@ msgid "Data path:"
|
||||
msgstr "Ruta de datos:"
|
||||
|
||||
msgid "Picons path:"
|
||||
msgstr "Ruda de Picons:"
|
||||
msgstr "Ruta de Picons:"
|
||||
|
||||
msgid "Network settings:"
|
||||
msgstr "Configuración de red:"
|
||||
@@ -527,7 +538,7 @@ msgid "Please, wait..."
|
||||
msgstr "Por favor, espere..."
|
||||
|
||||
msgid "Resizing..."
|
||||
msgstr "Redimensionamiento..."
|
||||
msgstr "Redimensionando..."
|
||||
|
||||
msgid "Select paths!"
|
||||
msgstr "Seleccione rutas!"
|
||||
@@ -569,3 +580,201 @@ msgstr "ningún cambio requerido!"
|
||||
msgid "This list does not contains IPTV streams!"
|
||||
msgstr "La lista no contiene secuencias IPTV!"
|
||||
|
||||
msgid "New empty configuration"
|
||||
msgstr "Nueva configuración vacía"
|
||||
|
||||
msgid "No data to save!"
|
||||
msgstr "No hay datos para guardar!"
|
||||
|
||||
msgid "Network"
|
||||
msgstr "Red"
|
||||
|
||||
msgid "Paths"
|
||||
msgstr "Rutas"
|
||||
|
||||
msgid "Program"
|
||||
msgstr "Programa"
|
||||
|
||||
msgid "Backup:"
|
||||
msgstr "Backup:"
|
||||
|
||||
msgid "Backup"
|
||||
msgstr "Backup"
|
||||
|
||||
msgid "Backups"
|
||||
msgstr "Backups"
|
||||
|
||||
msgid "Backup path:"
|
||||
msgstr "Ruta del backup:"
|
||||
|
||||
msgid "Restore bouquets"
|
||||
msgstr "Restaurar ramos"
|
||||
|
||||
msgid "Restore all"
|
||||
msgstr "Restaurar todo"
|
||||
|
||||
msgid "Before saving"
|
||||
msgstr "Antes de guardar"
|
||||
|
||||
msgid "Before downloading from the receiver"
|
||||
msgstr "Antes de recibir del receptor"
|
||||
|
||||
msgid "Set background color for the services"
|
||||
msgstr "Determinar color de fondo para servicios"
|
||||
|
||||
msgid "Marked as new:"
|
||||
msgstr "Marcado como nuevo:"
|
||||
|
||||
msgid "With an extra name in the bouquet:"
|
||||
msgstr "Con nombre adicional en ramo:"
|
||||
|
||||
msgid "Select"
|
||||
msgstr "Seleccione"
|
||||
|
||||
msgid "About"
|
||||
msgstr "Sobre"
|
||||
|
||||
msgid "Exit"
|
||||
msgstr "Salir"
|
||||
|
||||
msgid "Tools"
|
||||
msgstr "Herramientas"
|
||||
|
||||
# Import
|
||||
msgid "Import"
|
||||
msgstr "Importar"
|
||||
|
||||
msgid "Bouquet"
|
||||
msgstr "Ramo"
|
||||
|
||||
msgid "Bouquets and services"
|
||||
msgstr "Ramos y servicios"
|
||||
|
||||
msgid "The main list does not contain services for this bouquet!"
|
||||
msgstr "La lista principal no contiene servicios para este ramo!"
|
||||
|
||||
msgid "No bouquet file is selected!"
|
||||
msgstr "Nigún fichero de ramo ha sido seleccionado!"
|
||||
|
||||
msgid "Remove all unused"
|
||||
msgstr "Quite todos los"
|
||||
|
||||
msgid "Test"
|
||||
msgstr "Prueba"
|
||||
|
||||
msgid "Test connection"
|
||||
msgstr "Probar conexión"
|
||||
|
||||
msgid "Double click on the service in the bouquet list:"
|
||||
msgstr "Haga doble clic en el servicio en la lista de bouquet:"
|
||||
|
||||
msgid "Zap"
|
||||
msgstr "Zapear"
|
||||
|
||||
msgid "Play stream"
|
||||
msgstr "Reproducir secuencia"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "Desactivado"
|
||||
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Soporte para ver. 5 (experimental)"
|
||||
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "Habilitar API HTTP (experimental)"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Cambiar (ZAP) el canal (Ctrl + Z)"
|
||||
|
||||
msgid "Switch the channel and watch in the program(Ctrl + W)"
|
||||
msgstr "Cambiar el canal y ver en el programa (Ctrl + W)"
|
||||
|
||||
msgid "Play IPTV or other stream in the program(Ctrl + P)"
|
||||
msgstr "Reproducir IPTV u otro flujo en el programa (Ctrl + P)"
|
||||
|
||||
msgid "Export to m3u"
|
||||
msgstr "Exportar hacia m3u"
|
||||
|
||||
msgid "EPG configuration"
|
||||
msgstr "Configuración EPG"
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Aplicar"
|
||||
|
||||
msgid "EPG source"
|
||||
msgstr "Fuente EPG"
|
||||
|
||||
msgid "Service names source:"
|
||||
msgstr "Nombre de servicio fuente:"
|
||||
|
||||
msgid "Main service list"
|
||||
msgstr "Lista principal de servicios:"
|
||||
|
||||
msgid "XML file"
|
||||
msgstr "Archivo XML"
|
||||
|
||||
msgid "Use web source"
|
||||
msgstr "Usar fuente web"
|
||||
|
||||
msgid "Url to *.xml.gz file:"
|
||||
msgstr "URL del archivo *.xml.gz:"
|
||||
|
||||
msgid "Enable filtering"
|
||||
msgstr "Habilitar filtrar"
|
||||
|
||||
msgid "Filter by presence in the epg.dat file."
|
||||
msgstr "Filtrar por presencia del archivo epg.dat."
|
||||
|
||||
msgid "Paths to the epg.dat file:"
|
||||
msgstr "Ruta al archivo epg.dat:"
|
||||
|
||||
msgid "Local path:"
|
||||
msgstr "Ruta local:"
|
||||
|
||||
msgid "STB path:"
|
||||
msgstr "Ruta STB:"
|
||||
|
||||
msgid "Update on start"
|
||||
msgstr "Actualisar al iniciar"
|
||||
|
||||
msgid "Auto configuration by service names."
|
||||
msgstr "Auto configuración por nombres de servicios."
|
||||
|
||||
msgid "Save list to xml."
|
||||
msgstr "Guardar como XML."
|
||||
|
||||
msgid "Download XML file error."
|
||||
msgstr "Error bajando archivo XML."
|
||||
|
||||
msgid "Unsupported file type:"
|
||||
msgstr "Archivo no supportado:"
|
||||
|
||||
msgid "Unpacking data error."
|
||||
msgstr "Error abriende datos."
|
||||
|
||||
msgid "XML parsing error:"
|
||||
msgstr "Error analisando XML:"
|
||||
|
||||
msgid "Count of successfully configured services:"
|
||||
msgstr "Número de servicios configurados con éxito:"
|
||||
|
||||
msgid "Current epg.dat file does not contains references for the services of this bouquet!"
|
||||
msgstr "Archivo epg.dat actual no tiene referencias a servicios de este ramo!"
|
||||
|
||||
msgid "Use HTTP"
|
||||
msgstr "Utilizar HTTP"
|
||||
|
||||
msgid "Close playback"
|
||||
msgstr "Terminar reproducción"
|
||||
|
||||
msgid "Import YouTube playlist"
|
||||
msgstr "Importar lista de reproducción de YouTube"
|
||||
|
||||
msgid "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
|
||||
msgstr "Encontré un enlace al recurso de YouTube!\nIntentar obtener un enlace directo al video?"
|
||||
|
||||
msgid "Playlist import"
|
||||
msgstr "Importar lista de reproducción"
|
||||
|
||||
msgid "Getting link error:"
|
||||
msgstr "Recibido Link error:"
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright (C) 2018 Frank Neirynck
|
||||
# Copyright (C) 2018-2019 Frank Neirynck
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#Frank Neirynck <frank@insink.be>, 2018.
|
||||
# Frank Neirynck <frank@insink.be>, 2018-2019.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -10,6 +10,11 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Language-Team: \n"
|
||||
"X-Generator: Poedit 2.2.1\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "Frank Neirynck <frank@insink.be>"
|
||||
@@ -69,9 +74,6 @@ msgstr "Download"
|
||||
msgid "Edit"
|
||||
msgstr "Wijzig"
|
||||
|
||||
msgid "Edit "
|
||||
msgstr "Wijzig "
|
||||
|
||||
msgid "Edit mаrker text"
|
||||
msgstr "Wijzig mаrker tekst"
|
||||
|
||||
@@ -336,8 +338,12 @@ msgstr "Ontvang picons voor leveranciers"
|
||||
msgid "Load satellite providers."
|
||||
msgstr "Laad satelliet leveranciers."
|
||||
|
||||
msgid "To automatically set the identifiers for picons,\nfirst load the required services list into the main application window."
|
||||
msgstr "Om automatisch de ID in te stellen voor picons,\nlaad eerst de vereiste serviceslijst in via het hoofdvenster van het programma."
|
||||
msgid ""
|
||||
"To automatically set the identifiers for picons,\n"
|
||||
"first load the required services list into the main application window."
|
||||
msgstr ""
|
||||
"Om automatisch de ID in te stellen voor picons,\n"
|
||||
"laad eerst de vereiste serviceslijst in via het hoofdvenster van het programma."
|
||||
|
||||
# Satellites editor
|
||||
msgid "Satellites edit tool"
|
||||
@@ -384,8 +390,12 @@ msgstr "Gegevens Dienst"
|
||||
msgid "Transponder details"
|
||||
msgstr "Details Transponder"
|
||||
|
||||
msgid "Changes will be applied to all services of this transponder!\nContinue?"
|
||||
msgstr "Wijzigingen zullen worden doorgevoerd op alle diensten van deze transponder!\nDoorgaan?"
|
||||
msgid ""
|
||||
"Changes will be applied to all services of this transponder!\n"
|
||||
"Continue?"
|
||||
msgstr ""
|
||||
"Wijzigingen zullen worden doorgevoerd op alle diensten van deze transponder!\n"
|
||||
"Doorgaan?"
|
||||
|
||||
msgid "Reference"
|
||||
msgstr "Referentie"
|
||||
@@ -434,7 +444,7 @@ msgstr "Reset naar standaard"
|
||||
msgid "IPTV streams list configuration"
|
||||
msgstr "Configureren IPTV Streamlijst"
|
||||
|
||||
#Settings dialog
|
||||
# Settings dialog
|
||||
msgid "Preferences"
|
||||
msgstr "Voorkeuren"
|
||||
|
||||
@@ -568,3 +578,202 @@ msgstr "Geen wijzigingen vereist!"
|
||||
|
||||
msgid "This list does not contains IPTV streams!"
|
||||
msgstr "Deze lijst bevat geen IPTV streams!"
|
||||
|
||||
msgid "New empty configuration"
|
||||
msgstr "Nieuwe lege configuratie"
|
||||
|
||||
msgid "No data to save!"
|
||||
msgstr "Geen gegevens om op te slaan!"
|
||||
|
||||
msgid "Network"
|
||||
msgstr "Netwerk"
|
||||
|
||||
msgid "Paths"
|
||||
msgstr "Paden"
|
||||
|
||||
msgid "Program"
|
||||
msgstr "Programma"
|
||||
|
||||
msgid "Backup:"
|
||||
msgstr "Backup:"
|
||||
|
||||
msgid "Backup"
|
||||
msgstr "Backup"
|
||||
|
||||
msgid "Backups"
|
||||
msgstr "Backups"
|
||||
|
||||
msgid "Backup path:"
|
||||
msgstr "Backup pad:"
|
||||
|
||||
msgid "Restore bouquets"
|
||||
msgstr "Herstel boeketten"
|
||||
|
||||
msgid "Restore all"
|
||||
msgstr "Herstel alles"
|
||||
|
||||
msgid "Before saving"
|
||||
msgstr "Voor opslaan"
|
||||
|
||||
msgid "Before downloading from the receiver"
|
||||
msgstr "Voor download uit de ontvanger"
|
||||
|
||||
msgid "Set background color for the services"
|
||||
msgstr "Stel achtergrond kleur in voor diensten"
|
||||
|
||||
msgid "Marked as new:"
|
||||
msgstr "Gemarkeerd als nieuw:"
|
||||
|
||||
msgid "With an extra name in the bouquet:"
|
||||
msgstr "Met een extra naam in het boeket:"
|
||||
|
||||
msgid "Select"
|
||||
msgstr "Over"
|
||||
|
||||
msgid "About"
|
||||
msgstr "Over"
|
||||
|
||||
msgid "Exit"
|
||||
msgstr "Exit"
|
||||
|
||||
msgid "Tools"
|
||||
msgstr "Tools"
|
||||
|
||||
# Import
|
||||
msgid "Import"
|
||||
msgstr "Importeer"
|
||||
|
||||
msgid "Bouquet"
|
||||
msgstr "Boeket"
|
||||
|
||||
msgid "Bouquets and services"
|
||||
msgstr "Boeketten en diensten"
|
||||
|
||||
msgid "The main list does not contain services for this bouquet!"
|
||||
msgstr "De hoofdlijst bevat geen diensten voor dit boeket!"
|
||||
|
||||
msgid "No bouquet file is selected!"
|
||||
msgstr "Geen boeket geselecteerd!"
|
||||
|
||||
msgid "Remove all unused"
|
||||
msgstr "Verwijder alle ongebruikte"
|
||||
|
||||
msgid "Test"
|
||||
msgstr "Test"
|
||||
|
||||
msgid "Test connection"
|
||||
msgstr "Test verbinding"
|
||||
|
||||
msgid "Double click on the service in the bouquet list:"
|
||||
msgstr "Dubbelklik op de dienst in de boeket lijst:"
|
||||
|
||||
msgid "Zap"
|
||||
msgstr "Zap"
|
||||
|
||||
msgid "Play stream"
|
||||
msgstr "Speel stream af"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "Uitgeschakeld"
|
||||
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Ondersteuning voor ver. 5 inschakelen (experimenteel)"
|
||||
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "HTTP API inschakelen (experimenteel)"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Schakelaar (ZAP) naar het kanaal (CTRL + Z)"
|
||||
|
||||
msgid "Switch the channel and watch in the program(Ctrl + W)"
|
||||
msgstr "Schakel het kanaal in en bekijk het programma (CTRL + W)"
|
||||
|
||||
msgid "Play IPTV or other stream in the program(Ctrl + P)"
|
||||
msgstr "Speel IPTV of andere stream af en bekijk het programma (CTRL + P)"
|
||||
|
||||
msgid "Export to m3u"
|
||||
msgstr "Uitvoeren naar m3u"
|
||||
|
||||
msgid "EPG configuration"
|
||||
msgstr "Configureer EPG"
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Toepassen"
|
||||
|
||||
msgid "EPG source"
|
||||
msgstr "Bron EPG"
|
||||
|
||||
msgid "Service names source:"
|
||||
msgstr "Naam van bron van de dienst:"
|
||||
|
||||
msgid "Main service list"
|
||||
msgstr "Hoofdlijst diensten:"
|
||||
|
||||
msgid "XML file"
|
||||
msgstr "XML file"
|
||||
|
||||
msgid "Use web source"
|
||||
msgstr "Gebruik web bron"
|
||||
|
||||
msgid "Url to *.xml.gz file:"
|
||||
msgstr "URL van de *.xml.gz file:"
|
||||
|
||||
msgid "Enable filtering"
|
||||
msgstr "Zet filteren aan"
|
||||
|
||||
msgid "Filter by presence in the epg.dat file."
|
||||
msgstr "Filter op aanwezigheid van epg.dat. file"
|
||||
|
||||
msgid "Paths to the epg.dat file:"
|
||||
msgstr "Pad naar epg.dat:"
|
||||
|
||||
msgid "Local path:"
|
||||
msgstr "Lokaal pad:"
|
||||
|
||||
msgid "STB path:"
|
||||
msgstr "STB pad:"
|
||||
|
||||
msgid "Update on start"
|
||||
msgstr "Actualiseer bij start"
|
||||
|
||||
msgid "Auto configuration by service names."
|
||||
msgstr "Auto configuratie door dienst namen."
|
||||
|
||||
msgid "Save list to xml."
|
||||
msgstr "Opslaan als XML."
|
||||
|
||||
msgid "Download XML file error."
|
||||
msgstr "Fout bij downloaden XML."
|
||||
|
||||
msgid "Unsupported file type:"
|
||||
msgstr "Ongesupporteerd archieftype:"
|
||||
|
||||
msgid "Unpacking data error."
|
||||
msgstr "Fout bij uitpakken van de data."
|
||||
|
||||
msgid "XML parsing error:"
|
||||
msgstr "XML parsingfout:"
|
||||
|
||||
msgid "Count of successfully configured services:"
|
||||
msgstr "Aantal succesvol geconfigureerde diensten:"
|
||||
|
||||
msgid "Current epg.dat file does not contains references for the services of this bouquet!"
|
||||
msgstr "Huisige epg.dat bestand heeft geen referenties naar de diensten van dit boeket!"
|
||||
|
||||
msgid "Use HTTP"
|
||||
msgstr "Gebruik HTTP"
|
||||
|
||||
msgid "Close playback"
|
||||
msgstr "Stop afspelen"
|
||||
|
||||
msgid "Import YouTube playlist"
|
||||
msgstr "Importeer YouTube playlist"
|
||||
|
||||
msgid "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
|
||||
msgstr "Een link gevonden naar de YouTube bron!\nProberen een rechtstreekse link naar de video te vonden?"
|
||||
|
||||
msgid "Playlist import"
|
||||
msgstr "Importeer playlist"
|
||||
|
||||
msgid "Getting link error:"
|
||||
msgstr "Volgende Link error gekregen:"
|
||||
766
po/pt/demon-editor.po
Normal file
766
po/pt/demon-editor.po
Normal file
@@ -0,0 +1,766 @@
|
||||
# Copyright (C) 2018-2019 Frank Neirynck
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#Frank Neirynck <frank@insink.be>, 2018-2019.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Last-Translator: Frank Neirynck\n"
|
||||
"Language: pt\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "Frank Neirynck <frank@insink.be>"
|
||||
|
||||
# Main
|
||||
msgid "Service"
|
||||
msgstr "Serviço"
|
||||
|
||||
msgid "Package"
|
||||
msgstr "Pacote"
|
||||
|
||||
msgid "Type"
|
||||
msgstr "Tipo"
|
||||
|
||||
msgid "Picon"
|
||||
msgstr "Picon"
|
||||
|
||||
msgid "Freq"
|
||||
msgstr "Freq."
|
||||
|
||||
msgid "Rate"
|
||||
msgstr "Ratio"
|
||||
|
||||
msgid "Pol"
|
||||
msgstr "Pol."
|
||||
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
msgid "Pos"
|
||||
msgstr "Pos."
|
||||
|
||||
msgid "Num"
|
||||
msgstr "Núm"
|
||||
|
||||
msgid "Current IP:"
|
||||
msgstr "IP atual:"
|
||||
|
||||
msgid "Assign"
|
||||
msgstr "Atribuir"
|
||||
|
||||
msgid "Bouquet details"
|
||||
msgstr "Detalhes do Ramo"
|
||||
|
||||
msgid "Bouquets"
|
||||
msgstr "Ramos"
|
||||
|
||||
msgid "Copy"
|
||||
msgstr "Copiar"
|
||||
|
||||
msgid "Copy reference"
|
||||
msgstr "Copia de Referência"
|
||||
|
||||
msgid "Download"
|
||||
msgstr "Descarregar"
|
||||
|
||||
msgid "Edit"
|
||||
msgstr "Editar"
|
||||
|
||||
msgid "Edit mаrker text"
|
||||
msgstr "Editar texto do mаrcador"
|
||||
|
||||
msgid "FTP-transfer"
|
||||
msgstr "Transferência-FTP"
|
||||
|
||||
msgid "Global search"
|
||||
msgstr "Pesquisa Global"
|
||||
|
||||
msgid "Hide"
|
||||
msgstr "Esconder"
|
||||
|
||||
msgid "Hide/Skip On/Off Ctrl + H"
|
||||
msgstr "Esconder/Pular Acender/Desligar Ctrl + H"
|
||||
|
||||
msgid "Add IPTV or stream service"
|
||||
msgstr "Acrescentar serviço IPTV"
|
||||
|
||||
msgid "Import m3u"
|
||||
msgstr "Importar m3u"
|
||||
|
||||
msgid "Import m3u file"
|
||||
msgstr "Importar ficheiro m3u"
|
||||
|
||||
msgid "List configuration"
|
||||
msgstr "Lista configuração"
|
||||
|
||||
msgid "Rename for this bouquet"
|
||||
msgstr "Renomear para este ramo"
|
||||
|
||||
msgid "Set default name"
|
||||
msgstr "Estabelecer nome predeterminado"
|
||||
|
||||
msgid "Insert marker"
|
||||
msgstr "Inserir marcador"
|
||||
|
||||
msgid "Locate in services"
|
||||
msgstr "Procurar em serviços"
|
||||
|
||||
msgid "Locked"
|
||||
msgstr "Fechado"
|
||||
|
||||
msgid "Move"
|
||||
msgstr "Mover"
|
||||
|
||||
msgid "New"
|
||||
msgstr "Novo"
|
||||
|
||||
msgid "New bouquet"
|
||||
msgstr "Ramo novo"
|
||||
|
||||
msgid "Create bouquet"
|
||||
msgstr "Criar ramo"
|
||||
|
||||
msgid "For current satellite"
|
||||
msgstr "Para o satélite atual"
|
||||
|
||||
msgid "For current package"
|
||||
msgstr "Para o pacote atual"
|
||||
|
||||
msgid "For current type"
|
||||
msgstr "Para o tipo atual"
|
||||
|
||||
msgid "For each satellite"
|
||||
msgstr "Para cada Satélite"
|
||||
|
||||
msgid "For each package"
|
||||
msgstr "Para cada pacote"
|
||||
|
||||
msgid "For each type"
|
||||
msgstr "Para cada tipo"
|
||||
|
||||
msgid "Open"
|
||||
msgstr "Abrir"
|
||||
|
||||
msgid "Parent lock On/Off Ctrl + L"
|
||||
msgstr "Bloqueio parentesco Acender/Desligar Ctrl + L"
|
||||
|
||||
msgid "Picons"
|
||||
msgstr "Picons"
|
||||
|
||||
msgid "Picons downloader"
|
||||
msgstr "Picons descargar"
|
||||
|
||||
msgid "Satellites downloader"
|
||||
msgstr "Satélites descarregar"
|
||||
|
||||
msgid "Remove"
|
||||
msgstr "Remover"
|
||||
|
||||
msgid "Remove all unavailable"
|
||||
msgstr "Remover tudo lo indisponível"
|
||||
|
||||
msgid "Satellites editor"
|
||||
msgstr "Editor Satélites"
|
||||
|
||||
msgid "Save"
|
||||
msgstr "Guardar"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Procurar"
|
||||
|
||||
msgid "Services"
|
||||
msgstr "Serviços"
|
||||
|
||||
msgid "Services filter"
|
||||
msgstr "Filtro serviços"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Configurações"
|
||||
|
||||
msgid "Up"
|
||||
msgstr "Arriba"
|
||||
|
||||
msgid "Down"
|
||||
msgstr "Abaixo"
|
||||
|
||||
msgid "Active profile:"
|
||||
msgstr "Perfil ativo:"
|
||||
|
||||
msgid "All"
|
||||
msgstr "Tudo"
|
||||
|
||||
msgid "Are you sure?"
|
||||
msgstr "Tem certeza?"
|
||||
|
||||
msgid "Current data path:"
|
||||
msgstr "Rota de dados atual:"
|
||||
|
||||
msgid "Data:"
|
||||
msgstr "Dados:"
|
||||
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Editor de Canais e Satélites Enigma2 para GNU/Linux"
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Anfitrião:"
|
||||
|
||||
msgid "Loading data..."
|
||||
msgstr "Carregar dados..."
|
||||
|
||||
msgid "Receive"
|
||||
msgstr "Receber"
|
||||
|
||||
msgid "Receive files from receiver"
|
||||
msgstr "Receber ficheiros do seu receptor"
|
||||
|
||||
msgid "Receiver IP:"
|
||||
msgstr "Receptor IP:"
|
||||
|
||||
msgid "Remove unused bouquets"
|
||||
msgstr "Remover ramos sem usar"
|
||||
|
||||
msgid "Reset profile"
|
||||
msgstr "Restabelecer perfil"
|
||||
|
||||
msgid "Satellites"
|
||||
msgstr "Satélites"
|
||||
|
||||
msgid "Satellites.xml file:"
|
||||
msgstr "Ficheiro Satellites.xml:"
|
||||
|
||||
msgid "Selected"
|
||||
msgstr "Selecionado"
|
||||
|
||||
msgid "Send"
|
||||
msgstr "Enviar"
|
||||
|
||||
msgid "Send files to receiver"
|
||||
msgstr "Enviar ficheiros ao recetor"
|
||||
|
||||
msgid "Services and Bouquets files:"
|
||||
msgstr "Ficheiros de Serviços e ramos:"
|
||||
|
||||
msgid "User bouquet files:"
|
||||
msgstr "Importar ficheiros de ramos:"
|
||||
|
||||
msgid "Extra:"
|
||||
msgstr "Extra:"
|
||||
|
||||
# Filter bar
|
||||
msgid "Only free"
|
||||
msgstr "Somente de graça"
|
||||
|
||||
msgid "All positions"
|
||||
msgstr "Todas as posições"
|
||||
|
||||
msgid "All types"
|
||||
msgstr "Todos os tipos"
|
||||
|
||||
# Streams player
|
||||
msgid "Play"
|
||||
msgstr "Play"
|
||||
|
||||
msgid "Stop playback"
|
||||
msgstr "Deter a reprodução"
|
||||
|
||||
msgid "Previous stream in the list"
|
||||
msgstr "Sequência anterior na lista"
|
||||
|
||||
msgid "Next stream in the list"
|
||||
msgstr "Sequência siguinte na lista"
|
||||
|
||||
msgid "Toggle in fullscreen"
|
||||
msgstr "Trocar na ecrã completa"
|
||||
|
||||
msgid "Close"
|
||||
msgstr "Fexar"
|
||||
|
||||
# Picons dialog
|
||||
msgid "Load providers"
|
||||
msgstr "Carregar provedores"
|
||||
|
||||
msgid "Providers"
|
||||
msgstr "Fornecedores"
|
||||
|
||||
msgid "Receive picons"
|
||||
msgstr "Recever picons"
|
||||
|
||||
msgid "Picons name format:"
|
||||
msgstr "Picons formato nomes:"
|
||||
|
||||
msgid "Resize:"
|
||||
msgstr "Redimensionar:"
|
||||
|
||||
msgid "Current picons path:"
|
||||
msgstr "Rota atual Picons:"
|
||||
|
||||
msgid "Receiver picons path:"
|
||||
msgstr "Rota picons recetor:"
|
||||
|
||||
msgid "Picons download tool"
|
||||
msgstr "Picons ferramenta de descarga"
|
||||
|
||||
msgid "Transfer to receiver"
|
||||
msgstr "Transferir ao recetor"
|
||||
|
||||
msgid "Downloader"
|
||||
msgstr "Descarregador"
|
||||
|
||||
msgid "Converter"
|
||||
msgstr "Conversor"
|
||||
|
||||
msgid "Convert"
|
||||
msgstr "Converter"
|
||||
|
||||
msgid "Path to save:"
|
||||
msgstr "Rota para guardar:"
|
||||
|
||||
msgid "Path to Enigma2 picons:"
|
||||
msgstr "Rota a picons Enigma2:"
|
||||
|
||||
msgid "Specify the correct position value for the provider!"
|
||||
msgstr "Especifique a posição correta para o provedor!"
|
||||
|
||||
msgid "Converter between name formats"
|
||||
msgstr "Conversor entre formatos de nomes"
|
||||
|
||||
msgid "Receive picons for providers"
|
||||
msgstr "Recever picons para provedor"
|
||||
|
||||
msgid "Load satellite providers."
|
||||
msgstr "Carregar provedores Satélite."
|
||||
|
||||
msgid "To automatically set the identifiers for picons,\nfirst load the required services list into the main application window."
|
||||
msgstr "Para configurar automaticamente os identificadores para picons, \nprimeiro carregue a lista de serviços necessários na janela principal."
|
||||
|
||||
# Satellites editor
|
||||
msgid "Satellites edit tool"
|
||||
msgstr "Editor de Satélites"
|
||||
|
||||
msgid "Add"
|
||||
msgstr "Adicionar"
|
||||
|
||||
msgid "Satellite"
|
||||
msgstr "Satélite"
|
||||
|
||||
msgid "Transponder"
|
||||
msgstr "Transponder"
|
||||
|
||||
msgid "Satellite properties:"
|
||||
msgstr "Propriedades do Satélite:"
|
||||
|
||||
msgid "Transponder properties:"
|
||||
msgstr "Propriedades do Transponder:"
|
||||
|
||||
msgid "Name"
|
||||
msgstr "Nome"
|
||||
|
||||
msgid "Position"
|
||||
msgstr "Posição"
|
||||
|
||||
# Satellites update dialog
|
||||
msgid "Satellites update"
|
||||
msgstr "Atualisar Satélite"
|
||||
|
||||
msgid "Remove selection"
|
||||
msgstr "Remover seleçáo"
|
||||
|
||||
# Service details dialog
|
||||
msgid "Service data:"
|
||||
msgstr "Dados serviço:"
|
||||
|
||||
msgid "Transponder data:"
|
||||
msgstr "Dados Transponder:"
|
||||
|
||||
msgid "Service data"
|
||||
msgstr "Dados serviço"
|
||||
|
||||
msgid "Transponder details"
|
||||
msgstr "Detalhes Transponder"
|
||||
|
||||
msgid "Changes will be applied to all services of this transponder!\nContinue?"
|
||||
msgstr "Os mudanças serão aplicadas a todos os serviços deste transponder!\nContinuar?"
|
||||
|
||||
msgid "Reference"
|
||||
msgstr "Referência"
|
||||
|
||||
msgid "Namespace"
|
||||
msgstr "Namespace"
|
||||
|
||||
msgid "Flags:"
|
||||
msgstr "Flags:"
|
||||
|
||||
msgid "Delays (ms):"
|
||||
msgstr "Atraso (mc)"
|
||||
|
||||
msgid "Bitstream"
|
||||
msgstr "Sequência de Bits"
|
||||
|
||||
msgid "Description"
|
||||
msgstr "Descrição"
|
||||
|
||||
msgid "Source:"
|
||||
msgstr "Fonte:"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "Annular"
|
||||
|
||||
msgid "Update"
|
||||
msgstr "Atualizar"
|
||||
|
||||
msgid "Filter"
|
||||
msgstr "Filtrar"
|
||||
|
||||
msgid "Find"
|
||||
msgstr "Procurar"
|
||||
|
||||
# IPTV dialog
|
||||
msgid "Stream data"
|
||||
msgstr "Dados da sequência"
|
||||
|
||||
# IPTV list configuration dialog
|
||||
msgid "Starting values"
|
||||
msgstr "Valores iniciais"
|
||||
|
||||
msgid "Reset to default"
|
||||
msgstr "Restabelecer a predeterminado"
|
||||
|
||||
msgid "IPTV streams list configuration"
|
||||
msgstr "Configurar lista de sequências IPTV"
|
||||
|
||||
#Settings dialog
|
||||
msgid "Preferences"
|
||||
msgstr "Preferências"
|
||||
|
||||
msgid "Profile:"
|
||||
msgstr "Perfil:"
|
||||
|
||||
msgid "Timeout between commands in seconds"
|
||||
msgstr "Tempo de espera entre comandos em segundos"
|
||||
|
||||
msgid "Timeout:"
|
||||
msgstr "Time-out:"
|
||||
|
||||
msgid "Login:"
|
||||
msgstr "Usuário:"
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Opções"
|
||||
|
||||
msgid "Password:"
|
||||
msgstr "Palavra-passe:"
|
||||
|
||||
msgid "Picons:"
|
||||
msgstr "Picons:"
|
||||
|
||||
msgid "Port:"
|
||||
msgstr "Porta:"
|
||||
|
||||
msgid "Data path:"
|
||||
msgstr "Rota de dados:"
|
||||
|
||||
msgid "Picons path:"
|
||||
msgstr "Rota de Picons:"
|
||||
|
||||
msgid "Network settings:"
|
||||
msgstr "Configuração de rede:"
|
||||
|
||||
msgid "STB file paths:"
|
||||
msgstr "Rota de ficheirors STB:"
|
||||
|
||||
msgid "Local file paths:"
|
||||
msgstr "Rota de ficheiros local:"
|
||||
|
||||
# Dialogs messages
|
||||
msgid "Error. No bouquet is selected!"
|
||||
msgstr "Erro. Nenhum ramo está selecionado!"
|
||||
|
||||
msgid "This item is not allowed to be removed!"
|
||||
msgstr "Este artigo não pode ser eliminado!"
|
||||
|
||||
msgid "This item is not allowed to edit!"
|
||||
msgstr "Este artigo não pode ser editado!"
|
||||
|
||||
msgid "Not allowed in this context!"
|
||||
msgstr "Náo permitido neste contexto!"
|
||||
|
||||
msgid "Please, download files from receiver or setup your path for read data!"
|
||||
msgstr "Por favor, descarregue arquivos desde o recetor ou configure a sua rota para ler os dados!"
|
||||
|
||||
msgid "Reading data error!"
|
||||
msgstr "Erro de leitura de dados!"
|
||||
|
||||
msgid "No m3u file is selected!"
|
||||
msgstr "Nenhum ficheiro m3u foi selecionado!"
|
||||
|
||||
msgid "Not implemented yet!"
|
||||
msgstr "Ainda não implementado!"
|
||||
|
||||
msgid "The text of marker is empty, please try again!"
|
||||
msgstr "O texto do marcador está vazio, tente de novo!"
|
||||
|
||||
msgid "Please, select only one item!"
|
||||
msgstr "Por favor, selecione só um elemento!"
|
||||
|
||||
msgid "No png file is selected!"
|
||||
msgstr "Nenhum ficheiro png foi selecionado!"
|
||||
|
||||
msgid "No reference is present!"
|
||||
msgstr "Nenhuma referência presente!"
|
||||
|
||||
msgid "No selected item!"
|
||||
msgstr "Nenhum elemento selecionado!"
|
||||
|
||||
msgid "The task is already running!"
|
||||
msgstr "A tarefa ya está en execuçáo!"
|
||||
|
||||
msgid "Done!"
|
||||
msgstr "Feito!"
|
||||
|
||||
msgid "Please, wait..."
|
||||
msgstr "Por favor, espere..."
|
||||
|
||||
msgid "Resizing..."
|
||||
msgstr "A redimensionar..."
|
||||
|
||||
msgid "Select paths!"
|
||||
msgstr "Selecione as rotas!"
|
||||
|
||||
msgid "No satellite is selected!"
|
||||
msgstr "Nemhun Satélite selecionado!"
|
||||
|
||||
msgid "Please, select only one satellite!"
|
||||
msgstr "Selecione só um Satélite!"
|
||||
|
||||
msgid "Please check your parameters and try again."
|
||||
msgstr "Por favor rever seus parâmetros e volte a tentar!"
|
||||
|
||||
msgid "No satellites.xml file is selected!"
|
||||
msgstr "Nemhun satellites.xml selecionado!"
|
||||
|
||||
msgid "Error. Verify the data!"
|
||||
msgstr "Erro. Rever os dados!"
|
||||
|
||||
msgid "Operation not allowed in this context!"
|
||||
msgstr "Operação não permitida neste contexto!"
|
||||
|
||||
msgid "No VLC is found. Check that it is installed!"
|
||||
msgstr "VLC não achado. Verifique se está instalado!"
|
||||
|
||||
# Search unavailable streams dialog
|
||||
msgid "Please wait, streams testing in progress..."
|
||||
msgstr "Por favor aguarde uma prova das sequências..."
|
||||
|
||||
msgid "Found"
|
||||
msgstr "Achado"
|
||||
|
||||
msgid "unavailable streams."
|
||||
msgstr "Sequências não presentes"
|
||||
|
||||
msgid "No changes required!"
|
||||
msgstr "Nemhuma mudança requerida!"
|
||||
|
||||
msgid "This list does not contains IPTV streams!"
|
||||
msgstr "A lista não tem sequências IPTV!"
|
||||
|
||||
msgid "New empty configuration"
|
||||
msgstr "Nova configuração vazia"
|
||||
|
||||
msgid "No data to save!"
|
||||
msgstr "Não há dados para guardar!"
|
||||
|
||||
msgid "Network"
|
||||
msgstr "Rede"
|
||||
|
||||
msgid "Paths"
|
||||
msgstr "Rotas"
|
||||
|
||||
msgid "Program"
|
||||
msgstr "Programa"
|
||||
|
||||
msgid "Backup:"
|
||||
msgstr "Backup:"
|
||||
|
||||
msgid "Backup"
|
||||
msgstr "Backup"
|
||||
|
||||
msgid "Backups"
|
||||
msgstr "Backups"
|
||||
|
||||
msgid "Backup path:"
|
||||
msgstr "Rota do backup:"
|
||||
|
||||
msgid "Restore bouquets"
|
||||
msgstr "Restaurar ramos"
|
||||
|
||||
msgid "Restore all"
|
||||
msgstr "Restaurar tudo"
|
||||
|
||||
msgid "Before saving"
|
||||
msgstr "Antes de guardar"
|
||||
|
||||
msgid "Before downloading from the receiver"
|
||||
msgstr "Antes de recever do recetor"
|
||||
|
||||
msgid "Set background color for the services"
|
||||
msgstr "Determinar a cor de fundo para serviços"
|
||||
|
||||
msgid "Marked as new:"
|
||||
msgstr "Marcado como novo:"
|
||||
|
||||
msgid "With an extra name in the bouquet:"
|
||||
msgstr "Com nome adicional em ramo:"
|
||||
|
||||
msgid "Select"
|
||||
msgstr "Selecione"
|
||||
|
||||
msgid "About"
|
||||
msgstr "Acerca"
|
||||
|
||||
msgid "Exit"
|
||||
msgstr "Sair"
|
||||
|
||||
msgid "Tools"
|
||||
msgstr "Tools"
|
||||
|
||||
#Import
|
||||
msgid "Import"
|
||||
msgstr "Importar"
|
||||
|
||||
msgid "Bouquet"
|
||||
msgstr "Ramo"
|
||||
|
||||
msgid "Bouquets and services"
|
||||
msgstr "Ramos e serviços"
|
||||
|
||||
msgid "The main list does not contain services for this bouquet!"
|
||||
msgstr "A lista pricipal no tem serviços em esta ramo!"
|
||||
|
||||
msgid "No bouquet file is selected!"
|
||||
msgstr "Nemhuma ficheiro de ramo foi selecionado!"
|
||||
|
||||
msgid "Remove all unused"
|
||||
msgstr "Remova todos os não utilizados"
|
||||
|
||||
msgid "Test"
|
||||
msgstr "Test"
|
||||
|
||||
msgid "Test connection"
|
||||
msgstr "Testar a conexão"
|
||||
|
||||
msgid "Double click on the service in the bouquet list:"
|
||||
msgstr "Clique duas vezes no serviço na lista de ramos:"
|
||||
|
||||
msgid "Zap"
|
||||
msgstr "Zap"
|
||||
|
||||
msgid "Play stream"
|
||||
msgstr "Play stream"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "Desativado"
|
||||
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Ativar ver. 5 suporte (experimental)"
|
||||
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "Ativar HTTP API (experimental)"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Mudar(zap) o canal(Ctrl + Z)"
|
||||
|
||||
msgid "Switch the channel and watch in the program(Ctrl + W)"
|
||||
msgstr "Troque o canal e ver no programa(Ctrl + W)."
|
||||
|
||||
msgid "Play IPTV or other stream in the program(Ctrl + P)"
|
||||
msgstr "Tocar IPTV ou outro fluxo no programa(Ctrl + P)"
|
||||
|
||||
msgid "Export to m3u"
|
||||
msgstr "Exportar na m3u"
|
||||
|
||||
msgid "EPG configuration"
|
||||
msgstr "Configuraçao EPG"
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Aplicar"
|
||||
|
||||
msgid "EPG source"
|
||||
msgstr "Fonte EPG"
|
||||
|
||||
msgid "Service names source:"
|
||||
msgstr "Fonte de nomes de serviço:"
|
||||
|
||||
msgid "Main service list"
|
||||
msgstr "Lista de serviço principal:"
|
||||
|
||||
msgid "XML file"
|
||||
msgstr "Arquivo XML"
|
||||
|
||||
msgid "Use web source"
|
||||
msgstr "Usar fonte web"
|
||||
|
||||
msgid "Url to *.xml.gz file:"
|
||||
msgstr "Url para o arquivo *.xml.gz:"
|
||||
|
||||
msgid "Enable filtering"
|
||||
msgstr "Ativar filtragem"
|
||||
|
||||
msgid "Filter by presence in the epg.dat file."
|
||||
msgstr "Filtrar por presença no arquivo epg.dat."
|
||||
|
||||
msgid "Paths to the epg.dat file:"
|
||||
msgstr "Ruta para o arquivo epg.dat:"
|
||||
|
||||
msgid "Local path:"
|
||||
msgstr "Ruta local:"
|
||||
|
||||
msgid "STB path:"
|
||||
msgstr "Ruta STB:"
|
||||
|
||||
msgid "Update on start"
|
||||
msgstr "Atualizar no início"
|
||||
|
||||
msgid "Auto configuration by service names."
|
||||
msgstr "Configuração automática por nomes de serviço."
|
||||
|
||||
msgid "Save list to xml."
|
||||
msgstr "Salvar lista para XML."
|
||||
|
||||
msgid "Download XML file error."
|
||||
msgstr "Baixe o erro de arquivo XML."
|
||||
|
||||
msgid "Unsupported file type:"
|
||||
msgstr "Tipo de arquivo não suportado:"
|
||||
|
||||
msgid "Unpacking data error."
|
||||
msgstr "Descompactando o erro de dados."
|
||||
|
||||
msgid "XML parsing error:"
|
||||
msgstr "Erro de análise XML:"
|
||||
|
||||
msgid "Count of successfully configured services:"
|
||||
msgstr "Contagem de serviços configurados com sucesso:"
|
||||
|
||||
msgid "Current epg.dat file does not contains references for the services of this bouquet!"
|
||||
msgstr "O arquivo epg.dat não contém referências para os serviços deste buquê!"
|
||||
|
||||
msgid "Use HTTP"
|
||||
msgstr "Use HTTP"
|
||||
|
||||
msgid "Close playback"
|
||||
msgstr "Fechar reprodução"
|
||||
|
||||
msgid "Import YouTube playlist"
|
||||
msgstr "Importar YouTube playlist"
|
||||
|
||||
msgid "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
|
||||
msgstr "Encontrou um link para o recurso do YouTube!\nTentar obter um link direto para o vídeo?"
|
||||
|
||||
msgid "Playlist import"
|
||||
msgstr "Importação de lista de reprodução"
|
||||
|
||||
msgid "Getting link error:"
|
||||
msgstr "Obtendo erro de link:"
|
||||
@@ -1,10 +1,10 @@
|
||||
# Copyright (C) 2018 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2019 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Last-Translator: Dmitry Yefremov\n"
|
||||
"Last-Translator: Dmitriy Yefremov\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -68,9 +68,6 @@ msgstr "Загрузить"
|
||||
msgid "Edit"
|
||||
msgstr "Изменить"
|
||||
|
||||
msgid "Edit "
|
||||
msgstr "Изменить"
|
||||
|
||||
msgid "Edit mаrker text"
|
||||
msgstr "Изменить текст маркера"
|
||||
|
||||
@@ -571,16 +568,198 @@ msgstr "Текущий список не содержит потоков IPTV!"
|
||||
msgid "New empty configuration"
|
||||
msgstr "Новая конфигурация"
|
||||
|
||||
msgid "No data to save!"
|
||||
msgstr "Нет данных для сохранения!"
|
||||
|
||||
msgid "Network"
|
||||
msgstr "Сеть"
|
||||
|
||||
msgid "Paths"
|
||||
msgstr "Пути"
|
||||
|
||||
msgid "Program"
|
||||
msgstr "Программа"
|
||||
|
||||
msgid "Backup:"
|
||||
msgstr "Резервное копирование:"
|
||||
|
||||
msgid "Backup"
|
||||
msgstr "Резервное копирование"
|
||||
|
||||
msgid "Backups"
|
||||
msgstr "Резервные копии"
|
||||
|
||||
msgid "Backup path:"
|
||||
msgstr "Путь к резервным копиям:"
|
||||
|
||||
msgid "Restore bouquets"
|
||||
msgstr "Восстановить букеты"
|
||||
|
||||
msgid "Restore all"
|
||||
msgstr "Восстановить все"
|
||||
|
||||
msgid "Before saving"
|
||||
msgstr "Перед сохранением"
|
||||
|
||||
msgid "Before downloading from the receiver"
|
||||
msgstr "Перед загрузкой с ресивера"
|
||||
|
||||
msgid "Set background color for the services"
|
||||
msgstr "Установить цвет фона для сервисов"
|
||||
|
||||
msgid "Marked as new:"
|
||||
msgstr "Помеченные как новые:"
|
||||
|
||||
msgid "With an extra name in the bouquet:"
|
||||
msgstr "С пользовательским именем в букете:"
|
||||
|
||||
msgid "Select"
|
||||
msgstr "Выбрать"
|
||||
|
||||
msgid "About"
|
||||
msgstr "О программе"
|
||||
|
||||
msgid "Exit"
|
||||
msgstr "Выход"
|
||||
|
||||
msgid "Tools"
|
||||
msgstr "Инструменты"
|
||||
|
||||
#Import
|
||||
msgid "Import"
|
||||
msgstr "Импорт"
|
||||
|
||||
msgid "Bouquet"
|
||||
msgstr "Букета"
|
||||
|
||||
msgid "Bouquets and services"
|
||||
msgstr "Букетов и сервисов"
|
||||
|
||||
msgid "The main list does not contain services for this bouquet!"
|
||||
msgstr "Основной список не содержит сервисов для данного букета!"
|
||||
|
||||
msgid "No bouquet file is selected!"
|
||||
msgstr "Не выбран файл букета!"
|
||||
|
||||
msgid "Remove all unused"
|
||||
msgstr "Удалить все неиспользуемые"
|
||||
|
||||
msgid "Test"
|
||||
msgstr "Тестировать"
|
||||
|
||||
msgid "Test connection"
|
||||
msgstr "Тестировать соединение"
|
||||
|
||||
msgid "Double click on the service in the bouquet list:"
|
||||
msgstr "Двойной клик по сервису в списке букетов:"
|
||||
|
||||
msgid "Zap"
|
||||
msgstr "Переключить"
|
||||
|
||||
msgid "Play stream"
|
||||
msgstr "Воспр. потока"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "Выкл."
|
||||
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Включить поддержку вер. 5 (экспериментально)"
|
||||
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "Включить HTTP API (экспериментально)"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Переключить канал(Ctrl + Z)"
|
||||
|
||||
msgid "Switch the channel and watch in the program(Ctrl + W)"
|
||||
msgstr "Переклють канал и просмотр в программе(Ctrl + W)."
|
||||
|
||||
msgid "Play IPTV or other stream in the program(Ctrl + P)"
|
||||
msgstr "Воспроизведение IPTV или другого потока в программе(Ctrl + P)"
|
||||
|
||||
msgid "Export to m3u"
|
||||
msgstr "Экспорт в m3u"
|
||||
|
||||
msgid "EPG configuration"
|
||||
msgstr "Конфигурация EPG"
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Применить"
|
||||
|
||||
msgid "EPG source"
|
||||
msgstr "Источник EPG"
|
||||
|
||||
msgid "Service names source:"
|
||||
msgstr "Источник имен сервисов:"
|
||||
|
||||
msgid "Main service list"
|
||||
msgstr "Основной список сервисов:"
|
||||
|
||||
msgid "XML file"
|
||||
msgstr "Файл XML"
|
||||
|
||||
msgid "Use web source"
|
||||
msgstr "Использовать веб-источник"
|
||||
|
||||
msgid "Url to *.xml.gz file:"
|
||||
msgstr "URL к файлу *.xml.gz:"
|
||||
|
||||
msgid "Enable filtering"
|
||||
msgstr "Включить фильтрацию"
|
||||
|
||||
msgid "Filter by presence in the epg.dat file."
|
||||
msgstr "Фильтровать по наличию в файле epg.dat."
|
||||
|
||||
msgid "Paths to the epg.dat file:"
|
||||
msgstr "Пути к файлу epg.dat:"
|
||||
|
||||
msgid "Local path:"
|
||||
msgstr "Локальный путь:"
|
||||
|
||||
msgid "STB path:"
|
||||
msgstr "Путь в ресивере:"
|
||||
|
||||
msgid "Update on start"
|
||||
msgstr "Обновлять при запуске"
|
||||
|
||||
msgid "Auto configuration by service names."
|
||||
msgstr "Автонастройка по именам сервисов."
|
||||
|
||||
msgid "Save list to xml."
|
||||
msgstr "Сохранить список в XML."
|
||||
|
||||
msgid "Download XML file error."
|
||||
msgstr "Ошибка загрузки XML-файла."
|
||||
|
||||
msgid "Unsupported file type:"
|
||||
msgstr "Неподдерживаемый тип файла:"
|
||||
|
||||
msgid "Unpacking data error."
|
||||
msgstr "Ошибка распаковки данных."
|
||||
|
||||
msgid "XML parsing error:"
|
||||
msgstr "Ошибка парсинга XML:"
|
||||
|
||||
msgid "Count of successfully configured services:"
|
||||
msgstr "Количество успешно сконфигурированных сервисов:"
|
||||
|
||||
msgid "Current epg.dat file does not contains references for the services of this bouquet!"
|
||||
msgstr "Текущий файл epg.dat не содержит ссылок на сервисы данного букета!"
|
||||
|
||||
msgid "Use HTTP"
|
||||
msgstr "Использовать HTTP"
|
||||
|
||||
msgid "Close playback"
|
||||
msgstr "Закрыть воспроизведение"
|
||||
|
||||
msgid "Import YouTube playlist"
|
||||
msgstr "Импорт плейлиста YouTube"
|
||||
|
||||
msgid "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
|
||||
msgstr "Найдена ссылка на ресурс YouTube!\nПопробовать получить прямую ссылку на видео?"
|
||||
|
||||
msgid "Playlist import"
|
||||
msgstr "Импорт плейлиста"
|
||||
|
||||
msgid "Getting link error:"
|
||||
msgstr "Ошибка получения ссылки:"
|
||||
|
||||
Reference in New Issue
Block a user