mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-09 05:26:32 +02:00
Compare commits
168 Commits
1.0.2-b1
...
0.4.8-macO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb5afb0206 | ||
|
|
115f3960a7 | ||
|
|
6d37da072e | ||
|
|
99c3b1d194 | ||
|
|
43afaf77b8 | ||
|
|
38aabb1b94 | ||
|
|
ef501f1557 | ||
|
|
4679f9379c | ||
|
|
b2ea39f8a6 | ||
|
|
638be67425 | ||
|
|
9ca5a597d5 | ||
|
|
d95ba7336f | ||
|
|
c78b18ddb7 | ||
|
|
92984c5fa6 | ||
|
|
ca65f64a4f | ||
|
|
78dcccbd51 | ||
|
|
f984d10c82 | ||
|
|
c4ea451f52 | ||
|
|
36ec6d5079 | ||
|
|
91706c722f | ||
|
|
4ef8c4d186 | ||
|
|
f9e92b28d0 | ||
|
|
832bab91a4 | ||
|
|
951c99338f | ||
|
|
ee91eb9413 | ||
|
|
912c38825b | ||
|
|
de4d012784 | ||
|
|
351ce81e94 | ||
|
|
3a0f096a6c | ||
|
|
29088ec19e | ||
|
|
4c144951f0 | ||
|
|
dae6ad765a | ||
|
|
b934407d7e | ||
|
|
3fb5b82cc6 | ||
|
|
ba3ad9a9ef | ||
|
|
7a4620a374 | ||
|
|
174634ecbc | ||
|
|
73ae57d07b | ||
|
|
055a700586 | ||
|
|
04203240a7 | ||
|
|
a433e01b65 | ||
|
|
8f591a8b9a | ||
|
|
dcc217b0de | ||
|
|
d06334b0af | ||
|
|
6957a960ca | ||
|
|
9fe328b54e | ||
|
|
b3dc9b72c9 | ||
|
|
b6a4d46227 | ||
|
|
53776bdf62 | ||
|
|
ba9ba4129f | ||
|
|
a2411ba86e | ||
|
|
a6d8573999 | ||
|
|
7510d42fb9 | ||
|
|
036e666c9b | ||
|
|
c9c962e129 | ||
|
|
ea71af9462 | ||
|
|
0a5b51de6e | ||
|
|
8cb413ec92 | ||
|
|
5dfb702484 | ||
|
|
0cab4e1238 | ||
|
|
85f5c37f28 | ||
|
|
3df6d7bba0 | ||
|
|
e45c56f4cc | ||
|
|
7d03631924 | ||
|
|
7b9ec6a4b1 | ||
|
|
d640210ab0 | ||
|
|
f7e8283355 | ||
|
|
f93c81de19 | ||
|
|
e1804755d2 | ||
|
|
1cf56639c1 | ||
|
|
943b4c540f | ||
|
|
4602c51c01 | ||
|
|
a84cc7727f | ||
|
|
250e03af5d | ||
|
|
2c5f8eb0ed | ||
|
|
6f4ff4c97d | ||
|
|
ee29659739 | ||
|
|
8a1496a84c | ||
|
|
23c3035162 | ||
|
|
a506356547 | ||
|
|
0c284fb0d9 | ||
|
|
b437385325 | ||
|
|
c60bba5535 | ||
|
|
1c2d0ab9ea | ||
|
|
f35f7fbc8a | ||
|
|
42aaad291f | ||
|
|
9c8c617393 | ||
|
|
98fc963fa1 | ||
|
|
fbb5cd0352 | ||
|
|
5abe3de3b6 | ||
|
|
0b3f26ab84 | ||
|
|
2666146b5e | ||
|
|
be90b518c9 | ||
|
|
adeae58488 | ||
|
|
b204f042ee | ||
|
|
79d0e9d256 | ||
|
|
4dcfde8b53 | ||
|
|
42f687020b | ||
|
|
14bf79dbf9 | ||
|
|
f660beef16 | ||
|
|
99d17b36c3 | ||
|
|
3113fadcca | ||
|
|
67a394359d | ||
|
|
1acb7fdd81 | ||
|
|
dced81581c | ||
|
|
6a52988f1a | ||
|
|
4a1d714604 | ||
|
|
2e12e1ec87 | ||
|
|
4c6336e75f | ||
|
|
b7d0ba7f4b | ||
|
|
405e07bbc4 | ||
|
|
6ded67147b | ||
|
|
4a0e2acd9c | ||
|
|
5876f70884 | ||
|
|
3cb4f1095d | ||
|
|
af46c2fb1d | ||
|
|
cbcdf19be6 | ||
|
|
f326a9c723 | ||
|
|
53888a45dc | ||
|
|
c2d3cb7673 | ||
|
|
9d4b507559 | ||
|
|
2993fcd7f7 | ||
|
|
5bea9887db | ||
|
|
dd0edfc811 | ||
|
|
5bf6500809 | ||
|
|
d05da3f44c | ||
|
|
b251ce8b69 | ||
|
|
df36860239 | ||
|
|
6f27040164 | ||
|
|
4d488dd224 | ||
|
|
c728a59e92 | ||
|
|
98341064d3 | ||
|
|
fc00b25fd2 | ||
|
|
638f33ac5a | ||
|
|
88167912b3 | ||
|
|
450d7f4c72 | ||
|
|
24729c064c | ||
|
|
df3c2a3938 | ||
|
|
44bf8b96ff | ||
|
|
6b86db4aa4 | ||
|
|
bf9ad139e5 | ||
|
|
20fc199d02 | ||
|
|
e0a22f72fc | ||
|
|
8cadc47da5 | ||
|
|
499ca31992 | ||
|
|
e198f0b1e6 | ||
|
|
a43ac0de02 | ||
|
|
b14e6fac16 | ||
|
|
2dcc9a85b5 | ||
|
|
7ce9ba0db2 | ||
|
|
fc56b047a1 | ||
|
|
78dc62d46e | ||
|
|
20e7ee3478 | ||
|
|
ffa144367f | ||
|
|
237d09a711 | ||
|
|
272cbdeb2f | ||
|
|
f9191a7465 | ||
|
|
f22abe1d87 | ||
|
|
b94c08284a | ||
|
|
e303f25f99 | ||
|
|
b80dcb7d74 | ||
|
|
63c5df0ef6 | ||
|
|
c69888a72d | ||
|
|
07606077e5 | ||
|
|
e1f63bfed7 | ||
|
|
c0865beb3c | ||
|
|
05eff28b75 | ||
|
|
6d2150b731 |
@@ -1,13 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Name=DemonEditor
|
||||
Comment=Channel and satellite list editor for Enigma2
|
||||
Comment[ru]=Редактор списка каналов и спутников для Enigma2
|
||||
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
|
||||
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
|
||||
Icon=demon-editor
|
||||
Exec=bash -c 'cd $(dirname %k) && ./start.py'
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;Application;
|
||||
StartupNotify=false
|
||||
63
DemonEditor.spec
Normal file
63
DemonEditor.spec
Normal file
@@ -0,0 +1,63 @@
|
||||
import os
|
||||
import distutils.util
|
||||
|
||||
EXE_NAME = 'start.py'
|
||||
DIR_PATH = os.getcwd()
|
||||
COMPILING_PLATFORM = distutils.util.get_platform()
|
||||
PATH_EXE = [os.path.join(DIR_PATH, EXE_NAME)]
|
||||
STRIP = True
|
||||
|
||||
block_cipher = None
|
||||
|
||||
ui_files = [('app/ui/*.glade', 'ui'),
|
||||
('app/ui/*.css', 'ui'),
|
||||
('app/ui/*.ui', 'ui'),
|
||||
('app/ui/lang*', 'share/locale'),
|
||||
('app/ui/icons*', 'share/icons')
|
||||
]
|
||||
|
||||
a = Analysis([EXE_NAME],
|
||||
pathex=PATH_EXE,
|
||||
binaries=None,
|
||||
datas=ui_files,
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher)
|
||||
|
||||
pyz = PYZ(a.pure,
|
||||
a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
exclude_binaries=True,
|
||||
name='DemonEditor',
|
||||
debug=False,
|
||||
strip=STRIP,
|
||||
upx=True,
|
||||
console=False)
|
||||
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=STRIP,
|
||||
upx=True,
|
||||
name='DemonEditor')
|
||||
|
||||
app = BUNDLE(coll,
|
||||
name='DemonEditor.app',
|
||||
icon='icon.icns',
|
||||
bundle_identifier=None,
|
||||
info_plist={
|
||||
'NSPrincipalClass': 'NSApplication',
|
||||
'CFBundleName': 'DemonEditor',
|
||||
'CFBundleDisplayName': 'DemonEditor',
|
||||
'CFBundleGetInfoString': "Enigma2 channel and satellites editor",
|
||||
'CFBundleShortVersionString': "0.4.8 Pre-alpha",
|
||||
'NSHumanReadableCopyright': u"Copyright © 2020, Dmitriy Yefremov"
|
||||
})
|
||||
126
README.md
126
README.md
@@ -1,12 +1,9 @@
|
||||
# <img src="app/ui/icons/hicolor/96x96/apps/demon-editor.png" width="32" /> DemonEditor
|
||||
[](LICENSE) 
|
||||
### Enigma2 channel and satellite list editor for GNU/Linux.
|
||||
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
|
||||
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc).
|
||||
|
||||

|
||||
|
||||
## Main features of the program
|
||||
[](LICENSE)
|
||||
## Enigma2 channel and satellites list editor for macOS (experimental).
|
||||
**The functionality and performance of this version may be different from the Linux version!
|
||||
Not all features can be supported and tested!**
|
||||
### Main features of the program:
|
||||
* Editing bouquets, channels, satellites.
|
||||
* Import function.
|
||||
* Backup function.
|
||||
@@ -15,69 +12,62 @@ Focused on the convenience of working in lists from the keyboard. The mouse is a
|
||||
* Downloading of picons and updating of satellites (transponders) from the web.
|
||||
* Import to bouquet(Neutrino WEBTV) from m3u.
|
||||
* Export of bouquets with IPTV services in m3u.
|
||||
* Assignment of EPG from DVB or XML for IPTV services (only Enigma2, experimental).
|
||||
* Assignment of EPGs from DVB or XML for IPTV services (only Enigma2, experimental).
|
||||
* Preview (playback) of IPTV or other streams directly from the bouquet list (should be installed [VLC](https://www.videolan.org/vlc/)).
|
||||
|
||||
#### Keyboard shortcuts
|
||||
* **Ctrl + X** - only in bouquet list.
|
||||
* **Ctrl + C** - only in services list.
|
||||
Clipboard is **"rubber"**. There is an accumulation before the insertion!
|
||||
* **Ctrl + Insert** - copies the selected channels from the main list to the the bouquet beginning
|
||||
or inserts (creates) a new bouquet.
|
||||
* **Ctrl + BackSpace** - copies the selected channels from the main list to the bouquet end.
|
||||
* **Ctrl + E** - edit.
|
||||
* **Ctrl + R, F2** - rename.
|
||||
* **Ctrl + S, T** in Satellites edit tool for create satellite or transponder.
|
||||
* **Ctrl + L** - parental lock.
|
||||
* **Ctrl + H** - hide/skip.
|
||||
* **Ctrl + P** - start play IPTV or other stream in the bouquet list.
|
||||
* **Ctrl + Z** - switch(**zap**) the channel(works when the HTTP API is enabled, Enigma2 only).
|
||||
* **Ctrl + W** - switch to the channel and watch in the program.
|
||||
* **Space** - select/deselect.
|
||||
* **Left/Right** - remove selection.
|
||||
* **Ctrl + Up, Down, PageUp, PageDown, Home, End**- move selected items in the list.
|
||||
* **Ctrl + O** - (re)load user data from current dir.
|
||||
* **Ctrl + D** - load data from receiver.
|
||||
* **Ctrl + U/B** - upload data/bouquets to receiver.
|
||||
* **Ctrl + I** - extra info, details.
|
||||
* **Ctrl + F** - show/hide search bar.
|
||||
* **Ctrl + Shift + F** - show/hide filter bar.
|
||||
|
||||
For **multiple** selection with the mouse, press and hold the **Ctrl** key!
|
||||
#### Keyboard shortcuts:
|
||||
* **⌘ + X** - only in bouquet list.
|
||||
* **⌘ + C** - only in services list.
|
||||
Clipboard is **"rubber"**. There is an accumulation before the insertion!
|
||||
* **⌘ + E** - edit.
|
||||
* **⌘ + R, F2** - rename.
|
||||
* **⌘ + S, T** in Satellites edit tool for create satellite or transponder.
|
||||
* **⌘ + L** - parental lock.
|
||||
* **⌘ + H** - hide/skip.
|
||||
* **⌘ + P** - start play IPTV or other stream in the bouquet list.
|
||||
* **⌘ + Z** - switch(**zap**) the channel(works when the HTTP API is enabled, Enigma2 only).
|
||||
* **⌘ + W** - switch to the channel and watch in the program.
|
||||
* **⌘ + Up/Down** - move selected items in the list.
|
||||
* **⌘ + O** - (re)load user data from current dir.
|
||||
* **⌘ + D** - load data from receiver.
|
||||
* **⌘ + U/B** - upload data/bouquets to receiver.
|
||||
* **⌘ + F** - show/hide search bar.
|
||||
* **⇧ + ⌘ + F** - show/hide filter bar.
|
||||
* **Left/Right** - remove selection.
|
||||
|
||||
## Minimum requirements
|
||||
*Python >= 3.5.2, GTK+ >= 3.16 with PyGObject bindings, python3-requests.*
|
||||
|
||||
***Optional:** python3-gi-cairo, python3-pil.*
|
||||
## Installation and Launch
|
||||
* ### Linux
|
||||
To start the program, in most cases it is enough to download the [archive](https://github.com/DYefremov/DemonEditor/archive/master.zip), unpack and run it by
|
||||
double clicking on DemonEditor.desktop in the root directory, or launching from the console
|
||||
with the command:
|
||||
```./start.py```
|
||||
Extra folders can be deleted, excluding the *app* folder and root files like *DemonEditor.desktop* and *start.py*!
|
||||
For multiple mouse selection (including Drag and Drop), press and hold the **⌘** key!
|
||||
|
||||
To create a simple **debian package**, you can use the *build-deb.sh.*
|
||||
Users of **LTS** versions of [Ubuntu](https://ubuntu.com/) or distributions based on them can use [PPA](https://launchpad.net/~dmitriy-yefremov/+archive/ubuntu/demon-editor) repository.
|
||||
* ### macOS (experimental)
|
||||
**This program can also be run on macOS.**
|
||||
To work in this OS, you must use a [separate branch](https://github.com/DYefremov/DemonEditor/tree/experimental-mac).
|
||||
**The functionality and performance of this version may be different from the Linux version!**
|
||||
|
||||
## Important
|
||||
The program is tested only with [openATV](https://www.opena.tv/) image and **Formuler F1** receiver in [Linux Mint](https://linuxmint.com/) (MATE 64-bit) distribution!
|
||||
### Minimum requirements:
|
||||
Python >= **3.5**, GTK+ >= **3.16**, pygobject3, adwaita-icon-theme, python3-requests.
|
||||
#### Installation:
|
||||
```brew install python3 gtk+3 pygobject3 adwaita-icon-theme```
|
||||
```pip3 install requests```
|
||||
#### Optional:
|
||||
```brew install wget imagemagick```
|
||||
```pip3 install pyobjc```
|
||||
#### Launching:
|
||||
To start the program, just download the archive, unpack and run it from the terminal with the command: ```./start.py```
|
||||
### Building standalone application:
|
||||
Install [PyInstaller](https://www.pyinstaller.org/) with the command from the terminal:
|
||||
```pip3 install pyinstaller```
|
||||
and in th root dir run command:
|
||||
```pyinstaller DemonEditor.spec```
|
||||
### Standalone package:
|
||||
Users of the **64-bit version of the OS** can download a ready-made package from [here](https://github.com/DYefremov/DemonEditor/raw/experimental-mac/dist/DemonEditor.app.zip).
|
||||
Just unpack and run. Recommended copy the bundle to the **Application** directory.
|
||||
Perhaps in the security settings it will be necessary to allow the launch of this application!
|
||||
### Note:
|
||||
THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY.
|
||||
AUTHOR IS NOT LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY CONNECTION WITH THIS SOFTWARE.
|
||||
This package may contain components distributed under the GPL [v3](http://www.gnu.org/licenses/gpl-3.0.html) or lower license.
|
||||
By downloading this package you agree to the terms of this [license](http://www.gnu.org/licenses/gpl-3.0.html) and the possible inconvenience associated with this!
|
||||
|
||||
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2.
|
||||
**The package may not contain all the latest changes!**
|
||||
### Important:
|
||||
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2!
|
||||
Main supported *lamedb* format is version **4**. Versions **3** and **5** has only **experimental** support!
|
||||
For version **3** is only read mode available. When saving, version **4** format is used instead.
|
||||
For version **3** is only read mode available. When saving, version **4** format is used instead!
|
||||
|
||||
When using the multiple import feature, from *lamedb* will be taken data **only for channels that are in the selected bouquets!**
|
||||
If you need full set of the data, including *[satellites, terrestrial, cables].xml* (current files will be overwritten),
|
||||
just load your data via *"File/Open"* and press *"Save"*. When importing separate bouquet files, only those services
|
||||
(excluding IPTV) that are in the **current open lamedb** (main list of services) will be imported.
|
||||
#### Command line arguments:
|
||||
* **-l** - write logs to file.
|
||||
* **-d on/off** - turn on/off debug mode. Allows to display more information in the logs.
|
||||
|
||||
## License
|
||||
Licensed under the [MIT](LICENSE) license.
|
||||
When using the multiple import feature, from *lamedb* will be taken data **only for channels that are in the
|
||||
selected bouquets!** If you need full set of the data, including *[satellites, terrestrial, cables].xml* (current files will be overwritten),
|
||||
just load your data via *"File/Open"* and press *"Save"*. When importing separate bouquet files, only those services
|
||||
(excluding IPTV) that are in the **current open lamedb** (main list of services) will be imported.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
theme: jekyll-theme-slate
|
||||
theme: jekyll-theme-cayman
|
||||
show_downloads: true
|
||||
|
||||
@@ -20,14 +20,8 @@ def init_logger():
|
||||
log("Logging is enabled.", level=logging.INFO)
|
||||
|
||||
|
||||
def log(message, level=logging.ERROR, debug=False, fmt_message="{}"):
|
||||
""" The main logging function. """
|
||||
logger = logging.getLogger(_LOGGER_NAME)
|
||||
if debug:
|
||||
from traceback import format_exc
|
||||
logger.log(level, fmt_message.format(format_exc()))
|
||||
else:
|
||||
logger.log(level, message)
|
||||
def log(message, level=logging.ERROR):
|
||||
logging.getLogger(_LOGGER_NAME).log(level, message)
|
||||
|
||||
|
||||
def run_idle(func):
|
||||
|
||||
@@ -35,6 +35,24 @@ class DownloadType(Enum):
|
||||
EPG = 5
|
||||
|
||||
|
||||
class HttpRequestType(Enum):
|
||||
ZAP = "zap?sRef="
|
||||
INFO = "about"
|
||||
SIGNAL = "signal"
|
||||
STREAM = "stream.m3u?ref="
|
||||
STREAM_CURRENT = "streamcurrent.m3u"
|
||||
CURRENT = "getcurrent"
|
||||
TEST = None
|
||||
TOKEN = "session"
|
||||
PLAY = "mediaplayerplay?file="
|
||||
PLAYER_LIST = "mediaplayerlist?path=playlist"
|
||||
PLAYER_PLAY = "mediaplayercmd?command=play"
|
||||
PLAYER_NEXT = "mediaplayercmd?command=next"
|
||||
PLAYER_PREV = "mediaplayercmd?command=previous"
|
||||
PLAYER_STOP = "mediaplayercmd?command=stop"
|
||||
PLAYER_REMOVE = "mediaplayerremove?file="
|
||||
|
||||
|
||||
class TestException(Exception):
|
||||
pass
|
||||
|
||||
@@ -43,7 +61,7 @@ class HttpApiException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def download_data(*, settings, download_type=DownloadType.ALL, callback=print, files_filter=None):
|
||||
def download_data(*, settings, download_type=DownloadType.ALL, callback=print):
|
||||
with FTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
|
||||
ftp.encoding = "utf-8"
|
||||
callback("FTP OK.\n")
|
||||
@@ -53,7 +71,8 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=print, f
|
||||
if download_type is DownloadType.ALL or download_type is DownloadType.BOUQUETS:
|
||||
ftp.cwd(settings.services_path)
|
||||
file_list = BQ_FILES_LIST + DATA_FILES_LIST if download_type is DownloadType.ALL else BQ_FILES_LIST
|
||||
download_files(ftp, save_path, file_list, callback)
|
||||
for file in filter(lambda f: f.endswith(file_list), ftp.nlst()):
|
||||
download_file(ftp, file, save_path, callback)
|
||||
# *.xml and webtv
|
||||
if download_type in (DownloadType.ALL, DownloadType.SATELLITES):
|
||||
download_xml(ftp, save_path, settings.satellites_xml_path, STC_XML_FILE, callback)
|
||||
@@ -63,7 +82,7 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=print, f
|
||||
if download_type is DownloadType.PICONS:
|
||||
picons_path = settings.picons_local_path
|
||||
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
|
||||
download_picons(ftp, settings.picons_path, picons_path, callback, files_filter)
|
||||
download_picons(ftp, settings.picons_path, picons_path, callback)
|
||||
# epg.dat
|
||||
if download_type is DownloadType.EPG:
|
||||
stb_path = settings.services_path
|
||||
@@ -73,13 +92,14 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=print, f
|
||||
save_path = epg_options.get("epg_dat_path", save_path)
|
||||
|
||||
ftp.cwd(stb_path)
|
||||
download_files(ftp, save_path, "epg.dat", callback)
|
||||
for file in filter(lambda f: f.endswith("epg.dat"), ftp.nlst()):
|
||||
download_file(ftp, file, save_path, callback)
|
||||
|
||||
callback("\nDone.\n")
|
||||
|
||||
|
||||
def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False,
|
||||
callback=print, done_callback=None, use_http=False, files_filter=None):
|
||||
callback=print, done_callback=None, use_http=False):
|
||||
s_type = settings.setting_type
|
||||
data_path = settings.data_local_path
|
||||
host = settings.host
|
||||
@@ -98,8 +118,6 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
|
||||
message = "All user data will be reloaded!"
|
||||
elif download_type is DownloadType.SATELLITES:
|
||||
message = "Satellites.xml file will be updated!"
|
||||
elif download_type is DownloadType.PICONS:
|
||||
message = "Picons will be updated!"
|
||||
|
||||
params = urlencode({"text": message, "type": 2, "timeout": 5})
|
||||
ht.send((url + "message?{}".format(params), "Sending info message... "))
|
||||
@@ -109,17 +127,14 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
|
||||
ht.send((url + "powerstate?newstate=0", "Toggle Standby "))
|
||||
time.sleep(2)
|
||||
else:
|
||||
if download_type is not DownloadType.PICONS:
|
||||
# telnet
|
||||
tn = telnet(host=host,
|
||||
user=settings.telnet_user,
|
||||
password=settings.telnet_password,
|
||||
timeout=settings.telnet_timeout)
|
||||
next(tn)
|
||||
# terminate enigma or neutrino
|
||||
callback("Telnet initialization ...\n")
|
||||
tn.send("init 4")
|
||||
callback("Stopping GUI...\n")
|
||||
# telnet
|
||||
tn = telnet(host=host,
|
||||
user=settings.telnet_user,
|
||||
password=settings.telnet_password,
|
||||
timeout=settings.telnet_timeout)
|
||||
next(tn)
|
||||
# terminate enigma or neutrino
|
||||
tn.send("init 4")
|
||||
|
||||
with FTP(host=host, user=settings.user, passwd=settings.password) as ftp:
|
||||
ftp.encoding = "utf-8"
|
||||
@@ -147,12 +162,11 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
|
||||
upload_files(ftp, data_path, DATA_FILES_LIST, callback)
|
||||
|
||||
if download_type is DownloadType.PICONS:
|
||||
upload_picons(ftp, settings.picons_local_path, settings.picons_path, callback, files_filter)
|
||||
upload_picons(ftp, settings.picons_local_path, settings.picons_path, callback)
|
||||
|
||||
if tn and not use_http:
|
||||
# resume enigma or restart neutrino
|
||||
tn.send("init 3" if s_type is SettingsType.ENIGMA_2 else "init 6")
|
||||
callback("Starting...\n" if s_type is SettingsType.ENIGMA_2 else "Rebooting...\n")
|
||||
elif ht and use_http:
|
||||
if download_type is DownloadType.BOUQUETS:
|
||||
ht.send((url + "servicelistreload?mode=2", "Reloading Userbouquets."))
|
||||
@@ -186,10 +200,11 @@ def upload_files(ftp, data_path, file_list, callback):
|
||||
def remove_unused_bouquets(ftp, callback):
|
||||
files = []
|
||||
ftp.dir(files.append)
|
||||
bq_files = ("tv", "radio", "bouquets.xml", "ubouquets.xml")
|
||||
|
||||
for file in filter(lambda f: f.endswith(bq_files), map(lambda f: f.split()[-1], map(str.rstrip, files))):
|
||||
callback("Deleting file: {}. Status: {}\n".format(file, ftp.delete(file)))
|
||||
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_files, callback):
|
||||
@@ -202,12 +217,12 @@ def upload_xml(ftp, data_path, xml_path, xml_files, callback):
|
||||
def download_xml(ftp, data_path, xml_path, xml_files, callback):
|
||||
""" Used for download *.xml files. """
|
||||
ftp.cwd(xml_path)
|
||||
download_files(ftp, data_path, xml_files, callback)
|
||||
list(map(lambda f: download_file(ftp, f, data_path, callback), (f for f in ftp.nlst() if f.endswith(xml_files))))
|
||||
|
||||
|
||||
# ***************** Picons *******************#
|
||||
|
||||
def upload_picons(ftp, src, dest, callback, files_filter=None):
|
||||
def upload_picons(ftp, src, dest, callback):
|
||||
try:
|
||||
ftp.cwd(dest)
|
||||
except error_perm as e:
|
||||
@@ -215,25 +230,25 @@ def upload_picons(ftp, src, dest, callback, files_filter=None):
|
||||
ftp.mkd(dest) # if not exist
|
||||
ftp.cwd(dest)
|
||||
|
||||
for file_name in filter(picons_filter_function(files_filter), os.listdir(src)):
|
||||
send_file(file_name, src, ftp, callback)
|
||||
delete_picons(ftp, callback)
|
||||
|
||||
for file_name in os.listdir(src):
|
||||
if file_name.endswith(PICONS_SUF):
|
||||
send_file(file_name, src, ftp, callback)
|
||||
|
||||
|
||||
def download_picons(ftp, src, dest, callback, files_filter=None):
|
||||
def download_picons(ftp, src, dest, callback):
|
||||
try:
|
||||
ftp.cwd(src)
|
||||
except error_perm as e:
|
||||
callback(str(e))
|
||||
return
|
||||
|
||||
files = []
|
||||
ftp.dir(files.append)
|
||||
|
||||
for file in filter(picons_filter_function(files_filter), map(lambda f: f.split()[-1], map(str.rstrip, files))):
|
||||
for file in filter(lambda f: f.endswith(PICONS_SUF), ftp.nlst()):
|
||||
download_file(ftp, file, dest, callback)
|
||||
|
||||
|
||||
def delete_picons(ftp, callback, dest=None, files_filter=None):
|
||||
def delete_picons(ftp, callback, dest=None):
|
||||
if dest:
|
||||
try:
|
||||
ftp.cwd(dest)
|
||||
@@ -243,33 +258,22 @@ def delete_picons(ftp, callback, dest=None, files_filter=None):
|
||||
|
||||
files = []
|
||||
ftp.dir(files.append)
|
||||
|
||||
for file in filter(picons_filter_function(files_filter), map(lambda f: f.split()[-1], map(str.rstrip, files))):
|
||||
callback("Delete file: {}. Status: {}\n".format(file, ftp.delete(file)))
|
||||
for file in files:
|
||||
name = str(file).strip()
|
||||
if name.endswith(PICONS_SUF):
|
||||
name = name.split()[-1]
|
||||
callback("Delete file: {}. Status: {}\n".format(name, ftp.delete(name)))
|
||||
|
||||
|
||||
def remove_picons(*, settings, callback, done_callback=None, files_filter=None):
|
||||
def remove_picons(*, settings, callback, done_callback=None):
|
||||
with FTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
|
||||
ftp.encoding = "utf-8"
|
||||
callback("FTP OK.\n")
|
||||
delete_picons(ftp, callback, settings.picons_path, files_filter)
|
||||
delete_picons(ftp, callback, settings.picons_path)
|
||||
if done_callback:
|
||||
done_callback()
|
||||
|
||||
|
||||
def picons_filter_function(files_filter=None):
|
||||
return lambda f: f in files_filter if files_filter else f.endswith(PICONS_SUF)
|
||||
|
||||
|
||||
def download_files(ftp, save_path, file_list, callback):
|
||||
""" Downloads files from the receiver via FTP. """
|
||||
files = []
|
||||
ftp.dir(files.append)
|
||||
|
||||
for file in map(lambda f: f.split()[-1], filter(lambda s: s.endswith(file_list), map(str.rstrip, files))):
|
||||
download_file(ftp, file, save_path, callback)
|
||||
|
||||
|
||||
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))))
|
||||
@@ -292,7 +296,7 @@ def http(user, password, url, callback, use_ssl=False):
|
||||
|
||||
while True:
|
||||
url, message = yield
|
||||
resp = get_response(HttpAPI.Request.TEST, url, data).get("e2statetext", None)
|
||||
resp = get_response(HttpRequestType.TEST, url, data).get("e2statetext", None)
|
||||
callback("HTTP: {} {}\n".format(message, "Successful." if resp and message else ""))
|
||||
|
||||
|
||||
@@ -305,11 +309,11 @@ def telnet(host, port=23, user="", password="", timeout=5):
|
||||
time.sleep(1)
|
||||
command = yield
|
||||
if user != "":
|
||||
tn.read_until(b"login: ", timeout)
|
||||
tn.read_until(b"login: ")
|
||||
tn.write(user.encode("utf-8") + b"\n")
|
||||
time.sleep(timeout)
|
||||
if password != "":
|
||||
tn.read_until(b"Password: ", timeout)
|
||||
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"))
|
||||
@@ -326,124 +330,46 @@ def telnet(host, port=23, user="", password="", timeout=5):
|
||||
class HttpAPI:
|
||||
__MAX_WORKERS = 4
|
||||
|
||||
class Request(Enum):
|
||||
ZAP = "zap?sRef="
|
||||
INFO = "about"
|
||||
SIGNAL = "signal"
|
||||
STREAM = "stream.m3u?ref="
|
||||
STREAM_CURRENT = "streamcurrent.m3u"
|
||||
CURRENT = "getcurrent"
|
||||
TEST = None
|
||||
TOKEN = "session"
|
||||
# Player
|
||||
PLAY = "mediaplayerplay?file="
|
||||
PLAYER_LIST = "mediaplayerlist?path=playlist"
|
||||
PLAYER_PLAY = "mediaplayercmd?command=play"
|
||||
PLAYER_NEXT = "mediaplayercmd?command=next"
|
||||
PLAYER_PREV = "mediaplayercmd?command=previous"
|
||||
PLAYER_STOP = "mediaplayercmd?command=stop"
|
||||
PLAYER_REMOVE = "mediaplayerremove?file="
|
||||
# Remote control
|
||||
POWER = "powerstate?newstate="
|
||||
REMOTE = "remotecontrol?command="
|
||||
VOL = "vol?set=set"
|
||||
# EPG
|
||||
EPG = "epgservice?sRef="
|
||||
# Timer
|
||||
TIMER = ""
|
||||
TIMER_LIST = "timerlist"
|
||||
# Screenshot
|
||||
GRUB = "grab?format=jpg&"
|
||||
|
||||
class Remote(str, Enum):
|
||||
""" Args for HttpRequestType [REMOTE] class. """
|
||||
UP = "103"
|
||||
LEFT = "105"
|
||||
RIGHT = "106"
|
||||
DOWN = "108"
|
||||
MENU = "139"
|
||||
EXIT = "174"
|
||||
OK = "352"
|
||||
RED = "398"
|
||||
GREEN = "399"
|
||||
YELLOW = "400"
|
||||
BLUE = "401"
|
||||
|
||||
class Power(str, Enum):
|
||||
""" Args for HttpRequestType [POWER] class. """
|
||||
TOGGLE_STANDBY = "0"
|
||||
DEEP_STANDBY = "1"
|
||||
REBOOT = "2"
|
||||
RESTART_GUI = "3"
|
||||
WAKEUP = "4"
|
||||
STANDBY = "5"
|
||||
|
||||
def __init__(self, settings):
|
||||
from concurrent.futures import ThreadPoolExecutor as PoolExecutor
|
||||
self._executor = PoolExecutor(max_workers=self.__MAX_WORKERS)
|
||||
|
||||
self._settings = settings
|
||||
self._shutdown = False
|
||||
self._session_id = 0
|
||||
self._main_url = None
|
||||
self._base_url = None
|
||||
self._data = None
|
||||
self._is_owif = True
|
||||
self.init()
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor as PoolExecutor
|
||||
self._executor = PoolExecutor(max_workers=self.__MAX_WORKERS)
|
||||
|
||||
def send(self, req_type, ref, callback=print, ref_prefix=""):
|
||||
if self._shutdown:
|
||||
return
|
||||
|
||||
url = self._base_url + req_type.value
|
||||
data = self._data
|
||||
|
||||
if req_type is self.Request.ZAP or req_type is self.Request.STREAM:
|
||||
if req_type is HttpRequestType.ZAP or req_type is HttpRequestType.STREAM:
|
||||
url += urllib.parse.quote(ref)
|
||||
elif req_type is self.Request.PLAY or req_type is self.Request.PLAYER_REMOVE:
|
||||
elif req_type is HttpRequestType.PLAY or req_type is HttpRequestType.PLAYER_REMOVE:
|
||||
url += "{}{}".format(ref_prefix, urllib.parse.quote(ref).replace("%3A", "%253A"))
|
||||
elif req_type is self.Request.GRUB:
|
||||
data = None # Must be disabled for token-based security.
|
||||
url = "{}/{}{}".format(self._main_url, req_type.value, ref)
|
||||
elif req_type in (self.Request.REMOTE,
|
||||
self.Request.POWER,
|
||||
self.Request.VOL,
|
||||
self.Request.EPG,
|
||||
self.Request.TIMER):
|
||||
url += ref
|
||||
|
||||
def done_callback(f):
|
||||
callback(f.result())
|
||||
|
||||
future = self._executor.submit(get_response, req_type, url, data)
|
||||
future = self._executor.submit(get_response, req_type, url, self._data)
|
||||
future.add_done_callback(done_callback)
|
||||
|
||||
@run_task
|
||||
def init(self):
|
||||
user, password = self._settings.http_user, self._settings.http_password
|
||||
use_ssl = self._settings.http_use_ssl
|
||||
self._main_url = "http{}://{}:{}".format("s" if use_ssl else "", self._settings.host, self._settings.http_port)
|
||||
self._base_url = "{}/web/".format(self._main_url)
|
||||
init_auth(user, password, self._main_url, use_ssl)
|
||||
url = "{}/web/{}".format(self._main_url, self.Request.TOKEN.value)
|
||||
url = "http{}://{}:{}".format("s" if use_ssl else "", self._settings.host, self._settings.http_port)
|
||||
self._base_url = "{}/web/".format(url)
|
||||
init_auth(user, password, url, use_ssl)
|
||||
url = "{}/web/{}".format(url, HttpRequestType.TOKEN.value)
|
||||
s_id = get_session_id(user, password, url)
|
||||
if s_id != "0":
|
||||
self._data = urllib.parse.urlencode({"user": user, "password": password, "sessionid": s_id}).encode("utf-8")
|
||||
|
||||
self.send(self.Request.INFO, None, self.init_callback)
|
||||
|
||||
def init_callback(self, info):
|
||||
if info:
|
||||
version = info.get("e2webifversion", "").upper()
|
||||
self._is_owif = "OWIF" in version
|
||||
version_info = "Web Interface version: {}".format(version) if version else ""
|
||||
log("HTTP API initialized... {}".format(version_info))
|
||||
|
||||
@property
|
||||
def is_owif(self):
|
||||
""" Returns true if the web interface is OpenWebif. """
|
||||
return self._is_owif
|
||||
|
||||
@run_task
|
||||
def close(self):
|
||||
self._shutdown = True
|
||||
@@ -453,30 +379,22 @@ class HttpAPI:
|
||||
def get_response(req_type, url, data=None):
|
||||
try:
|
||||
with urlopen(Request(url, data=data), timeout=10) as f:
|
||||
if req_type is HttpAPI.Request.STREAM or req_type is HttpAPI.Request.STREAM_CURRENT:
|
||||
if req_type is HttpRequestType.STREAM or req_type is HttpRequestType.STREAM_CURRENT:
|
||||
return {"m3u": f.read().decode("utf-8")}
|
||||
elif req_type is HttpAPI.Request.GRUB:
|
||||
return {"img_data": f.read()}
|
||||
elif req_type is HttpAPI.Request.CURRENT:
|
||||
elif req_type is HttpRequestType.CURRENT:
|
||||
for el in ETree.fromstring(f.read().decode("utf-8")).iter("e2event"):
|
||||
return {el.tag: el.text for el in el.iter()} # return first[current] event from the list
|
||||
elif req_type is HttpAPI.Request.PLAYER_LIST:
|
||||
elif req_type is HttpRequestType.PLAYER_LIST:
|
||||
return [{el.tag: el.text for el in el.iter()} for el in
|
||||
ETree.fromstring(f.read().decode("utf-8")).iter("e2file")]
|
||||
elif req_type is HttpAPI.Request.EPG:
|
||||
return {"event_list": [{el.tag: el.text for el in el.iter()} for el in
|
||||
ETree.fromstring(f.read().decode("utf-8")).iter("e2event")]}
|
||||
elif req_type is HttpAPI.Request.TIMER_LIST:
|
||||
return {"timer_list": [{el.tag: el.text for el in el.iter()} for el in
|
||||
ETree.fromstring(f.read().decode("utf-8")).iter("e2timer")]}
|
||||
else:
|
||||
return {el.tag: el.text for el in ETree.fromstring(f.read().decode("utf-8")).iter()}
|
||||
except HTTPError as e:
|
||||
if req_type is HttpAPI.Request.TEST:
|
||||
if req_type is HttpRequestType.TEST:
|
||||
raise e
|
||||
return {"error_code": e.code}
|
||||
except (URLError, RemoteDisconnected, ConnectionResetError) as e:
|
||||
if req_type is HttpAPI.Request.TEST:
|
||||
if req_type is HttpRequestType.TEST:
|
||||
raise e
|
||||
except ETree.ParseError as e:
|
||||
log("Parsing response error: {}".format(e))
|
||||
@@ -503,11 +421,11 @@ def init_auth(user, password, url, use_ssl=False):
|
||||
|
||||
def get_session_id(user, password, url):
|
||||
data = urllib.parse.urlencode(dict(user=user, password=password)).encode("utf-8")
|
||||
return get_response(HttpAPI.Request.TOKEN, url, data=data).get("e2sessionid", "0")
|
||||
return get_response(HttpRequestType.TOKEN, url, data=data).get("e2sessionid", "0")
|
||||
|
||||
|
||||
def get_post_data(base_url, password, user):
|
||||
s_id = get_session_id(user, password, "{}/web/{}".format(base_url, HttpAPI.Request.TOKEN.value))
|
||||
s_id = get_session_id(user, password, "{}/web/{}".format(base_url, HttpRequestType.TOKEN.value))
|
||||
data = None
|
||||
if s_id != "0":
|
||||
data = urllib.parse.urlencode({"user": user, "password": password, "sessionid": s_id}).encode("utf-8")
|
||||
@@ -533,7 +451,7 @@ def test_http(host, port, user, password, timeout=5, use_ssl=False, skip_message
|
||||
data = get_post_data(base_url, password, user)
|
||||
|
||||
try:
|
||||
return get_response(HttpAPI.Request.TEST, "{}/web/{}".format(base_url, params), data).get("e2statetext", "")
|
||||
return get_response(HttpRequestType.TEST, "{}/web/{}".format(base_url, params), data).get("e2statetext", "")
|
||||
except (RemoteDisconnected, URLError, HTTPError) as e:
|
||||
raise TestException(e)
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ class BqServiceType(Enum):
|
||||
DEFAULT = "DEFAULT"
|
||||
IPTV = "IPTV"
|
||||
MARKER = "MARKER" # 64
|
||||
SPACE = "SPACE" # 832 [hidden marker]
|
||||
|
||||
|
||||
Bouquet = namedtuple("Bouquet", ["name", "type", "services", "locked", "hidden"])
|
||||
@@ -136,7 +135,7 @@ TRANSMISSION_MODE = {"0": "2k", "1": "8k", "2": "Auto", "3": "4k", "4": "1k", "5
|
||||
GUARD_INTERVAL = {"0": "1/32", "1": "1/16", "2": "1/8", "3": "1/4", "4": "Auto", "5": "1/128", "6": "19/128",
|
||||
"7": "19/256"}
|
||||
|
||||
HIERARCHY = {"0": "None", "1": "1", "2": "2", "3": "4", "4": "Auto"}
|
||||
HIERARCHY = {"0": "None", "1": "1", "2": "2", "3": "4", "4": "Auto"}
|
||||
|
||||
T_FEC = {"0": "1/2", "1": "2/3", "2": "3/4", "3": "5/6", "4": "7/8", "5": "Auto", "6": "6/7", "7": "8/9"}
|
||||
|
||||
@@ -146,8 +145,10 @@ T_SYSTEM = {"0": "DVB-T", "1": "DVB-T2", "-1": "DVB-T/T2"}
|
||||
C_MODULATION = {"0": "Auto", "1": "QAM16", "2": "QAM32", "3": "QAM64", "4": "QAM128", "5": "QAM256"}
|
||||
|
||||
# CAS
|
||||
CAS = {"C:26": "BISS", "C:0B": "Conax", "C:06": "Irdeto", "C:18": "Nagravision", "C:05": "Viaccess", "C:01": "SECA",
|
||||
"C:0E": "PowerVu", "C:4A": "DRE-Crypt", "C:7B": "DRE-Crypt", "C:56": "Verimatrix", "C:09": "VideoGuard"}
|
||||
CAS = {"C:2600": "BISS", "C:0b00": "Conax", "C:0b01": "Conax", "C:0b02": "Conax", "C:0baa": "Conax", "C:0602": "Irdeto",
|
||||
"C:0604": "Irdeto", "C:0606": "Irdeto", "C:0608": "Irdeto", "C:0622": "Irdeto", "C:0626": "Irdeto",
|
||||
"C:0664": "Irdeto", "C:0614": "Irdeto", "C:0692": "Irdeto", "C:1801": "Nagravision", "C:0500": "Viaccess",
|
||||
"C:0E00": "PowerVu", "C:4ae0": "DRE-Crypt", "C:4ae1": "DRE-Crypt", "C:7be1": "DRE-Crypt"}
|
||||
|
||||
# 'on' attribute 0070(hex) = 112(int) = ONID(ONID-TID on www.lyngsat.com)
|
||||
PROVIDER = {112: "HTB+", 253: "Tricolor TV"}
|
||||
|
||||
@@ -12,7 +12,6 @@ def get_blacklist(path):
|
||||
with open(path + __FILE_NAME, "r") as file:
|
||||
# filter empty values and "\n"
|
||||
return {*list(filter(None, (x.strip() for x in file.readlines())))}
|
||||
return {}
|
||||
|
||||
|
||||
def write_blacklist(path, channels):
|
||||
|
||||
@@ -24,8 +24,7 @@ def write_bouquets(path, bouquets, force_bq_names=False):
|
||||
srv_line = '#SERVICE 1:7:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
|
||||
line = []
|
||||
pattern = re.compile("[^\\w_()]+")
|
||||
m_index = [0]
|
||||
s_index = [0]
|
||||
current_marker = [0]
|
||||
|
||||
for bqs in bouquets:
|
||||
line.clear()
|
||||
@@ -38,30 +37,24 @@ def write_bouquets(path, bouquets, force_bq_names=False):
|
||||
else:
|
||||
bq_name = re.sub(pattern, "_", bq.name) if force_bq_names else "de{0:02d}".format(index)
|
||||
line.append(srv_line.format(2 if bq.type == BqType.RADIO.value else 1, bq_name, bq.type))
|
||||
write_bouquet(path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services, m_index, s_index)
|
||||
write_bouquet(path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services, current_marker)
|
||||
|
||||
with open(path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
|
||||
file.writelines(line)
|
||||
|
||||
|
||||
def write_bouquet(path, name, services, current_marker, current_space):
|
||||
def write_bouquet(path, name, services, current_marker):
|
||||
bouquet = ["#NAME {}\n".format(name)]
|
||||
marker = "#SERVICE 1:64:{:X}:0:0:0:0:0:0:0::{}\n"
|
||||
space = "#SERVICE 1:832:D:{}:0:0:0:0:0:0:\n"
|
||||
|
||||
for srv in services:
|
||||
s_type = srv.service_type
|
||||
|
||||
if s_type == BqServiceType.IPTV.name:
|
||||
if srv.service_type == BqServiceType.IPTV.name:
|
||||
bouquet.append("#SERVICE {}\n".format(srv.fav_id.strip()))
|
||||
elif s_type == BqServiceType.MARKER.name:
|
||||
elif srv.service_type == BqServiceType.MARKER.name:
|
||||
m_data = srv.fav_id.strip().split(":")
|
||||
m_data[2] = current_marker[0]
|
||||
current_marker[0] += 1
|
||||
bouquet.append(marker.format(m_data[2], m_data[-1]))
|
||||
elif s_type == BqServiceType.SPACE.name:
|
||||
bouquet.append(space.format(current_space[0]))
|
||||
current_space[0] += 1
|
||||
else:
|
||||
data = to_bouquet_id(srv)
|
||||
if srv.service:
|
||||
@@ -82,39 +75,28 @@ def to_bouquet_id(srv):
|
||||
return "{}:0:{:X}:{}:0:0:0:".format(1, data_type, srv.fav_id)
|
||||
|
||||
|
||||
def get_bouquet(path, bq_name, bq_type):
|
||||
def get_bouquet(path, name, bq_type):
|
||||
""" Parsing services ids from bouquet file. """
|
||||
with open(path + "userbouquet.{}.{}".format(bq_name, bq_type), encoding="utf-8", errors="replace") as file:
|
||||
with open(path + "userbouquet.{}.{}".format(name, bq_type), encoding="utf-8", errors="replace") as file:
|
||||
chs_list = file.read()
|
||||
services = []
|
||||
srvs = list(filter(None, chs_list.split("\n#SERVICE"))) # filtering ['']
|
||||
# May come across empty[wrong] files!
|
||||
if not srvs:
|
||||
log("Bouquet file 'userbouquet.{}.{}' is empty or wrong!".format(bq_name, bq_type))
|
||||
return "{} [empty]".format(bq_name), services
|
||||
|
||||
bq_name = srvs.pop(0)
|
||||
|
||||
for num, srv in enumerate(srvs, start=1):
|
||||
srv_data = srv.strip().split(":")
|
||||
if srv_data[1] == "64":
|
||||
m_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
services.append(BouquetService(desc.strip() if desc else "", BqServiceType.MARKER, srv, num))
|
||||
elif srv_data[1] == "832":
|
||||
m_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
services.append(BouquetService(desc.strip() if desc else "", BqServiceType.SPACE, srv, num))
|
||||
elif "http" in srv or srv_data[0] == "8193":
|
||||
stream_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
desc = desc.lstrip(":").strip() if desc else srv_data[-1].strip()
|
||||
services.append(BouquetService(desc, BqServiceType.IPTV, srv, num))
|
||||
for ch in srvs[1:]:
|
||||
ch_data = ch.strip().split(":")
|
||||
if ch_data[1] == "64":
|
||||
m_data, sep, desc = ch.partition("#DESCRIPTION")
|
||||
services.append(BouquetService(desc.strip() if desc else "", BqServiceType.MARKER, ch, ch_data[2]))
|
||||
elif "http" in ch:
|
||||
stream_data, sep, desc = ch.partition("#DESCRIPTION")
|
||||
services.append(BouquetService(desc.lstrip(":").strip() if desc else "", BqServiceType.IPTV, ch, 0))
|
||||
else:
|
||||
fav_id = "{}:{}:{}:{}".format(srv_data[3], srv_data[4], srv_data[5], srv_data[6])
|
||||
fav_id = "{}:{}:{}:{}".format(ch_data[3], ch_data[4], ch_data[5], ch_data[6])
|
||||
name = None
|
||||
if len(srv_data) == 12:
|
||||
name, sep, desc = str(srv_data[-1]).partition("\n#DESCRIPTION")
|
||||
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id.upper(), num))
|
||||
if len(ch_data) == 12:
|
||||
name, sep, desc = str(ch_data[-1]).partition("\n#DESCRIPTION")
|
||||
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id.upper(), 0))
|
||||
|
||||
return bq_name.lstrip("#NAME").strip(), services
|
||||
return srvs[0].lstrip("#NAME").strip(), services
|
||||
|
||||
|
||||
def parse_bouquets(path, bq_name, bq_type):
|
||||
|
||||
@@ -25,16 +25,11 @@ def write_services(path, services, format_version=4):
|
||||
|
||||
def write_to_lamedb(path, services):
|
||||
""" Writing lamedb file ver.4 """
|
||||
with open(path + _FILE_NAME, "w") as file:
|
||||
file.writelines(get_services_lines(services))
|
||||
|
||||
|
||||
def get_services_lines(services):
|
||||
""" Returns a list of strings from services for lamedb [v.4]. """
|
||||
lines = [_HEADER.format(4), "\ntransponders\n"]
|
||||
tr_lines = []
|
||||
services_lines = ["end\nservices\n"]
|
||||
tr_set = set()
|
||||
|
||||
for srv in services:
|
||||
data_id = str(srv.data_id).split(_SEP)
|
||||
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
|
||||
@@ -49,12 +44,12 @@ def get_services_lines(services):
|
||||
lines.extend(tr_lines)
|
||||
lines.extend(services_lines)
|
||||
lines.append("end\n" + _END_LINE)
|
||||
|
||||
return lines
|
||||
with open(path + _FILE_NAME, "w") as file:
|
||||
file.writelines(lines)
|
||||
|
||||
|
||||
def write_to_lamedb5(path, services):
|
||||
""" Writing lamedb5 file. """
|
||||
""" Writing lamedb5 file """
|
||||
lines = [_HEADER.format(5) + "\n"]
|
||||
services_lines = []
|
||||
tr_set = set()
|
||||
@@ -78,7 +73,7 @@ def write_to_lamedb5(path, services):
|
||||
|
||||
|
||||
def parse(path, version=4):
|
||||
""" Parsing lamedb. """
|
||||
""" Parsing lamedb """
|
||||
if version == 4:
|
||||
return parse_v4(path)
|
||||
elif version == 5:
|
||||
@@ -87,7 +82,7 @@ def parse(path, version=4):
|
||||
|
||||
|
||||
def parse_v3(services, transponders, path):
|
||||
""" Parsing version 3. """
|
||||
""" Parsing version 3 """
|
||||
for t in transponders:
|
||||
tr = transponders[t].lower()
|
||||
tr_type = tr[0:1]
|
||||
@@ -113,37 +108,32 @@ def parse_v3(services, transponders, path):
|
||||
|
||||
|
||||
def parse_v4(path):
|
||||
""" Parsing version 4. """
|
||||
""" Parsing version 4 """
|
||||
with open(path + _FILE_NAME, "r", encoding="utf-8", errors="replace") as file:
|
||||
try:
|
||||
data = str(file.read())
|
||||
except UnicodeDecodeError as e:
|
||||
log("lamedb parse error: " + str(e))
|
||||
else:
|
||||
return get_services_list(data, path)
|
||||
transponders, sep, services = data.partition("transponders") # 1 step
|
||||
pattern = re.compile("/[34]/$")
|
||||
match = re.search(pattern, transponders)
|
||||
if not match:
|
||||
msg = "lamedb parsing error: unsupported format."
|
||||
log(msg)
|
||||
raise SyntaxError(msg)
|
||||
|
||||
transponders, sep, services = services.partition("services") # 2 step
|
||||
services, sep, _ = services.partition("\nend") # 3 step
|
||||
|
||||
def get_services_list(data, path=None):
|
||||
""" Returns a list of services from a string data representation. """
|
||||
transponders, sep, services = data.partition("transponders") # 1 step
|
||||
pattern = re.compile("/[34]/$")
|
||||
match = re.search(pattern, transponders)
|
||||
if not match:
|
||||
msg = "lamedb parsing error: unsupported format."
|
||||
log(msg)
|
||||
raise SyntaxError(msg)
|
||||
if match.group() == "/3/":
|
||||
return parse_v3(services.split("\n"), parse_transponders(transponders.split("/")), path)
|
||||
|
||||
transponders, sep, services = services.partition("services") # 2 step
|
||||
services, sep, _ = services.partition("\nend") # 3 step
|
||||
|
||||
if match.group() == "/3/":
|
||||
return parse_v3(services.split("\n"), parse_transponders(transponders.split("/")), path)
|
||||
|
||||
return parse_services(services.split("\n"), parse_transponders(transponders.split("/")), path)
|
||||
return parse_services(services.split("\n"), parse_transponders(transponders.split("/")), path)
|
||||
|
||||
|
||||
def parse_v5(path):
|
||||
""" Parsing version 5. """
|
||||
""" Parsing version 5 """
|
||||
with open(path + "lamedb5", "r", encoding="utf-8", errors="replace") as file:
|
||||
lns = file.readlines()
|
||||
|
||||
@@ -151,9 +141,9 @@ def parse_v5(path):
|
||||
raise SyntaxError("lamedb v.5 parsing error: unsupported format.")
|
||||
|
||||
trs, srvs = {}, [""]
|
||||
for line in lns:
|
||||
if line.startswith("s:"):
|
||||
srv_data = line.strip("s:").split(",", 2)
|
||||
for l in lns:
|
||||
if l.startswith("s:"):
|
||||
srv_data = l.strip("s:").split(",", 2)
|
||||
srv_data[1] = srv_data[1].strip("\"")
|
||||
data_len = len(srv_data)
|
||||
if data_len == 3:
|
||||
@@ -161,15 +151,15 @@ def parse_v5(path):
|
||||
elif data_len == 2:
|
||||
srv_data.append("p:")
|
||||
srvs.extend(srv_data)
|
||||
elif line.startswith("t:"):
|
||||
tr, srv = line.split(",")
|
||||
elif l.startswith("t:"):
|
||||
tr, srv = l.split(",")
|
||||
trs[tr.strip("t:")] = srv.strip().replace(":", " ", 1)
|
||||
|
||||
return parse_services(srvs, trs, path)
|
||||
|
||||
|
||||
def parse_transponders(arg):
|
||||
""" Parsing transponders. """
|
||||
""" Parsing transponders """
|
||||
transponders = {}
|
||||
for ar in arg:
|
||||
tr = ar.replace("\n", "").split("\t")
|
||||
@@ -180,9 +170,9 @@ def parse_transponders(arg):
|
||||
|
||||
|
||||
def parse_services(services, transponders, path):
|
||||
""" Parsing services. """
|
||||
""" Parsing services """
|
||||
services_list = []
|
||||
blacklist = get_blacklist(path) if path else {}
|
||||
blacklist = str(get_blacklist(path))
|
||||
srvs = split(services, 3)
|
||||
if srvs[0][0] == "": # remove first empty element
|
||||
srvs.remove(srvs[0])
|
||||
@@ -218,13 +208,12 @@ def parse_services(services, transponders, path):
|
||||
# For comparison in bouquets. Needed in upper case!!!
|
||||
fav_id = "{}:{}:{}:{}".format(ssid, tid, nid, onid)
|
||||
picon_id = "1_0_{:X}_{}_{}_{}_{}_0_0_0.png".format(srv_type, ssid, tid, nid, onid)
|
||||
s_id = "1:0:{:X}:{}:{}:{}:{}:0:0:0:".format(srv_type, ssid, tid, nid, onid)
|
||||
|
||||
all_flags = srv[2].split(",")
|
||||
coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None
|
||||
flags = list(filter(lambda x: x.startswith("f:"), all_flags))
|
||||
hide = HIDE_ICON if flags and Flag.is_hide(int(flags[0][2:])) else None
|
||||
locked = LOCKED_ICON if s_id in blacklist else None
|
||||
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 ""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
""" Module for IPTV and streams support """
|
||||
import re
|
||||
import urllib.request
|
||||
from enum import Enum
|
||||
from urllib.parse import unquote, quote
|
||||
|
||||
from app.settings import SettingsType
|
||||
from app.ui.uicommons import IPTV_ICON
|
||||
@@ -18,7 +18,6 @@ class StreamType(Enum):
|
||||
NONE_TS = "4097"
|
||||
NONE_REC_1 = "5001"
|
||||
NONE_REC_2 = "5002"
|
||||
E_SERVICE_URI = "8193"
|
||||
|
||||
|
||||
def parse_m3u(path, s_type):
|
||||
@@ -65,7 +64,7 @@ def export_to_m3u(path, bouquet, s_type):
|
||||
lines.append("#EXTINF:-1,{}\n".format(s.name))
|
||||
if current_grp:
|
||||
lines.append(current_grp)
|
||||
lines.append("{}\n".format(unquote(data.strip())))
|
||||
lines.append("{}\n".format(urllib.request.unquote(data.strip())))
|
||||
elif s_type is BqServiceType.MARKER:
|
||||
current_grp = "#EXTGRP:{}\n".format(s.name)
|
||||
|
||||
@@ -76,8 +75,9 @@ def export_to_m3u(path, bouquet, s_type):
|
||||
def get_fav_id(url, service_name, s_type):
|
||||
""" Returns fav id depending on the profile. """
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
url = urllib.request.quote(url)
|
||||
stream_type = StreamType.NONE_TS.value
|
||||
return ENIGMA2_FAV_ID_FORMAT.format(stream_type, 1, 0, 0, 0, 0, quote(url), service_name, service_name, None)
|
||||
return ENIGMA2_FAV_ID_FORMAT.format(stream_type, 1, 0, 0, 0, 0, url, service_name, service_name, None)
|
||||
elif s_type is SettingsType.NEUTRINO_MP:
|
||||
return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1)
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ class Defaults(Enum):
|
||||
|
||||
|
||||
def get_settings():
|
||||
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
|
||||
|
||||
if not os.path.isfile(CONFIG_FILE) or os.stat(CONFIG_FILE).st_size == 0:
|
||||
write_settings(get_default_settings())
|
||||
|
||||
@@ -75,7 +77,6 @@ def get_default_transcoding_presets():
|
||||
|
||||
|
||||
def write_settings(config):
|
||||
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
|
||||
with open(CONFIG_FILE, "w") as config_file:
|
||||
json.dump(config, config_file, indent=" ")
|
||||
|
||||
@@ -124,10 +125,6 @@ class SettingsException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SettingsReadException(SettingsException):
|
||||
pass
|
||||
|
||||
|
||||
class PlayStreamsMode(IntEnum):
|
||||
""" Behavior mode when opening streams. """
|
||||
BUILT_IN = 0
|
||||
@@ -140,10 +137,7 @@ class Settings:
|
||||
__VERSION = 1
|
||||
|
||||
def __init__(self, ext_settings=None):
|
||||
try:
|
||||
settings = ext_settings or get_settings()
|
||||
except PermissionError as e:
|
||||
raise SettingsReadException(e)
|
||||
settings = ext_settings or get_settings()
|
||||
|
||||
if self.__VERSION > settings.get("version", 0):
|
||||
raise SettingsException("Outdated version of the settings format!")
|
||||
@@ -528,14 +522,6 @@ class Settings:
|
||||
def enable_yt_dl(self, value):
|
||||
self._settings["enable_yt_dl"] = value
|
||||
|
||||
@property
|
||||
def enable_yt_dl_update(self):
|
||||
return self._settings.get("enable_yt_dl_update", Defaults.ENABLE_YT_DL.value)
|
||||
|
||||
@enable_yt_dl_update.setter
|
||||
def enable_yt_dl_update(self, value):
|
||||
self._settings["enable_yt_dl_update"] = value
|
||||
|
||||
@property
|
||||
def enable_send_to(self):
|
||||
return self._settings.get("enable_send_to", Defaults.ENABLE_SEND_TO.value)
|
||||
@@ -612,14 +598,6 @@ class Settings:
|
||||
|
||||
# *********** Appearance *********** #
|
||||
|
||||
@property
|
||||
def dark_mode(self):
|
||||
return self._settings.get("dark_mode", False)
|
||||
|
||||
@dark_mode.setter
|
||||
def dark_mode(self, value):
|
||||
self._settings["dark_mode"] = value
|
||||
|
||||
@property
|
||||
def is_themes_support(self):
|
||||
return self._settings.get("is_themes_support", False)
|
||||
@@ -658,45 +636,6 @@ class Settings:
|
||||
def is_darwin(self):
|
||||
return IS_DARWIN
|
||||
|
||||
# *********** Download dialog *********** #
|
||||
|
||||
@property
|
||||
def use_http(self):
|
||||
return self._settings.get("use_http", True)
|
||||
|
||||
@use_http.setter
|
||||
def use_http(self, value):
|
||||
self._settings["use_http"] = value
|
||||
|
||||
@property
|
||||
def remove_unused_bouquets(self):
|
||||
return self._settings.get("remove_unused_bouquets", True)
|
||||
|
||||
@remove_unused_bouquets.setter
|
||||
def remove_unused_bouquets(self, value):
|
||||
self._settings["remove_unused_bouquets"] = value
|
||||
|
||||
# **************** Debug **************** #
|
||||
|
||||
@property
|
||||
def debug_mode(self):
|
||||
return self._settings.get("debug_mode", False)
|
||||
|
||||
@debug_mode.setter
|
||||
def debug_mode(self, value):
|
||||
self._settings["debug_mode"] = value
|
||||
|
||||
# **************** Experimental **************** #
|
||||
|
||||
@property
|
||||
def is_enable_experimental(self):
|
||||
""" Allows experimental functionality. """
|
||||
return self._settings.get("enable_experimental", False)
|
||||
|
||||
@is_enable_experimental.setter
|
||||
def is_enable_experimental(self, value):
|
||||
self._settings["enable_experimental"] = value
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -2,10 +2,11 @@ import glob
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
from collections import namedtuple
|
||||
from html.parser import HTMLParser
|
||||
|
||||
from app.commons import run_task, log
|
||||
from app.commons import run_task
|
||||
from app.settings import SettingsType
|
||||
|
||||
_ENIGMA2_PICON_KEY = "{:X}:{:X}:{}"
|
||||
@@ -32,9 +33,9 @@ class PiconsParser(HTMLParser):
|
||||
self.picons = []
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "td":
|
||||
if tag == 'td':
|
||||
self._is_td = True
|
||||
if tag == "th":
|
||||
if tag == 'th':
|
||||
self._is_th = True
|
||||
if tag == "img":
|
||||
self._current_row.append(attrs[0][1])
|
||||
@@ -45,16 +46,16 @@ class PiconsParser(HTMLParser):
|
||||
self._current_cell.append(data.strip())
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == "td":
|
||||
if tag == 'td':
|
||||
self._is_td = False
|
||||
elif tag == "th":
|
||||
elif tag == 'th':
|
||||
self._is_th = False
|
||||
|
||||
if tag in ("td", "th"):
|
||||
if tag in ('td', 'th'):
|
||||
final_cell = self._separator.join(self._current_cell).strip()
|
||||
self._current_row.append(final_cell)
|
||||
self._current_cell = []
|
||||
elif tag == "tr":
|
||||
elif tag == 'tr':
|
||||
row = self._current_row
|
||||
ln = len(row)
|
||||
|
||||
@@ -79,10 +80,6 @@ class PiconsParser(HTMLParser):
|
||||
|
||||
@staticmethod
|
||||
def parse(open_path, picons_path, tmp_path, provider, picon_ids, s_type=SettingsType.ENIGMA_2):
|
||||
if not os.path.isfile(open_path):
|
||||
log("PiconsParser error [parse]. No such file or directory: {}".format(open_path))
|
||||
return
|
||||
|
||||
with open(open_path, encoding="utf-8", errors="replace") as f:
|
||||
on_id, pos, ssid, single = provider.on_id, provider.pos, provider.ssid, provider.single
|
||||
neg_pos = pos.endswith("W")
|
||||
@@ -108,7 +105,8 @@ class PiconsParser(HTMLParser):
|
||||
shutil.copyfile(tmp_path + "www.lyngsat.com/" + p.ref.lstrip("."), p_name)
|
||||
except (TypeError, ValueError) as e:
|
||||
msg = "Picons format parse error: {}".format(p) + "\n" + str(e)
|
||||
log(msg)
|
||||
# log(msg)
|
||||
print(msg)
|
||||
|
||||
@staticmethod
|
||||
def format(ssid, on_id, namespace, picon_ids, s_type):
|
||||
@@ -127,7 +125,10 @@ class ProviderParser(HTMLParser):
|
||||
_POSITION_PATTERN = re.compile("at\s\d+\..*(?:E|W)']")
|
||||
_ONID_TID_PATTERN = re.compile("^\d+-\d+.*")
|
||||
_TRANSPONDER_FREQUENCY_PATTERN = re.compile("^\d+ [HVLR]+")
|
||||
_DOMAINS = {"/tvchannels/", "/radiochannels/", "/packages/"}
|
||||
_DOMAIN = "http://www.lyngsat.com"
|
||||
_TV_DOMAIN = _DOMAIN + "/tvchannels/"
|
||||
_RADIO_DOMAIN = _DOMAIN + "/radiochannels/"
|
||||
_PKG_DOMAIN = _DOMAIN + "/packages/"
|
||||
|
||||
def __init__(self, entities=False, separator=' '):
|
||||
|
||||
@@ -159,7 +160,7 @@ class ProviderParser(HTMLParser):
|
||||
self._current_row.append(attrs[0][1])
|
||||
if tag == "a":
|
||||
url = attrs[0][1]
|
||||
if any(d in url for d in self._DOMAINS):
|
||||
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]
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
""" Module for downloading satellites, transponders ans services from the web.
|
||||
|
||||
Sources: www.flysat.com, www.lyngsat.com.
|
||||
Replaces or updates the current satellites.xml file.
|
||||
""" Module for download satellites from internet ("flysat.com")
|
||||
for replace or update current satellites.xml file.
|
||||
"""
|
||||
import re
|
||||
|
||||
import requests
|
||||
from enum import Enum
|
||||
from html.parser import HTMLParser
|
||||
|
||||
import requests
|
||||
|
||||
from app.commons import log
|
||||
from app.eparser import Satellite, Transponder, is_transponder_valid
|
||||
from app.eparser.ecommons import (PLS_MODE, get_key_by_value, FEC, SYSTEM, POLARIZATION, MODULATION, SERVICE_TYPE,
|
||||
Service, CAS)
|
||||
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/69.0"}
|
||||
from app.eparser.ecommons import PLS_MODE
|
||||
|
||||
|
||||
class SatelliteSource(Enum):
|
||||
@@ -27,55 +22,11 @@ class SatelliteSource(Enum):
|
||||
return src.value
|
||||
|
||||
|
||||
class Cell:
|
||||
""" Cell representation for table parsers. """
|
||||
__slots__ = ["_text", "_url", "_img"]
|
||||
|
||||
def __init__(self, text=None, link=None, img=None):
|
||||
self._text = text
|
||||
self._url = link
|
||||
self._img = img
|
||||
|
||||
def __repr__(self):
|
||||
return "Cell({}, {}, {})".format(self._text, self._url, self._img)
|
||||
|
||||
def __str__(self):
|
||||
return "<Cell(text={}, link={}, img={})>".format(self._text, self._url, self._img)
|
||||
|
||||
def __iter__(self):
|
||||
return (x for x in (self._text, self._url, self._img))
|
||||
|
||||
def __len__(self):
|
||||
return 3
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self._text
|
||||
|
||||
@text.setter
|
||||
def text(self, value):
|
||||
self._text = value
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return self._url
|
||||
|
||||
@url.setter
|
||||
def url(self, value):
|
||||
self._url = value
|
||||
|
||||
@property
|
||||
def img(self):
|
||||
return self._img
|
||||
|
||||
@img.setter
|
||||
def img(self, value):
|
||||
self._img = value
|
||||
|
||||
|
||||
class SatellitesParser(HTMLParser):
|
||||
""" Parser for satellite html page. """
|
||||
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:45.0) Gecko/20100101 Firefox/59.02"}
|
||||
|
||||
def __init__(self, source=SatelliteSource.FLYSAT, entities=False, separator=' '):
|
||||
|
||||
HTMLParser.__init__(self)
|
||||
@@ -91,9 +42,9 @@ class SatellitesParser(HTMLParser):
|
||||
self._source = source
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "td":
|
||||
if tag == 'td':
|
||||
self._is_td = True
|
||||
if tag == "tr":
|
||||
if tag == 'tr':
|
||||
self._is_th = True
|
||||
if tag == "a":
|
||||
self._current_row.append(attrs[0][1])
|
||||
@@ -104,16 +55,16 @@ class SatellitesParser(HTMLParser):
|
||||
self._current_cell.append(data.strip())
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == "td":
|
||||
if tag == 'td':
|
||||
self._is_td = False
|
||||
elif tag == "tr":
|
||||
elif tag == 'tr':
|
||||
self._is_th = False
|
||||
|
||||
if tag in ("td", "th"):
|
||||
if tag in ('td', 'th'):
|
||||
final_cell = self._separator.join(self._current_cell).strip()
|
||||
self._current_row.append(final_cell)
|
||||
self._current_cell = []
|
||||
elif tag == "tr":
|
||||
elif tag == 'tr':
|
||||
row = self._current_row
|
||||
self._rows.append(row)
|
||||
self._current_row = []
|
||||
@@ -129,7 +80,7 @@ class SatellitesParser(HTMLParser):
|
||||
|
||||
for src in SatelliteSource.get_sources(self._source):
|
||||
try:
|
||||
request = requests.get(url=src, headers=_HEADERS)
|
||||
request = requests.get(url=src, headers=self._HEADERS)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
log(repr(e))
|
||||
return []
|
||||
@@ -147,24 +98,17 @@ class SatellitesParser(HTMLParser):
|
||||
|
||||
return list(map(get_sat, filter(lambda x: all(x) and len(x) == 5, self._rows)))
|
||||
elif self._source is SatelliteSource.LYNGSAT:
|
||||
extra_pattern = re.compile(r"^https://www\.lyngsat\.com/[\w-]+\.html")
|
||||
extra_pattern = re.compile("^https://www\.lyngsat\.com/[\w-]+\.html")
|
||||
base_url = "https://www.lyngsat.com/"
|
||||
sats = []
|
||||
names = set()
|
||||
current_pos = "0"
|
||||
for row in filter(lambda x: len(x) in (5, 7, 8), self._rows):
|
||||
r_len = len(row)
|
||||
if r_len == 7:
|
||||
current_pos = self.parse_position(row[2])
|
||||
name = row[1].rsplit("/")[-1].rstrip(".html").replace("-", " ")
|
||||
if name not in names:
|
||||
# [all in one] satellites
|
||||
sats.append((name, current_pos, row[5], base_url + row[1], False))
|
||||
names.add(name)
|
||||
name = row[4]
|
||||
if name not in names:
|
||||
sats.append((name, current_pos, row[5], base_url + row[3], False))
|
||||
names.add(name)
|
||||
sats.append((name, current_pos, row[5], base_url + row[1], False)) # [all in one] satellites
|
||||
sats.append((row[4], current_pos, row[5], base_url + row[3], False))
|
||||
if r_len == 8: # for a very limited number of satellites
|
||||
data = list(filter(None, row))
|
||||
urls = set()
|
||||
@@ -202,7 +146,7 @@ class SatellitesParser(HTMLParser):
|
||||
""" Getting transponders(sorted by frequency). """
|
||||
self._rows.clear()
|
||||
url = "https://www.flysat.com/" + sat_url if self._source is SatelliteSource.FLYSAT else sat_url
|
||||
request = requests.get(url=url, headers=_HEADERS)
|
||||
request = requests.get(url=url, headers=self._HEADERS)
|
||||
reason = request.reason
|
||||
trs = []
|
||||
if reason == "OK":
|
||||
@@ -303,198 +247,5 @@ class SatellitesParser(HTMLParser):
|
||||
trs.append(tr)
|
||||
|
||||
|
||||
class ServicesParser(HTMLParser):
|
||||
""" Services parser for LYNGSAT source. """
|
||||
|
||||
def __init__(self, source=SatelliteSource.LYNGSAT, entities=False, separator=' '):
|
||||
|
||||
HTMLParser.__init__(self)
|
||||
|
||||
self._S_TYPES = {"": "2", "MPEG-2 SD": "1", "SD": "1", "MPEG-4 SD": "22", "HEVC SD": "22", "MPEG-4 HD": "25",
|
||||
"MPEG-4 HD 1080": "25", "MPEG-4 HD 720": "25", "HEVC HD": "25", "HEVC UHD": "31",
|
||||
"HEVC UHD 4K": "31"}
|
||||
self._TR_PAT = re.compile(r"(DVB-S[2]?)/?(.*PSK)?\s+SR\s+(\d+)\s+FEC\s+(\d/\d).*ONID/TID:\s+(\d+)/(\d+)\s+.*")
|
||||
self._PTR_PAT = re.compile(r".*?(\d+\.\d°[EW]):\s+(\d+)\s+([RLHV]).*")
|
||||
self._TR = "s {}000:{}000:{}:{}:{}:{}:{}:{}"
|
||||
self._S2_TR = "{}:{}:{}:{}"
|
||||
|
||||
self._parse_html_entities = entities
|
||||
self._separator = separator
|
||||
self._is_td = False
|
||||
self._is_th = False
|
||||
self._current_row = []
|
||||
self._current_cell_text = []
|
||||
self._current_cell = Cell()
|
||||
self._rows = []
|
||||
self._source = source
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "td":
|
||||
self._is_td = True
|
||||
elif tag == "tr":
|
||||
self._is_th = True
|
||||
elif tag == "a" and not self._current_cell.url:
|
||||
self._current_cell.url = attrs[0][1]
|
||||
elif tag == "img":
|
||||
img_link = attrs[0][1]
|
||||
if img_link.startswith("/logo/"):
|
||||
self._current_cell.img = img_link
|
||||
|
||||
def handle_data(self, data):
|
||||
""" Save content to a cell """
|
||||
if self._is_td or self._is_th:
|
||||
self._current_cell_text.append(data.strip())
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == "td":
|
||||
self._is_td = False
|
||||
elif tag == "tr":
|
||||
self._is_th = False
|
||||
|
||||
if tag in ("td", "th"):
|
||||
final_cell = self._separator.join(self._current_cell_text).strip()
|
||||
self._current_cell.text = final_cell
|
||||
self._current_row.append(self._current_cell)
|
||||
self._current_cell_text = []
|
||||
self._current_cell = Cell()
|
||||
elif tag == "tr":
|
||||
row = self._current_row
|
||||
self._rows.append(row)
|
||||
self._current_row = []
|
||||
|
||||
def error(self, message):
|
||||
log("ServicesParser error: {}".format(message))
|
||||
|
||||
def init_data(self, url):
|
||||
""" Initializes data for the given URL. """
|
||||
if self._source is not SatelliteSource.LYNGSAT:
|
||||
raise ValueError("Unsupported source: {}!".format(self._source.name))
|
||||
|
||||
self._rows.clear()
|
||||
request = requests.get(url=url, headers=_HEADERS)
|
||||
reason = request.reason
|
||||
|
||||
if reason == "OK":
|
||||
self.feed(request.text)
|
||||
else:
|
||||
raise ValueError(reason)
|
||||
|
||||
def get_transponders_links(self, sat_url):
|
||||
""" Returns transponder links. """
|
||||
try:
|
||||
self.init_data(sat_url)
|
||||
except ValueError as e:
|
||||
log(e)
|
||||
else:
|
||||
url = "https://www.lyngsat.com/muxes/"
|
||||
return [row[1] for row in
|
||||
filter(lambda x: x and len(x) > 8 and x[1].url and x[1].url.startswith(url), self._rows)]
|
||||
return []
|
||||
|
||||
def get_transponder_services(self, tr_url, sat_position=None, use_pids=False):
|
||||
""" Returns services for given transponder.
|
||||
|
||||
@param tr_url: transponder URL.
|
||||
@param sat_position: custom satellite position. Sometimes required to adjust the namespace.
|
||||
@param use_pids: if possible use additional pids [video, audio].
|
||||
"""
|
||||
services = []
|
||||
try:
|
||||
self.init_data(tr_url)
|
||||
except ValueError as e:
|
||||
log(e)
|
||||
else:
|
||||
pos, freq, sr, fec, pol, namespace, tid, nid = sat_position or 0, 0, 0, 0, 0, 0, 0, 0
|
||||
sys = "DVB-S"
|
||||
tr_found = False
|
||||
pos_found = False
|
||||
tr = None
|
||||
# Transponder
|
||||
for r in filter(lambda x: x and len(x) == 2, self._rows):
|
||||
if not pos_found:
|
||||
pos_tr = re.match(self._PTR_PAT, r[1].text)
|
||||
if pos_tr:
|
||||
if not sat_position:
|
||||
pos = int(SatellitesParser.get_position(
|
||||
"".join(c for c in pos_tr.group(1) if c.isdigit() or c.isalpha())))
|
||||
freq = int(pos_tr.group(2))
|
||||
pol = get_key_by_value(POLARIZATION, pos_tr.group(3))
|
||||
pos_found = True
|
||||
|
||||
if pos_found and not tr_found:
|
||||
td = re.match(self._TR_PAT, r[1].text) or re.match(self._TR_PAT, r[0].text)
|
||||
if td:
|
||||
sys, mod, sr, _fec, nid, tid = td.group(1), td.group(2), td.group(3), td.group(4), td.group(
|
||||
5), td.group(6)
|
||||
neg_pos = False # POS = W
|
||||
# For negative (West) positions: 3600 - numeric position value!!!
|
||||
namespace = "{:04x}0000".format(3600 - pos if neg_pos else pos)
|
||||
inv = 2 # Default
|
||||
fec = get_key_by_value(FEC, _fec)
|
||||
sys = get_key_by_value(SYSTEM, sys)
|
||||
tr_flag = 1
|
||||
mod = get_key_by_value(MODULATION, mod)
|
||||
roll_off = 0 # 35% DVB-S2/DVB-S (default)
|
||||
pilot = 2 # Auto
|
||||
s2_flags = "" if sys == "DVB-S" else self._S2_TR.format(tr_flag, mod or 0, roll_off, pilot)
|
||||
nid, tid = int(nid), int(tid)
|
||||
tr = self._TR.format(freq, sr, pol, fec, pos, inv, sys, s2_flags)
|
||||
tr_found = True
|
||||
|
||||
if not tr:
|
||||
msg = "ServicesParser error [get transponder services]: {}"
|
||||
er = "Transponder [{}] not found or its type [T2-MI, etc] not supported yet.".format(freq)
|
||||
log(msg.format(er))
|
||||
return []
|
||||
|
||||
# Services
|
||||
for r in filter(lambda x: x and len(x) == 12 and (x[0].text.isdigit()), self._rows):
|
||||
sid, name, cas, pkg, s_type, v_pid, a_pid = r[0].text, r[2].text, r[4].text, r[5].text, r[
|
||||
6].text.strip(), r[7].text, r[8].text.split()
|
||||
|
||||
try:
|
||||
s_type = self._S_TYPES.get(s_type, "3") # 3 = Data
|
||||
_s_type = SERVICE_TYPE.get(s_type, SERVICE_TYPE.get("3")) # str repr
|
||||
sid = int(sid)
|
||||
data_id = "{:04x}:{}:{:04x}:{:04x}:{}:0:0".format(sid, namespace, tid, nid, s_type)
|
||||
fav_id = "{}:{}:{}:{}".format(sid, tid, nid, namespace)
|
||||
picon_id = "1_0_{:X}_{}_{}_{}_{}_0_0_0.png".format(int(s_type), sid, tid, nid, namespace)
|
||||
# Flags.
|
||||
flags = "p:{}".format(pkg)
|
||||
cas = ",".join(get_key_by_value(CAS, c) or "C:0000" for c in cas.split()) if cas else None
|
||||
if use_pids:
|
||||
v_pid = "c:00{:04x}".format(int(v_pid)) if v_pid else None
|
||||
a_pid = ",".join(["c:01{:04x}".format(int(p)) for p in a_pid]) if a_pid else None
|
||||
flags = ",".join(filter(None, (flags, v_pid, a_pid, cas)))
|
||||
else:
|
||||
flags = ",".join(filter(None, (flags, cas)))
|
||||
|
||||
srv = Service(flags_cas=flags,
|
||||
transponder_type="s",
|
||||
coded=None,
|
||||
service=name,
|
||||
locked=None,
|
||||
hide=None,
|
||||
package=pkg,
|
||||
service_type=_s_type,
|
||||
picon=r[1].img,
|
||||
picon_id=picon_id,
|
||||
ssid=sid,
|
||||
freq=freq,
|
||||
rate=sr,
|
||||
pol=pol,
|
||||
fec=fec,
|
||||
system=sys,
|
||||
pos=pos,
|
||||
data_id=data_id,
|
||||
fav_id=fav_id,
|
||||
transponder=tr)
|
||||
services.append(srv)
|
||||
except ValueError as e:
|
||||
log("ServicesParser error [get transponder services]: {}".format(e))
|
||||
|
||||
return services
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
210
app/tools/yt.py
210
app/tools/yt.py
@@ -1,21 +1,16 @@
|
||||
""" Module for working with YouTube service """
|
||||
import gzip
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import urllib
|
||||
from html.parser import HTMLParser
|
||||
from json import JSONDecodeError
|
||||
from urllib.error import URLError
|
||||
from urllib.parse import unquote
|
||||
from urllib.request import Request, urlopen, urlretrieve
|
||||
from urllib.request import Request
|
||||
|
||||
from app.commons import log
|
||||
from app.ui.uicommons import show_notification
|
||||
|
||||
_YT_PATTERN = re.compile(r"https://www.youtube.com/.+(?:v=)([\w-]{11}).*")
|
||||
_YT_LIST_PATTERN = re.compile(r"https://www.youtube.com/.+?(?:list=)([\w-]{18,})?.*")
|
||||
_YT_LIST_PATTERN = re.compile(r"https://www.youtube.com/.+?(?:list=)([\w-]{23,})?.*")
|
||||
_YT_VIDEO_PATTERN = re.compile(r"https://r\d+---sn-[\w]{10}-[\w]{3,5}.googlevideo.com/videoplayback?.*")
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/69.0",
|
||||
"DNT": "1",
|
||||
@@ -25,35 +20,9 @@ Quality = {137: "1080p", 136: "720p", 135: "480p", 134: "360p",
|
||||
133: "240p", 160: "144p", 0: "0p", 18: "360p", 22: "720p"}
|
||||
|
||||
|
||||
class YouTubeException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class YouTube:
|
||||
""" Helper class for working with YouTube service. """
|
||||
|
||||
_YT_INSTANCE = None
|
||||
_VIDEO_INFO_LINK = "https://youtube.com/get_video_info?video_id={}&hl=en"
|
||||
|
||||
VIDEO_LINK = "https://www.youtube.com/watch?v={}"
|
||||
|
||||
def __init__(self, settings, callback):
|
||||
self._settings = settings
|
||||
self._yt_dl = None
|
||||
self._callback = callback
|
||||
|
||||
if self._settings.enable_yt_dl:
|
||||
try:
|
||||
self._yt_dl = YouTubeDL.get_instance(self._settings, callback=self._callback)
|
||||
except YouTubeException:
|
||||
pass # NOP
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, settings, callback=log):
|
||||
if not cls._YT_INSTANCE:
|
||||
cls._YT_INSTANCE = YouTube(settings, callback)
|
||||
return cls._YT_INSTANCE
|
||||
|
||||
@staticmethod
|
||||
def is_yt_video_link(url):
|
||||
return re.match(_YT_VIDEO_PATTERN, url)
|
||||
@@ -72,29 +41,17 @@ class YouTube:
|
||||
if yt:
|
||||
return yt.group(1)
|
||||
|
||||
def get_yt_link(self, video_id, url=None, skip_errors=False):
|
||||
""" Getting link to YouTube video by id or URL.
|
||||
|
||||
Returns tuple from the video links dict and title.
|
||||
"""
|
||||
if self._settings.enable_yt_dl and url:
|
||||
if not self._yt_dl:
|
||||
self._yt_dl = YouTubeDL.get_instance(self._settings, self._callback)
|
||||
return self._yt_dl.get_yt_link(url, skip_errors)
|
||||
|
||||
return self.get_yt_link_by_id(video_id)
|
||||
|
||||
@staticmethod
|
||||
def get_yt_link_by_id(video_id):
|
||||
""" Getting link to YouTube video by id.
|
||||
def get_yt_link(video_id):
|
||||
""" Getting link to YouTube video by id.
|
||||
|
||||
Returns tuple from the video links dict and title.
|
||||
"""
|
||||
req = Request(YouTube._VIDEO_INFO_LINK.format(video_id), headers=_HEADERS)
|
||||
returns tuple from the video links dict and title
|
||||
"""
|
||||
req = Request("https://youtube.com/get_video_info?video_id={}&hl=en".format(video_id), headers=_HEADERS)
|
||||
|
||||
with urlopen(req, timeout=2) as resp:
|
||||
data = unquote(gzip.decompress(resp.read()).decode("utf-8")).split("&")
|
||||
out = {k: v for k, sep, v in (str(d).partition("=") for d in map(unquote, data))}
|
||||
with urllib.request.urlopen(req, timeout=2) as resp:
|
||||
data = urllib.request.unquote(gzip.decompress(resp.read()).decode("utf-8")).split("&")
|
||||
out = {k: v for k, sep, v in (str(d).partition("=") for d in map(urllib.request.unquote, data))}
|
||||
player_resp = out.get("player_response", None)
|
||||
|
||||
if player_resp:
|
||||
@@ -119,7 +76,7 @@ class YouTube:
|
||||
if stream_map:
|
||||
s_map = {k: v for k, sep, v in (str(d).partition("=") for d in stream_map.split("&"))}
|
||||
url, title = s_map.get("url", None), out.get("title", None)
|
||||
url, title = unquote(url) if url else "", title.replace("+", " ") if title else ""
|
||||
url, title = urllib.request.unquote(url) if url else "", title.replace("+", " ") if title else ""
|
||||
if url and title:
|
||||
return {Quality[0]: url}, title.replace("+", " ")
|
||||
|
||||
@@ -164,7 +121,7 @@ class PlayListParser(HTMLParser):
|
||||
|
||||
ct = resp.get("contents", None)
|
||||
if ct:
|
||||
for d in [(d.get("title", {}).get("runs", [{}])[0].get("text", ""),
|
||||
for d in [(d.get("title", {}).get("simpleText", ""),
|
||||
d.get("videoId", "")) for d in flat("playlistVideoRenderer", ct)]:
|
||||
self._playlist.append(d)
|
||||
self._is_script = False
|
||||
@@ -188,152 +145,13 @@ class PlayListParser(HTMLParser):
|
||||
"""
|
||||
request = Request("https://www.youtube.com/playlist?list={}&hl=en".format(play_list_id), headers=_HEADERS)
|
||||
|
||||
with urlopen(request, timeout=2) as resp:
|
||||
with urllib.request.urlopen(request, timeout=2) as resp:
|
||||
data = gzip.decompress(resp.read()).decode("utf-8")
|
||||
parser = PlayListParser()
|
||||
parser.feed(data)
|
||||
return parser.header, parser.playlist
|
||||
|
||||
|
||||
class YouTubeDL:
|
||||
""" Utility class [experimental] for working with youtube-dl.
|
||||
|
||||
[https://github.com/ytdl-org/youtube-dl]
|
||||
"""
|
||||
|
||||
_DL_INSTANCE = None
|
||||
_DownloadError = None
|
||||
_LATEST_RELEASE_URL = "https://api.github.com/repos/ytdl-org/youtube-dl/releases/latest"
|
||||
_OPTIONS = {"noplaylist": True, # Single video instead of a playlist [ignoring playlist in URL].
|
||||
"quiet": True, # Do not print messages to stdout.
|
||||
"simulate": True, # Do not download the video files.
|
||||
"cookiefile": "cookies.txt"} # File name where cookies should be read from and dumped to.
|
||||
|
||||
def __init__(self, settings, callback):
|
||||
self._path = settings.default_data_path + "tools/"
|
||||
self._update = settings.enable_yt_dl_update
|
||||
self._supported = {"22", "18"}
|
||||
self._dl = None
|
||||
self._callback = callback
|
||||
self._download_exception = None
|
||||
self._is_update_process = False
|
||||
|
||||
self.init()
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, settings, callback=print):
|
||||
if not cls._DL_INSTANCE:
|
||||
cls._DL_INSTANCE = YouTubeDL(settings, callback)
|
||||
return cls._DL_INSTANCE
|
||||
|
||||
def init(self):
|
||||
if not os.path.isfile(self._path + "youtube_dl/version.py"):
|
||||
self.get_latest_release()
|
||||
|
||||
if self._path not in sys.path:
|
||||
sys.path.append(self._path)
|
||||
|
||||
self.init_dl()
|
||||
|
||||
def init_dl(self):
|
||||
try:
|
||||
import youtube_dl
|
||||
except ModuleNotFoundError as e:
|
||||
log("YouTubeDLHelper error: {}".format(str(e)))
|
||||
raise YouTubeException(e)
|
||||
except ImportError as e:
|
||||
log("YouTubeDLHelper error: {}".format(str(e)))
|
||||
else:
|
||||
if self._update:
|
||||
if hasattr(youtube_dl.version, "__version__"):
|
||||
l_ver = self.get_last_release_id()
|
||||
cur_ver = youtube_dl.version.__version__
|
||||
if l_ver and youtube_dl.version.__version__ < l_ver:
|
||||
msg = "youtube-dl has new release!\nCurrent: {}. Last: {}.".format(cur_ver, l_ver)
|
||||
show_notification(msg)
|
||||
log(msg)
|
||||
self._callback(msg, False)
|
||||
self.get_latest_release()
|
||||
|
||||
self._DownloadError = youtube_dl.utils.DownloadError
|
||||
self._dl = youtube_dl.YoutubeDL(self._OPTIONS)
|
||||
msg = "youtube-dl initialized..."
|
||||
show_notification(msg)
|
||||
log(msg)
|
||||
|
||||
@staticmethod
|
||||
def get_last_release_id():
|
||||
""" Getting last release id. """
|
||||
url = "https://api.github.com/repos/ytdl-org/youtube-dl/releases/latest"
|
||||
try:
|
||||
with urlopen(url, timeout=10) as resp:
|
||||
return json.loads(resp.read().decode("utf-8")).get("tag_name", "0")
|
||||
except URLError as e:
|
||||
log("YouTubeDLHelper error [get last release id]: {}".format(e))
|
||||
|
||||
def get_latest_release(self):
|
||||
try:
|
||||
self._is_update_process = True
|
||||
log("Getting the last youtube-dl release...")
|
||||
|
||||
with urlopen(YouTubeDL._LATEST_RELEASE_URL, timeout=10) as resp:
|
||||
r = json.loads(resp.read().decode("utf-8"))
|
||||
zip_url = r.get("zipball_url", None)
|
||||
if zip_url:
|
||||
zip_file = self._path + "yt.zip"
|
||||
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
||||
f_name, headers = urlretrieve(zip_url, filename=zip_file)
|
||||
|
||||
import zipfile
|
||||
|
||||
with zipfile.ZipFile(f_name) as arch:
|
||||
|
||||
if os.path.isdir(self._path):
|
||||
shutil.rmtree(self._path)
|
||||
else:
|
||||
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
||||
|
||||
for info in arch.infolist():
|
||||
pref, sep, f = info.filename.partition("/youtube_dl/")
|
||||
if sep:
|
||||
arch.extract(info.filename)
|
||||
shutil.move(info.filename, "{}{}{}".format(self._path, sep, f))
|
||||
shutil.rmtree(pref)
|
||||
msg = "Getting the last youtube-dl release is done!"
|
||||
show_notification(msg)
|
||||
log(msg)
|
||||
self._callback(msg, False)
|
||||
return True
|
||||
except URLError as e:
|
||||
log("YouTubeDLHelper error: {}".format(e))
|
||||
raise YouTubeException(e)
|
||||
finally:
|
||||
self._is_update_process = False
|
||||
|
||||
def get_yt_link(self, url, skip_errors=False):
|
||||
""" Returns tuple from the video links [dict] and title. """
|
||||
if self._is_update_process:
|
||||
self._callback("Update process. Please wait.", False)
|
||||
return {}, ""
|
||||
|
||||
try:
|
||||
info = self._dl.extract_info(url, download=False)
|
||||
except URLError as e:
|
||||
log(str(e))
|
||||
raise YouTubeException(e)
|
||||
except self._DownloadError as e:
|
||||
log(str(e))
|
||||
if not skip_errors:
|
||||
raise YouTubeException(e)
|
||||
else:
|
||||
fmts = info.get("formats", None)
|
||||
if fmts:
|
||||
return {Quality.get(int(fm["format_id"])): fm.get("url", "") for fm in fmts if
|
||||
fm.get("format_id", "") in self._supported}, info.get("title", "")
|
||||
|
||||
return {}, info.get("title", "")
|
||||
|
||||
|
||||
def flat(key, d):
|
||||
for k, v in d.items():
|
||||
if k == key:
|
||||
|
||||
143
app/ui/app_menu_bar.ui
Normal file
143
app/ui/app_menu_bar.ui
Normal file
@@ -0,0 +1,143 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<menu id="app-menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">About</attribute>
|
||||
<attribute name="action">app.on_about_app</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Settings</attribute>
|
||||
<attribute name="action">app.on_settings</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Exit</attribute>
|
||||
<attribute name="action">app.on_close_app</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
<menu id="menu_bar">
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">File</attribute>
|
||||
<section>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">Import</attribute>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Bouquet</attribute>
|
||||
<attribute name="action">app.on_import_bouquet</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Bouquets and services</attribute>
|
||||
<attribute name="action">app.on_import_bouquets</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">New empty configuration</attribute>
|
||||
<attribute name="action">app.on_new_configuration</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Open</attribute>
|
||||
<attribute name="action">app.on_data_open</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Save</attribute>
|
||||
<attribute name="action">app.on_data_save</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">FTP-transfer</attribute>
|
||||
<attribute name="action">app.on_download</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">Edit</attribute>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Lock</attribute>
|
||||
<attribute name="action">app.on_locked</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Hide</attribute>
|
||||
<attribute name="action">app.on_hide</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">View</attribute>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Search</attribute>
|
||||
<attribute name="action">win.search</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Filter</attribute>
|
||||
<attribute name="action">win.filter</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">Tools</attribute>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Satellites editor</attribute>
|
||||
<attribute name="action">app.on_satellite_editor_show</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Picons manager</attribute>
|
||||
<attribute name="action">app.on_picons_manager_show</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Backups</attribute>
|
||||
<attribute name="action">app.on_backup_tool_show</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section id="telnet_section">
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">IPTV</attribute>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Add IPTV or stream service</attribute>
|
||||
<attribute name="action">app.on_iptv</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Import YouTube playlist</attribute>
|
||||
<attribute name="action">app.on_import_yt_list</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Import m3u</attribute>
|
||||
<attribute name="action">app.on_import_m3u</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Export to m3u</attribute>
|
||||
<attribute name="action">app.on_export_to_m3u</attribute>
|
||||
</item>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">EPG configuration</attribute>
|
||||
<attribute name="action">app.on_epg_list_configuration</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">List configuration</attribute>
|
||||
<attribute name="action">app.on_iptv_list_configuration</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Remove all unavailable</attribute>
|
||||
<attribute name="action">app.on_remove_all_unavailable</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
</menu>
|
||||
</interface>
|
||||
@@ -10,7 +10,7 @@ from app.commons import run_idle
|
||||
from app.settings import SettingsType
|
||||
from app.ui.dialogs import show_dialog, DialogType
|
||||
from app.ui.main_helper import append_text_to_tview
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK
|
||||
|
||||
|
||||
class RestoreType(Enum):
|
||||
@@ -59,13 +59,14 @@ class BackupDialog:
|
||||
def show(self):
|
||||
self._dialog_window.show()
|
||||
|
||||
@run_idle
|
||||
def init_data(self):
|
||||
if os.path.isdir(self._backup_path):
|
||||
for file in filter(lambda x: x.endswith(".zip"), os.listdir(self._backup_path)):
|
||||
self._model.append((file.rstrip(".zip"), False))
|
||||
try:
|
||||
files = os.listdir(self._backup_path)
|
||||
except FileNotFoundError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
os.makedirs(os.path.dirname(self._backup_path), exist_ok=True)
|
||||
for file in filter(lambda x: x.endswith(".zip"), files):
|
||||
self._model.append((file.rstrip(".zip"), False))
|
||||
|
||||
def on_restore_bouquets(self, item):
|
||||
self.restore(RestoreType.BOUQUETS)
|
||||
@@ -128,8 +129,6 @@ class BackupDialog:
|
||||
append_text_to_tview(name + "\n", self._text_view)
|
||||
except FileNotFoundError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
self._text_view.get_buffer().set_text("")
|
||||
|
||||
def restore(self, restore_type):
|
||||
model, paths = self._main_view.get_selection().get_selected_rows()
|
||||
@@ -176,7 +175,7 @@ class BackupDialog:
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if key is KeyboardKey.DELETE:
|
||||
self.on_remove(view)
|
||||
|
||||
@@ -33,6 +33,12 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="details_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">emblem-important-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="main_list_store">
|
||||
<columns>
|
||||
<!-- column-name date -->
|
||||
@@ -41,133 +47,89 @@ Author: Dmitriy Yefremov
|
||||
<column type="gboolean"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkMenu" id="popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="restore_bouquets_popup_menu_item">
|
||||
<property name="label" translatable="yes">Restore bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_restore_bouquets" swapped="no"/>
|
||||
<accelerator key="r" signal="activate" modifiers="Primary"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="restore_all_popup_menu_item">
|
||||
<property name="label" translatable="yes">Restore all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_restore_all" swapped="no"/>
|
||||
<accelerator key="e" signal="activate" modifiers="Primary"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="popup_menu_separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="remove_popup_menu_item">
|
||||
<property name="label">gtk-remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">user-trash-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_all_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-select-all-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_bouquets_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-revert-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="dialog_window">
|
||||
<property name="width_request">560</property>
|
||||
<property name="height_request">320</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Backups</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="icon_name">document-revert</property>
|
||||
<property name="gravity">center</property>
|
||||
<signal name="check-resize" handler="on_resize" swapped="no"/>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Backups</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="header_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="restore_bouquets_header_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Restore bouquets</property>
|
||||
<signal name="clicked" handler="on_restore_bouquets" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="restore_bouquets_header_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-revert</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="restore_all_header_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Restore all</property>
|
||||
<signal name="clicked" handler="on_restore_all" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="restore_all_header_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-select-all</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_header_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Remove</property>
|
||||
<signal name="clicked" handler="on_remove" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="remove_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">user-trash</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="info_check_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Details</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="info_check_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-dialog-info</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">1</property>
|
||||
<property name="margin_right">1</property>
|
||||
<property name="margin_top">1</property>
|
||||
<property name="margin_bottom">1</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkPaned" id="main_paned">
|
||||
<property name="visible">True</property>
|
||||
@@ -251,6 +213,106 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">15</property>
|
||||
<property name="margin_right">15</property>
|
||||
<child>
|
||||
<object class="GtkButtonBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="restore_bouquets_header_button">
|
||||
<property name="label" translatable="yes">Restore bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">restore_bouquets_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_restore_bouquets" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="restore_all_header_button">
|
||||
<property name="label" translatable="yes">Restore all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">restore_all_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_restore_all" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_header_button">
|
||||
<property name="label" translatable="yes">Remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">remove_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="clicked"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="info_check_button">
|
||||
<property name="label" translatable="yes">Details</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">details_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
|
||||
<accelerator key="i" signal="clicked" modifiers="Primary"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="info_bar">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -301,57 +363,4 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_popup_menu_item_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-revert-to-saved</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_popup_menu_item_image2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-select-all</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="restore_bouquets_popup_menu_item">
|
||||
<property name="label" translatable="yes">Restore bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">restore_popup_menu_item_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_restore_bouquets" swapped="no"/>
|
||||
<accelerator key="r" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="restore_all_popup_menu_item">
|
||||
<property name="label" translatable="yes">Restore all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">restore_popup_menu_item_image2</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_restore_all" swapped="no"/>
|
||||
<accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="popup_menu_separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="remove_popup_menu_item">
|
||||
<property name="label">gtk-remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
1850
app/ui/control.glade
1850
app/ui/control.glade
File diff suppressed because it is too large
Load Diff
@@ -1,680 +0,0 @@
|
||||
""" Receiver control module via HTTP API. """
|
||||
import os
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from urllib.parse import quote
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from .dialogs import get_dialogs_string, show_dialog, DialogType
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column
|
||||
from ..commons import run_task, run_with_delay, log, run_idle
|
||||
from ..connections import HttpAPI
|
||||
|
||||
|
||||
class ControlBox(Gtk.HBox):
|
||||
_TIME_STR = "%Y-%m-%d %H:%M"
|
||||
|
||||
class Tool(Enum):
|
||||
""" The currently displayed tool. """
|
||||
REMOTE = "control"
|
||||
EPG = "epg"
|
||||
TIMERS = "timers"
|
||||
TIMER = "timer"
|
||||
|
||||
class EpgRow(Gtk.ListBoxRow):
|
||||
def __init__(self, event: dict, **properties):
|
||||
super().__init__(**properties)
|
||||
|
||||
self._event_data = event
|
||||
h_box = Gtk.HBox()
|
||||
h_box.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
self._title = event.get("e2eventtitle", "")
|
||||
title_label = Gtk.Label(self._title)
|
||||
|
||||
self._desc = event.get("e2eventdescription", "")
|
||||
description = Gtk.Label()
|
||||
description.set_markup("<i>{}</i>".format(self._desc))
|
||||
description.set_line_wrap(True)
|
||||
description.set_max_width_chars(25)
|
||||
|
||||
start = int(event.get("e2eventstart", "0"))
|
||||
start_time = datetime.fromtimestamp(start)
|
||||
end_time = datetime.fromtimestamp(start + int(event.get("e2eventduration", "0")))
|
||||
time_label = Gtk.Label()
|
||||
time_label.set_margin_top(5)
|
||||
self._time_header = "{} - {}".format(start_time.strftime("%A, %H:%M"), end_time.strftime("%H:%M"))
|
||||
time_label.set_markup("<b>{}</b>".format(self._time_header))
|
||||
|
||||
h_box.add(time_label)
|
||||
h_box.add(title_label)
|
||||
h_box.add(description)
|
||||
sep = Gtk.Separator()
|
||||
sep.set_margin_top(5)
|
||||
h_box.add(sep)
|
||||
h_box.set_spacing(5)
|
||||
|
||||
self.add(h_box)
|
||||
self.show_all()
|
||||
|
||||
@property
|
||||
def event_data(self):
|
||||
return self._event_data
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return self._title
|
||||
|
||||
@property
|
||||
def desc(self):
|
||||
return self._desc
|
||||
|
||||
@property
|
||||
def time_header(self):
|
||||
return self._time_header
|
||||
|
||||
class TimerRow(Gtk.ListBoxRow):
|
||||
|
||||
_UI_PATH = UI_RESOURCES_PATH + "timer_row.glade"
|
||||
|
||||
def __init__(self, timer, **properties):
|
||||
super().__init__(**properties)
|
||||
|
||||
self._timer = timer
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.add_from_string(get_dialogs_string(self._UI_PATH))
|
||||
row_box = builder.get_object("timer_row_box")
|
||||
name_label = builder.get_object("timer_name_label")
|
||||
description_label = builder.get_object("timer_description_label")
|
||||
service_name_label = builder.get_object("timer_service_name_label")
|
||||
time_label = builder.get_object("timer_time_label")
|
||||
|
||||
name_label.set_text(timer.get("e2name", "") or "")
|
||||
description_label.set_text(timer.get("e2description", "") or "")
|
||||
service_name_label.set_text(timer.get("e2servicename", "") or "")
|
||||
# Time
|
||||
start_time = datetime.fromtimestamp(int(timer.get("e2timebegin", "0")))
|
||||
end_time = datetime.fromtimestamp(int(timer.get("e2timeend", "0")))
|
||||
time_label.set_text("{} - {}".format(start_time.strftime("%A, %H:%M"), end_time.strftime("%H:%M")))
|
||||
|
||||
self.add(row_box)
|
||||
self.show()
|
||||
|
||||
@property
|
||||
def timer(self):
|
||||
return self._timer
|
||||
|
||||
class TimerAction(Enum):
|
||||
ADD = 0
|
||||
EVENT = 1
|
||||
CHANGE = 2
|
||||
|
||||
def __init__(self, app, http_api, settings, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self._http_api = http_api
|
||||
self._settings = settings
|
||||
self._update_epg = False
|
||||
self._app = app
|
||||
self._last_tool = self.Tool.REMOTE
|
||||
self._timer_action = self.TimerAction.ADD
|
||||
self._current_timer = {}
|
||||
|
||||
handlers = {"on_visible_tool": self.on_visible_tool,
|
||||
"on_volume_changed": self.on_volume_changed,
|
||||
"on_epg_press": self.on_epg_press,
|
||||
"on_epg_filter_changed": self.on_epg_filter_changed,
|
||||
"on_timers_press": self.on_timers_press,
|
||||
"on_timers_drag_data_received": self.on_timers_drag_data_received}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.add_from_file(UI_RESOURCES_PATH + "control.glade")
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self.add(builder.get_object("main_box_frame"))
|
||||
self._stack = builder.get_object("stack")
|
||||
self._screenshot_image = builder.get_object("screenshot_image")
|
||||
self._screenshot_button_box = builder.get_object("screenshot_button_box")
|
||||
self._screenshot_check_button = builder.get_object("screenshot_check_button")
|
||||
self._screenshot_check_button.bind_property("active", self._screenshot_image, "visible")
|
||||
self._snr_value_label = builder.get_object("snr_value_label")
|
||||
self._ber_value_label = builder.get_object("ber_value_label")
|
||||
self._agc_value_label = builder.get_object("agc_value_label")
|
||||
self._volume_button = builder.get_object("volume_button")
|
||||
self._epg_list_box = builder.get_object("epg_list_box")
|
||||
self._epg_list_box.set_filter_func(self.epg_filter_function)
|
||||
self._epg_filter_entry = builder.get_object("epg_filter_entry")
|
||||
self._timers_list_box = builder.get_object("timers_list_box")
|
||||
self._app._control_revealer.bind_property("visible", self, "visible")
|
||||
# Timers
|
||||
self._timer_remove_button = builder.get_object("timer_remove_button")
|
||||
self._timer_remove_button.bind_property("visible", builder.get_object("timer_edit_button"), "visible")
|
||||
# Timer
|
||||
self._timer_name_entry = builder.get_object("timer_name_entry")
|
||||
self._timer_desc_entry = builder.get_object("timer_desc_entry")
|
||||
self._timer_service_entry = builder.get_object("timer_service_entry")
|
||||
self._timer_service_ref_entry = builder.get_object("timer_service_ref_entry")
|
||||
self._timer_event_id_entry = builder.get_object("timer_event_id_entry")
|
||||
self._timer_begins_entry = builder.get_object("timer_begins_entry")
|
||||
self._timer_ends_entry = builder.get_object("timer_ends_entry")
|
||||
self._timer_begins_calendar = builder.get_object("timer_begins_calendar")
|
||||
self._timer_begins_hr_button = builder.get_object("timer_begins_hr_button")
|
||||
self._timer_begins_min_button = builder.get_object("timer_begins_min_button")
|
||||
self._timer_ends_calendar = builder.get_object("timer_ends_calendar")
|
||||
self._timer_ends_hr_button = builder.get_object("timer_ends_hr_button")
|
||||
self._timer_ends_min_button = builder.get_object("timer_ends_min_button")
|
||||
self._timer_enabled_switch = builder.get_object("timer_enabled_switch")
|
||||
self._timer_action_combo_box = builder.get_object("timer_action_combo_box")
|
||||
self._timer_after_combo_box = builder.get_object("timer_after_combo_box")
|
||||
self._timer_mo_check_button = builder.get_object("timer_mo_check_button")
|
||||
self._timer_tu_check_button = builder.get_object("timer_tu_check_button")
|
||||
self._timer_we_check_button = builder.get_object("timer_we_check_button")
|
||||
self._timer_th_check_button = builder.get_object("timer_th_check_button")
|
||||
self._timer_fr_check_button = builder.get_object("timer_fr_check_button")
|
||||
self._timer_sa_check_button = builder.get_object("timer_sa_check_button")
|
||||
self._timer_su_check_button = builder.get_object("timer_su_check_button")
|
||||
self._timer_location_switch = builder.get_object("timer_location_switch")
|
||||
self._timer_location_entry = builder.get_object("timer_location_entry")
|
||||
self._timer_location_switch.bind_property("active", self._timer_location_entry, "sensitive")
|
||||
# Disable DnD for timer entries.
|
||||
self._timer_name_entry.drag_dest_unset()
|
||||
self._timer_desc_entry.drag_dest_unset()
|
||||
self._timer_service_entry.drag_dest_unset()
|
||||
# DnD initialization for the timer list.
|
||||
self._timers_list_box.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY)
|
||||
self._timers_list_box.drag_dest_add_text_targets()
|
||||
|
||||
builder.get_object("stack_switcher").set_visible(settings.is_enable_experimental)
|
||||
builder.get_object("epg_box").set_visible(settings.is_enable_experimental)
|
||||
builder.get_object("timers_box").set_visible(settings.is_enable_experimental)
|
||||
|
||||
self.init_actions(app)
|
||||
self.connect("hide", self.on_hide)
|
||||
self.show()
|
||||
|
||||
def init_actions(self, app):
|
||||
# Remote controller actions
|
||||
app.set_action("on_up", lambda a, v: self.on_remote_action(HttpAPI.Remote.UP))
|
||||
app.set_action("on_down", lambda a, v: self.on_remote_action(HttpAPI.Remote.DOWN))
|
||||
app.set_action("on_left", lambda a, v: self.on_remote_action(HttpAPI.Remote.LEFT))
|
||||
app.set_action("on_right", lambda a, v: self.on_remote_action(HttpAPI.Remote.RIGHT))
|
||||
app.set_action("on_ok", lambda a, v: self.on_remote_action(HttpAPI.Remote.OK))
|
||||
app.set_action("on_menu", lambda a, v: self.on_remote_action(HttpAPI.Remote.MENU))
|
||||
app.set_action("on_exit", lambda a, v: self.on_remote_action(HttpAPI.Remote.EXIT))
|
||||
app.set_action("on_red", lambda a, v: self.on_remote_action(HttpAPI.Remote.RED))
|
||||
app.set_action("on_green", lambda a, v: self.on_remote_action(HttpAPI.Remote.GREEN))
|
||||
app.set_action("on_yellow", lambda a, v: self.on_remote_action(HttpAPI.Remote.YELLOW))
|
||||
app.set_action("on_blue", lambda a, v: self.on_remote_action(HttpAPI.Remote.BLUE))
|
||||
# Power
|
||||
app.set_action("on_standby", lambda a, v: self.on_power_action(HttpAPI.Power.STANDBY))
|
||||
app.set_action("on_wake_up", lambda a, v: self.on_power_action(HttpAPI.Power.WAKEUP))
|
||||
app.set_action("on_reboot", lambda a, v: self.on_power_action(HttpAPI.Power.REBOOT))
|
||||
app.set_action("on_restart_gui", lambda a, v: self.on_power_action(HttpAPI.Power.RESTART_GUI))
|
||||
app.set_action("on_shutdown", lambda a, v: self.on_power_action(HttpAPI.Power.DEEP_STANDBY))
|
||||
# Screenshots
|
||||
app.set_action("on_screenshot_all", self.on_screenshot_all)
|
||||
app.set_action("on_screenshot_video", self.on_screenshot_video)
|
||||
app.set_action("on_screenshot_osd", self.on_screenshot_osd)
|
||||
# Timers
|
||||
app.set_action("on_timer_add", self.on_timer_add)
|
||||
app.set_action("on_timer_add_from_event", self.on_timer_add_from_event)
|
||||
app.set_action("on_timer_remove", self.on_timer_remove)
|
||||
app.set_action("on_timer_edit", self.on_timer_edit)
|
||||
app.set_action("on_timer_save", self.on_timer_save)
|
||||
app.set_action("on_timer_cancel", self.on_timer_cancel)
|
||||
app.set_action("on_timer_begins_set", self.on_timer_begins_set)
|
||||
app.set_action("on_timer_ends_set", self.on_timer_ends_set)
|
||||
|
||||
@property
|
||||
def update_epg(self):
|
||||
return self._update_epg
|
||||
|
||||
def on_visible_tool(self, stack, param):
|
||||
tool = self.Tool(stack.get_visible_child_name())
|
||||
self._update_epg = tool is self.Tool.EPG
|
||||
|
||||
if tool is self.Tool.TIMERS:
|
||||
self.update_timer_list()
|
||||
|
||||
if tool is not self.Tool.TIMER:
|
||||
self._last_tool = tool
|
||||
|
||||
def on_hide(self, item):
|
||||
self._update_epg = False
|
||||
|
||||
# ***************** Remote controller ********************* #
|
||||
|
||||
def on_remote(self, action, state=False):
|
||||
""" Shows/Hides [R key] remote controller. """
|
||||
action.set_state(state)
|
||||
self._remote_revealer.set_visible(state)
|
||||
self._remote_revealer.set_reveal_child(state)
|
||||
|
||||
if state:
|
||||
self._http_api.send(HttpAPI.Request.VOL, "state", self.update_volume)
|
||||
|
||||
def on_remote_action(self, action):
|
||||
self._http_api.send(HttpAPI.Request.REMOTE, action, self.on_response)
|
||||
|
||||
@run_with_delay(0.5)
|
||||
def on_volume_changed(self, button, value):
|
||||
self._http_api.send(HttpAPI.Request.VOL, "{:.0f}".format(value), self.on_response)
|
||||
|
||||
def update_volume(self, vol):
|
||||
if "error_code" in vol:
|
||||
return
|
||||
|
||||
GLib.idle_add(self._volume_button.set_value, int(vol.get("e2current", "0")))
|
||||
|
||||
def on_response(self, resp):
|
||||
if "error_code" in resp:
|
||||
return
|
||||
|
||||
if self._screenshot_check_button.get_active():
|
||||
ref = "mode=all" if self._http_api.is_owif else "d="
|
||||
self._http_api.send(HttpAPI.Request.GRUB, ref, self.update_screenshot)
|
||||
|
||||
@run_task
|
||||
def update_screenshot(self, data):
|
||||
if "error_code" in data:
|
||||
return
|
||||
|
||||
data = data.get("img_data", None)
|
||||
if data:
|
||||
from gi.repository import GdkPixbuf
|
||||
|
||||
loader = GdkPixbuf.PixbufLoader.new_with_type("jpeg")
|
||||
loader.set_size(280, 165)
|
||||
try:
|
||||
loader.write(data)
|
||||
pix = loader.get_pixbuf()
|
||||
except GLib.Error:
|
||||
pass # NOP
|
||||
else:
|
||||
GLib.idle_add(self._screenshot_image.set_from_pixbuf, pix)
|
||||
finally:
|
||||
loader.close()
|
||||
|
||||
def on_screenshot_all(self, action, value=None):
|
||||
self._http_api.send(HttpAPI.Request.GRUB, "mode=all" if self._http_api.is_owif else "d=",
|
||||
self.on_screenshot)
|
||||
|
||||
def on_screenshot_video(self, action, value=None):
|
||||
self._http_api.send(HttpAPI.Request.GRUB, "mode=video" if self._http_api.is_owif else "v=",
|
||||
self.on_screenshot)
|
||||
|
||||
def on_screenshot_osd(self, action, value=None):
|
||||
self._http_api.send(HttpAPI.Request.GRUB, "mode=osd" if self._http_api.is_owif else "o=",
|
||||
self.on_screenshot)
|
||||
|
||||
@run_task
|
||||
def on_screenshot(self, data):
|
||||
if "error_code" in data:
|
||||
return
|
||||
|
||||
img = data.get("img_data", None)
|
||||
if img:
|
||||
is_darwin = self._settings.is_darwin
|
||||
GLib.idle_add(self._screenshot_button_box.set_sensitive, is_darwin)
|
||||
path = os.path.expanduser("~/Desktop") if is_darwin else None
|
||||
|
||||
try:
|
||||
import tempfile
|
||||
import subprocess
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="wb", suffix=".jpg", dir=path, delete=not is_darwin) as tf:
|
||||
tf.write(img)
|
||||
cmd = ["open" if is_darwin else "xdg-open", tf.name]
|
||||
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
||||
finally:
|
||||
GLib.idle_add(self._screenshot_button_box.set_sensitive, True)
|
||||
|
||||
def on_power_action(self, action):
|
||||
self._http_api.send(HttpAPI.Request.POWER, action, lambda resp: log("Power status changed..."))
|
||||
|
||||
def update_signal(self, sig):
|
||||
self._snr_value_label.set_text(sig.get("e2snrdb", "0 dB").strip())
|
||||
self._ber_value_label.set_text(str(sig.get("e2ber", None) or "0").strip())
|
||||
self._agc_value_label.set_text(sig.get("e2acg", "0 %").strip())
|
||||
|
||||
# ************************ EPG **************************** #
|
||||
|
||||
def on_service_changed(self, ref):
|
||||
self._app._wait_dialog.show()
|
||||
self._http_api.send(HttpAPI.Request.EPG, ref, self.update_epg_data)
|
||||
|
||||
@run_idle
|
||||
def update_epg_data(self, epg):
|
||||
list(map(self._epg_list_box.remove, (r for r in self._epg_list_box)))
|
||||
list(map(lambda e: self._epg_list_box.add(self.EpgRow(e)), epg.get("event_list", [])))
|
||||
self._app._wait_dialog.hide()
|
||||
|
||||
def on_epg_press(self, list_box, event):
|
||||
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(list_box) > 0:
|
||||
row = list_box.get_selected_row()
|
||||
if row:
|
||||
self.set_timer_from_event_data(row.event_data)
|
||||
|
||||
def on_epg_filter_changed(self, entry):
|
||||
self._epg_list_box.invalidate_filter()
|
||||
|
||||
def epg_filter_function(self, row: EpgRow):
|
||||
txt = self._epg_filter_entry.get_text().upper()
|
||||
return any((not txt, txt in row.time_header.upper(), txt in row.title.upper(), txt in row.desc.upper()))
|
||||
|
||||
def on_timer_add_from_event(self, action, value=None):
|
||||
rows = self._epg_list_box.get_selected_rows()
|
||||
if not rows:
|
||||
self._app.show_error_dialog("No selected item!")
|
||||
return
|
||||
|
||||
refs = []
|
||||
for row in rows:
|
||||
event = row.event_data
|
||||
ref = "timeraddbyeventid?sRef={}&eventid={}&justplay=0".format(event.get("e2eventservicereference", ""),
|
||||
event.get("e2eventid", ""))
|
||||
refs.append(ref)
|
||||
|
||||
gen = self.write_timers_list(refs)
|
||||
GLib.idle_add(lambda: next(gen, False))
|
||||
|
||||
def write_timers_list(self, refs):
|
||||
self._app._wait_dialog.show()
|
||||
tasks = list(refs)
|
||||
for ref in refs:
|
||||
self._http_api.send(HttpAPI.Request.TIMER, ref, lambda x: tasks.pop())
|
||||
yield True
|
||||
|
||||
while tasks:
|
||||
yield True
|
||||
|
||||
self._stack.set_visible_child_name(self.Tool.TIMERS.value)
|
||||
|
||||
# *********************** Timers *************************** #
|
||||
|
||||
def on_timers_press(self, list_box, event):
|
||||
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(list_box) > 0:
|
||||
self.on_timer_edit()
|
||||
|
||||
def update_timer_list(self):
|
||||
self._app._wait_dialog.show()
|
||||
self._http_api.send(HttpAPI.Request.TIMER_LIST, "", self.update_timers_data)
|
||||
|
||||
@run_idle
|
||||
def update_timers_data(self, timers):
|
||||
list(map(self._timers_list_box.remove, (r for r in self._timers_list_box)))
|
||||
list(map(lambda t: self._timers_list_box.add(self.TimerRow(t)), timers.get("timer_list", [])))
|
||||
self._timer_remove_button.set_visible(len(self._timers_list_box))
|
||||
self._app._wait_dialog.hide()
|
||||
|
||||
def on_timer_add(self, action=None, value=None):
|
||||
self._timer_action = self.TimerAction.ADD
|
||||
date = datetime.now()
|
||||
self.set_begins_date(date)
|
||||
self.set_ends_date(date)
|
||||
self._timer_event_id_entry.set_text("")
|
||||
self._timer_location_switch.set_active(False)
|
||||
self.set_repetition_flags(0)
|
||||
self._stack.set_visible_child_name(self.Tool.TIMER.value)
|
||||
|
||||
def on_timer_remove(self, action, value=None):
|
||||
rows = self._timers_list_box.get_selected_rows()
|
||||
if not rows or show_dialog(DialogType.QUESTION, self._app._main_window) != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
refs = {}
|
||||
for row in rows:
|
||||
timer = row.timer
|
||||
ref = "timerdelete?sRef={}&begin={}&end={}".format(timer.get("e2servicereference", ""),
|
||||
timer.get("e2timebegin", ""),
|
||||
timer.get("e2timeend", ""))
|
||||
refs[ref] = row
|
||||
|
||||
self._app._wait_dialog.show("Deleting data...")
|
||||
gen = self.remove_timers(refs)
|
||||
GLib.idle_add(lambda: next(gen, False))
|
||||
|
||||
def remove_timers(self, refs):
|
||||
tasks = list(refs)
|
||||
removed = set()
|
||||
for ref in refs:
|
||||
yield from self.remove_timer(ref, removed, tasks)
|
||||
|
||||
while tasks:
|
||||
yield True
|
||||
|
||||
list(map(self._timers_list_box.remove, (refs[ref] for ref in refs if ref in removed)))
|
||||
self._app._wait_dialog.hide()
|
||||
self._timer_remove_button.set_visible(len(self._timers_list_box))
|
||||
yield True
|
||||
|
||||
def remove_timer(self, ref, removed, tasks=None):
|
||||
def callback(resp):
|
||||
if resp.get("e2state", "") == "True":
|
||||
log(resp.get("e2statetext", ""))
|
||||
removed.add(ref)
|
||||
else:
|
||||
log(resp.get("e2statetext", None) or "Timer deletion error.")
|
||||
if tasks:
|
||||
tasks.pop()
|
||||
|
||||
self._http_api.send(HttpAPI.Request.TIMER, ref, callback)
|
||||
yield True
|
||||
|
||||
def on_timer_edit(self, action=None, value=None):
|
||||
row = self._timers_list_box.get_selected_row()
|
||||
if row:
|
||||
self._timer_action = self.TimerAction.CHANGE
|
||||
|
||||
timer = row.timer
|
||||
self._current_timer = timer
|
||||
self._timer_name_entry.set_text(timer.get("e2name", ""))
|
||||
self._timer_desc_entry.set_text(timer.get("e2description", "") or "")
|
||||
self._timer_service_entry.set_text(timer.get("e2servicename", "") or "")
|
||||
self._timer_service_ref_entry.set_text(timer.get("e2servicereference", ""))
|
||||
self._timer_event_id_entry.set_text(timer.get("e2eit", ""))
|
||||
self._timer_enabled_switch.set_active((timer.get("e2disabled", "0") == "0"))
|
||||
self._timer_action_combo_box.set_active_id(timer.get("e2justplay", "0"))
|
||||
self._timer_after_combo_box.set_active_id(timer.get("e2afterevent", "0"))
|
||||
self.set_time_data(int(timer.get("e2timebegin", "0")), int(timer.get("e2timeend", "0")))
|
||||
location = timer.get("e2location", "")
|
||||
self._timer_location_entry.set_text("" if location == "None" else location)
|
||||
# Days
|
||||
self.set_repetition_flags(int(timer.get("e2repeated", "0")))
|
||||
self._stack.set_visible_child_name(self.Tool.TIMER.value)
|
||||
|
||||
def on_timer_save(self, action, value=None):
|
||||
args = []
|
||||
t_data = self.get_timer_data()
|
||||
s_ref = t_data.get("sRef", "")
|
||||
|
||||
if self._timer_action is self.TimerAction.EVENT:
|
||||
args.append("timeraddbyeventid?sRef={}".format(s_ref))
|
||||
args.append("eventid={}".format(t_data.get("eit", "0")))
|
||||
args.append("justplay={}".format(t_data.get("justplay", "")))
|
||||
args.append("tags={}".format(""))
|
||||
else:
|
||||
if self._timer_action is self.TimerAction.ADD:
|
||||
args.append("timeradd?sRef={}".format(s_ref))
|
||||
args.append("deleteOldOnSave={}".format(0))
|
||||
elif self._timer_action is self.TimerAction.CHANGE:
|
||||
args.append("timerchange?sRef={}".format(s_ref))
|
||||
args.append("channelOld={}".format(s_ref))
|
||||
args.append("beginOld={}".format(self._current_timer.get("e2timebegin", "0")))
|
||||
args.append("endOld={}".format(self._current_timer.get("e2timeend", "0")))
|
||||
args.append("deleteOldOnSave={}".format(1))
|
||||
|
||||
args.append("begin={}".format(t_data.get("begin", "")))
|
||||
args.append("end={}".format(t_data.get("end", "")))
|
||||
args.append("name={}".format(quote(t_data.get("name", ""))))
|
||||
args.append("description={}".format(quote(t_data.get("description", ""))))
|
||||
args.append("tags={}".format(""))
|
||||
args.append("eit={}".format("0"))
|
||||
args.append("disabled={}".format(t_data.get("disabled", "1")))
|
||||
args.append("justplay={}".format(t_data.get("justplay", "1")))
|
||||
args.append("afterevent={}".format(t_data.get("afterevent", "0")))
|
||||
args.append("repeated={}".format(self.get_repetition_flags()))
|
||||
|
||||
if self._timer_location_switch.get_active():
|
||||
args.append("dirname={}".format(self._timer_location_entry.get_text()))
|
||||
|
||||
self._http_api.send(HttpAPI.Request.TIMER, "&".join(args), self.timer_add_edit_callback)
|
||||
|
||||
@run_idle
|
||||
def timer_add_edit_callback(self, resp):
|
||||
if "error_code" in resp:
|
||||
msg = "Error getting timer status.\n{}".format(resp.get("error_code"))
|
||||
self._app.show_error_dialog(msg)
|
||||
log(msg)
|
||||
return
|
||||
|
||||
state = resp.get("e2state", None)
|
||||
if state == "False":
|
||||
msg = resp.get("e2statetext", "")
|
||||
self._app.show_error_dialog(msg)
|
||||
log(msg)
|
||||
if state == "True":
|
||||
log(resp.get("e2statetext", ""))
|
||||
self._stack.set_visible_child_name(self._last_tool.value)
|
||||
else:
|
||||
log("Error getting timer status. No response!")
|
||||
|
||||
def on_timer_cancel(self, action, value=None):
|
||||
self._stack.set_visible_child_name(self._last_tool.value)
|
||||
|
||||
def on_timer_begins_set(self, action, value=None):
|
||||
self.set_begins_date(self.get_begins_date())
|
||||
|
||||
def on_timer_ends_set(self, action, value=None):
|
||||
self.set_ends_date(self.get_ends_date())
|
||||
|
||||
def get_begins_date(self):
|
||||
date = self._timer_begins_calendar.get_date()
|
||||
return datetime(year=date.year, month=date.month + 1, day=date.day,
|
||||
hour=int(self._timer_begins_hr_button.get_value()),
|
||||
minute=int(self._timer_begins_min_button.get_value()))
|
||||
|
||||
def set_begins_date(self, date):
|
||||
hour = date.hour
|
||||
minute = date.minute
|
||||
self._timer_begins_hr_button.set_value(hour)
|
||||
self._timer_begins_min_button.set_value(minute)
|
||||
self._timer_begins_calendar.select_day(date.day)
|
||||
self._timer_begins_calendar.select_month(date.month - 1, date.year)
|
||||
self._timer_begins_entry.set_text("{}-{}-{} {}:{:02d}".format(date.year, date.month, date.day, hour, minute))
|
||||
|
||||
def get_ends_date(self):
|
||||
date = self._timer_ends_calendar.get_date()
|
||||
return datetime(year=date.year, month=date.month + 1, day=date.day,
|
||||
hour=int(self._timer_ends_hr_button.get_value()),
|
||||
minute=int(self._timer_ends_min_button.get_value()))
|
||||
|
||||
def set_ends_date(self, date):
|
||||
hour = date.hour
|
||||
minute = date.minute
|
||||
self._timer_ends_hr_button.set_value(hour)
|
||||
self._timer_ends_min_button.set_value(minute)
|
||||
self._timer_ends_calendar.select_day(date.day)
|
||||
self._timer_ends_calendar.select_month(date.month - 1, date.year)
|
||||
self._timer_ends_entry.set_text("{}-{}-{} {}:{:02d}".format(date.year, date.month, date.day, hour, minute))
|
||||
|
||||
def set_timer_from_event_data(self, timer):
|
||||
self._stack.set_visible_child_name(self.Tool.TIMER.value)
|
||||
self._timer_action = self.TimerAction.EVENT
|
||||
self._timer_name_entry.set_text(timer.get("e2eventtitle", ""))
|
||||
self._timer_desc_entry.set_text(timer.get("e2eventdescription", ""))
|
||||
self._timer_service_entry.set_text(timer.get("e2eventservicename", ""))
|
||||
self._timer_service_ref_entry.set_text(timer.get("e2eventservicereference", ""))
|
||||
self._timer_event_id_entry.set_text(timer.get("e2eventid", ""))
|
||||
self._timer_action_combo_box.set_active_id("1")
|
||||
self._timer_after_combo_box.set_active_id("3")
|
||||
start_time = int(timer.get("e2eventstart", "0"))
|
||||
self.set_time_data(start_time, start_time + int(timer.get("e2eventduration", "0")))
|
||||
|
||||
def set_time_data(self, start_time, end_time):
|
||||
""" Sets values for time widgets. """
|
||||
ev_time_start = datetime.fromtimestamp(start_time) or datetime.now()
|
||||
ev_time_end = datetime.fromtimestamp(end_time) or datetime.now()
|
||||
self._timer_begins_entry.set_text(ev_time_start.strftime(self._TIME_STR))
|
||||
self._timer_ends_entry.set_text(ev_time_end.strftime(self._TIME_STR))
|
||||
self._timer_begins_calendar.select_day(ev_time_start.day)
|
||||
self._timer_begins_calendar.select_month(ev_time_start.month - 1, ev_time_start.year)
|
||||
self._timer_ends_calendar.select_day(ev_time_end.day)
|
||||
self._timer_ends_calendar.select_month(ev_time_end.month - 1, ev_time_end.year)
|
||||
self._timer_begins_hr_button.set_value(ev_time_start.hour)
|
||||
self._timer_begins_min_button.set_value(ev_time_start.minute)
|
||||
self._timer_ends_hr_button.set_value(ev_time_end.hour)
|
||||
self._timer_ends_min_button.set_value(ev_time_end.minute)
|
||||
|
||||
def get_timer_data(self):
|
||||
""" Returns timer data as a dict. """
|
||||
return {"sRef": self._timer_service_ref_entry.get_text(),
|
||||
"begin": int(datetime.strptime(self._timer_begins_entry.get_text(), self._TIME_STR).timestamp()),
|
||||
"end": int(datetime.strptime(self._timer_ends_entry.get_text(), self._TIME_STR).timestamp()),
|
||||
"name": self._timer_name_entry.get_text(),
|
||||
"description": self._timer_desc_entry.get_text(),
|
||||
"dirname": "",
|
||||
"eit": self._timer_event_id_entry.get_text(),
|
||||
"disabled": int(not self._timer_enabled_switch.get_active()),
|
||||
"justplay": self._timer_action_combo_box.get_active_id(),
|
||||
"afterevent": self._timer_after_combo_box.get_active_id(),
|
||||
"repeated": self.get_repetition_flags()}
|
||||
|
||||
def get_repetition_flags(self):
|
||||
""" Returns flags for repetition. """
|
||||
day_flags = 0
|
||||
for i, box in enumerate((self._timer_mo_check_button,
|
||||
self._timer_tu_check_button,
|
||||
self._timer_we_check_button,
|
||||
self._timer_th_check_button,
|
||||
self._timer_fr_check_button,
|
||||
self._timer_sa_check_button,
|
||||
self._timer_su_check_button)):
|
||||
|
||||
if box.get_active():
|
||||
day_flags = day_flags | (1 << i)
|
||||
|
||||
return day_flags
|
||||
|
||||
def set_repetition_flags(self, flags):
|
||||
for i, box in enumerate((self._timer_mo_check_button,
|
||||
self._timer_tu_check_button,
|
||||
self._timer_we_check_button,
|
||||
self._timer_th_check_button,
|
||||
self._timer_fr_check_button,
|
||||
self._timer_sa_check_button,
|
||||
self._timer_su_check_button)):
|
||||
box.set_active(flags & 1 == 1)
|
||||
flags = flags >> 1
|
||||
|
||||
# ***************** Drag-and-drop ********************* #
|
||||
|
||||
def on_timers_drag_data_received(self, box, context, x, y, data, info, time):
|
||||
txt = data.get_text()
|
||||
if txt:
|
||||
itr_str, sep, source = txt.partition(self._app.DRAG_SEP)
|
||||
if not source:
|
||||
return
|
||||
|
||||
itrs = itr_str.split(",")
|
||||
if len(itrs) > 1:
|
||||
self._app.show_error_dialog("Please, select only one item!")
|
||||
return
|
||||
|
||||
fav_id = None
|
||||
if source == self._app.FAV_MODEL_NAME:
|
||||
model = self._app.fav_view.get_model()
|
||||
fav_id = model.get_value(model.get_iter_from_string(itrs[0]), Column.FAV_ID)
|
||||
elif source == self._app.SERVICE_MODEL_NAME:
|
||||
model = self._app.services_view.get_model()
|
||||
fav_id = model.get_value(model.get_iter_from_string(itrs[0]), Column.SRV_FAV_ID)
|
||||
|
||||
service = self._app.current_services.get(fav_id, None)
|
||||
if service:
|
||||
self._timer_name_entry.set_text(service.service)
|
||||
self._timer_service_entry.set_text(service.service)
|
||||
self._timer_service_ref_entry.set_text(service.picon_id.rstrip(".png").replace("_", ":"))
|
||||
self.on_timer_add()
|
||||
context.finish(True, False, time)
|
||||
34
app/ui/default_style.css
Normal file
34
app/ui/default_style.css
Normal file
@@ -0,0 +1,34 @@
|
||||
* {
|
||||
-GtkDialog-action-area-border: 5em;
|
||||
}
|
||||
|
||||
entry {
|
||||
min-height: 2em;
|
||||
}
|
||||
|
||||
button {
|
||||
min-height: 1.5em;
|
||||
padding: 0.1em;
|
||||
}
|
||||
|
||||
spinbutton {
|
||||
min-height: 1.5em;
|
||||
}
|
||||
|
||||
toolbutton {
|
||||
padding: 0.1em;
|
||||
}
|
||||
|
||||
spinner {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
infobar {
|
||||
min-height: 2em;
|
||||
}
|
||||
|
||||
switch slider {
|
||||
min-height: 1.5em;
|
||||
min-width: 1.5em;
|
||||
}
|
||||
@@ -30,7 +30,7 @@ Author: Dmitriy Yefremov
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for macOS. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkAboutDialog" id="about_dialog">
|
||||
@@ -40,17 +40,18 @@ Author: Dmitriy Yefremov
|
||||
<property name="icon_name">system-help</property>
|
||||
<property name="type_hint">normal</property>
|
||||
<property name="program_name">DemonEditor</property>
|
||||
<property name="version">1.0.2 Beta</property>
|
||||
<property name="version">0.4.8 Pre-alpha</property>
|
||||
<property name="copyright">2018-2020 Dmitriy Yefremov
|
||||
</property>
|
||||
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor for GNU/Linux.</property>
|
||||
<property name="website">https://dyefremov.github.io/DemonEditor/</property>
|
||||
<property name="comments" translatable="yes">Enigma2 channel and satellites list editor for MacOS.
|
||||
(Experimental)</property>
|
||||
<property name="website">https://github.com/DYefremov/DemonEditor/tree/experimental-mac</property>
|
||||
<property name="license" translatable="yes">Это приложение распространяется без каких-либо гарантий.
|
||||
Подробнее в <a href="http://opensource.org/licenses/mit-license.php">The MIT License (MIT)</a>.</property>
|
||||
<property name="authors">Dmitriy Yefremov
|
||||
</property>
|
||||
<property name="translator_credits" translatable="yes">translator-credits</property>
|
||||
<property name="artists">Program logo: <a href="http://ihad.tv">mfgeg</a></property>
|
||||
<property name="artists">Program logo: <a href="http://ihad.tv"> mfgeg</a></property>
|
||||
<property name="logo_icon_name">demon-editor</property>
|
||||
<property name="wrap_license">True</property>
|
||||
<property name="license_type">mit-x11</property>
|
||||
@@ -97,7 +98,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="action">
|
||||
@@ -107,8 +107,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<accelerator key="Return" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
@@ -176,7 +174,6 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkBox" id="box4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="spinner">
|
||||
@@ -213,10 +210,10 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="primary-toolbar"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="app-notification"/>
|
||||
</style>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
""" Common module for showing dialogs """
|
||||
import locale
|
||||
import gettext
|
||||
from enum import Enum
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from app.commons import run_idle
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN, IS_GNOME_SESSION
|
||||
@@ -73,23 +72,21 @@ class WaitDialog:
|
||||
self._dialog.destroy()
|
||||
|
||||
|
||||
def show_dialog(dialog_type, transient, text=None, settings=None, action_type=None, file_filter=None, buttons=None,
|
||||
title=None):
|
||||
""" Shows dialogs by name. """
|
||||
def show_dialog(dialog_type: DialogType, transient, text=None, settings=None, action_type=None, file_filter=None):
|
||||
""" Shows dialogs by name """
|
||||
if dialog_type in (DialogType.INFO, DialogType.ERROR):
|
||||
return get_message_dialog(transient, dialog_type, Gtk.ButtonsType.OK, text)
|
||||
elif dialog_type is DialogType.CHOOSER and settings:
|
||||
return get_file_chooser_dialog(transient, text, settings, action_type, file_filter, buttons, title)
|
||||
return get_file_chooser_dialog(transient, text, settings, action_type, file_filter)
|
||||
elif dialog_type is DialogType.INPUT:
|
||||
return get_input_dialog(transient, text)
|
||||
elif dialog_type is DialogType.QUESTION:
|
||||
action = action_type if action_type else Gtk.ButtonsType.OK_CANCEL
|
||||
return get_message_dialog(transient, DialogType.QUESTION, action, text or "Are you sure?")
|
||||
return get_message_dialog(transient, DialogType.QUESTION, Gtk.ButtonsType.OK_CANCEL, text or "Are you sure?")
|
||||
elif dialog_type is DialogType.ABOUT:
|
||||
return get_about_dialog(transient)
|
||||
|
||||
|
||||
def get_chooser_dialog(transient, settings, name, patterns, title=None):
|
||||
def get_chooser_dialog(transient, settings, name, patterns):
|
||||
file_filter = Gtk.FileFilter()
|
||||
file_filter.set_name(name)
|
||||
for p in patterns:
|
||||
@@ -99,30 +96,30 @@ def get_chooser_dialog(transient, settings, name, patterns, title=None):
|
||||
transient=transient,
|
||||
settings=settings,
|
||||
action_type=Gtk.FileChooserAction.OPEN,
|
||||
file_filter=file_filter,
|
||||
title=title)
|
||||
file_filter=file_filter)
|
||||
|
||||
|
||||
def get_file_chooser_dialog(transient, text, settings, action_type, file_filter, buttons=None, title=None):
|
||||
text = get_message(text) if text else ""
|
||||
action_type = Gtk.FileChooserAction.SELECT_FOLDER if action_type is None else action_type
|
||||
buttons = buttons or (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
|
||||
dialog = Gtk.FileChooserDialog(text, transient, action_type, buttons, use_header_bar=IS_GNOME_SESSION)
|
||||
dialog.set_title(get_message(title) if title else "")
|
||||
def get_file_chooser_dialog(transient, text, settings, action_type, file_filter):
|
||||
dialog = Gtk.FileChooserNative()
|
||||
dialog.set_title(get_message(text) if text else "")
|
||||
dialog.set_transient_for(transient)
|
||||
dialog.set_action(action_type if action_type is not None else Gtk.FileChooserAction.SELECT_FOLDER)
|
||||
dialog.set_create_folders(False)
|
||||
dialog.set_modal(True)
|
||||
|
||||
if file_filter is not None:
|
||||
dialog.add_filter(file_filter)
|
||||
|
||||
dialog.set_current_folder(settings.data_local_path)
|
||||
path = settings.data_local_path
|
||||
dialog.set_current_folder(path)
|
||||
response = dialog.run()
|
||||
|
||||
if response not in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
path = Path(dialog.get_filename() or dialog.get_current_folder())
|
||||
if path.is_dir():
|
||||
response = "{}/".format(path.resolve())
|
||||
elif path.is_file():
|
||||
response = str(path.resolve())
|
||||
if response == Gtk.ResponseType.ACCEPT:
|
||||
if dialog.get_filename():
|
||||
path = dialog.get_filename()
|
||||
if action_type is not Gtk.FileChooserAction.OPEN:
|
||||
path = path + "/"
|
||||
response = path
|
||||
dialog.destroy()
|
||||
|
||||
return response
|
||||
@@ -176,7 +173,7 @@ def get_dialog_from_xml(dialog_type, transient, use_header=0, title=""):
|
||||
|
||||
def get_message(message):
|
||||
""" returns translated message """
|
||||
return locale.dgettext(TEXT_DOMAIN, message)
|
||||
return gettext.dgettext(TEXT_DOMAIN, message)
|
||||
|
||||
|
||||
@lru_cache(maxsize=5)
|
||||
|
||||
481
app/ui/download_dialog.glade
Normal file → Executable file
481
app/ui/download_dialog.glade
Normal file → Executable file
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2020 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -30,12 +30,25 @@ Author: Dmitriy Yefremov
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018 Dmitriy Yefremov -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for macOS. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="download_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">network-receive-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="send_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">network-transmit-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="download_dialog_window">
|
||||
<property name="width_request">500</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">FTP-transfer</property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
@@ -43,235 +56,30 @@ Author: Dmitriy Yefremov
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<property name="gravity">center</property>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">FTP-transfer</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="header_left_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="receive_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Receive</property>
|
||||
<signal name="clicked" handler="on_receive" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="receive_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-goto-bottom</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="send_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Send</property>
|
||||
<signal name="clicked" handler="on_send" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="send_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-goto-top</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="options_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Options</property>
|
||||
<signal name="clicked" handler="on_settings" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-properties</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_dialog_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">1</property>
|
||||
<property name="margin_right">1</property>
|
||||
<property name="margin_bottom">1</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="selection_data_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label10">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="all_radio_button">
|
||||
<property name="label" translatable="yes">All</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">satellites_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="bouquets_radio_button">
|
||||
<property name="label" translatable="yes">Bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">satellites_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="satellites_radio_button">
|
||||
<property name="label" translatable="yes">Satellites</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">all_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="webtv_radio_button">
|
||||
<property name="label" translatable="yes">WebTV</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">all_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Profile:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="profile_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="active">0</property>
|
||||
<property name="has_frame">False</property>
|
||||
<signal name="changed" handler="on_profile_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="main_settings_box_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="label_xalign">0.019999999552965164</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<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_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
@@ -357,7 +165,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="receives_default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="on_remove_unused_bouquets_toggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -388,7 +195,6 @@ Author: Dmitriy Yefremov
|
||||
<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>
|
||||
<signal name="state-set" handler="on_use_http_state_set" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -427,8 +233,8 @@ Author: Dmitriy Yefremov
|
||||
<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_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="label_xalign">0.019999999552965164</property>
|
||||
@@ -437,8 +243,9 @@ Author: Dmitriy Yefremov
|
||||
<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_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="row_spacing">2</property>
|
||||
<property name="column_spacing">2</property>
|
||||
@@ -618,6 +425,229 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="selection_data_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label10">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="all_radio_button">
|
||||
<property name="label" translatable="yes">All</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">satellites_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="bouquets_radio_button">
|
||||
<property name="label" translatable="yes">Bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">satellites_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="satellites_radio_button">
|
||||
<property name="label" translatable="yes">Satellites</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">all_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="webtv_radio_button">
|
||||
<property name="label" translatable="yes">WebTV</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">all_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Profile:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="profile_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="active">0</property>
|
||||
<property name="has_frame">False</property>
|
||||
<signal name="changed" handler="on_profile_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="options_button">
|
||||
<property name="label" translatable="yes">Options</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Options</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="clicked" handler="on_settings" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_left">20</property>
|
||||
<property name="margin_right">20</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="receive_button">
|
||||
<property name="label" translatable="yes">Receive</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Receive</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">download_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_receive" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="send_button">
|
||||
<property name="label" translatable="yes">Send</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Send</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">send_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_send" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkExpander" id="expander">
|
||||
<property name="visible">True</property>
|
||||
@@ -654,7 +684,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
<property name="position">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -710,12 +740,9 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
<property name="position">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -2,7 +2,7 @@ import os
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from app.commons import run_idle, run_task, log
|
||||
from app.commons import run_idle, run_task
|
||||
from app.connections import download_data, DownloadType, upload_data
|
||||
from app.settings import SettingsType
|
||||
from app.ui.backup import backup_data, restore_data
|
||||
@@ -24,8 +24,6 @@ class DownloadDialog:
|
||||
"on_settings_button": self.on_settings_button,
|
||||
"on_settings": self.on_settings,
|
||||
"on_profile_changed": self.on_profile_changed,
|
||||
"on_use_http_state_set": self.on_use_http_state_set,
|
||||
"on_remove_unused_bouquets_toggled": self.on_remove_unused_bouquets_toggled,
|
||||
"on_info_bar_close": self.on_info_bar_close}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
@@ -66,6 +64,7 @@ class DownloadDialog:
|
||||
self.update_profiles()
|
||||
self.init_ui_settings()
|
||||
|
||||
@run_idle
|
||||
def init_ui_settings(self):
|
||||
self._host_entry.set_text(self._settings.host)
|
||||
self._data_path_entry.set_text(self._settings.data_local_path)
|
||||
@@ -73,8 +72,7 @@ class DownloadDialog:
|
||||
self._webtv_radio_button.set_visible(not is_enigma)
|
||||
self._http_radio_button.set_visible(is_enigma)
|
||||
self._use_http_box.set_visible(is_enigma)
|
||||
self._use_http_switch.set_active(is_enigma and self._settings.use_http)
|
||||
self._remove_unused_check_button.set_active(self._settings.remove_unused_bouquets)
|
||||
self._use_http_switch.set_active(is_enigma)
|
||||
|
||||
def update_profiles(self):
|
||||
self._profile_combo_box.remove_all()
|
||||
@@ -145,19 +143,13 @@ class DownloadDialog:
|
||||
self._s_type = self._settings.setting_type
|
||||
self.init_ui_settings()
|
||||
|
||||
def on_use_http_state_set(self, button, state):
|
||||
self._settings.use_http = state
|
||||
|
||||
def on_remove_unused_bouquets_toggled(self, button):
|
||||
self._settings.remove_unused_bouquets = button.get_active()
|
||||
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
|
||||
@run_task
|
||||
def download(self, download, d_type):
|
||||
""" Download/upload data from/to receiver """
|
||||
GLib.idle_add(self._expander.set_expanded, True)
|
||||
self._expander.set_expanded(True)
|
||||
self.clear_output()
|
||||
backup, backup_src, data_path = self._settings.backup_before_downloading, None, None
|
||||
|
||||
@@ -179,9 +171,8 @@ class DownloadDialog:
|
||||
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:
|
||||
msg = "Downloading data error: {}"
|
||||
log(msg.format(e), debug=self._settings.debug_mode, fmt_message=msg)
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
message = str(getattr(e, "message", str(e)))
|
||||
self.show_info_message(message, Gtk.MessageType.ERROR)
|
||||
if all((download, backup, data_path)):
|
||||
restore_data(backup_src, data_path)
|
||||
else:
|
||||
|
||||
@@ -33,6 +33,18 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="apply_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-save-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="auto_config_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-select-all-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="bouquet_list_store">
|
||||
<columns>
|
||||
<!-- column-name num -->
|
||||
@@ -75,10 +87,16 @@ Author: Dmitriy Yefremov
|
||||
<property name="image">copy_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_copy_ref" swapped="no"/>
|
||||
<accelerator key="c" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
<accelerator key="c" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="filter_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-find-replace-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="insert_link_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
@@ -100,7 +118,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="image">insert_link_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_assign_ref" swapped="no"/>
|
||||
<accelerator key="v" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
<accelerator key="v" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
@@ -130,6 +148,12 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="save_to_xml_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-save-as-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="services_list_store">
|
||||
<columns>
|
||||
<!-- column-name service -->
|
||||
@@ -559,58 +583,104 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="update_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">emblem-synchronizing-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="epg_dialog_window">
|
||||
<property name="width_request">480</property>
|
||||
<property name="height_request">320</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">EPG</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="default_width">480</property>
|
||||
<property name="default_height">240</property>
|
||||
<property name="default_height">320</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="icon_name">gtk-index</property>
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<property name="gravity">center</property>
|
||||
<signal name="check-resize" handler="on_resize" swapped="no"/>
|
||||
<signal name="delete-event" handler="on_close_dialog" swapped="no"/>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header_bar">
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">EPG</property>
|
||||
<property name="subtitle" translatable="yes">List configuration</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<property name="margin_left">1</property>
|
||||
<property name="margin_right">1</property>
|
||||
<property name="margin_bottom">1</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="left_header_box">
|
||||
<object class="GtkBox" id="main_actions_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="margin_left">15</property>
|
||||
<property name="margin_right">15</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="apply_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Apply</property>
|
||||
<signal name="clicked" handler="on_apply" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="apply_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-apply</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<object class="GtkButtonBox" id="left_action_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="apply_button">
|
||||
<property name="label" translatable="yes">Apply</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Apply</property>
|
||||
<property name="image">apply_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_apply" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="update_button">
|
||||
<property name="label" translatable="yes">Update</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Update</property>
|
||||
<property name="image">update_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_update" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="filter_button">
|
||||
<property name="label" translatable="yes">Filter</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Filter</property>
|
||||
<property name="image">filter_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
|
||||
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -618,20 +688,11 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="update_button">
|
||||
<child type="center">
|
||||
<object class="GtkLabel" id="list_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Update</property>
|
||||
<signal name="clicked" handler="on_update" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="update_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-refresh</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">List configuration</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -640,111 +701,106 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="filter_button">
|
||||
<object class="GtkButtonBox" id="right_action_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Filter</property>
|
||||
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="filter_button_image">
|
||||
<object class="GtkButton" id="auto_config_button">
|
||||
<property name="label" translatable="yes">Auto</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-spell-check</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Auto configuration by service names.</property>
|
||||
<property name="image">auto_config_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_auto_configuration" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="right_header_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="auto_config_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Auto configuration by service names.</property>
|
||||
<signal name="clicked" handler="on_auto_configuration" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="auto_config_button_image">
|
||||
<object class="GtkButton" id="save_to_xml_button">
|
||||
<property name="label" translatable="yes">Save</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-find-and-replace</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Save list to xml.</property>
|
||||
<property name="image">save_to_xml_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_save_to_xml" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="options_menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="direction">none</property>
|
||||
<property name="popover">options_popover</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="options_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Options</property>
|
||||
<property name="icon_name">applications-system-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Options</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="save_to_xml_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Save list to xml.</property>
|
||||
<signal name="clicked" handler="on_save_to_xml" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="save_to_xml_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-save-as</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="options_menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="direction">none</property>
|
||||
<property name="popover">options_popover</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="options_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Options</property>
|
||||
<property name="stock">gtk-properties</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkSearchBar" id="filter_bar">
|
||||
<property name="visible">True</property>
|
||||
@@ -753,7 +809,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkSearchEntry" id="filter_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_name">tools-check-spelling</property>
|
||||
<property name="primary_icon_name">edit-find-replace-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
<signal name="search-changed" handler="on_filter_changed" swapped="no"/>
|
||||
@@ -862,13 +918,14 @@ Author: Dmitriy Yefremov
|
||||
<property name="height_request">24</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">2</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-properties</property>
|
||||
<property name="icon_name">document-properties-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -890,6 +947,7 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="source_info_box">
|
||||
<property name="height_request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
@@ -1143,16 +1201,17 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="bouquet_info_bar_box">
|
||||
<property name="height_request">24</property>
|
||||
<property name="height_request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">2</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-properties</property>
|
||||
<property name="icon_name">document-properties-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -1189,7 +1248,9 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">2</property>
|
||||
<property name="stock">gtk-index</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
||||
@@ -15,7 +15,7 @@ from app.eparser.ecommons import BouquetService, BqServiceType
|
||||
from app.tools.epg import EPG, ChannelsParser
|
||||
from app.ui.dialogs import get_message, show_dialog, DialogType
|
||||
from .main_helper import on_popup_menu, update_entry_data
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, Column, EPG_ICON, KeyboardKey
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, Column, EPG_ICON, KeyboardKey, MOD_MASK
|
||||
|
||||
|
||||
class RefsSource(Enum):
|
||||
@@ -279,7 +279,7 @@ class EpgDialog:
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if ctrl and key is KeyboardKey.C:
|
||||
self.on_copy_ref()
|
||||
|
||||
@@ -26,13 +26,25 @@ THE SOFTWARE.
|
||||
Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface>
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for macOS. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="details_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">emblem-important-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="import_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-revert-symbolic-rtl</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="main_list_store">
|
||||
<columns>
|
||||
<!-- column-name name -->
|
||||
@@ -82,6 +94,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
<object class="GtkWindow" id="dialog_window">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Import</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="default_width">480</property>
|
||||
@@ -90,53 +103,8 @@ Author: Dmitriy Yefremov
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="gravity">center</property>
|
||||
<signal name="check-resize" handler="on_resize" swapped="no"/>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Import</property>
|
||||
<property name="subtitle" translatable="yes">Bouquets and services</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="import_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Import</property>
|
||||
<signal name="clicked" handler="on_import" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="import_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-revert-to-saved</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="info_check_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Details</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="info_check_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-dialog-info</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
@@ -161,8 +129,8 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="label" translatable="yes">Bouquets</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -260,8 +228,8 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="label" translatable="yes">Bouquet details</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -337,6 +305,62 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="actions_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_left">15</property>
|
||||
<property name="margin_right">15</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="layout_style">start</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="import_button">
|
||||
<property name="label" translatable="yes">Import</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Bouquets and services</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">import_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_import" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="info_check_button">
|
||||
<property name="label" translatable="yes">Details</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Details</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">details_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
|
||||
<accelerator key="i" signal="clicked"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
<property name="secondary">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="info_bar">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -382,7 +406,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from contextlib import suppress
|
||||
from pathlib import Path
|
||||
|
||||
from app.commons import run_idle, log
|
||||
from app.commons import run_idle
|
||||
from app.eparser import get_bouquets, get_services
|
||||
from app.eparser.ecommons import BqType, BqServiceType, Bouquet
|
||||
from app.eparser.enigma.bouquets import get_bouquet
|
||||
@@ -12,7 +12,7 @@ from app.ui.main_helper import on_popup_menu
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, KeyboardKey, Column
|
||||
|
||||
|
||||
def import_bouquet(transient, model, path, settings, services, appender, file_path=None):
|
||||
def import_bouquet(transient, model, path, settings, services, appender):
|
||||
""" Import of single bouquet """
|
||||
itr = model.get_iter(path)
|
||||
bq_type = BqType(model.get(itr, Column.BQ_TYPE)[0])
|
||||
@@ -30,7 +30,7 @@ def import_bouquet(transient, model, path, settings, services, appender, file_pa
|
||||
elif bq_type is BqType.WEBTV:
|
||||
f_pattern = "webtv.xml"
|
||||
|
||||
file_path = file_path or get_chooser_dialog(transient, settings, "bouquet files", (f_pattern,))
|
||||
file_path = get_chooser_dialog(transient, settings, "bouquet files", (f_pattern,))
|
||||
if file_path == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
@@ -119,7 +119,6 @@ class ImportDialog:
|
||||
self._services_model.clear()
|
||||
try:
|
||||
if not self._bouquets:
|
||||
log("Import [init data]: getting bouquets...")
|
||||
self._bouquets = get_bouquets(path, self._profile)
|
||||
for bqs in self._bouquets:
|
||||
for bq in bqs.bouquets:
|
||||
@@ -130,7 +129,6 @@ class ImportDialog:
|
||||
for srv in services:
|
||||
self._services[srv.fav_id] = srv
|
||||
except FileNotFoundError as e:
|
||||
log("Import error [init data]: {}".format(e))
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
|
||||
def on_import(self, item):
|
||||
@@ -141,17 +139,9 @@ class ImportDialog:
|
||||
if not self._bouquets or show_dialog(DialogType.QUESTION, self._dialog_window) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
self.import_data()
|
||||
|
||||
@run_idle
|
||||
def import_data(self):
|
||||
""" Importing data into models. """
|
||||
if not self._bouquets:
|
||||
return
|
||||
|
||||
log("Importing data...")
|
||||
services = set()
|
||||
to_delete = set()
|
||||
|
||||
for row in self._main_model:
|
||||
bq = (row[0], row[1])
|
||||
if row[-1]:
|
||||
@@ -161,16 +151,19 @@ class ImportDialog:
|
||||
services.add(srv)
|
||||
else:
|
||||
to_delete.add(bq)
|
||||
|
||||
bqs_to_delete = []
|
||||
for bqs in self._bouquets:
|
||||
for bq in bqs.bouquets:
|
||||
if (bq.name, bq.type) in to_delete:
|
||||
bqs_to_delete.append(bq)
|
||||
|
||||
for bqs in self._bouquets:
|
||||
bq = bqs.bouquets
|
||||
for b in bqs_to_delete:
|
||||
with suppress(ValueError):
|
||||
bq.remove(b)
|
||||
|
||||
self._append(self._bouquets, list(filter(lambda s: s.fav_id not in self._service_ids, services)))
|
||||
self._dialog_window.destroy()
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2020 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2019 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,8 +31,37 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="remove_selection_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-undo</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="yt_popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="select_all_popup_item">
|
||||
<property name="label">gtk-select-all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_select_all" object="yt_list_view" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="unselect_all_popup_item">
|
||||
<property name="label" translatable="yes">Remove selection</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">remove_selection_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_unselect_all" object="yt_list_view" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkDialog" id="search_unavailable_streams_dialog">
|
||||
<property name="use-header-bar">1</property>
|
||||
<property name="width_request">320</property>
|
||||
@@ -48,6 +77,9 @@ Author: Dmitriy Yefremov
|
||||
<property name="decorated">False</property>
|
||||
<property name="gravity">center</property>
|
||||
<signal name="response" handler="on_response" swapped="no"/>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="search_unavailable_dialog_box">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -219,9 +251,6 @@ Author: Dmitriy Yefremov
|
||||
<row>
|
||||
<col id="0">none-REC2</col>
|
||||
</row>
|
||||
<row>
|
||||
<col id="0">eServiceUri</col>
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkDialog" id="iptv_dialog">
|
||||
@@ -243,7 +272,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="action">
|
||||
@@ -252,7 +280,6 @@ Author: Dmitriy Yefremov
|
||||
<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_save" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
@@ -263,10 +290,12 @@ Author: Dmitriy Yefremov
|
||||
<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_save" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="iptv_dialog_box">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -736,7 +765,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="action">
|
||||
@@ -746,7 +774,6 @@ Author: Dmitriy Yefremov
|
||||
<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_apply" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
@@ -1218,35 +1245,6 @@ Author: Dmitriy Yefremov
|
||||
<action-widget response="-6">close_config_list_button</action-widget>
|
||||
</action-widgets>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_selection_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-undo</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="yt_popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="select_all_popup_item">
|
||||
<property name="label">gtk-select-all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_select_all" object="yt_list_view" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="unselect_all_popup_item">
|
||||
<property name="label" translatable="yes">Remove selection</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">remove_selection_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_unselect_all" object="yt_list_view" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkListStore" id="yt_liststore">
|
||||
<columns>
|
||||
<!-- column-name title -->
|
||||
@@ -1276,9 +1274,21 @@ Author: Dmitriy Yefremov
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkImage" id="yt_receive_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">network-receive-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="yt_import_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-revert-symbolic-rtl</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="yt_import_dialog_window">
|
||||
<property name="width_request">480</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">YouTube</property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
@@ -1287,82 +1297,110 @@ Author: Dmitriy Yefremov
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<property name="gravity">center</property>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="yt_header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="title" translatable="yes">YouTube</property>
|
||||
<property name="subtitle" translatable="yes">Playlist import</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="yt_receive_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Receive</property>
|
||||
<signal name="clicked" handler="on_receive" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="yt_receive_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-goto-bottom</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="d" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="yt_import_button">
|
||||
<property name="visible">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Import</property>
|
||||
<signal name="clicked" handler="on_import" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="yt_import_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">insert-link</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="yt_quality_combobox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Desired video quality</property>
|
||||
<property name="model">yt_quality_liststore</property>
|
||||
<property name="active">0</property>
|
||||
<property name="id_column">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="yt_quality_renderer"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="yt_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="yt_actions_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">15</property>
|
||||
<property name="margin_right">15</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="yt_receive_button">
|
||||
<property name="label" translatable="yes">Receive</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Receive</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">yt_receive_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_receive" swapped="no"/>
|
||||
<accelerator key="d" signal="clicked"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="center">
|
||||
<object class="GtkLabel" id="playlist_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Playlist import</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="yt_impotr_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="yt_quality_combobox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Desired video quality</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="model">yt_quality_liststore</property>
|
||||
<property name="active">0</property>
|
||||
<property name="id_column">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="yt_quality_renderer1"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="yt_import_button">
|
||||
<property name="label" translatable="yes">Import</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Import</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">yt_import_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_import" swapped="no"/>
|
||||
<accelerator key="i" signal="clicked"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="yt_url_entry">
|
||||
<property name="visible">True</property>
|
||||
@@ -1377,8 +1415,9 @@ Author: Dmitriy Yefremov
|
||||
<signal name="changed" handler="on_yt_url_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -1475,7 +1514,7 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="yt_info_bar_box">
|
||||
<property name="height_request">24</property>
|
||||
<property name="height_request">26</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">10</property>
|
||||
@@ -1489,7 +1528,8 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkImage" id="yt_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-properties</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="icon_name">document-properties-symbolic</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
||||
134
app/ui/iptv.py
134
app/ui/iptv.py
@@ -2,7 +2,7 @@ import concurrent.futures
|
||||
import re
|
||||
import urllib
|
||||
from urllib.error import HTTPError
|
||||
from urllib.parse import urlparse, unquote, quote
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
from gi.repository import GLib
|
||||
@@ -11,11 +11,11 @@ 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, get_fav_id, MARKER_FORMAT
|
||||
from app.settings import SettingsType
|
||||
from app.tools.yt import PlayListParser, YouTubeException, YouTube
|
||||
from app.tools.yt import YouTube, PlayListParser
|
||||
from .dialogs import Action, show_dialog, DialogType, get_dialogs_string, get_message
|
||||
from .main_helper import get_base_model, get_iptv_url, on_popup_menu
|
||||
from .uicommons import (Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON, Column, IS_GNOME_SESSION, KeyboardKey,
|
||||
get_yt_icon)
|
||||
from .uicommons import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON, Column, IS_GNOME_SESSION, KeyboardKey, \
|
||||
get_yt_icon
|
||||
|
||||
_DIGIT_ENTRY_NAME = "digit-entry"
|
||||
_ENIGMA2_REFERENCE = "{}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
|
||||
@@ -38,14 +38,12 @@ def get_stream_type(box):
|
||||
return StreamType.NONE_TS.value
|
||||
elif active == 2:
|
||||
return StreamType.NONE_REC_1.value
|
||||
elif active == 3:
|
||||
return StreamType.NONE_REC_2.value
|
||||
return StreamType.E_SERVICE_URI.value
|
||||
return StreamType.NONE_REC_2.value
|
||||
|
||||
|
||||
class IptvDialog:
|
||||
|
||||
def __init__(self, transient, view, services, bouquet, settings, action=Action.ADD):
|
||||
def __init__(self, transient, view, services, bouquet, profile=SettingsType.ENIGMA_2, action=Action.ADD):
|
||||
handlers = {"on_response": self.on_response,
|
||||
"on_entry_changed": self.on_entry_changed,
|
||||
"on_url_changed": self.on_url_changed,
|
||||
@@ -54,20 +52,18 @@ class IptvDialog:
|
||||
"on_yt_quality_changed": self.on_yt_quality_changed,
|
||||
"on_info_bar_close": self.on_info_bar_close}
|
||||
|
||||
self._action = action
|
||||
self._s_type = settings.setting_type
|
||||
self._settings = settings
|
||||
self._bouquet = bouquet
|
||||
self._services = services
|
||||
self._yt_links = None
|
||||
self._yt_dl = None
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
||||
("iptv_dialog", "stream_type_liststore", "yt_quality_liststore"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._action = action
|
||||
self._profile = profile
|
||||
self._bouquet = bouquet
|
||||
self._services = services
|
||||
self._yt_links = None
|
||||
|
||||
self._dialog = builder.get_object("iptv_dialog")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._name_entry = builder.get_object("name_entry")
|
||||
@@ -95,7 +91,7 @@ class IptvDialog:
|
||||
for el in self._digit_elems:
|
||||
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
if self._s_type is SettingsType.NEUTRINO_MP:
|
||||
if profile is SettingsType.NEUTRINO_MP:
|
||||
builder.get_object("iptv_dialog_ts_data_frame").set_visible(False)
|
||||
builder.get_object("iptv_type_label").set_visible(False)
|
||||
builder.get_object("reference_entry").set_visible(False)
|
||||
@@ -108,8 +104,8 @@ class IptvDialog:
|
||||
if self._action is Action.ADD:
|
||||
self._save_button.set_visible(False)
|
||||
self._add_button.set_visible(True)
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self.update_reference_entry()
|
||||
if self._profile is SettingsType.ENIGMA_2:
|
||||
self._update_reference_entry()
|
||||
self._stream_type_combobox.set_active(1)
|
||||
elif self._action is Action.EDIT:
|
||||
self._current_srv = get_base_model(self._model)[self._paths][:]
|
||||
@@ -119,7 +115,7 @@ class IptvDialog:
|
||||
self._dialog.run()
|
||||
|
||||
def on_response(self, dialog, response):
|
||||
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
if response == Gtk.ResponseType.CANCEL:
|
||||
self._dialog.destroy()
|
||||
|
||||
def on_save(self, item):
|
||||
@@ -130,16 +126,16 @@ class IptvDialog:
|
||||
self.show_info_message(get_message("Error. Verify the data!"), Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
self.save_enigma2_data() if self._s_type is SettingsType.ENIGMA_2 else self.save_neutrino_data()
|
||||
self.save_enigma2_data() if self._profile is SettingsType.ENIGMA_2 else self.save_neutrino_data()
|
||||
self._dialog.destroy()
|
||||
|
||||
def init_data(self, srv):
|
||||
name, fav_id = srv[2], srv[7]
|
||||
self._name_entry.set_text(name)
|
||||
self.init_enigma2_data(fav_id) if self._s_type is SettingsType.ENIGMA_2 else self.init_neutrino_data(fav_id)
|
||||
self.init_enigma2_data(fav_id) if self._profile is SettingsType.ENIGMA_2 else self.init_neutrino_data(fav_id)
|
||||
|
||||
def init_enigma2_data(self, fav_id):
|
||||
data, sep, desc = fav_id.partition("#DESCRIPTION")
|
||||
@@ -159,8 +155,6 @@ class IptvDialog:
|
||||
self._stream_type_combobox.set_active(2)
|
||||
elif stream_type is StreamType.NONE_REC_2:
|
||||
self._stream_type_combobox.set_active(3)
|
||||
elif stream_type is StreamType.E_SERVICE_URI:
|
||||
self._stream_type_combobox.set_active(4)
|
||||
except ValueError:
|
||||
self.show_info_message("Unknown stream type {}".format(s_type), Gtk.MessageType.ERROR)
|
||||
|
||||
@@ -169,17 +163,16 @@ class IptvDialog:
|
||||
self._tr_id_entry.set_text(str(int(data[4], 16)))
|
||||
self._net_id_entry.set_text(str(int(data[5], 16)))
|
||||
self._namespace_entry.set_text(str(int(data[6], 16)))
|
||||
self._url_entry.set_text(unquote(data[10].strip()))
|
||||
self.update_reference_entry()
|
||||
self._url_entry.set_text(urllib.request.unquote(data[10].strip()))
|
||||
self._update_reference_entry()
|
||||
|
||||
def init_neutrino_data(self, fav_id):
|
||||
data = fav_id.split("::")
|
||||
self._url_entry.set_text(data[0])
|
||||
self._description_entry.set_text(data[1])
|
||||
|
||||
def update_reference_entry(self):
|
||||
if self._s_type is SettingsType.ENIGMA_2 and is_data_correct(self._digit_elems):
|
||||
self.on_url_changed(self._url_entry)
|
||||
def _update_reference_entry(self):
|
||||
if self._profile is SettingsType.ENIGMA_2:
|
||||
self._reference_entry.set_text(_ENIGMA2_REFERENCE.format(self.get_type(),
|
||||
self._srv_type_entry.get_text(),
|
||||
int(self._sid_entry.get_text()),
|
||||
@@ -195,13 +188,12 @@ class IptvDialog:
|
||||
entry.set_name(_DIGIT_ENTRY_NAME)
|
||||
else:
|
||||
entry.set_name("GtkEntry")
|
||||
self.update_reference_entry()
|
||||
self._update_reference_entry()
|
||||
|
||||
def on_url_changed(self, entry):
|
||||
url_str = entry.get_text()
|
||||
url = urlparse(url_str)
|
||||
cond = all([url.scheme, url.netloc, url.path]) or self.get_type() == StreamType.E_SERVICE_URI.value
|
||||
entry.set_name("GtkEntry" if cond else _DIGIT_ENTRY_NAME)
|
||||
entry.set_name("GtkEntry" if all([url.scheme, url.netloc, url.path]) else _DIGIT_ENTRY_NAME)
|
||||
|
||||
yt_id = YouTube.get_yt_id(url_str)
|
||||
if yt_id:
|
||||
@@ -219,21 +211,10 @@ class IptvDialog:
|
||||
|
||||
def set_yt_url(self, entry, video_id):
|
||||
try:
|
||||
if not self._yt_dl:
|
||||
def callback(message, error=True):
|
||||
msg_type = Gtk.MessageType.ERROR if error else Gtk.MessageType.INFO
|
||||
self.show_info_message(message, msg_type)
|
||||
|
||||
self._yt_dl = YouTube.get_instance(self._settings, callback=callback)
|
||||
yield True
|
||||
links, title = self._yt_dl.get_yt_link(video_id, entry.get_text())
|
||||
yield True
|
||||
links, title = YouTube.get_yt_link(video_id)
|
||||
except urllib.error.URLError as e:
|
||||
self.show_info_message(get_message("Getting link error:") + (str(e)), Gtk.MessageType.ERROR)
|
||||
return
|
||||
except YouTubeException as e:
|
||||
self.show_info_message((str(e)), Gtk.MessageType.ERROR)
|
||||
return
|
||||
else:
|
||||
if self._action is Action.ADD:
|
||||
self._name_entry.set_text(title)
|
||||
@@ -251,9 +232,7 @@ class IptvDialog:
|
||||
yield True
|
||||
|
||||
def on_stream_type_changed(self, item):
|
||||
if self.get_type() == StreamType.E_SERVICE_URI.value:
|
||||
self.show_info_message("DreamOS only!", Gtk.MessageType.WARNING)
|
||||
self.update_reference_entry()
|
||||
self._update_reference_entry()
|
||||
|
||||
def on_yt_quality_changed(self, box):
|
||||
model = box.get_model()
|
||||
@@ -269,7 +248,7 @@ class IptvDialog:
|
||||
int(self._tr_id_entry.get_text()),
|
||||
int(self._net_id_entry.get_text()),
|
||||
int(self._namespace_entry.get_text()),
|
||||
quote(self._url_entry.get_text()),
|
||||
urllib.request.quote(self._url_entry.get_text()),
|
||||
name, name)
|
||||
self.update_bouquet_data(name, fav_id)
|
||||
|
||||
@@ -312,7 +291,7 @@ class IptvDialog:
|
||||
|
||||
class SearchUnavailableDialog:
|
||||
|
||||
def __init__(self, transient, model, fav_bouquet, iptv_rows, s_type):
|
||||
def __init__(self, transient, model, fav_bouquet, iptv_rows, profile):
|
||||
handlers = {"on_response": self.on_response}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
@@ -326,7 +305,7 @@ class SearchUnavailableDialog:
|
||||
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._s_type = s_type
|
||||
self._profile = profile
|
||||
self._iptv_rows = iptv_rows
|
||||
self._counter = -1
|
||||
self._max_rows = len(self._iptv_rows)
|
||||
@@ -354,7 +333,7 @@ class SearchUnavailableDialog:
|
||||
if not self._download_task:
|
||||
return
|
||||
try:
|
||||
req = Request(get_iptv_url(row, self._s_type))
|
||||
req = Request(get_iptv_url(row, self._profile))
|
||||
self.update_bar()
|
||||
urlopen(req, timeout=2)
|
||||
except HTTPError as e:
|
||||
@@ -396,7 +375,7 @@ class SearchUnavailableDialog:
|
||||
|
||||
class IptvListConfigurationDialog:
|
||||
|
||||
def __init__(self, transient, services, iptv_rows, bouquet, fav_model, s_type):
|
||||
def __init__(self, transient, services, iptv_rows, bouquet, fav_model, profile):
|
||||
handlers = {"on_apply": self.on_apply,
|
||||
"on_response": self.on_response,
|
||||
"on_stream_type_default_togged": self.on_stream_type_default_togged,
|
||||
@@ -410,18 +389,18 @@ class IptvListConfigurationDialog:
|
||||
"on_entry_changed": self.on_entry_changed,
|
||||
"on_info_bar_close": self.on_info_bar_close}
|
||||
|
||||
self._rows = iptv_rows
|
||||
self._services = services
|
||||
self._bouquet = bouquet
|
||||
self._fav_model = fav_model
|
||||
self._s_type = s_type
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
||||
("iptv_list_configuration_dialog", "stream_type_liststore"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._rows = iptv_rows
|
||||
self._services = services
|
||||
self._bouquet = bouquet
|
||||
self._fav_model = fav_model
|
||||
self._profile = profile
|
||||
|
||||
self._dialog = builder.get_object("iptv_list_configuration_dialog")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._info_bar = builder.get_object("list_configuration_info_bar")
|
||||
@@ -508,7 +487,7 @@ class IptvListConfigurationDialog:
|
||||
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
|
||||
return
|
||||
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
if self._profile is SettingsType.ENIGMA_2:
|
||||
reset = self._reset_to_default_switch.get_active()
|
||||
type_default = self._type_check_button.get_active()
|
||||
tid_default = self._tid_check_button.get_active()
|
||||
@@ -562,7 +541,7 @@ class IptvListConfigurationDialog:
|
||||
|
||||
|
||||
class YtListImportDialog:
|
||||
def __init__(self, transient, settings, appender):
|
||||
def __init__(self, transient, profile, appender):
|
||||
handlers = {"on_import": self.on_import,
|
||||
"on_receive": self.on_receive,
|
||||
"on_yt_url_entry_changed": self.on_url_entry_changed,
|
||||
@@ -574,19 +553,12 @@ class YtListImportDialog:
|
||||
"on_key_press": self.on_key_press,
|
||||
"on_close": self.on_close}
|
||||
|
||||
self.appender = appender
|
||||
self._s_type = settings.setting_type
|
||||
self._download_task = False
|
||||
self._yt_list_id = None
|
||||
self._yt_list_title = None
|
||||
self._settings = settings
|
||||
self._yt = None
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
||||
("yt_import_dialog_window", "yt_liststore", "yt_quality_liststore",
|
||||
"yt_popup_menu", "remove_selection_image"))
|
||||
"yt_popup_menu", "remove_selection_image", "yt_receive_image",
|
||||
"yt_import_image"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._dialog = builder.get_object("yt_import_dialog_window")
|
||||
@@ -612,6 +584,12 @@ class YtListImportDialog:
|
||||
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
|
||||
self.appender = appender
|
||||
self._profile = profile
|
||||
self._download_task = False
|
||||
self._yt_list_id = None
|
||||
self._yt_list_title = None
|
||||
|
||||
def show(self):
|
||||
self._dialog.show()
|
||||
|
||||
@@ -625,11 +603,7 @@ class YtListImportDialog:
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
||||
done_links = {}
|
||||
rows = list(filter(lambda r: r[2], self._model))
|
||||
if not self._yt:
|
||||
self._yt = YouTube.get_instance(self._settings)
|
||||
|
||||
futures = {executor.submit(self._yt.get_yt_link, r[1], YouTube.VIDEO_LINK.format(r[1]),
|
||||
True): r for r in rows}
|
||||
futures = {executor.submit(YouTube.get_yt_link, r[1]): r for r in rows}
|
||||
size = len(futures)
|
||||
counter = 0
|
||||
|
||||
@@ -641,8 +615,6 @@ class YtListImportDialog:
|
||||
done_links[futures[future]] = future.result()
|
||||
counter += 1
|
||||
self.update_progress_bar(counter / size)
|
||||
except YouTubeException as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
except Exception as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
@@ -676,8 +648,8 @@ class YtListImportDialog:
|
||||
self.update_active_elements(True)
|
||||
|
||||
def update_links(self, links):
|
||||
for link in links:
|
||||
yield self._model.append((link[0], link[1], True, None))
|
||||
for l in links:
|
||||
yield self._model.append((l[0], l[1], True, None))
|
||||
|
||||
size = len(self._model)
|
||||
self._yt_count_label.set_text(str(size))
|
||||
@@ -697,11 +669,11 @@ class YtListImportDialog:
|
||||
|
||||
act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0)
|
||||
for link in links:
|
||||
lnk, title = link or (None, None)
|
||||
lnk, title = link
|
||||
if not lnk:
|
||||
continue
|
||||
ln = lnk.get(act) if act in lnk else lnk[sorted(lnk, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
|
||||
fav_id = get_fav_id(ln, title, self._s_type)
|
||||
fav_id = get_fav_id(ln, title, self._profile)
|
||||
srv = Service(None, None, IPTV_ICON, title, *aggr[0:3], BqServiceType.IPTV.name, *aggr, None, fav_id, None)
|
||||
srvs.append(srv)
|
||||
self.appender(srvs)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
""" Helper module for the ui. """
|
||||
""" This is helper module for ui """
|
||||
import os
|
||||
import shutil
|
||||
from urllib.parse import unquote
|
||||
import urllib.request
|
||||
|
||||
from gi.repository import GdkPixbuf, GLib
|
||||
|
||||
@@ -16,28 +16,23 @@ from .uicommons import ViewTarget, BqGenType, Gtk, Gdk, HIDE_ICON, LOCKED_ICON,
|
||||
|
||||
# ***************** Markers *******************#
|
||||
|
||||
def insert_marker(view, bouquets, selected_bouquet, services, parent_window, m_type=BqServiceType.MARKER):
|
||||
def insert_marker(view, bouquets, selected_bouquet, services, parent_window):
|
||||
"""" Inserts marker into bouquet services list. """
|
||||
fav_id, text = "1:832:D:0:0:0:0:0:0:0:\n", None
|
||||
response = show_dialog(DialogType.INPUT, parent_window)
|
||||
if response == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
if m_type is BqServiceType.MARKER:
|
||||
response = show_dialog(DialogType.INPUT, parent_window)
|
||||
if response == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
if not response.strip():
|
||||
show_dialog(DialogType.ERROR, parent_window, "The text of marker is empty, please try again!")
|
||||
return
|
||||
|
||||
if not response.strip():
|
||||
show_dialog(DialogType.ERROR, parent_window, "The text of marker is empty, please try again!")
|
||||
return
|
||||
|
||||
fav_id = "1:64:0:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n".format(response, response)
|
||||
text = response
|
||||
|
||||
s_type = m_type.name
|
||||
fav_id = "1:64:0:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n".format(response, response)
|
||||
s_type = BqServiceType.MARKER.name
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
marker = (None, None, text, None, None, s_type, None, fav_id, None, None, None)
|
||||
marker = (None, None, response, None, None, s_type, None, fav_id, None, None, None)
|
||||
itr = model.insert_before(model.get_iter(paths[0]), marker) if paths else model.insert(0, marker)
|
||||
bouquets[selected_bouquet].insert(model.get_path(itr)[0], fav_id)
|
||||
services[fav_id] = Service(None, None, None, text, None, None, None, s_type, *[None] * 9, 0, fav_id, None)
|
||||
services[fav_id] = Service(None, None, None, response, None, None, None, s_type, *[None] * 9, 0, fav_id, None)
|
||||
|
||||
|
||||
# ***************** Movement *******************#
|
||||
@@ -46,57 +41,54 @@ def move_items(key, view: Gtk.TreeView):
|
||||
""" Move items in the tree view """
|
||||
selection = view.get_selection()
|
||||
model, paths = selection.get_selected_rows()
|
||||
if not paths:
|
||||
return
|
||||
|
||||
is_tree_store = type(model) is Gtk.TreeStore
|
||||
mod_length = len(model)
|
||||
if not is_tree_store and 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,))
|
||||
|
||||
if is_tree_store:
|
||||
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,))
|
||||
is_tree_store = False
|
||||
parent_paths = list(filter(lambda p: p.get_depth() == 1, paths))
|
||||
if parent_paths:
|
||||
paths = parent_paths
|
||||
min_path = model.get_path(model.get_iter_first())
|
||||
view.collapse_all()
|
||||
if mod_length == len(paths):
|
||||
return
|
||||
else:
|
||||
if not is_some_level(paths):
|
||||
return
|
||||
parent_itr = model.iter_parent(model.get_iter(paths[0]))
|
||||
parent_index = model.get_path(parent_itr)
|
||||
children_num = model.iter_n_children(parent_itr)
|
||||
if key in (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 is KeyboardKey.UP:
|
||||
top_path = Gtk.TreePath(paths[0])
|
||||
set_cursor(top_path, paths, selection, view)
|
||||
top_path.prev()
|
||||
move_up(top_path, model, paths)
|
||||
elif key is KeyboardKey.DOWN:
|
||||
down_path = Gtk.TreePath(paths[-1])
|
||||
set_cursor(down_path, paths, selection, view)
|
||||
down_path.next()
|
||||
if down_path < max_path:
|
||||
move_down(down_path, model, paths)
|
||||
else:
|
||||
max_path.prev()
|
||||
move_down(max_path, model, paths)
|
||||
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 (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)
|
||||
if type(model) is Gtk.TreeStore:
|
||||
parent_paths = list(filter(lambda p: p.get_depth() == 1, paths))
|
||||
if parent_paths:
|
||||
paths = parent_paths
|
||||
min_path = model.get_path(model.get_iter_first())
|
||||
view.collapse_all()
|
||||
if mod_length == len(paths):
|
||||
return
|
||||
else:
|
||||
if not is_some_level(paths):
|
||||
return
|
||||
parent_itr = model.iter_parent(model.get_iter(paths[0]))
|
||||
parent_index = model.get_path(parent_itr)
|
||||
children_num = model.iter_n_children(parent_itr)
|
||||
if key in (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 is KeyboardKey.UP:
|
||||
top_path = Gtk.TreePath(paths[0])
|
||||
set_cursor(top_path, paths, selection, view)
|
||||
top_path.prev()
|
||||
move_up(top_path, model, paths)
|
||||
elif key is KeyboardKey.DOWN:
|
||||
down_path = Gtk.TreePath(paths[-1])
|
||||
set_cursor(down_path, paths, selection, view)
|
||||
down_path.next()
|
||||
if down_path < max_path:
|
||||
move_down(down_path, model, paths)
|
||||
else:
|
||||
max_path.prev()
|
||||
move_down(max_path, model, paths)
|
||||
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 (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)
|
||||
|
||||
|
||||
def move_up(top_path, model, paths):
|
||||
@@ -164,8 +156,7 @@ def rename(view, parent_window, target, fav_view=None, service_view=None, servic
|
||||
return
|
||||
|
||||
srv_name = response
|
||||
if not model.get_value(itr, Column.FAV_BACKGROUND):
|
||||
model.set_value(itr, Column.FAV_SERVICE, response)
|
||||
model.set_value(itr, Column.FAV_SERVICE, response)
|
||||
|
||||
if service_view is not None:
|
||||
for row in get_base_model(service_view.get_model()):
|
||||
@@ -217,7 +208,6 @@ def set_flags(flag, services_view, fav_view, services, blacklist):
|
||||
if not paths:
|
||||
return
|
||||
|
||||
paths = get_base_paths(paths, model)
|
||||
model = get_base_model(model)
|
||||
|
||||
if flag is Flag.HIDE:
|
||||
@@ -246,14 +236,13 @@ def set_lock(blacklist, services, model, paths, target, services_model):
|
||||
locked = has_locked_hide(model, paths, col_num)
|
||||
|
||||
ids = []
|
||||
skip_type = {BqServiceType.MARKER.name, BqServiceType.SPACE.name}
|
||||
|
||||
for path in paths:
|
||||
itr = model.get_iter(path)
|
||||
fav_id = model.get_value(itr, Column.SRV_FAV_ID if target is ViewTarget.SERVICES else Column.FAV_ID)
|
||||
srv = services.get(fav_id, None)
|
||||
if srv and srv.service_type not in skip_type:
|
||||
bq_id = srv.data_id if srv.service_type == BqServiceType.IPTV.name else to_bouquet_id(srv)
|
||||
if srv:
|
||||
bq_id = to_bouquet_id(srv)
|
||||
if not bq_id:
|
||||
continue
|
||||
blacklist.discard(bq_id) if locked else blacklist.add(bq_id)
|
||||
@@ -373,20 +362,18 @@ def append_picons(picons, model):
|
||||
GLib.idle_add(lambda: next(app, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
|
||||
def assign_picons(target, srv_view, fav_view, transient, picons, settings, services, src_path=None, dst_path=None):
|
||||
""" Assigning picons and returns picons files list. """
|
||||
def assign_picon(target, srv_view, fav_view, transient, picons, settings, services, p_path=None):
|
||||
view = srv_view if target is ViewTarget.SERVICES else fav_view
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
picons_files = []
|
||||
|
||||
if not src_path:
|
||||
src_path = get_chooser_dialog(transient, settings, "*.png files", ("*.png",))
|
||||
if src_path == Gtk.ResponseType.CANCEL:
|
||||
return picons_files
|
||||
if not p_path:
|
||||
p_path = get_chooser_dialog(transient, settings, "*.png files", ("*.png",))
|
||||
if p_path == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
if not str(src_path).endswith(".png") or not os.path.isfile(src_path):
|
||||
if not str(p_path).endswith(".png") or not os.path.isfile(p_path):
|
||||
show_dialog(DialogType.ERROR, transient, text="No png file is selected!")
|
||||
return picons_files
|
||||
return
|
||||
|
||||
p_pos = Column.SRV_PICON
|
||||
col_num = Column.SRV_FAV_ID if target is ViewTarget.SERVICES else Column.FAV_ID
|
||||
@@ -402,32 +389,24 @@ def assign_picons(target, srv_view, fav_view, transient, picons, settings, servi
|
||||
picon_id = services.get(fav_id)[Column.SRV_PICON_ID]
|
||||
|
||||
if picon_id:
|
||||
picons_path = dst_path or settings.picons_local_path
|
||||
picons_path = settings.picons_local_path
|
||||
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
|
||||
picon_file = picons_path + picon_id
|
||||
try:
|
||||
shutil.copy(src_path, picon_file)
|
||||
except shutil.SameFileError:
|
||||
pass # NOP
|
||||
shutil.copy(p_path, picon_file)
|
||||
picon = get_picon_pixbuf(picon_file)
|
||||
picons[picon_id] = picon
|
||||
model.set_value(itr, p_pos, picon)
|
||||
if target is ViewTarget.SERVICES:
|
||||
set_picon(fav_id, fav_view.get_model(), picon, Column.FAV_ID, p_pos)
|
||||
else:
|
||||
picons_files.append(picon_file)
|
||||
picon = get_picon_pixbuf(picon_file)
|
||||
picons[picon_id] = picon
|
||||
model.set_value(itr, p_pos, picon)
|
||||
if target is ViewTarget.SERVICES:
|
||||
set_picon(fav_id, fav_view.get_model(), picon, Column.FAV_ID, p_pos)
|
||||
else:
|
||||
set_picon(fav_id, get_base_model(srv_view.get_model()), picon, Column.SRV_FAV_ID, p_pos)
|
||||
|
||||
return picons_files
|
||||
set_picon(fav_id, get_base_model(srv_view.get_model()), picon, Column.SRV_FAV_ID, p_pos)
|
||||
|
||||
|
||||
def set_picon(fav_id, model, picon, fav_id_pos, picon_pos):
|
||||
for row in model:
|
||||
if row[fav_id_pos] == fav_id:
|
||||
row[picon_pos] = picon
|
||||
return True
|
||||
return True
|
||||
break
|
||||
|
||||
|
||||
def remove_picon(target, srv_view, fav_view, picons, settings):
|
||||
@@ -600,28 +579,12 @@ def update_entry_data(entry, dialog, settings):
|
||||
|
||||
|
||||
def get_base_model(model):
|
||||
""" Returns base tree model if has wrappers [TreeModelSort, TreeModelFilter]. """
|
||||
""" Returns base tree model if has wrappers ("TreeModelSort" and "TreeModelFilter") """
|
||||
if type(model) is Gtk.TreeModelSort:
|
||||
return model.get_model().get_model()
|
||||
return model
|
||||
|
||||
|
||||
def get_base_itrs(itrs, model):
|
||||
""" Returns base iters from wrapper models. """
|
||||
if type(model) is Gtk.TreeModelSort:
|
||||
filter_model = model.get_model()
|
||||
return [filter_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(itr)) for itr in itrs]
|
||||
return itrs
|
||||
|
||||
|
||||
def get_base_paths(paths, model):
|
||||
""" Returns base paths from wrapper models. """
|
||||
if type(model) is Gtk.TreeModelSort:
|
||||
filter_model = model.get_model()
|
||||
return [filter_model.convert_path_to_child_path(model.convert_path_to_child_path(p)) for p in paths]
|
||||
return paths
|
||||
|
||||
|
||||
def get_model_data(view):
|
||||
""" Returns model name and base model from the given view """
|
||||
model = get_base_model(view.get_model())
|
||||
@@ -644,7 +607,7 @@ def get_iptv_url(row, s_type):
|
||||
data = list(filter(lambda x: "http" in x, data))
|
||||
if data:
|
||||
url = data[0]
|
||||
return unquote(url) if s_type is SettingsType.ENIGMA_2 else url
|
||||
return urllib.request.unquote(url) if s_type is SettingsType.ENIGMA_2 else url
|
||||
|
||||
|
||||
def on_popup_menu(menu, event):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,6 @@ import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse, unquote
|
||||
|
||||
from gi.repository import GLib, GdkPixbuf
|
||||
|
||||
@@ -14,9 +12,8 @@ from app.settings import SettingsType, Settings
|
||||
from app.tools.picons import PiconsParser, parse_providers, Provider, convert_to
|
||||
from app.tools.satellites import SatellitesParser, SatelliteSource
|
||||
from .dialogs import show_dialog, DialogType, get_message
|
||||
from .main_helper import (update_entry_data, append_text_to_tview, scroll_to, on_popup_menu, get_base_model, set_picon,
|
||||
get_picon_pixbuf)
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column, KeyboardKey
|
||||
from .main_helper import update_entry_data, append_text_to_tview, scroll_to, on_popup_menu, get_base_model
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, GTK_PATH, Column
|
||||
|
||||
|
||||
class PiconsDialog:
|
||||
@@ -30,9 +27,6 @@ class PiconsDialog:
|
||||
self._POS_PATTERN = re.compile(r"^\d+\.\d+[EW]?$")
|
||||
self._current_process = None
|
||||
self._terminate = False
|
||||
self._filter_binding = None
|
||||
self._services = None
|
||||
self._current_picon_info = None
|
||||
|
||||
handlers = {"on_receive": self.on_receive,
|
||||
"on_load_providers": self.on_load_providers,
|
||||
@@ -49,30 +43,16 @@ class PiconsDialog:
|
||||
"on_position_edited": self.on_position_edited,
|
||||
"on_visible_page": self.on_visible_page,
|
||||
"on_convert": self.on_convert,
|
||||
"on_picons_src_changed": self.on_picons_src_changed,
|
||||
"on_picons_dest_changed": self.on_picons_dest_changed,
|
||||
"on_picons_folder_changed": self.on_picons_folder_changed,
|
||||
"on_picons_view_drag_drop": self.on_picons_view_drag_drop,
|
||||
"on_picons_view_drag_data_received": self.on_picons_view_drag_data_received,
|
||||
"on_picons_view_drag_data_get": self.on_picons_view_drag_data_get,
|
||||
"on_picons_src_view_drag_drop": self.on_picons_src_view_drag_drop,
|
||||
"on_picons_src_view_drag_data_received": self.on_picons_src_view_drag_data_received,
|
||||
"on_picons_src_view_drag_end": self.on_picons_src_view_drag_end,
|
||||
"on_picon_info_image_drag_data_received": self.on_picon_info_image_drag_data_received,
|
||||
"on_send_button_drag_data_received": self.on_send_button_drag_data_received,
|
||||
"on_download_button_drag_data_received": self.on_download_button_drag_data_received,
|
||||
"on_remove_button_drag_data_received": self.on_remove_button_drag_data_received,
|
||||
"on_selective_send": self.on_selective_send,
|
||||
"on_selective_download": self.on_selective_download,
|
||||
"on_selective_remove": self.on_selective_remove,
|
||||
"on_local_remove": self.on_local_remove,
|
||||
"on_picons_dest_view_realize": self.on_picons_dest_view_realize,
|
||||
"on_picons_view_realize": self.on_picons_view_realize,
|
||||
"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_filter_toggled": self.on_filter_toggled,
|
||||
"on_fiter_srcs_toggled": self.on_fiter_srcs_toggled,
|
||||
"on_filter_services_switch": self.on_filter_services_switch,
|
||||
"on_picon_activated": self.on_picon_activated,
|
||||
"on_tree_view_key_press": self.on_tree_view_key_press,
|
||||
"on_popup_menu": on_popup_menu}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
@@ -81,23 +61,17 @@ class PiconsDialog:
|
||||
|
||||
self._dialog = builder.get_object("picons_dialog")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._picons_src_view = builder.get_object("picons_src_view")
|
||||
self._picons_dest_view = builder.get_object("picons_dest_view")
|
||||
self._picons_view = builder.get_object("picons_view")
|
||||
self._providers_view = builder.get_object("providers_view")
|
||||
self._satellites_view = builder.get_object("satellites_view")
|
||||
self._picons_src_filter_model = builder.get_object("picons_src_filter_model")
|
||||
self._picons_src_filter_model.set_visible_func(self.picons_src_filter_function)
|
||||
self._picons_dst_filter_model = builder.get_object("picons_dst_filter_model")
|
||||
self._picons_dst_filter_model.set_visible_func(self.picons_dst_filter_function)
|
||||
self._explorer_src_path_button = builder.get_object("explorer_src_path_button")
|
||||
self._explorer_dest_path_button = builder.get_object("explorer_dest_path_button")
|
||||
self._picons_filter_model = builder.get_object("picons_filter_model")
|
||||
self._picons_filter_model.set_visible_func(self.picons_filter_function)
|
||||
self._explorer_path_button = builder.get_object("explorer_path_button")
|
||||
self._expander = builder.get_object("expander")
|
||||
self._text_view = builder.get_object("text_view")
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._filter_bar = builder.get_object("filter_bar")
|
||||
self._filter_button = builder.get_object("filter_button")
|
||||
self._src_filter_button = builder.get_object("src_filter_button")
|
||||
self._dst_filter_button = builder.get_object("dst_filter_button")
|
||||
self._picons_filter_entry = builder.get_object("picons_filter_entry")
|
||||
self._ip_entry = builder.get_object("ip_entry")
|
||||
self._picons_entry = builder.get_object("picons_entry")
|
||||
@@ -106,17 +80,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._info_check_button = builder.get_object("info_check_button")
|
||||
self._picon_info_image = builder.get_object("picon_info_image")
|
||||
self._picon_info_label = builder.get_object("picon_info_label")
|
||||
self._load_providers_button = builder.get_object("load_providers_button")
|
||||
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_button = builder.get_object("send_button")
|
||||
self._download_button = builder.get_object("download_button")
|
||||
self._remove_button = builder.get_object("remove_button")
|
||||
self._cancel_button = builder.get_object("cancel_button")
|
||||
self._enigma2_radio_button = builder.get_object("enigma2_radio_button")
|
||||
self._neutrino_mp_radio_button = builder.get_object("neutrino_mp_radio_button")
|
||||
@@ -124,24 +93,17 @@ class PiconsDialog:
|
||||
self._resize_220_132_radio_button = builder.get_object("resize_220_132_radio_button")
|
||||
self._resize_100_60_radio_button = builder.get_object("resize_100_60_radio_button")
|
||||
self._satellite_label = builder.get_object("satellite_label")
|
||||
self._header_download_box = builder.get_object("header_download_box")
|
||||
self._explorer_action_box = builder.get_object("explorer_action_box")
|
||||
self._satellite_label.bind_property("visible", builder.get_object("loading_data_label"), "visible", 4)
|
||||
self._satellite_label.bind_property("visible", builder.get_object("loading_data_spinner"), "visible", 4)
|
||||
self._cancel_button.bind_property("visible", self._header_download_box, "visible", 4)
|
||||
self._convert_button.bind_property("visible", self._header_download_box, "visible", 4)
|
||||
self._load_providers_button.bind_property("visible", self._receive_button, "visible")
|
||||
self._cancel_button.bind_property("visible", builder.get_object("receive_button"), "visible", 4)
|
||||
self._cancel_button.bind_property("visible", self._load_providers_button, "visible", 4)
|
||||
self._convert_button.bind_property("visible", self._explorer_action_box, "visible", 4)
|
||||
downloader_action_box = builder.get_object("downloader_action_box")
|
||||
self._explorer_action_box.bind_property("visible", downloader_action_box, "visible", 4)
|
||||
self._convert_button.bind_property("visible", downloader_action_box, "visible", 4)
|
||||
self._filter_bar.bind_property("search-mode-enabled", self._filter_bar, "visible")
|
||||
self._explorer_src_path_button.bind_property("sensitive", builder.get_object("picons_view_sw"), "sensitive")
|
||||
self._filter_button.bind_property("active", builder.get_object("filter_service_box"), "visible")
|
||||
self._filter_button.bind_property("active", builder.get_object("src_title_grid"), "visible")
|
||||
self._filter_button.bind_property("active", builder.get_object("dst_title_grid"), "visible")
|
||||
self._filter_button.bind_property("visible", self._info_check_button, "visible")
|
||||
self._filter_button.bind_property("visible", self._send_button, "visible")
|
||||
self._filter_button.bind_property("visible", self._download_button, "visible")
|
||||
self._filter_button.bind_property("visible", self._remove_button, "visible")
|
||||
explorer_info_bar = builder.get_object("explorer_info_bar")
|
||||
explorer_info_bar.bind_property("visible", builder.get_object("explorer_info_bar_frame"), "visible")
|
||||
self._info_check_button.bind_property("active", explorer_info_bar, "visible")
|
||||
self._explorer_path_button.bind_property("sensitive", builder.get_object("picons_view_sw"), "sensitive")
|
||||
# Init drag-and-drop
|
||||
self.init_drag_and_drop()
|
||||
# Style
|
||||
@@ -168,34 +130,25 @@ class PiconsDialog:
|
||||
def show(self):
|
||||
self._dialog.show()
|
||||
|
||||
def on_picons_dest_view_realize(self, view):
|
||||
self._services = {s.picon_id: s for s in self._app.current_services.values() if s.picon_id}
|
||||
self._explorer_dest_path_button.select_filename(self._settings.picons_local_path)
|
||||
def on_picons_view_realize(self, view):
|
||||
self._explorer_path_button.set_current_folder(self._settings.picons_local_path)
|
||||
|
||||
def on_picons_src_changed(self, button):
|
||||
self.update_picons_data(self._picons_src_view, button)
|
||||
|
||||
def on_picons_dest_changed(self, button):
|
||||
self.update_picon_info()
|
||||
self.update_picons_data(self._picons_dest_view, button)
|
||||
|
||||
def update_picons_data(self, view, button):
|
||||
def on_picons_folder_changed(self, button):
|
||||
path = button.get_filename()
|
||||
if not path or not os.path.exists(path):
|
||||
return
|
||||
|
||||
GLib.idle_add(button.set_sensitive, False)
|
||||
gen = self.update_picons(path, view, button)
|
||||
GLib.idle_add(self._explorer_path_button.set_sensitive, False)
|
||||
gen = self.update_picons(path)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def update_picons(self, path, view, button):
|
||||
p_model = view.get_model()
|
||||
def update_picons(self, path):
|
||||
p_model = self._picons_view.get_model()
|
||||
if not p_model:
|
||||
button.set_sensitive(True)
|
||||
return
|
||||
|
||||
model = get_base_model(p_model)
|
||||
view.set_model(None)
|
||||
self._picons_view.set_model(None)
|
||||
factor = self._app.DEL_FACTOR
|
||||
|
||||
for index, itr in enumerate([row.iter for row in model]):
|
||||
@@ -207,91 +160,50 @@ class PiconsDialog:
|
||||
if self._terminate:
|
||||
return
|
||||
|
||||
p_path = "{}/{}".format(path, file)
|
||||
p = self.get_pixbuf_at_scale(p_path, 72, 48, True)
|
||||
if p:
|
||||
yield model.append((p, file, p_path))
|
||||
try:
|
||||
p = GdkPixbuf.Pixbuf.new_from_file_at_scale("{}/{}".format(path, file), 100, 60, True)
|
||||
except GLib.GError as e:
|
||||
pass
|
||||
else:
|
||||
yield model.append((p, file))
|
||||
|
||||
view.set_model(p_model)
|
||||
button.set_sensitive(True)
|
||||
self._picons_view.set_model(p_model)
|
||||
self._explorer_path_button.set_sensitive(True)
|
||||
yield True
|
||||
|
||||
def update_picons_from_file(self, view, uri):
|
||||
""" Adds picons in the view on dragging from file system. """
|
||||
path = Path(urlparse(unquote(uri)).path.strip())
|
||||
f_path = str(path.resolve())
|
||||
if not f_path:
|
||||
return
|
||||
|
||||
model = get_base_model(view.get_model())
|
||||
|
||||
if path.is_file():
|
||||
p = self.get_pixbuf_at_scale(f_path, 72, 48, True)
|
||||
if p:
|
||||
model.append((p, path.name, f_path))
|
||||
elif path.is_dir():
|
||||
self._explorer_src_path_button.select_filename(f_path)
|
||||
|
||||
def get_pixbuf_at_scale(self, path, width, height, p_ratio):
|
||||
try:
|
||||
return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, width, height, p_ratio)
|
||||
except GLib.GError:
|
||||
pass
|
||||
|
||||
# ***************** Drag-and-drop ********************* #
|
||||
|
||||
def init_drag_and_drop(self):
|
||||
self._picons_src_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
|
||||
self._picons_src_view.drag_source_add_uri_targets()
|
||||
self._picons_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
|
||||
self._picons_view.drag_source_add_uri_targets()
|
||||
self._picons_view.enable_model_drag_dest([], Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
|
||||
self._picons_view.drag_dest_add_text_targets()
|
||||
|
||||
self._picons_dest_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
|
||||
self._picons_dest_view.drag_source_add_uri_targets()
|
||||
|
||||
self._picons_src_view.enable_model_drag_dest([], Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
|
||||
self._picons_src_view.drag_dest_add_text_targets()
|
||||
|
||||
self._picon_info_image.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
|
||||
self._picon_info_image.drag_dest_add_uri_targets()
|
||||
|
||||
self._send_button.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
|
||||
self._send_button.drag_dest_add_uri_targets()
|
||||
|
||||
self._download_button.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
|
||||
self._download_button.drag_dest_add_uri_targets()
|
||||
|
||||
self._remove_button.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
|
||||
self._remove_button.drag_dest_add_uri_targets()
|
||||
|
||||
def on_picons_view_drag_data_get(self, view, drag_context, data, info, time):
|
||||
model, path = view.get_selection().get_selected_rows()
|
||||
if path:
|
||||
data.set_uris([Path(model[path][-1]).as_uri(),
|
||||
Path(self._explorer_dest_path_button.get_filename()).as_uri()])
|
||||
|
||||
def on_picons_src_view_drag_drop(self, view, drag_context, x, y, time):
|
||||
def on_picons_view_drag_drop(self, view, drag_context, x, y, time):
|
||||
view.stop_emission_by_name("drag_drop")
|
||||
targets = drag_context.list_targets()
|
||||
view.drag_get_data(drag_context, targets[-1] if targets else Gdk.atom_intern("text/plain", False), time)
|
||||
|
||||
def on_picons_src_view_drag_data_received(self, view, drag_context, x, y, data, info, time):
|
||||
def on_picons_view_drag_data_received(self, view, drag_context, x, y, data, info, time):
|
||||
view.stop_emission_by_name("drag_data_received")
|
||||
txt = data.get_text()
|
||||
if not txt:
|
||||
return
|
||||
|
||||
if txt.startswith("file://"):
|
||||
self.update_picons_from_file(view, txt)
|
||||
self.show_info_message("Not implemented yet!", Gtk.MessageType.WARNING)
|
||||
return
|
||||
|
||||
itr_str, sep, src = txt.partition("::::")
|
||||
if src == self._app.BQ_MODEL_NAME:
|
||||
return
|
||||
|
||||
path, pos = view.get_dest_row_at_pos(x, y) or (None, None)
|
||||
path, pos = view.get_dest_item_at_pos(x, y) or (None, None)
|
||||
if not path:
|
||||
return
|
||||
|
||||
model = view.get_model()
|
||||
p_path = "{}/{}".format(self._explorer_path_button.get_filename(), model.get_value(model.get_iter(path), 1))
|
||||
if src == self._app.FAV_MODEL_NAME:
|
||||
target_view = self._app.fav_view
|
||||
c_id = Column.FAV_ID
|
||||
@@ -300,28 +212,9 @@ class PiconsDialog:
|
||||
c_id = Column.SRV_FAV_ID
|
||||
|
||||
t_mod = target_view.get_model()
|
||||
dest_path = self._explorer_dest_path_button.get_filename() + "/"
|
||||
self.update_picons_dest_view(self._app.on_assign_picon(target_view, model[path][-1], dest_path))
|
||||
self._app.on_assign_picon(target_view, p_path)
|
||||
self.show_assign_info([t_mod.get_value(t_mod.get_iter_from_string(itr), c_id) for itr in itr_str.split(",")])
|
||||
|
||||
@run_idle
|
||||
def update_picons_dest_view(self, picons):
|
||||
""" Update destination view on adding/changing picons. """
|
||||
if picons:
|
||||
dest_model = get_base_model(self._picons_dest_view.get_model())
|
||||
paths = {r[1]: r.iter for r in dest_model}
|
||||
|
||||
for p_path in picons:
|
||||
p = self.get_pixbuf_at_scale(p_path, 72, 48, True)
|
||||
if p:
|
||||
p_name = Path(p_path).name
|
||||
itr = paths.get(p_name, None)
|
||||
if itr:
|
||||
dest_model.set_value(itr, 0, p)
|
||||
else:
|
||||
itr = dest_model.append((p, p_name, p_path))
|
||||
scroll_to(dest_model.get_path(itr), self._picons_dest_view)
|
||||
|
||||
@run_idle
|
||||
def show_assign_info(self, fav_ids):
|
||||
self._expander.set_expanded(True)
|
||||
@@ -332,138 +225,13 @@ class PiconsDialog:
|
||||
info = self._app.get_hint_for_srv_list(srv)
|
||||
self.append_output("Picon assignment for the service:\n{}\n{}\n".format(info, " * " * 30))
|
||||
|
||||
def on_picons_src_view_drag_end(self, view, drag_context):
|
||||
self.update_picons_dest_view(self._app.picons_buffer)
|
||||
def on_picons_view_drag_data_get(self, view, drag_context, data, info, time):
|
||||
model = view.get_model()
|
||||
path = view.get_selected_items()[0]
|
||||
p_path = "{}/{}".format(self._explorer_path_button.get_filename(), model.get_value(model.get_iter(path), 1))
|
||||
data.set_uris([p_path])
|
||||
|
||||
def on_picon_info_image_drag_data_received(self, img, drag_context, x, y, data, info, time):
|
||||
if not self._current_picon_info:
|
||||
self.show_info_message("No selected item!", Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
uris = data.get_uris()
|
||||
if len(uris) == 2:
|
||||
name, fav_id = self._current_picon_info
|
||||
src = urlparse(unquote(uris[0])).path
|
||||
dst = "{}/{}".format(urlparse(unquote(uris[1])).path, name)
|
||||
if src != dst:
|
||||
shutil.copy(src, dst)
|
||||
for row in get_base_model(self._picons_dest_view.get_model()):
|
||||
if name == row[1]:
|
||||
row[0] = self.get_pixbuf_at_scale(row[-1], 72, 48, True)
|
||||
img.set_from_pixbuf(self.get_pixbuf_at_scale(row[-1], 100, 60, True))
|
||||
|
||||
gen = self.update_picon_in_lists(dst, fav_id)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def on_send_button_drag_data_received(self, button, drag_context, x, y, data, info, time):
|
||||
path = self.get_path_from_uris(data)
|
||||
if path:
|
||||
self.on_send(files_filter={path.name}, path=path.parent)
|
||||
|
||||
def on_download_button_drag_data_received(self, button, drag_context, x, y, data, info, time):
|
||||
path = self.get_path_from_uris(data)
|
||||
if path:
|
||||
self.on_download(files_filter={path.name})
|
||||
|
||||
def on_remove_button_drag_data_received(self, button, drag_context, x, y, data, info, time):
|
||||
path = self.get_path_from_uris(data)
|
||||
if path:
|
||||
self.on_remove(files_filter={path.name})
|
||||
|
||||
def get_path_from_uris(self, data):
|
||||
uris = data.get_uris()
|
||||
if len(uris) == 2:
|
||||
return Path(urlparse(unquote(uris[0])).path).resolve()
|
||||
|
||||
def update_picon_in_lists(self, dst, fav_id):
|
||||
picon = get_picon_pixbuf(dst)
|
||||
p_pos = Column.SRV_PICON
|
||||
yield set_picon(fav_id, get_base_model(self._app.services_view.get_model()), picon, Column.SRV_FAV_ID, p_pos)
|
||||
yield set_picon(fav_id, get_base_model(self._app.fav_view.get_model()), picon, Column.FAV_ID, p_pos)
|
||||
|
||||
# ******************** Download/Upload/Remove ************************* #
|
||||
|
||||
def on_selective_send(self, view):
|
||||
path = self.get_selected_path(view)
|
||||
if path:
|
||||
self.on_send(files_filter={path.name}, path=path.parent)
|
||||
|
||||
def on_selective_download(self, view):
|
||||
path = self.get_selected_path(view)
|
||||
if path:
|
||||
self.on_download(files_filter={path.name})
|
||||
|
||||
def on_selective_remove(self, view):
|
||||
path = self.get_selected_path(view)
|
||||
if path:
|
||||
self.on_remove(files_filter={path.name})
|
||||
|
||||
def on_local_remove(self, view):
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
if paths and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.OK:
|
||||
itr = model.get_iter(paths.pop())
|
||||
p_path = Path(model.get_value(itr, 2)).resolve()
|
||||
if p_path.is_file():
|
||||
p_path.unlink()
|
||||
base_model = get_base_model(model)
|
||||
filter_model = model.get_model()
|
||||
itr = filter_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(itr))
|
||||
base_model.remove(itr)
|
||||
|
||||
def on_send(self, item=None, files_filter=None, path=None):
|
||||
dest_path = path or self.check_dest_path()
|
||||
if not dest_path:
|
||||
return
|
||||
|
||||
settings = Settings(self._settings.settings)
|
||||
settings.picons_local_path = "{}/".format(dest_path)
|
||||
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
|
||||
self.run_func(lambda: upload_data(settings=settings,
|
||||
download_type=DownloadType.PICONS,
|
||||
callback=self.append_output,
|
||||
done_callback=lambda: self.show_info_message(get_message("Done!"),
|
||||
Gtk.MessageType.INFO),
|
||||
files_filter=files_filter))
|
||||
|
||||
def on_download(self, item=None, files_filter=None, path=None):
|
||||
path = path or self.check_dest_path()
|
||||
if not path:
|
||||
return
|
||||
|
||||
settings = Settings(self._settings.settings)
|
||||
settings.picons_local_path = path + "/"
|
||||
self.run_func(lambda: download_data(settings=settings,
|
||||
download_type=DownloadType.PICONS,
|
||||
callback=self.append_output,
|
||||
files_filter=files_filter), True)
|
||||
|
||||
def on_remove(self, item=None, files_filter=None):
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
self.run_func(lambda: remove_picons(settings=self._settings,
|
||||
callback=self.append_output,
|
||||
done_callback=lambda: self.show_info_message(get_message("Done!"),
|
||||
Gtk.MessageType.INFO),
|
||||
files_filter=files_filter))
|
||||
|
||||
def get_selected_path(self, view):
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
if paths:
|
||||
return Path(model[paths.pop()][-1]).resolve()
|
||||
|
||||
def check_dest_path(self):
|
||||
""" Checks the destination path and returns if present. """
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
path = self._explorer_dest_path_button.get_filename()
|
||||
if not path:
|
||||
show_dialog(DialogType.ERROR, transient=self._dialog, text="Select paths!")
|
||||
return
|
||||
return path
|
||||
|
||||
# ******************** Downloader ************************* #
|
||||
# ******************** ####### ************************* #
|
||||
|
||||
def on_satellites_view_realize(self, view):
|
||||
self.get_satellites(view)
|
||||
@@ -500,7 +268,8 @@ class PiconsDialog:
|
||||
url = self._url_entry.get_text()
|
||||
|
||||
try:
|
||||
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
|
||||
exe = "{}wget".format("./" if GTK_PATH else "")
|
||||
self._current_process = subprocess.Popen([exe, "-pkP", self._TMP_DIR, url],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
@@ -563,10 +332,10 @@ class PiconsDialog:
|
||||
return
|
||||
self.process_provider(Provider(*prv))
|
||||
|
||||
if not self._resize_no_radio_button.get_active():
|
||||
if self._resize_no_radio_button.get_active():
|
||||
self.resize(self._picons_dir_entry.get_text())
|
||||
else:
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
finally:
|
||||
GLib.idle_add(self._cancel_button.hide)
|
||||
self._terminate = False
|
||||
@@ -574,7 +343,8 @@ class PiconsDialog:
|
||||
def process_provider(self, prv):
|
||||
url = prv.url
|
||||
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
|
||||
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
|
||||
exe = "{}wget".format("./" if GTK_PATH else "")
|
||||
self._current_process = subprocess.Popen([exe, "-pkP", self._TMP_DIR, url],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
@@ -595,24 +365,16 @@ class PiconsDialog:
|
||||
def append_output(self, char):
|
||||
append_text_to_tview(char, self._text_view)
|
||||
|
||||
@run_task
|
||||
def resize(self, path):
|
||||
self.show_info_message(get_message("Resizing..."), Gtk.MessageType.INFO)
|
||||
|
||||
exe = "{}mogrify".format("./" if GTK_PATH else "")
|
||||
is_220_132 = self._resize_220_132_radio_button.get_active()
|
||||
command = "{} -resize {}! *.png".format(exe, "220x132" if is_220_132 else "100x60").split()
|
||||
try:
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
except ImportError as e:
|
||||
self.show_info_message("{} {}".format(get_message("Conversion error."), e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
res = (220, 132) if self._resize_220_132_radio_button.get_active() else (100, 60)
|
||||
|
||||
for img_file in Path(path).glob("*.png"):
|
||||
img = Image.open(img_file)
|
||||
img = img.resize(res, Image.ANTIALIAS)
|
||||
img.save(img_file, "PNG", optimize=True)
|
||||
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
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)
|
||||
|
||||
def on_cancel(self, item=None):
|
||||
if self.is_task_running() and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
@@ -649,18 +411,50 @@ class PiconsDialog:
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
|
||||
def on_send(self, item):
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
settings = Settings(self._settings.settings)
|
||||
settings.picons_local_path = self._explorer_path_button.get_filename() + "/"
|
||||
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
|
||||
self.run_func(lambda: upload_data(settings=settings,
|
||||
download_type=DownloadType.PICONS,
|
||||
callback=self.append_output,
|
||||
done_callback=lambda: self.show_info_message(get_message("Done!"),
|
||||
Gtk.MessageType.INFO)))
|
||||
|
||||
def on_download(self, item):
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
settings = Settings(self._settings.settings)
|
||||
settings.picons_local_path = self._explorer_path_button.get_filename() + "/"
|
||||
self.run_func(lambda: download_data(settings=settings,
|
||||
download_type=DownloadType.PICONS,
|
||||
callback=self.append_output), True)
|
||||
|
||||
def on_remove(self, item):
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
self.run_func(lambda: remove_picons(settings=self._settings,
|
||||
callback=self.append_output,
|
||||
done_callback=lambda: self.show_info_message(get_message("Done!"),
|
||||
Gtk.MessageType.INFO)))
|
||||
|
||||
@run_task
|
||||
def run_func(self, func, update=False):
|
||||
try:
|
||||
GLib.idle_add(self._expander.set_expanded, True)
|
||||
GLib.idle_add(self._header_download_box.set_sensitive, False)
|
||||
GLib.idle_add(self._explorer_action_box.set_sensitive, False)
|
||||
func()
|
||||
except OSError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
finally:
|
||||
GLib.idle_add(self._header_download_box.set_sensitive, True)
|
||||
GLib.idle_add(self._explorer_action_box.set_sensitive, True)
|
||||
if update:
|
||||
self.on_picons_dest_changed(self._explorer_dest_path_button)
|
||||
self.on_picons_folder_changed(self._explorer_path_button)
|
||||
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
@@ -690,42 +484,23 @@ class PiconsDialog:
|
||||
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 7, select))
|
||||
self.update_receive_button_state()
|
||||
|
||||
# *********************** Filter **************************** #
|
||||
|
||||
def on_filter_toggled(self, button):
|
||||
active = button.get_active()
|
||||
self._filter_bar.set_search_mode(active)
|
||||
if not active:
|
||||
self._picons_filter_entry.set_text("")
|
||||
|
||||
def on_fiter_srcs_toggled(self, filter_model):
|
||||
""" Activates re-filtering for model when filter check-button has toggled. """
|
||||
GLib.idle_add(filter_model.refilter, priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def on_filter_services_switch(self, button, state):
|
||||
""" Activates or deactivates filtering in the main list of services. """
|
||||
if state:
|
||||
self._filter_binding = self._picons_filter_entry.bind_property("text", self._app.filter_entry, "text")
|
||||
self._app.filter_entry.set_text(self._picons_filter_entry.get_text())
|
||||
else:
|
||||
if self._filter_binding:
|
||||
self._filter_binding.unbind()
|
||||
self._app.filter_entry.set_text("")
|
||||
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_button.set_sensitive(suit if suit else False)
|
||||
|
||||
@run_with_delay(1)
|
||||
def on_picons_filter_changed(self, entry):
|
||||
GLib.idle_add(self._picons_src_filter_model.refilter, priority=GLib.PRIORITY_LOW)
|
||||
GLib.idle_add(self._picons_dst_filter_model.refilter, priority=GLib.PRIORITY_LOW)
|
||||
GLib.idle_add(self._picons_filter_model.refilter, priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def picons_src_filter_function(self, model, itr, data):
|
||||
return self.filter_function(itr, model, self._src_filter_button.get_active())
|
||||
|
||||
def picons_dst_filter_function(self, model, itr, data):
|
||||
return self.filter_function(itr, model, self._dst_filter_button.get_active())
|
||||
|
||||
def filter_function(self, itr, model, active):
|
||||
""" Main filtering function. """
|
||||
if any((not active, model is None, model == "None")):
|
||||
def picons_filter_function(self, model, itr, data):
|
||||
if self._picons_filter_model is None or self._picons_filter_model == "None":
|
||||
return True
|
||||
|
||||
t = model.get_value(itr, 1)
|
||||
@@ -736,49 +511,6 @@ class PiconsDialog:
|
||||
return txt in t.upper() or t in (
|
||||
map(lambda s: s.picon_id, filter(lambda s: txt in s.service.upper(), self._app.current_services.values())))
|
||||
|
||||
def on_picon_activated(self, view):
|
||||
if self._info_check_button.get_active():
|
||||
model, path = view.get_selection().get_selected_rows()
|
||||
if not path:
|
||||
return
|
||||
|
||||
row = model[path][:]
|
||||
name, path = row[1], row[-1]
|
||||
srv = self._services.get(row[1], None)
|
||||
self.update_picon_info(name, path, srv)
|
||||
|
||||
def update_picon_info(self, name=None, path=None, srv=None):
|
||||
self._picon_info_image.set_from_pixbuf(self.get_pixbuf_at_scale(path, 100, 60, True) if path else None)
|
||||
self._picon_info_label.set_text(self.get_service_info(srv))
|
||||
self._current_picon_info = (name, srv.fav_id) if srv else None
|
||||
|
||||
def get_service_info(self, srv):
|
||||
""" Returns short info about the service. """
|
||||
if not srv:
|
||||
return ""
|
||||
|
||||
if srv.service_type == "IPTV":
|
||||
return self._app.get_hint_for_srv_list(srv)
|
||||
|
||||
header, ref = self._app.get_hint_header_info(srv)
|
||||
return "{} {}: {}\n{}: {} {}: {}\n{}".format(header.rstrip(), get_message("Package"), srv.package,
|
||||
get_message("System"), srv.system, get_message("Freq"), srv.freq,
|
||||
ref)
|
||||
|
||||
def on_tree_view_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
if key is KeyboardKey.DELETE:
|
||||
self.on_local_remove(view)
|
||||
|
||||
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_button.set_sensitive(suit if suit else False)
|
||||
|
||||
def on_position_edited(self, render, path, value):
|
||||
model = self._providers_view.get_model()
|
||||
model.set_value(model.get_iter(path), 2, value)
|
||||
@@ -787,11 +519,10 @@ class PiconsDialog:
|
||||
def on_visible_page(self, stack: Gtk.Stack, param):
|
||||
name = stack.get_visible_child_name()
|
||||
self._convert_button.set_visible(name == "converter")
|
||||
self._load_providers_button.set_visible(name == "downloader")
|
||||
is_explorer = name == "explorer"
|
||||
self._filter_button.set_visible(is_explorer)
|
||||
self._explorer_action_box.set_visible(is_explorer)
|
||||
if is_explorer:
|
||||
self.on_picons_dest_changed(self._explorer_dest_path_button)
|
||||
self.on_picons_folder_changed(self._explorer_path_button)
|
||||
|
||||
@run_idle
|
||||
def on_convert(self, item):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
import concurrent.futures
|
||||
import re
|
||||
import time
|
||||
import concurrent.futures
|
||||
from math import fabs
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from app.commons import run_idle, run_task, log
|
||||
from app.commons import run_idle, run_task
|
||||
from app.eparser import get_satellites, write_satellites, Satellite, Transponder
|
||||
from app.eparser.ecommons import PLS_MODE, get_key_by_value
|
||||
from app.tools.satellites import SatellitesParser, SatelliteSource, ServicesParser
|
||||
from .dialogs import show_dialog, DialogType, get_dialogs_string, get_chooser_dialog, get_message
|
||||
from app.tools.satellites import SatellitesParser, SatelliteSource
|
||||
from .dialogs import show_dialog, DialogType, get_dialogs_string, get_chooser_dialog
|
||||
from .main_helper import move_items, scroll_to, append_text_to_tview, get_base_model, on_popup_menu
|
||||
from .search import SearchProvider
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, MOVE_KEYS, KeyboardKey, IS_GNOME_SESSION, MOD_MASK
|
||||
@@ -48,7 +48,8 @@ class SatellitesDialog:
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH),
|
||||
("satellites_editor_window", "satellites_tree_store", "popup_menu",
|
||||
"left_header_menu", "popup_menu_add_image", "popup_menu_add_image_2"))
|
||||
"left_header_menu", "popup_menu_add_image", "popup_menu_add_image_2",
|
||||
"sat_editor_save_image", "sat_editor_update_image"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._window = builder.get_object("satellites_editor_window")
|
||||
@@ -279,7 +280,7 @@ class SatellitesDialog:
|
||||
|
||||
@run_idle
|
||||
def on_update(self, item):
|
||||
SatellitesUpdateDialog(self._window, self._settings, self._sat_view.get_model()).show()
|
||||
SatellitesUpdateDialog(self._window, self._sat_view.get_model()).show()
|
||||
|
||||
@staticmethod
|
||||
def parse_data(model, path, itr, sats):
|
||||
@@ -326,7 +327,7 @@ class TransponderDialog:
|
||||
self._pls_code_entry = builder.get_object("pls_code_entry")
|
||||
self._is_id_entry = builder.get_object("is_id_entry")
|
||||
# pattern for frequency and rate entries (only digits)
|
||||
self._pattern = re.compile(r"\D")
|
||||
self._pattern = re.compile("\D")
|
||||
# style
|
||||
self._style_provider = Gtk.CssProvider()
|
||||
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
@@ -428,17 +429,16 @@ class SatelliteDialog:
|
||||
return Satellite(name=name, flags="0", position=pos, transponders=None)
|
||||
|
||||
|
||||
# ********************** Update dialogs ************************ #
|
||||
# ***************** Satellite update dialog *******************#
|
||||
|
||||
class UpdateDialog:
|
||||
""" Base dialog for update satellites, transponders and services from the web."""
|
||||
class SatellitesUpdateDialog:
|
||||
""" Dialog for update satellites over internet """
|
||||
|
||||
def __init__(self, transient, settings, title=None):
|
||||
def __init__(self, transient, main_model):
|
||||
handlers = {"on_update_satellites_list": self.on_update_satellites_list,
|
||||
"on_receive_data": self.on_receive_data,
|
||||
"on_receive_satellites_list": self.on_receive_satellites_list,
|
||||
"on_cancel_receive": self.on_cancel_receive,
|
||||
"on_satellite_toggled": self.on_satellite_toggled,
|
||||
"on_transponder_toggled": self.on_transponder_toggled,
|
||||
"on_selected_toggled": self.on_selected_toggled,
|
||||
"on_info_bar_close": self.on_info_bar_close,
|
||||
"on_filter_toggled": self.on_filter_toggled,
|
||||
"on_find_toggled": self.on_find_toggled,
|
||||
@@ -451,36 +451,27 @@ class UpdateDialog:
|
||||
"on_search_up": self.on_search_up,
|
||||
"on_quit": self.on_quit}
|
||||
|
||||
self._settings = settings
|
||||
self._download_task = False
|
||||
self._parser = None
|
||||
self._size_name = "{}_window_size".format("_".join(re.findall("[A-Z][^A-Z]*", self.__class__.__name__))).lower()
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(TEXT_DOMAIN)
|
||||
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
|
||||
("satellites_update_window", "update_source_store", "update_sat_list_store",
|
||||
"update_sat_list_model_filter", "update_sat_list_model_sort", "side_store",
|
||||
"pos_adjustment", "pos_adjustment2", "satellites_update_popup_menu",
|
||||
"remove_selection_image", "update_transponder_store", "update_service_store"))
|
||||
"remove_selection_image", "sat_update_cancel_image", "sat_receive_image",
|
||||
"sat_update_filter_image", "sat_update_search_image", "sat_update_image"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._window = builder.get_object("satellites_update_window")
|
||||
self._window.set_transient_for(transient)
|
||||
if title:
|
||||
self._window.set_title(title)
|
||||
|
||||
self._transponder_paned = builder.get_object("sat_update_tr_paned")
|
||||
self._main_model = main_model
|
||||
# self._dialog.get_content_area().set_border_width(0)
|
||||
self._sat_view = builder.get_object("sat_update_tree_view")
|
||||
self._transponder_view = builder.get_object("sat_update_tr_view")
|
||||
self._service_view = builder.get_object("sat_update_srv_view")
|
||||
self._source_box = builder.get_object("source_combo_box")
|
||||
self._sat_update_expander = builder.get_object("sat_update_expander")
|
||||
self._text_view = builder.get_object("text_view")
|
||||
self._receive_button = builder.get_object("receive_data_button")
|
||||
self._receive_button = builder.get_object("receive_sat_list_tool_button")
|
||||
self._sat_update_info_bar = builder.get_object("sat_update_info_bar")
|
||||
self._info_bar_message_label = builder.get_object("info_bar_message_label")
|
||||
self._receive_button.bind_property("visible", builder.get_object("cancel_data_button"), "visible", 4)
|
||||
# Filter
|
||||
self._filter_bar = builder.get_object("sat_update_filter_bar")
|
||||
self._from_pos_button = builder.get_object("from_pos_button")
|
||||
@@ -496,31 +487,21 @@ class UpdateDialog:
|
||||
builder.get_object("sat_update_search_down_button"),
|
||||
builder.get_object("sat_update_search_up_button"))
|
||||
|
||||
window_size = self._settings.get(self._size_name)
|
||||
if window_size:
|
||||
self._window.resize(*window_size)
|
||||
self._download_task = False
|
||||
self._parser = None
|
||||
|
||||
def show(self):
|
||||
self._window.show()
|
||||
|
||||
@property
|
||||
def is_download(self):
|
||||
return self._download_task
|
||||
|
||||
@is_download.setter
|
||||
def is_download(self, value):
|
||||
self._download_task = value
|
||||
self._receive_button.set_visible(not value)
|
||||
|
||||
@run_idle
|
||||
def on_update_satellites_list(self, item):
|
||||
if self.is_download:
|
||||
if self._download_task:
|
||||
show_dialog(DialogType.ERROR, self._window, "The task is already running!")
|
||||
return
|
||||
|
||||
model = get_base_model(self._sat_view.get_model())
|
||||
model.clear()
|
||||
self.is_download = True
|
||||
self._download_task = True
|
||||
src = self._source_box.get_active()
|
||||
if not self._parser:
|
||||
self._parser = SatellitesParser()
|
||||
@@ -532,7 +513,7 @@ class UpdateDialog:
|
||||
sats = self._parser.get_satellites_list(SatelliteSource.FLYSAT if src == 0 else SatelliteSource.LYNGSAT)
|
||||
if sats:
|
||||
callback(sats)
|
||||
self.is_download = False
|
||||
self._download_task = False
|
||||
|
||||
@run_idle
|
||||
def append_satellites(self, sats):
|
||||
@@ -541,16 +522,70 @@ class UpdateDialog:
|
||||
model.append(sat)
|
||||
|
||||
@run_idle
|
||||
def on_receive_data(self, item):
|
||||
if self.is_download:
|
||||
def on_receive_satellites_list(self, item):
|
||||
if self._download_task:
|
||||
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.update_expander()
|
||||
model = self._sat_view.get_model()
|
||||
start = time.time()
|
||||
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
|
||||
text = "Processing: {}\n"
|
||||
sats = []
|
||||
appender = self.append_output()
|
||||
next(appender)
|
||||
futures = {executor.submit(self._parser.get_satellite, sat[:-1]): sat for sat in [r for r in model if r[4]]}
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
if not self._download_task:
|
||||
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]))
|
||||
sats.append(data)
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
appender.send("Consumed : {:0.0f}s, {} satellites received.".format(start - time.time(), len(sats)))
|
||||
appender.close()
|
||||
|
||||
sats = {s[2]: s for s in sats} # key = position, v = satellite
|
||||
|
||||
for row in self._main_model:
|
||||
pos = row[-1]
|
||||
if pos in sats:
|
||||
sat = sats.pop(pos)
|
||||
itr = row.iter
|
||||
self.update_satellite(itr, row, sat)
|
||||
|
||||
for sat in sats.values():
|
||||
append_satellite(self._main_model, sat)
|
||||
|
||||
self._download_task = False
|
||||
|
||||
@run_idle
|
||||
def update_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):
|
||||
children = row.iterchildren()
|
||||
for ch in children:
|
||||
self._main_model.remove(ch.iter)
|
||||
|
||||
for tr in sat[3]:
|
||||
self._main_model.append(itr, ["Transponder:", *tr, None, None])
|
||||
|
||||
def append_output(self):
|
||||
@run_idle
|
||||
def append(t):
|
||||
@@ -563,15 +598,11 @@ class UpdateDialog:
|
||||
def on_cancel_receive(self, item=None):
|
||||
self._download_task = False
|
||||
|
||||
def on_satellite_toggled(self, toggle, path):
|
||||
def on_selected_toggled(self, toggle, path):
|
||||
model = self._sat_view.get_model()
|
||||
self.update_state(model, path, not toggle.get_active())
|
||||
self.update_receive_button_state(self._filter_model)
|
||||
|
||||
def on_transponder_toggled(self, toggle, path):
|
||||
model = self._transponder_view.get_model()
|
||||
model.set_value(model.get_iter(path), 2, not toggle.get_active())
|
||||
|
||||
@run_idle
|
||||
def update_receive_button_state(self, model):
|
||||
self._receive_button.set_sensitive((any(r[4] for r in model)))
|
||||
@@ -596,7 +627,7 @@ class UpdateDialog:
|
||||
self._filter_positions = self.get_positions()
|
||||
self._filter_model.refilter()
|
||||
|
||||
def filter_function(self, model, itr, data):
|
||||
def filter_function(self, model, iter, data):
|
||||
if self._filter_model is None or self._filter_model == "None":
|
||||
return True
|
||||
|
||||
@@ -607,7 +638,7 @@ class UpdateDialog:
|
||||
if from_pos > to_pos:
|
||||
from_pos, to_pos = to_pos, from_pos
|
||||
|
||||
return from_pos <= float(self._parser.get_position(model.get(itr, 1)[0])) <= to_pos
|
||||
return from_pos <= float(self._parser.get_position(model.get(iter, 1)[0])) <= to_pos
|
||||
|
||||
def get_positions(self):
|
||||
from_pos = round(self._from_pos_button.get_value(), 1) * (-1 if self._filter_from_combo_box.get_active() else 1)
|
||||
@@ -640,298 +671,10 @@ class UpdateDialog:
|
||||
self._filter_model.get_model().set_value(itr, 4, select)
|
||||
|
||||
def on_quit(self, window, event):
|
||||
self._settings.add(self._size_name, window.get_size())
|
||||
self.is_download = False
|
||||
self._download_task = False
|
||||
|
||||
|
||||
class SatellitesUpdateDialog(UpdateDialog):
|
||||
""" Dialog for update satellites from the web. """
|
||||
|
||||
def __init__(self, transient, settings, main_model):
|
||||
super().__init__(transient=transient, settings=settings)
|
||||
|
||||
self._main_model = main_model
|
||||
|
||||
@run_idle
|
||||
def on_receive_data(self, item):
|
||||
if self.is_download:
|
||||
show_dialog(DialogType.ERROR, self._window, "The task is already running!")
|
||||
return
|
||||
|
||||
self.receive_satellites()
|
||||
|
||||
@run_task
|
||||
def receive_satellites(self):
|
||||
self.is_download = True
|
||||
self.update_expander()
|
||||
model = self._sat_view.get_model()
|
||||
start = time.time()
|
||||
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
|
||||
text = "Processing: {}\n"
|
||||
sats = []
|
||||
appender = self.append_output()
|
||||
next(appender)
|
||||
futures = {executor.submit(self._parser.get_satellite, sat[:-1]): sat for sat in [r for r in model if r[4]]}
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
if not self.is_download:
|
||||
self.is_download = True
|
||||
executor.shutdown()
|
||||
appender.send("\nCanceled\n")
|
||||
appender.close()
|
||||
self.is_download = False
|
||||
return
|
||||
data = future.result()
|
||||
appender.send(text.format(data[0]))
|
||||
sats.append(data)
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
appender.send("Consumed: {:0.0f}s, {} satellites received.".format(time.time() - start, len(sats)))
|
||||
appender.close()
|
||||
|
||||
sats = {s[2]: s for s in sats} # key = position, v = satellite
|
||||
|
||||
for row in self._main_model:
|
||||
pos = row[-1]
|
||||
if pos in sats:
|
||||
sat = sats.pop(pos)
|
||||
itr = row.iter
|
||||
self.update_satellite(itr, row, sat)
|
||||
|
||||
for sat in sats.values():
|
||||
append_satellite(self._main_model, sat)
|
||||
|
||||
self.is_download = False
|
||||
|
||||
@run_idle
|
||||
def update_satellite(self, itr, row, sat):
|
||||
if self._main_model.iter_has_child(itr):
|
||||
children = row.iterchildren()
|
||||
for ch in children:
|
||||
self._main_model.remove(ch.iter)
|
||||
|
||||
for tr in sat[3]:
|
||||
self._main_model.append(itr, ["Transponder:", *tr, None, None])
|
||||
|
||||
|
||||
class ServicesUpdateDialog(UpdateDialog):
|
||||
""" Dialog for updating services from the web. """
|
||||
|
||||
def __init__(self, transient, settings, callback):
|
||||
super().__init__(transient=transient, settings=settings, title="Services update")
|
||||
|
||||
self._callback = callback
|
||||
self._satellite_paths = {}
|
||||
self._transponders = {}
|
||||
self._services = {}
|
||||
self._selected_transponders = set()
|
||||
self._services_parser = ServicesParser(source=SatelliteSource.LYNGSAT)
|
||||
|
||||
self._transponder_paned.set_visible(True)
|
||||
s_model = self._source_box.get_model()
|
||||
s_model.remove(s_model.get_iter_first())
|
||||
self._source_box.set_active(0)
|
||||
# Transponder view popup menu
|
||||
tr_popup_menu = Gtk.Menu()
|
||||
select_all_item = Gtk.ImageMenuItem.new_from_stock("gtk-select-all")
|
||||
select_all_item.connect("activate", lambda w: self.update_transponder_selection(True))
|
||||
tr_popup_menu.append(select_all_item)
|
||||
remove_selection_item = Gtk.ImageMenuItem.new_from_stock("gtk-undo")
|
||||
remove_selection_item.set_label(get_message("Remove selection"))
|
||||
remove_selection_item.connect("activate", lambda w: self.update_transponder_selection(False))
|
||||
tr_popup_menu.append(remove_selection_item)
|
||||
tr_popup_menu.show_all()
|
||||
|
||||
self._sat_view.connect("row-activated", self.on_activate_satellite)
|
||||
self._transponder_view.connect("row-activated", self.on_activate_transponder)
|
||||
self._transponder_view.connect("button-press-event", lambda w, e: on_popup_menu(tr_popup_menu, e))
|
||||
self._transponder_view.connect("select_all", lambda w: self.update_transponder_selection(True))
|
||||
|
||||
@run_idle
|
||||
def on_receive_data(self, item):
|
||||
if self.is_download:
|
||||
show_dialog(DialogType.ERROR, self._window, "The task is already running!")
|
||||
return
|
||||
|
||||
self.receive_services()
|
||||
|
||||
@run_task
|
||||
def receive_services(self):
|
||||
self.is_download = True
|
||||
self.update_expander()
|
||||
model = self._sat_view.get_model()
|
||||
appender = self.append_output()
|
||||
next(appender)
|
||||
|
||||
start = time.time()
|
||||
non_cached_sats = []
|
||||
sat_names = {}
|
||||
t_names = {}
|
||||
t_urls = []
|
||||
services = []
|
||||
|
||||
for r in (r for r in model if r[-1]):
|
||||
if not self.is_download:
|
||||
appender.send("\nCanceled\n")
|
||||
return
|
||||
|
||||
sat, url = r[0], r[3]
|
||||
trs = self._transponders.get(url, None)
|
||||
if trs:
|
||||
for t in filter(lambda tp: tp.url in self._selected_transponders, trs):
|
||||
t_urls.append(t.url)
|
||||
t_names[t.url] = t.text
|
||||
else:
|
||||
non_cached_sats.append(url)
|
||||
sat_names[url] = sat
|
||||
|
||||
if non_cached_sats:
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
|
||||
futures = {executor.submit(self._services_parser.get_transponders_links, u): u for u in non_cached_sats}
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
if not self.is_download:
|
||||
appender.send("\nCanceled.\n")
|
||||
self.is_download = False
|
||||
return
|
||||
|
||||
appender.send("Getting transponders for: {}.\n".format(sat_names.get(futures[future])))
|
||||
for t in future.result():
|
||||
t_urls.append(t.url)
|
||||
t_names[t.url] = t.text
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
appender.send("{} transponders received.\n\n".format(len(t_urls)))
|
||||
|
||||
non_cached_ts = []
|
||||
for tr in t_urls:
|
||||
srvs = self._services.get(tr)
|
||||
services.extend(srvs) if srvs else non_cached_ts.append(tr)
|
||||
|
||||
if non_cached_ts:
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
|
||||
futures = {executor.submit(self._services_parser.get_transponder_services, u): u for u in non_cached_ts}
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
if not self.is_download:
|
||||
appender.send("\nCanceled.\n")
|
||||
self.is_download = False
|
||||
return
|
||||
|
||||
appender.send("Getting services for: {}.\n".format(t_names.get(futures[future], "")))
|
||||
list(map(services.append, future.result()))
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
appender.send("Consumed: {:0.0f}s, {} services received.".format(time.time() - start, len(services)))
|
||||
|
||||
try:
|
||||
from app.eparser.enigma.lamedb import get_services_lines, get_services_list
|
||||
# Used for double checking!
|
||||
srvs = get_services_list("".join(get_services_lines(services)))
|
||||
except ValueError as e:
|
||||
log("ServicesUpdateDialog [on receive data] error: {}".format(e))
|
||||
else:
|
||||
self._callback(srvs)
|
||||
|
||||
self.is_download = False
|
||||
|
||||
@run_task
|
||||
def get_sat_list(self, src, callback):
|
||||
sats = self._parser.get_satellites_list(SatelliteSource.LYNGSAT)
|
||||
if sats:
|
||||
callback(sats)
|
||||
self.is_download = False
|
||||
|
||||
def on_satellite_toggled(self, toggle, path):
|
||||
model = self._sat_view.get_model()
|
||||
self.update_state(model, path, not toggle.get_active())
|
||||
self.update_receive_button_state(self._filter_model)
|
||||
|
||||
url = model.get_value(model.get_iter(path), 3)
|
||||
selected = toggle.get_active()
|
||||
transponders = self._transponders.get(url, None)
|
||||
|
||||
if transponders:
|
||||
for t in transponders:
|
||||
self._selected_transponders.add(t.url) if selected else self._selected_transponders.discard(t.url)
|
||||
|
||||
def on_transponder_toggled(self, toggle, path):
|
||||
model = self._transponder_view.get_model()
|
||||
itr = model.get_iter(path)
|
||||
active = not toggle.get_active()
|
||||
url = self.update_transponder_state(itr, model, active)
|
||||
|
||||
s_path = self._satellite_paths.get(url, None)
|
||||
if s_path:
|
||||
self.update_sat_state(model, s_path, active)
|
||||
|
||||
def update_sat_state(self, model, path, active):
|
||||
sat_model = self._sat_view.get_model()
|
||||
if active:
|
||||
self.update_state(sat_model, path, active)
|
||||
else:
|
||||
self.update_state(sat_model, path, any((r[-1] for r in model)))
|
||||
self.update_receive_button_state(self._filter_model)
|
||||
|
||||
def update_transponder_state(self, itr, model, active):
|
||||
model.set_value(itr, 2, active)
|
||||
url = model.get_value(itr, 1)
|
||||
self._selected_transponders.add(url) if active else self._selected_transponders.discard(url)
|
||||
return url
|
||||
|
||||
@run_task
|
||||
def on_activate_satellite(self, view, path, column):
|
||||
model = view.get_model()
|
||||
itr = model.get_iter(path)
|
||||
url, selected = model.get_value(itr, 3), model.get_value(itr, 4)
|
||||
transponders = self._transponders.get(url, None)
|
||||
if transponders is None:
|
||||
GLib.idle_add(view.set_sensitive, False)
|
||||
transponders = self._services_parser.get_transponders_links(url)
|
||||
self._transponders[url] = transponders
|
||||
|
||||
for t in transponders:
|
||||
t_url = t.url
|
||||
self._satellite_paths[t_url] = path
|
||||
self._selected_transponders.add(t_url) if selected else self._selected_transponders.discard(t_url)
|
||||
|
||||
self.append_transponders(self._transponder_view.get_model(), transponders)
|
||||
|
||||
@run_idle
|
||||
def append_transponders(self, model, trs_list):
|
||||
model.clear()
|
||||
list(map(model.append, [(t.text, t.url, t.url in self._selected_transponders) for t in trs_list]))
|
||||
self._sat_view.set_sensitive(True)
|
||||
|
||||
@run_task
|
||||
def on_activate_transponder(self, view, path, column):
|
||||
url = view.get_model()[path][1]
|
||||
services = self._services.get(url, None)
|
||||
if services is None:
|
||||
GLib.idle_add(view.set_sensitive, False)
|
||||
services = self._services_parser.get_transponder_services(url)
|
||||
self._services[url] = services
|
||||
|
||||
self.append_services(self._service_view.get_model(), services)
|
||||
|
||||
@run_idle
|
||||
def append_services(self, model, srv_list):
|
||||
model.clear()
|
||||
for s in srv_list:
|
||||
model.append((None, s.service, s.package, s.service_type, str(s.ssid), None))
|
||||
|
||||
self._transponder_view.set_sensitive(True)
|
||||
|
||||
def update_transponder_selection(self, select):
|
||||
m = self._transponder_view.get_model()
|
||||
if not len(m):
|
||||
return
|
||||
|
||||
s_path = self._satellite_paths.get({self.update_transponder_state(r.iter, m, select) for r in m}.pop(), None)
|
||||
if s_path:
|
||||
self.update_sat_state(m, s_path, select)
|
||||
|
||||
|
||||
# ************************* Commons ************************* #
|
||||
|
||||
# ***************** Commons *******************#
|
||||
|
||||
@run_idle
|
||||
def append_satellite(model, sat):
|
||||
|
||||
@@ -254,7 +254,6 @@
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Cancel</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="action">
|
||||
@@ -265,7 +264,6 @@
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Save current service</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_save" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
@@ -276,7 +274,6 @@
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Create and save as new service</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_create_new" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
@@ -1569,7 +1566,6 @@
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="action">
|
||||
@@ -1579,7 +1575,6 @@
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import os
|
||||
import re
|
||||
import os
|
||||
|
||||
from app.commons import run_idle
|
||||
from app.eparser import Service
|
||||
from app.eparser.ecommons import (MODULATION, Inversion, ROLL_OFF, Pilot, Flag, Pids, POLARIZATION, get_key_by_value,
|
||||
get_value_by_name, FEC_DEFAULT, PLS_MODE, SERVICE_TYPE, T_MODULATION, C_MODULATION,
|
||||
TrType, SystemCable, T_SYSTEM, BANDWIDTH, TRANSMISSION_MODE, GUARD_INTERVAL, T_FEC,
|
||||
HIERARCHY)
|
||||
from app.eparser.ecommons import MODULATION, Inversion, ROLL_OFF, Pilot, Flag, Pids, POLARIZATION, \
|
||||
get_key_by_value, get_value_by_name, FEC_DEFAULT, PLS_MODE, SERVICE_TYPE, T_MODULATION, C_MODULATION, TrType, \
|
||||
SystemCable, T_SYSTEM, BANDWIDTH, TRANSMISSION_MODE, GUARD_INTERVAL, HIERARCHY, T_FEC
|
||||
from app.settings import SettingsType
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, TEXT_DOMAIN, CODED_ICON, Column, IS_GNOME_SESSION
|
||||
from .dialogs import show_dialog, DialogType, Action, get_dialogs_string
|
||||
from .main_helper import get_base_model
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, TEXT_DOMAIN, CODED_ICON, Column, IS_GNOME_SESSION
|
||||
|
||||
_UI_PATH = UI_RESOURCES_PATH + "service_details_dialog.glade"
|
||||
|
||||
@@ -439,17 +438,14 @@ class ServiceDetailsDialog:
|
||||
def update_fav_view(self, old_service, new_service):
|
||||
model = self._fav_view.get_model()
|
||||
for row in filter(lambda r: old_service.fav_id == r[7], model):
|
||||
itr = row.iter
|
||||
if not model.get_value(itr, Column.FAV_BACKGROUND):
|
||||
model.set_value(itr, Column.FAV_SERVICE, new_service.service)
|
||||
|
||||
model.set(itr, {Column.FAV_CODED: new_service.coded,
|
||||
Column.FAV_LOCKED: new_service.locked,
|
||||
Column.FAV_HIDE: new_service.hide,
|
||||
Column.FAV_TYPE: new_service.service_type,
|
||||
Column.FAV_POS: new_service.pos,
|
||||
Column.FAV_ID: new_service.fav_id,
|
||||
Column.FAV_PICON: new_service.picon})
|
||||
model.set(row.iter, {1: new_service.coded,
|
||||
2: new_service.service,
|
||||
3: new_service.locked,
|
||||
4: new_service.hide,
|
||||
5: new_service.service_type,
|
||||
6: new_service.pos,
|
||||
7: new_service.fav_id,
|
||||
8: new_service.picon})
|
||||
|
||||
def update_picon_name(self, old_name, new_name):
|
||||
if not os.path.isdir(self._picons_dir_path):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,7 +36,6 @@ class SettingsDialog:
|
||||
"on_set_color_switch": self.on_set_color_switch,
|
||||
"on_force_bq_name": self.on_force_bq_name,
|
||||
"on_http_mode_switch": self.on_http_mode_switch,
|
||||
"on_experimental_switch": self.on_experimental_switch,
|
||||
"on_yt_dl_switch": self.on_yt_dl_switch,
|
||||
"on_default_path_mode_switch": self.on_default_path_mode_switch,
|
||||
"on_default_data_path_changed": self.on_default_data_path_changed,
|
||||
@@ -114,7 +113,6 @@ class SettingsDialog:
|
||||
self._support_ver5_switch = builder.get_object("support_ver5_switch")
|
||||
self._force_bq_name_switch = builder.get_object("force_bq_name_switch")
|
||||
# Streaming
|
||||
header_separator = builder.get_object("header_separator")
|
||||
self._apply_presets_button = builder.get_object("apply_presets_button")
|
||||
self._transcoding_switch = builder.get_object("transcoding_switch")
|
||||
self._edit_preset_switch = builder.get_object("edit_preset_switch")
|
||||
@@ -125,8 +123,6 @@ class SettingsDialog:
|
||||
self._audio_bitrate_field = builder.get_object("audio_bitrate_field")
|
||||
self._audio_channels_combo_box = builder.get_object("audio_channels_combo_box")
|
||||
self._audio_sample_rate_combo_box = builder.get_object("audio_sample_rate_combo_box")
|
||||
self._audio_codec_combo_box = builder.get_object("audio_codec_combo_box")
|
||||
self._apply_presets_button.bind_property("visible", header_separator, "visible")
|
||||
self._transcoding_switch.bind_property("active", builder.get_object("record_box"), "sensitive")
|
||||
self._edit_preset_switch.bind_property("active", self._apply_presets_button, "sensitive")
|
||||
self._edit_preset_switch.bind_property("active", builder.get_object("video_options_frame"), "sensitive")
|
||||
@@ -137,7 +133,8 @@ class SettingsDialog:
|
||||
# Program
|
||||
self._before_save_switch = builder.get_object("before_save_switch")
|
||||
self._before_downloading_switch = builder.get_object("before_downloading_switch")
|
||||
self._enable_experimental_box = builder.get_object("enable_experimental_box")
|
||||
self._program_frame = builder.get_object("program_frame")
|
||||
self._extra_support_grid = builder.get_object("extra_support_grid")
|
||||
self._colors_grid = builder.get_object("colors_grid")
|
||||
self._set_color_switch = builder.get_object("set_color_switch")
|
||||
self._new_color_button = builder.get_object("new_color_button")
|
||||
@@ -146,10 +143,9 @@ class SettingsDialog:
|
||||
self._bouquet_hints_switch = builder.get_object("bouquet_hints_switch")
|
||||
self._services_hints_switch = builder.get_object("services_hints_switch")
|
||||
self._lang_combo_box = builder.get_object("lang_combo_box")
|
||||
# Extra
|
||||
# HTTP API
|
||||
self._support_http_api_switch = builder.get_object("support_http_api_switch")
|
||||
self._enable_yt_dl_switch = builder.get_object("enable_yt_dl_switch")
|
||||
self._enable_update_yt_dl_switch = builder.get_object("enable_update_yt_dl_switch")
|
||||
self._enable_y_dl_switch = builder.get_object("enable_y_dl_switch")
|
||||
self._enable_send_to_switch = builder.get_object("enable_send_to_switch")
|
||||
self._click_mode_disabled_button = builder.get_object("click_mode_disabled_button")
|
||||
self._click_mode_stream_button = builder.get_object("click_mode_stream_button")
|
||||
@@ -158,24 +154,15 @@ class SettingsDialog:
|
||||
self._click_mode_zap_and_play_button = builder.get_object("click_mode_zap_and_play_button")
|
||||
self._click_mode_zap_button.bind_property("sensitive", self._click_mode_play_button, "sensitive")
|
||||
self._click_mode_zap_button.bind_property("sensitive", self._click_mode_zap_and_play_button, "sensitive")
|
||||
# EXPERIMENTAL
|
||||
self._enable_exp_switch = builder.get_object("enable_experimental_switch")
|
||||
self._enable_exp_switch.bind_property("active", builder.get_object("yt_dl_box"), "sensitive")
|
||||
self._enable_yt_dl_switch.bind_property("active", builder.get_object("yt_dl_update_box"), "sensitive")
|
||||
self._enable_exp_switch.bind_property("active", builder.get_object("v5_support_box"), "sensitive")
|
||||
self._enable_exp_switch.bind_property("active", builder.get_object("enable_direct_playback_box"), "sensitive")
|
||||
# Enigma2 only
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("bq_naming_grid"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("enable_http_box"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("enable_experimental_box"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("program_frame"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("experimental_box"), "sensitive")
|
||||
self._click_mode_zap_button.bind_property("sensitive", self._enable_send_to_switch, "sensitive")
|
||||
self._enable_send_to_switch.bind_property("sensitive", builder.get_object("enable_send_to_label"), "sensitive")
|
||||
self._extra_support_grid.bind_property("sensitive", builder.get_object("v5_support_grid"), "sensitive")
|
||||
self._extra_support_grid.bind_property("sensitive", builder.get_object("bq_naming_grid"), "sensitive")
|
||||
# Profiles
|
||||
self._profile_view = builder.get_object("profile_tree_view")
|
||||
self._profile_add_button = builder.get_object("profile_add_button")
|
||||
self._profile_remove_button = builder.get_object("profile_remove_button")
|
||||
self._apply_profile_button = builder.get_object("apply_profile_button")
|
||||
self._apply_profile_button.bind_property("visible", header_separator, "visible")
|
||||
self._apply_profile_button.bind_property("visible", builder.get_object("reset_button"), "visible")
|
||||
# Style
|
||||
self._style_provider = Gtk.CssProvider()
|
||||
@@ -195,7 +182,6 @@ class SettingsDialog:
|
||||
self._theme_thumbnail_image = builder.get_object("theme_thumbnail_image")
|
||||
self._theme_combo_box = builder.get_object("theme_combo_box")
|
||||
self._icon_theme_combo_box = builder.get_object("icon_theme_combo_box")
|
||||
self._dark_mode_switch = builder.get_object("dark_mode_switch")
|
||||
self._themes_support_switch = builder.get_object("themes_support_switch")
|
||||
self._themes_support_switch.bind_property("active", builder.get_object("gtk_theme_frame"), "sensitive")
|
||||
self._themes_support_switch.bind_property("active", builder.get_object("icon_theme_frame"), "sensitive")
|
||||
@@ -205,8 +191,10 @@ class SettingsDialog:
|
||||
def init_ui_elements(self, s_type):
|
||||
is_enigma_profile = s_type is SettingsType.ENIGMA_2
|
||||
self._neutrino_radio_button.set_active(s_type is SettingsType.NEUTRINO_MP)
|
||||
self.update_header_bar()
|
||||
self.update_title()
|
||||
self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(is_enigma_profile)
|
||||
self._program_frame.set_sensitive(is_enigma_profile)
|
||||
self._extra_support_grid.set_sensitive(is_enigma_profile)
|
||||
http_active = self._support_http_api_switch.get_active()
|
||||
self._click_mode_zap_button.set_sensitive(is_enigma_profile and http_active)
|
||||
self._lang_combo_box.set_active_id(self._ext_settings.language)
|
||||
@@ -221,15 +209,15 @@ class SettingsDialog:
|
||||
model.append((p, icon))
|
||||
if icon:
|
||||
scroll_to(ind, self._profile_view)
|
||||
self.on_profile_selected(self._profile_view, False)
|
||||
self.on_profile_selected(self._profile_view)
|
||||
self._profile_remove_button.set_sensitive(len(self._profile_view.get_model()) > 1)
|
||||
|
||||
def update_header_bar(self):
|
||||
label, sep, st = self._header_bar.get_subtitle().partition(":")
|
||||
def update_title(self):
|
||||
title = "{} [{}]"
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self._header_bar.set_subtitle("{}: {}".format(label, self._enigma_radio_button.get_label()))
|
||||
self._dialog.set_title(title.format(get_message("Options"), self._enigma_radio_button.get_label()))
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
self._header_bar.set_subtitle("{}: {}".format(label, self._neutrino_radio_button.get_label()))
|
||||
self._dialog.set_title(title.format(get_message("Options"), self._neutrino_radio_button.get_label()))
|
||||
|
||||
def show(self):
|
||||
self._dialog.run()
|
||||
@@ -292,12 +280,10 @@ class SettingsDialog:
|
||||
self.on_transcoding_preset_changed(self._presets_combo_box)
|
||||
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self._enable_exp_switch.set_active(self._settings.is_enable_experimental)
|
||||
self._support_ver5_switch.set_active(self._settings.v5_support)
|
||||
self._force_bq_name_switch.set_active(self._settings.force_bq_names)
|
||||
self._support_http_api_switch.set_active(self._settings.http_api_support)
|
||||
self._enable_yt_dl_switch.set_active(self._settings.enable_yt_dl)
|
||||
self._enable_update_yt_dl_switch.set_active(self._settings.enable_yt_dl_update)
|
||||
self._enable_y_dl_switch.set_active(self._settings.enable_yt_dl)
|
||||
self._enable_send_to_switch.set_active(self._settings.enable_send_to)
|
||||
self._set_color_switch.set_active(self._settings.use_colors)
|
||||
new_rgb = Gdk.RGBA()
|
||||
@@ -312,7 +298,7 @@ class SettingsDialog:
|
||||
else:
|
||||
self._neutrino_radio_button.activate()
|
||||
|
||||
def on_apply_profile_settings(self, item=None):
|
||||
def on_apply_profile_settings(self, item):
|
||||
if not self.is_data_correct(self._digit_elems):
|
||||
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
|
||||
return
|
||||
@@ -340,10 +326,9 @@ class SettingsDialog:
|
||||
self._settings.backup_local_path = self._backup_dir_field.get_text()
|
||||
|
||||
def apply_settings(self, item=None):
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.OK:
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
self.on_apply_profile_settings()
|
||||
self._ext_settings.profiles = self._settings.profiles
|
||||
self._ext_settings.backup_before_save = self._before_save_switch.get_active()
|
||||
self._ext_settings.backup_before_downloading = self._before_downloading_switch.get_active()
|
||||
@@ -360,21 +345,18 @@ class SettingsDialog:
|
||||
self._ext_settings.active_preset = self._presets_combo_box.get_active_id()
|
||||
|
||||
if self._ext_settings.is_darwin:
|
||||
self._ext_settings.dark_mode = self._dark_mode_switch.get_active()
|
||||
self._ext_settings.is_themes_support = self._themes_support_switch.get_active()
|
||||
self._ext_settings.theme = self._theme_combo_box.get_active_id()
|
||||
self._ext_settings.icon_theme = self._icon_theme_combo_box.get_active_id()
|
||||
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self._ext_settings.is_enable_experimental = self._enable_exp_switch.get_active()
|
||||
self._ext_settings.use_colors = self._set_color_switch.get_active()
|
||||
self._ext_settings.new_color = self._new_color_button.get_rgba().to_string()
|
||||
self._ext_settings.extra_color = self._extra_color_button.get_rgba().to_string()
|
||||
self._ext_settings.v5_support = self._support_ver5_switch.get_active()
|
||||
self._ext_settings.force_bq_names = self._force_bq_name_switch.get_active()
|
||||
self._ext_settings.http_api_support = self._support_http_api_switch.get_active()
|
||||
self._ext_settings.enable_yt_dl = self._enable_yt_dl_switch.get_active()
|
||||
self._ext_settings.enable_yt_dl_update = self._enable_update_yt_dl_switch.get_active()
|
||||
self._ext_settings.enable_yt_dl = self._enable_y_dl_switch.get_active()
|
||||
self._ext_settings.enable_send_to = self._enable_send_to_switch.get_active()
|
||||
|
||||
self._ext_settings.default_profile = list(filter(lambda r: r[1], self._profile_view.get_model()))[0][0]
|
||||
@@ -452,12 +434,6 @@ class SettingsDialog:
|
||||
self._click_mode_zap_and_play_button.get_active())):
|
||||
self._click_mode_disabled_button.set_active(True)
|
||||
|
||||
def on_experimental_switch(self, switch, state):
|
||||
if not state:
|
||||
self._support_ver5_switch.set_active(state)
|
||||
self._enable_send_to_switch.set_active(state)
|
||||
self._enable_yt_dl_switch.set_active(state)
|
||||
|
||||
def on_force_bq_name(self, switch, state):
|
||||
if self._main_stack.get_visible_child_name() != "extra":
|
||||
return
|
||||
@@ -488,7 +464,7 @@ class SettingsDialog:
|
||||
self._profiles[name] = self._s_type.get_default_settings()
|
||||
model.append((name, None))
|
||||
scroll_to(len(model) - 1, self._profile_view)
|
||||
self.on_profile_selected(self._profile_view, False)
|
||||
self.on_profile_selected(self._profile_view)
|
||||
self.on_reset()
|
||||
|
||||
def on_profile_edit(self, item=None):
|
||||
@@ -524,7 +500,7 @@ class SettingsDialog:
|
||||
row[0] = new_value
|
||||
self._profiles[new_value] = p_settings
|
||||
self.update_local_paths(new_value, old_name)
|
||||
self.on_profile_selected(self._profile_view, False)
|
||||
self.on_profile_selected(self._profile_view)
|
||||
|
||||
def update_local_paths(self, p_name, old_name, force_rename=False):
|
||||
data_path = self._settings.data_local_path
|
||||
@@ -546,10 +522,7 @@ class SettingsDialog:
|
||||
except OSError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
|
||||
def on_profile_selected(self, view, force=True):
|
||||
if force:
|
||||
self.on_apply_profile_settings()
|
||||
|
||||
def on_profile_selected(self, view):
|
||||
model, paths = self._profile_view.get_selection().get_selected_rows()
|
||||
if paths:
|
||||
profile = model.get_value(model.get_iter(paths), 0)
|
||||
@@ -650,7 +623,6 @@ class SettingsDialog:
|
||||
self._audio_bitrate_field.set_text(prs.get("ab", "0"))
|
||||
self._audio_channels_combo_box.set_active_id(prs.get("channels", "2"))
|
||||
self._audio_sample_rate_combo_box.set_active_id(prs.get("samplerate", "44100"))
|
||||
self._audio_codec_combo_box.set_active_id(prs.get("acodec", "mp3"))
|
||||
|
||||
def on_apply_presets(self, item):
|
||||
if not self.is_data_correct(self._digit_elems):
|
||||
@@ -668,7 +640,6 @@ class SettingsDialog:
|
||||
prs["ab"] = self._audio_bitrate_field.get_text()
|
||||
prs["channels"] = self._audio_channels_combo_box.get_active_id()
|
||||
prs["samplerate"] = self._audio_sample_rate_combo_box.get_active_id()
|
||||
prs["acodec"] = self._audio_codec_combo_box.get_active_id()
|
||||
self._ext_settings.transcoding_presets = presets
|
||||
self._edit_preset_switch.set_active(False)
|
||||
|
||||
@@ -777,7 +748,6 @@ class SettingsDialog:
|
||||
|
||||
@run_idle
|
||||
def init_appearance(self):
|
||||
self._dark_mode_switch.set_active(self._ext_settings.dark_mode)
|
||||
t_support = self._ext_settings.is_themes_support
|
||||
self._themes_support_switch.set_active(t_support)
|
||||
if t_support:
|
||||
|
||||
@@ -1,46 +1,21 @@
|
||||
#digit-entry {
|
||||
border-color: Red;
|
||||
border-width: 0.15em;
|
||||
}
|
||||
|
||||
#status-bar-button {
|
||||
padding: 1px;
|
||||
margin: 1px;
|
||||
margin: 0.1em;
|
||||
}
|
||||
|
||||
paned > separator {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 2px 24px;
|
||||
}
|
||||
|
||||
.red-button {
|
||||
background-image: none;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.green-button {
|
||||
background-image: none;
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.yellow-button {
|
||||
background-image: none;
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
.blue-button {
|
||||
background-image: none;
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
.time-entry {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
#textview-large {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.group {}
|
||||
|
||||
.group :first-child {
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
@@ -48,11 +23,10 @@ paned > separator {
|
||||
.group :last-child {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.group :not(:first-child):not(:last-child) {
|
||||
border-radius: 0;
|
||||
border-left-width: 0;
|
||||
border-right-width: 1px;
|
||||
border-right-width: 0;
|
||||
}
|
||||
250
app/ui/telnet.glade
Executable file
250
app/ui/telnet.glade
Executable file
@@ -0,0 +1,250 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2020 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkTextTagTable" id="tag_table">
|
||||
<child type="tag">
|
||||
<object class="GtkTextTag" id="end_tag">
|
||||
<property name="font">Normal</property>
|
||||
<property name="editable">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkTextBuffer" id="text_buffer">
|
||||
<property name="tag_table">tag_table</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="dialog_window">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">DemonEditor [Telnet client]</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="icon_name">terminal</property>
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<signal name="delete-event" handler="on_close" swapped="no"/>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="width_request">560</property>
|
||||
<property name="height_request">320</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="telnet_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view">
|
||||
<property name="name">textview-large</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="wrap_mode">char</property>
|
||||
<property name="left_margin">5</property>
|
||||
<property name="right_margin">5</property>
|
||||
<property name="buffer">text_buffer</property>
|
||||
<property name="overwrite">True</property>
|
||||
<property name="input_hints">GTK_INPUT_HINT_WORD_COMPLETION | GTK_INPUT_HINT_NONE</property>
|
||||
<property name="monospace">True</property>
|
||||
<signal name="key-press-event" handler="on_view_key_press" swapped="no"/>
|
||||
<signal name="realize" handler="on_text_view_realize" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="commands_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="profile_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="active">0</property>
|
||||
<property name="has_frame">False</property>
|
||||
<signal name="changed" handler="on_profile_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="connect_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Connect</property>
|
||||
<signal name="clicked" handler="on_connect" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="connect_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-connect</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="disconnect_button">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Disconnect</property>
|
||||
<signal name="clicked" handler="on_disconnect" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="disconnect_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-disconnect</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="center">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="clear_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Clear</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="clicked" handler="on_clear" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="clear_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-clear</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="info_bar">
|
||||
<property name="app_paintable">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<signal name="response" handler="on_info_bar_close" swapped="no"/>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child internal-child="content_area">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="info_bar_message_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label">Info</property>
|
||||
<property name="justify">center</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="wrap_mode">word-char</property>
|
||||
<property name="lines">2</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
256
app/ui/telnet.py
Executable file
256
app/ui/telnet.py
Executable file
@@ -0,0 +1,256 @@
|
||||
import re
|
||||
import selectors
|
||||
import socket
|
||||
from collections import deque
|
||||
from telnetlib import Telnet
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from app.commons import run_task, run_idle, log
|
||||
from app.settings import Settings
|
||||
from app.ui.uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK
|
||||
|
||||
|
||||
class ExtTelnet(Telnet):
|
||||
|
||||
def __init__(self, output_callback, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._output_callback = output_callback
|
||||
|
||||
def interact(self):
|
||||
"""Interaction function, emulates a very dumb telnet client."""
|
||||
with selectors.DefaultSelector() as selector:
|
||||
selector.register(self, selectors.EVENT_READ)
|
||||
|
||||
while True:
|
||||
for key, events in selector.select():
|
||||
if key.fileobj is self:
|
||||
try:
|
||||
text = self.read_very_eager()
|
||||
except EOFError as e:
|
||||
msg = "\n*** Connection closed by remote host ***\n"
|
||||
self._output_callback(msg)
|
||||
log(msg)
|
||||
raise e
|
||||
else:
|
||||
if text:
|
||||
self._output_callback(text)
|
||||
|
||||
|
||||
class TelnetDialog:
|
||||
""" Dialog of very simple telnet client. """
|
||||
_COLOR_PATTERN = re.compile("\x1b.*?m") # Color info
|
||||
_ERASING_PATTERN = re.compile("\x1b.*?K") # Erase to right
|
||||
_APP_MODE_PATTERN = re.compile("\x1b.*?(1h)|(1l)") # h - on, l - off
|
||||
_ALL_PATTERN = re.compile(r'(\x1b\[|\x9b)[0-?]*[@-~]')
|
||||
_NOT_SUPPORTED = {"mc", "mcedit", "vi", "nano"}
|
||||
|
||||
def __init__(self, transient, settings):
|
||||
self._handlers = {"on_profile_changed": self.on_profile_changed,
|
||||
"on_clear": self.on_clear,
|
||||
"on_text_view_realize": self.on_text_view_realize,
|
||||
"on_view_key_press": self.on_view_key_press,
|
||||
"on_info_bar_close": self.on_info_bar_close,
|
||||
"on_connect": self.on_connect,
|
||||
"on_disconnect": self.on_disconnect,
|
||||
"on_close": self.on_close}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.add_from_file(UI_RESOURCES_PATH + "telnet.glade")
|
||||
builder.connect_signals(self._handlers)
|
||||
self._dialog_window = builder.get_object("dialog_window")
|
||||
self._dialog_window.set_transient_for(transient)
|
||||
self._profile_combo_box = builder.get_object("profile_combo_box")
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._info_message_label = builder.get_object("info_bar_message_label")
|
||||
self._text_view = builder.get_object("text_view")
|
||||
self._buf = builder.get_object("text_buffer")
|
||||
self._end_tag = builder.get_object("end_tag")
|
||||
self._connect_button = builder.get_object("connect_button")
|
||||
self._connect_button.bind_property("visible", builder.get_object("disconnect_button"), "visible", 4)
|
||||
provider = Gtk.CssProvider()
|
||||
provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
builder.get_object("main_box").get_style_context().add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
|
||||
window_size = settings.get("telnet_dialog_window_size")
|
||||
if window_size:
|
||||
self._dialog_window.resize(*window_size)
|
||||
|
||||
self._ext_settings = settings
|
||||
self._settings = Settings(settings.settings)
|
||||
self._tn = None
|
||||
self._app_mode = False
|
||||
self._commands = deque(maxlen=10)
|
||||
|
||||
def show(self):
|
||||
self._dialog_window.show()
|
||||
|
||||
def on_close(self, window, event):
|
||||
""" Performs shutdown tasks """
|
||||
self._ext_settings.add("telnet_dialog_window_size", window.get_size())
|
||||
self.on_disconnect()
|
||||
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(True)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._info_message_label.set_text(text)
|
||||
|
||||
def on_text_view_realize(self, view):
|
||||
self.init_profiles()
|
||||
self.on_connect()
|
||||
|
||||
@run_idle
|
||||
def init_profiles(self):
|
||||
for p in self._settings.profiles:
|
||||
self._profile_combo_box.append(p, p)
|
||||
self._profile_combo_box.set_active_id(self._settings.current_profile)
|
||||
|
||||
@run_task
|
||||
def on_connect(self, item=None):
|
||||
try:
|
||||
GLib.idle_add(self._connect_button.set_visible, False)
|
||||
GLib.idle_add(self.on_info_bar_close)
|
||||
user, password = self._settings.telnet_user, self._settings.telnet_password
|
||||
timeout = self._settings.telnet_timeout
|
||||
|
||||
self._tn = ExtTelnet(self.append_output,
|
||||
host=self._settings.host,
|
||||
port=self._settings.telnet_port,
|
||||
timeout=timeout)
|
||||
|
||||
if user != "":
|
||||
self._tn.read_until(b"login: ")
|
||||
self._tn.write(user.encode("utf-8") + b"\n")
|
||||
if password != "":
|
||||
self._tn.read_until(b"Password: ")
|
||||
self._tn.write(password.encode("utf-8") + b"\n")
|
||||
|
||||
self._tn.interact()
|
||||
except (OSError, EOFError, socket.timeout, ConnectionRefusedError) as e:
|
||||
log("{}: {}".format(self.__class__.__name__, e))
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
finally:
|
||||
GLib.idle_add(self._connect_button.set_visible, True)
|
||||
|
||||
@run_task
|
||||
def on_disconnect(self, item=None):
|
||||
if self._tn:
|
||||
GLib.idle_add(self._connect_button.set_visible, True)
|
||||
self._tn.close()
|
||||
|
||||
def on_profile_changed(self, button):
|
||||
self._settings.current_profile = button.get_active_id()
|
||||
|
||||
def on_command_done(self, entry):
|
||||
command = entry.get_text()
|
||||
entry.set_text("")
|
||||
if command and self._tn:
|
||||
self._tn.write(command.encode("ascii") + b"\r")
|
||||
|
||||
def on_clear(self, item=None):
|
||||
self._buf.delete(self._buf.get_start_iter(), self._buf.get_end_iter())
|
||||
|
||||
def on_view_key_press(self, view, event):
|
||||
""" Handling keystrokes on press """
|
||||
if event.keyval == Gdk.KEY_Return:
|
||||
self.do_command()
|
||||
return True
|
||||
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
if ctrl and key is KeyboardKey.C:
|
||||
if self._tn and self._tn.sock:
|
||||
self._tn.write(b"\x03") # interrupt
|
||||
|
||||
# last commands navigation
|
||||
if key is KeyboardKey.UP:
|
||||
self.delete_last_command()
|
||||
if self._commands:
|
||||
cmd = self._commands.pop()
|
||||
self._commands.appendleft(cmd)
|
||||
self._buf.insert_at_cursor(cmd, -1)
|
||||
return True
|
||||
elif key is KeyboardKey.DOWN:
|
||||
self.delete_last_command()
|
||||
if self._commands:
|
||||
cmd = self._commands.popleft()
|
||||
self._commands.append(cmd)
|
||||
self._buf.insert_at_cursor(cmd, -1)
|
||||
return True
|
||||
|
||||
def delete_last_command(self):
|
||||
end = self._buf.get_end_iter()
|
||||
if end.ends_tag(self._end_tag):
|
||||
return
|
||||
|
||||
if end.backward_to_tag_toggle(self._end_tag):
|
||||
self._buf.delete(self._buf.get_end_iter(), end)
|
||||
|
||||
def do_command(self):
|
||||
count = self._buf.get_line_count()
|
||||
begin = self._buf.get_iter_at_line(count)
|
||||
end = self._buf.get_end_iter()
|
||||
command = []
|
||||
|
||||
while end.backward_to_tag_toggle(self._end_tag):
|
||||
command.append(self._buf.get_text(end, begin, False))
|
||||
break
|
||||
else: # if buf is empty
|
||||
command.append(self._buf.get_text(begin, end, False))
|
||||
|
||||
# to preventing duplication of the command in the buf
|
||||
self._buf.delete(end, begin)
|
||||
|
||||
if command and self._tn.sock:
|
||||
cmd = command[0]
|
||||
if cmd in self._NOT_SUPPORTED:
|
||||
self.show_info_message("'{}' is not supported by this client.".format(cmd), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
self._tn.write(cmd.encode("ascii") + b"\r")
|
||||
self._commands.append(cmd)
|
||||
|
||||
@run_idle
|
||||
def append_output(self, txt):
|
||||
t = txt.decode("ascii", errors="ignore")
|
||||
|
||||
ap = re.search(self._APP_MODE_PATTERN, t)
|
||||
if ap:
|
||||
on, of = ap.group(1), ap.group(2)
|
||||
if on:
|
||||
self._app_mode = True
|
||||
elif of:
|
||||
self._app_mode = False
|
||||
self.on_clear()
|
||||
|
||||
t = re.sub(self._ALL_PATTERN, "", t) # removing [replacing] ascii escape sequences
|
||||
|
||||
if self._app_mode:
|
||||
start, end = self._buf.get_start_iter(), self._buf.get_end_iter()
|
||||
count = self._buf.get_line_count()
|
||||
new_lines = t.split("\r\n")
|
||||
ext_lines = self._buf.get_text(start, end, True).split("\r\n")
|
||||
if count < len(new_lines):
|
||||
self._buf.set_text(re.sub(self._ERASING_PATTERN, "", t))
|
||||
else:
|
||||
for i, line in enumerate(new_lines):
|
||||
if line:
|
||||
ext_lines[i] = re.sub(self._ERASING_PATTERN, "", line)
|
||||
self._buf.set_text("\r\n".join(ext_lines))
|
||||
else:
|
||||
self._buf.insert_at_cursor(t, -1)
|
||||
|
||||
insert = self._buf.get_insert()
|
||||
self._text_view.scroll_to_mark(insert, 0.0, True, 0.0, 1.0)
|
||||
self._buf.apply_tag(self._end_tag, self._buf.get_start_iter(), self._buf.get_end_iter())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -1,141 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.2 -->
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<object class="GtkBox" id="timer_row_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">2</property>
|
||||
<property name="margin_right">2</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="timer_name_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="timer_name_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="semibold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="timer_description_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="timer_description_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<attributes>
|
||||
<attribute name="style" value="italic"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="timer_service_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="timer_service_name_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="timer_time_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<attributes>
|
||||
<attribute name="size" value="8000"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="timer_row_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="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
@@ -5,20 +5,21 @@ import gi
|
||||
from gi.repository import GLib
|
||||
|
||||
from app.commons import log
|
||||
from app.connections import HttpAPI
|
||||
from app.settings import IS_DARWIN
|
||||
from app.connections import HttpRequestType
|
||||
from app.tools.yt import YouTube
|
||||
from app.ui.iptv import get_yt_icon
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH
|
||||
|
||||
|
||||
class LinksTransmitter:
|
||||
""" The main class for the "send to" function.
|
||||
""" The main media bar class for the "send to" function..
|
||||
|
||||
It used for direct playback of media links by the enigma2 media player.
|
||||
"""
|
||||
__STREAM_PREFIX = "4097:0:1:0:0:0:0:0:0:0:"
|
||||
|
||||
def __init__(self, http_api, app_window, settings):
|
||||
def __init__(self, http_api, app_window):
|
||||
handlers = {"on_popup_menu": self.on_popup_menu,
|
||||
"on_status_icon_activate": self.on_status_icon_activate,
|
||||
"on_url_changed": self.on_url_changed,
|
||||
@@ -45,18 +46,20 @@ class LinksTransmitter:
|
||||
self._restore_menu_item = builder.get_object("restore_menu_item")
|
||||
self._status_active = None
|
||||
self._status_passive = None
|
||||
self._yt = YouTube.get_instance(settings)
|
||||
|
||||
try:
|
||||
gi.require_version("AppIndicator3", "0.1")
|
||||
from gi.repository import AppIndicator3
|
||||
except (ImportError, ValueError) as e:
|
||||
log("{}: Load library error: {}".format(__class__.__name__, e))
|
||||
if IS_DARWIN:
|
||||
self._tray = builder.get_object("status_icon")
|
||||
else:
|
||||
self._is_status_icon = False
|
||||
self._status_active = AppIndicator3.IndicatorStatus.ACTIVE
|
||||
self._status_passive = AppIndicator3.IndicatorStatus.PASSIVE
|
||||
try:
|
||||
gi.require_version("AppIndicator3", "0.1")
|
||||
from gi.repository import AppIndicator3
|
||||
except (ImportError, ValueError) as e:
|
||||
log("{}: Load library error: {}".format(__class__.__name__, e))
|
||||
self._tray = builder.get_object("status_icon")
|
||||
else:
|
||||
self._is_status_icon = False
|
||||
self._status_active = AppIndicator3.IndicatorStatus.ACTIVE
|
||||
self._status_passive = AppIndicator3.IndicatorStatus.PASSIVE
|
||||
|
||||
category = AppIndicator3.IndicatorCategory.APPLICATION_STATUS
|
||||
path = Path(UI_RESOURCES_PATH + "/icons/hicolor/scalable/apps/demon-editor.svg")
|
||||
@@ -114,7 +117,7 @@ class LinksTransmitter:
|
||||
|
||||
if yt_id:
|
||||
self._url_entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
|
||||
links, title = self._yt.get_yt_link(yt_id, url)
|
||||
links, title = YouTube.get_yt_link(yt_id)
|
||||
yield True
|
||||
if links:
|
||||
url = links[sorted(links, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
|
||||
@@ -124,7 +127,7 @@ class LinksTransmitter:
|
||||
else:
|
||||
self._url_entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
|
||||
|
||||
self._http_api.send(HttpAPI.Request.PLAY, url, self.on_done, self.__STREAM_PREFIX)
|
||||
self._http_api.send(HttpRequestType.PLAY, url, self.on_done, self.__STREAM_PREFIX)
|
||||
yield True
|
||||
|
||||
def on_done(self, res):
|
||||
@@ -134,21 +137,21 @@ class LinksTransmitter:
|
||||
GLib.idle_add(self._tool_bar.set_sensitive, True)
|
||||
|
||||
def on_previous(self, item):
|
||||
self._http_api.send(HttpAPI.Request.PLAYER_PREV, None, self.on_done)
|
||||
self._http_api.send(HttpRequestType.PLAYER_PREV, None, self.on_done)
|
||||
|
||||
def on_next(self, item):
|
||||
self._http_api.send(HttpAPI.Request.PLAYER_NEXT, None, self.on_done)
|
||||
self._http_api.send(HttpRequestType.PLAYER_NEXT, None, self.on_done)
|
||||
|
||||
def on_play(self, item):
|
||||
self._http_api.send(HttpAPI.Request.PLAYER_PLAY, None, self.on_done)
|
||||
self._http_api.send(HttpRequestType.PLAYER_PLAY, None, self.on_done)
|
||||
|
||||
def on_stop(self, item):
|
||||
self._http_api.send(HttpAPI.Request.PLAYER_STOP, None, self.on_done)
|
||||
self._http_api.send(HttpRequestType.PLAYER_STOP, None, self.on_done)
|
||||
|
||||
def on_clear(self, item):
|
||||
""" Remove added links in the playlist. """
|
||||
GLib.idle_add(self._tool_bar.set_sensitive, False)
|
||||
self._http_api.send(HttpAPI.Request.PLAYER_LIST, None, self.clear_playlist)
|
||||
self._http_api.send(HttpRequestType.PLAYER_LIST, None, self.clear_playlist)
|
||||
|
||||
def clear_playlist(self, res):
|
||||
GLib.idle_add(self._tool_bar.set_sensitive, not res)
|
||||
@@ -159,7 +162,7 @@ class LinksTransmitter:
|
||||
|
||||
for ref in res:
|
||||
GLib.idle_add(self._tool_bar.set_sensitive, False)
|
||||
self._http_api.send(HttpAPI.Request.PLAYER_REMOVE,
|
||||
self._http_api.send(HttpRequestType.PLAYER_REMOVE,
|
||||
ref.get("e2servicereference", ""),
|
||||
self.on_done,
|
||||
self.__STREAM_PREFIX)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import locale
|
||||
import os
|
||||
from enum import Enum, IntEnum
|
||||
from functools import lru_cache
|
||||
@@ -8,15 +7,15 @@ import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
gi.require_version("Gdk", "3.0")
|
||||
gi.require_version("Notify", "0.7")
|
||||
from gi.repository import Gtk, Gdk, Notify
|
||||
from gi.repository import Gtk, Gdk, GLib
|
||||
|
||||
# Init notify
|
||||
Notify.init("DemonEditor")
|
||||
# Setting mod mask for the keyboard depending on the platform.
|
||||
# Setting mod mask for keyboard depending on platform
|
||||
MOD_MASK = Gdk.ModifierType.MOD2_MASK if IS_DARWIN else Gdk.ModifierType.CONTROL_MASK
|
||||
# Path to *.glade files.
|
||||
UI_RESOURCES_PATH = "app/ui/" if os.path.exists("app/ui/") else "/usr/share/demoneditor/app/ui/"
|
||||
# Path to *.glade files
|
||||
UI_RESOURCES_PATH = "app/ui/" if os.path.exists("app/ui/") else "ui/"
|
||||
LANG_PATH = UI_RESOURCES_PATH + "lang"
|
||||
GTK_PATH = os.environ.get("GTK_PATH", None)
|
||||
|
||||
IS_GNOME_SESSION = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
|
||||
# Translation.
|
||||
TEXT_DOMAIN = "demon-editor"
|
||||
@@ -27,35 +26,56 @@ except SettingsException:
|
||||
pass
|
||||
else:
|
||||
os.environ["LANGUAGE"] = settings.language
|
||||
if UI_RESOURCES_PATH == "app/ui/":
|
||||
locale.bindtextdomain(TEXT_DOMAIN, UI_RESOURCES_PATH + "lang")
|
||||
|
||||
if settings.is_themes_support:
|
||||
st = Gtk.Settings().get_default()
|
||||
st.set_property("gtk-theme-name", settings.theme)
|
||||
st.set_property("gtk-icon-theme-name", settings.icon_theme)
|
||||
else:
|
||||
style_provider = Gtk.CssProvider()
|
||||
s_path = "{}default_style.css".format(GTK_PATH + "/" + UI_RESOURCES_PATH if GTK_PATH else UI_RESOURCES_PATH)
|
||||
style_provider.load_from_path(s_path)
|
||||
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||
|
||||
if IS_DARWIN:
|
||||
import gettext
|
||||
|
||||
if GTK_PATH:
|
||||
LANG_PATH = GTK_PATH + "/share/locale"
|
||||
gettext.bindtextdomain(TEXT_DOMAIN, LANG_PATH)
|
||||
# For launching from the bundle.
|
||||
if os.getcwd() == "/" and GTK_PATH:
|
||||
os.chdir(GTK_PATH)
|
||||
else:
|
||||
import locale
|
||||
|
||||
locale.bindtextdomain(TEXT_DOMAIN, LANG_PATH)
|
||||
|
||||
theme = Gtk.IconTheme.get_default()
|
||||
theme.append_search_path(UI_RESOURCES_PATH + "icons")
|
||||
theme.append_search_path(GTK_PATH + "/share/icons" if GTK_PATH else UI_RESOURCES_PATH + "icons")
|
||||
|
||||
_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None
|
||||
CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(
|
||||
"emblem-readonly", 16, 0) else _IMAGE_MISSING
|
||||
LOCKED_ICON = theme.load_icon("changes-prevent-symbolic", 16, 0) if theme.lookup_icon(
|
||||
"system-lock-screen", 16, 0) else _IMAGE_MISSING
|
||||
HIDE_ICON = theme.load_icon("go-jump", 16, 0) if theme.lookup_icon("go-jump", 16, 0) else _IMAGE_MISSING
|
||||
TV_ICON = theme.load_icon("tv-symbolic", 16, 0) if theme.lookup_icon("tv-symbolic", 16, 0) else _IMAGE_MISSING
|
||||
IPTV_ICON = theme.load_icon("emblem-shared", 16, 0) if theme.lookup_icon("emblem-shared", 16, 0) else None
|
||||
EPG_ICON = theme.load_icon("gtk-index", 16, 0) if theme.lookup_icon("gtk-index", 16, 0) else None
|
||||
DEFAULT_ICON = theme.load_icon("emblem-default", 16, 0) if theme.lookup_icon("emblem-default", 16, 0) else None
|
||||
|
||||
def get_theme_icon(icon_theme, name, size):
|
||||
try:
|
||||
return icon_theme.load_icon(name, size, 0)
|
||||
except GLib.Error:
|
||||
pass
|
||||
|
||||
|
||||
_IMAGE_MISSING = get_theme_icon(theme, "image-missing", 16)
|
||||
CODED_ICON = get_theme_icon(theme, "emblem-readonly", 16) or _IMAGE_MISSING
|
||||
LOCKED_ICON = get_theme_icon(theme, "changes-prevent-symbolic", 16) or _IMAGE_MISSING
|
||||
HIDE_ICON = get_theme_icon(theme, "go-jump", 16) or _IMAGE_MISSING
|
||||
TV_ICON = get_theme_icon(theme, "tv-symbolic", 16) or _IMAGE_MISSING
|
||||
IPTV_ICON = get_theme_icon(theme, "emblem-shared", 16)
|
||||
EPG_ICON = get_theme_icon(theme, "gtk-index", 16)
|
||||
DEFAULT_ICON = get_theme_icon(theme, "emblem-default", 16)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_yt_icon(icon_name, size=24):
|
||||
""" Getting YouTube icon.
|
||||
|
||||
If the icon is not found in the icon themes, the "Info" icon is returned by default!
|
||||
"""
|
||||
""" Getting YouTube icon. If the icon is not found in the icon themes, the "APPLY" icon is returned by default! """
|
||||
default_theme = Gtk.IconTheme.get_default()
|
||||
if default_theme.has_icon(icon_name):
|
||||
return default_theme.load_icon(icon_name, size, 0)
|
||||
@@ -64,54 +84,44 @@ def get_yt_icon(icon_name, size=24):
|
||||
import glob
|
||||
|
||||
for theme_name in map(os.path.basename, filter(os.path.isdir, glob.glob("/usr/share/icons/*"))):
|
||||
n_theme.set_custom_theme(theme_name)
|
||||
theme.set_custom_theme(theme_name)
|
||||
if n_theme.has_icon(icon_name):
|
||||
return n_theme.load_icon(icon_name, size, 0)
|
||||
|
||||
return default_theme.load_icon("info", size, 0)
|
||||
|
||||
|
||||
def show_notification(message, timeout=10000, urgency=1):
|
||||
""" Shows notification.
|
||||
|
||||
@param message: text to display
|
||||
@param timeout: milliseconds
|
||||
@param urgency: 0 - low, 1 - normal, 2 - critical
|
||||
"""
|
||||
notify = Notify.Notification.new("DemonEditor", message, "demon-editor")
|
||||
notify.set_urgency(urgency)
|
||||
notify.set_timeout(timeout)
|
||||
notify.show()
|
||||
if default_theme.lookup_icon(Gtk.STOCK_APPLY, size, 0):
|
||||
return default_theme.load_icon(Gtk.STOCK_APPLY, size, 0)
|
||||
|
||||
|
||||
class KeyboardKey(Enum):
|
||||
""" The raw(hardware) codes of the keyboard keys. """
|
||||
E = 26
|
||||
R = 27
|
||||
T = 28
|
||||
P = 33
|
||||
S = 39
|
||||
F = 41
|
||||
X = 53
|
||||
C = 54
|
||||
V = 55
|
||||
W = 25
|
||||
Z = 52
|
||||
INSERT = 118
|
||||
HOME = 110
|
||||
END = 115
|
||||
UP = 111
|
||||
DOWN = 116
|
||||
PAGE_UP = 112
|
||||
PAGE_DOWN = 117
|
||||
LEFT = 113
|
||||
RIGHT = 114
|
||||
F2 = 68
|
||||
SPACE = 65
|
||||
DELETE = 119
|
||||
BACK_SPACE = 22
|
||||
CTRL_L = 37
|
||||
CTRL_R = 105
|
||||
F = 3 if IS_DARWIN else 41
|
||||
E = 14 if IS_DARWIN else 26
|
||||
R = 15 if IS_DARWIN else 27
|
||||
T = 17 if IS_DARWIN else 28
|
||||
P = 35 if IS_DARWIN else 33
|
||||
S = 1 if IS_DARWIN else 39
|
||||
H = 4 if IS_DARWIN else 43
|
||||
L = 37 if IS_DARWIN else 46
|
||||
X = 7 if IS_DARWIN else 53
|
||||
C = 8 if IS_DARWIN else 54
|
||||
V = 9 if IS_DARWIN else 55
|
||||
W = 13 if IS_DARWIN else 25
|
||||
Z = 6 if IS_DARWIN else 52
|
||||
INSERT = -1 if IS_DARWIN else 118
|
||||
HOME = -1 if IS_DARWIN else 110
|
||||
END = -1 if IS_DARWIN else 115
|
||||
UP = 126 if IS_DARWIN else 111
|
||||
DOWN = 125 if IS_DARWIN else 116
|
||||
PAGE_UP = -1 if IS_DARWIN else 112
|
||||
PAGE_DOWN = -1 if IS_DARWIN else 117
|
||||
LEFT = 123 if IS_DARWIN else 113
|
||||
RIGHT = 123 if IS_DARWIN else 114
|
||||
F2 = 120 if IS_DARWIN else 68
|
||||
SPACE = 49 if IS_DARWIN else 65
|
||||
DELETE = 51 if IS_DARWIN else 119
|
||||
BACK_SPACE = 76 if IS_DARWIN else 22
|
||||
CTRL_L = 55 if IS_DARWIN else 37
|
||||
CTRL_R = 55 if IS_DARWIN else 105
|
||||
# Laptop codes
|
||||
HOME_KP = 79
|
||||
END_KP = 87
|
||||
|
||||
18
build-deb.sh
18
build-deb.sh
@@ -1,18 +0,0 @@
|
||||
#!/bin/bash
|
||||
VER="1.0.2_Beta"
|
||||
B_PATH="dist/DemonEditor"
|
||||
DEB_PATH="$B_PATH/usr/share/demoneditor"
|
||||
|
||||
mkdir -p $B_PATH
|
||||
cp -TRv deb $B_PATH
|
||||
rsync --exclude=app/ui/lang --exclude=app/ui/icons -arv app $DEB_PATH
|
||||
|
||||
cd dist
|
||||
fakeroot dpkg-deb --build DemonEditor
|
||||
mv DemonEditor.deb DemonEditor_$VER.deb
|
||||
|
||||
rm -R DemonEditor
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
demon-editor for Debian
|
||||
----------------------
|
||||
DemonEditor
|
||||
Enigma2 channel and satellites list editor for GNU/Linux.
|
||||
|
||||
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
|
||||
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc).
|
||||
|
||||
Main features of the program:
|
||||
Editing bouquets, channels, satellites.
|
||||
Import function.
|
||||
Backup function.
|
||||
Extended support of IPTV.
|
||||
Support of picons.
|
||||
Downloading of picons and updating of satellites (transponders) from the web.
|
||||
Import to bouquet(Neutrino WEBTV) from m3u.
|
||||
Export of bouquets with IPTV services in m3u.
|
||||
Assignment of EPGs from DVB or XML for IPTV services (only Enigma2, experimental).
|
||||
Preview (playback) of IPTV or other streams directly from the bouquet list (should be installed VLC).
|
||||
|
||||
Keyboard shortcuts:
|
||||
Ctrl + Insert - copies the selected channels from the main list to the the bouquet beginning or inserts (creates) a new bouquet.
|
||||
Ctrl + BackSpace - copies the selected channels from the main list to the bouquet end.
|
||||
Ctrl + X - only in bouquet list. Ctrl + C - only in services list.
|
||||
Clipboard is "rubber". There is an accumulation before the insertion!
|
||||
Ctrl + E - edit.
|
||||
Ctrl + R, F2 - rename.
|
||||
Ctrl + S, T in Satellites edit tool for create satellite or transponder.
|
||||
Ctrl + L - parental lock.
|
||||
Ctrl + H - hide/skip.
|
||||
Ctrl + P - start play IPTV or other stream in the bouquet list.
|
||||
Ctrl + Z - switch (zap) the channel (works when the HTTP API is enabled, Enigma2 only).
|
||||
Ctrl + W - switch to the channel and watch in the program.
|
||||
Space - select/deselect.
|
||||
Left/Right - remove selection.
|
||||
Ctrl + Up, Down, PageUp, PageDown, Home, End - move selected items in the list.
|
||||
Ctrl + O - (re)load user data from current dir.
|
||||
Ctrl + D - load data from receiver.
|
||||
Ctrl + U/B upload data/bouquets to receiver.
|
||||
Ctrl + F - show/hide search bar.
|
||||
Ctrl + Shift + F - show/hide filter bar.
|
||||
|
||||
For multiple selection with the mouse, press and hold the Ctrl key!
|
||||
|
||||
Minimum requirements:
|
||||
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings, python3-requests.
|
||||
|
||||
Important:
|
||||
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2!
|
||||
Main supported *lamedb* format is version **4**. Versions **3** and **5** has only **experimental** support!
|
||||
For version **3** is only read mode available. When saving, version **4** format is used instead!
|
||||
|
||||
When using the multiple import feature, from *lamedb* will be taken data **only for channels that are in the
|
||||
selected bouquets!** If you need full set of the data, including *[satellites, terrestrial, cables].xml* (current files will be overwritten),
|
||||
just load your data via *"File/Open"* and press *"Save"*. When importing separate bouquet files, only those services
|
||||
(excluding IPTV) that are in the **current open lamedb** (main list of services) will be imported.
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
Package: demon-editor
|
||||
Version: 1.0.2-Beta
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Essential: no
|
||||
Depends: python3 (>= 3.5)
|
||||
Maintainer: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
|
||||
Description: Enigma2 channel and satellites list editor
|
||||
@@ -1,26 +0,0 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Contact: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
|
||||
Source: https://github.com/DYefremov/DemonEditor
|
||||
|
||||
Files: *
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2020 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1 +0,0 @@
|
||||
README.source
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
python3 /usr/share/demoneditor/start.py $1
|
||||
@@ -1,13 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Name=DemonEditor
|
||||
Comment=Channel and satellite list editor for Enigma2
|
||||
Comment[ru]=Редактор списка каналов и спутников для Enigma2
|
||||
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
|
||||
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
|
||||
Icon=demon-editor
|
||||
Exec=/usr/bin/demon-editor
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;Application;
|
||||
StartupNotify=false
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
from app.ui.main_app_window import start_app
|
||||
|
||||
start_app()
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,634 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg1541"
|
||||
version="1.1"
|
||||
viewBox="0 0 16.743339 16.72816"
|
||||
height="63.224556"
|
||||
width="63.281971"
|
||||
sodipodi:docname="demon-editor.svg"
|
||||
inkscape:version="0.92.4 (unknown)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="716"
|
||||
id="namedview127"
|
||||
showgrid="true"
|
||||
fit-margin-left="0"
|
||||
inkscape:zoom="6.1714295"
|
||||
inkscape:cx="40.088627"
|
||||
inkscape:cy="31.742631"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1541"
|
||||
fit-margin-top="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid128"
|
||||
originx="-10.604603"
|
||||
originy="-1.1727724" />
|
||||
</sodipodi:namedview>
|
||||
<title
|
||||
id="title1088">DeamonEditor Icons</title>
|
||||
<defs
|
||||
id="defs1535">
|
||||
<linearGradient
|
||||
id="linearGradient2198">
|
||||
<stop
|
||||
id="stop2194"
|
||||
style="stop-color:#ffb320;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop2196"
|
||||
style="stop-color:#e7ff00;stop-opacity:1"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2192">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#ffb320;stop-opacity:1"
|
||||
id="stop2188" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#b3c54c;stop-opacity:1"
|
||||
id="stop2190" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3700-8">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#2e4f84;stop-opacity:1"
|
||||
id="stop2183" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#4c77c5;stop-opacity:1"
|
||||
id="stop2185" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1"
|
||||
y2="0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
|
||||
spreadMethod="pad"
|
||||
id="linearGradient1844">
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#29282b"
|
||||
offset="0"
|
||||
id="stop1826" />
|
||||
<stop
|
||||
id="stop1828"
|
||||
offset="0.13293758"
|
||||
style="stop-color:#b5bdcf;stop-opacity:1" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#92979f"
|
||||
offset="0.21261224"
|
||||
id="stop1832" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#737881"
|
||||
offset="0.29780689"
|
||||
id="stop1834" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#70757e"
|
||||
offset="0.29780689"
|
||||
id="stop1836" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#e4e6e8"
|
||||
offset="0.45395693"
|
||||
id="stop1838" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#696c76"
|
||||
offset="0.71871042"
|
||||
id="stop1840" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#29282b"
|
||||
offset="1"
|
||||
id="stop1842" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient1754"
|
||||
spreadMethod="pad"
|
||||
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
x1="0">
|
||||
<stop
|
||||
id="stop1736"
|
||||
offset="0"
|
||||
style="stop-opacity:1;stop-color:#29282b" />
|
||||
<stop
|
||||
style="stop-color:#b5bdcf;stop-opacity:1"
|
||||
offset="0.02582242"
|
||||
id="stop1738" />
|
||||
<stop
|
||||
id="stop1740"
|
||||
offset="0.14669065"
|
||||
style="stop-opacity:1;stop-color:#868c95" />
|
||||
<stop
|
||||
id="stop1742"
|
||||
offset="0.21261224"
|
||||
style="stop-opacity:1;stop-color:#92979f" />
|
||||
<stop
|
||||
id="stop1744"
|
||||
offset="0.29780689"
|
||||
style="stop-opacity:1;stop-color:#737881" />
|
||||
<stop
|
||||
id="stop1746"
|
||||
offset="0.29780689"
|
||||
style="stop-opacity:1;stop-color:#70757e" />
|
||||
<stop
|
||||
id="stop1748"
|
||||
offset="0.45395693"
|
||||
style="stop-opacity:1;stop-color:#e4e6e8" />
|
||||
<stop
|
||||
id="stop1750"
|
||||
offset="0.71871042"
|
||||
style="stop-opacity:1;stop-color:#ffffff" />
|
||||
<stop
|
||||
id="stop1752"
|
||||
offset="1"
|
||||
style="stop-opacity:1;stop-color:#1d191a" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1"
|
||||
y2="0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
|
||||
spreadMethod="pad"
|
||||
id="linearGradient1606">
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#29282b"
|
||||
offset="0"
|
||||
id="stop1590" />
|
||||
<stop
|
||||
id="stop1608"
|
||||
offset="0.03065561"
|
||||
style="stop-color:#b5bdcf;stop-opacity:1" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#868c95"
|
||||
offset="0.1125849"
|
||||
id="stop1592" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#92979f"
|
||||
offset="0.13955873"
|
||||
id="stop1594" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#737881"
|
||||
offset="0.1915853"
|
||||
id="stop1596" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#70757e"
|
||||
offset="0.25400829"
|
||||
id="stop1598" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#e4e6e8"
|
||||
offset="0.45395693"
|
||||
id="stop1600" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#ffffff"
|
||||
offset="0.71871042"
|
||||
id="stop1602" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#1d191a"
|
||||
offset="1"
|
||||
id="stop1604" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient886-0">
|
||||
<stop
|
||||
style="stop-color:#535353;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop888" />
|
||||
<stop
|
||||
style="stop-color:#f0f0f0;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop890" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient1473">
|
||||
<stop
|
||||
id="stop1469"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:0" />
|
||||
<stop
|
||||
id="stop1471"
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0.96088022" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2447-35">
|
||||
<stop
|
||||
id="stop2449"
|
||||
offset="0"
|
||||
style="stop-color:#00c62e;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop2451"
|
||||
offset="1"
|
||||
style="stop-color:#136100;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3795-1">
|
||||
<stop
|
||||
id="stop3797-1"
|
||||
offset="0"
|
||||
style="stop-color:#803400;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop3799-3"
|
||||
offset="1"
|
||||
style="stop-color:#c87137;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2447-3">
|
||||
<stop
|
||||
style="stop-color:#c60300;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop2443" />
|
||||
<stop
|
||||
style="stop-color:#c40e00;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop2445" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2458">
|
||||
<stop
|
||||
id="stop2454"
|
||||
offset="0"
|
||||
style="stop-color:#c60300;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop2456"
|
||||
offset="1"
|
||||
style="stop-color:#ee6000;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3519">
|
||||
<stop
|
||||
id="stop3521"
|
||||
offset="0"
|
||||
style="stop-color:#1d2120;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop3523"
|
||||
offset="1"
|
||||
style="stop-color:#545d5d;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient1446">
|
||||
<stop
|
||||
id="stop1442"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop1444"
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient1547"
|
||||
spreadMethod="pad"
|
||||
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
x1="0">
|
||||
<stop
|
||||
id="stop1529"
|
||||
offset="0"
|
||||
style="stop-opacity:1;stop-color:#ffffff" />
|
||||
<stop
|
||||
id="stop1531"
|
||||
offset="0.0263736"
|
||||
style="stop-opacity:1;stop-color:#29282b" />
|
||||
<stop
|
||||
id="stop1533"
|
||||
offset="0.263736"
|
||||
style="stop-opacity:1;stop-color:#868c95" />
|
||||
<stop
|
||||
id="stop1535"
|
||||
offset="0.395604"
|
||||
style="stop-opacity:1;stop-color:#92979f" />
|
||||
<stop
|
||||
id="stop1537"
|
||||
offset="0.39560401"
|
||||
style="stop-opacity:1;stop-color:#737881" />
|
||||
<stop
|
||||
id="stop1539"
|
||||
offset="0.42333773"
|
||||
style="stop-opacity:1;stop-color:#70757e" />
|
||||
<stop
|
||||
id="stop1541"
|
||||
offset="0.56268591"
|
||||
style="stop-opacity:1;stop-color:#e4e6e8" />
|
||||
<stop
|
||||
id="stop1543"
|
||||
offset="0.62400264"
|
||||
style="stop-opacity:1;stop-color:#ffffff" />
|
||||
<stop
|
||||
id="stop1545"
|
||||
offset="1"
|
||||
style="stop-opacity:1;stop-color:#1d191a" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient1527"
|
||||
spreadMethod="pad"
|
||||
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
x1="0">
|
||||
<stop
|
||||
id="stop1509"
|
||||
offset="0"
|
||||
style="stop-opacity:1;stop-color:#ffffff" />
|
||||
<stop
|
||||
id="stop1511"
|
||||
offset="0.0527472"
|
||||
style="stop-opacity:1;stop-color:#29282b" />
|
||||
<stop
|
||||
id="stop1513"
|
||||
offset="0.142147"
|
||||
style="stop-opacity:1;stop-color:#868c95" />
|
||||
<stop
|
||||
id="stop1515"
|
||||
offset="0.19700864"
|
||||
style="stop-opacity:1;stop-color:#92979f" />
|
||||
<stop
|
||||
id="stop1517"
|
||||
offset="0.25031137"
|
||||
style="stop-opacity:1;stop-color:#737881" />
|
||||
<stop
|
||||
id="stop1519"
|
||||
offset="0.3710371"
|
||||
style="stop-opacity:1;stop-color:#70757e" />
|
||||
<stop
|
||||
id="stop1521"
|
||||
offset="0.53961843"
|
||||
style="stop-opacity:1;stop-color:#e4e6e8" />
|
||||
<stop
|
||||
id="stop1523"
|
||||
offset="0.76283824"
|
||||
style="stop-opacity:1;stop-color:#ffffff" />
|
||||
<stop
|
||||
id="stop1525"
|
||||
offset="1"
|
||||
style="stop-opacity:1;stop-color:#1d191a" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0.26458333,0,0,0.26458333,-5.8254634,-78.732754)"
|
||||
y2="1179.7145"
|
||||
x2="66.791626"
|
||||
y1="1188.7661"
|
||||
x1="99.044022"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient1485"
|
||||
xlink:href="#linearGradient1473" />
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0.26458333,0,0,0.26458333,-2.2733744,-70.526334)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="1155.8046"
|
||||
x2="67.311417"
|
||||
y1="1161.6112"
|
||||
x1="77.19442"
|
||||
id="linearGradient1448"
|
||||
xlink:href="#linearGradient1446" />
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0.26458333,0,0,0.26458333,-18.198747,-79.859424)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="1186.8096"
|
||||
x2="146.16808"
|
||||
y1="1186.8096"
|
||||
x1="131.86871"
|
||||
id="linearGradient2180"
|
||||
xlink:href="#linearGradient886-0" />
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0.20863982,0,0,0.20863982,-0.63935937,-13.031239)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="1184.73"
|
||||
x2="101.19952"
|
||||
y1="1184.73"
|
||||
x1="83.066002"
|
||||
id="linearGradient2160"
|
||||
xlink:href="#linearGradient886-0" />
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0.26458333,0,0,0.26458333,-6.2370714,-102.12414)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="1267.1335"
|
||||
x2="117.99127"
|
||||
y1="1267.1335"
|
||||
x1="75.853806"
|
||||
id="linearGradient2131"
|
||||
xlink:href="#linearGradient886-0" />
|
||||
<linearGradient
|
||||
id="linearGradient3700-8-3">
|
||||
<stop
|
||||
id="stop3702-1"
|
||||
style="stop-color:#2e4f84;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop3704-8"
|
||||
style="stop-color:#4c77c5;stop-opacity:1"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<defs
|
||||
id="defs4922">
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="Adobe_OpacityMaskFilter"
|
||||
filterUnits="userSpaceOnUse"
|
||||
x="3.7850001"
|
||||
y="4.6750002"
|
||||
width="5.8829999"
|
||||
height="73.013">
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
|
||||
id="feColorMatrix4925" />
|
||||
</filter>
|
||||
</defs>
|
||||
<mask
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="3.785"
|
||||
y="4.675"
|
||||
width="5.883"
|
||||
height="73.013"
|
||||
id="SVGID_2_">
|
||||
<g
|
||||
style="filter:url(#Adobe_OpacityMaskFilter)"
|
||||
id="g4928">
|
||||
<linearGradient
|
||||
id="SVGID_3_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="3.7852001"
|
||||
y1="41.181198"
|
||||
x2="9.6680002"
|
||||
y2="41.181198">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop4931" />
|
||||
<stop
|
||||
offset="0.0029"
|
||||
style="stop-color:#FAFBFB"
|
||||
id="stop4933" />
|
||||
<stop
|
||||
offset="0.0756"
|
||||
style="stop-color:#BBBDBF"
|
||||
id="stop4935" />
|
||||
<stop
|
||||
offset="0.1438"
|
||||
style="stop-color:#898B8E"
|
||||
id="stop4937" />
|
||||
<stop
|
||||
offset="0.2053"
|
||||
style="stop-color:#646567"
|
||||
id="stop4939" />
|
||||
<stop
|
||||
offset="0.259"
|
||||
style="stop-color:#444446"
|
||||
id="stop4941" />
|
||||
<stop
|
||||
offset="0.3028"
|
||||
style="stop-color:#1D1C1D"
|
||||
id="stop4943" />
|
||||
<stop
|
||||
offset="0.3313"
|
||||
style="stop-color:#000000"
|
||||
id="stop4945" />
|
||||
</linearGradient>
|
||||
<rect
|
||||
style="fill:url(#SVGID_3_)"
|
||||
x="3.7850001"
|
||||
y="4.6750002"
|
||||
width="5.8829999"
|
||||
height="73.013"
|
||||
id="rect4947" />
|
||||
</g>
|
||||
</mask>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter1268"
|
||||
filterUnits="userSpaceOnUse"
|
||||
x="3.7850001"
|
||||
y="4.6750002"
|
||||
width="5.8829999"
|
||||
height="73.013">
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
|
||||
id="feColorMatrix1266" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="203.22046"
|
||||
x2="23.551136"
|
||||
y1="203.22046"
|
||||
x1="13.487289"
|
||||
id="linearGradient1160"
|
||||
xlink:href="#linearGradient3519" />
|
||||
<clipPath
|
||||
id="clipPath1890"
|
||||
clipPathUnits="userSpaceOnUse">
|
||||
<circle
|
||||
style="opacity:1;fill:#2d2d2d;fill-opacity:1;stroke:#434242;stroke-width:0.0575568;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="circle1892"
|
||||
cx="78.548424"
|
||||
cy="-31.019459"
|
||||
r="6.4721422"
|
||||
transform="scale(1,-1)" />
|
||||
</clipPath>
|
||||
<linearGradient
|
||||
gradientTransform="translate(-0.24389927,14.877856)"
|
||||
y2="27.314217"
|
||||
x2="84.864914"
|
||||
y1="27.314217"
|
||||
x1="72.164909"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient2134"
|
||||
xlink:href="#linearGradient3700-8-3" />
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata1538">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>DeamonEditor Icons</dc:title>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>mfgeg</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:date>7.1.2020</dc:date>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="matrix(1.1690805,0,0,1.1690805,-14.929261,-167.45253)"
|
||||
id="layer1">
|
||||
<g
|
||||
transform="translate(0.86526724,-82.691658)"
|
||||
id="g1351">
|
||||
<path
|
||||
d="m 13.715358,227.65981 h 2.97616 v 12.57332 h -2.97616 z m 5.79826,-1.73376 -0.327076,0.42227 c -0.179879,0.23226 -0.586303,0.67932 -0.903223,0.99412 -0.480677,0.47726 -0.640897,0.57235 -0.967777,0.57235 -0.223557,0 -0.380895,-0.0584 -0.624024,-0.25067 v 3.18307 c 0.172884,-0.0214 0.359647,-0.0333 0.575071,-0.0352 0.68403,-0.006 0.91815,0.0416 1.436333,0.29581 1.14586,0.56274 1.762492,1.5589 1.768251,2.8566 0.0094,2.10836 -1.870896,3.55161 -3.779655,3.16529 v 3.10345 c 4.345352,0.0758 4.093104,-2.37537 6.573781,-2.25837 1.486139,0.0748 1.333421,0.16555 1.796225,-1.064 l 0.259834,-0.6913 -0.803704,-0.66493 c -0.960855,-0.79478 -1.303983,-1.29079 -1.205013,-1.74132 0.06631,-0.30189 1.20818,-1.50093 1.779011,-1.86778 0.240181,-0.1543 0.244255,-0.17878 0.09575,-0.59661 -0.08522,-0.23979 -0.259522,-0.65746 -0.386789,-0.92744 l -0.23132,-0.49115 h -1.412666 c -1.868582,0 -1.802386,0.068 -1.805368,-1.82472 l -0.0021,-1.43582 -0.917745,-0.37171 z"
|
||||
style="opacity:1;fill:#000000;fill-opacity:0.5372549;stroke:none;stroke-width:0.18460207;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
|
||||
id="path2133"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path2106"
|
||||
style="opacity:1;fill:url(#linearGradient2131);fill-opacity:1;stroke:none;stroke-width:0.17733108;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
|
||||
d="m 13.832581,227.93117 h 2.858936 v 12.07807 h -2.858936 z m 5.569881,-1.6655 -0.314194,0.40566 c -0.172793,0.22309 -0.563209,0.65257 -0.867647,0.95496 -0.46174,0.45847 -0.615654,0.5498 -0.929658,0.5498 -0.214752,0 -0.365893,-0.056 -0.599446,-0.24079 v 3.05768 c 0.166077,-0.0206 0.345483,-0.0319 0.55242,-0.0338 0.657089,-0.006 0.881987,0.0399 1.379764,0.28416 1.100724,0.54057 1.693073,1.49747 1.698606,2.74408 0.009,2.02535 -1.797211,3.41174 -3.63079,3.04061 v 2.98122 c 4.174205,0.0728 3.931888,-2.28179 6.314859,-2.1694 1.427604,0.0718 1.2809,0.15902 1.725477,-1.02213 l 0.249597,-0.66408 -0.772046,-0.63871 c -0.923009,-0.76345 -1.252622,-1.23996 -1.157552,-1.67277 0.0637,-0.29001 1.160592,-1.44177 1.708939,-1.79419 0.230721,-0.14825 0.234635,-0.17174 0.09198,-0.57312 -0.08187,-0.23032 -0.249296,-0.63156 -0.371555,-0.89088 l -0.222207,-0.4718 h -1.357022 c -1.794983,0 -1.731396,0.0653 -1.734262,-1.75284 l -0.0021,-1.3793 -0.881603,-0.35705 z"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path2151"
|
||||
d="m 17.266983,230.95755 c -0.215637,0.003 -0.402408,0.014 -0.575465,0.0354 v 6.28854 c 1.910653,0.38671 3.792716,-1.05815 3.783336,-3.16865 -0.0058,-1.29897 -0.623064,-2.29597 -1.770059,-2.85927 -0.518699,-0.25451 -0.753103,-0.30234 -1.437812,-0.29607 z m 0.482341,1.17002 c 0.433747,-0.004 0.582202,0.0265 0.910784,0.18761 0.726595,0.35682 1.117604,0.98848 1.121256,1.81134 0.0059,1.33694 -1.186343,2.25211 -2.396695,2.00715 v -3.98359 c 0.109628,-0.0135 0.228054,-0.0214 0.364655,-0.0225 z"
|
||||
style="opacity:1;fill:url(#linearGradient2160);fill-opacity:1;stroke:none;stroke-width:0.18478528;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient2180);stroke-width:0.18478528;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
|
||||
d="m 17.266981,230.95753 c -0.215636,0.003 -0.402408,0.014 -0.575464,0.0354 v 6.28853 c 1.910652,0.38672 3.792715,-1.05815 3.783336,-3.16865 -0.0057,-1.29897 -0.623065,-2.29597 -1.77006,-2.85927 -0.518697,-0.2545 -0.753103,-0.30234 -1.437812,-0.29607 z m 0.482343,1.17001 c 0.433747,-0.004 0.5822,0.0265 0.910783,0.18762 0.726594,0.35681 1.117605,0.98848 1.121257,1.81133 0.006,1.33694 -1.186344,2.25211 -2.396697,2.00716 v -3.98359 c 0.109628,-0.0135 0.228055,-0.0214 0.364657,-0.0225 z"
|
||||
id="path2170"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path1422"
|
||||
d="m 17.266983,230.95755 c -0.215637,0.003 -0.402408,0.014 -0.575464,0.0354 v 6.28854 c 1.910652,0.38671 3.792715,-1.05815 3.783335,-3.16865 -0.0057,-1.29897 -0.623064,-2.29597 -1.770059,-2.85927 -0.518699,-0.25451 -0.753103,-0.30234 -1.437812,-0.29607 z m 0.482342,1.17002 c 0.433748,-0.004 0.582201,0.0265 0.910784,0.18761 0.726594,0.35682 1.117605,0.98848 1.121257,1.81134 0.006,1.33694 -1.186344,2.25211 -2.396697,2.00715 v -3.98359 c 0.109628,-0.0135 0.228054,-0.0214 0.364656,-0.0225 z"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient1448);stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path1454"
|
||||
d="m 19.402462,226.26149 -0.314194,0.40566 c -0.172793,0.22309 -0.563209,0.65259 -0.867644,0.95499 -0.461741,0.45847 -0.615657,0.54983 -0.929661,0.54983 -0.214752,0 -0.365893,-0.056 -0.599446,-0.2408 v -0.004 h -2.858741 v 12.0783 h 2.858741 c 4.174205,0.0728 3.931888,-2.28177 6.314861,-2.16937 1.427604,0.0718 1.280898,0.15899 1.725475,-1.02217 l 0.249597,-0.66402 -0.772046,-0.63873 c -0.923009,-0.76346 -1.252622,-1.23997 -1.157552,-1.67278 0.0637,-0.29001 1.160595,-1.44176 1.708939,-1.79419 0.230721,-0.14825 0.234635,-0.17171 0.09198,-0.57309 -0.08187,-0.23032 -0.249296,-0.63158 -0.371555,-0.8909 l -0.222207,-0.47181 h -1.357025 c -1.794983,0 -1.731393,0.0653 -1.734259,-1.75286 l -0.0021,-1.37925 -0.8816,-0.35708 z"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient1485);stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
transform="translate(63.49728,-55.136805)"
|
||||
id="g1174" />
|
||||
<g
|
||||
transform="translate(50.48327,11.624037)"
|
||||
id="g1812" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 22 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
dist/DemonEditor.app.zip
vendored
Normal file
BIN
dist/DemonEditor.app.zip
vendored
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "Charly\nDmitriy Yefremov"
|
||||
msgstr "Charly, Dmitriy Yefremov"
|
||||
|
||||
# Main
|
||||
msgid "Service"
|
||||
@@ -105,9 +105,6 @@ msgstr "Standardbezeichnung setzen"
|
||||
msgid "Insert marker"
|
||||
msgstr "Marker einfügen"
|
||||
|
||||
msgid "Insert space"
|
||||
msgstr "Leerzeichen einfügen"
|
||||
|
||||
msgid "Locate in services"
|
||||
msgstr "In den Diensten suchen"
|
||||
|
||||
@@ -163,7 +160,7 @@ msgid "Remove"
|
||||
msgstr "Entfernen"
|
||||
|
||||
msgid "Remove all unavailable"
|
||||
msgstr "Entfernen alle nicht verfügbaren"
|
||||
msgstr "Entfernt alle nicht verfügbaren"
|
||||
|
||||
msgid "Satellites editor"
|
||||
msgstr "Satelliten-Editor"
|
||||
@@ -204,8 +201,8 @@ msgstr "Aktueller Datenpfad:"
|
||||
msgid "Data:"
|
||||
msgstr "Daten:"
|
||||
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "Enigma2 Kanal- und Satellitenlisteneditor für GNU/Linux."
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Enigma2 Kanal- und Satellitenlisteneditor für GNU/Linux"
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Host:"
|
||||
@@ -519,9 +516,6 @@ msgstr "Bitte wählen Sie nur einen Eintrag aus!"
|
||||
msgid "No png file is selected!"
|
||||
msgstr "Es ist keine png-Datei ausgewählt!"
|
||||
|
||||
msgid "No profile selected!"
|
||||
msgstr "Kein Profil ausgewählt!"
|
||||
|
||||
msgid "No reference is present!"
|
||||
msgstr "Es liegt keine Referenz vor!"
|
||||
|
||||
@@ -677,11 +671,11 @@ msgstr "Play Stream"
|
||||
msgid "Disabled"
|
||||
msgstr "Ausgeschaltet"
|
||||
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Lamedb ver. 5 Unterstützung aktivieren"
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Lamedb ver. 5 Unterstützung aktivieren (experimentell)"
|
||||
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "HTTP-API aktivieren"
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "HTTP-API aktivieren (experimentell)"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Umschalten des Kanals (Strg + Z)"
|
||||
@@ -801,8 +795,8 @@ msgstr "Sprache:"
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Laden der zuletzt geöffneten Konfiguration beim Programmstart"
|
||||
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Aktivieren der direkten Wiedergabeleiste"
|
||||
msgid "Enable direct playback bar (experimental)"
|
||||
msgstr "Aktivieren der direkten Wiedergabeleiste (experimentell)"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Ermöglicht das direkte Senden und Abspielen von Medienlinks auf dem Box"
|
||||
@@ -993,182 +987,3 @@ msgstr "Alle Picons aus dem Receiver entfernen"
|
||||
msgid "Service reference"
|
||||
msgstr "Kanalreferenz"
|
||||
|
||||
msgid "Enable support for"
|
||||
msgstr "Unterstützung aktivieren für"
|
||||
|
||||
msgid "Auto-check for updates"
|
||||
msgstr "Automatische Prüfung auf Updates"
|
||||
|
||||
msgid "Filter services"
|
||||
msgstr "Dienste filtern"
|
||||
|
||||
msgid "Filter services in the main list."
|
||||
msgstr "Dienste in der Hauptliste filtern."
|
||||
|
||||
msgid "Destination:"
|
||||
msgstr "Ziel:"
|
||||
|
||||
msgid "EXPERIMENTAL!"
|
||||
msgstr "EXPERIMENTELL!"
|
||||
|
||||
msgid "Sorting data..."
|
||||
msgstr "Daten sortieren..."
|
||||
|
||||
msgid "There are unsaved changes.\n\n\t Save them now?"
|
||||
msgstr "Es gibt ungespeicherte Änderungen.\n\n\t Möchtest du jetzt speichern?"
|
||||
|
||||
msgid "Are you sure you want to change the order\n\t of services in this bouquet?"
|
||||
msgstr "Bist du sicher, dass du die Reihenfolge der Dienstleistungen\n\t in diesem Bouquet ändern willst?"
|
||||
|
||||
msgid "Remove from the receiver"
|
||||
msgstr "Aus dem Receiver entfernen"
|
||||
|
||||
msgid "Screenshot"
|
||||
msgstr "Screenshot"
|
||||
|
||||
msgid "Video"
|
||||
msgstr "Video"
|
||||
|
||||
msgid "The Neutrino has only experimental support. Not all features are supported!"
|
||||
msgstr "Die Neutrino hat nur experimentelle Unterstützung. Nicht alle Funktionen werden unterstützt!"
|
||||
|
||||
msgid "Enable experimental features"
|
||||
msgstr "Experimentelle Funktionen aktivieren"
|
||||
|
||||
msgid "Can't Playback!"
|
||||
msgstr "Kann nicht abgespielt werden!"
|
||||
|
||||
msgid "Enable Dark Mode"
|
||||
msgstr "Dunkelmodus aktivieren"
|
||||
|
||||
msgid "Extract..."
|
||||
msgstr "Entpacken..."
|
||||
|
||||
msgid "Unsupported format!"
|
||||
msgstr "Nicht unterstütztes Format!"
|
||||
|
||||
msgid "Combine with the current data?"
|
||||
msgstr "Mit den aktuellen Daten kombinieren?"
|
||||
|
||||
msgid "Importing data done!"
|
||||
msgstr "Daten importieren erledigt!"
|
||||
|
||||
msgid "Current service"
|
||||
msgstr "Aktueller Service"
|
||||
|
||||
msgid "Open folder"
|
||||
msgstr "Ordner öffnen"
|
||||
|
||||
msgid "Open archive"
|
||||
msgstr "Archiv öffnen"
|
||||
|
||||
msgid "Import from Web"
|
||||
msgstr "Import aus dem Web"
|
||||
|
||||
msgid "Control"
|
||||
msgstr "Steuerung"
|
||||
|
||||
msgid "Timers"
|
||||
msgstr "Timers"
|
||||
|
||||
msgid "Timer"
|
||||
msgstr "Timer"
|
||||
|
||||
msgid "Add timer"
|
||||
msgstr "Timer hinzufügen"
|
||||
|
||||
msgid "Hr."
|
||||
msgstr "Std."
|
||||
|
||||
msgid "Min."
|
||||
msgstr "Min."
|
||||
|
||||
msgid "Power"
|
||||
msgstr "Power"
|
||||
|
||||
msgid "Standby"
|
||||
msgstr "Standby"
|
||||
|
||||
msgid "Wake Up"
|
||||
msgstr "Aufwachen"
|
||||
|
||||
msgid "Reboot"
|
||||
msgstr "Neustarten"
|
||||
|
||||
msgid "Restart GUI"
|
||||
msgstr "GUI neustarten"
|
||||
|
||||
msgid "Shutdown"
|
||||
msgstr "Ausschalten"
|
||||
|
||||
msgid "Shut down"
|
||||
msgstr "Ausschalten"
|
||||
|
||||
msgid "Do Nothing"
|
||||
msgstr "Nichts tun"
|
||||
|
||||
msgid "Auto"
|
||||
msgstr "Auto"
|
||||
|
||||
msgid "Grab screenshot"
|
||||
msgstr "Screenshot schnappen"
|
||||
|
||||
msgid "Enabled:"
|
||||
msgstr "Aktiviert:"
|
||||
|
||||
msgid "Name:"
|
||||
msgstr "Name:"
|
||||
|
||||
msgid "Description:"
|
||||
msgstr "Beschreibung:"
|
||||
|
||||
msgid "Service:"
|
||||
msgstr "Service"
|
||||
|
||||
msgid "Service reference:"
|
||||
msgstr "Kanalreferenz"
|
||||
|
||||
msgid "Event ID:"
|
||||
msgstr "Ereignis-ID:"
|
||||
|
||||
msgid "Begins:"
|
||||
msgstr "Beginnt:"
|
||||
|
||||
msgid "Ends:"
|
||||
msgstr "Endet:"
|
||||
|
||||
msgid "Repeated:"
|
||||
msgstr "Wiederhole:"
|
||||
|
||||
msgid "Action:"
|
||||
msgstr "Aktion:"
|
||||
|
||||
msgid "After event:"
|
||||
msgstr "Nach dem Ereignis:"
|
||||
|
||||
msgid "Location:"
|
||||
msgstr "Zielverzeichnis:"
|
||||
|
||||
msgid "Mo"
|
||||
msgstr "Mo"
|
||||
|
||||
msgid "Tu"
|
||||
msgstr "Di"
|
||||
|
||||
msgid "We"
|
||||
msgstr "Mi"
|
||||
|
||||
msgid "Th"
|
||||
msgstr "Do"
|
||||
|
||||
msgid "Fr"
|
||||
msgstr "Fr"
|
||||
|
||||
msgid "Sa"
|
||||
msgstr "Sa"
|
||||
|
||||
msgid "Su"
|
||||
msgstr "So"
|
||||
|
||||
msgid "Set"
|
||||
msgstr "Einstellen"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright (C) 2018-2020 Frank Neirynck
|
||||
# Copyright (C) 2018-2019 Frank Neirynck
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
# Frank Neirynck <frank@insink.be>, 2018-2020.
|
||||
# Frank Neirynck <frank@insink.be>, 2018-2019.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -54,7 +54,7 @@ msgid "Current IP:"
|
||||
msgstr "IP actual:"
|
||||
|
||||
msgid "Assign"
|
||||
msgstr "Asignar"
|
||||
msgstr "Assignar"
|
||||
|
||||
msgid "Bouquet details"
|
||||
msgstr "Detalles bouquet"
|
||||
@@ -66,7 +66,7 @@ msgid "Copy"
|
||||
msgstr "Copiar"
|
||||
|
||||
msgid "Copy reference"
|
||||
msgstr "Copiar referencia"
|
||||
msgstr "Copia de referencia"
|
||||
|
||||
msgid "Download"
|
||||
msgstr "Descargar"
|
||||
@@ -123,7 +123,7 @@ msgid "New"
|
||||
msgstr "Nuevo"
|
||||
|
||||
msgid "New bouquet"
|
||||
msgstr "Nuevo bouquet"
|
||||
msgstr "Bouquet nuevo"
|
||||
|
||||
msgid "Create bouquet"
|
||||
msgstr "Crear bouquet"
|
||||
@@ -156,10 +156,10 @@ msgid "Picons"
|
||||
msgstr "Picons"
|
||||
|
||||
msgid "Picons downloader"
|
||||
msgstr "Descarga de picons"
|
||||
msgstr "Descargar picons"
|
||||
|
||||
msgid "Satellites downloader"
|
||||
msgstr "Descarga de satélites"
|
||||
msgstr "Descargar satélites"
|
||||
|
||||
msgid "Remove"
|
||||
msgstr "Quitar"
|
||||
@@ -206,11 +206,11 @@ msgstr "Ruta de datos actual:"
|
||||
msgid "Data:"
|
||||
msgstr "Datos:"
|
||||
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "Editor de canales y satélites Enigma2 para GNU/Linux."
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Editor de canales y satélites Enigma2 para GNU/Linux"
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Host:"
|
||||
msgstr "Anfitrión:"
|
||||
|
||||
msgid "Loading data..."
|
||||
msgstr "Cargando datos..."
|
||||
@@ -256,7 +256,7 @@ msgstr "Extra:"
|
||||
|
||||
# Filter bar
|
||||
msgid "Only free"
|
||||
msgstr "Solamente libres"
|
||||
msgstr "Solamente gratis"
|
||||
|
||||
msgid "All positions"
|
||||
msgstr "Todas las posiciones"
|
||||
@@ -272,10 +272,10 @@ msgid "Stop playback"
|
||||
msgstr "Detener la reproducción"
|
||||
|
||||
msgid "Previous stream in the list"
|
||||
msgstr "Anterior stream en la lista"
|
||||
msgstr "Anterior flujo en la lista"
|
||||
|
||||
msgid "Next stream in the list"
|
||||
msgstr "Siguiente stream en la lista"
|
||||
msgstr "Siguiente flujo en la lista"
|
||||
|
||||
msgid "Toggle in fullscreen"
|
||||
msgstr "Cambiar a pantalla completa"
|
||||
@@ -294,7 +294,7 @@ msgid "Receive picons"
|
||||
msgstr "Recibir picons"
|
||||
|
||||
msgid "Picons name format:"
|
||||
msgstr "Formato del nombre de los picons:"
|
||||
msgstr "Picons formato nombres:"
|
||||
|
||||
msgid "Resize:"
|
||||
msgstr "Redimensionar:"
|
||||
@@ -433,7 +433,7 @@ msgstr "Buscar"
|
||||
|
||||
# IPTV dialog
|
||||
msgid "Stream data"
|
||||
msgstr "Transmitir stream"
|
||||
msgstr "Transmitir flujo"
|
||||
|
||||
# IPTV list configuration dialog
|
||||
msgid "Starting values"
|
||||
@@ -443,7 +443,7 @@ msgid "Reset to default"
|
||||
msgstr "Restablecer a predeterminado"
|
||||
|
||||
msgid "IPTV streams list configuration"
|
||||
msgstr "Configurar lista de streams IPTV"
|
||||
msgstr "Configurar lista de flujos IPTV"
|
||||
|
||||
# Settings dialog
|
||||
msgid "Preferences"
|
||||
@@ -511,13 +511,13 @@ msgid "No m3u file is selected!"
|
||||
msgstr "¡No se ha seleccionado ningún fichero m3u!"
|
||||
|
||||
msgid "Not implemented yet!"
|
||||
msgstr "¡No implementado!"
|
||||
msgstr "¡Aún sin implementar!"
|
||||
|
||||
msgid "The text of marker is empty, please try again!"
|
||||
msgstr "¡El texto del marcador está vacío, inténtalo de nuevo!"
|
||||
|
||||
msgid "Please, select only one item!"
|
||||
msgstr "¡Por favor, seleccione un único elemento!"
|
||||
msgstr "¡Por favor, seleccione sólo un elemento!"
|
||||
|
||||
msgid "No png file is selected!"
|
||||
msgstr "¡No se ha seleccionado ningún fichero png!"
|
||||
@@ -526,7 +526,7 @@ msgid "No reference is present!"
|
||||
msgstr "¡Ninguna referencia presente!"
|
||||
|
||||
msgid "No selected item!"
|
||||
msgstr "¡No se ha seleccionado ningún elemento!"
|
||||
msgstr "¡Ningún elemento seleccionado!"
|
||||
|
||||
msgid "The task is already running!"
|
||||
msgstr "¡La tarea ya se está ejecutando!"
|
||||
@@ -562,23 +562,23 @@ msgid "Operation not allowed in this context!"
|
||||
msgstr "¡Operación no permitida en este contexto!"
|
||||
|
||||
msgid "No VLC is found. Check that it is installed!"
|
||||
msgstr "VLC no encontrado. ¡Compruebe que está instalado!"
|
||||
msgstr "VLC no encontrado. ¡Verifique que está instalado!"
|
||||
|
||||
# Search unavailable streams dialog
|
||||
msgid "Please wait, streams testing in progress..."
|
||||
msgstr "Por favor espere, hay una prueba de stream en progreso..."
|
||||
msgstr "Por favor espere, hay una prueba de flujo en progreso..."
|
||||
|
||||
msgid "Found"
|
||||
msgstr "Encontrado"
|
||||
|
||||
msgid "unavailable streams."
|
||||
msgstr "Streams no presentes."
|
||||
msgstr "Flujos no presentes."
|
||||
|
||||
msgid "No changes required!"
|
||||
msgstr "¡No se requieren cambios!"
|
||||
msgstr "¡Ningún cambio requerido!"
|
||||
|
||||
msgid "This list does not contains IPTV streams!"
|
||||
msgstr "¡La lista no contiene streams IPTV!"
|
||||
msgstr "¡La lista no contiene flujos IPTV!"
|
||||
|
||||
msgid "New empty configuration"
|
||||
msgstr "Nueva configuración vacía"
|
||||
@@ -672,16 +672,16 @@ msgid "Zap"
|
||||
msgstr "Zapear"
|
||||
|
||||
msgid "Play stream"
|
||||
msgstr "Reproducir stream"
|
||||
msgstr "Reproducir flujo"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "Desactivado"
|
||||
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Soporte para lamedb ver. 5"
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Soporte para ver. 5 (experimental)"
|
||||
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "Habilitar API HTTP"
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "Habilitar API HTTP (experimental)"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Poner el canal (Ctrl + Z)"
|
||||
@@ -690,7 +690,7 @@ msgid "Switch the channel and watch in the program(Ctrl + W)"
|
||||
msgstr "Poner el canal y ver en el programa (Ctrl + W)"
|
||||
|
||||
msgid "Play IPTV or other stream in the program(Ctrl + P)"
|
||||
msgstr "Reproducir IPTV u otro stream en el programa (Ctrl + P)"
|
||||
msgstr "Reproducir IPTV u otro flujo en el programa (Ctrl + P)"
|
||||
|
||||
msgid "Export to m3u"
|
||||
msgstr "Exportar a m3u"
|
||||
@@ -795,10 +795,10 @@ msgid "Language:"
|
||||
msgstr "Idioma:"
|
||||
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Cargar la última configuración usada al iniciar el programa"
|
||||
msgstr "Cargar la última configuración abierta al iniciar el programa"
|
||||
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Habilitar la barra de reproducción directa"
|
||||
msgid "Enable direct playback bar (experimental)"
|
||||
msgstr "Habilitar la barra de reproducción directa (experimental)"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Habilita el envío directo y la reproducción de enlaces de medios en el receptor"
|
||||
@@ -816,212 +816,4 @@ msgid "Remove added links in the playlist"
|
||||
msgstr "Quitar los enlaces añadidos en la lista de reproducción"
|
||||
|
||||
msgid "A bouquet with that name exists!"
|
||||
msgstr "¡Ya existe un bouquet con ese nombre!"
|
||||
|
||||
msgid "Details"
|
||||
msgstr "Detalles"
|
||||
|
||||
msgid "Profile"
|
||||
msgstr "Perfil"
|
||||
|
||||
msgid "Reset"
|
||||
msgstr "Reset"
|
||||
|
||||
msgid "File"
|
||||
msgstr "Fichero"
|
||||
|
||||
msgid "Picons manager"
|
||||
msgstr "Gestor de picons"
|
||||
|
||||
msgid "Explorer"
|
||||
msgstr "Explorador"
|
||||
|
||||
msgid "Satellite url:"
|
||||
msgstr "Url satélite:"
|
||||
|
||||
msgid "Cut"
|
||||
msgstr "Cortar"
|
||||
|
||||
msgid "Paste"
|
||||
msgstr "Pegar"
|
||||
|
||||
msgid "To the top"
|
||||
msgstr "Ir arriba"
|
||||
|
||||
msgid "To the end"
|
||||
msgstr "Al final"
|
||||
|
||||
msgid "View"
|
||||
msgstr "Vista"
|
||||
|
||||
msgid "Lock"
|
||||
msgstr "Bloqueo"
|
||||
|
||||
msgid "Parent lock"
|
||||
msgstr "Bloqueo parental"
|
||||
|
||||
msgid "Hide/Skip"
|
||||
msgstr "Escoder/Saltar"
|
||||
|
||||
msgid "IPTV tools"
|
||||
msgstr "Intrumentos IPTV"
|
||||
|
||||
msgid "Make profile folder as default for the additional data"
|
||||
msgstr "Usar por defecto el directorio de perfil para los datos adionales"
|
||||
|
||||
msgid "Default data path:"
|
||||
msgstr "Ruta estándar de datos:"
|
||||
|
||||
msgid "Streams record path:"
|
||||
msgstr "Ruta de grabación del stream:"
|
||||
|
||||
msgid "Record"
|
||||
msgstr "Grabación"
|
||||
|
||||
msgid "Record:"
|
||||
msgstr "Grabación:"
|
||||
|
||||
msgid "Record to disk:"
|
||||
msgstr "Grabación en disco:"
|
||||
|
||||
msgid "Streaming"
|
||||
msgstr "Streaming"
|
||||
|
||||
msgid "Activate transcoding"
|
||||
msgstr "Activar transcodificación"
|
||||
|
||||
msgid "Presets:"
|
||||
msgstr "Preajustes:"
|
||||
|
||||
msgid "Video options:"
|
||||
msgstr "Opciones vídeo:"
|
||||
|
||||
msgid "Audio options:"
|
||||
msgstr "Opciones audio:"
|
||||
|
||||
msgid "Bitrate (kb/s):"
|
||||
msgstr "Bitrate (kb/s):"
|
||||
|
||||
msgid "Codec:"
|
||||
msgstr "Códec:"
|
||||
|
||||
msgid "Width (px):"
|
||||
msgstr "Ancho (px):"
|
||||
|
||||
msgid "Height (px):"
|
||||
msgstr "Alto (px):"
|
||||
|
||||
msgid "Channels:"
|
||||
msgstr "Canales:"
|
||||
|
||||
msgid "Sample rate (Hz):"
|
||||
msgstr "Frecuencia de muestreo (Hz):"
|
||||
|
||||
msgid "Play streams mode:"
|
||||
msgstr "Modo de reproducción de los streams:"
|
||||
|
||||
msgid "Built-in player"
|
||||
msgstr "Reproductor interno"
|
||||
|
||||
msgid "VLC media player"
|
||||
msgstr "Reproductor VLC"
|
||||
|
||||
msgid "Only get m3u file"
|
||||
msgstr "Sólo bajar fichero m3u"
|
||||
|
||||
msgid "Save and restart the program to apply the settings."
|
||||
msgstr "Guardar y reiniciar el programa para aplicar la configuración."
|
||||
|
||||
msgid "Some images may have problems displaying the favorites list!"
|
||||
msgstr "Algunas imágenes pueden tener problemas al mostrar la lista de favoritos!"
|
||||
|
||||
msgid "Operates in standby mode or current active transponder!"
|
||||
msgstr "¡Funciona en modo de espera o transpondedor activo actual!"
|
||||
|
||||
msgid "No connection to the receiver!"
|
||||
msgstr "¡Desconectado del receptor!"
|
||||
|
||||
msgid "Signal level"
|
||||
msgstr "Nivel de señal"
|
||||
|
||||
msgid "Receiver info"
|
||||
msgstr "Información sobre el receptor"
|
||||
|
||||
msgid "A profile with that name exists!"
|
||||
msgstr "¡Ya existe un perfil con ese nombre!"
|
||||
|
||||
msgid "Show short info as hints in the main services list"
|
||||
msgstr "Mostrar información breve como sugerencias en la lista de servicios principal"
|
||||
|
||||
msgid "Show detailed info as hints in the bouquet list"
|
||||
msgstr "Mostrar información detallada como consejo en la lista de bouquets"
|
||||
|
||||
msgid "Enable alternate bouquet file naming"
|
||||
msgstr "Habilitar nombres alternativos para los ficheros de bouquets"
|
||||
|
||||
msgid "Allows you to name bouquet files using their names."
|
||||
msgstr "Permite nombrar ficheros de bouquets usando sus propios nombres."
|
||||
|
||||
msgid "Appearance"
|
||||
msgstr "Apariencia"
|
||||
|
||||
msgid "Enable Themes support"
|
||||
msgstr "Habilitar compatibilidad con temas"
|
||||
|
||||
msgid "Gtk3 Theme:"
|
||||
msgstr "Теma Gtk3:"
|
||||
|
||||
msgid "Icon Theme:"
|
||||
msgstr "Icono del tema:"
|
||||
|
||||
msgid "Gtk3 Themes and Icons:"
|
||||
msgstr "Tema Gtk3 e iconos:"
|
||||
|
||||
msgid "Deleting data..."
|
||||
msgstr "Borrando datos..."
|
||||
|
||||
msgid "Download from the receiver"
|
||||
msgstr "Descargar desde el receptor"
|
||||
|
||||
msgid "Remove all picons from the receiver"
|
||||
msgstr "Eliminar todos los picons del receptor"
|
||||
|
||||
msgid "Service reference"
|
||||
msgstr "Referencia del servicio"
|
||||
|
||||
msgid "Enable support for"
|
||||
msgstr "Habilitar soporte para"
|
||||
|
||||
msgid "Auto-check for updates"
|
||||
msgstr "Comprobación automática de actualizaciones"
|
||||
|
||||
msgid "Filter services"
|
||||
msgstr "Filtrar servicios"
|
||||
|
||||
msgid "Filter services in the main list."
|
||||
msgstr "Filtrar servicios en la lista principal."
|
||||
|
||||
msgid "Destination:"
|
||||
msgstr "Destino:"
|
||||
|
||||
msgid "EXPERIMENTAL!"
|
||||
msgstr "¡EXPERIMENTAL!"
|
||||
|
||||
msgid "Sorting data..."
|
||||
msgstr "Ordenando datos..."
|
||||
|
||||
msgid "There are unsaved changes.\n\n\t Save them now?"
|
||||
msgstr "Hay cambios sin guardar.\n\n\t ¿Desea guardarlos ahora?"
|
||||
|
||||
msgid "Are you sure you want to change the order\n\t of services in this bouquet?"
|
||||
msgstr "¿Está seguro de querer cambiar el orden\n\t de servicios en este bouquet?"
|
||||
|
||||
msgid "Remove from the receiver"
|
||||
msgstr "Eliminar del receptor"
|
||||
|
||||
msgid "Screenshot"
|
||||
msgstr "Captura de pantalla"
|
||||
|
||||
msgid "Video"
|
||||
msgstr "Vídео"
|
||||
|
||||
msgstr "¡Ya existe un bouquet con ese nombre!"
|
||||
@@ -201,8 +201,8 @@ msgstr "Huidig datapad:"
|
||||
msgid "Data:"
|
||||
msgstr "Data:"
|
||||
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "Enigma2 kanaal and satelliet lijst editor voor GNU/Linux."
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Enigma2 kanaal and satelliet lijst editor voor GNU/Linux"
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Host:"
|
||||
@@ -671,11 +671,11 @@ msgstr "Speel stream af"
|
||||
msgid "Disabled"
|
||||
msgstr "Uitgeschakeld"
|
||||
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Ondersteuning voor lamedb ver. 5 inschakelen"
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Ondersteuning voor ver. 5 inschakelen (experimenteel)"
|
||||
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "HTTP API inschakelen"
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "HTTP API inschakelen (experimenteel)"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Schakelaar (ZAP) naar het kanaal (CTRL + Z)"
|
||||
@@ -791,8 +791,8 @@ msgstr "Taal:"
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Laad de laatst geopende configuratie op bij opstart programma"
|
||||
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Laat onmiddelijk playback bar toe"
|
||||
msgid "Enable direct playback bar (experimental)"
|
||||
msgstr "Laat onmiddelijk playback bar toe (experimenteel)"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Laat rechtstreeks versturen van and playback en media links op de ontvanger toe"
|
||||
@@ -970,46 +970,3 @@ msgstr "Icoon Thema:"
|
||||
|
||||
msgid "Gtk3 Themes and Icons:"
|
||||
msgstr "Gtk3 en Icoon Themas:"
|
||||
|
||||
msgid "Deleting data..."
|
||||
msgstr "Wist data ..."
|
||||
|
||||
msgid "Download from the receiver"
|
||||
msgstr "Download van de ontvanger"
|
||||
|
||||
msgid "Service reference"
|
||||
msgstr "Service referentie"
|
||||
|
||||
msgid "Auto-check for updates"
|
||||
msgstr "Auto-check voor updates"
|
||||
|
||||
msgid "Filter services"
|
||||
msgstr "Filter diensten"
|
||||
|
||||
msgid "Filter services in the main list."
|
||||
msgstr "Filter diensten in de hoofdlijst."
|
||||
|
||||
msgid "Destination:"
|
||||
msgstr "Doel:"
|
||||
|
||||
msgid "EXPERIMENTAL!"
|
||||
msgstr "EXPERIMENTEEL!"
|
||||
|
||||
msgid "Sorting data..."
|
||||
msgstr "Data ordenen..."
|
||||
|
||||
msgid "There are unsaved changes.\n\n\t Save them now?"
|
||||
msgstr "Er zijn niet-bwaarde wijzigingen.\n\n\t Nu opslaan?"
|
||||
|
||||
msgid "Are you sure you want to change the order\n\t of services in this bouquet?"
|
||||
msgstr "Ben je zeker dat je de volgorde\n\t van de diensten in dit boeket wil wijzigen?"
|
||||
|
||||
msgid "Remove from the receiver"
|
||||
msgstr "Verwijder van de ontvanger"
|
||||
|
||||
msgid "Screenshot"
|
||||
msgstr "Schermafbeelding"
|
||||
|
||||
msgid "Video"
|
||||
msgstr "Vidео"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2020 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2019 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
@@ -179,6 +179,9 @@ msgstr "Zapisz"
|
||||
msgid "Search"
|
||||
msgstr "Szukaj"
|
||||
|
||||
msgid "Services"
|
||||
msgstr "Kanały"
|
||||
|
||||
msgid "Services filter"
|
||||
msgstr "Filtr kanałów"
|
||||
|
||||
@@ -206,8 +209,8 @@ msgstr "Aktualna ścieżka danych:"
|
||||
msgid "Data:"
|
||||
msgstr "Dane:"
|
||||
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "Edytor kanałów Enigma2 i listy satelitów dla GNU/Linux."
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Edytor kanałów Enigma2 i listy satelitów dla GNU/Linux"
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Host:"
|
||||
@@ -254,21 +257,6 @@ msgstr "Pliki bukietu użytkownika:"
|
||||
msgid "Extra:"
|
||||
msgstr "Dodatkowe"
|
||||
|
||||
msgid "IPTV tools"
|
||||
msgstr "Narzędzia IPTV"
|
||||
|
||||
msgid "Picons manager"
|
||||
msgstr "Menedżer pikonów"
|
||||
|
||||
msgid "Hide/Skip"
|
||||
msgstr "Ukryj/Pomiń"
|
||||
|
||||
msgid "Parent lock"
|
||||
msgstr "Blokada rodzicielska"
|
||||
|
||||
msgid "There are unsaved changes.\n\n\t Save them now?"
|
||||
msgstr "Istnieją niezapisane zmiany.\n\n\t Zapisać je teraz?"
|
||||
|
||||
# Filter bar
|
||||
msgid "Only free"
|
||||
msgstr "Tylko FTA"
|
||||
@@ -280,6 +268,9 @@ msgid "All types"
|
||||
msgstr "Wszystkie typy"
|
||||
|
||||
# Streams player
|
||||
msgid "Play"
|
||||
msgstr "Odtwarzaj"
|
||||
|
||||
msgid "Stop playback"
|
||||
msgstr "Zatrzymaj odtwarzanie"
|
||||
|
||||
@@ -390,12 +381,6 @@ msgid "Remove selection"
|
||||
msgstr "Usuń wybrane"
|
||||
|
||||
# Service details dialog
|
||||
msgid "To the top"
|
||||
msgstr "Przenieś na góre bukietu"
|
||||
|
||||
msgid "To the end"
|
||||
msgstr "Przenieś na koniec bukietu"
|
||||
|
||||
msgid "Service data:"
|
||||
msgstr "Dane usług:"
|
||||
|
||||
@@ -606,6 +591,15 @@ msgstr "Brak danych do zapisania!"
|
||||
msgid "Network"
|
||||
msgstr "Sieć"
|
||||
|
||||
msgid "Paths"
|
||||
msgstr "Ścieżki"
|
||||
|
||||
msgid "Program"
|
||||
msgstr "Program"
|
||||
|
||||
msgid "Backup:"
|
||||
msgstr "Kopia:"
|
||||
|
||||
msgid "Backup"
|
||||
msgstr "Kopia"
|
||||
|
||||
@@ -621,6 +615,21 @@ msgstr "Przywróć bukiety"
|
||||
msgid "Restore all"
|
||||
msgstr "Przywrócić wszystko"
|
||||
|
||||
msgid "Before saving"
|
||||
msgstr "Przed zapisaniem"
|
||||
|
||||
msgid "Before downloading from the receiver"
|
||||
msgstr "Przed pobraniem z odbiornika"
|
||||
|
||||
msgid "Set background color for the services"
|
||||
msgstr "Ustaw kolor tła dla usług"
|
||||
|
||||
msgid "Marked as new:"
|
||||
msgstr "Oznacz jako nowy:"
|
||||
|
||||
msgid "With an extra name in the bouquet:"
|
||||
msgstr "Z dodatkową nazwą w bukiecie:"
|
||||
|
||||
msgid "Select"
|
||||
msgstr "Wybierz"
|
||||
|
||||
@@ -633,15 +642,6 @@ msgstr "Wyjście"
|
||||
msgid "Tools"
|
||||
msgstr "Narzędzia"
|
||||
|
||||
msgid "Cut"
|
||||
msgstr "Wytnij"
|
||||
|
||||
msgid "Paste"
|
||||
msgstr "Wklej"
|
||||
|
||||
msgid "Insert space"
|
||||
msgstr "Wstaw spację"
|
||||
|
||||
# Import
|
||||
msgid "Import"
|
||||
msgstr "Importuj"
|
||||
@@ -664,12 +664,27 @@ msgstr "Usuń wszystkie nieużywane"
|
||||
msgid "Test"
|
||||
msgstr "Test"
|
||||
|
||||
msgid "Details"
|
||||
msgstr "Właściwości"
|
||||
|
||||
msgid "Test connection"
|
||||
msgstr "Testuj połączenie"
|
||||
|
||||
msgid "Double click on the service in the bouquet list:"
|
||||
msgstr "Kliknij dwukrotnie usługę na liście bukietów:"
|
||||
|
||||
msgid "Zap"
|
||||
msgstr "Przełącz"
|
||||
|
||||
msgid "Play stream"
|
||||
msgstr "Odtwórz strumień"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "Wyłączone"
|
||||
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Włącz wer. 5 wsparcie (eksperymentalne)"
|
||||
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "Włącz API HTTP (eksperymentalne)"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Przełącz(zap) kanał(Ctrl + Z)"
|
||||
|
||||
@@ -683,7 +698,7 @@ msgid "Export to m3u"
|
||||
msgstr "Eksportuj do m3u"
|
||||
|
||||
msgid "EPG configuration"
|
||||
msgstr "Konfiguruj EPG"
|
||||
msgstr "Koniguruj EPG"
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Zatwierdź"
|
||||
@@ -770,11 +785,26 @@ msgstr "Import listy odtwarzania"
|
||||
msgid "Getting link error:"
|
||||
msgstr "Błąd pobierania łącza:"
|
||||
|
||||
msgid "Extra"
|
||||
msgstr "Ekstra"
|
||||
|
||||
msgid "Apply profile settings"
|
||||
msgstr "Zastosuj ustawienia profilu"
|
||||
|
||||
msgid "Settings type:"
|
||||
msgstr "Ustawienia dla:"
|
||||
|
||||
msgid "Set default"
|
||||
msgstr "Ustaw domyślnie"
|
||||
msgstr "Uataw domyślnie"
|
||||
|
||||
msgid "Language:"
|
||||
msgstr "Język:"
|
||||
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Załaduj ostatnią otwartą konfigurację podczas uruchamiania programu"
|
||||
|
||||
msgid "Enable direct playback bar (experimental)"
|
||||
msgstr "Włącz pasek bezpośredniego odtwarzania (eksperymentalnie)"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Umożliwia bezpośrednie wysyłanie i odtwarzanie łączy multimedialnych w odbiorniku"
|
||||
@@ -795,10 +825,10 @@ msgid "Remove picons from the receiver"
|
||||
msgstr "Usuń pikony z odbiornika"
|
||||
|
||||
msgid "Use http to reload data in the receiver."
|
||||
msgstr "Użyj http aby przeładować dane w odbiorniku."
|
||||
msgstr "Użyj http aby ponownie załadować dane do odbiornika."
|
||||
|
||||
msgid "Apply profile settings"
|
||||
msgstr "Zastosuj ustawienia profilu"
|
||||
msgid "Zap and Play"
|
||||
msgstr "Przełącz i Odtwórz"
|
||||
|
||||
msgid "Drag or paste the link here"
|
||||
msgstr "Przeciągnij lub wklej tutaj link"
|
||||
@@ -808,197 +838,3 @@ msgstr "Usuń dodane linki z listy odtwarzania"
|
||||
|
||||
msgid "A bouquet with that name exists!"
|
||||
msgstr "Istnieje bukiet o tej nazwie!"
|
||||
|
||||
msgid "Channels:"
|
||||
msgstr "Kanały:"
|
||||
|
||||
msgid "Remove all picons from the receiver"
|
||||
msgstr "Usuń wszystkie pikony z odbiornika"
|
||||
|
||||
msgid "Download from the receiver"
|
||||
msgstr "Pobierz z odbiornika"
|
||||
|
||||
msgid "The Neutrino has only experimental support. Not all features are supported!"
|
||||
msgstr "Neutrino ma jedynie wsparcie eksperymentalne. Nie wszystkie funkcje są obsługiwane!"
|
||||
|
||||
msgid "Some images may have problems displaying the favorites list!"
|
||||
msgstr "Niektóre obrazy mogą mieć problemy z wyświetlaniem listy ulubionych!"
|
||||
|
||||
# Appearance
|
||||
msgid "Appearance"
|
||||
msgstr "Wygląd"
|
||||
|
||||
msgid "Enable Dark Mode"
|
||||
msgstr "Włącz tryb ciemny"
|
||||
|
||||
msgid "Enable Themes support"
|
||||
msgstr "Włącz obsługę motywów"
|
||||
|
||||
msgid "EXPERIMENTAL!"
|
||||
msgstr "EKSPERYMENTALNE!"
|
||||
|
||||
msgid "Gtk3 Theme:"
|
||||
msgstr "Gtk3 motyw:"
|
||||
|
||||
msgid "Icon Theme:"
|
||||
msgstr "Motyw ikon:"
|
||||
|
||||
msgid "Gtk3 Themes and Icons:"
|
||||
msgstr "Gtk3 motywy i ikony:"
|
||||
|
||||
msgid "Save and restart the program to apply the settings."
|
||||
msgstr "Zapisz i uruchom ponownie program, aby zastosować ustawienia."
|
||||
|
||||
# Extra
|
||||
msgid "Extra"
|
||||
msgstr "Ekstra"
|
||||
|
||||
msgid "Enable alternate bouquet file naming"
|
||||
msgstr "Włącz alternatywne nazewnictwo plików bukietów"
|
||||
|
||||
msgid "Allows you to name bouquet files using their names."
|
||||
msgstr "Pozwala nazwać pliki bukietów przy użyciu ich nazw."
|
||||
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "Włącz API HTTP"
|
||||
|
||||
msgid "Double click on the service in the bouquet list:"
|
||||
msgstr "Kliknij dwukrotnie usługę na liście bukietów:"
|
||||
|
||||
msgid "Zap"
|
||||
msgstr "Przełącz"
|
||||
|
||||
msgid "Play"
|
||||
msgstr "Odtwarzaj"
|
||||
|
||||
msgid "Zap and Play"
|
||||
msgstr "Przełącz i Odtwórz"
|
||||
|
||||
msgid "Play stream"
|
||||
msgstr "Odtwórz strumień"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "Wyłączone"
|
||||
|
||||
msgid "Enable experimental features"
|
||||
msgstr "Włącz funkcje eksperymentalne"
|
||||
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Włącz wsparcie dla lamedb w wer.5"
|
||||
|
||||
msgid "Enable support for"
|
||||
msgstr "Włącz obsługę"
|
||||
|
||||
msgid "Enables parsing links using youtube-dl to get direct links to media"
|
||||
msgstr "Umożliwia analizowanie linków za pomocą youtube-dl, aby uzyskać bezpośrednie linki do multimediów"
|
||||
|
||||
msgid "Auto-check for updates"
|
||||
msgstr "Automatyczne sprawdzanie aktualizacji"
|
||||
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Włącz pasek bezpośredniego odtwarzania"
|
||||
|
||||
#Program
|
||||
msgid "Program"
|
||||
msgstr "Program"
|
||||
|
||||
msgid "Language:"
|
||||
msgstr "Język:"
|
||||
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Załaduj ostatnią otwartą konfigurację podczas uruchamiania programu"
|
||||
|
||||
msgid "Show short info as hints in the main services list"
|
||||
msgstr "Pokaż krótkie informacje jako wskazówki na głównej liście usług"
|
||||
|
||||
msgid "Show detailed info as hints in the bouquet list"
|
||||
msgstr "Pokaż szczegółowe informacje jako wskazówki na liście bukietów"
|
||||
|
||||
msgid "Set background color for the services"
|
||||
msgstr "Ustaw kolor tła dla usług"
|
||||
|
||||
msgid "Marked as new:"
|
||||
msgstr "Oznacz jako nowy:"
|
||||
|
||||
msgid "With an extra name in the bouquet:"
|
||||
msgstr "Z dodatkową nazwą w bukiecie:"
|
||||
|
||||
msgid "Backup:"
|
||||
msgstr "Kopia:"
|
||||
|
||||
msgid "Before saving"
|
||||
msgstr "Przed zapisaniem"
|
||||
|
||||
msgid "Before downloading from the receiver"
|
||||
msgstr "Przed pobraniem z odbiornika"
|
||||
|
||||
#Streaming
|
||||
msgid "Streaming"
|
||||
msgstr "Transmisja"
|
||||
|
||||
msgid "Record to disk:"
|
||||
msgstr "Nagrywanie na dysk:"
|
||||
|
||||
msgid "Activate transcoding"
|
||||
msgstr "Aktywuj transkodowanie"
|
||||
|
||||
msgid "Presets:"
|
||||
msgstr "Ustawienia wstępne:"
|
||||
|
||||
msgid "720p TV/device"
|
||||
msgstr "Dla urządzeń z obsługą rozdzielczości 720p"
|
||||
|
||||
msgid "1080p TV/device"
|
||||
msgstr "Dla urządzeń z obsługą rozdzielczości 1080p"
|
||||
|
||||
msgid "Video options:"
|
||||
msgstr "Opcje wideo:"
|
||||
|
||||
msgid "Width (px):"
|
||||
msgstr "Szerokość (px):"
|
||||
|
||||
msgid "Height (px):"
|
||||
msgstr "Wysokość (px):"
|
||||
|
||||
msgid "Codec:"
|
||||
msgstr "Kodek:"
|
||||
|
||||
msgid "Audio options:"
|
||||
msgstr "Opcje audio:"
|
||||
|
||||
msgid "Services"
|
||||
msgstr "Kanały"
|
||||
|
||||
msgid "Sample rate (Hz):"
|
||||
msgstr "Częstotliwość próbkowania (Hz):"
|
||||
|
||||
msgid "Play streams mode:"
|
||||
msgstr "Odtwarzaj tryb strumieni:"
|
||||
|
||||
msgid "Bulit-in player"
|
||||
msgstr "Wbudowany odtwarzacz"
|
||||
|
||||
msgid "VLC media player"
|
||||
msgstr "Odtwarzacz multimedialny VLC"
|
||||
|
||||
msgid "Only get m3u file"
|
||||
msgstr "Tylko pobierz plik m3u"
|
||||
|
||||
# Paths
|
||||
msgid "Paths"
|
||||
msgstr "Ścieżki"
|
||||
|
||||
msgid "Make profile folder as default for the additional data"
|
||||
msgstr "Ustaw folder profilu jako domyślny dla dodatkowych danych"
|
||||
|
||||
msgid "Sets the profile folder as default to store picons, backups, etc."
|
||||
msgstr "Ustawia folder profilu jako domyślny do przechowywania pikonów, kopii zapasowych itp."
|
||||
|
||||
msgid "Default data path:"
|
||||
msgstr "Domyślna ścieżka danych:"
|
||||
|
||||
msgid "Record:"
|
||||
msgstr "Nagrania:"
|
||||
|
||||
msgid "Streams record path:"
|
||||
msgstr "Ścieżka zapisu nagrań:"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2020 Frank Neirynck
|
||||
# Copyright (C) 2018-2019 Frank Neirynck
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#Frank Neirynck <frank@insink.be>, 2018-2019.
|
||||
@@ -201,8 +201,8 @@ msgstr "Rota de dados atual:"
|
||||
msgid "Data:"
|
||||
msgstr "Dados:"
|
||||
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "Editor de Canais e Satélites Enigma2 para GNU/Linux."
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Editor de Canais e Satélites Enigma2 para GNU/Linux"
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Anfitrião:"
|
||||
@@ -663,11 +663,11 @@ msgstr "Play stream"
|
||||
msgid "Disabled"
|
||||
msgstr "Desativado"
|
||||
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Ativar lamedb ver. 5 suporte"
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Ativar ver. 5 suporte (experimental)"
|
||||
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "Ativar HTTP API"
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "Ativar HTTP API (experimental)"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Mudar(zap) o canal(Ctrl + Z)"
|
||||
@@ -763,250 +763,4 @@ msgid "Playlist import"
|
||||
msgstr "Importação de lista de reprodução"
|
||||
|
||||
msgid "Getting link error:"
|
||||
msgstr "Obtendo erro de link:"
|
||||
|
||||
msgid "Extra"
|
||||
msgstr "Extra"
|
||||
|
||||
msgid "Apply profile settings"
|
||||
msgstr "Aplicar ajustes de perfil"
|
||||
|
||||
msgid "Settings type:"
|
||||
msgstr "Tipo de ajustes:"
|
||||
|
||||
msgid "Set default"
|
||||
msgstr "Por defecto"
|
||||
|
||||
msgid "Language:"
|
||||
msgstr "Idioma:"
|
||||
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Cargar la última configuración abierta al iniciar el programa"
|
||||
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Habilitar la barra de reproducción directa"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Habilita el envío directo y la reproducción de enlaces de medios en el receptor"
|
||||
|
||||
msgid "Watch the channel in the program"
|
||||
msgstr "Ver el canal en el programa"
|
||||
|
||||
msgid "Zap and Play"
|
||||
msgstr "Zapear y reproducir"
|
||||
|
||||
msgid "Drag or paste the link here"
|
||||
msgstr "Soltar o pegar en enlace aquí"
|
||||
|
||||
msgid "Remove added links in the playlist"
|
||||
msgstr "Quitar los enlaces añadidos en la lista de reproducción"
|
||||
|
||||
msgid "A bouquet with that name exists!"
|
||||
msgstr "¡Ya existe un bouquet con ese nombre!"
|
||||
|
||||
msgid "Details"
|
||||
msgstr "Detalles"
|
||||
|
||||
msgid "Profile"
|
||||
msgstr "Perfil"
|
||||
|
||||
msgid "Reset"
|
||||
msgstr "Reset"
|
||||
|
||||
msgid "File"
|
||||
msgstr "Archivo"
|
||||
|
||||
msgid "Picons manager"
|
||||
msgstr "Picons manager"
|
||||
|
||||
msgid "Explorer"
|
||||
msgstr "Explorador"
|
||||
|
||||
msgid "Satellite url:"
|
||||
msgstr "Url Satelite:"
|
||||
|
||||
msgid "Cut"
|
||||
msgstr "Cortar"
|
||||
|
||||
msgid "Paste"
|
||||
msgstr "Pegar"
|
||||
|
||||
msgid "To the top"
|
||||
msgstr "Ir arriba"
|
||||
|
||||
msgid "To the end"
|
||||
msgstr "Al final"
|
||||
|
||||
msgid "View"
|
||||
msgstr "Vista"
|
||||
|
||||
msgid "Lock"
|
||||
msgstr "Bloqueo"
|
||||
|
||||
msgid "Parent lock"
|
||||
msgstr "Bloqueo parental"
|
||||
|
||||
msgid "Hide/Skip"
|
||||
msgstr "Escoder/Saltar"
|
||||
|
||||
msgid "IPTV tools"
|
||||
msgstr "Intrumentos IPTV"
|
||||
|
||||
msgid "Make profile folder as default for the additional data"
|
||||
msgstr "Has folder de perfil estandar para datos adicionales"
|
||||
|
||||
msgid "Default data path:"
|
||||
msgstr "Ruta estandar de datos:"
|
||||
|
||||
msgid "Streams record path:"
|
||||
msgstr "Ruta de gravacion de stream:"
|
||||
|
||||
msgid "Record"
|
||||
msgstr "Gravar"
|
||||
|
||||
msgid "Record:"
|
||||
msgstr "Gravar:"
|
||||
|
||||
msgid "Record to disk:"
|
||||
msgstr "Gravar en disco:"
|
||||
|
||||
msgid "Streaming"
|
||||
msgstr "Streameando"
|
||||
|
||||
msgid "Activate transcoding"
|
||||
msgstr "Activer transcodificacion"
|
||||
|
||||
msgid "Presets:"
|
||||
msgstr "Presets:"
|
||||
|
||||
msgid "Video options:"
|
||||
msgstr "Opciones Video:"
|
||||
|
||||
msgid "Audio options:"
|
||||
msgstr "Opciones Audio:"
|
||||
|
||||
msgid "Bitrate (kb/s):"
|
||||
msgstr "Bitrate (kb/s):"
|
||||
|
||||
msgid "Codec:"
|
||||
msgstr "Codec:"
|
||||
|
||||
msgid "Width (px):"
|
||||
msgstr "Ancho (px):"
|
||||
|
||||
msgid "Height (px):"
|
||||
msgstr "Alto (px):"
|
||||
|
||||
msgid "Channels:"
|
||||
msgstr "Canales:"
|
||||
|
||||
msgid "Sample rate (Hz):"
|
||||
msgstr "Sample rate (Гц):"
|
||||
|
||||
msgid "Play streams mode:"
|
||||
msgstr "Tocar en modo streams:"
|
||||
|
||||
msgid "Built-in player"
|
||||
msgstr "Reproductor interno"
|
||||
|
||||
msgid "VLC media player"
|
||||
msgstr "Reproductor VLC"
|
||||
|
||||
msgid "Only get m3u file"
|
||||
msgstr "Solo bajar archivo *.m3u"
|
||||
|
||||
msgid "Save and restart the program to apply the settings."
|
||||
msgstr "Guarde y reinicie el programa para aplicar la configuración."
|
||||
|
||||
msgid "Some images may have problems displaying the favorites list!"
|
||||
msgstr "Algunas imágenes pueden tener problemas para mostrar la lista de favoritos!"
|
||||
|
||||
msgid "Operates in standby mode or current active transponder!"
|
||||
msgstr "Funciona en modo de espera o transpondedor activo actual!"
|
||||
|
||||
msgid "No connection to the receiver!"
|
||||
msgstr "Sin conexión al receptor!"
|
||||
|
||||
msgid "Signal level"
|
||||
msgstr "Nivel de señal"
|
||||
|
||||
msgid "Receiver info"
|
||||
msgstr "Informacion sobre receptor"
|
||||
|
||||
msgid "A profile with that name exists!"
|
||||
msgstr "Existe un perfil con ese nombre!"
|
||||
|
||||
msgid "Show short info as hints in the main services list"
|
||||
msgstr "Mostrar información breve como sugerencias en la lista de servicios principal"
|
||||
|
||||
msgid "Show detailed info as hints in the bouquet list"
|
||||
msgstr "Mostrar información detallada como pistas en la lista de bouquet"
|
||||
|
||||
msgid "Enable alternate bouquet file naming"
|
||||
msgstr "Habilitar nombres alternativos de archivos de bouquet"
|
||||
|
||||
msgid "Allows you to name bouquet files using their names."
|
||||
msgstr "Le permite nombrar archivos de bouquet usando sus nombres."
|
||||
|
||||
msgid "Appearance"
|
||||
msgstr "Apariencia"
|
||||
|
||||
msgid "Enable Themes support"
|
||||
msgstr "Habilitar compatibilidad con temas"
|
||||
|
||||
msgid "Gtk3 Theme:"
|
||||
msgstr "Тема Gtk3:"
|
||||
|
||||
msgid "Icon Theme:"
|
||||
msgstr "Тема Icono:"
|
||||
|
||||
msgid "Gtk3 Themes and Icons:"
|
||||
msgstr "Tema Gtk3 e Iconos:"
|
||||
|
||||
msgid "Deleting data..."
|
||||
msgstr "Borrando datos ..."
|
||||
|
||||
msgid "Download from the receiver"
|
||||
msgstr "Descargar desde el receptor"
|
||||
|
||||
msgid "Remove all picons from the receiver"
|
||||
msgstr "Eliminar todos los picons del receptor"
|
||||
|
||||
msgid "Service reference"
|
||||
msgstr "Referencia de servicio"
|
||||
|
||||
msgid "Enable support for"
|
||||
msgstr "Habilitar soporte para"
|
||||
|
||||
msgid "Auto-check for updates"
|
||||
msgstr "Verificación automática de actualizaciones"
|
||||
|
||||
msgid "Filter services"
|
||||
msgstr "Filtrar servicios"
|
||||
|
||||
msgid "Filter services in the main list."
|
||||
msgstr "Filtrar servicios en la lista principal."
|
||||
|
||||
msgid "Destination:"
|
||||
msgstr "Destino:"
|
||||
|
||||
msgid "EXPERIMENTAL!"
|
||||
msgstr "EXPERIMENTAL!"
|
||||
|
||||
msgid "Sorting data..."
|
||||
msgstr "Ordenando datos..."
|
||||
|
||||
msgid "There are unsaved changes.\n\n\t Save them now?"
|
||||
msgstr "Hay cambios sin guardar.\n\n\t ¿Guardarlos ahora?"
|
||||
|
||||
msgid "Are you sure you want to change the order\n\t of services in this bouquet?"
|
||||
msgstr "¿Está seguro de que desea cambiar el orden\n\t de servicios en este bouquet?"
|
||||
|
||||
msgid "Remove from the receiver"
|
||||
msgstr "Retirar del receptor"
|
||||
|
||||
msgid "Screenshot"
|
||||
msgstr "Captura de pantalla"
|
||||
|
||||
msgid "Video"
|
||||
msgstr "Vidео"
|
||||
msgstr "Obtendo erro de link:"
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2020 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2019 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
@@ -104,9 +104,6 @@ msgstr "Имя по умолчанию"
|
||||
msgid "Insert marker"
|
||||
msgstr "Вставить маркер"
|
||||
|
||||
msgid "Insert space"
|
||||
msgstr "Вставить пробел"
|
||||
|
||||
msgid "Locate in services"
|
||||
msgstr "Найти в списке сервисов"
|
||||
|
||||
@@ -203,8 +200,8 @@ msgstr "Текущий путь к данным:"
|
||||
msgid "Data:"
|
||||
msgstr "Данные:"
|
||||
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "Редактор списка каналов и спутников Enigma2\n для GNU/Linux."
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Редактор списка каналов и спутников Enigma2\n для GNU/Linux"
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Адрес ресивера:"
|
||||
@@ -510,9 +507,6 @@ msgstr "Пожалуйста, выберите только один элеме
|
||||
msgid "No png file is selected!"
|
||||
msgstr "Не выбран png файл!"
|
||||
|
||||
msgid "No profile selected!"
|
||||
msgstr "Не выбран профиль!"
|
||||
|
||||
msgid "No reference is present!"
|
||||
msgstr "Ссылка не найдена!"
|
||||
|
||||
@@ -668,17 +662,17 @@ msgstr "Воспр. потока"
|
||||
msgid "Disabled"
|
||||
msgstr "Выкл."
|
||||
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Включить поддержку lamedb вер. 5"
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Включить поддержку lamedb вер. 5 (экспериментально)"
|
||||
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "Включить HTTP API"
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "Включить HTTP API (экспериментально)"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Переключить канал(Ctrl + Z)"
|
||||
|
||||
msgid "Switch the channel and watch in the program(Ctrl + W)"
|
||||
msgstr "Переключить канал и просмотр в программе(Ctrl + W)."
|
||||
msgstr "Переклють канал и просмотр в программе(Ctrl + W)."
|
||||
|
||||
msgid "Play IPTV or other stream in the program(Ctrl + P)"
|
||||
msgstr "Воспроизведение IPTV или другого потока в программе(Ctrl + P)"
|
||||
@@ -788,8 +782,8 @@ msgstr "Язык:"
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Загружать последнюю открытую конфигурацию при запуске программы"
|
||||
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Включить панель прямого воспроизведения"
|
||||
msgid "Enable direct playback bar (experimental)"
|
||||
msgstr "Включить панель прямого воспроизведения (экспериментально)"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Включает прямую отправку и воспроизведение медиа-ссылок на ресивере"
|
||||
@@ -980,182 +974,3 @@ msgstr "Удалить все пиконы с ресивера"
|
||||
msgid "Service reference"
|
||||
msgstr "Сервисная ссылка"
|
||||
|
||||
msgid "Enable support for"
|
||||
msgstr "Включить поддержку"
|
||||
|
||||
msgid "Auto-check for updates"
|
||||
msgstr "Автопроверка обновлений"
|
||||
|
||||
msgid "Filter services"
|
||||
msgstr "Фильтровать сервисы"
|
||||
|
||||
msgid "Filter services in the main list."
|
||||
msgstr "Фильтровать сервисы в основном списке."
|
||||
|
||||
msgid "Destination:"
|
||||
msgstr "Назначение:"
|
||||
|
||||
msgid "EXPERIMENTAL!"
|
||||
msgstr "ЭКСПЕРИМЕНТАЛЬНО!"
|
||||
|
||||
msgid "Sorting data..."
|
||||
msgstr "Сортировка данных..."
|
||||
|
||||
msgid "There are unsaved changes.\n\n\t Save them now?"
|
||||
msgstr "Имеются несохранённые изменения.\n\n\t Сохранить их сейчас?"
|
||||
|
||||
msgid "Are you sure you want to change the order\n\t of services in this bouquet?"
|
||||
msgstr "Вы уверены, что хотите изменить порядок\n\t сервисов в этом букете?"
|
||||
|
||||
msgid "Remove from the receiver"
|
||||
msgstr "Удалить с ресивера"
|
||||
|
||||
msgid "Screenshot"
|
||||
msgstr "Скриншот"
|
||||
|
||||
msgid "Video"
|
||||
msgstr "Видео"
|
||||
|
||||
msgid "The Neutrino has only experimental support. Not all features are supported!"
|
||||
msgstr "Neutrino имеет только экспериментальную поддержку. Поддерживаются не все функции!"
|
||||
|
||||
msgid "Enable experimental features"
|
||||
msgstr "Включить экспериментальные функции"
|
||||
|
||||
msgid "Can't Playback!"
|
||||
msgstr "Не удается воспроизвести!"
|
||||
|
||||
msgid "Enable Dark Mode"
|
||||
msgstr "Включить темный режим"
|
||||
|
||||
msgid "Extract..."
|
||||
msgstr "Извлечь..."
|
||||
|
||||
msgid "Unsupported format!"
|
||||
msgstr "Неподдерживаемый формат!"
|
||||
|
||||
msgid "Combine with the current data?"
|
||||
msgstr "Объединить с текущими данными?"
|
||||
|
||||
msgid "Importing data done!"
|
||||
msgstr "Импорт данных завершен!"
|
||||
|
||||
msgid "Current service"
|
||||
msgstr "Текущий сервис"
|
||||
|
||||
msgid "Open folder"
|
||||
msgstr "Открыть папку"
|
||||
|
||||
msgid "Open archive"
|
||||
msgstr "Открыть архив"
|
||||
|
||||
msgid "Import from Web"
|
||||
msgstr "Импорт из сети"
|
||||
|
||||
msgid "Control"
|
||||
msgstr "Управление"
|
||||
|
||||
msgid "Timers"
|
||||
msgstr "Таймеры"
|
||||
|
||||
msgid "Timer"
|
||||
msgstr "Таймер"
|
||||
|
||||
msgid "Add timer"
|
||||
msgstr "Добавить таймер"
|
||||
|
||||
msgid "Hr."
|
||||
msgstr "ч."
|
||||
|
||||
msgid "Min."
|
||||
msgstr "мин."
|
||||
|
||||
msgid "Power"
|
||||
msgstr "Питание"
|
||||
|
||||
msgid "Standby"
|
||||
msgstr "Режим ожидания"
|
||||
|
||||
msgid "Wake Up"
|
||||
msgstr "Пробуждение"
|
||||
|
||||
msgid "Reboot"
|
||||
msgstr "Перезагрузка"
|
||||
|
||||
msgid "Restart GUI"
|
||||
msgstr "Перезагрузить графический интерфейс"
|
||||
|
||||
msgid "Shutdown"
|
||||
msgstr "Выключение"
|
||||
|
||||
msgid "Shut down"
|
||||
msgstr "Выключить"
|
||||
|
||||
msgid "Do Nothing"
|
||||
msgstr "Ничего не делать"
|
||||
|
||||
msgid "Auto"
|
||||
msgstr "Авто"
|
||||
|
||||
msgid "Grab screenshot"
|
||||
msgstr "Сделать скриншот"
|
||||
|
||||
msgid "Enabled:"
|
||||
msgstr "Включен:"
|
||||
|
||||
msgid "Name:"
|
||||
msgstr "Имя:"
|
||||
|
||||
msgid "Description:"
|
||||
msgstr "Описание:"
|
||||
|
||||
msgid "Service:"
|
||||
msgstr "Сервис:"
|
||||
|
||||
msgid "Service reference:"
|
||||
msgstr "Сервисная ссылка:"
|
||||
|
||||
msgid "Event ID:"
|
||||
msgstr "ID события:"
|
||||
|
||||
msgid "Begins:"
|
||||
msgstr "Начало:"
|
||||
|
||||
msgid "Ends:"
|
||||
msgstr "Окончание:"
|
||||
|
||||
msgid "Repeated:"
|
||||
msgstr "Повтор:"
|
||||
|
||||
msgid "Action:"
|
||||
msgstr "Действие:"
|
||||
|
||||
msgid "After event:"
|
||||
msgstr "После события:"
|
||||
|
||||
msgid "Location:"
|
||||
msgstr "Расположение:"
|
||||
|
||||
msgid "Mo"
|
||||
msgstr "Пн"
|
||||
|
||||
msgid "Tu"
|
||||
msgstr "Вт"
|
||||
|
||||
msgid "We"
|
||||
msgstr "Ср"
|
||||
|
||||
msgid "Th"
|
||||
msgstr "Чт"
|
||||
|
||||
msgid "Fr"
|
||||
msgstr "Пт"
|
||||
|
||||
msgid "Sa"
|
||||
msgstr "Сб"
|
||||
|
||||
msgid "Su"
|
||||
msgstr "Вс"
|
||||
|
||||
msgid "Set"
|
||||
msgstr "Установить"
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: DemonEditor\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
|
||||
"PO-Revision-Date: 2020-06-08 21:53+0300\n"
|
||||
"PO-Revision-Date: 2020-05-11 20:02+0300\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
@@ -203,8 +203,8 @@ msgstr "Mevcut veri yolu:"
|
||||
msgid "Data:"
|
||||
msgstr "Veri:"
|
||||
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "GNU/Linux için Enigma2 kanalı ve uydu listesi editörü."
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "GNU/Linux için Enigma2 kanalı ve uydu listesi editörü"
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Ana bilgisayar:"
|
||||
@@ -213,7 +213,7 @@ msgid "Loading data..."
|
||||
msgstr "Veriler yükleniyor ..."
|
||||
|
||||
msgid "Receive"
|
||||
msgstr "Cihazdan Al"
|
||||
msgstr "Al"
|
||||
|
||||
msgid "Receive files from receiver"
|
||||
msgstr "Alıcıdan dosya al"
|
||||
@@ -237,7 +237,7 @@ msgid "Selected"
|
||||
msgstr "Seçildi"
|
||||
|
||||
msgid "Send"
|
||||
msgstr "Cihaza Gönder"
|
||||
msgstr "Gönder"
|
||||
|
||||
msgid "Send files to receiver"
|
||||
msgstr "Alıcıya dosya gönder"
|
||||
@@ -350,10 +350,10 @@ msgid "Add"
|
||||
msgstr "Ekle"
|
||||
|
||||
msgid "Satellite"
|
||||
msgstr "Uydu Ekle"
|
||||
msgstr "Uydu"
|
||||
|
||||
msgid "Transponder"
|
||||
msgstr "Transponder Ekle"
|
||||
msgstr "Transponder"
|
||||
|
||||
msgid "Satellite properties:"
|
||||
msgstr "Uydu özellikleri:"
|
||||
@@ -673,11 +673,11 @@ msgstr "Akışı oynat"
|
||||
msgid "Disabled"
|
||||
msgstr "Devre dışı"
|
||||
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Sürüm 5 desteğini etkinleştir"
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Sürüm 5 desteğini etkinleştir (deneysel)"
|
||||
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "HTTP API'sini etkinleştir"
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "HTTP API'sini etkinleştir (deneysel)"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Kanalı değiştir (zap) (Ctrl + Z)"
|
||||
@@ -797,8 +797,8 @@ msgstr "Dil:"
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Program açılışında son açık yapılandırmayı yükle"
|
||||
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Doğrudan oynatma çubuğunu etkinleştir"
|
||||
msgid "Enable direct playback bar (experimental)"
|
||||
msgstr "Doğrudan oynatma çubuğunu etkinleştir (deneysel)"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Alıcıdaki medya bağlantılarının doğrudan gönderilmesini ve oynatılmasını sağlar"
|
||||
@@ -825,7 +825,7 @@ msgid "Profile"
|
||||
msgstr "Profil"
|
||||
|
||||
msgid "Reset"
|
||||
msgstr "Yeniden Başlat"
|
||||
msgstr "Y.başlat"
|
||||
|
||||
msgid "File"
|
||||
msgstr "Dosya"
|
||||
@@ -976,15 +976,3 @@ msgstr "Simge Teması:"
|
||||
|
||||
msgid "Gtk3 Themes and Icons:"
|
||||
msgstr "Gtk3 Tema ve Simgeler:"
|
||||
|
||||
msgid "Deleting data..."
|
||||
msgstr "Veriler siliniyor..."
|
||||
|
||||
msgid "Download from the receiver"
|
||||
msgstr "Alıcıdan indir"
|
||||
|
||||
msgid "Remove all picons from the receiver"
|
||||
msgstr "Alıcıdaki tüm piconları kaldırın"
|
||||
|
||||
msgid "Service reference"
|
||||
msgstr "Servis referansı"
|
||||
|
||||
34
start.py
34
start.py
@@ -1,29 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
|
||||
|
||||
def update_icon():
|
||||
need_update = False
|
||||
icon_name = "DemonEditor.desktop"
|
||||
|
||||
with open(icon_name, "r") as f:
|
||||
lines = f.readlines()
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith("Icon="):
|
||||
icon_path = line.lstrip("Icon=")
|
||||
current_path = "{}/app/ui/icons/hicolor/96x96/apps/demon-editor.png".format(os.getcwd())
|
||||
if icon_path != current_path:
|
||||
need_update = True
|
||||
lines[i] = "Icon={}\n".format(current_path)
|
||||
break
|
||||
|
||||
if need_update:
|
||||
with open(icon_name, "w") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
try:
|
||||
from Cocoa import NSBundle
|
||||
except ImportError as e:
|
||||
print(e)
|
||||
else:
|
||||
ns_bundle = NSBundle.mainBundle()
|
||||
if ns_bundle:
|
||||
ns_bundle = ns_bundle.localizedInfoDictionary() or ns_bundle.infoDictionary()
|
||||
if ns_bundle:
|
||||
ns_bundle["CFBundleName"] = "DemonEditor"
|
||||
|
||||
if __name__ == "__main__":
|
||||
from multiprocessing import set_start_method
|
||||
from app.ui.main_app_window import start_app
|
||||
|
||||
update_icon()
|
||||
set_start_method("fork") # For compatibility [Python > 3.7]
|
||||
start_app()
|
||||
|
||||
Reference in New Issue
Block a user