Compare commits

...

180 Commits
0.3.2 ... 0.4.1

Author SHA1 Message Date
DYefremov
96f9e4cca8 minor fix for the cable channel save 2018-11-25 22:55:15 +03:00
DYefremov
cd1a3c5df2 added basic support of cable services 2018-11-25 19:34:28 +03:00
DYefremov
9f9db9257e upd README 2018-11-25 10:26:53 +03:00
DYefremov
f748fae549 minor gui changes for dialogs 2018-11-23 21:05:20 +03:00
DYefremov
c303162c09 little refactoring for ZAP function 2018-11-23 15:06:36 +03:00
DYefremov
6d4434c652 added Z key 2018-11-23 15:03:10 +03:00
Dmitriy
a92b7526eb some compatibility fix 2018-11-20 22:21:26 +03:00
DYefremov
c33f95e0b5 init http api only if enabled in settings 2018-11-19 17:53:33 +03:00
DYefremov
7bfeef9d73 added http api support option in the settings 2018-11-19 13:37:10 +03:00
DYefremov
506207f2e5 added basic http api skeleton 2018-11-17 23:19:17 +03:00
DYefremov
f9ef03b827 fix reference 2018-11-17 13:48:34 +03:00
DYefremov
b5dabf65c7 added groups import support for m3u 2018-11-16 23:08:40 +03:00
DYefremov
3fb3d04161 skip options for the lists 2018-11-16 22:35:47 +03:00
DYefremov
d76e379542 Added accelerators for popup menus 2018-11-16 21:46:27 +03:00
DYefremov
d046bd7c28 minor gui changes for the download dialog 2018-11-13 21:23:37 +03:00
DYefremov
bc2ec7fff1 refactoring of data uploading 2018-11-13 14:17:59 +03:00
DYefremov
e3c3f20da7 little refactoring 2018-11-12 13:43:05 +03:00
DYefremov
2254164f40 F2 code fix 2018-11-12 11:26:11 +03:00
DYefremov
2ae4ac6383 new implementation skeleton of data uploading 2018-11-11 18:35:45 +03:00
DYefremov
80cd4c89b0 little style changes for the iptv dialogs 2018-11-11 15:33:45 +03:00
DYefremov
3df6fd7d0e some style changes for the service details dialog 2018-11-10 23:31:35 +03:00
DYefremov
f3243c9e40 width changes for some dialogs elements 2018-11-09 16:57:47 +03:00
DYefremov
528df9602b checking if value exist for the keyboard key 2018-11-09 14:14:24 +03:00
DYefremov
cd83539855 authentication for the http test 2018-11-09 12:41:36 +03:00
DYefremov
ecc17fa4b2 added default bouquet name 2018-11-08 17:48:51 +03:00
DYefremov
4c555bb7f4 moving the tests functions 2018-11-08 13:07:24 +03:00
DYefremov
e2592bb87a changing settings from the download dialog 2018-11-06 23:43:49 +03:00
DYefremov
631564b22c writing transfer state to the callback 2018-11-06 23:43:13 +03:00
DYefremov
a8bc81fb13 simple output during data transferring 2018-11-06 21:21:47 +03:00
DYefremov
fecb77090d settings dialog minor changes 2018-11-05 23:24:13 +03:00
DYefremov
692495283c scrolling to the first item when copying to the fav list beginning 2018-11-05 14:23:33 +03:00
DYefremov
5ee6615d6f added keyboard keys for laptops 2018-11-05 11:09:15 +03:00
DYefremov
4e34057d16 new variant of working with keyboard keys 2018-11-05 00:31:44 +03:00
DYefremov
f16b47ebf1 added http default settings 2018-11-04 00:37:19 +03:00
DYefremov
3ac16cb7af http properties dialog prototype 2018-11-04 00:36:07 +03:00
DYefremov
2b3c357657 new download dialog 2018-11-04 00:33:50 +03:00
DYefremov
5658a3cfc3 cyrillic keyboard keys map 2018-11-02 23:13:51 +03:00
DYefremov
06c3cb6c42 little refactoring 2018-11-02 23:13:31 +03:00
DYefremov
b60bb6b3b1 added home, end move keys for laptops 2018-11-02 21:58:10 +03:00
DYefremov
b3648a2dae updating elements for download dialog 2018-10-29 21:49:31 +03:00
DYefremov
04efdab63a set default port for telnet 2018-10-29 21:23:31 +03:00
DYefremov
966dd13c23 new download dialog skeleton 2018-10-29 18:46:46 +03:00
DYefremov
2f6450f2b4 translation for context error message 2018-10-26 20:00:11 +03:00
DYefremov
0db2080e2b little improvements for rename 2018-10-26 19:28:40 +03:00
DYefremov
4210c44ee9 fixed cropping of some bouquets names 2018-10-25 16:39:54 +03:00
DYefremov
9d7fef36c2 renaming for the IPTV and MARKER types 2018-10-25 16:00:25 +03:00
DYefremov
303d4d6107 set elems for edit transponder data for terrestrial services as hidden 2018-10-22 17:41:57 +03:00
DYefremov
9ac847ce19 set picons resizing after all providers downloading 2018-10-21 19:20:27 +03:00
DYefremov
d2c7c297b1 base implementation of terrestrial channels editing 2018-10-21 18:34:14 +03:00
DYefremov
995def4be8 minor changes in bouquets generation 2018-10-21 17:24:58 +03:00
DYefremov
0f08ba67e7 catching connection errors when getting a satellites list 2018-10-21 16:09:57 +03:00
DYefremov
499c1aa104 satellites selective loading in the picons downloader 2018-10-21 11:12:57 +03:00
DYefremov
348b0743b4 refactoring for on popup menu function 2018-10-21 11:10:45 +03:00
DYefremov
769445fafe namespace for single channel 2018-10-21 00:17:22 +03:00
DYefremov
61af6e50ce onid for the single channels parsing 2018-10-20 07:27:12 +03:00
DYefremov
4fd7295762 deleting files when closing the picons downloader 2018-10-19 11:18:55 +03:00
DYefremov
b0ad01da6c added picons downloading for the single channels 2018-10-18 19:19:40 +03:00
DYefremov
4320adc46d show single channels in the picons downloader 2018-10-17 21:36:02 +03:00
DYefremov
e2ec359327 added satellites loading in the picons dialog 2018-10-17 00:12:31 +03:00
DYefremov
dc773339ab little fix for fav end copy 2018-10-16 14:12:07 +03:00
DYefremov
e5f8ebbf37 Merge branch 'master' into experimental 2018-10-15 14:09:14 +03:00
DYefremov
1489b3ba4f skip iptv stream deletion for 403 error code 2018-10-15 14:08:33 +03:00
DYefremov
e871e88f46 fix names saving for the neutrino webtv 2018-10-15 13:37:40 +03:00
DYefremov
ea9ce5dfaf Merge branch 'master' into experimental 2018-10-15 12:36:26 +03:00
DYefremov
a1dada9a55 fix showing iptv dialog for neutrino 2018-10-15 12:36:03 +03:00
DYefremov
b137a790a0 added terrestrial services reading support 2018-10-15 11:39:33 +03:00
DYefremov
9ef776d8ab moving download dialog in the separate file 2018-10-14 20:27:06 +03:00
DYefremov
e5b726cbe6 little refactoring for the download dialog 2018-10-14 16:01:05 +03:00
DYefremov
fcfac6c20b skip receiver reboot during uploading bouquets if reachable webif 2018-10-14 12:45:12 +03:00
DYefremov
e7a4b06945 little refactoring for dynamic elements 2018-10-14 11:37:53 +03:00
DYefremov
3fe84f82f9 upd README 2018-10-13 22:28:21 +03:00
DYefremov
3eee824160 added copying from the main list to the bouquet end 2018-10-13 22:27:32 +03:00
DYefremov
bfcab961d5 minor gui changes for picons dialog 2018-10-13 12:57:37 +03:00
Dmitriy
c3f4390d11 little optimisation 2018-10-13 10:48:39 +03:00
DYefremov
dce3f0104d fix saving in fav list after paste 2018-10-10 22:46:36 +03:00
DYefremov
0c4c0da17f upd README.md 2018-10-10 21:56:38 +03:00
DYefremov
1dec2c8fc1 added url checking before iptv stream save 2018-10-10 21:54:56 +03:00
DYefremov
6eb112eb38 little refactoring for delete and copy functions 2018-10-09 13:16:49 +03:00
DYefremov
8307f153cd added logger for the satellites parser 2018-10-08 23:51:02 +03:00
DYefremov
4796a558bb modulation value fix 2018-10-08 21:41:02 +03:00
DYefremov
61d257f801 fix for compatibility with xenial for some functions 2018-10-08 00:30:09 +03:00
DYefremov
af74e7c32c updating player navigation buttons state 2018-10-06 18:28:59 +03:00
DYefremov
bf474ee8d0 fix opening lamedb5 2018-10-06 16:29:41 +03:00
DYefremov
26e6c40a1b fix rename by Ctrl + R, F2 2018-10-06 15:14:29 +03:00
DYefremov
5723f29b60 translation for the streams player elements 2018-10-05 15:28:15 +03:00
DYefremov
bc65e1b446 added prev and next buttons for the stream player 2018-10-05 15:12:13 +03:00
DYefremov
1842bec2aa minor translation fix 2018-10-04 16:31:05 +03:00
DYefremov
9cc33b9a33 fixed remove all for iptv 2018-10-04 16:24:53 +03:00
DYefremov
c8fefae571 Merge remote-tracking branch 'origin/experimental' into experimental 2018-10-04 09:14:36 +03:00
DYefremov
6d6616425c added repo files 2018-10-04 09:14:14 +03:00
DYefremov
cb4ed7ebd1 update readme 2018-10-02 09:13:49 +03:00
DYefremov
a51929068a stream player minor changes 2018-10-01 20:16:05 +03:00
DYefremov
dff2071fa3 redesign ui elements for the player 2018-09-30 23:16:30 +03:00
DYefremov
fdd2d61a28 new player prototype 2018-09-30 00:12:15 +03:00
DYefremov
1532b213e4 little refactoring and optimization of hide, lock functionality 2018-09-29 21:57:17 +03:00
DYefremov
d2b76b08e1 fix editing from the bouquet list if main list in the filter mode 2018-09-28 10:09:36 +03:00
DYefremov
af40443730 translation for the filter bar elements 2018-09-27 22:12:27 +03:00
DYefremov
62d9e21433 positions update optimisation for the filter 2018-09-27 22:02:35 +03:00
DYefremov
ddfd3db7fa little translation correction 2018-09-26 15:07:26 +03:00
DYefremov
a50a7c426e little optimisation on data loading 2018-09-25 21:26:03 +03:00
DYefremov
e93760b2ac improved functionality of the config dialog for the IPTV streams list 2018-09-23 19:19:34 +03:00
DYefremov
4a80da7515 moving iptv dialogs in the separate file 2018-09-23 00:17:58 +03:00
DYefremov
962db5f736 added selecting row under the cursor for all views 2018-09-22 21:14:56 +03:00
DYefremov
1c0ca0dbeb selecting row under the cursor at the dragging begin 2018-09-22 21:08:28 +03:00
DYefremov
40faa4029d changes of button press handling in the view 2018-09-22 19:14:47 +03:00
DYefremov
86351c61ae Skipping terrestrial and cable channels during parsing 2018-09-22 19:06:23 +03:00
DYefremov
1b56899636 minor changes 2018-09-21 11:15:20 +03:00
DYefremov
b92a7f1bdb bouquets deletion fix 2018-09-21 11:09:40 +03:00
Dmitriy Yefremov
7b1db69867 Merge branch 'master' into experimental 2018-09-21 10:31:02 +03:00
DYefremov
8ae34fa6e6 minor changes 2018-09-21 10:16:30 +03:00
DYefremov
a507f9d401 Updatating bouquets type in the model and dict 2018-09-20 18:37:47 +03:00
DYefremov
ac724fc36f cut-copy-paste impl for the bouquets list 2018-09-20 16:36:03 +03:00
DYefremov
90564859b2 DnD implementation for the bouquets list 2018-09-20 11:06:33 +03:00
DYefremov
562a5e5c6d providers parsing optimisation for picons 2018-09-19 23:02:26 +03:00
DYefremov
49605b87f9 DnD skeleton for bouquets list 2018-09-19 11:46:41 +03:00
DYefremov
27bdac7b4f Changes in handling keystrokes. 2018-09-18 14:40:24 +03:00
DYefremov
b92c02fd63 added copy possibility for fav list 2018-09-18 10:35:10 +03:00
DYefremov
aec3874e0b new player prototype 2018-09-18 07:13:32 +03:00
DYefremov
08a31e9aa0 added verification on service type before rename for the bouquet 2018-09-16 23:40:02 +03:00
DYefremov
8850ff910c added verification on service type before rename for the bouquet 2018-09-16 23:39:31 +03:00
DYefremov
5f79f5b523 little changes for fav popup menu 2018-09-16 16:59:34 +03:00
DYefremov
62bf6eadac fixed removing picon for the iptv service 2018-09-16 15:41:06 +03:00
DYefremov
c7575c4646 translation for some elements 2018-09-15 17:26:01 +03:00
DYefremov
57ae0c8d53 parsing fix of some satellites during update from the web 2018-09-15 16:04:08 +03:00
DYefremov
c37838d2c0 little gui changes for satellite and transponder dialogs 2018-09-15 10:50:40 +03:00
DYefremov
fa9949d562 revert buttons 2018-09-14 14:39:27 +03:00
DYefremov
bf54187f28 settings dialog moved to a separate file 2018-09-14 14:23:25 +03:00
DYefremov
696bce8201 little changes for the filter bar 2018-09-12 17:49:28 +03:00
DYefremov
5d01bd5479 little changes for the filter bar 2018-09-12 17:46:45 +03:00
DYefremov
403426ba75 added free services filter in the main list 2018-09-12 17:26:22 +03:00
DYefremov
43a884159b simple implementation of filtering support by type and position in main list 2018-09-12 14:05:28 +03:00
DYefremov
f925aa5642 setting default name for the service in bouquet 2018-09-11 16:25:12 +03:00
DYefremov
8bdbe45a57 support for extra names in the bouquet list 2018-09-11 15:21:05 +03:00
DYefremov
b2a24974a3 redesign of the satellites update dialog 2018-09-10 22:37:42 +03:00
DYefremov
9dd6633c6a little style changes of service details dialog 2018-09-10 08:46:01 +03:00
DYefremov
1dc5461f90 new elements for the fav popup menu 2018-09-10 00:19:45 +03:00
DYefremov
15418d234d support of opening bouquets with different names of services in bouquet and main list 2018-09-09 23:38:00 +03:00
DYefremov
0ac439cc84 GUI redesign of the some dialogs 2018-09-08 09:58:54 +03:00
DYefremov
b4eda74c6d GUI redesign of the service details dialog 2018-09-08 00:19:20 +03:00
DYefremov
35d598f1f4 redesign of GUI of the IPTV dialog 2018-09-07 23:42:59 +03:00
DYefremov
d2272e5715 redesign of GUI of the picons dialog 2018-09-07 23:10:48 +03:00
DYefremov
252c2245f7 translation some elements 2018-09-02 23:16:32 +03:00
DYefremov
0e3d5df4bf picons support for iptv (enigma2) 2018-09-01 00:49:11 +03:00
DYefremov
e476c26bb5 changed popdown to the hide for compatibility 2018-08-31 17:45:52 +03:00
DYefremov
fb0996f94e full screen mode prototype for the stream player 2018-08-31 17:26:36 +03:00
DYefremov
1da72666d7 fix play 2018-08-25 23:53:53 +03:00
DYefremov
f790f7d0b5 new prototype of the streams player 2018-08-25 15:30:12 +03:00
DYefremov
76a0f43485 added delay for the search and filter functions 2018-08-24 12:03:51 +03:00
DYefremov
0dcbf98d1f added TID for iptv list config dialog 2018-08-22 00:09:52 +03:00
DYefremov
bc0eb37775 info bar close implementations 2018-08-21 17:28:29 +03:00
DYefremov
d494702257 base implementation of iptv list config dialog for enigma2 2018-08-21 17:19:44 +03:00
DYefremov
db60217474 iptv list configuration dialog skeleton 2018-08-19 23:27:13 +03:00
DYefremov
a399660a15 fixed get url for iptv 2018-08-19 10:55:41 +03:00
DYefremov
2eeb53537a little changes for gui elements 2018-08-18 17:36:57 +03:00
DYefremov
ab5f98a2b6 skeleton for IPTV lists configuration dialog 2018-08-18 17:35:30 +03:00
DYefremov
6d92ed667f append picon while adding a service 2018-08-18 12:31:12 +03:00
DYefremov
ff123b579f data loading refactoring 2018-08-18 11:35:44 +03:00
DYefremov
f9a66f8f75 data loading optimisation 2018-08-18 10:21:40 +03:00
DYefremov
32f33815c2 little gui changes 2018-08-15 10:51:19 +03:00
DYefremov
ea9ea98e1a replaced the dialog with a window 2018-08-05 00:54:30 +03:00
DYefremov
9c32d24a20 webtv download fix 2018-08-04 11:38:38 +03:00
DYefremov
25f483d760 clean 2018-08-01 22:34:30 +03:00
DYefremov
d9e471eaec download fix 2018-08-01 22:10:00 +03:00
DYefremov
e4f8a075f4 removed preview mode elements for IPTV 2018-08-01 11:05:29 +03:00
DYefremov
0a3dc8f79d translation for some elements 2018-07-30 22:48:48 +03:00
DYefremov
ad69df0b63 new popup menu for satellites editor 2018-07-13 17:12:02 +03:00
DYefremov
2a3a9e124b file name format fix. for bouquets 2018-07-13 12:28:13 +03:00
DYefremov
8afd1e8a80 select all, including the filter 2018-07-12 19:44:27 +03:00
DYefremov
ee8cc5b139 added (un)select all in the satellites update dialog 2018-07-12 19:11:32 +03:00
DYefremov
bed490f491 minor gui changes 2018-07-12 11:57:02 +03:00
DYefremov
620ff4bd60 added label of the current bouquet name 2018-07-09 19:00:05 +03:00
DYefremov
99ecb0f22e updating dynamic elements before popup menu 2018-07-09 11:38:36 +03:00
DYefremov
31603bfd41 little ui elements changes 2018-07-08 22:05:26 +03:00
DYefremov
abd803a58c lock/hide fix 2018-07-08 14:58:41 +03:00
DYefremov
f84e77cbce new gui for main window 2018-07-08 00:09:26 +03:00
DYefremov
170c8ffc55 new gui for satellites update dialog 2018-07-06 22:26:48 +03:00
DYefremov
bf3ba96fb9 new gui for satellites update dialog 2018-07-06 22:26:27 +03:00
DYefremov
b3e057a5a3 new gui for satellites editor 2018-07-05 17:59:17 +03:00
DYefremov
78c07f3934 added extra dialog for searching unavailable iptv streams 2018-07-03 19:30:45 +03:00
DYefremov
249a49aff5 test implementation of remove all unavailable iptv streams 2018-06-29 22:43:04 +03:00
48 changed files with 11042 additions and 7794 deletions

View File

@@ -8,4 +8,4 @@ Exec=bash -c 'cd $(dirname %k) && ./start.py'
Terminal=false
Type=Application
Categories=Utility;Application;
StartupNotify=true
StartupNotify=false

View File

@@ -4,8 +4,9 @@
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
### Keyboard shortcuts:
**Ctrl + X, C, V, Up, Down, PageUp, PageDown, Home, End, S, T, E, L, H, Space; Insert, Delete, F2, Enter, P.**
* **Insert** - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
* **Ctrl + 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.
@@ -13,8 +14,8 @@ Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **Ctrl + S, T** in Satellites edit tool for create satellite or transponder.
* **Ctrl + L** - parental lock.
* **Ctrl + H** - hide/skip.
* **P** - enable/disable preview mode for IPTV in the bouquet list.
* **Enter** - start play IPTV or other stream in the bouquet list.
* **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).
* **Space** - select/deselect.
* **Left/Right** - remove selection.
* **Ctrl + Up, Down, PageUp, PageDown, Home, End** - move selected items in the list.
@@ -22,15 +23,15 @@ Clipboard is **"rubber"**. There is an accumulation before the insertion!
* Multiple selections in lists only with Space key (as in file managers).
* Ability to import IPTV into bouquet (Neutrino WEBTV) from m3u files.
* Ability to download picons and update satellites (transponders) from web.
* Preview (playing) IPTV or other streams directly from the bouquet list(should be installed VLC).
* Preview (playing) IPTV or other streams directly from the bouquet list(should be installed VLC).
### Minimum requirements:
Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings.
#### Note.
To create a simple debian package, you can use the build-deb.sh
To create a simple debian package, you can use the *build-deb.sh.*
Tests only in image based on OpenPLi or last BPanther(neutrino) images with GM 990 Spark Reloaded receiver
in my preferred linux distro (Last Linux Mint 18.* - MATE 64-bit)!
Tests only with openATV image and Formuler F1 receiver in my preferred Linux distros
(latest Linux Mint 18.* and 19 MATE 64-bit)!
**Terrestrial and cable channels at the moment are not supported!**
**Terrestrial(DVB-T/T2) and cable channels are supported(Enigma2 only) with limitation!**

View File

@@ -1,6 +1,6 @@
import logging
from functools import wraps
from threading import Thread
from threading import Thread, Timer
from gi.repository import GLib
@@ -43,5 +43,31 @@ def run_task(func):
return wrapper
def run_with_delay(timeout=5):
""" Starts the function with a delay.
If the previous timer still works, it will canceled!
"""
def run_with(func):
timer = None
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal timer
if timer and timer.is_alive():
timer.cancel()
def run():
GLib.idle_add(func, *args, **kwargs, priority=GLib.PRIORITY_LOW)
timer = Timer(interval=timeout, function=run)
timer.start()
return wrapper
return run_with
if __name__ == "__main__":
pass

337
app/connections.py Normal file
View File

@@ -0,0 +1,337 @@
import json
import os
import socket
import time
import urllib
from enum import Enum
from ftplib import FTP, error_perm
from telnetlib import Telnet
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import urlopen, HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener, install_opener
from app.commons import log
from app.properties import Profile
_BQ_FILES_LIST = ("tv", "radio", # enigma 2
"myservices.xml", "bouquets.xml", "ubouquets.xml") # neutrino
_DATA_FILES_LIST = ("lamedb", "lamedb5", "services.xml", "blacklist", "whitelist",)
_SAT_XML_FILE = "satellites.xml"
_WEBTV_XML_FILE = "webtv.xml"
class DownloadType(Enum):
ALL = 0
BOUQUETS = 1
SATELLITES = 2
PICONS = 3
WEBTV = 4
class HttpRequestType(Enum):
ZAP = "zap?sRef="
INFO = "about"
SIGNAL = "tunersignal"
class TestException(Exception):
pass
def download_data(*, properties, download_type=DownloadType.ALL, callback=None):
with FTP(host=properties["host"], user=properties["user"], passwd=properties["password"]) as ftp:
ftp.encoding = "utf-8"
callback("FTP OK.\n")
save_path = properties["data_dir_path"]
os.makedirs(os.path.dirname(save_path), exist_ok=True)
files = []
# bouquets section
if download_type is DownloadType.ALL or download_type is DownloadType.BOUQUETS:
ftp.cwd(properties["services_path"])
ftp.dir(files.append)
file_list = _BQ_FILES_LIST + _DATA_FILES_LIST if download_type is DownloadType.ALL else _BQ_FILES_LIST
for file in files:
name = str(file).strip()
if name.endswith(file_list):
name = name.split()[-1]
download_file(ftp, name, save_path, callback)
# satellites.xml and webtv section
if download_type in (DownloadType.ALL, DownloadType.SATELLITES, DownloadType.WEBTV):
ftp.cwd(properties["satellites_xml_path"])
files.clear()
ftp.dir(files.append)
for file in files:
name = str(file).strip()
if download_type in (DownloadType.ALL, DownloadType.SATELLITES) and name.endswith(_SAT_XML_FILE):
download_file(ftp, _SAT_XML_FILE, save_path, callback)
if download_type in (DownloadType.ALL, DownloadType.WEBTV) and name.endswith(_WEBTV_XML_FILE):
download_file(ftp, _WEBTV_XML_FILE, save_path, callback)
if callback is not None:
callback("\nDone.\n")
def upload_data(*, properties, download_type=DownloadType.ALL, remove_unused=False, profile=Profile.ENIGMA_2,
callback=None, done_callback=None, use_http=False):
data_path = properties["data_dir_path"]
host = properties["host"]
base_url = "http://{}:{}/api/".format(host, properties.get("http_port", "80"))
tn, ht = None, None # telnet, http
try:
if profile is Profile.ENIGMA_2 and use_http:
ht = http(properties.get("http_user", ""), properties.get("http_password", ""), base_url, callback)
next(ht)
message = ""
if download_type is DownloadType.BOUQUETS:
message = "User bouquets will be updated!"
elif download_type is DownloadType.ALL:
message = "All user data will be reloaded!"
elif download_type is DownloadType.SATELLITES:
message = "Satellites.xml file will be updated!"
params = urlencode({"text": message, "type": 2, "timeout": 5})
url = base_url + "message?{}".format(params)
ht.send(url)
if download_type is DownloadType.ALL:
time.sleep(5)
ht.send(base_url + "/powerstate?newstate=0")
time.sleep(2)
else:
# telnet
tn = telnet(host=host, user=properties.get("telnet_user", "root"),
password=properties.get("telnet_password", ""),
timeout=properties.get("telnet_timeout", 5))
next(tn)
# terminate enigma or neutrino
tn.send("init 4")
with FTP(host=host, user=properties["user"], passwd=properties["password"]) as ftp:
ftp.encoding = "utf-8"
callback("FTP OK.\n")
sat_xml_path = properties["satellites_xml_path"]
services_path = properties["services_path"]
if download_type is DownloadType.SATELLITES:
upload_xml(ftp, data_path, sat_xml_path, _SAT_XML_FILE, callback)
if profile is Profile.NEUTRINO_MP and download_type is DownloadType.WEBTV:
upload_xml(ftp, data_path, sat_xml_path, _WEBTV_XML_FILE, callback)
if download_type is DownloadType.BOUQUETS:
ftp.cwd(services_path)
upload_bouquets(ftp, data_path, remove_unused, callback)
if download_type is DownloadType.ALL:
upload_xml(ftp, data_path, sat_xml_path, _SAT_XML_FILE, callback)
if profile is Profile.NEUTRINO_MP:
upload_xml(ftp, data_path, sat_xml_path, _WEBTV_XML_FILE, callback)
ftp.cwd(services_path)
upload_bouquets(ftp, data_path, remove_unused, callback)
upload_files(ftp, data_path, _DATA_FILES_LIST, callback)
if download_type is DownloadType.PICONS:
upload_picons(ftp, properties.get("picons_dir_path"), properties.get("picons_path"))
if tn and not use_http:
# resume enigma or restart neutrino
tn.send("init 3" if profile is Profile.ENIGMA_2 else "init 6")
elif ht and use_http:
if download_type is DownloadType.BOUQUETS:
ht.send(base_url + "/servicelistreload?mode=2")
elif download_type is DownloadType.ALL:
ht.send(base_url + "/servicelistreload?mode=0")
ht.send(base_url + "/powerstate?newstate=4")
if done_callback is not None:
done_callback()
finally:
if tn:
tn.close()
if ht:
ht.close()
def upload_bouquets(ftp, data_path, remove_unused, callback):
if remove_unused:
remove_unused_bouquets(ftp, callback)
upload_files(ftp, data_path, _BQ_FILES_LIST, callback)
def upload_files(ftp, data_path, file_list, callback):
for file_name in os.listdir(data_path):
if file_name == _SAT_XML_FILE or file_name == _WEBTV_XML_FILE:
continue
if file_name.endswith(file_list):
send_file(file_name, data_path, ftp, callback)
def remove_unused_bouquets(ftp, callback):
files = []
ftp.dir(files.append)
for file in files:
name = str(file).strip()
if name.endswith(("tv", "radio", "bouquets.xml", "ubouquets.xml")):
name = name.split()[-1]
callback("Deleting file: {}. Status: {}\n".format(name, ftp.delete(name)))
def upload_xml(ftp, data_path, xml_path, xml_file, callback):
""" Used for transfer satellites.xml or webtv.xml files """
ftp.cwd(xml_path)
send_file(xml_file, data_path, ftp, callback)
def upload_picons(ftp, src, dest):
try:
ftp.cwd(dest)
except error_perm as e:
if str(e).startswith("550"):
ftp.mkd(dest) # if not exist
ftp.cwd(dest)
files = []
ftp.dir(files.append)
picons_suf = (".jpg", ".png")
for file in files:
name = str(file).strip()
if name.endswith(picons_suf):
name = name.split()[-1]
ftp.delete(name)
for file_name in os.listdir(src):
if file_name.endswith(picons_suf):
send_file(file_name, src, ftp)
def download_file(ftp, name, save_path, callback):
with open(save_path + name, "wb") as f:
callback("Downloading file: {}. Status: {}\n".format(name, str(ftp.retrbinary("RETR " + name, f.write))))
def send_file(file_name, path, ftp, callback):
""" Opens the file in binary mode and transfers into receiver """
with open(path + file_name, "rb") as f:
callback("Uploading file: {}. Status: {}\n".format(file_name, str(ftp.storbinary("STOR " + file_name, f))))
def http(user, password, url, callback):
init_auth(user, password, url)
while True:
url = yield
with urlopen(url, timeout=5) as f:
msg = json.loads(f.read().decode("utf-8")).get("message", None)
if msg:
callback("HTTP: {}\n".format(msg))
def telnet(host, port=23, user="", password="", timeout=5):
try:
tn = Telnet(host=host, port=port, timeout=timeout)
except socket.timeout:
log("telnet error: socket timeout")
else:
time.sleep(1)
command = yield
if user != "":
tn.read_until(b"login: ")
tn.write(user.encode("utf-8") + b"\n")
time.sleep(timeout)
if password != "":
tn.read_until(b"Password: ")
tn.write(password.encode("utf-8") + b"\n")
time.sleep(timeout)
tn.write("{}\r\n".format(command).encode("utf-8"))
time.sleep(timeout)
command = yield
time.sleep(timeout)
tn.write("{}\r\n".format(command).encode("utf-8"))
time.sleep(timeout)
yield
# ***************** http api *******************#
def http_request(host, port, user, password):
base_url = "http://{}:{}/api/".format(host, port)
init_auth(user, password, base_url)
while True:
req_type, ref = yield
url = base_url
if req_type is HttpRequestType.ZAP:
url = base_url + "zap?sRef={}".format(urllib.parse.quote(ref))
elif req_type is HttpRequestType.INFO:
url = base_url + HttpRequestType.INFO.value
elif req_type is HttpRequestType.SIGNAL:
url = base_url + HttpRequestType.SIGNAL.value
try:
with urlopen(url, timeout=5) as f:
yield json.loads(f.read().decode("utf-8"))
except (URLError, HTTPError):
yield None
# ***************** Connections testing *******************#
def test_ftp(host, port, user, password, timeout=5):
try:
with FTP(host=host, user=user, passwd=password, timeout=timeout) as ftp:
return ftp.getwelcome()
except (error_perm, ConnectionRefusedError, OSError) as e:
raise TestException(e)
def test_http(host, port, user, password, timeout=5):
try:
params = urlencode({"text": "Connection test", "type": 2, "timeout": timeout})
url = "http://{}:{}/api/message?{}".format(host, port, params)
# authentication
init_auth(user, password, url)
with urlopen(url, timeout=5) as f:
return json.loads(f.read().decode("utf-8")).get("message", "")
except (URLError, HTTPError) as e:
raise TestException(e)
def init_auth(user, password, url):
""" Init authentication """
pass_mgr = HTTPPasswordMgrWithDefaultRealm()
pass_mgr.add_password(None, url, user, password)
auth_handler = HTTPBasicAuthHandler(pass_mgr)
opener = build_opener(auth_handler)
install_opener(opener)
def test_telnet(host, port, user, password, timeout=5):
try:
gen = telnet_test(host, port, user, password, timeout)
res = next(gen)
print(res)
res = next(gen)
return res
except (socket.timeout, OSError) as e:
raise TestException(e)
def telnet_test(host, port, user, password, timeout):
tn = Telnet(host=host, port=port, timeout=timeout)
time.sleep(1)
tn.read_until(b"login: ", timeout=2)
tn.write(user.encode("utf-8") + b"\n")
time.sleep(timeout)
tn.read_until(b"Password: ", timeout=2)
tn.write(password.encode("utf-8") + b"\n")
time.sleep(timeout)
yield tn.read_very_eager()
tn.close()
yield "Done"
if __name__ == "__main__":
pass

View File

@@ -114,7 +114,7 @@ FEC_DEFAULT = {"0": "Auto", "1": "1/2", "2": "2/3", "3": "3/4", "4": "5/6", "5":
SYSTEM = {"0": "DVB-S", "1": "DVB-S2"}
MODULATION = {"0": "Auto", "1": "QPSK", "2": "8PSK", "3": "16APSK", "5": "32APSK"}
MODULATION = {"0": "Auto", "1": "QPSK", "2": "8PSK", "4": "16APSK", "5": "32APSK"}
SERVICE_TYPE = {"-2": "Data", "1": "TV", "2": "Radio", "3": "Data", "10": "Radio", "22": "TV (H264)",
"25": "TV (HD)", "31": "TV (UHD)"}

View File

@@ -1,8 +1,11 @@
""" Module for parsing bouquets """
import re
from app.eparser.ecommons import BqServiceType, BouquetService, Bouquets, Bouquet, BqType
_TV_ROOT_FILE_NAME = "bouquets.tv"
_RADIO_ROOT_FILE_NAME = "bouquets.radio"
_DEFAULT_BOUQUET_NAME = "favourites"
def get_bouquets(path):
@@ -13,32 +16,39 @@ def get_bouquets(path):
def write_bouquets(path, bouquets):
srv_line = '#SERVICE 1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
line = []
pattern = re.compile("[^\w_()]+")
for bqs in bouquets:
line.clear()
line.append("#NAME {}\n".format(bqs.name))
for bq in bqs.bouquets:
line.append(srv_line.format(bq.name.replace(" ", "_"), bq.type))
write_bouquet(path, bq.name, bq.type, bq.services)
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)
line.append(srv_line.format(bq_name, bq.type))
write_bouquet(path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services)
with open(path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
file.writelines(line)
def write_bouquet(path, name, bq_type, channels):
def write_bouquet(path, name, channels):
bouquet = ["#NAME {}\n".format(name)]
for ch in channels:
if not ch: # if was duplicate
continue
if ch.service_type == BqServiceType.IPTV.name or ch.service_type == BqServiceType.MARKER.name:
bouquet.append("#SERVICE {}\n".format(ch.fav_id.strip()))
else:
bouquet.append("#SERVICE {}\n".format(to_bouquet_id(ch)))
data = to_bouquet_id(ch)
if ch.service:
bouquet.append("#SERVICE {}:{}\n#DESCRIPTION {}\n".format(data, ch.service, ch.service))
else:
bouquet.append("#SERVICE {}\n".format(data))
with open(path + "userbouquet.{}.{}".format(name.replace(" ", "_"), bq_type), "w", encoding="utf-8") as file:
with open(path, "w", encoding="utf-8") as file:
file.writelines(bouquet)
@@ -65,9 +75,12 @@ def get_bouquet(path, name, bq_type):
services.append(BouquetService(ch_data[-1].split("\n")[0], BqServiceType.IPTV, ch, 0))
else:
fav_id = "{}:{}:{}:{}".format(ch_data[3], ch_data[4], ch_data[5], ch_data[6])
services.append(BouquetService(None, BqServiceType.DEFAULT, fav_id, 0))
name = None
if len(ch_data) == 12:
name, desc = str(ch_data[-1]).split("\n#DESCRIPTION")
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id, 0))
return srvs[0].strip("#NAME").strip(), services
return srvs[0].lstrip("#NAME").strip(), services
def parse_bouquets(path, bq_name, bq_type):

View File

@@ -59,7 +59,11 @@ def write_to_lamedb5(path, services):
data_id = str(srv.data_id).split(_SEP)
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
tr_set.add("t:{},{}\n".format(tr_id, srv.transponder.replace(" ", ":", 1)))
services_lines.append("s:{},\"{}\",{}\n".format(srv.data_id, srv.service, srv.flags_cas))
# 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)
@@ -109,7 +113,12 @@ def parse_v5(path):
for l in lns:
if l.startswith("s:"):
srv_data = l.strip("s:").split(",", 2)
srv_data[1], srv_data[2] = srv_data[1].strip("\""), srv_data[2].strip()
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 l.startswith("t:"):
tr, srv = l.split(",")
@@ -142,6 +151,7 @@ def parse_services(services, transponders, path):
sp = "0"
tid = data[2]
nid = data[3]
srv_type = int(data[4])
transponder_id = "{}:{}:{}".format(data[1], tid, nid)
transponder = transponders.get(transponder_id, None)
@@ -151,7 +161,7 @@ def parse_services(services, transponders, path):
onid = str(data[1]).lstrip(sp).upper()
# For comparison in bouquets. Needed in upper case!!!
fav_id = "{}:{}:{}:{}".format(ssid, tid, nid, onid)
picon_id = "1_0_{}_{}_{}_{}_{}_0_0_0.png".format(1, ssid, tid, nid, onid)
picon_id = "1_0_{:X}_{}_{}_{}_{}_0_0_0.png".format(srv_type, ssid, tid, nid, onid)
all_flags = ch[2].split(",")
coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None
@@ -160,7 +170,7 @@ def parse_services(services, transponders, path):
locked = LOCKED_ICON if fav_id in blacklist else None
package = list(filter(lambda x: x.startswith("p:"), all_flags))
package = package[0][2:] if package else None
package = package[0][2:] if package else ""
if transponder is not None:
tr_type, sp, tr = str(transponder).partition(" ")
@@ -168,6 +178,17 @@ def parse_services(services, transponders, path):
service_type = SERVICE_TYPE.get(data[4], SERVICE_TYPE["-2"])
# removing all non printable symbols!
srv_name = "".join(c for c in ch[1] if c.isprintable())
if tr_type == "t":
system = "DVB-T"
pos = "T"
elif tr_type == "c":
system = "CABLE"
pos = "C"
else:
system = "DVB-S2" if len(tr) > 7 else "DVB-S"
pos = "{}.{}".format(tr[4][:-1], tr[4][-1:])
channels.append(Service(flags_cas=ch[2],
transponder_type=tr_type,
coded=coded,
@@ -181,10 +202,10 @@ def parse_services(services, transponders, path):
ssid=data[0],
freq=tr[0],
rate=tr[1],
pol=POLARIZATION[tr[2]],
pol=POLARIZATION.get(tr[2], None),
fec=FEC[tr[3]],
system="DVB-S2" if len(tr) > 7 else "DVB-S",
pos="{}.{}".format(tr[4][:-1], tr[4][-1:]),
system=system,
pos=pos,
data_id=ch[0],
fav_id=fav_id,
transponder=transponder))

View File

@@ -8,6 +8,7 @@ from .ecommons import BqServiceType, Service
# url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group
NEUTRINO_FAV_ID_FORMAT = "{}::{}::{}::{}::{}::{}::{}::{}::{}::{}"
ENIGMA2_FAV_ID_FORMAT = " {}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTION: {}\n"
MARKER_FORMAT = " 1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n"
class StreamType(Enum):
@@ -18,25 +19,32 @@ class StreamType(Enum):
def parse_m3u(path, profile):
with open(path) as file:
aggr = [None] * 10
channels = []
count = 0
services = []
groups = set()
counter = 0
name = None
fav_id = None
for line in file.readlines():
if line.startswith("#EXTINF"):
name = line[1 + line.index(","):].strip()
count += 1
elif count == 1:
count = 0
elif line.startswith("#EXTGRP") and profile is Profile.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)
services.append(mr)
elif not line.startswith("#"):
if profile is Profile.ENIGMA_2:
fav_id = ENIGMA2_FAV_ID_FORMAT.format(StreamType.DVB_TS.value, 1, 0, 0, 0, 0,
fav_id = ENIGMA2_FAV_ID_FORMAT.format(StreamType.NONE_TS.value, 1, 0, 0, 0, 0,
line.strip().replace(":", "%3a"), name, name, None)
elif profile is Profile.NEUTRINO_MP:
fav_id = NEUTRINO_FAV_ID_FORMAT.format(line.strip(), "", 0, None, None, None, None, "", "", 1)
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], BqServiceType.IPTV.name, *aggr, fav_id, None)
channels.append(srv)
services.append(srv)
return channels
return services
if __name__ == "__main__":

View File

@@ -7,6 +7,7 @@ from xml.dom.minidom import parse, Document
import os
from app.commons import log
from .ecommons import POLARIZATION, FEC, SYSTEM, MODULATION, PLS_MODE, Transponder, Satellite, get_key_by_value
__COMMENT = (" File was created in DemonEditor\n\n"
@@ -21,7 +22,7 @@ __COMMENT = (" File was created in DemonEditor\n\n"
"polarization: 0 - Horizontal, 1 - Vertical, 2 - Left Circular, 3 - Right Circular\n"
"fec_inner: 0 - Auto, 1 - 1/2, 2 - 2/3, 3 - 3/4, 4 - 5/6, 5 - 7/8, 6 - 8/9, 7 - 3/5,\n"
"8 - 4/5, 9 - 9/10, 15 - None\n"
"modulation: 0 - Auto, 1 - QPSK, 2 - 8PSK, 3 - 16APSK, 5 - 32APSK\n"
"modulation: 0 - Auto, 1 - QPSK, 2 - 8PSK, 4 - 16APSK, 5 - 32APSK\n"
"rolloff: 0 - 0.35, 1 - 0.25, 2 - 0.20, 3 - Auto\n"
"pilot: 0 - Off, 1 - On, 2 - Auto\n"
"inversion: 0 = Off, 1 = On, 2 = Auto (default)\n"
@@ -74,31 +75,38 @@ def write_satellites(satellites, data_path):
doc.unlink()
def parse_transponders(elem):
def parse_transponders(elem, sat_name):
""" Parsing satellite transponders """
transponders = []
for el in elem.getElementsByTagName("transponder"):
if el.hasAttributes():
atr = el.attributes
tr = Transponder(atr["frequency"].value,
atr["symbol_rate"].value,
POLARIZATION[atr["polarization"].value],
FEC[atr["fec_inner"].value],
SYSTEM[atr["system"].value],
MODULATION[atr["modulation"].value],
PLS_MODE[atr["pls_mode"].value] if "pls_mode" in atr else None,
atr["pls_code"].value if "pls_code" in atr else None,
atr["is_id"].value if "is_id" in atr else None)
transponders.append(tr)
try:
tr = Transponder(atr["frequency"].value,
atr["symbol_rate"].value,
POLARIZATION[atr["polarization"].value],
FEC[atr["fec_inner"].value],
SYSTEM[atr["system"].value],
MODULATION[atr["modulation"].value],
PLS_MODE[atr["pls_mode"].value] if "pls_mode" in atr else None,
atr["pls_code"].value if "pls_code" in atr else None,
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)
return transponders
def parse_sat(elem):
""" Parsing satellite """
return Satellite(elem.attributes["name"].value,
sat_name = elem.attributes["name"].value
return Satellite(sat_name,
elem.attributes["flags"].value,
elem.attributes["position"].value,
parse_transponders(elem))
parse_transponders(elem, sat_name))
@lru_cache(maxsize=1)

View File

@@ -1,180 +0,0 @@
import os
import socket
import time
from enum import Enum
from ftplib import FTP, error_perm
from telnetlib import Telnet
from app.commons import log
from app.properties import Profile
__DATA_FILES_LIST = ("tv", "radio", "lamedb", "lamedb5", "blacklist", "whitelist", # enigma 2
"services.xml", "myservices.xml", "bouquets.xml", "ubouquets.xml") # neutrino
_SATELLITES_XML_FILE = "satellites.xml"
_WEBTV_XML_FILE = "webtv.xml"
class DownloadDataType(Enum):
ALL = 0
BOUQUETS = 1
SATELLITES = 2
PICONS = 3
WEBTV = 4
def download_data(*, properties, download_type=DownloadDataType.ALL, callback=None):
with FTP(host=properties["host"]) as ftp:
ftp.login(user=properties["user"], passwd=properties["password"])
ftp.encoding = "utf-8"
save_path = properties["data_dir_path"]
os.makedirs(os.path.dirname(save_path), exist_ok=True)
files = []
# bouquets section
if download_type is DownloadDataType.ALL or download_type is DownloadDataType.BOUQUETS:
ftp.cwd(properties["services_path"])
ftp.dir(files.append)
for file in files:
name = str(file).strip()
if name.endswith(__DATA_FILES_LIST):
name = name.split()[-1]
download_file(ftp, name, save_path)
# satellites.xml and webtv section
if download_type in (DownloadDataType.ALL, DownloadDataType.SATELLITES, DownloadDataType.WEBTV):
ftp.cwd(properties["satellites_xml_path"])
files.clear()
ftp.dir(files.append)
for file in files:
name = str(file).strip()
if download_type in (DownloadDataType.ALL, DownloadDataType.SATELLITES):
if name.endswith(_SATELLITES_XML_FILE):
download_file(ftp, _SATELLITES_XML_FILE, save_path)
elif download_type in (DownloadDataType.ALL, DownloadDataType.WEBTV):
if name.endswith(_WEBTV_XML_FILE):
download_file(ftp, _WEBTV_XML_FILE, save_path)
if callback is not None:
callback()
def upload_data(*, properties, download_type=DownloadDataType.ALL, remove_unused=False, profile=Profile.ENIGMA_2,
callback=None):
data_path = properties["data_dir_path"]
host = properties["host"]
# telnet
tn = telnet(host=host, user=properties.get("telnet_user", "root"), password=properties.get("telnet_password", ""),
timeout=properties.get("telnet_timeout", 5))
next(tn)
# terminate enigma or neutrino
tn.send("init 4")
with FTP(host=host) as ftp:
ftp.login(user=properties["user"], passwd=properties["password"])
ftp.encoding = "utf-8"
if download_type is DownloadDataType.ALL or download_type is DownloadDataType.SATELLITES:
ftp.cwd(properties["satellites_xml_path"])
send = send_file(_SATELLITES_XML_FILE, data_path, ftp)
if download_type is DownloadDataType.SATELLITES:
tn.send("init 3" if profile is Profile.ENIGMA_2 else "init 6")
if callback is not None:
callback()
return send
if profile is Profile.NEUTRINO_MP and download_type in (DownloadDataType.ALL, DownloadDataType.WEBTV):
ftp.cwd(properties["satellites_xml_path"])
send = send_file(_WEBTV_XML_FILE, data_path, ftp)
if download_type is DownloadDataType.WEBTV:
tn.send("init 6")
if callback is not None:
callback()
return send
if download_type is DownloadDataType.ALL or download_type is DownloadDataType.BOUQUETS:
ftp.cwd(properties["services_path"])
if remove_unused:
files = []
ftp.dir(files.append)
for file in files:
name = str(file).strip()
if name.endswith(__DATA_FILES_LIST):
name = name.split()[-1]
ftp.delete(name)
for file_name in os.listdir(data_path):
if file_name == _SATELLITES_XML_FILE or file_name == _WEBTV_XML_FILE:
continue
if file_name.endswith(__DATA_FILES_LIST):
send_file(file_name, data_path, ftp)
if download_type is DownloadDataType.PICONS:
picons_dir_path = properties.get("picons_dir_path")
picons_path = properties.get("picons_path")
try:
ftp.cwd(picons_path)
except error_perm as e:
if str(e).startswith("550"):
ftp.mkd(picons_path) # if not exist
ftp.cwd(picons_path)
files = []
ftp.dir(files.append)
picons_suf = (".jpg", ".png")
for file in files:
name = str(file).strip()
if name.endswith(picons_suf):
name = name.split()[-1]
ftp.delete(name)
for file_name in os.listdir(picons_dir_path):
if file_name.endswith(picons_suf):
send_file(file_name, picons_dir_path, ftp)
# resume enigma or restart neutrino
tn.send("init 3" if profile is Profile.ENIGMA_2 else "init 6")
if callback is not None:
callback()
def download_file(ftp, name, save_path):
with open(save_path + name, "wb") as f:
ftp.retrbinary("RETR " + name, f.write)
def send_file(file_name, path, ftp):
""" Opens the file in binary mode and transfers into receiver """
with open(path + file_name, "rb") as f:
return ftp.storbinary("STOR " + file_name, f)
def telnet(host, port=23, user="", password="", timeout=5):
try:
tn = Telnet(host=host, port=port, timeout=timeout)
except socket.timeout:
log("telnet error: socket timeout")
else:
time.sleep(1)
command = yield
if user != "":
tn.read_until(b"login: ")
tn.write(user.encode("utf-8") + b"\n")
time.sleep(timeout)
if password != "":
tn.read_until(b"Password: ")
tn.write(password.encode("utf-8") + b"\n")
time.sleep(timeout)
tn.write("{}\r\n".format(command).encode("utf-8"))
time.sleep(timeout)
command = yield
time.sleep(timeout)
tn.write("{}\r\n".format(command).encode("utf-8"))
time.sleep(timeout)
tn.close()
yield
if __name__ == "__main__":
pass

View File

@@ -39,28 +39,20 @@ def write_config(config):
def get_default_settings():
return {
Profile.ENIGMA_2.value: {
"host": "127.0.0.1", "port": "21",
"user": "root", "password": "root",
"telnet_user": "", "telnet_password": "",
"telnet_port": "21", "telnet_timeout": 5,
"services_path": "/etc/enigma2/",
"user_bouquet_path": "/etc/enigma2/",
"satellites_xml_path": "/etc/tuxbox/",
"picons_path": "/usr/share/enigma2/picon",
"data_dir_path": DATA_PATH + "enigma2/",
"picons_dir_path": DATA_PATH + "enigma2/picons/",
"v5_support": False},
"host": "127.0.0.1", "port": "21", "user": "root", "password": "root",
"http_user": "root", "http_password": "", "http_port": "80", "http_timeout": 5,
"telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 5,
"services_path": "/etc/enigma2/", "user_bouquet_path": "/etc/enigma2/",
"satellites_xml_path": "/etc/tuxbox/", "data_dir_path": DATA_PATH + "enigma2/",
"picons_path": "/usr/share/enigma2/picon", "picons_dir_path": DATA_PATH + "enigma2/picons/",
"v5_support": False, "http_api_support": False},
Profile.NEUTRINO_MP.value: {
"host": "127.0.0.1", "port": "21",
"user": "root", "password": "root",
"telnet_user": "root", "telnet_password": "",
"telnet_port": "21", "telnet_timeout": 1,
"services_path": "/var/tuxbox/config/zapit/",
"user_bouquet_path": "/var/tuxbox/config/zapit/",
"satellites_xml_path": "/var/tuxbox/config/",
"picons_path": "/usr/share/tuxbox/neutrino/icons/logo/",
"data_dir_path": DATA_PATH + "neutrino/",
"picons_dir_path": DATA_PATH + "neutrino/picons/"},
"host": "127.0.0.1", "port": "21", "user": "root", "password": "root",
"http_user": "", "http_password": "", "http_port": "80", "http_timeout": 2,
"telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 1,
"services_path": "/var/tuxbox/config/zapit/", "user_bouquet_path": "/var/tuxbox/config/zapit/",
"satellites_xml_path": "/var/tuxbox/config/", "data_dir_path": DATA_PATH + "neutrino/",
"picons_path": "/usr/share/tuxbox/neutrino/icons/logo/", "picons_dir_path": DATA_PATH + "neutrino/picons/"},
"profile": Profile.ENIGMA_2.value}

View File

@@ -1,96 +1,53 @@
from app.commons import run_idle
from app.tools import vlc
from app.ui.uicommons import Gtk, Gdk
MRL = "url"
class Player:
_VLC_INSTANCE = None
def __init__(self, url):
handlers = {"on_play": self.on_play,
"on_stop": self.on_stop,
"on_drawing_area_realize": self.on_drawing_area_realize,
"on_press": self.on_press,
"on_key_release": self.on_key_release,
"on_state_changed": self.on_state_changed,
"on_close_window": self.on_close_window}
builder = Gtk.Builder()
builder.add_objects_from_file("player.glade", ("player_main_window",))
builder.connect_signals(handlers)
self._main_window = builder.get_object("player_main_window")
self._main_box = builder.get_object("main_box")
self._buttonbox = builder.get_object("buttonbox")
self._frame = builder.get_object("")
self._drawing_area = builder.get_object("drawing_area")
self._drawing_area.set_events(Gdk.ModifierType.BUTTON1_MASK)
self._player = Player.get_vlc_instance().media_player_new()
self._is_played = False
self._url = url
self._full_screen = False
def __init__(self):
self._is_playing = False
self._player = self.get_vlc_instance()
@staticmethod
def get_vlc_instance():
if Player._VLC_INSTANCE:
return Player._VLC_INSTANCE
_VLC_INSTANCE = vlc.Instance("--no-xlib")
_VLC_INSTANCE = vlc.Instance("--quiet --no-xlib").media_player_new()
return _VLC_INSTANCE
def on_play(self, item):
if not self._is_played:
def play(self, mrl=None):
if not self._is_playing:
if mrl:
self._player.set_mrl(mrl)
self._player.play()
self._is_played = True
self._is_playing = True
def on_stop(self, item):
if self._is_played:
def stop(self):
if self._is_playing:
self._player.stop()
self._is_played = False
self._is_playing = False
def on_press(self, area, event: Gdk.EventButton):
if event.button == Gdk.BUTTON_PRIMARY and event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
self.change_state()
def pause(self):
self._player.pause()
def on_state_changed(self, window, event):
if event.new_window_state & Gdk.WindowState.FULLSCREEN:
if self._main_box in window:
window.remove(self._main_box)
self._drawing_area.reparent(self._main_window)
else:
if self._drawing_area in self._main_window:
window.remove(self._drawing_area)
window.add(self._main_box)
self._main_box.pack_start(self._drawing_area, True, True, 0)
self._main_box.reorder_child(self._drawing_area, 0)
def change_state(self):
self._full_screen = not self._full_screen
self._main_window.fullscreen() if self._full_screen else self._main_window.unfullscreen()
def on_key_release(self, area, key):
if key.keyval in (Gdk.KEY_F, Gdk.KEY_f):
self.change_state()
def on_drawing_area_realize(self, widget):
win_id = widget.get_window().get_xid()
def release(self):
if self._player:
self._is_played = True
self._player.set_xwindow(win_id)
self._player.set_mrl(self._url)
self._player.play()
@run_idle
def on_close_window(self, *args):
if self._player:
self.on_stop(None)
self._is_playing = False
self._player.stop()
self._player.release()
Gtk.main_quit()
def show(self):
self._main_window.show()
Gtk.main()
def set_xwindow(self, xid):
self._player.set_xwindow(xid)
def set_mrl(self, mrl):
self._player.set_mrl(mrl)
def is_playing(self):
return self._is_playing
def set_full_screen(self, full):
self._player.set_fullscreen(full)
if __name__ == "__main__":
Player(MRL).show()
pass

View File

@@ -6,25 +6,26 @@ import shutil
from collections import namedtuple
from html.parser import HTMLParser
from app.commons import log, run_task
from app.commons import run_task
from app.properties import Profile
_ENIGMA2_PICON_KEY = "{:X}:{:X}:{:X}0000"
_ENIGMA2_PICON_KEY = "{:X}:{:X}:{}"
_NEUTRINO_PICON_KEY = "{:x}{:04x}{:04x}.png"
Provider = namedtuple("Provider", ["logo", "name", "pos", "url", "on_id", "selected"])
Provider = namedtuple("Provider", ["logo", "name", "pos", "url", "on_id", "ssid", "single", "selected"])
Picon = namedtuple("Picon", ["ref", "ssid", "v_pid"])
class PiconsParser(HTMLParser):
""" Parser for package html page. (https://www.lyngsat.com/packages/*provider-name*.html) """
def __init__(self, entities=False, separator=' '):
def __init__(self, entities=False, separator=' ', single=None):
HTMLParser.__init__(self)
self._parse_html_entities = entities
self._separator = separator
self._single = single
self._is_td = False
self._is_th = False
self._current_row = []
@@ -57,16 +58,20 @@ class PiconsParser(HTMLParser):
elif tag == 'tr':
row = self._current_row
ln = len(row)
if 9 < ln < 13:
url = None
if row[0].startswith("../logo/"):
url = row[0]
elif row[1].startswith("../logo/"):
url = row[1]
ssid = row[-4]
if url and len(ssid) > 2:
self.picons.append(Picon(url, ssid, row[-3]))
if self._single and ln == 4 and row[0].startswith("../../logo/"):
self.picons.append(Picon(row[0].strip("../"), "0", "0"))
else:
if 9 < ln < 13:
url = None
if row[0].startswith("../logo/"):
url = row[0]
elif row[1].startswith("../logo/"):
url = row[1]
ssid = row[-4]
if url and len(ssid) > 2:
self.picons.append(Picon(url, ssid, row[-3]))
self._current_row = []
@@ -74,9 +79,15 @@ class PiconsParser(HTMLParser):
pass
@staticmethod
def parse(open_path, picons_path, tmp_path, on_id, pos, picon_ids, profile=Profile.ENIGMA_2):
def parse(open_path, picons_path, tmp_path, provider, picon_ids, profile=Profile.ENIGMA_2):
with open(open_path, encoding="utf-8", errors="replace") as f:
parser = PiconsParser()
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
@@ -84,19 +95,25 @@ class PiconsParser(HTMLParser):
os.makedirs(picons_path, exist_ok=True)
for p in picons:
try:
name = PiconsParser.format(p.ssid, on_id, p.v_pid, pos, picon_ids, profile)
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, profile)
p_name = picons_path + (name if name else os.path.basename(p.ref))
shutil.copyfile(tmp_path + "www.lyngsat.com/" + p.ref.lstrip("."), p_name)
except (TypeError, ValueError) as e:
log("Picons format parse error: {}".format(p) + "\n" + str(e))
print(e)
msg = "Picons format parse error: {}".format(p) + "\n" + str(e)
# log(msg)
print(msg)
@staticmethod
def format(ssid, on_id, v_pid, pos, picon_ids, profile: Profile):
tr_id = int(ssid[:-2] if len(ssid) < 4 else ssid[:2])
def format(ssid, on_id, namespace, picon_ids, profile: Profile):
if profile is Profile.ENIGMA_2:
return picon_ids.get(_ENIGMA2_PICON_KEY.format(int(ssid), int(on_id), int(pos)), None)
return picon_ids.get(_ENIGMA2_PICON_KEY.format(int(ssid), int(on_id), namespace), None)
elif profile is Profile.NEUTRINO_MP:
tr_id = int(ssid[:-2] if len(ssid) < 4 else ssid[:2])
return _NEUTRINO_PICON_KEY.format(tr_id, int(on_id), int(ssid))
else:
return "{}.png".format(ssid)
@@ -106,22 +123,32 @@ class ProviderParser(HTMLParser):
""" Parser for satellite html page. (https://www.lyngsat.com/*sat-name*.html) """
_POSITION_PATTERN = re.compile("at\s\d+\..*(?:E|W)']")
_ONID_TID_PATTERN = re.compile("^\d+-\d+.*")
_TRANSPONDER_FREQUENCY_PATTERN = re.compile("^\d+ [HVLR]+")
_DOMAIN = "https://www.lyngsat.com"
_TV_DOMAIN = _DOMAIN + "/tvchannels/"
_RADIO_DOMAIN = _DOMAIN + "/radiochannels/"
_PKG_DOMAIN = _DOMAIN + "/packages/"
def __init__(self, entities=False, separator=' '):
HTMLParser.__init__(self)
self.convert_charrefs = False
self._ON_ID_BLACK_LIST = ("65535", "?", "0", "1")
self._parse_html_entities = entities
self._separator = separator
self._is_td = False
self._is_th = False
self._is_onid_tid = False
self._is_provider = False
self._current_row = []
self._current_cell = []
self.rows = []
self._ids = set()
self._prv_names = set()
self._positon = None
self._on_id = None
self._freq = None
def handle_starttag(self, tag, attrs):
if tag == 'td':
@@ -132,13 +159,23 @@ class ProviderParser(HTMLParser):
if attrs[0][1].startswith("logo/"):
self._current_row.append(attrs[0][1])
if tag == "a":
if "https://www.lyngsat.com/packages/" in attrs[0][1]:
self._current_row.append(attrs[0][1])
url = attrs[0][1]
if url.startswith((self._PKG_DOMAIN, self._TV_DOMAIN, self._RADIO_DOMAIN)):
self._current_row.append(url)
if tag == "font" and len(attrs) == 1:
atr = attrs[0]
if len(atr) == 2 and atr[1] == "darkgreen":
self._is_onid_tid = True
def handle_data(self, data):
""" Save content to a cell """
if self._is_td or self._is_th:
self._current_cell.append(data.strip())
if self._is_onid_tid:
m = self._ONID_TID_PATTERN.match(data)
if m:
self._on_id, tid = m.group().split("-")
self._is_onid_tid = False
def handle_endtag(self, tag):
if tag == 'td':
@@ -151,20 +188,47 @@ class ProviderParser(HTMLParser):
self._current_row.append(final_cell)
self._current_cell = []
elif tag == 'tr':
row = self._current_row
r = self._current_row
# Satellite position
if not self._positon:
pos = re.findall(self._POSITION_PATTERN, str(row))
pos = re.findall(self._POSITION_PATTERN, str(r))
if pos:
self._positon = "".join(c for c in str(pos) if c.isdigit() or c in ".EW")
if len(row) == 12:
on_id, sep, tid = str(row[-2]).partition("-")
if tid and on_id not in self._ON_ID_BLACK_LIST and on_id not in self._ids:
row[-2] = on_id
self.rows.append(row)
self._ids.add(on_id)
row[0] = self._positon
len_row = len(r)
if len_row > 2:
m = self._TRANSPONDER_FREQUENCY_PATTERN.match(r[1])
if m:
self._freq = m.group().split()[0]
if len_row == 12:
# Providers
name = r[5]
self._prv_names.add(name)
m = self._ONID_TID_PATTERN.match(str(r[-2]))
if m:
on_id, tid = m.group().split("-")
if on_id not in self._ids:
r[-2] = on_id
self._ids.add(on_id)
r[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,
ssid=None, single=False, selected=True))
elif 6 < len_row < 10:
# 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 name and url:
on_id = "{}::{}".format(self._on_id if self._on_id else "1", self._freq)
self.rows.append(Provider(logo=None, name=name, pos=self._positon, url=url, on_id=on_id,
ssid=ssid, single=True, selected=False))
self._current_row = []
def error(self, message):
@@ -180,10 +244,8 @@ def parse_providers(open_path):
with open(open_path, encoding="utf-8", errors="replace") as f:
parser.feed(f.read())
rows = parser.rows
if rows:
return [Provider(logo=r[2], name=r[5], pos=r[0], url=r[6], on_id=r[-2], selected=True) for r in rows]
return parser.rows
@run_task

View File

@@ -1,116 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkApplicationWindow" id="player_main_window">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Player</property>
<property name="icon_name">vlc</property>
<signal name="delete-event" handler="on_close_window" swapped="no"/>
<signal name="window-state-event" handler="on_state_changed" swapped="no"/>
<child>
<object class="GtkBox" id="main_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkDrawingArea" id="drawing_area">
<property name="width_request">320</property>
<property name="height_request">240</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
<signal name="button-press-event" handler="on_press" swapped="no"/>
<signal name="key-release-event" handler="on_key_release" swapped="no"/>
<signal name="realize" handler="on_drawing_area_realize" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButtonBox" id="buttonbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">3</property>
<property name="margin_right">5</property>
<property name="spacing">2</property>
<property name="homogeneous">True</property>
<property name="layout_style">start</property>
<child>
<object class="GtkButton" id="play_button">
<property name="label">gtk-media-play</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_play" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="stop_button">
<property name="label">gtk-media-stop</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_stop" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="close_button">
<property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_close_window" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
<property name="secondary">True</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -76,12 +76,17 @@ class SatellitesParser(HTMLParser):
self._source = source
for src in SatelliteSource.get_sources(self._source):
request = requests.get(url=src, headers=self._HEADERS)
reason = request.reason
if reason == "OK":
self.feed(request.text)
try:
request = requests.get(url=src, headers=self._HEADERS)
except requests.exceptions.ConnectionError as e:
print(repr(e))
return []
else:
print(reason)
reason = request.reason
if reason == "OK":
self.feed(request.text)
else:
print(reason)
if self._rows:
if self._source is SatelliteSource.FLYSAT:
@@ -90,14 +95,28 @@ class SatellitesParser(HTMLParser):
return list(map(get_sat, filter(lambda x: all(x) and len(x) == 5, self._rows)))
elif self._source is SatelliteSource.LYNGSAT:
rows = filter(lambda x: len(x) in (5, 7), self._rows)
extra_pattern = re.compile("^https://www\.lyngsat\.com/[\w-]+\.html")
sats = []
current_pos = "0"
for row in rows:
for row in filter(lambda x: len(x) in (5, 7, 8), self._rows):
r_len = len(row)
if r_len == 7:
current_pos = self.parse_position(row[2])
sats.append((row[4], current_pos, row[5], row[1], False))
if r_len == 8: # for a very limited number of satellites
data = list(filter(None, row))
urls = set()
sat_type = ""
for d in data:
url = re.match(extra_pattern, d)
if url:
urls.add(url.group(0))
if d in ("C", "Ku", "CKu"):
sat_type = d
current_pos = self.parse_position(data[1])
for url in urls:
name = url.rsplit("/")[-1].rstrip(".html").replace("-", " ")
sats.append((name, current_pos, sat_type, url, False))
elif r_len == 5:
sats.append((row[2], current_pos, row[3], row[1], False))
return sats

View File

@@ -53,10 +53,10 @@ import logging
logger = logging.getLogger(__name__)
__version__ = "3.0.0102"
__libvlc_version__ = "3.0.0"
__generator_version__ = "1.2"
build_date = "Mon Feb 19 18:13:20 2018 3.0.0"
__version__ = "3.0.3104"
__libvlc_version__ = "3.0.3"
__generator_version__ = "1.4"
build_date = "Fri Jul 13 15:18:27 2018 3.0.3"
# The libvlc doc states that filenames are expected to be in UTF8, do
# not rely on sys.getfilesystemencoding() which will be confused,
@@ -1112,7 +1112,7 @@ class Callback(ctypes.c_void_p):
class LogCb(ctypes.c_void_p):
"""Callback prototype for LibVLC log message handler.
@param data: data pointer as given to L{libvlc_log_set}().
@param level: message level (@ref libvlc_log_level).
@param level: message level (@ref L{LogLevel}).
@param ctx: message context (meta-information about the message).
@param fmt: printf() format string (as defined by ISO C11).
@param args: variable argument list for the format @note Log message handlers B{must} be thread-safe. @warning The message context pointer, the format string parameters and the variable arguments are only valid until the callback returns.
@@ -1318,7 +1318,7 @@ class CallbackDecorators(object):
LogCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, Log_ptr, ctypes.c_char_p, ctypes.c_void_p)
LogCb.__doc__ = '''Callback prototype for LibVLC log message handler.
@param data: data pointer as given to L{libvlc_log_set}().
@param level: message level (@ref libvlc_log_level).
@param level: message level (@ref L{LogLevel}).
@param ctx: message context (meta-information about the message).
@param fmt: printf() format string (as defined by ISO C11).
@param args: variable argument list for the format @note Log message handlers B{must} be thread-safe. @warning The message context pointer, the format string parameters and the variable arguments are only valid until the callback returns.
@@ -1745,7 +1745,7 @@ AudioOutputDevice._fields_ = [ # recursive struct
class TitleDescription(_Cstruct):
_fields = [
_fields_ = [
('duration', ctypes.c_longlong),
('name', ctypes.c_char_p),
('menu', ctypes.c_bool),
@@ -1753,7 +1753,7 @@ class TitleDescription(_Cstruct):
class ChapterDescription(_Cstruct):
_fields = [
_fields_ = [
('time_offset', ctypes.c_longlong),
('duration', ctypes.c_longlong),
('name', ctypes.c_char_p),
@@ -1761,7 +1761,7 @@ class ChapterDescription(_Cstruct):
class VideoViewpoint(_Cstruct):
_fields = [
_fields_ = [
('yaw', ctypes.c_float),
('pitch', ctypes.c_float),
('roll', ctypes.c_float),
@@ -1769,11 +1769,22 @@ class VideoViewpoint(_Cstruct):
]
class MediaDiscovererDescription(_Cstruct):
_fields_ = [
('name', ctypes.c_char_p),
('longname', ctypes.c_char_p),
('cat', MediaDiscovererCategory),
]
def __str__(self):
return '%s %s (%d) - %s' % (self.__class__.__name__, self.name, self.cat, self.longname)
# This struct depends on the MediaSlaveType enum that is defined only
# in > 2.2
if 'MediaSlaveType' in locals():
class MediaSlave(_Cstruct):
_fields = [
_fields_ = [
('psz_uri', ctypes.c_char_p),
('i_type', MediaSlaveType),
('i_priority', ctypes.c_uint)
@@ -1781,7 +1792,7 @@ if 'MediaSlaveType' in locals():
class RDDescription(_Cstruct):
_fields = [
_fields_ = [
('name', ctypes.c_char_p),
('longname', ctypes.c_char_p)
]
@@ -2612,7 +2623,7 @@ class Media(_Ctype):
return libvlc_media_add_option_flag(self, str_to_bytes(psz_options), i_flags)
def retain(self):
'''Retain a reference to a media descriptor object (libvlc_media_t). Use
'''Retain a reference to a media descriptor object (L{Media}). Use
L{release}() to decrement the reference count of a
media descriptor object.
'''
@@ -2667,7 +2678,7 @@ class Media(_Ctype):
'''Get current state of media descriptor object. Possible media states are
libvlc_NothingSpecial=0, libvlc_Opening, libvlc_Playing, libvlc_Paused,
libvlc_Stopped, libvlc_Ended, libvlc_Error.
See libvlc_state_t.
See L{State}.
@return: state of media descriptor object.
'''
return libvlc_media_get_state(self)
@@ -2708,7 +2719,7 @@ class Media(_Ctype):
To track when this is over you can listen to libvlc_MediaParsedChanged
event. However if this functions returns an error, you will not receive any
events.
It uses a flag to specify parse options (see libvlc_media_parse_flag_t). All
It uses a flag to specify parse options (see L{MediaParseFlag}). All
these flags can be combined. By default, media is parsed if it's a local
file.
@note: Parsing can be aborted with L{parse_stop}().
@@ -2716,7 +2727,7 @@ class Media(_Ctype):
See L{get_meta}
See L{tracks_get}
See L{get_parsed_status}
See libvlc_media_parse_flag_t.
See L{MediaParseFlag}.
@param parse_flag: parse options:
@param timeout: maximum time allowed to preparse the media. If -1, the default "preparse-timeout" option will be used as a timeout. If 0, it will wait indefinitely. If > 0, the timeout will be used (in milliseconds).
@return: -1 in case of error, 0 otherwise.
@@ -2736,8 +2747,8 @@ class Media(_Ctype):
def get_parsed_status(self):
'''Get Parsed status for media descriptor object.
See libvlc_MediaParsedChanged
See libvlc_media_parsed_status_t.
@return: a value of the libvlc_media_parsed_status_t enum.
See L{MediaParsedStatus}.
@return: a value of the L{MediaParsedStatus} enum.
@version: LibVLC 3.0.0 or later.
'''
return libvlc_media_get_parsed_status(self)
@@ -2760,7 +2771,7 @@ class Media(_Ctype):
def get_type(self):
'''Get the media type of the media descriptor object.
@return: media type.
@version: LibVLC 3.0.0 and later. See libvlc_media_type_t.
@version: LibVLC 3.0.0 and later. See L{MediaType}.
'''
return libvlc_media_get_type(self)
@@ -3179,7 +3190,7 @@ class MediaListPlayer(_Ctype):
def get_state(self):
'''Get current libvlc_state of media list player.
@return: libvlc_state_t for media list player.
@return: L{State} for media list player.
'''
return libvlc_media_list_player_get_state(self)
@@ -3306,8 +3317,14 @@ class MediaPlayer(_Ctype):
'''
titleDescription_pp = ctypes.POINTER(TitleDescription)()
n = libvlc_media_player_get_full_title_descriptions(self, ctypes.byref(titleDescription_pp))
info = ctypes.cast(ctypes.titleDescription_pp, ctypes.POINTER(ctypes.POINTER(TitleDescription) * n))
return info
info = ctypes.cast(titleDescription_pp, ctypes.POINTER(ctypes.POINTER(TitleDescription) * n))
try:
contents = info.contents
except ValueError:
# Media not parsed, no info.
return None
descr = (contents[i].contents for i in range(len(contents)))
return descr
def get_full_chapter_descriptions(self, i_chapters_of_title):
'''Get the full description of available chapters.
@@ -3317,8 +3334,14 @@ class MediaPlayer(_Ctype):
'''
chapterDescription_pp = ctypes.POINTER(ChapterDescription)()
n = libvlc_media_player_get_full_chapter_descriptions(self, ctypes.byref(chapterDescription_pp))
info = ctypes.cast(ctypes.chapterDescription_pp, ctypes.POINTER(ctypes.POINTER(ChapterDescription) * n))
return info
info = ctypes.cast(chapterDescription_pp, ctypes.POINTER(ctypes.POINTER(ChapterDescription) * n))
try:
contents = info.contents
except ValueError:
# Media not parsed, no info.
return None
descr = (contents[i].contents for i in range(len(contents)))
return descr
def video_get_size(self, num=0):
"""Get the video size in pixels as 2-tuple (width, height).
@@ -3788,7 +3811,7 @@ class MediaPlayer(_Ctype):
def get_state(self):
'''Get current movie state.
@return: the current state of the media player (playing, paused, ...) See libvlc_state_t.
@return: the current state of the media player (playing, paused, ...) See L{State}.
'''
return libvlc_media_player_get_state(self)
@@ -3997,7 +4020,7 @@ class MediaPlayer(_Ctype):
def video_set_teletext(self, i_page):
'''Set new teletext page to retrieve.
This function can also be used to send a teletext key.
@param i_page: teletex page number requested. This value can be 0 to disable teletext, a number in the range ]0;1000[ to show the requested page, or a \ref libvlc_teletext_key_t. 100 is the default teletext page.
@param i_page: teletex page number requested. This value can be 0 to disable teletext, a number in the range ]0;1000[ to show the requested page, or a \ref L{TeletextKey}. 100 is the default teletext page.
'''
return libvlc_video_set_teletext(self, i_page)
@@ -4068,7 +4091,7 @@ class MediaPlayer(_Ctype):
def video_get_logo_int(self, option):
'''Get integer logo option.
@param option: logo option to get, values of libvlc_video_logo_option_t.
@param option: logo option to get, values of L{VideoLogoOption}.
'''
return libvlc_video_get_logo_int(self, option)
@@ -4077,7 +4100,7 @@ class MediaPlayer(_Ctype):
are ignored.
Passing libvlc_logo_enable as option value has the side effect of
starting (arg !0) or stopping (arg 0) the logo filter.
@param option: logo option to set, values of libvlc_video_logo_option_t.
@param option: logo option to set, values of L{VideoLogoOption}.
@param value: logo option value.
'''
return libvlc_video_set_logo_int(self, option, value)
@@ -4085,14 +4108,14 @@ class MediaPlayer(_Ctype):
def video_set_logo_string(self, option, psz_value):
'''Set logo option as string. Options that take a different type value
are ignored.
@param option: logo option to set, values of libvlc_video_logo_option_t.
@param option: logo option to set, values of L{VideoLogoOption}.
@param psz_value: logo option value.
'''
return libvlc_video_set_logo_string(self, option, str_to_bytes(psz_value))
def video_get_adjust_int(self, option):
'''Get integer adjust option.
@param option: adjust option to get, values of libvlc_video_adjust_option_t.
@param option: adjust option to get, values of L{VideoAdjustOption}.
@version: LibVLC 1.1.1 and later.
'''
return libvlc_video_get_adjust_int(self, option)
@@ -4102,7 +4125,7 @@ class MediaPlayer(_Ctype):
are ignored.
Passing libvlc_adjust_enable as option value has the side effect of
starting (arg !0) or stopping (arg 0) the adjust filter.
@param option: adust option to set, values of libvlc_video_adjust_option_t.
@param option: adust option to set, values of L{VideoAdjustOption}.
@param value: adjust option value.
@version: LibVLC 1.1.1 and later.
'''
@@ -4110,7 +4133,7 @@ class MediaPlayer(_Ctype):
def video_get_adjust_float(self, option):
'''Get float adjust option.
@param option: adjust option to get, values of libvlc_video_adjust_option_t.
@param option: adjust option to get, values of L{VideoAdjustOption}.
@version: LibVLC 1.1.1 and later.
'''
return libvlc_video_get_adjust_float(self, option)
@@ -4118,7 +4141,7 @@ class MediaPlayer(_Ctype):
def video_set_adjust_float(self, option, value):
'''Set adjust option as float. Options that take a different type value
are ignored.
@param option: adust option to set, values of libvlc_video_adjust_option_t.
@param option: adust option to set, values of L{VideoAdjustOption}.
@param value: adjust option value.
@version: LibVLC 1.1.1 and later.
'''
@@ -4242,13 +4265,13 @@ class MediaPlayer(_Ctype):
def audio_get_channel(self):
'''Get current audio channel.
@return: the audio channel See libvlc_audio_output_channel_t.
@return: the audio channel See L{AudioOutputChannel}.
'''
return libvlc_audio_get_channel(self)
def audio_set_channel(self, channel):
'''Set current audio channel.
@param channel: the audio channel, See libvlc_audio_output_channel_t.
@param channel: the audio channel, See L{AudioOutputChannel}.
@return: 0 on success, -1 on error.
'''
return libvlc_audio_set_channel(self, channel)
@@ -5340,7 +5363,7 @@ def libvlc_media_add_option_flag(p_md, psz_options, i_flags):
def libvlc_media_retain(p_md):
'''Retain a reference to a media descriptor object (libvlc_media_t). Use
'''Retain a reference to a media descriptor object (L{Media}). Use
L{libvlc_media_release}() to decrement the reference count of a
media descriptor object.
@param p_md: the media descriptor.
@@ -5430,7 +5453,7 @@ def libvlc_media_get_state(p_md):
'''Get current state of media descriptor object. Possible media states are
libvlc_NothingSpecial=0, libvlc_Opening, libvlc_Playing, libvlc_Paused,
libvlc_Stopped, libvlc_Ended, libvlc_Error.
See libvlc_state_t.
See L{State}.
@param p_md: a media descriptor object.
@return: state of media descriptor object.
'''
@@ -5495,7 +5518,7 @@ def libvlc_media_parse_with_options(p_md, parse_flag, timeout):
To track when this is over you can listen to libvlc_MediaParsedChanged
event. However if this functions returns an error, you will not receive any
events.
It uses a flag to specify parse options (see libvlc_media_parse_flag_t). All
It uses a flag to specify parse options (see L{MediaParseFlag}). All
these flags can be combined. By default, media is parsed if it's a local
file.
@note: Parsing can be aborted with L{libvlc_media_parse_stop}().
@@ -5503,7 +5526,7 @@ def libvlc_media_parse_with_options(p_md, parse_flag, timeout):
See L{libvlc_media_get_meta}
See L{libvlc_media_tracks_get}
See L{libvlc_media_get_parsed_status}
See libvlc_media_parse_flag_t.
See L{MediaParseFlag}.
@param p_md: media descriptor object.
@param parse_flag: parse options:
@param timeout: maximum time allowed to preparse the media. If -1, the default "preparse-timeout" option will be used as a timeout. If 0, it will wait indefinitely. If > 0, the timeout will be used (in milliseconds).
@@ -5533,9 +5556,9 @@ def libvlc_media_parse_stop(p_md):
def libvlc_media_get_parsed_status(p_md):
'''Get Parsed status for media descriptor object.
See libvlc_MediaParsedChanged
See libvlc_media_parsed_status_t.
See L{MediaParsedStatus}.
@param p_md: media descriptor object.
@return: a value of the libvlc_media_parsed_status_t enum.
@return: a value of the L{MediaParsedStatus} enum.
@version: LibVLC 3.0.0 or later.
'''
f = _Cfunctions.get('libvlc_media_get_parsed_status', None) or \
@@ -5614,7 +5637,7 @@ def libvlc_media_get_type(p_md):
'''Get the media type of the media descriptor object.
@param p_md: media descriptor object.
@return: media type.
@version: LibVLC 3.0.0 and later. See libvlc_media_type_t.
@version: LibVLC 3.0.0 and later. See L{MediaType}.
'''
f = _Cfunctions.get('libvlc_media_get_type', None) or \
_Cfunction('libvlc_media_get_type', ((1,),), None,
@@ -7045,7 +7068,7 @@ def libvlc_media_player_set_rate(p_mi, rate):
def libvlc_media_player_get_state(p_mi):
'''Get current movie state.
@param p_mi: the Media Player.
@return: the current state of the media player (playing, paused, ...) See libvlc_state_t.
@return: the current state of the media player (playing, paused, ...) See L{State}.
'''
f = _Cfunctions.get('libvlc_media_player_get_state', None) or \
_Cfunction('libvlc_media_player_get_state', ((1,),), None,
@@ -7324,7 +7347,7 @@ def libvlc_video_new_viewpoint():
'''
f = _Cfunctions.get('libvlc_video_new_viewpoint', None) or \
_Cfunction('libvlc_video_new_viewpoint', (), None,
VideoViewpoint)
ctypes.POINTER(VideoViewpoint))
return f()
@@ -7339,7 +7362,7 @@ def libvlc_video_update_viewpoint(p_mi, p_viewpoint, b_absolute):
'''
f = _Cfunctions.get('libvlc_video_update_viewpoint', None) or \
_Cfunction('libvlc_video_update_viewpoint', ((1,), (1,), (1,),), None,
ctypes.c_int, MediaPlayer, VideoViewpoint, ctypes.c_bool)
ctypes.c_int, MediaPlayer, ctypes.POINTER(VideoViewpoint), ctypes.c_bool)
return f(p_mi, p_viewpoint, b_absolute)
@@ -7507,7 +7530,7 @@ def libvlc_video_set_teletext(p_mi, i_page):
'''Set new teletext page to retrieve.
This function can also be used to send a teletext key.
@param p_mi: the media player.
@param i_page: teletex page number requested. This value can be 0 to disable teletext, a number in the range ]0;1000[ to show the requested page, or a \ref libvlc_teletext_key_t. 100 is the default teletext page.
@param i_page: teletex page number requested. This value can be 0 to disable teletext, a number in the range ]0;1000[ to show the requested page, or a \ref L{TeletextKey}. 100 is the default teletext page.
'''
f = _Cfunctions.get('libvlc_video_set_teletext', None) or \
_Cfunction('libvlc_video_set_teletext', ((1,), (1,),), None,
@@ -7639,7 +7662,7 @@ def libvlc_video_set_marquee_string(p_mi, option, psz_text):
def libvlc_video_get_logo_int(p_mi, option):
'''Get integer logo option.
@param p_mi: libvlc media player instance.
@param option: logo option to get, values of libvlc_video_logo_option_t.
@param option: logo option to get, values of L{VideoLogoOption}.
'''
f = _Cfunctions.get('libvlc_video_get_logo_int', None) or \
_Cfunction('libvlc_video_get_logo_int', ((1,), (1,),), None,
@@ -7653,7 +7676,7 @@ def libvlc_video_set_logo_int(p_mi, option, value):
Passing libvlc_logo_enable as option value has the side effect of
starting (arg !0) or stopping (arg 0) the logo filter.
@param p_mi: libvlc media player instance.
@param option: logo option to set, values of libvlc_video_logo_option_t.
@param option: logo option to set, values of L{VideoLogoOption}.
@param value: logo option value.
'''
f = _Cfunctions.get('libvlc_video_set_logo_int', None) or \
@@ -7666,7 +7689,7 @@ def libvlc_video_set_logo_string(p_mi, option, psz_value):
'''Set logo option as string. Options that take a different type value
are ignored.
@param p_mi: libvlc media player instance.
@param option: logo option to set, values of libvlc_video_logo_option_t.
@param option: logo option to set, values of L{VideoLogoOption}.
@param psz_value: logo option value.
'''
f = _Cfunctions.get('libvlc_video_set_logo_string', None) or \
@@ -7678,7 +7701,7 @@ def libvlc_video_set_logo_string(p_mi, option, psz_value):
def libvlc_video_get_adjust_int(p_mi, option):
'''Get integer adjust option.
@param p_mi: libvlc media player instance.
@param option: adjust option to get, values of libvlc_video_adjust_option_t.
@param option: adjust option to get, values of L{VideoAdjustOption}.
@version: LibVLC 1.1.1 and later.
'''
f = _Cfunctions.get('libvlc_video_get_adjust_int', None) or \
@@ -7693,7 +7716,7 @@ def libvlc_video_set_adjust_int(p_mi, option, value):
Passing libvlc_adjust_enable as option value has the side effect of
starting (arg !0) or stopping (arg 0) the adjust filter.
@param p_mi: libvlc media player instance.
@param option: adust option to set, values of libvlc_video_adjust_option_t.
@param option: adust option to set, values of L{VideoAdjustOption}.
@param value: adjust option value.
@version: LibVLC 1.1.1 and later.
'''
@@ -7706,7 +7729,7 @@ def libvlc_video_set_adjust_int(p_mi, option, value):
def libvlc_video_get_adjust_float(p_mi, option):
'''Get float adjust option.
@param p_mi: libvlc media player instance.
@param option: adjust option to get, values of libvlc_video_adjust_option_t.
@param option: adjust option to get, values of L{VideoAdjustOption}.
@version: LibVLC 1.1.1 and later.
'''
f = _Cfunctions.get('libvlc_video_get_adjust_float', None) or \
@@ -7719,7 +7742,7 @@ def libvlc_video_set_adjust_float(p_mi, option, value):
'''Set adjust option as float. Options that take a different type value
are ignored.
@param p_mi: libvlc media player instance.
@param option: adust option to set, values of libvlc_video_adjust_option_t.
@param option: adust option to set, values of L{VideoAdjustOption}.
@param value: adjust option value.
@version: LibVLC 1.1.1 and later.
'''
@@ -7972,7 +7995,7 @@ def libvlc_audio_set_track(p_mi, i_track):
def libvlc_audio_get_channel(p_mi):
'''Get current audio channel.
@param p_mi: media player.
@return: the audio channel See libvlc_audio_output_channel_t.
@return: the audio channel See L{AudioOutputChannel}.
'''
f = _Cfunctions.get('libvlc_audio_get_channel', None) or \
_Cfunction('libvlc_audio_get_channel', ((1,),), None,
@@ -7983,7 +8006,7 @@ def libvlc_audio_get_channel(p_mi):
def libvlc_audio_set_channel(p_mi, channel):
'''Set current audio channel.
@param p_mi: media player.
@param channel: the audio channel, See libvlc_audio_output_channel_t.
@param channel: the audio channel, See L{AudioOutputChannel}.
@return: 0 on success, -1 on error.
'''
f = _Cfunctions.get('libvlc_audio_set_channel', None) or \
@@ -8348,7 +8371,7 @@ def libvlc_media_list_player_is_playing(p_mlp):
def libvlc_media_list_player_get_state(p_mlp):
'''Get current libvlc_state of media list player.
@param p_mlp: media list player instance.
@return: libvlc_state_t for media list player.
@return: L{State} for media list player.
'''
f = _Cfunctions.get('libvlc_media_list_player_get_state', None) or \
_Cfunction('libvlc_media_list_player_get_state', ((1,),), None,
@@ -8522,7 +8545,10 @@ def _dot2int(v):
'''
t = [int(i) for i in v.split('.')]
if len(t) == 3:
t.append(0)
if t[2] < 100:
t.append(0)
else: # 100 is arbitrary
t[2:4] = divmod(t[2], 100)
elif len(t) != 4:
raise ValueError('"i.i.i[.i]": %r' % (v,))
if min(t) < 0 or max(t) > 255:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,694 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1
The MIT License (MIT)
Copyright (c) 2018 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Author: Dmitriy Yefremov
-->
<interface>
<requires lib="gtk+" version="3.16"/>
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkWindow" id="download_dialog_window">
<property name="width_request">500</property>
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="icon_name">mail-send-receive</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="gravity">center</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header_bar">
<property name="visible">True</property>
<property name="can_focus">False</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="width_request">48</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">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="width_request">48</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">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 type="title">
<object class="GtkBox" id="header_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="margin_bottom">2</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="header_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">FTP-transfer</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="header_data_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="label10">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="all_radio_button">
<property name="label" translatable="yes">All</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">satellites_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="bouquets_radio_button">
<property name="label" translatable="yes">Bouquets</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">satellites_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="satellites_radio_button">
<property name="label" translatable="yes">Satellites</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">all_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="webtv_radio_button">
<property name="label" translatable="yes">WebTV</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">all_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="options_button">
<property name="width_request">48</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Options</property>
<signal name="clicked" handler="on_preferences" swapped="no"/>
<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>
</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="GtkFrame" id="main_settings_box_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
<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_bottom">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkGrid" id="main_settings_bo">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">2</property>
<property name="column_spacing">2</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel" id="ip_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Receiver IP:</property>
<property name="xalign">0.10000000149011612</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="host_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="max_width_chars">10</property>
<property name="text">127.0.0.1</property>
<property name="caps_lock_warning">False</property>
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="data_path_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Current data path:</property>
<property name="xalign">0.10000000149011612</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="data_path_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="text">data/</property>
<property name="caps_lock_warning">False</property>
<property name="primary_icon_name">folder-open-symbolic</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="extra_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_top">5</property>
<child>
<object class="GtkCheckButton" id="remove_unused_check_button">
<property name="label" translatable="yes">Remove unused bouquets</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="use_http_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="use_http_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Use HTTP</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="use_http_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Use http to reload data in the receiver.</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</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">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="settings_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkGrid" id="settings_grid">
<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">5</property>
<property name="row_spacing">2</property>
<property name="column_spacing">2</property>
<child>
<object class="GtkLabel" id="login_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Login:</property>
<property name="xalign">0.10000000149011612</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="login_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="text">root</property>
<property name="caps_lock_warning">False</property>
<property name="primary_icon_name">avatar-default-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="password_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Password:</property>
<property name="xalign">0.10000000149011612</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="password_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="visibility">False</property>
<property name="invisible_char">●</property>
<property name="text">root</property>
<property name="primary_icon_name">emblem-readonly</property>
<property name="input_purpose">password</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="port_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Port:</property>
<property name="xalign">0.10000000149011612</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="port_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="width_chars">8</property>
<property name="max_width_chars">8</property>
<property name="text" translatable="yes">21</property>
<property name="caps_lock_warning">False</property>
<property name="primary_icon_name">network-workgroup-symbolic</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="timeout_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="width_chars">8</property>
<property name="max_width_chars">8</property>
<property name="caps_lock_warning">False</property>
<property name="primary_icon_name">alarm-symbolic</property>
<property name="input_purpose">digits</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="timeout_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Timeout:</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkBox" id="settings_buttons_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkRadioButton" id="ftp_radio_button">
<property name="label" translatable="yes">FTP</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">False</property>
<property name="group">telnet_radio_button</property>
<signal name="toggled" handler="on_settings_button" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="http_radio_button">
<property name="label" translatable="yes">HTTP</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">False</property>
<property name="group">telnet_radio_button</property>
<signal name="toggled" handler="on_settings_button" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="telnet_radio_button">
<property name="label" translatable="yes">Telnet</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">False</property>
<property name="group">ftp_radio_button</property>
<signal name="toggled" handler="on_settings_button" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</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="margin_left">1</property>
<property name="margin_right">1</property>
<property name="margin_bottom">1</property>
<property name="resize_toplevel">True</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="height_request">120</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="text_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="left_margin">5</property>
<property name="right_margin">5</property>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="expander_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Extra:</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<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="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="homogeneous">True</property>
<property name="layout_style">expand</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkLabel" id="info_bar_message_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Info</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">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">4</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -1,45 +1,68 @@
from gi.repository import GLib
from app.commons import run_idle, run_task
from app.ftp import download_data, DownloadDataType, upload_data
from app.properties import Profile
from app.connections import download_data, DownloadType, upload_data
from app.properties import Profile, get_config
from app.ui.main_helper import append_text_to_tview
from app.ui.settings_dialog import show_settings_dialog
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .dialogs import show_dialog, DialogType, get_message
def show_download_dialog(transient, options, open_data, profile=Profile.ENIGMA_2):
dialog = DownloadDialog(transient, options, open_data, profile)
dialog.run()
dialog.destroy()
class DownloadDialog:
def __init__(self, transient, properties, open_data, profile):
def __init__(self, transient, properties, open_data_callback, profile=Profile.ENIGMA_2):
self._profile_properties = properties.get(profile.value)
self._properties = properties
self._open_data = open_data
self._open_data_callback = open_data_callback
self._profile = profile
handlers = {"on_receive": self.on_receive,
"on_send": self.on_send,
"on_settings_button": self.on_settings_button,
"on_preferences": self.on_preferences,
"on_info_bar_close": self.on_info_bar_close}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", ("download_dialog",))
builder.add_from_file(UI_RESOURCES_PATH + "download_dialog.glade")
builder.connect_signals(handlers)
self._dialog = builder.get_object("download_dialog")
self._dialog.set_transient_for(transient)
self._current_property = "FTP"
self._dialog_window = builder.get_object("download_dialog_window")
self._dialog_window.set_transient_for(transient)
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
self._host_entry = builder.get_object("host_entry").set_text(properties["host"])
self._data_path_entry = builder.get_object("data_path_entry").set_text(properties["data_dir_path"])
self._text_view = builder.get_object("text_view")
self._expander = builder.get_object("expander")
self._host_entry = builder.get_object("host_entry")
self._data_path_entry = builder.get_object("data_path_entry")
self._remove_unused_check_button = builder.get_object("remove_unused_check_button")
self._all_radio_button = builder.get_object("all_radio_button")
self._bouquets_radio_button = builder.get_object("bouquets_radio_button")
self._satellites_radio_button = builder.get_object("satellites_radio_button")
self._webtv_radio_button = builder.get_object("webtv_radio_button")
self._login_entry = builder.get_object("login_entry")
self._password_entry = builder.get_object("password_entry")
self._host_entry = builder.get_object("host_entry")
self._port_entry = builder.get_object("port_entry")
self._timeout_entry = builder.get_object("timeout_entry")
self._settings_buttons_box = builder.get_object("settings_buttons_box")
self._use_http_switch = builder.get_object("use_http_switch")
self.init_properties()
if profile is Profile.NEUTRINO_MP:
self._webtv_radio_button.set_visible(True)
# self._dialog.get_content_area().set_border_width(0)
builder.get_object("http_radio_button").set_visible(False)
builder.get_object("use_http_box").set_visible(False)
self._use_http_switch.set_active(False)
def show(self):
self._dialog_window.show()
def init_properties(self):
self._host_entry.set_text(self._profile_properties["host"])
self._data_path_entry.set_text(self._profile_properties["data_dir_path"])
@run_idle
def on_receive(self, item):
@@ -47,48 +70,79 @@ class DownloadDialog:
@run_idle
def on_send(self, item):
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.CANCEL:
if show_dialog(DialogType.QUESTION, self._dialog_window) != Gtk.ResponseType.CANCEL:
self.download(False, self.get_download_type())
def get_download_type(self):
download_type = DownloadDataType.ALL
download_type = DownloadType.ALL
if self._bouquets_radio_button.get_active():
download_type = DownloadDataType.BOUQUETS
download_type = DownloadType.BOUQUETS
elif self._satellites_radio_button.get_active():
download_type = DownloadDataType.SATELLITES
download_type = DownloadType.SATELLITES
elif self._webtv_radio_button.get_active():
download_type = DownloadDataType.WEBTV
download_type = DownloadType.WEB_TV
return download_type
def run(self):
return self._dialog.run()
def destroy(self):
self._dialog.destroy()
self._dialog_window.destroy()
def on_settings_button(self, button):
if button.get_active():
label = button.get_label()
if label == "Telnet":
self._login_entry.set_text(self._profile_properties.get("telnet_user", ""))
self._password_entry.set_text(self._profile_properties.get("telnet_password", ""))
self._port_entry.set_text(self._profile_properties.get("telnet_port", ""))
self._timeout_entry.set_text(str(self._profile_properties.get("telnet_timeout", 0)))
elif label == "HTTP":
self._login_entry.set_text(self._profile_properties.get("http_user", "root"))
self._password_entry.set_text(self._profile_properties.get("http_password", ""))
self._port_entry.set_text(self._profile_properties.get("http_port", ""))
self._timeout_entry.set_text(str(self._profile_properties.get("http_timeout", 0)))
elif label == "FTP":
self._login_entry.set_text(self._profile_properties.get("user", ""))
self._password_entry.set_text(self._profile_properties.get("password", ""))
self._port_entry.set_text(self._profile_properties.get("port", ""))
self._timeout_entry.set_text("")
self._current_property = label
def on_preferences(self, item):
show_settings_dialog(self._dialog_window, self._properties)
self._profile_properties = get_config().get(self._profile.value)
for button in self._settings_buttons_box.get_children():
if button.get_active():
self.on_settings_button(button)
self.init_properties()
break
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@run_idle
@run_task
def download(self, download, d_type):
""" Download/upload data from/to receiver """
try:
self._expander.set_expanded(True)
self.clear_output()
if download:
download_data(properties=self._properties, download_type=d_type)
download_data(properties=self._profile_properties, download_type=d_type, callback=self.append_output)
else:
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
upload_data(properties=self._properties,
upload_data(properties=self._profile_properties,
download_type=d_type,
remove_unused=self._remove_unused_check_button.get_active(),
profile=self._profile,
callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
callback=self.append_output,
done_callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO),
use_http=self._use_http_switch.get_active())
except Exception as e:
message = str(getattr(e, "message", str(e)))
self.show_info_message(message, Gtk.MessageType.ERROR)
else:
if download and d_type is not DownloadDataType.SATELLITES:
self._open_data()
if download and d_type is not DownloadType.SATELLITES:
GLib.idle_add(self._open_data_callback)
@run_idle
def show_info_message(self, text, message_type):
@@ -96,6 +150,14 @@ class DownloadDialog:
self._info_bar.set_message_type(message_type)
self._message_label.set_text(text)
@run_idle
def append_output(self, text):
append_text_to_tview(text, self._text_view)
@run_idle
def clear_output(self):
self._text_view.get_buffer().set_text("")
if __name__ == "__main__":
pass

1111
app/ui/iptv.glade Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,41 @@
import re
from urllib.error import HTTPError
from urllib.parse import urlparse
from urllib.request import Request, urlopen
from app.commons import run_idle, run_task
from app.eparser.ecommons import BqServiceType, Service
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT
from app.properties import Profile
from .uicommons import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON
from .dialogs import Action, show_dialog, DialogType
from .main_helper import get_base_model
from .main_helper import get_base_model, get_iptv_url
_DIGIT_ENTRY_NAME = "digit-entry"
_ENIGMA2_REFERENCE = "{}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
_PATTERN = re.compile("(?:^[\s]*$|\D)")
def is_data_correct(elems):
for elem in elems:
if elem.get_name() == _DIGIT_ENTRY_NAME:
return False
return True
class IptvDialog:
_DIGIT_ENTRY_NAME = "digit-entry"
_ENIGMA2_REFERENCE = "{}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
def __init__(self, transient, view, services, bouquet, profile=Profile.ENIGMA_2, action=Action.ADD):
handlers = {"on_entry_changed": self.on_entry_changed,
handlers = {"on_response": self.on_response,
"on_entry_changed": self.on_entry_changed,
"on_url_changed": self.on_url_changed,
"on_save": self.on_save,
"on_stream_type_changed": self.on_stream_type_changed}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", ("iptv_dialog", "stream_type_liststore"))
builder.add_objects_from_file(UI_RESOURCES_PATH + "iptv.glade", ("iptv_dialog", "stream_type_liststore"))
builder.connect_signals(handlers)
self._dialog = builder.get_object("iptv_dialog")
@@ -44,15 +58,16 @@ class IptvDialog:
self._bouquet = bouquet
self._services = services
self._model, self._paths = view.get_selection().get_selected_rows()
self._PATTERN = re.compile("(?:^[\s]*$|\D)")
# style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
for el in (self._srv_type_entry, self._sid_entry, self._tr_id_entry, self._net_id_entry, self._namespace_entry):
self._digit_elems = (self._srv_type_entry, self._sid_entry, self._tr_id_entry, self._net_id_entry,
self._namespace_entry)
for el in self._digit_elems:
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
if profile is Profile.NEUTRINO_MP:
builder.get_object("iptv_data_box").set_visible(False)
builder.get_object("iptv_dialog_ts_data_frame").set_visible(False)
builder.get_object("iptv_type_label").set_visible(False)
builder.get_object("reference_entry").set_visible(False)
builder.get_object("iptv_reference_label").set_visible(False)
@@ -72,10 +87,14 @@ class IptvDialog:
def show(self):
self._dialog.run()
self._dialog.destroy()
def on_response(self, dialog, response):
if response == Gtk.ResponseType.CANCEL:
self._dialog.destroy()
def on_save(self, item):
if not self.is_data_correct():
self.on_url_changed(self._url_entry)
if not is_data_correct(self._digit_elems) or self._url_entry.get_name() == _DIGIT_ENTRY_NAME:
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return
@@ -112,26 +131,26 @@ class IptvDialog:
def _update_reference_entry(self):
if self._profile is Profile.ENIGMA_2:
self._reference_entry.set_text(self._ENIGMA2_REFERENCE.format(self.get_type(),
self._srv_type_entry.get_text(),
int(self._sid_entry.get_text()),
int(self._tr_id_entry.get_text()),
int(self._net_id_entry.get_text()),
int(self._namespace_entry.get_text())))
self._reference_entry.set_text(_ENIGMA2_REFERENCE.format(self.get_type(),
self._srv_type_entry.get_text(),
int(self._sid_entry.get_text()),
int(self._tr_id_entry.get_text()),
int(self._net_id_entry.get_text()),
int(self._namespace_entry.get_text())))
def get_type(self):
return 1 if self._stream_type_combobox.get_active() == 0 else 4097
def on_entry_changed(self, entry):
if self._PATTERN.search(entry.get_text()):
entry.set_name(self._DIGIT_ENTRY_NAME)
if _PATTERN.search(entry.get_text()):
entry.set_name(_DIGIT_ENTRY_NAME)
else:
entry.set_name("GtkEntry")
self._update_reference_entry()
def on_url_changed(self, entry):
url = urlparse(entry.get_text())
entry.set_name("GtkEntry" if all([url.scheme, url.netloc, url.path]) else self._DIGIT_ENTRY_NAME)
entry.set_name("GtkEntry" if all([url.scheme, url.netloc, url.path]) else _DIGIT_ENTRY_NAME)
def on_stream_type_changed(self, item):
self._update_reference_entry()
@@ -174,12 +193,253 @@ class IptvDialog:
self._bouquet.insert(self._model.get_path(itr)[0], fav_id)
self._services[fav_id] = Service(None, None, IPTV_ICON, name, *aggr[0:3], s_type, *aggr, fav_id, None)
def is_data_correct(self):
for elem in (self._srv_type_entry, self._sid_entry, self._tr_id_entry, self._net_id_entry,
self._namespace_entry, self._url_entry):
if elem.get_name() == self._DIGIT_ENTRY_NAME:
return False
return True
class SearchUnavailableDialog:
def __init__(self, transient, model, fav_bouquet, iptv_rows, profile):
handlers = {"on_response": self.on_response}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "iptv.glade", ("search_unavailable_streams_dialog",))
builder.connect_signals(handlers)
self._dialog = builder.get_object("search_unavailable_streams_dialog")
self._dialog.set_transient_for(transient)
self._model = model
self._counter_label = builder.get_object("streams_rows_counter_label")
self._level_bar = builder.get_object("unavailable_streams_level_bar")
self._bouquet = fav_bouquet
self._profile = profile
self._iptv_rows = iptv_rows
self._counter = -1
self._max_rows = len(self._iptv_rows)
self._level_bar.set_max_value(self._max_rows)
self._download_task = True
self._to_delete = []
self.update_counter()
self.do_search()
@run_task
def do_search(self):
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
futures = {executor.submit(self.get_unavailable, row): row for row in self._iptv_rows}
for future in concurrent.futures.as_completed(futures):
if not self._download_task:
executor.shutdown()
return
future.result()
self._download_task = False
self.on_close()
def get_unavailable(self, row):
if not self._download_task:
return
try:
req = Request(get_iptv_url(row, self._profile))
self.update_bar()
urlopen(req, timeout=2)
except HTTPError as e:
if e.code != 403:
self.append_data(row)
except Exception:
self.append_data(row)
def append_data(self, row):
self._to_delete.append(self._model.get_iter(row.path))
self.update_counter()
@run_idle
def update_bar(self):
self._max_rows -= 1
self._level_bar.set_value(self._max_rows)
@run_idle
def update_counter(self):
self._counter += 1
self._counter_label.set_text(str(self._counter))
def show(self):
response = self._dialog.run()
return self._to_delete if response not in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT) else False
def on_response(self, dialog, response):
if response == Gtk.ResponseType.CANCEL:
self.on_close()
@run_idle
def on_close(self):
if self._download_task and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
self._download_task = False
self._dialog.destroy()
class IptvListConfigurationDialog:
def __init__(self, transient, services, iptv_rows, bouquet, profile):
handlers = {"on_apply": self.on_apply,
"on_response": self.on_response,
"on_stream_type_default_togged": self.on_stream_type_default_togged,
"on_stream_type_changed": self.on_stream_type_changed,
"on_default_type_toggled": self.on_default_type_toggled,
"on_auto_sid_toggled": self.on_auto_sid_toggled,
"on_default_tid_toggled": self.on_default_tid_toggled,
"on_default_nid_toggled": self.on_default_nid_toggled,
"on_default_namespace_toggled": self.on_default_namespace_toggled,
"on_reset_to_default": self.on_reset_to_default,
"on_entry_changed": self.on_entry_changed,
"on_info_bar_close": self.on_info_bar_close}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "iptv.glade",
("iptv_list_configuration_dialog", "stream_type_liststore"))
builder.connect_signals(handlers)
self._rows = iptv_rows
self._services = services
self._bouquet = bouquet
self._profile = profile
self._dialog = builder.get_object("iptv_list_configuration_dialog")
self._dialog.set_transient_for(transient)
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")
self._type_check_button = builder.get_object("type_default_check_button")
self._sid_auto_check_button = builder.get_object("sid_auto_check_button")
self._tid_check_button = builder.get_object("tid_default_check_button")
self._nid_check_button = builder.get_object("nid_default_check_button")
self._namespace_check_button = builder.get_object("namespace_default_check_button")
self._stream_type_combobox = builder.get_object("stream_type_list_combobox")
self._list_srv_type_entry = builder.get_object("list_srv_type_entry")
self._list_sid_entry = builder.get_object("list_sid_entry")
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._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,
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()
def on_stream_type_changed(self, box):
self.update_reference()
def on_stream_type_default_togged(self, button):
if button.get_active():
self._stream_type_combobox.set_active(1)
self._stream_type_combobox.set_sensitive(not button.get_active())
def on_default_type_toggled(self, button):
if button.get_active():
self._list_srv_type_entry.set_text("1")
self._list_srv_type_entry.set_sensitive(not button.get_active())
def on_auto_sid_toggled(self, button):
if button.get_active():
self._list_sid_entry.set_text("0")
self._list_sid_entry.set_sensitive(not button.get_active())
def on_default_tid_toggled(self, button):
if button.get_active():
self._list_tid_entry.set_text("0")
self._list_tid_entry.set_sensitive(not button.get_active())
def on_default_nid_toggled(self, button):
if button.get_active():
self._list_nid_entry.set_text("0")
self._list_nid_entry.set_sensitive(not button.get_active())
def on_default_namespace_toggled(self, button):
if button.get_active():
self._list_namespace_entry.set_text("0")
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)
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):
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):
el.set_active(True)
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@run_idle
def on_apply(self, item):
if not is_data_correct(self._digit_elems):
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return
if len(self._bouquet) != len(self._rows):
return
if self._profile is Profile.ENIGMA_2:
reset = self._reset_to_default_switch.get_active()
type_default = self._type_check_button.get_active()
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()
for index, row in enumerate(self._rows):
fav_id = row[7]
data, sep, desc = fav_id.partition("http")
data = data.split(":")
if reset:
data[0] = " 4097"
data[2], data[3], data[4], data[5], data[6] = "10000"
else:
data[0] = " 4097" if self._stream_type_combobox.get_active() == 1 else "1"
data[2] = "1" if type_default else self._list_srv_type_entry.get_text()
data[3] = "{:X}".format(index) if sid_auto else "0"
data[4] = "0" if tid_default else "{:X}".format(int(self._list_tid_entry.get_text()))
data[5] = "0" if nid_default else "{:X}".format(int(self._list_nid_entry.get_text()))
data[6] = "0" if namespace_default else "{:X}".format(int(self._list_namespace_entry.get_text()))
data = ":".join(data)
new_fav_id = "{}{}{}".format(data, sep, desc)
row[7] = new_fav_id
self._bouquet[index] = new_fav_id
srv = self._services.pop(fav_id, None)
self._services[new_fav_id] = srv._replace(fav_id=new_fav_id)
self._info_bar.set_visible(True)
@run_idle
def update_reference(self):
if is_data_correct(self._digit_elems):
stream_type = "4097" if self._stream_type_combobox.get_active() == 1 else "1"
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()
if __name__ == "__main__":

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,14 @@
""" This is helper module for ui """
import os
import shutil
from gi.repository import GdkPixbuf
from gi.repository import GdkPixbuf, GLib
from app.commons import run_task
from app.eparser import Service
from app.eparser.ecommons import Flag, BouquetService, Bouquet, BqType
from app.eparser.enigma.bouquets import BqServiceType, to_bouquet_id
from app.properties import Profile
from .uicommons import ViewTarget, BqGenType, Gtk, Gdk, HIDE_ICON, LOCKED_ICON
from .uicommons import ViewTarget, BqGenType, Gtk, Gdk, HIDE_ICON, LOCKED_ICON, KeyboardKey
from .dialogs import show_dialog, DialogType, get_chooser_dialog, WaitDialog
@@ -37,25 +37,6 @@ def insert_marker(view, bouquets, selected_bouquet, channels, parent_window):
channels[fav_id] = Service(None, None, None, response, None, None, None, s_type, *[None] * 9, max_num, fav_id, None)
def edit_marker(view, bouquets, selected_bouquet, channels, parent_window):
""" Edits marker text """
model, paths = view.get_selection().get_selected_rows()
itr = model.get_iter(paths[0])
name, fav_id = model.get(itr, 2, 7)
response = show_dialog(DialogType.INPUT, parent_window, text=name)
if response == Gtk.ResponseType.CANCEL:
return
bq_services = bouquets[selected_bouquet]
index = bq_services.index(fav_id)
old_ch = channels.pop(fav_id, None)
new_fav_id = "{}::{}\n#DESCRIPTION {}\n".format(fav_id.split("::")[0], response, response)
model.set(itr, {2: response, 7: new_fav_id})
channels[new_fav_id] = old_ch._replace(service=response, fav_id=new_fav_id)
bq_services.pop(index)
bq_services.insert(index, new_fav_id)
# ***************** Movement *******************#
def move_items(key, view: Gtk.TreeView):
@@ -65,6 +46,8 @@ def move_items(key, view: Gtk.TreeView):
if paths:
mod_length = len(model)
if mod_length == len(paths):
return
cursor_path = view.get_cursor()[0]
max_path = Gtk.TreePath.new_from_indices((mod_length,))
min_path = Gtk.TreePath.new_from_indices((0,))
@@ -84,17 +67,17 @@ def move_items(key, view: Gtk.TreeView):
parent_itr = model.iter_parent(model.get_iter(paths[0]))
parent_index = model.get_path(parent_itr)
children_num = model.iter_n_children(parent_itr)
if key in (Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down, Gdk.KEY_End):
if key in (KeyboardKey.PAGE_DOWN, KeyboardKey.END, KeyboardKey.END_KP, KeyboardKey.PAGE_DOWN_KP):
children_num -= 1
min_path = Gtk.TreePath.new_from_string("{}:{}".format(parent_index, 0))
max_path = Gtk.TreePath.new_from_string("{}:{}".format(parent_index, children_num))
is_tree_store = True
if key == Gdk.KEY_Up:
if key is KeyboardKey.UP:
top_path = Gtk.TreePath(paths[0])
top_path.prev()
move_up(top_path, model, paths)
elif key == Gdk.KEY_Down:
elif key is KeyboardKey.DOWN:
down_path = Gtk.TreePath(paths[-1])
down_path.next()
if down_path < max_path:
@@ -102,9 +85,9 @@ def move_items(key, view: Gtk.TreeView):
else:
max_path.prev()
move_down(max_path, model, paths)
elif key in (Gdk.KEY_Page_Up, Gdk.KEY_KP_Page_Up, Gdk.KEY_Home):
elif key in (KeyboardKey.PAGE_UP, KeyboardKey.HOME, KeyboardKey.PAGE_UP_KP, KeyboardKey.HOME_KP):
move_up(min_path if is_tree_store else cursor_path, model, paths)
elif key in (Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down, Gdk.KEY_End):
elif key in (KeyboardKey.PAGE_DOWN, KeyboardKey.END, KeyboardKey.END_KP, KeyboardKey.PAGE_DOWN_KP):
move_down(max_path if is_tree_store else cursor_path, model, paths)
@@ -137,19 +120,14 @@ def is_some_level(paths):
# ***************** Rename *******************#
def rename(view, parent_window, target, fav_view=None, service_view=None, channels=None):
model, paths = view.get_selection().get_selected_rows()
model = get_base_model(model)
if not paths:
return
elif len(paths) > 1:
show_dialog(DialogType.ERROR, parent_window, "Please, select only one item!")
def rename(view, parent_window, target, fav_view=None, service_view=None, services=None):
selection = get_selection(view, parent_window)
if not selection:
return
model, paths = selection
itr = model.get_iter(paths)
f_id = None
channel_name = None
f_id, srv_name, srv_type = None, None, None
if target is ViewTarget.SERVICES:
name, fav_id = model.get(itr, 3, 18)
@@ -157,7 +135,7 @@ def rename(view, parent_window, target, fav_view=None, service_view=None, channe
response = show_dialog(DialogType.INPUT, parent_window, name)
if response == Gtk.ResponseType.CANCEL:
return
channel_name = response
srv_name = response
model.set_value(itr, 3, response)
if fav_view is not None:
for row in fav_view.get_model():
@@ -165,13 +143,13 @@ def rename(view, parent_window, target, fav_view=None, service_view=None, channe
row[2] = response
break
elif target is ViewTarget.FAV:
name, fav_id = model.get(itr, 2, 7)
name, srv_type, fav_id = model.get(itr, 2, 5, 7)
f_id = fav_id
response = show_dialog(DialogType.INPUT, parent_window, name)
if response == Gtk.ResponseType.CANCEL:
return
channel_name = response
srv_name = response
model.set_value(itr, 2, response)
if service_view is not None:
@@ -180,14 +158,35 @@ def rename(view, parent_window, target, fav_view=None, service_view=None, channe
row[3] = response
break
old_ch = channels.get(f_id, None)
if old_ch:
channels[f_id] = old_ch._replace(service=channel_name)
old_srv = services.get(f_id, None)
if old_srv:
if srv_type == BqServiceType.IPTV.name or srv_type == BqServiceType.MARKER.name:
l, sep, r = f_id.partition("#DESCRIPTION")
old_name = old_srv.service.strip()
new_name = srv_name.strip()
new_fav_id = "".join((new_name.join(l.rsplit(old_name, 1)), sep, new_name.join(r.rsplit(old_name, 1))))
services[f_id] = old_srv._replace(service=srv_name, fav_id=new_fav_id)
else:
services[f_id] = old_srv._replace(service=srv_name)
def get_selection(view, parent):
""" Returns (model, paths) if possible """
model, paths = view.get_selection().get_selected_rows()
model = get_base_model(model)
if not paths:
return
elif len(paths) > 1:
show_dialog(DialogType.ERROR, parent, "Please, select only one item!")
return
return model, paths
# ***************** Flags *******************#
def set_flags(flag, services_view, fav_view, channels, blacklist):
def set_flags(flag, services_view, fav_view, services, blacklist):
""" Updates flags for services. Returns True if any was changed. """
target = ViewTarget.SERVICES if services_view.is_focus() else ViewTarget.FAV if fav_view.is_focus() else None
if not target:
@@ -207,19 +206,26 @@ def set_flags(flag, services_view, fav_view, channels, blacklist):
if flag is Flag.HIDE:
if target is ViewTarget.SERVICES:
set_hide(channels, model, paths)
set_hide(services, model, paths)
else:
fav_ids = [model.get_value(model.get_iter(path), 7) for path in paths]
srv_model = get_base_model(services_view.get_model())
srv_paths = [row.path for row in srv_model if row[18] in fav_ids]
set_hide(channels, srv_model, srv_paths)
set_hide(services, srv_model, srv_paths)
elif flag is Flag.LOCK:
set_lock(blacklist, channels, model, paths, target, services_model=get_base_model(services_view.get_model()))
set_lock(blacklist, services, model, paths, target, services_model=get_base_model(services_view.get_model()))
return True
update_fav_model(fav_view, services)
def set_lock(blacklist, channels, model, paths, target, services_model):
def update_fav_model(fav_view, services):
for row in get_base_model(fav_view.get_model()):
srv = services.get(row[7], None)
if srv:
row[3], row[4] = srv.locked, srv.hide
def set_lock(blacklist, services, model, paths, target, services_model):
col_num = 4 if target is ViewTarget.SERVICES else 3
locked = has_locked_hide(model, paths, col_num)
@@ -228,23 +234,29 @@ def set_lock(blacklist, channels, model, paths, target, services_model):
for path in paths:
itr = model.get_iter(path)
fav_id = model.get_value(itr, 18 if target is ViewTarget.SERVICES else 7)
channel = channels.get(fav_id, None)
if channel:
bq_id = to_bouquet_id(channel)
srv = services.get(fav_id, None)
if srv:
bq_id = to_bouquet_id(srv)
if not bq_id:
continue
blacklist.discard(bq_id) if locked else blacklist.add(bq_id)
model.set_value(itr, col_num, None if locked else LOCKED_ICON)
channels[fav_id] = channel._replace(locked=None if locked else LOCKED_ICON)
services[fav_id] = srv._replace(locked=None if locked else LOCKED_ICON)
ids.append(fav_id)
if target is ViewTarget.FAV and ids:
for ch in services_model:
if ch[18] in ids:
ch[4] = None if locked else LOCKED_ICON
gen = update_services_model(ids, locked, services_model)
GLib.idle_add(lambda: next(gen, False))
def set_hide(channels, model, paths):
def update_services_model(ids, locked, services_model):
for srv in services_model:
if srv[18] in ids:
srv[4] = None if locked else LOCKED_ICON
yield True
def set_hide(services, model, paths):
col_num = 5
hide = has_locked_hide(model, paths, col_num)
@@ -281,9 +293,9 @@ def set_hide(channels, model, paths):
model.set_value(itr, 0, (",".join(reversed(sorted(flags)))))
fav_id = model.get_value(itr, 18)
channel = channels.get(fav_id, None)
if channel:
channels[fav_id] = channel._replace(hide=None if hide else HIDE_ICON)
srv = services.get(fav_id, None)
if srv:
services[fav_id] = srv._replace(hide=None if hide else HIDE_ICON)
def has_locked_hide(model, paths, col_num):
@@ -324,15 +336,22 @@ def scroll_to(index, view, paths=None):
# ***************** Picons *********************#
def update_picons(path, picons, model):
def update_picons_data(path, picons):
if not os.path.exists(path):
return
for file in os.listdir(path):
picons[file] = get_picon_pixbuf(path + file)
for r in model:
model.set_value(model.get_iter(r.path), 8, picons.get(r[9], None))
def append_picons(picons, model):
def append_picons_data(pcs, mod):
for r in mod:
mod.set_value(mod.get_iter(r.path), 8, pcs.get(r[9], None))
yield True
app = append_picons_data(picons, model)
GLib.idle_add(lambda: next(app, False), priority=GLib.PRIORITY_LOW)
def assign_picon(target, srv_view, fav_view, transient, picons, options, services):
@@ -391,13 +410,17 @@ def remove_picon(target, srv_view, fav_view, picons, options):
fav_ids.append(model.get_value(itr, 18))
picon_ids.append(model.get_value(itr, 9))
else:
fav_ids.append(model.get_value(itr, 7))
srv_type, fav_id = model.get(itr, 5, 7)
if srv_type == BqServiceType.IPTV.name:
picon_ids.append("{}_{}_{}_{}_{}_{}_{}_{}_{}_{}.png".format(*fav_id.split(":")[0:10]).strip())
else:
fav_ids.append(fav_id)
def remove(md, path, itr):
if md.get_value(itr, 7 if target is ViewTarget.SERVICES else 18) in fav_ids:
md.set_value(itr, picon_pos, None)
def remove(md, path, it):
if md.get_value(it, 7 if target is ViewTarget.SERVICES else 18) in fav_ids:
md.set_value(it, picon_pos, None)
if target is ViewTarget.FAV:
picon_ids.append(md.get_value(itr, 9))
picon_ids.append(md.get_value(it, 9))
fav_view.get_model().foreach(remove) if target is ViewTarget.SERVICES else get_base_model(
srv_view.get_model()).foreach(remove)
@@ -458,7 +481,6 @@ def gen_bouquets(view, bq_view, transient, gen_type, tv_types, profile, callback
index = 6 if gen_type in (BqGenType.PACKAGE, BqGenType.EACH_PACKAGE) else 16 if gen_type in (
BqGenType.SAT, BqGenType.EACH_SAT) else 7
model, paths = view.get_selection().get_selected_rows()
model = get_base_model(model)
bq_type = BqType.BOUQUET.value if profile is Profile.NEUTRINO_MP else BqType.TV.value
if gen_type in (BqGenType.SAT, BqGenType.PACKAGE, BqGenType.TYPE):
if not is_only_one_item_selected(paths, transient):
@@ -525,6 +547,13 @@ def get_base_model(model):
return model
def get_model_data(view):
""" Returns model name and base model from the given view """
model = get_base_model(view.get_model())
model_name = model.get_name()
return model_name, model
def append_text_to_tview(char, view):
""" Appending text and scrolling to a given line in the text view. """
buf = view.get_buffer()
@@ -533,5 +562,21 @@ def append_text_to_tview(char, view):
view.scroll_to_mark(insert, 0.0, True, 0.0, 1.0)
def get_iptv_url(row, profile):
""" Returns url from iptv type row """
data = row[7].split(":" if profile is Profile.ENIGMA_2 else "::")
if profile is Profile.ENIGMA_2:
data = list(filter(lambda x: "http" in x, data))
if data:
url = data[0]
return url.replace("%3a", ":") if profile is Profile.ENIGMA_2 else url
def on_popup_menu(menu, event):
""" Shows popup menu for the view """
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_SECONDARY:
menu.popup(None, None, None, None, event.button, event.time)
if __name__ == "__main__":
pass

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,6 @@
import os
import re
import shutil
import subprocess
import tempfile
import time
@@ -6,20 +8,23 @@ import time
from gi.repository import GLib, GdkPixbuf
from app.commons import run_idle, run_task
from app.ftp import upload_data, DownloadDataType
from app.connections import upload_data, DownloadType
from app.tools.picons import PiconsParser, parse_providers, Provider, convert_to
from app.properties import Profile
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN
from app.tools.satellites import SatellitesParser, SatelliteSource
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, TV_ICON
from .dialogs import show_dialog, DialogType, get_message
from .main_helper import update_entry_data, append_text_to_tview
from .main_helper import update_entry_data, append_text_to_tview, scroll_to, on_popup_menu
class PiconsDialog:
def __init__(self, transient, options, picon_ids, profile=Profile.ENIGMA_2):
def __init__(self, transient, options, picon_ids, sat_positions, profile=Profile.ENIGMA_2):
self._picon_ids = picon_ids
self._sat_positions = sat_positions
self._TMP_DIR = tempfile.gettempdir() + "/"
self._BASE_URL = "www.lyngsat.com/packages/"
self._PATTERN = re.compile("^https://www\.lyngsat\.com/[\w-]+\.html$")
self._POS_PATTERN = re.compile("^\d+\.\d+[EW]?$")
self._current_process = None
self._terminate = False
@@ -34,16 +39,22 @@ class PiconsDialog:
"on_url_changed": self.on_url_changed,
"on_position_edited": self.on_position_edited,
"on_notebook_switch_page": self.on_notebook_switch_page,
"on_convert": self.on_convert}
"on_convert": self.on_convert,
"on_satellites_view_realize": self.on_satellites_view_realize,
"on_satellite_selection": self.on_satellite_selection,
"on_select_all": self.on_select_all,
"on_unselect_all": self.on_unselect_all,
"on_popup_menu": on_popup_menu}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "picons_dialog.glade",
("picons_dialog", "receive_image", "providers_list_store"))
builder.add_from_file(UI_RESOURCES_PATH + "picons_dialog.glade")
builder.connect_signals(handlers)
self._dialog = builder.get_object("picons_dialog")
self._dialog.set_transient_for(transient)
self._providers_tree_view = builder.get_object("providers_tree_view")
self._satellites_tree_view = builder.get_object("satellites_tree_view")
self._expander = builder.get_object("expander")
self._text_view = builder.get_object("text_view")
self._info_bar = builder.get_object("info_bar")
@@ -54,12 +65,12 @@ class PiconsDialog:
self._info_bar = builder.get_object("info_bar")
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
self._load_providers_tool_button = builder.get_object("load_providers_tool_button")
self._receive_tool_button = builder.get_object("receive_tool_button")
self._convert_tool_button = builder.get_object("convert_tool_button")
self._load_providers_button = builder.get_object("load_providers_button")
self._receive_button = builder.get_object("receive_button")
self._convert_button = builder.get_object("convert_button")
self._enigma2_path_button = builder.get_object("enigma2_path_button")
self._save_to_button = builder.get_object("save_to_button")
self._send_tool_button = builder.get_object("send_tool_button")
self._send_button = builder.get_object("send_button")
self._enigma2_radio_button = builder.get_object("enigma2_radio_button")
self._neutrino_mp_radio_button = builder.get_object("neutrino_mp_radio_button")
self._resize_no_radio_button = builder.get_object("resize_no_radio_button")
@@ -88,6 +99,29 @@ class PiconsDialog:
self._dialog.run()
self._dialog.destroy()
def on_satellites_view_realize(self, view):
self.get_satellites(view)
@run_task
def get_satellites(self, view):
sats = SatellitesParser().get_satellites_list(SatelliteSource.LYNGSAT)
gen = self.append_satellites(view.get_model(), sats)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def append_satellites(self, model, sats):
for sat in sats:
pos = sat[1]
name = "{} ({})".format(sat[0], pos)
pos = "{}{}".format("-" if pos[-1] == "W" else "", pos[:-1])
if not self._terminate and model:
if pos in self._sat_positions:
model.append((name, sat[3], pos))
yield True
def on_satellite_selection(self, view, path, column):
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)
@@ -108,7 +142,7 @@ class PiconsDialog:
providers = parse_providers(self._TMP_DIR + url[url.find("w"):])
if providers:
for p in providers:
model.append((self.get_pixbuf(p[0]), p.name, p.pos, p.url, p.on_id, p.selected))
model.append((self.get_pixbuf(p[0]) if p[0] else TV_ICON, *p[1:]))
self.update_receive_button_state()
def get_pixbuf(self, img_url):
@@ -130,15 +164,19 @@ class PiconsDialog:
providers = self.get_selected_providers()
for prv in providers:
if not prv[2] and prv[2][:-2].isdigit():
if not self._POS_PATTERN.match(prv[2]):
self.show_info_message(
get_message("Specify the correct position value for the provider!"), Gtk.MessageType.ERROR)
scroll_to(prv.path, self._providers_tree_view)
return
for prv in providers:
if self._terminate:
break
self.process_provider(Provider(*prv))
self.resize(self._picons_path)
if not self._terminate:
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
def process_provider(self, prv):
url = prv.url
@@ -149,12 +187,8 @@ class PiconsDialog:
universal_newlines=True)
GLib.io_add_watch(self._current_process.stderr, GLib.IO_IN, self.write_to_buffer)
self._current_process.wait()
path = self._TMP_DIR + self._BASE_URL + url[url.rfind("/") + 1:]
pos = "".join(c for c in prv.pos if c.isdigit())
PiconsParser.parse(path, self._picons_path, self._TMP_DIR, prv.on_id, pos,
self._picon_ids, self.get_picons_format())
self.resize(self._picons_path)
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
path = self._TMP_DIR + (url[url.find("//") + 2:] if prv.single else self._BASE_URL + url[url.rfind("/") + 1:])
PiconsParser.parse(path, self._picons_path, self._TMP_DIR, prv, self._picon_ids, self.get_picons_format())
def write_to_buffer(self, fd, condition):
if condition == GLib.IO_IN:
@@ -175,11 +209,15 @@ class PiconsDialog:
self.show_info_message(get_message("Resizing..."), Gtk.MessageType.INFO)
command = "mogrify -resize {}! *.png".format(
"320x240" if self._resize_220_132_radio_button.get_active() else "100x60").split()
self._current_process = subprocess.Popen(command, universal_newlines=True, cwd=path)
self._current_process.wait()
try:
self._current_process = subprocess.Popen(command, universal_newlines=True, cwd=path)
self._current_process.wait()
except FileNotFoundError as e:
self.show_info_message("Conversion error. " + str(e), Gtk.MessageType.ERROR)
self.on_cancel()
@run_task
def on_cancel(self, item):
def on_cancel(self, item=None):
if self._current_process:
self._terminate = True
self._current_process.terminate()
@@ -188,7 +226,9 @@ class PiconsDialog:
@run_idle
def on_close(self, item):
self.on_cancel(item)
self._dialog.destroy()
path = self._TMP_DIR + "www.lyngsat.com"
if os.path.exists(path):
shutil.rmtree(path)
def on_send(self, item):
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
@@ -203,10 +243,13 @@ class PiconsDialog:
self.show_dialog("The task is already running!", DialogType.ERROR)
return
upload_data(properties=self._properties,
download_type=DownloadDataType.PICONS,
profile=self._profile,
callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
try:
upload_data(properties=self._properties,
download_type=DownloadType.PICONS,
profile=self._profile,
callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
except OSError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@@ -223,13 +266,22 @@ class PiconsDialog:
@run_idle
def on_selected_toggled(self, toggle, path):
model = self._providers_tree_view.get_model()
model.set_value(model.get_iter(path), 5, not toggle.get_active())
model.set_value(model.get_iter(path), 7, not toggle.get_active())
self.update_receive_button_state()
def on_select_all(self, view):
self.update_selection(view, True)
def on_unselect_all(self, view):
self.update_selection(view, False)
def update_selection(self, view, select):
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 7, select))
def on_url_changed(self, entry):
suit = self._PATTERN.search(entry.get_text())
entry.set_name("GtkEntry" if suit else "digit-entry")
self._load_providers_tool_button.set_sensitive(suit if suit else False)
self._load_providers_button.set_sensitive(suit if suit else False)
def on_position_edited(self, render, path, value):
model = self._providers_tree_view.get_model()
@@ -237,10 +289,10 @@ class PiconsDialog:
@run_idle
def on_notebook_switch_page(self, nb, box, tab_num):
self._load_providers_tool_button.set_visible(not tab_num)
self._receive_tool_button.set_visible(not tab_num)
self._convert_tool_button.set_visible(tab_num)
self._send_tool_button.set_sensitive(not tab_num)
self._load_providers_button.set_visible(not tab_num)
self._receive_button.set_visible(not tab_num)
self._convert_button.set_visible(tab_num)
self._send_button.set_visible(not tab_num)
if self._enigma2_path_button.get_filename() is None:
self._enigma2_path_button.set_current_folder(self._enigma2_picons_path)
@@ -265,11 +317,11 @@ class PiconsDialog:
@run_idle
def update_receive_button_state(self):
self._receive_tool_button.set_sensitive(len(self.get_selected_providers()) > 0)
self._receive_button.set_sensitive(len(self.get_selected_providers()) > 0)
def get_selected_providers(self):
""" returns selected providers """
return [r for r in self._providers_tree_view.get_model() if r[5]]
return [r for r in self._providers_tree_view.get_model() if r[7]]
@run_idle
def show_dialog(self, message, dialog_type):

File diff suppressed because it is too large Load Diff

View File

@@ -7,9 +7,9 @@ from app.commons import run_idle, run_task
from app.eparser import get_satellites, write_satellites, Satellite, Transponder
from app.tools.satellites import SatellitesParser, SatelliteSource
from .search import SearchProvider
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, MOVE_KEYS
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, MOVE_KEYS, KeyboardKey
from .dialogs import show_dialog, DialogType, WaitDialog
from .main_helper import move_items, scroll_to, append_text_to_tview, get_base_model
from .main_helper import move_items, scroll_to, append_text_to_tview, get_base_model, on_popup_menu
def show_satellites_dialog(transient, options):
@@ -26,14 +26,16 @@ class SatellitesDialog:
handlers = {"on_open": self.on_open,
"on_remove": self.on_remove,
"on_save": self.on_save,
"on_save_as": self.on_save_as,
"on_update": self.on_update,
"on_up": self.on_up,
"on_down": self.on_down,
"on_popup_menu": self.on_popup_menu,
"on_popup_menu": on_popup_menu,
"on_satellite_add": self.on_satellite_add,
"on_transponder_add": self.on_transponder_add,
"on_edit": self.on_edit,
"on_key_release": self.on_key_release,
"on_popover_release": self.on_popover_release,
"on_row_activated": self.on_row_activated,
"on_resize": self.on_resize,
"on_quit": self.on_quit}
@@ -41,22 +43,19 @@ class SatellitesDialog:
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("satellites_editor_dialog", "satellites_tree_store", "popup_menu",
"add_popup_menu", "add_menu_icon", "receive_menu_icon"))
("satellites_editor_window", "satellites_tree_store", "popup_menu",
"left_header_menu", "add_header_popover_menu"))
builder.connect_signals(handlers)
# Adding custom image for add_menu_tool_button
add_menu_tool_button = builder.get_object("add_menu_tool_button")
add_menu_tool_button.set_image(builder.get_object("add_menu_icon"))
self._dialog = builder.get_object("satellites_editor_dialog")
self._dialog.set_transient_for(transient)
self._dialog.get_content_area().set_border_width(0) # The width of the border around the app dialog area!
self._window = builder.get_object("satellites_editor_window")
self._window.set_transient_for(transient)
# self._dialog.get_content_area().set_border_width(0) # The width of the border around the app dialog area!
self._sat_view = builder.get_object("satellites_editor_tree_view")
self._wait_dialog = WaitDialog(self._dialog)
self._wait_dialog = WaitDialog(self._window)
# Setting the last size of the dialog window if it was saved
window_size = self._options.get("sat_editor_window_size", None)
if window_size:
self._dialog.resize(*window_size)
self._window.resize(*window_size)
self._stores = {3: builder.get_object("pol_store"),
4: builder.get_object("fec_store"),
@@ -64,37 +63,41 @@ class SatellitesDialog:
6: builder.get_object("mod_store")}
self.on_satellites_list_load(self._sat_view.get_model())
@run_idle
def show(self):
self._dialog.run()
self._dialog.destroy()
self._window.show()
def on_resize(self, window):
""" Stores new size properties for dialog window after resize """
if self._options:
self._options["sat_editor_window_size"] = window.get_size()
def on_quit(self, item):
self._dialog.destroy()
@run_idle
def on_quit(self, *args):
self._window.destroy()
@run_idle
def on_open(self, model):
file_filter = Gtk.FileFilter()
file_filter.add_pattern("satellites.xml")
file_filter.set_name("satellites.xml")
response = show_dialog(dialog_type=DialogType.CHOOSER,
transient=self._dialog,
options=self._options,
action_type=Gtk.FileChooserAction.OPEN,
file_filter=file_filter)
response = self.get_file_dialog_response(Gtk.FileChooserAction.OPEN)
if response == Gtk.ResponseType.CANCEL:
return
if not str(response).endswith("satellites.xml"):
show_dialog(DialogType.ERROR, self._dialog, text="No satellites.xml file is selected!")
show_dialog(DialogType.ERROR, self._window, text="No satellites.xml file is selected!")
return
self._data_path = response
self.on_satellites_list_load(model)
def get_file_dialog_response(self, action: Gtk.FileChooserAction):
file_filter = Gtk.FileFilter()
file_filter.add_pattern("satellites.xml")
file_filter.set_name("satellites.xml")
response = show_dialog(dialog_type=DialogType.CHOOSER,
transient=self._window,
options=self._options,
action_type=action,
file_filter=file_filter)
return response
@staticmethod
def on_row_activated(view, path, column):
if view.row_expanded(path):
@@ -103,33 +106,37 @@ class SatellitesDialog:
view.expand_row(path, column)
def on_up(self, item):
move_items(Gdk.KEY_Up, self._sat_view)
move_items(KeyboardKey.UP, self._sat_view)
def on_down(self, item):
move_items(Gdk.KEY_Down, self._sat_view)
move_items(KeyboardKey.DOWN, self._sat_view)
def on_key_release(self, view, event):
""" Handling keystrokes """
key = event.keyval
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):
return
key = KeyboardKey(key_code)
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
if key == Gdk.KEY_Delete:
if key is KeyboardKey.DELETE:
self.on_remove(view)
elif key == Gdk.KEY_Insert:
elif key is KeyboardKey.INSERT:
pass
elif ctrl and key == Gdk.KEY_E or key == Gdk.KEY_e:
elif ctrl and key is KeyboardKey.E:
self.on_edit(view)
elif ctrl and key == Gdk.KEY_s or key == Gdk.KEY_S:
elif ctrl and key is KeyboardKey.S:
self.on_satellite()
elif ctrl and key == Gdk.KEY_t or key == Gdk.KEY_T:
elif ctrl and key is KeyboardKey.T:
self.on_transponder()
elif key == Gdk.KEY_space:
pass
elif ctrl and key in MOVE_KEYS:
move_items(key, self._sat_view)
elif key == Gdk.KEY_Left or key == Gdk.KEY_Right:
elif key is KeyboardKey.LEFT or key is KeyboardKey.RIGHT:
view.do_unselect_all(view)
def on_popover_release(self, menu, event):
menu.hide()
@run_idle
def on_satellites_list_load(self, model):
""" Load satellites data into model """
@@ -137,7 +144,7 @@ class SatellitesDialog:
self._wait_dialog.show()
satellites = get_satellites(self._data_path)
except FileNotFoundError as e:
show_dialog(DialogType.ERROR, self._dialog, getattr(e, "message", str(e)) +
show_dialog(DialogType.ERROR, self._window, getattr(e, "message", str(e)) +
"\n\nPlease, download files from receiver or setup your path for read data!")
else:
model.clear()
@@ -177,7 +184,7 @@ class SatellitesDialog:
def on_satellite(self, satellite=None, edited_itr=None):
""" Create or edit satellite"""
sat_dialog = SatelliteDialog(self._dialog, satellite)
sat_dialog = SatelliteDialog(self._window, satellite)
sat = sat_dialog.run()
sat_dialog.destroy()
@@ -198,10 +205,10 @@ class SatellitesDialog:
if paths is None:
return
elif len(paths) == 0:
show_dialog(DialogType.ERROR, self._dialog, "No satellite is selected!")
show_dialog(DialogType.ERROR, self._window, "No satellite is selected!")
return
dialog = TransponderDialog(self._dialog, transponder)
dialog = TransponderDialog(self._window, transponder)
tr = dialog.run()
dialog.destroy()
@@ -252,7 +259,7 @@ class SatellitesDialog:
"""
model, paths = view.get_selection().get_selected_rows()
if len(paths) > 1:
show_dialog(DialogType.ERROR, self._dialog, message)
show_dialog(DialogType.ERROR, self._window, message)
return
return paths
@@ -265,8 +272,9 @@ class SatellitesDialog:
for itr in [model.get_iter(path) for path in paths]:
model.remove(itr)
@run_idle
def on_save(self, view):
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
if show_dialog(DialogType.QUESTION, self._window) == Gtk.ResponseType.CANCEL:
return
model = view.get_model()
@@ -274,10 +282,15 @@ class SatellitesDialog:
model.foreach(self.parse_data, satellites)
write_satellites(satellites, self._data_path)
def on_save_as(self, item):
response = self.get_file_dialog_response(Gtk.FileChooserAction.SAVE)
if response == Gtk.ResponseType.CANCEL:
return
show_dialog(DialogType.ERROR, transient=self._window, text="Not implemented yet!")
@run_idle
def on_update(self, item):
dialog = SatellitesUpdateDialog(self._dialog, self._sat_view.get_model())
dialog.run()
dialog.destroy()
SatellitesUpdateDialog(self._window, self._sat_view.get_model()).show()
@staticmethod
def parse_data(model, path, itr, sats):
@@ -295,11 +308,6 @@ class SatellitesDialog:
satellite = Satellite(sat[0], sat[-2], sat[-1], transponders)
sats.append(satellite)
@staticmethod
def on_popup_menu(menu, event):
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_SECONDARY:
menu.popup(None, None, None, None, event.button, event.time)
# ***************** Transponder dialog *******************#
@@ -444,6 +452,9 @@ class SatellitesUpdateDialog:
"on_info_bar_close": self.on_info_bar_close,
"on_filter_toggled": self.on_filter_toggled,
"on_find_toggled": self.on_find_toggled,
"on_popup_menu": on_popup_menu,
"on_select_all": self.on_select_all,
"on_unselect_all": self.on_unselect_all,
"on_filter": self.on_filter,
"on_search": self.on_search,
"on_search_down": self.on_search_down,
@@ -453,13 +464,14 @@ class SatellitesUpdateDialog:
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("satellites_update_dialog", "update_source_store", "update_sat_list_store",
("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"))
"pos_adjustment", "pos_adjustment2", "satellites_update_popup_menu",
"remove_selection_image"))
builder.connect_signals(handlers)
self._dialog = builder.get_object("satellites_update_dialog")
self._dialog.set_transient_for(transient)
self._window = builder.get_object("satellites_update_window")
self._window.set_transient_for(transient)
self._main_model = main_model
# self._dialog.get_content_area().set_border_width(0)
self._sat_view = builder.get_object("sat_update_tree_view")
@@ -470,7 +482,7 @@ class SatellitesUpdateDialog:
self._sat_update_info_bar = builder.get_object("sat_update_info_bar")
self._info_bar_message_label = builder.get_object("info_bar_message_label")
# Filter
self._filter_info_bar = builder.get_object("sat_update_filter_info_bar")
self._filter_bar = builder.get_object("sat_update_filter_bar")
self._from_pos_button = builder.get_object("from_pos_button")
self._to_pos_button = builder.get_object("to_pos_button")
self._filter_from_combo_box = builder.get_object("filter_from_combo_box")
@@ -479,7 +491,7 @@ class SatellitesUpdateDialog:
self._filter_model.set_visible_func(self.filter_function)
self._filter_positions = (0, 0)
# Search
self._search_info_bar = builder.get_object("sat_update_search_info_bar")
self._search_bar = builder.get_object("sat_update_search_bar")
self._search_provider = SearchProvider((self._sat_view,),
builder.get_object("sat_update_search_down_button"),
builder.get_object("sat_update_search_up_button"))
@@ -487,17 +499,13 @@ class SatellitesUpdateDialog:
self._download_task = False
self._parser = None
def run(self):
if self._dialog.run() == Gtk.ResponseType.CANCEL:
self._download_task = False
return
def destroy(self):
self._dialog.destroy()
def show(self):
self._window.show()
@run_idle
def on_update_satellites_list(self, item):
if self._download_task:
show_dialog(DialogType.ERROR, self._dialog, "The task is already running!")
show_dialog(DialogType.ERROR, self._window, "The task is already running!")
return
model = get_base_model(self._sat_view.get_model())
@@ -522,18 +530,17 @@ class SatellitesUpdateDialog:
for sat in sats:
model.append(sat)
@run_task
@run_idle
def on_receive_satellites_list(self, item):
if self._download_task:
show_dialog(DialogType.ERROR, self._dialog, "The task is already running!")
show_dialog(DialogType.ERROR, self._window, "The task is already running!")
return
self.receive_satellites()
@run_task
def receive_satellites(self):
self._download_task = True
self._sat_update_expander.set_expanded(True)
self._text_view.get_buffer().set_text("", 0)
self.update_expander()
model = self._sat_view.get_model()
start = time.time()
@@ -545,9 +552,11 @@ class SatellitesUpdateDialog:
futures = {executor.submit(self._parser.get_satellite, sat[:-1]): sat for sat in [r for r in model if r[4]]}
for future in concurrent.futures.as_completed(futures):
if not self._download_task:
self._download_task = True
executor.shutdown()
appender.send("\nCanceled\n")
appender.close()
self._download_task = False
return
data = future.result()
appender.send(text.format(data[0]))
@@ -556,7 +565,7 @@ class SatellitesUpdateDialog:
appender.send("-" * 75 + "\n")
appender.send("Consumed : {:0.0f}s, {} satellites received.".format(start - time.time(), len(sats)))
appender.close()
# self.show_info_message(message, Gtk.MessageType.INFO)
sats = {s[2]: s for s in sats} # key = position, v = satellite
for row in self._main_model:
@@ -571,6 +580,11 @@ class SatellitesUpdateDialog:
self._download_task = False
@run_idle
def update_expander(self):
self._sat_update_expander.set_expanded(True)
self._text_view.get_buffer().set_text("", 0)
@run_idle
def update_satellite(self, itr, row, sat):
if self._main_model.iter_has_child(itr):
@@ -590,14 +604,12 @@ class SatellitesUpdateDialog:
text = yield
append(text)
@run_idle
def on_cancel_receive(self, item=None):
self._download_task = False
def on_selected_toggled(self, toggle, path):
s_model = self._sat_view.get_model()
itr = self._filter_model.convert_iter_to_child_iter(s_model.convert_iter_to_child_iter(s_model.get_iter(path)))
self._filter_model.get_model().set_value(itr, 4, not toggle.get_active())
model = self._sat_view.get_model()
self.update_state(model, path, not toggle.get_active())
self.update_receive_button_state(self._filter_model)
@run_idle
@@ -614,10 +626,10 @@ class SatellitesUpdateDialog:
self._sat_update_info_bar.set_visible(False)
def on_find_toggled(self, button: Gtk.ToggleToolButton):
self._search_info_bar.set_visible(button.get_active())
self._search_bar.set_search_mode(button.get_active())
def on_filter_toggled(self, button: Gtk.ToggleToolButton):
self._filter_info_bar.set_visible(button.get_active())
self._filter_bar.set_search_mode(button.get_active())
@run_idle
def on_filter(self, item):
@@ -651,7 +663,23 @@ class SatellitesUpdateDialog:
def on_search_up(self, item):
self._search_provider.on_search_up()
def on_quit(self):
def on_select_all(self, view):
self.update_selection(view, True)
def on_unselect_all(self, view):
self.update_selection(view, False)
def update_selection(self, view, select):
model = view.get_model()
view.get_model().foreach(lambda mod, path, itr: self.update_state(model, path, select))
self.update_receive_button_state(self._filter_model)
def update_state(self, model, path, select):
""" Updates checkbox state by given path in the list """
itr = self._filter_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(model.get_iter(path)))
self._filter_model.get_model().set_value(itr, 4, select)
def on_quit(self, window, event):
self._download_task = False

File diff suppressed because it is too large Load Diff

View File

@@ -164,11 +164,25 @@ class ServiceDetailsDialog:
def update_data_elements(self):
model, paths = self._services_view.get_selection().get_selected_rows()
itr = model.get_iter(paths)
# Unpacking to search for an iterator for the base model
filter_model = model.get_model()
itr = filter_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(itr))
self._current_model = get_base_model(model)
itr = None
if not paths:
# If editing from bouquet list and services list in the filter mode
fav_model, paths = self._fav_view.get_selection().get_selected_rows()
fav_id = fav_model[paths][7]
for row in self._current_model:
if row[-2] == fav_id:
itr = row.iter
break
else:
itr = model.get_iter(paths)
itr = filter_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(itr))
if not itr:
return
srv = Service(*self._current_model[itr][:])
self._old_service = srv
self._current_itr = itr
@@ -177,12 +191,16 @@ class ServiceDetailsDialog:
self._package_entry.set_text(srv.package)
self._sid_entry.set_text(str(int(srv.ssid, 16)))
# Transponder
tr_type = srv.transponder_type
self._freq_entry.set_text(srv.freq)
self._rate_entry.set_text(srv.rate)
self.select_active_text(self._pol_combo_box, srv.pol)
self.select_active_text(self._fec_combo_box, srv.fec)
self.select_active_text(self._sys_combo_box, srv.system)
self.set_sat_positions(srv.pos)
if tr_type in "tc" and self._profile is Profile.ENIGMA_2:
self.update_ui_for_terrestrial()
else:
self.set_sat_positions(srv.pos)
if self._profile is Profile.ENIGMA_2:
self.init_enigma2_service_data(srv)
@@ -332,17 +350,21 @@ class ServiceDetailsDialog:
fav_id, data_id = self.get_srv_data()
# transponder
transponder = self._old_service.transponder
tr_type = self._old_service.transponder_type
if self._tr_edit_switch.get_active():
transponder = self.get_transponder_data()
transponder = self.get_transponder_data(tr_type)
if self._transponder_services_iters:
self.update_transponder_services(transponder)
service = self.get_service(fav_id, data_id, transponder)
self.update_transponder_services(transponder, tr_type)
# service
service = self.get_service(fav_id, data_id, transponder, tr_type)
old_fav_id = self._old_service.fav_id
if old_fav_id != fav_id:
self.update_bouquets(fav_id, old_fav_id)
self._services[fav_id] = service
if self._old_service.picon_id != service.picon_id:
self.update_picon_name(self._old_service.picon_id, service.picon_id)
self._current_model.set(self._current_itr, {i: v for i, v in enumerate(service)})
self.update_fav_view(self._old_service, service)
self._old_service = service
@@ -371,6 +393,9 @@ class ServiceDetailsDialog:
8: new_service.picon})
def update_picon_name(self, old_name, new_name):
if not os.path.isdir(self._picons_dir_path):
return
for file_name in os.listdir(self._picons_dir_path):
if file_name == old_name:
old_file = os.path.join(self._picons_dir_path, old_name)
@@ -380,13 +405,12 @@ class ServiceDetailsDialog:
def on_new(self):
service = self.get_service(*self.get_srv_data(), self.get_transponder_data())
print(service)
show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
def get_service(self, fav_id, data_id, transponder):
freq, rate, pol, fec, system, pos = self.get_transponder_values()
def get_service(self, fav_id, data_id, transponder, tr_type):
freq, rate, pol, fec, system, pos = self.get_transponder_values(tr_type)
return Service(flags_cas=self.get_flags(),
transponder_type="s",
transponder_type=tr_type,
coded=CODED_ICON if self._cas_entry.get_text() else None,
service=self._name_entry.get_text(),
locked=self._old_service.locked,
@@ -464,16 +488,20 @@ class ServiceDetailsDialog:
fav_id = self._NEUTRINO_FAV_ID.format(tr_id, net_id, ssid)
return fav_id, self._old_service.data_id
def get_transponder_values(self):
freq = self._freq_entry.get_text()
rate = self._rate_entry.get_text()
pol = self._pol_combo_box.get_active_id()
fec = self._fec_combo_box.get_active_id()
system = self._sys_combo_box.get_active_id()
pos = str(round(self._sat_pos_button.get_value(), 1))
return freq, rate, pol, fec, system, pos
def get_transponder_values(self, tr_type):
if tr_type == "s":
freq = self._freq_entry.get_text()
rate = self._rate_entry.get_text()
pol = self._pol_combo_box.get_active_id()
fec = self._fec_combo_box.get_active_id()
system = self._sys_combo_box.get_active_id()
pos = str(round(self._sat_pos_button.get_value(), 1))
return freq, rate, pol, fec, system, pos
elif tr_type in "tc":
o_srv = self._old_service
return o_srv.freq, o_srv.rate, o_srv.pol, o_srv.fec, o_srv.system, o_srv.pos
def get_transponder_data(self):
def get_transponder_data(self, tr_type):
sys = self._sys_combo_box.get_active_id()
freq = self._freq_entry.get_text()
rate = self._rate_entry.get_text()
@@ -503,10 +531,10 @@ class ServiceDetailsDialog:
srv_sys = None
return self._NEUTRINO_TRANSPONDER_DATA.format(tr_id, on_id, freq, inv, rate, fec, pol, mod, srv_sys)
def update_transponder_services(self, transponder):
def update_transponder_services(self, transponder, tr_type):
for itr in self._transponder_services_iters:
srv = self._current_model[itr][:]
srv[-9], srv[-8], srv[-7], srv[-6], srv[-5], srv[-4] = self.get_transponder_values()
srv[-9], srv[-8], srv[-7], srv[-6], srv[-5], srv[-4] = self.get_transponder_values(tr_type)
srv[-1] = transponder
srv = Service(*srv)
self._services[srv.fav_id] = self._services.pop(srv.fav_id)._replace(transponder=transponder)
@@ -536,8 +564,12 @@ class ServiceDetailsDialog:
@run_idle
def on_tr_edit_toggled(self, switch: Gtk.Switch, active):
if active and self._action is Action.EDIT:
if self._old_service.transponder_type == "t":
show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
switch.set_active(False)
return
self._transponder_services_iters = []
response = TransponderServicesDialog(self._dialog,
self._current_model,
@@ -570,17 +602,24 @@ class ServiceDetailsDialog:
self.update_reference_entry()
def update_reference_entry(self):
srv_type = 0 if self._srv_type_entry.get_text() == "2" else 1
srv_type = int(self._srv_type_entry.get_text())
ssid = int(self._sid_entry.get_text())
tid = int(self._transponder_id_entry.get_text())
nid = int(self._network_id_entry.get_text())
if self._profile is Profile.ENIGMA_2:
on_id = int(self._namespace_entry.get_text())
ref = "1:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0".format(srv_type, ssid, tid, nid, on_id)
ref = "1:0:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0".format(srv_type, ssid, tid, nid, on_id)
self._reference_entry.set_text(ref)
else:
self._reference_entry.set_text("{:x}{:04x}{:04x}".format(tid, nid, ssid))
def update_ui_for_terrestrial(self):
self._pids_grid.set_visible(False)
tr_frame = self._builder.get_object("transponder_data_frame")
tr_frame.set_visible(False)
self._builder.get_object("srv_separator").set_visible(False)
self._reference_entry.set_max_width_chars(22)
class TransponderServicesDialog:
def __init__(self, transient, model, transponder, tr_iters):

1079
app/ui/settings_dialog.glade Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,7 @@
from enum import Enum
from app.commons import run_task, run_idle
from app.connections import test_telnet, test_ftp, TestException, test_http
from app.properties import write_config, Profile, get_default_settings
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .main_helper import update_entry_data
@@ -7,6 +11,12 @@ def show_settings_dialog(transient, options):
return SettingsDialog(transient, options).show()
class Property(Enum):
FTP = "ftp"
HTTP = "http"
TELNET = "telnet"
class SettingsDialog:
def __init__(self, transient, options):
@@ -14,12 +24,13 @@ class SettingsDialog:
"on_picons_dir_field_icon_press": self.on_picons_dir_field_icon_press,
"on_profile_changed": self.on_profile_changed,
"on_reset": self.on_reset,
"apply_settings": self.apply_settings}
"apply_settings": self.apply_settings,
"on_connection_test": self.on_connection_test,
"on_info_bar_close": self.on_info_bar_close}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade",
("settings_dialog", "telnet_timeout_adjustment"))
builder.add_from_file(UI_RESOURCES_PATH + "settings_dialog.glade")
builder.connect_signals(handlers)
self._dialog = builder.get_object("settings_dialog")
@@ -28,6 +39,9 @@ class SettingsDialog:
self._port_field = builder.get_object("port_field")
self._login_field = builder.get_object("login_field")
self._password_field = builder.get_object("password_field")
self._http_login_field = builder.get_object("http_login_field")
self._http_password_field = builder.get_object("http_password_field")
self._http_port_field = builder.get_object("http_port_field")
self._telnet_login_field = builder.get_object("telnet_login_field")
self._telnet_password_field = builder.get_object("telnet_password_field")
self._telnet_port_field = builder.get_object("telnet_port_field")
@@ -41,19 +55,24 @@ class SettingsDialog:
self._enigma_radio_button = builder.get_object("enigma_radio_button")
self._neutrino_radio_button = builder.get_object("neutrino_radio_button")
self._support_ver5_check_button = builder.get_object("support_ver5_check_button")
self._support_http_api_check_button = builder.get_object("support_http_api_check_button")
self._settings_stack = builder.get_object("settings_stack")
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
self._test_spinner = builder.get_object("test_spinner")
self._options = options
self._active_profile = options.get("profile")
self.set_settings()
profile = Profile(self._active_profile)
self._neutrino_radio_button.set_active(profile is Profile.NEUTRINO_MP)
self._support_ver5_check_button.set_sensitive(profile is not Profile.NEUTRINO_MP)
self._support_http_api_check_button.set_sensitive(profile is not Profile.NEUTRINO_MP)
self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(profile is not Profile.NEUTRINO_MP)
def show(self):
response = self._dialog.run()
if response == Gtk.ResponseType.OK:
self.apply_settings()
write_config(self._options)
self._dialog.destroy()
return response
@@ -66,8 +85,10 @@ class SettingsDialog:
def on_profile_changed(self, item):
profile = Profile.ENIGMA_2 if self._enigma_radio_button.get_active() else Profile.NEUTRINO_MP
self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(profile is not Profile.NEUTRINO_MP)
self.set_profile(profile)
self._support_ver5_check_button.set_sensitive(profile is Profile.ENIGMA_2)
self._support_http_api_check_button.set_sensitive(profile is Profile.ENIGMA_2)
def set_profile(self, profile):
self._active_profile = profile.value
@@ -90,6 +111,9 @@ class SettingsDialog:
self._port_field.set_text(options.get("port", ""))
self._login_field.set_text(options.get("user", ""))
self._password_field.set_text(options.get("password", ""))
self._http_login_field.set_text(options.get("http_user", ""))
self._http_password_field.set_text(options.get("http_password", ""))
self._http_port_field.set_text(options.get("http_port", "80"))
self._telnet_login_field.set_text(options.get("telnet_user", ""))
self._telnet_password_field.set_text(options.get("telnet_password", ""))
self._telnet_port_field.set_text(options.get("telnet_port", ""))
@@ -102,6 +126,7 @@ class SettingsDialog:
self._picons_dir_field.set_text(options.get("picons_dir_path", ""))
if Profile(self._active_profile) is Profile.ENIGMA_2:
self._support_ver5_check_button.set_active(options.get("v5_support", False))
self._support_http_api_check_button.set_active(options.get("http_api_support", False))
def apply_settings(self, item=None):
profile = Profile.ENIGMA_2 if self._enigma_radio_button.get_active() else Profile.NEUTRINO_MP
@@ -112,6 +137,9 @@ class SettingsDialog:
options["port"] = self._port_field.get_text()
options["user"] = self._login_field.get_text()
options["password"] = self._password_field.get_text()
options["http_user"] = self._http_login_field.get_text()
options["http_password"] = self._http_password_field.get_text()
options["http_port"] = self._http_port_field.get_text()
options["telnet_user"] = self._telnet_login_field.get_text()
options["telnet_password"] = self._telnet_password_field.get_text()
options["telnet_port"] = self._telnet_port_field.get_text()
@@ -124,6 +152,67 @@ class SettingsDialog:
options["picons_dir_path"] = self._picons_dir_field.get_text()
if profile is Profile.ENIGMA_2:
options["v5_support"] = self._support_ver5_check_button.get_active()
options["http_api_support"] = self._support_http_api_check_button.get_active()
write_config(self._options)
@run_task
def on_connection_test(self, item):
if self._test_spinner.get_state() is Gtk.StateType.ACTIVE:
return
self.show_spinner(True)
current_property = Property(self._settings_stack.get_visible_child_name())
if current_property is Property.HTTP:
self.test_http()
elif current_property is Property.TELNET:
self.test_telnet()
elif current_property is Property.FTP:
self.test_ftp()
def test_http(self):
user, password = self._http_login_field.get_text(), self._http_password_field.get_text()
host, port = self._host_field.get_text(), self._http_port_field.get_text()
try:
self.show_info_message(test_http(host, port, user, password), Gtk.MessageType.INFO)
except TestException as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
finally:
self.show_spinner(False)
def test_telnet(self):
timeout = int(self._telnet_timeout_spin_button.get_value())
host, port = self._host_field.get_text(), self._telnet_port_field.get_text()
user, password = self._telnet_login_field.get_text(), self._telnet_password_field.get_text()
try:
self.show_info_message(test_telnet(host, port, user, password, timeout), Gtk.MessageType.INFO)
self.show_spinner(False)
except TestException as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
self.show_spinner(False)
def test_ftp(self):
host, port = self._host_field.get_text(), self._port_field.get_text()
user, password = self._login_field.get_text(), self._password_field.get_text()
try:
self.show_info_message("OK. {}".format(test_ftp(host, port, user, password)), Gtk.MessageType.INFO)
except TestException as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
finally:
self.show_spinner(False)
@run_idle
def show_info_message(self, text, message_type):
self._info_bar.set_visible(True)
self._info_bar.set_message_type(message_type)
self._message_label.set_text(text)
@run_idle
def show_spinner(self, show):
self._test_spinner.start() if show else self._test_spinner.stop()
self._test_spinner.set_state(Gtk.StateType.ACTIVE if show else Gtk.StateType.NORMAL)
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
if __name__ == "__main__":

View File

@@ -20,15 +20,54 @@ theme = Gtk.IconTheme.get_default()
_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None
CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(
"emblem-readonly", 16, 0) else _IMAGE_MISSING
LOCKED_ICON = theme.load_icon("system-lock-screen", 16, 0) if theme.lookup_icon(
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.load_icon("emblem-shared", 16, 0) else None
# keys for move in lists
MOVE_KEYS = (Gdk.KEY_Up, Gdk.KEY_Page_Up, Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_Home, Gdk.KEY_End,
Gdk.KEY_KP_Page_Up, Gdk.KEY_KP_Page_Down) # KEY_KP_Page_Up(Down) for laptop!
class KeyboardKey(Enum):
""" The raw(hardware) codes of the keyboard keys """
E = 26
R = 27
T = 28
P = 33
S = 39
H = 43
L = 46
X = 53
C = 54
V = 55
Z = 52
INSERT = 118
HOME = 110
END = 115
UP = 111
DOWN = 116
PAGE_UP = 112
PAGE_DOWN = 117
LEFT = 113
RIGHT = 114
F2 = 68
DELETE = 119
BACK_SPACE = 22
CTRL_L = 37
CTRL_R = 105
# Laptop codes
HOME_KP = 79
END_KP = 87
PAGE_UP_KP = 81
PAGE_DOWN_KP = 89
@classmethod
def value_exist(cls, value):
return value in (val.value for val in cls.__members__.values())
# Keys for move in lists. KEY_KP_(NAME) for laptop!!!
MOVE_KEYS = (KeyboardKey.UP, KeyboardKey.PAGE_UP, KeyboardKey.DOWN, KeyboardKey.PAGE_DOWN, KeyboardKey.HOME,
KeyboardKey.END, KeyboardKey.HOME_KP, KeyboardKey.END_KP, KeyboardKey.PAGE_UP_KP, KeyboardKey.PAGE_DOWN_KP)
class ViewTarget(Enum):

View File

@@ -1,5 +1,5 @@
#!/bin/bash
VER="0.3.2_Pre-alpha"
VER="0.4.0_Pre-alpha"
B_PATH="dist/DemonEditor"
DEB_PATH="$B_PATH/usr/share/demoneditor"

View File

@@ -1,9 +1,9 @@
Package: DemonEditor
Version: 0.3.2-Pre-alpha
Version: 0.4.0-Pre-alpha
Section: utils
Priority: optional
Architecture: all
Essential: no
Depends: python3 (>= 3.5)
Maintainer: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
Description: Enigma2 channels and satellites list editor
Description: Enigma2 channel and satellites list editor

View File

@@ -8,4 +8,4 @@ Exec=/usr/bin/demoneditor.sh
Terminal=false
Type=Application
Categories=Utility;Application;
StartupNotify=true
StartupNotify=false

Binary file not shown.

View File

@@ -58,9 +58,6 @@ msgstr "Копировать"
msgid "Copy reference"
msgstr "Копировать ссылку"
msgid "Data"
msgstr ""
msgid "Download"
msgstr "Загрузить"
@@ -94,6 +91,15 @@ msgstr "Импортировать m3u"
msgid "Import m3u file"
msgstr "Импортировать файл m3u"
msgid "List configuration"
msgstr "Конфигурация списка"
msgid "Rename for this bouquet"
msgstr "Переименовать для букета"
msgid "Set default name"
msgstr "Имя по умолчанию"
msgid "Insert marker"
msgstr "Вставить маркер"
@@ -148,18 +154,12 @@ msgstr "Загрузчик пиконов"
msgid "Satellites downloader"
msgstr "Загрузчик спутников"
msgid "Preferences"
msgstr "Настройки"
msgid "Profile:"
msgstr "Профиль:"
msgid "Radio"
msgstr ""
msgid "Remove"
msgstr "Удалить"
msgid "Remove all unavailable"
msgstr "Удалить все недоступные"
msgid "Satellites editor"
msgstr "Редактор спутников"
@@ -196,42 +196,18 @@ msgstr "Вы уверены?"
msgid "Current data path:"
msgstr "Текущий путь к данным:"
msgid "Data dir:"
msgstr "Путь к данным:"
msgid "Data:"
msgstr "Данные:"
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
msgstr "Редактор списка каналов и спутников Enigma2\n для GNU/Linux"
msgid "FTP"
msgstr ""
msgid "Host:"
msgstr "Адрес ресивера:"
msgid "Loading data..."
msgstr "Загрузка данных..."
msgid "Login:"
msgstr "Логин:"
msgid "Options"
msgstr "Настройки"
msgid "Password:"
msgstr "Пароль:"
msgid "Picons dir:"
msgstr "Директория пиконов:"
msgid "Picons:"
msgstr "Пиконы:"
msgid "Port:"
msgstr "Порт:"
msgid "Receive"
msgstr "Получить"
@@ -242,7 +218,7 @@ msgid "Receiver IP:"
msgstr "IP адрес ресивера:"
msgid "Remove unused bouquets"
msgstr "Удалить не испрльзуемые букеты"
msgstr "Удалить неиспользуемые букеты"
msgid "Reset profile"
msgstr "Сброс профиля"
@@ -265,27 +241,44 @@ msgstr "Отправить файлы в ресивер"
msgid "Services and Bouquets files:"
msgstr "Файлы сервисов и букетов:"
msgid "Telnet"
msgstr ""
msgid "Timeout between commands in seconds"
msgstr "Пауза между коммандами в сек."
msgid "Timeout:"
msgstr "Тайм-аут:"
msgid "User bouquet files:"
msgstr "Файлы букетов:"
msgid "WebTV"
msgstr ""
msgid "Extra:"
msgstr "Дополнительно:"
# Filter bar
msgid "Only free"
msgstr "Только открытые"
msgid "All positions"
msgstr "Все позиции"
msgid "All types"
msgstr "Все типы"
# Streams player
msgid "Play"
msgstr "Воспроизведение"
msgid "Stop playback"
msgstr "Останов"
msgid "Previous stream in the list"
msgstr "Предыдущий поток в списке"
msgid "Next stream in the list"
msgstr "Следующий поток в списке"
msgid "Toggle in fullscreen"
msgstr "Показать во весь экран"
msgid "Close"
msgstr "Закрыть"
# Picons dialog
msgid "Load providers"
msgstr "Загрузить провайдеров"
msgstr "Загрузить провайдеры"
msgid "Providers"
msgstr "Провайдеры"
@@ -366,6 +359,13 @@ msgstr "Имя"
msgid "Position"
msgstr "Позиция"
# Satellites update dialog
msgid "Satellites update"
msgstr "Обновление спутников"
msgid "Remove selection"
msgstr "Снять выделение"
# Service details dialog
msgid "Service data:"
msgstr "Данные сервиса:"
@@ -419,6 +419,59 @@ msgstr "Поиск"
msgid "Stream data"
msgstr "Данные потока"
# IPTV list configuration dialog
msgid "Starting values"
msgstr "Стартовые значения"
msgid "Reset to default"
msgstr "Сбросить по умолчанию"
msgid "IPTV streams list configuration"
msgstr "Конфигурация списка IPTV потоков"
#Settings dialog
msgid "Preferences"
msgstr "Настройки"
msgid "Profile:"
msgstr "Профиль:"
msgid "Timeout between commands in seconds"
msgstr "Пауза между коммандами в сек."
msgid "Timeout:"
msgstr "Тайм-аут:"
msgid "Login:"
msgstr "Логин:"
msgid "Options"
msgstr "Настройки"
msgid "Password:"
msgstr "Пароль:"
msgid "Picons:"
msgstr "Пиконы:"
msgid "Port:"
msgstr "Порт:"
msgid "Data path:"
msgstr "Путь к данным:"
msgid "Picons path:"
msgstr "Путь к пиконам:"
msgid "Network settings:"
msgstr "Настройки сети:"
msgid "STB file paths:"
msgstr "Пути к файлам ресивера:"
msgid "Local file paths:"
msgstr "Пути к локальным файлам:"
# Dialogs messages
msgid "Error. No bouquet is selected!"
msgstr "Ошибка. Не выбран букет!"
@@ -429,6 +482,9 @@ msgstr "Этот элемент не разрешен к удалению!"
msgid "This item is not allowed to edit!"
msgstr "Элемент не предназначен для редактирования!"
msgid "Not allowed in this context!"
msgstr "Запрещено в данном контексте!"
msgid "Please, download files from receiver or setup your path for read data!"
msgstr "Пожалуйста, загрузите файлы из приемника или настройте путь для чтения данных!"
@@ -492,6 +548,23 @@ msgstr "Недопустимая операция в данном контекс
msgid "No VLC is found. Check that it is installed!"
msgstr "VLC не найден. Проверьте, что он установлен!"
# Search unavailable streams dialog
msgid "Please wait, streams testing in progress..."
msgstr "Подождите, идет тестирование потоков ..."
msgid "Found"
msgstr "Найдено"
msgid "unavailable streams."
msgstr "недоступных потоков."
msgid "No changes required!"
msgstr "Изменений не требуется!"
msgid "This list does not contains IPTV streams!"
msgstr "Текущий список не содержит потоков IPTV!"

5
repo/debian/changelog Normal file
View File

@@ -0,0 +1,5 @@
demon-editor (0.4.0-1~ppa1) bionic; urgency=low
* Initial release
-- Dmitriy Yefremov <dmitry.v.yefremov@gmail.com> Tue, 02 Oct 2018 12:41:40 +0300

1
repo/debian/compat Normal file
View File

@@ -0,0 +1 @@
10

12
repo/debian/control Normal file
View File

@@ -0,0 +1,12 @@
Source: demon-editor
Section: utils
Priority: optional
Maintainer: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
Build-Depends: python3 (>= 3.5), debhelper (>= 10)
Standards-Version: 4.1.2
Package: demon-editor
Architecture: all
Depends: python3 (>= 3.5)
Description: Enigma2 channel and satellites list editor

26
repo/debian/copyright Normal file
View File

@@ -0,0 +1,26 @@
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 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.

2
repo/debian/install Normal file
View File

@@ -0,0 +1,2 @@
usr/share /usr
usr/bin /usr

6
repo/debian/rules Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/make -f
export PYBUILD_NAME=demon-editor
%:
dh $@

View File

@@ -0,0 +1 @@
3.0 (native)

View File

@@ -0,0 +1 @@
extend-diff-ignore = "^[^/]*[.]egg-info/"