Compare commits

..

406 Commits

Author SHA1 Message Date
DYefremov
a34798f215 some gui corrections 2021-04-07 23:24:52 +03:00
DYefremov
d6738826d3 playback adaptation 2021-04-07 12:41:33 +03:00
DYefremov
a437ec6030 copy de *.mo file 2021-04-06 23:06:45 +03:00
Thomas Schmidt
9cc8605994 Update german translation 2021-04-06 23:06:38 +03:00
DYefremov
a972ee353f minor fixes 2021-04-06 23:06:10 +03:00
DYefremov
be2d64e480 sid value fix for some picons 2021-04-03 22:16:02 +03:00
DYefremov
76e732c8a0 fixed width for provider name 2021-04-03 22:15:58 +03:00
DYefremov
e81e13a5c0 minor fixes 2021-04-03 00:08:56 +03:00
DYefremov
5b12777223 Merge branch 'development-mac' into experimental-win
# Conflicts:
#	DemonEditor.spec
#	app/tools/media.py
#	app/ui/dialogs.glade
#	app/ui/main_app_window.py
#	app/ui/main_window.glade
#	app/ui/satellites_dialog.glade
#	app/ui/settings_dialog.glade
#	app/ui/settings_dialog.py
#	app/ui/uicommons.py
2021-04-02 21:51:05 +03:00
DYefremov
e0c953ee05 fixed picons download from the web 2021-04-01 23:00:18 +03:00
DYefremov
3dc4caf65d added support for ATSC services 2021-04-01 22:50:49 +03:00
DYefremov
8308b715dd bump version 2021-04-01 22:49:31 +03:00
DYefremov
3a8831f0f9 minor style fix for buttons 2021-03-27 17:43:35 +03:00
DYefremov
a020a23211 Russian, Belarusian and German translations update 2021-03-27 16:16:54 +03:00
DYefremov
a67c81235c added multiple choice of pos and type to filter feature 2021-03-27 16:15:44 +03:00
DYefremov
3ef587841e added base support for mpv 2021-03-23 22:20:49 +03:00
DYefremov
55a21fbc18 some corrections for playback mode 2021-03-16 00:26:28 +03:00
DYefremov
b60a9a69b6 minor fix for epg 2021-03-14 20:15:34 +03:00
DYefremov
8b517f6f88 added logo for the tooltip in the picon explorer 2021-03-14 13:21:32 +03:00
DYefremov
81dd12a038 minor icon fix 2021-03-13 10:39:42 +03:00
DYefremov
17de78f169 fixed picons download for providers 2021-03-12 16:47:11 +03:00
DYefremov
3411f32868 added display bouquet list in playback mode 2021-03-12 16:46:47 +03:00
DYefremov
5f669f4480 added new appearance options [list font, picons size] 2021-03-12 11:47:51 +03:00
DYefremov
f56e4b616a reworking of built-in player [GStreamer support] 2021-03-12 00:19:26 +03:00
DYefremov
653ef1422f some minor fixes 2021-03-06 14:32:33 +03:00
DYefremov
9d5e07af1f bump version 2021-03-02 15:18:13 +03:00
DYefremov
399c1ff01b copy tr *.mo file 2021-03-02 15:15:42 +03:00
audi06_19
ad8e6975b1 Turkish translations update (#45) 2021-03-02 15:15:26 +03:00
DYefremov
5f79b27daa streams playback improvements 2021-03-01 13:07:58 +03:00
DYefremov
3b23ddc1a7 setting encoding for file opening 2021-02-28 21:22:22 +03:00
DYefremov
a0e3566bec enabled language selection 2021-02-28 17:05:28 +03:00
DYefremov
1cada0408f appending providers fix 2021-02-26 13:11:19 +03:00
DYefremov
e1a5b8e39d reworking of built-in player [GStreamer support] 2021-02-26 12:36:19 +03:00
DYefremov
a46c6ae816 keyboard codes adaptation 2021-02-26 08:50:36 +03:00
DYefremov
33137ee879 basic adaptation of the code and gui 2021-02-25 11:45:29 +03:00
DYefremov
51181057b1 adaptation to Python 3.4 2021-02-23 11:10:06 +03:00
DYefremov
ca1e823bf1 improved satellites import [added KingOfSat support] 2021-02-21 08:44:33 +03:00
DYefremov
7fb2d9ac4a minor fix 2021-02-21 08:24:12 +03:00
DYefremov
f5a02ddf1d added info message after m3u import finished 2021-02-14 14:44:12 +03:00
DYefremov
1266f8e04b added custom sort function for position column 2021-02-14 14:39:27 +03:00
DYefremov
2dcee99981 preventing tooltips for an inactive window 2021-02-13 12:31:58 +03:00
DYefremov
7053628e56 fixed import of satellites from the web 2021-02-12 10:30:18 +03:00
DYefremov
b8e1f0e7fd refactoring of picons downloading 2021-02-10 23:46:34 +03:00
DYefremov
954f1c514a alt service timer delete/edit fix 2021-02-09 20:23:57 +03:00
DYefremov
60e1f6c467 Russian, Belarusian and German translations update 2021-02-08 22:31:43 +03:00
DYefremov
986f10c640 minor fix 2021-02-08 22:29:37 +03:00
DYefremov
4c95972381 streams play mode refactoring 2021-02-08 22:28:01 +03:00
DYefremov
052dd3efbe improved built-in player [added windowed mode] 2021-02-08 22:23:28 +03:00
DYefremov
4e867b6f22 added picons downloading to * .m3u import 2021-02-08 22:02:55 +03:00
DYefremov
c11278041e telnet login fix 2021-02-05 11:12:46 +03:00
DYefremov
b89df3d65d services web import fix 2021-02-03 19:11:54 +03:00
DYefremov
d252c69628 improved *.m3u import 2021-02-03 19:11:32 +03:00
DYefremov
6785e46745 added saving of bouquet file names 2021-02-03 10:26:05 +03:00
DYefremov
bbffeaa30e added epg display from the alternatives list 2021-01-21 19:16:22 +03:00
DYefremov
ce11723d34 improved service details editing [neutrino] 2021-01-21 19:10:08 +03:00
DYefremov
2cdefdca42 adaptation to the new format 2021-01-21 19:06:10 +03:00
DYefremov
52b2bb28b4 changed format for freq, sr and pol columns 2021-01-21 19:05:58 +03:00
DYefremov
672586e227 lamedb parsing refactoring 2021-01-21 19:05:41 +03:00
DYefremov
eaff4eec6c changed names for new config 2021-01-21 19:05:32 +03:00
DYefremov
f068696aad fix first bouquet name 2021-01-21 19:05:13 +03:00
DYefremov
f8eddd8710 fix drag icon in filter mode 2021-01-15 09:40:35 +03:00
DYefremov
a5206c89ef copyright update 2021-01-15 09:40:22 +03:00
DYefremov
6555c3c882 bump version 2021-01-15 09:38:30 +03:00
DYefremov
155ed02f11 Russian, Belarusian and German translations update 2021-01-12 11:56:02 +03:00
DYefremov
a1ce729ce2 changed alternatives naming 2021-01-12 11:24:35 +03:00
DYefremov
33ffccf57a added 8739 stream type 2021-01-11 15:32:55 +03:00
DYefremov
b9881fc345 naming alternatives fix 2021-01-10 22:47:06 +03:00
DYefremov
35ce913ab0 added moving alternatives in the list 2021-01-10 22:32:52 +03:00
DYefremov
29e1cb10a3 added editing of alternatives 2021-01-09 00:27:37 +03:00
DYefremov
558843c728 set extra tools visible by default 2021-01-08 13:17:16 +03:00
DYefremov
c3534052ae removed bq position option 2021-01-07 11:10:12 +03:00
DYefremov
1113fec26e added separate column for picon to fav list 2021-01-07 10:51:41 +03:00
DYefremov
2f55fb4e64 added basic support for alternatives 2021-01-06 01:54:10 +03:00
DYefremov
412a66e5e5 added switching position of fav list on the fly 2021-01-06 01:29:51 +03:00
DYefremov
676bc14f73 some streams detection fix 2021-01-02 18:10:58 +03:00
DYefremov
ec6ebb2a0e redesigned network settings 2020-12-30 23:38:42 +03:00
DYefremov
f8710a4bf0 bump version 2020-12-30 22:42:08 +03:00
DYefremov
b48f638495 _config.yml update 2020-12-28 00:19:35 +03:00
DYefremov
fd0559d76e README update 2020-12-28 00:19:03 +03:00
DYefremov
6c6948ce23 added new order to alternate layout 2020-12-25 09:38:11 +03:00
DYefremov
573d755e31 added display option for bouquet details list 2020-12-24 23:31:37 +03:00
DYefremov
912083f203 README update 2020-12-23 09:39:08 +03:00
DYefremov
4df0553333 Russian, Belarusian and German translations update 2020-12-23 09:38:27 +03:00
DYefremov
f0d535ba4e yt fix 2020-12-23 09:38:12 +03:00
DYefremov
88ef5563cf ftp client improvements 2020-12-22 14:31:38 +03:00
DYefremov
6431f2ccd8 minor fix 2020-12-22 14:31:25 +03:00
DYefremov
ca9b4a780d storing app window size on close 2020-12-19 12:38:40 +03:00
DYefremov
f74eead20b added alternate layout support 2020-12-18 22:18:13 +03:00
DYefremov
8d4d90fd9f added alternate layout option 2020-12-18 22:16:03 +03:00
DYefremov
4269d16d31 added ftp client to main window 2020-12-17 14:00:14 +03:00
DYefremov
9cf3e97bd3 added ftp client base class 2020-12-17 10:27:01 +03:00
DYefremov
3b85d35b62 added keyboard key 2020-12-17 10:25:30 +03:00
DYefremov
6bddd89206 ftp refactoring [func extension] 2020-12-14 15:12:28 +03:00
DYefremov
e16f2cba82 allowed to add dir during path config 2020-12-14 15:11:44 +03:00
DYefremov
59748aa9ba added encoding detection for *.m3u import 2020-12-07 09:32:00 +03:00
DYefremov
caf4925409 data load rework (#37 fix [can't decode byte]) 2020-12-05 14:13:29 +03:00
DYefremov
2ab540ccfa bump version 2020-12-05 14:12:04 +03:00
DYefremov
4b762802da added playlist extraction via youtube-dl 2020-12-05 14:10:09 +03:00
DYefremov
41a6e54e90 upd. README 2020-12-02 01:06:50 +03:00
DYefremov
7db02f2a9e added types to the service parser 2020-11-28 14:25:08 +03:00
DYefremov
fd40fd8d72 changes for popup menu actions 2020-11-27 21:27:23 +03:00
DYefremov
0b4313e4cf minor changes in control panel gui 2020-11-27 21:25:00 +03:00
DYefremov
a74628ed5c added sorting reset when loading data 2020-11-26 13:54:00 +03:00
DYefremov
443f6bf252 translations correction for app description 2020-11-26 13:53:49 +03:00
DYefremov
bb243ce281 added timeouts for telnet login 2020-11-26 13:53:20 +03:00
DYefremov
44049c380e config.yml update 2020-11-26 13:53:03 +03:00
DYefremov
281fe2a8f4 Russian, Belarusian and German translations update 2020-11-26 13:52:21 +03:00
DYefremov
39cc0ad8b3 some changes in the control panel 2020-11-26 13:51:46 +03:00
DYefremov
a625dc9f8b style correction 2020-11-26 13:51:29 +03:00
DYefremov
53f69b8f67 Russian, Belarusian and German translations update 2020-11-26 13:51:13 +03:00
DYefremov
94dfda0fa2 added basic support for timers via http api 2020-11-26 13:40:36 +03:00
DYefremov
cfe3f4c707 added styles 2020-11-26 13:39:36 +03:00
DYefremov
d18734910d small http api refactoring 2020-11-26 13:36:45 +03:00
DYefremov
d843633043 added epg display in control panel 2020-11-26 13:28:16 +03:00
DYefremov
b513d7a9b0 added prototype of simple control panel (#38) 2020-11-26 13:09:55 +03:00
DYefremov
92b2f840f8 fix update of sat positions after web import 2020-11-25 22:18:22 +03:00
DYefremov
9e4c8f388c added transponder view popup menu 2020-11-25 22:17:04 +03:00
DYefremov
664c52cfe1 minor compatibility fix 2020-11-25 22:15:27 +03:00
DYefremov
cc96cdd8fd added web import for services (#16) 2020-11-25 22:09:13 +03:00
DYefremov
15cca3f5f7 small decoupling for lamedb parsing 2020-11-25 21:27:22 +03:00
DYefremov
0ec2570043 added remote control requests 2020-11-25 21:26:51 +03:00
DYefremov
97cb26cd60 bump version 2020-11-25 21:26:36 +03:00
DYefremov
1da3eacc8c minor fix for the picon parser 2020-10-10 15:21:47 +03:00
DYefremov
cb6f185032 minor fix for yt 2020-10-06 11:27:09 +03:00
DYefremov
a8918bcf1f Russian, Belarusian and German translations update 2020-10-05 22:17:20 +03:00
DYefremov
c358197080 data loading refactoring 2020-10-05 22:16:54 +03:00
DYefremov
474fc3ec58 adaptation of dnd 2020-10-05 22:15:01 +03:00
DYefremov
c17bad215f changed getting of the drag icon 2020-09-30 20:58:30 +03:00
DYefremov
7891aca6e2 upd README 2020-09-28 13:48:04 +03:00
DYefremov
608de65897 Russian, Belarusian and German translations update 2020-09-28 13:46:08 +03:00
DYefremov
cbed3f7cca reworked and improved dnd for lists 2020-09-28 13:45:01 +03:00
DYefremov
08c1dca06d added support for loading and importing data via dnd 2020-09-28 13:30:12 +03:00
DYefremov
1edbd7d771 version update 2020-09-28 13:25:10 +03:00
DYefremov
0c3f6870dd added support for opening archives 2020-09-28 13:22:19 +03:00
DYefremov
f877872059 minor rework of the chooser dialog 2020-09-28 12:59:38 +03:00
DYefremov
335dfc005a displaying sid value in uppercase for tooltips(#34) 2020-09-12 22:52:18 +03:00
DYefremov
46450cf9b6 Display the sid value in tooltips in hex and dec format(#34). 2020-09-12 22:52:07 +03:00
Víctor Pont
9ed82ea129 Spanish translation update (#36) 2020-09-11 16:38:01 +03:00
DYefremov
555699c2a1 renaming bouquet fix [losing custom names](#33) 2020-09-08 12:45:32 +03:00
DYefremov
83b810286a upd README 2020-09-06 13:30:08 +03:00
DYefremov
61a56f1989 copy pl *.mo file 2020-09-02 23:04:08 +03:00
Wieslaw Weglowski
50ce4a688a Polish translation corrections (#31) 2020-09-02 22:53:47 +03:00
DYefremov
871b428b19 Russian, Belarusian and German translations update 2020-09-02 22:53:22 +03:00
DYefremov
3cd864cd84 version update 2020-08-31 22:27:45 +03:00
DYefremov
78c6a3c9fa minor fix for picon assignment 2020-08-31 22:27:20 +03:00
DYefremov
4c0904cf6c added Belarusian translation 2020-08-31 12:24:33 +03:00
DYefremov
7aa688df15 fix playback from the start screen 2020-08-31 12:20:37 +03:00
DYefremov
c91d58e0cf minor changes in sat dialogs 2020-08-27 23:21:19 +03:00
DYefremov
d071bb5d85 telnet password visibility fix 2020-08-24 22:44:30 +03:00
DYefremov
8cee77357c Polish translation update 2020-08-24 22:22:25 +03:00
DYefremov
20f53dee33 minor optimization 2020-08-24 22:22:09 +03:00
Wieslaw Weglowski
c454a33b3c Update demon-editor.po (#30) 2020-08-24 22:21:56 +03:00
DYefremov
5642b8871c upd README 2020-08-19 21:24:34 +03:00
DYefremov
e7480ec622 rework of the picons resizing 2020-08-19 21:20:07 +03:00
DYefremov
ecce001ce4 German and Russian translation update 2020-08-15 16:55:55 +03:00
DYefremov
7bae895458 minor yt fix 2020-08-15 16:55:40 +03:00
DYefremov
5b3bd48746 output fix for picons downloader 2020-08-09 00:14:58 +03:00
DYefremov
4769a814bd update *.spec 2020-08-09 00:05:46 +03:00
DYefremov
b08d4ed7d7 added dark mode support 2020-08-09 00:05:04 +03:00
DYefremov
233eb6bc53 added dark mode option 2020-08-08 14:47:57 +03:00
DYefremov
8b3d24c006 small changes of settings dialog appearance 2020-08-07 15:11:32 +03:00
DYefremov
48184c1fd9 minor fixes 2020-08-07 11:37:59 +03:00
DYefremov
46d91b93bc skipping enigma2 stop during picons upload 2020-08-06 21:19:47 +03:00
DYefremov
69989e784d minor correction of translations 2020-08-04 12:52:01 +03:00
DYefremov
ce1c978222 version update 2020-08-03 22:50:22 +03:00
DYefremov
5bcac35deb added option for experimental features 2020-08-03 22:23:44 +03:00
DYefremov
3ec5d264a0 Spanish, Portuguese and Dutch translation update 2020-07-27 21:29:07 +03:00
DYefremov
a2882b6589 upd README 2020-07-25 23:34:22 +03:00
DYefremov
31780bbf56 rm dist 2020-07-25 23:13:17 +03:00
DYefremov
286f1ffc3f version update 2020-07-25 13:43:46 +03:00
DYefremov
3bf97e5e0d minor change in dialogs appearance 2020-07-25 13:34:57 +03:00
DYefremov
c7c411c72b update ref fix 2020-07-24 11:00:20 +03:00
DYefremov
6b8a83511a slight appearance change of dialogs 2020-07-24 03:39:14 +03:00
DYefremov
f8e259293a German and Russian translation update 2020-07-22 17:33:36 +03:00
DYefremov
7adbf6b8a9 added keyboard[del] support 2020-07-22 17:32:57 +03:00
DYefremov
b68535e88a loading providers fix 2020-07-22 17:31:10 +03:00
DYefremov
b98ca359df fix display of cas 2020-07-22 17:30:36 +03:00
DYefremov
cbfd1486e1 added lock support for iptv 2020-07-22 17:30:03 +03:00
DYefremov
ad185f1efa German translation update 2020-07-22 17:29:33 +03:00
DYefremov
cea4ed1a66 Russian translation update 2020-07-22 17:28:38 +03:00
DYefremov
853d054a68 added audio codec option 2020-07-22 17:25:01 +03:00
DYefremov
8a1cead2f7 added notifications 2020-07-21 14:30:59 +03:00
DYefremov
37e0a8fdac dist update 2020-07-13 21:44:25 +03:00
DYefremov
29c66142ee minor fix 2020-07-13 21:00:26 +03:00
DYefremov
4fd2a2a600 bouquet import fix 2020-07-12 19:18:11 +03:00
DYefremov
6b360d48c4 added bouquets import via dnd 2020-07-12 16:44:40 +03:00
DYefremov
3a307b277c minor gui fix 2020-07-12 16:33:13 +03:00
DYefremov
9e685058a2 added debug mode option 2020-07-12 16:32:56 +03:00
DYefremov
3f07b09bb5 added sorting of bouquet services 2020-07-12 16:28:18 +03:00
DYefremov
1dca45f18f dist update 2020-07-04 14:02:52 +03:00
DYefremov
8b5ebc132d added download dialog options 2020-07-04 13:41:46 +03:00
DYefremov
b076db23bb auto save profile settings 2020-07-02 17:32:51 +03:00
DYefremov
41d479e18f dist update 2020-06-30 09:07:30 +03:00
DYefremov
cf540e5c9a picons explorer gui changes 2020-06-29 15:58:43 +03:00
DYefremov
3c7c8ebd83 fix lock/hide in filter mode 2020-06-22 19:46:36 +03:00
DYefremov
9b0c173eb8 skip of marker counting 2020-06-22 11:11:21 +03:00
DYefremov
208ce53c48 update *.spec 2020-06-21 10:27:15 +03:00
DYefremov
bb6679eddf fix getting path from uri 2020-06-21 00:52:50 +03:00
DYefremov
57f5e40439 changed filter entry icon 2020-06-15 21:39:13 +03:00
DYefremov
dad02e8e5c changed dnd for picons 2020-06-15 21:32:47 +03:00
DYefremov
844dab10a0 changed callback for screenshots 2020-06-15 16:57:39 +03:00
DYefremov
c1f5fd8006 small yt refactoring 2020-06-13 21:01:14 +03:00
DYefremov
86b974b632 get fav id fix 2020-06-13 19:14:31 +03:00
DYefremov
cf7e3a1b1b added space item to fav elements 2020-06-13 19:12:23 +03:00
DYefremov
bb07eb0a8a small dnd fix 2020-06-12 00:39:46 +03:00
DYefremov
f6de7d0fce version update 2020-06-11 11:48:45 +03:00
DYefremov
647b528899 added frame for info bar 2020-06-11 10:47:27 +03:00
DYefremov
7ed64c76ba added paned style 2020-06-11 10:27:23 +03:00
DYefremov
bcfdb09169 small http api init fix 2020-06-10 18:34:42 +03:00
DYefremov
c0c2ddef34 added basic youtube-dl support 2020-06-10 18:02:47 +03:00
DYefremov
b02eb37f1c corrections after merge 2020-06-10 18:01:23 +03:00
DYefremov
9b9f1d5492 added eserviceuri (8193) stream type 2020-06-10 11:56:01 +03:00
DYefremov
caba789e02 small rework of screenshot mode 2020-06-10 11:55:49 +03:00
DYefremov
5b1bffc078 impl local removing for picons 2020-06-10 11:55:41 +03:00
DYefremov
7e35a081a0 logging extension on data downloading 2020-06-10 11:55:32 +03:00
DYefremov
ccbc7a4315 added dnd for selective download/send 2020-06-10 11:55:21 +03:00
DYefremov
7f3f900725 added selective download/send of picons 2020-06-10 11:53:08 +03:00
DYefremov
921b936db0 added update picons dest view 2020-06-10 11:52:57 +03:00
DYefremov
bc6d372ade improved functionality of the picons explorer 2020-06-10 11:51:51 +03:00
DYefremov
3c28d12579 added youtube-dl options 2020-06-10 11:45:10 +03:00
DYefremov
e9544cc77f added app settings read exception 2020-06-10 11:42:26 +03:00
DYefremov
bd047e5f72 added services filtering from picons manager 2020-06-10 11:42:05 +03:00
DYefremov
adf7262ed6 adding picons to src via dnd 2020-06-10 11:41:23 +03:00
DYefremov
74a1ffea3a added basic screenshots support 2020-06-10 11:40:54 +03:00
DYefremov
6e78a539c3 modified cas values 2020-06-10 11:36:46 +03:00
DYefremov
c3ce3fc82e added space [hidden marker] support 2020-06-10 11:36:32 +03:00
DYefremov
8c61720423 version update 2020-06-10 11:32:54 +03:00
DYefremov
25e0e6939a copy tr *.mo file 2020-06-08 23:55:39 +03:00
audi06_19
e3232e48cf Turkish translation correction (#26) 2020-06-08 23:55:26 +03:00
DYefremov
aaa610852b dist update 2020-06-04 12:47:19 +03:00
DYefremov
04e9179025 added accelerator for input dialog 2020-06-04 11:46:21 +03:00
DYefremov
bce5636eaa added check for unsaved changes 2020-06-04 11:41:38 +03:00
DYefremov
0e10631931 rm unavailable iptv fix 2020-06-03 11:30:26 +03:00
DYefremov
77a3edead2 zap mode fix 2020-06-03 00:07:19 +03:00
DYefremov
8a8b249e14 small bq parsing changes (prevent #12) 2020-06-02 23:58:39 +03:00
DYefremov
4025f0933d copy pl *.mo file 2020-06-02 10:03:31 +03:00
DYefremov
bba4054bff Polish translation correction 2020-06-02 09:36:37 +03:00
Wieslaw Weglowski
e322d36023 Polish translation update(#23) 2020-06-02 09:07:31 +03:00
DYefremov
bb5afb0206 dist update 2020-05-23 01:36:35 +03:00
DYefremov
115f3960a7 drag on icon fix
(cherry picked from commit 0e50f1952d)
2020-05-23 01:22:09 +03:00
DYefremov
6d37da072e German translation update 2020-05-19 14:32:31 +03:00
DYefremov
99c3b1d194 Russian translation update 2020-05-19 14:32:17 +03:00
DYefremov
43afaf77b8 minor log changes 2020-05-19 12:01:11 +03:00
DYefremov
38aabb1b94 Dutch translation update 2020-05-18 16:31:24 +03:00
DYefremov
ef501f1557 small dnd fix 2020-05-18 12:40:21 +03:00
DYefremov
4679f9379c changed dnd for bouquets list 2020-05-17 15:28:44 +03:00
DYefremov
b2ea39f8a6 minor fixes 2020-05-17 14:55:18 +03:00
DYefremov
638be67425 dist update 2020-05-12 21:15:31 +03:00
DYefremov
9ca5a597d5 scaling picons on loading 2020-05-12 21:14:00 +03:00
DYefremov
d95ba7336f copy tr *.mo file 2020-05-12 17:26:12 +03:00
audi06
c78b18ddb7 Turkish translation update (#22) 2020-05-12 17:24:45 +03:00
DYefremov
92984c5fa6 start update 2020-05-12 15:19:47 +03:00
DYefremov
ca65f64a4f changed some dialogs elements 2020-05-12 14:13:01 +03:00
DYefremov
78dcccbd51 minor optimization and fix 2020-05-10 21:39:48 +03:00
DYefremov
f984d10c82 German translation update 2020-05-10 18:50:22 +03:00
DYefremov
c4ea451f52 small fix to prevent (#12) 2020-05-10 18:50:11 +03:00
DYefremov
36ec6d5079 added picons filter by service name 2020-05-10 18:49:59 +03:00
DYefremov
91706c722f Russian translation update 2020-05-10 15:13:06 +03:00
DYefremov
4ef8c4d186 fix to prevent #12 2020-05-08 17:15:34 +03:00
DYefremov
f9e92b28d0 dist update 2020-05-07 15:10:48 +03:00
DYefremov
832bab91a4 reworked settings dialog 2020-05-07 14:19:46 +03:00
DYefremov
951c99338f added skip upload if file not found 2020-05-05 00:04:05 +03:00
DYefremov
ee91eb9413 fix use colors 2020-05-04 22:33:05 +03:00
DYefremov
912c38825b redesigned appearance for most dialogs 2020-05-04 19:36:52 +03:00
DYefremov
de4d012784 fix to prevent (#21) 2020-05-04 19:20:51 +03:00
DYefremov
351ce81e94 minor fixes for yt 2020-05-03 02:05:36 +03:00
DYefremov
3a0f096a6c changed data dir creation 2020-05-03 01:42:28 +03:00
DYefremov
29088ec19e dist update 2020-04-30 14:12:58 +03:00
DYefremov
4c144951f0 reworking of download dialog 2020-04-30 14:09:01 +03:00
DYefremov
dae6ad765a added accelerators and tooltips 2020-04-30 13:55:40 +03:00
DYefremov
b934407d7e added download/upload of [terrestrial, cable].xml 2020-04-30 13:53:15 +03:00
DYefremov
3fb5b82cc6 slight optimization of loading/deleting data 2020-04-30 13:47:24 +03:00
DYefremov
ba3ad9a9ef setting text for wait dialog 2020-04-30 13:45:09 +03:00
DYefremov
7a4620a374 path resolve fix 2020-04-30 13:42:02 +03:00
DYefremov
174634ecbc reworking of picons dialog 2020-04-30 13:36:01 +03:00
DYefremov
73ae57d07b extracting themes with tar 2020-04-23 18:43:22 +03:00
DYefremov
055a700586 small refactoring of base icons init 2020-04-23 15:43:48 +03:00
DYefremov
04203240a7 minor fixes for filter and search 2020-04-23 10:33:56 +03:00
DYefremov
a433e01b65 added group style 2020-04-22 10:02:47 +03:00
DYefremov
8f591a8b9a small refactoring of chooser dialog 2020-04-21 14:45:34 +03:00
DYefremov
dcc217b0de upd README 2020-04-20 20:50:10 +03:00
DYefremov
d06334b0af added picons assignment by drag on icon 2020-04-20 13:55:38 +03:00
DYefremov
6957a960ca added hints support for the main list 2020-04-20 13:51:05 +03:00
DYefremov
9fe328b54e minor refactoring 2020-04-20 13:46:42 +03:00
DYefremov
b3dc9b72c9 version update 2020-04-16 21:42:07 +03:00
DYefremov
b6a4d46227 upd README 2020-04-16 21:41:05 +03:00
audi06
53776bdf62 added Turkish selection (#20) 2020-04-16 21:10:28 +03:00
DYefremov
ba9ba4129f copy tr *.mo file 2020-04-16 21:10:13 +03:00
audi06
a2411ba86e added Turkish translation (#19)
(cherry picked from commit 8d96f02e2e)
2020-04-16 21:10:01 +03:00
DYefremov
a6d8573999 added bouquet file naming option 2020-04-16 17:29:04 +03:00
DYefremov
7510d42fb9 minor fixes 2020-04-13 20:08:22 +03:00
DYefremov
036e666c9b styles decoupling 2020-04-13 20:07:57 +03:00
DYefremov
c9c962e129 added appearance settings 2020-04-13 13:54:54 +03:00
DYefremov
ea71af9462 dist update 2020-04-10 23:22:57 +03:00
DYefremov
0a5b51de6e style changes for some ui elements 2020-04-10 23:09:17 +03:00
DYefremov
8cb413ec92 added basic hints support 2020-04-08 18:40:27 +03:00
DYefremov
5dfb702484 added option for hints 2020-04-08 18:37:10 +03:00
DYefremov
0cab4e1238 epg options fix 2020-04-02 16:53:03 +03:00
DYefremov
85f5c37f28 changed some player args 2020-03-28 20:20:49 +03:00
DYefremov
3df6d7bba0 translations update for dist 2020-03-28 19:56:24 +03:00
DYefremov
e45c56f4cc changed toolbar elements position 2020-03-28 19:31:13 +03:00
DYefremov
7d03631924 basic implementation of the play mode 2020-03-28 18:45:05 +03:00
DYefremov
7b9ec6a4b1 small cleaning 2020-03-28 18:39:13 +03:00
DYefremov
d640210ab0 copy *.mo file 2020-03-24 15:38:35 +03:00
wwns
f7e8283355 Polish translation update (#18) 2020-03-24 15:38:10 +03:00
DYefremov
f93c81de19 wrap m3u data 2020-03-24 15:38:06 +03:00
DYefremov
e1804755d2 added play streams mode options 2020-03-24 15:38:01 +03:00
DYefremov
1cf56639c1 copy .mo file 2020-03-21 16:47:50 +03:00
Víctor Pont
943b4c540f Spanish translation update and corrections (#17) 2020-03-21 16:47:43 +03:00
DYefremov
4602c51c01 added simple telnet client 2020-03-17 14:10:49 +03:00
DYefremov
a84cc7727f minor fix 2020-03-14 09:09:11 +03:00
DYefremov
250e03af5d changed record button update 2020-03-13 14:37:21 +03:00
DYefremov
2c5f8eb0ed toolbar elements changes 2020-03-11 16:47:10 +03:00
DYefremov
6f4ff4c97d added transcoding options 2020-03-11 16:05:13 +03:00
DYefremov
ee29659739 added record of current service 2020-03-11 16:04:41 +03:00
DYefremov
8a1496a84c added new paths settings 2020-03-11 15:57:41 +03:00
DYefremov
23c3035162 fix service status info 2020-03-11 15:56:57 +03:00
DYefremov
a506356547 version update 2020-03-11 15:55:59 +03:00
DYefremov
0c284fb0d9 added picons multiple assignment 2020-03-11 15:50:23 +03:00
DYefremov
b437385325 German translation update
(cherry picked from commit 3d627b57a4)
2020-02-28 00:53:33 +03:00
DYefremov
c60bba5535 upd README 2020-02-24 12:54:29 +03:00
DYefremov
1c2d0ab9ea small fix
(cherry picked from commit 7444db7e21)
2020-02-24 12:51:56 +03:00
DYefremov
f35f7fbc8a update dist 2020-02-21 00:14:10 +03:00
DYefremov
42aaad291f Russian translation update
(cherry picked from commit 7554f40c6a)
2020-02-20 14:16:09 +03:00
DYefremov
9c8c617393 gui changes for send to 2020-02-20 12:11:41 +03:00
DYefremov
98fc963fa1 fix getting sats 2020-02-19 12:03:27 +03:00
DYefremov
fbb5cd0352 added appindicator support 2020-02-19 10:15:01 +03:00
DYefremov
5abe3de3b6 toolbar changes 2020-02-18 00:35:17 +03:00
DYefremov
0b3f26ab84 .mo file update 2020-02-17 09:21:40 +03:00
wwns
2666146b5e Polish translation update (#11)
Polish translation update.
2020-02-17 08:50:32 +03:00
DYefremov
be90b518c9 update of .mo file 2020-02-14 22:55:36 +03:00
wwns
adeae58488 Polish translation update (#10) 2020-02-14 22:20:07 +03:00
DYefremov
b204f042ee update dist 2020-02-12 22:29:50 +03:00
DYefremov
79d0e9d256 added icons path to .spec file 2020-02-12 21:45:14 +03:00
DYefremov
4dcfde8b53 revert of get yt icon 2020-02-12 21:16:47 +03:00
DYefremov
42f687020b moved get yt icon 2020-02-12 20:19:09 +03:00
DYefremov
14bf79dbf9 toolbar changes 2020-02-12 17:35:44 +03:00
DYefremov
f660beef16 update of data path 2020-02-12 13:53:18 +03:00
DYefremov
99d17b36c3 added Polish selection 2020-02-12 12:40:27 +03:00
wwns
3113fadcca added a Polish translation (#5)
* added a Polish translation

* added a Polish translation

* name change
2020-02-11 21:43:53 +03:00
DYefremov
67a394359d fix bq deletion 2020-02-10 19:28:59 +03:00
DYefremov
1acb7fdd81 changing profile on data download 2020-02-10 17:01:48 +03:00
DYefremov
dced81581c fix profile edit 2020-02-10 14:52:30 +03:00
DYefremov
6a52988f1a update dist 2020-02-01 18:55:31 +03:00
DYefremov
4a1d714604 added basic support for keyboard shortcuts 2020-02-01 17:45:36 +03:00
DYefremov
2e12e1ec87 auto rename bouquets with duplicate names 2020-02-01 09:34:45 +03:00
DYefremov
4c6336e75f added checking for bouquet names duplicate 2020-01-29 14:58:32 +03:00
DYefremov
b7d0ba7f4b added controls to the transmitter 2020-01-28 16:37:53 +03:00
DYefremov
405e07bbc4 added player requests 2020-01-28 16:36:08 +03:00
DYefremov
6ded67147b update dist 2020-01-24 20:47:24 +03:00
DYefremov
4a0e2acd9c minor gui changes 2020-01-24 01:06:08 +03:00
DYefremov
5876f70884 upd. README 2020-01-23 19:18:49 +03:00
DYefremov
3cb4f1095d fix picons downloading from bundle 2020-01-23 19:17:54 +03:00
DYefremov
af46c2fb1d minor fixes for player elems 2020-01-23 16:13:09 +03:00
DYefremov
cbcdf19be6 added remove and download to picons 2020-01-23 00:48:57 +03:00
DYefremov
f326a9c723 some corrections 2020-01-19 20:07:59 +03:00
DYefremov
53888a45dc Merge branch 'development' into experimental-mac
# Conflicts:
#	DemonEditor.desktop
#	README.md
#	app/connections.py
#	app/ui/main_app_window.py
#	app/ui/main_window.glade
#	app/ui/uicommons.py
#	build-deb.sh
#	deb/DEBIAN/control
#	deb/DEBIAN/copyright
#	deb/usr/bin/demon-editor
#	deb/usr/share/applications/DemonEditor.desktop
#	start.py
2020-01-18 23:08:45 +03:00
DYefremov
c2d3cb7673 some changes for translation 2019-12-28 23:21:19 +03:00
DYefremov
9d4b507559 Merge remote-tracking branch 'origin/experimental-mac' into experimental-mac 2019-12-21 13:57:37 -08:00
DYefremov
2993fcd7f7 update dist 2019-12-18 03:50:17 -08:00
DYefremov
5bea9887db minor clean 2019-12-18 08:56:41 +03:00
DYefremov
dd0edfc811 minor clean 2019-12-17 12:56:07 +03:00
DYefremov
5bf6500809 added exception 2019-12-17 12:03:15 +03:00
DYefremov
d05da3f44c minor player fix 2019-12-16 15:49:24 +03:00
DYefremov
b251ce8b69 fixes after merge 2019-12-16 10:47:18 +03:00
DYefremov
df36860239 Merge branch 'development' into experimental-mac
# Conflicts:
#	app/properties.py
#	app/tools/media.py
#	app/ui/dialogs.py
#	app/ui/main_app_window.py
#	app/ui/transmitter.py
#	deb/usr/share/locale/es/LC_MESSAGES/demon-editor.mo
2019-12-16 10:36:19 +03:00
DYefremov
6f27040164 upd README 2019-12-14 13:22:22 +03:00
DYefremov
4d488dd224 update dist 2019-12-08 02:38:28 -08:00
DYefremov
c728a59e92 update dist 2019-12-08 02:32:40 -08:00
DYefremov
98341064d3 fix set nso 2019-12-08 01:09:44 -08:00
DYefremov
fc00b25fd2 revert media player view 2019-12-08 01:20:06 +03:00
DYefremov
638f33ac5a update vlc 2019-12-07 21:07:51 +03:00
DYefremov
88167912b3 fix status image show 2019-12-06 15:20:14 +03:00
DYefremov
450d7f4c72 added dist 2019-12-06 08:50:26 +03:00
DYefremov
24729c064c base impl of send to 2019-12-05 10:52:04 -08:00
DYefremov
df3c2a3938 added build support 2019-12-05 10:47:49 -08:00
DYefremov
44bf8b96ff fix set icon after marge 2019-11-26 18:46:57 +03:00
DYefremov
6b86db4aa4 Merge branch 'development' into experimental-mac
# Conflicts:
#	app/connections.py
#	app/ui/main_app_window.py
#	app/ui/main_window.glade
#	deb/DEBIAN/control
2019-11-26 18:41:07 +03:00
DYefremov
bf9ad139e5 added style 2019-11-01 00:24:13 +03:00
DYefremov
20fc199d02 moved toolbar to the top 2019-10-28 11:59:58 +03:00
DYefremov
e0a22f72fc changed setting service info 2019-10-28 11:44:30 +03:00
DYefremov
8cadc47da5 added current service status info 2019-10-28 11:44:12 +03:00
DYefremov
499ca31992 added http-api prototype for vlc 2019-10-27 00:39:32 +03:00
DYefremov
e198f0b1e6 removed separators from toolbar 2019-10-26 15:19:39 +03:00
DYefremov
a43ac0de02 added bottom toolbar 2019-10-26 15:01:23 +03:00
DYefremov
b14e6fac16 added edit and view to the app bar menu 2019-10-25 19:17:31 +03:00
DYefremov
2dcc9a85b5 updated app bar menu 2019-10-23 12:38:23 +03:00
DYefremov
7ce9ba0db2 added prototype of playing current service 2019-10-23 12:24:57 +03:00
DYefremov
fc56b047a1 added prototype of playing current service 2019-10-23 12:20:20 +03:00
DYefremov
78dc62d46e added german translation 2019-10-23 12:19:31 +03:00
DYefremov
20e7ee3478 removing deb 2019-10-20 23:58:10 +03:00
DYefremov
ffa144367f slight refactoring of stream play 2019-10-20 23:48:35 +03:00
DYefremov
237d09a711 updated version 2019-10-20 23:48:28 +03:00
DYefremov
272cbdeb2f changed yt links icon to default 2019-10-19 09:31:51 +03:00
DYefremov
f9191a7465 minor gui changes 2019-10-18 21:07:48 +03:00
DYefremov
f22abe1d87 test nso 2019-10-17 08:11:04 -07:00
DYefremov
b94c08284a new gui test prototype for the player 2019-10-17 15:02:31 +03:00
DYefremov
e303f25f99 updated app bar menu 2019-10-14 22:09:14 +03:00
DYefremov
b80dcb7d74 fix save 2019-10-13 18:53:33 +03:00
DYefremov
63c5df0ef6 Improved functionality of the app menu bar 2019-10-13 14:07:46 +03:00
DYefremov
c69888a72d Merge remote-tracking branch 'origin/experimental-mac' into experimental-mac 2019-10-12 23:32:16 +03:00
DYefremov
07606077e5 added app menu bar prototype 2019-10-12 23:32:01 +03:00
DYefremov
e1f63bfed7 updating of dutch, spanish and portuguese 2019-10-12 23:23:48 +03:00
DYefremov
c0865beb3c Merge branch 'development' into experimental-mac 2019-10-11 15:54:53 +03:00
DYefremov
05eff28b75 upd. README 2019-10-11 14:34:08 +03:00
DYefremov
6d2150b731 changes to start on macos 2019-10-10 14:19:10 -07:00
78 changed files with 12267 additions and 8877 deletions

View File

@@ -1,13 +0,0 @@
[Desktop Entry]
Version=1.0
Name=DemonEditor
Comment=Channel and satellite list editor for Enigma2
Comment[ru]=Редактор списка каналов и спутников для Enigma2
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
Icon=demon-editor
Exec=bash -c 'cd $(dirname %k) && ./start.py'
Terminal=false
Type=Application
Categories=Utility;Application;
StartupNotify=false

48
DemonEditor.spec Normal file
View File

@@ -0,0 +1,48 @@
# -*- mode: python ; coding: utf-8 -*-
EXE_NAME = 'start.py'
DIR_PATH = os.getcwd()
PATH_EXE = [os.path.join(DIR_PATH, EXE_NAME)]
block_cipher = None
ui_files = [('app\\ui\\*.glade', 'ui'),
('app\\ui\\*.css', 'ui'),
('app\\ui\\*.ui', 'ui'),
('app\\ui\\lang*', 'share\\locale'),
('app\\ui\\icons*', 'share\\icons')
]
a = Analysis([EXE_NAME],
pathex=PATH_EXE,
binaries=[],
datas=ui_files,
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=['youtube_dl', 'tkinter'],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='DemonEditor',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False, icon='icon.ico')
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='DemonEditor')

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018-2020 Dmitriy Yefremov
Copyright (c) 2018-2021 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,34 +1,25 @@
# <img src="app/ui/icons/hicolor/96x96/apps/demon-editor.png" width="32" /> DemonEditor
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) ![platform](https://img.shields.io/badge/platform-linux%20|%20macos-lightgrey)
### Enigma2 channel and satellite list editor for GNU/Linux.
[<img src="https://user-images.githubusercontent.com/7511379/103152885-4a7bd880-479d-11eb-96e8-4ad0f5dc3e2e.png" width="560"/>](https://user-images.githubusercontent.com/7511379/103152885-4a7bd880-479d-11eb-96e8-4ad0f5dc3e2e.png)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) ![platform](https://img.shields.io/badge/platform-windows-lightgrey)
## Enigma2 channel and satellite list editor for MS Windows (experimental).
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).
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).
**The functionality and performance of this version may be different from the [Linux version](https://github.com/DYefremov/DemonEditor)!**
## Main features of the program
* Editing bouquets, channels, satellites.
[<img src="https://user-images.githubusercontent.com/7511379/100156250-a3fc9900-2eb9-11eb-8729-7bcb6ddcdd4a.png" width="480"/>](https://user-images.githubusercontent.com/7511379/100156250-a3fc9900-2eb9-11eb-8729-7bcb6ddcdd4a.png)
* Import function.
[<img src="https://user-images.githubusercontent.com/7511379/103150878-8a38c500-4789-11eb-9e03-0c8ee832ff99.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103150878-8a38c500-4789-11eb-9e03-0c8ee832ff99.png)
* Backup function.
[<img src="https://user-images.githubusercontent.com/7511379/103150886-a0df1c00-4789-11eb-88fa-91996b72d1f9.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103150886-a0df1c00-4789-11eb-88fa-91996b72d1f9.png)
* Support of picons.
[<img src="https://user-images.githubusercontent.com/7511379/103150891-accade00-4789-11eb-8804-e1807df89c99.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103150891-accade00-4789-11eb-8804-e1807df89c99.png)
* Importing services, downloading picons and updating satellites from the Web.
[<img src="https://user-images.githubusercontent.com/7511379/97909451-4c0ac080-1d59-11eb-9626-85bed91f7ccc.png" width="250"/>](https://user-images.githubusercontent.com/7511379/97909451-4c0ac080-1d59-11eb-9626-85bed91f7ccc.png)
[<img src="https://user-images.githubusercontent.com/7511379/103150872-77be8b80-4789-11eb-8a74-7a49fb3edd98.png" width="292"/>](https://user-images.githubusercontent.com/7511379/103150872-77be8b80-4789-11eb-8a74-7a49fb3edd98.png)
* Editing bouquets, channels, satellites.
* Import function.
* Backup function.
* Extended support of IPTV.
* Support of picons.
* Importing services, downloading picons and updating satellites from the Web.
* Import to bouquet(Neutrino WEBTV) from m3u.
* Export of bouquets with IPTV services in m3u.
* Assignment of EPG 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/)).
[<img src="https://user-images.githubusercontent.com/7511379/103151911-89a52c00-4793-11eb-9941-8430f4e87eef.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103151911-89a52c00-4793-11eb-9941-8430f4e87eef.png)
* Control panel with the ability to view EPG and manage timers (via HTTP API, experimental).
[<img src="https://user-images.githubusercontent.com/7511379/103150898-c79d5280-4789-11eb-9d16-e7f89225738b.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103150898-c79d5280-4789-11eb-9d16-e7f89225738b.png)
* Simple FTP client (experimental).
[<img src="https://user-images.githubusercontent.com/7511379/103152009-7e9ecb80-4794-11eb-85f1-c97e189a3195.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103152009-7e9ecb80-4794-11eb-85f1-c97e189a3195.png)
* 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/)).
* Control panel with the ability to view EPG and manage timers (via HTTP API, experimental).
* Simple FTP client (experimental).
#### Keyboard shortcuts
* **Ctrl + X** - only in bouquet list.
* **Ctrl + C** - only in services list.
@@ -53,29 +44,14 @@ Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **Ctrl + I** - extra info, details.
* **Ctrl + F** - show/hide search bar.
* **Ctrl + Shift + F** - show/hide filter bar.
For **multiple** selection with the mouse, press and hold the **Ctrl** key!
## Minimum requirements
*Python >= 3.5.2, GTK+ >= 3.16 with PyGObject bindings, python3-requests.*
***Optional:** python3-gi-cairo, python3-pil, python3-chardet.*
## Installation and Launch
* ### Linux
To start the program, in most cases it is enough to download the [archive](https://github.com/DYefremov/DemonEditor/archive/master.zip), unpack
and run it by double clicking on DemonEditor.desktop in the root directory,
or launching from the console with the command:```./start.py```
Extra folders can be deleted, excluding the *app* folder and root files like *DemonEditor.desktop* and *start.py*!
To create a simple **debian package**, you can use the *build-deb.sh.* You can also download a ready-made *.deb package from the [releases](https://github.com/DYefremov/DemonEditor/releases) page.
Users of **LTS** versions of [Ubuntu](https://ubuntu.com/) or distributions based on them can use [PPA](https://launchpad.net/~dmitriy-yefremov/+archive/ubuntu/demon-editor) repository.
* ### macOS (experimental)
**This program can also be run on macOS.**
To work in this OS, you must use a [separate branch](https://github.com/DYefremov/DemonEditor/tree/experimental-mac).
**The functionality and performance of this version may be different from the Linux version!**
*Python >= 3.4.4, GTK+ >= 3.16 with PyGObject bindings, python3-requests.*
## Important
The program is tested only with [openATV](https://www.opena.tv/) image and **Formuler F1** receiver in [Linux Mint](https://linuxmint.com/) (MATE 64-bit) distribution!
**This version is not fully tested and has experimental status!**
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!
@@ -85,9 +61,11 @@ When using the multiple import feature, from *lamedb* will be taken data **only
If you need full set of the data, including *[satellites, terrestrial, cables].xml* (current files will be overwritten),
just load your data via *"File/Open"* and press *"Save"*. When importing separate bouquet files, only those services
(excluding IPTV) that are in the **current open lamedb** (main list of services) will be imported.
#### Command line arguments:
* **-l** - write logs to file.
* **-d on/off** - turn on/off debug mode. Allows to display more information in the logs.
* **-t on/off** - show/hide simple built-in **telnet** client (experimental). **ANSI escape sequences are not supported!**
## License
Licensed under the [MIT](LICENSE) license.
Licensed under the [MIT](LICENSE) license.

View File

@@ -67,7 +67,7 @@ def run_with_delay(timeout=5):
timer.cancel()
def run():
GLib.idle_add(func, *args, **kwargs, priority=GLib.PRIORITY_LOW)
GLib.idle_add(func, priority=GLib.PRIORITY_LOW, *args, **kwargs)
timer = Timer(interval=timeout, function=run)
timer.start()

View File

@@ -6,7 +6,6 @@ import urllib
import xml.etree.ElementTree as ETree
from enum import Enum
from ftplib import FTP, CRLF, Error, error_perm
from http.client import RemoteDisconnected
from telnetlib import Telnet
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
@@ -654,7 +653,7 @@ def get_response(req_type, url, data=None):
if req_type is HttpAPI.Request.TEST:
raise e
return {"error_code": e.code}
except (URLError, RemoteDisconnected, ConnectionResetError) as e:
except (URLError, ConnectionResetError) as e:
if req_type is HttpAPI.Request.TEST:
raise e
except ETree.ParseError as e:
@@ -713,7 +712,7 @@ def test_http(host, port, user, password, timeout=5, use_ssl=False, skip_message
try:
return get_response(HttpAPI.Request.TEST, "{}/web/{}".format(base_url, params), data).get("e2statetext", "")
except (RemoteDisconnected, URLError, HTTPError) as e:
except (URLError, HTTPError) as e:
raise TestException(e)

View File

@@ -17,7 +17,8 @@ class BqServiceType(Enum):
ALT = "ALT" # Service with alternatives
Bouquet = namedtuple("Bouquet", ["name", "type", "services", "locked", "hidden"])
Bouquet = namedtuple("Bouquet", ["name", "type", "services", "locked", "hidden", "file"])
Bouquet.__new__.__defaults__ = (None, BqServiceType.DEFAULT, [], None, None, None) # For Python3 < 3.7
Bouquets = namedtuple("Bouquets", ["name", "type", "bouquets"])
BouquetService = namedtuple("BouquetService", ["name", "type", "data", "num"])
@@ -34,6 +35,7 @@ class TrType(Enum):
Satellite = "s"
Terrestrial = "t"
Cable = "c"
ATSC = "a"
class BqType(Enum):
@@ -146,6 +148,10 @@ 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"}
# ATSC
A_MODULATION = {"0": "Auto", "1": "QAM16", "2": "QAM32", "3": "QAM64", "4": "QAM128", "5": "QAM256", "6": "8VSB",
"7": "16VSB"}
# CAS
CAS = {"C:26": "BISS", "C:0B": "Conax", "C:06": "Irdeto", "C:18": "Nagravision", "C:05": "Viaccess", "C:01": "SECA",
"C:0E": "PowerVu", "C:4A": "DRE-Crypt", "C:7B": "DRE-Crypt", "C:56": "Verimatrix", "C:09": "VideoGuard"}

View File

@@ -9,14 +9,15 @@ __FILE_NAME = "blacklist"
def get_blacklist(path):
with suppress(FileNotFoundError):
with open(path + __FILE_NAME, "r") as file:
with open(path + __FILE_NAME, "r", encoding="utf-8") as file:
# filter empty values and "\n"
return {*list(filter(None, (x.strip() for x in file.readlines())))}
return {}
return set(filter(None, (x.strip() for x in file.readlines())))
return set()
def write_blacklist(path, channels):
with open(path + __FILE_NAME, "w") as file:
with open(path + __FILE_NAME, "w", encoding="utf-8") as file:
if channels:
file.writelines("\n".join(channels))

View File

@@ -27,9 +27,9 @@ class BouquetsWriter:
self._path = path
self._bouquets = bouquets
self._force_bq_names = force_bq_names
self._marker_index = 0
self._marker_index = 1
self._space_index = 0
self._alt_index = 0
self._alt_names = set()
def write(self):
line = []
@@ -38,13 +38,21 @@ class BouquetsWriter:
for bqs in self._bouquets:
line.clear()
line.append("#NAME {}\n".format(bqs.name))
bq_file_names = {b.file for b in bqs.bouquets}
count = 1
for bq in bqs.bouquets:
bq_name = bq.file
if not bq_name:
if self._force_bq_names:
bq_name = re.sub(pattern, "_", bq.name)
else:
bq_name = "de{0:02d}".format(count)
while bq_name in bq_file_names:
count += 1
bq_name = "de{0:02d}".format(count)
bq_file_names.add(bq_name)
for index, bq in enumerate(bqs.bouquets):
bq_name = bq.name
if bq_name == "Favourites (TV)" or bq_name == "Favourites (Radio)":
bq_name = _DEFAULT_BOUQUET_NAME
else:
bq_name = re.sub(pattern, "_", bq.name) if self._force_bq_names else "de{0:02d}".format(index)
line.append(self._SERVICE.format(2 if bq.type == BqType.RADIO.value else 1, bq_name, bq.type))
self.write_bouquet(self._path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services)
@@ -70,12 +78,13 @@ class BouquetsWriter:
services = srv.transponder
if services:
p = Path(path)
alt_name = srv.data_id
f_name = "alternatives.{}{}".format(alt_name, p.suffix)
if self._force_bq_names:
alt_name = re.sub(self._ALT_PAT, "_", srv.service).lower()
f_name = "alternatives.{}{}".format(alt_name, p.suffix)
else:
f_name = "alternatives.de{:02d}{}".format(self._alt_index, p.suffix)
self._alt_index += 1
alt_path = "{}/{}".format(p.parent, f_name)
bouquet.append(self._ALT.format(f_name))
self.write_bouquet(alt_path, srv.service, services)
@@ -135,7 +144,7 @@ class BouquetsReader:
else:
real_b_names[rb_name] = 0
bouquets[2].append(Bouquet(rb_name, bq_type, services, None, None))
bouquets[2].append(Bouquet(rb_name, bq_type, services, None, None, b_name))
else:
raise ValueError("No bouquet name found for: {}".format(line))
@@ -169,7 +178,7 @@ class BouquetsReader:
if alt:
alt_name, alt_type = alt.group(1), alt.group(2)
alt_bq_name, alt_srvs = BouquetsReader.get_bouquet(path, alt_name, alt_type, "alternatives")
services.append(BouquetService(alt_bq_name, BqServiceType.ALT, srv.lstrip(), tuple(alt_srvs)))
services.append(BouquetService(alt_bq_name, BqServiceType.ALT, alt_name, tuple(alt_srvs)))
elif srv_data[0].strip() in BouquetsReader._STREAM_TYPES or srv_data[10].startswith(("http", "rtsp")):
stream_data, sep, desc = srv.partition("#DESCRIPTION")
desc = desc.lstrip(":").strip() if desc else srv_data[-1].strip()

View File

@@ -13,282 +13,303 @@ _END_LINE = "# File was created in DemonEditor.\n# ....Enjoy watching!....\n"
def get_services(path, format_version):
return parse(path, format_version)
return LameDbReader(path, format_version).parse()
def write_services(path, services, format_version=4):
if format_version == 4:
write_to_lamedb(path, services)
elif format_version == 5:
write_to_lamedb5(path, services)
LameDbWriter(path, services, format_version).write()
def write_to_lamedb(path, services):
""" Writing lamedb file ver.4 """
with open(path + _FILE_NAME, "w") as file:
file.writelines(get_services_lines(services))
class LameDbReader:
""" Lamedb parser class.
Reads and parses the Enigma2 lamedb[5] file.
Supports versions 3, 4 and 5..
"""
__slots__ = ["_path", "_fmt"]
def __init__(self, path, fmt=4):
self._path = path
self._fmt = fmt
def parse(self):
""" Parsing lamedb. """
if self._fmt == 4:
return self.parse_v4()
elif self._fmt == 5:
return self.parse_v5()
raise SyntaxError("Unsupported version of the format.")
def parse_v3(self, services, transponders):
""" Parsing version 3. """
for t in transponders:
tr = transponders[t].lower()
tr_type = tr[0:1]
if tr_type == "c":
tr += ":0:0:0"
elif tr_type == "t" or tr_type == "a":
tr += ":0:0"
else:
tr_data = tr.split(_SEP)
len_data = len(tr_data)
if len_data == 6:
tr_data.append("0")
elif len_data == 9:
tr_data.insert(6, "0")
tr_data.append("0")
tr_data.append("2")
tr = _SEP.join(tr_data)
transponders[t] = tr
return self.parse_services(services, transponders)
def parse_v4(self):
""" Parsing version 4. """
with open(self._path + _FILE_NAME, "r", encoding="utf-8", errors="replace") as file:
try:
data = str(file.read())
except UnicodeDecodeError as e:
log("lamedb parse error: " + str(e))
else:
return self.get_services_list(data)
def parse_v5(self):
""" Parsing version 5. """
with open(self._path + "lamedb5", "r", encoding="utf-8", errors="replace") as file:
lns = file.readlines()
if lns and not lns[0].endswith("/5/\n"):
raise SyntaxError("lamedb ver.5 parsing error: unsupported format.")
trs, srvs = {}, [""]
for line in lns:
if line.startswith("s:"):
srv_data = line.strip("s:").split(",", 2)
srv_data[1] = srv_data[1].strip("\"")
data_len = len(srv_data)
if data_len == 3:
srv_data[2] = srv_data[2].strip()
elif data_len == 2:
srv_data.append("p:")
srvs.extend(srv_data)
elif line.startswith("t:"):
data = line.split(",")
len_data = len(data)
if len_data > 1:
tr, srv = data[0].strip("t:"), data[1].strip().replace(":", " ", 1)
trs[tr] = srv
else:
log("Error while parsing transponder data [ver. 5] for line: {}".format(line))
return self.parse_services(srvs, trs)
def parse_services(self, services, transponders):
""" Parsing services. """
services_list = []
blacklist = get_blacklist(self._path) if self._path else {}
srvs = self.split(services, 3)
if srvs[0][0] == "": # Remove first empty element.
srvs.remove(srvs[0])
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]
nid = data[3]
# For lamedb ver.3
is_v3 = False
if len(tid) < 4:
is_v3 = True
tid = "{:0>4}".format(tid)
data[2] = tid
if len(nid) < 4:
is_v3 = True
nid = "{:0>4}".format(nid)
data[3] = nid
if is_v3:
data[0] = "{:0>4}".format(data[0])
data_id = _SEP.join(data)
srv_type = int(data[4])
transponder_id = "{}:{}:{}".format(data[1], tid, nid)
transponder = transponders.get(transponder_id, None)
tid = tid.lstrip(sp).upper()
nid = nid.lstrip(sp).upper()
ssid = str(data[0]).lstrip(sp).upper()
onid = str(data[1]).lstrip(sp).upper()
# For comparison in bouquets. Needed in upper case!!!
fav_id = "{}:{}:{}:{}".format(ssid, tid, nid, onid)
picon_id = "1_0_{:X}_{}_{}_{}_{}_0_0_0.png".format(srv_type, ssid, tid, nid, onid)
s_id = "1:0:{:X}:{}:{}:{}:{}:0:0:0:".format(srv_type, ssid, tid, nid, onid)
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
locked = LOCKED_ICON if s_id in blacklist else None
package = list(filter(lambda x: x.startswith("p:"), all_flags))
package = package[0][2:] if package else ""
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 srv[1] if c.isprintable())
freq = tr[0]
rate = tr[1]
pol = None
fec = None
system = None
pos = None
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 = tr[4]
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])
elif tr_type is TrType.ATSC:
system = "ATSC"
pos = "T"
fec = FEC_DEFAULT.get("0")
# Formatting displayed values.
try:
freq = "{}".format(int(freq) // 1000)
rate = "{}".format(int(rate) // 1000)
if tr_type is TrType.Satellite:
pos = int(pos)
pos = "{:0.1f}{}".format(abs(pos / 10), "W" if pos < 0 else "E")
except ValueError as e:
log("Parse error [parse_services]: {}".format(e))
s = Service(srv[2], tr_type.value, coded, srv_name, locked, hide, package, service_type, None,
picon_id, data[0], freq, rate, pol, fec, system, pos, data_id, fav_id, transponder)
services_list.append(s)
return services_list
def get_services_list(self, data):
""" Returns a list of services from a string data representation. """
transponders, sep, services = data.partition("transponders") # 1 step
pattern = re.compile("/[34]/$")
match = re.search(pattern, transponders)
if not match:
msg = "lamedb parsing error: unsupported format."
log(msg)
raise SyntaxError(msg)
transponders, sep, services = services.partition("services") # 2 step
services, sep, _ = services.partition("\nend") # 3 step
if match.group() == "/3/":
return self.parse_v3(services.split("\n"), self.parse_transponders(transponders.split("/")))
return self.parse_services(services.split("\n"), self.parse_transponders(transponders.split("/")))
@staticmethod
def get_services_lines(services):
""" Returns a list of strings from services for lamedb [v.4]. """
lines = [_HEADER.format(4), "\ntransponders\n"]
tr_lines = []
services_lines = ["end\nservices\n"]
tr_set = set()
for srv in services:
data_id = str(srv.data_id).split(_SEP)
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
if tr_id not in tr_set:
transponder = "{}\n\t{}\n/\n".format(tr_id, srv.transponder)
tr_lines.append(transponder)
tr_set.add(tr_id)
# Services
services_lines.append("{}\n{}\n{}\n".format(srv.data_id, srv.service, srv.flags_cas))
tr_lines.sort()
lines.extend(tr_lines)
lines.extend(services_lines)
lines.append("end\n" + _END_LINE)
return lines
def parse_transponders(self, arg):
""" Parsing transponders. """
transponders = {}
for ar in arg:
tr = ar.replace("\n", "").split("\t")
if len(tr) == 2:
transponders[tr[0]] = tr[1]
return transponders
def split(self, itr, size):
""" Divide the iterable. """
srv = []
tmp = []
for i, line in enumerate(itr):
tmp.append(line)
if i % size == 0:
srv.append(tuple(tmp))
tmp.clear()
return srv
def get_services_lines(services):
""" Returns a list of strings from services for lamedb [v.4]. """
lines = [_HEADER.format(4), "\ntransponders\n"]
tr_lines = []
services_lines = ["end\nservices\n"]
tr_set = set()
for srv in services:
data_id = str(srv.data_id).split(_SEP)
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
if tr_id not in tr_set:
transponder = "{}\n\t{}\n/\n".format(tr_id, srv.transponder)
tr_lines.append(transponder)
tr_set.add(tr_id)
# Services
services_lines.append("{}\n{}\n{}\n".format(srv.data_id, srv.service, srv.flags_cas))
class LameDbWriter:
""" Writes the Enigma2 lamedb[5] file.
tr_lines.sort()
lines.extend(tr_lines)
lines.extend(services_lines)
lines.append("end\n" + _END_LINE)
Version 4 will be used instead of version 3!
"""
__slots__ = ["_path", "_fmt", "_services"]
return lines
def __init__(self, path, services, fmt=4):
self._path = path
self._fmt = fmt
self._services = services
def write(self):
if self._fmt == 4:
# Writing lamedb file ver.4
with open(self._path + _FILE_NAME, "w", encoding="utf-8") as file:
file.writelines(LameDbReader.get_services_lines(self._services))
elif self._fmt == 5:
self.write_to_lamedb5()
def write_to_lamedb5(path, services):
""" Writing lamedb5 file. """
lines = [_HEADER.format(5) + "\n"]
services_lines = []
tr_set = set()
def write_to_lamedb5(self):
""" Writing lamedb5 file. """
lines = [_HEADER.format(5) + "\n"]
services_lines = []
tr_set = set()
for srv in services:
data_id = str(srv.data_id).split(_SEP)
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
tr_set.add("t:{},{}\n".format(tr_id, srv.transponder.replace(" ", ":", 1)))
# Removing empty packages
flags = list(filter(lambda x: x != "p:", srv.flags_cas.split(",")))
flags = ",".join(flags)
flags = "," + flags if flags else ""
services_lines.append("s:{},\"{}\"{}\n".format(srv.data_id, srv.service, flags))
for srv in self._services:
data_id = str(srv.data_id).split(_SEP)
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
tr_set.add("t:{},{}\n".format(tr_id, srv.transponder.replace(" ", ":", 1)))
# Removing empty packages
flags = list(filter(lambda x: x != "p:", srv.flags_cas.split(",")))
flags = ",".join(flags)
flags = "," + flags if flags else ""
services_lines.append("s:{},\"{}\"{}\n".format(srv.data_id, srv.service, flags))
lines.extend(sorted(tr_set))
lines.extend(services_lines)
lines.append(_END_LINE)
lines.extend(sorted(tr_set))
lines.extend(services_lines)
lines.append(_END_LINE)
with open(path + "lamedb5", "w") as file:
file.writelines(lines)
def parse(path, version=4):
""" Parsing lamedb. """
if version == 4:
return parse_v4(path)
elif version == 5:
return parse_v5(path)
raise SyntaxError("Unsupported version of the format.")
def parse_v3(services, transponders, path):
""" Parsing version 3. """
for t in transponders:
tr = transponders[t].lower()
tr_type = tr[0:1]
if tr_type == "c":
tr += ":0:0:0"
elif tr_type == "t":
tr += ":0:0"
else:
tr_data = tr.split(_SEP)
len_data = len(tr_data)
if len_data == 6:
tr_data.append("0")
elif len_data == 9:
tr_data.insert(6, "0")
tr_data.append("0")
tr_data.append("2")
tr = _SEP.join(tr_data)
transponders[t] = tr
return parse_services(services, transponders, path)
def parse_v4(path):
""" Parsing version 4. """
with open(path + _FILE_NAME, "r", encoding="utf-8", errors="replace") as file:
try:
data = str(file.read())
except UnicodeDecodeError as e:
log("lamedb parse error: " + str(e))
else:
return get_services_list(data, path)
def get_services_list(data, path=None):
""" Returns a list of services from a string data representation. """
transponders, sep, services = data.partition("transponders") # 1 step
pattern = re.compile("/[34]/$")
match = re.search(pattern, transponders)
if not match:
msg = "lamedb parsing error: unsupported format."
log(msg)
raise SyntaxError(msg)
transponders, sep, services = services.partition("services") # 2 step
services, sep, _ = services.partition("\nend") # 3 step
if match.group() == "/3/":
return parse_v3(services.split("\n"), parse_transponders(transponders.split("/")), path)
return parse_services(services.split("\n"), parse_transponders(transponders.split("/")), path)
def parse_v5(path):
""" Parsing version 5. """
with open(path + "lamedb5", "r", encoding="utf-8", errors="replace") as file:
lns = file.readlines()
if lns and not lns[0].endswith("/5/\n"):
raise SyntaxError("lamedb v.5 parsing error: unsupported format.")
trs, srvs = {}, [""]
for line in lns:
if line.startswith("s:"):
srv_data = line.strip("s:").split(",", 2)
srv_data[1] = srv_data[1].strip("\"")
data_len = len(srv_data)
if data_len == 3:
srv_data[2] = srv_data[2].strip()
elif data_len == 2:
srv_data.append("p:")
srvs.extend(srv_data)
elif line.startswith("t:"):
tr, srv = line.split(",")
trs[tr.strip("t:")] = srv.strip().replace(":", " ", 1)
return parse_services(srvs, trs, path)
def parse_transponders(arg):
""" Parsing transponders. """
transponders = {}
for ar in arg:
tr = ar.replace("\n", "").split("\t")
if len(tr) == 2:
transponders[tr[0]] = tr[1]
return transponders
def parse_services(services, transponders, path):
""" Parsing services. """
services_list = []
blacklist = get_blacklist(path) if path else {}
srvs = split(services, 3)
if srvs[0][0] == "": # remove first empty element
srvs.remove(srvs[0])
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]
nid = data[3]
# For lamedb ver.3
is_v3 = False
if len(tid) < 4:
is_v3 = True
tid = "{:0>4}".format(tid)
data[2] = tid
if len(nid) < 4:
is_v3 = True
nid = "{:0>4}".format(nid)
data[3] = nid
if is_v3:
data[0] = "{:0>4}".format(data[0])
data_id = _SEP.join(data)
srv_type = int(data[4])
transponder_id = "{}:{}:{}".format(data[1], tid, nid)
transponder = transponders.get(transponder_id, None)
tid = tid.lstrip(sp).upper()
nid = nid.lstrip(sp).upper()
ssid = str(data[0]).lstrip(sp).upper()
onid = str(data[1]).lstrip(sp).upper()
# For comparison in bouquets. Needed in upper case!!!
fav_id = "{}:{}:{}:{}".format(ssid, tid, nid, onid)
picon_id = "1_0_{:X}_{}_{}_{}_{}_0_0_0.png".format(srv_type, ssid, tid, nid, onid)
s_id = "1:0:{:X}:{}:{}:{}:{}:0:0:0:".format(srv_type, ssid, tid, nid, onid)
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
locked = LOCKED_ICON if s_id in blacklist else None
package = list(filter(lambda x: x.startswith("p:"), all_flags))
package = package[0][2:] if package else ""
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 srv[1] if c.isprintable())
pol = None
fec = None
system = None
pos = None
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])
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):
""" Divide the iterable. """
srv = []
tmp = []
for i, line in enumerate(itr):
tmp.append(line)
if i % size == 0:
srv.append(tuple(tmp))
tmp.clear()
return srv
with open(self._path + "lamedb5", "w", encoding="utf-8") as file:
file.writelines(lines)
if __name__ == "__main__":

View File

@@ -3,9 +3,10 @@ import re
from enum import Enum
from urllib.parse import unquote, quote
from app.commons import log
from app.eparser.ecommons import BqServiceType, Service
from app.settings import SettingsType
from app.ui.uicommons import IPTV_ICON
from .ecommons import BqServiceType, Service
# url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group
NEUTRINO_FAV_ID_FORMAT = "{}::{}::{}::{}::{}::{}::{}::{}::{}::{}"
@@ -22,7 +23,7 @@ class StreamType(Enum):
E_SERVICE_HLS = "8739"
def parse_m3u(path, s_type, detect_encoding=True):
def parse_m3u(path, s_type, detect_encoding=True, params=None):
with open(path, "rb") as file:
data = file.read()
encoding = "utf-8"
@@ -37,28 +38,59 @@ def parse_m3u(path, s_type, detect_encoding=True):
encoding = enc.get("encoding", "utf-8")
aggr = [None] * 10
s_aggr = aggr[: -3]
services = []
groups = set()
counter = 0
marker_counter = 1
sid_counter = 1
name = None
picon = None
p_id = "1_0_1_0_0_0_0_0_0_0.png"
st = BqServiceType.IPTV.name
params = params or [0, 0, 0, 0]
for line in str(data, encoding=encoding, errors="ignore").splitlines():
if line.startswith("#EXTINF"):
name = line[1 + line.index(","):].strip()
inf, sep, line = line.partition(" ")
if not line:
line = inf
line, sep, name = line.rpartition(",")
data = re.split('"', line)
size = len(data)
if size < 3:
continue
d = {data[i].lower().strip(" ="): data[i + 1] for i in range(0, len(data) - 1, 2)}
picon = d.get("tvg-logo", None)
grp_name = d.get("group-title", None)
if grp_name not in groups:
groups.add(grp_name)
fav_id = MARKER_FORMAT.format(marker_counter, grp_name, grp_name)
marker_counter += 1
mr = Service(None, None, None, grp_name, None, None, None, BqServiceType.MARKER.name, None, None,
None, None, None, None, None, None, None, None, fav_id, None)
services.append(mr)
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)
fav_id = MARKER_FORMAT.format(counter, grp_name, grp_name)
counter += 1
mr = Service(None, None, None, grp_name, *aggr[0:3], BqServiceType.MARKER.name, *aggr, fav_id, None)
fav_id = MARKER_FORMAT.format(marker_counter, grp_name, grp_name)
marker_counter += 1
mr = Service(None, None, None, grp_name, None, None, None, BqServiceType.MARKER.name, None, None,
None, None, None, None, None, None, None, None, fav_id, None)
services.append(mr)
elif not line.startswith("#"):
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)
params[0] = sid_counter
sid_counter += 1
fav_id = get_fav_id(url, name, s_type, params)
if all((name, url, fav_id)):
srv = Service(None, None, IPTV_ICON, name, None, None, None, st, picon, p_id, None, None, None,
None, None, None, None, url, fav_id, None)
services.append(srv)
else:
log("*.m3u* parse error ['{}']: name[{}], url[{}], fav id[{}]".format(path, name, url, fav_id))
return services
@@ -86,12 +118,15 @@ def export_to_m3u(path, bouquet, s_type):
file.writelines(lines)
def get_fav_id(url, service_name, s_type):
def get_fav_id(url, service_name, settings_type, params=None, stream_type=None, s_type=1):
""" Returns fav id depending on the profile. """
if s_type is SettingsType.ENIGMA_2:
stream_type = StreamType.NONE_TS.value
return ENIGMA2_FAV_ID_FORMAT.format(stream_type, 1, 0, 0, 0, 0, quote(url), service_name, service_name, None)
elif s_type is SettingsType.NEUTRINO_MP:
if settings_type is SettingsType.ENIGMA_2:
stream_type = stream_type or StreamType.NONE_TS.value
params = params or (0, 0, 0, 0)
v1, v2, v3, v4 = params
return ENIGMA2_FAV_ID_FORMAT.format(stream_type, s_type, v1, v2, v3, v4, quote(url),
service_name, service_name, None)
elif settings_type is SettingsType.NEUTRINO_MP:
return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1)

View File

@@ -89,11 +89,8 @@ def parse_webtv(path, name, bq_type):
group = group.value if group else group
fav_id = NEUTRINO_FAV_ID_FORMAT.format(url, description, urlkey, account, usrname, psw, s_type, iconsrc,
iconsrc_b, group)
srv = BouquetService(name=title,
type=BqServiceType.IPTV,
data=fav_id,
num=0)
services.append(srv)
services.append(BouquetService(name=title, type=BqServiceType.IPTV, data=fav_id, num=0))
bouquet = Bouquet(name="default", type=bq_type, services=services, locked=None, hidden=None)
bouquets[2].append(bouquet)
@@ -125,14 +122,15 @@ def write_bouquet(file, bouquet):
root.appendChild(bq_elem)
for srv in bq.services:
f_data = srv.flags_cas.split(":")
tr_id, on, ssid = srv.fav_id.split(":")
srv_elem = doc.createElement("S")
srv_elem.setAttribute("i", ssid)
srv_elem.setAttribute("n", srv.service)
srv_elem.setAttribute("t", tr_id)
srv_elem.setAttribute("on", on)
srv_elem.setAttribute("s", srv.pos.replace(".", ""))
srv_elem.setAttribute("frq", srv.freq[:-3])
srv_elem.setAttribute("s", f_data[1])
srv_elem.setAttribute("frq", srv.freq)
srv_elem.setAttribute("l", "0") # temporary !!!
bq_elem.appendChild(srv_elem)

View File

@@ -1,5 +1,6 @@
from xml.dom.minidom import parse, Document
from app.commons import log
from ..ecommons import Service, POLARIZATION, FEC, SYSTEM, SERVICE_TYPE, PROVIDER
_FILE = "services.xml"
@@ -28,7 +29,7 @@ def write_services(path, services):
tr_atr = sat.split(":")
sat_elem = doc.createElement("sat")
sat_elem.setAttribute("name", tr_atr[0])
sat_elem.setAttribute("position", tr_atr[1].replace(".", ""))
sat_elem.setAttribute("position", tr_atr[1])
sat_elem.setAttribute("diseqc", tr_atr[2])
sat_elem.setAttribute("uncommited", tr_atr[3])
root.appendChild(sat_elem)
@@ -88,7 +89,6 @@ def parse_services(path):
if elem.hasAttributes():
sat_name = elem.attributes["name"].value
sat_pos = elem.attributes["position"].value
sat_pos = "{}.{}".format(sat_pos[:-1], sat_pos[-1:])
diseqc = elem.attributes.get("diseqc")
diseqc = diseqc.value if diseqc else diseqc
uncommited = elem.attributes.get("uncommited")
@@ -117,6 +117,15 @@ def parse_transponder(api, sat, sat_pos, services, tr_elem):
tr = "{}:{}:{}:{}:{}:{}:{}:{}:{}".format(tr_id, on, freq, inv, rate, fec, pol, mod, sys)
tr_id = tr_id.lstrip("0")
pol = POLARIZATION.get(pol)
# Formatting displayed values.
try:
freq = "{}".format(int(freq) // 1000)
rate = "{}".format(int(rate) // 1000)
sat_pos = int(sat_pos)
sat_pos = "{:0.1f}{}".format(abs(sat_pos / 10), "W" if sat_pos < 0 else "E")
except ValueError as e:
log("Neutrino parsing error [parse_transponder]: {}".format(e))
for srv_elem in tr_elem.getElementsByTagName("S"):
if srv_elem.hasAttributes():
@@ -141,27 +150,10 @@ def parse_transponder(api, sat, sat_pos, services, tr_elem):
data_id = "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}".format(api, srv_type, sys, num, f, v, a, p, pmt, tx, vt)
fav_id = "{}:{}:{}".format(tr_id, on.lstrip("0"), ssid.lstrip("0"))
picon_id = "{}{}{}.png".format(tr_id, on, ssid)
prv, st, = PROVIDER.get(int(on, 16)), SERVICE_TYPE.get(str(int(srv_type, 16)), SERVICE_TYPE.get("-2"))
srv = Service(flags_cas=sat,
transponder_type=None,
coded=None,
service=name,
locked=None,
hide=None,
package=PROVIDER.get(int(on, 16)),
service_type=SERVICE_TYPE.get(str(int(srv_type, 16))),
picon=None,
picon_id=picon_id,
ssid=ssid,
freq=freq,
rate=rate,
pol=POLARIZATION.get(pol),
fec=FEC.get(fec),
system=SYSTEM.get(sys),
pos=sat_pos,
data_id=data_id,
fav_id=fav_id,
transponder=tr)
srv = Service(sat, None, None, name, None, None, prv, st, None, picon_id, ssid, freq, rate, pol,
FEC.get(fec), SYSTEM.get(sys), sat_pos, data_id, fav_id, tr)
services.append(srv)

View File

@@ -53,9 +53,9 @@ def write_satellites(satellites, data_path):
transponder_child.setAttribute("frequency", tr.frequency)
transponder_child.setAttribute("symbol_rate", tr.symbol_rate)
transponder_child.setAttribute("polarization", get_key_by_value(POLARIZATION, tr.polarization))
transponder_child.setAttribute("fec_inner", get_key_by_value(FEC, tr.fec_inner))
transponder_child.setAttribute("system", get_key_by_value(SYSTEM, tr.system))
transponder_child.setAttribute("modulation", get_key_by_value(MODULATION, tr.modulation))
transponder_child.setAttribute("fec_inner", get_key_by_value(FEC, tr.fec_inner) or "0")
transponder_child.setAttribute("system", get_key_by_value(SYSTEM, tr.system) or "0")
transponder_child.setAttribute("modulation", get_key_by_value(MODULATION, tr.modulation) or "0")
if tr.pls_mode:
transponder_child.setAttribute("pls_mode", tr.pls_mode)
if tr.pls_code:
@@ -90,7 +90,6 @@ def parse_transponders(elem, sat_name):
atr["is_id"].value if "is_id" in atr else None)
except Exception as e:
message = "Error: can't parse transponder for '{}' satellite! {}".format(sat_name, repr(e))
print(message)
log(message)
else:
transponders.append(tr)

View File

@@ -5,16 +5,17 @@ import os
import sys
from enum import Enum, IntEnum
from functools import lru_cache
from pathlib import Path
from pprint import pformat
from textwrap import dedent
HOME_PATH = str(Path.home())
CONFIG_PATH = HOME_PATH + "/.config/demon-editor/"
SEP = os.sep
HOME_PATH = os.path.expanduser("~")
CONFIG_PATH = HOME_PATH + "{}.config{}demon-editor{}".format(SEP, SEP, SEP)
CONFIG_FILE = CONFIG_PATH + "config.json"
DATA_PATH = HOME_PATH + "/DemonEditor/data/"
DATA_PATH = HOME_PATH + "{}DemonEditor{}data{}".format(SEP, SEP, SEP)
IS_DARWIN = sys.platform == "darwin"
IS_WIN = sys.platform == "win32"
class Defaults(Enum):
@@ -30,19 +31,22 @@ class Defaults(Enum):
USE_COLORS = True
NEW_COLOR = "rgb(255,230,204)"
EXTRA_COLOR = "rgb(179,230,204)"
TOOLTIP_LOGO_SIZE = 96
LIST_PICON_SIZE = 32
FAV_CLICK_MODE = 0
PLAY_STREAMS_MODE = 1 if IS_DARWIN else 0
STREAM_LIB = "gst" if IS_WIN else "vlc"
PROFILE_FOLDER_DEFAULT = False
RECORDS_PATH = DATA_PATH + "records/"
RECORDS_PATH = DATA_PATH + "records{}".format(SEP)
ACTIVATE_TRANSCODING = False
ACTIVE_TRANSCODING_PRESET = "720p TV/device"
ACTIVE_TRANSCODING_PRESET = "720p TV{}device".format(SEP)
def get_settings():
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:
with open(CONFIG_FILE, "r", encoding="utf-8") as config_file:
return json.load(config_file)
@@ -76,18 +80,18 @@ def get_default_transcoding_presets():
def write_settings(config):
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
with open(CONFIG_FILE, "w") as config_file:
with open(CONFIG_FILE, "w", encoding="utf-8") as config_file:
json.dump(config, config_file, indent=" ")
def set_local_paths(settings, profile_name, data_path=DATA_PATH, use_profile_folder=False):
settings["data_local_path"] = "{}{}/".format(data_path, profile_name)
settings["data_local_path"] = "{}{}{}".format(data_path, profile_name, SEP)
if use_profile_folder:
settings["picons_local_path"] = "{}{}/{}/".format(data_path, profile_name, "picons")
settings["backup_local_path"] = "{}{}/{}/".format(data_path, profile_name, "backup")
settings["picons_local_path"] = "{}{}{}{}{}".format(data_path, profile_name, SEP, "picons", SEP)
settings["backup_local_path"] = "{}{}{}{}{}".format(data_path, profile_name, SEP, "backup", SEP)
else:
settings["picons_local_path"] = "{}{}/{}/".format(data_path, "picons", profile_name)
settings["backup_local_path"] = "{}{}/{}/".format(data_path, "backup", profile_name)
settings["picons_local_path"] = "{}{}{}{}{}".format(data_path, "picons", SEP, profile_name, SEP)
settings["backup_local_path"] = "{}{}{}{}{}".format(data_path, "backup", SEP, profile_name, SEP)
class SettingsType(IntEnum):
@@ -97,28 +101,29 @@ class SettingsType(IntEnum):
def get_default_settings(self):
""" Returns default settings for current type """
if self is self.ENIGMA_2:
if self is SettingsType.ENIGMA_2:
return {"setting_type": self.value,
"host": "127.0.0.1", "port": "21", "timeout": 5,
"user": "root", "password": "root",
"http_port": "80", "http_timeout": 5, "http_use_ssl": False,
"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/",
"satellites_xml_path": "/etc/tuxbox/", "data_local_path": "{}enigma2{}".format(DATA_PATH, SEP),
"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:
"picons_local_path": "{}enigma2{}picons{}".format(DATA_PATH, SEP, SEP),
"backup_local_path": "{}enigma2{}backup{}".format(DATA_PATH, SEP, SEP)}
elif self is SettingsType.NEUTRINO_MP:
return {"setting_type": self,
"host": "127.0.0.1", "port": "21", "timeout": 5,
"user": "root", "password": "root",
"http_port": "80", "http_timeout": 2, "http_use_ssl": False,
"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/",
"satellites_xml_path": "/var/tuxbox/config/",
"data_local_path": "{}neutrino{}".format(DATA_PATH, SEP),
"picons_path": "/usr/share/tuxbox/neutrino/icons/logo/",
"picons_local_path": DATA_PATH + "neutrino/picons/",
"backup_local_path": DATA_PATH + "neutrino/backup/"}
"picons_local_path": "{}neutrino{}picons{}".format(DATA_PATH, SEP, SEP),
"backup_local_path": "{}neutrino{}backup{}".format(DATA_PATH, SEP, SEP)}
class SettingsException(Exception):
@@ -132,7 +137,7 @@ class SettingsReadException(SettingsException):
class PlayStreamsMode(IntEnum):
""" Behavior mode when opening streams. """
BUILT_IN = 0
VLC = 1
WINDOW = 1
M3U = 2
@@ -180,7 +185,7 @@ class Settings:
self._cp_settings[k] = v
def_path = self.default_data_path
def_path += "enigma2/" if self.setting_type is SettingsType.ENIGMA_2 else "neutrino/"
def_path += "enigma2{}".format(SEP) if self.setting_type is SettingsType.ENIGMA_2 else "neutrino{}".format(SEP)
set_local_paths(self._cp_settings, self._current_profile, def_path, self.profile_folder_is_default)
if force_write:
@@ -436,6 +441,14 @@ class Settings:
def play_streams_mode(self, value):
self._settings["play_streams_mode"] = value
@property
def stream_lib(self):
return self._settings.get("stream_lib", Defaults.STREAM_LIB.value)
@stream_lib.setter
def stream_lib(self, value):
self._settings["stream_lib"] = value
# *********** EPG ************ #
@property
@@ -513,30 +526,6 @@ class Settings:
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)
@@ -581,6 +570,54 @@ class Settings:
# *********** Appearance *********** #
@property
def list_font(self):
return self._settings.get("list_font", "")
@list_font.setter
def list_font(self, value):
self._settings["list_font"] = value
@property
def list_picon_size(self):
return self._settings.get("list_picon_size", Defaults.LIST_PICON_SIZE.value)
@list_picon_size.setter
def list_picon_size(self, value):
self._settings["list_picon_size"] = value
@property
def tooltip_logo_size(self):
return self._settings.get("tooltip_logo_size", Defaults.TOOLTIP_LOGO_SIZE.value)
@tooltip_logo_size.setter
def tooltip_logo_size(self, value):
self._settings["tooltip_logo_size"] = 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 dark_mode(self):
return self._settings.get("dark_mode", False)
@@ -624,7 +661,7 @@ class Settings:
@property
@lru_cache(1)
def themes_path(self):
return "{}/.themes/".format(HOME_PATH)
return "{}{}.themes{}".format(HOME_PATH, SEP, SEP)
@property
def icon_theme(self):
@@ -637,7 +674,7 @@ class Settings:
@property
@lru_cache(1)
def icon_themes_path(self):
return "{}/.icons/".format(HOME_PATH)
return "{}{}.icons{}".format(HOME_PATH, SEP, SEP)
@property
def is_darwin(self):

View File

@@ -1,59 +1,359 @@
import os
import subprocess
import sys
from abc import ABC, abstractmethod
from datetime import datetime
from enum import Enum
from urllib.request import urlopen
from app.commons import run_task, log, _DATE_FORMAT
from app.settings import PlayStreamsMode
class Player:
class Player(ABC):
""" Base player class. Also used as a factory. """
@abstractmethod
def get_play_mode(self):
pass
@abstractmethod
def play(self, mrl=None):
pass
@abstractmethod
def stop(self):
pass
@abstractmethod
def pause(self):
pass
@abstractmethod
def set_time(self, time):
pass
@abstractmethod
def release(self):
pass
@abstractmethod
def is_playing(self):
pass
@abstractmethod
def get_instance(self, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
pass
def get_window_handle(self, widget):
""" Returns the identifier [pointer] for the window.
Based on gtkvlc.py[get_window_pointer] example from here:
https://github.com/oaubert/python-vlc/tree/master/examples
"""
if sys.platform == "linux":
return widget.get_window().get_xid()
else:
is_darwin = sys.platform == "darwin"
try:
import ctypes
libgdk = ctypes.CDLL("libgdk-3.0.dylib" if is_darwin else "libgdk-3-0.dll")
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
else:
# https://gitlab.gnome.org/GNOME/pygobject/-/issues/112
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object]
gpointer = ctypes.pythonapi.PyCapsule_GetPointer(widget.get_window().__gpointer__, None)
get_pointer = libgdk.gdk_quartz_window_get_nsview if is_darwin else libgdk.gdk_win32_window_get_handle
get_pointer.restype = ctypes.c_void_p
get_pointer.argtypes = [ctypes.c_void_p]
return get_pointer(gpointer)
def get_video_widget(self, widget):
from gi.repository import Gtk, Gdk
area = Gtk.DrawingArea(visible=True)
area.connect("draw", self.on_drawing_area_draw)
area.set_events(Gdk.ModifierType.BUTTON1_MASK)
widget.add(area)
return area
def on_drawing_area_draw(self, widget, cr):
""" Used for black background drawing in the player drawing area. """
cr.set_source_rgb(0, 0, 0)
cr.paint()
@staticmethod
def make(name, mode, widget, buf_cb=None, position_cb=None, error_cb=None, playing_cb=None):
""" Factory method. We will not use a separate factory to return a specific implementation.
@param name: implementation name.
@param mode: current player mode [Built-in or windowed].
@param widget: parent of video widget.
@param buf_cb: buffering callback.
@param position_cb: time (position) callback.
@param error_cb: error callback.
@param playing_cb: playing state callback.
Throws a NameError if there is no implementation for the given name.
"""
if name == "mpv":
return MpvPlayer.get_instance(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
elif name == "gst":
return GstPlayer.get_instance(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
elif name == "vlc":
return VlcPlayer.get_instance(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
else:
raise NameError("There is no such [{}] implementation.".format(name))
class MpvPlayer(Player):
""" Simple wrapper for MPV media player.
Uses python-mvp [https://github.com/jaseg/python-mpv].
"""
__INSTANCE = None
def __init__(self, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
try:
from app.tools import mpv
self._player = mpv.MPV(wid=str(self.get_window_handle(self.get_video_widget(widget))))
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
raise ImportError("No libmpv is found. Check that it is installed!")
else:
self._mode = mode
self._is_playing = False
@self._player.event_callback(mpv.MpvEventID.FILE_LOADED)
def on_open(event):
log("Starting playback...")
playing_cb()
@self._player.event_callback(mpv.MpvEventID.END_FILE)
def on_end(event):
event = event.get("event", {})
if event.get("reason", mpv.MpvEventEndFile.ERROR) == mpv.MpvEventEndFile.ERROR:
log("Stream playback error: {}".format(event.get("error", mpv.ErrorCode.GENERIC)))
error_cb()
@classmethod
def get_instance(cls, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
if not cls.__INSTANCE:
cls.__INSTANCE = MpvPlayer(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
return cls.__INSTANCE
def get_play_mode(self):
return self._mode
@run_task
def play(self, mrl=None):
if not mrl:
return
self._player.play(mrl)
self._is_playing = True
@run_task
def stop(self):
self._player.stop()
self._is_playing = True
def pause(self):
pass
def set_time(self, time):
pass
@run_task
def release(self):
self._player.terminate()
self.__INSTANCE = None
def is_playing(self):
return self._is_playing
class GstPlayer(Player):
""" Simple wrapper for GStreamer playbin. """
__INSTANCE = None
def __init__(self, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
try:
import gi
gi.require_version("Gst", "1.0")
gi.require_version("GstVideo", "1.0")
from gi.repository import Gst, GstVideo
# Initialization of GStreamer.
Gst.init(sys.argv)
except (OSError, ValueError) as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
raise ImportError("No GStreamer is found. Check that it is installed!")
else:
self._error_cb = error_cb
self._playing_cb = playing_cb
self.STATE = Gst.State
self.STAT_RETURN = Gst.StateChangeReturn
self._mode = mode
self._is_playing = False
self._player = Gst.ElementFactory.make("playbin", "player")
# Initialization of the playback widget.
vid_widget = self.get_video_widget(widget)
widget.add(vid_widget)
vid_widget.show()
self._player.set_window_handle(self.get_window_handle(vid_widget))
bus = self._player.get_bus()
bus.add_signal_watch()
bus.connect("message::error", self.on_error)
bus.connect("message::state-changed", self.on_state_changed)
bus.connect("message::eos", self.on_eos)
@classmethod
def get_instance(cls, mode, widget, buf_cb=None, position_cb=None, error_cb=None, playing_cb=None):
if not cls.__INSTANCE:
cls.__INSTANCE = GstPlayer(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
return cls.__INSTANCE
def get_play_mode(self):
return self._mode
def play(self, mrl=None):
self._player.set_state(self.STATE.READY)
if not mrl:
return
self._player.set_property("uri", mrl)
log("Setting the URL for playback: {}".format(mrl))
ret = self._player.set_state(self.STATE.PLAYING)
if ret == self.STAT_RETURN.FAILURE:
log("ERROR: Unable to set the 'PLAYING' state for '{}'.".format(mrl))
else:
self._is_playing = True
def stop(self):
log("Stop playback...")
self._player.set_state(self.STATE.READY)
self._is_playing = False
def pause(self):
self._player.set_state(self.STATE.PAUSED)
def set_time(self, time):
pass
@run_task
def release(self):
self._is_playing = False
self._player.set_state(self.STATE.NULL)
self.__INSTANCE = None
def set_mrl(self, mrl):
self._player.set_property("uri", mrl)
def is_playing(self):
return self._is_playing
def on_error(self, bus, msg):
err, dbg = msg.parse_error()
log(err)
self._error_cb()
def on_state_changed(self, bus, msg):
if not msg.src == self._player:
# Not from the player.
return
old_state, new_state, pending = msg.parse_state_changed()
if new_state is self.STATE.PLAYING:
log("Starting playback...")
self._playing_cb()
self.get_stream_info()
def on_eos(self, bus, msg):
""" Called when an end-of-stream message appears. """
self._player.set_state(self.STATE.READY)
self._is_playing = False
def get_stream_info(self):
log("Getting stream info...")
nr_video = self._player.get_property("n-video")
for i in range(nr_video):
# Retrieve the stream's video tags.
tags = self._player.emit("get-video-tags", i)
if tags:
_, cod = tags.get_string("video-codec")
log("Video codec: {}".format(cod or "unknown"))
nr_audio = self._player.get_property("n-audio")
for i in range(nr_audio):
# Retrieve the stream's video tags.
tags = self._player.emit("get-audio-tags", i)
if tags:
_, cod = tags.get_string("audio-codec")
log("Audio codec: {}".format(cod or "unknown"))
class VlcPlayer(Player):
""" Simple wrapper for VLC media player.
Uses python-vlc [https://github.com/oaubert/python-vlc].
"""
__VLC_INSTANCE = None
__PLAY_STREAMS_MODE = PlayStreamsMode.BUILT_IN
def __init__(self, rewind_callback, position_callback, error_callback, playing_callback):
def __init__(self, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
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()
except (OSError, AttributeError) as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
raise ImportError("No VLC is found. Check that it is installed!")
else:
self._mode = mode
self._is_playing = False
ev_mgr = self._player.event_manager()
if rewind_callback:
if buf_cb:
# TODO look other EventType options
ev_mgr.event_attach(EventType.MediaPlayerBuffering,
lambda et, p: rewind_callback(p.get_media().get_duration()),
lambda et, p: buf_cb(p.get_media().get_duration()),
self._player)
if position_callback:
if position_cb:
ev_mgr.event_attach(EventType.MediaPlayerTimeChanged,
lambda et, p: position_callback(p.get_time()),
lambda et, p: position_cb(p.get_time()),
self._player)
if error_callback:
if error_cb:
ev_mgr.event_attach(EventType.MediaPlayerEncounteredError,
lambda et, p: error_callback(),
lambda et, p: error_cb(),
self._player)
if playing_callback:
if playing_cb:
ev_mgr.event_attach(EventType.MediaPlayerPlaying,
lambda et, p: playing_callback(),
lambda et, p: playing_cb(),
self._player)
self.init_video_widget(widget)
@classmethod
def get_instance(cls, rewind_callback=None, position_callback=None, error_callback=None, playing_callback=None):
def get_instance(cls, mode, widget, buf_cb=None, position_cb=None, error_cb=None, playing_cb=None):
if not cls.__VLC_INSTANCE:
cls.__VLC_INSTANCE = Player(rewind_callback, position_callback, error_callback, playing_callback)
cls.__VLC_INSTANCE = VlcPlayer(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
return cls.__VLC_INSTANCE
@staticmethod
def get_play_mode():
return Player.__PLAY_STREAMS_MODE
def get_play_mode(self):
return self._mode
@run_task
def play(self, mrl=None):
@@ -80,29 +380,7 @@ class Player:
self._is_playing = False
self._player.stop()
self._player.release()
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))
self.__VLC_INSTANCE = None
def set_mrl(self, mrl):
self._player.set_mrl(mrl)
@@ -110,94 +388,14 @@ class Player:
def is_playing(self):
return self._is_playing
def set_full_screen(self, full):
self._player.set_fullscreen(full)
class HttpPlayer:
""" Simple wrapper for VLC media player to interact over http. """
__VLC_INSTANCE = None
__PLAY_STREAMS_MODE = PlayStreamsMode.VLC
class Commands(Enum):
STATUS = "http://127.0.0.1:{}/requests/status.xml"
PLAY = "http://127.0.0.1:{}/requests/status.xml?command=in_play&input={}"
STOP = "http://127.0.0.1:{}/requests/status.xml?command=pl_stop"
CLEAR = "http://127.0.0.1:{}/requests/status.xml?command=pl_empty"
def __init__(self, exe, port, is_darwin):
from concurrent.futures import ThreadPoolExecutor as PoolExecutor
self._executor = PoolExecutor(max_workers=1)
self._cmd = [exe, "--no-stats", "--verbose=-1", "--extraintf", "http", "--http-port", port, "--quiet"]
if not is_darwin:
self._cmd.append("--one-instance")
self._p = None
self._state = None
self._port = port
@classmethod
def get_instance(cls, settings):
if not cls.__VLC_INSTANCE:
import shutil
is_darwin = settings.is_darwin
# TODO Add options[vlc_exe and port] to the settings!
exe = "/Applications/VLC.app/Contents/MacOS/VLC" if is_darwin else "/usr/bin/vlc"
if shutil.which(exe) is None:
raise ImportError
cls.__VLC_INSTANCE = HttpPlayer(exe=exe, port=str(9090), is_darwin=is_darwin)
return cls.__VLC_INSTANCE
@staticmethod
def get_play_mode():
return HttpPlayer.__PLAY_STREAMS_MODE
@run_task
def play(self, mrl=None):
if not self._p or self._p and self._p.poll() is not None:
self._p = subprocess.Popen(self._cmd + [mrl], preexec_fn=os.setsid)
self._p.communicate()
def init_video_widget(self, widget):
video_widget = self.get_video_widget(widget)
if sys.platform == "linux":
self._player.set_xwindow(video_widget.get_window().get_xid())
elif sys.platform == "darwin":
self._player.set_nsobject(self.get_window_handle(video_widget))
else:
self._executor.submit(self.open_command, self.Commands.CLEAR)
self._executor.submit(self.open_command, self.Commands.PLAY, mrl)
def open_command(self, command, url=None):
if command is self.Commands.PLAY:
url = self.Commands.PLAY.value.format(self._port, url)
else:
url = command.value.format(self._port)
try:
with urlopen(url, timeout=5) as f:
self._state = command
except Exception as e:
log("{}[open_command, {}] error: {}".format(__class__.__name__, command, e))
def stop(self):
if self._state is self.Commands.PLAY:
self._executor.submit(self.open_command, self.Commands.STOP)
def pause(self):
pass
def set_time(self, time):
pass
@run_task
def release(self):
if self._p and self._p.poll() is None:
import signal
# Good explanation here: https://stackoverflow.com/a/4791612
os.killpg(os.getpgid(self._p.pid), signal.SIGTERM)
def is_playing(self):
return self._state is self.Commands.PLAY
def set_full_screen(self, full):
pass
log("Video widget initialization error: platform '{}' is not supported. ".format(sys.platform))
class Recorder:

1941
app/tools/mpv.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -5,18 +5,22 @@ import shutil
from collections import namedtuple
from html.parser import HTMLParser
import requests
from app.commons import run_task, log
from app.settings import SettingsType
from .satellites import _HEADERS
_ENIGMA2_PICON_KEY = "{:X}:{:X}:{}"
_NEUTRINO_PICON_KEY = "{:x}{:04x}{:04x}.png"
Provider = namedtuple("Provider", ["logo", "name", "pos", "url", "on_id", "ssid", "single", "selected"])
Picon = namedtuple("Picon", ["ref", "ssid", "v_pid"])
Picon = namedtuple("Picon", ["ref", "ssid"])
class PiconsParser(HTMLParser):
""" Parser for package html page. (https://www.lyngsat.com/packages/*provider-name*.html) """
_BASE_URL = "https://www.lyngsat.com"
def __init__(self, entities=False, separator=' ', single=None):
@@ -58,19 +62,16 @@ class PiconsParser(HTMLParser):
row = self._current_row
ln = len(row)
if self._single and ln == 4 and row[0].startswith("../../logo/"):
self.picons.append(Picon(row[0].strip("../"), "0", "0"))
if self._single and ln == 4 and row[0].startswith("/logo/"):
self.picons.append(Picon(row[0].strip(), "0"))
else:
if 9 < ln < 13:
if ln > 8:
url = None
if row[0].startswith("../logo/"):
url = row[0]
elif row[1].startswith("../logo/"):
url = row[1]
if row[2].startswith("/logo/"):
url = row[2]
ssid = row[-4]
if url and len(ssid) > 2:
self.picons.append(Picon(url, ssid, row[-3]))
if url and row[0].isdigit():
self.picons.append(Picon(url, row[0]))
self._current_row = []
@@ -78,37 +79,47 @@ class PiconsParser(HTMLParser):
pass
@staticmethod
def parse(open_path, picons_path, tmp_path, provider, picon_ids, s_type=SettingsType.ENIGMA_2):
if not os.path.isfile(open_path):
log("PiconsParser error [parse]. No such file or directory: {}".format(open_path))
def parse(provider, picons_path, picon_ids, s_type=SettingsType.ENIGMA_2):
""" Returns tuple(url, picon file name) list. """
req = requests.get(provider.url, timeout=5)
if req.status_code == 200:
logo_data = req.text
else:
log("Provider picons downloading error: {} {}".format(provider.url, req.reason))
return
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")
pos = int("".join(c for c in pos if c.isdigit()))
# For negative (West) positions 3600 - numeric position value!!!
if neg_pos:
pos = 3600 - pos
parser = PiconsParser(single=single)
parser.reset()
parser.feed(f.read())
picons = parser.picons
if picons:
os.makedirs(picons_path, exist_ok=True)
for p in picons:
try:
if single:
on_id, freq = on_id.strip().split("::")
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, 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:
msg = "Picons format parse error: {}".format(p) + "\n" + str(e)
log(msg)
on_id, pos, ssid, single = provider.on_id, provider.pos, provider.ssid, provider.single
neg_pos = pos.endswith("W")
pos = int("".join(c for c in pos if c.isdigit()))
# For negative (West) positions 3600 - numeric position value!!!
if neg_pos:
pos = 3600 - pos
parser = PiconsParser(single=provider.single)
parser.reset()
parser.feed(logo_data)
picons = parser.picons
picons_data = []
if picons:
for p in picons:
try:
if single:
on_id, freq = on_id.strip().split("::")
namespace = "{:X}{:X}".format(int(pos), int(freq))
else:
namespace = "{:X}0000".format(int(pos))
if single and not ssid.isdigit():
ssid = "".join(c for c in ssid if c.isdigit()) or "0"
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))
picons_data.append(("{}{}".format(PiconsParser._BASE_URL, p.ref), p_name))
except (TypeError, ValueError) as e:
msg = "Picons format parse error: {}".format(p) + "\n" + str(e)
log(msg)
return picons_data
@staticmethod
def format(ssid, on_id, namespace, picon_ids, s_type):
@@ -127,7 +138,8 @@ 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]+")
_DOMAINS = {"/tvchannels/", "/radiochannels/", "/packages/"}
_DOMAINS = {"/tvchannels/", "/radiochannels/", "/packages/", "/logo/"}
_BASE_URL = "https://www.lyngsat.com"
def __init__(self, entities=False, separator=' '):
@@ -155,7 +167,7 @@ class ProviderParser(HTMLParser):
if tag == 'tr':
self._is_th = True
if tag == "img":
if attrs[0][1].startswith("logo/"):
if attrs[0][1].startswith("/logo/"):
self._current_row.append(attrs[0][1])
if tag == "a":
url = attrs[0][1]
@@ -187,41 +199,47 @@ class ProviderParser(HTMLParser):
self._current_row.append(final_cell)
self._current_cell = []
elif tag == 'tr':
r = self._current_row
row = self._current_row
# Satellite position
if not self._positon:
pos = re.findall(self._POSITION_PATTERN, str(r))
pos = re.findall(self._POSITION_PATTERN, str(row))
if pos:
self._positon = "".join(c for c in str(pos) if c.isdigit() or c in ".EW")
len_row = len(r)
len_row = len(row)
if len_row > 2:
m = self._TRANSPONDER_FREQUENCY_PATTERN.match(r[1])
m = self._TRANSPONDER_FREQUENCY_PATTERN.match(row[1])
if m:
self._freq = m.group().split()[0]
if len_row == 12:
if len_row == 14:
# Providers
name = r[5]
name = row[6]
self._prv_names.add(name)
m = self._ONID_TID_PATTERN.match(str(r[-2]))
m = self._ONID_TID_PATTERN.match(str(row[9]))
if m:
on_id, tid = m.group().split("-")
if on_id not in self._ids:
r[-2] = on_id
row[-2] = on_id
self._ids.add(on_id)
r[0] = self._positon
row[0] = self._positon
if name + on_id not in self._prv_names:
self._prv_names.add(name + on_id)
self.rows.append(Provider(logo=r[2], name=name, pos=self._positon, url=r[6], on_id=on_id,
logo_data = None
req = requests.get(self._BASE_URL + row[3], timeout=5)
if req.status_code == 200:
logo_data = req.content
else:
log("Downloading provider logo error: {}".format(req.reason))
self.rows.append(Provider(logo=logo_data, name=name, pos=self._positon, url=row[5], on_id=on_id,
ssid=None, single=False, selected=True))
elif 6 < len_row < 10:
elif 6 < len_row < 14:
# Single services
name, url, ssid = None, None, None
if r[0].startswith("http"):
name, url, ssid = r[1], r[0], r[4]
elif r[1].startswith("http"):
name, url, ssid = r[2], r[1], r[5]
if row[0].startswith("http"):
name, url, ssid = row[1], row[0], row[0]
elif row[1].startswith("http"):
name, url, ssid = row[2], row[1], row[0]
if name and url:
on_id = "{}::{}".format(self._on_id if self._on_id else "1", self._freq)
@@ -237,14 +255,51 @@ class ProviderParser(HTMLParser):
super().reset()
def parse_providers(open_path):
def parse_providers(url):
""" Returns a list of providers sorted by logo [single channels after providers]. """
parser = ProviderParser()
parser.reset()
with open(open_path, encoding="utf-8", errors="replace") as f:
parser.feed(f.read())
request = requests.get(url=url, headers=_HEADERS)
if request.status_code == 200:
parser.feed(request.text)
else:
log("Parse providers error [{}]: {}".format(url, request.reason))
return parser.rows
def srt(p):
if p.logo is None:
return 1
return 0
providers = parser.rows
providers.sort(key=srt)
return providers
def download_picon(src_url, dest_path, callback):
""" Downloads and saves the picon to file. """
err_msg = "Picon download error: {} [{}]"
timeout = (3, 5) # connect and read timeouts
if callback:
callback("Downloading: {}.\n".format(os.path.basename(dest_path)))
req = requests.get(src_url, timeout=timeout, stream=True)
if req.status_code != 200:
err_msg = err_msg.format(src_url, req.reason)
log(err_msg)
if callback:
callback(err_msg + "\n")
else:
try:
with open(dest_path, "wb") as f:
for chunk in req:
f.write(chunk)
except OSError as e:
err_msg = "Saving picon [{}] error: {}".format(dest_path, e)
log(err_msg)
if callback:
callback(err_msg + "\n")
@run_task

View File

@@ -14,13 +14,14 @@ from app.eparser import Satellite, Transponder, is_transponder_valid
from app.eparser.ecommons import (PLS_MODE, get_key_by_value, FEC, SYSTEM, POLARIZATION, MODULATION, SERVICE_TYPE,
Service, CAS)
_HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/69.0"}
_HEADERS = {"User-Agent": "Mozilla/5.0 (Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0"}
class SatelliteSource(Enum):
FLYSAT = ("https://www.flysat.com/satlist.php",)
LYNGSAT = ("https://www.lyngsat.com/asia.html", "https://www.lyngsat.com/europe.html",
"https://www.lyngsat.com/atlantic.html", "https://www.lyngsat.com/america.html")
KINGOFSAT = ("https://en.kingofsat.net/satellites.php",)
@staticmethod
def get_sources(src):
@@ -96,7 +97,9 @@ class SatellitesParser(HTMLParser):
if tag == "tr":
self._is_th = True
if tag == "a":
self._current_row.append(attrs[0][1])
for atr in attrs:
if atr[0] == "href":
self._current_row.append(atr[1])
def handle_data(self, data):
""" Save content to a cell """
@@ -182,6 +185,11 @@ class SatellitesParser(HTMLParser):
elif r_len == 5:
sats.append((row[2], current_pos, row[3], base_url + row[1], False))
return sats
elif source is SatelliteSource.KINGOFSAT:
def get_sat(r):
return r[3], self.parse_position(r[1]), None, r[0], False
return list(map(get_sat, filter(lambda x: len(x) == 17, self._rows)))
def get_satellite(self, sat):
pos = sat[1]
@@ -201,16 +209,29 @@ class SatellitesParser(HTMLParser):
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=_HEADERS)
reason = request.reason
trs = []
if reason == "OK":
self.feed(request.text)
if self._source is SatelliteSource.FLYSAT:
self.get_transponders_for_fly_sat(trs)
elif self._source is SatelliteSource.LYNGSAT:
self.get_transponders_for_lyng_sat(trs)
url = sat_url
if self._source is SatelliteSource.FLYSAT:
url = "https://www.flysat.com/" + sat_url
elif self._source is SatelliteSource.KINGOFSAT:
url = "https://en.kingofsat.net/" + sat_url
try:
request = requests.get(url=url, headers=_HEADERS)
except requests.exceptions.ConnectionError as e:
log("Getting transponders error: {}".format(e))
else:
if request.status_code == 200:
self.feed(request.text)
if self._source is SatelliteSource.FLYSAT:
self.get_transponders_for_fly_sat(trs)
elif self._source is SatelliteSource.LYNGSAT:
self.get_transponders_for_lyng_sat(trs)
elif self._source is SatelliteSource.KINGOFSAT:
self.get_transponders_for_king_of_sat(trs)
else:
log("SatellitesParser [get transponders] error: {} {}".format(url, request.reason))
return sorted(trs, key=lambda x: int(x.frequency))
@@ -266,42 +287,54 @@ class SatellitesParser(HTMLParser):
trs.extend(n_trs)
def get_transponders_for_lyng_sat(self, trs):
""" Parsing transponders for LyngSat """
""" Parsing transponders for LyngSat. """
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)
sr_fec_pattern = re.compile(r"(DVB-S[2]?)\s+(.+PSK)?.*?(\d+)\s+(\d/\d)\s*(?:T2-MI\s+PLP\s+(\d+))?.*")
zeros = "000"
pls_modes = {v: k for k, v in PLS_MODE.items()}
pls_mode, pls_code, pls_id = None, None, None
for r in filter(lambda x: len(x) > 8, self._rows):
for frq in r[1], r[2], r[3]:
for row in filter(lambda x: len(x) > 8, self._rows):
for frq in row[1], row[2], row[3]:
freq = re.match(frq_pol_pattern, frq)
if freq:
break
if not freq:
continue
frq, pol = freq.group(1), freq.group(2)
sr_fec = re.match(sr_fec_pattern, r[-3])
srf = " ".join(row[3:5])
sr_fec = re.search(sr_fec_pattern, srf)
if not sr_fec:
continue
sr, fec, mod = sr_fec.group(1), sr_fec.group(2), sr_fec.group(3)
sys, mod, sr, fec = sr_fec.group(1), sr_fec.group(2), sr_fec.group(3), sr_fec.group(4)
mod = mod.strip() if mod else "Auto"
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)
pls_id = sr_fec.group(5)
tr = Transponder(frq + zeros, sr + zeros, pol, fec, sys, mod, pls_mode, pls_code, pls_id)
if is_transponder_valid(tr):
trs.append(tr)
def get_transponders_for_king_of_sat(self, trs):
""" Getting transponders for KingOfSat source.
Since the *.ini file contains incomplete information, it is not used.
"""
zeros = "000"
pos_pat = re.compile(r".*?(\d+\.\d°[EW]).*")
pat = re.compile(
r"(\d+).00\s+([RLHV])\s+(DVB-S[2]?)\s+(?:T2-MI, PLP (\d+)\s+)?(.*PSK).*?(?:Stream\s+(\d+))?\s+(\d+)\s+(\d+/\d+)$")
for row in filter(lambda r: len(r) == 16 and pos_pat.match(r[0]), self._rows):
res = pat.search(" ".join((row[0], row[2], row[3], row[8], row[9], row[10])))
if res:
freq, sr, pol, fec, sys = res.group(1), res.group(7), res.group(2), res.group(8), res.group(3)
mod, pls_id, pls_code = res.group(5), res.group(4), res.group(6)
tr = Transponder(freq + zeros, sr + zeros, pol, fec, sys, mod, None, pls_code, pls_id)
if is_transponder_valid(tr):
trs.append(tr)
class ServicesParser(HTMLParser):
""" Services parser for LYNGSAT source. """
@@ -313,8 +346,9 @@ class ServicesParser(HTMLParser):
self._S_TYPES = {"": "2", "MPEG-2 SD": "1", "SD": "1", "MPEG-4 SD": "22", "HEVC SD": "22", "MPEG-4 HD": "25",
"MPEG-4 HD 1080": "25", "MPEG-4 HD 720": "25", "HEVC HD": "25", "HEVC UHD": "31",
"HEVC UHD 4K": "31"}
self._TR_PAT = re.compile(r"(DVB-S[2]?)/?(.*PSK)?\s+SR\s+(\d+)\s+FEC\s+(\d/\d).*ONID/TID:\s+(\d+)/(\d+)\s+.*")
self._PTR_PAT = re.compile(r".*?(\d+\.\d°[EW]):\s+(\d+)\s+([RLHV]).*")
self._TR_PAT = re.compile(
r".*?(\d+)\s+([RLHV]).*(DVB-S[2]?)/?(.*PSK)?\s(T2-MI)?\s?SR-FEC:\s(\d+)-(\d/\d)\s+.*ONID-TID:\s+(\d+)-(\d+).*")
self._POS_PAT = re.compile(r".*?(\d+\.\d°[EW]).*")
self._TR = "s {}000:{}000:{}:{}:{}:{}:{}:{}"
self._S2_TR = "{}:{}:{}:{}"
@@ -406,26 +440,33 @@ class ServicesParser(HTMLParser):
else:
pos, freq, sr, fec, pol, namespace, tid, nid = sat_position or 0, 0, 0, 0, 0, 0, 0, 0
sys = "DVB-S"
tr_found = False
pos_found = False
tr = None
# Transponder
for r in filter(lambda x: x and len(x) == 2, self._rows):
for r in filter(lambda x: x and 6 < len(x) < 9, self._rows):
if not pos_found:
pos_tr = re.match(self._PTR_PAT, r[1].text)
if pos_tr:
if not sat_position:
pos = int(SatellitesParser.get_position(
"".join(c for c in pos_tr.group(1) if c.isdigit() or c.isalpha())))
freq = int(pos_tr.group(2))
pol = get_key_by_value(POLARIZATION, pos_tr.group(3))
pos_found = True
pos_tr = re.match(self._POS_PAT, r[0].text)
if not pos_tr:
continue
if pos_found and not tr_found:
td = re.match(self._TR_PAT, r[1].text) or re.match(self._TR_PAT, r[0].text)
if not sat_position:
pos = int(SatellitesParser.get_position(
"".join(c for c in pos_tr.group(1) if c.isdigit() or c.isalpha())))
pos_found = True
if pos_found:
text = " ".join(c.text for c in r[1:])
td = re.match(self._TR_PAT, text)
if td:
sys, mod, sr, _fec, nid, tid = td.group(1), td.group(2), td.group(3), td.group(4), td.group(
5), td.group(6)
freq, pol = int(td.group(1)), get_key_by_value(POLARIZATION, td.group(2))
if td.group(5):
log("Detected T2-MI transponder!")
continue
sys, mod, sr, _fec, = td.group(3), td.group(4), td.group(6), td.group(7)
nid, tid = td.group(8), td.group(9)
neg_pos = False # POS = W
# For negative (West) positions: 3600 - numeric position value!!!
namespace = "{:04x}0000".format(3600 - pos if neg_pos else pos)
@@ -439,7 +480,6 @@ class ServicesParser(HTMLParser):
s2_flags = "" if sys == "DVB-S" else self._S2_TR.format(tr_flag, mod or 0, roll_off, pilot)
nid, tid = int(nid), int(tid)
tr = self._TR.format(freq, sr, pol, fec, pos, inv, sys, s2_flags)
tr_found = True
if not tr:
msg = "ServicesParser error [get transponder services]: {}"
@@ -449,8 +489,8 @@ class ServicesParser(HTMLParser):
# Services
for r in filter(lambda x: x and len(x) == 12 and (x[0].text.isdigit()), self._rows):
sid, name, cas, pkg, s_type, v_pid, a_pid = r[0].text, r[2].text, r[4].text, r[5].text, r[
6].text.strip(), r[7].text, r[8].text.split()
sid, name, s_type, v_pid, a_pid, cas, pkg = r[0].text, r[2].text, r[4].text, r[
5].text.strip(), r[6].text.split(), r[9].text, r[10].text.strip()
try:
s_type = self._S_TYPES.get(s_type, "3") # 3 = Data
@@ -469,27 +509,8 @@ class ServicesParser(HTMLParser):
else:
flags = ",".join(filter(None, (flags, cas)))
srv = Service(flags_cas=flags,
transponder_type="s",
coded=None,
service=name,
locked=None,
hide=None,
package=pkg,
service_type=_s_type,
picon=r[1].img,
picon_id=picon_id,
ssid=sid,
freq=freq,
rate=sr,
pol=pol,
fec=fec,
system=sys,
pos=pos,
data_id=data_id,
fav_id=fav_id,
transponder=tr)
services.append(srv)
services.append(Service(flags, "s", None, name, None, None, pkg, _s_type, r[1].img, picon_id,
sid, freq, sr, pol, fec, sys, pos, data_id, fav_id, tr))
except ValueError as e:
log("ServicesParser error [get transponder services]: {}".format(e))

View File

@@ -6,12 +6,12 @@ import re
import shutil
import sys
from html.parser import HTMLParser
from json import JSONDecodeError
from urllib.error import URLError
from urllib.parse import unquote
from urllib.request import Request, urlopen, urlretrieve
from app.commons import log
from app.settings import SEP
from app.ui.uicommons import show_notification
_YT_PATTERN = re.compile(r"https://www.youtube.com/.+(?:v=)([\w-]{11}).*")
@@ -100,7 +100,7 @@ class YouTube:
if player_resp:
try:
resp = json.loads(player_resp)
except JSONDecodeError as e:
except Exception as e:
log("{}: Parsing player response error: {}".format(__class__.__name__, e))
else:
det = resp.get("videoDetails", None)
@@ -170,7 +170,7 @@ class PlayListParser(HTMLParser):
try:
resp = json.loads(data)
except JSONDecodeError as e:
except YouTubeException as e:
log("{}: Parsing data error: {}".format(__class__.__name__, e))
else:
sb = resp.get("sidebar", None)
@@ -230,7 +230,7 @@ class YouTubeDL:
"cookiefile": "cookies.txt"} # File name where cookies should be read from and dumped to.
def __init__(self, settings, callback):
self._path = settings.default_data_path + "tools/"
self._path = settings.default_data_path + "tools{}".format(SEP)
self._update = settings.enable_yt_dl_update
self._supported = {"22", "18"}
self._dl = None
@@ -247,7 +247,7 @@ class YouTubeDL:
return cls._DL_INSTANCE
def init(self):
if not os.path.isfile(self._path + "youtube_dl/version.py"):
if not os.path.isfile(self._path + "youtube_dl{}version.py".format(SEP)):
self.get_latest_release()
if self._path not in sys.path:
@@ -314,7 +314,7 @@ class YouTubeDL:
os.makedirs(os.path.dirname(self._path), exist_ok=True)
for info in arch.infolist():
pref, sep, f = info.filename.partition("/youtube_dl/")
pref, sep, f = info.filename.partition("{}youtube_dl{}".format(SEP, SEP))
if sep:
arch.extract(info.filename)
shutil.move(info.filename, "{}{}{}".format(self._path, sep, f))

154
app/ui/app_menu_bar.ui Normal file
View File

@@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="menu_bar">
<submenu>
<attribute name="label" translatable="yes">File</attribute>
<section>
<submenu>
<attribute name="label" translatable="yes">Import</attribute>
<section>
<item>
<attribute name="label" translatable="yes">Bouquet</attribute>
<attribute name="action">app.on_import_bouquet</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Bouquets and services</attribute>
<attribute name="action">app.on_import_bouquets</attribute>
</item>
</section>
</submenu>
<item>
<attribute name="label" translatable="yes">Import from Web</attribute>
<attribute name="action">app.on_import_from_web</attribute>
</item>
<item>
<attribute name="label" translatable="yes">New empty configuration</attribute>
<attribute name="action">app.on_new_configuration</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Open</attribute>
<attribute name="action">app.on_data_open</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Extract...</attribute>
<attribute name="action">app.on_archive_open</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Save</attribute>
<attribute name="action">app.on_data_save</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">FTP-transfer</attribute>
<attribute name="action">app.on_download</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Settings</attribute>
<attribute name="action">app.on_settings</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Exit</attribute>
<attribute name="action">app.on_close_app</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">Edit</attribute>
<section>
<item>
<attribute name="label" translatable="yes">Lock</attribute>
<attribute name="action">app.on_locked</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Hide</attribute>
<attribute name="action">app.on_hide</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">View</attribute>
<section>
<item>
<attribute name="label" translatable="yes">Search</attribute>
<attribute name="action">win.search</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Filter</attribute>
<attribute name="action">win.filter</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">Tools</attribute>
<section>
<item>
<attribute name="label" translatable="yes">Satellites editor</attribute>
<attribute name="action">app.on_satellite_editor_show</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Picons manager</attribute>
<attribute name="action">app.on_picons_manager_show</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Backups</attribute>
<attribute name="action">app.on_backup_tool_show</attribute>
</item>
</section>
<section id="telnet_section">
</section>
<section>
<submenu>
<attribute name="label" translatable="yes">IPTV</attribute>
<item>
<attribute name="label" translatable="yes">Add IPTV or stream service</attribute>
<attribute name="action">app.on_iptv</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Import YouTube playlist</attribute>
<attribute name="action">app.on_import_yt_list</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Import m3u</attribute>
<attribute name="action">app.on_import_m3u</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Export to m3u</attribute>
<attribute name="action">app.on_export_to_m3u</attribute>
</item>
<section>
<item>
<attribute name="label" translatable="yes">EPG configuration</attribute>
<attribute name="action">app.on_epg_list_configuration</attribute>
</item>
<item>
<attribute name="label" translatable="yes">List configuration</attribute>
<attribute name="action">app.on_iptv_list_configuration</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Remove all unavailable</attribute>
<attribute name="action">app.on_remove_all_unavailable</attribute>
</item>
</section>
</submenu>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">Help</attribute>
<section>
<item>
<attribute name="label" translatable="yes">About</attribute>
<attribute name="action">app.on_about_app</attribute>
</item>
</section>
</submenu>
</menu>
</interface>

View File

@@ -10,7 +10,7 @@ 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
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK
class RestoreType(Enum):
@@ -176,7 +176,7 @@ class BackupDialog:
if not KeyboardKey.value_exist(key_code):
return
key = KeyboardKey(key_code)
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
ctrl = event.state & MOD_MASK
if key is KeyboardKey.DELETE:
self.on_remove(view)

View File

@@ -33,6 +33,12 @@ Author: Dmitriy Yefremov
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkImage" id="details_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">emblem-important-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkListStore" id="main_list_store">
<columns>
<!-- column-name date -->
@@ -41,133 +47,89 @@ Author: Dmitriy Yefremov
<column type="gboolean"/>
</columns>
</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="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="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>
<object class="GtkImage" id="remove_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">user-trash-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="restore_all_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-select-all-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="restore_bouquets_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-revert-symbolic</property>
<property name="icon_size">1</property>
</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="title" translatable="yes">Backups</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="tooltip_text" translatable="yes">Details</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>
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox" id="main_box">
<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_top">1</property>
<property name="margin_bottom">1</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkPaned" id="main_paned">
<property name="visible">True</property>
@@ -251,6 +213,106 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">15</property>
<property name="margin_right">15</property>
<child>
<object class="GtkButtonBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<property name="layout_style">expand</property>
<child>
<object class="GtkButton" id="restore_bouquets_header_button">
<property name="label" translatable="yes">Restore bouquets</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
<property name="image">restore_bouquets_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_restore_bouquets" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="restore_all_header_button">
<property name="label" translatable="yes">Restore all</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
<property name="image">restore_all_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_restore_all" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="remove_header_button">
<property name="label" translatable="yes">Remove</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
<property name="image">remove_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_remove" swapped="no"/>
<accelerator key="Delete" signal="clicked"/>
</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>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="info_check_button">
<property name="label" translatable="yes">Details</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="valign">center</property>
<property name="image">details_image</property>
<property name="always_show_image">True</property>
<property name="draw_indicator">False</property>
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</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="GtkInfoBar" id="info_bar">
<property name="can_focus">False</property>
@@ -301,57 +363,4 @@ Author: Dmitriy Yefremov
</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

@@ -367,7 +367,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="margin_left">1</property>
<property name="margin_right">1</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
@@ -432,7 +431,9 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="margin_bottom">10</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkButton" id="standby_button">
@@ -969,7 +970,7 @@ audio-volume-medium-symbolic</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Filter</property>
<property name="primary_icon_stock">gtk-spell-check</property>
<property name="primary_icon_name">tools-check-spelling</property>
<property name="primary_icon_name">edit-find-replace-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<property name="placeholder_text" translatable="yes">Filter</property>
@@ -1177,7 +1178,7 @@ audio-volume-medium-symbolic</property>
<object class="GtkEntry" id="timer_service_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">gtk-edit</property>
</object>
<packing>
<property name="left_attach">1</property>
@@ -1188,7 +1189,7 @@ audio-volume-medium-symbolic</property>
<object class="GtkEntry" id="timer_desc_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">gtk-edit</property>
</object>
<packing>
<property name="left_attach">1</property>
@@ -1199,7 +1200,7 @@ audio-volume-medium-symbolic</property>
<object class="GtkEntry" id="timer_name_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">gtk-edit</property>
</object>
<packing>
<property name="left_attach">1</property>
@@ -1486,7 +1487,7 @@ audio-volume-medium-symbolic</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="primary_icon_name">gtk-edit</property>
</object>
</child>
<style>
@@ -1557,7 +1558,7 @@ audio-volume-medium-symbolic</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="primary_icon_name">gtk-edit</property>
</object>
</child>
<style>
@@ -1606,13 +1607,13 @@ audio-volume-medium-symbolic</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="timer_location_image">
<property name="visible">True</property>
<property name="visible">False</property>
<property name="can_focus">False</property>
<property name="icon_name">gtk-edit</property>
<property name="icon_name">document-edit-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
@@ -1622,8 +1623,8 @@ audio-volume-medium-symbolic</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
@@ -1639,7 +1640,7 @@ audio-volume-medium-symbolic</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">gtk-edit</property>
<property name="placeholder_text" translatable="yes">Default</property>
</object>
<packing>

View File

@@ -6,10 +6,12 @@ from urllib.parse import quote
from gi.repository import GLib
from .dialogs import get_dialogs_string, show_dialog, DialogType
from app.settings import IS_WIN
from .dialogs import get_dialogs_string, show_dialog, DialogType, get_message
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column
from ..commons import run_task, run_with_delay, log, run_idle
from ..connections import HttpAPI
from ..eparser.ecommons import BqServiceType
class ControlBox(Gtk.HBox):
@@ -60,19 +62,19 @@ class ControlBox(Gtk.HBox):
@property
def event_data(self):
return self._event_data
return self._event_data or {}
@property
def title(self):
return self._title
return self._title or ""
@property
def desc(self):
return self._desc
return self._desc or ""
@property
def time_header(self):
return self._time_header
return self._time_header or ""
class TimerRow(Gtk.ListBoxRow):
@@ -312,18 +314,17 @@ class ControlBox(Gtk.HBox):
img = data.get("img_data", None)
if img:
is_darwin = self._settings.is_darwin
GLib.idle_add(self._screenshot_button_box.set_sensitive, is_darwin)
path = os.path.expanduser("~/Desktop") if is_darwin else None
GLib.idle_add(self._screenshot_button_box.set_sensitive, IS_WIN)
path = os.path.expanduser("~/Desktop") if IS_WIN else None
try:
import tempfile
import subprocess
with tempfile.NamedTemporaryFile(mode="wb", suffix=".jpg", dir=path, delete=not is_darwin) as tf:
with tempfile.NamedTemporaryFile(mode="wb", suffix=".jpg", dir=path, delete=not IS_WIN) as tf:
tf.write(img)
cmd = ["open" if is_darwin else "xdg-open", tf.name]
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
f_name = tf.name
subprocess.Popen([f_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()
finally:
GLib.idle_add(self._screenshot_button_box.set_sensitive, True)
@@ -339,7 +340,7 @@ class ControlBox(Gtk.HBox):
def on_service_changed(self, ref):
self._app._wait_dialog.show()
self._http_api.send(HttpAPI.Request.EPG, ref, self.update_epg_data)
self._http_api.send(HttpAPI.Request.EPG, quote(ref), self.update_epg_data)
@run_idle
def update_epg_data(self, epg):
@@ -356,7 +357,7 @@ class ControlBox(Gtk.HBox):
def on_epg_filter_changed(self, entry):
self._epg_list_box.invalidate_filter()
def epg_filter_function(self, row: EpgRow):
def epg_filter_function(self, row):
txt = self._epg_filter_entry.get_text().upper()
return any((not txt, txt in row.time_header.upper(), txt in row.title.upper(), txt in row.desc.upper()))
@@ -423,7 +424,7 @@ class ControlBox(Gtk.HBox):
refs = {}
for row in rows:
timer = row.timer
ref = "timerdelete?sRef={}&begin={}&end={}".format(timer.get("e2servicereference", ""),
ref = "timerdelete?sRef={}&begin={}&end={}".format(quote(timer.get("e2servicereference", "")),
timer.get("e2timebegin", ""),
timer.get("e2timeend", ""))
refs[ref] = row
@@ -484,7 +485,7 @@ class ControlBox(Gtk.HBox):
def on_timer_save(self, action, value=None):
args = []
t_data = self.get_timer_data()
s_ref = t_data.get("sRef", "")
s_ref = quote(t_data.get("sRef", ""))
if self._timer_action is self.TimerAction.EVENT:
args.append("timeraddbyeventid?sRef={}".format(s_ref))
@@ -669,6 +670,12 @@ class ControlBox(Gtk.HBox):
service = self._app.current_services.get(fav_id, None)
if service:
if service.service_type == BqServiceType.ALT.name:
msg = "Alternative service.\n\n {}".format(get_message("Not implemented yet!"))
show_dialog(DialogType.ERROR, transient=self._app._main_window, text=msg)
context.finish(False, False, time)
return
self._timer_name_entry.set_text(service.service)
self._timer_service_entry.set_text(service.service)
self._timer_service_ref_entry.set_text(service.picon_id.rstrip(".png").replace("_", ":"))

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2020 Dmitriy Yefremov
Copyright (c) 2018-2021 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
@@ -30,8 +30,8 @@ Author: Dmitriy Yefremov
<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-2020 Dmitriy Yefremov -->
<!-- interface-description Enigma2 channel and satellites list editor for macOS. -->
<!-- interface-copyright 2018-2021 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkAboutDialog" id="about_dialog">
<property name="can_focus">False</property>
@@ -40,15 +40,16 @@ 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">1.0.4 Beta</property>
<property name="copyright">2018-2020 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor for GNU/Linux.</property>
<property name="website">https://dyefremov.github.io/DemonEditor/</property>
<property name="version">1.0.7 Alpha</property>
<property name="copyright">2018-2021 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor for MS Windows.
(Experimental)</property>
<property name="website">https://github.com/DYefremov/DemonEditor/tree/experimental-win</property>
<property name="license" translatable="yes">Это приложение распространяется без каких-либо гарантий.
Подробнее в &lt;a href="http://opensource.org/licenses/mit-license.php"&gt;The MIT License (MIT)&lt;/a&gt;.</property>
Подробнее в &lt;a href="http://opensource.org/licenses/mit-license.php"&gt;The MIT License (MIT)&lt;/a&gt;.</property>
<property name="authors">Dmitriy Yefremov
</property>
</property>
<property name="translator_credits" translatable="yes">translator-credits</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>
@@ -65,7 +66,9 @@ Author: Dmitriy Yefremov
<child internal-child="action_area">
<object class="GtkButtonBox" id="aboutdialog_action_area">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<property name="margin_left">50</property>
<property name="margin_right">50</property>
<property name="layout_style">expand</property>
</object>
<packing>
<property name="expand">False</property>
@@ -86,36 +89,63 @@ Author: Dmitriy Yefremov
<property name="window_position">center</property>
<property name="default_width">320</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="type_hint">utility</property>
<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>
<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>
<accelerator key="Return" signal="activate"/>
</object>
<child type="titlebar">
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">4</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>
<property name="valign">center</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="input_dialog_cancel_button">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="input_dialog_ok_button">
<property name="label" translatable="yes">OK</property>
<property name="width_request">100</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
<accelerator key="Return" signal="activate"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="input_entry">
<property name="visible">True</property>
@@ -124,7 +154,7 @@ Author: Dmitriy Yefremov
<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>
@@ -132,7 +162,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">0</property>
</packing>
</child>
</object>
@@ -142,7 +172,7 @@ Author: Dmitriy Yefremov
<action-widget response="ok">input_dialog_ok_button</action-widget>
</action-widgets>
</object>
<object class="GtkDialog" id="wait_dialog">
<object class="GtkWindow" id="wait_dialog">
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="modal">True</property>
@@ -152,69 +182,45 @@ Author: Dmitriy Yefremov
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="decorated">False</property>
<property name="gravity">center</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="wait_dialog_vbox">
<property name="width_request">120</property>
<object class="GtkBox" id="wait_dialog_box">
<property name="width_request">100</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog-action_area4">
<child>
<object class="GtkSpinner" id="spinner">
<property name="width_request">150</property>
<property name="height_request">45</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="opacity">0</property>
<property name="layout_style">end</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box4">
<object class="GtkLabel" id="wait_dialog_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkSpinner" id="spinner">
<property name="width_request">150</property>
<property name="height_request">45</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="wait_dialog_label">
<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="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>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="label" translatable="yes">Loading data...</property>
</object>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</child> <!-- NOP -->
<style>
<class name="app-notification"/>
</style>

View File

@@ -1,10 +1,11 @@
""" Common module for showing dialogs """
import locale
import gettext
from enum import Enum
from functools import lru_cache
from pathlib import Path
from app.commons import run_idle
from app.settings import SEP
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN, IS_GNOME_SESSION
@@ -17,7 +18,7 @@ class Dialog(Enum):
<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="width_request">250</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="skip_taskbar_hint">True</property>
@@ -120,7 +121,7 @@ def get_file_chooser_dialog(transient, text, settings, action_type, file_filter,
if response not in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
path = Path(dialog.get_filename() or dialog.get_current_folder())
if path.is_dir():
response = "{}/".format(path.resolve())
response = "{}{}".format(path.resolve(), SEP)
elif path.is_file():
response = str(path.resolve())
dialog.destroy()
@@ -176,12 +177,12 @@ def get_dialog_from_xml(dialog_type, transient, use_header=0, title=""):
def get_message(message):
""" returns translated message """
return locale.dgettext(TEXT_DOMAIN, message)
return gettext.dgettext(TEXT_DOMAIN, message)
@lru_cache(maxsize=5)
def get_dialogs_string(path):
with open(path, "r") as f:
with open(path, "r", encoding="utf-8") as f:
return "".join(f)

463
app/ui/download_dialog.glade Normal file → Executable file
View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
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
@@ -30,12 +30,25 @@ Author: Dmitriy Yefremov
<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-description Enigma2 channel and satellites list editor for macOS. -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkImage" id="download_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-receive-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="send_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-transmit-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkWindow" id="download_dialog_window">
<property name="width_request">550</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">FTP-transfer</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
@@ -44,226 +57,20 @@ Author: Dmitriy Yefremov
<property name="skip_pager_hint">True</property>
<property name="gravity">center</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">FTP-transfer</property>
<property name="spacing">5</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkBox" id="header_left_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkButton" id="receive_button">
<property name="visible">True</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="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">0</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">Send</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">1</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="options_button">
<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_settings" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-properties</property>
</object>
</child>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<placeholder/>
</child>
<child>
<object class="GtkBox" id="main_dialog_box">
<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>
<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" id="profile_box">
<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>
<signal name="changed" handler="on_profile_changed" swapped="no"/>
</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_left">10</property>
<property name="margin_right">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">5</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
@@ -271,8 +78,8 @@ Author: Dmitriy Yefremov
<object class="GtkBox" id="main_settings_box">
<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_left">10</property>
<property name="margin_right">10</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
@@ -424,6 +231,228 @@ Author: Dmitriy Yefremov
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<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="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</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>
<signal name="changed" handler="on_profile_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="options_button">
<property name="label" translatable="yes">Options</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_settings" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkButtonBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="margin_left">20</property>
<property name="margin_right">20</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<property name="homogeneous">True</property>
<property name="layout_style">expand</property>
<child>
<object class="GtkButton" id="receive_button">
<property name="label" translatable="yes">Receive</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Receive</property>
<property name="valign">center</property>
<property name="image">download_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_receive" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="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">Send</property>
<property name="valign">center</property>
<property name="image">send_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_send" swapped="no"/>
</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">6</property>
</packing>
</child>
<child>
<object class="GtkExpander" id="expander">
<property name="visible">True</property>
@@ -460,7 +489,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
<property name="position">7</property>
</packing>
</child>
<child>
@@ -516,7 +545,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
<property name="position">8</property>
</packing>
</child>
</object>

View File

@@ -33,6 +33,18 @@ Author: Dmitriy Yefremov
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkImage" id="apply_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-save-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="auto_config_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-select-all-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkListStore" id="bouquet_list_store">
<columns>
<!-- column-name num -->
@@ -79,6 +91,12 @@ Author: Dmitriy Yefremov
</object>
</child>
</object>
<object class="GtkImage" id="filter_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-find-replace-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="insert_link_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -130,6 +148,12 @@ Author: Dmitriy Yefremov
</object>
</child>
</object>
<object class="GtkImage" id="save_to_xml_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-save-as-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkListStore" id="services_list_store">
<columns>
<!-- column-name service -->
@@ -312,7 +336,7 @@ Author: Dmitriy Yefremov
<object class="GtkEntry" id="url_to_xml_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_stock">gtk-connect</property>
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
<property name="primary_icon_activatable">False</property>
</object>
<packing>
@@ -412,8 +436,8 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/data/epg/</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="secondary_icon_stock">gtk-directory</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Select</property>
<signal name="icon-press" handler="on_field_icon_press" swapped="no"/>
@@ -442,7 +466,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/enigma2/</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="primary_icon_activatable">False</property>
</object>
<packing>
@@ -559,58 +583,104 @@ Author: Dmitriy Yefremov
</object>
</child>
</object>
<object class="GtkImage" id="update_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">emblem-synchronizing-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkWindow" id="epg_dialog_window">
<property name="width_request">480</property>
<property name="height_request">320</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">EPG</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">480</property>
<property name="default_height">240</property>
<property name="default_height">320</property>
<property name="destroy_with_parent">True</property>
<property name="icon_name">gtk-index</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="gravity">center</property>
<signal name="check-resize" handler="on_resize" swapped="no"/>
<signal name="delete-event" handler="on_close_dialog" swapped="no"/>
<child type="titlebar">
<object class="GtkHeaderBar" id="header_bar">
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox" id="main_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">EPG</property>
<property name="subtitle" translatable="yes">List configuration</property>
<property name="spacing">2</property>
<property name="show_close_button">True</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="GtkBox" id="left_header_box">
<object class="GtkBox" id="main_actions_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<property name="margin_left">15</property>
<property name="margin_right">15</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<property name="spacing">10</property>
<child>
<object class="GtkButton" id="apply_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Apply</property>
<signal name="clicked" handler="on_apply" swapped="no"/>
<child>
<object class="GtkImage" id="apply_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-apply</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="GtkSeparator">
<object class="GtkButtonBox" id="left_action_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">expand</property>
<child>
<object class="GtkButton" id="apply_button">
<property name="label" translatable="yes">Apply</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Apply</property>
<property name="image">apply_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_apply" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="update_button">
<property name="label" translatable="yes">Update</property>
<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>
<property name="image">update_image</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">1</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="filter_button">
<property name="label" translatable="yes">Filter</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Filter</property>
<property name="image">filter_image</property>
<property name="always_show_image">True</property>
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -618,20 +688,11 @@ Author: Dmitriy Yefremov
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="update_button">
<child type="center">
<object class="GtkLabel" id="list_label">
<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_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-refresh</property>
</object>
</child>
<property name="can_focus">False</property>
<property name="label" translatable="yes">List configuration</property>
</object>
<packing>
<property name="expand">False</property>
@@ -640,111 +701,106 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkToggleButton" id="filter_button">
<object class="GtkButtonBox" id="right_action_box">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Filter</property>
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
<property name="can_focus">False</property>
<property name="layout_style">expand</property>
<child>
<object class="GtkImage" id="filter_button_image">
<object class="GtkButton" id="auto_config_button">
<property name="label" translatable="yes">Auto</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-spell-check</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Auto configuration by service names.</property>
<property name="image">auto_config_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_auto_configuration" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="right_header_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkButton" id="auto_config_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Auto configuration by service names.</property>
<signal name="clicked" handler="on_auto_configuration" swapped="no"/>
<child>
<object class="GtkImage" id="auto_config_button_image">
<object class="GtkButton" id="save_to_xml_button">
<property name="label" translatable="yes">Save</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-find-and-replace</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Save list to xml.</property>
<property name="image">save_to_xml_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_save_to_xml" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkMenuButton" id="options_menu_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="direction">none</property>
<property name="popover">options_popover</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkImage" id="options_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Options</property>
<property name="icon_name">applications-system-symbolic</property>
<property name="icon_size">1</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Options</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="save_to_xml_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Save list to xml.</property>
<signal name="clicked" handler="on_save_to_xml" swapped="no"/>
<child>
<object class="GtkImage" id="save_to_xml_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-save-as</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="GtkMenuButton" id="options_menu_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="direction">none</property>
<property name="popover">options_popover</property>
<child>
<object class="GtkImage" id="options_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Options</property>
<property name="stock">gtk-properties</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</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="GtkSearchBar" id="filter_bar">
<property name="visible">True</property>
@@ -753,7 +809,7 @@ Author: Dmitriy Yefremov
<object class="GtkSearchEntry" id="filter_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">tools-check-spelling</property>
<property name="primary_icon_name">edit-find-replace-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<signal name="search-changed" handler="on_filter_changed" swapped="no"/>
@@ -862,13 +918,14 @@ Author: Dmitriy Yefremov
<property name="height_request">24</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">2</property>
<property name="margin_left">5</property>
<property name="spacing">2</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-properties</property>
<property name="icon_name">document-properties-symbolic</property>
<property name="icon_size">1</property>
</object>
<packing>
<property name="expand">False</property>
@@ -890,6 +947,7 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="source_info_box">
<property name="height_request">26</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
@@ -1143,16 +1201,17 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="bouquet_info_bar_box">
<property name="height_request">24</property>
<property name="height_request">26</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">2</property>
<property name="margin_left">5</property>
<property name="spacing">2</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-properties</property>
<property name="icon_name">document-properties-symbolic</property>
<property name="icon_size">1</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1189,7 +1248,9 @@ Author: Dmitriy Yefremov
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">2</property>
<property name="stock">gtk-index</property>
<property name="icon_size">1</property>
</object>
<packing>
<property name="expand">False</property>

View File

@@ -8,14 +8,13 @@ from enum import Enum
from urllib.error import HTTPError, URLError
from gi.repository import GLib
from app.commons import run_idle
from app.commons import run_idle, run_task
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
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, Column, EPG_ICON, KeyboardKey, MOD_MASK
class RefsSource(Enum):
@@ -186,7 +185,7 @@ class EpgDialog:
def init_bouquet_data(self):
for r in self._ex_fav_model:
row = [*r[:]]
row = list(r[:])
fav_id = r[Column.FAV_ID]
self._services[fav_id] = self._ex_services[fav_id].fav_id
yield self._bouquet_model.append(row)
@@ -279,7 +278,7 @@ class EpgDialog:
if not KeyboardKey.value_exist(key_code):
return
key = KeyboardKey(key_code)
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
ctrl = event.state & MOD_MASK
if ctrl and key is KeyboardKey.C:
self.on_copy_ref()
@@ -539,6 +538,7 @@ class EpgDialog:
# ***************** Downloads *********************#
@run_task
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)

View File

@@ -89,7 +89,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="margin_left">1</property>
<property name="margin_right">1</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>

View File

@@ -26,13 +26,25 @@ THE SOFTWARE.
Author: Dmitriy Yefremov
-->
<interface>
<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-description Enigma2 channel and satellites list editor for macOS. -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkImage" id="details_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">emblem-important-symbolic</property>
</object>
<object class="GtkImage" id="import_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-revert-symbolic-rtl</property>
<property name="icon_size">1</property>
</object>
<object class="GtkListStore" id="main_list_store">
<columns>
<!-- column-name name -->
@@ -82,6 +94,7 @@ Author: Dmitriy Yefremov
</object>
<object class="GtkWindow" id="dialog_window">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Import</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">480</property>
@@ -90,53 +103,8 @@ Author: Dmitriy Yefremov
<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="tooltip_text" translatable="yes">Details</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>
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox" id="main_box">
@@ -161,8 +129,8 @@ Author: Dmitriy Yefremov
<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="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label" translatable="yes">Bouquets</property>
</object>
<packing>
@@ -260,8 +228,8 @@ Author: Dmitriy Yefremov
<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="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label" translatable="yes">Bouquet details</property>
</object>
<packing>
@@ -337,6 +305,62 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButtonBox" id="actions_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="margin_left">15</property>
<property name="margin_right">15</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="layout_style">start</property>
<child>
<object class="GtkButton" id="import_button">
<property name="label" translatable="yes">Import</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Bouquets and services</property>
<property name="valign">center</property>
<property name="image">import_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_import" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="info_check_button">
<property name="label" translatable="yes">Details</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Details</property>
<property name="valign">center</property>
<property name="image">details_image</property>
<property name="always_show_image">True</property>
<property name="draw_indicator">False</property>
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
<accelerator key="i" signal="clicked"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="secondary">True</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="GtkInfoBar" id="info_bar">
<property name="can_focus">False</property>
@@ -382,7 +406,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">2</property>
</packing>
</child>
</object>

View File

@@ -20,7 +20,7 @@ def import_bouquet(transient, model, path, settings, services, appender, file_pa
if profile is SettingsType.ENIGMA_2:
pattern = ".{}".format(bq_type.value)
f_pattern = "userbouquet.*{}".format(pattern)
f_pattern = "*" + pattern if settings.is_darwin else "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"
@@ -33,11 +33,15 @@ def import_bouquet(transient, model, path, settings, services, appender, file_pa
if file_path == Gtk.ResponseType.CANCEL:
return
if not str(file_path).endswith(pattern):
if not file_path.endswith(pattern):
show_dialog(DialogType.ERROR, transient, text="No bouquet file is selected!")
return
if profile is SettingsType.ENIGMA_2:
if settings.is_darwin and file_path.rfind("userbouquet.") < 0:
show_dialog(DialogType.ERROR, transient, text="No bouquet file is selected!")
return
bq = get_enigma2_bouquet(file_path)
imported = list(filter(lambda x: x.data in services or x.type is BqServiceType.IPTV, bq.services))

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,24 @@
import concurrent.futures
import os
import re
import urllib
from urllib.error import HTTPError
from urllib.parse import urlparse, unquote, quote
from urllib.request import Request, urlopen
from gi.repository import GLib
import requests
from gi.repository import GLib, Gio, GdkPixbuf
from app.commons import run_idle, run_task
from app.commons import run_idle, run_task, log
from app.eparser.ecommons import BqServiceType, Service
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT, get_fav_id, MARKER_FORMAT
from app.eparser.iptv import (NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT, get_fav_id, MARKER_FORMAT,
parse_m3u)
from app.settings import SettingsType
from app.tools.yt import YouTubeException, YouTube
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)
from app.ui.dialogs import Action, show_dialog, DialogType, get_dialogs_string, get_message
from app.ui.main_helper import get_base_model, get_iptv_url, on_popup_menu, get_picon_pixbuf
from app.ui.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"
@@ -295,14 +298,14 @@ class IptvDialog:
self._bouquet[self._paths[0][0]] = 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, *aggr[0:3])
srv = (None, None, name, None, None, s_type, None, fav_id, None, None, None)
itr = self._model.insert_after(self._model.get_iter(self._paths[0]),
srv) if self._paths else self._model.insert(0, srv)
self._model.set_value(itr, 1, IPTV_ICON)
self._bouquet.insert(self._model.get_path(itr)[0], fav_id)
self._services[fav_id] = Service(None, None, IPTV_ICON, name, *aggr[0:3], s_type, *aggr, fav_id, None)
self._services[fav_id] = Service(None, None, IPTV_ICON, name, None, None, None, s_type, None,
None, None, None, None, None, None, None, None, None, fav_id, None)
@run_idle
def on_info_bar_close(self, bar=None, resp=None):
@@ -399,9 +402,10 @@ class SearchUnavailableDialog:
self._dialog.destroy()
class IptvListConfigurationDialog:
class IptvListDialog:
""" Base class for working with iptv lists. """
def __init__(self, transient, services, iptv_rows, bouquet, fav_model, s_type):
def __init__(self, transient, s_type):
handlers = {"on_apply": self.on_apply,
"on_response": self.on_response,
"on_stream_type_default_togged": self.on_stream_type_default_togged,
@@ -415,10 +419,6 @@ class IptvListConfigurationDialog:
"on_entry_changed": self.on_entry_changed,
"on_info_bar_close": self.on_info_bar_close}
self._rows = iptv_rows
self._services = services
self._bouquet = bouquet
self._fav_model = fav_model
self._s_type = s_type
builder = Gtk.Builder()
@@ -429,6 +429,8 @@ class IptvListConfigurationDialog:
self._dialog = builder.get_object("iptv_list_configuration_dialog")
self._dialog.set_transient_for(transient)
self._data_box = builder.get_object("iptv_list_data_box")
self._start_values_grid = builder.get_object("start_values_grid")
self._info_bar = builder.get_object("list_configuration_info_bar")
self._reference_label = builder.get_object("reference_label")
self._stream_type_check_button = builder.get_object("stream_type_default_check_button")
@@ -443,22 +445,26 @@ class IptvListConfigurationDialog:
self._list_tid_entry = builder.get_object("list_tid_entry")
self._list_nid_entry = builder.get_object("list_nid_entry")
self._list_namespace_entry = builder.get_object("list_namespace_entry")
self._reset_to_default_switch = builder.get_object("reset_to_default_lists_switch")
# style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._apply_button = builder.get_object("list_configuration_apply_button")
# Style
style_provider = Gtk.CssProvider()
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._default_elems = (self._stream_type_check_button, self._type_check_button, self._sid_auto_check_button,
self._tid_check_button, self._nid_check_button, self._namespace_check_button)
self._digit_elems = (self._list_srv_type_entry, self._list_sid_entry, self._list_tid_entry,
self._list_nid_entry, self._list_namespace_entry)
for el in self._digit_elems:
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
def show(self):
self._dialog.run()
def on_response(self, dialog, response):
if response == Gtk.ResponseType.CANCEL:
self._dialog.destroy()
if response == Gtk.ResponseType.APPLY:
return True
self._dialog.destroy()
def on_stream_type_changed(self, box):
self.update_reference()
@@ -494,19 +500,51 @@ class IptvListConfigurationDialog:
self._list_namespace_entry.set_sensitive(not button.get_active())
@run_idle
def on_reset_to_default(self, item, active):
item.set_sensitive(not active)
def on_reset_to_default(self, item):
self._stream_type_combobox.set_active(1)
self._list_srv_type_entry.set_text("1")
for el in (self._list_sid_entry, self._list_nid_entry, self._list_tid_entry, self._list_namespace_entry):
for el in self._digit_elems[1:]:
el.set_text("0")
for el in (self._stream_type_check_button, self._type_check_button, self._sid_auto_check_button,
self._tid_check_button, self._nid_check_button, self._namespace_check_button):
for el in self._default_elems:
el.set_active(True)
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
def on_apply(self, item):
pass
@run_idle
def update_reference(self):
if is_data_correct(self._digit_elems):
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]))
def on_entry_changed(self, entry):
if _PATTERN.search(entry.get_text()):
entry.set_name(_DIGIT_ENTRY_NAME)
else:
entry.set_name("GtkEntry")
self.update_reference()
def is_default_values(self):
return any(el.get_text() == "0" for el in self._digit_elems[2:])
def is_all_data_default(self):
return all(el.get_active() for el in self._default_elems)
class IptvListConfigurationDialog(IptvListDialog):
def __init__(self, transient, services, iptv_rows, bouquet, fav_model, s_type):
super().__init__(transient, s_type)
self._rows = iptv_rows
self._bouquet = bouquet
self._fav_model = fav_model
self._services = services
@run_idle
def on_apply(self, item):
if not is_data_correct(self._digit_elems):
@@ -514,14 +552,13 @@ class IptvListConfigurationDialog:
return
if self._s_type 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()
sid_auto = self._sid_auto_check_button.get_active()
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)
stream_type = 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()))
@@ -532,7 +569,7 @@ class IptvListConfigurationDialog:
data, sep, desc = fav_id.partition("http")
data = data.split(":")
if reset:
if self.is_all_data_default():
data[2], data[3], data[4], data[5], data[6] = "10000"
else:
data[0], data[2], data[4], data[5], data[6] = stream_type, srv_type, tid, nid, namespace
@@ -551,19 +588,208 @@ class IptvListConfigurationDialog:
self._info_bar.set_visible(True)
@run_idle
def update_reference(self):
if is_data_correct(self._digit_elems):
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]))
def on_entry_changed(self, entry):
if _PATTERN.search(entry.get_text()):
entry.set_name(_DIGIT_ENTRY_NAME)
class M3uImportDialog(IptvListDialog):
""" Import dialog for *.m3u* playlists. """
def __init__(self, transient, s_type, m3_path, app):
super().__init__(transient, s_type)
self._app = app
self._picons = app._picons
self._pic_path = app._settings.picons_local_path
self._services = None
self._url_count = 0
self._errors_count = 0
self._max_count = 0
self._is_download = False
self._cancellable = Gio.Cancellable()
self._dialog.set_title(get_message("Playlist import"))
self._dialog.connect("delete-event", self.on_close)
self._apply_button.set_label(get_message("Import"))
# Progress
self._progress_bar = Gtk.ProgressBar(visible=False, valign="center")
self._spinner = Gtk.Spinner(active=False)
self._info_label = Gtk.Label(visible=True, ellipsize="end", max_width_chars=30)
load_label = Gtk.Label(label=get_message("Loading data..."))
self._spinner.bind_property("active", self._spinner, "visible")
self._spinner.bind_property("visible", load_label, "visible")
self._spinner.bind_property("active", self._start_values_grid, "sensitive", 4)
progress_box = Gtk.HBox(visible=True, spacing=2)
progress_box.add(self._progress_bar)
progress_box.pack_end(self._spinner, False, False, 0)
progress_box.pack_start(load_label, False, False, 0)
# Picons
self._picons_switch = Gtk.Switch(visible=True)
self._picon_box = Gtk.HBox(visible=True, sensitive=False, spacing=2)
self._picon_box.pack_end(self._picons_switch, False, False, 0)
self._picon_box.pack_end(Gtk.Label(visible=True, label=get_message("Download picons")), False, False, 0)
# Extra box
extra_box = Gtk.HBox(visible=True, spacing=2, margin_bottom=5, margin_top=5)
extra_box.set_center_widget(progress_box)
extra_box.pack_start(self._info_label, False, False, 5)
extra_box.pack_end(self._picon_box, True, True, 5)
frame = Gtk.Frame(visible=True)
frame.add(extra_box)
self._data_box.add(frame)
self.get_m3u(m3_path, s_type)
@run_task
def get_m3u(self, path, s_type):
try:
GLib.idle_add(self._spinner.set_property, "active", True)
self._services = parse_m3u(path, s_type)
for s in self._services:
if s.picon:
GLib.idle_add(self._picon_box.set_sensitive, True)
break
finally:
msg = "{} {}.".format(get_message("Streams detected:"), len(self._services) if self._services else 0)
GLib.idle_add(self._info_label.set_text, msg)
GLib.idle_add(self._spinner.set_property, "active", False)
def on_apply(self, item):
if not is_data_correct(self._digit_elems):
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return
picons = {}
services = self._services
if not self.is_all_data_default():
services = []
params = [int(el.get_text()) for el in self._digit_elems]
s_type = params[0]
params = params[1:]
stream_type = get_stream_type(self._stream_type_combobox)
for i, s in enumerate(self._services, start=params[0]):
# Skipping markers.
if not s.data_id:
services.append(s)
continue
params[0] = i
picon_id = "{}_0_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png".format(stream_type, s_type, *params)
fav_id = get_fav_id(s.data_id, s.service, self._s_type, params, stream_type, s_type)
if s.picon:
picons[s.picon] = picon_id
services.append(s._replace(picon=None, picon_id=picon_id, data_id=None, fav_id=fav_id))
if self._picons_switch.get_active():
if self.is_default_values():
show_dialog(DialogType.ERROR, self._dialog,
"Set values for TID, NID and Namespace for correct naming of the picons!")
return
self.download_picons(picons)
else:
entry.set_name("GtkEntry")
self.update_reference()
GLib.idle_add(self._info_bar.set_visible, True, priority=GLib.PRIORITY_LOW)
self._app.append_imported_services(services)
@run_task
def download_picons(self, picons):
self._is_download = True
os.makedirs(os.path.dirname(self._pic_path), exist_ok=True)
GLib.idle_add(self._apply_button.set_sensitive, False)
GLib.idle_add(self._progress_bar.set_visible, True)
self._errors_count = 0
self._url_count = len(picons)
self._max_count = self._url_count
self._cancellable.reset()
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
futures = {executor.submit(self.download_picon, p, picons.get(p, None)): p for p in filter(None, picons)}
done, not_done = concurrent.futures.wait(futures, timeout=0)
while self._is_download and not_done:
done, not_done = concurrent.futures.wait(not_done, timeout=5)
for future in not_done:
future.cancel()
concurrent.futures.wait(not_done)
self.update_progress(self._url_count)
self.on_done()
def download_picon(self, url, pic_data):
err_msg = "Picon download error: {} [{}]"
timeout = (3, 5) # connect and read timeouts
req = requests.get(url, timeout=timeout)
if req.status_code != 200:
log(err_msg.format(url, req.reason))
self.update_progress(1)
else:
self.on_picon_load_done(req.content, pic_data)
@run_idle
def on_picon_load_done(self, data, user_data):
try:
self._info_label.set_text("Processing: {}".format(user_data))
f = Gio.MemoryInputStream.new_from_data(data)
pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(f, 220, 132, False, self._cancellable)
path = "{}{}".format(self._pic_path, user_data)
pixbuf.savev(path, "png", [], [])
self._picons[user_data] = get_picon_pixbuf(path)
except GLib.GError as e:
self.update_progress(1)
if e.code != Gio.IOErrorEnum.CANCELLED:
log("Loading picon [{}] data error: {}".format(user_data, e))
else:
self.update_progress()
@run_idle
def update_progress(self, error=0):
self._errors_count += error
self._url_count -= 1
frac = 1 - self._url_count / self._max_count
self._progress_bar.set_fraction(frac)
@run_idle
def on_done(self):
self._progress_bar.set_visible(False)
self._progress_bar.set_fraction(0.0)
self._apply_button.set_sensitive(True)
self._info_label.set_text("Errors: {}.".format(self._errors_count))
self._is_download = False
gen = self.update_fav_model()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def update_fav_model(self):
services = self._app._services
picons = self._app._picons
model = self._app.fav_view.get_model()
for r in model:
s = services.get(r[Column.FAV_ID], None)
if s:
model.set_value(r.iter, Column.FAV_PICON, picons.get(s.picon_id, None))
yield True
self._info_bar.set_visible(True)
yield True
def on_response(self, dialog, response):
if response == Gtk.ResponseType.APPLY:
return True
if response == Gtk.ResponseType.CANCEL and not self._is_download or not self.on_close():
self._dialog.destroy()
def on_close(self, window=None, event=None):
if self._is_download:
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.OK:
self._is_download = False
self._cancellable.cancel()
return False
return True
return False
class YtListImportDialog:
@@ -591,7 +817,8 @@ class YtListImportDialog:
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"))
"yt_popup_menu", "remove_selection_image", "yt_receive_image",
"yt_import_image"))
builder.connect_signals(handlers)
self._dialog = builder.get_object("yt_import_dialog_window")
@@ -693,13 +920,13 @@ class YtListImportDialog:
@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)
mk = Service(None, None, None, title, None, None, None, BqServiceType.MARKER.name, None,
None, None, None, None, None, None, None, None, 0, fav_id, None)
srvs.append(mk)
act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0)
@@ -709,7 +936,8 @@ class YtListImportDialog:
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._s_type)
srv = Service(None, None, IPTV_ICON, title, *aggr[0:3], BqServiceType.IPTV.name, *aggr, None, fav_id, None)
srv = Service(None, None, IPTV_ICON, title, None, None, None, BqServiceType.IPTV.name, None, None, None,
None, None, None, None, None, None, None, fav_id, None)
srvs.append(srv)
self.appender(srvs)

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,7 @@ def insert_marker(view, bouquets, selected_bouquet, services, parent_window, m_t
marker = (None, None, text, 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)
services[fav_id] = Service(None, None, None, text, None, None, None, s_type, *[None] * 9, 0, fav_id, None)
services[fav_id] = Service(None, None, None, text, None, None, None, s_type, None, None, None, None, None, None, None, None, None, 0, fav_id, None)
# ***************** Movement *******************#
@@ -280,7 +280,7 @@ def set_hide(services, model, paths):
for path in paths:
itr = model.get_iter(path)
model.set_value(itr, col_num, None if hide else HIDE_ICON)
flags = [*model.get_value(itr, 0).split(",")]
flags = list(model.get_value(itr, 0).split(","))
index, flag = None, None
for i, fl in enumerate(flags):
if fl.startswith("f:"):
@@ -353,12 +353,12 @@ def scroll_to(index, view, paths=None):
# ***************** Picons *********************#
def update_picons_data(path, picons):
def update_picons_data(path, picons, size=32):
if not os.path.exists(path):
return
for file in os.listdir(path):
pf = get_picon_pixbuf(path + file)
pf = get_picon_pixbuf(path + file, size)
if pf:
picons[file] = pf
@@ -530,7 +530,7 @@ def get_picon_pixbuf(path, size=32):
# ***************** Bouquets *********************#
def gen_bouquets(view, bq_view, transient, gen_type, tv_types, s_type, callback):
def gen_bouquets(view, bq_view, transient, gen_type, s_type, callback):
""" Auto-generate and append list of bouquets """
fav_id_index = Column.SRV_FAV_ID
index = Column.SRV_TYPE
@@ -545,8 +545,6 @@ def gen_bouquets(view, bq_view, transient, gen_type, tv_types, s_type, callback)
if not is_only_one_item_selected(paths, transient):
return
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], s_type)
@@ -592,7 +590,9 @@ def get_bouquets_names(model):
def update_entry_data(entry, dialog, settings):
""" Updates value in text entry from chooser dialog. """
response = show_dialog(dialog_type=DialogType.CHOOSER, transient=dialog, settings=settings, create_dir=True)
response = show_dialog(dialog_type=DialogType.CHOOSER, transient=dialog, settings=settings,
action_type=Gtk.FileChooserAction.CREATE_FOLDER if settings.is_darwin else None,
create_dir=True)
if response not in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
entry.set_text(response)
return response

File diff suppressed because it is too large Load Diff

View File

@@ -32,6 +32,34 @@ Author: Dmitriy Yefremov
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkImage" id="cancel_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">window-close-symbolic</property>
</object>
<object class="GtkImage" id="convert_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-redo-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="filter_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-find-replace-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="info_toggle_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">emblem-important-symbolic</property>
</object>
<object class="GtkImage" id="load_providers">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-server-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkListStore" id="picons_dest_list_store">
<columns>
<!-- column-name picon -->
@@ -177,10 +205,28 @@ Author: Dmitriy Yefremov
<column type="gboolean"/>
</columns>
</object>
<object class="GtkImage" id="receive_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-receive-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="receive_picons_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-receive-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="remove_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">user-trash-symbolic</property>
</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>
<property name="icon_size">1</property>
</object>
<object class="GtkMenu" id="providers_popup_menu">
<property name="visible">True</property>
@@ -216,232 +262,23 @@ Author: Dmitriy Yefremov
<column type="gchararray"/>
</columns>
</object>
<object class="GtkImage" id="send_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-transmit-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkWindow" id="picons_dialog">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Picons manager</property>
<property name="window_position">center-on-parent</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>
<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="spacing">2</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="can_focus">False</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="GtkImage" id="cancel_button_image">
<property name="visible">True</property>
<property name="can_focus">False</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="sensitive">False</property>
<property name="can_focus">False</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">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="receive_button">
<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</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">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="send_button">
<property name="visible">True</property>
<property name="can_focus">False</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"/>
<signal name="drag-data-received" handler="on_send_button_drag_data_received" 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-go-up</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="download_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Download from the receiver</property>
<signal name="clicked" handler="on_download" swapped="no"/>
<signal name="drag-data-received" handler="on_download_button_drag_data_received" 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">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Remove all picons from the receiver</property>
<signal name="clicked" handler="on_remove" swapped="no"/>
<signal name="drag-data-received" handler="on_remove_button_drag_data_received" 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 type="title">
<object class="GtkStackSwitcher" id="sctack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stack">stack</property>
</object>
</child>
<child>
<object class="GtkButton" id="convert_button">
<property name="can_focus">False</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">4</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="info_check_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Details</property>
<property name="draw_indicator">False</property>
<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>
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="filter_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Filter</property>
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
<child>
<object class="GtkImage" id="filter_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Filter</property>
<property name="stock">gtk-select-all</property>
</object>
</child>
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">4</property>
</packing>
</child>
</object>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox" id="main_box">
@@ -452,6 +289,7 @@ Author: Dmitriy Yefremov
<object class="GtkFrame" id="stack_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="label_xalign">0.5</property>
<property name="shadow_type">none</property>
<child>
@@ -460,8 +298,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="margin_top">10</property>
<signal name="notify::visible-child" handler="on_visible_page" swapped="no"/>
<child>
<object class="GtkBox" id="explorer_box">
@@ -512,49 +349,6 @@ Author: Dmitriy Yefremov
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkSearchBar" id="search_bar">
<property name="can_focus">False</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<child>
<object class="GtkSearchEntry" id="picons_search_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">edit-find-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkSearchBar" id="filter_bar">
<property name="can_focus">False</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<child>
<object class="GtkSearchEntry" id="picons_filter_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_stock">gtk-spell-check</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<signal name="search-changed" handler="on_picons_filter_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="explorer_paths_frame">
<property name="visible">True</property>
@@ -569,6 +363,7 @@ Author: Dmitriy Yefremov
<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="wide_handle">True</property>
<child>
<object class="GtkBox" id="src_picons_box">
@@ -580,10 +375,33 @@ Author: Dmitriy Yefremov
<object class="GtkBox" id="src_title_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child type="center">
<object class="GtkLabel" id="explorer_src_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Source:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="src_title_grid">
<property name="can_focus">False</property>
<property name="column_spacing">2</property>
<child>
<object class="GtkLabel" id="src_filter_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Filter</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="src_filter_button">
<property name="visible">True</property>
@@ -594,17 +412,6 @@ Author: Dmitriy Yefremov
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_fiter_srcs_toggled" object="picons_src_filter_model" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="src_filter_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Filter</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
@@ -614,21 +421,10 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child type="center">
<object class="GtkLabel" id="explorer_src_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Source:</property>
</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>
@@ -661,7 +457,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="model">picons_src_sort_model</property>
<property name="headers_visible">False</property>
<property name="tooltip_column">1</property>
<property name="tooltip_column">0</property>
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_popup_menu" object="picons_src_view_popup_menu" swapped="no"/>
<signal name="drag-data-get" handler="on_picons_view_drag_data_get" swapped="no"/>
@@ -669,6 +465,7 @@ Author: Dmitriy Yefremov
<signal name="drag-drop" handler="on_picons_src_view_drag_drop" swapped="no"/>
<signal name="drag-end" handler="on_picons_src_view_drag_end" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<signal name="query-tooltip" handler="on_view_query_tooltip" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="picons_src_view_selection"/>
</child>
@@ -815,12 +612,13 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="model">picons_dst_sort_model</property>
<property name="headers_visible">False</property>
<property name="tooltip_column">1</property>
<property name="tooltip_column">0</property>
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_popup_menu" object="picons_dest_view_popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_picon_activated" swapped="no"/>
<signal name="drag-data-get" handler="on_picons_view_drag_data_get" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<signal name="query-tooltip" handler="on_view_query_tooltip" swapped="no"/>
<signal name="realize" handler="on_picons_dest_view_realize" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="picons_dest_view_selection"/>
@@ -917,8 +715,8 @@ Author: Dmitriy Yefremov
<property name="spacing">10</property>
<child>
<object class="GtkImage" id="picon_info_image">
<property name="width_request">128</property>
<property name="height_request">72</property>
<property name="width_request">100</property>
<property name="height_request">60</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
@@ -935,6 +733,7 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="picon_info_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="expand">False</property>
@@ -964,6 +763,49 @@ Author: Dmitriy Yefremov
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkSearchBar" id="search_bar">
<property name="can_focus">False</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<child>
<object class="GtkSearchEntry" id="picons_search_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">edit-find-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">7</property>
</packing>
</child>
<child>
<object class="GtkSearchBar" id="filter_bar">
<property name="can_focus">False</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<child>
<object class="GtkSearchEntry" id="picons_filter_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">edit-find-replace-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<signal name="search-changed" handler="on_picons_filter_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">8</property>
</packing>
</child>
</object>
<packing>
<property name="name">explorer</property>
@@ -993,7 +835,7 @@ Author: Dmitriy Yefremov
<property name="wide_handle">True</property>
<child>
<object class="GtkBox" id="satellites_box">
<property name="width_request">200</property>
<property name="width_request">180</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">2</property>
@@ -1118,7 +960,6 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="proveders_pox">
<property name="width_request">280</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">2</property>
@@ -1183,7 +1024,9 @@ Author: Dmitriy Yefremov
</attributes>
</child>
<child>
<object class="GtkCellRendererText" id="name_cellrenderertext"/>
<object class="GtkCellRendererText" id="name_cellrenderertext">
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
@@ -1353,7 +1196,7 @@ Author: Dmitriy Yefremov
<object class="GtkEntry" id="picons_dir_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="secondary_icon_name">folder-open</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<property name="primary_icon_activatable">False</property>
<signal name="icon-press" handler="on_picons_dir_open" swapped="no"/>
</object>
@@ -1410,7 +1253,7 @@ Author: Dmitriy Yefremov
<property name="halign">center</property>
<child>
<object class="GtkRadioButton" id="enigma2_radio_button">
<property name="label" translatable="yes">Enigma2 (default)</property>
<property name="label" translatable="yes">Enigma2</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
@@ -1478,7 +1321,7 @@ Author: Dmitriy Yefremov
<property name="halign">center</property>
<child>
<object class="GtkRadioButton" id="resize_no_radio_button">
<property name="label" translatable="yes">No(default)</property>
<property name="label" translatable="yes">No</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
@@ -1676,8 +1519,12 @@ Author: Dmitriy Yefremov
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
<child type="label">
<object class="GtkStackSwitcher" id="sctack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stack">stack</property>
</object>
</child>
</object>
<packing>
@@ -1686,14 +1533,220 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="action_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_left">15</property>
<property name="margin_right">15</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<child>
<object class="GtkButtonBox" id="explorer_action_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">expand</property>
<child>
<object class="GtkButton" id="download_button">
<property name="label" translatable="yes">Receive</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Download from the receiver</property>
<property name="image">receive_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_download" swapped="no"/>
<signal name="drag-data-received" handler="on_download_button_drag_data_received" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="secondary">True</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">send_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_send" swapped="no"/>
<signal name="drag-data-received" handler="on_send_button_drag_data_received" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="remove_button">
<property name="label" translatable="yes">Remove</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Remove all picons from the receiver</property>
<property name="image">remove_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_remove" swapped="no"/>
<signal name="drag-data-received" handler="on_remove_button_drag_data_received" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="filter_button">
<property name="label" translatable="yes">Filter</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Filter</property>
<property name="image">filter_image</property>
<property name="always_show_image">True</property>
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="info_toggle_button">
<property name="label" translatable="yes">Details</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="image">info_toggle_button_image</property>
<property name="always_show_image">True</property>
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">5</property>
<property name="secondary">True</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="GtkButtonBox" id="downloader_action_box">
<property name="can_focus">False</property>
<property name="layout_style">expand</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="yes">Cancel</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Cancel</property>
<property name="valign">center</property>
<property name="image">cancel_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_cancel" swapped="no"/>
<accelerator key="z" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</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">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Load providers</property>
<property name="valign">center</property>
<property name="image">load_providers</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">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">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Receive picons</property>
<property name="valign">center</property>
<property name="image">receive_picons_image</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>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="convert_button">
<property name="label" translatable="yes">Convert</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Convert</property>
<property name="valign">center</property>
<property name="image">convert_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_convert" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkExpander" id="expander">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="resize_toplevel">True</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="height_request">150</property>
<property name="height_request">100</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
@@ -1704,6 +1757,8 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="wrap_mode">word-char</property>
<property name="left_margin">5</property>
<property name="right_margin">5</property>
<property name="overwrite">True</property>
</object>
</child>
@@ -1720,7 +1775,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>
@@ -1777,7 +1832,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">4</property>
</packing>
</child>
</object>

View File

@@ -1,22 +1,21 @@
import os
import re
import shutil
import subprocess
import tempfile
from pathlib import Path
from urllib.parse import urlparse, unquote
from gi.repository import GLib, GdkPixbuf
from gi.repository import GLib, GdkPixbuf, Gio
from app.commons import run_idle, run_task, run_with_delay
from app.connections import upload_data, DownloadType, download_data, remove_picons
from app.settings import SettingsType, Settings
from app.tools.picons import PiconsParser, parse_providers, Provider, convert_to
from app.tools.picons import PiconsParser, parse_providers, Provider, convert_to, download_picon
from app.tools.satellites import SatellitesParser, SatelliteSource
from .dialogs import show_dialog, DialogType, get_message
from .main_helper import (update_entry_data, append_text_to_tview, scroll_to, on_popup_menu, get_base_model, set_picon,
get_picon_pixbuf)
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column, KeyboardKey
from .main_helper import update_entry_data, append_text_to_tview, scroll_to, on_popup_menu, get_base_model, set_picon, \
get_picon_pixbuf
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column, GTK_PATH, KeyboardKey
class PiconsDialog:
@@ -30,6 +29,7 @@ class PiconsDialog:
self._POS_PATTERN = re.compile(r"^\d+\.\d+[EW]?$")
self._current_process = None
self._terminate = False
self._is_downloading = False
self._filter_binding = None
self._services = None
self._current_picon_info = None
@@ -72,6 +72,7 @@ class PiconsDialog:
"on_fiter_srcs_toggled": self.on_fiter_srcs_toggled,
"on_filter_services_switch": self.on_filter_services_switch,
"on_picon_activated": self.on_picon_activated,
"on_view_query_tooltip": self.on_view_query_tooltip,
"on_tree_view_key_press": self.on_tree_view_key_press,
"on_popup_menu": on_popup_menu}
@@ -103,10 +104,8 @@ class PiconsDialog:
self._picons_entry = builder.get_object("picons_entry")
self._url_entry = builder.get_object("url_entry")
self._picons_dir_entry = builder.get_object("picons_dir_entry")
self._info_bar = builder.get_object("info_bar")
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
self._info_check_button = builder.get_object("info_check_button")
self._info_toggle_button = builder.get_object("info_toggle_button")
self._picon_info_image = builder.get_object("picon_info_image")
self._picon_info_label = builder.get_object("picon_info_label")
self._load_providers_button = builder.get_object("load_providers_button")
@@ -124,24 +123,24 @@ class PiconsDialog:
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._explorer_action_box = builder.get_object("explorer_action_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)
self._load_providers_button.bind_property("visible", self._receive_button, "visible")
self._cancel_button.bind_property("visible", builder.get_object("receive_button"), "visible", 4)
self._cancel_button.bind_property("visible", self._load_providers_button, "visible", 4)
self._convert_button.bind_property("visible", self._explorer_action_box, "visible", 4)
downloader_action_box = builder.get_object("downloader_action_box")
self._explorer_action_box.bind_property("visible", downloader_action_box, "visible", 4)
self._convert_button.bind_property("visible", downloader_action_box, "visible", 4)
self._filter_bar.bind_property("search-mode-enabled", self._filter_bar, "visible")
self._explorer_src_path_button.bind_property("sensitive", builder.get_object("picons_view_sw"), "sensitive")
self._filter_button.bind_property("active", builder.get_object("filter_service_box"), "visible")
self._filter_button.bind_property("active", builder.get_object("src_title_grid"), "visible")
self._filter_button.bind_property("active", builder.get_object("dst_title_grid"), "visible")
self._filter_button.bind_property("visible", self._info_check_button, "visible")
self._filter_button.bind_property("visible", self._send_button, "visible")
self._filter_button.bind_property("visible", self._download_button, "visible")
self._filter_button.bind_property("visible", self._remove_button, "visible")
self._filter_button.bind_property("visible", self._info_toggle_button, "visible")
explorer_info_bar = builder.get_object("explorer_info_bar")
explorer_info_bar.bind_property("visible", builder.get_object("explorer_info_bar_frame"), "visible")
self._info_check_button.bind_property("active", explorer_info_bar, "visible")
self._info_toggle_button.bind_property("active", explorer_info_bar, "visible")
# Init drag-and-drop
self.init_drag_and_drop()
# Style
@@ -265,8 +264,9 @@ class PiconsDialog:
def on_picons_view_drag_data_get(self, view, drag_context, data, info, time):
model, path = view.get_selection().get_selected_rows()
if path:
data.set_uris([Path(model[path][-1]).as_uri(),
Path(self._explorer_dest_path_button.get_filename()).as_uri()])
p_uri = Path(model[path][-1]).as_uri()
dest_uri = Path(self._explorer_dest_path_button.get_filename()).as_uri()
data.set_uris(["{}::::{}".format(p_uri, dest_uri)])
def on_picons_src_view_drag_drop(self, view, drag_context, x, y, time):
view.stop_emission_by_name("drag_drop")
@@ -341,10 +341,11 @@ class PiconsDialog:
return
uris = data.get_uris()
if len(uris) == 2:
if uris:
name, fav_id = self._current_picon_info
src = urlparse(unquote(uris[0])).path
dst = "{}/{}".format(urlparse(unquote(uris[1])).path, name)
src, sep, dst = uris[0].partition("::::")
src = urlparse(unquote(src)).path
dst = "{}/{}".format(urlparse(unquote(dst)).path, name)
if src != dst:
shutil.copy(src, dst)
for row in get_base_model(self._picons_dest_view.get_model()):
@@ -357,7 +358,7 @@ class PiconsDialog:
def on_send_button_drag_data_received(self, button, drag_context, x, y, data, info, time):
path = self.get_path_from_uris(data)
if path:
if path and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.OK:
self.on_send(files_filter={path.name}, path=path.parent)
def on_download_button_drag_data_received(self, button, drag_context, x, y, data, info, time):
@@ -372,8 +373,9 @@ class PiconsDialog:
def get_path_from_uris(self, data):
uris = data.get_uris()
if len(uris) == 2:
return Path(urlparse(unquote(uris[0])).path).resolve()
if uris:
src, sep, dst = uris[0].partition("::::")
return Path(urlparse(unquote(src)).path).resolve()
def update_picon_in_lists(self, dst, fav_id):
picon = get_picon_pixbuf(dst)
@@ -385,7 +387,7 @@ class PiconsDialog:
def on_selective_send(self, view):
path = self.get_selected_path(view)
if path:
if path and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.OK:
self.on_send(files_filter={path.name}, path=path.parent)
def on_selective_download(self, view):
@@ -438,7 +440,7 @@ class PiconsDialog:
files_filter=files_filter), True)
def on_remove(self, item=None, files_filter=None):
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.OK:
return
self.run_func(lambda: remove_picons(settings=self._settings,
@@ -480,7 +482,7 @@ class PiconsDialog:
try:
for sat in sats:
pos = sat[1]
name, pos = "{} ({})".format(sat[0], pos), "{}{}".format("-" if pos[-1] == "W" else "", pos[:-1])
name = "{} ({})".format(sat[0], pos)
if not self._terminate and model:
if pos in self._sat_positions:
@@ -492,49 +494,29 @@ class PiconsDialog:
model = view.get_model()
self._url_entry.set_text(model.get(model.get_iter(path), 1)[0])
@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()
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_view.get_model()
model.clear()
self.append_providers(url, model)
model = self._providers_view.get_model()
model.clear()
self.get_providers(model)
@run_task
def append_providers(self, url, model):
self._current_process.wait()
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_providers(self, model):
providers = parse_providers(self._url_entry.get_text())
if providers:
self.append_providers(providers, model)
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 append_providers(self, providers, model):
for p in providers:
prv = p._replace(logo=self.get_pixbuf(p[0]) if p[0] else TV_ICON)
model.append(prv)
self.update_receive_button_state()
def get_pixbuf(self, img_data):
if img_data:
f = Gio.MemoryInputStream.new_from_data(img_data)
return GdkPixbuf.Pixbuf.new_from_stream_at_scale(f, 48, 32, True, None)
def on_receive(self, item):
self._cancel_button.show()
@@ -542,12 +524,12 @@ class PiconsDialog:
@run_task
def start_download(self):
if self._current_process.poll() is None:
if self._is_downloading:
self.show_dialog("The task is already running!", DialogType.ERROR)
return
self._terminate = False
self._expander.set_expanded(True)
self._is_downloading = True
GLib.idle_add(self._expander.set_expanded, True)
providers = self.get_selected_providers()
for prv in providers:
@@ -558,38 +540,50 @@ class PiconsDialog:
return
try:
for prv in providers:
if self._terminate:
return
self.process_provider(Provider(*prv))
picons_path = self._picons_dir_entry.get_text()
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
picons = []
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
# Getting links to picons.
futures = {executor.submit(self.process_provider, Provider(*p), picons_path): p for p in providers}
for future in concurrent.futures.as_completed(futures):
if not self._is_downloading:
executor.shutdown()
return
pic = future.result()
if pic:
picons.extend(pic)
# Getting picon images.
futures = {executor.submit(download_picon, pic[0], pic[1], self.append_output): pic for pic in picons}
done, not_done = concurrent.futures.wait(futures, timeout=0)
while self._is_downloading and not_done:
done, not_done = concurrent.futures.wait(not_done, timeout=5)
for future in not_done:
future.cancel()
concurrent.futures.wait(not_done)
if not self._is_downloading:
return
if not self._resize_no_radio_button.get_active():
self.resize(self._picons_dir_entry.get_text())
self.resize(picons_path)
else:
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
finally:
GLib.idle_add(self._cancel_button.hide)
self._terminate = False
self._is_downloading = False
def process_provider(self, prv):
url = prv.url
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
GLib.io_add_watch(self._current_process.stderr, GLib.IO_IN, self.write_to_buffer)
self._current_process.wait()
path = self._TMP_DIR + (url[url.find("//") + 2:] if prv.single else self._BASE_URL + url[url.rfind("/") + 1:])
PiconsParser.parse(path, self._picons_dir_entry.get_text(),
self._TMP_DIR, prv, self._picon_ids, self.get_picons_format())
def write_to_buffer(self, fd, condition):
if condition == GLib.IO_IN:
char = fd.read(1)
self.append_output(char)
return True
return False
def process_provider(self, prv, picons_path):
self.append_output("Getting links to picons for: {}.\n".format(prv.name))
return PiconsParser.parse(prv, picons_path, self._picon_ids, self.get_picons_format())
@run_idle
def append_output(self, char):
@@ -615,7 +609,7 @@ class PiconsDialog:
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
def on_cancel(self, item=None):
if self.is_task_running() and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
if self._is_downloading and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return True
self.terminate_task()
@@ -623,16 +617,15 @@ class PiconsDialog:
@run_task
def terminate_task(self):
self._terminate = True
if self._current_process:
self._current_process.terminate()
self.show_info_message(get_message("The task is canceled!"), Gtk.MessageType.WARNING)
self._is_downloading = False
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._terminate = True
self._is_downloading = False
self.save_window_size(window)
self.clean_data()
self._app.update_picons()
@@ -653,12 +646,12 @@ class PiconsDialog:
def run_func(self, func, update=False):
try:
GLib.idle_add(self._expander.set_expanded, True)
GLib.idle_add(self._header_download_box.set_sensitive, False)
GLib.idle_add(self._explorer_action_box.set_sensitive, False)
func()
except OSError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
finally:
GLib.idle_add(self._header_download_box.set_sensitive, True)
GLib.idle_add(self._explorer_action_box.set_sensitive, True)
if update:
self.on_picons_dest_changed(self._explorer_dest_path_button)
@@ -667,9 +660,10 @@ class PiconsDialog:
@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._info_bar.set_visible(False)
self._message_label.set_text(get_message(text))
self._info_bar.set_message_type(message_type)
self._info_bar.set_visible(True)
def on_picons_dir_open(self, entry, icon, event_button):
update_entry_data(entry, self._dialog, settings=self._settings)
@@ -737,7 +731,7 @@ class PiconsDialog:
map(lambda s: s.picon_id, filter(lambda s: txt in s.service.upper(), self._app.current_services.values())))
def on_picon_activated(self, view):
if self._info_check_button.get_active():
if self._info_toggle_button.get_active():
model, path = view.get_selection().get_selected_rows()
if not path:
return
@@ -765,6 +759,20 @@ class PiconsDialog:
get_message("System"), srv.system, get_message("Freq"), srv.freq,
ref)
def on_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
dest = view.get_dest_row_at_pos(x, y)
if not dest:
return False
path, pos = dest
model = view.get_model()
row = model[path][:]
tooltip.set_icon(get_picon_pixbuf(row[-1], size=self._settings.tooltip_logo_size))
tooltip.set_text(row[1])
view.set_tooltip_row(tooltip, path)
return True
def on_tree_view_key_press(self, view, event):
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):
@@ -787,9 +795,8 @@ class PiconsDialog:
def on_visible_page(self, stack: Gtk.Stack, param):
name = stack.get_visible_child_name()
self._convert_button.set_visible(name == "converter")
self._load_providers_button.set_visible(name == "downloader")
is_explorer = name == "explorer"
self._filter_button.set_visible(is_explorer)
self._explorer_action_box.set_visible(is_explorer)
if is_explorer:
self.on_picons_dest_changed(self._explorer_dest_path_button)
@@ -834,9 +841,6 @@ class PiconsDialog:
return picon_format
def is_task_running(self):
return self._current_process and self._current_process.poll() is None
if __name__ == "__main__":
pass

File diff suppressed because it is too large Load Diff

View File

@@ -48,7 +48,8 @@ class SatellitesDialog:
builder.set_translation_domain(TEXT_DOMAIN)
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"))
"left_header_menu", "popup_menu_add_image", "popup_menu_add_image_2",
"sat_editor_save_image", "sat_editor_update_image"))
builder.connect_signals(handlers)
self._window = builder.get_object("satellites_editor_window")
@@ -184,7 +185,7 @@ class SatellitesDialog:
model.set(edited_itr, {0: sat.name, 10: sat.flags, 11: sat.position})
else:
index = self.get_sat_position_index(sat.position, model)
model.insert(None, index, [sat.name, *self._aggr, sat.flags, sat.position])
model.insert(None, index, [sat.name, None, None, None, None, None, None, None, None, None, sat.flags, sat.position])
scroll_to(index, view)
def on_transponder(self, transponder=None, edited_itr=None):
@@ -209,7 +210,8 @@ class SatellitesDialog:
4: tr.fec_inner, 5: tr.system, 6: tr.modulation,
7: tr.pls_mode, 8: tr.pls_code, 9: tr.is_id})
else:
row = ["Transponder:", *tr, None, None]
row = ["Transponder:", tr.frequency, tr.symbol_rate, tr.polarization, tr.fec_inner,
tr.system, tr.modulation, tr.pls_mode, tr.pls_code, tr.is_id, None, None]
model, paths = view.get_selection().get_selected_rows()
itr = model.get_iter(paths[0])
view.expand_row(paths[0], 0)
@@ -253,13 +255,22 @@ class SatellitesDialog:
return paths
@staticmethod
def on_remove(view):
@run_idle
def on_remove(self, view):
""" Removal of selected satellites and transponders.
The satellites are removed first! Then transponders.
"""
selection = view.get_selection()
model, paths = selection.get_selected_rows()
for itr in [model.get_iter(path) for path in paths]:
model.remove(itr)
itrs = [model.get_iter(path) for path in paths]
satellites = list(filter(model.iter_has_child, itrs))
if len(satellites):
# Removing selected satellites.
list(map(model.remove, satellites))
else:
# Removing selected transponders.
list(map(model.remove, itrs))
@run_idle
def on_save(self, view):
@@ -462,7 +473,9 @@ class UpdateDialog:
("satellites_update_window", "update_source_store", "update_sat_list_store",
"update_sat_list_model_filter", "update_sat_list_model_sort", "side_store",
"pos_adjustment", "pos_adjustment2", "satellites_update_popup_menu",
"remove_selection_image", "update_transponder_store", "update_service_store"))
"remove_selection_image", "sat_update_cancel_image", "sat_receive_image",
"sat_update_filter_image", "sat_update_search_image", "sat_update_image",
"update_transponder_store", "update_service_store"))
builder.connect_signals(handlers)
self._window = builder.get_object("satellites_update_window")
@@ -529,7 +542,13 @@ class UpdateDialog:
@run_task
def get_sat_list(self, src, callback):
sats = self._parser.get_satellites_list(SatelliteSource.FLYSAT if src == 0 else SatelliteSource.LYNGSAT)
sat_src = SatelliteSource.FLYSAT
if src == 1:
sat_src = SatelliteSource.LYNGSAT
elif src == 2:
sat_src = SatelliteSource.KINGOFSAT
sats = self._parser.get_satellites_list(sat_src)
if sats:
callback(sats)
self.is_download = False
@@ -711,7 +730,8 @@ class SatellitesUpdateDialog(UpdateDialog):
self._main_model.remove(ch.iter)
for tr in sat[3]:
self._main_model.append(itr, ["Transponder:", *tr, None, None])
self._main_model.append(itr, ["Transponder:", tr.frequency, tr.symbol_rate, tr.polarization, tr.fec_inner,
tr.system, tr.modulation, tr.pls_mode, tr.pls_code, tr.is_id, None, None])
class ServicesUpdateDialog(UpdateDialog):
@@ -728,8 +748,8 @@ class ServicesUpdateDialog(UpdateDialog):
self._services_parser = ServicesParser(source=SatelliteSource.LYNGSAT)
self._transponder_paned.set_visible(True)
s_model = self._source_box.get_model()
s_model.remove(s_model.get_iter_first())
self._source_box.remove(0)
self._source_box.remove(1)
self._source_box.set_active(0)
# Transponder view popup menu
tr_popup_menu = Gtk.Menu()
@@ -823,9 +843,10 @@ class ServicesUpdateDialog(UpdateDialog):
appender.send("Consumed: {:0.0f}s, {} services received.".format(time.time() - start, len(services)))
try:
from app.eparser.enigma.lamedb import get_services_lines, get_services_list
from app.eparser.enigma.lamedb import LameDbReader
# Used for double checking!
srvs = get_services_list("".join(get_services_lines(services)))
reader = LameDbReader(path=None)
srvs = reader.get_services_list("".join(reader.get_services_lines(services)))
except ValueError as e:
log("ServicesUpdateDialog [on receive data] error: {}".format(e))
else:
@@ -937,9 +958,10 @@ class ServicesUpdateDialog(UpdateDialog):
def append_satellite(model, sat):
""" Common function for append satellite to the model """
name, flags, pos, transponders = sat
parent = model.append(None, [name, *(None,) * 9, flags, pos])
for transponder in transponders:
model.append(parent, ["Transponder:", *transponder, None, None])
parent = model.append(None, [name, None, None, None, None, None, None, None, None, None, flags, pos])
for tr in transponders:
model.append(parent, ["Transponder:", tr.frequency, tr.symbol_rate, tr.polarization, tr.fec_inner, tr.system,
tr.modulation, tr.pls_mode, tr.pls_code, tr.is_id, None, None])
if __name__ == "__main__":

View File

@@ -1,7 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<!-- Generated with glade 3.22.2
The MIT License (MIT)
Copyright (c) 2018-2021 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 satellite list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2021 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="fec_list_store">
<columns>
<!-- column-name fec -->
@@ -9,40 +40,53 @@
</columns>
<data>
<row>
<col id="0" translatable="yes">Auto</col>
<col id="0">Auto</col>
</row>
<row>
<col id="0" translatable="yes">1/2</col>
<col id="0">1/2</col>
</row>
<row>
<col id="0" translatable="yes">2/3</col>
</row>
<row>
<col id="0" translatable="yes">3/4</col>
<col id="0">3/4</col>
</row>
<row>
<col id="0" translatable="yes">5/6</col>
<col id="0">5/6</col>
</row>
<row>
<col id="0" translatable="yes">7/8</col>
<col id="0">7/8</col>
</row>
<row>
<col id="0" translatable="yes">8/9</col>
<col id="0">8/9</col>
</row>
<row>
<col id="0" translatable="yes">3/5</col>
<col id="0">3/5</col>
</row>
<row>
<col id="0" translatable="yes">4/5</col>
<col id="0">4/5</col>
</row>
<row>
<col id="0" translatable="yes">6/7</col>
<col id="0">6/7</col>
</row>
<row>
<col id="0" translatable="yes">9/10</col>
<col id="0">9/10</col>
</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="GtkListStore" id="invertion_list_store">
<columns>
<!-- column-name invertion -->
@@ -50,13 +94,13 @@
</columns>
<data>
<row>
<col id="0" translatable="yes">Off</col>
<col id="0">Off</col>
</row>
<row>
<col id="0" translatable="yes">On</col>
<col id="0">On</col>
</row>
<row>
<col id="0" translatable="yes">Auto</col>
<col id="0">Auto</col>
</row>
</data>
</object>
@@ -67,19 +111,19 @@
</columns>
<data>
<row>
<col id="0" translatable="yes">Auto</col>
<col id="0">Auto</col>
</row>
<row>
<col id="0" translatable="yes">QPSK</col>
<col id="0">QPSK</col>
</row>
<row>
<col id="0" translatable="yes">8PSK</col>
<col id="0">8PSK</col>
</row>
<row>
<col id="0" translatable="yes">16APSK</col>
<col id="0">16APSK</col>
</row>
<row>
<col id="0" translatable="yes">32APSK</col>
<col id="0">32APSK</col>
</row>
</data>
</object>
@@ -90,13 +134,13 @@
</columns>
<data>
<row>
<col id="0" translatable="yes">Off</col>
<col id="0">Off</col>
</row>
<row>
<col id="0" translatable="yes">On</col>
<col id="0">On</col>
</row>
<row>
<col id="0" translatable="yes">Auto</col>
<col id="0">Auto</col>
</row>
</data>
</object>
@@ -107,13 +151,13 @@
</columns>
<data>
<row>
<col id="0" translatable="yes">Root</col>
<col id="0">Root</col>
</row>
<row>
<col id="0" translatable="yes">Gold</col>
<col id="0">Gold</col>
</row>
<row>
<col id="0" translatable="yes">Combo</col>
<col id="0">Combo</col>
</row>
</data>
</object>
@@ -124,16 +168,16 @@
</columns>
<data>
<row>
<col id="0" translatable="yes">H</col>
<col id="0">H</col>
</row>
<row>
<col id="0" translatable="yes">V</col>
<col id="0">V</col>
</row>
<row>
<col id="0" translatable="yes">R</col>
<col id="0">R</col>
</row>
<row>
<col id="0" translatable="yes">L</col>
<col id="0">L</col>
</row>
</data>
</object>
@@ -144,21 +188,20 @@
</columns>
<data>
<row>
<col id="0" translatable="yes">35%</col>
<col id="0">35%</col>
</row>
<row>
<col id="0" translatable="yes">25%</col>
<col id="0">25%</col>
</row>
<row>
<col id="0" translatable="yes">20%</col>
<col id="0">20%</col>
</row>
<row>
<col id="0" translatable="yes">Auto</col>
<col id="0">Auto</col>
</row>
</data>
</object>
<object class="GtkAdjustment" id="sat_pos_adjustment">
<property name="lower">-180</property>
<property name="upper">180</property>
<property name="step_increment">0.10000000000000001</property>
<property name="page_increment">10</property>
@@ -210,26 +253,13 @@
</columns>
<data>
<row>
<col id="0" translatable="yes">DVB-S</col>
<col id="0">DVB-S</col>
</row>
<row>
<col id="0" translatable="yes">DVB-S2</col>
<col id="0">DVB-S2</col>
</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">{use_header}</property>
<property name="can_focus">False</property>
@@ -246,40 +276,6 @@
<child type="titlebar">
<placeholder/>
</child>
<child type="action">
<object class="GtkButton" id="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="tooltip_text" translatable="yes">Cancel</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
</child>
<child type="action">
<object class="GtkButton" id="apply_button">
<property name="label">gtk-save</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Save current service</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_save" swapped="no"/>
</object>
</child>
<child type="action">
<object class="GtkButton" id="create_button">
<property name="label">gtk-new</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Create and save as new service</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_create_new" swapped="no"/>
</object>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="dialog_vbox">
<property name="can_focus">False</property>
@@ -290,6 +286,55 @@
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_cancel" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="apply_button">
<property name="label" translatable="yes">Apply</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Save current service</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_save" swapped="no"/>
<accelerator key="Return" signal="activate"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="create_button">
<property name="label" translatable="yes">Create</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Create and save as new service</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_create_new" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -335,7 +380,6 @@
<object class="GtkEntry" id="name_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_stock">gtk-edit</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -357,7 +401,7 @@
<object class="GtkEntry" id="package_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_stock">gtk-edit</property>
</object>
<packing>
<property name="left_attach">1</property>
@@ -381,7 +425,7 @@
<property name="can_focus">True</property>
<property name="width_chars">10</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
<signal name="key-release-event" handler="update_reference" swapped="no"/>
</object>
@@ -426,7 +470,7 @@
<property name="can_focus">True</property>
<property name="width_chars">7</property>
<property name="max_width_chars">7</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
<signal name="changed" handler="update_reference" swapped="no"/>
</object>
@@ -864,7 +908,7 @@
<property name="tooltip_text">C:0000,C:a1b2,etc.</property>
<property name="width_chars">15</property>
<property name="max_width_chars">26</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="placeholder_text" translatable="yes">C:0000,C:a1b2,etc.</property>
<signal name="changed" handler="on_cas_entry_changed" swapped="no"/>
</object>
@@ -967,7 +1011,7 @@
<property name="can_focus">True</property>
<property name="width_chars">12</property>
<property name="max_width_chars">12</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
</object>
<packing>
@@ -993,7 +1037,7 @@
<property name="can_focus">True</property>
<property name="width_chars">12</property>
<property name="max_width_chars">12</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
</object>
<packing>
@@ -1072,7 +1116,7 @@
<property name="can_focus">True</property>
<property name="width_chars">12</property>
<property name="max_width_chars">12</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
<signal name="key-release-event" handler="update_reference" swapped="no"/>
</object>
@@ -1092,21 +1136,6 @@
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="sat_pos_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="input_purpose">number</property>
<property name="adjustment">sat_pos_adjustment</property>
<property name="digits">1</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="tid_label">
<property name="visible">True</property>
@@ -1125,7 +1154,7 @@
<property name="can_focus">True</property>
<property name="width_chars">8</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
<signal name="key-release-event" handler="update_reference" swapped="no"/>
</object>
@@ -1152,7 +1181,7 @@
<property name="can_focus">True</property>
<property name="width_chars">8</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
<signal name="key-release-event" handler="update_reference" swapped="no"/>
</object>
@@ -1161,6 +1190,50 @@
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="sat_pos_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">1</property>
<child>
<object class="GtkSpinButton" id="sat_pos_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="input_purpose">number</property>
<property name="adjustment">sat_pos_adjustment</property>
<property name="digits">1</property>
<property name="numeric">True</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="pos_side_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="active">0</property>
<items>
<item id="E">E</item>
<item id="W">W</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -1317,7 +1390,7 @@
<property name="can_focus">True</property>
<property name="width_chars">8</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_digit_entry_changed" swapped="no"/>
</object>
<packing>
@@ -1343,7 +1416,7 @@
<property name="can_focus">True</property>
<property name="width_chars">8</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_digit_entry_changed" swapped="no"/>
</object>
<packing>
@@ -1369,7 +1442,7 @@
<property name="can_focus">True</property>
<property name="width_chars">8</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_stock">gtk-edit</property>
<signal name="changed" handler="on_digit_entry_changed" swapped="no"/>
</object>
<packing>
@@ -1495,10 +1568,10 @@
</child>
<child>
<object class="GtkImage" id="tr_edit_switch_image">
<property name="visible">True</property>
<property name="visible">False</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="stock">gtk-edit</property>
<property name="icon_name">document-edit-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1526,9 +1599,6 @@
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">cancel_button</action-widget>
</action-widgets>
</object>
<object class="GtkListStore" id="transponder_services_liststore">
<columns>
@@ -1562,26 +1632,6 @@
<child>
<placeholder/>
</child>
<child type="action">
<object class="GtkButton" id="tr_services_no_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="action">
<object class="GtkButton" id="tr_services_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="tr_services_dialog_vbox">
<property name="can_focus">False</property>
@@ -1590,6 +1640,35 @@
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="valign">center</property>
<child>
<object class="GtkButton" id="tr_services_no_button">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</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="tr_services_ok_button">
<property name="label" translatable="yes">OK</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</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>
@@ -1616,7 +1695,7 @@
<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="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>

View File

@@ -1,12 +1,12 @@
import os
import re
from app.commons import run_idle
from app.commons import run_idle, log
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, T_MODULATION, C_MODULATION,
TrType, SystemCable, T_SYSTEM, BANDWIDTH, TRANSMISSION_MODE, GUARD_INTERVAL, T_FEC,
HIERARCHY)
HIERARCHY, A_MODULATION)
from app.settings import SettingsType
from .dialogs import show_dialog, DialogType, Action, get_dialogs_string
from .main_helper import get_base_model
@@ -43,7 +43,8 @@ class ServiceDetailsDialog:
"update_reference": self.update_reference,
"on_cas_entry_changed": self.on_cas_entry_changed,
"on_digit_entry_changed": self.on_digit_entry_changed,
"on_non_empty_entry_changed": self.on_non_empty_entry_changed}
"on_non_empty_entry_changed": self.on_non_empty_entry_changed,
"on_cancel": lambda item: self._dialog.destroy()}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
@@ -54,7 +55,7 @@ class ServiceDetailsDialog:
self._dialog = builder.get_object("service_details_dialog")
self._dialog.set_transient_for(transient)
self._s_type = settings.setting_type
self._tr_type = None
self._tr_type = TrType.Satellite
self._satellites_xml_path = settings.data_local_path + "satellites.xml"
self._picons_dir_path = settings.picons_local_path
self._services_view = srv_view
@@ -120,6 +121,7 @@ class ServiceDetailsDialog:
self._pids_grid = builder.get_object("pids_grid")
# Transponder elements
self._sat_pos_button = builder.get_object("sat_pos_button")
self._pos_side_box = builder.get_object("pos_side_box")
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")
@@ -137,7 +139,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_lp_combo_box)
self._rate_entry, self._rate_lp_combo_box, self._pos_side_box)
if self._action is Action.EDIT:
self.update_data_elements()
@@ -145,12 +147,7 @@ class ServiceDetailsDialog:
self.init_default_data_elements()
def show(self):
response = self._dialog.run()
if response == Gtk.ResponseType.OK:
pass
self._dialog.destroy()
return response
self._dialog.show()
@run_idle
def init_default_data_elements(self):
@@ -209,6 +206,8 @@ class ServiceDetailsDialog:
self.update_ui_for_terrestrial()
elif self._tr_type is TrType.Cable:
self.update_ui_for_cable()
elif self._tr_type is TrType.ATSC:
self.update_ui_for_atsc()
else:
self.set_sat_positions(srv.pos)
@@ -310,6 +309,11 @@ class ServiceDetailsDialog:
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]))
elif tr_type is TrType.ATSC:
self._sys_combo_box.set_active(0)
self.select_active_text(self._mod_combo_box, A_MODULATION.get(tr_data[2]))
self.select_active_text(self._invertion_combo_box, Inversion(tr_data[1]).name)
# Should be called last to properly initialize the reference
self._srv_type_entry.set_text(data[4])
@@ -336,7 +340,8 @@ class ServiceDetailsDialog:
def set_sat_positions(self, sat_pos):
""" Sat positions initialisation """
self._sat_pos_button.set_value(float(sat_pos))
self._sat_pos_button.set_value(float(sat_pos[:-1]))
self._pos_side_box.set_active_id(sat_pos[-1:])
def on_system_changed(self, box):
if not self._tr_edit_switch.get_active():
@@ -376,18 +381,18 @@ class ServiceDetailsDialog:
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
self.on_edit() if self._action is Action.EDIT else self.on_new()
self._dialog.destroy()
if 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!")
return True
def on_edit(self):
""" Edit current service. """
fav_id, data_id = self.get_srv_data()
# transponder
# Transponder
transponder = self._old_service.transponder
if self._tr_edit_switch.get_active():
try:
@@ -397,17 +402,24 @@ class ServiceDetailsDialog:
transponder = self.get_terrestrial_transponder_data()
elif self._tr_type is TrType.Cable:
transponder = self.get_cable_transponder_data()
elif self._tr_type is TrType.ATSC:
transponder = self.get_atsc_transponder_data()
except Exception as e:
print(e)
log("Edit service error: {}".format(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
self.update_transponder_services(transponder, self.get_sat_position())
# Service
service = self.get_service(fav_id, data_id, transponder)
old_fav_id = self._old_service.fav_id
if old_fav_id != fav_id:
if fav_id in self._services:
msg = "{}\n\n\t{}".format("A similar service is already in this list!", "Are you sure?")
if show_dialog(DialogType.QUESTION, transient=self._dialog, text=msg) != Gtk.ResponseType.OK:
return False
self.update_bouquets(fav_id, old_fav_id)
self._services[fav_id] = service
if self._old_service.picon_id != service.picon_id:
@@ -415,7 +427,7 @@ class ServiceDetailsDialog:
flags = service.flags_cas
extra_data = {Column.SRV_TOOLTIP: None, Column.SRV_BACKGROUND: None}
if flags:
if self._s_type is SettingsType.ENIGMA_2 and 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
@@ -424,6 +436,7 @@ class ServiceDetailsDialog:
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
return True
def update_bouquets(self, fav_id, old_fav_id):
self._services.pop(old_fav_id, None)
@@ -491,7 +504,9 @@ class ServiceDetailsDialog:
if self._s_type is SettingsType.ENIGMA_2:
return self.get_enigma2_flags()
elif self._s_type is SettingsType.NEUTRINO_MP:
return self._old_service.flags_cas
flags = self._old_service.flags_cas.split(":")
flags[1] = self.get_sat_position()
return ":".join(flags)
def get_enigma2_flags(self):
flags = ["p:{}".format(self._package_entry.get_text())]
@@ -543,7 +558,9 @@ class ServiceDetailsDialog:
return fav_id, data_id
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
data_id = self._old_service.data_id.split(":")
data_id[1] = "{:x}".format(int(service_type))
return fav_id, ":".join(data_id)
# ***************** Transponder ********************* #
@@ -551,27 +568,27 @@ class ServiceDetailsDialog:
freq = self._freq_entry.get_text()
fec = self._fec_combo_box.get_active_id()
system = self._sys_combo_box.get_active_id()
o_srv = self._old_service
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()
pos = str(round(self._sat_pos_button.get_value(), 1))
pos = "{}{}".format(round(self._sat_pos_button.get_value(), 1), self._pos_side_box.get_active_id())
return freq, rate, pol, fec, system, pos
elif self._tr_type is TrType.Terrestrial:
o_srv = self._old_service
elif self._tr_type in (TrType.Terrestrial, TrType.ATSC):
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_satellite_transponder_data(self):
sys = self._sys_combo_box.get_active_id()
freq = self._freq_entry.get_text()
rate = self._rate_entry.get_text()
freq = "{}000".format(self._freq_entry.get_text())
rate = "{}000".format(self._rate_entry.get_text())
pol = self.get_value_from_combobox_id(self._pol_combo_box, POLARIZATION)
fec = self.get_value_from_combobox_id(self._fec_combo_box, FEC_DEFAULT)
sat_pos = str(round(self._sat_pos_button.get_value(), 1)).replace(".", "")
sat_pos = self.get_sat_position()
inv = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
srv_sys = "0" # !!!
@@ -595,12 +612,17 @@ class ServiceDetailsDialog:
srv_sys = None
return self._NEUTRINO_TRANSPONDER_DATA.format(tr_id, on_id, freq, inv, rate, fec, pol, mod, srv_sys)
def get_sat_position(self):
sat_pos = self._sat_pos_button.get_value() * (-1 if self._pos_side_box.get_active_id() == "W" else 1)
sat_pos = str(round(sat_pos, 1)).replace(".", "")
return sat_pos
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[1] = "{}000".format(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)
@@ -610,28 +632,50 @@ class ServiceDetailsDialog:
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[1] = "{}000".format(self._freq_entry.get_text())
tr_data[2] = "{}000".format(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):
def get_atsc_transponder_data(self):
tr_data = re.split("\s|:", self._old_service.transponder)
# frequency, inversion, modulation, system
tr_data[1] = "{}000".format(self._freq_entry.get_text())
tr_data[2] = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
tr_data[3] = self.get_value_from_combobox_id(self._mod_combo_box, A_MODULATION)
return "{} {}".format(tr_data[0], ":".join(tr_data[1:]))
def update_transponder_services(self, transponder, sat_pos):
for itr in self._transponder_services_iters:
srv = self._current_model[itr][:Column.SRV_TOOLTIP]
srv = self._current_model[itr][:]
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)})
fav_id = srv[Column.SRV_FAV_ID]
old_srv = self._services.pop(fav_id, None)
if not old_srv:
log("Update transponder services error: No service found for ID {}".format(srv[Column.SRV_FAV_ID]))
continue
if self._s_type is SettingsType.NEUTRINO_MP:
flags = srv[Column.SRV_CAS_FLAGS].split(":")
flags[1] = sat_pos
srv[Column.SRV_CAS_FLAGS] = ":".join(flags)
self._services[fav_id] = Service(*srv[:Column.SRV_TOOLTIP])
self._current_model.set_row(itr, srv)
# ***************** Others *********************#
@@ -660,16 +704,16 @@ class ServiceDetailsDialog:
if active and self._action is Action.EDIT:
self._transponder_services_iters = []
response = TransponderServicesDialog(self._dialog,
self._current_model,
self._services_view,
self._old_service.transponder,
self._transponder_services_iters).show()
if response == Gtk.ResponseType.CANCEL or response == -4:
if response == Gtk.ResponseType.CANCEL or response == Gtk.ResponseType.DELETE_EVENT:
switch.set_active(False)
self._transponder_services_iters = None
return
self.update_dvb_s2_elements(active and (self._sys_combo_box.get_active_id() == "DVB-S2"
or self._old_service.transponder_type in "tc"))
or self._old_service.transponder_type in "tca"))
for elem in self._TRANSPONDER_ELEMENTS:
elem.set_sensitive(active)
@@ -800,6 +844,18 @@ class ServiceDetailsDialog:
self._transponder_id_entry.set_max_width_chars(8)
self._network_id_entry.set_max_width_chars(8)
def update_ui_for_atsc(self):
self.update_ui_for_cable()
tr_grid = self._builder.get_object("tr_grid")
tr_grid.remove_column(1)
tr_grid.remove_column(1)
# Init models
fec_model, modulation_model, system_model = self.get_models_for_non_satellite()
system_model.append((TrType.ATSC.name,))
[modulation_model.append((v,)) for k, v in A_MODULATION.items()]
# Extra
self._namespace_entry.set_max_width_chars(25)
def get_transponder_grid_for_non_satellite(self):
self._pids_grid.set_visible(False)
tr_grid = self._builder.get_object("tr_grid")
@@ -817,7 +873,7 @@ class ServiceDetailsDialog:
class TransponderServicesDialog:
def __init__(self, transient, model, transponder, tr_iters):
def __init__(self, transient, services_view, transponder, tr_iters):
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),
@@ -825,15 +881,18 @@ class TransponderServicesDialog:
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)
self.append_services(services_view, 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):
def append_services(self, view, transponder, tr_iters):
model = view.get_model()
filter_model = model.get_model()
for row in model:
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))
itr = model.get_iter(row.path)
tr_iters.append(filter_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(itr)))
def show(self):
response = self._dialog.run()

File diff suppressed because it is too large Load Diff

View File

@@ -3,10 +3,10 @@ import re
from app.commons import run_task, run_idle, log
from app.connections import test_telnet, test_ftp, TestException, test_http, HttpApiException
from app.settings import SettingsType, Settings, PlayStreamsMode
from app.settings import SettingsType, Settings, PlayStreamsMode, IS_WIN
from app.ui.dialogs import show_dialog, DialogType, get_message, get_chooser_dialog
from .main_helper import update_entry_data, scroll_to, get_picon_pixbuf
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON, APP_FONT
def show_settings_dialog(transient, options):
@@ -50,6 +50,7 @@ class SettingsDialog:
"on_apply_presets": self.on_apply_presets,
"on_digit_entry_changed": self.on_digit_entry_changed,
"on_view_popup_menu": self.on_view_popup_menu,
"on_list_font_reset": self.on_list_font_reset,
"on_theme_changed": self.on_theme_changed,
"on_theme_add": self.on_theme_add,
"on_theme_remove": self.on_theme_remove,
@@ -104,7 +105,6 @@ class SettingsDialog:
self._support_ver5_switch = builder.get_object("support_ver5_switch")
self._force_bq_name_switch = builder.get_object("force_bq_name_switch")
# Streaming
header_separator = builder.get_object("header_separator")
self._apply_presets_button = builder.get_object("apply_presets_button")
self._transcoding_switch = builder.get_object("transcoding_switch")
self._edit_preset_switch = builder.get_object("edit_preset_switch")
@@ -116,26 +116,31 @@ class SettingsDialog:
self._audio_channels_combo_box = builder.get_object("audio_channels_combo_box")
self._audio_sample_rate_combo_box = builder.get_object("audio_sample_rate_combo_box")
self._audio_codec_combo_box = builder.get_object("audio_codec_combo_box")
self._apply_presets_button.bind_property("visible", header_separator, "visible")
self._transcoding_switch.bind_property("active", builder.get_object("record_box"), "sensitive")
self._edit_preset_switch.bind_property("active", self._apply_presets_button, "sensitive")
self._edit_preset_switch.bind_property("active", builder.get_object("video_options_frame"), "sensitive")
self._edit_preset_switch.bind_property("active", builder.get_object("audio_options_frame"), "sensitive")
self._play_in_built_radio_button = builder.get_object("play_in_built_radio_button")
self._play_in_vlc_radio_button = builder.get_object("play_in_vlc_radio_button")
self._play_in_window_radio_button = builder.get_object("play_in_window_radio_button")
self._get_m3u_radio_button = builder.get_object("get_m3u_radio_button")
self._gst_lib_button = builder.get_object("gst_lib_button")
self._vlc_lib_button = builder.get_object("vlc_lib_button")
self._mpv_lib_button = builder.get_object("mpv_lib_button")
# Program
self._before_save_switch = builder.get_object("before_save_switch")
self._before_downloading_switch = builder.get_object("before_downloading_switch")
self._enable_experimental_box = builder.get_object("enable_experimental_box")
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")
self._bouquet_hints_switch = builder.get_object("bouquet_hints_switch")
self._services_hints_switch = builder.get_object("services_hints_switch")
self._lang_combo_box = builder.get_object("lang_combo_box")
# Appearance
self._list_font_button = builder.get_object("list_font_button")
self._picons_size_button = builder.get_object("picons_size_button")
self._tooltip_logo_size_button = builder.get_object("tooltip_logo_size_button")
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")
# Extra
self._support_http_api_switch = builder.get_object("support_http_api_switch")
self._enable_yt_dl_switch = builder.get_object("enable_yt_dl_switch")
@@ -165,7 +170,7 @@ class SettingsDialog:
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", header_separator, "visible")
self._apply_profile_button.bind_property("visible", builder.get_object("reset_button"), "visible")
# Style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
@@ -177,26 +182,29 @@ class SettingsDialog:
self.init_ui_elements(self._s_type)
self.init_profiles()
if self._settings.is_darwin:
# Appearance
self._appearance_box = builder.get_object("appearance_box")
self._appearance_box.set_visible(True)
if IS_WIN:
builder.get_object("streams_lib_frame").set_visible(False)
# Themes
enable_exp = self._settings.is_enable_experimental
builder.get_object("style_frame").set_visible(enable_exp)
builder.get_object("themes_support_frame").set_visible(enable_exp)
self._layout_switch = builder.get_object("layout_switch")
self._layout_switch.set_active(self._ext_settings.alternate_layout)
self._theme_frame = builder.get_object("theme_frame")
self._theme_frame.set_visible(enable_exp)
self._theme_thumbnail_image = builder.get_object("theme_thumbnail_image")
self._theme_combo_box = builder.get_object("theme_combo_box")
self._icon_theme_combo_box = builder.get_object("icon_theme_combo_box")
self._dark_mode_switch = builder.get_object("dark_mode_switch")
self._layout_switch = builder.get_object("layout_switch")
self._layout_switch.bind_property("active", builder.get_object("bouquet_box"), "sensitive")
self._themes_support_switch = builder.get_object("themes_support_switch")
self._themes_support_switch.bind_property("active", builder.get_object("gtk_theme_frame"), "sensitive")
self._themes_support_switch.bind_property("active", builder.get_object("icon_theme_frame"), "sensitive")
self.init_appearance()
self._themes_support_switch.bind_property("active", self._theme_frame, "sensitive")
self.init_themes()
@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.update_title()
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._ext_settings.language)
@@ -214,12 +222,12 @@ class SettingsDialog:
self.on_profile_selected(self._profile_view, False)
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(":")
def update_title(self):
title = "{} [{}]"
if self._s_type is SettingsType.ENIGMA_2:
self._header_bar.set_subtitle("{}: {}".format(label, self._enigma_radio_button.get_label()))
self._dialog.set_title(title.format(get_message("Options"), 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()))
self._dialog.set_title(title.format(get_message("Options"), self._neutrino_radio_button.get_label()))
def show(self):
self._dialog.run()
@@ -269,6 +277,7 @@ class SettingsDialog:
self._before_downloading_switch.set_active(self._settings.backup_before_downloading)
self.set_fav_click_mode(self._settings.fav_click_mode)
self.set_play_stream_mode(self._settings.play_streams_mode)
self.set_stream_lib(self._settings.stream_lib)
self._load_on_startup_switch.set_active(self._settings.load_last_config)
self._bouquet_hints_switch.set_active(self._settings.show_bq_hints)
self._services_hints_switch.set_active(self._settings.show_srv_hints)
@@ -276,6 +285,9 @@ class SettingsDialog:
self._transcoding_switch.set_active(self._settings.activate_transcoding)
self._presets_combo_box.set_active_id(self._settings.active_preset)
self.on_transcoding_preset_changed(self._presets_combo_box)
self._picons_size_button.set_active_id(str(self._settings.list_picon_size))
self._tooltip_logo_size_button.set_active_id(str(self._settings.tooltip_logo_size))
self._list_font_button.set_font(self._settings.list_font)
if self._s_type is SettingsType.ENIGMA_2:
self._enable_exp_switch.set_active(self._settings.is_enable_experimental)
@@ -331,6 +343,7 @@ class SettingsDialog:
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.play_streams_mode = self.get_play_stream_mode()
self._ext_settings.stream_lib = self.get_stream_lib()
self._ext_settings.language = self._lang_combo_box.get_active_id()
self._ext_settings.load_last_config = self._load_on_startup_switch.get_active()
self._ext_settings.show_bq_hints = self._bouquet_hints_switch.get_active()
@@ -340,8 +353,11 @@ class SettingsDialog:
self._ext_settings.records_path = self._record_data_dir_field.get_text()
self._ext_settings.activate_transcoding = self._transcoding_switch.get_active()
self._ext_settings.active_preset = self._presets_combo_box.get_active_id()
self._ext_settings.list_picon_size = int(self._picons_size_button.get_active_id())
self._ext_settings.tooltip_logo_size = int(self._tooltip_logo_size_button.get_active_id())
self._ext_settings.list_font = self._list_font_button.get_font()
if self._ext_settings.is_darwin:
if IS_WIN:
self._ext_settings.dark_mode = self._dark_mode_switch.get_active()
self._ext_settings.alternate_layout = self._layout_switch.get_active()
self._ext_settings.is_themes_support = self._themes_support_switch.get_active()
@@ -597,29 +613,48 @@ class SettingsDialog:
return FavClickMode.DISABLED
def on_play_mode_changed(self, button):
if self._main_stack.get_visible_child_name() != "streaming":
if self._main_stack.get_visible_child_name() != "streaming" or not button.get_active():
return
if self._settings.is_darwin:
is_gst = self._gst_lib_button.get_active()
self._play_in_built_radio_button.set_sensitive(is_gst)
self._play_in_window_radio_button.set_active(not is_gst and self._play_in_built_radio_button.get_active())
if button.get_active():
self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING)
@run_idle
def set_play_stream_mode(self, mode):
self._play_in_built_radio_button.set_sensitive(not self._settings.is_darwin)
self._play_in_built_radio_button.set_active(mode is PlayStreamsMode.BUILT_IN)
self._play_in_vlc_radio_button.set_active(mode is PlayStreamsMode.VLC)
self._play_in_window_radio_button.set_active(mode is PlayStreamsMode.WINDOW)
self._get_m3u_radio_button.set_active(mode is PlayStreamsMode.M3U)
if self._settings.is_darwin and self._settings.stream_lib != "gst":
self._play_in_built_radio_button.set_sensitive(False)
def get_play_stream_mode(self):
if self._play_in_built_radio_button.get_active():
return PlayStreamsMode.BUILT_IN
if self._play_in_vlc_radio_button.get_active():
return PlayStreamsMode.VLC
if self._play_in_window_radio_button.get_active():
return PlayStreamsMode.WINDOW
if self._get_m3u_radio_button.get_active():
return PlayStreamsMode.M3U
return self._settings.play_streams_mode
def set_stream_lib(self, mode):
self._vlc_lib_button.set_active(mode == "vlc")
self._gst_lib_button.set_active(mode == "gst")
self._mpv_lib_button.set_active(mode == "mpv")
def get_stream_lib(self):
if self._gst_lib_button.get_active():
return "gst"
elif self._vlc_lib_button.get_active():
return "vlc"
return "mpv"
def on_transcoding_preset_changed(self, button):
presets = self._settings.transcoding_presets
prs = presets.get(button.get_active_id())
@@ -664,6 +699,11 @@ class SettingsDialog:
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_list_font_reset(self, button):
self._list_font_button.set_font(APP_FONT)
# ******************* Themes *********************** #
def on_theme_changed(self, button):
if self._main_stack.get_visible_child_name() != "appearance":
return
@@ -702,7 +742,7 @@ class SettingsDialog:
response = get_chooser_dialog(self._dialog, self._settings, "Themes Archive [*.xz, *.zip]", ("*.xz", "*.zip"))
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
self._appearance_box.set_sensitive(False)
self._theme_frame.set_sensitive(False)
self.unpack_theme(response, path, button)
@run_task
@@ -719,7 +759,6 @@ class SettingsDialog:
log("Unpacking end.")
finally:
self.update_theme_button(button, dst)
self._appearance_box.set_sensitive(True)
@run_idle
def update_theme_button(self, button, dst):
@@ -732,6 +771,7 @@ class SettingsDialog:
button.append(theme, theme)
button.set_active_id(theme)
self.show_info_message("Done!", Gtk.MessageType.INFO)
self._theme_frame.set_sensitive(True)
@run_idle
def remove_theme(self, button, path):
@@ -755,9 +795,8 @@ class SettingsDialog:
button.set_active(0)
@run_idle
def init_appearance(self):
def init_themes(self):
self._dark_mode_switch.set_active(self._ext_settings.dark_mode)
self._layout_switch.set_active(self._ext_settings.alternate_layout)
t_support = self._ext_settings.is_themes_support
self._themes_support_switch.set_active(t_support)
if t_support:

250
app/ui/telnet.glade Executable file
View File

@@ -0,0 +1,250 @@
<?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-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkTextTagTable" id="tag_table">
<child type="tag">
<object class="GtkTextTag" id="end_tag">
<property name="font">Normal</property>
<property name="editable">False</property>
</object>
</child>
</object>
<object class="GtkTextBuffer" id="text_buffer">
<property name="tag_table">tag_table</property>
</object>
<object class="GtkWindow" id="dialog_window">
<property name="can_focus">False</property>
<property name="title" translatable="yes">DemonEditor [Telnet client]</property>
<property name="destroy_with_parent">True</property>
<property name="icon_name">terminal</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<signal name="delete-event" handler="on_close" swapped="no"/>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox" id="main_box">
<property name="width_request">560</property>
<property name="height_request">320</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkScrolledWindow" id="telnet_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="text_view">
<property name="name">textview-large</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="wrap_mode">char</property>
<property name="left_margin">5</property>
<property name="right_margin">5</property>
<property name="buffer">text_buffer</property>
<property name="overwrite">True</property>
<property name="input_hints">GTK_INPUT_HINT_WORD_COMPLETION | GTK_INPUT_HINT_NONE</property>
<property name="monospace">True</property>
<signal name="key-press-event" handler="on_view_key_press" swapped="no"/>
<signal name="realize" handler="on_text_view_realize" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="commands_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_bottom">2</property>
<property name="spacing">2</property>
<child>
<object class="GtkComboBoxText" id="profile_combo_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">0</property>
<property name="has_frame">False</property>
<signal name="changed" handler="on_profile_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="connect_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Connect</property>
<signal name="clicked" handler="on_connect" swapped="no"/>
<child>
<object class="GtkImage" id="connect_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-connect</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="GtkButton" id="disconnect_button">
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Disconnect</property>
<signal name="clicked" handler="on_disconnect" swapped="no"/>
<child>
<object class="GtkImage" id="disconnect_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-disconnect</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child type="center">
<placeholder/>
</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">Clear</property>
<property name="halign">center</property>
<property name="valign">center</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="pack_type">end</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="GtkInfoBar" id="info_bar">
<property name="app_paintable">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<property name="show_close_button">True</property>
<signal name="response" handler="on_info_bar_close" swapped="no"/>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="info_bar_message_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">Info</property>
<property name="justify">center</property>
<property name="wrap">True</property>
<property name="wrap_mode">word-char</property>
<property name="lines">2</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

256
app/ui/telnet.py Executable file
View File

@@ -0,0 +1,256 @@
import re
import selectors
import socket
from collections import deque
from telnetlib import Telnet
from gi.repository import GLib
from app.commons import run_task, run_idle, log
from app.settings import Settings
from app.ui.uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK
class ExtTelnet(Telnet):
def __init__(self, output_callback, **kwargs):
super().__init__(**kwargs)
self._output_callback = output_callback
def interact(self):
"""Interaction function, emulates a very dumb telnet client."""
with selectors.DefaultSelector() as selector:
selector.register(self, selectors.EVENT_READ)
while True:
for key, events in selector.select():
if key.fileobj is self:
try:
text = self.read_very_eager()
except EOFError as e:
msg = "\n*** Connection closed by remote host ***\n"
self._output_callback(msg)
log(msg)
raise e
else:
if text:
self._output_callback(text)
class TelnetDialog:
""" Dialog of very simple telnet client. """
_COLOR_PATTERN = re.compile("\x1b.*?m") # Color info
_ERASING_PATTERN = re.compile("\x1b.*?K") # Erase to right
_APP_MODE_PATTERN = re.compile("\x1b.*?(1h)|(1l)") # h - on, l - off
_ALL_PATTERN = re.compile(r'(\x1b\[|\x9b)[0-?]*[@-~]')
_NOT_SUPPORTED = {"mc", "mcedit", "vi", "nano"}
def __init__(self, transient, settings):
self._handlers = {"on_profile_changed": self.on_profile_changed,
"on_clear": self.on_clear,
"on_text_view_realize": self.on_text_view_realize,
"on_view_key_press": self.on_view_key_press,
"on_info_bar_close": self.on_info_bar_close,
"on_connect": self.on_connect,
"on_disconnect": self.on_disconnect,
"on_close": self.on_close}
builder = Gtk.Builder()
builder.add_from_file(UI_RESOURCES_PATH + "telnet.glade")
builder.connect_signals(self._handlers)
self._dialog_window = builder.get_object("dialog_window")
self._dialog_window.set_transient_for(transient)
self._profile_combo_box = builder.get_object("profile_combo_box")
self._info_bar = builder.get_object("info_bar")
self._info_message_label = builder.get_object("info_bar_message_label")
self._text_view = builder.get_object("text_view")
self._buf = builder.get_object("text_buffer")
self._end_tag = builder.get_object("end_tag")
self._connect_button = builder.get_object("connect_button")
self._connect_button.bind_property("visible", builder.get_object("disconnect_button"), "visible", 4)
provider = Gtk.CssProvider()
provider.load_from_path(UI_RESOURCES_PATH + "style.css")
builder.get_object("main_box").get_style_context().add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
window_size = settings.get("telnet_dialog_window_size")
if window_size:
self._dialog_window.resize(*window_size)
self._ext_settings = settings
self._settings = Settings(settings.settings)
self._tn = None
self._app_mode = False
self._commands = deque(maxlen=10)
def show(self):
self._dialog_window.show()
def on_close(self, window, event):
""" Performs shutdown tasks """
self._ext_settings.add("telnet_dialog_window_size", window.get_size())
self.on_disconnect()
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._info_message_label.set_text(text)
def on_text_view_realize(self, view):
self.init_profiles()
self.on_connect()
@run_idle
def init_profiles(self):
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_task
def on_connect(self, item=None):
try:
GLib.idle_add(self._connect_button.set_visible, False)
GLib.idle_add(self.on_info_bar_close)
user, password = self._settings.user, self._settings.password
timeout = self._settings.telnet_timeout
self._tn = ExtTelnet(self.append_output,
host=self._settings.host,
port=self._settings.telnet_port,
timeout=timeout)
if user != "":
self._tn.read_until(b"login: ")
self._tn.write(user.encode("utf-8") + b"\n")
if password != "":
self._tn.read_until(b"Password: ")
self._tn.write(password.encode("utf-8") + b"\n")
self._tn.interact()
except (OSError, EOFError, socket.timeout, ConnectionRefusedError) as e:
log("{}: {}".format(self.__class__.__name__, e))
self.show_info_message(str(e), Gtk.MessageType.ERROR)
finally:
GLib.idle_add(self._connect_button.set_visible, True)
@run_task
def on_disconnect(self, item=None):
if self._tn:
GLib.idle_add(self._connect_button.set_visible, True)
self._tn.close()
def on_profile_changed(self, button):
self._settings.current_profile = button.get_active_id()
def on_command_done(self, entry):
command = entry.get_text()
entry.set_text("")
if command and self._tn:
self._tn.write(command.encode("ascii") + b"\r")
def on_clear(self, item=None):
self._buf.delete(self._buf.get_start_iter(), self._buf.get_end_iter())
def on_view_key_press(self, view, event):
""" Handling keystrokes on press """
if event.keyval == Gdk.KEY_Return:
self.do_command()
return True
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):
return
key = KeyboardKey(key_code)
ctrl = event.state & MOD_MASK
if ctrl and key is KeyboardKey.C:
if self._tn and self._tn.sock:
self._tn.write(b"\x03") # interrupt
# last commands navigation
if key is KeyboardKey.UP:
self.delete_last_command()
if self._commands:
cmd = self._commands.pop()
self._commands.appendleft(cmd)
self._buf.insert_at_cursor(cmd, -1)
return True
elif key is KeyboardKey.DOWN:
self.delete_last_command()
if self._commands:
cmd = self._commands.popleft()
self._commands.append(cmd)
self._buf.insert_at_cursor(cmd, -1)
return True
def delete_last_command(self):
end = self._buf.get_end_iter()
if end.ends_tag(self._end_tag):
return
if end.backward_to_tag_toggle(self._end_tag):
self._buf.delete(self._buf.get_end_iter(), end)
def do_command(self):
count = self._buf.get_line_count()
begin = self._buf.get_iter_at_line(count)
end = self._buf.get_end_iter()
command = []
while end.backward_to_tag_toggle(self._end_tag):
command.append(self._buf.get_text(end, begin, False))
break
else: # if buf is empty
command.append(self._buf.get_text(begin, end, False))
# to preventing duplication of the command in the buf
self._buf.delete(end, begin)
if command and self._tn.sock:
cmd = command[0]
if cmd in self._NOT_SUPPORTED:
self.show_info_message("'{}' is not supported by this client.".format(cmd), Gtk.MessageType.ERROR)
else:
self._tn.write(cmd.encode("ascii") + b"\r")
self._commands.append(cmd)
@run_idle
def append_output(self, txt):
t = txt.decode("ascii", errors="ignore")
ap = re.search(self._APP_MODE_PATTERN, t)
if ap:
on, of = ap.group(1), ap.group(2)
if on:
self._app_mode = True
elif of:
self._app_mode = False
self.on_clear()
t = re.sub(self._ALL_PATTERN, "", t) # removing [replacing] ascii escape sequences
if self._app_mode:
start, end = self._buf.get_start_iter(), self._buf.get_end_iter()
count = self._buf.get_line_count()
new_lines = t.split("\r\n")
ext_lines = self._buf.get_text(start, end, True).split("\r\n")
if count < len(new_lines):
self._buf.set_text(re.sub(self._ERASING_PATTERN, "", t))
else:
for i, line in enumerate(new_lines):
if line:
ext_lines[i] = re.sub(self._ERASING_PATTERN, "", line)
self._buf.set_text("\r\n".join(ext_lines))
else:
self._buf.insert_at_cursor(t, -1)
insert = self._buf.get_insert()
self._text_view.scroll_to_mark(insert, 0.0, True, 0.0, 1.0)
self._buf.apply_tag(self._end_tag, self._buf.get_start_iter(), self._buf.get_end_iter())
if __name__ == "__main__":
pass

View File

@@ -5,6 +5,7 @@ import gi
from gi.repository import GLib
from app.commons import log
from app.settings import IS_WIN
from app.connections import HttpAPI
from app.tools.yt import YouTube
from app.ui.iptv import get_yt_icon
@@ -12,7 +13,7 @@ from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH
class LinksTransmitter:
""" The main class for the "send to" function.
""" The main media bar class for the "send to" function..
It used for direct playback of media links by the enigma2 media player.
"""
@@ -47,16 +48,19 @@ class LinksTransmitter:
self._status_passive = None
self._yt = YouTube.get_instance(settings)
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))
if IS_WIN:
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
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")

View File

@@ -1,25 +1,27 @@
import locale
import os
from enum import Enum, IntEnum
from functools import lru_cache
from app.settings import Settings, SettingsException, IS_DARWIN
from app.settings import Settings, SettingsException, IS_WIN, SEP
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
gi.require_version("Notify", "0.7")
from gi.repository import Gtk, Gdk, Notify
from gi.repository import Gtk, Gdk, GLib
# Init notify
Notify.init("DemonEditor")
# Setting mod mask for the keyboard depending on the platform.
MOD_MASK = Gdk.ModifierType.MOD2_MASK if IS_DARWIN else Gdk.ModifierType.CONTROL_MASK
# Path to *.glade files.
UI_RESOURCES_PATH = "app/ui/" if os.path.exists("app/ui/") else "/usr/share/demoneditor/app/ui/"
# Setting mod mask for keyboard depending on platform
MOD_MASK = Gdk.ModifierType.CONTROL_MASK
# Path to *.glade files
UI_PATH = "app{}ui{}".format(SEP, SEP)
UI_RESOURCES_PATH = UI_PATH if os.path.exists(UI_PATH) else "ui{}".format(SEP)
LANG_PATH = UI_RESOURCES_PATH + "lang"
GTK_PATH = os.environ.get("GTK_PATH", None)
NOTIFY_IS_INIT = False
IS_GNOME_SESSION = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
# Translation.
TEXT_DOMAIN = "demon-editor"
APP_FONT = None
try:
settings = Settings.get_instance()
@@ -27,27 +29,34 @@ except SettingsException:
pass
else:
os.environ["LANGUAGE"] = settings.language
if UI_RESOURCES_PATH == "app/ui/":
locale.bindtextdomain(TEXT_DOMAIN, UI_RESOURCES_PATH + "lang")
st = Gtk.Settings().get_default()
APP_FONT = st.get_property("gtk-font-name")
st.set_property("gtk-application-prefer-dark-theme", settings.dark_mode)
if settings.is_themes_support:
st = Gtk.Settings().get_default()
st.set_property("gtk-theme-name", settings.theme)
st.set_property("gtk-icon-theme-name", settings.icon_theme)
theme = Gtk.IconTheme.get_default()
theme.append_search_path(UI_RESOURCES_PATH + "icons")
theme.append_search_path(GTK_PATH + "{}share{}icons".format(SEP, SEP) if GTK_PATH else 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
LOCKED_ICON = theme.load_icon("changes-prevent-symbolic", 16, 0) if theme.lookup_icon(
"system-lock-screen", 16, 0) else _IMAGE_MISSING
HIDE_ICON = theme.load_icon("go-jump", 16, 0) if theme.lookup_icon("go-jump", 16, 0) else _IMAGE_MISSING
TV_ICON = theme.load_icon("tv-symbolic", 16, 0) if theme.lookup_icon("tv-symbolic", 16, 0) else _IMAGE_MISSING
IPTV_ICON = theme.load_icon("emblem-shared", 16, 0) if theme.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
def get_theme_icon(icon_theme, name, size):
try:
return icon_theme.load_icon(name, size, 0)
except GLib.Error:
pass
_IMAGE_MISSING = get_theme_icon(theme, "image-missing", 16)
CODED_ICON = get_theme_icon(theme, "emblem-readonly", 16) or _IMAGE_MISSING
LOCKED_ICON = get_theme_icon(theme, "changes-prevent-symbolic", 16) or _IMAGE_MISSING
HIDE_ICON = get_theme_icon(theme, "go-jump", 16) or _IMAGE_MISSING
TV_ICON = get_theme_icon(theme, "tv-symbolic", 16) or _IMAGE_MISSING
IPTV_ICON = get_theme_icon(theme, "emblem-shared", 16)
EPG_ICON = get_theme_icon(theme, "gtk-index", 16)
DEFAULT_ICON = get_theme_icon(theme, "emblem-default", 16)
@lru_cache(maxsize=1)
@@ -61,14 +70,17 @@ def get_yt_icon(icon_name, size=24):
return default_theme.load_icon(icon_name, size, 0)
n_theme = Gtk.IconTheme.new()
p_path = "{}usr{}share{}icons{}*".format(SEP, SEP, SEP, SEP)
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)
for theme_name in map(os.path.basename, filter(os.path.isdir, glob.glob(p_path))):
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)
if default_theme.lookup_icon(Gtk.STOCK_APPLY, size, 0):
return default_theme.load_icon(Gtk.STOCK_APPLY, size, 0)
def show_notification(message, timeout=10000, urgency=1):
@@ -78,41 +90,38 @@ def show_notification(message, timeout=10000, urgency=1):
@param timeout: milliseconds
@param urgency: 0 - low, 1 - normal, 2 - critical
"""
notify = Notify.Notification.new("DemonEditor", message, "demon-editor")
notify.set_urgency(urgency)
notify.set_timeout(timeout)
notify.show()
pass
class KeyboardKey(Enum):
""" The raw(hardware) codes of the keyboard keys. """
E = 26
R = 27
T = 28
P = 33
S = 39
F = 41
X = 53
C = 54
V = 55
W = 25
Z = 52
INSERT = 118
HOME = 110
END = 115
UP = 111
DOWN = 116
PAGE_UP = 112
PAGE_DOWN = 117
LEFT = 113
RIGHT = 114
F2 = 68
F7 = 73
SPACE = 65
DELETE = 119
BACK_SPACE = 22
CTRL_L = 37
CTRL_R = 105
E = 69 if IS_WIN else 26
R = 82 if IS_WIN else 27
T = 84 if IS_WIN else 28
P = 80 if IS_WIN else 33
S = 83 if IS_WIN else 39
F = 70 if IS_WIN else 41
X = 88 if IS_WIN else 53
C = 67 if IS_WIN else 54
V = 86 if IS_WIN else 55
W = 87 if IS_WIN else 25
Z = 90 if IS_WIN else 52
INSERT = 45 if IS_WIN else 118
HOME = 36 if IS_WIN else 110
END = 35 if IS_WIN else 115
UP = 38 if IS_WIN else 111
DOWN = 40 if IS_WIN else 116
PAGE_UP = 33 if IS_WIN else 112
PAGE_DOWN = 34 if IS_WIN else 117
LEFT = 37 if IS_WIN else 113
RIGHT = 39 if IS_WIN else 114
F2 = 113 if IS_WIN else 68
F7 = 118 if IS_WIN else 73
SPACE = 32 if IS_WIN else 65
DELETE = 46 if IS_WIN else 119
BACK_SPACE = 8 if IS_WIN else 22
CTRL_L = 17 if IS_WIN else 37
CTRL_R = 163 if IS_WIN else 105
# Laptop codes
HOME_KP = 79
END_KP = 87

View File

@@ -1,18 +0,0 @@
#!/bin/bash
VER="1.0.4_Beta"
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 --exclude=app/ui/icons -arv app $DEB_PATH
cd dist
fakeroot dpkg-deb --build DemonEditor
mv DemonEditor.deb DemonEditor_$VER.deb
rm -R DemonEditor

View File

@@ -1,58 +0,0 @@
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).
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).
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!
Ctrl + E - edit.
Ctrl + R, F2 - rename.
Ctrl + S, T in Satellites edit tool for create satellite or transponder.
Ctrl + L - parental lock.
Ctrl + H - hide/skip.
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 + 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.
Ctrl + F - show/hide search bar.
Ctrl + Shift + F - show/hide filter bar.
For multiple selection with the mouse, press and hold the Ctrl key!
Minimum requirements:
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings, python3-requests.
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!
When using the multiple import feature, from *lamedb* will be taken data **only for channels that are in the
selected bouquets!** If you need full set of the data, including *[satellites, terrestrial, cables].xml* (current files will be overwritten),
just load your data via *"File/Open"* and press *"Save"*. When importing separate bouquet files, only those services
(excluding IPTV) that are in the **current open lamedb** (main list of services) will be imported.

View File

@@ -1,9 +0,0 @@
Package: demon-editor
Version: 1.0.4-Beta
Section: utils
Priority: optional
Architecture: all
Essential: no
Depends: python3 (>= 3.5)
Maintainer: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
Description: Enigma2 channel and satellite list editor

View File

@@ -1,26 +0,0 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Contact: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
Source: https://github.com/DYefremov/DemonEditor
Files: *
MIT License
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.

View File

@@ -1 +0,0 @@
README.source

View File

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

View File

@@ -1,13 +0,0 @@
[Desktop Entry]
Version=1.0
Name=DemonEditor
Comment=Channel and satellite list editor for Enigma2
Comment[ru]=Редактор списка каналов и спутников для Enigma2
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
Icon=demon-editor
Exec=/usr/bin/demon-editor
Terminal=false
Type=Application
Categories=Utility;Application;
StartupNotify=false

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,634 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 22 KiB

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2020 Dmitriy Yefremov
# Copyright (C) 2018-2021 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
@@ -909,13 +909,13 @@ msgid "Sample rate (Hz):"
msgstr "Частата дыскр. (Гц):"
msgid "Play streams mode:"
msgstr "Рэжым прайгравання струменяў:"
msgstr "Рэжым прайгравання патокаў:"
msgid "Built-in player"
msgstr "Убудаваны плэер"
msgid "VLC media player"
msgstr "VLC медыяплэер"
msgid "In a separate window"
msgstr "У асобным акне"
msgid "Only get m3u file"
msgstr "Атрымаць файл *.m3u"
@@ -1201,3 +1201,30 @@ msgstr "Толькі DreamOS!"
msgid "A similar service is already in this list!"
msgstr "Падобны сэрвіс ужо ёсць у гэтым спісе!"
msgid "Play mode has been changed!\nRestart the program to apply the settings."
msgstr "Зменены рэжым прайгравання!\nПеразапусціце праграму для ўжывання налад."
msgid "Set values for TID, NID and Namespace for correct naming of the picons!"
msgstr "Усталюйце значэнні TID, NID і пр. імёнаў для слушнага наймення пiконаў!"
msgid "Streams detected:"
msgstr "Выяўлена патокаў:"
msgid "Download picons"
msgstr "Загрузіць пiконы"
msgid "Errors:"
msgstr "Памылак:"
msgid "Use to play streams:"
msgstr "Скарыстаць для прайгравання патокаў:"
msgid "Font in the lists:"
msgstr "Шрыфт у спісах:"
msgid "Picons size in the lists:"
msgstr "Памер пiконаў у спісах:"
msgid "Logo size in tooltips:"
msgstr "Памер лагатыпа ва ўсплыўных падказках:"

View File

@@ -1,18 +1,18 @@
# Copyright (C) 2018-2020 Dmitriy Yefremov
# Copyright (C) 2018-2021 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
# Charly, 2019.
# Dmitriy Yefremov, 2020.
# Dmitriy Yefremov, 2020-2021.
msgid ""
msgstr ""
"Last-Translator: Dmitriy Yefremov\n"
"Last-Translator: Thomas Schmidt\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "translator-credits"
msgstr "Charly\nDmitriy Yefremov"
msgstr "Charly\nDmitriy Yefremov\nThomas Schmidt"
# Main
msgid "Service"
@@ -31,7 +31,7 @@ msgid "Freq"
msgstr "Freq"
msgid "Rate"
msgstr "Bewertung"
msgstr "SR"
msgid "Pol"
msgstr "Pol."
@@ -666,16 +666,16 @@ msgid "Test connection"
msgstr "Test Verbindung"
msgid "Double click on the service in the bouquet list:"
msgstr "Doppelklicke auf das Service in der Bouquetliste:"
msgstr "Doppelklick auf das Service in der Bouquet-Liste:"
msgid "Zap"
msgstr "Zap"
msgid "Play stream"
msgstr "Play Stream"
msgstr "Stream abspielen"
msgid "Disabled"
msgstr "Ausgeschaltet"
msgstr "Deaktiviert"
msgid "Enable lamedb ver. 5 support"
msgstr "Lamedb ver. 5 Unterstützung aktivieren"
@@ -693,7 +693,7 @@ 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"
msgstr "Exportieren nach m3u"
msgid "EPG configuration"
msgstr "EPG Konfiguration"
@@ -720,13 +720,13 @@ msgid "Url to *.xml.gz file:"
msgstr "Url zur *.xml.gz Datei:"
msgid "Enable filtering"
msgstr "Filterung einschalten"
msgstr "Filter aktivieren"
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:"
msgstr "Pfad zur epg.dat Datei:"
msgid "Local path:"
msgstr "Local path:"
@@ -750,7 +750,7 @@ msgid "Unsupported file type:"
msgstr "Nicht unterstützter Dateityp:"
msgid "Unpacking data error."
msgstr "Fehler beim Entpacken von Daten."
msgstr "Fehler beim Entpacken der Daten."
msgid "XML parsing error:"
msgstr "XML Parsing-Fehler:"
@@ -765,7 +765,7 @@ msgid "Use HTTP"
msgstr "HTTP verwenden"
msgid "Close playback"
msgstr "Wiedergabe schliessen"
msgstr "Wiedergabe schließen"
msgid "Import YouTube playlist"
msgstr "YouTube-Wiedergabeliste importieren"
@@ -774,8 +774,8 @@ 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?"
"Link zur YouTube-Ressource gefunden!\n"
"Soll versucht werden, einen Direkt-Link zum Video zu erzeugen?"
msgid "Playlist import"
msgstr "Playlist-Import"
@@ -784,7 +784,7 @@ msgid "Getting link error:"
msgstr "Link-Fehler erhalten:"
msgid "Extra"
msgstr "Extra"
msgstr "Extras"
msgid "Apply profile settings"
msgstr "Profileinstellungen anwenden"
@@ -793,7 +793,7 @@ msgid "Settings type:"
msgstr "Art der Einstellungen:"
msgid "Set default"
msgstr "Standard setzen"
msgstr "Standard wiederherstellen"
msgid "Language:"
msgstr "Sprache:"
@@ -805,16 +805,16 @@ msgid "Enable direct playback bar"
msgstr "Aktivieren der direkten Wiedergabeleiste"
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"
msgstr "Ermöglicht das direkte Senden und Abspielen von Medienlinks auf dem Receiver"
msgid "Watch the channel in the program"
msgstr "Gucken den Kanal im Programm an"
msgstr "Kanal im Programm ansehen"
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"
msgstr "Link hineinziehen oder einfügen"
msgid "Remove added links in the playlist"
msgstr "Hinzugefügte Links in der Wiedergabeliste entfernen"
@@ -832,13 +832,13 @@ msgid "Reset"
msgstr "Reset"
msgid "File"
msgstr "Ablage"
msgstr "Datei"
msgid "Picons manager"
msgstr "Picons-Manager"
msgid "Explorer"
msgstr ""
msgstr "Explorer"
msgid "Satellite url:"
msgstr "Satellit URL:"
@@ -916,40 +916,40 @@ msgid "Height (px):"
msgstr "Höhe (px):"
msgid "Channels:"
msgstr "Kanälen:"
msgstr "Kanäle:"
msgid "Sample rate (Hz):"
msgstr "Samplerate (Hz):"
msgstr "Sample-Rate (Hz):"
msgid "Play streams mode:"
msgstr "Streams Abspielen-Modus:"
msgstr "Stream-Abspielmodus:"
msgid "Built-in player"
msgstr "Integrierter Player"
msgid "VLC media player"
msgstr "VLC Media Player"
msgid "In a separate window"
msgstr "In einem separaten Fenster"
msgid "Only get m3u file"
msgstr "Nur m3u-Datei erhalten"
msgid "Save and restart the program to apply the settings."
msgstr "Speicher und starte das Programm neu, um die Einstellungen zu übernehmen."
msgstr "Änderungen speichern und anschließend das Programm neustarten, um die Einstellungen zu übernehmen."
msgid "Some images may have problems displaying the favorites list!"
msgstr "Einige Images können Probleme mit der Anzeige der Favoritenliste haben!"
msgstr "Einige Bilder können Probleme in der Anzeige der Favoritenliste haben!"
msgid "Operates in standby mode or current active transponder!"
msgstr "Arbeitet im Standby-Modus oder auf dem aktuell aktiven Transponder!"
msgstr "Arbeitet im Standby-Modus oder der Transponder ist bereits in Verwendung!"
msgid "No connection to the receiver!"
msgstr "Keine Verbindung zum Box!"
msgstr "Keine Verbindung zum Receiver!"
msgid "Signal level"
msgstr "Signalpegel"
msgid "Receiver info"
msgstr "Box-Info"
msgstr "Receiver-Info"
msgid "A profile with that name exists!"
msgstr "Ein Profil mit diesem Namen existiert!"
@@ -958,10 +958,10 @@ msgid "Show short info as hints in the main services list"
msgstr "Kurzinfos als Tooltips in der Hauptliste anzeigen"
msgid "Show detailed info as hints in the bouquet list"
msgstr "Detaillierteinfos als Tooltips in der Bouquetliste anzeigen"
msgstr "Detaillierte Informationen als Tooltips in der Bouquetliste anzeigen"
msgid "Enable alternate bouquet file naming"
msgstr "Aktivieren der Alternativerbenennung für Bouquet-Dateien"
msgstr "Aktivieren der alternativen Benennung für Bouquet-Dateien"
msgid "Allows you to name bouquet files using their names."
msgstr "Ermöglicht Bouquet-Dateien mit ihren Namen zu benennen."
@@ -970,7 +970,7 @@ msgid "Appearance"
msgstr "Aussehen"
msgid "Enable Themes support"
msgstr "Unterstützung von Themen aktivieren"
msgstr "Theme Unterstützung aktivieren"
msgid "Gtk3 Theme:"
msgstr "Gtk3-Theme:"
@@ -979,7 +979,7 @@ msgid "Icon Theme:"
msgstr "Icon-Theme:"
msgid "Gtk3 Themes and Icons:"
msgstr "Gtk3 Themes and Icons:"
msgstr "Gtk3 Themes und Icons:"
msgid "Deleting data..."
msgstr "Daten löschen..."
@@ -988,7 +988,7 @@ msgid "Download from the receiver"
msgstr "Downloaden vom Receiver"
msgid "Remove all picons from the receiver"
msgstr "Alle Picons aus dem Receiver entfernen"
msgstr "Alle Picons vom dem Receiver entfernen"
msgid "Service reference"
msgstr "Kanalreferenz"
@@ -1021,7 +1021,7 @@ msgid "Are you sure you want to change the order\n\t of services in this bouquet
msgstr "Bist du sicher, dass du die Reihenfolge der Dienstleistungen\n\t in diesem Bouquet ändern willst?"
msgid "Remove from the receiver"
msgstr "Aus dem Receiver entfernen"
msgstr "Vom Receiver entfernen"
msgid "Screenshot"
msgstr "Screenshot"
@@ -1039,7 +1039,7 @@ msgid "Can't Playback!"
msgstr "Kann nicht abgespielt werden!"
msgid "Enable Dark Mode"
msgstr "Dunkelmodus aktivieren"
msgstr "Dunkler Modus aktivieren"
msgid "Extract..."
msgstr "Entpacken..."
@@ -1051,7 +1051,7 @@ msgid "Combine with the current data?"
msgstr "Mit den aktuellen Daten kombinieren?"
msgid "Importing data done!"
msgstr "Daten importieren erledigt!"
msgstr "Daten-Import abgeschlossen!"
msgid "Current service"
msgstr "Aktueller Service"
@@ -1063,13 +1063,13 @@ msgid "Open archive"
msgstr "Archiv öffnen"
msgid "Import from Web"
msgstr "Import aus dem Web"
msgstr "Import aus dem Internet"
msgid "Control"
msgstr "Steuerung"
msgid "Timers"
msgstr "Timers"
msgstr "Timer"
msgid "Timer"
msgstr "Timer"
@@ -1111,7 +1111,7 @@ msgid "Auto"
msgstr "Auto"
msgid "Grab screenshot"
msgstr "Screenshot schnappen"
msgstr "Screenshot aufnehmen"
msgid "Enabled:"
msgstr "Aktiviert:"
@@ -1138,7 +1138,7 @@ msgid "Ends:"
msgstr "Endet:"
msgid "Repeated:"
msgstr "Wiederhole:"
msgstr "Wiederholt:"
msgid "Action:"
msgstr "Aktion:"
@@ -1214,3 +1214,30 @@ msgstr "Nur DreamOS!"
msgid "A similar service is already in this list!"
msgstr "Ein ähnlicher Dienst ist bereits in dieser Liste enthalten!"
msgid "Play mode has been changed!\nRestart the program to apply the settings."
msgstr "Abspiel-Modus wurde geändert!\nStarte das Programm neu, um die Einstellungen zu übernehmen."
msgid "Set values for TID, NID and Namespace for correct naming of the picons!"
msgstr "Stelle die Werte für TID, NID und Namespace für die korrekte Benennung der Picons ein!"
msgid "Streams detected:"
msgstr "Streams gefunden:"
msgid "Download picons"
msgstr "Download picons"
msgid "Errors:"
msgstr "Fehler:"
msgid "Use to play streams:"
msgstr "Zum Abspielen von Streams verwenden:"
msgid "Font in the lists:"
msgstr "Schrift in den Listen:"
msgid "Picons size in the lists:"
msgstr "Picons Größe in den Listen:"
msgid "Logo size in tooltips:"
msgstr "Logo-Größe in Tooltips:"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2020 Dmitriy Yefremov
# Copyright (C) 2018-2021 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
@@ -914,8 +914,8 @@ msgstr "Режим воспроизведения потоков:"
msgid "Built-in player"
msgstr "Встроенный плеер"
msgid "VLC media player"
msgstr "VLC медиаплеер"
msgid "In a separate window"
msgstr "В отдельном окне"
msgid "Only get m3u file"
msgstr "Получить файл *.m3u"
@@ -1198,3 +1198,30 @@ msgstr "Только DreamOS!"
msgid "A similar service is already in this list!"
msgstr "Подобный сервис уже есть в этом списке!"
msgid "Play mode has been changed!\nRestart the program to apply the settings."
msgstr "Изменен режим воспроизведения!\nПерезапустите программу для применения настроек."
msgid "Set values for TID, NID and Namespace for correct naming of the picons!"
msgstr "Установите значения TID, NID и пр. имен для правильного именования пиконов!"
msgid "Streams detected:"
msgstr "Обнаружено потоков:"
msgid "Download picons"
msgstr "Загрузить пиконы"
msgid "Errors:"
msgstr "Ошибок:"
msgid "Use to play streams:"
msgstr "Использовать для воспроизведения потоков:"
msgid "Font in the lists:"
msgstr "Шрифт в списках:"
msgid "Picons size in the lists:"
msgstr "Размер пиконов в списках:"
msgid "Logo size in tooltips:"
msgstr "Размер логотипа во всплывающих подсказках:"

View File

@@ -3,13 +3,13 @@ msgstr ""
"Project-Id-Version: DemonEditor\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
"PO-Revision-Date: 2020-06-08 21:53+0300\n"
"PO-Revision-Date: 2021-02-22 23:53+0300\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
"Language-Team: \n"
"X-Generator: Poedit 2.2.1\n"
"X-Generator: Poedit 2.4.1\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: tr\n"
@@ -107,6 +107,9 @@ msgstr "Varsayılan adı ayarla"
msgid "Insert marker"
msgstr "İşaretçi ekle"
msgid "Insert space"
msgstr "Boşluk ekle"
msgid "Locate in services"
msgstr "Hizmetlerde bulun"
@@ -518,6 +521,9 @@ msgstr "Lütfen sadece bir ürün seçiniz!"
msgid "No png file is selected!"
msgstr "Hiçbir png dosyası seçilmedi!"
msgid "No profile selected!"
msgstr "Profil seçilmedi!"
msgid "No reference is present!"
msgstr "Referans yok!"
@@ -923,8 +929,8 @@ msgstr "Akışları oynatma modu:"
msgid "Built-in player"
msgstr "Dahili oynatıcı"
msgid "VLC media player"
msgstr "VLC media player"
msgid "In a separate window"
msgstr "Ayrı bir pencerede"
msgid "Only get m3u file"
msgstr "Sadece m3u dosyası al"
@@ -988,3 +994,254 @@ msgstr "Alıcıdaki tüm piconları kaldırın"
msgid "Service reference"
msgstr "Servis referansı"
msgid "Enable support for"
msgstr "Temalar için desteği etkinleştir"
msgid "Auto-check for updates"
msgstr "Güncellemeleri otomatik kontrol et"
msgid "Filter services"
msgstr "Services filtrele"
msgid "Filter services in the main list."
msgstr "Ana listedeki services filtreleyin."
msgid "Destination:"
msgstr "Hedef:"
msgid "EXPERIMENTAL!"
msgstr "EXPERIMENTAL!"
msgid "Sorting data..."
msgstr "Verilerin sıralanması..."
msgid ""
"There are unsaved changes.\n"
"\n"
"\t Save them now?"
msgstr ""
"Kaydedilmemiş değişiklikler var.\n"
"\n"
"\tŞimdi kaydedilsin mi?"
msgid ""
"Are you sure you want to change the order\n"
"\t of services in this bouquet?"
msgstr ""
"Sırayı değiştirmek istediğinizden emin misiniz\n"
"\t Bu buketdeki hizmetlerin sayısı?"
msgid "Remove from the receiver"
msgstr "Alıcıdaki tüm piconları kaldırın"
msgid "Screenshot"
msgstr "Ekran görüntüsü"
msgid "Video"
msgstr "Video"
msgid "The Neutrino has only experimental support. Not all features are supported!"
msgstr "Neutrino'nun yalnızca experimental desteği var. Tüm özellikler desteklenmiyor!"
msgid "Enable experimental features"
msgstr "Experimental özellikleri etkinleştirin"
msgid "Can't Playback!"
msgstr "Oynatılamıyor!"
msgid "Enable Dark Mode"
msgstr "Koyu Modu Etkinleştir"
msgid "Extract..."
msgstr "Çıkart..."
msgid "Unsupported format!"
msgstr "Desteklenmeyen format!"
msgid "Combine with the current data?"
msgstr "Mevcut verilerle birleştirilsin mi?"
msgid "Importing data done!"
msgstr "Verilerin içe aktarılması tamamlandı!"
msgid "Current service"
msgstr "Mevcut service"
msgid "Open folder"
msgstr "Dosya aç"
msgid "Open archive"
msgstr "Arşiv aç"
msgid "Import from Web"
msgstr "Web'den içe aktar"
msgid "Control"
msgstr "Control"
msgid "Timers"
msgstr "Zamanlayıcılar"
msgid "Timer"
msgstr "Zamanlayıcı"
msgid "Add timer"
msgstr "Zamanlayıcı ekle"
msgid "Hr."
msgstr "Hr."
msgid "Min."
msgstr "Min."
msgid "Power"
msgstr "Power"
msgid "Standby"
msgstr "Standby"
msgid "Wake Up"
msgstr "Wake Up"
msgid "Reboot"
msgstr "Reboot"
msgid "Restart GUI"
msgstr "Restart GUI"
msgid "Shutdown"
msgstr "Shutdown"
msgid "Shut down"
msgstr "Kapat"
msgid "Do Nothing"
msgstr "Hiçbir şey yapma"
msgid "Auto"
msgstr "Auto"
msgid "Grab screenshot"
msgstr "Ekran görüntüsü al"
msgid "Enabled:"
msgstr "Etkin:"
msgid "Name:"
msgstr "Ad:"
msgid "Description:"
msgstr "Açıklama:"
msgid "Service:"
msgstr "Service:"
msgid "Service reference:"
msgstr "Servis referansı:"
msgid "Event ID:"
msgstr "Olay Kimliği:"
msgid "Begins:"
msgstr "Başlıyor:"
msgid "Ends:"
msgstr "Bitiş:"
msgid "Repeated:"
msgstr "Tekrar:"
msgid "Action:"
msgstr "Aksiyon:"
msgid "After event:"
msgstr "Olaydan sonra:"
msgid "Location:"
msgstr "Konum:"
msgid "Mo"
msgstr "Pzt"
msgid "Tu"
msgstr "Sal"
msgid "We"
msgstr "Çar"
msgid "Th"
msgstr "Per"
msgid "Fr"
msgstr "Cum"
msgid "Sa"
msgstr "Cmt"
msgid "Su"
msgstr "Paz"
msgid "Set"
msgstr "Yüklemek"
msgid "Services update"
msgstr "Servisleri güncelle"
msgid "Create folder"
msgstr "Klasör oluştur"
msgid "FTP client"
msgstr "FTP istemcisi"
msgid "The file size is too large!"
msgstr "Dosya boyutu çok büyük!"
msgid "Connect"
msgstr "Bağlan"
msgid "Disconnect"
msgstr "Bağlantıyı kes"
msgid "Size"
msgstr "Boyut"
msgid "Date"
msgstr "Saat"
msgid "Attr."
msgstr "Özellik."
msgid "Toggle display position"
msgstr "Görüntü konumunu değiştir"
msgid "Alternatives"
msgstr "Alternatifler"
msgid "Add alternatives"
msgstr "Alternatif ekleyin"
msgid "DreamOS only!"
msgstr "Sadece DreamOS!"
msgid "A similar service is already in this list!"
msgstr "Bu listede zaten benzer bir hizmet var!"
msgid ""
"Play mode has been changed!\n"
"Restart the program to apply the settings."
msgstr ""
"Oynatma modu değiştirildi!\n"
"Ayarları uygulamak için programı yeniden başlatın."
msgid "Set values for TID, NID and Namespace for correct naming of the picons!"
msgstr "Piconların doğru isimlendirilmesi için TID, NID ve Namespace değerlerini ayarlayın!"
msgid "Streams detected:"
msgstr "Akışlar algılandı:"
msgid "Download picons"
msgstr "Picon'lar indirin"
msgid "Errors:"
msgstr "Hatalar:"

View File

@@ -1,29 +1,7 @@
#!/usr/bin/env python3
import os
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 multiprocessing import freeze_support
from app.ui.main_app_window import start_app
update_icon()
freeze_support()
start_app()