Compare commits

...

173 Commits
0.2.0 ... 0.3.2

Author SHA1 Message Date
DYefremov
93fd3cd2c3 stream play fix for enigma2 2018-09-01 23:17:03 +03:00
DYefremov
03c291a61e added lamedb5 to ftp 2018-06-05 20:45:47 +03:00
DYefremov
97d9ce8b68 lamedb5 reading fix 2018-06-01 18:41:59 +03:00
DYefremov
1a55df6674 lamedb5 writing 2018-06-01 17:45:26 +03:00
DYefremov
6ac10c1380 opening lamedb5 2018-06-01 11:16:30 +03:00
DYefremov
13270b6152 v5 support skeleton 2018-05-28 18:45:31 +03:00
DYefremov
b2c0359017 applying filter after values change 2018-05-22 21:59:17 +03:00
DYefremov
8a6dd1da93 added full screen mode for the player 2018-05-19 16:24:20 +03:00
DYefremov
2e1410ca36 satellites list update in the separate thread 2018-05-12 14:36:38 +03:00
DYefremov
3d96181450 translation for some elements 2018-05-12 12:42:42 +03:00
DYefremov
fe749ca594 added checking that vlc is installed 2018-05-12 12:21:34 +03:00
DYefremov
1b6cd58112 added transponder validity checking 2018-05-12 11:47:19 +03:00
DYefremov
8d405d223a new src for the sat update dialog 2018-05-10 23:28:51 +03:00
DYefremov
81ad19043a new source for sat update dialog 2018-05-10 00:44:42 +03:00
DYefremov
34db58f8e0 selection fix in satellites update dialog 2018-05-07 23:55:22 +03:00
DYefremov
890163af4a added search feature for satellites update dialog 2018-05-07 18:19:00 +03:00
DYefremov
c4e8a6646d added simple filter for satellites update dialog 2018-05-07 15:19:05 +03:00
DYefremov
639c8511bf added ui elements for find and filter for update dialog 2018-05-07 00:44:46 +03:00
DYefremov
5e082fc5d7 new header variant for sat update dialog 2018-05-06 00:08:56 +03:00
DYefremov
7f393ff9ba getting of satellites in separate processes 2018-05-05 22:01:50 +03:00
DYefremov
b37aac0cd9 satellite data update in the main model 2018-05-02 21:23:09 +03:00
DYefremov
d857c4b786 append output for sat update dialog 2018-05-01 21:05:18 +03:00
DYefremov
6ffd1d7926 receive_satellites skeleton 2018-04-30 18:37:02 +03:00
DYefremov
415ad79c80 new satellites update dialog skeleton 2018-04-30 11:11:55 +03:00
DYefremov
da4fef7f6b test implementation of IPTV preview 2018-04-29 15:36:35 +03:00
DYefremov
76c034435d iptv preview mode skeleton 2018-04-29 01:44:28 +03:00
Dmitriy Yefremov
f9239f0642 new variant of satellites download skeleton 2018-04-25 17:26:29 +03:00
DYefremov
7f096df998 satellites dialog skeleton 2018-04-23 23:14:07 +03:00
Dmitriy Yefremov
3f0738d874 skeleton of satellites downloader 2018-04-23 14:42:41 +03:00
Dmitriy Yefremov
b310b640b4 little fix for move in tree model 2018-04-16 19:42:48 +03:00
Dmitriy Yefremov
18caa58336 Update README.md 2018-04-16 18:53:49 +03:00
Dmitriy Yefremov
03e5909c23 support of "Home" and "End" keys, new variant of move in lists 2018-04-16 18:50:48 +03:00
Dmitriy Yefremov
d9071632d2 grouping the scattered rows on move 2018-04-14 00:05:08 +03:00
DYefremov
694269113a iptv reference fix 2018-04-10 16:03:36 +03:00
DYefremov
78f347a505 bouquet creation place depending on the profile 2018-04-10 13:32:43 +03:00
DYefremov
eb9be7b190 fav view update after service edit 2018-04-10 13:04:21 +03:00
DYefremov
952aeb4d22 little refactoring for bq selection 2018-04-10 11:15:50 +03:00
Dmitriy Yefremov
8a865513b3 translation for services popup menu 2018-04-09 21:28:19 +03:00
DYefremov
30e38dde3f bouquet auto generation by type 2018-04-07 23:49:36 +03:00
DYefremov
5f68eb0f1a service deletion refactoring 2018-04-06 17:57:04 +03:00
DYefremov
ce92134a00 refactoring for multiple selection in the bouquets 2018-04-06 16:02:16 +03:00
DYefremov
2c80d13170 basic implementation of bouquets generation 2018-04-04 16:52:58 +03:00
Dmitriy Yefremov
a49d6490c5 bouquets gen skeleton 2018-04-03 22:47:29 +03:00
DYefremov
dc76a7801e Skeleton of the bouquets auto-generation feature 2018-04-02 23:55:41 +03:00
DYefremov
0b0d3ded8c IPTV elements translation 2018-04-01 09:54:50 +03:00
DYefremov
c05dd026fb Merge remote-tracking branch 'origin/experimental' into experimental 2018-04-01 09:39:29 +03:00
DYefremov
dca94271b0 checking data in IPTV dialog 2018-04-01 09:39:14 +03:00
Dmitriy Yefremov
8d7aa8736e Update README.md 2018-03-30 09:41:05 +03:00
Dmitriy Yefremov
1d693670f4 Merge branch 'experimental' into testing 2018-03-30 09:07:41 +03:00
Dmitriy Yefremov
52f50cdaf5 upd readme 2018-03-30 09:07:17 +03:00
DYefremov
d2ac5d5ac4 impl of checking the CAID field 2018-03-29 13:49:01 +03:00
Dmitriy Yefremov
fe579358f6 Merge branch 'experimental' into testing 2018-03-28 17:30:00 +03:00
Dmitriy Yefremov
db942ee10b set defaults for dvb-s2 after system change 2018-03-28 17:29:07 +03:00
DYefremov
b4648a6efd Merge remote-tracking branch 'origin/testing' into testing 2018-03-27 23:05:39 +03:00
DYefremov
647f468feb Merge branch 'experimental' into testing 2018-03-27 23:05:21 +03:00
DYefremov
ef608df76b picon name update after service edit 2018-03-27 23:03:56 +03:00
DYefremov
8e32373a99 picon name update after service edit 2018-03-27 23:03:01 +03:00
DYefremov
7f817944fa reference update in the service dialog 2018-03-26 17:33:20 +03:00
Dmitriy Yefremov
7752da92b1 Merge branch 'experimental' into testing 2018-03-25 10:47:33 +03:00
Dmitriy Yefremov
24023d438d ui changes for the service dialog 2018-03-25 10:46:30 +03:00
DYefremov
9e0d8840f3 init neutrino ui elms for service dialog 2018-03-23 21:42:54 +03:00
Dmitriy Yefremov
d762f097d0 state update for navigation buttons 2018-03-20 23:42:06 +03:00
DYefremov
336aa47177 added default init in service dialog 2018-03-20 00:33:00 +03:00
DYefremov
1eeccd654a added validation for non empty entries in service dialog 2018-03-18 18:11:16 +03:00
DYefremov
e37abef359 decoupling for [on save] in service dialog 2018-03-18 17:13:21 +03:00
Dmitriy Yefremov
c120f42ee1 skeleton of getting transponder data for neutrino 2018-03-17 22:18:10 +03:00
DYefremov
1531548e51 add impl for iptv 2018-03-16 00:10:33 +03:00
DYefremov
72ebdceb6e edit impl for iptv 2018-03-15 23:10:22 +03:00
Dmitriy Yefremov
791fa2b5f6 init neutrino data skeleton for iptv dialog 2018-03-13 23:27:55 +03:00
Dmitriy Yefremov
47df44c202 little optimisation 2018-03-13 10:42:56 +03:00
Dmitriy Yefremov
d7635370ba iptv dialog init data 2018-03-12 22:47:43 +03:00
DYefremov
1d577750c0 Iptv dialog skeleton 2018-03-11 21:52:10 +03:00
DYefremov
b56685edb1 Ui skeleton for IPTV dialog 2018-03-10 19:12:56 +03:00
DYefremov
f7f230f40e Separation of service creation and editing 2018-03-10 17:49:53 +03:00
Dmitriy Yefremov
88e19e2fd1 added warning message to the picons dialog 2018-03-09 17:40:26 +03:00
DYefremov
ae6f0e1ae2 pos fix for picons dialog 2018-03-07 23:56:21 +03:00
DYefremov
c3e880890e little picons dialog changes 2018-03-07 22:43:42 +03:00
Dmitriy Yefremov
a7edb6d0f6 Merge branch 'experimental' into testing 2018-03-06 20:46:25 +03:00
DYefremov
03e18401cc global search navigation skeleton 2018-03-06 19:06:16 +03:00
DYefremov
1d6b8c2558 Dialogs translation 2018-03-06 11:34:06 +03:00
Dmitriy Yefremov
ccd111cd94 added ui elements for search navigation 2018-03-05 22:45:21 +03:00
Dmitriy Yefremov
e8f3b5df8a convert to impl 2018-03-04 19:37:41 +03:00
DYefremov
c4c9c73809 neutrino data init skeleton for service dialog 2018-03-03 23:24:43 +03:00
DYefremov
a4514ebb2b fix tv counter 2018-03-03 20:55:08 +03:00
Dmitriy Yefremov
7b44df9afd added converter tab for picons dialog 2018-03-03 13:38:33 +03:00
DYefremov
3a018e9654 added local translation 2018-03-02 17:08:40 +03:00
DYefremov
9d4e571d89 added local translation 2018-03-02 17:06:53 +03:00
DYefremov
f62184c96c type entry for service dialog 2018-03-02 15:12:21 +03:00
Dmitriy Yefremov
320183554c status bar changes 2018-03-01 20:39:00 +03:00
Dmitriy Yefremov
a525816eca russian translation 2018-03-01 00:18:05 +03:00
DYefremov
0e11a223ad russian translate 2018-02-27 22:05:09 +03:00
Dmitriy Yefremov
6e4b992a79 russian translation skeleton 2018-02-27 14:55:03 +03:00
DYefremov
1164d38e5c e2 impl for set transponder 2018-02-26 16:26:53 +03:00
Dmitriy Yefremov
4288d62a53 set transponder for services 2018-02-25 22:59:36 +03:00
Dmitriy Yefremov
b17bd13fb5 revert transponder services dialog 2018-02-25 17:52:21 +03:00
Dmitriy Yefremov
7124fd6a92 new ui variant for services details dialog 2018-02-24 16:12:05 +03:00
DYefremov
81f354207d ServicesListDialog skeleton 2018-02-23 23:56:19 +03:00
Dmitriy Yefremov
15cb611764 fix flags 2018-02-23 17:36:49 +03:00
Dmitriy Yefremov
6115433aba skeleton of implementation of service editing for Enigma 2018-02-23 17:14:08 +03:00
Dmitriy Yefremov
0c5b9165ef intermediate impl of save in service dialog 2018-02-20 23:34:07 +03:00
DYefremov
c432646f30 on save skeleton for service details dialog 2018-02-20 00:20:32 +03:00
DYefremov
f70913832c showing flags in the service details dialog 2018-02-19 23:18:01 +03:00
DYefremov
25ee7f3538 added simple wait dialog 2018-02-18 17:14:02 +03:00
DYefremov
5a76601ae6 little refactoring in UI for edit/rename 2018-02-18 11:23:31 +03:00
Dmitriy Yefremov
4367fe6ead skeleton implementation of service details show 2018-02-17 17:07:32 +03:00
Dmitriy Yefremov
242642a7ed Update README.md 2018-02-17 16:40:40 +03:00
Dmitriy Yefremov
074fc960e5 skeleton implementation of service details show 2018-02-17 16:23:41 +03:00
DYefremov
e26d08ca8e filling pids in service dialog 2018-02-16 07:41:19 +03:00
DYefremov
8c433680a9 little refactoring ) 2018-02-16 01:58:43 +03:00
DYefremov
547046bddb service details dialog data filling 2018-02-16 01:56:28 +03:00
Dmitriy Yefremov
12983bb1a6 new variant of service details dialog 2018-02-15 18:05:13 +03:00
Dmitriy Yefremov
5dc20232ef Update README.md 2018-02-15 15:22:09 +03:00
Dmitriy Yefremov
0040ecee32 Update README.md 2018-02-15 15:21:08 +03:00
Dmitriy Yefremov
e8f30b667d service dialog elements update 2018-02-15 15:16:34 +03:00
DYefremov
99a9f081fa service details dialog ui changes 2018-02-14 19:06:08 +03:00
Dmitriy Yefremov
51605ae680 service details dialog skeleton 2018-02-14 00:00:52 +03:00
Dmitriy Yefremov
ca06400071 little ui changes 2018-02-13 17:21:25 +03:00
Dmitriy Yefremov
588df32b2f added IPTV icon 2018-02-12 14:27:21 +03:00
Dmitriy Yefremov
ab7f560b4f webtv download/upload 2018-02-12 13:34:00 +03:00
DYefremov
ffce103eae m3u gui elements for neutrino 2018-02-11 23:14:22 +03:00
Dmitriy Yefremov
90418f0e28 webtv parse for neutrino 2018-02-11 16:47:20 +03:00
Dmitriy Yefremov
0daaf6d1e5 neutrino save services fix 2018-02-11 10:50:21 +03:00
DYefremov
47f26b0f4c little fix 2018-02-10 15:49:44 +03:00
DYefremov
e67ce41667 copy/paste fix 2018-02-06 14:20:59 +03:00
DYefremov
0cff24486a insert marker to empty bouquet fix 2018-02-06 13:56:17 +03:00
DYefremov
850ba0d96a little ui changes 2018-02-06 09:49:51 +03:00
Dmitriy Yefremov
303c9a0267 write bouquet fix 2018-02-05 22:17:06 +03:00
Dmitriy Yefremov
0f95165088 set encoding for ftp, fix file names for bouquets 2018-02-05 18:24:49 +03:00
Dmitriy Yefremov
105cf9c90c new algorithm for picons retrieving 2018-02-05 14:44:42 +03:00
Dmitriy Yefremov
cb40a8d0de name space for picons 2018-02-04 18:09:37 +03:00
DYefremov
f19ab37bc8 sat position for picons dialog 2018-02-03 23:25:15 +03:00
DYefremov
d716bd6a86 Displaying picons in lists for Neutrino 2018-02-03 21:07:01 +03:00
Dmitriy Yefremov
5b13b22823 lamedb fix 2018-02-03 17:23:16 +03:00
Dmitriy Yefremov
49076fe477 bouquet names fix 2018-02-03 16:45:54 +03:00
DYefremov
50a517b6f1 simple skeleton for global search 2018-02-02 12:45:58 +03:00
Dmitriy Yefremov
184c3b18ba assign picon skeleton 2018-02-01 21:43:44 +03:00
Dmitriy Yefremov
2f8dcaf47b data backup before save 2018-02-01 14:02:59 +03:00
Dmitriy Yefremov
dbe18b345f skeleton for picon remove 2018-02-01 13:10:06 +03:00
Dmitriy Yefremov
5d68ec8176 added search bar 2018-01-31 16:02:26 +03:00
DYefremov
83e58f9375 little decoupling, filter bar 2018-01-31 00:13:42 +03:00
Dmitriy Yefremov
fe199d78a4 picon reference 2018-01-30 12:37:04 +03:00
DYefremov
d5f7acb019 popup menus for picons 2018-01-29 18:07:47 +03:00
DYefremov
a141c34ee7 Display picons in the fav list 2018-01-29 14:46:34 +03:00
Dmitriy Yefremov
25c9189e1a Display picons in the services list 2018-01-28 23:10:54 +03:00
Dmitriy Yefremov
d9390aa7be Update README.md 2018-01-26 21:26:03 +03:00
Dmitriy Yefremov
e12cc86e5f Update readme 2018-01-26 21:21:40 +03:00
DYefremov
f1ef9fe4aa decoupling deletion, deletion with considering filter 2018-01-26 15:15:39 +03:00
Dmitriy Yefremov
728bfd0b20 added remove selection keys 2018-01-25 21:43:48 +03:00
Dmitriy Yefremov
1d6022b6db fix lock\hide 2018-01-25 21:05:24 +03:00
DYefremov
8609d30ac9 bouquets fix, new implementation of services filter 2018-01-25 16:11:52 +03:00
DYefremov
fde06dca89 filter revert 2018-01-24 13:39:11 +03:00
Dmitriy Yefremov
e41bf5f58f little gui changes 2018-01-24 00:05:15 +03:00
Dmitriy Yefremov
b1488df9ce little gui changes 2018-01-23 22:58:43 +03:00
DYefremov
c6e4b3624b services filter skeleton 2018-01-23 16:18:28 +03:00
DYefremov
26b843921b force ctrl for fav and services lists 2018-01-22 14:51:34 +03:00
Dmitriy Yefremov
e73638d006 resize option for picons 2018-01-20 22:17:18 +03:00
Dmitriy Yefremov
cf3c05f324 mkdir if not exist for picons 2018-01-20 14:04:07 +03:00
Dmitriy Yefremov
030b7c4957 upload data fix 2018-01-20 11:29:34 +03:00
Dmitriy Yefremov
d7ed3e20a4 Update README.md 2018-01-19 13:15:49 +03:00
Dmitriy Yefremov
c69b0ac9e1 neutrino fav id fix 2018-01-19 11:15:35 +03:00
Dmitriy Yefremov
c5c88a8958 telnet options 2018-01-18 12:38:58 +03:00
Dmitriy Yefremov
24c064b450 telnet options 2018-01-18 00:57:58 +03:00
Dmitriy Yefremov
240d724b59 picons name format for neutrino 2018-01-17 01:18:02 +03:00
Dmitriy Yefremov
5b410241a9 upload picons impl 2018-01-16 18:51:08 +03:00
Dmitriy Yefremov
a6ffe4999a termination of process for picons 2018-01-16 12:11:54 +03:00
Dmitriy Yefremov
f67a79e869 base implementation of picons parser 2018-01-16 01:16:03 +03:00
Dmitriy Yefremov
d37c088112 load providers skeleton for picons 2018-01-15 14:56:17 +03:00
Dmitriy Yefremov
adf117c88d picons parser skeleton 2018-01-12 14:32:36 +03:00
Dmitriy Yefremov
98da7acd96 changes in dialogues 2018-01-11 17:59:59 +03:00
Dmitriy Yefremov
c82763081a added telnet options 2018-01-10 22:13:25 +03:00
Dmitriy Yefremov
1a39557964 properties for picons 2018-01-10 18:09:44 +03:00
Dmitriy Yefremov
c274c9e91d skeleton for picons dialog 2018-01-10 12:15:41 +03:00
Dmitriy Yefremov
dd1ec89592 GUI skeleton for picons 2018-01-08 22:00:48 +03:00
43 changed files with 18355 additions and 1064 deletions

View File

@@ -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

View File

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

View File

@@ -1,6 +1,6 @@
from app.commons import run_task
from app.properties import Profile
from .ecommons import Service, Satellite, Transponder, Bouquet, Bouquets
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
@@ -10,17 +10,17 @@ from .neutrino.services import get_services as get_neutrino_services, write_serv
from .satxml import get_satellites, write_satellites
def get_services(data_path, profile):
def get_services(data_path, profile, format_version):
if profile is Profile.ENIGMA_2:
return get_enigma_services(data_path)
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):
def write_services(path, channels, profile, format_version):
if profile is Profile.ENIGMA_2:
write_enigma_services(path, channels)
write_enigma_services(path, channels, format_version)
elif profile is Profile.NEUTRINO_MP:
write_neutrino_services(path, channels)

View File

@@ -2,8 +2,8 @@
from collections import namedtuple
from enum import Enum
Service = namedtuple("Service", ["flags_cas", "transponder_type", "coded", "service", "locked", "hide",
"package", "service_type", "ssid", "freq", "rate", "pol", "fec",
Service = namedtuple("Service", ["flags_cas", "transponder_type", "coded", "service", "locked", "hide", "package",
"service_type", "picon", "picon_id", "ssid", "freq", "rate", "pol", "fec",
"system", "pos", "data_id", "fav_id", "transponder"])
@@ -27,15 +27,29 @@ Transponder = namedtuple("Transponder", ["frequency", "symbol_rate", "polarizati
"system", "modulation", "pls_mode", "pls_code", "is_id"])
class Type(Enum):
""" Types of DVB transponders """
class TrType(Enum):
""" Transponders type """
Satellite = "s"
Terestrial = "t"
Cable = "c"
class FLAG(Enum):
""" Service flags """
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.
@@ -43,9 +57,48 @@ class FLAG(Enum):
NEW = 40 # Marked as new at the last scan
@staticmethod
def hide_values():
return 2, 3, 6, 7, 10, 42, 43, 46, 47
def is_hide(value: int):
return value & 1 << 1
@staticmethod
def is_keep(value: int):
return value & 1 << 0
@staticmethod
def is_pids(value: int):
return value & 1 << 2
@staticmethod
def is_new(value: int):
return value & 1 << 5
class Pids(Enum):
VIDEO = "c:00"
AUDIO = "c:01"
TELETEXT = "c:02"
PCR = "c:03"
AC3 = "c:04"
VIDEO_TYPE = "c:05"
AUDIO_CHANNEL = "c:06"
BIT_STREAM_DELAY = "c:07" # in ms
PCM_DELAY = "c:08" # in ms
SUBTITLE = "c:09"
class Inversion(Enum):
Off = "0"
On = "1"
Auto = "2"
class Pilot(Enum):
Off = "0"
On = "1"
Auto = "2"
ROLL_OFF = {"0": "35%", "1": "25%", "2": "20%", "3": "Auto"}
POLARIZATION = {"0": "H", "1": "V", "2": "L", "3": "R"}
@@ -56,13 +109,15 @@ FEC = {"0": "Auto", "1": "1/2", "2": "2/3", "3": "3/4", "4": "5/6", "5": "7/8",
"17": "4/5", "18": "9/10", "19": "1/2", "20": "2/3", "21": "3/4", "22": "5/6", "23": "7/8", "24": "8/9",
"25": "3/5", "26": "4/5", "27": "9/10", "28": "Auto"}
FEC_DEFAULT = {"0": "Auto", "1": "1/2", "2": "2/3", "3": "3/4", "4": "5/6", "5": "7/8", "6": "8/9", "7": "3/5",
"8": "4/5", "9": "9/10"}
SYSTEM = {"0": "DVB-S", "1": "DVB-S2"}
MODULATION = {"0": "Auto", "1": "QPSK", "2": "8PSK", "3": "16APSK", "5": "32APSK"}
SERVICE_TYPE = {"-2": "Unknown", "1": "TV", "2": "Radio", "3": "Data",
"10": "Radio", "12": "Data", "22": "TV", "25": "TV (HD)",
"136": "Data", "139": "Data"}
SERVICE_TYPE = {"-2": "Data", "1": "TV", "2": "Radio", "3": "Data", "10": "Radio", "22": "TV (H264)",
"25": "TV (HD)", "31": "TV (UHD)"}
CAS = {"C:2600": "BISS", "C:0b00": "Conax", "C:0b01": "Conax", "C:0b02": "Conax", "C:0baa": "Conax", "C:0602": "Irdeto",
"C:0604": "Irdeto", "C:0606": "Irdeto", "C:0608": "Irdeto", "C:0622": "Irdeto", "C:0626": "Irdeto",
@@ -71,3 +126,42 @@ CAS = {"C:2600": "BISS", "C:0b00": "Conax", "C:0b01": "Conax", "C:0b02": "Conax"
# 'on' attribute 0070(hex) = 112(int) = ONID(ONID-TID on www.lyngsat.com)
PROVIDER = {112: "HTB+", 253: "Tricolor TV"}
# ************* subsidiary functions ****************
def get_key_by_value(dc: dict, value):
""" Returns key from dict by value """
for k, v in dc.items():
if v == value:
return k
def get_value_by_name(en, name):
""" Returns value by name from enums """
for n in en:
if n.name == name:
return n.value
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

View File

@@ -1,13 +1,13 @@
""" Module for parsing bouquets """
from app.eparser.ecommons import BqServiceType, BouquetService, Bouquets, Bouquet
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"
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):
@@ -19,10 +19,10 @@ def write_bouquets(path, bouquets):
line.append("#NAME {}\n".format(bqs.name))
for bq in bqs.bouquets:
line.append(srv_line.format(bq.name, bq.type))
line.append(srv_line.format(bq.name.replace(" ", "_"), bq.type))
write_bouquet(path, bq.name, bq.type, bq.services)
with open(path + "bouquets.{}".format(bqs.type), "w") as file:
with open(path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
file.writelines(line)
@@ -38,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"
@@ -85,10 +81,10 @@ def parse_bouquets(path, bq_name, bq_type):
_, _, name = line.partition(nm_sep)
bouquets = Bouquets(name.strip(), bq_type, [])
if bouquets and "#SERVICE" in line:
name = line.split(".")[1]
bouquets[2].append(Bouquet(name=name,
b_name, services = get_bouquet(path, line.split(".")[1], bq_type)
bouquets[2].append(Bouquet(name=b_name,
type=bq_type,
services=get_bouquet(path, name, bq_type),
services=services,
locked=None,
hidden=None))

View File

@@ -1,25 +1,32 @@
""" This module used for parsing lamedb file
""" This module used for parsing and write 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 app.commons import log
from app.ui import CODED_ICON, LOCKED_ICON, HIDE_ICON
from app.ui.uicommons import CODED_ICON, LOCKED_ICON, HIDE_ICON
from .blacklist import get_blacklist
from ..ecommons import Service, POLARIZATION, SYSTEM, FEC, SERVICE_TYPE, FLAG
from ..ecommons import Service, POLARIZATION, FEC, SERVICE_TYPE, Flag
_HEADER = "eDVB services /4/"
_FILE_PATH = "../data/lamedb"
_HEADER = "eDVB services /{}/"
_SEP = ":" # separator
_FILE_NAME = "lamedb"
_END_LINE = "# File was created in DemonEditor.\n# ....Enjoy watching!....\n"
def get_services(path):
return parse(path)
def get_services(path, format_version):
return parse(path, format_version)
def write_services(path, services):
lines = [_HEADER, "\ntransponders\n"]
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()
@@ -37,14 +44,42 @@ def write_services(path, services):
tr_lines.sort()
lines.extend(tr_lines)
lines.extend(services_lines)
lines.append("end\nFile was created in DemonEditor.\n....Enjoy watching!....\n")
lines.append("end\n" + _END_LINE)
with open(path + _FILE_NAME, "w") as file:
file.writelines(lines)
def parse(path):
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())
@@ -57,9 +92,30 @@ def parse(path):
log(msg)
raise SyntaxError(msg)
transponders, sep, services = services.partition("services") # 2 step
services, sep, _ = services.partition("end") # 3 step
services, sep, _ = services.partition("\nend") # 3 step
return parse_services(services.split("\n"), transponders.split("/"), path)
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):
@@ -76,9 +132,7 @@ def parse_transponders(arg):
def parse_services(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])
@@ -86,39 +140,50 @@ def parse_services(services, transponders, path):
for ch in srv:
data = str(ch[0]).split(_SEP)
sp = "0"
tid = data[2]
nid = data[3]
transponder_id = "{}:{}:{}".format(data[1], tid, nid)
transponder = transponders.get(transponder_id, None)
tid = tid.lstrip(sp).upper()
nid = nid.lstrip(sp).upper()
ssid = str(data[0]).lstrip(sp).upper()
onid = str(data[1]).lstrip(sp).upper()
# For comparison in bouquets. Needed in upper case!!!
fav_id = "{}:{}:{}:{}".format(str(data[0]).lstrip(sp), str(data[2]).lstrip(sp),
str(data[3]).lstrip(sp), str(data[1]).lstrip(sp)).upper()
fav_id = "{}:{}:{}:{}".format(ssid, tid, nid, onid)
picon_id = "1_0_{}_{}_{}_{}_{}_0_0_0.png".format(1, ssid, tid, nid, onid)
all_flags = ch[2].split(",")
coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None
flags = list(filter(lambda x: x.startswith("f:"), all_flags))
hide = HIDE_ICON if flags and int(flags[0][2:]) in FLAG.hide_values() else None
hide = HIDE_ICON if flags and Flag.is_hide(int(flags[0][2:])) else None
locked = LOCKED_ICON if fav_id in blacklist else None
package = list(filter(lambda x: x.startswith("p:"), all_flags))
package = package[0][2:] if package else None
transponder_id = "{}:{}:{}".format(data[1], data[2], data[3])
transponder = transponders.get(transponder_id, None)
if transponder is not None:
tr_type, sp, tr = str(transponder).partition(" ")
tr = tr.split(_SEP)
service_type = SERVICE_TYPE.get(data[4], SERVICE_TYPE["-2"])
# removing all non printable symbols!
srv_name = "".join(c for c in ch[1] if c.isprintable())
channels.append(Service(flags_cas=ch[2],
transponder_type=tr_type,
coded=coded,
service=ch[1],
service=srv_name,
locked=locked,
hide=hide,
package=package,
service_type=service_type,
picon=None,
picon_id=picon_id,
ssid=data[0],
freq=tr[0],
rate=tr[1],
pol=POLARIZATION[tr[2]],
fec=FEC[tr[3]],
system=SYSTEM[tr[6]],
system="DVB-S2" if len(tr) > 7 else "DVB-S",
pos="{}.{}".format(tr[4][:-1], tr[4][-1:]),
data_id=ch[0],
fav_id=fav_id,

View File

@@ -1,21 +1,40 @@
""" Module for IPTV and streams support """
from enum import Enum
from app.properties import Profile
from app.ui.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(Service(*aggr[0:3], name, *aggr[0:3], BqServiceType.IPTV.name, *aggr, fav_id, None))
if profile is Profile.ENIGMA_2:
fav_id = ENIGMA2_FAV_ID_FORMAT.format(StreamType.DVB_TS.value, 1, 0, 0, 0, 0,
line.strip().replace(":", "%3a"), name, name, None)
elif profile is Profile.NEUTRINO_MP:
fav_id = NEUTRINO_FAV_ID_FORMAT.format(line.strip(), "", 0, None, None, None, None, "", "", 1)
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], BqServiceType.IPTV.name, *aggr, fav_id, None)
channels.append(srv)
return channels

View File

@@ -1,23 +1,21 @@
import os
from contextlib import suppress
from enum import Enum
from xml.dom.minidom import parse, Document
from app.ui import LOCKED_ICON, HIDE_ICON
from ..ecommons import Bouquets, Bouquet, BouquetService, BqServiceType
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"
class BqType(Enum):
BOUQUET = "bouquet"
TV = "tv"
_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_bouquets(path + _U_FILE, "FAV", BqType.TV.value),
parse_webtv(path + _W_FILE, "WEBTV", BqType.WEBTV.value))
def parse_bouquets(file, name, bq_type):
@@ -40,7 +38,8 @@ def parse_bouquets(file, name, bq_type):
if srv_elem.hasAttributes():
ssid = srv_elem.attributes["i"].value
on = srv_elem.attributes["on"].value
fav_id = "{}:{}".format(on, ssid)
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,
@@ -48,25 +47,74 @@ def parse_bouquets(file, name, bq_type):
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):
if len(bouquets) < 2:
for f in path + _FILE, path + _U_FILE:
with suppress(FileNotFoundError):
os.remove(f)
for bq in bouquets:
bq_type = BqType(bq.type)
write_bouquet(path + (_FILE if bq_type is BqType.BOUQUET else _U_FILE), bq)
if bq_type is BqType.WEBTV:
write_webtv(path + _W_FILE, bq)
else:
write_bouquet(path + (_FILE if bq_type is BqType.BOUQUET else _U_FILE), bq)
def write_bouquet(file, bouquet):
doc = Document()
root = doc.createElement("zapit")
doc.appendChild(root)
comment = doc.createComment(" File was created in DemonEditor. Enjoy watching! ")
comment = doc.createComment(_COMMENT)
doc.appendChild(comment)
for bq in bouquet.bouquets:
@@ -78,11 +126,11 @@ def write_bouquet(file, bouquet):
root.appendChild(bq_elem)
for srv in bq.services:
on, sep, ssid = srv.fav_id.partition(":")
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", srv.transponder.split(":")[0].lstrip("0"))
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])
@@ -92,5 +140,43 @@ def write_bouquet(file, bouquet):
doc.writexml(open(file, "w"), addindent=" ", newl="\n", encoding="UTF-8")
def write_webtv(file, bouquet):
doc = Document()
root = doc.createElement("webtvs")
doc.appendChild(root)
comment = doc.createComment(_COMMENT)
doc.appendChild(comment)
for bq in bouquet.bouquets:
for srv in bq.services:
url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group = srv.fav_id.split("::")
srv_elem = doc.createElement("webtv")
srv_elem.setAttribute("title", srv.service)
srv_elem.setAttribute("url", url)
if description != "None":
srv_elem.setAttribute("description", description)
if urlkey != "None":
srv_elem.setAttribute("urlkey", urlkey)
if account != "None":
srv_elem.setAttribute("account", account)
if usrname != "None":
srv_elem.setAttribute("usrname", usrname)
if psw != "None":
srv_elem.setAttribute("psw", psw)
if s_type != "None":
srv_elem.setAttribute("type", s_type)
if iconsrc != "None":
srv_elem.setAttribute("iconsrc", iconsrc)
if iconsrc_b != "None":
srv_elem.setAttribute("iconsrc_b", iconsrc_b)
if group != "None":
srv_elem.setAttribute("group", group)
root.appendChild(srv_elem)
doc.writexml(open(file, "w"), addindent=" ", newl="\n", encoding="UTF-8")
if __name__ == "__main__":
pass

View File

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

View File

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

View File

@@ -2,25 +2,31 @@ import os
import socket
import time
from enum import Enum
from ftplib import FTP
from ftplib import FTP, error_perm
from telnetlib import Telnet
from app.commons import log
from app.properties import Profile
__DATA_FILES_LIST = ("tv", "radio", "lamedb", "blacklist", "whitelist", # enigma 2
__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 = []
@@ -33,40 +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, profile=Profile.ENIGMA_2):
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, user=None if profile is Profile.ENIGMA_2 else "root", password=None,
timeout=5 if profile is Profile.ENIGMA_2 else 1)
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 or 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:
@@ -81,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)
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 """
@@ -94,7 +150,7 @@ def send_file(file_name, path, ftp):
return ftp.storbinary("STOR " + file_name, f)
def telnet(host, port=23, user=None, password=None, timeout=5):
def telnet(host, port=23, user="", password="", timeout=5):
try:
tn = Telnet(host=host, port=port, timeout=timeout)
except socket.timeout:
@@ -102,11 +158,11 @@ def telnet(host, port=23, user=None, password=None, timeout=5):
else:
time.sleep(1)
command = yield
if user is not None:
if user != "":
tn.read_until(b"login: ")
tn.write(user.encode("utf-8") + b"\n")
time.sleep(timeout)
if password is not None:
if password != "":
tn.read_until(b"Password: ")
tn.write(password.encode("utf-8") + b"\n")
time.sleep(timeout)

View File

@@ -41,17 +41,26 @@ def get_default_settings():
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 + "enigma2/"},
"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/",
"data_dir_path": DATA_PATH + "neutrino/"},
"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}

0
app/tools/__init__.py Normal file
View File

96
app/tools/media.py Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1 @@
import gi
import os
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/"
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

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +1,48 @@
""" 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:
@@ -51,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

View File

@@ -1,8 +1,8 @@
from app.commons import run_idle, run_task
from app.ftp import download_data, DownloadDataType, upload_data
from app.properties import Profile
from . import Gtk, UI_RESOURCES_PATH
from .dialogs import show_dialog, DialogType
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .dialogs import show_dialog, DialogType, get_message
def show_download_dialog(transient, options, open_data, profile=Profile.ENIGMA_2):
@@ -22,6 +22,7 @@ class DownloadDialog:
"on_info_bar_close": self.on_info_bar_close}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", ("download_dialog",))
builder.connect_signals(handlers)
@@ -35,16 +36,19 @@ class DownloadDialog:
self._all_radio_button = builder.get_object("all_radio_button")
self._bouquets_radio_button = builder.get_object("bouquets_radio_button")
self._satellites_radio_button = builder.get_object("satellites_radio_button")
self._webtv_radio_button = builder.get_object("webtv_radio_button")
if profile is Profile.NEUTRINO_MP:
self._webtv_radio_button.set_visible(True)
# self._dialog.get_content_area().set_border_width(0)
@run_idle
def on_receive(self, item):
self.download(True, d_type=self.get_download_type())
self.download(True, self.get_download_type())
@run_idle
def on_send(self, item):
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.CANCEL:
self.download(d_type=self.get_download_type())
self.download(False, self.get_download_type())
def get_download_type(self):
download_type = DownloadDataType.ALL
@@ -52,6 +56,8 @@ class DownloadDialog:
download_type = DownloadDataType.BOUQUETS
elif self._satellites_radio_button.get_active():
download_type = DownloadDataType.SATELLITES
elif self._webtv_radio_button.get_active():
download_type = DownloadDataType.WEBTV
return download_type
def run(self):
@@ -60,27 +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(),
profile=self._profile)
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
View 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

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -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.commons import run_task
from app.eparser import Service
from app.eparser.ecommons import FLAG
from app.eparser.ecommons import Flag, BouquetService, Bouquet, BqType
from app.eparser.enigma.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.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] = Service(None, None, None, response, None, None, None, s_type, *[None] * 7, max_num, fav_id, None)
marker = (None, None, response, None, None, s_type, None, fav_id, None)
itr = model.insert_before(model.get_iter(paths[0]), marker) if paths else model.insert(0, marker)
bouquets[selected_bouquet].insert(model.get_path(itr)[0], fav_id)
channels[fav_id] = Service(None, None, None, response, None, None, None, s_type, *[None] * 9, max_num, fav_id, None)
def edit_marker(view, bouquets, selected_bouquet, channels, parent_window):
@@ -53,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] = Service(*old_ch[0:3], response, *old_ch[4:15], old_ch.data_id, new_fav_id, None)
channels[new_fav_id] = old_ch._replace(service=response, fav_id=new_fav_id)
bq_services.pop(index)
bq_services.insert(index, new_fav_id)
# ***************** 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] = Service(*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] = Service(*channel[:4], None if locked else LOCKED_ICON, *channel[5:])
channels[fav_id] = channel._replace(locked=None if locked else LOCKED_ICON)
ids.append(fav_id)
if target is ViewTarget.FAV and ids:
for ch in services_model:
if ch[16] in ids:
if ch[18] in ids:
ch[4] = None if locked else LOCKED_ICON
@@ -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]
else:
value = "f:{}".format(value) if value > 10 else "f:0{}".format(value)
value = "f:{:02d}".format(value)
if index is not None:
flags[index] = value
else:
flags.append(value)
model.set_value(itr, 0, (",".join(reversed(sorted(flags)))))
fav_id = model.get_value(itr, 16)
fav_id = model.get_value(itr, 18)
channel = channels.get(fav_id, None)
if channel:
channels[fav_id] = Service(*channel[:5], None if hide else HIDE_ICON, *channel[6:])
channels[fav_id] = channel._replace(hide=None if hide else HIDE_ICON)
def has_locked_hide(model, paths, col_num):
@@ -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
View 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
View 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

View File

@@ -66,6 +66,9 @@
<row>
<col id="0" translatable="yes">9/10</col>
</row>
<row>
<col id="0" translatable="yes">Auto</col>
</row>
</data>
</object>
<object class="GtkListStore" id="mod_store">
@@ -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>

View File

@@ -1,22 +1,22 @@
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):
@@ -26,6 +26,7 @@ class SatellitesDialog:
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
View 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

File diff suppressed because it is too large Load Diff

View 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

View File

@@ -1,6 +1,6 @@
from app.properties import write_config, Profile, get_default_settings
from app.ui.dialogs import show_dialog, DialogType
from . import Gtk, UI_RESOURCES_PATH
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .main_helper import update_entry_data
def show_settings_dialog(transient, options):
@@ -8,12 +8,18 @@ def show_settings_dialog(transient, options):
class SettingsDialog:
def __init__(self, transient, options):
handlers = {"on_data_dir_field_icon_press": self.on_data_dir_field_icon_press,
"on_picons_dir_field_icon_press": self.on_picons_dir_field_icon_press,
"on_profile_changed": self.on_profile_changed,
"on_reset": self.on_reset}
"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._dialog = builder.get_object("settings_dialog")
@@ -22,17 +28,26 @@ class SettingsDialog:
self._port_field = builder.get_object("port_field")
self._login_field = builder.get_object("login_field")
self._password_field = builder.get_object("password_field")
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._user_bouquet_field = builder.get_object("user_bouquet_field")
self._satellites_xml_field = builder.get_object("satellites_xml_field")
self._data_dir_field = builder.get_object("data_dir_field")
self._picons_field = builder.get_object("picons_field")
self._picons_dir_field = builder.get_object("picons_dir_field")
self._enigma_radio_button = builder.get_object("enigma_radio_button")
self._neutrino_radio_button = builder.get_object("neutrino_radio_button")
self._support_ver5_check_button = builder.get_object("support_ver5_check_button")
self._options = options
self._active_profile = options.get("profile")
self.set_settings()
self._neutrino_radio_button.set_active(Profile(self._active_profile) is Profile.NEUTRINO_MP)
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()
@@ -44,13 +59,15 @@ class SettingsDialog:
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.get(self._options.get("profile")))
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):
self.set_profile(Profile.ENIGMA_2 if self._enigma_radio_button.get_active() else Profile.NEUTRINO_MP)
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
@@ -69,28 +86,44 @@ class SettingsDialog:
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._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._data_dir_field.set_text(options.get("data_dir_path"))
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):
profile = Profile.ENIGMA_2.value if self._enigma_radio_button.get_active() else Profile.NEUTRINO_MP.value
self._active_profile = profile
self._options["profile"] = profile
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
View 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

6
build-deb.sh Normal file → Executable file
View File

@@ -1,11 +1,11 @@
#!/bin/env bash
VER="0.2.0_Pre-alpha"
#!/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
cp -Rv app $DEB_PATH
rsync --exclude=app/ui/lang -arv app $DEB_PATH
cp -Rv start.py $DEB_PATH
cd dist

View File

@@ -1,9 +1,9 @@
Package: DemonEditor
Version: 0.2.0-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

View File

@@ -1,25 +1,36 @@
# DemonEditor
Enigma2 channel and satellites list editor for GNU/Linux.
## Enigma2 channel and satellites list editor for GNU/Linux.
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
Keyboard shortcuts:
Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
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.
Ability to import IPTV into bouquet from m3u files!
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
Tests only in image based on OpenPLi or last BPanther(neutrino) images with GM 990 Spark Reloaded receiver
in my preferred linux distro (Last Linux Mint 18.* - MATE 64-bit)!
Minimum requirements: Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
**Terrestrial and cable channels at the moment are not supported!**
Terrestrial and cable channels at the moment are not supported!

Binary file not shown.

3
po/build.sh Normal file
View File

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

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

Binary file not shown.

506
po/ru/demon-editor.po Normal file
View 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 не найден. Проверьте, что он установлен!"