Compare commits

...

294 Commits
0.4.2 ... 0.4.7

Author SHA1 Message Date
DYefremov
aa4b31edfc copy .mo file 2020-02-27 23:53:28 +03:00
DYefremov
3d627b57a4 German translation update 2020-02-27 23:50:54 +03:00
DYefremov
6b1bec500c upd README 2020-02-24 12:48:17 +03:00
DYefremov
7444db7e21 small fix 2020-02-24 12:17:10 +03:00
DYefremov
8be92a9c7e copy .mo file 2020-02-20 14:20:09 +03:00
DYefremov
7554f40c6a Russian translation update 2020-02-20 14:07:35 +03:00
DYefremov
17ab321e44 gui changes for send to 2020-02-20 11:38:45 +03:00
DYefremov
ac345d4ef3 fix getting sats 2020-02-19 12:04:02 +03:00
DYefremov
e2a56a316d .mo files update 2020-02-17 09:19:39 +03:00
wwns
9b79bf2b81 Polish translation update (#11)
Polish translation update.
2020-02-17 08:54:17 +03:00
DYefremov
ee2a9bda90 added appindicator support 2020-02-15 12:51:16 +03:00
DYefremov
da0c5fa8a6 updated .mo files 2020-02-14 22:50:35 +03:00
wwns
e202ec6abe Polish translation update (#10) 2020-02-14 22:28:15 +03:00
DYefremov
e87be79f42 minor fix 2020-02-13 20:15:18 +03:00
DYefremov
6372ac474c toolbar changes 2020-02-13 01:09:40 +03:00
DYefremov
c6b0f70c8e update of data path 2020-02-12 13:51:40 +03:00
DYefremov
6a921ad394 added Polish selection 2020-02-12 12:39:12 +03:00
DYefremov
4c8743517f copy .mo file 2020-02-11 22:02:20 +03:00
wwns
74ec0fe956 added a Polish translation (#5)
* added a Polish translation

* added a Polish translation

* name change
2020-02-11 21:52:54 +03:00
DYefremov
041f717a01 hotkey refactoring 2020-02-11 13:18:14 +03:00
DYefremov
67dbdb19d7 fix bq deletion 2020-02-10 19:24:48 +03:00
DYefremov
de49179dd2 changing profile on data download 2020-02-10 17:00:46 +03:00
DYefremov
2723d255fe fix profile edit 2020-02-10 14:45:05 +03:00
DYefremov
4515b2538b moved get yt icon 2020-02-07 16:56:01 +03:00
DYefremov
88e3a22cf0 auto rename bouquets with duplicate names 2020-01-31 15:09:56 +03:00
DYefremov
7ac63b81c0 added checking for bouquet names duplicate 2020-01-29 14:50:02 +03:00
DYefremov
234611b686 added controls to the transmitter 2020-01-28 15:08:57 +03:00
DYefremov
fdb2691430 added player requests 2020-01-28 14:51:23 +03:00
DYefremov
d81700c30c minor gui changes 2020-01-24 00:53:42 +03:00
DYefremov
e91c4c33a5 added reset button hiding 2020-01-24 00:04:43 +03:00
DYefremov
40bf54e94f fix size of picons 2020-01-23 23:42:28 +03:00
DYefremov
4a50c36ab4 added remove and download to picons 2020-01-23 00:47:01 +03:00
DYefremov
ea305dadf1 upd. README 2020-01-18 21:49:21 +03:00
DYefremov
136fd118cb minor fixes 2020-01-18 15:28:46 +03:00
DYefremov
7df7e0b630 changed status update 2020-01-17 00:34:18 +03:00
DYefremov
0b4e923037 added callbacks to the player 2020-01-16 14:08:34 +03:00
DYefremov
c2d2361de9 scripts update 2020-01-15 07:26:59 +03:00
DYefremov
cc15594338 added app icon 2020-01-15 07:24:16 +03:00
DYefremov
0f64234312 added stream mode 2020-01-14 18:26:05 +03:00
DYefremov
a1098750da fix data loading if pixbuf error 2020-01-12 00:33:33 +03:00
DYefremov
47c80b2b29 minor fix of status update 2020-01-11 20:40:16 +03:00
DYefremov
dfbf019c64 added token security support 2020-01-11 17:58:50 +03:00
DYefremov
9082d5c96e added last configuration load feature 2020-01-09 13:14:49 +03:00
DYefremov
2e3ec1c99d http api refactoring 2020-01-08 21:33:24 +03:00
DYefremov
024d48b464 set settings type to the profile 2020-01-08 14:50:04 +03:00
DYefremov
0571a29b66 added elems to profiles edit 2020-01-07 12:36:29 +03:00
DYefremov
1d62e2660b added http api compatibility test 2020-01-06 13:17:56 +03:00
DYefremov
bcbe2b3f46 added https support to the settings 2020-01-03 23:26:55 +03:00
DYefremov
d0638f7158 added language selection 2020-01-02 15:47:48 +03:00
DYefremov
614c87cbf3 base implementation of profiles support 2019-12-27 23:05:37 +03:00
DYefremov
5aec42548e settings refactoring 2019-12-22 20:42:29 +03:00
DYefremov
3859c84c0e added exception 2019-12-17 11:59:57 +03:00
DYefremov
77bb4a7fef minor player fix 2019-12-16 15:45:41 +03:00
DYefremov
a8b4047239 player refactoring 2019-12-16 09:57:27 +03:00
DYefremov
a4800ace14 small clean after refactoring of the settings 2019-12-15 19:01:27 +03:00
Víctor Pont
f26f806147 Spanish translation corrections (#3)
* Spanish translation corrections
2019-12-14 14:30:24 +03:00
DYefremov
98d3c04a08 settings refactoring 2019-12-13 13:31:07 +03:00
DYefremov
24311827bf update vlc 2019-12-07 21:06:18 +03:00
DYefremov
17a3ec4fef base impl of send to 2019-12-04 23:06:38 +03:00
DYefremov
30927b2546 prototype of send to for yt links 2019-11-24 21:58:32 +03:00
DYefremov
84a4cef5b5 minor changes for the settings dialog 2019-11-22 09:34:17 +03:00
DYefremov
5fe2559789 added connection status icon 2019-11-21 23:13:06 +03:00
DYefremov
1e35a69539 http api refactoring 2019-11-21 16:59:43 +03:00
DYefremov
16c58907f4 prototype of send to 2019-11-05 23:04:21 +03:00
DYefremov
783e78dc14 added send to and yt_dl settings support 2019-11-04 20:18:24 +03:00
DYefremov
c7e1f05955 added play to the request types 2019-11-03 18:11:49 +03:00
DYefremov
e11f68e3bd added style 2019-10-29 13:39:11 +03:00
DYefremov
27f60bcea2 changed setting service info 2019-10-28 11:03:09 +03:00
DYefremov
0a16265aa2 added current service status info 2019-10-28 00:45:47 +03:00
DYefremov
49d8c0ef92 added prototype of playing current service 2019-10-23 11:51:28 +03:00
DYefremov
998a6eb118 added german translation 2019-10-21 10:47:27 +03:00
DYefremov
f7d75c2404 slight refactoring of stream play 2019-10-20 23:46:25 +03:00
DYefremov
9396ec197d updated version 2019-10-20 23:34:56 +03:00
DYefremov
9ad9de4821 minor gui changes 2019-10-20 23:33:09 +03:00
DYefremov
408a4cfa32 fix of services counter reset 2019-10-14 00:17:06 +03:00
DYefremov
fe3fb1fefe updating of dutch, spanish and portuguese 2019-10-11 15:51:20 +03:00
DYefremov
95b5c7aa74 fix hide info box on new config creation 2019-10-10 12:55:58 +03:00
DYefremov
d2e80c6d44 translation update 2019-10-06 09:50:16 +03:00
DYefremov
57f157ef9b added app info box 2019-10-04 21:31:41 +03:00
DYefremov
f62f104c8d added rewind support to the player 2019-10-02 14:44:58 +03:00
DYefremov
8d6b1303dc slight refactoring of http api 2019-09-28 21:57:41 +03:00
DYefremov
de770169fa changed requests to compressed 2019-09-28 17:44:33 +03:00
DYefremov
7b2e467111 updated yt playlist parser 2019-09-22 16:54:20 +03:00
DYefremov
1c1dff2497 fix sensitivity for iptv elems in neutrino 2019-09-16 17:44:33 +03:00
DYefremov
309f960e1e minor clean 2019-09-10 00:37:48 +03:00
DYefremov
dadb73280c changed callbacks for loading picons 2019-09-10 00:28:38 +03:00
DYefremov
81260211a4 default callback set 2019-09-10 00:24:51 +03:00
DYefremov
d4d1dd397d slight refactoring of picons downloader 2019-09-09 23:48:36 +03:00
DYefremov
fe3e1ef30a fix apply default data for some iptv services 2019-09-08 18:45:12 +03:00
DYefremov
1f46eae6be changed counting of marker number 2019-09-04 10:39:46 +03:00
DYefremov
a398b0c6e9 revert default formats for youtube 2019-08-18 17:02:32 +03:00
DYefremov
272b6af3ad quality selection for a single yt link 2019-08-18 00:06:44 +03:00
DYefremov
d976e02cf6 added quality selection for yt playlist 2019-08-17 22:53:05 +03:00
DYefremov
fb6951896f changed getting yt link 2019-08-13 19:22:08 +03:00
DYefremov
9c62a49968 added min width 2019-08-11 21:24:51 +03:00
DYefremov
99bb508912 minor refactoring of data appending 2019-08-08 21:15:43 +03:00
DYefremov
e382a51f81 optimisation of data load/save 2019-08-01 01:05:30 +03:00
DYefremov
fdd30b2ac9 hiding header items during playback 2019-07-23 10:47:01 +03:00
DYefremov
f2b99f9eea fixed the order of links in the yt dialog 2019-07-19 16:02:00 +03:00
DYefremov
dea5723bb7 reworking of yt dialog 2019-06-30 22:13:26 +03:00
DYefremov
236a7a15d0 added keyboard shortcuts for the yt dialog 2019-06-28 23:25:38 +03:00
DYefremov
cfe281116a added logging for get yt link 2019-06-28 08:58:33 +03:00
DYefremov
984e8ca088 added popup menu for yt dialog 2019-06-28 00:02:34 +03:00
DYefremov
62eae7f029 minor changes in the yt dialog 2019-06-27 22:10:44 +03:00
DYefremov
204962b531 append yt list data 2019-06-26 15:57:22 +03:00
DYefremov
767c06a0ff merged yt dialog 2019-06-26 15:56:23 +03:00
DYefremov
d4ec28e9cd added getting max num of markers 2019-06-26 15:38:34 +03:00
DYefremov
8fee65cabb yt list import dialog skeleton 2019-06-24 00:36:54 +03:00
DYefremov
95069bbf24 refactoring of getting fav id 2019-06-23 23:25:03 +03:00
DYefremov
60f106bc2a added simple parser to handle yt playlists 2019-06-21 14:54:09 +03:00
DYefremov
4becdf1d6e added tooltip text for yt icon 2019-06-19 23:23:32 +03:00
DYefremov
f2f027c6f4 moved youtube logic to the extra module 2019-06-19 22:34:22 +03:00
DYefremov
edeab12b50 skeleton of basic support for youtube links 2019-06-18 18:46:27 +03:00
DYefremov
896aa7f66e added info bar for iptv dialog 2019-06-18 18:26:42 +03:00
DYefremov
6b6220a3ac youtube links detection skeleton 2019-06-15 23:50:42 +03:00
DYefremov
3fa46c6c6c version update 2019-06-15 22:06:41 +03:00
DYefremov
a0b322b188 set text for the question dialog 2019-06-15 21:43:24 +03:00
DYefremov
3550f58603 update spanish, dutch and portuguese 2019-06-11 21:24:06 +03:00
DYefremov
729c85be77 fix type for radio bouquets 2019-06-11 21:03:51 +03:00
DYefremov
f8aee1b807 update russian 2019-06-08 16:04:47 +03:00
DYefremov
75d93f6a19 improved xml download for the epg dialog 2019-06-08 15:45:41 +03:00
DYefremov
4581cc7d4f upd README 2019-06-05 11:53:52 +03:00
DYefremov
2f8ea069e1 bouquet name for xml file when saving from the epg dialog 2019-06-04 13:31:54 +03:00
DYefremov
8afcec6b7e changed date format in the header 2019-06-04 13:06:02 +03:00
DYefremov
b5a9321c5c small refactoring of getting refs from xml 2019-06-04 01:22:26 +03:00
DYefremov
7ee781c39b lazy init of epg data 2019-06-03 15:47:04 +03:00
DYefremov
48f3c1a4d6 added export to m3u for neutrino 2019-05-30 15:56:04 +03:00
DYefremov
717bac6446 fix on open 2019-05-30 12:57:31 +03:00
DYefremov
fe1323f8cf deleted extra dialog 2019-05-30 11:12:22 +03:00
DYefremov
0f30d74edc changing header bar elements 2019-05-29 14:31:44 +03:00
DYefremov
0686c91a5d fix provider name for neutrino 2019-05-29 12:57:03 +03:00
DYefremov
a84090cda7 added support of coupled satellites 2019-05-27 22:17:29 +03:00
DYefremov
291b3aa289 minor appearance changes 2019-05-27 11:02:41 +03:00
DYefremov
dd92ffc9b1 fix getting some transponders 2019-05-27 00:17:38 +03:00
DYefremov
97a8f793c3 lazy loading of satellites list 2019-05-26 22:11:52 +03:00
DYefremov
3ad2e3d6b6 disable cache 2019-05-26 21:50:02 +03:00
DYefremov
7d6763ffb5 fix show iptv services in the info box of import dialog 2019-05-20 20:01:28 +03:00
DYefremov
6582be7a0d added arg for the start script 2019-05-19 14:22:43 +03:00
DYefremov
1e45621bd8 added data recovery if download error 2019-05-19 00:37:07 +03:00
DYefremov
61bcb85bbc global update settings from the download dialog 2019-05-14 22:12:36 +03:00
DYefremov
9eee9ac424 small refactoring of the init of dynamic elems 2019-05-13 14:42:23 +03:00
DYefremov
3f720afedc added command line params support 2019-05-12 16:26:58 +03:00
DYefremov
cd19c5fd9c optional logging 2019-05-12 16:26:19 +03:00
DYefremov
61ca2f3e8b minor changes for the input dialog 2019-05-11 13:27:46 +03:00
DYefremov
75fc7adc88 input dialog refactoring 2019-05-11 00:09:20 +03:00
DYefremov
3678a9d29d satellite dialogs refactoring 2019-05-10 14:42:32 +03:00
DYefremov
a5927dd2b6 service details refactoring 2019-05-10 14:41:33 +03:00
DYefremov
34e0ed4748 setting selected bouquet after rename 2019-05-09 23:51:47 +03:00
DYefremov
d7a214b445 iptv dialogs refactoring 2019-05-09 14:48:29 +03:00
DYefremov
e194827af7 get about dialog 2019-05-09 12:53:11 +03:00
DYefremov
e9e53da5cc dialogs refactoring 2019-05-09 11:11:54 +03:00
DYefremov
822497317d minor changes 2019-05-09 00:01:49 +03:00
DYefremov
c2047bd7b5 question dialog refactoring 2019-05-08 23:35:42 +03:00
DYefremov
3636da60d6 input dialog refactoring 2019-05-08 23:05:32 +03:00
DYefremov
2eebd55b77 updating counter on reset 2019-05-07 22:08:04 +03:00
DYefremov
12f76f8e28 added reset for the epg dialog 2019-05-07 17:22:18 +03:00
DYefremov
28e6cca919 update spanish, dutch and portuguese 2019-05-07 13:25:17 +03:00
DYefremov
9b53538da6 new impl of data mapping for the epg dialog 2019-05-07 00:04:53 +03:00
DYefremov
994541bad5 update russian 2019-05-05 11:49:24 +03:00
DYefremov
3cbb16febe minor gui changes 2019-05-05 11:26:11 +03:00
DYefremov
2b61fa07b9 update russian 2019-05-05 11:08:16 +03:00
DYefremov
406f4bd0f0 little mapping improvements for services with cyrillic names 2019-05-04 23:54:58 +03:00
DYefremov
1ec6b817e9 support of epg.dat download from the receiver 2019-05-04 20:13:57 +03:00
DYefremov
7c55692c99 small decoupling of dialogs 2019-05-04 11:21:20 +03:00
DYefremov
3aa29a788d added groups support by export to m3u 2019-05-01 17:21:51 +03:00
DYefremov
55b0dccc80 added info dialog 2019-05-01 17:19:31 +03:00
DYefremov
edb97cbf8c added keyboard shortcuts for the epg dialog 2019-05-01 13:11:19 +03:00
DYefremov
7620f03e2b added info bars for the epg dialog 2019-04-30 14:17:45 +03:00
DYefremov
cced856297 added base support of xml sources for epg dialog 2019-04-27 19:05:37 +03:00
DYefremov
3bcfd66971 added elements in the epg options widget 2019-04-26 22:07:21 +03:00
DYefremov
e7e7c667e9 added options widget for the epg dialog 2019-04-25 00:18:49 +03:00
DYefremov
6de0bc4201 added popup menus for epg dialog 2019-04-24 21:53:01 +03:00
DYefremov
878520b7f9 epg config dialog skeleton 2019-04-24 20:27:47 +03:00
DYefremov
63ac413982 saving list to xml 2019-04-22 20:25:19 +03:00
DYefremov
171c58c546 epg assignment by drag 2019-04-22 00:12:04 +03:00
DYefremov
6758ae3d16 assign epg data 2019-04-21 21:48:47 +03:00
DYefremov
329513d2a7 epg config dialog skeleton 2019-04-21 01:18:54 +03:00
DYefremov
be195e9001 added epg icon 2019-04-20 20:44:56 +03:00
DYefremov
635a3fb966 added epg skeleton 2019-04-18 23:05:19 +03:00
DYefremov
281f7a28f3 added export to m3u 2019-04-18 21:43:35 +03:00
DYefremov
507f5817c2 update version 2019-04-18 19:12:52 +03:00
DYefremov
d3822474ba small internal refactoring of iptv list config dialog 2019-04-14 20:24:57 +03:00
DYefremov
e1ce9f3006 added non-rec stream types for iptv 2019-04-14 00:03:52 +03:00
DYefremov
c2b0768857 minor cleaning 2019-04-13 15:23:24 +03:00
DYefremov
283d85ef8e fix reading of bouquet names for some configs 2019-04-12 23:29:04 +03:00
DYefremov
f5656d8d5f update spanish, dutch and portuguese 2019-04-08 11:28:49 +03:00
DYefremov
5dd5a09bfc show error message if no item is selected by import 2019-04-05 13:46:37 +03:00
DYefremov
1b5f3372b4 update russian 2019-04-04 21:01:58 +03:00
DYefremov
974e964f42 minor gui changes 2019-04-04 20:38:30 +03:00
DYefremov
8cb6ed02d2 update russian 2019-04-01 10:11:57 +03:00
DYefremov
8bb3b780d1 minor tooltips changes 2019-04-01 10:09:55 +03:00
DYefremov
3000c8830c minor gui changes 2019-03-31 21:51:53 +03:00
DYefremov
ac550e016d update russian 2019-03-31 21:40:55 +03:00
DYefremov
7420751806 Merge remote-tracking branch 'origin/development' into development 2019-03-31 21:10:41 +03:00
DYefremov
f35889e8e4 minor changes in the gui of the settings dialog 2019-03-31 21:10:27 +03:00
DYefremov
857b252f4c upd README 2019-03-28 10:34:13 +03:00
DYefremov
572584a14f fix transponders duplication 2019-03-23 11:16:43 +03:00
DYefremov
7e4ac3e69c fix pls mode 2019-03-22 00:54:44 +03:00
DYefremov
0d73ffa79d show error dialog refactoring 2019-03-19 21:44:05 +03:00
DYefremov
5e2f1ddb84 added extra method for error dialog showing 2019-03-19 00:12:33 +03:00
DYefremov
6c4040901f upd README 2019-03-18 23:37:37 +03:00
DYefremov
103e09b900 added download/upload data using Ctrl + D/U/B shortcuts 2019-03-18 23:04:05 +03:00
DYefremov
8ddc517ab7 added skip message for http test 2019-03-18 23:03:42 +03:00
DYefremov
b26d982db4 added skip message for http test 2019-03-18 22:54:59 +03:00
DYefremov
3733bc395b fix insert stream 2019-03-14 13:43:13 +03:00
DYefremov
26bfbafc0e little cleaning 2019-03-14 12:40:32 +03:00
DYefremov
84d1a18111 added single import for neutrino 2019-03-14 12:37:48 +03:00
DYefremov
1cdacd5276 added support of multistream transponders by update from web 2019-03-12 13:39:30 +03:00
DYefremov
354715558c fix set pls mode for transponder dialog 2019-03-12 10:39:55 +03:00
DYefremov
bca1613bff Added Ctrl + O/Q shortcuts 2019-03-10 18:11:38 +03:00
DYefremov
36b533b890 added double click mode option for bouquet list 2019-03-10 15:33:28 +03:00
DYefremov
2eabccc1a9 fix single import for empty bouquets list 2019-03-06 08:40:38 +03:00
DYefremov
75cd78277e added remove all unused picons 2019-03-03 12:50:40 +03:00
DYefremov
5181b732ed added confirmation dialog before import 2019-02-27 20:12:41 +03:00
DYefremov
513c0e8d3d update dutch, spanish and portuguese 2019-02-26 20:52:01 +03:00
DYefremov
f932feb305 update russian 2019-02-25 23:37:05 +03:00
DYefremov
6a2fda5ec0 changes for single bouquet import 2019-02-25 23:35:50 +03:00
DYefremov
86c30dd2c1 changes for single bouquet import 2019-02-25 23:35:20 +03:00
DYefremov
0ed41c473d minor gui changes 2019-02-24 15:47:19 +03:00
DYefremov
5078a854d2 import single bouquet skeleton 2019-02-23 13:54:00 +03:00
DYefremov
353bf04924 added import elements 2019-02-23 13:53:16 +03:00
DYefremov
474ff8e303 changed data opening from import dialog 2019-02-15 13:04:52 +03:00
DYefremov
5834bd4a0b added selection with space key 2019-02-09 15:25:44 +03:00
DYefremov
81f31e5d8d added space key 2019-02-09 15:16:03 +03:00
DYefremov
267f645c16 little refactoring of working with models 2019-02-09 12:46:06 +03:00
DYefremov
8c10d7d6a5 added columns for bouquets model 2019-02-09 12:43:27 +03:00
DYefremov
bbdb47ee7a enabled import for neutrino 2019-02-09 10:25:49 +03:00
DYefremov
7f6856e6aa import for empty config 2019-02-09 09:55:35 +03:00
DYefremov
3439a3ad0a base implementation of import 2019-02-08 19:11:30 +03:00
DYefremov
ee2e2ac49d added bouquet details for import dialog 2019-02-06 00:19:04 +03:00
DYefremov
a745167fb7 update version 2019-02-05 17:02:23 +03:00
DYefremov
8551bc2459 added import bouquets dialog 2019-02-05 16:58:54 +03:00
DYefremov
38ceb6f65d fix switching(zap) channel 2019-02-03 23:44:53 +03:00
DYefremov
33fe78911a changed download icon 2019-01-31 16:57:42 +03:00
DYefremov
3552415054 changed moving up and down in lists 2019-01-30 14:39:58 +03:00
DYefremov
a2ec6f1e1f small changes of moving from keyboard 2019-01-30 09:10:43 +03:00
DYefremov
5f90d07853 fix markers reading in some configs 2019-01-28 23:47:05 +03:00
DYefremov
d88548eece fix opening some ext configs 2019-01-28 23:10:35 +03:00
DYefremov
9ead5b3918 minor fix 2019-01-28 01:04:11 +03:00
DYefremov
1a376e6922 minor fix 2019-01-28 00:28:46 +03:00
DYefremov
eb29f2e1b2 fix parsing for some m3u 2019-01-27 23:59:06 +03:00
DYefremov
bfaab7b2fb fix get iptv url 2019-01-27 23:28:53 +03:00
DYefremov
3e6a7f8a42 changed parsing for iptv services 2019-01-27 23:20:07 +03:00
DYefremov
aff34d7627 minor gui changes 2019-01-27 09:10:56 +03:00
DYefremov
8ef0a451ff upd README 2019-01-24 10:30:47 +03:00
DYefremov
8678f02fd6 added rate lp setup for terrestrial transponder 2019-01-24 10:04:27 +03:00
DYefremov
a0a64606cd enabled transponder edit for terrestrial services 2019-01-23 16:36:50 +03:00
DYefremov
6574296278 little refactoring 2019-01-23 16:35:19 +03:00
DYefremov
d8414f56ee update data for terrestrial services 2019-01-23 13:14:06 +03:00
DYefremov
bb9499392b enabled transponder edit for cable services 2019-01-23 00:43:02 +03:00
DYefremov
069ea9348f ui changes for terrestrial transponder editing 2019-01-21 18:04:46 +03:00
DYefremov
8c932c8913 ui changes for cable transponder edit 2019-01-20 00:25:02 +03:00
DYefremov
562501dda8 added system for terrestrial services 2019-01-19 23:31:52 +03:00
DYefremov
79583869e4 added portuguese 2019-01-19 11:34:19 +03:00
DYefremov
5c9abcee21 updated spanish and dutch 2019-01-19 11:33:09 +03:00
DYefremov
e8377ed174 skeleton of transponder edit for cable and terrestrial services 2019-01-18 18:35:22 +03:00
DYefremov
16f8df0238 added elems for cable and terrestrial services 2019-01-18 18:26:48 +03:00
DYefremov
dab772e25c changed header menu 2019-01-14 10:37:18 +03:00
DYefremov
37791a5537 keyboard support for backup tool 2019-01-12 20:08:17 +03:00
DYefremov
eb55cc76be completion of the path to backups 2019-01-12 19:17:20 +03:00
DYefremov
f728a01963 update version 2019-01-12 18:12:06 +03:00
DYefremov
8aae503e35 support of setting backup path 2019-01-12 18:10:04 +03:00
DYefremov
fe499d6c94 added extra info to the backup tool 2019-01-12 13:57:40 +03:00
DYefremov
cd2c820324 fix iptv dialog show 2019-01-08 18:06:32 +03:00
DYefremov
b4c5af4c04 minor icons changes 2019-01-06 14:43:07 +03:00
DYefremov
f239957ca2 header bar icons changes 2019-01-06 14:05:03 +03:00
DYefremov
3b835e6f34 updating russian translation 2019-01-05 23:31:16 +03:00
DYefremov
4ab3d2d0e1 updated russian translation 2019-01-05 23:17:06 +03:00
DYefremov
3781686213 revert 2019-01-05 22:58:34 +03:00
DYefremov
0247aeed52 fix user bouquets restore for neutrino 2019-01-05 22:53:51 +03:00
DYefremov
3d34539c16 added backup options 2019-01-03 23:32:28 +03:00
DYefremov
0a06b36f60 setting background color for the services 2018-12-23 16:15:48 +03:00
DYefremov
ea1d536c6c added elems for color changing 2018-12-22 23:37:35 +03:00
DYefremov
5087266693 Merge branch 'master' into experimental 2018-12-22 15:35:32 +03:00
DYefremov
3e617e329c Merge branch 'testing' into experimental 2018-12-22 15:34:49 +03:00
DYefremov
62bcb64640 basic implementation of bouquets restore 2018-12-22 15:26:47 +03:00
DYefremov
300ea3b6d9 base implementation of backup restoring 2018-12-21 00:48:45 +03:00
DYefremov
3ae1901757 simple implementation of backups removing 2018-12-20 22:59:01 +03:00
DYefremov
2b63a59c91 added prototype of backup tool gui 2018-12-20 18:14:19 +03:00
DYefremov
ec69bae2a6 compressing backups to zip 2018-12-19 19:22:07 +03:00
DYefremov
c07e0b606b new setting dialog prototype 2018-12-19 18:21:20 +03:00
DYefremov
61745df2a7 little refactoring 2018-12-19 14:43:43 +03:00
DYefremov
146c59e0db сhanged the style of services highlighting 2018-12-19 14:42:29 +03:00
DYefremov
c7c0a4055b fix getting the maximum marker number, little refactoring 2018-12-18 19:23:44 +03:00
DYefremov
6c7d39889d Column refactoring 2018-12-17 18:31:57 +03:00
DYefremov
9d9626d065 added background for extra names in fav list 2018-12-16 22:44:45 +03:00
DYefremov
9bf7a10bf1 Merge branch 'master' into experimental 2018-12-16 17:50:39 +03:00
DYefremov
9659514feb changed version 2018-12-16 17:49:11 +03:00
DYefremov
f39ddd4315 updating service background after edit 2018-12-16 17:47:55 +03:00
DYefremov
90bd9e0211 added background for new services 2018-12-16 17:28:07 +03:00
DYefremov
b993eba2df Merge remote-tracking branch 'origin/experimental' into experimental 2018-12-11 20:45:55 +03:00
DYefremov
dbab024778 bouquet selection refactoring 2018-12-11 19:09:55 +03:00
DYefremov
7999fc6893 Merge branch 'testing' into experimental 2018-12-11 15:26:46 +03:00
DYefremov
3fc5a3fd68 Added services column data function prototypes 2018-12-11 14:10:44 +03:00
78 changed files with 17639 additions and 5340 deletions

View File

@@ -3,7 +3,7 @@ Version=1.0
Name=DemonEditor
Comment=Channels and satellites list editor for Enigma2
Comment[ru]=Редактор списка каналов и спутников для Enigma2
Icon=accessories-text-editor
Icon=demon-editor
Exec=bash -c 'cd $(dirname %k) && ./start.py'
Terminal=false
Type=Application

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018 Dmitriy Yefremov
Copyright (c) 2018-2019 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,14 +1,28 @@
# DemonEditor
# <img src="app/ui/icons/hicolor/96x96/apps/demon-editor.png" width="32" /> DemonEditor
## Enigma2 channel and satellites list editor for GNU/Linux.
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
### Keyboard shortcuts:
### Main features of the program:
* Editing bouquets, channels, satellites.
* Import function.
* Backup function.
* Extended support of IPTV.
* Support of picons.
* Downloading of picons and updating of satellites (transponders) from the web.
* Import to bouquet(Neutrino WEBTV) from m3u.
* Export of bouquets with IPTV services in m3u.
* Assignment of EPGs from DVB or XML for IPTV services (only Enigma2, experimental).
* Preview (playback) of IPTV or other streams directly from the bouquet list (should be installed [VLC](https://www.videolan.org/vlc/)).
### Keyboard shortcuts:
* **Ctrl + X** - only in bouquet list.
* **Ctrl + C** - only in services list.
Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **Ctrl + Insert** - copies the selected channels from the main list to the the bouquet beginning
or inserts (creates) a new bouquet.
* **Ctrl + BackSpace** - copies the selected channels from the main list to the bouquet end.
* **Ctrl + X** - only in bouquet list. **Ctrl + C** - only in services list.
Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **Ctrl + BackSpace** - copies the selected channels from the main list to the bouquet end.
* **Ctrl + E** - edit.
* **Ctrl + R, F2** - rename.
* **Ctrl + S, T** in Satellites edit tool for create satellite or transponder.
@@ -20,31 +34,33 @@ Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **Space** - select/deselect.
* **Left/Right** - remove selection.
* **Ctrl + Up, Down, PageUp, PageDown, Home, End** - move selected items in the list.
### 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.16 with PyGObject bindings.
* **Ctrl + O** - (re)load user data from current dir.
* **Ctrl + D** - load data from receiver.
* **Ctrl + U/B** upload data/bouquets to receiver.
* **Ctrl + F** - show/hide search bar.
* **Ctrl + Shift + F** - show/hide filter bar.
### Launching
For multiple mouse selection (including Drag and Drop), press and hold the **Ctrl** key!
### Minimum requirements:
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings, python3-requests.
### Launching:
To start the program, in most cases it is enough to download the archive, unpack and run it by
double clicking on DemonEditor.desktop in the root directory, or launching from the console
with the command: ```python3 ./start.py```
with the command: ```./start.py```
Extra folders can be deleted, excluding the *app* folder and root files like *DemonEditor.desktop* and *start.py*!
### Note.
To create a simple **debian package**, you can use the *build-deb.sh.*
### Note:
To create a simple **debian package**, you can use the *build-deb.sh.*
Users of **LTS** versions of [Ubuntu](https://ubuntu.com/) or those based on them can use [PPA](https://launchpad.net/~dmitriy-yefremov/+archive/ubuntu/demon-editor) repository.
Tests only with openATV image and Formuler F1 receiver in my preferred Linux distros
(latest Linux Mint 18.* and 19 MATE 64-bit)!
The program is tested only with [openATV](https://www.opena.tv/) image and **Formuler F1** receiver in my favourite Linux distributions
(the latest versions of [Linux Mint](https://linuxmint.com/) 18.* and 19* MATE 64-bit)!
**Terrestrial(DVB-T/T2) and cable channels are supported(Enigma2 only) with limitation!**
Main supported **lamedb** format is version **4**. Versions **3** and **5** has only experimental support!
For version **3** is only read mode available. When saving, version **4** format is used instead!
### Important:
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2!
Main supported **lamedb** format is version **4**. Versions **3** and **5** has only **experimental** support!
For version **3** is only read mode available. When saving, version **4** format is used instead!

View File

@@ -7,11 +7,19 @@ from gi.repository import GLib
_LOG_FILE = "demon-editor.log"
_DATE_FORMAT = "%d-%m-%y %H:%M:%S"
_LOGGER_NAME = "main_logger"
logging.Logger(_LOGGER_NAME)
logging.basicConfig(level=logging.INFO,
filename=_LOG_FILE,
format="%(asctime)s %(message)s",
datefmt=_DATE_FORMAT)
_USE_LOG = False
def init_logger():
global _USE_LOG
_USE_LOG = True
logging.Logger(_LOGGER_NAME)
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(message)s",
datefmt=_DATE_FORMAT,
handlers=[logging.FileHandler(_LOG_FILE),
logging.StreamHandler()])
log("Logging is enabled.", level=logging.INFO)
def get_logger():
@@ -19,7 +27,7 @@ def get_logger():
def log(message, level=logging.ERROR):
get_logger().log(level, message)
get_logger().log(level, message) if _USE_LOG else print(message)
def run_idle(func):

View File

@@ -1,17 +1,20 @@
import json
import os
import re
import socket
import time
import urllib
import xml.etree.ElementTree as ETree
from enum import Enum
from ftplib import FTP, error_perm
from http.client import RemoteDisconnected
from telnetlib import Telnet
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import urlopen, HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener, install_opener
from urllib.request import urlopen, HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, \
build_opener, install_opener, Request
from app.commons import log
from app.properties import Profile
from app.commons import log, run_task
from app.settings import SettingsType
_BQ_FILES_LIST = ("tv", "radio", # enigma 2
"myservices.xml", "bouquets.xml", "ubouquets.xml") # neutrino
@@ -20,6 +23,7 @@ _DATA_FILES_LIST = ("lamedb", "lamedb5", "services.xml", "blacklist", "whitelist
_SAT_XML_FILE = "satellites.xml"
_WEBTV_XML_FILE = "webtv.xml"
_PICONS_SUF = (".jpg", ".png")
class DownloadType(Enum):
@@ -28,29 +32,45 @@ class DownloadType(Enum):
SATELLITES = 2
PICONS = 3
WEBTV = 4
EPG = 5
class HttpRequestType(Enum):
ZAP = "zap?sRef="
INFO = "about"
SIGNAL = "tunersignal"
STREAM = "streamcurrentm3u"
SIGNAL = "signal"
STREAM = "stream.m3u?ref="
STREAM_CURRENT = "streamcurrent.m3u"
CURRENT = "getcurrent"
TEST = None
TOKEN = "session"
PLAY = "mediaplayerplay?file="
PLAYER_LIST = "mediaplayerlist?path=playlist"
PLAYER_PLAY = "mediaplayercmd?command=play"
PLAYER_NEXT = "mediaplayercmd?command=next"
PLAYER_PREV = "mediaplayercmd?command=previous"
PLAYER_STOP = "mediaplayercmd?command=stop"
PLAYER_REMOVE = "mediaplayerremove?file="
class TestException(Exception):
pass
def download_data(*, properties, download_type=DownloadType.ALL, callback=None):
with FTP(host=properties["host"], user=properties["user"], passwd=properties["password"]) as ftp:
class HttpApiException(Exception):
pass
def download_data(*, settings, download_type=DownloadType.ALL, callback=print):
with FTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
ftp.encoding = "utf-8"
callback("FTP OK.\n")
save_path = properties["data_dir_path"]
save_path = settings.data_local_path
os.makedirs(os.path.dirname(save_path), exist_ok=True)
files = []
# bouquets section
# bouquets
if download_type is DownloadType.ALL or download_type is DownloadType.BOUQUETS:
ftp.cwd(properties["services_path"])
ftp.cwd(settings.services_path)
ftp.dir(files.append)
file_list = _BQ_FILES_LIST + _DATA_FILES_LIST if download_type is DownloadType.ALL else _BQ_FILES_LIST
for file in files:
@@ -58,9 +78,9 @@ def download_data(*, properties, download_type=DownloadType.ALL, callback=None):
if name.endswith(file_list):
name = name.split()[-1]
download_file(ftp, name, save_path, callback)
# satellites.xml and webtv section
# satellites.xml and webtv
if download_type in (DownloadType.ALL, DownloadType.SATELLITES, DownloadType.WEBTV):
ftp.cwd(properties["satellites_xml_path"])
ftp.cwd(settings.satellites_xml_path)
files.clear()
ftp.dir(files.append)
@@ -71,20 +91,40 @@ def download_data(*, properties, download_type=DownloadType.ALL, callback=None):
if download_type in (DownloadType.ALL, DownloadType.WEBTV) and name.endswith(_WEBTV_XML_FILE):
download_file(ftp, _WEBTV_XML_FILE, save_path, callback)
if callback is not None:
callback("\nDone.\n")
if download_type is DownloadType.PICONS:
picons_path = settings.picons_local_path
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
download_picons(ftp, settings.picons_path, picons_path, callback)
# epg.dat
if download_type is DownloadType.EPG:
stb_path = settings.services_path
epg_options = settings.epg_options
if epg_options:
stb_path = epg_options.epg_dat_stb_path or stb_path
save_path = epg_options.epg_dat_path or save_path
ftp.cwd(stb_path)
ftp.dir(files.append)
for file in files:
name = str(file).strip()
if name.endswith("epg.dat"):
name = name.split()[-1]
download_file(ftp, name, save_path, callback)
callback("\nDone.\n")
def upload_data(*, properties, download_type=DownloadType.ALL, remove_unused=False, profile=Profile.ENIGMA_2,
callback=None, done_callback=None, use_http=False):
data_path = properties["data_dir_path"]
host = properties["host"]
base_url = "http://{}:{}/api/".format(host, properties.get("http_port", "80"))
def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False,
callback=print, done_callback=None, use_http=False):
s_type = settings.setting_type
data_path = settings.data_local_path
host = settings.host
base_url = "http{}://{}:{}".format("s" if settings.http_use_ssl else "", host, settings.http_port)
url = "{}/web/".format(base_url)
tn, ht = None, None # telnet, http
try:
if profile is Profile.ENIGMA_2 and use_http:
ht = http(properties.get("http_user", ""), properties.get("http_password", ""), base_url, callback)
if s_type is SettingsType.ENIGMA_2 and use_http:
ht = http(settings.http_user, settings.http_password, base_url, callback, settings.http_use_ssl)
next(ht)
message = ""
if download_type is DownloadType.BOUQUETS:
@@ -95,32 +135,32 @@ def upload_data(*, properties, download_type=DownloadType.ALL, remove_unused=Fal
message = "Satellites.xml file will be updated!"
params = urlencode({"text": message, "type": 2, "timeout": 5})
url = base_url + "message?{}".format(params)
ht.send(url)
ht.send((url + "message?{}".format(params), "Sending info message... "))
if download_type is DownloadType.ALL:
time.sleep(5)
ht.send(base_url + "/powerstate?newstate=0")
ht.send((url + "powerstate?newstate=0", "Toggle Standby "))
time.sleep(2)
else:
# telnet
tn = telnet(host=host, user=properties.get("telnet_user", "root"),
password=properties.get("telnet_password", ""),
timeout=properties.get("telnet_timeout", 5))
tn = telnet(host=host,
user=settings.telnet_user,
password=settings.telnet_password,
timeout=settings.telnet_timeout)
next(tn)
# terminate enigma or neutrino
tn.send("init 4")
with FTP(host=host, user=properties["user"], passwd=properties["password"]) as ftp:
with FTP(host=host, user=settings.user, passwd=settings.password) as ftp:
ftp.encoding = "utf-8"
callback("FTP OK.\n")
sat_xml_path = properties["satellites_xml_path"]
services_path = properties["services_path"]
sat_xml_path = settings.satellites_xml_path
services_path = settings.services_path
if download_type is DownloadType.SATELLITES:
upload_xml(ftp, data_path, sat_xml_path, _SAT_XML_FILE, callback)
if profile is Profile.NEUTRINO_MP and download_type is DownloadType.WEBTV:
if s_type is SettingsType.NEUTRINO_MP and download_type is DownloadType.WEBTV:
upload_xml(ftp, data_path, sat_xml_path, _WEBTV_XML_FILE, callback)
if download_type is DownloadType.BOUQUETS:
@@ -129,7 +169,7 @@ def upload_data(*, properties, download_type=DownloadType.ALL, remove_unused=Fal
if download_type is DownloadType.ALL:
upload_xml(ftp, data_path, sat_xml_path, _SAT_XML_FILE, callback)
if profile is Profile.NEUTRINO_MP:
if s_type is SettingsType.NEUTRINO_MP:
upload_xml(ftp, data_path, sat_xml_path, _WEBTV_XML_FILE, callback)
ftp.cwd(services_path)
@@ -137,17 +177,17 @@ def upload_data(*, properties, download_type=DownloadType.ALL, remove_unused=Fal
upload_files(ftp, data_path, _DATA_FILES_LIST, callback)
if download_type is DownloadType.PICONS:
upload_picons(ftp, properties.get("picons_dir_path"), properties.get("picons_path"))
upload_picons(ftp, settings.picons_local_path, settings.picons_path, callback)
if tn and not use_http:
# resume enigma or restart neutrino
tn.send("init 3" if profile is Profile.ENIGMA_2 else "init 6")
tn.send("init 3" if s_type is SettingsType.ENIGMA_2 else "init 6")
elif ht and use_http:
if download_type is DownloadType.BOUQUETS:
ht.send(base_url + "/servicelistreload?mode=2")
ht.send((url + "servicelistreload?mode=2", "Reloading Userbouquets."))
elif download_type is DownloadType.ALL:
ht.send(base_url + "/servicelistreload?mode=0")
ht.send(base_url + "/powerstate?newstate=4")
ht.send((url + "servicelistreload?mode=0", "Reloading lamedb and Userbouquets."))
ht.send((url + "powerstate?newstate=4", "Wakeup from Standby."))
if done_callback is not None:
done_callback()
@@ -188,24 +228,64 @@ def upload_xml(ftp, data_path, xml_path, xml_file, callback):
send_file(xml_file, data_path, ftp, callback)
def upload_picons(ftp, src, dest):
# ***************** Picons *******************#
def upload_picons(ftp, src, dest, callback):
try:
ftp.cwd(dest)
except error_perm as e:
if str(e).startswith("550"):
ftp.mkd(dest) # if not exist
ftp.cwd(dest)
delete_picons(ftp, callback)
for file_name in os.listdir(src):
if file_name.endswith(_PICONS_SUF):
send_file(file_name, src, ftp, callback)
def download_picons(ftp, src, dest, callback):
try:
ftp.cwd(src)
except error_perm as e:
callback(str(e))
return
files = []
ftp.dir(files.append)
picons_suf = (".jpg", ".png")
for file in files:
name = str(file).strip()
if name.endswith(picons_suf):
if name.endswith(_PICONS_SUF):
name = name.split()[-1]
ftp.delete(name)
for file_name in os.listdir(src):
if file_name.endswith(picons_suf):
send_file(file_name, src, ftp)
download_file(ftp, name, dest, callback)
def delete_picons(ftp, callback, dest=None):
if dest:
try:
ftp.cwd(dest)
except error_perm as e:
callback(str(e))
return
files = []
ftp.dir(files.append)
for file in files:
name = str(file).strip()
if name.endswith(_PICONS_SUF):
name = name.split()[-1]
callback("Delete file: {}. Status: {}\n".format(name, ftp.delete(name)))
def remove_picons(*, settings, callback, done_callback=None):
with FTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
ftp.encoding = "utf-8"
callback("FTP OK.\n")
delete_picons(ftp, callback, settings.picons_path)
if done_callback:
done_callback()
def download_file(ftp, name, save_path, callback):
@@ -219,14 +299,14 @@ def send_file(file_name, path, ftp, callback):
callback("Uploading file: {}. Status: {}\n".format(file_name, str(ftp.storbinary("STOR " + file_name, f))))
def http(user, password, url, callback):
init_auth(user, password, url)
def http(user, password, url, callback, use_ssl=False):
init_auth(user, password, url, use_ssl)
data = get_post_data(url, password, url)
while True:
url = yield
with urlopen(url, timeout=5) as f:
msg = json.loads(f.read().decode("utf-8")).get("message", None)
if msg:
callback("HTTP: {}\n".format(msg))
url, message = yield
resp = get_response(HttpRequestType.TEST, url, data).get("e2statetext", None)
callback("HTTP: {} {}\n".format(message, "Successful." if resp and message else ""))
def telnet(host, port=23, user="", password="", timeout=5):
@@ -254,36 +334,112 @@ def telnet(host, port=23, user="", password="", timeout=5):
yield
# ***************** http api *******************#
# ***************** HTTP API *******************#
def http_request(host, port, user, password):
base_url = "http://{}:{}/api/".format(host, port)
init_auth(user, password, base_url)
while True:
req_type, ref = yield
url = base_url
if req_type is HttpRequestType.ZAP:
url = base_url + "zap?sRef={}".format(urllib.parse.quote(ref))
elif req_type is HttpRequestType.INFO:
url = base_url + HttpRequestType.INFO.value
elif req_type is HttpRequestType.SIGNAL:
url = base_url + HttpRequestType.SIGNAL.value
elif req_type is HttpRequestType.STREAM:
url = base_url + HttpRequestType.STREAM.value
class HttpAPI:
__MAX_WORKERS = 4
try:
with urlopen(url, timeout=5) as f:
if req_type is HttpRequestType.STREAM:
yield f.read().decode("utf-8")
else:
yield json.loads(f.read().decode("utf-8"))
except (URLError, HTTPError):
yield None
def __init__(self, settings):
self._settings = settings
self._shutdown = False
self._session_id = 0
self._base_url = None
self._data = None
self.init()
from concurrent.futures import ThreadPoolExecutor as PoolExecutor
self._executor = PoolExecutor(max_workers=self.__MAX_WORKERS)
def send(self, req_type, ref, callback=print, ref_prefix=""):
if self._shutdown:
return
url = self._base_url + req_type.value
if req_type is HttpRequestType.ZAP or req_type is HttpRequestType.STREAM:
url += urllib.parse.quote(ref)
elif req_type is HttpRequestType.PLAY or req_type is HttpRequestType.PLAYER_REMOVE:
url += "{}{}".format(ref_prefix, urllib.parse.quote(ref).replace("%3A", "%253A"))
future = self._executor.submit(get_response, req_type, url, self._data)
future.add_done_callback(lambda f: callback(f.result()))
@run_task
def init(self):
user, password = self._settings.http_user, self._settings.http_password
use_ssl = self._settings.http_use_ssl
url = "http{}://{}:{}".format("s" if use_ssl else "", self._settings.host, self._settings.http_port)
self._base_url = "{}/web/".format(url)
init_auth(user, password, url, use_ssl)
url = "{}/web/{}".format(url, HttpRequestType.TOKEN.value)
s_id = get_session_id(user, password, url)
if s_id != "0":
self._data = urllib.parse.urlencode({"user": user, "password": password, "sessionid": s_id}).encode("utf-8")
@run_task
def close(self):
self._shutdown = True
self._executor.shutdown()
def get_response(req_type, url, data=None):
try:
with urlopen(Request(url, data=data), timeout=10) as f:
if req_type is HttpRequestType.STREAM or req_type is HttpRequestType.STREAM_CURRENT:
return f.read().decode("utf-8")
elif req_type is HttpRequestType.CURRENT:
for el in ETree.fromstring(f.read().decode("utf-8")).iter("e2event"):
return {el.tag: el.text for el in el.iter()} # return first[current] event from the list
elif req_type is HttpRequestType.PLAYER_LIST:
return [{el.tag: el.text for el in el.iter()} for el in
ETree.fromstring(f.read().decode("utf-8")).iter("e2file")]
else:
return {el.tag: el.text for el in ETree.fromstring(f.read().decode("utf-8")).iter()}
except HTTPError as e:
if req_type is HttpRequestType.TEST:
raise e
return {"error_code": e.code}
except (URLError, RemoteDisconnected, ConnectionResetError) as e:
if req_type is HttpRequestType.TEST:
raise e
except ETree.ParseError as e:
log("Parsing response error: {}".format(e))
return {"error_code": -1}
def init_auth(user, password, url, use_ssl=False):
""" Init authentication """
pass_mgr = HTTPPasswordMgrWithDefaultRealm()
pass_mgr.add_password(None, url, user, password)
auth_handler = HTTPBasicAuthHandler(pass_mgr)
if use_ssl:
import ssl
from urllib.request import HTTPSHandler
opener = build_opener(auth_handler, HTTPSHandler(context=ssl._create_unverified_context()))
else:
opener = build_opener(auth_handler)
install_opener(opener)
def get_session_id(user, password, url):
data = urllib.parse.urlencode(dict(user=user, password=password)).encode("utf-8")
return get_response(HttpRequestType.TOKEN, url, data=data).get("e2sessionid", "0")
def get_post_data(base_url, password, user):
s_id = get_session_id(user, password, "{}/web/{}".format(base_url, HttpRequestType.TOKEN.value))
data = None
if s_id != "0":
data = urllib.parse.urlencode({"user": user, "password": password, "sessionid": s_id}).encode("utf-8")
return data
# ***************** Connections testing *******************#
def test_ftp(host, port, user, password, timeout=5):
try:
with FTP(host=host, user=user, passwd=password, timeout=timeout) as ftp:
@@ -292,35 +448,30 @@ def test_ftp(host, port, user, password, timeout=5):
raise TestException(e)
def test_http(host, port, user, password, timeout=5):
def test_http(host, port, user, password, timeout=5, use_ssl=False, skip_message=False):
params = urlencode({"text": "Connection test", "type": 2, "timeout": timeout})
params = "statusinfo" if skip_message else "message?{}".format(params)
base_url = "http{}://{}:{}".format("s" if use_ssl else "", host, port)
# authentication
init_auth(user, password, base_url, use_ssl)
data = get_post_data(base_url, password, user)
try:
params = urlencode({"text": "Connection test", "type": 2, "timeout": timeout})
url = "http://{}:{}/api/message?{}".format(host, port, params)
# authentication
init_auth(user, password, url)
with urlopen(url, timeout=5) as f:
return json.loads(f.read().decode("utf-8")).get("message", "")
except (URLError, HTTPError) as e:
return get_response(HttpRequestType.TEST, "{}/web/{}".format(base_url, params), data).get("e2statetext", "")
except (RemoteDisconnected, URLError, HTTPError) as e:
raise TestException(e)
def init_auth(user, password, url):
""" Init authentication """
pass_mgr = HTTPPasswordMgrWithDefaultRealm()
pass_mgr.add_password(None, url, user, password)
auth_handler = HTTPBasicAuthHandler(pass_mgr)
opener = build_opener(auth_handler)
install_opener(opener)
def test_telnet(host, port, user, password, timeout=5):
try:
gen = telnet_test(host, port, user, password, timeout)
res = next(gen)
print(res)
res = next(gen)
return res
msg = str(res, encoding="utf8").strip()
log(msg)
next(gen)
if re.search("password", msg, re.IGNORECASE):
raise TestException(msg)
return msg
except (socket.timeout, OSError) as e:
raise TestException(e)
@@ -329,14 +480,14 @@ def telnet_test(host, port, user, password, timeout):
tn = Telnet(host=host, port=port, timeout=timeout)
time.sleep(1)
tn.read_until(b"login: ", timeout=2)
tn.write(user.encode("utf-8") + b"\n")
tn.write(user.encode("utf-8") + b"\r")
time.sleep(timeout)
tn.read_until(b"Password: ", timeout=2)
tn.write(password.encode("utf-8") + b"\n")
tn.write(password.encode("utf-8") + b"\r")
time.sleep(timeout)
yield tn.read_very_eager()
tn.close()
yield "Done"
yield
if __name__ == "__main__":

View File

@@ -1,5 +1,5 @@
from app.commons import run_task
from app.properties import Profile
from app.settings import SettingsType
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
@@ -10,33 +10,33 @@ 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, format_version):
if profile is Profile.ENIGMA_2:
def get_services(data_path, s_type, format_version):
if s_type is SettingsType.ENIGMA_2:
return get_enigma_services(data_path, format_version)
elif profile is Profile.NEUTRINO_MP:
elif s_type is SettingsType.NEUTRINO_MP:
return get_neutrino_services(data_path)
@run_task
def write_services(path, channels, profile, format_version):
if profile is Profile.ENIGMA_2:
def write_services(path, channels, s_type, format_version):
if s_type is SettingsType.ENIGMA_2:
write_enigma_services(path, channels, format_version)
elif profile is Profile.NEUTRINO_MP:
elif s_type is SettingsType.NEUTRINO_MP:
write_neutrino_services(path, channels)
def get_bouquets(path, profile):
if profile is Profile.ENIGMA_2:
def get_bouquets(path, s_type):
if s_type is SettingsType.ENIGMA_2:
return get_enigma_bouquets(path)
elif profile is Profile.NEUTRINO_MP:
elif s_type is SettingsType.NEUTRINO_MP:
return get_neutrino_bouquets(path)
@run_task
def write_bouquets(path, bouquets, profile):
if profile is Profile.ENIGMA_2:
def write_bouquets(path, bouquets, s_type):
if s_type is SettingsType.ENIGMA_2:
write_enigma_bouquets(path, bouquets)
elif profile is Profile.NEUTRINO_MP:
elif s_type is SettingsType.NEUTRINO_MP:
write_neutrino_bouquets(path, bouquets)

View File

@@ -30,7 +30,7 @@ Transponder = namedtuple("Transponder", ["frequency", "symbol_rate", "polarizati
class TrType(Enum):
""" Transponders type """
Satellite = "s"
Terestrial = "t"
Terrestrial = "t"
Cable = "c"
@@ -98,6 +98,12 @@ class Pilot(Enum):
Auto = "2"
class SystemCable(Enum):
""" System of cable service """
ANNEX_A = "0"
ANNEX_C = "1"
ROLL_OFF = {"0": "35%", "1": "25%", "2": "20%", "3": "Auto"}
POLARIZATION = {"0": "H", "1": "V", "2": "L", "3": "R"}
@@ -110,7 +116,7 @@ FEC = {"0": "Auto", "1": "1/2", "2": "2/3", "3": "3/4", "4": "5/6", "5": "7/8",
"25": "3/5", "26": "4/5", "27": "9/10", "28": "Auto"}
FEC_DEFAULT = {"0": "Auto", "1": "1/2", "2": "2/3", "3": "3/4", "4": "5/6", "5": "7/8", "6": "8/9", "7": "3/5",
"8": "4/5", "9": "9/10"}
"8": "4/5", "9": "9/10", "10": "6/7", "15": "None"}
SYSTEM = {"0": "DVB-S", "1": "DVB-S2"}
@@ -119,6 +125,26 @@ MODULATION = {"0": "Auto", "1": "QPSK", "2": "8PSK", "4": "16APSK", "5": "32APSK
SERVICE_TYPE = {"-2": "Data", "1": "TV", "2": "Radio", "3": "Data", "10": "Radio", "22": "TV (H264)",
"25": "TV (HD)", "31": "TV (UHD)"}
# Terrestrial
BANDWIDTH = {"0": "8MHz", "1": "7MHz", "2": "6MHz", "3": "Auto", "4": "5MHz", "5": "1/712MHz", "6": "10MHz"}
T_MODULATION = {"0": "QPSK", "1": "QAM16", "2": "QAM64", "3": "Auto", "4": "QAM256"}
TRANSMISSION_MODE = {"0": "2k", "1": "8k", "2": "Auto", "3": "4k", "4": "1k", "5": "16k", "6": "32k"}
GUARD_INTERVAL = {"0": "1/32", "1": "1/16", "2": "1/8", "3": "1/4", "4": "Auto", "5": "1/128", "6": "19/128",
"7": "19/256"}
HIERARCHY = {"0": "None", "1": "1", "2": "2", "3": "4", "4": "Auto"}
T_FEC = {"0": "1/2", "1": "2/3", "2": "3/4", "3": "5/6", "4": "7/8", "5": "Auto", "6": "6/7", "7": "8/9"}
T_SYSTEM = {"0": "DVB-T", "1": "DVB-T2", "-1": "DVB-T/T2"}
# Cable
C_MODULATION = {"0": "Auto", "1": "QAM16", "2": "QAM32", "3": "QAM64", "4": "QAM128", "5": "QAM256"}
# CAS
CAS = {"C:2600": "BISS", "C:0b00": "Conax", "C:0b01": "Conax", "C:0b02": "Conax", "C:0baa": "Conax", "C:0602": "Irdeto",
"C:0604": "Irdeto", "C:0606": "Irdeto", "C:0608": "Irdeto", "C:0622": "Irdeto", "C:0626": "Irdeto",
"C:0664": "Irdeto", "C:0614": "Irdeto", "C:0692": "Irdeto", "C:1801": "Nagravision", "C:0500": "Viaccess",

View File

@@ -1,6 +1,8 @@
""" Module for parsing bouquets """
import re
from collections import Counter
from app.commons import log
from app.eparser.ecommons import BqServiceType, BouquetService, Bouquets, Bouquet, BqType
_TV_ROOT_FILE_NAME = "bouquets.tv"
@@ -14,9 +16,10 @@ def get_bouquets(path):
def write_bouquets(path, bouquets):
srv_line = '#SERVICE 1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
srv_line = '#SERVICE 1:7:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
line = []
pattern = re.compile("[^\w_()]+")
pattern = re.compile("[^\\w_()]+")
current_marker = [0]
for bqs in bouquets:
line.clear()
@@ -28,23 +31,29 @@ def write_bouquets(path, bouquets):
bq_name = _DEFAULT_BOUQUET_NAME
else:
bq_name = re.sub(pattern, "_", bq.name)
line.append(srv_line.format(bq_name, bq.type))
write_bouquet(path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services)
line.append(srv_line.format(2 if bq.type == BqType.RADIO.value else 1, bq_name, bq.type))
write_bouquet(path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services, current_marker)
with open(path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
file.writelines(line)
def write_bouquet(path, name, channels):
def write_bouquet(path, name, services, current_marker):
bouquet = ["#NAME {}\n".format(name)]
marker = "#SERVICE 1:64:{:X}:0:0:0:0:0:0:0::{}\n"
for ch in channels:
if ch.service_type == BqServiceType.IPTV.name or ch.service_type == BqServiceType.MARKER.name:
bouquet.append("#SERVICE {}\n".format(ch.fav_id.strip()))
for srv in services:
if srv.service_type == BqServiceType.IPTV.name:
bouquet.append("#SERVICE {}\n".format(srv.fav_id.strip()))
elif srv.service_type == BqServiceType.MARKER.name:
m_data = srv.fav_id.strip().split(":")
m_data[2] = current_marker[0]
current_marker[0] += 1
bouquet.append(marker.format(m_data[2], m_data[-1]))
else:
data = to_bouquet_id(ch)
if ch.service:
bouquet.append("#SERVICE {}:{}\n#DESCRIPTION {}\n".format(data, ch.service, ch.service))
data = to_bouquet_id(srv)
if srv.service:
bouquet.append("#SERVICE {}:{}\n#DESCRIPTION {}\n".format(data, srv.service, srv.service))
else:
bouquet.append("#SERVICE {}\n".format(data))
@@ -52,13 +61,13 @@ def write_bouquet(path, name, channels):
file.writelines(bouquet)
def to_bouquet_id(ch):
def to_bouquet_id(srv):
""" Creates bouquet channel id """
data_type = ch.data_id
data_type = srv.data_id
if data_type and len(data_type) > 4:
data_type = int(ch.data_id.split(":")[4])
data_type = int(srv.data_id.split(":")[4])
return "{}:0:{:X}:{}:0:0:0:".format(1, data_type, ch.fav_id)
return "{}:0:{:X}:{}:0:0:0:".format(1, data_type, srv.fav_id)
def get_bouquet(path, name, bq_type):
@@ -70,15 +79,17 @@ def get_bouquet(path, name, bq_type):
for ch in srvs[1:]:
ch_data = ch.strip().split(":")
if ch_data[1] == "64":
services.append(BouquetService(ch_data[-1].split("\n")[0], BqServiceType.MARKER, ch, ch_data[2]))
marker_data = ch.split("#DESCRIPTION", 1)
services.append(BouquetService(marker_data[1].strip(), BqServiceType.MARKER, ch, ch_data[2]))
elif "http" in ch:
services.append(BouquetService(ch_data[-1].split("\n")[0], BqServiceType.IPTV, ch, 0))
stream_data = ch.split("#DESCRIPTION", 1)
services.append(BouquetService(stream_data[-1].strip(":").strip(), BqServiceType.IPTV, ch, 0))
else:
fav_id = "{}:{}:{}:{}".format(ch_data[3], ch_data[4], ch_data[5], ch_data[6])
name = None
if len(ch_data) == 12:
name, desc = str(ch_data[-1]).split("\n#DESCRIPTION")
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id, 0))
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id.upper(), 0))
return srvs[0].lstrip("#NAME").strip(), services
@@ -88,18 +99,38 @@ def parse_bouquets(path, bq_name, bq_type):
lines = file.readlines()
bouquets = None
nm_sep = "#NAME"
bq_pattern = re.compile(".*userbouquet\\.+(.*)\\.+[tv|radio].*")
b_names = set()
real_b_names = Counter()
for line in lines:
if nm_sep in line:
_, _, name = line.partition(nm_sep)
bouquets = Bouquets(name.strip(), bq_type, [])
if bouquets and "#SERVICE" in line:
b_name, services = get_bouquet(path, line.split(".")[1], bq_type)
bouquets[2].append(Bouquet(name=b_name,
type=bq_type,
services=services,
locked=None,
hidden=None))
name = re.match(bq_pattern, line)
if name:
b_name = name.group(1)
if b_name in b_names:
raise ValueError("The list of bouquets contains duplicate [{}] names!".format(b_name))
else:
b_names.add(b_name)
rb_name, services = get_bouquet(path, b_name, bq_type)
if rb_name in real_b_names:
log("Bouquet file 'userbouquet.{}.{}' has duplicate name: {}".format(b_name, bq_type, rb_name))
real_b_names[rb_name] += 1
rb_name = "{} {}".format(rb_name, real_b_names[rb_name])
else:
real_b_names[rb_name] = 0
bouquets[2].append(Bouquet(name=rb_name,
type=bq_type,
services=services,
locked=None,
hidden=None))
else:
raise ValueError("No bouquet name found for: {}".format(line))
return bouquets

View File

@@ -4,7 +4,7 @@ import re
from app.commons import log
from app.ui.uicommons import CODED_ICON, LOCKED_ICON, HIDE_ICON
from .blacklist import get_blacklist
from ..ecommons import Service, POLARIZATION, FEC, SERVICE_TYPE, Flag
from ..ecommons import Service, POLARIZATION, FEC, SERVICE_TYPE, Flag, T_FEC, TrType, FEC_DEFAULT, T_SYSTEM
_HEADER = "eDVB services /{}/"
_SEP = ":" # separator
@@ -170,15 +170,15 @@ def parse_transponders(arg):
def parse_services(services, transponders, path):
""" Parsing channels """
channels = []
""" Parsing services """
services_list = []
blacklist = str(get_blacklist(path))
srv = split(services, 3)
if srv[0][0] == "": # remove first empty element
srv.remove(srv[0])
srvs = split(services, 3)
if srvs[0][0] == "": # remove first empty element
srvs.remove(srvs[0])
for ch in srv:
data_id = str(ch[0]).lower() # lower is for lamedb ver.3
for srv in srvs:
data_id = str(srv[0]).lower() # lower is for lamedb ver.3
data = data_id.split(_SEP)
sp = "0"
tid = data[2]
@@ -209,7 +209,7 @@ def parse_services(services, transponders, path):
fav_id = "{}:{}:{}:{}".format(ssid, tid, nid, onid)
picon_id = "1_0_{:X}_{}_{}_{}_{}_0_0_0.png".format(srv_type, ssid, tid, nid, onid)
all_flags = ch[2].split(",")
all_flags = srv[2].split(",")
coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None
flags = list(filter(lambda x: x.startswith("f:"), all_flags))
hide = HIDE_ICON if flags and Flag.is_hide(int(flags[0][2:])) else None
@@ -220,42 +220,51 @@ def parse_services(services, transponders, path):
if transponder is not None:
tr_type, sp, tr = str(transponder).partition(" ")
tr_type = TrType(tr_type)
tr = tr.split(_SEP)
service_type = SERVICE_TYPE.get(data[4], SERVICE_TYPE["-2"])
# removing all non printable symbols!
srv_name = "".join(c for c in ch[1] if c.isprintable())
srv_name = "".join(c for c in srv[1] if c.isprintable())
pol = None
fec = None
system = None
pos = None
if tr_type == "t":
system = "DVB-T"
pos = "T"
elif tr_type == "c":
system = "CABLE"
pos = "C"
else:
if tr_type is TrType.Satellite:
pol = POLARIZATION.get(tr[2], None)
fec = FEC.get(tr[3], None)
system = "DVB-S2" if len(tr) > 7 else "DVB-S"
pos = "{}.{}".format(tr[4][:-1], tr[4][-1:])
if tr_type is TrType.Terrestrial:
system = T_SYSTEM.get(tr[9], None)
pos = "T"
fec = T_FEC.get(tr[3], None)
elif tr_type is TrType.Cable:
system = "DVB-C"
pos = "C"
fec = FEC_DEFAULT.get(tr[4])
channels.append(Service(flags_cas=ch[2],
transponder_type=tr_type,
coded=coded,
service=srv_name,
locked=locked,
hide=hide,
package=package,
service_type=service_type,
picon=None,
picon_id=picon_id,
ssid=data[0],
freq=tr[0],
rate=tr[1],
pol=POLARIZATION.get(tr[2], None),
fec=FEC[tr[3]],
system=system,
pos=pos,
data_id=data_id,
fav_id=fav_id,
transponder=transponder))
return channels
services_list.append(Service(flags_cas=srv[2],
transponder_type=tr_type.value,
coded=coded,
service=srv_name,
locked=locked,
hide=hide,
package=package,
service_type=service_type,
picon=None,
picon_id=picon_id,
ssid=data[0],
freq=tr[0],
rate=tr[1],
pol=pol,
fec=fec,
system=system,
pos=pos,
data_id=data_id,
fav_id=fav_id,
transponder=transponder))
return services_list
def split(itr, size):

View File

@@ -1,7 +1,9 @@
""" Module for IPTV and streams support """
import re
import urllib.request
from enum import Enum
from app.properties import Profile
from app.settings import SettingsType
from app.ui.uicommons import IPTV_ICON
from .ecommons import BqServiceType, Service
@@ -14,20 +16,22 @@ MARKER_FORMAT = " 1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n"
class StreamType(Enum):
DVB_TS = "1"
NONE_TS = "4097"
NONE_REC_1 = "5001"
NONE_REC_2 = "5002"
def parse_m3u(path, profile):
def parse_m3u(path, s_type):
with open(path) as file:
aggr = [None] * 10
services = []
groups = set()
counter = 0
name = None
fav_id = None
for line in file.readlines():
if line.startswith("#EXTINF"):
name = line[1 + line.index(","):].strip()
elif line.startswith("#EXTGRP") and profile is Profile.ENIGMA_2:
elif line.startswith("#EXTGRP") and s_type is SettingsType.ENIGMA_2:
grp_name = line.strip("#EXTGRP:").strip()
if grp_name not in groups:
groups.add(grp_name)
@@ -36,16 +40,47 @@ def parse_m3u(path, profile):
mr = Service(None, None, None, grp_name, *aggr[0:3], BqServiceType.MARKER.name, *aggr, fav_id, None)
services.append(mr)
elif not line.startswith("#"):
if profile is Profile.ENIGMA_2:
fav_id = ENIGMA2_FAV_ID_FORMAT.format(StreamType.NONE_TS.value, 1, 0, 0, 0, 0,
line.strip().replace(":", "%3a"), name, name, None)
elif profile is Profile.NEUTRINO_MP:
fav_id = NEUTRINO_FAV_ID_FORMAT.format(line.strip(), "", 0, None, None, None, None, "", "", 1)
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], BqServiceType.IPTV.name, *aggr, fav_id, None)
services.append(srv)
url = line.strip()
fav_id = get_fav_id(url, name, s_type)
if name and url:
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], BqServiceType.IPTV.name, *aggr, fav_id, None)
services.append(srv)
return services
def export_to_m3u(path, bouquet, s_type):
pattern = re.compile(".*:(http.*):.*") if s_type is SettingsType.ENIGMA_2 else re.compile("(http.*?)::::.*")
lines = ["#EXTM3U\n"]
current_grp = None
for s in bouquet.services:
s_type = s.type
if s_type is BqServiceType.IPTV:
res = re.match(pattern, s.data)
if not res:
continue
data = res.group(1)
lines.append("#EXTINF:-1,{}\n".format(s.name))
if current_grp:
lines.append(current_grp)
lines.append("{}\n".format(urllib.request.unquote(data.strip())))
elif s_type is BqServiceType.MARKER:
current_grp = "#EXTGRP:{}\n".format(s.name)
with open(path + "{}.m3u".format(bouquet.name), "w", encoding="utf-8") as file:
file.writelines(lines)
def get_fav_id(url, service_name, s_type):
""" Returns fav id depending on the profile. """
if s_type is SettingsType.ENIGMA_2:
url = urllib.request.quote(url)
stream_type = StreamType.NONE_TS.value
return ENIGMA2_FAV_ID_FORMAT.format(stream_type, 1, 0, 0, 0, 0, url, service_name, service_name, None)
elif s_type is SettingsType.NEUTRINO_MP:
return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1)
if __name__ == "__main__":
pass

View File

@@ -50,10 +50,9 @@ def parse_bouquets(file, name, bq_type):
if BqType(bq_type) is BqType.BOUQUET:
for bq in bouquets.bouquets:
if bq.services:
name = bq.name
name = name[name.index("]") + 1:]
key = int(bq.services[0].data.split(":")[1], 16)
if key not in PROVIDER:
pos, sep, name = bq.name.partition("]")
PROVIDER[key] = name
return bouquets

View File

@@ -2,13 +2,10 @@
For more info see __COMMENT
"""
from functools import lru_cache
from xml.dom.minidom import parse, Document
import os
from app.commons import log
from .ecommons import POLARIZATION, FEC, SYSTEM, MODULATION, PLS_MODE, Transponder, Satellite, get_key_by_value
from .ecommons import POLARIZATION, FEC, SYSTEM, MODULATION, Transponder, Satellite, get_key_by_value
__COMMENT = (" File was created in DemonEditor\n\n"
"usable flags are\n"
@@ -33,7 +30,7 @@ __COMMENT = (" File was created in DemonEditor\n\n"
def get_satellites(path):
return parse_satellites(path, os.path.getsize(path))
return parse_satellites(path)
def write_satellites(satellites, data_path):
@@ -60,7 +57,7 @@ def write_satellites(satellites, data_path):
transponder_child.setAttribute("system", get_key_by_value(SYSTEM, tr.system))
transponder_child.setAttribute("modulation", get_key_by_value(MODULATION, tr.modulation))
if tr.pls_mode:
transponder_child.setAttribute("pls_mode", get_key_by_value(PLS_MODE, tr.pls_mode))
transponder_child.setAttribute("pls_mode", tr.pls_mode)
if tr.pls_code:
transponder_child.setAttribute("pls_code", tr.pls_code)
if tr.is_id:
@@ -88,7 +85,7 @@ def parse_transponders(elem, sat_name):
FEC[atr["fec_inner"].value],
SYSTEM[atr["system"].value],
MODULATION[atr["modulation"].value],
PLS_MODE[atr["pls_mode"].value] if "pls_mode" in atr else None,
atr["pls_mode"].value if "pls_mode" in atr else None,
atr["pls_code"].value if "pls_code" in atr else None,
atr["is_id"].value if "is_id" in atr else None)
except Exception as e:
@@ -109,8 +106,7 @@ def parse_sat(elem):
parse_transponders(elem, sat_name))
@lru_cache(maxsize=1)
def parse_satellites(path, file_size):
def parse_satellites(path):
""" Parsing satellites from xml"""
dom = parse(path)
satellites = []

View File

@@ -1,60 +0,0 @@
import json
import os
from enum import Enum
from pathlib import Path
CONFIG_PATH = str(Path.home()) + "/.config/demon-editor/"
CONFIG_FILE = CONFIG_PATH + "config.json"
DATA_PATH = "data/"
class Profile(Enum):
""" Profiles for settings """
ENIGMA_2 = "0"
NEUTRINO_MP = "1"
def get_config():
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) # create dir if not exist
os.makedirs(os.path.dirname(DATA_PATH), exist_ok=True)
if not os.path.isfile(CONFIG_FILE) or os.stat(CONFIG_FILE).st_size == 0:
reset_config()
with open(CONFIG_FILE, "r") as config_file:
return json.load(config_file)
def reset_config():
with open(CONFIG_FILE, "w") as default_config_file:
json.dump(get_default_settings(), default_config_file)
def write_config(config):
assert isinstance(config, dict)
with open(CONFIG_FILE, "w") as config_file:
json.dump(config, config_file)
def get_default_settings():
return {
Profile.ENIGMA_2.value: {
"host": "127.0.0.1", "port": "21", "user": "root", "password": "root",
"http_user": "root", "http_password": "", "http_port": "80", "http_timeout": 5,
"telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 5,
"services_path": "/etc/enigma2/", "user_bouquet_path": "/etc/enigma2/",
"satellites_xml_path": "/etc/tuxbox/", "data_dir_path": DATA_PATH + "enigma2/",
"picons_path": "/usr/share/enigma2/picon", "picons_dir_path": DATA_PATH + "enigma2/picons/",
"v5_support": False, "http_api_support": False},
Profile.NEUTRINO_MP.value: {
"host": "127.0.0.1", "port": "21", "user": "root", "password": "root",
"http_user": "", "http_password": "", "http_port": "80", "http_timeout": 2,
"telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 1,
"services_path": "/var/tuxbox/config/zapit/", "user_bouquet_path": "/var/tuxbox/config/zapit/",
"satellites_xml_path": "/var/tuxbox/config/", "data_dir_path": DATA_PATH + "neutrino/",
"picons_path": "/usr/share/tuxbox/neutrino/icons/logo/", "picons_dir_path": DATA_PATH + "neutrino/picons/"},
"profile": Profile.ENIGMA_2.value}
if __name__ == "__main__":
pass

470
app/settings.py Normal file
View File

@@ -0,0 +1,470 @@
import copy
import json
import locale
import os
from enum import Enum, IntEnum
from pathlib import Path
from pprint import pformat
from textwrap import dedent
HOME_PATH = str(Path.home())
CONFIG_PATH = HOME_PATH + "/.config/demon-editor/"
CONFIG_FILE = CONFIG_PATH + "config.json"
DATA_PATH = HOME_PATH + "/DemonEditor/data/"
class Defaults(Enum):
""" Default program settings """
DEFAULT_PROFILE = "default"
BACKUP_BEFORE_DOWNLOADING = True
BACKUP_BEFORE_SAVE = True
V5_SUPPORT = False
HTTP_API_SUPPORT = False
ENABLE_YT_DL = False
ENABLE_SEND_TO = False
USE_COLORS = True
NEW_COLOR = "rgb(255,230,204)"
EXTRA_COLOR = "rgb(179,230,204)"
FAV_CLICK_MODE = 0
def get_default_settings(profile_name="default"):
def_settings = SettingsType.ENIGMA_2.get_default_settings()
set_local_paths(def_settings, profile_name)
return {
"version": 1,
"default_profile": Defaults.DEFAULT_PROFILE.value,
"profiles": {profile_name: def_settings},
"v5_support": Defaults.V5_SUPPORT.value,
"http_api_support": Defaults.HTTP_API_SUPPORT.value,
"enable_yt_dl": Defaults.ENABLE_YT_DL.value,
"enable_send_to": Defaults.ENABLE_SEND_TO.value,
"use_colors": Defaults.USE_COLORS.value,
"new_color": Defaults.NEW_COLOR.value,
"extra_color": Defaults.EXTRA_COLOR.value,
"fav_click_mode": Defaults.FAV_CLICK_MODE.value
}
def set_local_paths(settings, profile_name):
settings["data_local_path"] = "{}{}/".format(settings["data_local_path"], profile_name)
settings["picons_local_path"] = "{}{}/".format(settings["picons_local_path"], profile_name)
settings["backup_local_path"] = "{}{}/".format(settings["backup_local_path"], profile_name)
class SettingsType(IntEnum):
""" Profiles for settings """
ENIGMA_2 = 0
NEUTRINO_MP = 1
def get_default_settings(self):
""" Returns default settings for current type """
if self is self.ENIGMA_2:
return {"setting_type": self.value,
"host": "127.0.0.1", "port": "21", "user": "root", "password": "root", "timeout": 5,
"http_user": "root", "http_password": "", "http_port": "80",
"http_timeout": 5, "http_use_ssl": False,
"telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 5,
"services_path": "/etc/enigma2/", "user_bouquet_path": "/etc/enigma2/",
"satellites_xml_path": "/etc/tuxbox/", "data_local_path": DATA_PATH + "enigma2/",
"picons_path": "/usr/share/enigma2/picon/",
"picons_local_path": DATA_PATH + "enigma2/picons/",
"backup_local_path": DATA_PATH + "enigma2/backup/"}
elif self is self.NEUTRINO_MP:
return {"setting_type": self,
"host": "127.0.0.1", "port": "21", "user": "root", "password": "root", "timeout": 5,
"http_user": "", "http_password": "", "http_port": "80", "http_timeout": 2, "http_use_ssl": False,
"telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 1,
"services_path": "/var/tuxbox/config/zapit/", "user_bouquet_path": "/var/tuxbox/config/zapit/",
"satellites_xml_path": "/var/tuxbox/config/", "data_local_path": DATA_PATH + "neutrino/",
"picons_path": "/usr/share/tuxbox/neutrino/icons/logo/",
"picons_local_path": DATA_PATH + "neutrino/picons/",
"backup_local_path": DATA_PATH + "neutrino/backup/"}
class SettingsException(Exception):
pass
class Settings:
__INSTANCE = None
__VERSION = 1
def __init__(self, ext_settings=None):
settings = ext_settings or get_settings()
if self.__VERSION > settings.get("version", 0):
raise SettingsException("Outdated version of the settings format!")
self._settings = settings
self._current_profile = self._settings.get("default_profile", "default")
self._profiles = self._settings.get("profiles", {"default": SettingsType.ENIGMA_2.get_default_settings()})
self._cp_settings = self._profiles.get(self._current_profile, None) # Current profile settings
if not self._cp_settings:
raise SettingsException("Error reading settings [current profile].")
def __str__(self):
return dedent(""" Current profile: {}
Current profile options:
{}
Full config:
{}
""").format(self._current_profile,
pformat(self._cp_settings),
pformat(self._settings))
@classmethod
def get_instance(cls):
if not cls.__INSTANCE:
cls.__INSTANCE = Settings()
return cls.__INSTANCE
def save(self):
write_settings(self._settings)
def reset(self, force_write=False):
for k, v in self.setting_type.get_default_settings().items():
self._cp_settings[k] = v
set_local_paths(self._cp_settings, self._current_profile)
if force_write:
self.save()
@staticmethod
def reset_to_default():
write_settings(get_default_settings())
def get_default(self, p_name):
""" Returns default value for current settings type """
return self.setting_type.get_default_settings().get(p_name)
def add(self, name, value):
""" Adds extra options """
self._settings[name] = value
def get(self, name):
""" Returns extra options or None """
return self._settings.get(name, None)
@property
def settings(self):
""" Returns copy of the current settings! """
return copy.deepcopy(self._settings)
@settings.setter
def settings(self, value):
""" Sets copy of the settings! """
self._settings = copy.deepcopy(value)
@property
def current_profile(self):
return self._current_profile
@current_profile.setter
def current_profile(self, value):
self._current_profile = value
self._cp_settings = self._profiles.get(self._current_profile)
@property
def default_profile(self):
return self._settings.get("default_profile", "default")
@default_profile.setter
def default_profile(self, value):
self._settings["default_profile"] = value
@property
def profiles(self):
return self._profiles
@profiles.setter
def profiles(self, ps):
self._profiles = ps
self._settings["profiles"] = self._profiles
@property
def setting_type(self):
return SettingsType(self._cp_settings.get("setting_type", SettingsType.ENIGMA_2.value))
@setting_type.setter
def setting_type(self, s_type):
self._cp_settings["setting_type"] = s_type.value
@property
def language(self):
return self._settings.get("language", locale.getlocale()[0] or "en_US")
@language.setter
def language(self, value):
self._settings["language"] = value
@property
def load_last_config(self):
return self._settings.get("load_last_config", False)
@load_last_config.setter
def load_last_config(self, value):
self._settings["load_last_config"] = value
@property
def host(self):
return self._cp_settings.get("host", self.get_default("host"))
@host.setter
def host(self, value):
self._cp_settings["host"] = value
@property
def port(self):
return self._cp_settings.get("port", self.get_default("port"))
@port.setter
def port(self, value):
self._cp_settings["port"] = value
@property
def user(self):
return self._cp_settings.get("user", self.get_default("user"))
@user.setter
def user(self, value):
self._cp_settings["user"] = value
@property
def password(self):
return self._cp_settings.get("password", self.get_default("password"))
@password.setter
def password(self, value):
self._cp_settings["password"] = value
@property
def http_user(self):
return self._cp_settings.get("http_user", self.get_default("http_user"))
@http_user.setter
def http_user(self, value):
self._cp_settings["http_user"] = value
@property
def http_password(self):
return self._cp_settings.get("http_password", self.get_default("http_password"))
@http_password.setter
def http_password(self, value):
self._cp_settings["http_password"] = value
@property
def http_port(self):
return self._cp_settings.get("http_port", self.get_default("http_port"))
@http_port.setter
def http_port(self, value):
self._cp_settings["http_port"] = value
@property
def http_timeout(self):
return self._cp_settings.get("http_timeout", self.get_default("http_timeout"))
@http_timeout.setter
def http_timeout(self, value):
self._cp_settings["http_timeout"] = value
@property
def http_use_ssl(self):
return self._cp_settings.get("http_use_ssl", self.get_default("http_use_ssl"))
@http_use_ssl.setter
def http_use_ssl(self, value):
self._cp_settings["http_use_ssl"] = value
@property
def telnet_user(self):
return self._cp_settings.get("telnet_user", self.get_default("telnet_user"))
@telnet_user.setter
def telnet_user(self, value):
self._cp_settings["telnet_user"] = value
@property
def telnet_password(self):
return self._cp_settings.get("telnet_password", self.get_default("telnet_password"))
@telnet_password.setter
def telnet_password(self, value):
self._cp_settings["telnet_password"] = value
@property
def telnet_port(self):
return self._cp_settings.get("telnet_port", self.get_default("telnet_port"))
@telnet_port.setter
def telnet_port(self, value):
self._cp_settings["telnet_port"] = value
@property
def telnet_timeout(self):
return self._cp_settings.get("telnet_timeout", self.get_default("telnet_timeout"))
@telnet_timeout.setter
def telnet_timeout(self, value):
self._cp_settings["telnet_timeout"] = value
@property
def services_path(self):
return self._cp_settings.get("services_path", self.get_default("services_path"))
@services_path.setter
def services_path(self, value):
self._cp_settings["services_path"] = value
@property
def user_bouquet_path(self):
return self._cp_settings.get("user_bouquet_path", self.get_default("user_bouquet_path"))
@user_bouquet_path.setter
def user_bouquet_path(self, value):
self._cp_settings["user_bouquet_path"] = value
@property
def satellites_xml_path(self):
return self._cp_settings.get("satellites_xml_path", self.get_default("satellites_xml_path"))
@satellites_xml_path.setter
def satellites_xml_path(self, value):
self._cp_settings["satellites_xml_path"] = value
@property
def data_local_path(self):
return self._cp_settings.get("data_local_path", self.get_default("data_local_path"))
@data_local_path.setter
def data_local_path(self, value):
self._cp_settings["data_local_path"] = value
@property
def picons_path(self):
return self._cp_settings.get("picons_path", self.get_default("picons_path"))
@picons_path.setter
def picons_path(self, value):
self._cp_settings["picons_path"] = value
@property
def picons_local_path(self):
return self._cp_settings.get("picons_local_path", self.get_default("picons_local_path"))
@picons_local_path.setter
def picons_local_path(self, value):
self._cp_settings["picons_local_path"] = value
@property
def backup_local_path(self):
return self._cp_settings.get("backup_local_path", self.get_default("backup_local_path"))
@backup_local_path.setter
def backup_local_path(self, value):
self._cp_settings["backup_local_path"] = value
# ***** Program settings *****
@property
def backup_before_save(self):
return self._settings.get("backup_before_save", Defaults.BACKUP_BEFORE_SAVE.value)
@backup_before_save.setter
def backup_before_save(self, value):
self._settings["backup_before_save"] = value
@property
def backup_before_downloading(self):
return self._settings.get("backup_before_downloading", Defaults.BACKUP_BEFORE_DOWNLOADING.value)
@backup_before_downloading.setter
def backup_before_downloading(self, value):
self._settings["backup_before_downloading"] = value
@property
def v5_support(self):
return self._settings.get("v5_support", Defaults.V5_SUPPORT.value)
@v5_support.setter
def v5_support(self, value):
self._settings["v5_support"] = value
@property
def http_api_support(self):
return self._settings.get("http_api_support", Defaults.HTTP_API_SUPPORT.value)
@http_api_support.setter
def http_api_support(self, value):
self._settings["http_api_support"] = value
@property
def enable_yt_dl(self):
return self._settings.get("enable_yt_dl", Defaults.ENABLE_YT_DL.value)
@enable_yt_dl.setter
def enable_yt_dl(self, value):
self._settings["enable_yt_dl"] = value
@property
def enable_send_to(self):
return self._settings.get("enable_send_to", Defaults.ENABLE_SEND_TO.value)
@enable_send_to.setter
def enable_send_to(self, value):
self._settings["enable_send_to"] = value
@property
def use_colors(self):
return self._settings.get("use_colors", Defaults.USE_COLORS.value)
@use_colors.setter
def use_colors(self, value):
self._settings["use_colors"] = value
@property
def new_color(self):
return self._settings.get("new_color", Defaults.NEW_COLOR.value)
@new_color.setter
def new_color(self, value):
self._settings["new_color"] = value
@property
def extra_color(self):
return self._settings.get("extra_color", Defaults.EXTRA_COLOR.value)
@extra_color.setter
def extra_color(self, value):
self._settings["extra_color"] = value
@property
def fav_click_mode(self):
return self._settings.get("fav_click_mode", Defaults.FAV_CLICK_MODE.value)
@fav_click_mode.setter
def fav_click_mode(self, value):
self._settings["fav_click_mode"] = value
def get_settings():
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) # create dir if not exist
os.makedirs(os.path.dirname(DATA_PATH), exist_ok=True)
if not os.path.isfile(CONFIG_FILE) or os.stat(CONFIG_FILE).st_size == 0:
write_settings(get_default_settings())
with open(CONFIG_FILE, "r") as config_file:
return json.load(config_file)
def write_settings(config):
with open(CONFIG_FILE, "w") as config_file:
json.dump(config, config_file, indent=" ")
if __name__ == "__main__":
pass

110
app/tools/epg.py Normal file
View File

@@ -0,0 +1,110 @@
""" Module for working with epg.dat file """
import struct
from datetime import datetime
from xml.dom.minidom import parse, Node, Document
from app.eparser.ecommons import BqServiceType, BouquetService
class EPG:
@staticmethod
def get_epg_refs(path):
""" The read algorithm was taken from the eEPGCache::load() function from this source:
https://github.com/OpenPLi/enigma2/blob/44d9b92f5260c7de1b3b3a1b9a9cbe0f70ca4bf0/lib/dvb/epgcache.cpp#L1300
"""
refs = set()
with open(path, mode="rb") as f:
crc = struct.unpack("<I", f.read(4))[0]
if crc != int(0x98765432):
raise ValueError("Epg file has incorrect byte order!")
header = f.read(13).decode()
if header != "ENIGMA_EPG_V7":
raise ValueError("Unsupported format of epd.dat file!")
channels_count = struct.unpack("<I", f.read(4))[0]
for i in range(channels_count):
sid, nid, tsid, events_size = struct.unpack("<IIII", f.read(16))
service_id = "{:X}:{:X}:{:X}".format(sid, tsid, nid)
for j in range(events_size):
_type, _len = struct.unpack("<BB", f.read(2))
f.read(10)
n_crc = (_len - 10) // 4
if n_crc > 0:
[f.read(4) for n in range(n_crc)]
refs.add(service_id)
return refs
class ChannelsParser:
_COMMENT = "File was created in DemonEditor"
@staticmethod
def get_refs_from_xml(path):
""" Returns tuple from references and description. """
refs = []
dom = parse(path)
description = "".join(n.data + "\n" for n in dom.childNodes if n.nodeType == Node.COMMENT_NODE)
for elem in dom.getElementsByTagName("channels"):
c_count = 0
comment_count = 0
current_data = ""
if elem.hasChildNodes():
for n in elem.childNodes:
if n.nodeType == Node.COMMENT_NODE:
c_count += 1
comment_count += 1
txt = n.data.strip()
if comment_count:
comment_count -= 1
else:
ref_data = current_data.split(":")
refs.append(BouquetService(name=txt,
type=BqServiceType.DEFAULT,
data="{}:{}:{}:{}".format(*ref_data[3:7]).upper(),
num="{}:{}:{}".format(*ref_data[3:6]).upper()))
if n.hasChildNodes():
for s_node in n.childNodes:
if s_node.nodeType == Node.TEXT_NODE:
comment_count -= 1
current_data = s_node.data
return refs, description
@staticmethod
def write_refs_to_xml(path, services):
header = '<?xml version="1.0" encoding="utf-8"?>\n<!-- {} -->\n<!-- {} -->\n<channels>\n'.format(
"Created in DemonEditor.", datetime.now().strftime("%d.%m.%Y %H:%M:%S"))
doc = Document()
lines = [header]
for srv in services:
srv_type = srv.type
if srv_type is BqServiceType.IPTV:
channel_child = doc.createElement("channel")
channel_child.setAttribute("id", str(srv.num))
data = srv.data.strip().split(":")
channel_child.appendChild(doc.createTextNode(":".join(data[:10])))
comment = doc.createComment(srv.name)
lines.append("{} {}\n".format(str(channel_child.toxml()), str(comment.toxml())))
elif srv_type is BqServiceType.MARKER:
comment = doc.createComment(srv.name)
lines.append("{}\n".format(str(comment.toxml())))
lines.append("</channels>")
doc.unlink()
with open(path, "w", encoding="utf-8") as f:
f.writelines(lines)
if __name__ == "__main__":
pass

View File

@@ -1,20 +1,48 @@
from app.commons import run_task
from app.tools import vlc
import sys
from app.commons import run_task, log
class Player:
_VLC_INSTANCE = None
__VLC_INSTANCE = None
def __init__(self):
self._is_playing = False
self._player = self.get_vlc_instance()
def __init__(self, rewind_callback, position_callback, error_callback, playing_callback):
try:
from app.tools import vlc
from app.tools.vlc import EventType
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
raise ImportError
else:
self._is_playing = False
args = "--quiet {}".format("" if sys.platform == "darwin" else "--no-xlib")
self._player = vlc.Instance(args).media_player_new()
ev_mgr = self._player.event_manager()
@staticmethod
def get_vlc_instance():
if Player._VLC_INSTANCE:
return Player._VLC_INSTANCE
_VLC_INSTANCE = vlc.Instance("--quiet --no-xlib").media_player_new()
return _VLC_INSTANCE
if rewind_callback:
# TODO look other EventType options
ev_mgr.event_attach(EventType.MediaPlayerBuffering,
lambda et, p: rewind_callback(p.get_media().get_duration()),
self._player)
if position_callback:
ev_mgr.event_attach(EventType.MediaPlayerTimeChanged,
lambda et, p: position_callback(p.get_time()),
self._player)
if error_callback:
ev_mgr.event_attach(EventType.MediaPlayerEncounteredError,
lambda et, p: error_callback(),
self._player)
if playing_callback:
ev_mgr.event_attach(EventType.MediaPlayerPlaying,
lambda et, p: playing_callback(),
self._player)
@classmethod
def get_instance(cls, rewind_callback=None, position_callback=None, error_callback=None, playing_callback=None):
if not cls.__VLC_INSTANCE:
cls.__VLC_INSTANCE = Player(rewind_callback, position_callback, error_callback, playing_callback)
return cls.__VLC_INSTANCE
@run_task
def play(self, mrl=None):
@@ -32,6 +60,9 @@ class Player:
def pause(self):
self._player.pause()
def set_time(self, time):
self._player.set_time(time)
@run_task
def release(self):
if self._player:
@@ -42,6 +73,26 @@ class Player:
def set_xwindow(self, xid):
self._player.set_xwindow(xid)
def set_nso(self, widget):
""" Used on MacOS to set NSObject.
Based on gtkvlc.py[get_window_pointer] example from here:
https://github.com/oaubert/python-vlc/tree/master/examples
"""
try:
import ctypes
g_dll = ctypes.CDLL("libgdk-3.0.dylib")
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
else:
get_nsview = g_dll.gdk_quartz_window_get_nsview
get_nsview.restype, get_nsview.argtypes = ctypes.c_void_p, [ctypes.c_void_p]
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object]
# Get the C void* pointer to the window
pointer = ctypes.pythonapi.PyCapsule_GetPointer(widget.get_window().__gpointer__, None)
self._player.set_nsobject(get_nsview(pointer))
def set_mrl(self, mrl):
self._player.set_mrl(mrl)

View File

@@ -7,7 +7,7 @@ from collections import namedtuple
from html.parser import HTMLParser
from app.commons import run_task
from app.properties import Profile
from app.settings import SettingsType
_ENIGMA2_PICON_KEY = "{:X}:{:X}:{}"
_NEUTRINO_PICON_KEY = "{:x}{:04x}{:04x}.png"
@@ -79,7 +79,7 @@ class PiconsParser(HTMLParser):
pass
@staticmethod
def parse(open_path, picons_path, tmp_path, provider, picon_ids, profile=Profile.ENIGMA_2):
def parse(open_path, picons_path, tmp_path, provider, picon_ids, s_type=SettingsType.ENIGMA_2):
with open(open_path, encoding="utf-8", errors="replace") as f:
on_id, pos, ssid, single = provider.on_id, provider.pos, provider.ssid, provider.single
neg_pos = pos.endswith("W")
@@ -100,7 +100,7 @@ class PiconsParser(HTMLParser):
namespace = "{:X}{:X}".format(int(pos), int(freq))
else:
namespace = "{:X}0000".format(int(pos))
name = PiconsParser.format(ssid if single else p.ssid, on_id, namespace, picon_ids, profile)
name = PiconsParser.format(ssid if single else p.ssid, on_id, namespace, picon_ids, s_type)
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:
@@ -109,10 +109,10 @@ class PiconsParser(HTMLParser):
print(msg)
@staticmethod
def format(ssid, on_id, namespace, picon_ids, profile: Profile):
if profile is Profile.ENIGMA_2:
def format(ssid, on_id, namespace, picon_ids, s_type):
if s_type is SettingsType.ENIGMA_2:
return picon_ids.get(_ENIGMA2_PICON_KEY.format(int(ssid), int(on_id), namespace), None)
elif profile is Profile.NEUTRINO_MP:
elif s_type is SettingsType.NEUTRINO_MP:
tr_id = int(ssid[:-2] if len(ssid) < 4 else ssid[:2])
return _NEUTRINO_PICON_KEY.format(tr_id, int(on_id), int(ssid))
else:
@@ -125,7 +125,7 @@ class ProviderParser(HTMLParser):
_POSITION_PATTERN = re.compile("at\s\d+\..*(?:E|W)']")
_ONID_TID_PATTERN = re.compile("^\d+-\d+.*")
_TRANSPONDER_FREQUENCY_PATTERN = re.compile("^\d+ [HVLR]+")
_DOMAIN = "https://www.lyngsat.com"
_DOMAIN = "http://www.lyngsat.com"
_TV_DOMAIN = _DOMAIN + "/tvchannels/"
_RADIO_DOMAIN = _DOMAIN + "/radiochannels/"
_PKG_DOMAIN = _DOMAIN + "/packages/"
@@ -249,12 +249,12 @@ def parse_providers(open_path):
@run_task
def convert_to(src_path, dest_path, profile, callback, done_callback):
def convert_to(src_path, dest_path, s_type, 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"
pattern = "/*_0_0_0.png" if s_type is SettingsType.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("_")

View File

@@ -2,11 +2,14 @@
for replace or update current satellites.xml file.
"""
import re
import requests
from enum import Enum
from html.parser import HTMLParser
from app.commons import log
from app.eparser import Satellite, Transponder, is_transponder_valid
from app.eparser.ecommons import PLS_MODE
class SatelliteSource(Enum):
@@ -79,14 +82,14 @@ class SatellitesParser(HTMLParser):
try:
request = requests.get(url=src, headers=self._HEADERS)
except requests.exceptions.ConnectionError as e:
print(repr(e))
log(repr(e))
return []
else:
reason = request.reason
if reason == "OK":
self.feed(request.text)
else:
print(reason)
log(reason)
if self._rows:
if self._source is SatelliteSource.FLYSAT:
@@ -96,13 +99,16 @@ class SatellitesParser(HTMLParser):
return list(map(get_sat, filter(lambda x: all(x) and len(x) == 5, self._rows)))
elif self._source is SatelliteSource.LYNGSAT:
extra_pattern = re.compile("^https://www\.lyngsat\.com/[\w-]+\.html")
base_url = "https://www.lyngsat.com/"
sats = []
current_pos = "0"
for row in filter(lambda x: len(x) in (5, 7, 8), self._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))
name = row[1].rsplit("/")[-1].rstrip(".html").replace("-", " ")
sats.append((name, current_pos, row[5], base_url + row[1], False)) # [all in one] satellites
sats.append((row[4], current_pos, row[5], base_url + row[3], False))
if r_len == 8: # for a very limited number of satellites
data = list(filter(None, row))
urls = set()
@@ -116,14 +122,14 @@ class SatellitesParser(HTMLParser):
current_pos = self.parse_position(data[1])
for url in urls:
name = url.rsplit("/")[-1].rstrip(".html").replace("-", " ")
sats.append((name, current_pos, sat_type, url, False))
sats.append((name, current_pos, sat_type, base_url + url, False))
elif r_len == 5:
sats.append((row[2], current_pos, row[3], row[1], False))
sats.append((row[2], current_pos, row[3], base_url + row[1], False))
return sats
def get_satellite(self, sat):
pos = sat[1]
return Satellite(name=sat[0] + " ({})".format(pos),
return Satellite(name="{} {}".format(pos, sat[0]),
flags="0",
position=self.get_position(pos.replace(".", "")),
transponders=self.get_transponders(sat[3]))
@@ -137,6 +143,7 @@ class SatellitesParser(HTMLParser):
return "{}{}".format("-" if pos[-1] == "W" else "", pos[:-1])
def get_transponders(self, sat_url):
""" Getting transponders(sorted by frequency). """
self._rows.clear()
url = "https://www.flysat.com/" + sat_url if self._source is SatelliteSource.FLYSAT else sat_url
request = requests.get(url=url, headers=self._HEADERS)
@@ -148,13 +155,23 @@ class SatellitesParser(HTMLParser):
self.get_transponders_for_fly_sat(trs)
elif self._source is SatelliteSource.LYNGSAT:
self.get_transponders_for_lyng_sat(trs)
return trs
return sorted(trs, key=lambda x: int(x.frequency))
def get_transponders_for_fly_sat(self, trs):
""" Parsing transponders for FlySat """
pls_pattern = re.compile("(PLS:)+ (Root|Gold|Combo)+ (\\d+)?")
is_id_pattern = re.compile("(Stream) (\\d+)")
pls_modes = {v: k for k, v in PLS_MODE.items()}
n_trs = []
if self._rows:
zeros = "000"
is_ids = []
for r in self._rows:
if len(r) == 1:
is_ids.extend(re.findall(is_id_pattern, r[0]))
continue
if len(r) < 3:
continue
data = r[2].split(" ")
@@ -171,18 +188,41 @@ class SatellitesParser(HTMLParser):
sys, mod = sys
mod = "QPSK" if sys == "DVB-S" else mod
tr = Transponder(freq + zeros, sr + zeros, pol, fec, sys, mod, None, None, None)
if is_transponder_valid(tr):
trs.append(tr)
pls = re.findall(pls_pattern, r[1])
pls_code = None
pls_mode = None
if pls:
pls_code = pls[0][2]
pls_mode = pls_modes.get(pls[0][1], None)
if is_ids:
tr = trs.pop()
for index, is_id in enumerate(is_ids):
tr = tr._replace(is_id=is_id[1])
if is_transponder_valid(tr):
n_trs.append(tr)
else:
tr = Transponder(freq + zeros, sr + zeros, pol, fec, sys, mod, pls_mode, pls_code, None)
if is_transponder_valid(tr):
trs.append(tr)
is_ids.clear()
trs.extend(n_trs)
def get_transponders_for_lyng_sat(self, trs):
""" Parsing transponders for LyngSat """
frq_pol_pattern = re.compile("(\d{4,5}).*([RLHV])(.*\d$)")
sr_fec_pattern = re.compile("^(\d{4,5})-(\d/\d)(.+PSK)?(.*)?$")
sys_pattern = re.compile("(DVB-S[2]?)(.*)?")
frq_pol_pattern = re.compile("(\\d{4,5})\\s+([RLHV]).*")
sr_fec_pattern = re.compile("^(\\d{4,5})-(\\d/\\d)(.+PSK)?(.*)?$")
sys_pattern = re.compile("(DVB-S[2]?) ?(PLS+ (Root|Gold|Combo)+ (\\d+))* ?(multistream stream (\\d+))?",
re.IGNORECASE)
zeros = "000"
pls_modes = {v: k for k, v in PLS_MODE.items()}
for r in filter(lambda x: len(x) > 8, self._rows):
freq = re.match(frq_pol_pattern, r[2])
for frq in r[1], r[2], r[3]:
freq = re.match(frq_pol_pattern, frq)
if freq:
break
if not freq:
continue
frq, pol = freq.group(1), freq.group(2)
@@ -191,12 +231,18 @@ class SatellitesParser(HTMLParser):
continue
sr, fec, mod = sr_fec.group(1), sr_fec.group(2), sr_fec.group(3)
mod = mod.strip() if mod else "Auto"
sys = re.match(sys_pattern, r[-4])
if not sys:
continue
sys = sys.group(1)
tr = Transponder(frq + zeros, sr + zeros, pol, fec, sys, mod, None, None, None)
res = re.match(sys_pattern, r[-4])
if not res:
continue
sys = res.group(1)
pls_mode = res.group(3)
pls_mode = pls_modes.get(pls_mode.capitalize(), None) if pls_mode else pls_mode
pls_code = res.group(4)
pls_id = res.group(6)
tr = Transponder(frq + zeros, sr + zeros, pol, fec, sys, mod, pls_mode, pls_code, pls_id)
if is_transponder_valid(tr):
trs.append(tr)

File diff suppressed because it is too large Load Diff

167
app/tools/yt.py Normal file
View File

@@ -0,0 +1,167 @@
""" Module for working with YouTube service """
import gzip
import json
import re
import urllib
from html.parser import HTMLParser
from json import JSONDecodeError
from urllib.request import Request
from app.commons import log
_YT_PATTERN = re.compile(r"https://www.youtube.com/.+(?:v=)([\w-]{11}).*")
_YT_LIST_PATTERN = re.compile(r"https://www.youtube.com/.+?(?:list=)([\w-]{23,})?.*")
_YT_VIDEO_PATTERN = re.compile(r"https://r\d+---sn-[\w]{10}-[\w]{3,5}.googlevideo.com/videoplayback?.*")
_HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/69.0",
"DNT": "1",
"Accept-Encoding": "gzip, deflate"}
Quality = {137: "1080p", 136: "720p", 135: "480p", 134: "360p",
133: "240p", 160: "144p", 0: "0p", 18: "360p", 22: "720p"}
class YouTube:
""" Helper class for working with YouTube service. """
@staticmethod
def is_yt_video_link(url):
return re.match(_YT_VIDEO_PATTERN, url)
@staticmethod
def get_yt_id(url):
""" Returns video id or None """
yt = re.search(_YT_PATTERN, url)
if yt:
return yt.group(1)
@staticmethod
def get_yt_list_id(url):
""" Returns playlist id or None """
yt = re.search(_YT_LIST_PATTERN, url)
if yt:
return yt.group(1)
@staticmethod
def get_yt_link(video_id):
""" Getting link to YouTube video by id.
returns tuple from the video links dict and title
"""
req = Request("https://youtube.com/get_video_info?video_id={}&hl=en".format(video_id), headers=_HEADERS)
with urllib.request.urlopen(req, timeout=2) as resp:
data = urllib.request.unquote(gzip.decompress(resp.read()).decode("utf-8")).split("&")
out = {k: v for k, sep, v in (str(d).partition("=") for d in map(urllib.request.unquote, data))}
player_resp = out.get("player_response", None)
if player_resp:
try:
resp = json.loads(player_resp)
except JSONDecodeError as e:
log("{}: Parsing player response error: {}".format(__class__.__name__, e))
else:
det = resp.get("videoDetails", None)
title = det.get("title", None) if det else None
streaming_data = resp.get("streamingData", None)
fmts = streaming_data.get("formats", None) if streaming_data else None
if fmts:
urls = {Quality[i["itag"]]: i["url"] for i in
filter(lambda i: i.get("itag", -1) in Quality, fmts)}
if urls and title:
return urls, title.replace("+", " ")
stream_map = out.get("url_encoded_fmt_stream_map", None)
if stream_map:
s_map = {k: v for k, sep, v in (str(d).partition("=") for d in stream_map.split("&"))}
url, title = s_map.get("url", None), out.get("title", None)
url, title = urllib.request.unquote(url) if url else "", title.replace("+", " ") if title else ""
if url and title:
return {Quality[0]: url}, title.replace("+", " ")
rsn = out.get("reason", None)
rsn = rsn.replace("+", " ") if rsn else ""
log("{}: Getting link to video with id {} filed! Cause: {}".format(__class__.__name__, video_id, rsn))
return None, rsn
class PlayListParser(HTMLParser):
""" Very simple parser to handle YouTube playlist pages. """
def __init__(self):
super().__init__()
self._is_header = False
self._header = ""
self._playlist = []
self._is_script = False
def handle_starttag(self, tag, attrs):
if tag == "script":
self._is_script = True
def handle_data(self, data):
if self._is_script:
data = data.lstrip()
if data.startswith('window["ytInitialData"] = '):
data = data.split(";")[0].lstrip('window["ytInitialData"] = ')
try:
resp = json.loads(data)
except JSONDecodeError as e:
log("{}: Parsing data error: {}".format(__class__.__name__, e))
else:
sb = resp.get("sidebar", None)
if sb:
for t in [t["runs"][0] for t in flat("title", sb) if "runs" in t]:
txt = t.get("text", None)
if txt:
self._header = txt
break
ct = resp.get("contents", None)
if ct:
for d in [(d["title"]["simpleText"], d["videoId"]) for d in flat("playlistVideoRenderer", ct)]:
self._playlist.append(d)
self._is_script = False
def error(self, message):
log("{} Parsing error: {}".format(__class__.__name__, message))
@property
def header(self):
return self._header
@property
def playlist(self):
return self._playlist
@staticmethod
def get_yt_playlist(play_list_id):
""" Getting YouTube playlist by id.
returns tuple from the playlist header and list of tuples (title, video id)
"""
request = Request("https://www.youtube.com/playlist?list={}&hl=en".format(play_list_id), headers=_HEADERS)
with urllib.request.urlopen(request, timeout=2) as resp:
data = gzip.decompress(resp.read()).decode("utf-8")
parser = PlayListParser()
parser.feed(data)
return parser.header, parser.playlist
def flat(key, d):
for k, v in d.items():
if k == key:
yield v
elif isinstance(v, dict):
yield from flat(key, v)
elif isinstance(v, list):
for el in v:
if isinstance(el, dict):
yield from flat(key, el)
if __name__ == "__main__":
pass

220
app/ui/backup.py Normal file
View File

@@ -0,0 +1,220 @@
import os
import shutil
import tempfile
import time
import zipfile
from datetime import datetime
from enum import Enum
from app.commons import run_idle
from app.settings import SettingsType
from app.ui.dialogs import show_dialog, DialogType
from app.ui.main_helper import append_text_to_tview
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey
class RestoreType(Enum):
BOUQUETS = 0
ALL = 1
class BackupDialog:
def __init__(self, transient, settings, callback):
handlers = {"on_restore_bouquets": self.on_restore_bouquets,
"on_restore_all": self.on_restore_all,
"on_remove": self.on_remove,
"on_view_popup_menu": self.on_view_popup_menu,
"on_info_button_toggled": self.on_info_button_toggled,
"on_info_bar_close": self.on_info_bar_close,
"on_cursor_changed": self.on_cursor_changed,
"on_resize": self.on_resize,
"on_key_release": self.on_key_release}
builder = Gtk.Builder()
builder.set_translation_domain("demon-editor")
builder.add_from_file(UI_RESOURCES_PATH + "backup_dialog.glade")
builder.connect_signals(handlers)
self._settings = settings
self._s_type = settings.setting_type
self._data_path = self._settings.data_local_path
self._backup_path = self._settings.backup_local_path or self._data_path + "backup/"
self._open_data_callback = callback
self._dialog_window = builder.get_object("dialog_window")
self._dialog_window.set_transient_for(transient)
self._model = builder.get_object("main_list_store")
self._main_view = builder.get_object("main_view")
self._text_view = builder.get_object("text_view")
self._text_view_scrolled_window = builder.get_object("text_view_scrolled_window")
self._info_check_button = builder.get_object("info_check_button")
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("message_label")
# Setting the last size of the dialog window if it was saved
window_size = self._settings.get("backup_tool_window_size")
if window_size:
self._dialog_window.resize(*window_size)
self.init_data()
def show(self):
self._dialog_window.show()
def init_data(self):
try:
files = os.listdir(self._backup_path)
except FileNotFoundError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
for file in filter(lambda x: x.endswith(".zip"), files):
self._model.append((file.rstrip(".zip"), False))
def on_restore_bouquets(self, item):
self.restore(RestoreType.BOUQUETS)
def on_restore_all(self, item):
self.restore(RestoreType.ALL)
def on_remove(self, item):
model, paths = self._main_view.get_selection().get_selected_rows()
if not paths:
show_dialog(DialogType.ERROR, self._dialog_window, "No selected item!")
return
if show_dialog(DialogType.QUESTION, self._dialog_window) == Gtk.ResponseType.CANCEL:
return
itrs_to_delete = []
try:
for itr in map(model.get_iter, paths):
file_name = model.get_value(itr, 0)
os.remove("{}{}{}".format(self._backup_path, file_name, ".zip"))
itrs_to_delete.append(itr)
except FileNotFoundError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
list(map(model.remove, itrs_to_delete))
def on_view_popup_menu(self, menu, event):
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_SECONDARY:
menu.popup(None, None, None, None, event.button, event.time)
def on_info_button_toggled(self, button):
active = button.get_active()
self._text_view_scrolled_window.set_visible(active)
if active:
self.on_cursor_changed(self._main_view)
@run_idle
def show_info_message(self, text, message_type):
self._info_bar.set_visible(True)
self._info_bar.set_message_type(message_type)
self._message_label.set_text(text)
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
def on_cursor_changed(self, view):
if not self._info_check_button.get_active():
return
model, paths = view.get_selection().get_selected_rows()
if paths:
try:
file_name = self._backup_path + model.get_value(model.get_iter(paths[0]), 0) + ".zip"
created = time.ctime(os.path.getctime(file_name))
self._text_view.get_buffer().set_text(
"Created: {}\n********** Files: **********\n".format(created))
with zipfile.ZipFile(file_name) as zip_file:
for name in zip_file.namelist():
append_text_to_tview(name + "\n", self._text_view)
except FileNotFoundError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
def restore(self, restore_type):
model, paths = self._main_view.get_selection().get_selected_rows()
if not paths:
show_dialog(DialogType.ERROR, self._dialog_window, "No selected item!")
return
if len(paths) > 1:
show_dialog(DialogType.ERROR, self._dialog_window, "Please, select only one item!")
return
if show_dialog(DialogType.QUESTION, self._dialog_window) == Gtk.ResponseType.CANCEL:
return
file_name = model.get_value(model.get_iter(paths[0]), 0)
full_file_name = self._backup_path + file_name + ".zip"
try:
if restore_type is RestoreType.ALL:
clear_data_path(self._data_path)
shutil.unpack_archive(full_file_name, self._data_path)
elif restore_type is RestoreType.BOUQUETS:
tmp_dir = tempfile.gettempdir() + "/" + file_name
cond = (".tv", ".radio") if self._s_type is SettingsType.ENIGMA_2 else "bouquets.xml"
shutil.unpack_archive(full_file_name, tmp_dir)
for file in filter(lambda f: f.endswith(cond), os.listdir(self._data_path)):
os.remove(os.path.join(self._data_path, file))
for file in filter(lambda f: f.endswith(cond), os.listdir(tmp_dir)):
shutil.move(os.path.join(tmp_dir, file), self._data_path + file)
shutil.rmtree(tmp_dir)
except FileNotFoundError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
self.show_info_message("Done!", Gtk.MessageType.INFO)
self._open_data_callback(self._data_path)
def on_resize(self, window):
if self._settings:
self._settings.add("backup_tool_window_size", window.get_size())
def on_key_release(self, view, event):
""" Handling keystrokes """
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):
return
key = KeyboardKey(key_code)
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
if key is KeyboardKey.DELETE:
self.on_remove(view)
elif ctrl and key is KeyboardKey.E:
self.restore(RestoreType.ALL)
elif ctrl and key is KeyboardKey.R:
self.restore(RestoreType.BOUQUETS)
def backup_data(path, backup_path, move=True):
""" Creating data backup from a folder at the specified path
Returns full path to the compressed file.
"""
backup_path = "{}{}/".format(backup_path, datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
os.makedirs(os.path.dirname(path), exist_ok=True)
# backup files in data dir(skipping dirs and satellites.xml)
for file in filter(lambda f: f != "satellites.xml" and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
src, dst = os.path.join(path, file), backup_path + file
shutil.move(src, dst) if move else shutil.copy(src, dst)
# compressing to zip and delete remaining files
zip_file = shutil.make_archive(backup_path, "zip", backup_path)
shutil.rmtree(backup_path)
return zip_file
def restore_data(src, dst):
""" Unpacks backup data. """
clear_data_path(dst)
shutil.unpack_archive(src, dst)
def clear_data_path(path):
""" Clearing data at the specified path excluding satellites.xml file """
for file in filter(lambda f: f != "satellites.xml" and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
os.remove(os.path.join(path, file))
if __name__ == "__main__":
pass

355
app/ui/backup_dialog.glade Normal file
View File

@@ -0,0 +1,355 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1
The MIT License (MIT)
Copyright (c) 2018 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Author: Dmitriy Yefremov
-->
<interface>
<requires lib="gtk+" version="3.16"/>
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="main_list_store">
<columns>
<!-- column-name date -->
<column type="gchararray"/>
<!-- column-name selected -->
<column type="gboolean"/>
</columns>
</object>
<object class="GtkWindow" id="dialog_window">
<property name="width_request">560</property>
<property name="height_request">320</property>
<property name="can_focus">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="destroy_with_parent">True</property>
<property name="icon_name">document-revert</property>
<property name="gravity">center</property>
<signal name="check-resize" handler="on_resize" swapped="no"/>
<child type="titlebar">
<object class="GtkHeaderBar" id="header_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Backups</property>
<property name="spacing">2</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkBox" id="header_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkButton" id="restore_bouquets_header_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Restore bouquets</property>
<signal name="clicked" handler="on_restore_bouquets" swapped="no"/>
<child>
<object class="GtkImage" id="restore_bouquets_header_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-revert</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="restore_all_header_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Restore all</property>
<signal name="clicked" handler="on_restore_all" swapped="no"/>
<child>
<object class="GtkImage" id="restore_all_header_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-select-all</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="remove_header_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Remove</property>
<signal name="clicked" handler="on_remove" swapped="no"/>
<child>
<object class="GtkImage" id="remove_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">user-trash</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkCheckButton" id="info_check_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">False</property>
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
<child>
<object class="GtkImage" id="info_check_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-info</property>
</object>
</child>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="main_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkPaned" id="main_paned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="wide_handle">True</property>
<child>
<object class="GtkScrolledWindow" id="main_view_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="main_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="model">main_list_store</property>
<property name="headers_visible">False</property>
<property name="search_column">0</property>
<property name="rubber_banding">True</property>
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
<signal name="key-release-event" handler="on_key_release" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="main_view_selection">
<property name="mode">multiple</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="backup_date_column">
<property name="title" translatable="yes">Backup</property>
<property name="clickable">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">0</property>
<child>
<object class="GtkCellRendererText" id="date_render">
<property name="xpad">10</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="text_view_scrolled_window">
<property name="can_focus">False</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="text_view">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="pixels_above_lines">5</property>
<property name="editable">False</property>
<property name="left_margin">10</property>
<property name="right_margin">10</property>
<property name="indent">10</property>
<property name="cursor_visible">False</property>
<property name="accepts_tab">False</property>
</object>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="can_focus">False</property>
<property name="show_close_button">True</property>
<signal name="response" handler="on_info_bar_close" swapped="no"/>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkLabel" id="message_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">message</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkImage" id="restore_popup_menu_item_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-revert-to-saved</property>
</object>
<object class="GtkImage" id="restore_popup_menu_item_image2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-select-all</property>
</object>
<object class="GtkMenu" id="popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="restore_bouquets_popup_menu_item">
<property name="label" translatable="yes">Restore bouquets</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">restore_popup_menu_item_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_restore_bouquets" swapped="no"/>
<accelerator key="r" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="restore_all_popup_menu_item">
<property name="label" translatable="yes">Restore all</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">restore_popup_menu_item_image2</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_restore_all" swapped="no"/>
<accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="popup_menu_separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="remove_popup_menu_item">
<property name="label">gtk-remove</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_remove" swapped="no"/>
<accelerator key="Delete" signal="activate"/>
</object>
</child>
</object>
</interface>

View File

@@ -40,8 +40,8 @@ Author: Dmitriy Yefremov
<property name="icon_name">system-help</property>
<property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property>
<property name="version">0.4.2 Pre-alpha</property>
<property name="copyright">2018 Dmitriy Yefremov
<property name="version">0.4.7 Pre-alpha</property>
<property name="copyright">2018-2020 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellites list editor for GNU/Linux</property>
<property name="website">https://dyefremov.github.io/DemonEditor/</property>
@@ -50,7 +50,8 @@ Author: Dmitriy Yefremov
<property name="authors">Dmitriy Yefremov
</property>
<property name="translator_credits" translatable="yes">translator-credits</property>
<property name="logo_icon_name">accessories-text-editor</property>
<property name="artists">Program logo: &lt;a href="http://ihad.tv"&gt; mfgeg&lt;/a&gt;</property>
<property name="logo_icon_name">demon-editor</property>
<property name="wrap_license">True</property>
<property name="license_type">mit-x11</property>
<child>
@@ -75,121 +76,57 @@ Author: Dmitriy Yefremov
</object>
</child>
</object>
<object class="GtkMessageDialog" id="error_dialog">
<property name="width_request">320</property>
<object class="GtkDialog" id="input_dialog">
<property name="use-header-bar">{use_header}</property>
<property name="title" translatable="yes">Transponder</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">{title}</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center</property>
<property name="default_width">320</property>
<property name="default_height">240</property>
<property name="destroy_with_parent">True</property>
<property name="icon_name">accessories-text-editor</property>
<property name="type_hint">dialog</property>
<property name="message_type">error</property>
<property name="buttons">ok</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="error_dialog_vbox">
<property name="can_focus">False</property>
<property name="resize_mode">immediate</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="messagedialog-action_area8">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="gravity">center</property>
<child type="action">
<object class="GtkButton" id="input_dialog_cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
</child>
</object>
<object class="GtkDialog" id="input_dialog">
<property name="can_focus">False</property>
<property name="title"> </property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="destroy_with_parent">True</property>
<property name="icon_name">gtk-edit</property>
<property name="type_hint">dialog</property>
<child type="titlebar">
<placeholder/>
<child type="action">
<object class="GtkButton" id="input_dialog_ok_button">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="input_dialog_vbox">
<property name="width_request">320</property>
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog-action_area2">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button3">
<property name="label">gtk-undo</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button4">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="input_dialog_box">
<object class="GtkEntry" id="input_entry">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkEntry" id="input_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="secondary_icon_sensitive">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<property name="can_focus">True</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="secondary_icon_sensitive">False</property>
</object>
<packing>
<property name="expand">False</property>
@@ -200,108 +137,10 @@ Author: Dmitriy Yefremov
</object>
</child>
<action-widgets>
<action-widget response="-6">button3</action-widget>
<action-widget response="-5">button4</action-widget>
<action-widget response="cancel">input_dialog_cancel_button</action-widget>
<action-widget response="ok">input_dialog_ok_button</action-widget>
</action-widgets>
</object>
<object class="GtkFileChooserDialog" id="path_chooser_dialog">
<property name="can_focus">False</property>
<property name="title" translatable="yes"> </property>
<property name="modal">True</property>
<property name="destroy_with_parent">True</property>
<property name="icon_name">document-open</property>
<property name="type_hint">dialog</property>
<property name="action">select-folder</property>
<property name="do_overwrite_confirmation">True</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="filechooser_dialog_vbox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="filechooser_dialog_action_area">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button2">
<property name="label">gtk-undo</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button1">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">button2</action-widget>
<action-widget response="-12">button1</action-widget>
</action-widgets>
</object>
<object class="GtkMessageDialog" id="question_dialog">
<property name="width_request">320</property>
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="default_width">320</property>
<property name="default_height">240</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="message_type">question</property>
<property name="buttons">ok-cancel</property>
<property name="text" translatable="yes">Are you sure?</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="question_dialog_vbox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="messagedialog-action_area">
<property name="can_focus">False</property>
<property name="homogeneous">True</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkDialog" id="wait_dialog">
<property name="can_focus">False</property>
<property name="resizable">False</property>
@@ -316,8 +155,8 @@ Author: Dmitriy Yefremov
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox4">
<property name="width_request">118</property>
<object class="GtkBox" id="wait_dialog_vbox">
<property name="width_request">120</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child internal-child="action_area">

View File

@@ -1,9 +1,32 @@
""" Common module for showing dialogs """
import locale
from enum import Enum
from functools import lru_cache
from app.commons import run_idle
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN, IS_GNOME_SESSION
class Dialog(Enum):
MESSAGE = """
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.16"/>
<object class="GtkMessageDialog" id="message_dialog">
<property name="use-header-bar">{use_header}</property>
<property name="can_focus">False</property>
<property name="modal">True</property>
<property name="default_width">320</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="gravity">center</property>
<property name="message_type">{message_type}</property>
<property name="buttons">{buttons_type}</property>
</object>
</interface>
"""
class Action(Enum):
@@ -12,12 +35,16 @@ class Action(Enum):
class DialogType(Enum):
INPUT = "input_dialog"
CHOOSER = "path_chooser_dialog"
ERROR = "error_dialog"
QUESTION = "question_dialog"
ABOUT = "about_dialog"
WAIT = "wait_dialog"
INPUT = "input"
CHOOSER = "chooser"
ERROR = "error"
QUESTION = "question"
INFO = "info"
ABOUT = "about"
WAIT = "wait"
def __str__(self):
return self.value
class WaitDialog:
@@ -40,74 +67,110 @@ class WaitDialog:
self._dialog.destroy()
def show_dialog(dialog_type: DialogType, transient, text=None, options=None, action_type=None, file_filter=None):
def show_dialog(dialog_type: DialogType, transient, text=None, settings=None, action_type=None, file_filter=None):
""" Shows dialogs by name """
builder, dialog = get_dialog_from_xml(dialog_type, transient)
if dialog_type is DialogType.CHOOSER and options:
if action_type is not None:
dialog.set_action(action_type)
if file_filter is not None:
dialog.add_filter(file_filter)
path = options.get("data_dir_path")
dialog.set_current_folder(path)
response = dialog.run()
if response == -12: # -12 for fix assertion 'gtk_widget_get_can_default (widget)' failed
if dialog.get_filename():
path = dialog.get_filename()
if action_type is not Gtk.FileChooserAction.OPEN:
path = path + "/"
response = path
dialog.destroy()
return response
if dialog_type is DialogType.INPUT:
entry = builder.get_object("input_entry")
entry.set_text(text if text else "")
response = dialog.run()
txt = entry.get_text()
dialog.destroy()
return txt if response == Gtk.ResponseType.OK else Gtk.ResponseType.CANCEL
if text:
dialog.set_markup(get_message(text))
response = dialog.run()
dialog.destroy()
return response
if dialog_type in (DialogType.INFO, DialogType.ERROR):
return get_message_dialog(transient, dialog_type, Gtk.ButtonsType.OK, text)
elif dialog_type is DialogType.CHOOSER and settings:
return get_file_chooser_dialog(transient, text, settings, action_type, file_filter)
elif dialog_type is DialogType.INPUT:
return get_input_dialog(transient, text)
elif dialog_type is DialogType.QUESTION:
return get_message_dialog(transient, DialogType.QUESTION, Gtk.ButtonsType.OK_CANCEL, text or "Are you sure?")
elif dialog_type is DialogType.ABOUT:
return get_about_dialog(transient)
def get_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):
def get_chooser_dialog(transient, settings, 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,
settings=settings,
action_type=Gtk.FileChooserAction.OPEN,
file_filter=file_filter)
def get_file_chooser_dialog(transient, text, settings, action_type, file_filter):
dialog = Gtk.FileChooserDialog(get_message(text) if text else "", transient,
action_type if action_type is not None else Gtk.FileChooserAction.SELECT_FOLDER,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK),
use_header_bar=IS_GNOME_SESSION)
if file_filter is not None:
dialog.add_filter(file_filter)
path = settings.data_local_path
dialog.set_current_folder(path)
response = dialog.run()
if response == Gtk.ResponseType.OK:
if dialog.get_filename():
path = dialog.get_filename()
if action_type is not Gtk.FileChooserAction.OPEN:
path = path + "/"
response = path
dialog.destroy()
return response
def get_input_dialog(transient, text):
builder, dialog = get_dialog_from_xml(DialogType.INPUT, transient, use_header=IS_GNOME_SESSION)
entry = builder.get_object("input_entry")
entry.set_text(text if text else "")
response = dialog.run()
txt = entry.get_text()
dialog.destroy()
return txt if response == Gtk.ResponseType.OK else Gtk.ResponseType.CANCEL
def get_message_dialog(transient, message_type, buttons_type, text):
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
dialog_str = Dialog.MESSAGE.value.format(use_header=0, message_type=message_type, buttons_type=int(buttons_type))
builder.add_from_string(dialog_str)
dialog = builder.get_object("message_dialog")
dialog.set_transient_for(transient)
dialog.set_markup(get_message(text))
response = dialog.run()
dialog.destroy()
return response
def get_about_dialog(transient):
builder, dialog = get_dialog_from_xml(DialogType.ABOUT, transient)
dialog.set_transient_for(transient)
response = dialog.run()
dialog.destroy()
return response
def get_dialog_from_xml(dialog_type, transient, use_header=0, title=""):
dialog_name = dialog_type.value + "_dialog"
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
dialog_str = get_dialogs_string(UI_RESOURCES_PATH + "dialogs.glade").format(use_header=use_header, title=title)
builder.add_objects_from_string(dialog_str, (dialog_name,))
dialog = builder.get_object(dialog_name)
dialog.set_transient_for(transient)
return builder, dialog
def get_message(message):
""" returns translated message """
return locale.dgettext(TEXT_DOMAIN, message)
@lru_cache(maxsize=5)
def get_dialogs_string(path):
with open(path, "r") as f:
return "".join(f)
if __name__ == "__main__":
pass

View File

@@ -26,7 +26,7 @@ THE SOFTWARE.
Author: Dmitriy Yefremov
-->
<interface>
<interface domain="demon-editor">
<requires lib="gtk+" version="3.16"/>
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
@@ -47,6 +47,7 @@ Author: Dmitriy Yefremov
<object class="GtkHeaderBar" id="header_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">FTP-transfer</property>
<property name="spacing">5</property>
<property name="show_close_button">True</property>
<child>
@@ -56,7 +57,6 @@ Author: Dmitriy Yefremov
<property name="spacing">2</property>
<child>
<object class="GtkButton" id="receive_button">
<property name="width_request">48</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
@@ -78,7 +78,6 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkButton" id="send_button">
<property name="width_request">48</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
@@ -100,120 +99,13 @@ Author: Dmitriy Yefremov
</child>
</object>
</child>
<child type="title">
<object class="GtkBox" id="header_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="margin_bottom">2</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="header_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">FTP-transfer</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="header_data_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="label10">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</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="all_radio_button">
<property name="label" translatable="yes">All</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">satellites_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="bouquets_radio_button">
<property name="label" translatable="yes">Bouquets</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">satellites_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="satellites_radio_button">
<property name="label" translatable="yes">Satellites</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">all_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="webtv_radio_button">
<property name="label" translatable="yes">WebTV</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">all_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="options_button">
<property name="width_request">48</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Options</property>
<signal name="clicked" handler="on_preferences" swapped="no"/>
<signal name="clicked" handler="on_settings" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
@@ -238,13 +130,150 @@ Author: Dmitriy Yefremov
<property name="margin_bottom">1</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkBox" id="selection_data_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="margin_top">10</property>
<property name="margin_bottom">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="label10">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</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="all_radio_button">
<property name="label" translatable="yes">All</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">satellites_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="bouquets_radio_button">
<property name="label" translatable="yes">Bouquets</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">satellites_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="satellites_radio_button">
<property name="label" translatable="yes">Satellites</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">all_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="webtv_radio_button">
<property name="label" translatable="yes">WebTV</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">all_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Profile:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="profile_combo_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="focus_on_click">False</property>
<property name="active">0</property>
<property name="has_frame">False</property>
<property name="has_entry">True</property>
<signal name="changed" handler="on_profile_changed" swapped="no"/>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can_focus">True</property>
<property name="has_tooltip">True</property>
<property name="editable">False</property>
<property name="has_frame">False</property>
<property name="max_width_chars">9</property>
</object>
</child>
</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>
<child>
<object class="GtkFrame" id="main_settings_box_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
@@ -253,6 +282,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<child>
@@ -319,7 +349,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="position">1</property>
</packing>
</child>
<child>
@@ -398,7 +428,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="position">2</property>
</packing>
</child>
<child>
@@ -590,7 +620,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">3</property>
</packing>
</child>
<child>
@@ -629,7 +659,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
<property name="position">4</property>
</packing>
</child>
<child>
@@ -685,9 +715,12 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
<property name="position">5</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>

View File

@@ -1,29 +1,32 @@
import os
from gi.repository import GLib
from app.commons import run_idle, run_task
from app.connections import download_data, DownloadType, upload_data
from app.properties import Profile, get_config
from app.settings import SettingsType
from app.ui.backup import backup_data, restore_data
from app.ui.main_helper import append_text_to_tview
from app.ui.settings_dialog import show_settings_dialog
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .dialogs import show_dialog, DialogType, get_message
from .uicommons import Gtk, UI_RESOURCES_PATH
class DownloadDialog:
def __init__(self, transient, properties, open_data_callback, profile=Profile.ENIGMA_2):
self._profile_properties = properties.get(profile.value)
self._properties = properties
def __init__(self, transient, settings, open_data_callback, update_settings_callback):
self._s_type = settings.setting_type
self._settings = settings
self._open_data_callback = open_data_callback
self._profile = profile
self._update_settings_callback = update_settings_callback
handlers = {"on_receive": self.on_receive,
"on_send": self.on_send,
"on_settings_button": self.on_settings_button,
"on_preferences": self.on_preferences,
"on_settings": self.on_settings,
"on_profile_changed": self.on_profile_changed,
"on_info_bar_close": self.on_info_bar_close}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_from_file(UI_RESOURCES_PATH + "download_dialog.glade")
builder.connect_signals(handlers)
@@ -34,7 +37,6 @@ class DownloadDialog:
self._message_label = builder.get_object("info_bar_message_label")
self._text_view = builder.get_object("text_view")
self._expander = builder.get_object("expander")
self._host_entry = builder.get_object("host_entry")
self._data_path_entry = builder.get_object("data_path_entry")
self._remove_unused_check_button = builder.get_object("remove_unused_check_button")
@@ -49,20 +51,34 @@ class DownloadDialog:
self._timeout_entry = builder.get_object("timeout_entry")
self._settings_buttons_box = builder.get_object("settings_buttons_box")
self._use_http_switch = builder.get_object("use_http_switch")
self.init_properties()
self._http_radio_button = builder.get_object("http_radio_button")
self._use_http_box = builder.get_object("use_http_box")
self._profile_combo_box = builder.get_object("profile_combo_box")
if profile is Profile.NEUTRINO_MP:
self._webtv_radio_button.set_visible(True)
builder.get_object("http_radio_button").set_visible(False)
builder.get_object("use_http_box").set_visible(False)
self._use_http_switch.set_active(False)
self.init_settings()
def show(self):
self._dialog_window.show()
def init_properties(self):
self._host_entry.set_text(self._profile_properties["host"])
self._data_path_entry.set_text(self._profile_properties["data_dir_path"])
def init_settings(self):
self.update_profiles()
self.init_ui_settings()
@run_idle
def init_ui_settings(self):
self._host_entry.set_text(self._settings.host)
self._data_path_entry.set_text(self._settings.data_local_path)
is_enigma = self._s_type is SettingsType.ENIGMA_2
self._webtv_radio_button.set_visible(not is_enigma)
self._http_radio_button.set_visible(is_enigma)
self._use_http_box.set_visible(is_enigma)
self._use_http_switch.set_active(is_enigma)
def update_profiles(self):
self._profile_combo_box.remove_all()
for p in self._settings.profiles:
self._profile_combo_box.append(p, p)
self._profile_combo_box.set_active_id(self._settings.current_profile)
@run_idle
def on_receive(self, item):
@@ -80,7 +96,7 @@ class DownloadDialog:
elif self._satellites_radio_button.get_active():
download_type = DownloadType.SATELLITES
elif self._webtv_radio_button.get_active():
download_type = DownloadType.WEB_TV
download_type = DownloadType.WEBTV
return download_type
def destroy(self):
@@ -90,31 +106,42 @@ class DownloadDialog:
if button.get_active():
label = button.get_label()
if label == "Telnet":
self._login_entry.set_text(self._profile_properties.get("telnet_user", ""))
self._password_entry.set_text(self._profile_properties.get("telnet_password", ""))
self._port_entry.set_text(self._profile_properties.get("telnet_port", ""))
self._timeout_entry.set_text(str(self._profile_properties.get("telnet_timeout", 0)))
self._login_entry.set_text(self._settings.telnet_user)
self._password_entry.set_text(self._settings.telnet_password)
self._port_entry.set_text(self._settings.telnet_port)
self._timeout_entry.set_text(str(self._settings.telnet_timeout))
elif label == "HTTP":
self._login_entry.set_text(self._profile_properties.get("http_user", "root"))
self._password_entry.set_text(self._profile_properties.get("http_password", ""))
self._port_entry.set_text(self._profile_properties.get("http_port", ""))
self._timeout_entry.set_text(str(self._profile_properties.get("http_timeout", 0)))
self._login_entry.set_text(self._settings.http_user)
self._password_entry.set_text(self._settings.http_password)
self._port_entry.set_text(self._settings.http_port)
self._timeout_entry.set_text(str(self._settings.http_timeout))
elif label == "FTP":
self._login_entry.set_text(self._profile_properties.get("user", ""))
self._password_entry.set_text(self._profile_properties.get("password", ""))
self._port_entry.set_text(self._profile_properties.get("port", ""))
self._login_entry.set_text(self._settings.user)
self._password_entry.set_text(self._settings.password)
self._port_entry.set_text(self._settings.port)
self._timeout_entry.set_text("")
self._current_property = label
def on_preferences(self, item):
show_settings_dialog(self._dialog_window, self._properties)
self._profile_properties = get_config().get(self._profile.value)
def on_settings(self, item):
response = show_settings_dialog(self._dialog_window, self._settings)
if response != Gtk.ResponseType.CANCEL:
self._s_type = self._settings.setting_type
self.update_profiles()
gen = self._update_settings_callback()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
for button in self._settings_buttons_box.get_children():
if button.get_active():
self.on_settings_button(button)
self.init_properties()
break
for button in self._settings_buttons_box.get_children():
if button.get_active():
self.on_settings_button(button)
break
def on_profile_changed(self, box):
active = box.get_active_text()
if active in self._settings.profiles:
self._settings.current_profile = active
self._profile_combo_box.set_active_id(active)
self._s_type = self._settings.setting_type
self.init_ui_settings()
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@@ -122,24 +149,32 @@ class DownloadDialog:
@run_task
def download(self, download, d_type):
""" Download/upload data from/to receiver """
try:
self._expander.set_expanded(True)
self.clear_output()
self._expander.set_expanded(True)
self.clear_output()
backup, backup_src, data_path = self._settings.backup_before_downloading, None, None
try:
if download:
download_data(properties=self._profile_properties, download_type=d_type, callback=self.append_output)
if backup and d_type is not DownloadType.SATELLITES:
data_path = self._settings.data_local_path or self._data_path_entry.get_text()
os.makedirs(os.path.dirname(data_path), exist_ok=True)
backup_path = self._settings.backup_local_path or data_path + "backup/"
backup_src = backup_data(data_path, backup_path, d_type is DownloadType.ALL)
download_data(settings=self._settings, download_type=d_type, callback=self.append_output)
else:
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
upload_data(properties=self._profile_properties,
upload_data(settings=self._settings,
download_type=d_type,
remove_unused=self._remove_unused_check_button.get_active(),
profile=self._profile,
callback=self.append_output,
done_callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO),
use_http=self._use_http_switch.get_active())
except Exception as e:
message = str(getattr(e, "message", str(e)))
self.show_info_message(message, Gtk.MessageType.ERROR)
if all((download, backup, data_path)):
restore_data(backup_src, data_path)
else:
if download and d_type is not DownloadType.SATELLITES:
GLib.idle_add(self._open_data_callback)

1283
app/ui/epg_dialog.glade Normal file

File diff suppressed because it is too large Load Diff

549
app/ui/epg_dialog.py Normal file
View File

@@ -0,0 +1,549 @@
import gzip
import locale
import os
import re
import shutil
import urllib.request
from enum import Enum
from urllib.error import HTTPError, URLError
from gi.repository import GLib
from app.commons import run_idle
from app.connections import download_data, DownloadType
from app.eparser.ecommons import BouquetService, BqServiceType
from app.tools.epg import EPG, ChannelsParser
from app.ui.dialogs import get_message, show_dialog, DialogType
from .main_helper import on_popup_menu, update_entry_data
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, Column, EPG_ICON, KeyboardKey
class RefsSource(Enum):
SERVICES = 0
XML = 1
class EpgDialog:
def __init__(self, transient, settings, services, bouquet, fav_model, bouquet_name):
handlers = {"on_close_dialog": self.on_close_dialog,
"on_apply": self.on_apply,
"on_update": self.on_update,
"on_save_to_xml": self.on_save_to_xml,
"on_auto_configuration": self.on_auto_configuration,
"on_filter_toggled": self.on_filter_toggled,
"on_filter_changed": self.on_filter_changed,
"on_info_bar_close": self.on_info_bar_close,
"on_popup_menu": on_popup_menu,
"on_bouquet_popup_menu": self.on_bouquet_popup_menu,
"on_copy_ref": self.on_copy_ref,
"on_assign_ref": self.on_assign_ref,
"on_reset": self.on_reset,
"on_list_reset": self.on_list_reset,
"on_drag_begin": self.on_drag_begin,
"on_drag_data_get": self.on_drag_data_get,
"on_drag_data_received": self.on_drag_data_received,
"on_resize": self.on_resize,
"on_names_source_changed": self.on_names_source_changed,
"on_options_save": self.on_options_save,
"on_use_web_source_switch": self.on_use_web_source_switch,
"on_enable_filtering_switch": self.on_enable_filtering_switch,
"on_update_on_start_switch": self.on_update_on_start_switch,
"on_field_icon_press": self.on_field_icon_press,
"on_key_release": self.on_key_release}
self._services = {}
self._ex_services = services
self._ex_fav_model = fav_model
self._settings = settings
self._bouquet = bouquet
self._bouquet_name = bouquet_name
self._current_ref = []
self._enable_dat_filter = False
self._use_web_source = False
self._update_epg_data_on_start = False
self._refs_source = RefsSource.SERVICES
self._show_tooltips = True
self._download_xml_is_active = False
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_from_file(UI_RESOURCES_PATH + "epg_dialog.glade")
builder.connect_signals(handlers)
self._dialog = builder.get_object("epg_dialog_window")
self._dialog.set_transient_for(transient)
self._source_view = builder.get_object("source_view")
self._bouquet_view = builder.get_object("bouquet_view")
self._bouquet_model = builder.get_object("bouquet_list_store")
self._services_model = builder.get_object("services_list_store")
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
self._assign_ref_popup_item = builder.get_object("bouquet_assign_ref_popup_item")
self._left_header_box = builder.get_object("left_header_box")
self._xml_download_progress_bar = builder.get_object("xml_download_progress_bar")
# Filter
self._filter_bar = builder.get_object("filter_bar")
self._filter_entry = builder.get_object("filter_entry")
self._services_filter_model = builder.get_object("services_filter_model")
self._services_filter_model.set_visible_func(self.services_filter_function)
# Info
self._source_count_label = builder.get_object("source_count_label")
self._source_info_label = builder.get_object("source_info_label")
self._bouquet_count_label = builder.get_object("bouquet_count_label")
self._bouquet_epg_count_label = builder.get_object("bouquet_epg_count_label")
# Options
self._xml_radiobutton = builder.get_object("xml_radiobutton")
self._xml_chooser_button = builder.get_object("xml_chooser_button")
self._names_source_box = builder.get_object("names_source_box")
self._web_source_box = builder.get_object("web_source_box")
self._use_web_source_switch = builder.get_object("use_web_source_switch")
self._url_to_xml_entry = builder.get_object("url_to_xml_entry")
self._enable_filtering_switch = builder.get_object("enable_filtering_switch")
self._epg_dat_path_entry = builder.get_object("epg_dat_path_entry")
self._epg_dat_stb_path_entry = builder.get_object("epg_dat_stb_path_entry")
self._update_on_start_switch = builder.get_object("update_on_start_switch")
self._epg_dat_source_box = builder.get_object("epg_dat_source_box")
# Setting the last size of the dialog window
window_size = self._settings.get("epg_tool_window_size")
if window_size:
self._dialog.resize(*window_size)
self.init_drag_and_drop()
self.on_update()
def show(self):
self._dialog.show()
def on_close_dialog(self, window, event):
self._download_xml_is_active = False
@run_idle
def on_apply(self, item):
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
self._bouquet.clear()
list(map(self._bouquet.append, [r[Column.FAV_ID] for r in self._bouquet_model]))
for index, row in enumerate(self._ex_fav_model):
fav_id = self._bouquet[index]
row[Column.FAV_ID] = fav_id
if row[Column.FAV_TYPE] == BqServiceType.IPTV.name:
old_fav_id = self._services[fav_id]
srv = self._ex_services.pop(old_fav_id, None)
if srv:
self._ex_services[fav_id] = srv._replace(fav_id=fav_id)
self._dialog.destroy()
@run_idle
def on_update(self, item=None):
self.clear_data()
self.init_options()
gen = self.init_data()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def clear_data(self):
self._services_model.clear()
self._bouquet_model.clear()
self._services.clear()
self._source_info_label.set_text("")
self._bouquet_epg_count_label.set_text("")
self.on_info_bar_close()
def init_data(self):
gen = self.init_bouquet_data()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
refs = None
if self._enable_dat_filter:
if self._update_epg_data_on_start:
try:
self.download_epg_from_stb()
except OSError as e:
self.show_info_message("Download epg.dat file error: {}".format(e), Gtk.MessageType.ERROR)
return
yield True
try:
refs = EPG.get_epg_refs(self._epg_dat_path_entry.get_text() + "epg.dat")
except FileNotFoundError as e:
self.show_info_message("Read data error: {}".format(e), Gtk.MessageType.ERROR)
return
yield True
if self._refs_source is RefsSource.SERVICES:
self.init_lamedb_source(refs)
elif self._refs_source is RefsSource.XML:
xml_gen = self.init_xml_source(refs)
try:
yield from xml_gen
except ValueError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
self.show_info_message("Unknown names source!", Gtk.MessageType.ERROR)
yield True
def init_bouquet_data(self):
for r in self._ex_fav_model:
row = [*r[:]]
fav_id = r[Column.FAV_ID]
self._services[fav_id] = self._ex_services[fav_id].fav_id
yield self._bouquet_model.append(row)
self._bouquet_count_label.set_text(str(len(self._bouquet_model)))
yield True
def init_lamedb_source(self, refs):
srvs = {k[:k.rfind(":")]: v for k, v in self._ex_services.items()}
s_types = (BqServiceType.MARKER.value, BqServiceType.IPTV.value)
filtered = filter(None, [srvs.get(ref) for ref in refs]) if refs else filter(
lambda s: s.service_type not in s_types, self._ex_services.values())
list(map(self._services_model.append, map(lambda s: (s.service, s.fav_id), filtered)))
self.update_source_count_info()
def init_xml_source(self, refs):
path = self._epg_dat_path_entry.get_text() if self._use_web_source else self._xml_chooser_button.get_filename()
if not path:
self.show_info_message("The path to the xml file is not set!", Gtk.MessageType.ERROR)
return
if self._use_web_source:
# Downloading gzipped xml file that contains services names with references from the web.
self._download_xml_is_active = True
self.update_active_header_elements(False)
url = self._url_to_xml_entry.get_text()
try:
with urllib.request.urlopen(url, timeout=2) as fp:
headers = fp.info()
content_type = headers.get("Content-Type", "")
if content_type != "application/gzip":
self._download_xml_is_active = False
raise ValueError("{} {} {}".format(get_message("Download XML file error."),
get_message("Unsupported file type:"),
content_type))
file_name = os.path.basename(url)
data_path = self._epg_dat_path_entry.get_text()
with open(data_path + file_name, "wb") as tfp:
bs = 1024 * 8
size = -1
read = 0
b_num = 0
if "content-length" in headers:
size = int(headers["Content-Length"])
while self._download_xml_is_active:
block = fp.read(bs)
if not block:
break
read += len(block)
tfp.write(block)
b_num += 1
self.update_download_progress(b_num * bs / size)
yield True
path = tfp.name.rstrip(".gz")
except (HTTPError, URLError) as e:
raise ValueError("{} {}".format(get_message("Download XML file error."), e))
else:
try:
with open(path, "wb") as f_out:
with gzip.open(tfp.name, "rb") as f:
shutil.copyfileobj(f, f_out)
os.remove(tfp.name)
except Exception as e:
raise ValueError("{} {}".format(get_message("Unpacking data error."), e))
finally:
self._download_xml_is_active = False
self.update_active_header_elements(True)
try:
s_refs, info = ChannelsParser.get_refs_from_xml(path)
yield True
except Exception as e:
raise ValueError("{} {}".format(get_message("XML parsing error:"), e))
else:
if refs:
s_refs = filter(lambda x: x.num in refs, s_refs)
list(map(lambda s: self._services_model.append((s.name, s.data)), s_refs))
self.update_source_info(info)
self.update_source_count_info()
yield True
def on_key_release(self, view, event):
""" Handling keystrokes """
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):
return
key = KeyboardKey(key_code)
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
if ctrl and key is KeyboardKey.C:
self.on_copy_ref()
elif ctrl and key is KeyboardKey.V:
self.on_assign_ref()
@run_idle
def on_save_to_xml(self, item):
response = show_dialog(DialogType.CHOOSER, self._dialog, settings=self._settings)
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
services = []
iptv_types = (BqServiceType.IPTV.value, BqServiceType.MARKER.value)
for r in self._bouquet_model:
srv_type = r[Column.FAV_TYPE]
if srv_type in iptv_types:
srv = BouquetService(name=r[Column.FAV_SERVICE],
type=BqServiceType(srv_type),
data=r[Column.FAV_ID],
num=r[Column.FAV_NUM])
services.append(srv)
ChannelsParser.write_refs_to_xml("{}{}.xml".format(response, self._bouquet_name), services)
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
@run_idle
def on_auto_configuration(self, item):
""" Simple mapping of services by name. """
use_cyrillic = locale.getdefaultlocale()[0] in ("ru_RU", "be_BY", "uk_UA", "sr_RS")
tr = None
if use_cyrillic:
# may be not entirely correct
symbols = (u"АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯІÏҐЎЈЂЉЊЋЏTB",
u"ABVGDEEJZIJKLMNOPRSTUFHZCSS_Y_EUAIEGUEDLNCJTV")
tr = {ord(k): ord(v) for k, v in zip(*symbols)}
source = {}
for row in self._services_model:
name = re.sub("\\W+", "", str(row[0])).upper()
name = name.translate(tr) if use_cyrillic else name
source[name] = row[1]
success_count = 0
not_founded = {}
for r in self._bouquet_model:
if r[Column.FAV_TYPE] != BqServiceType.IPTV.value:
continue
name = re.sub("\\W+", "", str(r[Column.FAV_SERVICE])).upper()
if use_cyrillic:
name = name.translate(tr)
ref = source.get(name, None) # Not [pop], because the list may contain duplicates or similar names!
if ref:
self.assign_data(r, ref, True)
success_count += 1
else:
not_founded[name] = r
# Additional attempt to search in the remaining elements
for n in not_founded:
for k in source:
if k.startswith(n):
self.assign_data(not_founded[n], source[k], True)
success_count += 1
break
self.update_epg_count()
self.show_info_message("{} {} {}".format(get_message("Done!"),
get_message("Count of successfully configured services:"),
success_count), Gtk.MessageType.INFO)
def assign_data(self, row, ref, show_error=False):
if row[Column.FAV_TYPE] != BqServiceType.IPTV.value:
if not show_error:
self.show_info_message(get_message("Not allowed in this context!"), Gtk.MessageType.ERROR)
return
fav_id = row[Column.FAV_ID]
fav_id_data = fav_id.split(":")
fav_id_data[3:7] = ref.split(":")
new_fav_id = ":".join(fav_id_data)
service = self._services.pop(fav_id, None)
if service:
self._services[new_fav_id] = service
row[Column.FAV_ID] = new_fav_id
row[Column.FAV_LOCKED] = EPG_ICON
row[Column.FAV_TOOLTIP] = ":".join(fav_id_data[:10]) if self._show_tooltips else None
def on_filter_toggled(self, button: Gtk.ToggleButton):
self._filter_bar.set_search_mode(button.get_active())
def on_filter_changed(self, entry):
self._services_filter_model.refilter()
def services_filter_function(self, model, itr, data):
txt = self._filter_entry.get_text().upper()
return model is None or model == "None" or txt in model.get_value(itr, 0).upper()
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
def on_copy_ref(self, item=None):
model, paths = self._source_view.get_selection().get_selected_rows()
self._current_ref.clear()
if paths:
self._current_ref.append(model[paths][1])
def on_assign_ref(self, item=None):
if self._current_ref:
model, paths = self._bouquet_view.get_selection().get_selected_rows()
self.assign_data(model[paths], self._current_ref.pop())
self.update_epg_count()
@run_idle
def on_reset(self, item):
model, paths = self._bouquet_view.get_selection().get_selected_rows()
if paths:
row = self._bouquet_model[paths]
self.reset_row_data(row)
self.update_epg_count()
@run_idle
def on_list_reset(self, item):
list(map(self.reset_row_data, self._bouquet_model))
self.update_epg_count()
def reset_row_data(self, row):
default_fav_id = self._services.pop(row[Column.FAV_ID], None)
if default_fav_id:
self._services[default_fav_id] = default_fav_id
row[Column.FAV_ID], row[Column.FAV_LOCKED], row[Column.FAV_TOOLTIP] = default_fav_id, None, None
@run_idle
def show_info_message(self, text, message_type):
self._info_bar.set_visible(True)
self._info_bar.set_message_type(message_type)
self._message_label.set_text(text)
@run_idle
def update_source_info(self, info):
lines = info.split("\n")
self._source_info_label.set_text(lines[0] if lines else "")
self._source_view.set_tooltip_text(info)
@run_idle
def update_source_count_info(self):
source_count = len(self._services_model)
self._source_count_label.set_text(str(source_count))
if self._enable_dat_filter and source_count == 0:
msg = get_message("Current epg.dat file does not contains references for the services of this bouquet!")
self.show_info_message(msg, Gtk.MessageType.WARNING)
@run_idle
def update_epg_count(self):
count = len(list((filter(None, [r[Column.FAV_LOCKED] for r in self._bouquet_model]))))
self._bouquet_epg_count_label.set_text(str(count))
@run_idle
def update_active_header_elements(self, state):
self._left_header_box.set_sensitive(state)
self._xml_download_progress_bar.set_visible(not state)
self._source_info_label.set_text("" if state else "Downloading XML:")
@run_idle
def update_download_progress(self, value):
self._xml_download_progress_bar.set_fraction(value)
def on_bouquet_popup_menu(self, menu, event):
self._assign_ref_popup_item.set_sensitive(self._current_ref)
on_popup_menu(menu, event)
# ***************** Drag-and-drop *********************#
def init_drag_and_drop(self):
""" Enable drag-and-drop """
target = []
self._source_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, target, Gdk.DragAction.COPY)
self._source_view.drag_source_add_text_targets()
self._bouquet_view.enable_model_drag_dest(target, Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
self._bouquet_view.drag_dest_add_text_targets()
def on_drag_begin(self, view, context):
""" Selects a row under the cursor in the view at the dragging beginning. """
selection = view.get_selection()
if selection.count_selected_rows() > 1:
view.do_toggle_cursor_row(view)
def on_drag_data_get(self, view: Gtk.TreeView, drag_context, data, info, time):
model, paths = view.get_selection().get_selected_rows()
if paths:
val = model.get_value(model.get_iter(paths), 1)
data.set_text(val, -1)
def on_drag_data_received(self, view: Gtk.TreeView, drag_context, x, y, data, info, time):
path, pos = view.get_dest_row_at_pos(x, y)
model = view.get_model()
self.assign_data(model[path], data.get_text())
self.update_epg_count()
return False
# ***************** Options *********************#
def init_options(self):
epg_dat_path = self._settings.data_local_path + "epg/"
self._epg_dat_path_entry.set_text(epg_dat_path)
default_epg_data_stb_path = "/etc/enigma2"
epg_options = self._settings.get("epg_options")
if epg_options:
self._refs_source = RefsSource.XML if epg_options.get("xml_source", False) else RefsSource.SERVICES
self._xml_radiobutton.set_active(self._refs_source is RefsSource.XML)
self._use_web_source = epg_options.get("use_web_source", False)
self._use_web_source_switch.set_active(self._use_web_source)
self._url_to_xml_entry.set_text(epg_options.get("url_to_xml", ""))
self._enable_dat_filter = epg_options.get("enable_filtering", False)
self._enable_filtering_switch.set_active(self._enable_dat_filter)
epg_dat_path = epg_options.get("epg_dat_path", epg_dat_path)
self._epg_dat_path_entry.set_text(epg_dat_path)
self._epg_dat_stb_path_entry.set_text(epg_options.get("epg_dat_stb_path", default_epg_data_stb_path))
self._update_epg_data_on_start = epg_options.get("epg_data_update_on_start", False)
self._update_on_start_switch.set_active(self._update_epg_data_on_start)
local_xml_path = epg_options.get("local_path_to_xml", None)
if local_xml_path:
self._xml_chooser_button.set_filename(local_xml_path)
os.makedirs(os.path.dirname(self._epg_dat_path_entry.get_text()), exist_ok=True)
def on_options_save(self, item=None):
epg_options = {"xml_source": self._xml_radiobutton.get_active(),
"use_web_source": self._use_web_source_switch.get_active(),
"local_path_to_xml": self._xml_chooser_button.get_filename(),
"url_to_xml": self._url_to_xml_entry.get_text(),
"enable_filtering": self._enable_filtering_switch.get_active(),
"epg_dat_path": self._epg_dat_path_entry.get_text(),
"epg_dat_stb_path": self._epg_dat_stb_path_entry.get_text(),
"epg_data_update_on_start": self._update_on_start_switch.get_active()}
self._settings.add("epg_options", epg_options)
def on_resize(self, window):
if self._settings:
self._settings.add("epg_tool_window_size", window.get_size())
def on_names_source_changed(self, button):
self._refs_source = RefsSource.XML if button.get_active() else RefsSource.SERVICES
self._names_source_box.set_sensitive(button.get_active())
def on_enable_filtering_switch(self, switch, state):
self._epg_dat_source_box.set_sensitive(state)
self._update_on_start_switch.set_active(False if not state else self._update_epg_data_on_start)
def on_update_on_start_switch(self, switch, state):
pass
def on_use_web_source_switch(self, switch, state):
self._web_source_box.set_sensitive(state)
self._xml_chooser_button.set_sensitive(not state)
def on_field_icon_press(self, entry, icon, event_button):
update_entry_data(entry, self._dialog, self._settings)
# ***************** Downloads *********************#
def download_epg_from_stb(self):
""" Download the epg.dat file via ftp from the receiver. """
download_data(settings=self._settings, download_type=DownloadType.EPG, callback=print)
if __name__ == "__main__":
pass

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,634 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg1541"
version="1.1"
viewBox="0 0 16.743339 16.72816"
height="63.224556"
width="63.281971"
sodipodi:docname="demon-editor.svg"
inkscape:version="0.92.4 (unknown)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="716"
id="namedview127"
showgrid="true"
fit-margin-left="0"
inkscape:zoom="6.1714295"
inkscape:cx="40.088627"
inkscape:cy="31.742631"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1541"
fit-margin-top="0"
fit-margin-right="0"
fit-margin-bottom="0">
<inkscape:grid
type="xygrid"
id="grid128"
originx="-10.604603"
originy="-1.1727724" />
</sodipodi:namedview>
<title
id="title1088">DeamonEditor Icons</title>
<defs
id="defs1535">
<linearGradient
id="linearGradient2198">
<stop
id="stop2194"
style="stop-color:#ffb320;stop-opacity:1"
offset="0" />
<stop
id="stop2196"
style="stop-color:#e7ff00;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient2192">
<stop
offset="0"
style="stop-color:#ffb320;stop-opacity:1"
id="stop2188" />
<stop
offset="1"
style="stop-color:#b3c54c;stop-opacity:1"
id="stop2190" />
</linearGradient>
<linearGradient
id="linearGradient3700-8">
<stop
offset="0"
style="stop-color:#2e4f84;stop-opacity:1"
id="stop2183" />
<stop
offset="1"
style="stop-color:#4c77c5;stop-opacity:1"
id="stop2185" />
</linearGradient>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
spreadMethod="pad"
id="linearGradient1844">
<stop
style="stop-opacity:1;stop-color:#29282b"
offset="0"
id="stop1826" />
<stop
id="stop1828"
offset="0.13293758"
style="stop-color:#b5bdcf;stop-opacity:1" />
<stop
style="stop-opacity:1;stop-color:#92979f"
offset="0.21261224"
id="stop1832" />
<stop
style="stop-opacity:1;stop-color:#737881"
offset="0.29780689"
id="stop1834" />
<stop
style="stop-opacity:1;stop-color:#70757e"
offset="0.29780689"
id="stop1836" />
<stop
style="stop-opacity:1;stop-color:#e4e6e8"
offset="0.45395693"
id="stop1838" />
<stop
style="stop-opacity:1;stop-color:#696c76"
offset="0.71871042"
id="stop1840" />
<stop
style="stop-opacity:1;stop-color:#29282b"
offset="1"
id="stop1842" />
</linearGradient>
<linearGradient
id="linearGradient1754"
spreadMethod="pad"
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
gradientUnits="userSpaceOnUse"
y2="0"
x2="1"
y1="0"
x1="0">
<stop
id="stop1736"
offset="0"
style="stop-opacity:1;stop-color:#29282b" />
<stop
style="stop-color:#b5bdcf;stop-opacity:1"
offset="0.02582242"
id="stop1738" />
<stop
id="stop1740"
offset="0.14669065"
style="stop-opacity:1;stop-color:#868c95" />
<stop
id="stop1742"
offset="0.21261224"
style="stop-opacity:1;stop-color:#92979f" />
<stop
id="stop1744"
offset="0.29780689"
style="stop-opacity:1;stop-color:#737881" />
<stop
id="stop1746"
offset="0.29780689"
style="stop-opacity:1;stop-color:#70757e" />
<stop
id="stop1748"
offset="0.45395693"
style="stop-opacity:1;stop-color:#e4e6e8" />
<stop
id="stop1750"
offset="0.71871042"
style="stop-opacity:1;stop-color:#ffffff" />
<stop
id="stop1752"
offset="1"
style="stop-opacity:1;stop-color:#1d191a" />
</linearGradient>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
spreadMethod="pad"
id="linearGradient1606">
<stop
style="stop-opacity:1;stop-color:#29282b"
offset="0"
id="stop1590" />
<stop
id="stop1608"
offset="0.03065561"
style="stop-color:#b5bdcf;stop-opacity:1" />
<stop
style="stop-opacity:1;stop-color:#868c95"
offset="0.1125849"
id="stop1592" />
<stop
style="stop-opacity:1;stop-color:#92979f"
offset="0.13955873"
id="stop1594" />
<stop
style="stop-opacity:1;stop-color:#737881"
offset="0.1915853"
id="stop1596" />
<stop
style="stop-opacity:1;stop-color:#70757e"
offset="0.25400829"
id="stop1598" />
<stop
style="stop-opacity:1;stop-color:#e4e6e8"
offset="0.45395693"
id="stop1600" />
<stop
style="stop-opacity:1;stop-color:#ffffff"
offset="0.71871042"
id="stop1602" />
<stop
style="stop-opacity:1;stop-color:#1d191a"
offset="1"
id="stop1604" />
</linearGradient>
<linearGradient
id="linearGradient886-0">
<stop
style="stop-color:#535353;stop-opacity:1"
offset="0"
id="stop888" />
<stop
style="stop-color:#f0f0f0;stop-opacity:1"
offset="1"
id="stop890" />
</linearGradient>
<linearGradient
id="linearGradient1473">
<stop
id="stop1469"
offset="0"
style="stop-color:#ffffff;stop-opacity:0" />
<stop
id="stop1471"
offset="1"
style="stop-color:#ffffff;stop-opacity:0.96088022" />
</linearGradient>
<linearGradient
id="linearGradient2447-35">
<stop
id="stop2449"
offset="0"
style="stop-color:#00c62e;stop-opacity:1" />
<stop
id="stop2451"
offset="1"
style="stop-color:#136100;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient3795-1">
<stop
id="stop3797-1"
offset="0"
style="stop-color:#803400;stop-opacity:1" />
<stop
id="stop3799-3"
offset="1"
style="stop-color:#c87137;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient2447-3">
<stop
style="stop-color:#c60300;stop-opacity:1"
offset="0"
id="stop2443" />
<stop
style="stop-color:#c40e00;stop-opacity:1"
offset="1"
id="stop2445" />
</linearGradient>
<linearGradient
id="linearGradient2458">
<stop
id="stop2454"
offset="0"
style="stop-color:#c60300;stop-opacity:1" />
<stop
id="stop2456"
offset="1"
style="stop-color:#ee6000;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient3519">
<stop
id="stop3521"
offset="0"
style="stop-color:#1d2120;stop-opacity:1" />
<stop
id="stop3523"
offset="1"
style="stop-color:#545d5d;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient1446">
<stop
id="stop1442"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop1444"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient1547"
spreadMethod="pad"
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
gradientUnits="userSpaceOnUse"
y2="0"
x2="1"
y1="0"
x1="0">
<stop
id="stop1529"
offset="0"
style="stop-opacity:1;stop-color:#ffffff" />
<stop
id="stop1531"
offset="0.0263736"
style="stop-opacity:1;stop-color:#29282b" />
<stop
id="stop1533"
offset="0.263736"
style="stop-opacity:1;stop-color:#868c95" />
<stop
id="stop1535"
offset="0.395604"
style="stop-opacity:1;stop-color:#92979f" />
<stop
id="stop1537"
offset="0.39560401"
style="stop-opacity:1;stop-color:#737881" />
<stop
id="stop1539"
offset="0.42333773"
style="stop-opacity:1;stop-color:#70757e" />
<stop
id="stop1541"
offset="0.56268591"
style="stop-opacity:1;stop-color:#e4e6e8" />
<stop
id="stop1543"
offset="0.62400264"
style="stop-opacity:1;stop-color:#ffffff" />
<stop
id="stop1545"
offset="1"
style="stop-opacity:1;stop-color:#1d191a" />
</linearGradient>
<linearGradient
id="linearGradient1527"
spreadMethod="pad"
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
gradientUnits="userSpaceOnUse"
y2="0"
x2="1"
y1="0"
x1="0">
<stop
id="stop1509"
offset="0"
style="stop-opacity:1;stop-color:#ffffff" />
<stop
id="stop1511"
offset="0.0527472"
style="stop-opacity:1;stop-color:#29282b" />
<stop
id="stop1513"
offset="0.142147"
style="stop-opacity:1;stop-color:#868c95" />
<stop
id="stop1515"
offset="0.19700864"
style="stop-opacity:1;stop-color:#92979f" />
<stop
id="stop1517"
offset="0.25031137"
style="stop-opacity:1;stop-color:#737881" />
<stop
id="stop1519"
offset="0.3710371"
style="stop-opacity:1;stop-color:#70757e" />
<stop
id="stop1521"
offset="0.53961843"
style="stop-opacity:1;stop-color:#e4e6e8" />
<stop
id="stop1523"
offset="0.76283824"
style="stop-opacity:1;stop-color:#ffffff" />
<stop
id="stop1525"
offset="1"
style="stop-opacity:1;stop-color:#1d191a" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.26458333,0,0,0.26458333,-5.8254634,-78.732754)"
y2="1179.7145"
x2="66.791626"
y1="1188.7661"
x1="99.044022"
gradientUnits="userSpaceOnUse"
id="linearGradient1485"
xlink:href="#linearGradient1473" />
<linearGradient
gradientTransform="matrix(0.26458333,0,0,0.26458333,-2.2733744,-70.526334)"
gradientUnits="userSpaceOnUse"
y2="1155.8046"
x2="67.311417"
y1="1161.6112"
x1="77.19442"
id="linearGradient1448"
xlink:href="#linearGradient1446" />
<linearGradient
gradientTransform="matrix(0.26458333,0,0,0.26458333,-18.198747,-79.859424)"
gradientUnits="userSpaceOnUse"
y2="1186.8096"
x2="146.16808"
y1="1186.8096"
x1="131.86871"
id="linearGradient2180"
xlink:href="#linearGradient886-0" />
<linearGradient
gradientTransform="matrix(0.20863982,0,0,0.20863982,-0.63935937,-13.031239)"
gradientUnits="userSpaceOnUse"
y2="1184.73"
x2="101.19952"
y1="1184.73"
x1="83.066002"
id="linearGradient2160"
xlink:href="#linearGradient886-0" />
<linearGradient
gradientTransform="matrix(0.26458333,0,0,0.26458333,-6.2370714,-102.12414)"
gradientUnits="userSpaceOnUse"
y2="1267.1335"
x2="117.99127"
y1="1267.1335"
x1="75.853806"
id="linearGradient2131"
xlink:href="#linearGradient886-0" />
<linearGradient
id="linearGradient3700-8-3">
<stop
id="stop3702-1"
style="stop-color:#2e4f84;stop-opacity:1"
offset="0" />
<stop
id="stop3704-8"
style="stop-color:#4c77c5;stop-opacity:1"
offset="1" />
</linearGradient>
<defs
id="defs4922">
<filter
style="color-interpolation-filters:sRGB"
id="Adobe_OpacityMaskFilter"
filterUnits="userSpaceOnUse"
x="3.7850001"
y="4.6750002"
width="5.8829999"
height="73.013">
<feColorMatrix
type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
id="feColorMatrix4925" />
</filter>
</defs>
<mask
maskUnits="userSpaceOnUse"
x="3.785"
y="4.675"
width="5.883"
height="73.013"
id="SVGID_2_">
<g
style="filter:url(#Adobe_OpacityMaskFilter)"
id="g4928">
<linearGradient
id="SVGID_3_"
gradientUnits="userSpaceOnUse"
x1="3.7852001"
y1="41.181198"
x2="9.6680002"
y2="41.181198">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop4931" />
<stop
offset="0.0029"
style="stop-color:#FAFBFB"
id="stop4933" />
<stop
offset="0.0756"
style="stop-color:#BBBDBF"
id="stop4935" />
<stop
offset="0.1438"
style="stop-color:#898B8E"
id="stop4937" />
<stop
offset="0.2053"
style="stop-color:#646567"
id="stop4939" />
<stop
offset="0.259"
style="stop-color:#444446"
id="stop4941" />
<stop
offset="0.3028"
style="stop-color:#1D1C1D"
id="stop4943" />
<stop
offset="0.3313"
style="stop-color:#000000"
id="stop4945" />
</linearGradient>
<rect
style="fill:url(#SVGID_3_)"
x="3.7850001"
y="4.6750002"
width="5.8829999"
height="73.013"
id="rect4947" />
</g>
</mask>
<filter
style="color-interpolation-filters:sRGB"
id="filter1268"
filterUnits="userSpaceOnUse"
x="3.7850001"
y="4.6750002"
width="5.8829999"
height="73.013">
<feColorMatrix
type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
id="feColorMatrix1266" />
</filter>
<linearGradient
gradientUnits="userSpaceOnUse"
y2="203.22046"
x2="23.551136"
y1="203.22046"
x1="13.487289"
id="linearGradient1160"
xlink:href="#linearGradient3519" />
<clipPath
id="clipPath1890"
clipPathUnits="userSpaceOnUse">
<circle
style="opacity:1;fill:#2d2d2d;fill-opacity:1;stroke:#434242;stroke-width:0.0575568;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle1892"
cx="78.548424"
cy="-31.019459"
r="6.4721422"
transform="scale(1,-1)" />
</clipPath>
<linearGradient
gradientTransform="translate(-0.24389927,14.877856)"
y2="27.314217"
x2="84.864914"
y1="27.314217"
x1="72.164909"
gradientUnits="userSpaceOnUse"
id="linearGradient2134"
xlink:href="#linearGradient3700-8-3" />
</defs>
<metadata
id="metadata1538">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>DeamonEditor Icons</dc:title>
<dc:publisher>
<cc:Agent>
<dc:title>mfgeg</dc:title>
</cc:Agent>
</dc:publisher>
<dc:date>7.1.2020</dc:date>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="matrix(1.1690805,0,0,1.1690805,-14.929261,-167.45253)"
id="layer1">
<g
transform="translate(0.86526724,-82.691658)"
id="g1351">
<path
d="m 13.715358,227.65981 h 2.97616 v 12.57332 h -2.97616 z m 5.79826,-1.73376 -0.327076,0.42227 c -0.179879,0.23226 -0.586303,0.67932 -0.903223,0.99412 -0.480677,0.47726 -0.640897,0.57235 -0.967777,0.57235 -0.223557,0 -0.380895,-0.0584 -0.624024,-0.25067 v 3.18307 c 0.172884,-0.0214 0.359647,-0.0333 0.575071,-0.0352 0.68403,-0.006 0.91815,0.0416 1.436333,0.29581 1.14586,0.56274 1.762492,1.5589 1.768251,2.8566 0.0094,2.10836 -1.870896,3.55161 -3.779655,3.16529 v 3.10345 c 4.345352,0.0758 4.093104,-2.37537 6.573781,-2.25837 1.486139,0.0748 1.333421,0.16555 1.796225,-1.064 l 0.259834,-0.6913 -0.803704,-0.66493 c -0.960855,-0.79478 -1.303983,-1.29079 -1.205013,-1.74132 0.06631,-0.30189 1.20818,-1.50093 1.779011,-1.86778 0.240181,-0.1543 0.244255,-0.17878 0.09575,-0.59661 -0.08522,-0.23979 -0.259522,-0.65746 -0.386789,-0.92744 l -0.23132,-0.49115 h -1.412666 c -1.868582,0 -1.802386,0.068 -1.805368,-1.82472 l -0.0021,-1.43582 -0.917745,-0.37171 z"
style="opacity:1;fill:#000000;fill-opacity:0.5372549;stroke:none;stroke-width:0.18460207;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
id="path2133"
inkscape:connector-curvature="0" />
<path
id="path2106"
style="opacity:1;fill:url(#linearGradient2131);fill-opacity:1;stroke:none;stroke-width:0.17733108;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
d="m 13.832581,227.93117 h 2.858936 v 12.07807 h -2.858936 z m 5.569881,-1.6655 -0.314194,0.40566 c -0.172793,0.22309 -0.563209,0.65257 -0.867647,0.95496 -0.46174,0.45847 -0.615654,0.5498 -0.929658,0.5498 -0.214752,0 -0.365893,-0.056 -0.599446,-0.24079 v 3.05768 c 0.166077,-0.0206 0.345483,-0.0319 0.55242,-0.0338 0.657089,-0.006 0.881987,0.0399 1.379764,0.28416 1.100724,0.54057 1.693073,1.49747 1.698606,2.74408 0.009,2.02535 -1.797211,3.41174 -3.63079,3.04061 v 2.98122 c 4.174205,0.0728 3.931888,-2.28179 6.314859,-2.1694 1.427604,0.0718 1.2809,0.15902 1.725477,-1.02213 l 0.249597,-0.66408 -0.772046,-0.63871 c -0.923009,-0.76345 -1.252622,-1.23996 -1.157552,-1.67277 0.0637,-0.29001 1.160592,-1.44177 1.708939,-1.79419 0.230721,-0.14825 0.234635,-0.17174 0.09198,-0.57312 -0.08187,-0.23032 -0.249296,-0.63156 -0.371555,-0.89088 l -0.222207,-0.4718 h -1.357022 c -1.794983,0 -1.731396,0.0653 -1.734262,-1.75284 l -0.0021,-1.3793 -0.881603,-0.35705 z"
inkscape:connector-curvature="0" />
<path
id="path2151"
d="m 17.266983,230.95755 c -0.215637,0.003 -0.402408,0.014 -0.575465,0.0354 v 6.28854 c 1.910653,0.38671 3.792716,-1.05815 3.783336,-3.16865 -0.0058,-1.29897 -0.623064,-2.29597 -1.770059,-2.85927 -0.518699,-0.25451 -0.753103,-0.30234 -1.437812,-0.29607 z m 0.482341,1.17002 c 0.433747,-0.004 0.582202,0.0265 0.910784,0.18761 0.726595,0.35682 1.117604,0.98848 1.121256,1.81134 0.0059,1.33694 -1.186343,2.25211 -2.396695,2.00715 v -3.98359 c 0.109628,-0.0135 0.228054,-0.0214 0.364655,-0.0225 z"
style="opacity:1;fill:url(#linearGradient2160);fill-opacity:1;stroke:none;stroke-width:0.18478528;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
inkscape:connector-curvature="0" />
<path
style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient2180);stroke-width:0.18478528;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
d="m 17.266981,230.95753 c -0.215636,0.003 -0.402408,0.014 -0.575464,0.0354 v 6.28853 c 1.910652,0.38672 3.792715,-1.05815 3.783336,-3.16865 -0.0057,-1.29897 -0.623065,-2.29597 -1.77006,-2.85927 -0.518697,-0.2545 -0.753103,-0.30234 -1.437812,-0.29607 z m 0.482343,1.17001 c 0.433747,-0.004 0.5822,0.0265 0.910783,0.18762 0.726594,0.35681 1.117605,0.98848 1.121257,1.81133 0.006,1.33694 -1.186344,2.25211 -2.396697,2.00716 v -3.98359 c 0.109628,-0.0135 0.228055,-0.0214 0.364657,-0.0225 z"
id="path2170"
inkscape:connector-curvature="0" />
<path
id="path1422"
d="m 17.266983,230.95755 c -0.215637,0.003 -0.402408,0.014 -0.575464,0.0354 v 6.28854 c 1.910652,0.38671 3.792715,-1.05815 3.783335,-3.16865 -0.0057,-1.29897 -0.623064,-2.29597 -1.770059,-2.85927 -0.518699,-0.25451 -0.753103,-0.30234 -1.437812,-0.29607 z m 0.482342,1.17002 c 0.433748,-0.004 0.582201,0.0265 0.910784,0.18761 0.726594,0.35682 1.117605,0.98848 1.121257,1.81134 0.006,1.33694 -1.186344,2.25211 -2.396697,2.00715 v -3.98359 c 0.109628,-0.0135 0.228054,-0.0214 0.364656,-0.0225 z"
style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient1448);stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path1454"
d="m 19.402462,226.26149 -0.314194,0.40566 c -0.172793,0.22309 -0.563209,0.65259 -0.867644,0.95499 -0.461741,0.45847 -0.615657,0.54983 -0.929661,0.54983 -0.214752,0 -0.365893,-0.056 -0.599446,-0.2408 v -0.004 h -2.858741 v 12.0783 h 2.858741 c 4.174205,0.0728 3.931888,-2.28177 6.314861,-2.16937 1.427604,0.0718 1.280898,0.15899 1.725475,-1.02217 l 0.249597,-0.66402 -0.772046,-0.63873 c -0.923009,-0.76346 -1.252622,-1.23997 -1.157552,-1.67278 0.0637,-0.29001 1.160595,-1.44176 1.708939,-1.79419 0.230721,-0.14825 0.234635,-0.17171 0.09198,-0.57309 -0.08187,-0.23032 -0.249296,-0.63158 -0.371555,-0.8909 l -0.222207,-0.47181 h -1.357025 c -1.794983,0 -1.731393,0.0653 -1.734259,-1.75286 l -0.0021,-1.37925 -0.8816,-0.35708 z"
style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient1485);stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
</g>
<g
transform="translate(63.49728,-55.136805)"
id="g1174" />
<g
transform="translate(50.48327,11.624037)"
id="g1812" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

389
app/ui/import_dialog.glade Normal file
View File

@@ -0,0 +1,389 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1
The MIT License (MIT)
Copyright (c) 2018-2019 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Author: Dmitriy Yefremov
-->
<interface>
<requires lib="gtk+" version="3.16"/>
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="main_list_store">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
<!-- column-name type -->
<column type="gchararray"/>
<!-- column-name selected -->
<column type="gboolean"/>
</columns>
</object>
<object class="GtkImage" id="remove_selection_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-undo</property>
</object>
<object class="GtkMenu" id="popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="select_all_popup_item">
<property name="label">gtk-select-all</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_select_all" object="main_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="unselect_all_popup_item">
<property name="label" translatable="yes">Remove selection</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">remove_selection_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_unselect_all" object="main_view" swapped="no"/>
</object>
</child>
</object>
<object class="GtkListStore" id="services_list_store">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
<!-- column-name type -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkWindow" id="dialog_window">
<property name="can_focus">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">480</property>
<property name="default_height">320</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<signal name="check-resize" handler="on_resize" swapped="no"/>
<child type="titlebar">
<object class="GtkHeaderBar" id="header_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Import</property>
<property name="subtitle" translatable="yes">Bouquets and services</property>
<property name="spacing">2</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkButton" id="import_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Import</property>
<signal name="clicked" handler="on_import" swapped="no"/>
<child>
<object class="GtkImage" id="import_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-revert-to-saved</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkCheckButton" id="info_check_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">False</property>
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
<child>
<object class="GtkImage" id="info_check_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-info</property>
</object>
</child>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="main_box">
<property name="width_request">480</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">1</property>
<property name="margin_right">1</property>
<property name="margin_bottom">1</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkPaned" id="main_paned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="wide_handle">True</property>
<child>
<object class="GtkBox" id="bouquets_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="label" translatable="yes">Bouquets</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="bouquets_screlled_window">
<property name="width_request">200</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="main_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">main_list_store</property>
<property name="headers_clickable">False</property>
<property name="search_column">0</property>
<signal name="button-press-event" handler="on_popup_menu" object="popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
<signal name="select-all" handler="on_select_all" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection">
<property name="mode">multiple</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_name_column">
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="bq_name_renderer">
<property name="xalign">0.019999999552965164</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_type_column">
<property name="title" translatable="yes">Type</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="bq_type_renderer">
<property name="xalign">0.50999999046325684</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_selected_column">
<property name="title" translatable="yes">Selected</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererToggle" id="bq_selected_renderer">
<property name="xalign">0.50999999046325684</property>
<signal name="toggled" handler="on_selected_toggled" swapped="no"/>
</object>
<attributes>
<attribute name="active">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkBox" id="services_box">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="label" translatable="yes">Bouquet details</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="services_view_scrolled_window">
<property name="width_request">150</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="services_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">services_list_store</property>
<property name="headers_clickable">False</property>
<property name="search_column">0</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="service_name_column">
<property name="title" translatable="yes">Service</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="info_name_renderer">
<property name="xalign">0.019999999552965164</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="service_type_column">
<property name="title" translatable="yes">Type</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="info_type_renderer">
<property name="xalign">0.50999999046325684</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="can_focus">False</property>
<property name="show_close_button">True</property>
<signal name="response" handler="on_info_bar_close" swapped="no"/>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkLabel" id="message_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">message</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

234
app/ui/imports.py Normal file
View File

@@ -0,0 +1,234 @@
from contextlib import suppress
from pathlib import Path
from app.commons import run_idle
from app.eparser import get_bouquets, get_services
from app.eparser.ecommons import BqType, BqServiceType, Bouquet
from app.eparser.enigma.bouquets import get_bouquet
from app.eparser.neutrino.bouquets import parse_webtv, parse_bouquets as get_neutrino_bouquets
from app.settings import SettingsType
from app.ui.dialogs import show_dialog, DialogType, get_chooser_dialog, get_message
from app.ui.main_helper import on_popup_menu
from .uicommons import Gtk, UI_RESOURCES_PATH, KeyboardKey, Column
def import_bouquet(transient, model, path, settings, services, appender):
""" Import of single bouquet """
itr = model.get_iter(path)
bq_type = BqType(model.get(itr, Column.BQ_TYPE)[0])
pattern, f_pattern = None, None
profile = settings.setting_type
if profile is SettingsType.ENIGMA_2:
pattern = ".{}".format(bq_type.value)
f_pattern = "userbouquet.*{}".format(pattern)
elif profile is SettingsType.NEUTRINO_MP:
pattern = "webtv.xml" if bq_type is BqType.WEBTV else "bouquets.xml"
f_pattern = "bouquets.xml"
if bq_type is BqType.TV:
f_pattern = "ubouquets.xml"
elif bq_type is BqType.WEBTV:
f_pattern = "webtv.xml"
file_path = get_chooser_dialog(transient, settings, f_pattern, "bouquet files")
if file_path == Gtk.ResponseType.CANCEL:
return
if not str(file_path).endswith(pattern):
show_dialog(DialogType.ERROR, transient, text="No bouquet file is selected!")
return
if profile is SettingsType.ENIGMA_2:
bq = get_enigma2_bouquet(file_path)
imported = list(filter(lambda x: x.data in services or x.type is BqServiceType.IPTV, bq.services))
if len(imported) == 0:
show_dialog(DialogType.ERROR, transient, text="The main list does not contain services for this bouquet!")
return
if model.iter_n_children(itr):
appender(bq, itr)
else:
p_itr = model.iter_parent(itr)
appender(bq, p_itr) if p_itr else appender(bq, itr)
elif profile is SettingsType.NEUTRINO_MP:
if bq_type is BqType.WEBTV:
bqs = parse_webtv(file_path, "WEBTV", bq_type.value)
else:
bqs = get_neutrino_bouquets(file_path, "", bq_type.value)
file_path = "{}/".format(Path(file_path).parent)
ImportDialog(transient, file_path, settings, services.keys(), lambda b, s: appender(b), (bqs,)).show()
def get_enigma2_bouquet(path):
path, sep, f_name = path.rpartition("userbouquet.")
name, sep, suf = f_name.rpartition(".")
bq = get_bouquet(path, name, suf)
bouquet = Bouquet(name=bq[0], type=BqType(suf).value, services=bq[1], locked=None, hidden=None)
return bouquet
class ImportDialog:
def __init__(self, transient, path, settings, service_ids, appender, bouquets=None):
handlers = {"on_import": self.on_import,
"on_cursor_changed": self.on_cursor_changed,
"on_info_button_toggled": self.on_info_button_toggled,
"on_selected_toggled": self.on_selected_toggled,
"on_info_bar_close": self.on_info_bar_close,
"on_select_all": self.on_select_all,
"on_unselect_all": self.on_unselect_all,
"on_popup_menu": on_popup_menu,
"on_resize": self.on_resize,
"on_key_press": self.on_key_press}
builder = Gtk.Builder()
builder.set_translation_domain("demon-editor")
builder.add_from_file(UI_RESOURCES_PATH + "import_dialog.glade")
builder.connect_signals(handlers)
self._bq_services = {}
self._services = {}
self._service_ids = service_ids
self._append = appender
self._profile = settings.setting_type
self._settings = settings
self._bouquets = bouquets
self._dialog_window = builder.get_object("dialog_window")
self._dialog_window.set_transient_for(transient)
self._main_model = builder.get_object("main_list_store")
self._main_view = builder.get_object("main_view")
self._services_view = builder.get_object("services_view")
self._services_model = builder.get_object("services_list_store")
self._services_box = builder.get_object("services_box")
self._info_check_button = builder.get_object("info_check_button")
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("message_label")
window_size = self._settings.get("import_dialog_window_size")
if window_size:
self._dialog_window.resize(*window_size)
self.init_data(path)
def show(self):
self._dialog_window.show()
@run_idle
def init_data(self, path):
self._main_model.clear()
self._services_model.clear()
try:
if not self._bouquets:
self._bouquets = get_bouquets(path, self._profile)
for bqs in self._bouquets:
for bq in bqs.bouquets:
self._main_model.append((bq.name, bq.type, True))
self._bq_services[(bq.name, bq.type)] = bq.services
# Note! Getting default format ver. 4
services = get_services(path, self._profile, 4 if self._profile is SettingsType.ENIGMA_2 else 0)
for srv in services:
self._services[srv.fav_id] = srv
except FileNotFoundError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
def on_import(self, item):
if not any(r[-1] for r in self._main_model):
self.show_info_message(get_message("No selected item!"), Gtk.MessageType.ERROR)
return
if not self._bouquets or show_dialog(DialogType.QUESTION, self._dialog_window) == Gtk.ResponseType.CANCEL:
return
services = set()
to_delete = set()
for row in self._main_model:
bq = (row[0], row[1])
if row[-1]:
for bq_srv in self._bq_services.get(bq, []):
srv = self._services.get(bq_srv.data, None)
if srv:
services.add(srv)
else:
to_delete.add(bq)
bqs_to_delete = []
for bqs in self._bouquets:
for bq in bqs.bouquets:
if (bq.name, bq.type) in to_delete:
bqs_to_delete.append(bq)
for bqs in self._bouquets:
bq = bqs.bouquets
for b in bqs_to_delete:
with suppress(ValueError):
bq.remove(b)
self._append(self._bouquets, list(filter(lambda s: s.fav_id not in self._service_ids, services)))
self._dialog_window.destroy()
@run_idle
def on_cursor_changed(self, view):
if not self._info_check_button.get_active():
return
self._services_model.clear()
model, paths = view.get_selection().get_selected_rows()
if not paths:
return
bq_services = self._bq_services.get(model.get(model.get_iter(paths[0]), 0, 1))
for bq_srv in bq_services:
if bq_srv.type is BqServiceType.DEFAULT:
srv = self._services.get(bq_srv.data, None)
if srv:
self._services_model.append((srv.service, srv.service_type))
else:
self._services_model.append((bq_srv.name, bq_srv.type.value))
def on_info_button_toggled(self, button):
active = button.get_active()
self._services_box.set_visible(active)
def on_selected_toggled(self, toggle, path):
self._main_model.set_value(self._main_model.get_iter(path), 2, not toggle.get_active())
@run_idle
def show_info_message(self, text, message_type):
self._info_bar.set_visible(True)
self._info_bar.set_message_type(message_type)
self._message_label.set_text(text)
@run_idle
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
def on_select_all(self, view):
self.update_selection(view, True)
def on_unselect_all(self, view):
self.update_selection(view, False)
def update_selection(self, view, select):
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 2, select))
def on_resize(self, window):
if self._settings:
self._settings.add("import_dialog_window_size", window.get_size())
def on_key_press(self, view, event):
""" Handling keystrokes """
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):
return
key = KeyboardKey(key_code)
if key is KeyboardKey.SPACE:
path, column = view.get_cursor()
itr = self._main_model.get_iter(path)
selected = self._main_model.get_value(itr, 2)
self._main_model.set_value(itr, 2, not selected)
if __name__ == "__main__":
pass

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018 Dmitriy Yefremov
Copyright (c) 2018-2019 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkDialog" id="search_unavailable_streams_dialog">
<property name="use-header-bar">1</property>
@@ -45,18 +45,9 @@ Author: Dmitriy Yefremov
<property name="type_hint">dialog</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="decorated">False</property>
<property name="gravity">center</property>
<signal name="response" handler="on_response" swapped="no"/>
<child type="action">
<object class="GtkButton" id="search_unavailable_cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
</child>
<child type="titlebar">
<placeholder/>
</child>
@@ -65,6 +56,16 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">1</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="search_unavailable_box_frame">
<property name="visible">True</property>
@@ -74,42 +75,17 @@ Author: Dmitriy Yefremov
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label_xalign">0</property>
<property name="label_yalign">1</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox" id="search_unavailable_main_box">
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Please wait, streams testing in progress...</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLevelBar" id="unavailable_streams_level_bar">
<property name="height_request">10</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="inverted">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<property name="column_spacing">10</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
@@ -157,11 +133,59 @@ Author: Dmitriy Yefremov
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLevelBar" id="unavailable_streams_level_bar">
<property name="height_request">10</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="inverted">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="search_unavailable_cancel_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Cancel</property>
<child>
<object class="GtkImage" id="cancel_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-cancel</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Please wait, streams testing in progress...</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<child type="label_item">
@@ -192,10 +216,16 @@ Author: Dmitriy Yefremov
<row>
<col id="0">non-TS</col>
</row>
<row>
<col id="0">none-REC1</col>
</row>
<row>
<col id="0">none-REC2</col>
</row>
</data>
</object>
<object class="GtkDialog" id="iptv_dialog">
<property name="use-header-bar">1</property>
<property name="use-header-bar">{use_header}</property>
<property name="width_request">480</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Stream data</property>
@@ -400,14 +430,49 @@ Author: Dmitriy Yefremov
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkEntry" id="url_entry">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_url_changed" swapped="no"/>
<property name="can_focus">False</property>
<child>
<object class="GtkEntry" id="url_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Link to YouTube resource.</property>
<signal name="changed" handler="on_url_changed" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="yt_iptv_quality_combobox">
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Desired video quality</property>
<property name="model">yt_quality_liststore</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<signal name="changed" handler="on_yt_quality_changed" swapped="no"/>
<property name="active">0</property>
<property name="id_column">0</property>
<child>
<object class="GtkCellRendererText" id="yt_quality_renderer"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="label">
@@ -592,6 +657,54 @@ Author: Dmitriy Yefremov
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="can_focus">False</property>
<property name="show_close_button">True</property>
<signal name="response" handler="on_info_bar_close" swapped="no"/>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkLabel" id="info_bar_message_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">label</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -606,10 +719,10 @@ Author: Dmitriy Yefremov
</action-widgets>
</object>
<object class="GtkDialog" id="iptv_list_configuration_dialog">
<property name="use-header-bar">1</property>
<property name="use-header-bar">{use_header}</property>
<property name="width_request">400</property>
<property name="can_focus">False</property>
<property name="title"> IPTV streams list configuration</property>
<property name="title" translatable="yes">IPTV streams list configuration</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center</property>
@@ -1108,4 +1221,379 @@ Author: Dmitriy Yefremov
<action-widget response="-6">close_config_list_button</action-widget>
</action-widgets>
</object>
<object class="GtkImage" id="remove_selection_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-undo</property>
</object>
<object class="GtkMenu" id="yt_popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="select_all_popup_item">
<property name="label">gtk-select-all</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_select_all" object="yt_list_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="unselect_all_popup_item">
<property name="label" translatable="yes">Remove selection</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">remove_selection_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_unselect_all" object="yt_list_view" swapped="no"/>
</object>
</child>
</object>
<object class="GtkListStore" id="yt_liststore">
<columns>
<!-- column-name title -->
<column type="gchararray"/>
<!-- column-name id -->
<column type="gchararray"/>
<!-- column-name selected -->
<column type="gboolean"/>
<!-- column-name tooltip -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkListStore" id="yt_quality_liststore">
<columns>
<!-- column-name quality -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Auto</col>
</row>
<row>
<col id="0">720p</col>
</row>
<row>
<col id="0">360p</col>
</row>
</data>
</object>
<object class="GtkWindow" id="yt_import_dialog_window">
<property name="width_request">480</property>
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">480</property>
<property name="destroy_with_parent">True</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="gravity">center</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="yt_header_bar">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="title" translatable="yes">YouTube</property>
<property name="subtitle" translatable="yes">Playlist import</property>
<property name="spacing">2</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkButton" id="yt_receive_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Receive</property>
<signal name="clicked" handler="on_receive" swapped="no"/>
<child>
<object class="GtkImage" id="yt_receive_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-goto-bottom</property>
</object>
</child>
<accelerator key="d" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="yt_import_button">
<property name="visible">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Import</property>
<signal name="clicked" handler="on_import" swapped="no"/>
<child>
<object class="GtkImage" id="yt_import_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">insert-link</property>
</object>
</child>
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="yt_quality_combobox">
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Desired video quality</property>
<property name="model">yt_quality_liststore</property>
<property name="active">0</property>
<property name="id_column">0</property>
<child>
<object class="GtkCellRendererText" id="yt_quality_renderer"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="yt_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkEntry" id="yt_url_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Link to YouTube resource.</property>
<property name="placeholder_text" translatable="yes">YouTube playlist URL:</property>
<signal name="changed" handler="on_yt_url_entry_changed" swapped="no"/>
</object>
<packing>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="yt_list_view_scrolled_window">
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="min_content_height">150</property>
<child>
<object class="GtkTreeView" id="yt_list_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">yt_liststore</property>
<property name="search_column">0</property>
<property name="enable_grid_lines">horizontal</property>
<property name="tooltip_column">3</property>
<signal name="button-press-event" handler="on_popup_menu" object="yt_popup_menu" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
<signal name="select-all" handler="on_select_all" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="yt_title_column">
<property name="resizable">True</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Title</property>
<property name="expand">True</property>
<property name="clickable">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">0</property>
<child>
<object class="GtkCellRendererText" id="yt_title_renderer">
<property name="xpad">5</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="yt_id_column">
<property name="visible">False</property>
<property name="title" translatable="yes">ID</property>
<child>
<object class="GtkCellRendererText" id="yt_id_renderer"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="yt_selected_column">
<property name="min_width">50</property>
<property name="max_width">100</property>
<property name="title" translatable="yes">Selected</property>
<property name="clickable">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">2</property>
<child>
<object class="GtkCellRendererToggle" id="yt_selected_renderer">
<property name="width">50</property>
<signal name="toggled" handler="on_selected_toggled" swapped="no"/>
</object>
<attributes>
<attribute name="active">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="yt_tooltip_column">
<property name="visible">False</property>
<property name="title" translatable="yes">Tooltip</property>
<child>
<object class="GtkCellRendererText" id="yt_tooltip_renderer"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="yt_info_bar_box">
<property name="height_request">24</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">10</property>
<property name="spacing">2</property>
<child>
<object class="GtkBox" id="yt_cout_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkImage" id="yt_count_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-properties</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="yt_count_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkProgressBar" id="yt_progress_bar">
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="pulse_step">0.01</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="yt_info_bar">
<property name="can_focus">False</property>
<property name="show_close_button">True</property>
<signal name="response" handler="on_yt_info_bar_close" swapped="no"/>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkLabel" id="yt_info_bar_message_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">info</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -1,20 +1,26 @@
import concurrent.futures
import re
import urllib
from urllib.error import HTTPError
from urllib.parse import urlparse
from urllib.request import Request, urlopen
from gi.repository import GLib
from app.commons import run_idle, run_task
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, get_iptv_url
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT, get_fav_id, MARKER_FORMAT
from app.settings import SettingsType
from app.tools.yt import YouTube, PlayListParser
from .dialogs import Action, show_dialog, DialogType, get_dialogs_string, get_message
from .main_helper import get_base_model, get_iptv_url, on_popup_menu
from .uicommons import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON, Column, IS_GNOME_SESSION, KeyboardKey, \
get_yt_icon
_DIGIT_ENTRY_NAME = "digit-entry"
_ENIGMA2_REFERENCE = "{}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
_PATTERN = re.compile("(?:^[\s]*$|\D)")
_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
_UI_PATH = UI_RESOURCES_PATH + "iptv.glade"
def is_data_correct(elems):
@@ -24,20 +30,40 @@ def is_data_correct(elems):
return True
def get_stream_type(box):
active = box.get_active()
if active == 0:
return StreamType.DVB_TS.value
elif active == 1:
return StreamType.NONE_TS.value
elif active == 2:
return StreamType.NONE_REC_1.value
return StreamType.NONE_REC_2.value
class IptvDialog:
def __init__(self, transient, view, services, bouquet, profile=Profile.ENIGMA_2, action=Action.ADD):
def __init__(self, transient, view, services, bouquet, profile=SettingsType.ENIGMA_2, action=Action.ADD):
handlers = {"on_response": self.on_response,
"on_entry_changed": self.on_entry_changed,
"on_url_changed": self.on_url_changed,
"on_save": self.on_save,
"on_stream_type_changed": self.on_stream_type_changed}
"on_stream_type_changed": self.on_stream_type_changed,
"on_yt_quality_changed": self.on_yt_quality_changed,
"on_info_bar_close": self.on_info_bar_close}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "iptv.glade", ("iptv_dialog", "stream_type_liststore"))
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("iptv_dialog", "stream_type_liststore", "yt_quality_liststore"))
builder.connect_signals(handlers)
self._action = action
self._profile = profile
self._bouquet = bouquet
self._services = services
self._yt_links = None
self._dialog = builder.get_object("iptv_dialog")
self._dialog.set_transient_for(transient)
self._name_entry = builder.get_object("name_entry")
@@ -53,10 +79,9 @@ class IptvDialog:
self._add_button = builder.get_object("iptv_dialog_add_button")
self._save_button = builder.get_object("iptv_dialog_save_button")
self._stream_type_combobox = builder.get_object("stream_type_combobox")
self._action = action
self._profile = profile
self._bouquet = bouquet
self._services = services
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
self._yt_quality_box = builder.get_object("yt_iptv_quality_combobox")
self._model, self._paths = view.get_selection().get_selected_rows()
# style
self._style_provider = Gtk.CssProvider()
@@ -66,7 +91,7 @@ class IptvDialog:
for el in self._digit_elems:
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:
if profile is SettingsType.NEUTRINO_MP:
builder.get_object("iptv_dialog_ts_data_frame").set_visible(False)
builder.get_object("iptv_type_label").set_visible(False)
builder.get_object("reference_entry").set_visible(False)
@@ -79,8 +104,9 @@ class IptvDialog:
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:
if self._profile is SettingsType.ENIGMA_2:
self._update_reference_entry()
self._stream_type_combobox.set_active(1)
elif self._action is Action.EDIT:
self._current_srv = get_base_model(self._model)[self._paths][:]
self.init_data(self._current_srv)
@@ -93,35 +119,51 @@ class IptvDialog:
self._dialog.destroy()
def on_save(self, item):
self.on_url_changed(self._url_entry)
if self._action is Action.ADD:
self.on_url_changed(self._url_entry)
if not is_data_correct(self._digit_elems) or self._url_entry.get_name() == _DIGIT_ENTRY_NAME:
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
self.show_info_message(get_message("Error. Verify the data!"), Gtk.MessageType.ERROR)
return
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
self.save_enigma2_data() if self._profile is Profile.ENIGMA_2 else self.save_neutrino_data()
self.save_enigma2_data() if self._profile is SettingsType.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)
self.init_enigma2_data(fav_id) if self._profile is SettingsType.ENIGMA_2 else self.init_neutrino_data(fav_id)
def init_enigma2_data(self, fav_id):
data, sep, desc = fav_id.partition("#DESCRIPTION:")
data, sep, desc = fav_id.partition("#DESCRIPTION")
self._description_entry.set_text(desc.strip())
data = data.split(":")
if len(data) < 12:
if len(data) < 11:
return
self._stream_type_combobox.set_active(0 if StreamType(data[0].strip()) is StreamType.DVB_TS else 1)
s_type = data[0].strip()
try:
stream_type = StreamType(s_type)
if stream_type is StreamType.DVB_TS:
self._stream_type_combobox.set_active(0)
elif stream_type is StreamType.NONE_TS:
self._stream_type_combobox.set_active(1)
elif stream_type is StreamType.NONE_REC_1:
self._stream_type_combobox.set_active(2)
elif stream_type is StreamType.NONE_REC_2:
self._stream_type_combobox.set_active(3)
except ValueError:
self.show_info_message("Unknown stream type {}".format(s_type), Gtk.MessageType.ERROR)
self._srv_type_entry.set_text(data[2])
self._sid_entry.set_text(str(int(data[3], 16)))
self._tr_id_entry.set_text(str(int(data[4], 16)))
self._net_id_entry.set_text(str(int(data[5], 16)))
self._namespace_entry.set_text(str(int(data[6], 16)))
self._url_entry.set_text(data[10].replace("%3a", ":"))
self._url_entry.set_text(urllib.request.unquote(data[10].strip()))
self._update_reference_entry()
def init_neutrino_data(self, fav_id):
@@ -130,7 +172,7 @@ class IptvDialog:
self._description_entry.set_text(data[1])
def _update_reference_entry(self):
if self._profile is Profile.ENIGMA_2:
if self._profile is SettingsType.ENIGMA_2:
self._reference_entry.set_text(_ENIGMA2_REFERENCE.format(self.get_type(),
self._srv_type_entry.get_text(),
int(self._sid_entry.get_text()),
@@ -139,7 +181,7 @@ class IptvDialog:
int(self._namespace_entry.get_text())))
def get_type(self):
return 1 if self._stream_type_combobox.get_active() == 0 else 4097
return get_stream_type(self._stream_type_combobox)
def on_entry_changed(self, entry):
if _PATTERN.search(entry.get_text()):
@@ -149,12 +191,55 @@ class IptvDialog:
self._update_reference_entry()
def on_url_changed(self, entry):
url = urlparse(entry.get_text())
url_str = entry.get_text()
url = urlparse(url_str)
entry.set_name("GtkEntry" if all([url.scheme, url.netloc, url.path]) else _DIGIT_ENTRY_NAME)
yt_id = YouTube.get_yt_id(url_str)
if yt_id:
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
text = "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
if show_dialog(DialogType.QUESTION, self._dialog, text=text) == Gtk.ResponseType.OK:
entry.set_sensitive(False)
gen = self.set_yt_url(entry, yt_id)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
elif YouTube.is_yt_video_link(url_str):
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
else:
entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
self._yt_quality_box.set_visible(False)
def set_yt_url(self, entry, video_id):
try:
links, title = YouTube.get_yt_link(video_id)
except urllib.error.URLError as e:
self.show_info_message(get_message("Getting link error:") + (str(e)), Gtk.MessageType.ERROR)
return
else:
if self._action is Action.ADD:
self._name_entry.set_text(title)
if links:
if len(links) > 1:
self._yt_quality_box.set_visible(True)
entry.set_text(links[sorted(links, key=lambda x: int(x.rstrip("p")), reverse=True)[0]])
self._yt_links = links
else:
msg = get_message("Getting link error:") + " No link received for id: {}".format(video_id)
self.show_info_message(msg, Gtk.MessageType.ERROR)
finally:
entry.set_sensitive(True)
yield True
def on_stream_type_changed(self, item):
self._update_reference_entry()
def on_yt_quality_changed(self, box):
model = box.get_model()
active = model.get_value(box.get_active_iter(), 0)
if self._yt_links and active in self._yt_links:
self._url_entry.set_text(self._yt_links[active])
def save_enigma2_data(self):
name = self._name_entry.get_text().strip()
fav_id = ENIGMA2_FAV_ID_FORMAT.format(self.get_type(),
@@ -163,7 +248,7 @@ class IptvDialog:
int(self._tr_id_entry.get_text()),
int(self._net_id_entry.get_text()),
int(self._namespace_entry.get_text()),
self._url_entry.get_text().replace(":", "%3a"),
urllib.request.quote(self._url_entry.get_text()),
name, name)
self.update_bouquet_data(name, fav_id)
@@ -182,17 +267,27 @@ class IptvDialog:
old_srv = self._services.pop(self._current_srv[7])
self._services[fav_id] = old_srv._replace(service=name, fav_id=fav_id)
self._bouquet[self._paths[0][0]] = fav_id
self._model.set(self._model.get_iter(self._paths), {2: name, 7: fav_id})
self._model.set(self._model.get_iter(self._paths), {Column.FAV_SERVICE: name, Column.FAV_ID: fav_id})
else:
aggr = [None] * 10
s_type = BqServiceType.IPTV.name
srv = (None, None, name, None, None, s_type, None, fav_id, None)
srv = (None, None, name, None, None, s_type, None, fav_id, *aggr[0:3])
itr = self._model.insert_after(self._model.get_iter(self._paths[0]),
srv) if self._paths else self._model.insert(0, srv)
self._model.set_value(itr, 1, IPTV_ICON)
self._bouquet.insert(self._model.get_path(itr)[0], fav_id)
self._services[fav_id] = Service(None, None, IPTV_ICON, name, *aggr[0:3], s_type, *aggr, fav_id, None)
@run_idle
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@run_idle
def show_info_message(self, text, message_type):
self._info_bar.set_visible(True)
self._info_bar.set_message_type(message_type)
self._message_label.set_text(text)
class SearchUnavailableDialog:
@@ -280,7 +375,7 @@ class SearchUnavailableDialog:
class IptvListConfigurationDialog:
def __init__(self, transient, services, iptv_rows, bouquet, profile):
def __init__(self, transient, services, iptv_rows, bouquet, fav_model, profile):
handlers = {"on_apply": self.on_apply,
"on_response": self.on_response,
"on_stream_type_default_togged": self.on_stream_type_default_togged,
@@ -296,13 +391,14 @@ class IptvListConfigurationDialog:
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "iptv.glade",
("iptv_list_configuration_dialog", "stream_type_liststore"))
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("iptv_list_configuration_dialog", "stream_type_liststore"))
builder.connect_signals(handlers)
self._rows = iptv_rows
self._services = services
self._bouquet = bouquet
self._fav_model = fav_model
self._profile = profile
self._dialog = builder.get_object("iptv_list_configuration_dialog")
@@ -391,10 +487,7 @@ class IptvListConfigurationDialog:
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return
if len(self._bouquet) != len(self._rows):
return
if self._profile is Profile.ENIGMA_2:
if self._profile is SettingsType.ENIGMA_2:
reset = self._reset_to_default_switch.get_active()
type_default = self._type_check_button.get_active()
tid_default = self._tid_check_button.get_active()
@@ -402,35 +495,40 @@ class IptvListConfigurationDialog:
nid_default = self._nid_check_button.get_active()
namespace_default = self._namespace_check_button.get_active()
stream_type = StreamType.NONE_TS.value if reset else get_stream_type(self._stream_type_combobox)
srv_type = "1" if type_default else self._list_srv_type_entry.get_text()
tid = "0" if tid_default else "{:X}".format(int(self._list_tid_entry.get_text()))
nid = "0" if nid_default else "{:X}".format(int(self._list_nid_entry.get_text()))
namespace = "0" if namespace_default else "{:X}".format(int(self._list_namespace_entry.get_text()))
for index, row in enumerate(self._rows):
fav_id = row[7]
fav_id = row[Column.FAV_ID]
data, sep, desc = fav_id.partition("http")
data = data.split(":")
if reset:
data[0] = " 4097"
data[2], data[3], data[4], data[5], data[6] = "10000"
else:
data[0] = " 4097" if self._stream_type_combobox.get_active() == 1 else "1"
data[2] = "1" if type_default else self._list_srv_type_entry.get_text()
data[0], data[2], data[4], data[5], data[6] = stream_type, srv_type, tid, nid, namespace
data[3] = "{:X}".format(index) if sid_auto else "0"
data[4] = "0" if tid_default else "{:X}".format(int(self._list_tid_entry.get_text()))
data[5] = "0" if nid_default else "{:X}".format(int(self._list_nid_entry.get_text()))
data[6] = "0" if namespace_default else "{:X}".format(int(self._list_namespace_entry.get_text()))
data = ":".join(data)
new_fav_id = "{}{}{}".format(data, sep, desc)
row[7] = new_fav_id
self._bouquet[index] = new_fav_id
row[Column.FAV_ID] = new_fav_id
srv = self._services.pop(fav_id, None)
self._services[new_fav_id] = srv._replace(fav_id=new_fav_id)
if srv:
self._services[new_fav_id] = srv._replace(fav_id=new_fav_id)
self._bouquet.clear()
list(map(lambda r: self._bouquet.append(r[Column.FAV_ID]), self._fav_model))
self._info_bar.set_visible(True)
@run_idle
def update_reference(self):
if is_data_correct(self._digit_elems):
stream_type = "4097" if self._stream_type_combobox.get_active() == 1 else "1"
stream_type = get_stream_type(self._stream_type_combobox)
self._reference_label.set_text(
_ENIGMA2_REFERENCE.format(stream_type, *[int(elem.get_text()) for elem in self._digit_elems]))
@@ -442,5 +540,210 @@ class IptvListConfigurationDialog:
self.update_reference()
class YtListImportDialog:
def __init__(self, transient, profile, appender):
handlers = {"on_import": self.on_import,
"on_receive": self.on_receive,
"on_yt_url_entry_changed": self.on_url_entry_changed,
"on_yt_info_bar_close": self.on_info_bar_close,
"on_popup_menu": on_popup_menu,
"on_selected_toggled": self.on_selected_toggled,
"on_select_all": self.on_select_all,
"on_unselect_all": self.on_unselect_all,
"on_key_press": self.on_key_press,
"on_close": self.on_close}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("yt_import_dialog_window", "yt_liststore", "yt_quality_liststore",
"yt_popup_menu", "remove_selection_image"))
builder.connect_signals(handlers)
self._dialog = builder.get_object("yt_import_dialog_window")
self._dialog.set_transient_for(transient)
self._list_view_scrolled_window = builder.get_object("yt_list_view_scrolled_window")
self._model = builder.get_object("yt_liststore")
self._progress_bar = builder.get_object("yt_progress_bar")
self._info_bar_box = builder.get_object("yt_info_bar_box")
self._message_label = builder.get_object("yt_info_bar_message_label")
self._info_bar = builder.get_object("yt_info_bar")
self._yt_count_label = builder.get_object("yt_count_label")
self._url_entry = builder.get_object("yt_url_entry")
self._receive_button = builder.get_object("yt_receive_button")
self._import_button = builder.get_object("yt_import_button")
self._quality_box = builder.get_object("yt_quality_combobox")
self._quality_model = builder.get_object("yt_quality_liststore")
self._import_button.bind_property("visible", self._quality_box, "visible")
self._import_button.bind_property("sensitive", self._quality_box, "sensitive")
self._receive_button.bind_property("sensitive", self._import_button, "sensitive")
# style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
self.appender = appender
self._profile = profile
self._download_task = False
self._yt_list_id = None
self._yt_list_title = None
def show(self):
self._dialog.show()
@run_task
def on_import(self, item):
self.on_info_bar_close()
self.update_active_elements(False)
self._download_task = True
try:
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
done_links = {}
rows = list(filter(lambda r: r[2], self._model))
futures = {executor.submit(YouTube.get_yt_link, r[1]): r for r in rows}
size = len(futures)
counter = 0
for future in concurrent.futures.as_completed(futures):
if not self._download_task:
executor.shutdown()
return
done_links[futures[future]] = future.result()
counter += 1
self.update_progress_bar(counter / size)
except Exception as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
if self._download_task:
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
self.append_services([done_links[r] for r in rows])
finally:
self._download_task = False
self.update_active_elements(True)
def on_receive(self, item):
self.show_invisible_elements()
self.update_active_elements(False)
self._model.clear()
self._yt_count_label.set_text("0")
self.on_info_bar_close()
self.update_refs_list()
@run_task
def update_refs_list(self):
if self._yt_list_id:
try:
self._yt_list_title, links = PlayListParser.get_yt_playlist(self._yt_list_id)
except Exception as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
return
else:
gen = self.update_links(links)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
finally:
self.update_active_elements(True)
def update_links(self, links):
for l in links:
yield self._model.append((l[0], l[1], True, None))
size = len(self._model)
self._yt_count_label.set_text(str(size))
self._import_button.set_visible(size)
yield True
@run_idle
def append_services(self, links):
aggr = [None] * 9
srvs = []
if self._yt_list_title:
title = self._yt_list_title
fav_id = MARKER_FORMAT.format(0, title, title)
mk = Service(None, None, None, title, *aggr[0:3], BqServiceType.MARKER.name, *aggr, 0, fav_id, None)
srvs.append(mk)
act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0)
for link in links:
lnk, title = link
if not lnk:
continue
ln = lnk.get(act) if act in lnk else lnk[sorted(lnk, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
fav_id = get_fav_id(ln, title, self._profile)
srv = Service(None, None, IPTV_ICON, title, *aggr[0:3], BqServiceType.IPTV.name, *aggr, None, fav_id, None)
srvs.append(srv)
self.appender(srvs)
@run_idle
def update_active_elements(self, sensitive):
self._url_entry.set_sensitive(sensitive)
self._receive_button.set_sensitive(sensitive)
def show_invisible_elements(self):
self._list_view_scrolled_window.set_visible(True)
self._info_bar_box.set_visible(True)
self._dialog.set_resizable(True)
def on_url_entry_changed(self, entry):
url_str = entry.get_text()
yt_id = YouTube.get_yt_list_id(url_str)
entry.set_name("GtkEntry" if yt_id else _DIGIT_ENTRY_NAME)
self._receive_button.set_sensitive(bool(yt_id))
self._import_button.set_sensitive(bool(yt_id))
self._yt_list_id = yt_id
if yt_id:
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
else:
entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
@run_idle
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@run_idle
def update_progress_bar(self, value):
self._progress_bar.set_visible(value < 1)
self._progress_bar.set_fraction(value)
@run_idle
def show_info_message(self, text, message_type):
self._info_bar.set_visible(True)
self._info_bar.set_message_type(message_type)
self._message_label.set_text(text)
def on_selected_toggled(self, toggle, path):
self._model.set_value(self._model.get_iter(path), 2, not toggle.get_active())
def on_select_all(self, view):
self.update_selection(view, True)
def on_unselect_all(self, view):
self.update_selection(view, False)
def update_selection(self, view, select):
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 2, select))
def on_key_press(self, view, event):
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):
return
key = KeyboardKey(key_code)
if key is KeyboardKey.SPACE:
path, column = view.get_cursor()
itr = self._model.get_iter(path)
selected = self._model.get_value(itr, 2)
self._model.set_value(itr, 2, not selected)
def on_close(self, window, event):
if self._download_task and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return True
self._download_task = False
if __name__ == "__main__":
pass

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,22 @@
""" This is helper module for ui """
import os
import shutil
import urllib.request
from gi.repository import GdkPixbuf, GLib
from app.commons import run_task
from app.eparser import Service
from app.eparser.ecommons import Flag, BouquetService, Bouquet, BqType
from app.eparser.enigma.bouquets import BqServiceType, to_bouquet_id
from app.properties import Profile
from .uicommons import ViewTarget, BqGenType, Gtk, Gdk, HIDE_ICON, LOCKED_ICON, KeyboardKey
from app.settings import SettingsType
from .dialogs import show_dialog, DialogType, get_chooser_dialog, WaitDialog
from .uicommons import ViewTarget, BqGenType, Gtk, Gdk, HIDE_ICON, LOCKED_ICON, KeyboardKey, Column
# ***************** Markers *******************#
def insert_marker(view, bouquets, selected_bouquet, channels, parent_window):
def insert_marker(view, bouquets, selected_bouquet, services, parent_window):
"""" Inserts marker into bouquet services list. """
response = show_dialog(DialogType.INPUT, parent_window)
if response == Gtk.ResponseType.CANCEL:
@@ -24,17 +26,13 @@ def insert_marker(view, bouquets, selected_bouquet, channels, parent_window):
show_dialog(DialogType.ERROR, parent_window, "The text of marker is empty, please try again!")
return
# Searching for max num value in all marker services (if empty default = 0)
max_num = max(map(lambda num: int(num.data_id, 16),
filter(lambda ch: ch.service_type == BqServiceType.MARKER.name, channels.values())), default=0)
max_num = '{:X}'.format(max_num + 1)
fav_id = "1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n".format(max_num, response, response)
fav_id = "1:64:0:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n".format(response, response)
s_type = BqServiceType.MARKER.name
model, paths = view.get_selection().get_selected_rows()
marker = (None, None, response, None, None, s_type, None, fav_id, None)
marker = (None, None, response, None, None, s_type, None, fav_id, None, None, None)
itr = model.insert_before(model.get_iter(paths[0]), marker) if paths else model.insert(0, marker)
bouquets[selected_bouquet].insert(model.get_path(itr)[0], fav_id)
channels[fav_id] = Service(None, None, None, response, None, None, None, s_type, *[None] * 9, max_num, fav_id, None)
services[fav_id] = Service(None, None, None, response, None, None, None, s_type, *[None] * 9, 0, fav_id, None)
# ***************** Movement *******************#
@@ -75,10 +73,12 @@ def move_items(key, view: Gtk.TreeView):
if key is KeyboardKey.UP:
top_path = Gtk.TreePath(paths[0])
set_cursor(top_path, paths, selection, view)
top_path.prev()
move_up(top_path, model, paths)
elif key is KeyboardKey.DOWN:
down_path = Gtk.TreePath(paths[-1])
set_cursor(down_path, paths, selection, view)
down_path.next()
if down_path < max_path:
move_down(down_path, model, paths)
@@ -118,6 +118,12 @@ def is_some_level(paths):
return True
def set_cursor(dest_path, paths, selection, view):
view.set_cursor(dest_path, view.get_column(0), False)
for p in paths:
selection.select_path(p)
# ***************** Rename *******************#
def rename(view, parent_window, target, fav_view=None, service_view=None, services=None):
@@ -130,32 +136,32 @@ def rename(view, parent_window, target, fav_view=None, service_view=None, servic
f_id, srv_name, srv_type = None, None, None
if target is ViewTarget.SERVICES:
name, fav_id = model.get(itr, 3, 18)
name, fav_id = model.get(itr, Column.SRV_SERVICE, Column.SRV_FAV_ID)
f_id = fav_id
response = show_dialog(DialogType.INPUT, parent_window, name)
if response == Gtk.ResponseType.CANCEL:
return
srv_name = response
model.set_value(itr, 3, response)
model.set_value(itr, Column.SRV_SERVICE, response)
if fav_view is not None:
for row in fav_view.get_model():
if row[7] == fav_id:
row[2] = response
if row[Column.FAV_ID] == fav_id:
row[Column.FAV_SERVICE] = response
break
elif target is ViewTarget.FAV:
name, srv_type, fav_id = model.get(itr, 2, 5, 7)
name, srv_type, fav_id = model.get(itr, Column.FAV_SERVICE, Column.FAV_TYPE, Column.FAV_ID)
f_id = fav_id
response = show_dialog(DialogType.INPUT, parent_window, name)
if response == Gtk.ResponseType.CANCEL:
return
srv_name = response
model.set_value(itr, 2, response)
model.set_value(itr, Column.FAV_SERVICE, response)
if service_view is not None:
for row in get_base_model(service_view.get_model()):
if row[18] == fav_id:
row[3] = response
if row[Column.SRV_FAV_ID] == fav_id:
row[Column.SRV_SERVICE] = response
break
old_srv = services.get(f_id, None)
@@ -208,9 +214,9 @@ def set_flags(flag, services_view, fav_view, services, blacklist):
if target is ViewTarget.SERVICES:
set_hide(services, model, paths)
else:
fav_ids = [model.get_value(model.get_iter(path), 7) for path in paths]
fav_ids = [model.get_value(model.get_iter(path), Column.FAV_ID) for path in paths]
srv_model = get_base_model(services_view.get_model())
srv_paths = [row.path for row in srv_model if row[18] in fav_ids]
srv_paths = [row.path for row in srv_model if row[Column.SRV_FAV_ID] in fav_ids]
set_hide(services, srv_model, srv_paths)
elif flag is Flag.LOCK:
set_lock(blacklist, services, model, paths, target, services_model=get_base_model(services_view.get_model()))
@@ -220,20 +226,20 @@ def set_flags(flag, services_view, fav_view, services, blacklist):
def update_fav_model(fav_view, services):
for row in get_base_model(fav_view.get_model()):
srv = services.get(row[7], None)
srv = services.get(row[Column.FAV_ID], None)
if srv:
row[3], row[4] = srv.locked, srv.hide
row[Column.FAV_LOCKED], row[Column.FAV_HIDE] = srv.locked, srv.hide
def set_lock(blacklist, services, model, paths, target, services_model):
col_num = 4 if target is ViewTarget.SERVICES else 3
col_num = Column.SRV_LOCKED if target is ViewTarget.SERVICES else Column.FAV_LOCKED
locked = has_locked_hide(model, paths, col_num)
ids = []
for path in paths:
itr = model.get_iter(path)
fav_id = model.get_value(itr, 18 if target is ViewTarget.SERVICES else 7)
fav_id = model.get_value(itr, Column.SRV_FAV_ID if target is ViewTarget.SERVICES else Column.FAV_ID)
srv = services.get(fav_id, None)
if srv:
bq_id = to_bouquet_id(srv)
@@ -251,13 +257,13 @@ def set_lock(blacklist, services, model, paths, target, services_model):
def update_services_model(ids, locked, services_model):
for srv in services_model:
if srv[18] in ids:
srv[4] = None if locked else LOCKED_ICON
if srv[Column.SRV_FAV_ID] in ids:
srv[Column.SRV_LOCKED] = None if locked else LOCKED_ICON
yield True
def set_hide(services, model, paths):
col_num = 5
col_num = Column.SRV_HIDE
hide = has_locked_hide(model, paths, col_num)
for path in paths:
@@ -292,7 +298,7 @@ def set_hide(services, model, paths):
flags.append(value)
model.set_value(itr, 0, (",".join(reversed(sorted(flags)))))
fav_id = model.get_value(itr, 18)
fav_id = model.get_value(itr, Column.SRV_FAV_ID)
srv = services.get(fav_id, None)
if srv:
services[fav_id] = srv._replace(hide=None if hide else HIDE_ICON)
@@ -317,9 +323,9 @@ def locate_in_services(fav_view, services_view, parent_window):
show_dialog(DialogType.ERROR, parent_window, "Please, select only one item!")
return
fav_id = model.get_value(model.get_iter(paths[0]), 7)
fav_id = model.get_value(model.get_iter(paths[0]), Column.FAV_ID)
for index, row in enumerate(services_view.get_model()):
if row[18] == fav_id:
if row[Column.SRV_FAV_ID] == fav_id:
scroll_to(index, services_view)
break
@@ -341,26 +347,28 @@ def update_picons_data(path, picons):
return
for file in os.listdir(path):
picons[file] = get_picon_pixbuf(path + file)
pf = get_picon_pixbuf(path + file)
if pf:
picons[file] = pf
def append_picons(picons, model):
def append_picons_data(pcs, mod):
for r in mod:
mod.set_value(mod.get_iter(r.path), 8, pcs.get(r[9], None))
mod.set_value(mod.get_iter(r.path), Column.SRV_PICON, pcs.get(r[Column.SRV_PICON_ID], None))
yield True
app = append_picons_data(picons, model)
GLib.idle_add(lambda: next(app, False), priority=GLib.PRIORITY_LOW)
def assign_picon(target, srv_view, fav_view, transient, picons, options, services):
def assign_picon(target, srv_view, fav_view, transient, picons, settings, 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")
response = get_chooser_dialog(transient, settings, "*.png", "png files")
if response == Gtk.ResponseType.CANCEL:
return
@@ -368,23 +376,25 @@ def assign_picon(target, srv_view, fav_view, transient, picons, options, service
show_dialog(DialogType.ERROR, transient, text="No png file is selected!")
return
picon_pos = 8
picon_pos = Column.SRV_PICON
model = get_base_model(model)
itr = model.get_iter(paths)
fav_id = model.get_value(itr, 18 if target is ViewTarget.SERVICES else 7)
picon_id = services.get(fav_id)[9]
fav_id = model.get_value(itr, Column.SRV_FAV_ID if target is ViewTarget.SERVICES else Column.FAV_ID)
picon_id = services.get(fav_id)[Column.SRV_PICON_ID]
if picon_id:
picon_file = options.get("picons_dir_path") + picon_id
if os.path.isfile(response):
picons_path = settings.picons_local_path
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
picon_file = picons_path + picon_id
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)
set_picon(fav_id, fav_view.get_model(), picon, Column.FAV_ID, picon_pos)
else:
set_picon(fav_id, get_base_model(srv_view.get_model()), picon, 18, picon_pos)
set_picon(fav_id, get_base_model(srv_view.get_model()), picon, Column.SRV_FAV_ID, picon_pos)
def set_picon(fav_id, model, picon, fav_id_pos, picon_pos):
@@ -394,46 +404,38 @@ def set_picon(fav_id, model, picon, fav_id_pos, picon_pos):
break
def remove_picon(target, srv_view, fav_view, picons, options):
def remove_picon(target, srv_view, fav_view, picons, settings):
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
picon_pos = Column.SRV_PICON # picon position is equal for services and fav
for path in paths:
itr = model.get_iter(path)
model.set_value(itr, picon_pos, None)
if target is ViewTarget.SERVICES:
fav_ids.append(model.get_value(itr, 18))
picon_ids.append(model.get_value(itr, 9))
fav_ids.append(model.get_value(itr, Column.SRV_FAV_ID))
picon_ids.append(model.get_value(itr, Column.SRV_PICON_ID))
else:
srv_type, fav_id = model.get(itr, 5, 7)
srv_type, fav_id = model.get(itr, Column.FAV_TYPE, Column.FAV_ID)
if srv_type == BqServiceType.IPTV.name:
picon_ids.append("{}_{}_{}_{}_{}_{}_{}_{}_{}_{}.png".format(*fav_id.split(":")[0:10]).strip())
else:
fav_ids.append(fav_id)
def remove(md, path, it):
if md.get_value(it, 7 if target is ViewTarget.SERVICES else 18) in fav_ids:
if md.get_value(it, Column.FAV_ID if target is ViewTarget.SERVICES else Column.SRV_FAV_ID) in fav_ids:
md.set_value(it, picon_pos, None)
if target is ViewTarget.FAV:
picon_ids.append(md.get_value(it, 9))
picon_ids.append(md.get_value(it, Column.SRV_PICON_ID))
fav_view.get_model().foreach(remove) if target is ViewTarget.SERVICES else get_base_model(
srv_view.get_model()).foreach(remove)
pions_path = options.get("picons_dir_path")
backup_path = options.get("data_dir_path") + "backup/picons/"
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
for p_id in picon_ids:
picons[p_id] = None
src = pions_path + p_id
if os.path.isfile(src):
shutil.move(src, backup_path + p_id)
remove_picons(settings, picon_ids, picons)
def copy_picon_reference(target, view, services, clipboard, transient):
@@ -443,13 +445,13 @@ def copy_picon_reference(target, view, services, clipboard, transient):
return
if target is ViewTarget.SERVICES:
picon_id = model.get_value(model.get_iter(paths), 9)
picon_id = model.get_value(model.get_iter(paths), Column.SRV_PICON_ID)
if picon_id:
clipboard.set_text(picon_id.rstrip(".png"), -1)
else:
show_dialog(DialogType.ERROR, transient, "No reference is present!")
elif target is ViewTarget.FAV:
fav_id = model.get_value(model.get_iter(paths), 7)
fav_id = model.get_value(model.get_iter(paths), Column.FAV_ID)
srv = services.get(fav_id, None)
if srv and srv.picon_id:
clipboard.set_text(srv.picon_id.rstrip(".png"), -1)
@@ -457,6 +459,23 @@ def copy_picon_reference(target, view, services, clipboard, transient):
show_dialog(DialogType.ERROR, transient, "No reference is present!")
def remove_all_unused_picons(settings, picons, services):
ids = {s.picon_id for s in services}
pcs = list(filter(lambda x: x not in ids, picons))
remove_picons(settings, pcs, picons)
def remove_picons(settings, picon_ids, picons):
pions_path = settings.picons_local_path
backup_path = settings.backup_local_path + "picons/"
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
for p_id in picon_ids:
picons[p_id] = None
src = pions_path + p_id
if os.path.isfile(src):
shutil.move(src, backup_path + p_id)
def is_only_one_item_selected(paths, transient):
if len(paths) > 1:
show_dialog(DialogType.ERROR, transient, "Please, select only one item!")
@@ -470,37 +489,44 @@ def is_only_one_item_selected(paths, transient):
def get_picon_pixbuf(path):
return GdkPixbuf.Pixbuf.new_from_file_at_scale(filename=path, width=32, height=32, preserve_aspect_ratio=True)
try:
return GdkPixbuf.Pixbuf.new_from_file_at_scale(filename=path, width=32, height=32, preserve_aspect_ratio=True)
except GLib.GError as e:
pass
# ***************** Bouquets *********************#
def gen_bouquets(view, bq_view, transient, gen_type, tv_types, profile, callback):
def gen_bouquets(view, bq_view, transient, gen_type, tv_types, s_type, callback):
""" Auto-generate and append list of bouquets """
fav_id_index = 18
index = 6 if gen_type in (BqGenType.PACKAGE, BqGenType.EACH_PACKAGE) else 16 if gen_type in (
BqGenType.SAT, BqGenType.EACH_SAT) else 7
fav_id_index = Column.SRV_FAV_ID
index = Column.SRV_TYPE
if gen_type in (BqGenType.PACKAGE, BqGenType.EACH_PACKAGE):
index = Column.SRV_PACKAGE
elif gen_type in (BqGenType.SAT, BqGenType.EACH_SAT):
index = Column.SRV_POS
model, paths = view.get_selection().get_selected_rows()
bq_type = BqType.BOUQUET.value if profile is Profile.NEUTRINO_MP else BqType.TV.value
bq_type = BqType.BOUQUET.value if s_type is SettingsType.NEUTRINO_MP else BqType.TV.value
if gen_type in (BqGenType.SAT, BqGenType.PACKAGE, BqGenType.TYPE):
if not is_only_one_item_selected(paths, transient):
return
service = Service(*model[paths][:])
service = Service(*model[paths][:Column.SRV_TOOLTIP])
if service.service_type not in tv_types:
bq_type = BqType.RADIO.value
append_bouquets(bq_type, bq_view, callback, fav_id_index, index, model,
[service.package if gen_type is BqGenType.PACKAGE else
service.pos if gen_type is BqGenType.SAT else service.service_type], profile)
service.pos if gen_type is BqGenType.SAT else service.service_type], s_type)
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)
{row[index] for row in model}, s_type, 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
def append_bouquets(bq_type, bq_view, callback, fav_id_index, index, model, names, s_type, wait_dialog=None):
bq_index = 0 if s_type is SettingsType.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)
@@ -531,9 +557,9 @@ def get_bouquets_names(model):
# ***************** Others *********************#
def update_entry_data(entry, dialog, options):
def update_entry_data(entry, dialog, settings):
""" Updates value in text entry from chooser dialog """
response = show_dialog(dialog_type=DialogType.CHOOSER, transient=dialog, options=options)
response = show_dialog(dialog_type=DialogType.CHOOSER, transient=dialog, settings=settings)
if response not in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
entry.set_text(response)
return response
@@ -562,14 +588,14 @@ def append_text_to_tview(char, view):
view.scroll_to_mark(insert, 0.0, True, 0.0, 1.0)
def get_iptv_url(row, profile):
def get_iptv_url(row, s_type):
""" Returns url from iptv type row """
data = row[7].split(":" if profile is Profile.ENIGMA_2 else "::")
if profile is Profile.ENIGMA_2:
data = row[Column.FAV_ID].split(":" if s_type is SettingsType.ENIGMA_2 else "::")
if s_type is SettingsType.ENIGMA_2:
data = list(filter(lambda x: "http" in x, data))
if data:
url = data[0]
return url.replace("%3a", ":") if profile is Profile.ENIGMA_2 else url
return urllib.request.unquote(url) if s_type is SettingsType.ENIGMA_2 else url
def on_popup_menu(menu, event):

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018 Dmitriy Yefremov
Copyright (c) 2018-2019 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -31,28 +31,8 @@ Author: Dmitriy Yefremov
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-goto-bottom</property>
</object>
<object class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-server-symbolic</property>
</object>
<object class="GtkImage" id="image3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-goto-top</property>
</object>
<object class="GtkImage" id="image4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-execute</property>
</object>
<object class="GtkListStore" id="providers_list_store">
<columns>
<!-- column-name logo -->
@@ -119,158 +99,64 @@ Author: Dmitriy Yefremov
<property name="destroy_with_parent">True</property>
<property name="icon_name">emblem-photos</property>
<property name="type_hint">dialog</property>
<signal name="destroy" handler="on_close" swapped="no"/>
<signal name="delete-event" handler="on_close" swapped="no"/>
<child type="titlebar">
<object class="GtkHeaderBar" id="header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Picons download tool</property>
<property name="spacing">2</property>
<property name="show_close_button">True</property>
<child type="title">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Cancel</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_cancel" swapped="no"/>
<child>
<object class="GtkLabel">
<object class="GtkImage" id="cancel_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="label" translatable="yes">Picons download tool</property>
<property name="xalign">0.52999997138977051</property>
<property name="stock">gtk-cancel</property>
</object>
</child>
<accelerator key="z" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
<object class="GtkBox" id="header_download_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkButton" id="load_providers_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Load providers</property>
<property name="halign">center</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_load_providers" swapped="no"/>
<child>
<object class="GtkImage" id="load_providers_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-server-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_bottom">5</property>
<property name="spacing">2</property>
<child>
<object class="GtkButton">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Cancel</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_cancel" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="receive_button">
<property name="label" translatable="yes">Receive picons</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Receive picons for providers</property>
<property name="halign">center</property>
<property name="image">image1</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_receive" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="convert_button">
<property name="label" translatable="yes">Convert</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="halign">center</property>
<property name="image">image4</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="load_providers_button">
<property name="label" translatable="yes">Load providers</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Load satellite providers.</property>
<property name="halign">center</property>
<property name="image">image2</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_load_providers" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkButton" id="send_button">
<property name="label" translatable="yes">Send</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Transfer to receiver</property>
<property name="image">image3</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_send" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -278,7 +164,130 @@ Author: Dmitriy Yefremov
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="receive_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Receive picons</property>
<property name="halign">center</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_receive" swapped="no"/>
<child>
<object class="GtkImage" id="receive_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-goto-bottom</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="send_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Transfer to receiver</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_send" swapped="no"/>
<child>
<object class="GtkImage" id="send_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-goto-top</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkButton" id="download_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Download picons from the receiver</property>
<signal name="clicked" handler="on_download" swapped="no"/>
<child>
<object class="GtkImage" id="download_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-go-down</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkButton" id="remove_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Remove picons from the receiver</property>
<signal name="clicked" handler="on_remove" swapped="no"/>
<child>
<object class="GtkImage" id="remove_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-delete</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
</object>
<packing>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkButton" id="convert_button">
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Convert</property>
<property name="halign">center</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_convert" swapped="no"/>
<child>
<object class="GtkImage" id="convert_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-execute</property>
</object>
</child>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
@@ -286,15 +295,11 @@ Author: Dmitriy Yefremov
<object class="GtkBox" id="picons_dialog_vbox">
<property name="width_request">640</property>
<property name="can_focus">False</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog_action_area">
<property name="can_focus">False</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
<property name="homogeneous">True</property>
<property name="layout_style">spread</property>
</object>
@@ -304,17 +309,6 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkNotebook" id="notebook">
<property name="visible">True</property>
@@ -326,8 +320,8 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
@@ -404,10 +398,47 @@ Author: Dmitriy Yefremov
</object>
</child>
<child type="label">
<object class="GtkLabel">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Satellite</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="satellite_label">
<property name="can_focus">False</property>
<property name="label" translatable="yes">Satellite</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="loading_data_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">2</property>
<property name="label" translatable="yes">Loading data...</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkSpinner" id="loading_data_spinner">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">2</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
@@ -658,7 +689,7 @@ Author: Dmitriy Yefremov
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<property name="secondary_icon_name">folder-open</property>
<property name="primary_icon_activatable">False</property>
<signal name="icon-press" handler="on_picons_dir_open" swapped="no"/>
</object>
@@ -988,7 +1019,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">8</property>
<property name="position">0</property>
</packing>
</child>
<child>
@@ -1025,7 +1056,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">13</property>
<property name="position">1</property>
</packing>
</child>
<child>
@@ -1079,7 +1110,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">14</property>
<property name="position">2</property>
</packing>
</child>
</object>

View File

@@ -3,28 +3,27 @@ import re
import shutil
import subprocess
import tempfile
import time
from gi.repository import GLib, GdkPixbuf
from app.commons import run_idle, run_task
from app.connections import upload_data, DownloadType
from app.connections import upload_data, DownloadType, download_data, remove_picons
from app.settings import SettingsType
from app.tools.picons import PiconsParser, parse_providers, Provider, convert_to
from app.properties import Profile
from app.tools.satellites import SatellitesParser, SatelliteSource
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, TV_ICON
from .dialogs import show_dialog, DialogType, get_message
from .main_helper import update_entry_data, append_text_to_tview, scroll_to, on_popup_menu
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, TV_ICON
class PiconsDialog:
def __init__(self, transient, options, picon_ids, sat_positions, profile=Profile.ENIGMA_2):
def __init__(self, transient, settings, picon_ids, sat_positions):
self._picon_ids = picon_ids
self._sat_positions = sat_positions
self._TMP_DIR = tempfile.gettempdir() + "/"
self._BASE_URL = "www.lyngsat.com/packages/"
self._PATTERN = re.compile("^https://www\.lyngsat\.com/[\w-]+\.html$")
self._POS_PATTERN = re.compile("^\d+\.\d+[EW]?$")
self._PATTERN = re.compile(r"^https://www\.lyngsat\.com/[\w-]+\.html$")
self._POS_PATTERN = re.compile(r"^\d+\.\d+[EW]?$")
self._current_process = None
self._terminate = False
@@ -33,6 +32,8 @@ class PiconsDialog:
"on_cancel": self.on_cancel,
"on_close": self.on_close,
"on_send": self.on_send,
"on_download": self.on_download,
"on_remove": self.on_remove,
"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,
@@ -71,33 +72,42 @@ class PiconsDialog:
self._enigma2_path_button = builder.get_object("enigma2_path_button")
self._save_to_button = builder.get_object("save_to_button")
self._send_button = builder.get_object("send_button")
self._cancel_button = builder.get_object("cancel_button")
self._enigma2_radio_button = builder.get_object("enigma2_radio_button")
self._neutrino_mp_radio_button = builder.get_object("neutrino_mp_radio_button")
self._resize_no_radio_button = builder.get_object("resize_no_radio_button")
self._resize_220_132_radio_button = builder.get_object("resize_220_132_radio_button")
self._resize_100_60_radio_button = builder.get_object("resize_100_60_radio_button")
self._satellite_label = builder.get_object("satellite_label")
self._header_download_box = builder.get_object("header_download_box")
self._satellite_label.bind_property("visible", builder.get_object("loading_data_label"), "visible", 4)
self._satellite_label.bind_property("visible", builder.get_object("loading_data_spinner"), "visible", 4)
self._cancel_button.bind_property("visible", self._header_download_box, "visible", 4)
self._convert_button.bind_property("visible", self._header_download_box, "visible", 4)
# 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._settings = settings
self._s_type = settings.setting_type
self._ip_entry.set_text(self._settings.host)
self._picons_entry.set_text(self._settings.picons_path)
self._picons_path = self._settings.picons_local_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:
window_size = self._settings.get("picons_downloader_window_size")
if window_size:
self._dialog.resize(*window_size)
if not len(self._picon_ids) and self._s_type is SettingsType.ENIGMA_2:
message = get_message("To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window.")
self.show_info_message(message, Gtk.MessageType.WARNING)
self._satellite_label.show()
def show(self):
self._dialog.run()
self._dialog.destroy()
def on_satellites_view_realize(self, view):
self.get_satellites(view)
@@ -105,18 +115,22 @@ class PiconsDialog:
@run_task
def get_satellites(self, view):
sats = SatellitesParser().get_satellites_list(SatelliteSource.LYNGSAT)
if not sats:
self.show_info_message("Getting satellites list error!", Gtk.MessageType.ERROR)
gen = self.append_satellites(view.get_model(), sats)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def append_satellites(self, model, sats):
for sat in sats:
pos = sat[1]
name = "{} ({})".format(sat[0], pos)
pos = "{}{}".format("-" if pos[-1] == "W" else "", pos[:-1])
if not self._terminate and model:
if pos in self._sat_positions:
model.append((name, sat[3], pos))
yield True
try:
for sat in sats:
pos = sat[1]
name, pos = "{} ({})".format(sat[0], pos), "{}{}".format("-" if pos[-1] == "W" else "", pos[:-1])
if not self._terminate and model:
if pos in self._sat_positions:
yield model.append((name, sat[3], pos))
finally:
self._satellite_label.show()
def on_satellite_selection(self, view, path, column):
model = view.get_model()
@@ -125,32 +139,49 @@ class PiconsDialog:
@run_idle
def on_load_providers(self, item):
self._expander.set_expanded(True)
self.on_info_bar_close()
self._cancel_button.show()
url = self._url_entry.get_text()
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
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)
try:
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
except FileNotFoundError as e:
self._cancel_button.hide()
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
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.append_providers(url, model)
@run_task
def append_providers(self, url, model):
self._current_process.wait()
providers = parse_providers(self._TMP_DIR + url[url.find("w"):])
if providers:
for p in providers:
model.append((self.get_pixbuf(p[0]) if p[0] else TV_ICON, *p[1:]))
self.update_receive_button_state()
try:
self._terminate = False
providers = parse_providers(self._TMP_DIR + url[url.find("w"):])
except FileNotFoundError:
pass # NOP
else:
if providers:
for p in providers:
if self._terminate:
return
model.append((self.get_pixbuf(p[0]) if p[0] else TV_ICON, *p[1:]))
self.update_receive_button_state()
finally:
GLib.idle_add(self._cancel_button.hide)
self._terminate = False
def get_pixbuf(self, img_url):
return GdkPixbuf.Pixbuf.new_from_file_at_scale(filename=self._TMP_DIR + "www.lyngsat.com/" + img_url,
width=48, height=48, preserve_aspect_ratio=True)
@run_idle
def on_receive(self, item):
self._cancel_button.show()
self.start_download()
@run_task
@@ -170,13 +201,19 @@ class PiconsDialog:
scroll_to(prv.path, self._providers_tree_view)
return
for prv in providers:
if self._terminate:
break
self.process_provider(Provider(*prv))
self.resize(self._picons_path)
if not self._terminate:
try:
for prv in providers:
if self._terminate:
return
self.process_provider(Provider(*prv))
if self._resize_no_radio_button.get_active():
self.resize(self._picons_path)
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
finally:
GLib.idle_add(self._cancel_button.hide)
self._terminate = False
def process_provider(self, prv):
url = prv.url
@@ -195,37 +232,52 @@ class PiconsDialog:
char = fd.read(1)
self.append_output(char)
return True
else:
return False
return False
@run_idle
def append_output(self, char):
append_text_to_tview(char, self._text_view)
def resize(self, path):
if self._resize_no_radio_button.get_active():
return
self.show_info_message(get_message("Resizing..."), Gtk.MessageType.INFO)
command = "mogrify -resize {}! *.png".format(
"320x240" if self._resize_220_132_radio_button.get_active() else "100x60").split()
"220x132" if self._resize_220_132_radio_button.get_active() else "100x60").split()
try:
self._current_process = subprocess.Popen(command, universal_newlines=True, cwd=path)
self._current_process.wait()
except FileNotFoundError as e:
self.show_info_message("Conversion error. " + str(e), Gtk.MessageType.ERROR)
self.on_cancel()
def on_cancel(self, item=None):
if self.is_task_running() and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return True
self.terminate_task()
@run_task
def on_cancel(self, item=None):
if self._current_process:
self._terminate = True
self._current_process.terminate()
time.sleep(1)
def terminate_task(self):
self._terminate = True
@run_idle
def on_close(self, item):
self.on_cancel(item)
if self._current_process:
self._current_process.terminate()
self.show_info_message(get_message("The task is canceled!"), Gtk.MessageType.WARNING)
def on_close(self, window, event):
if self.on_cancel():
return True
self.save_window_size(window)
self.clean_data()
GLib.idle_add(self._dialog.destroy)
def save_window_size(self, window):
t, _ = self._text_view.get_allocated_size()
b, _ = self._info_bar.get_allocated_size()
size = window.get_size()
self._settings.add("picons_downloader_window_size", (size.width, size.height - t.height - b.height))
@run_task
def clean_data(self):
path = self._TMP_DIR + "www.lyngsat.com"
if os.path.exists(path):
shutil.rmtree(path)
@@ -235,21 +287,38 @@ class PiconsDialog:
return
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
self.upload_picons()
self.run_func(lambda: upload_data(settings=self._settings,
download_type=DownloadType.PICONS,
callback=self.append_output,
done_callback=lambda: self.show_info_message(get_message("Done!"),
Gtk.MessageType.INFO)))
@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)
def on_download(self, item):
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
self.run_func(lambda: download_data(settings=self._settings,
download_type=DownloadType.PICONS,
callback=self.append_output))
def on_remove(self, item):
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
self.run_func(lambda: remove_picons(settings=self._settings,
callback=self.append_output,
done_callback=lambda: self.show_info_message(get_message("Done!"),
Gtk.MessageType.INFO)))
@run_task
def run_func(self, func):
try:
upload_data(properties=self._properties,
download_type=DownloadType.PICONS,
profile=self._profile,
callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
GLib.idle_add(self._expander.set_expanded, True)
GLib.idle_add(self._header_download_box.set_sensitive, False)
func()
except OSError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
GLib.idle_add(self._header_download_box.set_sensitive, True)
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@@ -261,7 +330,7 @@ class PiconsDialog:
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})
update_entry_data(entry, self._dialog, settings=self._settings)
@run_idle
def on_selected_toggled(self, toggle, path):
@@ -276,7 +345,8 @@ class PiconsDialog:
self.update_selection(view, False)
def update_selection(self, view, select):
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 7, select))
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 7, select))
self.update_receive_button_state()
def on_url_changed(self, entry):
suit = self._PATTERN.search(entry.get_text())
@@ -289,13 +359,7 @@ class PiconsDialog:
@run_idle
def on_notebook_switch_page(self, nb, box, tab_num):
self._load_providers_button.set_visible(not tab_num)
self._receive_button.set_visible(not tab_num)
self._convert_button.set_visible(tab_num)
self._send_button.set_visible(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):
@@ -311,13 +375,16 @@ class PiconsDialog:
self._expander.set_expanded(True)
convert_to(src_path=picons_path,
dest_path=save_path,
profile=Profile.ENIGMA_2,
s_type=SettingsType.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_button.set_sensitive(len(self.get_selected_providers()) > 0)
try:
self._receive_button.set_sensitive(len(self.get_selected_providers()) > 0)
except TypeError:
pass # NOP
def get_selected_providers(self):
""" returns selected providers """
@@ -328,13 +395,16 @@ class PiconsDialog:
show_dialog(dialog_type, self._dialog, message)
def get_picons_format(self):
picon_format = Profile.ENIGMA_2
picon_format = SettingsType.ENIGMA_2
if self._neutrino_mp_radio_button.get_active():
picon_format = Profile.NEUTRINO_MP
picon_format = SettingsType.NEUTRINO_MP
return picon_format
def is_task_running(self):
return self._current_process and self._current_process.poll() is None
if __name__ == "__main__":
pass

View File

@@ -1,10 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<!-- Generated with glade 3.22.1
The MIT License (MIT)
Copyright (c) 2018-2019 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Author: Dmitriy Yefremov
-->
<!--
The MIT License (MIT)
Copyright (c) 2018 Dmitriy Yefremov
Copyright (c) 2018-2019 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -31,7 +57,7 @@ Author: Dmitriy Yefremov
<requires lib="gtk+" version="3.16"/>
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="fec_store">
<columns>
@@ -76,7 +102,6 @@ Author: Dmitriy Yefremov
</object>
<object class="GtkPopoverMenu" id="left_header_menu">
<property name="can_focus">False</property>
<signal name="button-release-event" handler="on_popover_release" swapped="no"/>
<child>
<object class="GtkBox" id="left_popover_munu_box">
<property name="visible">True</property>
@@ -89,16 +114,52 @@ Author: Dmitriy Yefremov
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkButton" id="open_left_popover_munu_button">
<property name="label">gtk-open</property>
<object class="GtkModelButton" id="open_popover_munu_button">
<property name="width_request">85</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<property name="text" translatable="yes">Open</property>
<signal name="clicked" handler="on_open" object="satellites_tree_store" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="save_popover_munu_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="text" translatable="yes">Save</property>
<signal name="clicked" handler="on_save" object="satellites_editor_tree_view" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="save_as_popover_munu_button">
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="text" translatable="yes">Save as...</property>
<signal name="clicked" handler="on_save_as" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
@@ -106,15 +167,12 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkButton" id="save_left_popover_munu_button">
<property name="label">gtk-save</property>
<object class="GtkModelButton" id="update_popover_munu_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_save" object="satellites_editor_tree_view" swapped="no"/>
<property name="text" translatable="yes">Update</property>
<signal name="clicked" handler="on_update" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -130,69 +188,22 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkButton" id="save_as_left_popover_munu_button">
<property name="label">gtk-save-as</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_save_as" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkButton" id="update_left_popover_munu_button">
<property name="label">gtk-refresh</property>
<object class="GtkModelButton" id="exit_popover_menu_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_update" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">7</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">8</property>
</packing>
</child>
<child>
<object class="GtkButton" id="exit_left_popover_menu_button">
<property name="label">gtk-quit</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<property name="text" translatable="yes">Exit</property>
<signal name="clicked" handler="on_quit" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">9</property>
<property name="position">6</property>
</packing>
</child>
</object>
@@ -414,6 +425,7 @@ Author: Dmitriy Yefremov
<object class="GtkHeaderBar" id="satellites_editor_header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Satellites edit tool</property>
<property name="spacing">1</property>
<property name="show_close_button">True</property>
<child>
@@ -477,6 +489,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Update</property>
<signal name="clicked" handler="on_update" swapped="no"/>
<child>
<object class="GtkImage" id="update_header_button_img">
@@ -494,13 +507,6 @@ Author: Dmitriy Yefremov
</child>
</object>
</child>
<child type="title">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Satellites edit tool</property>
</object>
</child>
</object>
</child>
<child>
@@ -535,6 +541,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="satellite_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="title" translatable="yes">Satellite</property>
<property name="expand">True</property>
<child>
@@ -548,6 +555,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="freq_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="title" translatable="yes">Freq</property>
<property name="expand">True</property>
<child>
@@ -561,6 +569,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="rate_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="title" translatable="yes">Rate</property>
<property name="expand">True</property>
<child>
@@ -574,6 +583,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="pol_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="title" translatable="yes">Pol</property>
<property name="expand">True</property>
<child>
@@ -587,6 +597,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="fec_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="title" translatable="yes">FEC</property>
<property name="expand">True</property>
<child>
@@ -600,6 +611,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="sys_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="title" translatable="yes">System</property>
<property name="expand">True</property>
<child>
@@ -613,6 +625,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="mod_column">
<property name="resizable">True</property>
<property name="min_width">20</property>
<property name="title" translatable="yes">Mod</property>
<property name="expand">True</property>
<child>
@@ -711,7 +724,8 @@ Author: Dmitriy Yefremov
</data>
</object>
<object class="GtkDialog" id="satellite_dialog">
<property name="use-header-bar">1</property>
<property name="use-header-bar">{use_header}</property>
<property name="title" translatable="yes">Satellite</property>
<property name="can_focus">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
@@ -720,41 +734,29 @@ Author: Dmitriy Yefremov
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="gravity">center</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="satellites_dialog_header">
<child type="action">
<object class="GtkButton" id="sat_cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Satellite</property>
<property name="spacing">2</property>
<child>
<object class="GtkButton" id="sat_cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
</child>
<child>
<object class="GtkButton" id="sat_ok_button">
<property name="label">gtk-ok</property>
<property name="width_request">90</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
</child>
<child type="action">
<object class="GtkButton" id="sat_ok_button">
<property name="label">gtk-ok</property>
<property name="width_request">90</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox3">
<object class="GtkBox" id="satelitte_dialog_vbox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
@@ -780,7 +782,7 @@ Author: Dmitriy Yefremov
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkGrid" id="grid3">
<object class="GtkGrid" id="satellite_dialog_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
@@ -829,6 +831,7 @@ Author: Dmitriy Yefremov
<object class="GtkSpinButton" id="sat_position_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">5</property>
<property name="secondary_icon_activatable">False</property>
<property name="secondary_icon_sensitive">False</property>
<property name="input_purpose">number</property>
@@ -901,7 +904,8 @@ Author: Dmitriy Yefremov
</data>
</object>
<object class="GtkDialog" id="transponder_dialog">
<property name="use-header-bar">1</property>
<property name="use-header-bar">{use_header}</property>
<property name="title" translatable="yes">Transponder</property>
<property name="width_request">320</property>
<property name="can_focus">False</property>
<property name="resizable">False</property>
@@ -913,37 +917,25 @@ Author: Dmitriy Yefremov
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="gravity">center</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="transponder_dialog_header">
<child type="action">
<object class="GtkButton" id="tr_cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Transponder</property>
<property name="spacing">2</property>
<child>
<object class="GtkButton" id="tr_cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
</child>
<child>
<object class="GtkButton" id="tr_ok_button">
<property name="label">gtk-ok</property>
<property name="width_request">90</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
</child>
<child type="action">
<object class="GtkButton" id="tr_ok_button">
<property name="label">gtk-ok</property>
<property name="width_request">90</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
</child>
<child internal-child="vbox">

View File

@@ -3,14 +3,19 @@ import time
import concurrent.futures
from math import fabs
from gi.repository import GLib
from app.commons import run_idle, run_task
from app.eparser import get_satellites, write_satellites, Satellite, Transponder
from app.eparser.ecommons import PLS_MODE, get_key_by_value
from app.tools.satellites import SatellitesParser, SatelliteSource
from .search import SearchProvider
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, MOVE_KEYS, KeyboardKey
from .dialogs import show_dialog, DialogType, WaitDialog
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, MOVE_KEYS, KeyboardKey, IS_GNOME_SESSION
from .dialogs import show_dialog, DialogType, get_dialogs_string
from .main_helper import move_items, scroll_to, append_text_to_tview, get_base_model, on_popup_menu
_UI_PATH = UI_RESOURCES_PATH + "satellites_dialog.glade"
def show_satellites_dialog(transient, options):
SatellitesDialog(transient, options).show()
@@ -19,9 +24,9 @@ def show_satellites_dialog(transient, options):
class SatellitesDialog:
_aggr = [None for x in range(9)] # aggregate
def __init__(self, transient, options):
self._data_path = options.get("data_dir_path") + "satellites.xml"
self._options = options
def __init__(self, transient, settings):
self._data_path = settings.data_local_path + "satellites.xml"
self._settings = settings
handlers = {"on_open": self.on_open,
"on_remove": self.on_remove,
@@ -35,24 +40,22 @@ class SatellitesDialog:
"on_transponder_add": self.on_transponder_add,
"on_edit": self.on_edit,
"on_key_release": self.on_key_release,
"on_popover_release": self.on_popover_release,
"on_row_activated": self.on_row_activated,
"on_resize": self.on_resize,
"on_quit": self.on_quit}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("satellites_editor_window", "satellites_tree_store", "popup_menu",
"left_header_menu", "popup_menu_add_image", "popup_menu_add_image_2"))
builder.add_objects_from_string(get_dialogs_string(_UI_PATH),
("satellites_editor_window", "satellites_tree_store", "popup_menu",
"left_header_menu", "popup_menu_add_image", "popup_menu_add_image_2"))
builder.connect_signals(handlers)
self._window = builder.get_object("satellites_editor_window")
self._window.set_transient_for(transient)
self._sat_view = builder.get_object("satellites_editor_tree_view")
self._wait_dialog = WaitDialog(self._window)
# Setting the last size of the dialog window if it was saved
window_size = self._options.get("sat_editor_window_size", None)
window_size = self._settings.get("sat_editor_window_size")
if window_size:
self._window.resize(*window_size)
@@ -60,15 +63,20 @@ class SatellitesDialog:
4: builder.get_object("fec_store"),
5: builder.get_object("system_store"),
6: builder.get_object("mod_store")}
self.on_satellites_list_load(self._sat_view.get_model())
self.load_satellites_list(self._sat_view.get_model())
def load_satellites_list(self, model):
gen = self.on_satellites_list_load(model)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def show(self):
self._window.show()
def on_resize(self, window):
""" Stores new size properties for dialog window after resize """
if self._options:
self._options["sat_editor_window_size"] = window.get_size()
if self._settings:
self._settings.add("sat_editor_window_size", window.get_size())
@run_idle
def on_quit(self, *args):
@@ -84,7 +92,7 @@ class SatellitesDialog:
show_dialog(DialogType.ERROR, self._window, text="No satellites.xml file is selected!")
return
self._data_path = response
self.on_satellites_list_load(model)
self.load_satellites_list(model)
def get_file_dialog_response(self, action: Gtk.FileChooserAction):
file_filter = Gtk.FileFilter()
@@ -92,7 +100,7 @@ class SatellitesDialog:
file_filter.set_name("satellites.xml")
response = show_dialog(dialog_type=DialogType.CHOOSER,
transient=self._window,
options=self._options,
settings=self._settings,
action_type=action,
file_filter=file_filter)
return response
@@ -133,28 +141,20 @@ class SatellitesDialog:
elif key is KeyboardKey.LEFT or key is KeyboardKey.RIGHT:
view.do_unselect_all(view)
def on_popover_release(self, menu, event):
menu.hide()
@run_idle
def on_satellites_list_load(self, model):
""" Load satellites data into model """
try:
self._wait_dialog.show()
satellites = get_satellites(self._data_path)
yield True
except FileNotFoundError as e:
show_dialog(DialogType.ERROR, self._window, getattr(e, "message", str(e)) +
"\n\nPlease, download files from receiver or setup your path for read data!")
return
else:
model.clear()
self.append_data(model, satellites)
finally:
self._wait_dialog.hide()
@run_idle
def append_data(self, model, satellites):
for sat in satellites:
append_satellite(model, sat)
for sat in satellites:
append_satellite(model, sat)
yield True
def on_add(self, view):
""" Common adding """
@@ -319,9 +319,9 @@ class TransponderDialog:
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("transponder_dialog", "pol_store", "fec_store", "mod_store", "system_store",
"pls_mode_store"))
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("transponder_dialog", "pol_store", "fec_store", "mod_store", "system_store",
"pls_mode_store"))
builder.connect_signals(handlers)
self._dialog = builder.get_object("transponder_dialog")
@@ -364,7 +364,7 @@ class TransponderDialog:
self._fec_box.set_active_id(transponder.fec_inner)
self._sys_box.set_active_id(transponder.system)
self._mod_box.set_active_id(transponder.modulation)
self._pls_mode_box.set_active_id(transponder.pls_mode)
self._pls_mode_box.set_active_id(PLS_MODE.get(transponder.pls_mode, None))
self._is_id_entry.set_text(transponder.is_id if transponder.is_id else "")
self._pls_code_entry.set_text(transponder.pls_code if transponder.pls_code else "")
@@ -375,7 +375,7 @@ class TransponderDialog:
fec_inner=self._fec_box.get_active_id(),
system=self._sys_box.get_active_id(),
modulation=self._mod_box.get_active_id(),
pls_mode=self._pls_mode_box.get_active_id(),
pls_mode=get_key_by_value(PLS_MODE, self._pls_mode_box.get_active_id()),
pls_code=self._pls_code_entry.get_text(),
is_id=self._is_id_entry.get_text())
@@ -403,8 +403,8 @@ class SatelliteDialog:
def __init__(self, transient, satellite: Satellite = None):
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("satellite_dialog", "side_store", "pos_adjustment"))
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("satellite_dialog", "side_store", "pos_adjustment"))
self._dialog = builder.get_object("satellite_dialog")
self._dialog.set_transient_for(transient)

View File

@@ -35,6 +35,9 @@
<row>
<col id="0" translatable="yes">4/5</col>
</row>
<row>
<col id="0" translatable="yes">6/7</col>
</row>
<row>
<col id="0" translatable="yes">9/10</col>
</row>
@@ -214,8 +217,21 @@
</row>
</data>
</object>
<object class="GtkComboBox" id="rate_lp_combo_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="model">fec_list_store</property>
<property name="id_column">0</property>
<child>
<object class="GtkCellRendererText" id="rate_lp_cellrenderertext"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<object class="GtkDialog" id="service_details_dialog">
<property name="use-header-bar">1</property>
<property name="use-header-bar">{use_header}</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Service data</property>
<property name="resizable">False</property>
@@ -271,6 +287,16 @@
<property name="margin_right">2</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="service_data_frame">
<property name="visible">True</property>
@@ -913,7 +939,7 @@
<property name="can_focus">False</property>
<property name="column_spacing">2</property>
<child>
<object class="GtkLabel" id="label1">
<object class="GtkLabel" id="position_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Position</property>
@@ -924,7 +950,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label16">
<object class="GtkLabel" id="freq_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Freq</property>
@@ -950,7 +976,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label17">
<object class="GtkLabel" id="rate_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Rate</property>
@@ -976,9 +1002,11 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label18">
<object class="GtkLabel" id="pol_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="label" translatable="yes">Pol</property>
</object>
<packing>
@@ -1006,9 +1034,11 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label19">
<object class="GtkLabel" id="fec_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="label" translatable="yes">FEC</property>
</object>
<packing>
@@ -1052,7 +1082,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label30">
<object class="GtkLabel" id="namespace_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Namespace</property>
@@ -1078,7 +1108,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label4">
<object class="GtkLabel" id="tid_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">TID</property>
@@ -1105,7 +1135,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label8">
<object class="GtkLabel" id="nid_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">NID</property>
@@ -1149,7 +1179,7 @@
<property name="margin_bottom">5</property>
<property name="column_spacing">2</property>
<child>
<object class="GtkLabel" id="label25">
<object class="GtkLabel" id="inversion_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Inversion</property>
@@ -1160,7 +1190,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label23">
<object class="GtkLabel" id="rolloff_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Rolloff</property>
@@ -1172,7 +1202,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label24">
<object class="GtkLabel" id="pilot_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Pilot</property>
@@ -1240,7 +1270,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label10">
<object class="GtkLabel" id="pls_mode_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">PLS mode</property>
@@ -1270,7 +1300,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label29">
<object class="GtkLabel" id="tr_flag_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Flag</property>
@@ -1296,7 +1326,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label9">
<object class="GtkLabel" id="pls_code_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">PLS code</property>
@@ -1322,7 +1352,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label28">
<object class="GtkLabel" id="stream_id_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Stream ID</property>
@@ -1348,7 +1378,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label21">
<object class="GtkLabel" id="system_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">System</property>
@@ -1380,7 +1410,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="label22">
<object class="GtkLabel" id="mod_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Mod</property>
@@ -1517,7 +1547,7 @@
</columns>
</object>
<object class="GtkDialog" id="tr_services_dialog">
<property name="use-header-bar">1</property>
<property name="use-header-bar">{use_header}</property>
<property name="width_request">480</property>
<property name="height_request">300</property>
<property name="can_focus">False</property>
@@ -1557,23 +1587,69 @@
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="srv_list_dialog_hdr_label">
<object class="GtkInfoBar" id="srv_list_dialog_info_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">20</property>
<property name="margin_right">20</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label" translatable="yes">Changes will be applied to all services of this transponder!
<property name="message_type">warning</property>
<property name="show_close_button">True</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<child>
<object class="GtkLabel" id="srv_list_dialog_hdr_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">20</property>
<property name="margin_right">20</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label" translatable="yes">Changes will be applied to all services of this transponder!
Continue?</property>
<property name="justify">center</property>
<property name="lines">2</property>
<property name="justify">center</property>
<property name="lines">2</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="spacing">16</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
<property name="position">0</property>
</packing>
</child>
<child>
@@ -1582,9 +1658,6 @@ Continue?</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="margin_left">1</property>
<property name="margin_right">1</property>
<property name="margin_bottom">1</property>
<child>
<object class="GtkTreeView" id="tr_services_list_treeview">
<property name="visible">True</property>

View File

@@ -4,12 +4,15 @@ import os
from app.commons import run_idle
from app.eparser import Service
from app.eparser.ecommons import MODULATION, Inversion, ROLL_OFF, Pilot, Flag, Pids, POLARIZATION, \
get_key_by_value, get_value_by_name, FEC_DEFAULT, PLS_MODE, SERVICE_TYPE
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
get_key_by_value, get_value_by_name, FEC_DEFAULT, PLS_MODE, SERVICE_TYPE, T_MODULATION, C_MODULATION, TrType, \
SystemCable, T_SYSTEM, BANDWIDTH, TRANSMISSION_MODE, GUARD_INTERVAL, HIERARCHY, T_FEC
from app.settings import SettingsType
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, TEXT_DOMAIN, CODED_ICON, Column, IS_GNOME_SESSION
from .dialogs import show_dialog, DialogType, Action, get_dialogs_string
from .main_helper import get_base_model
_UI_PATH = UI_RESOURCES_PATH + "service_details_dialog.glade"
class ServiceDetailsDialog:
_ENIGMA2_DATA_ID = "{:04x}:{:08x}:{:04x}:{:04x}:{}:{}"
@@ -31,7 +34,7 @@ class ServiceDetailsDialog:
_DIGIT_ENTRY_NAME = "digit-entry"
def __init__(self, transient, options, srv_view, fav_view, services, bouquets, action=Action.EDIT):
def __init__(self, transient, settings, srv_view, fav_view, services, bouquets, new_color, action=Action.EDIT):
handlers = {"on_system_changed": self.on_system_changed,
"on_save": self.on_save,
"on_create_new": self.on_create_new,
@@ -43,28 +46,30 @@ class ServiceDetailsDialog:
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_from_file(UI_RESOURCES_PATH + "service_details_dialog.glade")
builder.add_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION))
builder.connect_signals(handlers)
self._builder = builder
self._dialog = builder.get_object("service_details_dialog")
self._dialog.set_transient_for(transient)
self._profile = Profile(options["profile"])
self._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._s_type = settings.setting_type
self._tr_type = None
self._satellites_xml_path = settings.data_local_path + "satellites.xml"
self._picons_dir_path = settings.picons_local_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._new_color = new_color
self._transponder_services_iters = None
self._current_model = None
self._current_itr = None
# Patterns
self._DIGIT_PATTERN = re.compile("\D")
self._NON_EMPTY_PATTERN = re.compile("(?:^[\s]*$|\D)")
self._CAID_PATTERN = re.compile("(?:^[\s]*$)|(C:[0-9a-z]{4})(,C:[0-9a-z]{4})*")
self._DIGIT_PATTERN = re.compile("\\D")
self._NON_EMPTY_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
self._CAID_PATTERN = re.compile("(?:^[\\s]*$)|(C:[0-9a-fA-F]{1,4})(,C:[0-9a-fA-F]{1,4})*")
# Buttons
self._apply_button = builder.get_object("apply_button")
self._create_button = builder.get_object("create_button")
@@ -116,6 +121,7 @@ class ServiceDetailsDialog:
self._sat_pos_button = builder.get_object("sat_pos_button")
self._pol_combo_box = builder.get_object("pol_combo_box")
self._fec_combo_box = builder.get_object("fec_combo_box")
self._rate_lp_combo_box = builder.get_object("rate_lp_combo_box")
self._sys_combo_box = builder.get_object("sys_combo_box")
self._mod_combo_box = builder.get_object("mod_combo_box")
self._invertion_combo_box = builder.get_object("invertion_combo_box")
@@ -130,7 +136,7 @@ class ServiceDetailsDialog:
self._TRANSPONDER_ELEMENTS = (self._sat_pos_button, self._pol_combo_box, self._invertion_combo_box,
self._sys_combo_box, self._freq_entry, self._transponder_id_entry,
self._network_id_entry, self._namespace_entry, self._fec_combo_box,
self._rate_entry)
self._rate_entry, self._rate_lp_combo_box)
if self._action is Action.EDIT:
self.update_data_elements()
@@ -183,7 +189,7 @@ class ServiceDetailsDialog:
if not itr:
return
srv = Service(*self._current_model[itr][:])
srv = Service(*self._current_model[itr][: Column.SRV_TOOLTIP])
self._old_service = srv
self._current_itr = itr
# Service
@@ -191,21 +197,24 @@ class ServiceDetailsDialog:
self._package_entry.set_text(srv.package)
self._sid_entry.set_text(str(int(srv.ssid, 16)))
# Transponder
tr_type = srv.transponder_type
if self._s_type is SettingsType.ENIGMA_2:
self._tr_type = TrType(srv.transponder_type)
self._freq_entry.set_text(srv.freq)
self._rate_entry.set_text(srv.rate)
self.select_active_text(self._pol_combo_box, srv.pol)
self.select_active_text(self._fec_combo_box, srv.fec)
self.select_active_text(self._sys_combo_box, srv.system)
if tr_type in "tc" and self._profile is Profile.ENIGMA_2:
if self._tr_type is TrType.Terrestrial:
self.update_ui_for_terrestrial()
elif self._tr_type is TrType.Cable:
self.update_ui_for_cable()
else:
self.set_sat_positions(srv.pos)
if self._profile is Profile.ENIGMA_2:
if self._s_type is SettingsType.ENIGMA_2:
self.init_enigma2_service_data(srv)
self.init_enigma2_transponder_data(srv)
elif self._profile is Profile.NEUTRINO_MP:
elif self._s_type is SettingsType.NEUTRINO_MP:
self.init_neutrino_data(srv)
self.init_neutrino_ui_elements()
@@ -264,21 +273,42 @@ class ServiceDetailsDialog:
""" Transponder data initialisation """
data = srv.data_id.split(":")
tr_data = srv.transponder.split(":")
if srv.system == "DVB-S2":
self.select_active_text(self._mod_combo_box, MODULATION.get(tr_data[8]))
self.select_active_text(self._rolloff_combo_box, ROLL_OFF.get(tr_data[9]))
self.select_active_text(self._pilot_combo_box, Pilot(tr_data[10]).name)
self._tr_flag_entry.set_text(tr_data[7])
if len(tr_data) > 12:
self._stream_id_entry.set_text(tr_data[11])
self._pls_code_entry.set_text(tr_data[12])
self.select_active_text(self._pls_mode_combo_box, PLS_MODE.get(tr_data[13]))
tr_type = TrType(srv.transponder_type)
self._namespace_entry.set_text(str(int(data[1], 16)))
self._transponder_id_entry.set_text(str(int(data[2], 16)))
self._network_id_entry.set_text(str(int(data[3], 16)))
self.select_active_text(self._invertion_combo_box, Inversion(tr_data[5]).name)
if tr_type is TrType.Satellite:
self.select_active_text(self._invertion_combo_box, Inversion(tr_data[5]).name)
if srv.system == "DVB-S2":
self.select_active_text(self._mod_combo_box, MODULATION.get(tr_data[8]))
self.select_active_text(self._rolloff_combo_box, ROLL_OFF.get(tr_data[9]))
self.select_active_text(self._pilot_combo_box, Pilot(tr_data[10]).name)
self._tr_flag_entry.set_text(tr_data[7])
if len(tr_data) > 12:
self._stream_id_entry.set_text(tr_data[11])
self._pls_code_entry.set_text(tr_data[12])
self.select_active_text(self._pls_mode_combo_box, PLS_MODE.get(tr_data[13]))
elif tr_type is TrType.Cable:
self.select_active_text(self._invertion_combo_box, Inversion(tr_data[2]).name)
self.select_active_text(self._mod_combo_box, C_MODULATION.get(tr_data[3]))
self.select_active_text(self._fec_combo_box, FEC_DEFAULT.get(tr_data[4]))
self.select_active_text(self._sys_combo_box, SystemCable(tr_data[5]).name)
elif tr_type is TrType.Terrestrial:
self.select_active_text(self._fec_combo_box, T_FEC.get(tr_data[2]))
self.select_active_text(self._rate_lp_combo_box, T_FEC.get(tr_data[3]))
# Pol -> Bandwidth
self.select_active_text(self._pol_combo_box, BANDWIDTH.get(tr_data[1]))
self.select_active_text(self._mod_combo_box, T_MODULATION.get(tr_data[4]))
# Transmission Mode -> Roll off
self.select_active_text(self._rolloff_combo_box, TRANSMISSION_MODE.get(tr_data[5]))
# GuardInterval -> Pilot
self.select_active_text(self._pilot_combo_box, GUARD_INTERVAL.get(tr_data[6]))
# Hierarchy -> Pls Mode
self.select_active_text(self._pls_mode_combo_box, HIERARCHY.get(tr_data[7]))
self.select_active_text(self._invertion_combo_box, Inversion(tr_data[8]).name)
self.select_active_text(self._sys_combo_box, T_SYSTEM.get(tr_data[9]))
# Should be called last to properly initialize the reference
self._srv_type_entry.set_text(data[4])
@@ -295,7 +325,9 @@ class ServiceDetailsDialog:
def init_neutrino_ui_elements(self):
self._builder.get_object("flags_box").set_visible(False)
self._builder.get_object("pids_grid").set_visible(False)
self._builder.get_object("tr_grid").remove_column(7)
tr_grid = self._builder.get_object("tr_grid")
tr_grid.remove_column(7)
tr_grid.set_margin_bottom(5)
self._builder.get_object("tr_extra_expander").set_visible(False)
self._builder.get_object("srv_separator").set_visible(False)
@@ -346,17 +378,32 @@ class ServiceDetailsDialog:
self.on_edit() if self._action is Action.EDIT else self.on_new()
self._dialog.destroy()
def on_new(self):
""" Create new service. """
service = self.get_service(*self.get_srv_data(), self.get_satellite_transponder_data())
show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
def on_edit(self):
""" Edit current service. """
fav_id, data_id = self.get_srv_data()
# transponder
transponder = self._old_service.transponder
tr_type = self._old_service.transponder_type
if self._tr_edit_switch.get_active():
transponder = self.get_transponder_data(tr_type)
if self._transponder_services_iters:
self.update_transponder_services(transponder, tr_type)
try:
if self._tr_type is TrType.Satellite:
transponder = self.get_satellite_transponder_data()
elif self._tr_type is TrType.Terrestrial:
transponder = self.get_terrestrial_transponder_data()
elif self._tr_type is TrType.Cable:
transponder = self.get_cable_transponder_data()
except Exception as e:
print(e)
show_dialog(DialogType.ERROR, transient=self._dialog, text="Error getting transponder parameters!")
else:
if self._transponder_services_iters:
self.update_transponder_services(transponder)
# service
service = self.get_service(fav_id, data_id, transponder, tr_type)
service = self.get_service(fav_id, data_id, transponder)
old_fav_id = self._old_service.fav_id
if old_fav_id != fav_id:
self.update_bouquets(fav_id, old_fav_id)
@@ -365,6 +412,14 @@ class ServiceDetailsDialog:
if self._old_service.picon_id != service.picon_id:
self.update_picon_name(self._old_service.picon_id, service.picon_id)
flags = service.flags_cas
extra_data = {Column.SRV_TOOLTIP: None, Column.SRV_BACKGROUND: None}
if flags:
f_flags = list(filter(lambda x: x.startswith("f:"), flags.split(",")))
if f_flags and Flag.is_new(int(f_flags[0][2:])):
extra_data[Column.SRV_BACKGROUND] = self._new_color
self._current_model.set(self._current_itr, extra_data)
self._current_model.set(self._current_itr, {i: v for i, v in enumerate(service)})
self.update_fav_view(self._old_service, service)
self._old_service = service
@@ -403,14 +458,12 @@ class ServiceDetailsDialog:
os.rename(old_file, new_file)
break
def on_new(self):
service = self.get_service(*self.get_srv_data(), self.get_transponder_data())
show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
# ***************** Service ********************* #
def get_service(self, fav_id, data_id, transponder, tr_type):
freq, rate, pol, fec, system, pos = self.get_transponder_values(tr_type)
def get_service(self, fav_id, data_id, transponder):
freq, rate, pol, fec, system, pos = self.get_transponder_values()
return Service(flags_cas=self.get_flags(),
transponder_type=tr_type,
transponder_type=self._old_service.transponder_type,
coded=CODED_ICON if self._cas_entry.get_text() else None,
service=self._name_entry.get_text(),
locked=self._old_service.locked,
@@ -431,9 +484,9 @@ class ServiceDetailsDialog:
transponder=transponder)
def get_flags(self):
if self._profile is Profile.ENIGMA_2:
if self._s_type is SettingsType.ENIGMA_2:
return self.get_enigma2_flags()
elif self._profile is Profile.NEUTRINO_MP:
elif self._s_type is SettingsType.NEUTRINO_MP:
return self._old_service.flags_cas
def get_enigma2_flags(self):
@@ -479,29 +532,36 @@ class ServiceDetailsDialog:
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:
if self._s_type is SettingsType.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:
elif self._s_type is SettingsType.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, tr_type):
if tr_type == "s":
# ***************** Transponder ********************* #
def get_transponder_values(self):
freq = self._freq_entry.get_text()
fec = self._fec_combo_box.get_active_id()
system = self._sys_combo_box.get_active_id()
if self._tr_type is TrType.Satellite or self._s_type is SettingsType.NEUTRINO_MP:
freq = self._freq_entry.get_text()
rate = self._rate_entry.get_text()
pol = self._pol_combo_box.get_active_id()
fec = self._fec_combo_box.get_active_id()
system = self._sys_combo_box.get_active_id()
pos = str(round(self._sat_pos_button.get_value(), 1))
return freq, rate, pol, fec, system, pos
elif tr_type in "tc":
elif self._tr_type is TrType.Terrestrial:
o_srv = self._old_service
return o_srv.freq, o_srv.rate, o_srv.pol, o_srv.fec, o_srv.system, o_srv.pos
return freq, o_srv.rate, o_srv.pol, fec, system, o_srv.pos
elif self._tr_type is TrType.Cable:
o_srv = self._old_service
return freq, self._rate_entry.get_text(), o_srv.pol, fec, o_srv.system, o_srv.pos
def get_transponder_data(self, tr_type):
def get_satellite_transponder_data(self):
sys = self._sys_combo_box.get_active_id()
freq = self._freq_entry.get_text()
rate = self._rate_entry.get_text()
@@ -511,7 +571,7 @@ class ServiceDetailsDialog:
inv = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
srv_sys = "0" # !!!
if self._profile is Profile.ENIGMA_2:
if self._s_type is SettingsType.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
@@ -525,24 +585,53 @@ class ServiceDetailsDialog:
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:
elif self._s_type is SettingsType.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, tr_type):
def get_terrestrial_transponder_data(self):
tr_data = re.split("\s|:", self._old_service.transponder)
# frequency, bandwidth, code rate HP, code rate LP, modulation, transmission mode, guard interval, hierarchy,
# inversion, system, plp_id
# Bandwidth -> Pol, Rate HP -> FEC, TransmissionMode -> Roll off, GuardInterval -> Pilot, Hierarchy -> Pls Mode
tr_data[1] = self._freq_entry.get_text()
tr_data[2] = self.get_value_from_combobox_id(self._pol_combo_box, BANDWIDTH)
tr_data[3] = self.get_value_from_combobox_id(self._fec_combo_box, T_FEC)
tr_data[4] = self.get_value_from_combobox_id(self._rate_lp_combo_box, T_FEC)
tr_data[5] = self.get_value_from_combobox_id(self._mod_combo_box, T_MODULATION)
tr_data[6] = self.get_value_from_combobox_id(self._rolloff_combo_box, TRANSMISSION_MODE)
tr_data[7] = self.get_value_from_combobox_id(self._pilot_combo_box, GUARD_INTERVAL)
tr_data[8] = self.get_value_from_combobox_id(self._pls_mode_combo_box, HIERARCHY)
tr_data[9] = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
tr_data[10] = self.get_value_from_combobox_id(self._sys_combo_box, T_SYSTEM)
return "{} {}".format(tr_data[0], ":".join(tr_data[1:]))
def get_cable_transponder_data(self):
tr_data = re.split("\s|:", self._old_service.transponder)
# frequency, symbol_rate, modulation, inversion, fec_inner, system;
tr_data[1] = self._freq_entry.get_text()
tr_data[2] = self._rate_entry.get_text()
tr_data[3] = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
tr_data[4] = self.get_value_from_combobox_id(self._mod_combo_box, C_MODULATION)
tr_data[5] = self.get_value_from_combobox_id(self._fec_combo_box, FEC_DEFAULT)
tr_data[6] = get_value_by_name(SystemCable, self._sys_combo_box.get_active_id())
return "{} {}".format(tr_data[0], ":".join(tr_data[1:]))
def update_transponder_services(self, transponder):
for itr in self._transponder_services_iters:
srv = self._current_model[itr][:]
srv[-9], srv[-8], srv[-7], srv[-6], srv[-5], srv[-4] = self.get_transponder_values(tr_type)
srv[-1] = transponder
srv = self._current_model[itr][:Column.SRV_TOOLTIP]
srv[Column.SRV_FREQ], srv[Column.SRV_RATE], srv[Column.SRV_POL], srv[Column.SRV_FEC], srv[
Column.SRV_SYSTEM], srv[Column.SRV_POS] = self.get_transponder_values()
srv[Column.SRV_TRANSPONDER] = transponder
srv = Service(*srv)
self._services[srv.fav_id] = self._services.pop(srv.fav_id)._replace(transponder=transponder)
self._current_model.set(itr, {i: v for i, v in enumerate(srv)})
# ***************** Others *********************#
def select_active_text(self, box: Gtk.ComboBox, text):
def select_active_text(self, box, text):
model = box.get_model()
for index, row in enumerate(model):
if row[0] == text:
@@ -563,13 +652,8 @@ class ServiceDetailsDialog:
return get_key_by_value(dc, cb_id)
@run_idle
def on_tr_edit_toggled(self, switch: Gtk.Switch, active):
def on_tr_edit_toggled(self, switch, active):
if active and self._action is Action.EDIT:
if self._old_service.transponder_type == "t":
show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
switch.set_active(False)
return
self._transponder_services_iters = []
response = TransponderServicesDialog(self._dialog,
self._current_model,
@@ -580,7 +664,8 @@ class ServiceDetailsDialog:
self._transponder_services_iters = None
return
self.update_dvb_s2_elements(active and self._sys_combo_box.get_active_id() == "DVB-S2")
self.update_dvb_s2_elements(active and (self._sys_combo_box.get_active_id() == "DVB-S2"
or self._old_service.transponder_type in "tc"))
for elem in self._TRANSPONDER_ELEMENTS:
elem.set_sensitive(active)
@@ -597,7 +682,7 @@ class ServiceDetailsDialog:
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):
if not self.is_data_correct() or (event is None and self._s_type is SettingsType.NEUTRINO_MP):
return
self.update_reference_entry()
@@ -606,7 +691,7 @@ class ServiceDetailsDialog:
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:
if self._s_type is SettingsType.ENIGMA_2:
on_id = int(self._namespace_entry.get_text())
ref = "1:0:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0".format(srv_type, ssid, tid, nid, on_id)
self._reference_entry.set_text(ref)
@@ -614,28 +699,136 @@ class ServiceDetailsDialog:
self._reference_entry.set_text("{:x}{:04x}{:04x}".format(tid, nid, ssid))
def update_ui_for_terrestrial(self):
tr_grid = self.get_transponder_grid_for_non_satellite()
tr_grid.remove_column(1)
tr_grid.insert_column(1)
extra_tr_grid = self._builder.get_object("extra_transponder_grid")
for i in range(4):
extra_tr_grid.remove_column(6)
# Bandwidth -> Pol
pol_label = self._builder.get_object("pol_label")
pol_label.set_text("Bandwidth")
tr_grid.attach(pol_label, 1, 0, 1, 1)
tr_grid.attach(self._pol_combo_box, 1, 1, 1, 1)
# Rate HP -> FEC
self._builder.get_object("fec_label").set_text("Rate HP")
# Rate LP
tr_grid.insert_column(3)
rate_lp_label = self._builder.get_object("pls_code_label")
rate_lp_label.set_text("Rate LP")
tr_grid.attach(rate_lp_label, 3, 0, 1, 1)
tr_grid.attach(self._rate_lp_combo_box, 3, 1, 1, 1)
# Modulation
tr_grid.insert_column(4)
extra_tr_grid.remove_column(1)
tr_grid.attach(self._builder.get_object("mod_label"), 4, 0, 1, 1)
tr_grid.attach(self._mod_combo_box, 4, 1, 1, 1)
# TransmissionMode -> Roll off
rolloff_label = self._builder.get_object("rolloff_label")
rolloff_label.set_text("T mode")
# GuardInterval -> Pilot
pilot_label = self._builder.get_object("pilot_label")
pilot_label.set_text("Guard Interval")
# Hierarchy -> Pls Mode
pls_mode_label = self._builder.get_object("pls_mode_label")
pls_mode_label.set_text("Hierarchy")
# Models
fec_model, modulation_model, sys_model = self.get_models_for_non_satellite()
pol_model = self._pol_combo_box.get_model()
roll_off_model = self._rolloff_combo_box.get_model()
pilot_model = self._pilot_combo_box.get_model()
pls_model = self._pls_mode_combo_box.get_model()
# Models clearing
for m in pol_model, roll_off_model, pilot_model, pls_model:
m.clear()
self.init_terrestrial_models((pol_model, modulation_model, roll_off_model, pilot_model, pls_model, sys_model),
(BANDWIDTH, T_MODULATION, TRANSMISSION_MODE, GUARD_INTERVAL, HIERARCHY, T_SYSTEM))
# Removing the latest FEC elements from the model
for itr in [fec_model.get_iter(Gtk.TreePath.new_from_string(str(i))) for i in range(7, 11)]:
fec_model.remove(itr)
# Extra
self._namespace_entry.set_max_width_chars(15)
self._sys_combo_box.set_hexpand(False)
def init_terrestrial_models(self, models, properties):
for index, model in enumerate(models):
for v in properties[index].values():
model.append((v,))
def update_ui_for_cable(self):
tr_grid = self.get_transponder_grid_for_non_satellite()
tr_box = self._builder.get_object("tr_box")
# Models
fec_model, modulation_model, system_model = self.get_models_for_non_satellite()
extra_tr_grid = self._builder.get_object("extra_transponder_grid")
for child in extra_tr_grid.get_children():
extra_tr_grid.remove(child)
tr_grid.remove(extra_tr_grid)
tr_grid.insert_column(3)
tr_grid.insert_column(4)
tr_grid.insert_column(5)
# Modulation
tr_grid.attach(self._builder.get_object("mod_label"), 3, 0, 1, 1)
tr_grid.attach(self._mod_combo_box, 3, 1, 1, 1)
for v in C_MODULATION.values():
modulation_model.append((v,))
# Inversion
tr_grid.attach(self._builder.get_object("inversion_label"), 4, 0, 1, 1)
tr_grid.attach(self._invertion_combo_box, 4, 1, 1, 1)
# System
tr_grid.attach(self._builder.get_object("system_label"), 5, 0, 1, 1)
tr_grid.attach(self._sys_combo_box, 5, 1, 1, 1)
system_model.append((SystemCable.ANNEX_A.name,))
system_model.append((SystemCable.ANNEX_C.name,))
# FEC
fec_model.append(("None",))
# Extra
tr_box.remove(self._tr_extra_expander)
tr_grid.set_margin_bottom(5)
self._freq_entry.set_width_chars(10)
self._freq_entry.set_max_width_chars(10)
self._rate_entry.set_width_chars(10)
self._rate_entry.set_max_width_chars(10)
self._transponder_id_entry.set_max_width_chars(8)
self._network_id_entry.set_max_width_chars(8)
def get_transponder_grid_for_non_satellite(self):
self._pids_grid.set_visible(False)
tr_frame = self._builder.get_object("transponder_data_frame")
tr_frame.set_visible(False)
self._builder.get_object("srv_separator").set_visible(False)
self._reference_entry.set_max_width_chars(22)
tr_grid = self._builder.get_object("tr_grid")
tr_grid.remove_column(0)
tr_grid.remove_column(2)
return tr_grid
def get_models_for_non_satellite(self):
fec_model = self._fec_combo_box.get_model()
modulation_model = self._mod_combo_box.get_model()
modulation_model.clear()
system_model = self._sys_combo_box.get_model()
system_model.clear()
return fec_model, modulation_model, system_model
class TransponderServicesDialog:
def __init__(self, transient, model, transponder, tr_iters):
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "service_details_dialog.glade",
("tr_services_dialog", "transponder_services_liststore"))
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("tr_services_dialog", "transponder_services_liststore"))
self._dialog = builder.get_object("tr_services_dialog")
self._dialog.set_transient_for(transient)
self._srv_model = builder.get_object("transponder_services_liststore")
self.append_services(model, transponder, tr_iters)
builder.get_object("srv_list_dialog_info_bar").connect("response", lambda bar, resp: bar.hide())
def append_services(self, model, transponder, tr_iters):
for row in model:
if row[-1] == transponder:
self._srv_model.append((row[3], row[6], row[7], row[10], row[11], row[16]))
if row[Column.SRV_TRANSPONDER] == transponder:
self._srv_model.append((row[Column.SRV_SERVICE], row[Column.SRV_PACKAGE], row[Column.SRV_TYPE],
row[Column.SRV_SSID], row[Column.SRV_FREQ], row[Column.SRV_POS]))
tr_iters.append(model.get_iter(row.path))
def show(self):

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,13 @@
import os
from enum import Enum
from pathlib import Path
from app.commons import run_task, run_idle
from app.connections import test_telnet, test_ftp, TestException, test_http
from app.properties import write_config, Profile, get_default_settings
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .main_helper import update_entry_data
from app.connections import test_telnet, test_ftp, TestException, test_http, HttpApiException
from app.settings import SettingsType, Settings
from app.ui.dialogs import show_dialog, DialogType
from .main_helper import update_entry_data, scroll_to
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON
def show_settings_dialog(transient, options):
@@ -19,22 +22,43 @@ class Property(Enum):
class SettingsDialog:
def __init__(self, transient, options):
handlers = {"on_data_dir_field_icon_press": self.on_data_dir_field_icon_press,
"on_picons_dir_field_icon_press": self.on_picons_dir_field_icon_press,
"on_profile_changed": self.on_profile_changed,
def __init__(self, transient, settings: Settings):
handlers = {"on_field_icon_press": self.on_field_icon_press,
"on_settings_type_changed": self.on_settings_type_changed,
"on_reset": self.on_reset,
"on_response": self.on_response,
"apply_settings": self.apply_settings,
"on_apply_profile_settings": self.on_apply_profile_settings,
"on_connection_test": self.on_connection_test,
"on_info_bar_close": self.on_info_bar_close}
"on_info_bar_close": self.on_info_bar_close,
"on_set_color_switch_state": self.on_set_color_switch_state,
"on_http_mode_switch_state": self.on_http_mode_switch_state,
"on_yt_dl_switch_state": self.on_yt_dl_switch_state,
"on_send_to_switch_state": self.on_send_to_switch_state,
"on_profile_add": self.on_profile_add,
"on_profile_edit": self.on_profile_edit,
"on_profile_remove": self.on_profile_remove,
"on_profile_deleted": self.on_profile_deleted,
"on_profile_inserted": self.on_profile_inserted,
"on_profile_edited": self.on_profile_edited,
"on_profile_selected": self.on_profile_selected,
"on_profile_set_default": self.on_profile_set_default,
"on_lang_changed": self.on_lang_changed,
"on_main_settings_visible": self.on_main_settings_visible,
"on_network_settings_visible": self.on_network_settings_visible,
"on_http_use_ssl_toggled": self.on_http_use_ssl_toggled,
"on_click_mode_togged": self.on_click_mode_togged,
"on_view_popup_menu": self.on_view_popup_menu}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_from_file(UI_RESOURCES_PATH + "settings_dialog.glade")
builder.connect_signals(handlers)
self._dialog = builder.get_object("settings_dialog")
self._dialog.set_transient_for(transient)
self._header_bar = builder.get_object("header_bar")
self._main_stack = builder.get_object("main_stack")
# Network
self._host_field = builder.get_object("host_field")
self._port_field = builder.get_object("port_field")
self._login_field = builder.get_object("login_field")
@@ -42,119 +66,216 @@ class SettingsDialog:
self._http_login_field = builder.get_object("http_login_field")
self._http_password_field = builder.get_object("http_password_field")
self._http_port_field = builder.get_object("http_port_field")
self._http_use_ssl_check_button = builder.get_object("http_use_ssl_check_button")
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._settings_stack = builder.get_object("settings_stack")
# Paths
self._services_field = builder.get_object("services_field")
self._user_bouquet_field = builder.get_object("user_bouquet_field")
self._satellites_xml_field = builder.get_object("satellites_xml_field")
self._data_dir_field = builder.get_object("data_dir_field")
self._picons_field = builder.get_object("picons_field")
self._picons_dir_field = builder.get_object("picons_dir_field")
self._enigma_radio_button = builder.get_object("enigma_radio_button")
self._neutrino_radio_button = builder.get_object("neutrino_radio_button")
self._support_ver5_check_button = builder.get_object("support_ver5_check_button")
self._support_http_api_check_button = builder.get_object("support_http_api_check_button")
self._settings_stack = builder.get_object("settings_stack")
self._backup_dir_field = builder.get_object("backup_dir_field")
# Info bar
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
self._test_spinner = builder.get_object("test_spinner")
self._options = options
self._active_profile = options.get("profile")
# Settings type
self._enigma_radio_button = builder.get_object("enigma_radio_button")
self._neutrino_radio_button = builder.get_object("neutrino_radio_button")
self._support_ver5_switch = builder.get_object("support_ver5_switch")
# Program
self._before_save_switch = builder.get_object("before_save_switch")
self._before_downloading_switch = builder.get_object("before_downloading_switch")
self._program_frame = builder.get_object("program_frame")
self._extra_support_grid = builder.get_object("extra_support_grid")
self._colors_grid = builder.get_object("colors_grid")
self._set_color_switch = builder.get_object("set_color_switch")
self._new_color_button = builder.get_object("new_color_button")
self._extra_color_button = builder.get_object("extra_color_button")
self._load_on_startup_switch = builder.get_object("load_on_startup_switch")
# HTTP API
self._support_http_api_switch = builder.get_object("support_http_api_switch")
self._enable_y_dl_switch = builder.get_object("enable_y_dl_switch")
self._enable_send_to_switch = builder.get_object("enable_send_to_switch")
self._click_mode_disabled_button = builder.get_object("click_mode_disabled_button")
self._click_mode_stream_button = builder.get_object("click_mode_stream_button")
self._click_mode_play_button = builder.get_object("click_mode_play_button")
self._click_mode_zap_button = builder.get_object("click_mode_zap_button")
self._click_mode_zap_and_play_button = builder.get_object("click_mode_zap_and_play_button")
self._click_mode_zap_button.bind_property("sensitive", self._click_mode_play_button, "sensitive")
self._click_mode_zap_button.bind_property("sensitive", self._click_mode_zap_and_play_button, "sensitive")
self._click_mode_zap_button.bind_property("sensitive", self._enable_send_to_switch, "sensitive")
self._enable_send_to_switch.bind_property("sensitive", builder.get_object("enable_send_to_label"), "sensitive")
self._extra_support_grid.bind_property("sensitive", builder.get_object("v5_support_grid"), "sensitive")
# Profiles
self._profile_view = builder.get_object("profile_tree_view")
self._profile_add_button = builder.get_object("profile_add_button")
self._profile_remove_button = builder.get_object("profile_remove_button")
self._apply_profile_button = builder.get_object("apply_profile_button")
self._apply_profile_button.bind_property("visible", builder.get_object("header_separator"), "visible")
self._apply_profile_button.bind_property("visible", builder.get_object("reset_button"), "visible")
# Language
self._lang_combo_box = builder.get_object("lang_combo_box")
# Settings
self._ext_settings = settings
self._settings = Settings(settings.settings)
self._profiles = self._settings.profiles
self._s_type = self._settings.setting_type
self.set_settings()
profile = Profile(self._active_profile)
self._neutrino_radio_button.set_active(profile is Profile.NEUTRINO_MP)
self._support_ver5_check_button.set_sensitive(profile is not Profile.NEUTRINO_MP)
self._support_http_api_check_button.set_sensitive(profile is not Profile.NEUTRINO_MP)
self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(profile is not Profile.NEUTRINO_MP)
self.init_ui_elements(self._s_type)
self.init_profiles()
@run_idle
def init_ui_elements(self, s_type):
is_enigma_profile = s_type is SettingsType.ENIGMA_2
self._neutrino_radio_button.set_active(s_type is SettingsType.NEUTRINO_MP)
self.update_header_bar()
self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(is_enigma_profile)
self._program_frame.set_sensitive(is_enigma_profile)
self._extra_support_grid.set_sensitive(is_enigma_profile)
http_active = self._support_http_api_switch.get_active()
self._click_mode_zap_button.set_sensitive(is_enigma_profile and http_active)
self._lang_combo_box.set_active_id(self._settings.language)
self.on_info_bar_close() if is_enigma_profile else self.show_info_message(
"The Neutrino has only experimental support. Not all features are supported!", Gtk.MessageType.WARNING)
def init_profiles(self):
p_def = self._settings.default_profile
model = self._profile_view.get_model()
for ind, p in enumerate(self._profiles):
icon = DEFAULT_ICON if p == p_def else None
model.append((p, icon))
if icon:
scroll_to(ind, self._profile_view)
self._profile_remove_button.set_sensitive(len(self._profile_view.get_model()) > 1)
def update_header_bar(self):
label, sep, st = self._header_bar.get_subtitle().partition(":")
if self._s_type is SettingsType.ENIGMA_2:
self._header_bar.set_subtitle("{}: {}".format(label, self._enigma_radio_button.get_label()))
elif self._s_type is SettingsType.NEUTRINO_MP:
self._header_bar.set_subtitle("{}: {}".format(label, self._neutrino_radio_button.get_label()))
def show(self):
response = self._dialog.run()
if response == Gtk.ResponseType.OK:
self.apply_settings()
self._dialog.run()
def on_response(self, dialog, resp):
if resp == Gtk.ResponseType.OK and not self.apply_settings():
return
self._dialog.destroy()
return resp
return response
def on_field_icon_press(self, entry, icon, event_button):
update_entry_data(entry, self._dialog, self._settings)
def on_data_dir_field_icon_press(self, entry, icon, event_button):
update_entry_data(entry, self._dialog, self._options.get(self._options.get("profile")))
def on_settings_type_changed(self, item):
s_type = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active() else SettingsType.NEUTRINO_MP
if s_type is not self._s_type:
self._settings.setting_type = s_type
self._s_type = s_type
self.on_reset()
self.init_ui_elements(s_type)
def on_picons_dir_field_icon_press(self, entry, icon, event_button):
update_entry_data(entry, self._dialog, self._options.get(self._options.get("profile")))
def on_profile_changed(self, item):
profile = Profile.ENIGMA_2 if self._enigma_radio_button.get_active() else Profile.NEUTRINO_MP
self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(profile is not Profile.NEUTRINO_MP)
self.set_profile(profile)
self._support_ver5_check_button.set_sensitive(profile is Profile.ENIGMA_2)
self._support_http_api_check_button.set_sensitive(profile is Profile.ENIGMA_2)
def set_profile(self, profile):
self._active_profile = profile.value
self.set_settings()
def on_reset(self, item):
def_settings = get_default_settings()
for key in def_settings:
current = self._options.get(key)
if type(current) is str:
continue
default = def_settings.get(key)
for k in default:
current[k] = default.get(k)
def on_reset(self, item=None):
self._settings.reset()
self.set_settings()
def set_settings(self):
options = self._options.get(self._active_profile)
self._host_field.set_text(options.get("host", ""))
self._port_field.set_text(options.get("port", ""))
self._login_field.set_text(options.get("user", ""))
self._password_field.set_text(options.get("password", ""))
self._http_login_field.set_text(options.get("http_user", ""))
self._http_password_field.set_text(options.get("http_password", ""))
self._http_port_field.set_text(options.get("http_port", "80"))
self._telnet_login_field.set_text(options.get("telnet_user", ""))
self._telnet_password_field.set_text(options.get("telnet_password", ""))
self._telnet_port_field.set_text(options.get("telnet_port", ""))
self._telnet_timeout_spin_button.set_value(options.get("telnet_timeout", 5))
self._services_field.set_text(options.get("services_path", ""))
self._user_bouquet_field.set_text(options.get("user_bouquet_path", ""))
self._satellites_xml_field.set_text(options.get("satellites_xml_path", ""))
self._picons_field.set_text(options.get("picons_path", ""))
self._data_dir_field.set_text(options.get("data_dir_path", ""))
self._picons_dir_field.set_text(options.get("picons_dir_path", ""))
if Profile(self._active_profile) is Profile.ENIGMA_2:
self._support_ver5_check_button.set_active(options.get("v5_support", False))
self._support_http_api_check_button.set_active(options.get("http_api_support", False))
self._s_type = self._settings.setting_type
self._host_field.set_text(self._settings.host)
self._port_field.set_text(self._settings.port)
self._login_field.set_text(self._settings.user)
self._password_field.set_text(self._settings.password)
self._http_login_field.set_text(self._settings.http_user)
self._http_password_field.set_text(self._settings.http_password)
self._http_port_field.set_text(self._settings.http_port)
self._http_use_ssl_check_button.set_active(self._settings.http_use_ssl)
self._telnet_login_field.set_text(self._settings.telnet_user)
self._telnet_password_field.set_text(self._settings.telnet_password)
self._telnet_port_field.set_text(self._settings.telnet_port)
self._telnet_timeout_spin_button.set_value(self._settings.telnet_timeout)
self._services_field.set_text(self._settings.services_path)
self._user_bouquet_field.set_text(self._settings.user_bouquet_path)
self._satellites_xml_field.set_text(self._settings.satellites_xml_path)
self._picons_field.set_text(self._settings.picons_path)
self._data_dir_field.set_text(self._settings.data_local_path)
self._picons_dir_field.set_text(self._settings.picons_local_path)
self._backup_dir_field.set_text(self._settings.backup_local_path)
self._before_save_switch.set_active(self._settings.backup_before_save)
self._before_downloading_switch.set_active(self._settings.backup_before_downloading)
self.set_fav_click_mode(self._settings.fav_click_mode)
self._load_on_startup_switch.set_active(self._settings.load_last_config)
if self._s_type is SettingsType.ENIGMA_2:
self._support_ver5_switch.set_active(self._settings.v5_support)
self._support_http_api_switch.set_active(self._settings.http_api_support)
self._enable_y_dl_switch.set_active(self._settings.enable_yt_dl)
self._enable_send_to_switch.set_active(self._settings.enable_send_to)
self._set_color_switch.set_active(self._settings.use_colors)
new_rgb = Gdk.RGBA()
new_rgb.parse(self._settings.new_color)
extra_rgb = Gdk.RGBA()
extra_rgb.parse(self._settings.extra_color)
self._new_color_button.set_rgba(new_rgb)
self._extra_color_button.set_rgba(extra_rgb)
if self._s_type is SettingsType.ENIGMA_2:
self._enigma_radio_button.activate()
else:
self._neutrino_radio_button.activate()
def on_apply_profile_settings(self, item):
self._s_type = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active() else SettingsType.NEUTRINO_MP
self._settings.setting_type = self._s_type
self._settings.host = self._host_field.get_text()
self._settings.port = self._port_field.get_text()
self._settings.user = self._login_field.get_text()
self._settings.password = self._password_field.get_text()
self._settings.http_user = self._http_login_field.get_text()
self._settings.http_password = self._http_password_field.get_text()
self._settings.http_port = self._http_port_field.get_text()
self._settings.http_use_ssl = self._http_use_ssl_check_button.get_active()
self._settings.telnet_user = self._telnet_login_field.get_text()
self._settings.telnet_password = self._telnet_password_field.get_text()
self._settings.telnet_port = self._telnet_port_field.get_text()
self._settings.telnet_timeout = int(self._telnet_timeout_spin_button.get_value())
self._settings.services_path = self._services_field.get_text()
self._settings.user_bouquet_path = self._user_bouquet_field.get_text()
self._settings.satellites_xml_path = self._satellites_xml_field.get_text()
self._settings.picons_path = self._picons_field.get_text()
self._settings.data_local_path = self._data_dir_field.get_text()
self._settings.picons_local_path = self._picons_dir_field.get_text()
self._settings.backup_local_path = self._backup_dir_field.get_text()
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["http_user"] = self._http_login_field.get_text()
options["http_password"] = self._http_password_field.get_text()
options["http_port"] = self._http_port_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()
options["http_api_support"] = self._support_http_api_check_button.get_active()
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
write_config(self._options)
self._ext_settings.profiles = self._settings.profiles
self._ext_settings.backup_before_save = self._before_save_switch.get_active()
self._ext_settings.backup_before_downloading = self._before_downloading_switch.get_active()
self._ext_settings.fav_click_mode = self.get_fav_click_mode()
self._ext_settings.language = self._lang_combo_box.get_active_id()
self._ext_settings.load_last_config = self._load_on_startup_switch.get_active()
if self._s_type is SettingsType.ENIGMA_2:
self._ext_settings.use_colors = self._set_color_switch.get_active()
self._ext_settings.new_color = self._new_color_button.get_rgba().to_string()
self._ext_settings.extra_color = self._extra_color_button.get_rgba().to_string()
self._ext_settings.v5_support = self._support_ver5_switch.get_active()
self._ext_settings.http_api_support = self._support_http_api_switch.get_active()
self._ext_settings.enable_yt_dl = self._enable_y_dl_switch.get_active()
self._ext_settings.enable_send_to = self._enable_send_to_switch.get_active()
self._ext_settings.default_profile = list(filter(lambda r: r[1], self._profile_view.get_model()))[0][0]
self._ext_settings.save()
return True
@run_task
def on_connection_test(self, item):
@@ -172,10 +293,13 @@ class SettingsDialog:
def test_http(self):
user, password = self._http_login_field.get_text(), self._http_password_field.get_text()
host, port = self._host_field.get_text(), self._http_port_field.get_text()
use_ssl = self._http_use_ssl_check_button.get_active()
try:
self.show_info_message(test_http(host, port, user, password), Gtk.MessageType.INFO)
self.show_info_message(test_http(host, port, user, password, use_ssl=use_ssl), Gtk.MessageType.INFO)
except TestException as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
except HttpApiException as e:
self.show_info_message(str(e), Gtk.MessageType.WARNING)
finally:
self.show_spinner(False)
@@ -214,6 +338,165 @@ class SettingsDialog:
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
def on_set_color_switch_state(self, switch, state):
self._colors_grid.set_sensitive(state)
def on_http_mode_switch_state(self, switch, state):
self._click_mode_zap_button.set_sensitive(state)
if any((self._click_mode_play_button.get_active(),
self._click_mode_zap_button.get_active(),
self._click_mode_zap_and_play_button.get_active())):
self._click_mode_disabled_button.set_active(True)
def on_yt_dl_switch_state(self, switch, state):
self.show_info_message("Not implemented yet!", Gtk.MessageType.WARNING)
def on_send_to_switch_state(self, switch, state):
self.show_info_message("Not implemented yet!", Gtk.MessageType.WARNING)
def on_profile_add(self, item):
model = self._profile_view.get_model()
count = 0
name = "profile"
while name in self._profiles:
count += 1
name = "profile{}".format(count)
self._profiles[name] = self._s_type.get_default_settings()
model.append((name, None))
scroll_to(len(model) - 1, self._profile_view)
self.on_profile_selected(self._profile_view)
p = name + "/"
self._settings.data_local_path += p
self._settings.picons_local_path += p
self._settings.backup_local_path += p
self.on_reset()
def on_profile_edit(self, item=None):
model, paths = self._profile_view.get_selection().get_selected_rows()
self._profile_view.set_cursor(paths, self._profile_view.get_column(0), True)
def on_profile_remove(self, item):
model, paths = self._profile_view.get_selection().get_selected_rows()
if paths:
row = model[paths]
is_default = row[1]
self._profiles.pop(row[0], None)
del model[paths]
if is_default:
model.set_value(model.get_iter_first(), 1, DEFAULT_ICON)
def on_profile_deleted(self, model, paths):
self._profile_remove_button.set_sensitive(len(model) > 1)
def on_profile_edited(self, render, path, new_value):
row = self._profile_view.get_model()[path]
p_name = row[0]
if p_name == new_value:
return
if new_value in self._profiles:
show_dialog(DialogType.ERROR, self._dialog, "A profile with that name exists!")
return
p_name = self._profiles.pop(p_name, None)
if p_name:
row[0] = new_value
self._profiles[new_value] = p_name
self.update_local_paths(new_value)
self.on_profile_selected(self._profile_view)
def update_local_paths(self, p_name, force_rename=False):
data_path = self._settings.data_local_path
picons_path = self._settings.picons_local_path
backup_path = self._settings.backup_local_path
self._settings.data_local_path = "{}/{}/".format(Path(data_path).parent, p_name)
self._settings.picons_local_path = "{}/{}/".format(Path(picons_path).parent, p_name)
self._settings.backup_local_path = "{}/{}/".format(Path(backup_path).parent, p_name)
if force_rename:
try:
if os.path.isdir(picons_path):
os.rename(picons_path, self._settings.picons_local_path)
if os.path.isdir(data_path):
os.rename(data_path, self._settings.data_local_path)
if os.path.isdir(backup_path):
os.rename(backup_path, self._settings.backup_local_path)
except OSError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
def on_profile_selected(self, view):
model, paths = self._profile_view.get_selection().get_selected_rows()
if paths:
profile = model.get_value(model.get_iter(paths), 0)
self._settings.current_profile = profile
self.set_settings()
def on_profile_set_default(self, item):
model, paths = self._profile_view.get_selection().get_selected_rows()
if paths:
itr = model.get_iter(paths)
model.foreach(lambda m, p, i: model.set_value(i, 1, None))
model.set_value(itr, 1, DEFAULT_ICON)
self._settings.default_profile = model.get_value(itr, 0)
def on_profile_inserted(self, model, path, itr):
self._profile_remove_button.set_sensitive(len(model) > 1)
def on_lang_changed(self, box):
if box.get_active_id() != self._settings.language:
self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING)
def on_main_settings_visible(self, stack, param):
self._apply_profile_button.set_visible(stack.get_visible_child_name() == "profiles")
def on_network_settings_visible(self, stack, param):
self._http_use_ssl_check_button.set_visible(Property(stack.get_visible_child_name()) is Property.HTTP)
def on_http_use_ssl_toggled(self, button):
active = button.get_active()
self._settings.http_use_ssl = active
port = "443" if active else "80"
self._http_port_field.set_text(port)
self._settings.http_port = port
def on_click_mode_togged(self, button):
if self._main_stack.get_visible_child_name() != "extra":
return
mode = self.get_fav_click_mode()
if mode is FavClickMode.PLAY:
self.show_info_message("Operates in standby mode or current active transponder!", Gtk.MessageType.WARNING)
else:
self.on_info_bar_close()
@run_idle
def set_fav_click_mode(self, mode):
mode = FavClickMode(mode)
self._click_mode_disabled_button.set_active(mode is FavClickMode.DISABLED)
self._click_mode_stream_button.set_active(mode is FavClickMode.STREAM)
self._click_mode_play_button.set_active(mode is FavClickMode.PLAY)
self._click_mode_zap_button.set_active(mode is FavClickMode.ZAP)
self._click_mode_zap_and_play_button.set_active(mode is FavClickMode.ZAP_PLAY)
def get_fav_click_mode(self):
if self._click_mode_zap_button.get_active():
return FavClickMode.ZAP
if self._click_mode_play_button.get_active():
return FavClickMode.PLAY
if self._click_mode_zap_and_play_button.get_active():
return FavClickMode.ZAP_PLAY
if self._click_mode_stream_button.get_active():
return FavClickMode.STREAM
return FavClickMode.DISABLED
def on_view_popup_menu(self, menu, event):
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_SECONDARY:
menu.popup(None, None, None, None, event.button, event.time)
if __name__ == "__main__":
pass

View File

@@ -1,3 +1,8 @@
#digit-entry {
border-color: Red;
}
}
#status-bar-button {
padding: 1px;
margin: 1px;
}

233
app/ui/transmitter.glade Normal file
View File

@@ -0,0 +1,233 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1
The MIT License (MIT)
Copyright (c) 2018-2020 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.16"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkWindow" id="main_window">
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="window_position">mouse</property>
<property name="destroy_with_parent">True</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="decorated">False</property>
<property name="gravity">center</property>
<property name="has_resize_grip">True</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox" id="tool_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">1</property>
<child>
<object class="GtkButton" id="previous_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Previous stream in the list</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_left">1</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<signal name="clicked" handler="on_previous" swapped="no"/>
<child>
<object class="GtkImage" id="previous_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-media-previous</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="next_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Next stream in the list</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<signal name="clicked" handler="on_next" swapped="no"/>
<child>
<object class="GtkImage" id="next_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-media-next</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="url_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Drag or paste the link here</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<property name="primary_icon_stock">gtk-paste</property>
<signal name="activate" handler="on_url_activate" swapped="no"/>
<signal name="changed" handler="on_url_changed" swapped="no"/>
<signal name="drag-data-received" handler="on_drag_data_received" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="play_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Play</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<signal name="clicked" handler="on_play" swapped="no"/>
<child>
<object class="GtkImage" id="play_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-media-play</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="stop_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Stop playback</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<signal name="clicked" handler="on_stop" swapped="no"/>
<child>
<object class="GtkImage" id="stop_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-media-stop</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkButton" id="clear_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Remove added links in the playlist</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_right">1</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<signal name="clicked" handler="on_clear" swapped="no"/>
<child>
<object class="GtkImage" id="clear_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-clear</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
<style>
<class name="primary-toolbar"/>
</style>
</object>
</child>
</object>
<object class="GtkImage" id="show_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">view-restore</property>
</object>
<object class="GtkMenu" id="staus_popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="show_menu_item">
<property name="label" translatable="yes">Show/Hide</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">show_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_status_icon_activate" object="main_window" swapped="no"/>
</object>
</child>
</object>
<object class="GtkStatusIcon" id="status_icon">
<property name="icon_name">demon-editor</property>
<property name="has_tooltip">True</property>
<signal name="activate" handler="on_status_icon_activate" object="main_window" swapped="no"/>
<signal name="popup-menu" handler="on_popup_menu" object="staus_popup_menu" swapped="no"/>
</object>
</interface>

174
app/ui/transmitter.py Normal file
View File

@@ -0,0 +1,174 @@
from pathlib import Path
from urllib.parse import urlparse
import gi
from gi.repository import GLib
from app.commons import log
from app.connections import HttpRequestType
from app.tools.yt import YouTube
from app.ui.iptv import get_yt_icon
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH
class LinksTransmitter:
""" The main class for the "send to" function.
It used for direct playback of media links by the enigma2 media player.
"""
__STREAM_PREFIX = "4097:0:1:0:0:0:0:0:0:0:"
def __init__(self, http_api, app_window):
handlers = {"on_popup_menu": self.on_popup_menu,
"on_status_icon_activate": self.on_status_icon_activate,
"on_url_changed": self.on_url_changed,
"on_url_activate": self.on_url_activate,
"on_drag_data_received": self.on_drag_data_received,
"on_previous": self.on_previous,
"on_next": self.on_next,
"on_stop": self.on_stop,
"on_clear": self.on_clear,
"on_play": self.on_play}
self._http_api = http_api
self._app_window = app_window
self._is_status_icon = True
builder = Gtk.Builder()
builder.add_from_file(UI_RESOURCES_PATH + "transmitter.glade")
builder.connect_signals(handlers)
self._main_window = builder.get_object("main_window")
self._url_entry = builder.get_object("url_entry")
self._tool_bar = builder.get_object("tool_bar")
self._popup_menu = builder.get_object("staus_popup_menu")
self._restore_menu_item = builder.get_object("restore_menu_item")
self._status_active = None
self._status_passive = None
try:
gi.require_version("AppIndicator3", "0.1")
from gi.repository import AppIndicator3
except (ImportError, ValueError) as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
self._tray = builder.get_object("status_icon")
else:
self._is_status_icon = False
self._status_active = AppIndicator3.IndicatorStatus.ACTIVE
self._status_passive = AppIndicator3.IndicatorStatus.PASSIVE
category = AppIndicator3.IndicatorCategory.APPLICATION_STATUS
path = Path(UI_RESOURCES_PATH + "/icons/hicolor/scalable/apps/demon-editor.svg").resolve()
path = str(path) if path.is_file() else "demon-editor"
self._tray = AppIndicator3.Indicator.new("DemonEditor", path, category)
self._tray.set_status(self._status_active)
self._tray.set_secondary_activate_target(builder.get_object("show_menu_item"))
self._tray.set_menu(self._popup_menu)
style_provider = Gtk.CssProvider()
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
def show(self, show):
if self._is_status_icon:
self._tray.set_visible(show)
elif self._status_active:
self._tray.set_status(self._status_active if show else self._status_passive)
if not show:
self.hide()
def hide(self):
self._main_window.hide()
def on_popup_menu(self, menu, button, time):
menu.popup(None, None, None, None, button, time)
def on_status_icon_activate(self, window):
visible = window.get_visible()
window.hide() if visible else window.show()
self._app_window.present() if visible else self._app_window.iconify()
def on_url_changed(self, entry):
entry.set_name("GtkEntry" if self.is_url(entry.get_text()) else "digit-entry")
def on_url_activate(self, entry):
gen = self.activate_url(entry.get_text())
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def on_drag_data_received(self, entry, drag_context, x, y, data, info, time):
url = data.get_text()
GLib.idle_add(entry.set_text, url)
gen = self.activate_url(url)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def activate_url(self, url):
self._url_entry.set_name("GtkEntry")
self._url_entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
if self.is_url(url):
self._tool_bar.set_sensitive(False)
yt_id = YouTube.get_yt_id(url)
yield True
if yt_id:
self._url_entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
links, title = YouTube.get_yt_link(yt_id)
yield True
if links:
url = links[sorted(links, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
else:
self.on_done(links)
return
else:
self._url_entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
self._http_api.send(HttpRequestType.PLAY, url, self.on_done, self.__STREAM_PREFIX)
yield True
def on_done(self, res):
""" Play callback """
res = res.get("e2state", None) if res else res
self._url_entry.set_name("GtkEntry" if res else "digit-entry")
GLib.idle_add(self._tool_bar.set_sensitive, True)
def on_previous(self, item):
self._http_api.send(HttpRequestType.PLAYER_PREV, None, self.on_done)
def on_next(self, item):
self._http_api.send(HttpRequestType.PLAYER_NEXT, None, self.on_done)
def on_play(self, item):
self._http_api.send(HttpRequestType.PLAYER_PLAY, None, self.on_done)
def on_stop(self, item):
self._http_api.send(HttpRequestType.PLAYER_STOP, None, self.on_done)
def on_clear(self, item):
""" Remove added links in the playlist. """
GLib.idle_add(self._tool_bar.set_sensitive, False)
self._http_api.send(HttpRequestType.PLAYER_LIST, None, self.clear_playlist)
def clear_playlist(self, res):
GLib.idle_add(self._tool_bar.set_sensitive, not res)
if "error_code" in res:
log("Error clearing playlist. There may be no http connection.")
self.on_done(res)
return
for ref in res:
GLib.idle_add(self._tool_bar.set_sensitive, False)
self._http_api.send(HttpRequestType.PLAYER_REMOVE,
ref.get("e2servicereference", ""),
self.on_done,
self.__STREAM_PREFIX)
@staticmethod
def is_url(text):
""" Simple url checking. """
result = urlparse(text)
return result.scheme and result.netloc
if __name__ == "__main__":
pass

View File

@@ -1,22 +1,34 @@
import locale
import os
from enum import Enum, IntEnum
from functools import lru_cache
import gi
from enum import Enum
gi.require_version('Gtk', '3.0')
from app.settings import Settings, SettingsException
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "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/"
IS_GNOME_SESSION = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
# 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")
try:
settings = Settings.get_instance()
except SettingsException:
pass
else:
os.environ["LANGUAGE"] = settings.language
if UI_RESOURCES_PATH == "app/ui/":
locale.bindtextdomain(TEXT_DOMAIN, UI_RESOURCES_PATH + "lang")
theme = Gtk.IconTheme.get_default()
theme.append_search_path(UI_RESOURCES_PATH + "icons")
_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
@@ -24,18 +36,37 @@ LOCKED_ICON = theme.load_icon("changes-prevent-symbolic", 16, 0) if theme.lookup
"system-lock-screen", 16, 0) else _IMAGE_MISSING
HIDE_ICON = theme.load_icon("go-jump", 16, 0) if theme.lookup_icon("go-jump", 16, 0) else _IMAGE_MISSING
TV_ICON = theme.load_icon("tv-symbolic", 16, 0) if theme.lookup_icon("tv-symbolic", 16, 0) else _IMAGE_MISSING
IPTV_ICON = theme.load_icon("emblem-shared", 16, 0) if theme.load_icon("emblem-shared", 16, 0) else None
IPTV_ICON = theme.load_icon("emblem-shared", 16, 0) if theme.lookup_icon("emblem-shared", 16, 0) else None
EPG_ICON = theme.load_icon("gtk-index", 16, 0) if theme.lookup_icon("gtk-index", 16, 0) else None
DEFAULT_ICON = theme.load_icon("emblem-default", 16, 0) if theme.lookup_icon("emblem-default", 16, 0) else None
@lru_cache(maxsize=1)
def get_yt_icon(icon_name, size=24):
""" Getting YouTube icon. If the icon is not found in the icon themes, the "Info" icon is returned by default! """
default_theme = Gtk.IconTheme.get_default()
if default_theme.has_icon(icon_name):
return default_theme.load_icon(icon_name, size, 0)
n_theme = Gtk.IconTheme.new()
import glob
for theme_name in map(os.path.basename, filter(os.path.isdir, glob.glob("/usr/share/icons/*"))):
n_theme.set_custom_theme(theme_name)
if n_theme.has_icon(icon_name):
return n_theme.load_icon(icon_name, size, 0)
return default_theme.load_icon("info", size, 0)
class KeyboardKey(Enum):
""" The raw(hardware) codes of the keyboard keys """
""" The raw(hardware) codes of the keyboard keys. """
E = 26
R = 27
T = 28
P = 33
S = 39
H = 43
L = 46
F = 41
X = 53
C = 54
V = 55
@@ -51,6 +82,7 @@ class KeyboardKey(Enum):
LEFT = 113
RIGHT = 114
F2 = 68
SPACE = 65
DELETE = 119
BACK_SPACE = 22
CTRL_L = 37
@@ -71,15 +103,24 @@ MOVE_KEYS = (KeyboardKey.UP, KeyboardKey.PAGE_UP, KeyboardKey.DOWN, KeyboardKey.
KeyboardKey.END, KeyboardKey.HOME_KP, KeyboardKey.END_KP, KeyboardKey.PAGE_UP_KP, KeyboardKey.PAGE_DOWN_KP)
class FavClickMode(IntEnum):
""" Double click mode on the service in the bouquet(FAV) list. """
DISABLED = 0
STREAM = 1
PLAY = 2
ZAP = 3
ZAP_PLAY = 4
class ViewTarget(Enum):
""" Used for set target view """
""" Used for set target view. """
BOUQUET = 0
FAV = 1
SERVICES = 2
class BqGenType(Enum):
""" Bouquet generation type """
""" Bouquet generation type. """
SAT = 0
EACH_SAT = 1
PACKAGE = 2
@@ -88,5 +129,53 @@ class BqGenType(Enum):
EACH_TYPE = 5
class Column(IntEnum):
""" Column nums in the views """
# main view
SRV_CAS_FLAGS = 0
SRV_STANDARD = 1
SRV_CODED = 2
SRV_SERVICE = 3
SRV_LOCKED = 4
SRV_HIDE = 5
SRV_PACKAGE = 6
SRV_TYPE = 7
SRV_PICON = 8
SRV_PICON_ID = 9
SRV_SSID = 10
SRV_FREQ = 11
SRV_RATE = 12
SRV_POL = 13
SRV_FEC = 14
SRV_SYSTEM = 15
SRV_POS = 16
SRV_DATA_ID = 17
SRV_FAV_ID = 18
SRV_TRANSPONDER = 19
SRV_TOOLTIP = 20
SRV_BACKGROUND = 21
# fav view
FAV_NUM = 0
FAV_CODED = 1
FAV_SERVICE = 2
FAV_LOCKED = 3
FAV_HIDE = 4
FAV_TYPE = 5
FAV_POS = 6
FAV_ID = 7
FAV_PICON = 8
FAV_TOOLTIP = 9
FAV_BACKGROUND = 10
# bouquets view
BQ_NAME = 0
BQ_LOCKED = 1
BQ_HIDDEN = 2
BQ_TYPE = 3
def __index__(self):
""" Overridden to get the index in slices directly """
return self.value
if __name__ == "__main__":
pass

View File

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

View File

@@ -1,15 +1,13 @@
demon-editor for Debian
----------------------
DemonEditor
Enigma2 channel and satellites list editor for GNU/Linux.
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
Keyboard shortcuts:
Ctrl + Insert - copies the selected channels from the main list to the the bouquet beginning
or inserts (creates) a new bouquet.
Keyboard shortcuts:
Ctrl + Insert - copies the selected channels from the main list to the the bouquet beginning or inserts (creates) a new bouquet.
Ctrl + BackSpace - copies the selected channels from the main list to the bouquet end.
Ctrl + X - only in bouquet list. Ctrl + C - only in services list.
Clipboard is "rubber". There is an accumulation before the insertion!
@@ -19,28 +17,32 @@ Keyboard shortcuts:
Ctrl + L - parental lock.
Ctrl + H - hide/skip.
Ctrl + P - start play IPTV or other stream in the bouquet list.
Ctrl + Z - switch(zap) the channel(works when the HTTP API is enabled, Enigma2 only).
Ctrl + Z - switch (zap) the channel (works when the HTTP API is enabled, Enigma2 only).
Ctrl + W - switch to the channel and watch in the program.
Space - select/deselect.
Left/Right - remove selection.
Ctrl + Up, Down, PageUp, PageDown, Home, End - move selected items in the list.
Ctrl + O - (re)load user data from current dir.
Ctrl + D - load data from receiver.
Ctrl + U/B upload data/bouquets to receiver.
Extra:
Import feature.
Multiple selections in lists only with Space key (as in file managers).
Ability to import IPTV into bouquet (Neutrino WEBTV) from m3u files.
Ability to download picons and update satellites (transponders) from web.
Preview (playing) IPTV or other streams directly from the bouquet list(should be installed VLC).
Ability to import into bouquet (Neutrino WEB TV) from m3u.
Ability to export bouquets with IPTV services to m3u.
Assignment EPG from DVB or XML for IPTV services(Enigma2 only).
Preview (playing) IPTV or other streams directly from the bouquet list (should be installed VLC).
Minimum requirements:
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings.
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings.
Launching
Note.
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2!
Terrestrial(DVB-T/T2) and cable channels are supported(Enigma2 only) with limitation!
Important:
Main supported lamedb format is version 4. Versions 3 and 5 has only experimental support!
For version **3** is only read mode available. When saving, version **4** format is used instead!
Main supported lamedb format is version 4. Versions 3 and 5 has only experimental support!
For version 3 is only read mode available. When saving, version 4 format is used instead!
-- Dmitriy Yefremov <dmitry.v.yefremov@gmail.com> Thu, 06 Dec 2018 11:27:21 +0300

View File

@@ -1,5 +1,5 @@
Package: DemonEditor
Version: 0.4.2-Pre-alpha
Package: demon-editor
Version: 0.4.7-Pre-alpha
Section: utils
Priority: optional
Architecture: all

View File

@@ -5,7 +5,7 @@ Source: https://github.com/DYefremov/DemonEditor
Files: *
MIT License
Copyright (c) 2018 Dmitriy Yefremov
Copyright (c) 2018-2020 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

2
deb/usr/bin/demon-editor Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
python3 /usr/share/demoneditor/start.py $1

View File

@@ -1,2 +0,0 @@
#!/bin/bash
python3 /usr/share/demoneditor/start.py

View File

@@ -3,8 +3,8 @@ Version=1.0
Name=DemonEditor
Comment=Channels and satellites list editor for Enigma2
Comment[ru]=Редактор списка каналов и спутников для Enigma2
Icon=accessories-text-editor
Exec=/usr/bin/demoneditor.sh
Icon=demon-editor
Exec=/usr/bin/demon-editor
Terminal=false
Type=Application
Categories=Utility;Application;

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env python3
from app.ui.main_app_window import start_app
start_app()

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,634 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg1541"
version="1.1"
viewBox="0 0 16.743339 16.72816"
height="63.224556"
width="63.281971"
sodipodi:docname="demon-editor.svg"
inkscape:version="0.92.4 (unknown)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="716"
id="namedview127"
showgrid="true"
fit-margin-left="0"
inkscape:zoom="6.1714295"
inkscape:cx="40.088627"
inkscape:cy="31.742631"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1541"
fit-margin-top="0"
fit-margin-right="0"
fit-margin-bottom="0">
<inkscape:grid
type="xygrid"
id="grid128"
originx="-10.604603"
originy="-1.1727724" />
</sodipodi:namedview>
<title
id="title1088">DeamonEditor Icons</title>
<defs
id="defs1535">
<linearGradient
id="linearGradient2198">
<stop
id="stop2194"
style="stop-color:#ffb320;stop-opacity:1"
offset="0" />
<stop
id="stop2196"
style="stop-color:#e7ff00;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient2192">
<stop
offset="0"
style="stop-color:#ffb320;stop-opacity:1"
id="stop2188" />
<stop
offset="1"
style="stop-color:#b3c54c;stop-opacity:1"
id="stop2190" />
</linearGradient>
<linearGradient
id="linearGradient3700-8">
<stop
offset="0"
style="stop-color:#2e4f84;stop-opacity:1"
id="stop2183" />
<stop
offset="1"
style="stop-color:#4c77c5;stop-opacity:1"
id="stop2185" />
</linearGradient>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
spreadMethod="pad"
id="linearGradient1844">
<stop
style="stop-opacity:1;stop-color:#29282b"
offset="0"
id="stop1826" />
<stop
id="stop1828"
offset="0.13293758"
style="stop-color:#b5bdcf;stop-opacity:1" />
<stop
style="stop-opacity:1;stop-color:#92979f"
offset="0.21261224"
id="stop1832" />
<stop
style="stop-opacity:1;stop-color:#737881"
offset="0.29780689"
id="stop1834" />
<stop
style="stop-opacity:1;stop-color:#70757e"
offset="0.29780689"
id="stop1836" />
<stop
style="stop-opacity:1;stop-color:#e4e6e8"
offset="0.45395693"
id="stop1838" />
<stop
style="stop-opacity:1;stop-color:#696c76"
offset="0.71871042"
id="stop1840" />
<stop
style="stop-opacity:1;stop-color:#29282b"
offset="1"
id="stop1842" />
</linearGradient>
<linearGradient
id="linearGradient1754"
spreadMethod="pad"
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
gradientUnits="userSpaceOnUse"
y2="0"
x2="1"
y1="0"
x1="0">
<stop
id="stop1736"
offset="0"
style="stop-opacity:1;stop-color:#29282b" />
<stop
style="stop-color:#b5bdcf;stop-opacity:1"
offset="0.02582242"
id="stop1738" />
<stop
id="stop1740"
offset="0.14669065"
style="stop-opacity:1;stop-color:#868c95" />
<stop
id="stop1742"
offset="0.21261224"
style="stop-opacity:1;stop-color:#92979f" />
<stop
id="stop1744"
offset="0.29780689"
style="stop-opacity:1;stop-color:#737881" />
<stop
id="stop1746"
offset="0.29780689"
style="stop-opacity:1;stop-color:#70757e" />
<stop
id="stop1748"
offset="0.45395693"
style="stop-opacity:1;stop-color:#e4e6e8" />
<stop
id="stop1750"
offset="0.71871042"
style="stop-opacity:1;stop-color:#ffffff" />
<stop
id="stop1752"
offset="1"
style="stop-opacity:1;stop-color:#1d191a" />
</linearGradient>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
spreadMethod="pad"
id="linearGradient1606">
<stop
style="stop-opacity:1;stop-color:#29282b"
offset="0"
id="stop1590" />
<stop
id="stop1608"
offset="0.03065561"
style="stop-color:#b5bdcf;stop-opacity:1" />
<stop
style="stop-opacity:1;stop-color:#868c95"
offset="0.1125849"
id="stop1592" />
<stop
style="stop-opacity:1;stop-color:#92979f"
offset="0.13955873"
id="stop1594" />
<stop
style="stop-opacity:1;stop-color:#737881"
offset="0.1915853"
id="stop1596" />
<stop
style="stop-opacity:1;stop-color:#70757e"
offset="0.25400829"
id="stop1598" />
<stop
style="stop-opacity:1;stop-color:#e4e6e8"
offset="0.45395693"
id="stop1600" />
<stop
style="stop-opacity:1;stop-color:#ffffff"
offset="0.71871042"
id="stop1602" />
<stop
style="stop-opacity:1;stop-color:#1d191a"
offset="1"
id="stop1604" />
</linearGradient>
<linearGradient
id="linearGradient886-0">
<stop
style="stop-color:#535353;stop-opacity:1"
offset="0"
id="stop888" />
<stop
style="stop-color:#f0f0f0;stop-opacity:1"
offset="1"
id="stop890" />
</linearGradient>
<linearGradient
id="linearGradient1473">
<stop
id="stop1469"
offset="0"
style="stop-color:#ffffff;stop-opacity:0" />
<stop
id="stop1471"
offset="1"
style="stop-color:#ffffff;stop-opacity:0.96088022" />
</linearGradient>
<linearGradient
id="linearGradient2447-35">
<stop
id="stop2449"
offset="0"
style="stop-color:#00c62e;stop-opacity:1" />
<stop
id="stop2451"
offset="1"
style="stop-color:#136100;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient3795-1">
<stop
id="stop3797-1"
offset="0"
style="stop-color:#803400;stop-opacity:1" />
<stop
id="stop3799-3"
offset="1"
style="stop-color:#c87137;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient2447-3">
<stop
style="stop-color:#c60300;stop-opacity:1"
offset="0"
id="stop2443" />
<stop
style="stop-color:#c40e00;stop-opacity:1"
offset="1"
id="stop2445" />
</linearGradient>
<linearGradient
id="linearGradient2458">
<stop
id="stop2454"
offset="0"
style="stop-color:#c60300;stop-opacity:1" />
<stop
id="stop2456"
offset="1"
style="stop-color:#ee6000;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient3519">
<stop
id="stop3521"
offset="0"
style="stop-color:#1d2120;stop-opacity:1" />
<stop
id="stop3523"
offset="1"
style="stop-color:#545d5d;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient1446">
<stop
id="stop1442"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop1444"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient1547"
spreadMethod="pad"
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
gradientUnits="userSpaceOnUse"
y2="0"
x2="1"
y1="0"
x1="0">
<stop
id="stop1529"
offset="0"
style="stop-opacity:1;stop-color:#ffffff" />
<stop
id="stop1531"
offset="0.0263736"
style="stop-opacity:1;stop-color:#29282b" />
<stop
id="stop1533"
offset="0.263736"
style="stop-opacity:1;stop-color:#868c95" />
<stop
id="stop1535"
offset="0.395604"
style="stop-opacity:1;stop-color:#92979f" />
<stop
id="stop1537"
offset="0.39560401"
style="stop-opacity:1;stop-color:#737881" />
<stop
id="stop1539"
offset="0.42333773"
style="stop-opacity:1;stop-color:#70757e" />
<stop
id="stop1541"
offset="0.56268591"
style="stop-opacity:1;stop-color:#e4e6e8" />
<stop
id="stop1543"
offset="0.62400264"
style="stop-opacity:1;stop-color:#ffffff" />
<stop
id="stop1545"
offset="1"
style="stop-opacity:1;stop-color:#1d191a" />
</linearGradient>
<linearGradient
id="linearGradient1527"
spreadMethod="pad"
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
gradientUnits="userSpaceOnUse"
y2="0"
x2="1"
y1="0"
x1="0">
<stop
id="stop1509"
offset="0"
style="stop-opacity:1;stop-color:#ffffff" />
<stop
id="stop1511"
offset="0.0527472"
style="stop-opacity:1;stop-color:#29282b" />
<stop
id="stop1513"
offset="0.142147"
style="stop-opacity:1;stop-color:#868c95" />
<stop
id="stop1515"
offset="0.19700864"
style="stop-opacity:1;stop-color:#92979f" />
<stop
id="stop1517"
offset="0.25031137"
style="stop-opacity:1;stop-color:#737881" />
<stop
id="stop1519"
offset="0.3710371"
style="stop-opacity:1;stop-color:#70757e" />
<stop
id="stop1521"
offset="0.53961843"
style="stop-opacity:1;stop-color:#e4e6e8" />
<stop
id="stop1523"
offset="0.76283824"
style="stop-opacity:1;stop-color:#ffffff" />
<stop
id="stop1525"
offset="1"
style="stop-opacity:1;stop-color:#1d191a" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.26458333,0,0,0.26458333,-5.8254634,-78.732754)"
y2="1179.7145"
x2="66.791626"
y1="1188.7661"
x1="99.044022"
gradientUnits="userSpaceOnUse"
id="linearGradient1485"
xlink:href="#linearGradient1473" />
<linearGradient
gradientTransform="matrix(0.26458333,0,0,0.26458333,-2.2733744,-70.526334)"
gradientUnits="userSpaceOnUse"
y2="1155.8046"
x2="67.311417"
y1="1161.6112"
x1="77.19442"
id="linearGradient1448"
xlink:href="#linearGradient1446" />
<linearGradient
gradientTransform="matrix(0.26458333,0,0,0.26458333,-18.198747,-79.859424)"
gradientUnits="userSpaceOnUse"
y2="1186.8096"
x2="146.16808"
y1="1186.8096"
x1="131.86871"
id="linearGradient2180"
xlink:href="#linearGradient886-0" />
<linearGradient
gradientTransform="matrix(0.20863982,0,0,0.20863982,-0.63935937,-13.031239)"
gradientUnits="userSpaceOnUse"
y2="1184.73"
x2="101.19952"
y1="1184.73"
x1="83.066002"
id="linearGradient2160"
xlink:href="#linearGradient886-0" />
<linearGradient
gradientTransform="matrix(0.26458333,0,0,0.26458333,-6.2370714,-102.12414)"
gradientUnits="userSpaceOnUse"
y2="1267.1335"
x2="117.99127"
y1="1267.1335"
x1="75.853806"
id="linearGradient2131"
xlink:href="#linearGradient886-0" />
<linearGradient
id="linearGradient3700-8-3">
<stop
id="stop3702-1"
style="stop-color:#2e4f84;stop-opacity:1"
offset="0" />
<stop
id="stop3704-8"
style="stop-color:#4c77c5;stop-opacity:1"
offset="1" />
</linearGradient>
<defs
id="defs4922">
<filter
style="color-interpolation-filters:sRGB"
id="Adobe_OpacityMaskFilter"
filterUnits="userSpaceOnUse"
x="3.7850001"
y="4.6750002"
width="5.8829999"
height="73.013">
<feColorMatrix
type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
id="feColorMatrix4925" />
</filter>
</defs>
<mask
maskUnits="userSpaceOnUse"
x="3.785"
y="4.675"
width="5.883"
height="73.013"
id="SVGID_2_">
<g
style="filter:url(#Adobe_OpacityMaskFilter)"
id="g4928">
<linearGradient
id="SVGID_3_"
gradientUnits="userSpaceOnUse"
x1="3.7852001"
y1="41.181198"
x2="9.6680002"
y2="41.181198">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop4931" />
<stop
offset="0.0029"
style="stop-color:#FAFBFB"
id="stop4933" />
<stop
offset="0.0756"
style="stop-color:#BBBDBF"
id="stop4935" />
<stop
offset="0.1438"
style="stop-color:#898B8E"
id="stop4937" />
<stop
offset="0.2053"
style="stop-color:#646567"
id="stop4939" />
<stop
offset="0.259"
style="stop-color:#444446"
id="stop4941" />
<stop
offset="0.3028"
style="stop-color:#1D1C1D"
id="stop4943" />
<stop
offset="0.3313"
style="stop-color:#000000"
id="stop4945" />
</linearGradient>
<rect
style="fill:url(#SVGID_3_)"
x="3.7850001"
y="4.6750002"
width="5.8829999"
height="73.013"
id="rect4947" />
</g>
</mask>
<filter
style="color-interpolation-filters:sRGB"
id="filter1268"
filterUnits="userSpaceOnUse"
x="3.7850001"
y="4.6750002"
width="5.8829999"
height="73.013">
<feColorMatrix
type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
id="feColorMatrix1266" />
</filter>
<linearGradient
gradientUnits="userSpaceOnUse"
y2="203.22046"
x2="23.551136"
y1="203.22046"
x1="13.487289"
id="linearGradient1160"
xlink:href="#linearGradient3519" />
<clipPath
id="clipPath1890"
clipPathUnits="userSpaceOnUse">
<circle
style="opacity:1;fill:#2d2d2d;fill-opacity:1;stroke:#434242;stroke-width:0.0575568;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle1892"
cx="78.548424"
cy="-31.019459"
r="6.4721422"
transform="scale(1,-1)" />
</clipPath>
<linearGradient
gradientTransform="translate(-0.24389927,14.877856)"
y2="27.314217"
x2="84.864914"
y1="27.314217"
x1="72.164909"
gradientUnits="userSpaceOnUse"
id="linearGradient2134"
xlink:href="#linearGradient3700-8-3" />
</defs>
<metadata
id="metadata1538">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>DeamonEditor Icons</dc:title>
<dc:publisher>
<cc:Agent>
<dc:title>mfgeg</dc:title>
</cc:Agent>
</dc:publisher>
<dc:date>7.1.2020</dc:date>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="matrix(1.1690805,0,0,1.1690805,-14.929261,-167.45253)"
id="layer1">
<g
transform="translate(0.86526724,-82.691658)"
id="g1351">
<path
d="m 13.715358,227.65981 h 2.97616 v 12.57332 h -2.97616 z m 5.79826,-1.73376 -0.327076,0.42227 c -0.179879,0.23226 -0.586303,0.67932 -0.903223,0.99412 -0.480677,0.47726 -0.640897,0.57235 -0.967777,0.57235 -0.223557,0 -0.380895,-0.0584 -0.624024,-0.25067 v 3.18307 c 0.172884,-0.0214 0.359647,-0.0333 0.575071,-0.0352 0.68403,-0.006 0.91815,0.0416 1.436333,0.29581 1.14586,0.56274 1.762492,1.5589 1.768251,2.8566 0.0094,2.10836 -1.870896,3.55161 -3.779655,3.16529 v 3.10345 c 4.345352,0.0758 4.093104,-2.37537 6.573781,-2.25837 1.486139,0.0748 1.333421,0.16555 1.796225,-1.064 l 0.259834,-0.6913 -0.803704,-0.66493 c -0.960855,-0.79478 -1.303983,-1.29079 -1.205013,-1.74132 0.06631,-0.30189 1.20818,-1.50093 1.779011,-1.86778 0.240181,-0.1543 0.244255,-0.17878 0.09575,-0.59661 -0.08522,-0.23979 -0.259522,-0.65746 -0.386789,-0.92744 l -0.23132,-0.49115 h -1.412666 c -1.868582,0 -1.802386,0.068 -1.805368,-1.82472 l -0.0021,-1.43582 -0.917745,-0.37171 z"
style="opacity:1;fill:#000000;fill-opacity:0.5372549;stroke:none;stroke-width:0.18460207;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
id="path2133"
inkscape:connector-curvature="0" />
<path
id="path2106"
style="opacity:1;fill:url(#linearGradient2131);fill-opacity:1;stroke:none;stroke-width:0.17733108;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
d="m 13.832581,227.93117 h 2.858936 v 12.07807 h -2.858936 z m 5.569881,-1.6655 -0.314194,0.40566 c -0.172793,0.22309 -0.563209,0.65257 -0.867647,0.95496 -0.46174,0.45847 -0.615654,0.5498 -0.929658,0.5498 -0.214752,0 -0.365893,-0.056 -0.599446,-0.24079 v 3.05768 c 0.166077,-0.0206 0.345483,-0.0319 0.55242,-0.0338 0.657089,-0.006 0.881987,0.0399 1.379764,0.28416 1.100724,0.54057 1.693073,1.49747 1.698606,2.74408 0.009,2.02535 -1.797211,3.41174 -3.63079,3.04061 v 2.98122 c 4.174205,0.0728 3.931888,-2.28179 6.314859,-2.1694 1.427604,0.0718 1.2809,0.15902 1.725477,-1.02213 l 0.249597,-0.66408 -0.772046,-0.63871 c -0.923009,-0.76345 -1.252622,-1.23996 -1.157552,-1.67277 0.0637,-0.29001 1.160592,-1.44177 1.708939,-1.79419 0.230721,-0.14825 0.234635,-0.17174 0.09198,-0.57312 -0.08187,-0.23032 -0.249296,-0.63156 -0.371555,-0.89088 l -0.222207,-0.4718 h -1.357022 c -1.794983,0 -1.731396,0.0653 -1.734262,-1.75284 l -0.0021,-1.3793 -0.881603,-0.35705 z"
inkscape:connector-curvature="0" />
<path
id="path2151"
d="m 17.266983,230.95755 c -0.215637,0.003 -0.402408,0.014 -0.575465,0.0354 v 6.28854 c 1.910653,0.38671 3.792716,-1.05815 3.783336,-3.16865 -0.0058,-1.29897 -0.623064,-2.29597 -1.770059,-2.85927 -0.518699,-0.25451 -0.753103,-0.30234 -1.437812,-0.29607 z m 0.482341,1.17002 c 0.433747,-0.004 0.582202,0.0265 0.910784,0.18761 0.726595,0.35682 1.117604,0.98848 1.121256,1.81134 0.0059,1.33694 -1.186343,2.25211 -2.396695,2.00715 v -3.98359 c 0.109628,-0.0135 0.228054,-0.0214 0.364655,-0.0225 z"
style="opacity:1;fill:url(#linearGradient2160);fill-opacity:1;stroke:none;stroke-width:0.18478528;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
inkscape:connector-curvature="0" />
<path
style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient2180);stroke-width:0.18478528;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
d="m 17.266981,230.95753 c -0.215636,0.003 -0.402408,0.014 -0.575464,0.0354 v 6.28853 c 1.910652,0.38672 3.792715,-1.05815 3.783336,-3.16865 -0.0057,-1.29897 -0.623065,-2.29597 -1.77006,-2.85927 -0.518697,-0.2545 -0.753103,-0.30234 -1.437812,-0.29607 z m 0.482343,1.17001 c 0.433747,-0.004 0.5822,0.0265 0.910783,0.18762 0.726594,0.35681 1.117605,0.98848 1.121257,1.81133 0.006,1.33694 -1.186344,2.25211 -2.396697,2.00716 v -3.98359 c 0.109628,-0.0135 0.228055,-0.0214 0.364657,-0.0225 z"
id="path2170"
inkscape:connector-curvature="0" />
<path
id="path1422"
d="m 17.266983,230.95755 c -0.215637,0.003 -0.402408,0.014 -0.575464,0.0354 v 6.28854 c 1.910652,0.38671 3.792715,-1.05815 3.783335,-3.16865 -0.0057,-1.29897 -0.623064,-2.29597 -1.770059,-2.85927 -0.518699,-0.25451 -0.753103,-0.30234 -1.437812,-0.29607 z m 0.482342,1.17002 c 0.433748,-0.004 0.582201,0.0265 0.910784,0.18761 0.726594,0.35682 1.117605,0.98848 1.121257,1.81134 0.006,1.33694 -1.186344,2.25211 -2.396697,2.00715 v -3.98359 c 0.109628,-0.0135 0.228054,-0.0214 0.364656,-0.0225 z"
style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient1448);stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path1454"
d="m 19.402462,226.26149 -0.314194,0.40566 c -0.172793,0.22309 -0.563209,0.65259 -0.867644,0.95499 -0.461741,0.45847 -0.615657,0.54983 -0.929661,0.54983 -0.214752,0 -0.365893,-0.056 -0.599446,-0.2408 v -0.004 h -2.858741 v 12.0783 h 2.858741 c 4.174205,0.0728 3.931888,-2.28177 6.314861,-2.16937 1.427604,0.0718 1.280898,0.15899 1.725475,-1.02217 l 0.249597,-0.66402 -0.772046,-0.63873 c -0.923009,-0.76346 -1.252622,-1.23997 -1.157552,-1.67278 0.0637,-0.29001 1.160595,-1.44176 1.708939,-1.79419 0.230721,-0.14825 0.234635,-0.17171 0.09198,-0.57309 -0.08187,-0.23032 -0.249296,-0.63158 -0.371555,-0.8909 l -0.222207,-0.47181 h -1.357025 c -1.794983,0 -1.731393,0.0653 -1.734259,-1.75286 l -0.0021,-1.37925 -0.8816,-0.35708 z"
style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient1485);stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
</g>
<g
transform="translate(63.49728,-55.136805)"
id="g1174" />
<g
transform="translate(50.48327,11.624037)"
id="g1812" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

822
po/de/demon-editor.po Normal file
View File

@@ -0,0 +1,822 @@
# Copyright (C) 2018-2020 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
# Charly, 2019.
# Dmitriy Yefremov, 2020.
msgid ""
msgstr ""
"Last-Translator: Dmitriy Yefremov\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"X-Generator: Poedit 2.0.6\n"
msgid "translator-credits"
msgstr "Charly"
# Main
msgid "Service"
msgstr "Service\t"
msgid "Package"
msgstr "Paket"
msgid "Type"
msgstr "Model"
msgid "Picon"
msgstr "Picon"
msgid "Freq"
msgstr "Freq"
msgid "Rate"
msgstr "Bewertung"
msgid "Pol"
msgstr "Pol."
msgid "System"
msgstr "System"
msgid "Pos"
msgstr "Pos."
msgid "Num"
msgstr "Num"
msgid "Current IP:"
msgstr "Akt.IP:"
msgid "Assign"
msgstr "Zuweisen"
msgid "Bouquet details"
msgstr "Bouquet-Details"
msgid "Bouquets"
msgstr "Bouquets"
msgid "Copy"
msgstr "Kopie"
msgid "Copy reference"
msgstr "Kopiervorlage"
msgid "Download"
msgstr "Download"
msgid "Edit"
msgstr "Bearbeiten"
msgid "Edit mаrker text"
msgstr "Bearb. Marker Text"
msgid "FTP-transfer"
msgstr "FTP-Transfer"
msgid "Global search"
msgstr "Globale Suche"
msgid "Hide"
msgstr "Ausblenden"
msgid "Hide/Skip On/Off Ctrl + H"
msgstr "Ausblenden/Überspringen Ein/Aus Ctrl + H"
msgid "Add IPTV or stream service"
msgstr "Hinzufügen von IPTV oder Streaming-Service"
msgid "Import m3u"
msgstr "Import m3u"
msgid "Import m3u file"
msgstr "m3u-Datei importieren"
msgid "List configuration"
msgstr "Listen-Konfiguration"
msgid "Rename for this bouquet"
msgstr "Bouquet Umbenennen"
msgid "Set default name"
msgstr "Standardbezeichnung setzen"
msgid "Insert marker"
msgstr "Marker einfügen"
msgid "Locate in services"
msgstr "In den Diensten suchen"
msgid "Locked"
msgstr "Gesperrt"
msgid "Move"
msgstr "Verschieben"
msgid "New"
msgstr "Neu"
msgid "New bouquet"
msgstr "Neues Bouquet"
msgid "Create bouquet"
msgstr "Bouquet erstellen"
msgid "For current satellite"
msgstr "Für den akt. Satelliten"
msgid "For current package"
msgstr "Für das akt. Paket"
msgid "For current type"
msgstr "Für den akt. Typ"
msgid "For each satellite"
msgstr "Für alle Satelliten"
msgid "For each package"
msgstr "Für jedes Paket"
msgid "For each type"
msgstr "Für jeden Typ"
msgid "Open"
msgstr "Öffnen"
msgid "Parent lock On/Off Ctrl + L"
msgstr "Elternsperre Ein/Aus Ctrl + L"
msgid "Picons"
msgstr "Picons"
msgid "Picons downloader"
msgstr "Picons Downloader"
msgid "Satellites downloader"
msgstr "Satellites Downloader"
msgid "Remove"
msgstr "Entfernen"
msgid "Remove all unavailable"
msgstr "Entfernt alle nicht verfügbaren"
msgid "Satellites editor"
msgstr "Satelliten-Editor"
msgid "Save"
msgstr "Speichern"
msgid "Search"
msgstr "Suche"
msgid "Services"
msgstr "Services"
msgid "Services filter"
msgstr "Service-Filter"
msgid "Settings"
msgstr "Einstellungen"
msgid "Up"
msgstr "Aufwärts"
msgid "Down"
msgstr "Runter"
msgid "Active profile:"
msgstr "Aktives Profil:"
msgid "All"
msgstr "Alle"
msgid "Are you sure?"
msgstr "Bist du sicher?"
msgid "Current data path:"
msgstr "Aktueller Datenpfad:"
msgid "Data:"
msgstr "Daten:"
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
msgstr "Enigma2 Kanal- und Satellitenlisteneditor für GNU/Linux"
msgid "Host:"
msgstr "Host:"
msgid "Loading data..."
msgstr "Daten laden..."
msgid "Receive"
msgstr "Empfangen"
msgid "Receive files from receiver"
msgstr "Dateien vom Receiver empfangen"
msgid "Receiver IP:"
msgstr "Receiver IP:"
msgid "Remove unused bouquets"
msgstr "Nicht benötigte Bouquets entf."
msgid "Reset profile"
msgstr "Profil zurücksetzen"
msgid "Satellites"
msgstr "Satelliten"
msgid "Satellites.xml file:"
msgstr "Satellites.xml Datei:"
msgid "Selected"
msgstr "Ausgewählt"
msgid "Send"
msgstr "Senden"
msgid "Send files to receiver"
msgstr "Dateien zum Receiver senden"
msgid "Services and Bouquets files:"
msgstr "Services- und Bouquet-Dateien:"
msgid "User bouquet files:"
msgstr "Benutzer-Bouquetdateien:"
msgid "Extra:"
msgstr "Extra:"
# Filter bar
msgid "Only free"
msgstr "Nur freie"
msgid "All positions"
msgstr "Alle Positionen"
msgid "All types"
msgstr "Alle Typen"
# Streams player
msgid "Play"
msgstr "Abspielen"
msgid "Stop playback"
msgstr "Wiedergabe beenden"
msgid "Previous stream in the list"
msgstr "Vorheriger Stream in der Liste"
msgid "Next stream in the list"
msgstr "Nächster Stream in der Liste"
msgid "Toggle in fullscreen"
msgstr "Zu Vollbildmodus umschalten"
msgid "Close"
msgstr "Schließen"
# Picons dialog
msgid "Load providers"
msgstr "Provider laden"
msgid "Providers"
msgstr "Provider"
msgid "Receive picons"
msgstr "Picons empfangen"
msgid "Picons name format:"
msgstr "Picon Namens-Format:"
msgid "Resize:"
msgstr "Größe ändern:"
msgid "Current picons path:"
msgstr "Aktueller Piconspfad:"
msgid "Receiver picons path:"
msgstr "Receiver Piconspfad:"
msgid "Picons download tool"
msgstr "Picons Download-Tool"
msgid "Transfer to receiver"
msgstr "Übertragung zum Receiver"
msgid "Downloader"
msgstr "Downloader"
msgid "Converter"
msgstr "Konverter"
msgid "Convert"
msgstr "Konvertierung"
msgid "Path to save:"
msgstr "Speicherpfad:"
msgid "Path to Enigma2 picons:"
msgstr "Pfad zu Enigma2-Picons:"
msgid "Specify the correct position value for the provider!"
msgstr "Geben Sie den richtigen Positionswert für den Provider an!"
msgid "Converter between name formats"
msgstr "Konverter zwischen Namensformaten"
msgid "Receive picons for providers"
msgstr "Picons für Provider erhalten"
msgid "Load satellite providers."
msgstr "Lade Satellitenprovider."
msgid ""
"To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window."
msgstr ""
"Um die IDs für Picons automatisch einzustellen, \n"
"laden Sie zunächst die Liste der gewünschten Services in das Hauptfenster."
# Satellites editor
msgid "Satellites edit tool"
msgstr "Satellitenbearbeitungs-Tool"
msgid "Add"
msgstr "Hinzufügen"
msgid "Satellite"
msgstr "Satellit"
msgid "Transponder"
msgstr "Transponder"
msgid "Satellite properties:"
msgstr "Satelliten-Eigenschaften:"
msgid "Transponder properties:"
msgstr "Transponder-Eigenschaften:"
msgid "Name"
msgstr "Name"
msgid "Position"
msgstr "Position"
# Satellites update dialog
msgid "Satellites update"
msgstr "Satelliten-Aktualisierung"
msgid "Remove selection"
msgstr "Auswahl entfernen"
# Service details dialog
msgid "Service data:"
msgstr "Service-Daten:"
msgid "Transponder data:"
msgstr "Transponder-Daten:"
msgid "Service data"
msgstr "Servicedaten"
msgid "Transponder details"
msgstr "Transponderdetails"
msgid ""
"Changes will be applied to all services of this transponder!\n"
"Continue?"
msgstr ""
"Änderungen werden auf alle Services dieses Transponders angewendet!\n"
"Fortsetzen?"
msgid "Reference"
msgstr "Referenz"
msgid "Namespace"
msgstr "Namensbereich"
msgid "Flags:"
msgstr "Kennzeichen:"
msgid "Delays (ms):"
msgstr "Verzögerungen (ms):"
msgid "Bitstream"
msgstr "Bitstream"
msgid "Description"
msgstr "Bezeichnung"
msgid "Source:"
msgstr "Quelle:"
msgid "Cancel"
msgstr "Abbrechen"
msgid "Update"
msgstr "Aktualisieren"
msgid "Filter"
msgstr "Filter"
msgid "Find"
msgstr "Finde"
# IPTV dialog
msgid "Stream data"
msgstr "Stream-Daten"
# IPTV list configuration dialog
msgid "Starting values"
msgstr "Startwerte"
msgid "Reset to default"
msgstr "Auf Standard zurücksetzen"
msgid "IPTV streams list configuration"
msgstr "Konfiguration der IPTV-Stream Liste"
# Settings dialog
msgid "Preferences"
msgstr "Einstellungen"
msgid "Profile:"
msgstr "Profil:"
msgid "Timeout between commands in seconds"
msgstr "Wartezeit zwischen den Befehlen in Sekunden."
msgid "Timeout:"
msgstr "Timeout:"
msgid "Login:"
msgstr "Login:"
msgid "Options"
msgstr "Optionen"
msgid "Password:"
msgstr "Passwort:"
msgid "Picons:"
msgstr "Picons:"
msgid "Port:"
msgstr "Port:"
msgid "Data path:"
msgstr "Datenpfad:"
msgid "Picons path:"
msgstr "Piconspfad:"
msgid "Network settings:"
msgstr "Netzwerkeinstellungen:"
msgid "STB file paths:"
msgstr "STB-Dateipfade:"
msgid "Local file paths:"
msgstr "Lokale Dateipfade:"
# Dialogs messages
msgid "Error. No bouquet is selected!"
msgstr "Fehler. Es wurde kein Bouquet ausgewählt!"
msgid "This item is not allowed to be removed!"
msgstr "Dieser Eintrag darf nicht entfernt werden!"
msgid "This item is not allowed to edit!"
msgstr "Dieser Eintrag darf nicht bearbeitet werden!"
msgid "Not allowed in this context!"
msgstr "In diesem Zusammenhang nicht erlaubt!"
msgid "Please, download files from receiver or setup your path for read data!"
msgstr "Bitte lade die Dateien vom Receiver herunter oder richte den Pfad für eingelesene Daten ein!"
msgid "Reading data error!"
msgstr "Lesefehler!"
msgid "No m3u file is selected!"
msgstr "Es ist keine m3u-Datei ausgewählt!"
msgid "Not implemented yet!"
msgstr "Noch nicht realisiert!"
msgid "The text of marker is empty, please try again!"
msgstr "Der Text des Markers ist leer, bitte versuchen Sie es erneut!"
msgid "Please, select only one item!"
msgstr "Bitte wählen Sie nur einen Eintrag aus!"
msgid "No png file is selected!"
msgstr "Es ist keine png-Datei ausgewählt!"
msgid "No reference is present!"
msgstr "Es liegt keine Referenz vor!"
msgid "No selected item!"
msgstr "Kein ausgewählter Eintrag!"
msgid "The task is already running!"
msgstr "Die Aufgabe läuft bereits!"
msgid "Done!"
msgstr "Fertig!"
msgid "Please, wait..."
msgstr "Bitte, warten..."
msgid "Resizing..."
msgstr "Größe ändern..."
msgid "Select paths!"
msgstr "Pfade auswählen!"
msgid "No satellite is selected!"
msgstr "Kein Satellit ist ausgewählt!"
msgid "Please, select only one satellite!"
msgstr "Bitte wähle nur einen Satelliten aus!"
msgid "Please check your parameters and try again."
msgstr "Bitte überprüfe die Einstellungen und versuche es erneut."
msgid "No satellites.xml file is selected!"
msgstr "Keine satellites.xml Datei ausgewählt!"
msgid "Error. Verify the data!"
msgstr "Fehler. Prüfe die Daten!"
msgid "Operation not allowed in this context!"
msgstr "Vorgang hier nicht zulässig!"
msgid "No VLC is found. Check that it is installed!"
msgstr "VLC nicht gefunden. Überprüfe, ob es installiert ist!"
# Search unavailable streams dialog
msgid "Please wait, streams testing in progress..."
msgstr "Bitte warte, Stream-Test läuft..."
msgid "Found"
msgstr "Gefunden"
msgid "unavailable streams."
msgstr "nicht verfügbarer Stream."
msgid "No changes required!"
msgstr "Keine Änderungen nötig!"
msgid "This list does not contains IPTV streams!"
msgstr "Diese Liste enthält keine IPTV-Streams!"
msgid "New empty configuration"
msgstr "Neue leere Einstellung"
msgid "No data to save!"
msgstr "Keine Daten zum Speichern!"
msgid "Network"
msgstr "Netzwerk"
msgid "Paths"
msgstr "Pfade"
msgid "Program"
msgstr "Programm"
msgid "Backup:"
msgstr "Sicherung:"
msgid "Backup"
msgstr "Sicherung"
msgid "Backups"
msgstr "Sicherungen"
msgid "Backup path:"
msgstr "Sicherungspfad:"
msgid "Restore bouquets"
msgstr "Bouquets wiederherstellen"
msgid "Restore all"
msgstr "Alles wiederherstellen"
msgid "Before saving"
msgstr "Vor dem Speichern"
msgid "Before downloading from the receiver"
msgstr "Vor dem Herunterladen vom Receiver"
msgid "Set background color for the services"
msgstr "Hintergrundfarbe für die Services setzen"
msgid "Marked as new:"
msgstr "Als neu markiert:"
msgid "With an extra name in the bouquet:"
msgstr "Mit einem extra Namen im Bouquet:"
msgid "Select"
msgstr "Auswählen"
msgid "About"
msgstr "Über"
msgid "Exit"
msgstr "Beenden"
msgid "Tools"
msgstr "Werkzeug"
# Import
msgid "Import"
msgstr "Import"
msgid "Bouquet"
msgstr "Bouquet"
msgid "Bouquets and services"
msgstr "Bouquets und Services"
msgid "The main list does not contain services for this bouquet!"
msgstr "Die Hauptliste enthält keine Services für dieses Bouquet!"
msgid "No bouquet file is selected!"
msgstr "Keine Bouquet-Datei ausgewählt!"
msgid "Remove all unused"
msgstr "Alles ungenutzte entfernen"
msgid "Test"
msgstr "Test"
msgid "Test connection"
msgstr "Test Verbindung"
msgid "Double click on the service in the bouquet list:"
msgstr "Doppelklicke auf das Service in der Bouquetliste:"
msgid "Zap"
msgstr "Zap"
msgid "Play stream"
msgstr "Play Stream"
msgid "Disabled"
msgstr "Ausgeschaltet"
msgid "Enable ver. 5 support (experimental)"
msgstr "Lamedb ver. 5 Unterstützung aktivieren (experimentell)"
msgid "Enable HTTP API (experimental)"
msgstr "HTTP-API aktivieren (experimentell)"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Umschalten des Kanals (Strg + Z)"
msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Kanal wechseln und im Programm ansehen(Strg + W)"
msgid "Play IPTV or other stream in the program(Ctrl + P)"
msgstr "Wiedergabe von IPTV oder anderen Streams im Programm(Strg + P)"
msgid "Export to m3u"
msgstr "Export nach m3u"
msgid "EPG configuration"
msgstr "EPG Konfiguration"
msgid "Apply"
msgstr "Anwenden"
msgid "EPG source"
msgstr "EPG Quelle"
msgid "Service names source:"
msgstr "Quelle der Dienstnamen:"
msgid "Main service list"
msgstr "Hauptdienstliste"
msgid "XML file"
msgstr "XML-Datei"
msgid "Use web source"
msgstr "Web-Quelle verwenden"
msgid "Url to *.xml.gz file:"
msgstr "Url zur *.xml.gz Datei:"
msgid "Enable filtering"
msgstr "Filterung einschalten"
msgid "Filter by presence in the epg.dat file."
msgstr "Filtern nach dem Vorhandensein in der epg.dat Datei."
msgid "Paths to the epg.dat file:"
msgstr "Pfade zur epg.dat Datei:"
msgid "Local path:"
msgstr "Local path:"
msgid "STB path:"
msgstr "STB-Pfad:"
msgid "Update on start"
msgstr "Update beim Start"
msgid "Auto configuration by service names."
msgstr "Automatische Konfiguration nach Dienstnamen."
msgid "Save list to xml."
msgstr "Liste in XML speichern."
msgid "Download XML file error."
msgstr "Fehler beim Herunterladen der XML-Datei."
msgid "Unsupported file type:"
msgstr "Nicht unterstützter Dateityp:"
msgid "Unpacking data error."
msgstr "Fehler beim Entpacken von Daten."
msgid "XML parsing error:"
msgstr "XML Parsing-Fehler:"
msgid "Count of successfully configured services:"
msgstr "Anzahl der erfolgreich konfigurierten Dienste:"
msgid "Current epg.dat file does not contains references for the services of this bouquet!"
msgstr "Die aktuelle epg.dat Datei enthält keine Referenzen für die Dienste dieses Bouquets!"
msgid "Use HTTP"
msgstr "HTTP verwenden"
msgid "Close playback"
msgstr "Wiedergabe schliessen"
msgid "Import YouTube playlist"
msgstr "YouTube-Wiedergabeliste importieren"
msgid ""
"Found a link to the YouTube resource!\n"
"Try to get a direct link to the video?"
msgstr ""
"Ich habe einen Link zur YouTube-Ressource gefunden!\n"
"Versuchen einen direkten Link zum Video zu bekommen?"
msgid "Playlist import"
msgstr "Playlist-Import"
msgid "Getting link error:"
msgstr "Link-Fehler erhalten:"
msgid "Extra"
msgstr "Extra"
msgid "Apply profile settings"
msgstr "Profileinstellungen anwenden"
msgid "Settings type:"
msgstr "Art der Einstellungen:"
msgid "Set default"
msgstr "Standard setzen"
msgid "Language:"
msgstr "Sprache:"
msgid "Load the last open configuration at program startup"
msgstr "Laden der zuletzt geöffneten Konfiguration beim Programmstart"
msgid "Enable direct playback bar (experimental)"
msgstr "Aktivieren der direkten Wiedergabeleiste (experimentell)"
msgid "Enables direct sending and playback of media links on the receiver"
msgstr "Ermöglicht das direkte Senden und Abspielen von Medienlinks auf dem Box"
msgid "Watch the channel in the program"
msgstr "Gucken den Kanal im Programm an"
msgid "Zap and Play"
msgstr "Zap und Abspielen"
msgid "Drag or paste the link here"
msgstr "Ziehe den Link hierher oder füge ihn ein"
msgid "Remove added links in the playlist"
msgstr "Hinzugefügte Links in der Wiedergabeliste entfernen"
msgid "A bouquet with that name exists!"
msgstr "Bouquet mit diesem Namen existiert!"

View File

@@ -1,7 +1,7 @@
# Copyright (C) 2018 Frank Neirynck
# Copyright (C) 2018-2019 Frank Neirynck
# This file is distributed under the MIT license.
#
#Frank Neirynck <frank@insink.be>, 2018.
# Frank Neirynck <frank@insink.be>, 2018-2019.
#
msgid ""
msgstr ""
@@ -10,6 +10,11 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"X-Generator: Poedit 2.2.1\n"
msgid "translator-credits"
msgstr "Frank Neirynck <frank@insink.be>"
@@ -31,7 +36,7 @@ msgid "Freq"
msgstr "Frec."
msgid "Rate"
msgstr "Rate"
msgstr "Ratio"
msgid "Pol"
msgstr "Pol."
@@ -52,16 +57,16 @@ msgid "Assign"
msgstr "Assignar"
msgid "Bouquet details"
msgstr "Detailes Ramo"
msgstr "Detalles bouquet"
msgid "Bouquets"
msgstr "Ramos"
msgstr "Bouquets"
msgid "Copy"
msgstr "Copiar"
msgid "Copy reference"
msgstr "Copia de Referencia"
msgstr "Copia de referencia"
msgid "Download"
msgstr "Descargar"
@@ -69,14 +74,11 @@ msgstr "Descargar"
msgid "Edit"
msgstr "Editar"
msgid "Edit "
msgstr "Editar "
msgid "Edit mаrker text"
msgstr "Editar testo del mаrcador"
msgstr "Editar texto del mаrcador"
msgid "FTP-transfer"
msgstr "Transferencia-FTP"
msgstr "Transferencia FTP"
msgid "Global search"
msgstr "Búsqueda Global"
@@ -97,22 +99,22 @@ msgid "Import m3u file"
msgstr "Importar fichero m3u"
msgid "List configuration"
msgstr "Lista configuración"
msgstr "Listar configuración"
msgid "Rename for this bouquet"
msgstr "Renombrar para este ramo"
msgstr "Renombrar para este bouquet"
msgid "Set default name"
msgstr "Establecer nombre predeterminado"
msgid "Insert marker"
msgstr "Inserter marcador"
msgstr "Insertar marcador"
msgid "Locate in services"
msgstr "Buscar en servicios"
msgid "Locked"
msgstr "Cerrado"
msgstr "Bloqueado"
msgid "Move"
msgstr "Mover"
@@ -121,13 +123,13 @@ msgid "New"
msgstr "Nuevo"
msgid "New bouquet"
msgstr "Ramo nuevo"
msgstr "Bouquet nuevo"
msgid "Create bouquet"
msgstr "Crear ramo"
msgstr "Crear bouquet"
msgid "For current satellite"
msgstr "Para el Satélite actual"
msgstr "Para el satélite actual"
msgid "For current package"
msgstr "Para el paquete actual"
@@ -136,7 +138,7 @@ msgid "For current type"
msgstr "Para el tipo actual"
msgid "For each satellite"
msgstr "Para cada Satélite"
msgstr "Para cada satélite"
msgid "For each package"
msgstr "Para cada paquete"
@@ -148,25 +150,25 @@ msgid "Open"
msgstr "Abrir"
msgid "Parent lock On/Off Ctrl + L"
msgstr "Bloqueo parentesco Encender/Apagar Ctrl + L"
msgstr "Bloqueo parental Encender/Apagar Ctrl + L"
msgid "Picons"
msgstr "Picons"
msgid "Picons downloader"
msgstr "Picons descargar"
msgstr "Descargar picons"
msgid "Satellites downloader"
msgstr "Satélites descargar"
msgstr "Descargar satélites"
msgid "Remove"
msgstr "Remover"
msgstr "Quitar"
msgid "Remove all unavailable"
msgstr "Remover toto lo indisponible"
msgstr "Quitar todo lo indisponible"
msgid "Satellites editor"
msgstr "Editor Satélites"
msgstr "Editor de satélites"
msgid "Save"
msgstr "Guardar"
@@ -181,7 +183,7 @@ msgid "Services filter"
msgstr "Filtro servicios"
msgid "Settings"
msgstr "Configuraciones"
msgstr "Configuración"
msgid "Up"
msgstr "Arriba"
@@ -196,7 +198,7 @@ msgid "All"
msgstr "Todo"
msgid "Are you sure?"
msgstr "Estás seguro?"
msgstr "¿Estás seguro?"
msgid "Current data path:"
msgstr "Ruta de datos actual:"
@@ -205,13 +207,13 @@ msgid "Data:"
msgstr "Datos:"
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
msgstr "Editor de Canales y Satélites Enigma2 para GNU/Linux"
msgstr "Editor de canales y satélites Enigma2 para GNU/Linux"
msgid "Host:"
msgstr "Anfitrión:"
msgid "Loading data..."
msgstr "Cargar datos..."
msgstr "Cargando datos..."
msgid "Receive"
msgstr "Recibir"
@@ -220,10 +222,10 @@ msgid "Receive files from receiver"
msgstr "Recibir ficheros de su receptor"
msgid "Receiver IP:"
msgstr "Receptor IP:"
msgstr "IP del receptor:"
msgid "Remove unused bouquets"
msgstr "Remover ramos sin usar"
msgstr "Quitar bouquets sin usar"
msgid "Reset profile"
msgstr "Restablecer perfil"
@@ -232,7 +234,7 @@ msgid "Satellites"
msgstr "Satélites"
msgid "Satellites.xml file:"
msgstr "Fichero Satellites.xml:"
msgstr "Fichero satellites.xml:"
msgid "Selected"
msgstr "Seleccionado"
@@ -244,10 +246,10 @@ msgid "Send files to receiver"
msgstr "Enviar ficheros al receptor"
msgid "Services and Bouquets files:"
msgstr "Ficheros de Servicios y ramos:"
msgstr "Ficheros de servicios y bouquets:"
msgid "User bouquet files:"
msgstr "Importar ficheros de ramos:"
msgstr "Importar ficheros de bouquets:"
msgid "Extra:"
msgstr "Extra:"
@@ -260,20 +262,20 @@ msgid "All positions"
msgstr "Todas las posiciones"
msgid "All types"
msgstr "Todas los tipos"
msgstr "Todos los tipos"
# Streams player
msgid "Play"
msgstr "Play"
msgstr "Reproducir"
msgid "Stop playback"
msgstr "Detener la reproducción"
msgid "Previous stream in the list"
msgstr "Secuencia anterior en la lista"
msgstr "Anterior flujo en la lista"
msgid "Next stream in the list"
msgstr "Secuencia siguiente en la lista"
msgstr "Siguiente flujo en la lista"
msgid "Toggle in fullscreen"
msgstr "Cambiar a pantalla completa"
@@ -283,7 +285,7 @@ msgstr "Cerrar"
# Picons dialog
msgid "Load providers"
msgstr "Cargar Proveedores"
msgstr "Cargar proveedores"
msgid "Providers"
msgstr "Proveedores"
@@ -298,19 +300,19 @@ msgid "Resize:"
msgstr "Redimensionar:"
msgid "Current picons path:"
msgstr "Ruta acual Picons:"
msgstr "Ruta actual picons:"
msgid "Receiver picons path:"
msgstr "Ruta picons receptor:"
msgid "Picons download tool"
msgstr "Picons herramiento de descarga"
msgstr "Herramienta de descarga de picons"
msgid "Transfer to receiver"
msgstr "Transferir al receptor"
msgid "Downloader"
msgstr "Descargador"
msgstr "Programa de descarga"
msgid "Converter"
msgstr "Convertidor"
@@ -319,29 +321,34 @@ msgid "Convert"
msgstr "Convertir"
msgid "Path to save:"
msgstr "Ruto para guardar:"
msgstr "Ruta para guardar:"
msgid "Path to Enigma2 picons:"
msgstr "Ruta a picons Enigma2:"
msgstr "Ruta a picons de Enigma2:"
msgid "Specify the correct position value for the provider!"
msgstr "Especifique la posición correcta para el proveedor!"
msgstr "¡Especifique el valor correcto de la posición del proveedor!"
msgid "Converter between name formats"
msgstr "Conversor entre formatos de nombre"
msgid "Receive picons for providers"
msgstr "Recibir picons para proovedor"
msgstr "Recibir picons de proveedores"
msgid "Load satellite providers."
msgstr "Cargar proovedores Satélite."
msgstr "Cargar proveedores de satélite."
msgid "To automatically set the identifiers for picons,\nfirst load the required services list into the main application window."
msgstr "Para configurar automáticamente los identificadores para picons, \nprimero cargue la lista de servicios requeridos en la ventana principal."
msgid ""
"To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window."
msgstr ""
"Para configurar automáticamente los identificadores para picons,\n"
"cargue primero la lista de servicios requeridos en la ventana principal."
# Satellites editor
msgid "Satellites edit tool"
msgstr "Edito de Satélites"
msgstr "Editor de satélites"
msgid "Add"
msgstr "Añadir"
@@ -353,10 +360,10 @@ msgid "Transponder"
msgstr "Transpondedor"
msgid "Satellite properties:"
msgstr "Propiedades del Satélite:"
msgstr "Propiedades del satélite:"
msgid "Transponder properties:"
msgstr "Propiedades del Transpondedor:"
msgstr "Propiedades del transpondedor:"
msgid "Name"
msgstr "Nombre"
@@ -366,26 +373,30 @@ msgstr "Posición"
# Satellites update dialog
msgid "Satellites update"
msgstr "Actualisar Satélite"
msgstr "Actualizar satélites"
msgid "Remove selection"
msgstr "Remover selección"
msgstr "Quitar selección"
# Service details dialog
msgid "Service data:"
msgstr "Datos servicio:"
msgid "Transponder data:"
msgstr "Datos Transpondedor:"
msgstr "Datos transpondedor:"
msgid "Service data"
msgstr "Datos servicio"
msgid "Transponder details"
msgstr "Detailes Transpondedor"
msgstr "Detalles transpondedor"
msgid "Changes will be applied to all services of this transponder!\nContinue?"
msgstr "Los cambios se aplicarán a todos los servicios de este transpondedor!\nContinuar?"
msgid ""
"Changes will be applied to all services of this transponder!\n"
"Continue?"
msgstr ""
"Los cambios se aplicarán a todos los servicios de este transpondedor!\n"
"¿Continuar?"
msgid "Reference"
msgstr "Referencia"
@@ -397,10 +408,10 @@ msgid "Flags:"
msgstr "Flags:"
msgid "Delays (ms):"
msgstr "Retraso (mc)"
msgstr "Retraso (ms)"
msgid "Bitstream"
msgstr "Secuencia de Bits"
msgstr "Secuencia de bits"
msgid "Description"
msgstr "Descripción"
@@ -409,10 +420,10 @@ msgid "Source:"
msgstr "Fuente:"
msgid "Cancel"
msgstr "Annular"
msgstr "Cancelar"
msgid "Update"
msgstr "Actualisar"
msgstr "Actualizar"
msgid "Filter"
msgstr "Filtrar"
@@ -422,19 +433,19 @@ msgstr "Buscar"
# IPTV dialog
msgid "Stream data"
msgstr "Datos de la Secuencia"
msgstr "Transmitir flujo"
# IPTV list configuration dialog
msgid "Starting values"
msgstr "Volores iniciales"
msgstr "Valores iniciales"
msgid "Reset to default"
msgstr "Restablecer a predeterminado"
msgid "IPTV streams list configuration"
msgstr "Configurar lista de Secuencias IPTV"
msgstr "Configurar lista de flujos IPTV"
#Settings dialog
# Settings dialog
msgid "Preferences"
msgstr "Preferencias"
@@ -445,7 +456,7 @@ msgid "Timeout between commands in seconds"
msgstr "Tiempo de espera entre comandos en segundos"
msgid "Timeout:"
msgstr "Time-out:"
msgstr "Tiempo de espera:"
msgid "Login:"
msgstr "Usuario:"
@@ -460,112 +471,310 @@ msgid "Picons:"
msgstr "Picons:"
msgid "Port:"
msgstr "Puerta:"
msgstr "Puerto:"
msgid "Data path:"
msgstr "Ruta de datos:"
msgid "Picons path:"
msgstr "Ruda de Picons:"
msgstr "Ruta de picons:"
msgid "Network settings:"
msgstr "Configuración de red:"
msgid "STB file paths:"
msgstr "Ruta de ficherors STB:"
msgstr "Rutas de ficheros del receptor:"
msgid "Local file paths:"
msgstr "Ruta de ficheros local:"
msgstr "Rutas de ficheros local:"
# Dialogs messages
msgid "Error. No bouquet is selected!"
msgstr "Error. Ningún ramo está seleccionado!"
msgstr "Error. ¡Ningún bouquet seleccionado!"
msgid "This item is not allowed to be removed!"
msgstr "Este artículo no puede ser eliminado!"
msgstr "¡Este elemento no puede ser quitado!"
msgid "This item is not allowed to edit!"
msgstr "Este artículo no puede ser editado!"
msgstr "¡Este elemento no puede ser editado!"
msgid "Not allowed in this context!"
msgstr "No permitido en este contexto!"
msgstr "¡No permitido en este contexto!"
msgid "Please, download files from receiver or setup your path for read data!"
msgstr "Por favor, descargue archivos desde el receptor o configure su ruta para leer los datos!"
msgstr "Por favor, descargue ficheros desde el receptor o configure la ruta para leer los datos!"
msgid "Reading data error!"
msgstr "Error de lectura de datos!"
msgstr "¡Error de lectura de datos!"
msgid "No m3u file is selected!"
msgstr "Ningún archivo m3u ha sido seleccionado!"
msgstr "¡No se ha seleccionado ningún fichero m3u!"
msgid "Not implemented yet!"
msgstr "Aun no implementado!"
msgstr "¡Aún sin implementar!"
msgid "The text of marker is empty, please try again!"
msgstr "El texto del marcador está vacío, inténtalo de nuevo!"
msgstr "¡El texto del marcador está vacío, inténtalo de nuevo!"
msgid "Please, select only one item!"
msgstr "Por favor, seleccione solo un elemento!"
msgstr "¡Por favor, seleccione sólo un elemento!"
msgid "No png file is selected!"
msgstr "Ningún fichero png seleccionado!"
msgstr "¡No se ha seleccionado ningún fichero png!"
msgid "No reference is present!"
msgstr "Ninguna referencia presente!"
msgstr "¡Ninguna referencia presente!"
msgid "No selected item!"
msgstr "Ningún elemento seleccionado!"
msgstr "¡Ningún elemento seleccionado!"
msgid "The task is already running!"
msgstr "La tarea ya se está ejecutando!"
msgstr "¡La tarea ya se está ejecutando!"
msgid "Done!"
msgstr "Hecho!"
msgstr "¡Hecho!"
msgid "Please, wait..."
msgstr "Por favor, espere..."
msgid "Resizing..."
msgstr "Redimensionamiento..."
msgstr "Redimensionando..."
msgid "Select paths!"
msgstr "Seleccione rutas!"
msgstr "¡Seleccione rutas!"
msgid "No satellite is selected!"
msgstr "Ningún Satélite seleccionado!"
msgstr "¡Ningún satélite seleccionado!"
msgid "Please, select only one satellite!"
msgstr "Seleccione solo un Satélite!"
msgstr "¡Seleccione sólo un Satélite!"
msgid "Please check your parameters and try again."
msgstr "Por favor revise sus parámetros y vuelva a intentar!"
msgstr "¡Por favor revise sus parámetros y vuelva a intentarlo!"
msgid "No satellites.xml file is selected!"
msgstr "Ningún satellites.xml seleccionado!"
msgstr "¡Ningún satellites.xml seleccionado!"
msgid "Error. Verify the data!"
msgstr "Error. Revise sus datos!"
msgstr "Error. ¡Revise los datos!"
msgid "Operation not allowed in this context!"
msgstr "Operación no permitida en este contexto!"
msgstr "¡Operación no permitida en este contexto!"
msgid "No VLC is found. Check that it is installed!"
msgstr "VLC no encontrado. Verifica si está instalado!"
msgstr "VLC no encontrado. ¡Verifique que está instalado!"
# Search unavailable streams dialog
msgid "Please wait, streams testing in progress..."
msgstr "Por favor espera una prueba de las secuencias..."
msgstr "Por favor espere, hay una prueba de flujo en progreso..."
msgid "Found"
msgstr "Encontrado"
msgid "unavailable streams."
msgstr "Secuencias no presentes"
msgstr "Flujos no presentes."
msgid "No changes required!"
msgstr "ningún cambio requerido!"
msgstr "¡Ningún cambio requerido!"
msgid "This list does not contains IPTV streams!"
msgstr "La lista no contiene secuencias IPTV!"
msgstr "¡La lista no contiene flujos IPTV!"
msgid "New empty configuration"
msgstr "Nueva configuración vacía"
msgid "No data to save!"
msgstr "¡No hay datos que guardar!"
msgid "Network"
msgstr "Red"
msgid "Paths"
msgstr "Rutas"
msgid "Program"
msgstr "Programa"
msgid "Backup:"
msgstr "Copia de seguridad:"
msgid "Backup"
msgstr "Copia de seguridad"
msgid "Backups"
msgstr "Copias de seguridad"
msgid "Backup path:"
msgstr "Ruta de la copia de seguridad:"
msgid "Restore bouquets"
msgstr "Restaurar bouquets"
msgid "Restore all"
msgstr "Restaurar todo"
msgid "Before saving"
msgstr "Antes de guardar"
msgid "Before downloading from the receiver"
msgstr "Antes de recibir del receptor"
msgid "Set background color for the services"
msgstr "Determinar color de fondo de los servicios"
msgid "Marked as new:"
msgstr "Marcado como nuevo:"
msgid "With an extra name in the bouquet:"
msgstr "Con nombre adicional en bouquet:"
msgid "Select"
msgstr "Seleccione"
msgid "About"
msgstr "Acerca de"
msgid "Exit"
msgstr "Salir"
msgid "Tools"
msgstr "Herramientas"
# Import
msgid "Import"
msgstr "Importar"
msgid "Bouquet"
msgstr "Bouquet"
msgid "Bouquets and services"
msgstr "Bouquets y servicios"
msgid "The main list does not contain services for this bouquet!"
msgstr "¡La lista principal no contiene servicios para este bouquet!"
msgid "No bouquet file is selected!"
msgstr "¡No se ha seleccionado nigún fichero de bouquet!"
msgid "Remove all unused"
msgstr "Quitar todos sin usar"
msgid "Test"
msgstr "Prueba"
msgid "Test connection"
msgstr "Probar conexión"
msgid "Double click on the service in the bouquet list:"
msgstr "Haga doble clic en el servicio en la lista de bouquet:"
msgid "Zap"
msgstr "Zapear"
msgid "Play stream"
msgstr "Reproducir flujo"
msgid "Disabled"
msgstr "Desactivado"
msgid "Enable ver. 5 support (experimental)"
msgstr "Soporte para ver. 5 (experimental)"
msgid "Enable HTTP API (experimental)"
msgstr "Habilitar API HTTP (experimental)"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Poner el canal (Ctrl + Z)"
msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Poner el canal y ver en el programa (Ctrl + W)"
msgid "Play IPTV or other stream in the program(Ctrl + P)"
msgstr "Reproducir IPTV u otro flujo en el programa (Ctrl + P)"
msgid "Export to m3u"
msgstr "Exportar a m3u"
msgid "EPG configuration"
msgstr "Configuración EPG"
msgid "Apply"
msgstr "Aplicar"
msgid "EPG source"
msgstr "Fuente EPG"
msgid "Service names source:"
msgstr "Origen nombres de servicio:"
msgid "Main service list"
msgstr "Lista principal de servicios:"
msgid "XML file"
msgstr "Fichero XML"
msgid "Use web source"
msgstr "Usar fuente web"
msgid "Url to *.xml.gz file:"
msgstr "URL del fichero *.xml.gz:"
msgid "Enable filtering"
msgstr "Habilitar filtrado"
msgid "Filter by presence in the epg.dat file."
msgstr "Filtrar según presencia del fichero epg.dat."
msgid "Paths to the epg.dat file:"
msgstr "Ruta al fichero epg.dat:"
msgid "Local path:"
msgstr "Ruta local:"
msgid "STB path:"
msgstr "Ruta receptor:"
msgid "Update on start"
msgstr "Actualizar al inicio"
msgid "Auto configuration by service names."
msgstr "Auto configuración según nombres de servicios."
msgid "Save list to xml."
msgstr "Guardar como XML."
msgid "Download XML file error."
msgstr "Error bajando fichero XML."
msgid "Unsupported file type:"
msgstr "Fichero no soportado:"
msgid "Unpacking data error."
msgstr "Error abriendo datos."
msgid "XML parsing error:"
msgstr "Error analizando XML:"
msgid "Count of successfully configured services:"
msgstr "Número de servicios configurados con éxito:"
msgid "Current epg.dat file does not contains references for the services of this bouquet!"
msgstr "¡El fichero epg.dat actual no tiene referencias a servicios de este bouquet!"
msgid "Use HTTP"
msgstr "Utilizar HTTP"
msgid "Close playback"
msgstr "Terminar reproducción"
msgid "Import YouTube playlist"
msgstr "Importar lista de reproducción de YouTube"
msgid "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
msgstr "¡Encontrado enlace al recurso de YouTube!\n¿Intentar obtener un enlace directo al vídeo?"
msgid "Playlist import"
msgstr "Importar lista de reproducción"
msgid "Getting link error:"
msgstr "Error en el enlace:"

View File

@@ -1,7 +1,7 @@
# Copyright (C) 2018 Frank Neirynck
# Copyright (C) 2018-2019 Frank Neirynck
# This file is distributed under the MIT license.
#
#Frank Neirynck <frank@insink.be>, 2018.
# Frank Neirynck <frank@insink.be>, 2018-2019.
#
msgid ""
msgstr ""
@@ -10,6 +10,11 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"X-Generator: Poedit 2.2.1\n"
msgid "translator-credits"
msgstr "Frank Neirynck <frank@insink.be>"
@@ -69,9 +74,6 @@ msgstr "Download"
msgid "Edit"
msgstr "Wijzig"
msgid "Edit "
msgstr "Wijzig "
msgid "Edit mаrker text"
msgstr "Wijzig mаrker tekst"
@@ -336,8 +338,12 @@ msgstr "Ontvang picons voor leveranciers"
msgid "Load satellite providers."
msgstr "Laad satelliet leveranciers."
msgid "To automatically set the identifiers for picons,\nfirst load the required services list into the main application window."
msgstr "Om automatisch de ID in te stellen voor picons,\nlaad eerst de vereiste serviceslijst in via het hoofdvenster van het programma."
msgid ""
"To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window."
msgstr ""
"Om automatisch de ID in te stellen voor picons,\n"
"laad eerst de vereiste serviceslijst in via het hoofdvenster van het programma."
# Satellites editor
msgid "Satellites edit tool"
@@ -384,8 +390,12 @@ msgstr "Gegevens Dienst"
msgid "Transponder details"
msgstr "Details Transponder"
msgid "Changes will be applied to all services of this transponder!\nContinue?"
msgstr "Wijzigingen zullen worden doorgevoerd op alle diensten van deze transponder!\nDoorgaan?"
msgid ""
"Changes will be applied to all services of this transponder!\n"
"Continue?"
msgstr ""
"Wijzigingen zullen worden doorgevoerd op alle diensten van deze transponder!\n"
"Doorgaan?"
msgid "Reference"
msgstr "Referentie"
@@ -434,7 +444,7 @@ msgstr "Reset naar standaard"
msgid "IPTV streams list configuration"
msgstr "Configureren IPTV Streamlijst"
#Settings dialog
# Settings dialog
msgid "Preferences"
msgstr "Voorkeuren"
@@ -568,3 +578,202 @@ msgstr "Geen wijzigingen vereist!"
msgid "This list does not contains IPTV streams!"
msgstr "Deze lijst bevat geen IPTV streams!"
msgid "New empty configuration"
msgstr "Nieuwe lege configuratie"
msgid "No data to save!"
msgstr "Geen gegevens om op te slaan!"
msgid "Network"
msgstr "Netwerk"
msgid "Paths"
msgstr "Paden"
msgid "Program"
msgstr "Programma"
msgid "Backup:"
msgstr "Backup:"
msgid "Backup"
msgstr "Backup"
msgid "Backups"
msgstr "Backups"
msgid "Backup path:"
msgstr "Backup pad:"
msgid "Restore bouquets"
msgstr "Herstel boeketten"
msgid "Restore all"
msgstr "Herstel alles"
msgid "Before saving"
msgstr "Voor opslaan"
msgid "Before downloading from the receiver"
msgstr "Voor download uit de ontvanger"
msgid "Set background color for the services"
msgstr "Stel achtergrond kleur in voor diensten"
msgid "Marked as new:"
msgstr "Gemarkeerd als nieuw:"
msgid "With an extra name in the bouquet:"
msgstr "Met een extra naam in het boeket:"
msgid "Select"
msgstr "Over"
msgid "About"
msgstr "Over"
msgid "Exit"
msgstr "Exit"
msgid "Tools"
msgstr "Tools"
# Import
msgid "Import"
msgstr "Importeer"
msgid "Bouquet"
msgstr "Boeket"
msgid "Bouquets and services"
msgstr "Boeketten en diensten"
msgid "The main list does not contain services for this bouquet!"
msgstr "De hoofdlijst bevat geen diensten voor dit boeket!"
msgid "No bouquet file is selected!"
msgstr "Geen boeket geselecteerd!"
msgid "Remove all unused"
msgstr "Verwijder alle ongebruikte"
msgid "Test"
msgstr "Test"
msgid "Test connection"
msgstr "Test verbinding"
msgid "Double click on the service in the bouquet list:"
msgstr "Dubbelklik op de dienst in de boeket lijst:"
msgid "Zap"
msgstr "Zap"
msgid "Play stream"
msgstr "Speel stream af"
msgid "Disabled"
msgstr "Uitgeschakeld"
msgid "Enable ver. 5 support (experimental)"
msgstr "Ondersteuning voor ver. 5 inschakelen (experimenteel)"
msgid "Enable HTTP API (experimental)"
msgstr "HTTP API inschakelen (experimenteel)"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Schakelaar (ZAP) naar het kanaal (CTRL + Z)"
msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Schakel het kanaal in en bekijk het programma (CTRL + W)"
msgid "Play IPTV or other stream in the program(Ctrl + P)"
msgstr "Speel IPTV of andere stream af en bekijk het programma (CTRL + P)"
msgid "Export to m3u"
msgstr "Uitvoeren naar m3u"
msgid "EPG configuration"
msgstr "Configureer EPG"
msgid "Apply"
msgstr "Toepassen"
msgid "EPG source"
msgstr "Bron EPG"
msgid "Service names source:"
msgstr "Naam van bron van de dienst:"
msgid "Main service list"
msgstr "Hoofdlijst diensten:"
msgid "XML file"
msgstr "XML file"
msgid "Use web source"
msgstr "Gebruik web bron"
msgid "Url to *.xml.gz file:"
msgstr "URL van de *.xml.gz file:"
msgid "Enable filtering"
msgstr "Zet filteren aan"
msgid "Filter by presence in the epg.dat file."
msgstr "Filter op aanwezigheid van epg.dat. file"
msgid "Paths to the epg.dat file:"
msgstr "Pad naar epg.dat:"
msgid "Local path:"
msgstr "Lokaal pad:"
msgid "STB path:"
msgstr "STB pad:"
msgid "Update on start"
msgstr "Actualiseer bij start"
msgid "Auto configuration by service names."
msgstr "Auto configuratie door dienst namen."
msgid "Save list to xml."
msgstr "Opslaan als XML."
msgid "Download XML file error."
msgstr "Fout bij downloaden XML."
msgid "Unsupported file type:"
msgstr "Ongesupporteerd archieftype:"
msgid "Unpacking data error."
msgstr "Fout bij uitpakken van de data."
msgid "XML parsing error:"
msgstr "XML parsingfout:"
msgid "Count of successfully configured services:"
msgstr "Aantal succesvol geconfigureerde diensten:"
msgid "Current epg.dat file does not contains references for the services of this bouquet!"
msgstr "Huisige epg.dat bestand heeft geen referenties naar de diensten van dit boeket!"
msgid "Use HTTP"
msgstr "Gebruik HTTP"
msgid "Close playback"
msgstr "Stop afspelen"
msgid "Import YouTube playlist"
msgstr "Importeer YouTube playlist"
msgid "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
msgstr "Een link gevonden naar de YouTube bron!\nProberen een rechtstreekse link naar de video te vonden?"
msgid "Playlist import"
msgstr "Importeer playlist"
msgid "Getting link error:"
msgstr "Volgende Link error gekregen:"

782
po/pl/demon-editor.po Normal file
View File

@@ -0,0 +1,782 @@
# Copyright (C) 2018-2019 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
msgid ""
msgstr ""
"Last-Translator: wwns\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"X-Generator: Poedit 2.3\n"
msgid "translator-credits"
msgstr "wwns"
# Main
msgid "Service"
msgstr "Serwis"
msgid "Package"
msgstr "Pakiet"
msgid "Type"
msgstr "Typ"
msgid "Picon"
msgstr "Pikon"
msgid "Freq"
msgstr "Freq"
msgid "Rate"
msgstr "Rate"
msgid "Pol"
msgstr "Pol"
msgid "System"
msgstr "System"
msgid "Pos"
msgstr "Pos"
msgid "Num"
msgstr "Num"
msgid "Current IP:"
msgstr "Adres IP:"
msgid "Assign"
msgstr "Przypisz"
msgid "Bouquet details"
msgstr "Bukiet szczegóły"
msgid "Bouquets"
msgstr "Bukiety"
msgid "Copy"
msgstr "Kopiuj"
msgid "Copy reference"
msgstr "Kopiuj odniesienie"
msgid "Download"
msgstr "Pobierz"
msgid "Edit"
msgstr "Edytuj"
msgid "Edit mаrker text"
msgstr "Edytuj tekst znacznika"
msgid "FTP-transfer"
msgstr "Transfer FTP"
msgid "Global search"
msgstr "Globalne wyszukiwanie"
msgid "Hide"
msgstr "Ukryj"
msgid "Hide/Skip On/Off Ctrl + H"
msgstr "Ukryj/Pomiń Wł./Wył Ctrl + H"
msgid "Add IPTV or stream service"
msgstr "Dodaj strumień IPTV"
msgid "Import m3u"
msgstr "Importuj m3u"
msgid "Import m3u file"
msgstr "Importuj plik m3u"
msgid "List configuration"
msgstr "Konfiguracja listy"
msgid "Rename for this bouquet"
msgstr "Zmień nazwę tego serwisu"
msgid "Set default name"
msgstr "Ustaw domyślną nazwę"
msgid "Insert marker"
msgstr "Wstaw znacznik"
msgid "Locate in services"
msgstr "Znajdź w usługach"
msgid "Locked"
msgstr "Zablokowany"
msgid "Move"
msgstr "Przenieś"
msgid "New"
msgstr "Nowy"
msgid "New bouquet"
msgstr "Nowy bukiet"
msgid "Create bouquet"
msgstr "Utwórz bukiet"
msgid "For current satellite"
msgstr "Dla bieżącego satelity"
msgid "For current package"
msgstr "Dla bieżącego pakietu"
msgid "For current type"
msgstr "Dla bieżącego typu"
msgid "For each satellite"
msgstr "Dla każdego satelity"
msgid "For each package"
msgstr "Dla każdego pakietu"
msgid "For each type"
msgstr "Dla każdego typu"
msgid "Open"
msgstr "Otwórz"
msgid "Parent lock On/Off Ctrl + L"
msgstr "Blokada rodzicielska On/Off Ctrl + L"
msgid "Picons"
msgstr "Pikony"
msgid "Picons downloader"
msgstr "Pobieranie pikonów"
msgid "Satellites downloader"
msgstr "Pobierania satelitów"
msgid "Remove"
msgstr "Usuń"
msgid "Remove all unavailable"
msgstr "Usuń wszystkie niedostępne"
msgid "Satellites editor"
msgstr "Edytor satelitów"
msgid "Save"
msgstr "Zapisz"
msgid "Search"
msgstr "Szukaj"
msgid "Services"
msgstr "Kanały"
msgid "Services filter"
msgstr "Filtr kanałów"
msgid "Settings"
msgstr "Ustawienia"
msgid "Up"
msgstr "Góra"
msgid "Down"
msgstr "Dół"
msgid "Active profile:"
msgstr "Aktywny Profil:"
msgid "All"
msgstr "Wszystko"
msgid "Are you sure?"
msgstr "Czy na pewno?"
msgid "Current data path:"
msgstr "Aktualna ścieżka danych:"
msgid "Data:"
msgstr "Dane:"
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
msgstr "Edytor kanałów Enigma2 i listy satelitów dla GNU/Linux"
msgid "Host:"
msgstr "Host:"
msgid "Loading data..."
msgstr "Ładowanie danych…."
msgid "Receive"
msgstr "Pobieranie"
msgid "Receive files from receiver"
msgstr "Pobieranie plików z odbiornika"
msgid "Receiver IP:"
msgstr "Odbiornik IP:"
msgid "Remove unused bouquets"
msgstr "Usuń nieużywany bouquet"
msgid "Reset profile"
msgstr "Reset profilu"
msgid "Satellites"
msgstr "Satelity"
msgid "Satellites.xml file:"
msgstr "Plik Satellites.xml:"
msgid "Selected"
msgstr "Wybrany"
msgid "Send"
msgstr "Wyślij"
msgid "Send files to receiver"
msgstr "Wysyłanie plików do odbiornika"
msgid "Services and Bouquets files:"
msgstr "Usługi i bukiety plików:"
msgid "User bouquet files:"
msgstr "Pliki bukietu użytkownika:"
msgid "Extra:"
msgstr "Ekstra:"
# Filter bar
msgid "Only free"
msgstr "Tylko FTA"
msgid "All positions"
msgstr "Wszystkie pozycje"
msgid "All types"
msgstr "Wszystkie typy"
# Streams player
msgid "Play"
msgstr "Odtwarzaj"
msgid "Stop playback"
msgstr "Zatrzymaj odtwarzanie"
msgid "Previous stream in the list"
msgstr "Poprzedni strumień na liście"
msgid "Next stream in the list"
msgstr "Następny strumień na liście"
msgid "Toggle in fullscreen"
msgstr "Przełącz na pełny ekran"
msgid "Close"
msgstr "Zamknij"
# Picons dialog
msgid "Load providers"
msgstr "Załaduj dostawców"
msgid "Providers"
msgstr "Dostawca"
msgid "Receive picons"
msgstr "Pobierz pikony"
msgid "Picons name format:"
msgstr "Format nazw pikon:"
msgid "Resize:"
msgstr "Zmień rozmiar:"
msgid "Current picons path:"
msgstr "Aktualna ścieżka pikon:"
msgid "Receiver picons path:"
msgstr "Ścieżka pikon odbiornika:"
msgid "Picons download tool"
msgstr "Narzędzie pobierania pikon"
msgid "Transfer to receiver"
msgstr "Wyślij do odbiornika"
msgid "Downloader"
msgstr "Pobieranie"
msgid "Converter"
msgstr "Konwerter"
msgid "Convert"
msgstr "Konwertuj"
msgid "Path to save:"
msgstr "Zapisz do:"
msgid "Path to Enigma2 picons:"
msgstr "Ścieżka do pikon Enigma2:"
msgid "Specify the correct position value for the provider!"
msgstr "Podaj poprawną wartość pozycji dla dostawcy!"
msgid "Converter between name formats"
msgstr "Konwerter między formatami nazw"
msgid "Receive picons for providers"
msgstr "Pobierz pikony od nadawcy"
msgid "Load satellite providers."
msgstr "Załaduj dostawców satelitarnych."
msgid ""
"To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window."
msgstr ""
"Aby automatycznie ustawić identyfikatory pikon,\n"
"najpierw załaduj listę wymaganych usług do głównego okna aplikacji."
# Satellites editor
msgid "Satellites edit tool"
msgstr "Narzędzie do edycji satelitów"
msgid "Add"
msgstr "Dodaj"
msgid "Satellite"
msgstr "Satelita"
msgid "Transponder"
msgstr "Transponder"
msgid "Satellite properties:"
msgstr "Właściwości satelity:"
msgid "Transponder properties:"
msgstr "Właściwości transpondera:"
msgid "Name"
msgstr "Nazwa"
msgid "Position"
msgstr "Pozycja"
# Satellites update dialog
msgid "Satellites update"
msgstr "Aktualizacja satelitów"
msgid "Remove selection"
msgstr "Usuń wybrane"
# Service details dialog
msgid "Service data:"
msgstr "Dane usług:"
msgid "Transponder data:"
msgstr "Dane transpondera:"
msgid "Service data"
msgstr "Dane usług"
msgid "Transponder details"
msgstr "Szczegóły transpondera"
msgid ""
"Changes will be applied to all services of this transponder!\n"
"Continue?"
msgstr ""
"Zmiany zostaną zastosowane do wszystkich usług transpondera!\n"
"kontynuować?"
msgid "Reference"
msgstr "Odniesienie"
msgid "Namespace"
msgstr "Namespace"
msgid "Flags:"
msgstr "Flagi:"
msgid "Delays (ms):"
msgstr "Zwłoka (ms):"
msgid "Bitstream"
msgstr "Bitstream"
msgid "Description"
msgstr "Opis"
msgid "Source:"
msgstr "Źródło:"
msgid "Cancel"
msgstr "Anuluj"
msgid "Update"
msgstr "Uaktualnienie"
msgid "Filter"
msgstr "Filtr"
msgid "Find"
msgstr "Znajdź"
# IPTV dialog
msgid "Stream data"
msgstr "Przesyłanie danych"
# IPTV list configuration dialog
msgid "Starting values"
msgstr "Wartości początkowe"
msgid "Reset to default"
msgstr "Ustawienia domyślne"
msgid "IPTV streams list configuration"
msgstr "Konfiguracja listy strumieni IPTV"
# Settings dialog
msgid "Preferences"
msgstr "Preferencje"
msgid "Profile:"
msgstr "Profil:"
msgid "Timeout between commands in seconds"
msgstr "Limit czasu między poleceniami w sekundach"
msgid "Timeout:"
msgstr "Koniec czasu:"
msgid "Login:"
msgstr "Login:"
msgid "Options"
msgstr "Opcje"
msgid "Password:"
msgstr "Hasło:"
msgid "Picons:"
msgstr "Pikony:"
msgid "Port:"
msgstr "Port:"
msgid "Data path:"
msgstr "Ścieżka danych:"
msgid "Picons path:"
msgstr "Ścieżka pikon:"
msgid "Network settings:"
msgstr "Ustawienia sieci:"
msgid "STB file paths:"
msgstr "Ścieżki do plików w STB:"
msgid "Local file paths:"
msgstr "Lokalne ścieżki plików:"
# Dialogs messages
msgid "Error. No bouquet is selected!"
msgstr "Błąd. Nie wybrano żadnego bukietu!"
msgid "This item is not allowed to be removed!"
msgstr "Tego elementu nie można usunąć!"
msgid "This item is not allowed to edit!"
msgstr "Tego elementu nie można edytować!"
msgid "Not allowed in this context!"
msgstr "Niedozwolone w tym kontekście!"
msgid "Please, download files from receiver or setup your path for read data!"
msgstr "Pobierz pliki z odbiornika lub ustaw ścieżkę do odczytu danych!"
msgid "Reading data error!"
msgstr "Błąd odczytu danych!"
msgid "No m3u file is selected!"
msgstr "Nie wybrano pliku m3u!"
msgid "Not implemented yet!"
msgstr "Jeszcze niezaimplementowane!"
msgid "The text of marker is empty, please try again!"
msgstr "Tekst znacznika jest pusty, spróbuj ponownie!"
msgid "Please, select only one item!"
msgstr "Wybierz tylko jeden element!"
msgid "No png file is selected!"
msgstr "Nie wybrano pliku png!"
msgid "No reference is present!"
msgstr "Brak referencji!"
msgid "No selected item!"
msgstr "Brak wybranego elementu!"
msgid "The task is already running!"
msgstr "Zadanie już działa!"
msgid "Done!"
msgstr "Zrobione!"
msgid "Please, wait..."
msgstr "Proszę czekać ..."
msgid "Resizing..."
msgstr "Zmiana rozmiaru..."
msgid "Select paths!"
msgstr "Wybierz ścieżki!"
msgid "No satellite is selected!"
msgstr "Nie wybrano satelity!"
msgid "Please, select only one satellite!"
msgstr "Wybierz tylko jednego satelitę!"
msgid "Please check your parameters and try again."
msgstr "Sprawdź parametry i spróbuj ponownie."
msgid "No satellites.xml file is selected!"
msgstr "Nie wybrano pliku satellites.xml!"
msgid "Error. Verify the data!"
msgstr "Błąd. Zweryfikuj dane!"
msgid "Operation not allowed in this context!"
msgstr "Operacja niedozwolona w tym kontekście!"
msgid "No VLC is found. Check that it is installed!"
msgstr "Nie znaleziono VLC. Sprawdź, czy jest zainstalowany!"
# Search unavailable streams dialog
msgid "Please wait, streams testing in progress..."
msgstr "Proszę czekać, trwa testowanie strumieni..."
msgid "Found"
msgstr "Znaleziono"
msgid "unavailable streams."
msgstr "niedostępne strumienie."
msgid "No changes required!"
msgstr "Nie wymaga zmian!"
msgid "This list does not contains IPTV streams!"
msgstr "Ta lista nie zawiera strumieni IPTV!"
msgid "New empty configuration"
msgstr "Nowa pusta konfiguracja"
msgid "No data to save!"
msgstr "Brak danych do zapisania!"
msgid "Network"
msgstr "Sieć"
msgid "Paths"
msgstr "Ścieżki"
msgid "Program"
msgstr "Program"
msgid "Backup:"
msgstr "Kopia:"
msgid "Backup"
msgstr "Kopia"
msgid "Backups"
msgstr "Kopie zapasowe"
msgid "Backup path:"
msgstr "Ścieżka kopii:"
msgid "Restore bouquets"
msgstr "Przywróć bukiety"
msgid "Restore all"
msgstr "Przywrócić wszystko"
msgid "Before saving"
msgstr "Przed zapisaniem"
msgid "Before downloading from the receiver"
msgstr "Przed pobraniem z odbiornika"
msgid "Set background color for the services"
msgstr "Ustaw kolor tła dla usług"
msgid "Marked as new:"
msgstr "Oznacz jako nowy:"
msgid "With an extra name in the bouquet:"
msgstr "Z dodatkową nazwą w bukiecie:"
msgid "Select"
msgstr "Wybierz"
msgid "About"
msgstr "Wersja"
msgid "Exit"
msgstr "Wyjście"
msgid "Tools"
msgstr "Narzędzia"
# Import
msgid "Import"
msgstr "Importuj"
msgid "Bouquet"
msgstr "Bukiet"
msgid "Bouquets and services"
msgstr "Bukiety i kanały"
msgid "The main list does not contain services for this bouquet!"
msgstr "Główna lista nie zawiera kanałów dla tego bukietu!"
msgid "No bouquet file is selected!"
msgstr "Nie wybrano pliku bukietu!"
msgid "Remove all unused"
msgstr "Usuń wszystkie nieużywane"
msgid "Test"
msgstr "Test"
msgid "Test connection"
msgstr "Testuj połączenie"
msgid "Double click on the service in the bouquet list:"
msgstr "Kliknij dwukrotnie usługę na liście bukietów:"
msgid "Zap"
msgstr "Przełącz"
msgid "Play stream"
msgstr "Odtwórz strumień"
msgid "Disabled"
msgstr "Wyłączone"
msgid "Enable ver. 5 support (experimental)"
msgstr "Włącz wer. 5 wsparcie (eksperymentalne)"
msgid "Enable HTTP API (experimental)"
msgstr "Włącz API HTTP (eksperymentalne)"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Przełącz(zap) kanał(Ctrl + Z)"
msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Przełącz kanał i oglądaj w programie(Ctrl + W)"
msgid "Play IPTV or other stream in the program(Ctrl + P)"
msgstr "Odtwórz IPTV lub inny strumień w programie(Ctrl + P)"
msgid "Export to m3u"
msgstr "Eksportuj do m3u"
msgid "EPG configuration"
msgstr "Koniguruj EPG"
msgid "Apply"
msgstr "Zatwierdź"
msgid "EPG source"
msgstr "Źródło EPG"
msgid "Service names source:"
msgstr "Źródło nazw usług:"
msgid "Main service list"
msgstr "Główna lista usług"
msgid "XML file"
msgstr "Plik XML"
msgid "Use web source"
msgstr "Użyj źródła internetowego"
msgid "Url to *.xml.gz file:"
msgstr "URL do pliku *.xml.gz:"
msgid "Enable filtering"
msgstr "Włącz filtrowanie"
msgid "Filter by presence in the epg.dat file."
msgstr "Filtruj według ustawień w pliku epg.dat."
msgid "Paths to the epg.dat file:"
msgstr "Ścieżka do pliku epg.dat:"
msgid "Local path:"
msgstr "Ścieżka lokalna:"
msgid "STB path:"
msgstr "Ścieżka STB:"
msgid "Update on start"
msgstr "Aktualizuj przy starcie"
msgid "Auto configuration by service names."
msgstr "Automatyczna konfiguracja serwisu według nazw."
msgid "Save list to xml."
msgstr "Zapisz listę do XML."
msgid "Download XML file error."
msgstr "Błąd pobierania pliku XML."
msgid "Unsupported file type:"
msgstr "Nieobsługiwany typ pliku:"
msgid "Unpacking data error."
msgstr "Błąd rozpakowywania danych."
msgid "XML parsing error:"
msgstr "Błąd analizy XML:"
msgid "Count of successfully configured services:"
msgstr "Liczba pomyślnie skonfigurowanych usług:"
msgid "Current epg.dat file does not contains references for the services of this bouquet!"
msgstr "Bieżący plik epg.dat nie zawiera odniesień do usług tego bukietu!"
msgid "Use HTTP"
msgstr "Użyj HTTP"
msgid "Close playback"
msgstr "Zamknij odtwarzanie"
msgid "Import YouTube playlist"
msgstr "Importuj listę odtwarzania YouTube"
msgid ""
"Found a link to the YouTube resource!\n"
"Try to get a direct link to the video?"
msgstr ""
"Znaleziono link do zasobu YouTube!\n"
"Chcesz uzyskać bezpośredni link do filmu?"
msgid "Playlist import"
msgstr "Import listy odtwarzania"
msgid "Getting link error:"
msgstr "Błąd pobierania łącza:"

766
po/pt/demon-editor.po Normal file
View File

@@ -0,0 +1,766 @@
# Copyright (C) 2018-2019 Frank Neirynck
# This file is distributed under the MIT license.
#
#Frank Neirynck <frank@insink.be>, 2018-2019.
#
msgid ""
msgstr ""
"Last-Translator: Frank Neirynck\n"
"Language: pt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "translator-credits"
msgstr "Frank Neirynck <frank@insink.be>"
# Main
msgid "Service"
msgstr "Serviço"
msgid "Package"
msgstr "Pacote"
msgid "Type"
msgstr "Tipo"
msgid "Picon"
msgstr "Picon"
msgid "Freq"
msgstr "Freq."
msgid "Rate"
msgstr "Ratio"
msgid "Pol"
msgstr "Pol."
msgid "System"
msgstr "Sistema"
msgid "Pos"
msgstr "Pos."
msgid "Num"
msgstr "Núm"
msgid "Current IP:"
msgstr "IP atual:"
msgid "Assign"
msgstr "Atribuir"
msgid "Bouquet details"
msgstr "Detalhes do Ramo"
msgid "Bouquets"
msgstr "Ramos"
msgid "Copy"
msgstr "Copiar"
msgid "Copy reference"
msgstr "Copia de Referência"
msgid "Download"
msgstr "Descarregar"
msgid "Edit"
msgstr "Editar"
msgid "Edit mаrker text"
msgstr "Editar texto do mаrcador"
msgid "FTP-transfer"
msgstr "Transferência-FTP"
msgid "Global search"
msgstr "Pesquisa Global"
msgid "Hide"
msgstr "Esconder"
msgid "Hide/Skip On/Off Ctrl + H"
msgstr "Esconder/Pular Acender/Desligar Ctrl + H"
msgid "Add IPTV or stream service"
msgstr "Acrescentar serviço IPTV"
msgid "Import m3u"
msgstr "Importar m3u"
msgid "Import m3u file"
msgstr "Importar ficheiro m3u"
msgid "List configuration"
msgstr "Lista configuração"
msgid "Rename for this bouquet"
msgstr "Renomear para este ramo"
msgid "Set default name"
msgstr "Estabelecer nome predeterminado"
msgid "Insert marker"
msgstr "Inserir marcador"
msgid "Locate in services"
msgstr "Procurar em serviços"
msgid "Locked"
msgstr "Fechado"
msgid "Move"
msgstr "Mover"
msgid "New"
msgstr "Novo"
msgid "New bouquet"
msgstr "Ramo novo"
msgid "Create bouquet"
msgstr "Criar ramo"
msgid "For current satellite"
msgstr "Para o satélite atual"
msgid "For current package"
msgstr "Para o pacote atual"
msgid "For current type"
msgstr "Para o tipo atual"
msgid "For each satellite"
msgstr "Para cada Satélite"
msgid "For each package"
msgstr "Para cada pacote"
msgid "For each type"
msgstr "Para cada tipo"
msgid "Open"
msgstr "Abrir"
msgid "Parent lock On/Off Ctrl + L"
msgstr "Bloqueio parentesco Acender/Desligar Ctrl + L"
msgid "Picons"
msgstr "Picons"
msgid "Picons downloader"
msgstr "Picons descargar"
msgid "Satellites downloader"
msgstr "Satélites descarregar"
msgid "Remove"
msgstr "Remover"
msgid "Remove all unavailable"
msgstr "Remover tudo lo indisponível"
msgid "Satellites editor"
msgstr "Editor Satélites"
msgid "Save"
msgstr "Guardar"
msgid "Search"
msgstr "Procurar"
msgid "Services"
msgstr "Serviços"
msgid "Services filter"
msgstr "Filtro serviços"
msgid "Settings"
msgstr "Configurações"
msgid "Up"
msgstr "Arriba"
msgid "Down"
msgstr "Abaixo"
msgid "Active profile:"
msgstr "Perfil ativo:"
msgid "All"
msgstr "Tudo"
msgid "Are you sure?"
msgstr "Tem certeza?"
msgid "Current data path:"
msgstr "Rota de dados atual:"
msgid "Data:"
msgstr "Dados:"
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
msgstr "Editor de Canais e Satélites Enigma2 para GNU/Linux"
msgid "Host:"
msgstr "Anfitrião:"
msgid "Loading data..."
msgstr "Carregar dados..."
msgid "Receive"
msgstr "Receber"
msgid "Receive files from receiver"
msgstr "Receber ficheiros do seu receptor"
msgid "Receiver IP:"
msgstr "Receptor IP:"
msgid "Remove unused bouquets"
msgstr "Remover ramos sem usar"
msgid "Reset profile"
msgstr "Restabelecer perfil"
msgid "Satellites"
msgstr "Satélites"
msgid "Satellites.xml file:"
msgstr "Ficheiro Satellites.xml:"
msgid "Selected"
msgstr "Selecionado"
msgid "Send"
msgstr "Enviar"
msgid "Send files to receiver"
msgstr "Enviar ficheiros ao recetor"
msgid "Services and Bouquets files:"
msgstr "Ficheiros de Serviços e ramos:"
msgid "User bouquet files:"
msgstr "Importar ficheiros de ramos:"
msgid "Extra:"
msgstr "Extra:"
# Filter bar
msgid "Only free"
msgstr "Somente de graça"
msgid "All positions"
msgstr "Todas as posições"
msgid "All types"
msgstr "Todos os tipos"
# Streams player
msgid "Play"
msgstr "Play"
msgid "Stop playback"
msgstr "Deter a reprodução"
msgid "Previous stream in the list"
msgstr "Sequência anterior na lista"
msgid "Next stream in the list"
msgstr "Sequência siguinte na lista"
msgid "Toggle in fullscreen"
msgstr "Trocar na ecrã completa"
msgid "Close"
msgstr "Fexar"
# Picons dialog
msgid "Load providers"
msgstr "Carregar provedores"
msgid "Providers"
msgstr "Fornecedores"
msgid "Receive picons"
msgstr "Recever picons"
msgid "Picons name format:"
msgstr "Picons formato nomes:"
msgid "Resize:"
msgstr "Redimensionar:"
msgid "Current picons path:"
msgstr "Rota atual Picons:"
msgid "Receiver picons path:"
msgstr "Rota picons recetor:"
msgid "Picons download tool"
msgstr "Picons ferramenta de descarga"
msgid "Transfer to receiver"
msgstr "Transferir ao recetor"
msgid "Downloader"
msgstr "Descarregador"
msgid "Converter"
msgstr "Conversor"
msgid "Convert"
msgstr "Converter"
msgid "Path to save:"
msgstr "Rota para guardar:"
msgid "Path to Enigma2 picons:"
msgstr "Rota a picons Enigma2:"
msgid "Specify the correct position value for the provider!"
msgstr "Especifique a posição correta para o provedor!"
msgid "Converter between name formats"
msgstr "Conversor entre formatos de nomes"
msgid "Receive picons for providers"
msgstr "Recever picons para provedor"
msgid "Load satellite providers."
msgstr "Carregar provedores Satélite."
msgid "To automatically set the identifiers for picons,\nfirst load the required services list into the main application window."
msgstr "Para configurar automaticamente os identificadores para picons, \nprimeiro carregue a lista de serviços necessários na janela principal."
# Satellites editor
msgid "Satellites edit tool"
msgstr "Editor de Satélites"
msgid "Add"
msgstr "Adicionar"
msgid "Satellite"
msgstr "Satélite"
msgid "Transponder"
msgstr "Transponder"
msgid "Satellite properties:"
msgstr "Propriedades do Satélite:"
msgid "Transponder properties:"
msgstr "Propriedades do Transponder:"
msgid "Name"
msgstr "Nome"
msgid "Position"
msgstr "Posição"
# Satellites update dialog
msgid "Satellites update"
msgstr "Atualisar Satélite"
msgid "Remove selection"
msgstr "Remover seleçáo"
# Service details dialog
msgid "Service data:"
msgstr "Dados serviço:"
msgid "Transponder data:"
msgstr "Dados Transponder:"
msgid "Service data"
msgstr "Dados serviço"
msgid "Transponder details"
msgstr "Detalhes Transponder"
msgid "Changes will be applied to all services of this transponder!\nContinue?"
msgstr "Os mudanças serão aplicadas a todos os serviços deste transponder!\nContinuar?"
msgid "Reference"
msgstr "Referência"
msgid "Namespace"
msgstr "Namespace"
msgid "Flags:"
msgstr "Flags:"
msgid "Delays (ms):"
msgstr "Atraso (mc)"
msgid "Bitstream"
msgstr "Sequência de Bits"
msgid "Description"
msgstr "Descrição"
msgid "Source:"
msgstr "Fonte:"
msgid "Cancel"
msgstr "Annular"
msgid "Update"
msgstr "Atualizar"
msgid "Filter"
msgstr "Filtrar"
msgid "Find"
msgstr "Procurar"
# IPTV dialog
msgid "Stream data"
msgstr "Dados da sequência"
# IPTV list configuration dialog
msgid "Starting values"
msgstr "Valores iniciais"
msgid "Reset to default"
msgstr "Restabelecer a predeterminado"
msgid "IPTV streams list configuration"
msgstr "Configurar lista de sequências IPTV"
#Settings dialog
msgid "Preferences"
msgstr "Preferências"
msgid "Profile:"
msgstr "Perfil:"
msgid "Timeout between commands in seconds"
msgstr "Tempo de espera entre comandos em segundos"
msgid "Timeout:"
msgstr "Time-out:"
msgid "Login:"
msgstr "Usuário:"
msgid "Options"
msgstr "Opções"
msgid "Password:"
msgstr "Palavra-passe:"
msgid "Picons:"
msgstr "Picons:"
msgid "Port:"
msgstr "Porta:"
msgid "Data path:"
msgstr "Rota de dados:"
msgid "Picons path:"
msgstr "Rota de Picons:"
msgid "Network settings:"
msgstr "Configuração de rede:"
msgid "STB file paths:"
msgstr "Rota de ficheirors STB:"
msgid "Local file paths:"
msgstr "Rota de ficheiros local:"
# Dialogs messages
msgid "Error. No bouquet is selected!"
msgstr "Erro. Nenhum ramo está selecionado!"
msgid "This item is not allowed to be removed!"
msgstr "Este artigo não pode ser eliminado!"
msgid "This item is not allowed to edit!"
msgstr "Este artigo não pode ser editado!"
msgid "Not allowed in this context!"
msgstr "Náo permitido neste contexto!"
msgid "Please, download files from receiver or setup your path for read data!"
msgstr "Por favor, descarregue arquivos desde o recetor ou configure a sua rota para ler os dados!"
msgid "Reading data error!"
msgstr "Erro de leitura de dados!"
msgid "No m3u file is selected!"
msgstr "Nenhum ficheiro m3u foi selecionado!"
msgid "Not implemented yet!"
msgstr "Ainda não implementado!"
msgid "The text of marker is empty, please try again!"
msgstr "O texto do marcador está vazio, tente de novo!"
msgid "Please, select only one item!"
msgstr "Por favor, selecione só um elemento!"
msgid "No png file is selected!"
msgstr "Nenhum ficheiro png foi selecionado!"
msgid "No reference is present!"
msgstr "Nenhuma referência presente!"
msgid "No selected item!"
msgstr "Nenhum elemento selecionado!"
msgid "The task is already running!"
msgstr "A tarefa ya está en execuçáo!"
msgid "Done!"
msgstr "Feito!"
msgid "Please, wait..."
msgstr "Por favor, espere..."
msgid "Resizing..."
msgstr "A redimensionar..."
msgid "Select paths!"
msgstr "Selecione as rotas!"
msgid "No satellite is selected!"
msgstr "Nemhun Satélite selecionado!"
msgid "Please, select only one satellite!"
msgstr "Selecione só um Satélite!"
msgid "Please check your parameters and try again."
msgstr "Por favor rever seus parâmetros e volte a tentar!"
msgid "No satellites.xml file is selected!"
msgstr "Nemhun satellites.xml selecionado!"
msgid "Error. Verify the data!"
msgstr "Erro. Rever os dados!"
msgid "Operation not allowed in this context!"
msgstr "Operação não permitida neste contexto!"
msgid "No VLC is found. Check that it is installed!"
msgstr "VLC não achado. Verifique se está instalado!"
# Search unavailable streams dialog
msgid "Please wait, streams testing in progress..."
msgstr "Por favor aguarde uma prova das sequências..."
msgid "Found"
msgstr "Achado"
msgid "unavailable streams."
msgstr "Sequências não presentes"
msgid "No changes required!"
msgstr "Nemhuma mudança requerida!"
msgid "This list does not contains IPTV streams!"
msgstr "A lista não tem sequências IPTV!"
msgid "New empty configuration"
msgstr "Nova configuração vazia"
msgid "No data to save!"
msgstr "Não há dados para guardar!"
msgid "Network"
msgstr "Rede"
msgid "Paths"
msgstr "Rotas"
msgid "Program"
msgstr "Programa"
msgid "Backup:"
msgstr "Backup:"
msgid "Backup"
msgstr "Backup"
msgid "Backups"
msgstr "Backups"
msgid "Backup path:"
msgstr "Rota do backup:"
msgid "Restore bouquets"
msgstr "Restaurar ramos"
msgid "Restore all"
msgstr "Restaurar tudo"
msgid "Before saving"
msgstr "Antes de guardar"
msgid "Before downloading from the receiver"
msgstr "Antes de recever do recetor"
msgid "Set background color for the services"
msgstr "Determinar a cor de fundo para serviços"
msgid "Marked as new:"
msgstr "Marcado como novo:"
msgid "With an extra name in the bouquet:"
msgstr "Com nome adicional em ramo:"
msgid "Select"
msgstr "Selecione"
msgid "About"
msgstr "Acerca"
msgid "Exit"
msgstr "Sair"
msgid "Tools"
msgstr "Tools"
#Import
msgid "Import"
msgstr "Importar"
msgid "Bouquet"
msgstr "Ramo"
msgid "Bouquets and services"
msgstr "Ramos e serviços"
msgid "The main list does not contain services for this bouquet!"
msgstr "A lista pricipal no tem serviços em esta ramo!"
msgid "No bouquet file is selected!"
msgstr "Nemhuma ficheiro de ramo foi selecionado!"
msgid "Remove all unused"
msgstr "Remova todos os não utilizados"
msgid "Test"
msgstr "Test"
msgid "Test connection"
msgstr "Testar a conexão"
msgid "Double click on the service in the bouquet list:"
msgstr "Clique duas vezes no serviço na lista de ramos:"
msgid "Zap"
msgstr "Zap"
msgid "Play stream"
msgstr "Play stream"
msgid "Disabled"
msgstr "Desativado"
msgid "Enable ver. 5 support (experimental)"
msgstr "Ativar ver. 5 suporte (experimental)"
msgid "Enable HTTP API (experimental)"
msgstr "Ativar HTTP API (experimental)"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Mudar(zap) o canal(Ctrl + Z)"
msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Troque o canal e ver no programa(Ctrl + W)."
msgid "Play IPTV or other stream in the program(Ctrl + P)"
msgstr "Tocar IPTV ou outro fluxo no programa(Ctrl + P)"
msgid "Export to m3u"
msgstr "Exportar na m3u"
msgid "EPG configuration"
msgstr "Configuraçao EPG"
msgid "Apply"
msgstr "Aplicar"
msgid "EPG source"
msgstr "Fonte EPG"
msgid "Service names source:"
msgstr "Fonte de nomes de serviço:"
msgid "Main service list"
msgstr "Lista de serviço principal:"
msgid "XML file"
msgstr "Arquivo XML"
msgid "Use web source"
msgstr "Usar fonte web"
msgid "Url to *.xml.gz file:"
msgstr "Url para o arquivo *.xml.gz:"
msgid "Enable filtering"
msgstr "Ativar filtragem"
msgid "Filter by presence in the epg.dat file."
msgstr "Filtrar por presença no arquivo epg.dat."
msgid "Paths to the epg.dat file:"
msgstr "Ruta para o arquivo epg.dat:"
msgid "Local path:"
msgstr "Ruta local:"
msgid "STB path:"
msgstr "Ruta STB:"
msgid "Update on start"
msgstr "Atualizar no início"
msgid "Auto configuration by service names."
msgstr "Configuração automática por nomes de serviço."
msgid "Save list to xml."
msgstr "Salvar lista para XML."
msgid "Download XML file error."
msgstr "Baixe o erro de arquivo XML."
msgid "Unsupported file type:"
msgstr "Tipo de arquivo não suportado:"
msgid "Unpacking data error."
msgstr "Descompactando o erro de dados."
msgid "XML parsing error:"
msgstr "Erro de análise XML:"
msgid "Count of successfully configured services:"
msgstr "Contagem de serviços configurados com sucesso:"
msgid "Current epg.dat file does not contains references for the services of this bouquet!"
msgstr "O arquivo epg.dat não contém referências para os serviços deste buquê!"
msgid "Use HTTP"
msgstr "Use HTTP"
msgid "Close playback"
msgstr "Fechar reprodução"
msgid "Import YouTube playlist"
msgstr "Importar YouTube playlist"
msgid "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
msgstr "Encontrou um link para o recurso do YouTube!\nTentar obter um link direto para o vídeo?"
msgid "Playlist import"
msgstr "Importação de lista de reprodução"
msgid "Getting link error:"
msgstr "Obtendo erro de link:"

View File

@@ -1,10 +1,10 @@
# Copyright (C) 2018 Dmitriy Yefremov
# Copyright (C) 2018-2019 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
msgid ""
msgstr ""
"Last-Translator: Dmitry Yefremov\n"
"Last-Translator: Dmitriy Yefremov\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -68,9 +68,6 @@ msgstr "Загрузить"
msgid "Edit"
msgstr "Изменить"
msgid "Edit "
msgstr "Изменить"
msgid "Edit mаrker text"
msgstr "Изменить текст маркера"
@@ -571,16 +568,237 @@ msgstr "Текущий список не содержит потоков IPTV!"
msgid "New empty configuration"
msgstr "Новая конфигурация"
msgid "No data to save!"
msgstr "Нет данных для сохранения!"
msgid "Network"
msgstr "Сеть"
msgid "Paths"
msgstr "Пути"
msgid "Program"
msgstr "Программа"
msgid "Backup:"
msgstr "Резервное копирование:"
msgid "Backup"
msgstr "Резервное копирование"
msgid "Backups"
msgstr "Резервные копии"
msgid "Backup path:"
msgstr "Путь к резервным копиям:"
msgid "Restore bouquets"
msgstr "Восстановить букеты"
msgid "Restore all"
msgstr "Восстановить все"
msgid "Before saving"
msgstr "Перед сохранением"
msgid "Before downloading from the receiver"
msgstr "Перед загрузкой с ресивера"
msgid "Set background color for the services"
msgstr "Установить цвет фона для сервисов"
msgid "Marked as new:"
msgstr "Помеченные как новые:"
msgid "With an extra name in the bouquet:"
msgstr "С пользовательским именем в букете:"
msgid "Select"
msgstr "Выбрать"
msgid "About"
msgstr "О программе"
msgid "Exit"
msgstr "Выход"
msgid "Tools"
msgstr "Инструменты"
#Import
msgid "Import"
msgstr "Импорт"
msgid "Bouquet"
msgstr "Букета"
msgid "Bouquets and services"
msgstr "Букетов и сервисов"
msgid "The main list does not contain services for this bouquet!"
msgstr "Основной список не содержит сервисов для данного букета!"
msgid "No bouquet file is selected!"
msgstr "Не выбран файл букета!"
msgid "Remove all unused"
msgstr "Удалить все неиспользуемые"
msgid "Test"
msgstr "Тестировать"
msgid "Test connection"
msgstr "Тестировать соединение"
msgid "Double click on the service in the bouquet list:"
msgstr "Двойной клик по сервису в списке букетов:"
msgid "Zap"
msgstr "Переключить"
msgid "Play stream"
msgstr "Воспр. потока"
msgid "Disabled"
msgstr "Выкл."
msgid "Enable ver. 5 support (experimental)"
msgstr "Включить поддержку вер. 5 (экспериментально)"
msgid "Enable HTTP API (experimental)"
msgstr "Включить HTTP API (экспериментально)"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Переключить канал(Ctrl + Z)"
msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Переклють канал и просмотр в программе(Ctrl + W)."
msgid "Play IPTV or other stream in the program(Ctrl + P)"
msgstr "Воспроизведение IPTV или другого потока в программе(Ctrl + P)"
msgid "Export to m3u"
msgstr "Экспорт в m3u"
msgid "EPG configuration"
msgstr "Конфигурация EPG"
msgid "Apply"
msgstr "Применить"
msgid "EPG source"
msgstr "Источник EPG"
msgid "Service names source:"
msgstr "Источник имен сервисов:"
msgid "Main service list"
msgstr "Основной список сервисов:"
msgid "XML file"
msgstr "Файл XML"
msgid "Use web source"
msgstr "Использовать веб-источник"
msgid "Url to *.xml.gz file:"
msgstr "URL к файлу *.xml.gz:"
msgid "Enable filtering"
msgstr "Включить фильтрацию"
msgid "Filter by presence in the epg.dat file."
msgstr "Фильтровать по наличию в файле epg.dat."
msgid "Paths to the epg.dat file:"
msgstr "Пути к файлу epg.dat:"
msgid "Local path:"
msgstr "Локальный путь:"
msgid "STB path:"
msgstr "Путь в ресивере:"
msgid "Update on start"
msgstr "Обновлять при запуске"
msgid "Auto configuration by service names."
msgstr "Автонастройка по именам сервисов."
msgid "Save list to xml."
msgstr "Сохранить список в XML."
msgid "Download XML file error."
msgstr "Ошибка загрузки XML-файла."
msgid "Unsupported file type:"
msgstr "Неподдерживаемый тип файла:"
msgid "Unpacking data error."
msgstr "Ошибка распаковки данных."
msgid "XML parsing error:"
msgstr "Ошибка парсинга XML:"
msgid "Count of successfully configured services:"
msgstr "Количество успешно сконфигурированных сервисов:"
msgid "Current epg.dat file does not contains references for the services of this bouquet!"
msgstr "Текущий файл epg.dat не содержит ссылок на сервисы данного букета!"
msgid "Use HTTP"
msgstr "Использовать HTTP"
msgid "Close playback"
msgstr "Закрыть воспроизведение"
msgid "Import YouTube playlist"
msgstr "Импорт плейлиста YouTube"
msgid "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
msgstr "Найдена ссылка на ресурс YouTube!\nПопробовать получить прямую ссылку на видео?"
msgid "Playlist import"
msgstr "Импорт плейлиста"
msgid "Getting link error:"
msgstr "Ошибка получения ссылки:"
msgid "Extra"
msgstr "Дополнительно"
msgid "Apply profile settings"
msgstr "Применить настройки профиля"
msgid "Settings type:"
msgstr "Тип настроек:"
msgid "Set default"
msgstr "Установить по умолчанию"
msgid "Language:"
msgstr "Язык:"
msgid "Load the last open configuration at program startup"
msgstr "Загружать последнюю открытую конфигурацию при запуске программы"
msgid "Enable direct playback bar (experimental)"
msgstr "Включить панель прямого воспроизведения (экспериментально)"
msgid "Enables direct sending and playback of media links on the receiver"
msgstr "Включает прямую отправку и воспроизведение медиа-ссылок на ресивере"
msgid "Watch the channel in the program"
msgstr "Просмотр канала в программе"
msgid "Zap and Play"
msgstr "Перекл. и просмотр"
msgid "Drag or paste the link here"
msgstr "Перетащите или вставьте ссылку здесь"
msgid "Remove added links in the playlist"
msgstr "Удалить добавленные ссылки из плейлиста"
msgid "A bouquet with that name exists!"
msgstr "Букет с таким именем существует!"

View File

@@ -1,4 +1,29 @@
#!/usr/bin/env python3
from app.ui.main_app_window import start_app
import os
start_app()
def update_icon():
need_update = False
icon_name = "DemonEditor.desktop"
with open(icon_name, "r") as f:
lines = f.readlines()
for i, line in enumerate(lines):
if line.startswith("Icon="):
icon_path = line.lstrip("Icon=")
current_path = "{}/app/ui/icons/hicolor/96x96/apps/demon-editor.png".format(os.getcwd())
if icon_path != current_path:
need_update = True
lines[i] = "Icon={}\n".format(current_path)
break
if need_update:
with open(icon_name, "w") as f:
f.writelines(lines)
if __name__ == "__main__":
from app.ui.main_app_window import start_app
update_icon()
start_app()