mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-08 15:47:08 +02:00
Compare commits
191 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93fd3cd2c3 | ||
|
|
03c291a61e | ||
|
|
97d9ce8b68 | ||
|
|
1a55df6674 | ||
|
|
6ac10c1380 | ||
|
|
13270b6152 | ||
|
|
b2c0359017 | ||
|
|
8a6dd1da93 | ||
|
|
2e1410ca36 | ||
|
|
3d96181450 | ||
|
|
fe749ca594 | ||
|
|
1b6cd58112 | ||
|
|
8d405d223a | ||
|
|
81ad19043a | ||
|
|
34db58f8e0 | ||
|
|
890163af4a | ||
|
|
c4e8a6646d | ||
|
|
639c8511bf | ||
|
|
5e082fc5d7 | ||
|
|
7f393ff9ba | ||
|
|
b37aac0cd9 | ||
|
|
d857c4b786 | ||
|
|
6ffd1d7926 | ||
|
|
415ad79c80 | ||
|
|
da4fef7f6b | ||
|
|
76c034435d | ||
|
|
f9239f0642 | ||
|
|
7f096df998 | ||
|
|
3f0738d874 | ||
|
|
b310b640b4 | ||
|
|
18caa58336 | ||
|
|
03e5909c23 | ||
|
|
d9071632d2 | ||
|
|
694269113a | ||
|
|
78f347a505 | ||
|
|
eb9be7b190 | ||
|
|
952aeb4d22 | ||
|
|
8a865513b3 | ||
|
|
30e38dde3f | ||
|
|
5f68eb0f1a | ||
|
|
ce92134a00 | ||
|
|
2c80d13170 | ||
|
|
a49d6490c5 | ||
|
|
dc76a7801e | ||
|
|
0b0d3ded8c | ||
|
|
c05dd026fb | ||
|
|
dca94271b0 | ||
|
|
8d7aa8736e | ||
|
|
1d693670f4 | ||
|
|
52f50cdaf5 | ||
|
|
d2ac5d5ac4 | ||
|
|
fe579358f6 | ||
|
|
db942ee10b | ||
|
|
b4648a6efd | ||
|
|
647f468feb | ||
|
|
ef608df76b | ||
|
|
8e32373a99 | ||
|
|
7f817944fa | ||
|
|
7752da92b1 | ||
|
|
24023d438d | ||
|
|
9e0d8840f3 | ||
|
|
d762f097d0 | ||
|
|
336aa47177 | ||
|
|
1eeccd654a | ||
|
|
e37abef359 | ||
|
|
c120f42ee1 | ||
|
|
1531548e51 | ||
|
|
72ebdceb6e | ||
|
|
791fa2b5f6 | ||
|
|
47df44c202 | ||
|
|
d7635370ba | ||
|
|
1d577750c0 | ||
|
|
b56685edb1 | ||
|
|
f7f230f40e | ||
|
|
88e19e2fd1 | ||
|
|
ae6f0e1ae2 | ||
|
|
c3e880890e | ||
|
|
a7edb6d0f6 | ||
|
|
03e18401cc | ||
|
|
1d6b8c2558 | ||
|
|
ccd111cd94 | ||
|
|
e8f3b5df8a | ||
|
|
c4c9c73809 | ||
|
|
a4514ebb2b | ||
|
|
7b44df9afd | ||
|
|
3a018e9654 | ||
|
|
9d4e571d89 | ||
|
|
f62184c96c | ||
|
|
320183554c | ||
|
|
a525816eca | ||
|
|
0e11a223ad | ||
|
|
6e4b992a79 | ||
|
|
1164d38e5c | ||
|
|
4288d62a53 | ||
|
|
b17bd13fb5 | ||
|
|
7124fd6a92 | ||
|
|
81f354207d | ||
|
|
15cb611764 | ||
|
|
6115433aba | ||
|
|
0c5b9165ef | ||
|
|
c432646f30 | ||
|
|
f70913832c | ||
|
|
25ee7f3538 | ||
|
|
5a76601ae6 | ||
|
|
4367fe6ead | ||
|
|
242642a7ed | ||
|
|
074fc960e5 | ||
|
|
e26d08ca8e | ||
|
|
8c433680a9 | ||
|
|
547046bddb | ||
|
|
12983bb1a6 | ||
|
|
5dc20232ef | ||
|
|
0040ecee32 | ||
|
|
e8f30b667d | ||
|
|
99a9f081fa | ||
|
|
51605ae680 | ||
|
|
ca06400071 | ||
|
|
588df32b2f | ||
|
|
ab7f560b4f | ||
|
|
ffce103eae | ||
|
|
90418f0e28 | ||
|
|
0daaf6d1e5 | ||
|
|
47f26b0f4c | ||
|
|
e67ce41667 | ||
|
|
0cff24486a | ||
|
|
850ba0d96a | ||
|
|
303c9a0267 | ||
|
|
0f95165088 | ||
|
|
105cf9c90c | ||
|
|
cb40a8d0de | ||
|
|
f19ab37bc8 | ||
|
|
d716bd6a86 | ||
|
|
5b13b22823 | ||
|
|
49076fe477 | ||
|
|
50a517b6f1 | ||
|
|
184c3b18ba | ||
|
|
2f8dcaf47b | ||
|
|
dbe18b345f | ||
|
|
5d68ec8176 | ||
|
|
83e58f9375 | ||
|
|
fe199d78a4 | ||
|
|
d5f7acb019 | ||
|
|
a141c34ee7 | ||
|
|
25c9189e1a | ||
|
|
d9390aa7be | ||
|
|
e12cc86e5f | ||
|
|
f1ef9fe4aa | ||
|
|
728bfd0b20 | ||
|
|
1d6022b6db | ||
|
|
8609d30ac9 | ||
|
|
fde06dca89 | ||
|
|
e41bf5f58f | ||
|
|
b1488df9ce | ||
|
|
c6e4b3624b | ||
|
|
26b843921b | ||
|
|
e73638d006 | ||
|
|
cf3c05f324 | ||
|
|
030b7c4957 | ||
|
|
d7ed3e20a4 | ||
|
|
c69b0ac9e1 | ||
|
|
c5c88a8958 | ||
|
|
24c064b450 | ||
|
|
240d724b59 | ||
|
|
5b410241a9 | ||
|
|
a6ffe4999a | ||
|
|
f67a79e869 | ||
|
|
d37c088112 | ||
|
|
adf117c88d | ||
|
|
98da7acd96 | ||
|
|
c82763081a | ||
|
|
1a39557964 | ||
|
|
c274c9e91d | ||
|
|
dd1ec89592 | ||
|
|
8ce9823a0c | ||
|
|
cc08fa8096 | ||
|
|
dfdf0f9d3a | ||
|
|
a3cf34ba2a | ||
|
|
0f02055c0c | ||
|
|
347dd15233 | ||
|
|
6dccdc258a | ||
|
|
c8c9a0bbf0 | ||
|
|
4dfa126795 | ||
|
|
9a0aa1e28f | ||
|
|
0fb708ca9b | ||
|
|
74d4c9e038 | ||
|
|
f229169d29 | ||
|
|
8263f39591 | ||
|
|
cf25057658 | ||
|
|
300fedf684 | ||
|
|
5e954c7ec9 | ||
|
|
d723ecd7f7 |
@@ -1,8 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Name=DemonEditor
|
||||
Comment=Channels and satellites editor for Enigma2
|
||||
Comment[ru]=Редактор каналов и спутников для Enigma2
|
||||
Comment=Channels and satellites list editor for Enigma2
|
||||
Comment[ru]=Редактор списка каналов и спутников для Enigma2
|
||||
Icon=accessories-text-editor
|
||||
Exec=bash -c 'cd $(dirname %k) && ./start.py'
|
||||
Terminal=false
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Dmitriy Yefremov
|
||||
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
|
||||
|
||||
47
README.md
47
README.md
@@ -1,23 +1,36 @@
|
||||
# DemonEditor
|
||||
Enigma2 channel and satellites list editor for GNU/Linux.
|
||||
Focused on the convenience of working in lists from the keyboard.
|
||||
The mouse is also fully supported (Drag and Drop etc)
|
||||
|
||||
Keyboard shortcuts:
|
||||
Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
|
||||
Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
|
||||
Ctrl + X - only in bouquet list. Ctrl + C - only in services list.
|
||||
Clipboard is "rubber". There is an accumulation before the insertion!
|
||||
Ctrl + E, F2 - edit/rename.
|
||||
Ctrl + S, T, E in Satellites edit tool for create and edit satellite or transponder.
|
||||
Ctrl + L - parental lock.
|
||||
Ctrl + H - hide/skip.
|
||||
## Enigma2 channel and satellites list editor for GNU/Linux.
|
||||
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
|
||||
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
|
||||
### Keyboard shortcuts:
|
||||
**Ctrl + X, C, V, Up, Down, PageUp, PageDown, Home, End, S, T, E, L, H, Space; Insert, Delete, F2, Enter, P.**
|
||||
* **Insert** - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
|
||||
* **Ctrl + X** - only in bouquet list. **Ctrl + C** - only in services list.
|
||||
Clipboard is **"rubber"**. There is an accumulation before the insertion!
|
||||
* **Ctrl + E** - edit.
|
||||
* **Ctrl + R, F2** - rename.
|
||||
* **Ctrl + S, T** in Satellites edit tool for create satellite or transponder.
|
||||
* **Ctrl + L** - parental lock.
|
||||
* **Ctrl + H** - hide/skip.
|
||||
* **P** - enable/disable preview mode for IPTV in the bouquet list.
|
||||
* **Enter** - start play IPTV or other stream in the bouquet list.
|
||||
* **Space** - select/deselect.
|
||||
* **Left/Right** - remove selection.
|
||||
* **Ctrl + Up, Down, PageUp, PageDown, Home, End** - move selected items in the list.
|
||||
### 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).
|
||||
### Minimum requirements:
|
||||
Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
|
||||
#### Note.
|
||||
To create a simple debian package, you can use the build-deb.sh
|
||||
|
||||
Ability to import IPTV into bouquet from m3u files!
|
||||
|
||||
Tests only on OpenPLi based image with GM 990 Spark Reloaded receiver
|
||||
Tests only in image based on OpenPLi or last BPanther(neutrino) images with GM 990 Spark Reloaded receiver
|
||||
in my preferred linux distro (Last Linux Mint 18.* - MATE 64-bit)!
|
||||
|
||||
Minimum requirements: Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
|
||||
**Terrestrial and cable channels at the moment are not supported!**
|
||||
|
||||
|
||||
Terrestrial and cable channels at the moment are not supported!
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
""" This module only for common constants """
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Type(Enum):
|
||||
""" Types of DVB transponders """
|
||||
Satellite = "s"
|
||||
Terestrial = "t"
|
||||
Cable = "c"
|
||||
|
||||
|
||||
class FLAG(Enum):
|
||||
""" Service flags """
|
||||
KEEP = 1 # Do not automatically update the services parameters.
|
||||
HIDE = 2
|
||||
PIDS = 4 # Always use the cached instead of current pids.
|
||||
LOCK = 8
|
||||
NEW = 40 # Marked as new at the last scan
|
||||
|
||||
@staticmethod
|
||||
def hide_values():
|
||||
return 2, 3, 6, 7, 10, 42, 43, 46, 47
|
||||
|
||||
|
||||
POLARIZATION = {"0": "H", "1": "V", "2": "L", "3": "R"}
|
||||
|
||||
PLS_MODE = {"0": "Root", "1": "Gold", "2": "Combo"}
|
||||
|
||||
FEC = {"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", "15": None}
|
||||
|
||||
SYSTEM = {"0": "DVB-S", "1": "DVB-S2"}
|
||||
|
||||
MODULATION = {"0": "Auto", "1": "QPSK", "2": "8PSK", "3": "16APSK", "5": "32APSK"}
|
||||
|
||||
SERVICE_TYPE = {"-2": "Unknown", "1": "TV", "2": "Radio", "3": "Data",
|
||||
"10": "Radio", "12": "Data", "22": "TV", "25": "TV (HD)",
|
||||
"136": "Data", "139": "Data"}
|
||||
|
||||
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",
|
||||
"C:0E00": "PowerVu", "C:4ae0": "DRE-Crypt", "C:4ae1": "DRE-Crypt", "C:7be1": "DRE-Crypt"}
|
||||
@@ -1,10 +1,44 @@
|
||||
from .lamedb import get_channels, write_channels, Channel
|
||||
from .bouquets import get_bouquets, write_bouquets, to_bouquet_id, Bouquet, Bouquets
|
||||
from .satxml import get_satellites, write_satellites, Satellite, Transponder
|
||||
from .blacklist import get_blacklist, write_blacklist
|
||||
from app.commons import run_task
|
||||
from app.properties import Profile
|
||||
from .ecommons import Service, Satellite, Transponder, Bouquet, Bouquets, is_transponder_valid
|
||||
from .enigma.blacklist import get_blacklist, write_blacklist
|
||||
from .enigma.bouquets import get_bouquets as get_enigma_bouquets, write_bouquets as write_enigma_bouquets, to_bouquet_id
|
||||
from .enigma.lamedb import get_services as get_enigma_services, write_services as write_enigma_services
|
||||
from .iptv import parse_m3u
|
||||
from .neutrino.bouquets import get_bouquets as get_neutrino_bouquets, write_bouquets as write_neutrino_bouquets
|
||||
from .neutrino.services import get_services as get_neutrino_services, write_services as write_neutrino_services
|
||||
from .satxml import get_satellites, write_satellites
|
||||
|
||||
|
||||
def get_services(data_path, profile, format_version):
|
||||
if profile is Profile.ENIGMA_2:
|
||||
return get_enigma_services(data_path, format_version)
|
||||
elif profile is Profile.NEUTRINO_MP:
|
||||
return get_neutrino_services(data_path)
|
||||
|
||||
|
||||
@run_task
|
||||
def write_services(path, channels, profile, format_version):
|
||||
if profile is Profile.ENIGMA_2:
|
||||
write_enigma_services(path, channels, format_version)
|
||||
elif profile is Profile.NEUTRINO_MP:
|
||||
write_neutrino_services(path, channels)
|
||||
|
||||
|
||||
def get_bouquets(path, profile):
|
||||
if profile is Profile.ENIGMA_2:
|
||||
return get_enigma_bouquets(path)
|
||||
elif profile is Profile.NEUTRINO_MP:
|
||||
return get_neutrino_bouquets(path)
|
||||
|
||||
|
||||
@run_task
|
||||
def write_bouquets(path, bouquets, profile):
|
||||
if profile is Profile.ENIGMA_2:
|
||||
write_enigma_bouquets(path, bouquets)
|
||||
elif profile is Profile.NEUTRINO_MP:
|
||||
write_neutrino_bouquets(path, bouquets)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
|
||||
167
app/eparser/ecommons.py
Normal file
167
app/eparser/ecommons.py
Normal file
@@ -0,0 +1,167 @@
|
||||
""" Common elements module """
|
||||
from collections import namedtuple
|
||||
from enum import Enum
|
||||
|
||||
Service = namedtuple("Service", ["flags_cas", "transponder_type", "coded", "service", "locked", "hide", "package",
|
||||
"service_type", "picon", "picon_id", "ssid", "freq", "rate", "pol", "fec",
|
||||
"system", "pos", "data_id", "fav_id", "transponder"])
|
||||
|
||||
|
||||
# ***************** Bouquets *******************#
|
||||
|
||||
class BqServiceType(Enum):
|
||||
DEFAULT = "DEFAULT"
|
||||
IPTV = "IPTV"
|
||||
MARKER = "MARKER" # 64
|
||||
|
||||
|
||||
Bouquet = namedtuple("Bouquet", ["name", "type", "services", "locked", "hidden"])
|
||||
Bouquets = namedtuple("Bouquets", ["name", "type", "bouquets"])
|
||||
BouquetService = namedtuple("BouquetService", ["name", "type", "data", "num"])
|
||||
|
||||
# ***************** Satellites *******************#
|
||||
|
||||
Satellite = namedtuple("Satellite", ["name", "flags", "position", "transponders"])
|
||||
|
||||
Transponder = namedtuple("Transponder", ["frequency", "symbol_rate", "polarization", "fec_inner",
|
||||
"system", "modulation", "pls_mode", "pls_code", "is_id"])
|
||||
|
||||
|
||||
class TrType(Enum):
|
||||
""" Transponders type """
|
||||
Satellite = "s"
|
||||
Terestrial = "t"
|
||||
Cable = "c"
|
||||
|
||||
|
||||
class BqType(Enum):
|
||||
""" Bouquet type"""
|
||||
BOUQUET = "bouquet"
|
||||
TV = "tv"
|
||||
RADIO = "radio"
|
||||
WEBTV = "webtv"
|
||||
|
||||
|
||||
class Flag(Enum):
|
||||
""" Service flags
|
||||
|
||||
K - last bit (1)
|
||||
H - second from end (10)
|
||||
P - third (100)
|
||||
N - sixth (100000)
|
||||
"""
|
||||
KEEP = 1 # Do not automatically update the services parameters.
|
||||
HIDE = 2
|
||||
PIDS = 4 # Always use the cached instead of current pids.
|
||||
LOCK = 8
|
||||
NEW = 40 # Marked as new at the last scan
|
||||
|
||||
@staticmethod
|
||||
def is_hide(value: int):
|
||||
return value & 1 << 1
|
||||
|
||||
@staticmethod
|
||||
def is_keep(value: int):
|
||||
return value & 1 << 0
|
||||
|
||||
@staticmethod
|
||||
def is_pids(value: int):
|
||||
return value & 1 << 2
|
||||
|
||||
@staticmethod
|
||||
def is_new(value: int):
|
||||
return value & 1 << 5
|
||||
|
||||
|
||||
class Pids(Enum):
|
||||
VIDEO = "c:00"
|
||||
AUDIO = "c:01"
|
||||
TELETEXT = "c:02"
|
||||
PCR = "c:03"
|
||||
AC3 = "c:04"
|
||||
VIDEO_TYPE = "c:05"
|
||||
AUDIO_CHANNEL = "c:06"
|
||||
BIT_STREAM_DELAY = "c:07" # in ms
|
||||
PCM_DELAY = "c:08" # in ms
|
||||
SUBTITLE = "c:09"
|
||||
|
||||
|
||||
class Inversion(Enum):
|
||||
Off = "0"
|
||||
On = "1"
|
||||
Auto = "2"
|
||||
|
||||
|
||||
class Pilot(Enum):
|
||||
Off = "0"
|
||||
On = "1"
|
||||
Auto = "2"
|
||||
|
||||
|
||||
ROLL_OFF = {"0": "35%", "1": "25%", "2": "20%", "3": "Auto"}
|
||||
|
||||
POLARIZATION = {"0": "H", "1": "V", "2": "L", "3": "R"}
|
||||
|
||||
PLS_MODE = {"0": "Root", "1": "Gold", "2": "Combo"}
|
||||
|
||||
FEC = {"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", "10": "1/2", "11": "2/3", "12": "3/4", "13": "5/6", "14": "7/8", "15": "8/9", "16": "3/5",
|
||||
"17": "4/5", "18": "9/10", "19": "1/2", "20": "2/3", "21": "3/4", "22": "5/6", "23": "7/8", "24": "8/9",
|
||||
"25": "3/5", "26": "4/5", "27": "9/10", "28": "Auto"}
|
||||
|
||||
FEC_DEFAULT = {"0": "Auto", "1": "1/2", "2": "2/3", "3": "3/4", "4": "5/6", "5": "7/8", "6": "8/9", "7": "3/5",
|
||||
"8": "4/5", "9": "9/10"}
|
||||
|
||||
SYSTEM = {"0": "DVB-S", "1": "DVB-S2"}
|
||||
|
||||
MODULATION = {"0": "Auto", "1": "QPSK", "2": "8PSK", "3": "16APSK", "5": "32APSK"}
|
||||
|
||||
SERVICE_TYPE = {"-2": "Data", "1": "TV", "2": "Radio", "3": "Data", "10": "Radio", "22": "TV (H264)",
|
||||
"25": "TV (HD)", "31": "TV (UHD)"}
|
||||
|
||||
CAS = {"C:2600": "BISS", "C:0b00": "Conax", "C:0b01": "Conax", "C:0b02": "Conax", "C:0baa": "Conax", "C:0602": "Irdeto",
|
||||
"C:0604": "Irdeto", "C:0606": "Irdeto", "C:0608": "Irdeto", "C:0622": "Irdeto", "C:0626": "Irdeto",
|
||||
"C:0664": "Irdeto", "C:0614": "Irdeto", "C:0692": "Irdeto", "C:1801": "Nagravision", "C:0500": "Viaccess",
|
||||
"C:0E00": "PowerVu", "C:4ae0": "DRE-Crypt", "C:4ae1": "DRE-Crypt", "C:7be1": "DRE-Crypt"}
|
||||
|
||||
# 'on' attribute 0070(hex) = 112(int) = ONID(ONID-TID on www.lyngsat.com)
|
||||
PROVIDER = {112: "HTB+", 253: "Tricolor TV"}
|
||||
|
||||
|
||||
# ************* subsidiary functions ****************
|
||||
|
||||
def get_key_by_value(dc: dict, value):
|
||||
""" Returns key from dict by value """
|
||||
for k, v in dc.items():
|
||||
if v == value:
|
||||
return k
|
||||
|
||||
|
||||
def get_value_by_name(en, name):
|
||||
""" Returns value by name from enums """
|
||||
for n in en:
|
||||
if n.name == name:
|
||||
return n.value
|
||||
|
||||
|
||||
def is_transponder_valid(tr: Transponder):
|
||||
""" Checks transponder validity """
|
||||
try:
|
||||
int(tr.frequency)
|
||||
int(tr.symbol_rate)
|
||||
tr.pls_mode is None or int(tr.pls_mode)
|
||||
tr.pls_code is None or int(tr.pls_code)
|
||||
tr.is_id is None or int(tr.is_id)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
if tr.polarization not in POLARIZATION.values():
|
||||
return False
|
||||
if tr.fec_inner not in FEC.values():
|
||||
return False
|
||||
if tr.system not in SYSTEM.values():
|
||||
return False
|
||||
if tr.modulation not in MODULATION.values():
|
||||
return False
|
||||
|
||||
return True
|
||||
0
app/eparser/enigma/__init__.py
Normal file
0
app/eparser/enigma/__init__.py
Normal file
@@ -1,28 +1,16 @@
|
||||
""" Module for parsing bouquets """
|
||||
from collections import namedtuple
|
||||
from enum import Enum
|
||||
from app.eparser.ecommons import BqServiceType, BouquetService, Bouquets, Bouquet, BqType
|
||||
|
||||
_BOUQUETS_PATH = "../data/"
|
||||
_TV_ROOT_FILE_NAME = "bouquets.tv"
|
||||
_RADIO_ROOT_FILE_NAME = "bouquets.radio"
|
||||
|
||||
|
||||
class BqServiceType(Enum):
|
||||
DEFAULT = "DEFAULT"
|
||||
IPTV = "IPTV"
|
||||
MARKER = "MARKER" # 64
|
||||
|
||||
|
||||
Bouquet = namedtuple("Bouquet", ["name", "type", "services"])
|
||||
Bouquets = namedtuple("Bouquets", ["name", "type", "bouquets"])
|
||||
BouquetService = namedtuple("BouquetService", ["name", "type", "data", "num"])
|
||||
|
||||
|
||||
def get_bouquets(path):
|
||||
return parse_bouquets(path, "bouquets.tv", "tv"), parse_bouquets(path, "bouquets.radio", "radio")
|
||||
return parse_bouquets(path, "bouquets.tv", BqType.TV.value), parse_bouquets(path, "bouquets.radio",
|
||||
BqType.RADIO.value)
|
||||
|
||||
|
||||
def write_bouquets(path, bouquets, bouquets_services):
|
||||
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'
|
||||
line = []
|
||||
|
||||
@@ -31,10 +19,10 @@ def write_bouquets(path, bouquets, bouquets_services):
|
||||
line.append("#NAME {}\n".format(bqs.name))
|
||||
|
||||
for bq in bqs.bouquets:
|
||||
line.append(srv_line.format(bq.name, bq.type))
|
||||
line.append(srv_line.format(bq.name.replace(" ", "_"), bq.type))
|
||||
write_bouquet(path, bq.name, bq.type, bq.services)
|
||||
|
||||
with open(path + "bouquets.{}".format(bqs.type), "w") as file:
|
||||
with open(path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
|
||||
file.writelines(line)
|
||||
|
||||
|
||||
@@ -50,44 +38,40 @@ def write_bouquet(path, name, bq_type, channels):
|
||||
else:
|
||||
bouquet.append("#SERVICE {}\n".format(to_bouquet_id(ch)))
|
||||
|
||||
with open(path + "userbouquet.{}.{}".format(name, bq_type), "w") as file:
|
||||
with open(path + "userbouquet.{}.{}".format(name.replace(" ", "_"), bq_type), "w", encoding="utf-8") as file:
|
||||
file.writelines(bouquet)
|
||||
|
||||
|
||||
def to_bouquet_id(ch):
|
||||
""" Creates bouquet channel id """
|
||||
data_type = ch.data_id
|
||||
if data_type:
|
||||
data_type = int(ch.data_id.split(":")[-2])
|
||||
if data_type == 22:
|
||||
data_type = 16
|
||||
elif data_type == 25:
|
||||
data_type = 19
|
||||
service = "{}:0:{}:{}:0:0:0:".format(1, data_type, ch.fav_id)
|
||||
if data_type and len(data_type) > 4:
|
||||
data_type = int(ch.data_id.split(":")[4])
|
||||
|
||||
return service
|
||||
return "{}:0:{:X}:{}:0:0:0:".format(1, data_type, ch.fav_id)
|
||||
|
||||
|
||||
def get_bouquet(path, name, bq_type):
|
||||
""" Parsing services ids from bouquet file """
|
||||
with open(path + "userbouquet.{}.{}".format(name, bq_type)) as file:
|
||||
with open(path + "userbouquet.{}.{}".format(name, bq_type), encoding="utf-8", errors="replace") as file:
|
||||
chs_list = file.read()
|
||||
services = []
|
||||
for ch in list(filter(lambda x: len(x) > 1, chs_list.split("#SERVICE")[1:])): # filtering ['']
|
||||
srvs = list(filter(None, chs_list.split("\n#SERVICE"))) # filtering ['']
|
||||
for ch in srvs[1:]:
|
||||
ch_data = ch.strip().split(":")
|
||||
if ch_data[1] == "64":
|
||||
services.append(BouquetService(ch_data[-1].split("\n")[0], BqServiceType.MARKER, ch, ch_data[2]))
|
||||
elif "http" in ch:
|
||||
services.append(BouquetService(ch_data[-1].split("\n")[0], BqServiceType.IPTV, ch, 0))
|
||||
else:
|
||||
services.append(BouquetService(None, BqServiceType.DEFAULT,
|
||||
"{}:{}:{}:{}".format(ch_data[3], ch_data[4], ch_data[5], ch_data[6]), 0))
|
||||
fav_id = "{}:{}:{}:{}".format(ch_data[3], ch_data[4], ch_data[5], ch_data[6])
|
||||
services.append(BouquetService(None, BqServiceType.DEFAULT, fav_id, 0))
|
||||
|
||||
return services
|
||||
return srvs[0].strip("#NAME").strip(), services
|
||||
|
||||
|
||||
def parse_bouquets(path, bq_name, bq_type):
|
||||
with open(path + bq_name) as file:
|
||||
with open(path + bq_name, encoding="utf-8", errors="replace") as file:
|
||||
lines = file.readlines()
|
||||
bouquets = None
|
||||
nm_sep = "#NAME"
|
||||
@@ -97,8 +81,12 @@ def parse_bouquets(path, bq_name, bq_type):
|
||||
_, _, name = line.partition(nm_sep)
|
||||
bouquets = Bouquets(name.strip(), bq_type, [])
|
||||
if bouquets and "#SERVICE" in line:
|
||||
name = line.split(".")[1]
|
||||
bouquets[2].append(Bouquet(name=name, type=bq_type, services=get_bouquet(path, name, bq_type)))
|
||||
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))
|
||||
|
||||
return bouquets
|
||||
|
||||
208
app/eparser/enigma/lamedb.py
Normal file
208
app/eparser/enigma/lamedb.py
Normal file
@@ -0,0 +1,208 @@
|
||||
""" This module used for parsing and write lamedb file
|
||||
|
||||
Currently implemented only for satellite channels!!!
|
||||
"""
|
||||
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
|
||||
|
||||
_HEADER = "eDVB services /{}/"
|
||||
_SEP = ":" # separator
|
||||
_FILE_NAME = "lamedb"
|
||||
_END_LINE = "# File was created in DemonEditor.\n# ....Enjoy watching!....\n"
|
||||
|
||||
|
||||
def get_services(path, format_version):
|
||||
return parse(path, format_version)
|
||||
|
||||
|
||||
def write_services(path, services, format_version=4):
|
||||
if format_version == 4:
|
||||
write_to_lamedb(path, services)
|
||||
elif format_version == 5:
|
||||
write_to_lamedb5(path, services)
|
||||
|
||||
|
||||
def write_to_lamedb(path, services):
|
||||
""" Writing lamedb file ver.4 """
|
||||
lines = [_HEADER.format(4), "\ntransponders\n"]
|
||||
tr_lines = []
|
||||
services_lines = ["end\nservices\n"]
|
||||
tr_set = set()
|
||||
|
||||
for srv in services:
|
||||
data_id = str(srv.data_id).split(_SEP)
|
||||
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
|
||||
if tr_id not in tr_set:
|
||||
transponder = "{}\n\t{}\n/\n".format(tr_id, srv.transponder)
|
||||
tr_lines.append(transponder)
|
||||
tr_set.add(tr_id)
|
||||
# Services
|
||||
services_lines.append("{}\n{}\n{}\n".format(srv.data_id, srv.service, srv.flags_cas))
|
||||
|
||||
tr_lines.sort()
|
||||
lines.extend(tr_lines)
|
||||
lines.extend(services_lines)
|
||||
lines.append("end\n" + _END_LINE)
|
||||
with open(path + _FILE_NAME, "w") as file:
|
||||
file.writelines(lines)
|
||||
|
||||
|
||||
def write_to_lamedb5(path, services):
|
||||
""" Writing lamedb5 file """
|
||||
lines = [_HEADER.format(5) + "\n"]
|
||||
services_lines = []
|
||||
tr_set = set()
|
||||
|
||||
for srv in services:
|
||||
data_id = str(srv.data_id).split(_SEP)
|
||||
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
|
||||
tr_set.add("t:{},{}\n".format(tr_id, srv.transponder.replace(" ", ":", 1)))
|
||||
services_lines.append("s:{},\"{}\",{}\n".format(srv.data_id, srv.service, srv.flags_cas))
|
||||
|
||||
lines.extend(sorted(tr_set))
|
||||
lines.extend(services_lines)
|
||||
lines.append(_END_LINE)
|
||||
|
||||
with open(path + "lamedb5", "w") as file:
|
||||
file.writelines(lines)
|
||||
|
||||
|
||||
def parse(path, version=4):
|
||||
""" Parsing lamedb """
|
||||
if version == 4:
|
||||
return parse_v4(path)
|
||||
elif version == 5:
|
||||
return parse_v5(path)
|
||||
raise SyntaxError("Unsupported version of the format.")
|
||||
|
||||
|
||||
def parse_v4(path):
|
||||
""" Parsing version 4 """
|
||||
with open(path + _FILE_NAME, "r", encoding="utf-8", errors="replace") as file:
|
||||
try:
|
||||
data = str(file.read())
|
||||
except UnicodeDecodeError as e:
|
||||
log("lamedb parse error: " + str(e))
|
||||
else:
|
||||
transponders, sep, services = data.partition("transponders") # 1 step
|
||||
if not transponders.endswith("/4/\n"):
|
||||
msg = "lamedb parsing error: unsupported format.\n Only version 4 is supported!"
|
||||
log(msg)
|
||||
raise SyntaxError(msg)
|
||||
transponders, sep, services = services.partition("services") # 2 step
|
||||
services, sep, _ = services.partition("\nend") # 3 step
|
||||
|
||||
return parse_services(services.split("\n"), parse_transponders(transponders.split("/")), path)
|
||||
|
||||
|
||||
def parse_v5(path):
|
||||
""" Parsing version 5 """
|
||||
with open(path + "lamedb5", "r", encoding="utf-8", errors="replace") as file:
|
||||
lns = file.readlines()
|
||||
|
||||
if lns and not lns[0].endswith("/5/\n"):
|
||||
raise SyntaxError("lamedb v.5 parsing error: unsupported format.")
|
||||
|
||||
trs, srvs = {}, [""]
|
||||
for l in lns:
|
||||
if l.startswith("s:"):
|
||||
srv_data = l.strip("s:").split(",", 2)
|
||||
srv_data[1], srv_data[2] = srv_data[1].strip("\""), srv_data[2].strip()
|
||||
srvs.extend(srv_data)
|
||||
elif l.startswith("t:"):
|
||||
tr, srv = l.split(",")
|
||||
trs[tr.strip("t:")] = srv.strip().replace(":", " ", 1)
|
||||
|
||||
return parse_services(srvs, trs, path)
|
||||
|
||||
|
||||
def parse_transponders(arg):
|
||||
""" Parsing transponders """
|
||||
transponders = {}
|
||||
for ar in arg:
|
||||
tr = ar.replace("\n", "").split("\t")
|
||||
if len(tr) == 2:
|
||||
transponders[tr[0]] = tr[1]
|
||||
|
||||
return transponders
|
||||
|
||||
|
||||
def parse_services(services, transponders, path):
|
||||
""" Parsing channels """
|
||||
channels = []
|
||||
blacklist = str(get_blacklist(path))
|
||||
srv = split(services, 3)
|
||||
if srv[0][0] == "": # remove first empty element
|
||||
srv.remove(srv[0])
|
||||
|
||||
for ch in srv:
|
||||
data = str(ch[0]).split(_SEP)
|
||||
sp = "0"
|
||||
tid = data[2]
|
||||
nid = data[3]
|
||||
transponder_id = "{}:{}:{}".format(data[1], tid, nid)
|
||||
transponder = transponders.get(transponder_id, None)
|
||||
|
||||
tid = tid.lstrip(sp).upper()
|
||||
nid = nid.lstrip(sp).upper()
|
||||
ssid = str(data[0]).lstrip(sp).upper()
|
||||
onid = str(data[1]).lstrip(sp).upper()
|
||||
# For comparison in bouquets. Needed in upper case!!!
|
||||
fav_id = "{}:{}:{}:{}".format(ssid, tid, nid, onid)
|
||||
picon_id = "1_0_{}_{}_{}_{}_{}_0_0_0.png".format(1, ssid, tid, nid, onid)
|
||||
|
||||
all_flags = ch[2].split(",")
|
||||
coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None
|
||||
flags = list(filter(lambda x: x.startswith("f:"), all_flags))
|
||||
hide = HIDE_ICON if flags and Flag.is_hide(int(flags[0][2:])) else None
|
||||
locked = LOCKED_ICON if fav_id in blacklist else None
|
||||
|
||||
package = list(filter(lambda x: x.startswith("p:"), all_flags))
|
||||
package = package[0][2:] if package else None
|
||||
|
||||
if transponder is not None:
|
||||
tr_type, sp, tr = str(transponder).partition(" ")
|
||||
tr = tr.split(_SEP)
|
||||
service_type = SERVICE_TYPE.get(data[4], SERVICE_TYPE["-2"])
|
||||
# removing all non printable symbols!
|
||||
srv_name = "".join(c for c in ch[1] if c.isprintable())
|
||||
channels.append(Service(flags_cas=ch[2],
|
||||
transponder_type=tr_type,
|
||||
coded=coded,
|
||||
service=srv_name,
|
||||
locked=locked,
|
||||
hide=hide,
|
||||
package=package,
|
||||
service_type=service_type,
|
||||
picon=None,
|
||||
picon_id=picon_id,
|
||||
ssid=data[0],
|
||||
freq=tr[0],
|
||||
rate=tr[1],
|
||||
pol=POLARIZATION[tr[2]],
|
||||
fec=FEC[tr[3]],
|
||||
system="DVB-S2" if len(tr) > 7 else "DVB-S",
|
||||
pos="{}.{}".format(tr[4][:-1], tr[4][-1:]),
|
||||
data_id=ch[0],
|
||||
fav_id=fav_id,
|
||||
transponder=transponder))
|
||||
return channels
|
||||
|
||||
|
||||
def split(itr, size):
|
||||
""" Divide the iterable. """
|
||||
srv = []
|
||||
tmp = []
|
||||
for i, line in enumerate(itr):
|
||||
tmp.append(line)
|
||||
if i % size == 0:
|
||||
srv.append(tuple(tmp))
|
||||
tmp.clear()
|
||||
|
||||
return srv
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -1,22 +1,40 @@
|
||||
from app.eparser.bouquets import BqServiceType
|
||||
from . import Channel
|
||||
""" Module for IPTV and streams support """
|
||||
from enum import Enum
|
||||
|
||||
from app.properties import Profile
|
||||
from app.ui.uicommons import IPTV_ICON
|
||||
from .ecommons import BqServiceType, Service
|
||||
|
||||
# url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group
|
||||
NEUTRINO_FAV_ID_FORMAT = "{}::{}::{}::{}::{}::{}::{}::{}::{}::{}"
|
||||
ENIGMA2_FAV_ID_FORMAT = " {}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTION: {}\n"
|
||||
|
||||
|
||||
def parse_m3u(path):
|
||||
class StreamType(Enum):
|
||||
DVB_TS = "1"
|
||||
NONE_TS = "4097"
|
||||
|
||||
|
||||
def parse_m3u(path, profile):
|
||||
with open(path) as file:
|
||||
aggr = [None] * 8
|
||||
aggr = [None] * 10
|
||||
channels = []
|
||||
count = 0
|
||||
name = None
|
||||
fav_id = None
|
||||
for line in file.readlines():
|
||||
if line.startswith("#EXTINF"):
|
||||
name = line[1 + line.index(","):].strip()
|
||||
count += 1
|
||||
elif count == 1:
|
||||
count = 0
|
||||
fav_id = " 1:0:1:0:0:0:0:0:0:0:{}:{}\n#DESCRIPTION: {}\n".format(
|
||||
line.strip().replace(":", "%3a"), name, name, None)
|
||||
channels.append(Channel(*aggr[0:3], name, *aggr[0:3], BqServiceType.IPTV.name, *aggr, fav_id, None))
|
||||
if profile is Profile.ENIGMA_2:
|
||||
fav_id = ENIGMA2_FAV_ID_FORMAT.format(StreamType.DVB_TS.value, 1, 0, 0, 0, 0,
|
||||
line.strip().replace(":", "%3a"), name, name, None)
|
||||
elif profile is Profile.NEUTRINO_MP:
|
||||
fav_id = NEUTRINO_FAV_ID_FORMAT.format(line.strip(), "", 0, None, None, None, None, "", "", 1)
|
||||
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], BqServiceType.IPTV.name, *aggr, fav_id, None)
|
||||
channels.append(srv)
|
||||
|
||||
return channels
|
||||
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
""" This module used for parsing lamedb file
|
||||
|
||||
Currently implemented only for satellite channels!!!
|
||||
Description of format taken from here: http://www.satsupreme.com/showthread.php/194074-Lamedb-format-explained
|
||||
"""
|
||||
from collections import namedtuple
|
||||
|
||||
from app.commons import log
|
||||
from app.eparser.__constants import POLARIZATION, SYSTEM, FEC, SERVICE_TYPE, FLAG
|
||||
from app.ui import CODED_ICON, LOCKED_ICON, HIDE_ICON
|
||||
from .blacklist import get_blacklist
|
||||
|
||||
_HEADER = "eDVB services /4/"
|
||||
_FILE_PATH = "../data/lamedb"
|
||||
_SEP = ":" # separator
|
||||
_FILE_NAME = "lamedb"
|
||||
|
||||
Channel = namedtuple("Channel", ["flags_cas", "transponder_type", "coded", "service", "locked", "hide",
|
||||
"package", "service_type", "ssid", "freq", "rate", "pol", "fec",
|
||||
"system", "pos", "data_id", "fav_id", "transponder"])
|
||||
|
||||
|
||||
def get_channels(path):
|
||||
return parse(path)
|
||||
|
||||
|
||||
def write_channels(path, channels):
|
||||
lines = [_HEADER, "\ntransponders\n"]
|
||||
tr_lines = []
|
||||
services_lines = ["end\nservices\n"]
|
||||
tr_set = set()
|
||||
|
||||
for ch in channels:
|
||||
data_id = str(ch.data_id).split(_SEP)
|
||||
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
|
||||
if tr_id not in tr_set:
|
||||
transponder = "{}\n\t{}\n/\n".format(tr_id, ch.transponder)
|
||||
tr_lines.append(transponder)
|
||||
tr_set.add(tr_id)
|
||||
# Services
|
||||
services_lines.append("{}\n{}\n{}\n".format(ch.data_id, ch.service, ch.flags_cas))
|
||||
|
||||
tr_lines.sort()
|
||||
lines.extend(tr_lines)
|
||||
lines.extend(services_lines)
|
||||
lines.append("end\nFile was created in DemonEditor.\n....Enjoy watching!....\n")
|
||||
|
||||
with open(path + _FILE_NAME, "w") as file:
|
||||
file.writelines(lines)
|
||||
|
||||
|
||||
def parse(path):
|
||||
""" Parsing lamedb """
|
||||
with open(path + _FILE_NAME, "r", encoding="utf-8", errors="replace") as file:
|
||||
try:
|
||||
data = str(file.read())
|
||||
except UnicodeDecodeError as e:
|
||||
log("lamedb parse error: " + str(e))
|
||||
else:
|
||||
transponders, sep, services = data.partition("transponders") # 1 step
|
||||
if not transponders.endswith("/4/\n"):
|
||||
msg = "lamedb parsing error: unsupported format.\n Only version 4 is supported!"
|
||||
log(msg)
|
||||
raise SyntaxError(msg)
|
||||
transponders, sep, services = services.partition("services") # 2 step
|
||||
services, sep, _ = services.partition("end") # 3 step
|
||||
|
||||
return parse_channels(services.split("\n"), transponders.split("/"), path)
|
||||
|
||||
|
||||
def parse_transponders(arg):
|
||||
""" Parsing transponders """
|
||||
transponders = {}
|
||||
for ar in arg:
|
||||
tr = ar.replace("\n", "").split("\t")
|
||||
if len(tr) == 2:
|
||||
transponders[tr[0]] = tr[1]
|
||||
|
||||
return transponders
|
||||
|
||||
|
||||
def parse_channels(services, transponders, path):
|
||||
""" Parsing channels """
|
||||
channels = []
|
||||
transponders = parse_transponders(transponders)
|
||||
blacklist = str(get_blacklist(path))
|
||||
|
||||
srv = split(services, 3)
|
||||
if srv[0][0] == "": # remove first empty element
|
||||
srv.remove(srv[0])
|
||||
|
||||
for ch in srv:
|
||||
data = str(ch[0]).split(_SEP)
|
||||
sp = "0"
|
||||
# For comparison in bouquets. Needed in upper case!!!
|
||||
fav_id = "{}:{}:{}:{}".format(str(data[0]).lstrip(sp), str(data[2]).lstrip(sp),
|
||||
str(data[3]).lstrip(sp), str(data[1]).lstrip(sp)).upper()
|
||||
all_flags = ch[2].split(",")
|
||||
coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None
|
||||
flags = list(filter(lambda x: x.startswith("f:"), all_flags))
|
||||
hide = HIDE_ICON if flags and int(flags[0][2:]) in FLAG.hide_values() else None
|
||||
locked = LOCKED_ICON if fav_id in blacklist else None
|
||||
|
||||
package = list(filter(lambda x: x.startswith("p:"), all_flags))
|
||||
package = package[0][2:] if package else None
|
||||
|
||||
transponder_id = "{}:{}:{}".format(data[1], data[2], data[3])
|
||||
transponder = transponders.get(transponder_id, None)
|
||||
|
||||
if transponder is not None:
|
||||
tr_type, sp, tr = str(transponder).partition(" ")
|
||||
tr = tr.split(_SEP)
|
||||
service_type = SERVICE_TYPE.get(data[4], SERVICE_TYPE["-2"])
|
||||
channels.append(Channel(flags_cas=ch[2],
|
||||
transponder_type=tr_type,
|
||||
coded=coded,
|
||||
service=ch[1],
|
||||
locked=locked,
|
||||
hide=hide,
|
||||
package=package,
|
||||
service_type=service_type,
|
||||
ssid=data[0],
|
||||
freq=tr[0],
|
||||
rate=tr[1],
|
||||
pol=POLARIZATION[tr[2]],
|
||||
fec=FEC[tr[3]],
|
||||
system=SYSTEM[tr[6]],
|
||||
pos="{}.{}".format(tr[4][:-1], tr[4][-1:]),
|
||||
data_id=ch[0],
|
||||
fav_id=fav_id,
|
||||
transponder=transponder))
|
||||
return channels
|
||||
|
||||
|
||||
def split(itr, size):
|
||||
""" Divide the iterable. """
|
||||
srv = []
|
||||
tmp = []
|
||||
for i, line in enumerate(itr):
|
||||
tmp.append(line)
|
||||
if i % size == 0:
|
||||
srv.append(tuple(tmp))
|
||||
tmp.clear()
|
||||
|
||||
return srv
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
0
app/eparser/neutrino/__init__.py
Normal file
0
app/eparser/neutrino/__init__.py
Normal file
182
app/eparser/neutrino/bouquets.py
Normal file
182
app/eparser/neutrino/bouquets.py
Normal file
@@ -0,0 +1,182 @@
|
||||
import os
|
||||
from xml.dom.minidom import parse, Document
|
||||
|
||||
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT
|
||||
from app.ui.uicommons import LOCKED_ICON, HIDE_ICON
|
||||
from ..ecommons import Bouquets, Bouquet, BouquetService, BqServiceType, PROVIDER, BqType
|
||||
|
||||
_FILE = "bouquets.xml"
|
||||
_U_FILE = "ubouquets.xml"
|
||||
_W_FILE = "webtv.xml"
|
||||
|
||||
_COMMENT = " File was created in DemonEditor. Enjoy watching! "
|
||||
|
||||
|
||||
def get_bouquets(path):
|
||||
return (parse_bouquets(path + _FILE, "Providers", BqType.BOUQUET.value),
|
||||
parse_bouquets(path + _U_FILE, "FAV", BqType.TV.value),
|
||||
parse_webtv(path + _W_FILE, "WEBTV", BqType.WEBTV.value))
|
||||
|
||||
|
||||
def parse_bouquets(file, name, bq_type):
|
||||
bouquets = Bouquets(name=name, type=bq_type, bouquets=[])
|
||||
if not os.path.exists(file):
|
||||
return bouquets
|
||||
|
||||
dom = parse(file)
|
||||
|
||||
for elem in dom.getElementsByTagName("Bouquet"):
|
||||
if elem.hasAttributes():
|
||||
bq_name = elem.attributes["name"].value
|
||||
hidden = elem.attributes.get("hidden")
|
||||
hidden = hidden.value if hidden else hidden
|
||||
locked = elem.attributes.get("locked")
|
||||
locked = locked.value if locked else locked
|
||||
# epg = elem.attributes["epg"].value
|
||||
services = []
|
||||
for srv_elem in elem.getElementsByTagName("S"):
|
||||
if srv_elem.hasAttributes():
|
||||
ssid = srv_elem.attributes["i"].value
|
||||
on = srv_elem.attributes["on"].value
|
||||
tr_id = srv_elem.attributes["t"].value
|
||||
fav_id = "{}:{}:{}".format(tr_id, on, ssid)
|
||||
services.append(BouquetService(None, BqServiceType.DEFAULT, fav_id, 0))
|
||||
bouquets[2].append(Bouquet(name=bq_name,
|
||||
type=bq_type,
|
||||
services=services,
|
||||
locked=LOCKED_ICON if locked == "1" else None,
|
||||
hidden=HIDE_ICON if hidden == "1" else None))
|
||||
|
||||
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:
|
||||
PROVIDER[key] = name
|
||||
|
||||
return bouquets
|
||||
|
||||
|
||||
def parse_webtv(path, name, bq_type):
|
||||
bouquets = Bouquets(name=name, type=bq_type, bouquets=[])
|
||||
if not os.path.exists(path):
|
||||
return bouquets
|
||||
|
||||
dom = parse(path)
|
||||
services = []
|
||||
for elem in dom.getElementsByTagName("webtv"):
|
||||
if elem.hasAttributes():
|
||||
title = elem.attributes["title"].value
|
||||
url = elem.attributes["url"].value
|
||||
description = elem.attributes.get("description")
|
||||
description = description.value if description else description
|
||||
urlkey = elem.attributes.get("urlkey", None)
|
||||
urlkey = urlkey.value if urlkey else urlkey
|
||||
account = elem.attributes.get("account", None)
|
||||
account = account.value if account else account
|
||||
usrname = elem.attributes.get("usrname", None)
|
||||
usrname = usrname.value if usrname else usrname
|
||||
psw = elem.attributes.get("psw", None)
|
||||
psw = psw.value if psw else psw
|
||||
s_type = elem.attributes.get("type", None)
|
||||
s_type = s_type.value if s_type else s_type
|
||||
iconsrc = elem.attributes.get("iconsrc", None)
|
||||
iconsrc = iconsrc.value if iconsrc else iconsrc
|
||||
iconsrc_b = elem.attributes.get("iconsrc_b", None)
|
||||
iconsrc_b = iconsrc_b.value if iconsrc_b else iconsrc_b
|
||||
group = elem.attributes.get("group", None)
|
||||
group = group.value if group else group
|
||||
fav_id = NEUTRINO_FAV_ID_FORMAT.format(url, description, urlkey, account, usrname, psw, s_type, iconsrc,
|
||||
iconsrc_b, group)
|
||||
srv = BouquetService(name=title,
|
||||
type=BqServiceType.IPTV,
|
||||
data=fav_id,
|
||||
num=0)
|
||||
services.append(srv)
|
||||
bouquet = Bouquet(name="default", type=bq_type, services=services, locked=None, hidden=None)
|
||||
bouquets[2].append(bouquet)
|
||||
|
||||
return bouquets
|
||||
|
||||
|
||||
def write_bouquets(path, bouquets):
|
||||
for bq in bouquets:
|
||||
bq_type = BqType(bq.type)
|
||||
if bq_type is BqType.WEBTV:
|
||||
write_webtv(path + _W_FILE, bq)
|
||||
else:
|
||||
write_bouquet(path + (_FILE if bq_type is BqType.BOUQUET else _U_FILE), bq)
|
||||
|
||||
|
||||
def write_bouquet(file, bouquet):
|
||||
doc = Document()
|
||||
root = doc.createElement("zapit")
|
||||
doc.appendChild(root)
|
||||
comment = doc.createComment(_COMMENT)
|
||||
doc.appendChild(comment)
|
||||
|
||||
for bq in bouquet.bouquets:
|
||||
bq_elem = doc.createElement("Bouquet")
|
||||
bq_elem.setAttribute("name", bq.name)
|
||||
bq_elem.setAttribute("hidden", "1" if bq.hidden else "0")
|
||||
bq_elem.setAttribute("locked", "1" if bq.locked else "0")
|
||||
bq_elem.setAttribute("epg", "0")
|
||||
root.appendChild(bq_elem)
|
||||
|
||||
for srv in bq.services:
|
||||
tr_id, on, ssid = srv.fav_id.split(":")
|
||||
srv_elem = doc.createElement("S")
|
||||
srv_elem.setAttribute("i", ssid)
|
||||
srv_elem.setAttribute("n", srv.service)
|
||||
srv_elem.setAttribute("t", tr_id)
|
||||
srv_elem.setAttribute("on", on)
|
||||
srv_elem.setAttribute("s", srv.pos.replace(".", ""))
|
||||
srv_elem.setAttribute("frq", srv.freq[:-3])
|
||||
srv_elem.setAttribute("l", "0") # temporary !!!
|
||||
bq_elem.appendChild(srv_elem)
|
||||
|
||||
doc.writexml(open(file, "w"), addindent=" ", newl="\n", encoding="UTF-8")
|
||||
|
||||
|
||||
def write_webtv(file, bouquet):
|
||||
doc = Document()
|
||||
root = doc.createElement("webtvs")
|
||||
doc.appendChild(root)
|
||||
comment = doc.createComment(_COMMENT)
|
||||
doc.appendChild(comment)
|
||||
|
||||
for bq in bouquet.bouquets:
|
||||
for srv in bq.services:
|
||||
url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group = srv.fav_id.split("::")
|
||||
srv_elem = doc.createElement("webtv")
|
||||
srv_elem.setAttribute("title", srv.service)
|
||||
srv_elem.setAttribute("url", url)
|
||||
|
||||
if description != "None":
|
||||
srv_elem.setAttribute("description", description)
|
||||
if urlkey != "None":
|
||||
srv_elem.setAttribute("urlkey", urlkey)
|
||||
if account != "None":
|
||||
srv_elem.setAttribute("account", account)
|
||||
if usrname != "None":
|
||||
srv_elem.setAttribute("usrname", usrname)
|
||||
if psw != "None":
|
||||
srv_elem.setAttribute("psw", psw)
|
||||
if s_type != "None":
|
||||
srv_elem.setAttribute("type", s_type)
|
||||
if iconsrc != "None":
|
||||
srv_elem.setAttribute("iconsrc", iconsrc)
|
||||
if iconsrc_b != "None":
|
||||
srv_elem.setAttribute("iconsrc_b", iconsrc_b)
|
||||
if group != "None":
|
||||
srv_elem.setAttribute("group", group)
|
||||
|
||||
root.appendChild(srv_elem)
|
||||
|
||||
doc.writexml(open(file, "w"), addindent=" ", newl="\n", encoding="UTF-8")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
169
app/eparser/neutrino/services.py
Normal file
169
app/eparser/neutrino/services.py
Normal file
@@ -0,0 +1,169 @@
|
||||
from xml.dom.minidom import parse, Document
|
||||
|
||||
from ..ecommons import Service, POLARIZATION, FEC, SYSTEM, SERVICE_TYPE, PROVIDER
|
||||
|
||||
_FILE = "services.xml"
|
||||
_TR_ATTR_NAMES = ("id", "on", "frq", "inv", "sr", "fec", "pol", "mod", "sys") # transponder attributes
|
||||
_SRV_ATTR_NAMES = ("t", "s", "num", "f", "v", "a", "p", "pmt", "tx", "vt") # service attributes
|
||||
|
||||
|
||||
def write_services(path, services):
|
||||
doc = Document()
|
||||
root = doc.createElement("zapit")
|
||||
root.setAttribute("api", "4")
|
||||
doc.appendChild(root)
|
||||
comment = doc.createComment(" File was created in DemonEditor. Enjoy watching! ")
|
||||
doc.appendChild(comment)
|
||||
|
||||
sats = {}
|
||||
for srv in services:
|
||||
flag = srv[0]
|
||||
if flag in sats:
|
||||
sats.get(flag).append(srv)
|
||||
else:
|
||||
srv_list = [srv]
|
||||
sats[flag] = srv_list
|
||||
|
||||
for sat in sats:
|
||||
tr_atr = sat.split(":")
|
||||
sat_elem = doc.createElement("sat")
|
||||
sat_elem.setAttribute("name", tr_atr[0])
|
||||
sat_elem.setAttribute("position", tr_atr[1].replace(".", ""))
|
||||
sat_elem.setAttribute("diseqc", tr_atr[2])
|
||||
sat_elem.setAttribute("uncommited", tr_atr[3])
|
||||
root.appendChild(sat_elem)
|
||||
|
||||
transponers = {}
|
||||
for srv in sats.get(sat):
|
||||
flag = srv[-1]
|
||||
if flag in transponers:
|
||||
transponers.get(flag).append(srv)
|
||||
else:
|
||||
srv_list = [srv]
|
||||
transponers[flag] = srv_list
|
||||
|
||||
for tr in transponers:
|
||||
tr_elem = doc.createElement("TS")
|
||||
tr_atr = tr.split(":")
|
||||
for i, value in enumerate(tr_atr):
|
||||
if value == "None":
|
||||
continue
|
||||
tr_elem.setAttribute(_TR_ATTR_NAMES[i], value)
|
||||
sat_elem.appendChild(tr_elem)
|
||||
|
||||
for srv in transponers.get(tr):
|
||||
srv_elem = doc.createElement("S")
|
||||
srv_elem.setAttribute("i", srv.ssid)
|
||||
srv_elem.setAttribute("n", srv.service)
|
||||
|
||||
srv_attrs = srv.data_id.split(":")
|
||||
api = srv_attrs.pop(0)
|
||||
|
||||
if api == "3":
|
||||
root.setAttribute("api", "3") # !!!
|
||||
for i, value in enumerate(srv_attrs):
|
||||
if value == "None":
|
||||
continue
|
||||
srv_elem.setAttribute(_SRV_ATTR_NAMES[i], value)
|
||||
|
||||
tr_elem.appendChild(srv_elem)
|
||||
|
||||
doc.writexml(open(path + _FILE, "w"), addindent=" ", newl="\n", encoding="UTF-8")
|
||||
doc.unlink()
|
||||
|
||||
|
||||
def get_services(path):
|
||||
return parse_services(path)
|
||||
|
||||
|
||||
def parse_services(path):
|
||||
""" Parsing services from xml"""
|
||||
dom = parse(path + _FILE)
|
||||
services = []
|
||||
|
||||
for root in dom.getElementsByTagName("zapit"):
|
||||
api = root.attributes["api"].value
|
||||
|
||||
for elem in root.getElementsByTagName("sat"):
|
||||
if elem.hasAttributes():
|
||||
sat_name = elem.attributes["name"].value
|
||||
sat_pos = elem.attributes["position"].value
|
||||
sat_pos = "{}.{}".format(sat_pos[:-1], sat_pos[-1:])
|
||||
diseqc = elem.attributes.get("diseqc")
|
||||
diseqc = diseqc.value if diseqc else diseqc
|
||||
uncommited = elem.attributes.get("uncommited")
|
||||
uncommited = uncommited.value if uncommited else uncommited
|
||||
sat = "{}:{}:{}:{}".format(sat_name, sat_pos, diseqc, uncommited)
|
||||
|
||||
for tr_elem in elem.getElementsByTagName("TS"):
|
||||
if tr_elem.hasAttributes():
|
||||
parse_transponder(api, sat, sat_pos, services, tr_elem)
|
||||
|
||||
return services
|
||||
|
||||
|
||||
def parse_transponder(api, sat, sat_pos, services, tr_elem):
|
||||
tr_id = tr_elem.attributes["id"].value
|
||||
on = tr_elem.attributes["on"].value
|
||||
freq = tr_elem.attributes["frq"].value
|
||||
rate = tr_elem.attributes["sr"].value
|
||||
inv = tr_elem.attributes["inv"].value
|
||||
fec = tr_elem.attributes["fec"].value
|
||||
pol = tr_elem.attributes["pol"].value
|
||||
mod = tr_elem.attributes.get("mod")
|
||||
mod = mod.value if mod else mod
|
||||
sys = tr_elem.attributes.get("sys")
|
||||
sys = sys.value if sys else sys
|
||||
|
||||
tr = "{}:{}:{}:{}:{}:{}:{}:{}:{}".format(tr_id, on, freq, inv, rate, fec, pol, mod, sys)
|
||||
tr_id = tr_id.lstrip("0")
|
||||
|
||||
for srv_elem in tr_elem.getElementsByTagName("S"):
|
||||
if srv_elem.hasAttributes():
|
||||
ssid = srv_elem.attributes["i"].value
|
||||
name = srv_elem.attributes["n"].value
|
||||
srv_type = srv_elem.attributes["t"].value
|
||||
sys = srv_elem.attributes["s"].value
|
||||
num = srv_elem.attributes.get("num")
|
||||
num = num.value if num else num
|
||||
f = srv_elem.attributes.get("f")
|
||||
f = f.value if f else f
|
||||
v, a, p, pmt, tx, vt = [None] * 6
|
||||
# For v3 is possible so: '<S i="0001" n="name" t="1" s="0" num="770" f="4"/>' (equals v4 api)
|
||||
if api == "3" and len(srv_elem.attributes) > 6:
|
||||
v = srv_elem.attributes["v"].value
|
||||
a = srv_elem.attributes["a"].value
|
||||
p = srv_elem.attributes["p"].value
|
||||
pmt = srv_elem.attributes["pmt"].value
|
||||
tx = srv_elem.attributes["tx"].value
|
||||
vt = srv_elem.attributes["vt"].value
|
||||
|
||||
data_id = "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}".format(api, srv_type, sys, num, f, v, a, p, pmt, tx, vt)
|
||||
fav_id = "{}:{}:{}".format(tr_id, on.lstrip("0"), ssid.lstrip("0"))
|
||||
picon_id = "{}{}{}.png".format(tr_id, on, ssid)
|
||||
|
||||
srv = Service(flags_cas=sat,
|
||||
transponder_type=None,
|
||||
coded=None,
|
||||
service=name,
|
||||
locked=None,
|
||||
hide=None,
|
||||
package=PROVIDER.get(int(on, 16)),
|
||||
service_type=SERVICE_TYPE.get(str(int(srv_type, 16))),
|
||||
picon=None,
|
||||
picon_id=picon_id,
|
||||
ssid=ssid,
|
||||
freq=freq,
|
||||
rate=rate,
|
||||
pol=POLARIZATION.get(pol),
|
||||
fec=FEC.get(fec),
|
||||
system=SYSTEM.get(sys),
|
||||
pos=sat_pos,
|
||||
data_id=data_id,
|
||||
fav_id=fav_id,
|
||||
transponder=tr)
|
||||
services.append(srv)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -2,18 +2,15 @@
|
||||
|
||||
For more info see __COMMENT
|
||||
"""
|
||||
from collections import namedtuple
|
||||
from functools import lru_cache
|
||||
from xml.dom.minidom import parse, Document
|
||||
|
||||
from app.eparser.__constants import POLARIZATION, FEC, SYSTEM, MODULATION, PLS_MODE
|
||||
import os
|
||||
|
||||
Satellite = namedtuple("Satellite", ["name", "flags", "position", "transponders"])
|
||||
|
||||
Transponder = namedtuple("Transponder", ["frequency", "symbol_rate", "polarization", "fec_inner",
|
||||
"system", "modulation", "pls_mode", "pls_code", "is_id"])
|
||||
from .ecommons import POLARIZATION, FEC, SYSTEM, MODULATION, PLS_MODE, Transponder, Satellite, get_key_by_value
|
||||
|
||||
__COMMENT = (" File was created in DemonEditor\n\n"
|
||||
"useable flags are\n"
|
||||
"usable flags are\n"
|
||||
" 1: Network Scan\n"
|
||||
" 2: use BAT\n"
|
||||
" 4: use ONIT\n"
|
||||
@@ -35,7 +32,7 @@ __COMMENT = (" File was created in DemonEditor\n\n"
|
||||
|
||||
|
||||
def get_satellites(path):
|
||||
return parse_satellites(path)
|
||||
return parse_satellites(path, os.path.getsize(path))
|
||||
|
||||
|
||||
def write_satellites(satellites, data_path):
|
||||
@@ -104,7 +101,8 @@ def parse_sat(elem):
|
||||
parse_transponders(elem))
|
||||
|
||||
|
||||
def parse_satellites(path):
|
||||
@lru_cache(maxsize=1)
|
||||
def parse_satellites(path, file_size):
|
||||
""" Parsing satellites from xml"""
|
||||
dom = parse(path)
|
||||
satellites = []
|
||||
@@ -116,11 +114,5 @@ def parse_satellites(path):
|
||||
return satellites
|
||||
|
||||
|
||||
def get_key_by_value(dictionary, value):
|
||||
for k, v in dictionary.items():
|
||||
if v == value:
|
||||
return k
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
116
app/ftp.py
116
app/ftp.py
@@ -2,22 +2,33 @@ import os
|
||||
import socket
|
||||
import time
|
||||
from enum import Enum
|
||||
from ftplib import FTP
|
||||
from ftplib import FTP, error_perm
|
||||
from telnetlib import Telnet
|
||||
|
||||
__DATA_FILES_LIST = ("tv", "radio", "lamedb", "blacklist", "whitelist")
|
||||
from app.commons import log
|
||||
from app.properties import Profile
|
||||
|
||||
__DATA_FILES_LIST = ("tv", "radio", "lamedb", "lamedb5", "blacklist", "whitelist", # enigma 2
|
||||
"services.xml", "myservices.xml", "bouquets.xml", "ubouquets.xml") # neutrino
|
||||
|
||||
_SATELLITES_XML_FILE = "satellites.xml"
|
||||
_WEBTV_XML_FILE = "webtv.xml"
|
||||
|
||||
|
||||
class DownloadDataType(Enum):
|
||||
ALL = 0
|
||||
BOUQUETS = 1
|
||||
SATELLITES = 2
|
||||
PICONS = 3
|
||||
WEBTV = 4
|
||||
|
||||
|
||||
def download_data(*, properties, download_type=DownloadDataType.ALL):
|
||||
def download_data(*, properties, download_type=DownloadDataType.ALL, callback=None):
|
||||
with FTP(host=properties["host"]) as ftp:
|
||||
ftp.login(user=properties["user"], passwd=properties["password"])
|
||||
ftp.encoding = "utf-8"
|
||||
save_path = properties["data_dir_path"]
|
||||
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
||||
files = []
|
||||
# bouquets section
|
||||
if download_type is DownloadDataType.ALL or download_type is DownloadDataType.BOUQUETS:
|
||||
@@ -28,39 +39,57 @@ def download_data(*, properties, download_type=DownloadDataType.ALL):
|
||||
name = str(file).strip()
|
||||
if name.endswith(__DATA_FILES_LIST):
|
||||
name = name.split()[-1]
|
||||
with open(save_path + name, "wb") as f:
|
||||
ftp.retrbinary("RETR " + name, f.write)
|
||||
# satellites.xml section
|
||||
if download_type is DownloadDataType.ALL or download_type is DownloadDataType.SATELLITES:
|
||||
download_file(ftp, name, save_path)
|
||||
# satellites.xml and webtv section
|
||||
if download_type in (DownloadDataType.ALL, DownloadDataType.SATELLITES, DownloadDataType.WEBTV):
|
||||
ftp.cwd(properties["satellites_xml_path"])
|
||||
files.clear()
|
||||
ftp.dir(files.append)
|
||||
|
||||
for file in files:
|
||||
name = str(file).strip()
|
||||
xml_file = "satellites.xml"
|
||||
if name.endswith(xml_file):
|
||||
with open(save_path + xml_file, 'wb') as f:
|
||||
ftp.retrbinary("RETR " + xml_file, f.write)
|
||||
if download_type in (DownloadDataType.ALL, DownloadDataType.SATELLITES):
|
||||
if name.endswith(_SATELLITES_XML_FILE):
|
||||
download_file(ftp, _SATELLITES_XML_FILE, save_path)
|
||||
elif download_type in (DownloadDataType.ALL, DownloadDataType.WEBTV):
|
||||
if name.endswith(_WEBTV_XML_FILE):
|
||||
download_file(ftp, _WEBTV_XML_FILE, save_path)
|
||||
|
||||
if callback is not None:
|
||||
callback()
|
||||
|
||||
|
||||
def upload_data(*, properties, download_type=DownloadDataType.ALL, remove_unused=False):
|
||||
def upload_data(*, properties, download_type=DownloadDataType.ALL, remove_unused=False, profile=Profile.ENIGMA_2,
|
||||
callback=None):
|
||||
data_path = properties["data_dir_path"]
|
||||
host = properties["host"]
|
||||
# telnet
|
||||
tn = telnet(host=host)
|
||||
tn = telnet(host=host, user=properties.get("telnet_user", "root"), password=properties.get("telnet_password", ""),
|
||||
timeout=properties.get("telnet_timeout", 5))
|
||||
next(tn)
|
||||
# terminate enigma
|
||||
# terminate enigma or neutrino
|
||||
tn.send("init 4")
|
||||
|
||||
with FTP(host=host) as ftp:
|
||||
ftp.login(user=properties["user"], passwd=properties["password"])
|
||||
ftp.encoding = "utf-8"
|
||||
|
||||
if download_type is DownloadDataType.ALL or download_type is DownloadDataType.SATELLITES:
|
||||
ftp.cwd(properties["satellites_xml_path"])
|
||||
file_name = "satellites.xml"
|
||||
send = send_file(file_name, data_path, ftp)
|
||||
if download_type == DownloadDataType.SATELLITES:
|
||||
send = send_file(_SATELLITES_XML_FILE, data_path, ftp)
|
||||
if download_type is DownloadDataType.SATELLITES:
|
||||
tn.send("init 3" if profile is Profile.ENIGMA_2 else "init 6")
|
||||
if callback is not None:
|
||||
callback()
|
||||
return send
|
||||
|
||||
if profile is Profile.NEUTRINO_MP and download_type in (DownloadDataType.ALL, DownloadDataType.WEBTV):
|
||||
ftp.cwd(properties["satellites_xml_path"])
|
||||
send = send_file(_WEBTV_XML_FILE, data_path, ftp)
|
||||
if download_type is DownloadDataType.WEBTV:
|
||||
tn.send("init 6")
|
||||
if callback is not None:
|
||||
callback()
|
||||
return send
|
||||
|
||||
if download_type is DownloadDataType.ALL or download_type is DownloadDataType.BOUQUETS:
|
||||
@@ -75,12 +104,45 @@ def upload_data(*, properties, download_type=DownloadDataType.ALL, remove_unused
|
||||
ftp.delete(name)
|
||||
|
||||
for file_name in os.listdir(data_path):
|
||||
if file_name == "satellites.xml":
|
||||
if file_name == _SATELLITES_XML_FILE or file_name == _WEBTV_XML_FILE:
|
||||
continue
|
||||
file_name, send_file(file_name, data_path, ftp)
|
||||
# resume enigma
|
||||
tn.send("init 3")
|
||||
if file_name.endswith(__DATA_FILES_LIST):
|
||||
send_file(file_name, data_path, ftp)
|
||||
|
||||
if download_type is DownloadDataType.PICONS:
|
||||
picons_dir_path = properties.get("picons_dir_path")
|
||||
picons_path = properties.get("picons_path")
|
||||
try:
|
||||
ftp.cwd(picons_path)
|
||||
except error_perm as e:
|
||||
if str(e).startswith("550"):
|
||||
ftp.mkd(picons_path) # if not exist
|
||||
ftp.cwd(picons_path)
|
||||
|
||||
files = []
|
||||
ftp.dir(files.append)
|
||||
picons_suf = (".jpg", ".png")
|
||||
|
||||
for file in files:
|
||||
name = str(file).strip()
|
||||
if name.endswith(picons_suf):
|
||||
name = name.split()[-1]
|
||||
ftp.delete(name)
|
||||
for file_name in os.listdir(picons_dir_path):
|
||||
if file_name.endswith(picons_suf):
|
||||
send_file(file_name, picons_dir_path, ftp)
|
||||
|
||||
# resume enigma or restart neutrino
|
||||
tn.send("init 3" if profile is Profile.ENIGMA_2 else "init 6")
|
||||
|
||||
if callback is not None:
|
||||
callback()
|
||||
|
||||
|
||||
def download_file(ftp, name, save_path):
|
||||
with open(save_path + name, "wb") as f:
|
||||
ftp.retrbinary("RETR " + name, f.write)
|
||||
|
||||
|
||||
def send_file(file_name, path, ftp):
|
||||
""" Opens the file in binary mode and transfers into receiver """
|
||||
@@ -88,14 +150,22 @@ def send_file(file_name, path, ftp):
|
||||
return ftp.storbinary("STOR " + file_name, f)
|
||||
|
||||
|
||||
def telnet(host, port=23, user="root", password="root", timeout=5):
|
||||
def telnet(host, port=23, user="", password="", timeout=5):
|
||||
try:
|
||||
tn = Telnet(host=host, port=port, timeout=timeout)
|
||||
except socket.timeout:
|
||||
print("socket timeout")
|
||||
log("telnet error: socket timeout")
|
||||
else:
|
||||
time.sleep(1)
|
||||
command = yield
|
||||
if user != "":
|
||||
tn.read_until(b"login: ")
|
||||
tn.write(user.encode("utf-8") + b"\n")
|
||||
time.sleep(timeout)
|
||||
if password != "":
|
||||
tn.read_until(b"Password: ")
|
||||
tn.write(password.encode("utf-8") + b"\n")
|
||||
time.sleep(timeout)
|
||||
tn.write("{}\r\n".format(command).encode("utf-8"))
|
||||
time.sleep(timeout)
|
||||
command = yield
|
||||
|
||||
@@ -1,25 +1,35 @@
|
||||
import json
|
||||
import os
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CONFIG_PATH = str(Path.home()) + "/.config/demon-editor/"
|
||||
CONFIG_FILE = CONFIG_PATH + "config.json"
|
||||
DATA_PATH = "data/"
|
||||
|
||||
|
||||
class Profile(Enum):
|
||||
""" Profiles for settings """
|
||||
ENIGMA_2 = "0"
|
||||
NEUTRINO_MP = "1"
|
||||
|
||||
|
||||
def get_config():
|
||||
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) # create dir if not exist
|
||||
os.makedirs(os.path.dirname(DATA_PATH), exist_ok=True)
|
||||
|
||||
if not os.path.isfile(CONFIG_FILE) or os.stat(CONFIG_FILE).st_size == 0:
|
||||
with open(CONFIG_FILE, "w") as default_config_file:
|
||||
json.dump(get_default_settings(), default_config_file)
|
||||
reset_config()
|
||||
|
||||
with open(CONFIG_FILE, "r") as config_file:
|
||||
return json.load(config_file)
|
||||
|
||||
|
||||
def reset_config():
|
||||
with open(CONFIG_FILE, "w") as default_config_file:
|
||||
json.dump(get_default_settings(), default_config_file)
|
||||
|
||||
|
||||
def write_config(config):
|
||||
assert isinstance(config, dict)
|
||||
with open(CONFIG_FILE, "w") as config_file:
|
||||
@@ -27,12 +37,31 @@ def write_config(config):
|
||||
|
||||
|
||||
def get_default_settings():
|
||||
return {"host": "127.0.0.1", "port": "21",
|
||||
return {
|
||||
Profile.ENIGMA_2.value: {
|
||||
"host": "127.0.0.1", "port": "21",
|
||||
"user": "root", "password": "root",
|
||||
"telnet_user": "", "telnet_password": "",
|
||||
"telnet_port": "21", "telnet_timeout": 5,
|
||||
"services_path": "/etc/enigma2/",
|
||||
"user_bouquet_path": "/etc/enigma2/",
|
||||
"satellites_xml_path": "/etc/tuxbox/",
|
||||
"data_dir_path": DATA_PATH}
|
||||
"picons_path": "/usr/share/enigma2/picon",
|
||||
"data_dir_path": DATA_PATH + "enigma2/",
|
||||
"picons_dir_path": DATA_PATH + "enigma2/picons/",
|
||||
"v5_support": False},
|
||||
Profile.NEUTRINO_MP.value: {
|
||||
"host": "127.0.0.1", "port": "21",
|
||||
"user": "root", "password": "root",
|
||||
"telnet_user": "root", "telnet_password": "",
|
||||
"telnet_port": "21", "telnet_timeout": 1,
|
||||
"services_path": "/var/tuxbox/config/zapit/",
|
||||
"user_bouquet_path": "/var/tuxbox/config/zapit/",
|
||||
"satellites_xml_path": "/var/tuxbox/config/",
|
||||
"picons_path": "/usr/share/tuxbox/neutrino/icons/logo/",
|
||||
"data_dir_path": DATA_PATH + "neutrino/",
|
||||
"picons_dir_path": DATA_PATH + "neutrino/picons/"},
|
||||
"profile": Profile.ENIGMA_2.value}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
0
app/tools/__init__.py
Normal file
0
app/tools/__init__.py
Normal file
96
app/tools/media.py
Normal file
96
app/tools/media.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from app.commons import run_idle
|
||||
from app.tools import vlc
|
||||
from app.ui.uicommons import Gtk, Gdk
|
||||
|
||||
MRL = "url"
|
||||
|
||||
|
||||
class Player:
|
||||
_VLC_INSTANCE = None
|
||||
|
||||
def __init__(self, url):
|
||||
handlers = {"on_play": self.on_play,
|
||||
"on_stop": self.on_stop,
|
||||
"on_drawing_area_realize": self.on_drawing_area_realize,
|
||||
"on_press": self.on_press,
|
||||
"on_key_release": self.on_key_release,
|
||||
"on_state_changed": self.on_state_changed,
|
||||
"on_close_window": self.on_close_window}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.add_objects_from_file("player.glade", ("player_main_window",))
|
||||
builder.connect_signals(handlers)
|
||||
self._main_window = builder.get_object("player_main_window")
|
||||
self._main_box = builder.get_object("main_box")
|
||||
self._buttonbox = builder.get_object("buttonbox")
|
||||
self._frame = builder.get_object("")
|
||||
self._drawing_area = builder.get_object("drawing_area")
|
||||
self._drawing_area.set_events(Gdk.ModifierType.BUTTON1_MASK)
|
||||
self._player = Player.get_vlc_instance().media_player_new()
|
||||
self._is_played = False
|
||||
self._url = url
|
||||
self._full_screen = False
|
||||
|
||||
@staticmethod
|
||||
def get_vlc_instance():
|
||||
if Player._VLC_INSTANCE:
|
||||
return Player._VLC_INSTANCE
|
||||
_VLC_INSTANCE = vlc.Instance("--no-xlib")
|
||||
return _VLC_INSTANCE
|
||||
|
||||
def on_play(self, item):
|
||||
if not self._is_played:
|
||||
self._player.play()
|
||||
self._is_played = True
|
||||
|
||||
def on_stop(self, item):
|
||||
if self._is_played:
|
||||
self._player.stop()
|
||||
self._is_played = False
|
||||
|
||||
def on_press(self, area, event: Gdk.EventButton):
|
||||
if event.button == Gdk.BUTTON_PRIMARY and event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
|
||||
self.change_state()
|
||||
|
||||
def on_state_changed(self, window, event):
|
||||
if event.new_window_state & Gdk.WindowState.FULLSCREEN:
|
||||
if self._main_box in window:
|
||||
window.remove(self._main_box)
|
||||
self._drawing_area.reparent(self._main_window)
|
||||
else:
|
||||
if self._drawing_area in self._main_window:
|
||||
window.remove(self._drawing_area)
|
||||
window.add(self._main_box)
|
||||
self._main_box.pack_start(self._drawing_area, True, True, 0)
|
||||
self._main_box.reorder_child(self._drawing_area, 0)
|
||||
|
||||
def change_state(self):
|
||||
self._full_screen = not self._full_screen
|
||||
self._main_window.fullscreen() if self._full_screen else self._main_window.unfullscreen()
|
||||
|
||||
def on_key_release(self, area, key):
|
||||
if key.keyval in (Gdk.KEY_F, Gdk.KEY_f):
|
||||
self.change_state()
|
||||
|
||||
def on_drawing_area_realize(self, widget):
|
||||
win_id = widget.get_window().get_xid()
|
||||
if self._player:
|
||||
self._is_played = True
|
||||
self._player.set_xwindow(win_id)
|
||||
self._player.set_mrl(self._url)
|
||||
self._player.play()
|
||||
|
||||
@run_idle
|
||||
def on_close_window(self, *args):
|
||||
if self._player:
|
||||
self.on_stop(None)
|
||||
self._player.release()
|
||||
Gtk.main_quit()
|
||||
|
||||
def show(self):
|
||||
self._main_window.show()
|
||||
Gtk.main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Player(MRL).show()
|
||||
208
app/tools/picons.py
Normal file
208
app/tools/picons.py
Normal file
@@ -0,0 +1,208 @@
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
from collections import namedtuple
|
||||
from html.parser import HTMLParser
|
||||
|
||||
from app.commons import log, run_task
|
||||
from app.properties import Profile
|
||||
|
||||
_ENIGMA2_PICON_KEY = "{:X}:{:X}:{:X}0000"
|
||||
_NEUTRINO_PICON_KEY = "{:x}{:04x}{:04x}.png"
|
||||
|
||||
Provider = namedtuple("Provider", ["logo", "name", "pos", "url", "on_id", "selected"])
|
||||
Picon = namedtuple("Picon", ["ref", "ssid", "v_pid"])
|
||||
|
||||
|
||||
class PiconsParser(HTMLParser):
|
||||
""" Parser for package html page. (https://www.lyngsat.com/packages/*provider-name*.html) """
|
||||
|
||||
def __init__(self, entities=False, separator=' '):
|
||||
|
||||
HTMLParser.__init__(self)
|
||||
|
||||
self._parse_html_entities = entities
|
||||
self._separator = separator
|
||||
self._is_td = False
|
||||
self._is_th = False
|
||||
self._current_row = []
|
||||
self._current_cell = []
|
||||
self.picons = []
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == 'td':
|
||||
self._is_td = True
|
||||
if tag == 'th':
|
||||
self._is_th = True
|
||||
if tag == "img":
|
||||
self._current_row.append(attrs[0][1])
|
||||
|
||||
def handle_data(self, data):
|
||||
""" Save content to a cell """
|
||||
if self._is_td or self._is_th:
|
||||
self._current_cell.append(data.strip())
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == 'td':
|
||||
self._is_td = False
|
||||
elif tag == 'th':
|
||||
self._is_th = False
|
||||
|
||||
if tag in ('td', 'th'):
|
||||
final_cell = self._separator.join(self._current_cell).strip()
|
||||
self._current_row.append(final_cell)
|
||||
self._current_cell = []
|
||||
elif tag == 'tr':
|
||||
row = self._current_row
|
||||
ln = len(row)
|
||||
if 9 < ln < 13:
|
||||
url = None
|
||||
if row[0].startswith("../logo/"):
|
||||
url = row[0]
|
||||
elif row[1].startswith("../logo/"):
|
||||
url = row[1]
|
||||
|
||||
ssid = row[-4]
|
||||
if url and len(ssid) > 2:
|
||||
self.picons.append(Picon(url, ssid, row[-3]))
|
||||
|
||||
self._current_row = []
|
||||
|
||||
def error(self, message):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def parse(open_path, picons_path, tmp_path, on_id, pos, picon_ids, profile=Profile.ENIGMA_2):
|
||||
with open(open_path, encoding="utf-8", errors="replace") as f:
|
||||
parser = PiconsParser()
|
||||
parser.reset()
|
||||
parser.feed(f.read())
|
||||
picons = parser.picons
|
||||
if picons:
|
||||
os.makedirs(picons_path, exist_ok=True)
|
||||
for p in picons:
|
||||
try:
|
||||
name = PiconsParser.format(p.ssid, on_id, p.v_pid, pos, picon_ids, profile)
|
||||
p_name = picons_path + (name if name else os.path.basename(p.ref))
|
||||
shutil.copyfile(tmp_path + "www.lyngsat.com/" + p.ref.lstrip("."), p_name)
|
||||
except (TypeError, ValueError) as e:
|
||||
log("Picons format parse error: {}".format(p) + "\n" + str(e))
|
||||
print(e)
|
||||
|
||||
@staticmethod
|
||||
def format(ssid, on_id, v_pid, pos, picon_ids, profile: Profile):
|
||||
tr_id = int(ssid[:-2] if len(ssid) < 4 else ssid[:2])
|
||||
if profile is Profile.ENIGMA_2:
|
||||
return picon_ids.get(_ENIGMA2_PICON_KEY.format(int(ssid), int(on_id), int(pos)), None)
|
||||
elif profile is Profile.NEUTRINO_MP:
|
||||
return _NEUTRINO_PICON_KEY.format(tr_id, int(on_id), int(ssid))
|
||||
else:
|
||||
return "{}.png".format(ssid)
|
||||
|
||||
|
||||
class ProviderParser(HTMLParser):
|
||||
""" Parser for satellite html page. (https://www.lyngsat.com/*sat-name*.html) """
|
||||
|
||||
_POSITION_PATTERN = re.compile("at\s\d+\..*(?:E|W)']")
|
||||
|
||||
def __init__(self, entities=False, separator=' '):
|
||||
|
||||
HTMLParser.__init__(self)
|
||||
|
||||
self._ON_ID_BLACK_LIST = ("65535", "?", "0", "1")
|
||||
self._parse_html_entities = entities
|
||||
self._separator = separator
|
||||
self._is_td = False
|
||||
self._is_th = False
|
||||
self._is_provider = False
|
||||
self._current_row = []
|
||||
self._current_cell = []
|
||||
self.rows = []
|
||||
self._ids = set()
|
||||
self._positon = None
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == 'td':
|
||||
self._is_td = True
|
||||
if tag == 'tr':
|
||||
self._is_th = True
|
||||
if tag == "img":
|
||||
if attrs[0][1].startswith("logo/"):
|
||||
self._current_row.append(attrs[0][1])
|
||||
if tag == "a":
|
||||
if "https://www.lyngsat.com/packages/" in attrs[0][1]:
|
||||
self._current_row.append(attrs[0][1])
|
||||
|
||||
def handle_data(self, data):
|
||||
""" Save content to a cell """
|
||||
if self._is_td or self._is_th:
|
||||
self._current_cell.append(data.strip())
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == 'td':
|
||||
self._is_td = False
|
||||
elif tag == 'tr':
|
||||
self._is_th = False
|
||||
|
||||
if tag in ('td', 'th'):
|
||||
final_cell = self._separator.join(self._current_cell).strip()
|
||||
self._current_row.append(final_cell)
|
||||
self._current_cell = []
|
||||
elif tag == 'tr':
|
||||
row = self._current_row
|
||||
# Satellite position
|
||||
if not self._positon:
|
||||
pos = re.findall(self._POSITION_PATTERN, str(row))
|
||||
if pos:
|
||||
self._positon = "".join(c for c in str(pos) if c.isdigit() or c in ".EW")
|
||||
|
||||
if len(row) == 12:
|
||||
on_id, sep, tid = str(row[-2]).partition("-")
|
||||
if tid and on_id not in self._ON_ID_BLACK_LIST and on_id not in self._ids:
|
||||
row[-2] = on_id
|
||||
self.rows.append(row)
|
||||
self._ids.add(on_id)
|
||||
row[0] = self._positon
|
||||
self._current_row = []
|
||||
|
||||
def error(self, message):
|
||||
pass
|
||||
|
||||
def reset(self):
|
||||
super().reset()
|
||||
|
||||
|
||||
def parse_providers(open_path):
|
||||
parser = ProviderParser()
|
||||
parser.reset()
|
||||
|
||||
with open(open_path, encoding="utf-8", errors="replace") as f:
|
||||
parser.feed(f.read())
|
||||
rows = parser.rows
|
||||
|
||||
if rows:
|
||||
return [Provider(logo=r[2], name=r[5], pos=r[0], url=r[6], on_id=r[-2], selected=True) for r in rows]
|
||||
|
||||
|
||||
@run_task
|
||||
def convert_to(src_path, dest_path, profile, callback, done_callback):
|
||||
""" Converts names format of picons.
|
||||
|
||||
Copies resulting files from src to dest and writes state to callback.
|
||||
"""
|
||||
pattern = "/*_0_0_0.png" if profile is Profile.ENIGMA_2 else "/*.png"
|
||||
for file in glob.glob(src_path + pattern):
|
||||
base_name = os.path.basename(file)
|
||||
pic_data = base_name.rstrip(".png").split("_")
|
||||
dest_file = _NEUTRINO_PICON_KEY.format(int(pic_data[4], 16), int(pic_data[5], 16), int(pic_data[3], 16))
|
||||
dest = "{}/{}".format(dest_path, dest_file)
|
||||
callback('Converting "{}" to "{}"\n'.format(base_name, dest_file))
|
||||
shutil.copyfile(file, dest)
|
||||
|
||||
done_callback()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
116
app/tools/player.glade
Normal file
116
app/tools/player.glade
Normal file
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.18.3 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.12"/>
|
||||
<object class="GtkApplicationWindow" id="player_main_window">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Player</property>
|
||||
<property name="icon_name">vlc</property>
|
||||
<signal name="delete-event" handler="on_close_window" swapped="no"/>
|
||||
<signal name="window-state-event" handler="on_state_changed" swapped="no"/>
|
||||
<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="GtkDrawingArea" id="drawing_area">
|
||||
<property name="width_request">320</property>
|
||||
<property name="height_request">240</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="has_focus">True</property>
|
||||
<signal name="button-press-event" handler="on_press" swapped="no"/>
|
||||
<signal name="key-release-event" handler="on_key_release" swapped="no"/>
|
||||
<signal name="realize" handler="on_drawing_area_realize" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">2</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="separator">
|
||||
<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">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="buttonbox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">3</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="layout_style">start</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="play_button">
|
||||
<property name="label">gtk-media-play</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_play" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="stop_button">
|
||||
<property name="label">gtk-media-stop</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_stop" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="close_button">
|
||||
<property name="label">gtk-close</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_close_window" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">2</property>
|
||||
<property name="secondary">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
186
app/tools/satellites.py
Normal file
186
app/tools/satellites.py
Normal file
@@ -0,0 +1,186 @@
|
||||
""" Module for download satellites from internet ("flysat.com")
|
||||
for replace or update current satellites.xml file.
|
||||
"""
|
||||
import re
|
||||
import requests
|
||||
from enum import Enum
|
||||
from html.parser import HTMLParser
|
||||
|
||||
from app.eparser import Satellite, Transponder, is_transponder_valid
|
||||
|
||||
|
||||
class SatelliteSource(Enum):
|
||||
FLYSAT = ("https://www.flysat.com/satlist.php",)
|
||||
LYNGSAT = ("https://www.lyngsat.com/asia.html", "https://www.lyngsat.com/europe.html",
|
||||
"https://www.lyngsat.com/atlantic.html", "https://www.lyngsat.com/america.html")
|
||||
|
||||
@staticmethod
|
||||
def get_sources(src):
|
||||
return src.value
|
||||
|
||||
|
||||
class SatellitesParser(HTMLParser):
|
||||
""" Parser for satellite html page. """
|
||||
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:45.0) Gecko/20100101 Firefox/59.02"}
|
||||
|
||||
def __init__(self, source=SatelliteSource.FLYSAT, entities=False, separator=' '):
|
||||
|
||||
HTMLParser.__init__(self)
|
||||
|
||||
self._parse_html_entities = entities
|
||||
self._separator = separator
|
||||
self._is_td = False
|
||||
self._is_th = False
|
||||
self._is_provider = False
|
||||
self._current_row = []
|
||||
self._current_cell = []
|
||||
self._rows = []
|
||||
self._source = source
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == 'td':
|
||||
self._is_td = True
|
||||
if tag == 'tr':
|
||||
self._is_th = True
|
||||
if tag == "a":
|
||||
self._current_row.append(attrs[0][1])
|
||||
|
||||
def handle_data(self, data):
|
||||
""" Save content to a cell """
|
||||
if self._is_td or self._is_th:
|
||||
self._current_cell.append(data.strip())
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == 'td':
|
||||
self._is_td = False
|
||||
elif tag == 'tr':
|
||||
self._is_th = False
|
||||
|
||||
if tag in ('td', 'th'):
|
||||
final_cell = self._separator.join(self._current_cell).strip()
|
||||
self._current_row.append(final_cell)
|
||||
self._current_cell = []
|
||||
elif tag == 'tr':
|
||||
row = self._current_row
|
||||
self._rows.append(row)
|
||||
self._current_row = []
|
||||
|
||||
def error(self, message):
|
||||
pass
|
||||
|
||||
def get_satellites_list(self, source):
|
||||
""" Getting complete list of satellites. """
|
||||
self.reset()
|
||||
self._rows.clear()
|
||||
self._source = source
|
||||
|
||||
for src in SatelliteSource.get_sources(self._source):
|
||||
request = requests.get(url=src, headers=self._HEADERS)
|
||||
reason = request.reason
|
||||
if reason == "OK":
|
||||
self.feed(request.text)
|
||||
else:
|
||||
print(reason)
|
||||
|
||||
if self._rows:
|
||||
if self._source is SatelliteSource.FLYSAT:
|
||||
def get_sat(r):
|
||||
return r[1], self.parse_position(r[2]), r[3], r[0], False
|
||||
|
||||
return list(map(get_sat, filter(lambda x: all(x) and len(x) == 5, self._rows)))
|
||||
elif self._source is SatelliteSource.LYNGSAT:
|
||||
rows = filter(lambda x: len(x) in (5, 7), self._rows)
|
||||
sats = []
|
||||
current_pos = "0"
|
||||
for row in rows:
|
||||
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))
|
||||
elif r_len == 5:
|
||||
sats.append((row[2], current_pos, row[3], row[1], False))
|
||||
return sats
|
||||
|
||||
def get_satellite(self, sat):
|
||||
pos = sat[1]
|
||||
return Satellite(name=sat[0] + " ({})".format(pos),
|
||||
flags="0",
|
||||
position=self.get_position(pos.replace(".", "")),
|
||||
transponders=self.get_transponders(sat[3]))
|
||||
|
||||
@staticmethod
|
||||
def parse_position(pos_str):
|
||||
return "".join(c for c in pos_str if c.isdigit() or c.isalpha() or c == ".")
|
||||
|
||||
@staticmethod
|
||||
def get_position(pos):
|
||||
return "{}{}".format("-" if pos[-1] == "W" else "", pos[:-1])
|
||||
|
||||
def get_transponders(self, sat_url):
|
||||
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)
|
||||
reason = request.reason
|
||||
trs = []
|
||||
if reason == "OK":
|
||||
self.feed(request.text)
|
||||
if self._source is SatelliteSource.FLYSAT:
|
||||
self.get_transponders_for_fly_sat(trs)
|
||||
elif self._source is SatelliteSource.LYNGSAT:
|
||||
self.get_transponders_for_lyng_sat(trs)
|
||||
return trs
|
||||
|
||||
def get_transponders_for_fly_sat(self, trs):
|
||||
""" Parsing transponders for FlySat """
|
||||
if self._rows:
|
||||
zeros = "000"
|
||||
for r in self._rows:
|
||||
if len(r) < 3:
|
||||
continue
|
||||
data = r[2].split(" ")
|
||||
if len(data) != 2:
|
||||
continue
|
||||
sr, fec = data
|
||||
data = r[1].split(" ")
|
||||
if len(data) < 3:
|
||||
continue
|
||||
freq, pol, sys = data[0], data[1], data[2]
|
||||
sys = sys.split("/")
|
||||
if len(sys) != 2:
|
||||
continue
|
||||
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)
|
||||
|
||||
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]?)(.*)?")
|
||||
zeros = "000"
|
||||
for r in filter(lambda x: len(x) > 8, self._rows):
|
||||
freq = re.match(frq_pol_pattern, r[2])
|
||||
if not freq:
|
||||
continue
|
||||
frq, pol = freq.group(1), freq.group(2)
|
||||
sr_fec = re.match(sr_fec_pattern, r[-3])
|
||||
if not sr_fec:
|
||||
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)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
8749
app/tools/vlc.py
Normal file
8749
app/tools/vlc.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1 @@
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk, Gdk
|
||||
|
||||
UI_RESOURCES_PATH = "app/ui/" # path to *.glade files
|
||||
|
||||
theme = Gtk.IconTheme.get_default()
|
||||
_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None
|
||||
CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(
|
||||
"emblem-readonly", 16, 0) else _IMAGE_MISSING
|
||||
LOCKED_ICON = theme.load_icon("system-lock-screen", 16, 0) if theme.lookup_icon(
|
||||
"system-lock-screen", 16, 0) else _IMAGE_MISSING
|
||||
HIDE_ICON = theme.load_icon("go-jump", 16, 0) if theme.lookup_icon("go-jump", 16, 0) else _IMAGE_MISSING
|
||||
TV_ICON = theme.load_icon("tv-symbolic", 16, 0) if theme.lookup_icon("tv-symbolic", 16, 0) else _IMAGE_MISSING
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
1252
app/ui/dialogs.glade
1252
app/ui/dialogs.glade
File diff suppressed because it is too large
Load Diff
@@ -1,35 +1,60 @@
|
||||
""" Common module for showing dialogs """
|
||||
import locale
|
||||
from enum import Enum
|
||||
|
||||
from . import Gtk, UI_RESOURCES_PATH
|
||||
from app.commons import run_idle
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
|
||||
|
||||
|
||||
class Action(Enum):
|
||||
EDIT = 0
|
||||
ADD = 1
|
||||
|
||||
|
||||
class DialogType(Enum):
|
||||
INPUT = "input_dialog"
|
||||
MESSAGE = ""
|
||||
CHOOSER = "path_chooser_dialog"
|
||||
ERROR = "error_dialog"
|
||||
QUESTION = "question_dialog"
|
||||
ABOUT = "about_dialog"
|
||||
WAIT = "wait_dialog"
|
||||
|
||||
|
||||
class WaitDialog:
|
||||
def __init__(self, transient, text=None):
|
||||
builder, dialog = get_dialog_from_xml(DialogType.WAIT, transient)
|
||||
self._dialog = dialog
|
||||
self._dialog.set_transient_for(transient)
|
||||
if text is not None:
|
||||
builder.get_object("wait_dialog_label").set_text(text)
|
||||
|
||||
def show(self):
|
||||
self._dialog.show()
|
||||
|
||||
@run_idle
|
||||
def hide(self):
|
||||
self._dialog.hide()
|
||||
|
||||
@run_idle
|
||||
def destroy(self):
|
||||
self._dialog.destroy()
|
||||
|
||||
|
||||
def show_dialog(dialog_type: DialogType, transient, text=None, options=None, action_type=None, file_filter=None):
|
||||
""" Shows dialogs by name """
|
||||
builder = Gtk.Builder()
|
||||
builder.add_from_file(UI_RESOURCES_PATH + "dialogs.glade")
|
||||
dialog = builder.get_object(dialog_type.value)
|
||||
dialog.set_transient_for(transient)
|
||||
builder, dialog = get_dialog_from_xml(dialog_type, transient)
|
||||
|
||||
if dialog_type is DialogType.CHOOSER and options:
|
||||
if action_type is not None:
|
||||
dialog.set_action(action_type)
|
||||
if file_filter is not None:
|
||||
dialog.add_filter(file_filter)
|
||||
dialog.set_current_folder(options["data_dir_path"])
|
||||
|
||||
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
|
||||
path = options["data_dir_path"]
|
||||
if dialog.get_filename():
|
||||
path = dialog.get_filename()
|
||||
if action_type is not Gtk.FileChooserAction.OPEN:
|
||||
@@ -50,12 +75,39 @@ def show_dialog(dialog_type: DialogType, transient, text=None, options=None, act
|
||||
return txt if response == Gtk.ResponseType.OK else Gtk.ResponseType.CANCEL
|
||||
|
||||
if text:
|
||||
dialog.set_markup(text)
|
||||
dialog.set_markup(get_message(text))
|
||||
response = dialog.run()
|
||||
dialog.destroy()
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def get_dialog_from_xml(dialog_type, transient):
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", (dialog_type.value,))
|
||||
dialog = builder.get_object(dialog_type.value)
|
||||
dialog.set_transient_for(transient)
|
||||
|
||||
return builder, dialog
|
||||
|
||||
|
||||
def get_chooser_dialog(transient, options, pattern, name):
|
||||
file_filter = Gtk.FileFilter()
|
||||
file_filter.add_pattern(pattern)
|
||||
file_filter.set_name(name)
|
||||
|
||||
return show_dialog(dialog_type=DialogType.CHOOSER,
|
||||
transient=transient,
|
||||
options=options,
|
||||
action_type=Gtk.FileChooserAction.OPEN,
|
||||
file_filter=file_filter)
|
||||
|
||||
|
||||
def get_message(message):
|
||||
""" returns translated message """
|
||||
return locale.dgettext(TEXT_DOMAIN, message)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
from app.commons import run_idle, run_task
|
||||
from app.ftp import download_data, DownloadDataType, upload_data
|
||||
from . import Gtk, UI_RESOURCES_PATH
|
||||
from .dialogs import show_dialog, DialogType
|
||||
from app.properties import Profile
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
|
||||
from .dialogs import show_dialog, DialogType, get_message
|
||||
|
||||
|
||||
def show_download_dialog(transient, options, open_data):
|
||||
dialog = DownloadDialog(transient, options, open_data)
|
||||
def show_download_dialog(transient, options, open_data, profile=Profile.ENIGMA_2):
|
||||
dialog = DownloadDialog(transient, options, open_data, profile)
|
||||
dialog.run()
|
||||
dialog.destroy()
|
||||
|
||||
|
||||
class DownloadDialog:
|
||||
def __init__(self, transient, properties, open_data):
|
||||
def __init__(self, transient, properties, open_data, profile):
|
||||
self._properties = properties
|
||||
self._open_data = open_data
|
||||
self._profile = profile
|
||||
|
||||
handlers = {"on_receive": self.on_receive,
|
||||
"on_send": self.on_send,
|
||||
"on_info_bar_close": self.on_info_bar_close}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", ("download_dialog",))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
@@ -33,16 +36,19 @@ class DownloadDialog:
|
||||
self._all_radio_button = builder.get_object("all_radio_button")
|
||||
self._bouquets_radio_button = builder.get_object("bouquets_radio_button")
|
||||
self._satellites_radio_button = builder.get_object("satellites_radio_button")
|
||||
self._webtv_radio_button = builder.get_object("webtv_radio_button")
|
||||
if profile is Profile.NEUTRINO_MP:
|
||||
self._webtv_radio_button.set_visible(True)
|
||||
# self._dialog.get_content_area().set_border_width(0)
|
||||
|
||||
@run_idle
|
||||
def on_receive(self, item):
|
||||
self.download(True, d_type=self.get_download_type())
|
||||
self.download(True, self.get_download_type())
|
||||
|
||||
@run_idle
|
||||
def on_send(self, item):
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.CANCEL:
|
||||
self.download(d_type=self.get_download_type())
|
||||
self.download(False, self.get_download_type())
|
||||
|
||||
def get_download_type(self):
|
||||
download_type = DownloadDataType.ALL
|
||||
@@ -50,6 +56,8 @@ class DownloadDialog:
|
||||
download_type = DownloadDataType.BOUQUETS
|
||||
elif self._satellites_radio_button.get_active():
|
||||
download_type = DownloadDataType.SATELLITES
|
||||
elif self._webtv_radio_button.get_active():
|
||||
download_type = DownloadDataType.WEBTV
|
||||
return download_type
|
||||
|
||||
def run(self):
|
||||
@@ -58,26 +66,27 @@ class DownloadDialog:
|
||||
def destroy(self):
|
||||
self._dialog.destroy()
|
||||
|
||||
def on_info_bar_close(self, *args):
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
|
||||
@run_idle
|
||||
@run_task
|
||||
def download(self, download=False, d_type=DownloadDataType.ALL):
|
||||
def download(self, download, d_type):
|
||||
""" Download/upload data from/to receiver """
|
||||
try:
|
||||
if download:
|
||||
download_data(properties=self._properties, download_type=d_type)
|
||||
else:
|
||||
self.show_info_message("Please, wait...", Gtk.MessageType.INFO)
|
||||
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
|
||||
upload_data(properties=self._properties,
|
||||
download_type=d_type,
|
||||
remove_unused=self._remove_unused_check_button.get_active())
|
||||
remove_unused=self._remove_unused_check_button.get_active(),
|
||||
profile=self._profile,
|
||||
callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
|
||||
except Exception as e:
|
||||
message = str(getattr(e, "message", str(e)))
|
||||
self.show_info_message(message, Gtk.MessageType.ERROR)
|
||||
else:
|
||||
self.show_info_message("Done!", Gtk.MessageType.INFO)
|
||||
if download and d_type is not DownloadDataType.SATELLITES:
|
||||
self._open_data()
|
||||
|
||||
|
||||
186
app/ui/iptv.py
Normal file
186
app/ui/iptv.py
Normal file
@@ -0,0 +1,186 @@
|
||||
import re
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from app.eparser.ecommons import BqServiceType, Service
|
||||
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT
|
||||
from app.properties import Profile
|
||||
from .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
|
||||
|
||||
|
||||
class IptvDialog:
|
||||
_DIGIT_ENTRY_NAME = "digit-entry"
|
||||
_ENIGMA2_REFERENCE = "{}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
|
||||
|
||||
def __init__(self, transient, view, services, bouquet, profile=Profile.ENIGMA_2, action=Action.ADD):
|
||||
handlers = {"on_entry_changed": self.on_entry_changed,
|
||||
"on_url_changed": self.on_url_changed,
|
||||
"on_save": self.on_save,
|
||||
"on_stream_type_changed": self.on_stream_type_changed}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", ("iptv_dialog", "stream_type_liststore"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._dialog = builder.get_object("iptv_dialog")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._name_entry = builder.get_object("name_entry")
|
||||
self._description_entry = builder.get_object("description_entry")
|
||||
self._url_entry = builder.get_object("url_entry")
|
||||
self._reference_entry = builder.get_object("reference_entry")
|
||||
self._srv_type_entry = builder.get_object("srv_type_entry")
|
||||
self._sid_entry = builder.get_object("sid_entry")
|
||||
self._tr_id_entry = builder.get_object("tr_id_entry")
|
||||
self._net_id_entry = builder.get_object("net_id_entry")
|
||||
self._namespace_entry = builder.get_object("namespace_entry")
|
||||
self._stream_type_combobox = builder.get_object("stream_type_combobox")
|
||||
self._add_button = builder.get_object("iptv_dialog_add_button")
|
||||
self._save_button = builder.get_object("iptv_dialog_save_button")
|
||||
self._stream_type_combobox = builder.get_object("stream_type_combobox")
|
||||
self._action = action
|
||||
self._profile = profile
|
||||
self._bouquet = bouquet
|
||||
self._services = services
|
||||
self._model, self._paths = view.get_selection().get_selected_rows()
|
||||
self._PATTERN = re.compile("(?:^[\s]*$|\D)")
|
||||
# style
|
||||
self._style_provider = Gtk.CssProvider()
|
||||
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
for el in (self._srv_type_entry, self._sid_entry, self._tr_id_entry, self._net_id_entry, self._namespace_entry):
|
||||
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
if profile is Profile.NEUTRINO_MP:
|
||||
builder.get_object("iptv_data_box").set_visible(False)
|
||||
builder.get_object("iptv_type_label").set_visible(False)
|
||||
builder.get_object("reference_entry").set_visible(False)
|
||||
builder.get_object("iptv_reference_label").set_visible(False)
|
||||
self._stream_type_combobox.set_visible(False)
|
||||
else:
|
||||
self._description_entry.set_visible(False)
|
||||
builder.get_object("iptv_description_label").set_visible(False)
|
||||
|
||||
if self._action is Action.ADD:
|
||||
self._save_button.set_visible(False)
|
||||
self._add_button.set_visible(True)
|
||||
if self._profile is Profile.ENIGMA_2:
|
||||
self._update_reference_entry()
|
||||
elif self._action is Action.EDIT:
|
||||
self._current_srv = get_base_model(self._model)[self._paths][:]
|
||||
self.init_data(self._current_srv)
|
||||
|
||||
def show(self):
|
||||
self._dialog.run()
|
||||
self._dialog.destroy()
|
||||
|
||||
def on_save(self, item):
|
||||
if not self.is_data_correct():
|
||||
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
|
||||
return
|
||||
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
self.save_enigma2_data() if self._profile is Profile.ENIGMA_2 else self.save_neutrino_data()
|
||||
self._dialog.destroy()
|
||||
|
||||
def init_data(self, srv):
|
||||
name, fav_id = srv[2], srv[7]
|
||||
self._name_entry.set_text(name)
|
||||
self.init_enigma2_data(fav_id) if self._profile is Profile.ENIGMA_2 else self.init_neutrino_data(fav_id)
|
||||
|
||||
def init_enigma2_data(self, fav_id):
|
||||
data, sep, desc = fav_id.partition("#DESCRIPTION:")
|
||||
self._description_entry.set_text(desc.strip())
|
||||
data = data.split(":")
|
||||
if len(data) < 12:
|
||||
return
|
||||
self._stream_type_combobox.set_active(0 if StreamType(data[0].strip()) is StreamType.DVB_TS else 1)
|
||||
self._srv_type_entry.set_text(data[2])
|
||||
self._sid_entry.set_text(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._update_reference_entry()
|
||||
|
||||
def init_neutrino_data(self, fav_id):
|
||||
data = fav_id.split("::")
|
||||
self._url_entry.set_text(data[0])
|
||||
self._description_entry.set_text(data[1])
|
||||
|
||||
def _update_reference_entry(self):
|
||||
if self._profile is Profile.ENIGMA_2:
|
||||
self._reference_entry.set_text(self._ENIGMA2_REFERENCE.format(self.get_type(),
|
||||
self._srv_type_entry.get_text(),
|
||||
int(self._sid_entry.get_text()),
|
||||
int(self._tr_id_entry.get_text()),
|
||||
int(self._net_id_entry.get_text()),
|
||||
int(self._namespace_entry.get_text())))
|
||||
|
||||
def get_type(self):
|
||||
return 1 if self._stream_type_combobox.get_active() == 0 else 4097
|
||||
|
||||
def on_entry_changed(self, entry):
|
||||
if self._PATTERN.search(entry.get_text()):
|
||||
entry.set_name(self._DIGIT_ENTRY_NAME)
|
||||
else:
|
||||
entry.set_name("GtkEntry")
|
||||
self._update_reference_entry()
|
||||
|
||||
def on_url_changed(self, entry):
|
||||
url = urlparse(entry.get_text())
|
||||
entry.set_name("GtkEntry" if all([url.scheme, url.netloc, url.path]) else self._DIGIT_ENTRY_NAME)
|
||||
|
||||
def on_stream_type_changed(self, item):
|
||||
self._update_reference_entry()
|
||||
|
||||
def save_enigma2_data(self):
|
||||
name = self._name_entry.get_text().strip()
|
||||
fav_id = ENIGMA2_FAV_ID_FORMAT.format(self.get_type(),
|
||||
self._srv_type_entry.get_text(),
|
||||
int(self._sid_entry.get_text()),
|
||||
int(self._tr_id_entry.get_text()),
|
||||
int(self._net_id_entry.get_text()),
|
||||
int(self._namespace_entry.get_text()),
|
||||
self._url_entry.get_text().replace(":", "%3a"),
|
||||
name, name)
|
||||
self.update_bouquet_data(name, fav_id)
|
||||
|
||||
def save_neutrino_data(self):
|
||||
if self._action is Action.EDIT:
|
||||
id_data = self._current_srv[7].split("::")
|
||||
else:
|
||||
id_data = ["", "", "0", None, None, None, None, "", "", "1"]
|
||||
id_data[0] = self._url_entry.get_text()
|
||||
id_data[1] = self._description_entry.get_text()
|
||||
self.update_bouquet_data(self._name_entry.get_text(), NEUTRINO_FAV_ID_FORMAT.format(*id_data))
|
||||
self._dialog.destroy()
|
||||
|
||||
def update_bouquet_data(self, name, fav_id):
|
||||
if self._action is Action.EDIT:
|
||||
old_srv = self._services.pop(self._current_srv[7])
|
||||
self._services[fav_id] = old_srv._replace(service=name, fav_id=fav_id)
|
||||
self._bouquet[self._paths[0][0]] = fav_id
|
||||
self._model.set(self._model.get_iter(self._paths), {2: name, 7: fav_id})
|
||||
else:
|
||||
aggr = [None] * 10
|
||||
s_type = BqServiceType.IPTV.name
|
||||
srv = (None, None, name, None, None, s_type, None, fav_id, None)
|
||||
itr = self._model.insert_after(self._model.get_iter(self._paths[0]),
|
||||
srv) if self._paths else self._model.insert(0, srv)
|
||||
self._model.set_value(itr, 1, IPTV_ICON)
|
||||
self._bouquet.insert(self._model.get_path(itr)[0], fav_id)
|
||||
self._services[fav_id] = Service(None, None, IPTV_ICON, name, *aggr[0:3], s_type, *aggr, fav_id, None)
|
||||
|
||||
def is_data_correct(self):
|
||||
for elem in (self._srv_type_entry, self._sid_entry, self._tr_id_entry, self._net_id_entry,
|
||||
self._namespace_entry, self._url_entry):
|
||||
if elem.get_name() == self._DIGIT_ENTRY_NAME:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
BIN
app/ui/lang/ru/LC_MESSAGES/demon-editor.mo
Normal file
BIN
app/ui/lang/ru/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,15 @@
|
||||
""" This is helper module for ui """
|
||||
from enum import Enum
|
||||
import os
|
||||
import shutil
|
||||
from gi.repository import GdkPixbuf
|
||||
|
||||
from app.eparser import Channel
|
||||
from app.eparser.__constants import FLAG
|
||||
from app.eparser.bouquets import BqServiceType, to_bouquet_id
|
||||
from . import Gtk, Gdk, HIDE_ICON, LOCKED_ICON
|
||||
from .dialogs import show_dialog, DialogType
|
||||
|
||||
|
||||
class ViewTarget(Enum):
|
||||
""" Used for set target view """
|
||||
BOUQUET = 0
|
||||
FAV = 1
|
||||
SERVICES = 2
|
||||
from app.commons import run_task
|
||||
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
|
||||
from .dialogs import show_dialog, DialogType, get_chooser_dialog, WaitDialog
|
||||
|
||||
|
||||
# ***************** Markers *******************#
|
||||
@@ -28,15 +25,16 @@ def insert_marker(view, bouquets, selected_bouquet, channels, parent_window):
|
||||
return
|
||||
|
||||
# Searching for max num value in all marker services (if empty default = 0)
|
||||
max_num = max(map(lambda num: int(num.data_id, 16),
|
||||
max_num = max(map(lambda num: int(num.data_id, 18),
|
||||
filter(lambda ch: ch.service_type == BqServiceType.MARKER.name, channels.values())), default=0)
|
||||
max_num = '{:x}'.format(max_num + 1)
|
||||
fav_id = "1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n".format(max_num, response, response)
|
||||
s_type = BqServiceType.MARKER.name
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
itr = model.insert_before(model.get_iter(paths[0]), (None, None, response, None, None, s_type, None, fav_id))
|
||||
channels[fav_id] = Channel(None, None, None, response, None, None, None, s_type, *[None] * 7, max_num, fav_id, None)
|
||||
marker = (None, None, response, None, None, s_type, None, fav_id, None)
|
||||
itr = model.insert_before(model.get_iter(paths[0]), marker) if paths else model.insert(0, marker)
|
||||
bouquets[selected_bouquet].insert(model.get_path(itr)[0], fav_id)
|
||||
channels[fav_id] = Service(None, None, None, response, None, None, None, s_type, *[None] * 9, max_num, fav_id, None)
|
||||
|
||||
|
||||
def edit_marker(view, bouquets, selected_bouquet, channels, parent_window):
|
||||
@@ -53,47 +51,95 @@ def edit_marker(view, bouquets, selected_bouquet, channels, parent_window):
|
||||
old_ch = channels.pop(fav_id, None)
|
||||
new_fav_id = "{}::{}\n#DESCRIPTION {}\n".format(fav_id.split("::")[0], response, response)
|
||||
model.set(itr, {2: response, 7: new_fav_id})
|
||||
channels[new_fav_id] = Channel(*old_ch[0:3], response, *old_ch[4:15], old_ch.data_id, new_fav_id, None)
|
||||
channels[new_fav_id] = old_ch._replace(service=response, fav_id=new_fav_id)
|
||||
bq_services.pop(index)
|
||||
bq_services.insert(index, new_fav_id)
|
||||
|
||||
|
||||
# ***************** Movement *******************#
|
||||
|
||||
def move_items(key, view):
|
||||
""" Move items in tree view """
|
||||
def move_items(key, view: Gtk.TreeView):
|
||||
""" Move items in the tree view """
|
||||
selection = view.get_selection()
|
||||
model, paths = selection.get_selected_rows()
|
||||
|
||||
if paths:
|
||||
# for correct down move!
|
||||
if key in (Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down):
|
||||
paths = reversed(paths)
|
||||
mod_length = len(model)
|
||||
cursor_path = view.get_cursor()[0]
|
||||
max_path = Gtk.TreePath.new_from_indices((mod_length,))
|
||||
min_path = Gtk.TreePath.new_from_indices((0,))
|
||||
is_tree_store = False
|
||||
|
||||
for path in paths:
|
||||
itr = model.get_iter(path)
|
||||
if key == Gdk.KEY_Down:
|
||||
next_itr = model.iter_next(itr)
|
||||
if next_itr:
|
||||
model.move_after(itr, next_itr)
|
||||
elif key == Gdk.KEY_Up:
|
||||
prev_itr = model.iter_previous(itr)
|
||||
if prev_itr:
|
||||
model.move_before(itr, prev_itr)
|
||||
elif key == Gdk.KEY_Page_Up or key == Gdk.KEY_KP_Page_Up:
|
||||
up_itr = model.get_iter(view.get_cursor()[0])
|
||||
if up_itr:
|
||||
model.move_before(itr, up_itr)
|
||||
elif key == Gdk.KEY_Page_Down or key == Gdk.KEY_KP_Page_Down:
|
||||
down_itr = model.get_iter(view.get_cursor()[0])
|
||||
if down_itr:
|
||||
model.move_after(itr, down_itr)
|
||||
if type(model) is Gtk.TreeStore:
|
||||
parent_paths = list(filter(lambda p: p.get_depth() == 1, paths))
|
||||
if parent_paths:
|
||||
paths = parent_paths
|
||||
min_path = model.get_path(model.get_iter_first())
|
||||
view.collapse_all()
|
||||
if mod_length == len(paths):
|
||||
return
|
||||
else:
|
||||
if not is_some_level(paths):
|
||||
return
|
||||
parent_itr = model.iter_parent(model.get_iter(paths[0]))
|
||||
parent_index = model.get_path(parent_itr)
|
||||
children_num = model.iter_n_children(parent_itr)
|
||||
if key in (Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down, Gdk.KEY_End):
|
||||
children_num -= 1
|
||||
min_path = Gtk.TreePath.new_from_string("{}:{}".format(parent_index, 0))
|
||||
max_path = Gtk.TreePath.new_from_string("{}:{}".format(parent_index, children_num))
|
||||
is_tree_store = True
|
||||
|
||||
if key == Gdk.KEY_Up:
|
||||
top_path = Gtk.TreePath(paths[0])
|
||||
top_path.prev()
|
||||
move_up(top_path, model, paths)
|
||||
elif key == Gdk.KEY_Down:
|
||||
down_path = Gtk.TreePath(paths[-1])
|
||||
down_path.next()
|
||||
if down_path < max_path:
|
||||
move_down(down_path, model, paths)
|
||||
else:
|
||||
max_path.prev()
|
||||
move_down(max_path, model, paths)
|
||||
elif key in (Gdk.KEY_Page_Up, Gdk.KEY_KP_Page_Up, Gdk.KEY_Home):
|
||||
move_up(min_path if is_tree_store else cursor_path, model, paths)
|
||||
elif key in (Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down, Gdk.KEY_End):
|
||||
move_down(max_path if is_tree_store else cursor_path, model, paths)
|
||||
|
||||
|
||||
# ***************** Edit *******************#
|
||||
def move_up(top_path, model, paths):
|
||||
top_iter = model.get_iter(top_path)
|
||||
for path in paths:
|
||||
itr = model.get_iter(path)
|
||||
model.move_before(itr, top_iter)
|
||||
top_path.next()
|
||||
top_iter = model.get_iter(top_path)
|
||||
|
||||
def edit(view, parent_window, target, fav_view=None, service_view=None, channels=None):
|
||||
|
||||
def move_down(down_path, model, paths):
|
||||
top_iter = model.get_iter(down_path)
|
||||
for path in reversed(paths):
|
||||
itr = model.get_iter(path)
|
||||
model.move_after(itr, top_iter)
|
||||
down_path.prev()
|
||||
top_iter = model.get_iter(down_path)
|
||||
|
||||
|
||||
def is_some_level(paths):
|
||||
for i in range(1, len(paths)):
|
||||
prev = paths[i - 1]
|
||||
current = paths[i]
|
||||
if len(prev) != len(current) or (len(prev) == 2 and len(current) == 2 and prev[0] != current[0]):
|
||||
return
|
||||
return True
|
||||
|
||||
|
||||
# ***************** Rename *******************#
|
||||
|
||||
def rename(view, parent_window, target, fav_view=None, service_view=None, channels=None):
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
model = get_base_model(model)
|
||||
|
||||
if not paths:
|
||||
return
|
||||
@@ -106,7 +152,7 @@ def edit(view, parent_window, target, fav_view=None, service_view=None, channels
|
||||
channel_name = None
|
||||
|
||||
if target is ViewTarget.SERVICES:
|
||||
name, fav_id = model.get(itr, 3, 16)
|
||||
name, fav_id = model.get(itr, 3, 18)
|
||||
f_id = fav_id
|
||||
response = show_dialog(DialogType.INPUT, parent_window, name)
|
||||
if response == Gtk.ResponseType.CANCEL:
|
||||
@@ -129,14 +175,14 @@ def edit(view, parent_window, target, fav_view=None, service_view=None, channels
|
||||
model.set_value(itr, 2, response)
|
||||
|
||||
if service_view is not None:
|
||||
for row in service_view.get_model():
|
||||
if row[16] == fav_id:
|
||||
for row in get_base_model(service_view.get_model()):
|
||||
if row[18] == fav_id:
|
||||
row[3] = response
|
||||
break
|
||||
|
||||
old_ch = channels.get(f_id, None)
|
||||
if old_ch:
|
||||
channels[f_id] = Channel(*old_ch[0:3], channel_name, *old_ch[4:])
|
||||
channels[f_id] = old_ch._replace(service=channel_name)
|
||||
|
||||
|
||||
# ***************** Flags *******************#
|
||||
@@ -157,16 +203,18 @@ def set_flags(flag, services_view, fav_view, channels, blacklist):
|
||||
if not paths:
|
||||
return
|
||||
|
||||
if flag is FLAG.HIDE:
|
||||
model = get_base_model(model)
|
||||
|
||||
if flag is Flag.HIDE:
|
||||
if target is ViewTarget.SERVICES:
|
||||
set_hide(channels, model, paths)
|
||||
else:
|
||||
fav_ids = [model.get_value(model.get_iter(path), 7) for path in paths]
|
||||
srv_model = services_view.get_model()
|
||||
srv_paths = [row.path for row in srv_model if row[16] in fav_ids]
|
||||
srv_model = get_base_model(services_view.get_model())
|
||||
srv_paths = [row.path for row in srv_model if row[18] in fav_ids]
|
||||
set_hide(channels, srv_model, srv_paths)
|
||||
elif flag is FLAG.LOCK:
|
||||
set_lock(blacklist, channels, model, paths, target, services_model=services_view.get_model())
|
||||
elif flag is Flag.LOCK:
|
||||
set_lock(blacklist, channels, model, paths, target, services_model=get_base_model(services_view.get_model()))
|
||||
|
||||
return True
|
||||
|
||||
@@ -179,18 +227,20 @@ def set_lock(blacklist, channels, model, paths, target, services_model):
|
||||
|
||||
for path in paths:
|
||||
itr = model.get_iter(path)
|
||||
fav_id = model.get_value(itr, 16 if target is ViewTarget.SERVICES else 7)
|
||||
fav_id = model.get_value(itr, 18 if target is ViewTarget.SERVICES else 7)
|
||||
channel = channels.get(fav_id, None)
|
||||
if channel:
|
||||
bq_id = to_bouquet_id(channel)
|
||||
if not bq_id:
|
||||
continue
|
||||
blacklist.discard(bq_id) if locked else blacklist.add(bq_id)
|
||||
model.set_value(itr, col_num, None if locked else LOCKED_ICON)
|
||||
channels[fav_id] = Channel(*channel[:4], None if locked else LOCKED_ICON, *channel[5:])
|
||||
channels[fav_id] = channel._replace(locked=None if locked else LOCKED_ICON)
|
||||
ids.append(fav_id)
|
||||
|
||||
if target is ViewTarget.FAV and ids:
|
||||
for ch in services_model:
|
||||
if ch[16] in ids:
|
||||
if ch[18] in ids:
|
||||
ch[4] = None if locked else LOCKED_ICON
|
||||
|
||||
|
||||
@@ -212,28 +262,28 @@ def set_hide(channels, model, paths):
|
||||
value = int(flag[2:]) if flag else 0
|
||||
|
||||
if not hide:
|
||||
if value in FLAG.hide_values():
|
||||
if Flag.is_hide(value):
|
||||
continue # skip if already hidden
|
||||
value += FLAG.HIDE.value
|
||||
value += Flag.HIDE.value
|
||||
else:
|
||||
if value not in FLAG.hide_values():
|
||||
if not Flag.is_hide(value):
|
||||
continue # skip if already allowed to show
|
||||
value -= FLAG.HIDE.value
|
||||
value -= Flag.HIDE.value
|
||||
|
||||
if value == 0 and index is not None:
|
||||
del flags[index]
|
||||
del flags[index]
|
||||
else:
|
||||
value = "f:{}".format(value) if value > 10 else "f:0{}".format(value)
|
||||
value = "f:{:02d}".format(value)
|
||||
if index is not None:
|
||||
flags[index] = value
|
||||
else:
|
||||
flags.append(value)
|
||||
|
||||
model.set_value(itr, 0, (",".join(reversed(sorted(flags)))))
|
||||
fav_id = model.get_value(itr, 16)
|
||||
fav_id = model.get_value(itr, 18)
|
||||
channel = channels.get(fav_id, None)
|
||||
if channel:
|
||||
channels[fav_id] = Channel(*channel[:5], None if hide else HIDE_ICON, *channel[6:])
|
||||
channels[fav_id] = channel._replace(hide=None if hide else HIDE_ICON)
|
||||
|
||||
|
||||
def has_locked_hide(model, paths, col_num):
|
||||
@@ -257,7 +307,7 @@ def locate_in_services(fav_view, services_view, parent_window):
|
||||
|
||||
fav_id = model.get_value(model.get_iter(paths[0]), 7)
|
||||
for index, row in enumerate(services_view.get_model()):
|
||||
if row[16] == fav_id:
|
||||
if row[18] == fav_id:
|
||||
scroll_to(index, services_view)
|
||||
break
|
||||
|
||||
@@ -272,5 +322,216 @@ def scroll_to(index, view, paths=None):
|
||||
selection.select_path(index)
|
||||
|
||||
|
||||
# ***************** Picons *********************#
|
||||
|
||||
def update_picons(path, picons, model):
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
|
||||
for file in os.listdir(path):
|
||||
picons[file] = get_picon_pixbuf(path + file)
|
||||
|
||||
for r in model:
|
||||
model.set_value(model.get_iter(r.path), 8, picons.get(r[9], None))
|
||||
|
||||
|
||||
def assign_picon(target, srv_view, fav_view, transient, picons, options, services):
|
||||
view = srv_view if target is ViewTarget.SERVICES else fav_view
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
if not is_only_one_item_selected(paths, transient):
|
||||
return
|
||||
|
||||
response = get_chooser_dialog(transient, options, "*.png", "png files")
|
||||
if response == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
if not str(response).endswith(".png"):
|
||||
show_dialog(DialogType.ERROR, transient, text="No png file is selected!")
|
||||
return
|
||||
|
||||
picon_pos = 8
|
||||
model = get_base_model(model)
|
||||
itr = model.get_iter(paths)
|
||||
fav_id = model.get_value(itr, 18 if target is ViewTarget.SERVICES else 7)
|
||||
picon_id = services.get(fav_id)[9]
|
||||
|
||||
if picon_id:
|
||||
picon_file = options.get("picons_dir_path") + picon_id
|
||||
if os.path.isfile(response):
|
||||
shutil.copy(response, picon_file)
|
||||
picon = get_picon_pixbuf(picon_file)
|
||||
picons[picon_id] = picon
|
||||
model.set_value(itr, picon_pos, picon)
|
||||
if target is ViewTarget.SERVICES:
|
||||
set_picon(fav_id, fav_view.get_model(), picon, 7, picon_pos)
|
||||
else:
|
||||
set_picon(fav_id, get_base_model(srv_view.get_model()), picon, 18, picon_pos)
|
||||
|
||||
|
||||
def set_picon(fav_id, model, picon, fav_id_pos, picon_pos):
|
||||
for row in model:
|
||||
if row[fav_id_pos] == fav_id:
|
||||
row[picon_pos] = picon
|
||||
break
|
||||
|
||||
|
||||
def remove_picon(target, srv_view, fav_view, picons, options):
|
||||
view = srv_view if target is ViewTarget.SERVICES else fav_view
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
model = get_base_model(model)
|
||||
|
||||
fav_ids = []
|
||||
picon_ids = []
|
||||
picon_pos = 8 # picon position is equal for services and fav
|
||||
|
||||
for path in paths:
|
||||
itr = model.get_iter(path)
|
||||
model.set_value(itr, picon_pos, None)
|
||||
if target is ViewTarget.SERVICES:
|
||||
fav_ids.append(model.get_value(itr, 18))
|
||||
picon_ids.append(model.get_value(itr, 9))
|
||||
else:
|
||||
fav_ids.append(model.get_value(itr, 7))
|
||||
|
||||
def remove(md, path, itr):
|
||||
if md.get_value(itr, 7 if target is ViewTarget.SERVICES else 18) in fav_ids:
|
||||
md.set_value(itr, picon_pos, None)
|
||||
if target is ViewTarget.FAV:
|
||||
picon_ids.append(md.get_value(itr, 9))
|
||||
|
||||
fav_view.get_model().foreach(remove) if target is ViewTarget.SERVICES else get_base_model(
|
||||
srv_view.get_model()).foreach(remove)
|
||||
|
||||
pions_path = options.get("picons_dir_path")
|
||||
backup_path = options.get("data_dir_path") + "backup/picons/"
|
||||
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
|
||||
|
||||
for p_id in picon_ids:
|
||||
picons[p_id] = None
|
||||
src = pions_path + p_id
|
||||
if os.path.isfile(src):
|
||||
shutil.move(src, backup_path + p_id)
|
||||
|
||||
|
||||
def copy_picon_reference(target, view, services, clipboard, transient):
|
||||
""" Copying picon id to clipboard """
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
if not is_only_one_item_selected(paths, transient):
|
||||
return
|
||||
|
||||
if target is ViewTarget.SERVICES:
|
||||
picon_id = model.get_value(model.get_iter(paths), 9)
|
||||
if picon_id:
|
||||
clipboard.set_text(picon_id.rstrip(".png"), -1)
|
||||
else:
|
||||
show_dialog(DialogType.ERROR, transient, "No reference is present!")
|
||||
elif target is ViewTarget.FAV:
|
||||
fav_id = model.get_value(model.get_iter(paths), 7)
|
||||
srv = services.get(fav_id, None)
|
||||
if srv and srv.picon_id:
|
||||
clipboard.set_text(srv.picon_id.rstrip(".png"), -1)
|
||||
else:
|
||||
show_dialog(DialogType.ERROR, transient, "No reference is present!")
|
||||
|
||||
|
||||
def is_only_one_item_selected(paths, transient):
|
||||
if len(paths) > 1:
|
||||
show_dialog(DialogType.ERROR, transient, "Please, select only one item!")
|
||||
return False
|
||||
|
||||
if not paths:
|
||||
show_dialog(DialogType.ERROR, transient, "No selected item!")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def get_picon_pixbuf(path):
|
||||
return GdkPixbuf.Pixbuf.new_from_file_at_scale(filename=path, width=32, height=32, preserve_aspect_ratio=True)
|
||||
|
||||
|
||||
# ***************** Bouquets *********************#
|
||||
|
||||
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
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
model = get_base_model(model)
|
||||
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][:])
|
||||
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,
|
||||
[service.package if gen_type is BqGenType.PACKAGE else
|
||||
service.pos if gen_type is BqGenType.SAT else service.service_type], profile)
|
||||
else:
|
||||
wait_dialog = WaitDialog(transient)
|
||||
wait_dialog.show()
|
||||
append_bouquets(bq_type, bq_view, callback, fav_id_index, index, model,
|
||||
{row[index] for row in model}, profile, wait_dialog)
|
||||
|
||||
|
||||
@run_task
|
||||
def append_bouquets(bq_type, bq_view, callback, fav_id_index, index, model, names, profile, wait_dialog=None):
|
||||
bq_index = 0 if profile is Profile.ENIGMA_2 else 1
|
||||
bq_view.expand_row(Gtk.TreePath(bq_index), 0)
|
||||
bqs_model = bq_view.get_model()
|
||||
bouquets_names = get_bouquets_names(bqs_model)
|
||||
|
||||
for pos, name in enumerate(sorted(names)):
|
||||
if name not in bouquets_names:
|
||||
services = [BouquetService(None, BqServiceType.DEFAULT, row[fav_id_index], 0)
|
||||
for row in model if row[index] == name]
|
||||
callback(Bouquet(name=name, type=bq_type, services=services, locked=None, hidden=None),
|
||||
bqs_model.get_iter(bq_index))
|
||||
|
||||
if wait_dialog is not None:
|
||||
wait_dialog.destroy()
|
||||
|
||||
|
||||
def get_bouquets_names(model):
|
||||
""" Returns all current bouquets names """
|
||||
bouquets_names = []
|
||||
for row in model:
|
||||
itr = row.iter
|
||||
if model.iter_has_child(itr):
|
||||
num_of_children = model.iter_n_children(itr)
|
||||
for num in range(num_of_children):
|
||||
child_itr = model.iter_nth_child(itr, num)
|
||||
bouquets_names.append(model[child_itr][0])
|
||||
return bouquets_names
|
||||
|
||||
|
||||
# ***************** Others *********************#
|
||||
|
||||
def update_entry_data(entry, dialog, options):
|
||||
""" Updates value in text entry from chooser dialog """
|
||||
response = show_dialog(dialog_type=DialogType.CHOOSER, transient=dialog, options=options)
|
||||
if response not in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
entry.set_text(response)
|
||||
return response
|
||||
return False
|
||||
|
||||
|
||||
def get_base_model(model):
|
||||
""" Returns base tree model if has wrappers ("TreeModelSort" and "TreeModelFilter") """
|
||||
if type(model) is Gtk.TreeModelSort:
|
||||
return model.get_model().get_model()
|
||||
return model
|
||||
|
||||
|
||||
def append_text_to_tview(char, view):
|
||||
""" Appending text and scrolling to a given line in the text view. """
|
||||
buf = view.get_buffer()
|
||||
buf.insert_at_cursor(char)
|
||||
insert = buf.get_insert()
|
||||
view.scroll_to_mark(insert, 0.0, True, 0.0, 1.0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
842
app/ui/picons_dialog.glade
Normal file
842
app/ui/picons_dialog.glade
Normal file
@@ -0,0 +1,842 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.18.3 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.12"/>
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<object class="GtkListStore" id="providers_list_store">
|
||||
<columns>
|
||||
<!-- column-name logo -->
|
||||
<column type="GdkPixbuf"/>
|
||||
<!-- column-name name -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name pos -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name url -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name on_id -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name selected -->
|
||||
<column type="gboolean"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkDialog" id="picons_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Picons download tool</property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="icon_name">emblem-photos</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="picons_dialog_vbox">
|
||||
<property name="width_request">320</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>
|
||||
<child>
|
||||
<object class="GtkButton" id="cancel_button">
|
||||
<property name="label">gtk-close</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_close" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</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="GtkGrid" id="grid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="column_spacing">2</property>
|
||||
<property name="column_homogeneous">True</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="ip_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="picons_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="ip_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Receiver IP:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="res_picons_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Receiver picons path:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">0</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="GtkLabel" id="picons_dir_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Current picons path:</property>
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="picons_dir_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="secondary_icon_name">folder-open-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<signal name="icon-press" handler="on_picons_dir_open" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid" id="format_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="column_spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="name_format_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Picons name format:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="box2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_right">5</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="enigma2_radio_button">
|
||||
<property name="label" translatable="yes">Enigma2 (default)</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">neutrino_mp_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="neutrino_mp_radio_button">
|
||||
<property name="label" translatable="yes">Neutrino-MP</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">enigma2_radio_button</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">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="resize_format_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Resize:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="resize_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="resize_no_radio_button">
|
||||
<property name="label" translatable="yes">No(default)</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">resize_100_60_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="resize_220_132_radio_button">
|
||||
<property name="label" translatable="yes">220x132</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">resize_100_60_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="resize_100_60_radio_button">
|
||||
<property name="label" translatable="yes">100x60</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">resize_no_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="separator3">
|
||||
<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="orientation">vertical</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="primary-toolbar"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">3</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>
|
||||
<property name="can_focus">True</property>
|
||||
<signal name="switch-page" handler="on_notebook_switch_page" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkBox" id="downloader_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">1</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="url_label">
|
||||
<property name="height_request">24</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Satellite url (www.lyngsat.com):</property>
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
<style>
|
||||
<class name="primary-toolbar"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">1</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="url_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_name">network-workgroup-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="placeholder_text" translatable="yes">https://www.lyngsat.com/*satellite*.html</property>
|
||||
<property name="input_purpose">url</property>
|
||||
<signal name="changed" handler="on_url_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="providers_scrolled_window">
|
||||
<property name="height_request">150</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">out</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="providers_tree_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="has_focus">True</property>
|
||||
<property name="model">providers_list_store</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="treeview_selection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="provider_column">
|
||||
<property name="spacing">15</property>
|
||||
<property name="title" translatable="yes">Providers</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="logo_cellrendererpixbuf"/>
|
||||
<attributes>
|
||||
<attribute name="pixbuf">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="name_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="pos_column">
|
||||
<property name="sizing">autosize</property>
|
||||
<property name="title" translatable="yes">Position</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="pos_cellrenderertext1">
|
||||
<property name="xalign">0.50999999046325684</property>
|
||||
<property name="editable">True</property>
|
||||
<signal name="edited" handler="on_position_edited" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="url_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title" translatable="yes">Url</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="url_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="on_id_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title" translatable="yes">ONID</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="onid_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="selected_column">
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="cellrenderer_toggle">
|
||||
<signal name="toggled" handler="on_selected_toggled" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">5</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>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="downloader_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Downloader</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="converter_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="converter_header_label">
|
||||
<property name="height_request">24</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Converter between name formats</property>
|
||||
<style>
|
||||
<class name="primary-toolbar"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid" id="converter_grid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="row_spacing">5</property>
|
||||
<property name="column_spacing">2</property>
|
||||
<property name="column_homogeneous">True</property>
|
||||
<child>
|
||||
<object class="GtkFileChooserButton" id="enigma2_path_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="action">select-folder</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="picons_path_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Path to Enigma2 picons:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="save_to_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Path to save:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFileChooserButton" id="save_to_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="action">select-folder</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="converter_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Converter between name formats</property>
|
||||
<property name="label" translatable="yes">Converter</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="separator2">
|
||||
<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">10</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolbar" id="toolbar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="show_arrow">False</property>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="cancel_tool_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Cancel</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-cancel</property>
|
||||
<signal name="clicked" handler="on_cancel" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorToolItem" id="separatortoolitem2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="convert_tool_button">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="is_important">True</property>
|
||||
<property name="label" translatable="yes">Convert</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-execute</property>
|
||||
<signal name="clicked" handler="on_convert" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="receive_tool_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Receive picons for providers</property>
|
||||
<property name="is_important">True</property>
|
||||
<property name="label" translatable="yes">Receive picons</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="icon_name">go-bottom</property>
|
||||
<signal name="clicked" handler="on_receive" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="load_providers_tool_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Load satellite providers.</property>
|
||||
<property name="is_important">True</property>
|
||||
<property name="label" translatable="yes">Load providers</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="icon_name">network-server-symbolic</property>
|
||||
<signal name="clicked" handler="on_load_providers" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorToolItem" id="separatortoolitem1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="send_tool_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Transfer to receiver</property>
|
||||
<property name="label" translatable="yes">Send</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="icon_name">go-top</property>
|
||||
<signal name="clicked" handler="on_send" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="primary-toolbar"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">11</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkExpander" id="expander">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="resize_toplevel">True</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolled_window">
|
||||
<property name="height_request">150</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="min_content_width">240</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="wrap_mode">word-char</property>
|
||||
<property name="overwrite">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="expander_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Extra:</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">13</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="info_bar">
|
||||
<property name="app_paintable">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</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" id="infobar-action_area1">
|
||||
<property name="can_focus">False</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" id="infobar-content_area1">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="info_bar_message_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label">Info</property>
|
||||
<property name="justify">center</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="wrap_mode">word-char</property>
|
||||
<property name="lines">2</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</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>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">14</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="separator">
|
||||
<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">15</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkListStore" id="sat_position_list_store">
|
||||
<columns>
|
||||
<!-- column-name pos -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
</interface>
|
||||
288
app/ui/picons_downloader.py
Normal file
288
app/ui/picons_downloader.py
Normal file
@@ -0,0 +1,288 @@
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from gi.repository import GLib, GdkPixbuf
|
||||
|
||||
from app.commons import run_idle, run_task
|
||||
from app.ftp import upload_data, DownloadDataType
|
||||
from app.tools.picons import PiconsParser, parse_providers, Provider, convert_to
|
||||
from app.properties import Profile
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN
|
||||
from .dialogs import show_dialog, DialogType, get_message
|
||||
from .main_helper import update_entry_data, append_text_to_tview
|
||||
|
||||
|
||||
class PiconsDialog:
|
||||
def __init__(self, transient, options, picon_ids, profile=Profile.ENIGMA_2):
|
||||
self._picon_ids = picon_ids
|
||||
self._TMP_DIR = tempfile.gettempdir() + "/"
|
||||
self._BASE_URL = "www.lyngsat.com/packages/"
|
||||
self._PATTERN = re.compile("^https://www\.lyngsat\.com/[\w-]+\.html$")
|
||||
self._current_process = None
|
||||
self._terminate = False
|
||||
|
||||
handlers = {"on_receive": self.on_receive,
|
||||
"on_load_providers": self.on_load_providers,
|
||||
"on_cancel": self.on_cancel,
|
||||
"on_close": self.on_close,
|
||||
"on_send": self.on_send,
|
||||
"on_info_bar_close": self.on_info_bar_close,
|
||||
"on_picons_dir_open": self.on_picons_dir_open,
|
||||
"on_selected_toggled": self.on_selected_toggled,
|
||||
"on_url_changed": self.on_url_changed,
|
||||
"on_position_edited": self.on_position_edited,
|
||||
"on_notebook_switch_page": self.on_notebook_switch_page,
|
||||
"on_convert": self.on_convert}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_file(UI_RESOURCES_PATH + "picons_dialog.glade",
|
||||
("picons_dialog", "receive_image", "providers_list_store"))
|
||||
builder.connect_signals(handlers)
|
||||
self._dialog = builder.get_object("picons_dialog")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._providers_tree_view = builder.get_object("providers_tree_view")
|
||||
self._expander = builder.get_object("expander")
|
||||
self._text_view = builder.get_object("text_view")
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._ip_entry = builder.get_object("ip_entry")
|
||||
self._picons_entry = builder.get_object("picons_entry")
|
||||
self._url_entry = builder.get_object("url_entry")
|
||||
self._picons_dir_entry = builder.get_object("picons_dir_entry")
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._message_label = builder.get_object("info_bar_message_label")
|
||||
self._load_providers_tool_button = builder.get_object("load_providers_tool_button")
|
||||
self._receive_tool_button = builder.get_object("receive_tool_button")
|
||||
self._convert_tool_button = builder.get_object("convert_tool_button")
|
||||
self._enigma2_path_button = builder.get_object("enigma2_path_button")
|
||||
self._save_to_button = builder.get_object("save_to_button")
|
||||
self._send_tool_button = builder.get_object("send_tool_button")
|
||||
self._enigma2_radio_button = builder.get_object("enigma2_radio_button")
|
||||
self._neutrino_mp_radio_button = builder.get_object("neutrino_mp_radio_button")
|
||||
self._resize_no_radio_button = builder.get_object("resize_no_radio_button")
|
||||
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")
|
||||
# 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._properties = options.get(profile.value)
|
||||
self._profile = profile
|
||||
self._ip_entry.set_text(self._properties.get("host", ""))
|
||||
self._picons_entry.set_text(self._properties.get("picons_path", ""))
|
||||
self._picons_path = self._properties.get("picons_dir_path", "")
|
||||
self._picons_dir_entry.set_text(self._picons_path)
|
||||
self._enigma2_picons_path = self._picons_path
|
||||
if profile is Profile.NEUTRINO_MP:
|
||||
self._enigma2_picons_path = options.get(Profile.ENIGMA_2.value).get("picons_dir_path", "")
|
||||
if not len(self._picon_ids) and self._profile is Profile.ENIGMA_2:
|
||||
message = get_message("To automatically set the identifiers for picons,\n"
|
||||
"first load the required services list into the main application window.")
|
||||
self.show_info_message(message, Gtk.MessageType.WARNING)
|
||||
|
||||
def show(self):
|
||||
self._dialog.run()
|
||||
self._dialog.destroy()
|
||||
|
||||
@run_idle
|
||||
def on_load_providers(self, item):
|
||||
self._expander.set_expanded(True)
|
||||
url = self._url_entry.get_text()
|
||||
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
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]), p.name, p.pos, p.url, p.on_id, p.selected))
|
||||
self.update_receive_button_state()
|
||||
|
||||
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.start_download()
|
||||
|
||||
@run_task
|
||||
def start_download(self):
|
||||
if self._current_process.poll() is None:
|
||||
self.show_dialog("The task is already running!", DialogType.ERROR)
|
||||
return
|
||||
|
||||
self._terminate = False
|
||||
self._expander.set_expanded(True)
|
||||
|
||||
providers = self.get_selected_providers()
|
||||
for prv in providers:
|
||||
if not prv[2] and prv[2][:-2].isdigit():
|
||||
self.show_info_message(
|
||||
get_message("Specify the correct position value for the provider!"), Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
for prv in providers:
|
||||
if self._terminate:
|
||||
break
|
||||
self.process_provider(Provider(*prv))
|
||||
|
||||
def process_provider(self, prv):
|
||||
url = prv.url
|
||||
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
|
||||
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
GLib.io_add_watch(self._current_process.stderr, GLib.IO_IN, self.write_to_buffer)
|
||||
self._current_process.wait()
|
||||
path = self._TMP_DIR + self._BASE_URL + url[url.rfind("/") + 1:]
|
||||
pos = "".join(c for c in prv.pos if c.isdigit())
|
||||
PiconsParser.parse(path, self._picons_path, self._TMP_DIR, prv.on_id, pos,
|
||||
self._picon_ids, self.get_picons_format())
|
||||
self.resize(self._picons_path)
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
|
||||
def write_to_buffer(self, fd, condition):
|
||||
if condition == GLib.IO_IN:
|
||||
char = fd.read(1)
|
||||
self.append_output(char)
|
||||
return True
|
||||
else:
|
||||
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()
|
||||
self._current_process = subprocess.Popen(command, universal_newlines=True, cwd=path)
|
||||
self._current_process.wait()
|
||||
|
||||
@run_task
|
||||
def on_cancel(self, item):
|
||||
if self._current_process:
|
||||
self._terminate = True
|
||||
self._current_process.terminate()
|
||||
time.sleep(1)
|
||||
|
||||
@run_idle
|
||||
def on_close(self, item):
|
||||
self.on_cancel(item)
|
||||
self._dialog.destroy()
|
||||
|
||||
def on_send(self, item):
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
|
||||
self.upload_picons()
|
||||
|
||||
@run_task
|
||||
def upload_picons(self):
|
||||
if self._current_process is not None and self._current_process.poll() is None:
|
||||
self.show_dialog("The task is already running!", DialogType.ERROR)
|
||||
return
|
||||
|
||||
upload_data(properties=self._properties,
|
||||
download_type=DownloadDataType.PICONS,
|
||||
profile=self._profile,
|
||||
callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
|
||||
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
|
||||
@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_picons_dir_open(self, entry, icon, event_button):
|
||||
update_entry_data(entry, self._dialog, options={"data_dir_path": self._picons_path})
|
||||
|
||||
@run_idle
|
||||
def on_selected_toggled(self, toggle, path):
|
||||
model = self._providers_tree_view.get_model()
|
||||
model.set_value(model.get_iter(path), 5, not toggle.get_active())
|
||||
self.update_receive_button_state()
|
||||
|
||||
def on_url_changed(self, entry):
|
||||
suit = self._PATTERN.search(entry.get_text())
|
||||
entry.set_name("GtkEntry" if suit else "digit-entry")
|
||||
self._load_providers_tool_button.set_sensitive(suit if suit else False)
|
||||
|
||||
def on_position_edited(self, render, path, value):
|
||||
model = self._providers_tree_view.get_model()
|
||||
model.set_value(model.get_iter(path), 2, value)
|
||||
|
||||
@run_idle
|
||||
def on_notebook_switch_page(self, nb, box, tab_num):
|
||||
self._load_providers_tool_button.set_visible(not tab_num)
|
||||
self._receive_tool_button.set_visible(not tab_num)
|
||||
self._convert_tool_button.set_visible(tab_num)
|
||||
self._send_tool_button.set_sensitive(not tab_num)
|
||||
|
||||
if self._enigma2_path_button.get_filename() is None:
|
||||
self._enigma2_path_button.set_current_folder(self._enigma2_picons_path)
|
||||
|
||||
@run_idle
|
||||
def on_convert(self, item):
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
picons_path = self._enigma2_path_button.get_filename()
|
||||
save_path = self._save_to_button.get_filename()
|
||||
if not picons_path or not save_path:
|
||||
show_dialog(DialogType.ERROR, transient=self._dialog, text="Select paths!")
|
||||
return
|
||||
|
||||
self._expander.set_expanded(True)
|
||||
convert_to(src_path=picons_path,
|
||||
dest_path=save_path,
|
||||
profile=Profile.ENIGMA_2,
|
||||
callback=self.append_output,
|
||||
done_callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
|
||||
|
||||
@run_idle
|
||||
def update_receive_button_state(self):
|
||||
self._receive_tool_button.set_sensitive(len(self.get_selected_providers()) > 0)
|
||||
|
||||
def get_selected_providers(self):
|
||||
""" returns selected providers """
|
||||
return [r for r in self._providers_tree_view.get_model() if r[5]]
|
||||
|
||||
@run_idle
|
||||
def show_dialog(self, message, dialog_type):
|
||||
show_dialog(dialog_type, self._dialog, message)
|
||||
|
||||
def get_picons_format(self):
|
||||
picon_format = Profile.ENIGMA_2
|
||||
|
||||
if self._neutrino_mp_radio_button.get_active():
|
||||
picon_format = Profile.NEUTRINO_MP
|
||||
|
||||
return picon_format
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -66,6 +66,9 @@
|
||||
<row>
|
||||
<col id="0" translatable="yes">9/10</col>
|
||||
</row>
|
||||
<row>
|
||||
<col id="0" translatable="yes">Auto</col>
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkListStore" id="mod_store">
|
||||
@@ -191,6 +194,11 @@
|
||||
<property name="step_increment">0.10000000000000001</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="pos_adjustment2">
|
||||
<property name="upper">180</property>
|
||||
<property name="step_increment">0.10000000000000001</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkTreeStore" id="satellites_tree_store">
|
||||
<columns>
|
||||
<!-- column-name satelitte -->
|
||||
@@ -243,6 +251,8 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -446,6 +456,31 @@
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="update_tool_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Update from internet</property>
|
||||
<property name="label" translatable="yes">Receive from internet</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-refresh</property>
|
||||
<signal name="clicked" handler="on_update" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorToolItem" id="separatortoolitem4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="up_tool_button">
|
||||
<property name="visible">True</property>
|
||||
@@ -585,7 +620,7 @@
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="freq_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title" translatable="yes">Freq.</property>
|
||||
<property name="title" translatable="yes">Freq</property>
|
||||
<property name="expand">True</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="frequency_cellrenderertext"/>
|
||||
@@ -611,7 +646,7 @@
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="pol_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title" translatable="yes">Pol.</property>
|
||||
<property name="title" translatable="yes">Pol</property>
|
||||
<property name="expand">True</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="sat_pol_cellrenderertext"/>
|
||||
@@ -624,7 +659,7 @@
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fec_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title" translatable="yes">Fec.</property>
|
||||
<property name="title" translatable="yes">FEC</property>
|
||||
<property name="expand">True</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="set_fec_cellrenderertext"/>
|
||||
@@ -650,7 +685,7 @@
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="mod_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title" translatable="yes">Mod.</property>
|
||||
<property name="title" translatable="yes">Mod</property>
|
||||
<property name="expand">True</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="mod_cellrenderertext"/>
|
||||
@@ -734,13 +769,24 @@
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
<property name="position">4</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="GtkSeparator" id="separator5">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
@@ -1060,7 +1106,7 @@
|
||||
<object class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Freq.</property>
|
||||
<property name="label" translatable="yes">Freq</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
@@ -1082,7 +1128,7 @@
|
||||
<object class="GtkLabel" id="label3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Pol.</property>
|
||||
<property name="label" translatable="yes">Pol</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
@@ -1093,7 +1139,7 @@
|
||||
<object class="GtkLabel" id="label4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Fec.</property>
|
||||
<property name="label" translatable="yes">FEC</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">3</property>
|
||||
@@ -1104,7 +1150,7 @@
|
||||
<object class="GtkLabel" id="label5">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Sys.</property>
|
||||
<property name="label" translatable="yes">System</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">4</property>
|
||||
@@ -1115,7 +1161,7 @@
|
||||
<object class="GtkLabel" id="label6">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Mod.</property>
|
||||
<property name="label" translatable="yes">Mod</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">5</property>
|
||||
@@ -1348,7 +1394,7 @@
|
||||
<object class="GtkLabel" id="expander_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Extra</property>
|
||||
<property name="label" translatable="yes">Extra:</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -1372,4 +1418,734 @@
|
||||
<action-widget response="-5">button2</action-widget>
|
||||
</action-widgets>
|
||||
</object>
|
||||
<object class="GtkListStore" id="update_sat_list_store">
|
||||
<columns>
|
||||
<!-- column-name satellite -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name position -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name type -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name url -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name selected -->
|
||||
<column type="gboolean"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkTreeModelFilter" id="update_sat_list_model_filter">
|
||||
<property name="child_model">update_sat_list_store</property>
|
||||
</object>
|
||||
<object class="GtkTreeModelSort" id="update_sat_list_model_sort">
|
||||
<property name="model">update_sat_list_model_filter</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="update_source_store">
|
||||
<columns>
|
||||
<!-- column-name source -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
<data>
|
||||
<row>
|
||||
<col id="0">FlySat</col>
|
||||
</row>
|
||||
<row>
|
||||
<col id="0">LyngSat</col>
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkDialog" id="satellites_update_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Satellites update</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="default_height">480</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="sat_update_dialog_vbox">
|
||||
<property name="width_request">480</property>
|
||||
<property name="height_request">320</property>
|
||||
<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="sat_update_dialog_action_area">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">center</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="sat_update_close_button">
|
||||
<property name="label">gtk-close</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>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="satellites_update_main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkToolbar" id="sat_update_header_tool_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkToolItem" id="sat_update_source_tool_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="source_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin_left">2</property>
|
||||
<property name="label" translatable="yes">Source:</property>
|
||||
<property name="track_visited_links">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolItem" id="source_tool_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="source_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="model">update_source_store</property>
|
||||
<property name="active">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="source_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolItem" id="sep_tool_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="sat_update_filter_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="is_important">True</property>
|
||||
<property name="label" translatable="yes">Filter</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-select-all</property>
|
||||
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="sat_update_find_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="is_important">True</property>
|
||||
<property name="label" translatable="yes">Find</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-find</property>
|
||||
<signal name="toggled" handler="on_find_toggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="primary-toolbar"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="sat_update_search_info_bar">
|
||||
<property name="app_paintable">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox" id="infobar-action_area4">
|
||||
<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" id="infobar-content_area4">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="search_bar_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="sat_update_search_entry">
|
||||
<property name="width_request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_name">edit-find-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
<signal name="search-changed" handler="on_search" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="sat_update_search_down_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<signal name="clicked" handler="on_search_down" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkArrow" id="arrow1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="arrow_type">down</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="sat_update_search_up_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<signal name="clicked" handler="on_search_up" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkArrow" id="arrow2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="arrow_type">up</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</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>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="sat_update_filter_info_bar">
|
||||
<property name="app_paintable">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox" id="infobar-action_area3">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</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" id="infobar-content_area3">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<child>
|
||||
<object class="GtkGrid" id="source_header_grid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="column_spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="from_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">From:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="from_pos_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="input_purpose">number</property>
|
||||
<property name="adjustment">pos_adjustment</property>
|
||||
<property name="digits">1</property>
|
||||
<property name="numeric">True</property>
|
||||
<signal name="changed" handler="on_filter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="filter_from_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="model">side_store</property>
|
||||
<property name="active">0</property>
|
||||
<signal name="changed" handler="on_filter" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="from_filter_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="to_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">To:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">3</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="to_pos_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="input_purpose">number</property>
|
||||
<property name="adjustment">pos_adjustment2</property>
|
||||
<property name="digits">1</property>
|
||||
<property name="numeric">True</property>
|
||||
<signal name="changed" handler="on_filter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">4</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="filter_to_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="model">side_store</property>
|
||||
<property name="active">0</property>
|
||||
<signal name="changed" handler="on_filter" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="filter_to_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">5</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="filter_apply_button">
|
||||
<property name="label">gtk-apply</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="clicked" handler="on_filter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">6</property>
|
||||
<property name="top_attach">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">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>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="separator8">
|
||||
<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">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="sat_update_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="sat_update_tree_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">update_sat_list_model_sort</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="sat_update_treeview_selection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="upd_satellite_column">
|
||||
<property name="title" translatable="yes">Satellite</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_satellite_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="upd_position_column">
|
||||
<property name="title" translatable="yes">Position</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">1</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_position_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="upd_type_column">
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">2</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_type_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="upd_url_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title" translatable="yes">Url</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_url_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="upd_selected_treeviewcolumn">
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">4</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="upd_selected_cellrenderertoggle">
|
||||
<signal name="toggled" handler="on_selected_toggled" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="separator7">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">2</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolbar" id="sat_update_toolbar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="cancel_tool_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="is_important">True</property>
|
||||
<property name="label" translatable="yes">Cancel</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-cancel</property>
|
||||
<signal name="clicked" handler="on_cancel_receive" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="update_sat_list_tool_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="is_important">True</property>
|
||||
<property name="label" translatable="yes">Update</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-refresh</property>
|
||||
<signal name="clicked" handler="on_update_satellites_list" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="receive_sat_list_tool_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="is_important">True</property>
|
||||
<property name="label" translatable="yes">Receive</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-goto-bottom</property>
|
||||
<signal name="clicked" handler="on_receive_satellites_list" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="primary-toolbar"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkExpander" id="sat_update_expander">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="text_view_scrolled_window">
|
||||
<property name="height_request">120</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="editable">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="Extra:">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Extra:</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="sat_update_info_bar">
|
||||
<property name="app_paintable">True</property>
|
||||
<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" id="infobar-action_area2">
|
||||
<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" id="infobar-content_area2">
|
||||
<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="label" translatable="yes">Info</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>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">9</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="separator6">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">2</property>
|
||||
<property name="position">10</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<action-widgets>
|
||||
<action-widget response="-6">sat_update_close_button</action-widget>
|
||||
</action-widgets>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
import re
|
||||
import time
|
||||
import concurrent.futures
|
||||
from math import fabs
|
||||
|
||||
from app.commons import run_idle
|
||||
from app.commons import run_idle, run_task
|
||||
from app.eparser import get_satellites, write_satellites, Satellite, Transponder
|
||||
from . import Gtk, Gdk, UI_RESOURCES_PATH
|
||||
from .dialogs import show_dialog, DialogType
|
||||
from .main_helper import move_items, scroll_to
|
||||
from app.tools.satellites import SatellitesParser, SatelliteSource
|
||||
from .search import SearchProvider
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, MOVE_KEYS
|
||||
from .dialogs import show_dialog, DialogType, WaitDialog
|
||||
from .main_helper import move_items, scroll_to, append_text_to_tview, get_base_model
|
||||
|
||||
|
||||
def show_satellites_dialog(transient, options):
|
||||
dialog = SatellitesDialog(transient, options)
|
||||
dialog.run()
|
||||
dialog.destroy()
|
||||
SatellitesDialog(transient, options).show()
|
||||
|
||||
|
||||
class SatellitesDialog:
|
||||
__slots__ = ["_dialog", "_data_path", "_stores", "_options", "_sat_view"]
|
||||
|
||||
_aggr = [None for x in range(9)] # aggregate
|
||||
|
||||
def __init__(self, transient, options):
|
||||
self._data_path = options["data_dir_path"] + "satellites.xml"
|
||||
self._data_path = options.get("data_dir_path") + "satellites.xml"
|
||||
self._options = options
|
||||
|
||||
handlers = {"on_open": self.on_open,
|
||||
"on_remove": self.on_remove,
|
||||
"on_save": self.on_save,
|
||||
"on_update": self.on_update,
|
||||
"on_up": self.on_up,
|
||||
"on_down": self.on_down,
|
||||
"on_popup_menu": self.on_popup_menu,
|
||||
@@ -38,9 +39,10 @@ class SatellitesDialog:
|
||||
"on_quit": self.on_quit}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
|
||||
("satellites_editor_dialog", "satellites_tree_store",
|
||||
"popup_menu", "add_popup_menu", "add_menu_icon"))
|
||||
("satellites_editor_dialog", "satellites_tree_store", "popup_menu",
|
||||
"add_popup_menu", "add_menu_icon", "receive_menu_icon"))
|
||||
builder.connect_signals(handlers)
|
||||
# Adding custom image for add_menu_tool_button
|
||||
add_menu_tool_button = builder.get_object("add_menu_tool_button")
|
||||
@@ -50,6 +52,7 @@ class SatellitesDialog:
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._dialog.get_content_area().set_border_width(0) # The width of the border around the app dialog area!
|
||||
self._sat_view = builder.get_object("satellites_editor_tree_view")
|
||||
self._wait_dialog = WaitDialog(self._dialog)
|
||||
# Setting the last size of the dialog window if it was saved
|
||||
window_size = self._options.get("sat_editor_window_size", None)
|
||||
if window_size:
|
||||
@@ -61,10 +64,9 @@ class SatellitesDialog:
|
||||
6: builder.get_object("mod_store")}
|
||||
self.on_satellites_list_load(self._sat_view.get_model())
|
||||
|
||||
def run(self):
|
||||
@run_idle
|
||||
def show(self):
|
||||
self._dialog.run()
|
||||
|
||||
def destroy(self):
|
||||
self._dialog.destroy()
|
||||
|
||||
def on_resize(self, window):
|
||||
@@ -73,7 +75,7 @@ class SatellitesDialog:
|
||||
self._options["sat_editor_window_size"] = window.get_size()
|
||||
|
||||
def on_quit(self, item):
|
||||
self.destroy()
|
||||
self._dialog.destroy()
|
||||
|
||||
def on_open(self, model):
|
||||
file_filter = Gtk.FileFilter()
|
||||
@@ -123,15 +125,16 @@ class SatellitesDialog:
|
||||
self.on_transponder()
|
||||
elif key == Gdk.KEY_space:
|
||||
pass
|
||||
elif ctrl and key in (Gdk.KEY_Up, Gdk.KEY_Page_Up, Gdk.KEY_KP_Page_Up): # KEY_KP_Page_Up for laptop!
|
||||
move_items(key, self._sat_view)
|
||||
elif ctrl and key in (Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down):
|
||||
elif ctrl and key in MOVE_KEYS:
|
||||
move_items(key, self._sat_view)
|
||||
elif key == Gdk.KEY_Left or key == Gdk.KEY_Right:
|
||||
view.do_unselect_all(view)
|
||||
|
||||
@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)
|
||||
except FileNotFoundError as e:
|
||||
show_dialog(DialogType.ERROR, self._dialog, getattr(e, "message", str(e)) +
|
||||
@@ -139,13 +142,13 @@ class SatellitesDialog:
|
||||
else:
|
||||
model.clear()
|
||||
self.append_data(model, satellites)
|
||||
finally:
|
||||
self._wait_dialog.hide()
|
||||
|
||||
@run_idle
|
||||
def append_data(self, model, satellites):
|
||||
for name, flags, pos, transponders in satellites:
|
||||
parent = model.append(None, [name, *self._aggr, flags, pos])
|
||||
for transponder in transponders:
|
||||
model.append(parent, ["Transponder:", *transponder, None, None])
|
||||
for sat in satellites:
|
||||
append_satellite(model, sat)
|
||||
|
||||
def on_add(self, view):
|
||||
""" Common adding """
|
||||
@@ -248,9 +251,7 @@ class SatellitesDialog:
|
||||
returns selected path or None
|
||||
"""
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
paths_count = len(paths)
|
||||
|
||||
if paths_count > 1:
|
||||
if len(paths) > 1:
|
||||
show_dialog(DialogType.ERROR, self._dialog, message)
|
||||
return
|
||||
|
||||
@@ -260,9 +261,8 @@ class SatellitesDialog:
|
||||
def on_remove(view):
|
||||
selection = view.get_selection()
|
||||
model, paths = selection.get_selected_rows()
|
||||
itrs = [model.get_iter(path) for path in paths]
|
||||
|
||||
for itr in itrs:
|
||||
for itr in [model.get_iter(path) for path in paths]:
|
||||
model.remove(itr)
|
||||
|
||||
def on_save(self, view):
|
||||
@@ -274,6 +274,11 @@ class SatellitesDialog:
|
||||
model.foreach(self.parse_data, satellites)
|
||||
write_satellites(satellites, self._data_path)
|
||||
|
||||
def on_update(self, item):
|
||||
dialog = SatellitesUpdateDialog(self._dialog, self._sat_view.get_model())
|
||||
dialog.run()
|
||||
dialog.destroy()
|
||||
|
||||
@staticmethod
|
||||
def parse_data(model, path, itr, sats):
|
||||
if model.iter_has_child(itr):
|
||||
@@ -296,6 +301,8 @@ class SatellitesDialog:
|
||||
menu.popup(None, None, None, None, event.button, event.time)
|
||||
|
||||
|
||||
# ***************** Transponder dialog *******************#
|
||||
|
||||
class TransponderDialog:
|
||||
""" Shows dialog for adding or edit transponder """
|
||||
|
||||
@@ -304,10 +311,9 @@ class TransponderDialog:
|
||||
handlers = {"on_entry_changed": self.on_entry_changed}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
|
||||
("transponder_dialog",
|
||||
"pol_store", "fec_store",
|
||||
"mod_store", "system_store",
|
||||
("transponder_dialog", "pol_store", "fec_store", "mod_store", "system_store",
|
||||
"pls_mode_store"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
@@ -382,11 +388,14 @@ class TransponderDialog:
|
||||
return True
|
||||
|
||||
|
||||
# ***************** Satellite dialog *******************#
|
||||
|
||||
class SatelliteDialog:
|
||||
""" Shows dialog for adding or edit satellite """
|
||||
|
||||
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"))
|
||||
|
||||
@@ -422,5 +431,240 @@ class SatelliteDialog:
|
||||
return Satellite(name=name, flags="0", position=pos, transponders=None)
|
||||
|
||||
|
||||
# ***************** Satellite update dialog *******************#
|
||||
|
||||
class SatellitesUpdateDialog:
|
||||
""" Dialog for update satellites over internet """
|
||||
|
||||
def __init__(self, transient, main_model):
|
||||
handlers = {"on_update_satellites_list": self.on_update_satellites_list,
|
||||
"on_receive_satellites_list": self.on_receive_satellites_list,
|
||||
"on_cancel_receive": self.on_cancel_receive,
|
||||
"on_selected_toggled": self.on_selected_toggled,
|
||||
"on_info_bar_close": self.on_info_bar_close,
|
||||
"on_filter_toggled": self.on_filter_toggled,
|
||||
"on_find_toggled": self.on_find_toggled,
|
||||
"on_filter": self.on_filter,
|
||||
"on_search": self.on_search,
|
||||
"on_search_down": self.on_search_down,
|
||||
"on_search_up": self.on_search_up,
|
||||
"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_update_dialog", "update_source_store", "update_sat_list_store",
|
||||
"update_sat_list_model_filter", "update_sat_list_model_sort", "side_store",
|
||||
"pos_adjustment", "pos_adjustment2"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._dialog = builder.get_object("satellites_update_dialog")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._main_model = main_model
|
||||
# self._dialog.get_content_area().set_border_width(0)
|
||||
self._sat_view = builder.get_object("sat_update_tree_view")
|
||||
self._source_box = builder.get_object("source_combo_box")
|
||||
self._sat_update_expander = builder.get_object("sat_update_expander")
|
||||
self._text_view = builder.get_object("text_view")
|
||||
self._receive_button = builder.get_object("receive_sat_list_tool_button")
|
||||
self._sat_update_info_bar = builder.get_object("sat_update_info_bar")
|
||||
self._info_bar_message_label = builder.get_object("info_bar_message_label")
|
||||
# Filter
|
||||
self._filter_info_bar = builder.get_object("sat_update_filter_info_bar")
|
||||
self._from_pos_button = builder.get_object("from_pos_button")
|
||||
self._to_pos_button = builder.get_object("to_pos_button")
|
||||
self._filter_from_combo_box = builder.get_object("filter_from_combo_box")
|
||||
self._filter_to_combo_box = builder.get_object("filter_to_combo_box")
|
||||
self._filter_model = builder.get_object("update_sat_list_model_filter")
|
||||
self._filter_model.set_visible_func(self.filter_function)
|
||||
self._filter_positions = (0, 0)
|
||||
# Search
|
||||
self._search_info_bar = builder.get_object("sat_update_search_info_bar")
|
||||
self._search_provider = SearchProvider((self._sat_view,),
|
||||
builder.get_object("sat_update_search_down_button"),
|
||||
builder.get_object("sat_update_search_up_button"))
|
||||
|
||||
self._download_task = False
|
||||
self._parser = None
|
||||
|
||||
def run(self):
|
||||
if self._dialog.run() == Gtk.ResponseType.CANCEL:
|
||||
self._download_task = False
|
||||
return
|
||||
|
||||
def destroy(self):
|
||||
self._dialog.destroy()
|
||||
|
||||
def on_update_satellites_list(self, item):
|
||||
if self._download_task:
|
||||
show_dialog(DialogType.ERROR, self._dialog, "The task is already running!")
|
||||
return
|
||||
|
||||
model = get_base_model(self._sat_view.get_model())
|
||||
model.clear()
|
||||
self._download_task = True
|
||||
src = self._source_box.get_active()
|
||||
if not self._parser:
|
||||
self._parser = SatellitesParser()
|
||||
|
||||
self.get_sat_list(src, self.append_satellites)
|
||||
|
||||
@run_task
|
||||
def get_sat_list(self, src, callback):
|
||||
sats = self._parser.get_satellites_list(SatelliteSource.FLYSAT if src == 0 else SatelliteSource.LYNGSAT)
|
||||
if sats:
|
||||
callback(sats)
|
||||
self._download_task = False
|
||||
|
||||
@run_idle
|
||||
def append_satellites(self, sats):
|
||||
model = get_base_model(self._sat_view.get_model())
|
||||
for sat in sats:
|
||||
model.append(sat)
|
||||
|
||||
@run_task
|
||||
def on_receive_satellites_list(self, item):
|
||||
if self._download_task:
|
||||
show_dialog(DialogType.ERROR, self._dialog, "The task is already running!")
|
||||
return
|
||||
self.receive_satellites()
|
||||
|
||||
@run_task
|
||||
def receive_satellites(self):
|
||||
self._download_task = True
|
||||
self._sat_update_expander.set_expanded(True)
|
||||
self._text_view.get_buffer().set_text("", 0)
|
||||
model = self._sat_view.get_model()
|
||||
start = time.time()
|
||||
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
|
||||
text = "Processing: {}\n"
|
||||
sats = []
|
||||
appender = self.append_output()
|
||||
next(appender)
|
||||
futures = {executor.submit(self._parser.get_satellite, sat[:-1]): sat for sat in [r for r in model if r[4]]}
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
if not self._download_task:
|
||||
executor.shutdown()
|
||||
appender.send("\nCanceled\n")
|
||||
appender.close()
|
||||
return
|
||||
data = future.result()
|
||||
appender.send(text.format(data[0]))
|
||||
sats.append(data)
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
appender.send("Consumed : {:0.0f}s, {} satellites received.".format(start - time.time(), len(sats)))
|
||||
appender.close()
|
||||
# self.show_info_message(message, Gtk.MessageType.INFO)
|
||||
sats = {s[2]: s for s in sats} # key = position, v = satellite
|
||||
|
||||
for row in self._main_model:
|
||||
pos = row[-1]
|
||||
if pos in sats:
|
||||
sat = sats.pop(pos)
|
||||
itr = row.iter
|
||||
self.update_satellite(itr, row, sat)
|
||||
|
||||
for sat in sats.values():
|
||||
append_satellite(self._main_model, sat)
|
||||
|
||||
self._download_task = False
|
||||
|
||||
@run_idle
|
||||
def update_satellite(self, itr, row, sat):
|
||||
if self._main_model.iter_has_child(itr):
|
||||
children = row.iterchildren()
|
||||
for ch in children:
|
||||
self._main_model.remove(ch.iter)
|
||||
|
||||
for tr in sat[3]:
|
||||
self._main_model.append(itr, ["Transponder:", *tr, None, None])
|
||||
|
||||
def append_output(self):
|
||||
@run_idle
|
||||
def append(t):
|
||||
append_text_to_tview(t, self._text_view)
|
||||
|
||||
while True:
|
||||
text = yield
|
||||
append(text)
|
||||
|
||||
@run_idle
|
||||
def on_cancel_receive(self, item=None):
|
||||
self._download_task = False
|
||||
|
||||
def on_selected_toggled(self, toggle, path):
|
||||
s_model = self._sat_view.get_model()
|
||||
itr = self._filter_model.convert_iter_to_child_iter(s_model.convert_iter_to_child_iter(s_model.get_iter(path)))
|
||||
self._filter_model.get_model().set_value(itr, 4, not toggle.get_active())
|
||||
self.update_receive_button_state(self._filter_model)
|
||||
|
||||
@run_idle
|
||||
def update_receive_button_state(self, model):
|
||||
self._receive_button.set_sensitive((any(r[4] for r in model)))
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._sat_update_info_bar.set_visible(True)
|
||||
self._sat_update_info_bar.set_message_type(message_type)
|
||||
self._info_bar_message_label.set_text(text)
|
||||
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._sat_update_info_bar.set_visible(False)
|
||||
|
||||
def on_find_toggled(self, button: Gtk.ToggleToolButton):
|
||||
self._search_info_bar.set_visible(button.get_active())
|
||||
|
||||
def on_filter_toggled(self, button: Gtk.ToggleToolButton):
|
||||
self._filter_info_bar.set_visible(button.get_active())
|
||||
|
||||
@run_idle
|
||||
def on_filter(self, item):
|
||||
self._filter_positions = self.get_positions()
|
||||
self._filter_model.refilter()
|
||||
|
||||
def filter_function(self, model, iter, data):
|
||||
if self._filter_model is None or self._filter_model == "None":
|
||||
return True
|
||||
|
||||
from_pos, to_pos = self._filter_positions
|
||||
if from_pos == 0 and to_pos == 0:
|
||||
return True
|
||||
|
||||
if from_pos > to_pos:
|
||||
from_pos, to_pos = to_pos, from_pos
|
||||
|
||||
return from_pos <= float(self._parser.get_position(model.get(iter, 1)[0])) <= to_pos
|
||||
|
||||
def get_positions(self):
|
||||
from_pos = round(self._from_pos_button.get_value(), 1) * (-1 if self._filter_from_combo_box.get_active() else 1)
|
||||
to_pos = round(self._to_pos_button.get_value(), 1) * (-1 if self._filter_to_combo_box.get_active() else 1)
|
||||
return from_pos, to_pos
|
||||
|
||||
def on_search(self, entry):
|
||||
self._search_provider.search(entry.get_text())
|
||||
|
||||
def on_search_down(self, item):
|
||||
self._search_provider.on_search_down()
|
||||
|
||||
def on_search_up(self, item):
|
||||
self._search_provider.on_search_up()
|
||||
|
||||
def on_quit(self):
|
||||
self._download_task = False
|
||||
|
||||
|
||||
# ***************** Commons *******************#
|
||||
|
||||
@run_idle
|
||||
def append_satellite(model, sat):
|
||||
""" Common function for append satellite to the model """
|
||||
name, flags, pos, transponders = sat
|
||||
parent = model.append(None, [name, *(None,) * 9, flags, pos])
|
||||
for transponder in transponders:
|
||||
model.append(parent, ["Transponder:", *transponder, None, None])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
55
app/ui/search.py
Normal file
55
app/ui/search.py
Normal file
@@ -0,0 +1,55 @@
|
||||
""" This is helper module for search features """
|
||||
|
||||
|
||||
class SearchProvider:
|
||||
def __init__(self, views, down_button, up_button):
|
||||
self._paths = []
|
||||
self._current_index = -1
|
||||
self._max_indexes = 0
|
||||
self._views = views
|
||||
self._up_button = up_button
|
||||
self._down_button = down_button
|
||||
|
||||
def search(self, text):
|
||||
self._current_index = -1
|
||||
self._paths.clear()
|
||||
for view in self._views:
|
||||
model = view.get_model()
|
||||
selection = view.get_selection()
|
||||
selection.unselect_all()
|
||||
if not text:
|
||||
continue
|
||||
|
||||
text = text.upper()
|
||||
for r in model:
|
||||
if text in str(r[:]).upper():
|
||||
path = r.path
|
||||
selection.select_path(r.path)
|
||||
self._paths.append((view, path))
|
||||
|
||||
self._max_indexes = len(self._paths) - 1
|
||||
if self._max_indexes > 0:
|
||||
self.on_search_down()
|
||||
|
||||
def scroll_to(self, index):
|
||||
view, path = self._paths[index]
|
||||
view.scroll_to_cell(path, None)
|
||||
self.update_navigation_buttons()
|
||||
|
||||
def on_search_down(self):
|
||||
if self._current_index < self._max_indexes:
|
||||
self._current_index += 1
|
||||
self.scroll_to(self._current_index)
|
||||
|
||||
def on_search_up(self):
|
||||
if self._current_index > -1:
|
||||
self._current_index -= 1
|
||||
self.scroll_to(self._current_index)
|
||||
|
||||
def update_navigation_buttons(self):
|
||||
self._up_button.set_sensitive(self._current_index > 0)
|
||||
self._down_button.set_sensitive(self._current_index < self._max_indexes)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
1774
app/ui/service_details_dialog.glade
Normal file
1774
app/ui/service_details_dialog.glade
Normal file
File diff suppressed because it is too large
Load Diff
609
app/ui/service_details_dialog.py
Normal file
609
app/ui/service_details_dialog.py
Normal file
@@ -0,0 +1,609 @@
|
||||
import re
|
||||
import os
|
||||
|
||||
from app.commons import run_idle
|
||||
from app.eparser import Service
|
||||
from app.eparser.ecommons import MODULATION, Inversion, ROLL_OFF, Pilot, Flag, Pids, POLARIZATION, \
|
||||
get_key_by_value, get_value_by_name, FEC_DEFAULT, PLS_MODE, SERVICE_TYPE
|
||||
from app.properties import Profile
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, TEXT_DOMAIN, CODED_ICON
|
||||
from .dialogs import show_dialog, DialogType, Action
|
||||
from .main_helper import get_base_model
|
||||
|
||||
|
||||
class ServiceDetailsDialog:
|
||||
_ENIGMA2_DATA_ID = "{:04x}:{:08x}:{:04x}:{:04x}:{}:{}"
|
||||
|
||||
_ENIGMA2_FAV_ID = "{:X}:{:X}:{:X}:{:X}"
|
||||
|
||||
_ENIGMA2_TRANSPONDER_DATA = "{} {}:{}:{}:{}:{}:{}:{}"
|
||||
|
||||
_NEUTRINO_FAV_ID = "{:x}:{:x}:{:x}"
|
||||
|
||||
_NEUTRINO_TRANSPONDER_DATA = "{:04x}:{:04x}:{}:{}:{}:{}:{}:{}:{}"
|
||||
|
||||
_DIGIT_ENTRY_ELEMENTS = ("bitstream_entry", "pcm_entry", "video_pid_entry", "pcr_pid_entry", "srv_type_entry",
|
||||
"ac3_pid_entry", "ac3plus_pid_entry", "acc_pid_entry", "he_acc_pid_entry",
|
||||
"teletext_pid_entry", "pls_code_entry", "stream_id_entry", "tr_flag_entry",
|
||||
"audio_pid_entry")
|
||||
_NOT_EMPTY_DIGIT_ELEMENTS = ("sid_entry", "freq_entry", "rate_entry", "transponder_id_entry", "network_id_entry",
|
||||
"namespace_entry", "srv_type_entry")
|
||||
|
||||
_DIGIT_ENTRY_NAME = "digit-entry"
|
||||
|
||||
def __init__(self, transient, options, srv_view, fav_view, services, bouquets, action=Action.EDIT):
|
||||
handlers = {"on_system_changed": self.on_system_changed,
|
||||
"on_save": self.on_save,
|
||||
"on_create_new": self.on_create_new,
|
||||
"on_tr_edit_toggled": self.on_tr_edit_toggled,
|
||||
"update_reference": self.update_reference,
|
||||
"on_cas_entry_changed": self.on_cas_entry_changed,
|
||||
"on_digit_entry_changed": self.on_digit_entry_changed,
|
||||
"on_non_empty_entry_changed": self.on_non_empty_entry_changed}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_from_file(UI_RESOURCES_PATH + "service_details_dialog.glade")
|
||||
builder.connect_signals(handlers)
|
||||
self._builder = builder
|
||||
|
||||
self._dialog = builder.get_object("service_details_dialog")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._profile = Profile(options["profile"])
|
||||
self._satellites_xml_path = options.get(self._profile.value)["data_dir_path"] + "satellites.xml"
|
||||
self._picons_dir_path = options.get(self._profile.value)["picons_dir_path"]
|
||||
self._services_view = srv_view
|
||||
self._fav_view = fav_view
|
||||
self._action = action
|
||||
self._old_service = None
|
||||
self._services = services
|
||||
self._bouquets = bouquets
|
||||
self._transponder_services_iters = None
|
||||
self._current_model = None
|
||||
self._current_itr = None
|
||||
# Patterns
|
||||
self._DIGIT_PATTERN = re.compile("\D")
|
||||
self._NON_EMPTY_PATTERN = re.compile("(?:^[\s]*$|\D)")
|
||||
self._CAID_PATTERN = re.compile("(?:^[\s]*$)|(C:[0-9a-z]{4})(,C:[0-9a-z]{4})*")
|
||||
# Buttons
|
||||
self._apply_button = builder.get_object("apply_button")
|
||||
self._create_button = builder.get_object("create_button")
|
||||
# style
|
||||
self._style_provider = Gtk.CssProvider()
|
||||
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
# initialization only digit elements
|
||||
self._digit_elements = {k: builder.get_object(k) for k in self._DIGIT_ENTRY_ELEMENTS}
|
||||
for elem in self._digit_elements.values():
|
||||
elem.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
# initialization of non empty elements
|
||||
self._non_empty_elements = {k: builder.get_object(k) for k in self._NOT_EMPTY_DIGIT_ELEMENTS}
|
||||
for elem in self._non_empty_elements.values():
|
||||
elem.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
self._sid_entry = self._non_empty_elements.get("sid_entry")
|
||||
self._bitstream_entry = self._digit_elements.get("bitstream_entry")
|
||||
self._pcm_entry = self._digit_elements.get("pcm_entry")
|
||||
self._video_pid_entry = self._digit_elements.get("video_pid_entry")
|
||||
self._pcr_pid_entry = self._digit_elements.get("pcr_pid_entry")
|
||||
self._audio_pid_entry = self._digit_elements.get("audio_pid_entry")
|
||||
self._ac3_pid_entry = self._digit_elements.get("ac3_pid_entry")
|
||||
self._ac3plus_pid_entry = self._digit_elements.get("ac3plus_pid_entry")
|
||||
self._acc_pid_entry = self._digit_elements.get("acc_pid_entry")
|
||||
self._he_acc_pid_entry = self._digit_elements.get("he_acc_pid_entry")
|
||||
self._teletext_pid_entry = self._digit_elements.get("teletext_pid_entry")
|
||||
self._transponder_id_entry = self._non_empty_elements.get("transponder_id_entry")
|
||||
self._network_id_entry = self._non_empty_elements.get("network_id_entry")
|
||||
self._freq_entry = self._non_empty_elements.get("freq_entry")
|
||||
self._rate_entry = self._non_empty_elements.get("rate_entry")
|
||||
self._pls_code_entry = self._digit_elements.get("pls_code_entry")
|
||||
self._stream_id_entry = self._digit_elements.get("stream_id_entry")
|
||||
self._tr_flag_entry = self._digit_elements.get("tr_flag_entry")
|
||||
self._namespace_entry = self._non_empty_elements.get("namespace_entry")
|
||||
# Service elements
|
||||
self._name_entry = builder.get_object("name_entry")
|
||||
self._package_entry = builder.get_object("package_entry")
|
||||
self._srv_type_entry = self._non_empty_elements.get("srv_type_entry")
|
||||
self._service_type_combo_box = builder.get_object("service_type_combo_box")
|
||||
self._cas_entry = builder.get_object("cas_entry")
|
||||
self._reference_entry = builder.get_object("reference_entry")
|
||||
self._keep_check_button = builder.get_object("keep_check_button")
|
||||
self._hide_check_button = builder.get_object("hide_check_button")
|
||||
self._use_pids_check_button = builder.get_object("use_pids_check_button")
|
||||
self._new_check_button = builder.get_object("new_check_button")
|
||||
self._pids_grid = builder.get_object("pids_grid")
|
||||
# Transponder elements
|
||||
self._sat_pos_button = builder.get_object("sat_pos_button")
|
||||
self._pol_combo_box = builder.get_object("pol_combo_box")
|
||||
self._fec_combo_box = builder.get_object("fec_combo_box")
|
||||
self._sys_combo_box = builder.get_object("sys_combo_box")
|
||||
self._mod_combo_box = builder.get_object("mod_combo_box")
|
||||
self._invertion_combo_box = builder.get_object("invertion_combo_box")
|
||||
self._rolloff_combo_box = builder.get_object("rolloff_combo_box")
|
||||
self._pilot_combo_box = builder.get_object("pilot_combo_box")
|
||||
self._pls_mode_combo_box = builder.get_object("pls_mode_combo_box")
|
||||
self._tr_edit_switch = builder.get_object("tr_edit_switch")
|
||||
self._tr_extra_expander = builder.get_object("tr_extra_expander")
|
||||
|
||||
self._DVB_S2_ELEMENTS = (self._mod_combo_box, self._rolloff_combo_box, self._pilot_combo_box,
|
||||
self._pls_mode_combo_box, self._pls_code_entry, self._stream_id_entry)
|
||||
self._TRANSPONDER_ELEMENTS = (self._sat_pos_button, self._pol_combo_box, self._invertion_combo_box,
|
||||
self._sys_combo_box, self._freq_entry, self._transponder_id_entry,
|
||||
self._network_id_entry, self._namespace_entry, self._fec_combo_box,
|
||||
self._rate_entry)
|
||||
|
||||
if self._action is Action.EDIT:
|
||||
self.update_data_elements()
|
||||
elif self._action is Action.ADD:
|
||||
self.init_default_data_elements()
|
||||
|
||||
def show(self):
|
||||
response = self._dialog.run()
|
||||
if response == Gtk.ResponseType.OK:
|
||||
pass
|
||||
self._dialog.destroy()
|
||||
|
||||
return response
|
||||
|
||||
@run_idle
|
||||
def init_default_data_elements(self):
|
||||
self._apply_button.set_visible(False)
|
||||
self._create_button.set_visible(True)
|
||||
self._tr_edit_switch.set_sensitive(False)
|
||||
self.on_tr_edit_toggled(self._tr_edit_switch.set_active(True), True)
|
||||
for elem in self._non_empty_elements.values():
|
||||
elem.set_text(" ")
|
||||
elem.set_text("")
|
||||
self._new_check_button.set_active(True)
|
||||
self._tr_extra_expander.activate()
|
||||
self._service_type_combo_box.set_active(0)
|
||||
self._pol_combo_box.set_active(0)
|
||||
self._fec_combo_box.set_active(0)
|
||||
self._sys_combo_box.set_active(0)
|
||||
self._invertion_combo_box.set_active(2)
|
||||
|
||||
def update_data_elements(self):
|
||||
model, paths = self._services_view.get_selection().get_selected_rows()
|
||||
itr = model.get_iter(paths)
|
||||
# Unpacking to search for an iterator for the base model
|
||||
filter_model = model.get_model()
|
||||
itr = filter_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(itr))
|
||||
self._current_model = get_base_model(model)
|
||||
srv = Service(*self._current_model[itr][:])
|
||||
self._old_service = srv
|
||||
self._current_itr = itr
|
||||
# Service
|
||||
self._name_entry.set_text(srv.service)
|
||||
self._package_entry.set_text(srv.package)
|
||||
self._sid_entry.set_text(str(int(srv.ssid, 16)))
|
||||
# Transponder
|
||||
self._freq_entry.set_text(srv.freq)
|
||||
self._rate_entry.set_text(srv.rate)
|
||||
self.select_active_text(self._pol_combo_box, srv.pol)
|
||||
self.select_active_text(self._fec_combo_box, srv.fec)
|
||||
self.select_active_text(self._sys_combo_box, srv.system)
|
||||
self.set_sat_positions(srv.pos)
|
||||
|
||||
if self._profile is Profile.ENIGMA_2:
|
||||
self.init_enigma2_service_data(srv)
|
||||
self.init_enigma2_transponder_data(srv)
|
||||
elif self._profile is Profile.NEUTRINO_MP:
|
||||
self.init_neutrino_data(srv)
|
||||
self.init_neutrino_ui_elements()
|
||||
|
||||
# ***************** Init Enigma2 data *********************#
|
||||
|
||||
@run_idle
|
||||
def init_enigma2_service_data(self, srv):
|
||||
""" Service data initialisation """
|
||||
flags = srv.flags_cas
|
||||
if flags:
|
||||
flags = flags.split(",")
|
||||
self.init_enigma2_flags(flags)
|
||||
self.init_enigma2_pids(flags)
|
||||
self.init_enigma2_cas(flags)
|
||||
|
||||
def init_enigma2_flags(self, flags):
|
||||
f_flags = list(filter(lambda x: x.startswith("f:"), flags))
|
||||
if f_flags:
|
||||
value = int(f_flags[0][2:])
|
||||
self._keep_check_button.set_active(Flag.is_keep(value))
|
||||
self._hide_check_button.set_active(Flag.is_hide(value))
|
||||
self._use_pids_check_button.set_active(Flag.is_pids(value))
|
||||
self._new_check_button.set_active(Flag.is_new(value))
|
||||
|
||||
def init_enigma2_cas(self, flags):
|
||||
cas = list(filter(lambda x: x.startswith("C:"), flags))
|
||||
if cas:
|
||||
self._cas_entry.set_text(",".join(cas))
|
||||
|
||||
def init_enigma2_pids(self, flags):
|
||||
pids = list(filter(lambda x: x.startswith("c:"), flags))
|
||||
if pids:
|
||||
for pid in pids:
|
||||
if pid.startswith(Pids.VIDEO.value):
|
||||
self._video_pid_entry.set_text(str(int(pid[4:], 16)))
|
||||
elif pid.startswith(Pids.AUDIO.value):
|
||||
self._audio_pid_entry.set_text(str(int(pid[4:], 16)))
|
||||
elif pid.startswith(Pids.TELETEXT.value):
|
||||
self._teletext_pid_entry.set_text(str(int(pid[4:], 16)))
|
||||
elif pid.startswith(Pids.PCR.value):
|
||||
self._pcr_pid_entry.set_text(str(int(pid[4:], 16)))
|
||||
elif pid.startswith(Pids.AC3.value):
|
||||
self._ac3_pid_entry.set_text(str(int(pid[4:], 16)))
|
||||
elif pid.startswith(Pids.VIDEO_TYPE.value):
|
||||
pass
|
||||
elif pid.startswith(Pids.AUDIO_CHANNEL.value):
|
||||
pass
|
||||
elif pid.startswith(Pids.BIT_STREAM_DELAY.value):
|
||||
self._bitstream_entry.set_text(str(int(pid[4:], 16)))
|
||||
elif pid.startswith(Pids.PCM_DELAY.value):
|
||||
self._pcm_entry.set_text(str(int(pid[4:], 16)))
|
||||
elif pid.startswith(Pids.SUBTITLE.value):
|
||||
pass
|
||||
|
||||
def init_enigma2_transponder_data(self, srv):
|
||||
""" Transponder data initialisation """
|
||||
data = srv.data_id.split(":")
|
||||
tr_data = srv.transponder.split(":")
|
||||
|
||||
if srv.system == "DVB-S2":
|
||||
self.select_active_text(self._mod_combo_box, MODULATION.get(tr_data[8]))
|
||||
self.select_active_text(self._rolloff_combo_box, ROLL_OFF.get(tr_data[9]))
|
||||
self.select_active_text(self._pilot_combo_box, Pilot(tr_data[10]).name)
|
||||
self._tr_flag_entry.set_text(tr_data[7])
|
||||
if len(tr_data) > 12:
|
||||
self._stream_id_entry.set_text(tr_data[11])
|
||||
self._pls_code_entry.set_text(tr_data[12])
|
||||
self.select_active_text(self._pls_mode_combo_box, PLS_MODE.get(tr_data[13]))
|
||||
|
||||
self._namespace_entry.set_text(str(int(data[1], 16)))
|
||||
self._transponder_id_entry.set_text(str(int(data[2], 16)))
|
||||
self._network_id_entry.set_text(str(int(data[3], 16)))
|
||||
self.select_active_text(self._invertion_combo_box, Inversion(tr_data[5]).name)
|
||||
# Should be called last to properly initialize the reference
|
||||
self._srv_type_entry.set_text(data[4])
|
||||
|
||||
# ***************** Init Neutrino data *********************#
|
||||
|
||||
def init_neutrino_data(self, srv):
|
||||
tr_data = srv.transponder.split(":")
|
||||
self._transponder_id_entry.set_text(str(int(tr_data[0], 16)))
|
||||
self._network_id_entry.set_text(str(int(tr_data[1], 16)))
|
||||
self.select_active_text(self._invertion_combo_box, Inversion(tr_data[3]).name)
|
||||
self.select_active_text(self._service_type_combo_box, srv.service_type)
|
||||
self.update_reference_entry()
|
||||
|
||||
def init_neutrino_ui_elements(self):
|
||||
self._builder.get_object("flags_box").set_visible(False)
|
||||
self._builder.get_object("pids_grid").set_visible(False)
|
||||
self._builder.get_object("tr_grid").remove_column(7)
|
||||
self._builder.get_object("tr_extra_expander").set_visible(False)
|
||||
self._builder.get_object("srv_separator").set_visible(False)
|
||||
|
||||
# ***************** Init Sat positions *********************#
|
||||
|
||||
def set_sat_positions(self, sat_pos):
|
||||
""" Sat positions initialisation """
|
||||
self._sat_pos_button.set_value(float(sat_pos))
|
||||
|
||||
def on_system_changed(self, box):
|
||||
if not self._tr_edit_switch.get_active():
|
||||
return
|
||||
active = box.get_active()
|
||||
self.update_dvb_s2_elements(active)
|
||||
|
||||
def update_dvb_s2_elements(self, active):
|
||||
for elem in self._DVB_S2_ELEMENTS:
|
||||
elem.set_sensitive(active)
|
||||
self._pls_code_entry.set_name("GtkEntry")
|
||||
self._stream_id_entry.set_name("GtkEntry")
|
||||
|
||||
if active:
|
||||
if not self._mod_combo_box.get_active_id():
|
||||
self._mod_combo_box.set_active_id(MODULATION["2"])
|
||||
if not self._rolloff_combo_box.get_active_id():
|
||||
self._rolloff_combo_box.set_active_id(ROLL_OFF["0"])
|
||||
if not self._pilot_combo_box.get_active_id():
|
||||
self._pilot_combo_box.set_active_id(Pilot.Auto.name)
|
||||
if not self._pls_mode_combo_box.get_active_id():
|
||||
self._pls_mode_combo_box.set_active_id(PLS_MODE["0"])
|
||||
|
||||
# ***************** Save data *********************#
|
||||
|
||||
def on_save(self, item):
|
||||
self.save_data()
|
||||
|
||||
def on_create_new(self, item):
|
||||
self.save_data()
|
||||
|
||||
def save_data(self):
|
||||
if not self.is_data_correct():
|
||||
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
|
||||
return
|
||||
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
self.on_edit() if self._action is Action.EDIT else self.on_new()
|
||||
self._dialog.destroy()
|
||||
|
||||
def on_edit(self):
|
||||
fav_id, data_id = self.get_srv_data()
|
||||
# transponder
|
||||
transponder = self._old_service.transponder
|
||||
if self._tr_edit_switch.get_active():
|
||||
transponder = self.get_transponder_data()
|
||||
if self._transponder_services_iters:
|
||||
self.update_transponder_services(transponder)
|
||||
service = self.get_service(fav_id, data_id, transponder)
|
||||
old_fav_id = self._old_service.fav_id
|
||||
if old_fav_id != fav_id:
|
||||
self.update_bouquets(fav_id, old_fav_id)
|
||||
self._services[fav_id] = service
|
||||
if self._old_service.picon_id != service.picon_id:
|
||||
self.update_picon_name(self._old_service.picon_id, service.picon_id)
|
||||
self._current_model.set(self._current_itr, {i: v for i, v in enumerate(service)})
|
||||
self.update_fav_view(self._old_service, service)
|
||||
self._old_service = service
|
||||
|
||||
def update_bouquets(self, fav_id, old_fav_id):
|
||||
self._services.pop(old_fav_id, None)
|
||||
for bq in self._bouquets.values():
|
||||
indexes = []
|
||||
for i, f_id in enumerate(bq):
|
||||
if old_fav_id == f_id:
|
||||
indexes.append(i)
|
||||
for i in indexes:
|
||||
bq[i] = fav_id
|
||||
|
||||
@run_idle
|
||||
def update_fav_view(self, old_service, new_service):
|
||||
model = self._fav_view.get_model()
|
||||
for row in filter(lambda r: old_service.fav_id == r[7], model):
|
||||
model.set(row.iter, {1: new_service.coded,
|
||||
2: new_service.service,
|
||||
3: new_service.locked,
|
||||
4: new_service.hide,
|
||||
5: new_service.service_type,
|
||||
6: new_service.pos,
|
||||
7: new_service.fav_id,
|
||||
8: new_service.picon})
|
||||
|
||||
def update_picon_name(self, old_name, new_name):
|
||||
for file_name in os.listdir(self._picons_dir_path):
|
||||
if file_name == old_name:
|
||||
old_file = os.path.join(self._picons_dir_path, old_name)
|
||||
new_file = os.path.join(self._picons_dir_path, new_name)
|
||||
os.rename(old_file, new_file)
|
||||
break
|
||||
|
||||
def on_new(self):
|
||||
service = self.get_service(*self.get_srv_data(), self.get_transponder_data())
|
||||
print(service)
|
||||
show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
|
||||
|
||||
def get_service(self, fav_id, data_id, transponder):
|
||||
freq, rate, pol, fec, system, pos = self.get_transponder_values()
|
||||
return Service(flags_cas=self.get_flags(),
|
||||
transponder_type="s",
|
||||
coded=CODED_ICON if self._cas_entry.get_text() else None,
|
||||
service=self._name_entry.get_text(),
|
||||
locked=self._old_service.locked,
|
||||
hide=HIDE_ICON if self._hide_check_button.get_active() else None,
|
||||
package=self._package_entry.get_text(),
|
||||
service_type=SERVICE_TYPE.get(self._srv_type_entry.get_text(), SERVICE_TYPE["3"]),
|
||||
picon=self._old_service.picon,
|
||||
picon_id=self._reference_entry.get_text().replace(":", "_") + ".png",
|
||||
ssid="{:04x}".format(int(self._sid_entry.get_text())),
|
||||
freq=freq,
|
||||
rate=rate,
|
||||
pol=pol,
|
||||
fec=fec,
|
||||
system=system,
|
||||
pos=pos,
|
||||
data_id=data_id,
|
||||
fav_id=fav_id,
|
||||
transponder=transponder)
|
||||
|
||||
def get_flags(self):
|
||||
if self._profile is Profile.ENIGMA_2:
|
||||
return self.get_enigma2_flags()
|
||||
elif self._profile is Profile.NEUTRINO_MP:
|
||||
return self._old_service.flags_cas
|
||||
|
||||
def get_enigma2_flags(self):
|
||||
flags = ["p:{}".format(self._package_entry.get_text())]
|
||||
# cas
|
||||
cas = self._cas_entry.get_text()
|
||||
if cas:
|
||||
flags.append(cas)
|
||||
# pids
|
||||
video_pid = self._video_pid_entry.get_text()
|
||||
if video_pid:
|
||||
flags.append("{}{:04x}".format(Pids.VIDEO.value, int(video_pid)))
|
||||
audio_pid = self._audio_pid_entry.get_text()
|
||||
if audio_pid:
|
||||
flags.append("{}{:04x}".format(Pids.AUDIO.value, int(audio_pid)))
|
||||
teletext_pid = self._teletext_pid_entry.get_text()
|
||||
if teletext_pid:
|
||||
flags.append("{}{:04x}".format(Pids.TELETEXT.value, int(teletext_pid)))
|
||||
pcr_pid = self._pcr_pid_entry.get_text()
|
||||
if pcr_pid:
|
||||
flags.append("{}{:04x}".format(Pids.PCR.value, int(pcr_pid)))
|
||||
ac3_pid = self._ac3_pid_entry.get_text()
|
||||
if ac3_pid:
|
||||
flags.append("{}{:04x}".format(Pids.AC3.value, int(ac3_pid)))
|
||||
bitstream_pid = self._bitstream_entry.get_text()
|
||||
if bitstream_pid:
|
||||
flags.append("{}{:04x}".format(Pids.BIT_STREAM_DELAY.value, int(bitstream_pid)))
|
||||
pcm_pid = self._pcm_entry.get_text()
|
||||
if pcm_pid:
|
||||
flags.append("{}{:04x}".format(Pids.PCM_DELAY.value, int(pcm_pid)))
|
||||
# flags
|
||||
f_flags = Flag.KEEP.value if self._keep_check_button.get_active() else 0
|
||||
f_flags = f_flags + Flag.HIDE.value if self._hide_check_button.get_active() else f_flags
|
||||
f_flags = f_flags + Flag.PIDS.value if self._use_pids_check_button.get_active() else f_flags
|
||||
f_flags = f_flags + Flag.NEW.value if self._new_check_button.get_active() else f_flags
|
||||
if f_flags:
|
||||
flags.append("f:{:02d}".format(f_flags))
|
||||
|
||||
return ",".join(flags)
|
||||
|
||||
def get_srv_data(self):
|
||||
ssid = int(self._sid_entry.get_text())
|
||||
net_id, tr_id = int(self._network_id_entry.get_text()), int(self._transponder_id_entry.get_text())
|
||||
service_type = self._srv_type_entry.get_text()
|
||||
|
||||
if self._profile is Profile.ENIGMA_2:
|
||||
namespace = int(self._namespace_entry.get_text())
|
||||
data_id = self._ENIGMA2_DATA_ID.format(ssid, namespace, tr_id, net_id, service_type, 0)
|
||||
fav_id = self._ENIGMA2_FAV_ID.format(ssid, tr_id, net_id, namespace)
|
||||
return fav_id, data_id
|
||||
elif self._profile is Profile.NEUTRINO_MP:
|
||||
fav_id = self._NEUTRINO_FAV_ID.format(tr_id, net_id, ssid)
|
||||
return fav_id, self._old_service.data_id
|
||||
|
||||
def get_transponder_values(self):
|
||||
freq = self._freq_entry.get_text()
|
||||
rate = self._rate_entry.get_text()
|
||||
pol = self._pol_combo_box.get_active_id()
|
||||
fec = self._fec_combo_box.get_active_id()
|
||||
system = self._sys_combo_box.get_active_id()
|
||||
pos = str(round(self._sat_pos_button.get_value(), 1))
|
||||
return freq, rate, pol, fec, system, pos
|
||||
|
||||
def get_transponder_data(self):
|
||||
sys = self._sys_combo_box.get_active_id()
|
||||
freq = self._freq_entry.get_text()
|
||||
rate = self._rate_entry.get_text()
|
||||
pol = self.get_value_from_combobox_id(self._pol_combo_box, POLARIZATION)
|
||||
fec = self.get_value_from_combobox_id(self._fec_combo_box, FEC_DEFAULT)
|
||||
sat_pos = str(round(self._sat_pos_button.get_value(), 1)).replace(".", "")
|
||||
inv = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
|
||||
srv_sys = "0" # !!!
|
||||
|
||||
if self._profile is Profile.ENIGMA_2:
|
||||
dvb_s_tr = self._ENIGMA2_TRANSPONDER_DATA.format("s", freq, rate, pol, fec, sat_pos, inv, srv_sys)
|
||||
if sys == "DVB-S":
|
||||
return dvb_s_tr
|
||||
if sys == "DVB-S2":
|
||||
flag = self._tr_flag_entry.get_text()
|
||||
mod = self.get_value_from_combobox_id(self._mod_combo_box, MODULATION)
|
||||
roll_off = self.get_value_from_combobox_id(self._rolloff_combo_box, ROLL_OFF)
|
||||
pilot = get_value_by_name(Pilot, self._pilot_combo_box.get_active_id())
|
||||
pls_mode = self.get_value_from_combobox_id(self._pls_mode_combo_box, PLS_MODE)
|
||||
pls_code = self._pls_code_entry.get_text()
|
||||
st_id = self._stream_id_entry.get_text()
|
||||
pls = ":{}:{}:{}".format(st_id, pls_code, pls_mode) if pls_mode and pls_code and st_id else ""
|
||||
return "{}:{}:{}:{}:{}{}".format(dvb_s_tr, flag, mod, roll_off, pilot, pls)
|
||||
elif self._profile is Profile.NEUTRINO_MP:
|
||||
on_id, tr_id = int(self._network_id_entry.get_text()), int(self._transponder_id_entry.get_text())
|
||||
mod = self.get_value_from_combobox_id(self._mod_combo_box, MODULATION) if sys == "DVB-S2" else None
|
||||
srv_sys = None
|
||||
return self._NEUTRINO_TRANSPONDER_DATA.format(tr_id, on_id, freq, inv, rate, fec, pol, mod, srv_sys)
|
||||
|
||||
def update_transponder_services(self, transponder):
|
||||
for itr in self._transponder_services_iters:
|
||||
srv = self._current_model[itr][:]
|
||||
srv[-9], srv[-8], srv[-7], srv[-6], srv[-5], srv[-4] = self.get_transponder_values()
|
||||
srv[-1] = transponder
|
||||
srv = Service(*srv)
|
||||
self._services[srv.fav_id] = self._services.pop(srv.fav_id)._replace(transponder=transponder)
|
||||
self._current_model.set(itr, {i: v for i, v in enumerate(srv)})
|
||||
|
||||
# ***************** Others *********************#
|
||||
|
||||
def select_active_text(self, box: Gtk.ComboBox, text):
|
||||
model = box.get_model()
|
||||
for index, row in enumerate(model):
|
||||
if row[0] == text:
|
||||
box.set_active(index)
|
||||
break
|
||||
|
||||
def on_digit_entry_changed(self, entry):
|
||||
entry.set_name(self._DIGIT_ENTRY_NAME if self._DIGIT_PATTERN.search(entry.get_text()) else "GtkEntry")
|
||||
|
||||
def on_non_empty_entry_changed(self, entry):
|
||||
entry.set_name(self._DIGIT_ENTRY_NAME if self._NON_EMPTY_PATTERN.search(entry.get_text()) else "GtkEntry")
|
||||
|
||||
def on_cas_entry_changed(self, entry):
|
||||
entry.set_name("GtkEntry" if self._CAID_PATTERN.fullmatch(entry.get_text()) else self._DIGIT_ENTRY_NAME)
|
||||
|
||||
def get_value_from_combobox_id(self, box: Gtk.ComboBox, dc: dict):
|
||||
cb_id = box.get_active_id()
|
||||
return get_key_by_value(dc, cb_id)
|
||||
|
||||
@run_idle
|
||||
def on_tr_edit_toggled(self, switch: Gtk.Switch, active):
|
||||
|
||||
if active and self._action is Action.EDIT:
|
||||
self._transponder_services_iters = []
|
||||
response = TransponderServicesDialog(self._dialog,
|
||||
self._current_model,
|
||||
self._old_service.transponder,
|
||||
self._transponder_services_iters).show()
|
||||
if response == Gtk.ResponseType.CANCEL or response == -4:
|
||||
switch.set_active(False)
|
||||
self._transponder_services_iters = None
|
||||
return
|
||||
|
||||
self.update_dvb_s2_elements(active and self._sys_combo_box.get_active_id() == "DVB-S2")
|
||||
|
||||
for elem in self._TRANSPONDER_ELEMENTS:
|
||||
elem.set_sensitive(active)
|
||||
|
||||
def is_data_correct(self):
|
||||
for elem in self._digit_elements.values():
|
||||
if elem.get_name() == self._DIGIT_ENTRY_NAME:
|
||||
return False
|
||||
for elem in self._non_empty_elements.values():
|
||||
if elem.get_name() == self._DIGIT_ENTRY_NAME:
|
||||
return False
|
||||
if self._cas_entry.get_name() == self._DIGIT_ENTRY_NAME:
|
||||
return False
|
||||
return True
|
||||
|
||||
def update_reference(self, entry, event=None):
|
||||
if not self.is_data_correct() or (event is None and self._profile is Profile.NEUTRINO_MP):
|
||||
return
|
||||
self.update_reference_entry()
|
||||
|
||||
def update_reference_entry(self):
|
||||
srv_type = 0 if self._srv_type_entry.get_text() == "2" else 1
|
||||
ssid = int(self._sid_entry.get_text())
|
||||
tid = int(self._transponder_id_entry.get_text())
|
||||
nid = int(self._network_id_entry.get_text())
|
||||
if self._profile is Profile.ENIGMA_2:
|
||||
on_id = int(self._namespace_entry.get_text())
|
||||
ref = "1:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0".format(srv_type, ssid, tid, nid, on_id)
|
||||
self._reference_entry.set_text(ref)
|
||||
else:
|
||||
self._reference_entry.set_text("{:x}{:04x}{:04x}".format(tid, nid, ssid))
|
||||
|
||||
|
||||
class TransponderServicesDialog:
|
||||
def __init__(self, transient, model, transponder, tr_iters):
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_file(UI_RESOURCES_PATH + "service_details_dialog.glade",
|
||||
("tr_services_dialog", "transponder_services_liststore"))
|
||||
self._dialog = builder.get_object("tr_services_dialog")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._srv_model = builder.get_object("transponder_services_liststore")
|
||||
self.append_services(model, transponder, tr_iters)
|
||||
|
||||
def append_services(self, model, transponder, tr_iters):
|
||||
for row in model:
|
||||
if row[-1] == transponder:
|
||||
self._srv_model.append((row[3], row[6], row[7], row[10], row[11], row[16]))
|
||||
tr_iters.append(model.get_iter(row.path))
|
||||
|
||||
def show(self):
|
||||
response = self._dialog.run()
|
||||
self._dialog.destroy()
|
||||
return response
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -1,54 +1,129 @@
|
||||
from app.properties import write_config
|
||||
from app.ui.dialogs import show_dialog, DialogType
|
||||
from . import Gtk, UI_RESOURCES_PATH
|
||||
from app.properties import write_config, Profile, get_default_settings
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
|
||||
from .main_helper import update_entry_data
|
||||
|
||||
|
||||
def show_settings_dialog(transient, options):
|
||||
SettingsDialog(transient, options)
|
||||
return SettingsDialog(transient, options).show()
|
||||
|
||||
|
||||
class SettingsDialog:
|
||||
|
||||
def __init__(self, transient, options):
|
||||
handlers = {"on_data_dir_field_icon_press": self.on_data_dir_field_icon_press}
|
||||
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,
|
||||
"on_profile_changed": self.on_profile_changed,
|
||||
"on_reset": self.on_reset,
|
||||
"apply_settings": self.apply_settings}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", ("settings_dialog", ))
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade",
|
||||
("settings_dialog", "telnet_timeout_adjustment"))
|
||||
builder.connect_signals(handlers)
|
||||
self._options = options
|
||||
|
||||
self._dialog = builder.get_object("settings_dialog")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._host_field = builder.get_object("host_field")
|
||||
self._host_field.set_text(options["host"])
|
||||
self._port_field = builder.get_object("port_field")
|
||||
self._port_field.set_text(options["port"])
|
||||
self._login_field = builder.get_object("login_field")
|
||||
self._login_field.set_text(options["user"])
|
||||
self._password_field = builder.get_object("password_field")
|
||||
self._password_field.set_text(options["password"])
|
||||
self._telnet_login_field = builder.get_object("telnet_login_field")
|
||||
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._services_field = builder.get_object("services_field")
|
||||
self._services_field.set_text(options["services_path"])
|
||||
self._user_bouquet_field = builder.get_object("user_bouquet_field")
|
||||
self._user_bouquet_field.set_text(options["user_bouquet_path"])
|
||||
self._satellites_xml_field = builder.get_object("satellites_xml_field")
|
||||
self._satellites_xml_field.set_text(options["satellites_xml_path"])
|
||||
self._data_dir_field = builder.get_object("data_dir_field")
|
||||
self._data_dir_field.set_text(options["data_dir_path"])
|
||||
self._picons_field = builder.get_object("picons_field")
|
||||
self._picons_dir_field = builder.get_object("picons_dir_field")
|
||||
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")
|
||||
|
||||
if self._dialog.run() == Gtk.ResponseType.OK:
|
||||
options["host"] = self._host_field.get_text()
|
||||
options["port"] = self._port_field.get_text()
|
||||
options["user"] = self._login_field.get_text()
|
||||
options["password"] = self._password_field.get_text()
|
||||
options["services_path"] = self._services_field.get_text()
|
||||
options["user_bouquet_path"] = self._user_bouquet_field.get_text()
|
||||
options["satellites_xml_path"] = self._satellites_xml_field.get_text()
|
||||
options["data_dir_path"] = self._data_dir_field.get_text()
|
||||
write_config(options)
|
||||
self._options = options
|
||||
self._active_profile = options.get("profile")
|
||||
self.set_settings()
|
||||
profile = Profile(self._active_profile)
|
||||
self._neutrino_radio_button.set_active(profile is Profile.NEUTRINO_MP)
|
||||
self._support_ver5_check_button.set_sensitive(profile is not Profile.NEUTRINO_MP)
|
||||
|
||||
def show(self):
|
||||
response = self._dialog.run()
|
||||
if response == Gtk.ResponseType.OK:
|
||||
self.apply_settings()
|
||||
write_config(self._options)
|
||||
self._dialog.destroy()
|
||||
|
||||
return response
|
||||
|
||||
def on_data_dir_field_icon_press(self, entry, icon, event_button):
|
||||
response = show_dialog(dialog_type=DialogType.CHOOSER, transient=self._dialog, options=self._options)
|
||||
if response != Gtk.ResponseType.CANCEL:
|
||||
entry.set_text(response)
|
||||
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):
|
||||
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.set_profile(profile)
|
||||
self._support_ver5_check_button.set_sensitive(profile is Profile.ENIGMA_2)
|
||||
|
||||
def set_profile(self, profile):
|
||||
self._active_profile = profile.value
|
||||
self.set_settings()
|
||||
|
||||
def on_reset(self, item):
|
||||
def_settings = get_default_settings()
|
||||
for key in def_settings:
|
||||
current = self._options.get(key)
|
||||
if type(current) is str:
|
||||
continue
|
||||
default = def_settings.get(key)
|
||||
for k in default:
|
||||
current[k] = default.get(k)
|
||||
self.set_settings()
|
||||
|
||||
def set_settings(self):
|
||||
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._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", ""))
|
||||
if Profile(self._active_profile) is Profile.ENIGMA_2:
|
||||
self._support_ver5_check_button.set_active(options.get("v5_support", False))
|
||||
|
||||
def apply_settings(self, item=None):
|
||||
profile = Profile.ENIGMA_2 if self._enigma_radio_button.get_active() else Profile.NEUTRINO_MP
|
||||
self._active_profile = profile.value
|
||||
self._options["profile"] = self._active_profile
|
||||
options = self._options.get(self._active_profile)
|
||||
options["host"] = self._host_field.get_text()
|
||||
options["port"] = self._port_field.get_text()
|
||||
options["user"] = self._login_field.get_text()
|
||||
options["password"] = self._password_field.get_text()
|
||||
options["telnet_user"] = self._telnet_login_field.get_text()
|
||||
options["telnet_password"] = self._telnet_password_field.get_text()
|
||||
options["telnet_port"] = self._telnet_port_field.get_text()
|
||||
options["telnet_timeout"] = int(self._telnet_timeout_spin_button.get_value())
|
||||
options["services_path"] = self._services_field.get_text()
|
||||
options["user_bouquet_path"] = self._user_bouquet_field.get_text()
|
||||
options["satellites_xml_path"] = self._satellites_xml_field.get_text()
|
||||
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()
|
||||
if profile is Profile.ENIGMA_2:
|
||||
options["v5_support"] = self._support_ver5_check_button.get_active()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
52
app/ui/uicommons.py
Normal file
52
app/ui/uicommons.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import locale
|
||||
import os
|
||||
|
||||
import gi
|
||||
from enum import Enum
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk, Gdk
|
||||
|
||||
# path to *.glade files
|
||||
UI_RESOURCES_PATH = "app/ui/" if os.path.exists("app/ui/") else "/usr/share/demoneditor/app/ui/"
|
||||
|
||||
# translation
|
||||
TEXT_DOMAIN = "demon-editor"
|
||||
if UI_RESOURCES_PATH == "app/ui/":
|
||||
LANG_DIR = UI_RESOURCES_PATH + "lang"
|
||||
locale.bindtextdomain(TEXT_DOMAIN, UI_RESOURCES_PATH + "lang")
|
||||
|
||||
theme = Gtk.IconTheme.get_default()
|
||||
_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None
|
||||
CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(
|
||||
"emblem-readonly", 16, 0) else _IMAGE_MISSING
|
||||
LOCKED_ICON = theme.load_icon("system-lock-screen", 16, 0) if theme.lookup_icon(
|
||||
"system-lock-screen", 16, 0) else _IMAGE_MISSING
|
||||
HIDE_ICON = theme.load_icon("go-jump", 16, 0) if theme.lookup_icon("go-jump", 16, 0) else _IMAGE_MISSING
|
||||
TV_ICON = theme.load_icon("tv-symbolic", 16, 0) if theme.lookup_icon("tv-symbolic", 16, 0) else _IMAGE_MISSING
|
||||
IPTV_ICON = theme.load_icon("emblem-shared", 16, 0) if theme.load_icon("emblem-shared", 16, 0) else None
|
||||
|
||||
# keys for move in lists
|
||||
MOVE_KEYS = (Gdk.KEY_Up, Gdk.KEY_Page_Up, Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_Home, Gdk.KEY_End,
|
||||
Gdk.KEY_KP_Page_Up, Gdk.KEY_KP_Page_Down) # KEY_KP_Page_Up(Down) for laptop!
|
||||
|
||||
|
||||
class ViewTarget(Enum):
|
||||
""" Used for set target view """
|
||||
BOUQUET = 0
|
||||
FAV = 1
|
||||
SERVICES = 2
|
||||
|
||||
|
||||
class BqGenType(Enum):
|
||||
""" Bouquet generation type """
|
||||
SAT = 0
|
||||
EACH_SAT = 1
|
||||
PACKAGE = 2
|
||||
EACH_PACKAGE = 3
|
||||
TYPE = 4
|
||||
EACH_TYPE = 5
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
19
build-deb.sh
Executable file
19
build-deb.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
VER="0.3.2_Pre-alpha"
|
||||
B_PATH="dist/DemonEditor"
|
||||
DEB_PATH="$B_PATH/usr/share/demoneditor"
|
||||
|
||||
mkdir -p $B_PATH
|
||||
cp -TRv deb $B_PATH
|
||||
rsync --exclude=app/ui/lang -arv app $DEB_PATH
|
||||
cp -Rv start.py $DEB_PATH
|
||||
|
||||
cd dist
|
||||
fakeroot dpkg-deb --build DemonEditor
|
||||
mv DemonEditor.deb DemonEditor_$VER.deb
|
||||
|
||||
rm -R DemonEditor
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Package: DemonEditor
|
||||
Version: 0.1.1-Pre-alpha
|
||||
Version: 0.3.2-Pre-alpha
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Essential: no
|
||||
Depends: python3 (>= 3.5)
|
||||
Maintainer: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
|
||||
Description: Enigma2 channels and satellites editor
|
||||
Description: Enigma2 channels and satellites list editor
|
||||
|
||||
@@ -5,7 +5,7 @@ Source: https://github.com/DYefremov/DemonEditor
|
||||
Files: *
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Dmitriy Yefremov
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Dmitriy Yefremov
|
||||
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
|
||||
|
||||
@@ -1,23 +1,36 @@
|
||||
# DemonEditor
|
||||
Enigma2 channel and satellites list editor for GNU/Linux.
|
||||
Focused on the convenience of working in lists from the keyboard.
|
||||
The mouse is also fully supported (Drag and Drop etc)
|
||||
|
||||
Keyboard shortcuts:
|
||||
Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
|
||||
Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
|
||||
Ctrl + X - only in bouquet list.
|
||||
Ctrl + C - only in services list. Clipboard is "rubber". There is an accumulation before the insertion!
|
||||
F2 - rename the bouquet.
|
||||
Ctrl + S, T, E in Satellites edit tool for create and edit satellite or transponder.
|
||||
Ctrl + L - parental lock.
|
||||
Ctrl + H - hide/skip.
|
||||
## Enigma2 channel and satellites list editor for GNU/Linux.
|
||||
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
|
||||
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
|
||||
### Keyboard shortcuts:
|
||||
**Ctrl + X, C, V, Up, Down, PageUp, PageDown, Home, End, S, T, E, L, H, Space; Insert, Delete, F2, Enter, P.**
|
||||
* **Insert** - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
|
||||
* **Ctrl + X** - only in bouquet list. **Ctrl + C** - only in services list.
|
||||
Clipboard is **"rubber"**. There is an accumulation before the insertion!
|
||||
* **Ctrl + E** - edit.
|
||||
* **Ctrl + R, F2** - rename.
|
||||
* **Ctrl + S, T** in Satellites edit tool for create satellite or transponder.
|
||||
* **Ctrl + L** - parental lock.
|
||||
* **Ctrl + H** - hide/skip.
|
||||
* **P** - enable/disable preview mode for IPTV in the bouquet list.
|
||||
* **Enter** - start play IPTV or other stream in the bouquet list.
|
||||
* **Space** - select/deselect.
|
||||
* **Left/Right** - remove selection.
|
||||
* **Ctrl + Up, Down, PageUp, PageDown, Home, End** - move selected items in the list.
|
||||
### 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).
|
||||
### Minimum requirements:
|
||||
Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
|
||||
#### Note.
|
||||
To create a simple debian package, you can use the build-deb.sh
|
||||
|
||||
Ability to import IPTV into bouquet from m3u files!
|
||||
|
||||
Tests only on OpenPLi based image with GM 990 Spark Reloaded receiver
|
||||
Tests only in image based on OpenPLi or last BPanther(neutrino) images with GM 990 Spark Reloaded receiver
|
||||
in my preferred linux distro (Last Linux Mint 18.* - MATE 64-bit)!
|
||||
|
||||
Minimum requirements: Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
|
||||
**Terrestrial and cable channels at the moment are not supported!**
|
||||
|
||||
|
||||
Terrestrial and cable channels at the moment are not supported!
|
||||
|
||||
BIN
deb/usr/share/locale/ru/LC_MESSAGES/demon-editor.mo
Normal file
BIN
deb/usr/share/locale/ru/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
3
po/build.sh
Normal file
3
po/build.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
#xgettext --keyword=translatable --sort-output -L Glade -o po/demon-editor.po app/ui/main_window.glade
|
||||
#msgfmt demon-editor.po -o demon-editor.mo
|
||||
BIN
po/ru/demon-editor.mo
Normal file
BIN
po/ru/demon-editor.mo
Normal file
Binary file not shown.
506
po/ru/demon-editor.po
Normal file
506
po/ru/demon-editor.po
Normal file
@@ -0,0 +1,506 @@
|
||||
# Copyright (C) 2018 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
# Dmitriy Yefremov , 2018.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
|
||||
# Main
|
||||
msgid "Service"
|
||||
msgstr "Сервис"
|
||||
|
||||
msgid "Package"
|
||||
msgstr "Пакет"
|
||||
|
||||
msgid "Type"
|
||||
msgstr "Тип"
|
||||
|
||||
msgid "Picon"
|
||||
msgstr "Пикон"
|
||||
|
||||
msgid "Freq"
|
||||
msgstr "Частота"
|
||||
|
||||
msgid "Rate"
|
||||
msgstr "Сим. скорость"
|
||||
|
||||
msgid "Pol"
|
||||
msgstr "Пол."
|
||||
|
||||
msgid "System"
|
||||
msgstr "Система"
|
||||
|
||||
msgid "Pos"
|
||||
msgstr "Поз."
|
||||
|
||||
msgid "Num"
|
||||
msgstr "№"
|
||||
|
||||
msgid "Current IP:"
|
||||
msgstr "Текущий IP:"
|
||||
|
||||
msgid "Assign"
|
||||
msgstr "Привязать"
|
||||
|
||||
msgid "Bouquet details"
|
||||
msgstr "Сервисы букета"
|
||||
|
||||
msgid "Bouquets"
|
||||
msgstr "Букеты"
|
||||
|
||||
msgid "Copy"
|
||||
msgstr "Копировать"
|
||||
|
||||
msgid "Copy reference"
|
||||
msgstr "Копировать ссылку"
|
||||
|
||||
msgid "Data"
|
||||
msgstr ""
|
||||
|
||||
msgid "Download"
|
||||
msgstr "Загрузить"
|
||||
|
||||
msgid "Edit"
|
||||
msgstr "Изменить"
|
||||
|
||||
msgid "Edit "
|
||||
msgstr "Изменить"
|
||||
|
||||
msgid "Edit mаrker text"
|
||||
msgstr "Изменить текст маркера"
|
||||
|
||||
msgid "FTP-transfer"
|
||||
msgstr "Передача установок по FTP"
|
||||
|
||||
msgid "Global search"
|
||||
msgstr "Глобальный поиск"
|
||||
|
||||
msgid "Hide"
|
||||
msgstr "Пропустить"
|
||||
|
||||
msgid "Hide/Skip On/Off Ctrl + H"
|
||||
msgstr "Скрыть/Пропустить Вкл/Выкл Ctrl + H"
|
||||
|
||||
msgid "Add IPTV or stream service"
|
||||
msgstr "Добавить IPTV или поток"
|
||||
|
||||
msgid "Import m3u"
|
||||
msgstr "Импортировать m3u"
|
||||
|
||||
msgid "Import m3u file"
|
||||
msgstr "Импортировать файл m3u"
|
||||
|
||||
msgid "Insert marker"
|
||||
msgstr "Вставить маркер"
|
||||
|
||||
msgid "Locate in services"
|
||||
msgstr "Найти в списке сервисов"
|
||||
|
||||
msgid "Locked"
|
||||
msgstr "Заблокирован"
|
||||
|
||||
msgid "Move"
|
||||
msgstr "Переместить"
|
||||
|
||||
msgid "New"
|
||||
msgstr "Новый"
|
||||
|
||||
msgid "New bouquet"
|
||||
msgstr "Новый букет"
|
||||
|
||||
msgid "Create bouquet"
|
||||
msgstr "Создать букет"
|
||||
|
||||
msgid "For current satellite"
|
||||
msgstr "Для текущего спутника"
|
||||
|
||||
msgid "For current package"
|
||||
msgstr "Для текущего пакета"
|
||||
|
||||
msgid "For current type"
|
||||
msgstr "Для текущего типа"
|
||||
|
||||
msgid "For each satellite"
|
||||
msgstr "Для каждого спутника"
|
||||
|
||||
msgid "For each package"
|
||||
msgstr "Для каждого пакета"
|
||||
|
||||
msgid "For each type"
|
||||
msgstr "Для каждого типа"
|
||||
|
||||
msgid "Open"
|
||||
msgstr "Открыть"
|
||||
|
||||
msgid "Parent lock On/Off Ctrl + L"
|
||||
msgstr "Родительский замок Вкл/Выкл Ctrl + L"
|
||||
|
||||
msgid "Picons"
|
||||
msgstr "Пиконы"
|
||||
|
||||
msgid "Picons downloader"
|
||||
msgstr "Загрузчик пиконов"
|
||||
|
||||
msgid "Satellites downloader"
|
||||
msgstr "Загрузчик спутников"
|
||||
|
||||
msgid "Preferences"
|
||||
msgstr "Настройки"
|
||||
|
||||
msgid "Profile:"
|
||||
msgstr "Профиль:"
|
||||
|
||||
msgid "Radio"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove"
|
||||
msgstr "Удалить"
|
||||
|
||||
msgid "Satellites editor"
|
||||
msgstr "Редактор спутников"
|
||||
|
||||
msgid "Save"
|
||||
msgstr "Сохранить"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
msgid "Services"
|
||||
msgstr "Сервисы"
|
||||
|
||||
msgid "Services filter"
|
||||
msgstr "Фильтр сервисов"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Настройки"
|
||||
|
||||
msgid "Up"
|
||||
msgstr "Переместить вверх"
|
||||
|
||||
msgid "Down"
|
||||
msgstr "Переместить вниз"
|
||||
|
||||
msgid "Active profile:"
|
||||
msgstr "Активный профиль:"
|
||||
|
||||
msgid "All"
|
||||
msgstr "Все"
|
||||
|
||||
msgid "Are you sure?"
|
||||
msgstr "Вы уверены?"
|
||||
|
||||
msgid "Current data path:"
|
||||
msgstr "Текущий путь к данным:"
|
||||
|
||||
msgid "Data dir:"
|
||||
msgstr "Путь к данным:"
|
||||
|
||||
msgid "Data:"
|
||||
msgstr "Данные:"
|
||||
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Редактор списка каналов и спутников Enigma2\n для GNU/Linux"
|
||||
|
||||
msgid "FTP"
|
||||
msgstr ""
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Адрес ресивера:"
|
||||
|
||||
msgid "Loading data..."
|
||||
msgstr "Загрузка данных..."
|
||||
|
||||
msgid "Login:"
|
||||
msgstr "Логин:"
|
||||
|
||||
msgid "Options"
|
||||
msgstr "Настройки"
|
||||
|
||||
msgid "Password:"
|
||||
msgstr "Пароль:"
|
||||
|
||||
msgid "Picons dir:"
|
||||
msgstr "Директория пиконов:"
|
||||
|
||||
msgid "Picons:"
|
||||
msgstr "Пиконы:"
|
||||
|
||||
msgid "Port:"
|
||||
msgstr "Порт:"
|
||||
|
||||
msgid "Receive"
|
||||
msgstr "Получить"
|
||||
|
||||
msgid "Receive files from receiver"
|
||||
msgstr "Получить файлы из ресивера"
|
||||
|
||||
msgid "Receiver IP:"
|
||||
msgstr "IP адрес ресивера:"
|
||||
|
||||
msgid "Remove unused bouquets"
|
||||
msgstr "Удалить не испрльзуемые букеты"
|
||||
|
||||
msgid "Reset profile"
|
||||
msgstr "Сброс профиля"
|
||||
|
||||
msgid "Satellites"
|
||||
msgstr "Спутники"
|
||||
|
||||
msgid "Satellites.xml file:"
|
||||
msgstr "Файл satellites.xml:"
|
||||
|
||||
msgid "Selected"
|
||||
msgstr "Выбор"
|
||||
|
||||
msgid "Send"
|
||||
msgstr "Отправить"
|
||||
|
||||
msgid "Send files to receiver"
|
||||
msgstr "Отправить файлы в ресивер"
|
||||
|
||||
msgid "Services and Bouquets files:"
|
||||
msgstr "Файлы сервисов и букетов:"
|
||||
|
||||
msgid "Telnet"
|
||||
msgstr ""
|
||||
|
||||
msgid "Timeout between commands in seconds"
|
||||
msgstr "Пауза между коммандами в сек."
|
||||
|
||||
msgid "Timeout:"
|
||||
msgstr "Тайм-аут:"
|
||||
|
||||
msgid "User bouquet files:"
|
||||
msgstr "Файлы букетов:"
|
||||
|
||||
msgid "WebTV"
|
||||
msgstr ""
|
||||
|
||||
msgid "Extra:"
|
||||
msgstr "Дополнительно:"
|
||||
|
||||
# Picons dialog
|
||||
msgid "Load providers"
|
||||
msgstr "Загрузить провайдеров"
|
||||
|
||||
msgid "Providers"
|
||||
msgstr "Провайдеры"
|
||||
|
||||
msgid "Receive picons"
|
||||
msgstr "Загрузить пиконы"
|
||||
|
||||
msgid "Picons name format:"
|
||||
msgstr "Формат имени пиконов:"
|
||||
|
||||
msgid "Resize:"
|
||||
msgstr "Обрезать:"
|
||||
|
||||
msgid "Current picons path:"
|
||||
msgstr "Текущий путь к пиконам:"
|
||||
|
||||
msgid "Receiver picons path:"
|
||||
msgstr "Путь к пиконам ресивера:"
|
||||
|
||||
msgid "Picons download tool"
|
||||
msgstr "Загрузчик пиконов"
|
||||
|
||||
msgid "Transfer to receiver"
|
||||
msgstr "Загрузить в ресивер"
|
||||
|
||||
msgid "Downloader"
|
||||
msgstr "Загрузчик"
|
||||
|
||||
msgid "Converter"
|
||||
msgstr "Конвертер"
|
||||
|
||||
msgid "Convert"
|
||||
msgstr "Конвертировать"
|
||||
|
||||
msgid "Path to save:"
|
||||
msgstr "Путь для сохранения:"
|
||||
|
||||
msgid "Path to Enigma2 picons:"
|
||||
msgstr "Путь к пиконам формата Enigma2:"
|
||||
|
||||
msgid "Specify the correct position value for the provider!"
|
||||
msgstr "Укажите правильное значение позиции для провайдера!"
|
||||
|
||||
msgid "Converter between name formats"
|
||||
msgstr "Конвертер формата имен"
|
||||
|
||||
msgid "Receive picons for providers"
|
||||
msgstr "Получение пиконов для провайдеров"
|
||||
|
||||
msgid "Load satellite providers."
|
||||
msgstr "Загрузка провайдеров"
|
||||
|
||||
msgid "To automatically set the identifiers for picons,\nfirst load the required services list into the main application window."
|
||||
msgstr "Для автоматического именования пиконов,\nзагрузите список необходимых сервисов в главное окно программы."
|
||||
|
||||
# Satellites editor
|
||||
msgid "Satellites edit tool"
|
||||
msgstr "Редактор спутников"
|
||||
|
||||
msgid "Add"
|
||||
msgstr "Добавить"
|
||||
|
||||
msgid "Satellite"
|
||||
msgstr "Спутник"
|
||||
|
||||
msgid "Transponder"
|
||||
msgstr "Транспондер"
|
||||
|
||||
msgid "Satellite properties:"
|
||||
msgstr "Параметры спутника:"
|
||||
|
||||
msgid "Transponder properties:"
|
||||
msgstr "Параметры транспондера:"
|
||||
|
||||
msgid "Name"
|
||||
msgstr "Имя"
|
||||
|
||||
msgid "Position"
|
||||
msgstr "Позиция"
|
||||
|
||||
# Service details dialog
|
||||
msgid "Service data:"
|
||||
msgstr "Данные сервиса:"
|
||||
|
||||
msgid "Transponder data:"
|
||||
msgstr "Данные транспондера:"
|
||||
|
||||
msgid "Service data"
|
||||
msgstr "Данные сервиса"
|
||||
|
||||
msgid "Transponder details"
|
||||
msgstr "Данные транспондера"
|
||||
|
||||
msgid "Changes will be applied to all services of this transponder!\nContinue?"
|
||||
msgstr "Изменения будут применены ко всем сервисам данного транспондера!\nПродолжить?"
|
||||
|
||||
msgid "Reference"
|
||||
msgstr "Ссылка"
|
||||
|
||||
msgid "Namespace"
|
||||
msgstr "Пр. имен"
|
||||
|
||||
msgid "Flags:"
|
||||
msgstr "Флаги:"
|
||||
|
||||
msgid "Delays (ms):"
|
||||
msgstr "Задержки (mc)"
|
||||
|
||||
msgid "Bitstream"
|
||||
msgstr "Поток"
|
||||
|
||||
msgid "Description"
|
||||
msgstr "Описание"
|
||||
|
||||
msgid "Source:"
|
||||
msgstr "Источник:"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
msgid "Update"
|
||||
msgstr "Обновить"
|
||||
|
||||
msgid "Filter"
|
||||
msgstr "Фильтр"
|
||||
|
||||
msgid "Find"
|
||||
msgstr "Поиск"
|
||||
|
||||
# IPTV dialog
|
||||
msgid "Stream data"
|
||||
msgstr "Данные потока"
|
||||
|
||||
# Dialogs messages
|
||||
msgid "Error. No bouquet is selected!"
|
||||
msgstr "Ошибка. Не выбран букет!"
|
||||
|
||||
msgid "This item is not allowed to be removed!"
|
||||
msgstr "Этот элемент не разрешен к удалению!"
|
||||
|
||||
msgid "This item is not allowed to edit!"
|
||||
msgstr "Элемент не предназначен для редактирования!"
|
||||
|
||||
msgid "Please, download files from receiver or setup your path for read data!"
|
||||
msgstr "Пожалуйста, загрузите файлы из приемника или настройте путь для чтения данных!"
|
||||
|
||||
msgid "Reading data error!"
|
||||
msgstr "Ошибка чтения данных!"
|
||||
|
||||
msgid "No m3u file is selected!"
|
||||
msgstr "Не выбран m3u файл!"
|
||||
|
||||
msgid "Not implemented yet!"
|
||||
msgstr "Пока не реализовано!"
|
||||
|
||||
msgid "The text of marker is empty, please try again!"
|
||||
msgstr "Текст маркера пуст, попробуйте еще!"
|
||||
|
||||
msgid "Please, select only one item!"
|
||||
msgstr "Пожалуйста, выберите только один элемент!"
|
||||
|
||||
msgid "No png file is selected!"
|
||||
msgstr "Не выбран png файл!"
|
||||
|
||||
msgid "No reference is present!"
|
||||
msgstr "Ссылка не найдена!"
|
||||
|
||||
msgid "No selected item!"
|
||||
msgstr "Не выбран элемент!"
|
||||
|
||||
msgid "The task is already running!"
|
||||
msgstr "Задача уже запущена!"
|
||||
|
||||
msgid "Done!"
|
||||
msgstr "Готово!"
|
||||
|
||||
msgid "Please, wait..."
|
||||
msgstr "Пожалуйста, подождите..."
|
||||
|
||||
msgid "Resizing..."
|
||||
msgstr "Изменение размера..."
|
||||
|
||||
msgid "Select paths!"
|
||||
msgstr "Укажите пути!"
|
||||
|
||||
msgid "No satellite is selected!"
|
||||
msgstr "Не выбран спутник!"
|
||||
|
||||
msgid "Please, select only one satellite!"
|
||||
msgstr "Пожалуйста, выберите только один спутник!"
|
||||
|
||||
msgid "Please check your parameters and try again."
|
||||
msgstr "Пожалуйста, проверте параметры и попробуйте снова!"
|
||||
|
||||
msgid "No satellites.xml file is selected!"
|
||||
msgstr "Не выбран файл satellites.xml!"
|
||||
|
||||
msgid "Error. Verify the data!"
|
||||
msgstr "Ошибка. Проверьте данные!"
|
||||
|
||||
msgid "Operation not allowed in this context!"
|
||||
msgstr "Недопустимая операция в данном контексте!"
|
||||
|
||||
msgid "No VLC is found. Check that it is installed!"
|
||||
msgstr "VLC не найден. Проверьте, что он установлен!"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user