Compare commits

..

94 Commits

Author SHA1 Message Date
DYefremov
e2aa21060b minor fix for the picon parser 2020-10-10 15:19:00 +03:00
DYefremov
b15691207b minor fix for yt 2020-10-06 11:25:26 +03:00
DYefremov
ab3ca134a7 Russian, Belarusian and German translations update 2020-10-04 14:53:42 +03:00
DYefremov
7f839f3fa0 data loading refactoring (prevent #37) 2020-10-02 13:42:43 +03:00
DYefremov
c3e8eac4b9 changed getting of the drag icon 2020-09-30 20:54:03 +03:00
DYefremov
04f808843d upd README 2020-09-25 18:02:32 +03:00
DYefremov
eea4a68993 Russian, Belarusian and German translations update 2020-09-25 18:01:36 +03:00
DYefremov
bfba5b5237 reworked and improved dnd for lists 2020-09-24 23:17:15 +03:00
DYefremov
e203a38966 added support for loading and importing data via dnd 2020-09-19 12:32:08 +03:00
DYefremov
a3b5609138 version update 2020-09-17 17:18:58 +03:00
DYefremov
b3c131b753 added support for opening archives 2020-09-17 17:16:00 +03:00
DYefremov
a8ea5ad974 minor rework of the chooser dialog 2020-09-17 10:00:02 +03:00
DYefremov
831184af2e displaying sid value in uppercase for tooltips(#34) 2020-09-12 14:11:42 +03:00
DYefremov
1f51766dea Display the sid value in tooltips in hex and dec format(#34). 2020-09-12 11:33:17 +03:00
DYefremov
f8f1536213 copy es *.mo file 2020-09-11 16:36:03 +03:00
Víctor Pont
0accfbd3d1 Spanish translation update (#36) 2020-09-11 16:24:59 +03:00
DYefremov
ee462b24f7 renaming bouquet fix [losing custom names](#33) 2020-09-08 12:21:10 +03:00
DYefremov
17ee189db8 upd README 2020-09-07 20:05:24 +03:00
DYefremov
8389293b4b copy pl *.mo file 2020-09-02 23:02:46 +03:00
Wieslaw Weglowski
99e0c79b6c Polish translation corrections (#31) 2020-09-02 19:16:21 +03:00
DYefremov
02cdbc4e56 Russian, Belarusian and German translations update 2020-09-02 14:16:48 +03:00
DYefremov
d57f0490d2 version update 2020-08-31 22:21:43 +03:00
DYefremov
9680347180 minor fix for picon assignment 2020-08-31 22:20:56 +03:00
DYefremov
7fbdc32f91 added Belarusian translation 2020-08-31 11:46:52 +03:00
DYefremov
a69f54435d fix playback from the start screen 2020-08-30 16:25:10 +03:00
DYefremov
0d47433f80 small rework of picons manager header 2020-08-30 14:26:18 +03:00
DYefremov
d16a8e44f6 resize fix in satellite dialog 2020-08-30 14:16:28 +03:00
DYefremov
053a834d6d telnet password visibility fix 2020-08-24 22:42:05 +03:00
DYefremov
62c1ef852c Polish translation update 2020-08-24 22:17:20 +03:00
DYefremov
b5234c55e8 minor optimization 2020-08-24 22:06:30 +03:00
Wieslaw Weglowski
472ebba8e9 Update demon-editor.po (#30) 2020-08-24 21:17:19 +03:00
DYefremov
d427cf66b0 rework of the picons resizing 2020-08-19 20:41:51 +03:00
DYefremov
9fe3d8077f German and Russian translation update 2020-08-15 16:51:47 +03:00
DYefremov
2f12ef7bdd minor yt fix 2020-08-15 16:50:34 +03:00
DYefremov
b6ad661e39 added dark mode option 2020-08-08 14:43:26 +03:00
DYefremov
6355e0d75a minor fixes 2020-08-07 11:31:40 +03:00
DYefremov
29016056c2 skipping enigma2 stop during picons upload 2020-08-06 21:16:21 +03:00
DYefremov
23fe71e5cc minor correction of translations 2020-08-04 12:45:10 +03:00
DYefremov
80e4edd084 version update 2020-08-03 22:47:44 +03:00
DYefremov
3d0bb6ad3c added option for experimental features 2020-08-03 22:41:14 +03:00
DYefremov
a8937d0698 Spanish, Portuguese and Dutch translation update 2020-07-27 16:30:10 +03:00
DYefremov
b97997b7a0 update ref fix 2020-07-24 11:37:27 +03:00
DYefremov
0003c6c5d5 German and Russian translation update 2020-07-20 11:32:56 +03:00
DYefremov
13b9d64bd0 added keyboard[del] support 2020-07-20 11:11:10 +03:00
DYefremov
c68511b223 loading providers fix 2020-07-19 20:51:18 +03:00
DYefremov
c6ef61222e fix display of cas 2020-07-19 14:59:37 +03:00
DYefremov
8309353784 version update 2020-07-18 21:08:52 +03:00
DYefremov
3f65975ac2 added lock support for iptv 2020-07-18 20:55:15 +03:00
DYefremov
4f6443e6e3 German translation update 2020-07-17 09:52:12 +03:00
DYefremov
d517b3b9d6 Russian translation update 2020-07-17 09:51:45 +03:00
DYefremov
faf228fa6f added audio codec option 2020-07-16 20:45:27 +03:00
DYefremov
eaf5e39458 added notifications 2020-07-15 11:16:09 +03:00
DYefremov
8a2539d57b minor fix 2020-07-12 22:28:49 +03:00
DYefremov
8c827b126f added bouquets import via dnd 2020-07-12 11:11:32 +03:00
DYefremov
db1bfb0fb9 minor gui fix 2020-07-11 13:00:01 +03:00
DYefremov
8ba7751b97 added debug mode option 2020-07-11 12:58:03 +03:00
DYefremov
65b58c9d08 added sorting of bouquet services 2020-07-09 22:29:33 +03:00
DYefremov
72aed5ff6e added download dialog options 2020-07-04 13:38:39 +03:00
DYefremov
ad07469c35 auto save profile settings 2020-07-02 17:24:21 +03:00
DYefremov
6f0de03b22 fix lock/hide in filter mode 2020-06-22 19:45:30 +03:00
DYefremov
1cec96d2b5 skip of marker counting (#27) 2020-06-22 11:07:44 +03:00
DYefremov
a9935dd0a7 changed callback for screenshots 2020-06-15 17:00:28 +03:00
DYefremov
65dfd6c1c4 small yt refactoring 2020-06-13 20:57:37 +03:00
DYefremov
65c24a324a get fav id fix 2020-06-13 01:01:20 +03:00
DYefremov
78b0cc1517 added space item to fav elements 2020-06-12 22:37:29 +03:00
DYefremov
d66d4e6402 added frame for info bar 2020-06-11 10:44:53 +03:00
DYefremov
7b11822664 small http api init fix 2020-06-10 18:35:49 +03:00
DYefremov
8c5f27cc8a added basic youtube-dl support 2020-06-10 11:10:41 +03:00
DYefremov
b51c8a9aed added eserviceuri (8193) stream type 2020-06-09 18:38:18 +03:00
DYefremov
2c4302f57d copy tr *.mo file 2020-06-08 23:50:41 +03:00
audi06_19
bd0ac077f9 Turkish translation correction (#26) 2020-06-08 23:50:17 +03:00
DYefremov
105b907392 small rework of screenshot mode 2020-06-08 19:32:18 +03:00
DYefremov
b4fb684af4 impl local removing for picons 2020-06-08 14:32:49 +03:00
DYefremov
20a1bac22e logging extension on data downloading 2020-06-08 13:33:46 +03:00
DYefremov
9a9229f67c added dnd for selective download/send 2020-06-08 13:16:50 +03:00
DYefremov
a941c96c61 added selective download/send of picons 2020-06-07 18:44:46 +03:00
DYefremov
05a6e36589 added update picons dest view 2020-06-06 09:36:11 +03:00
DYefremov
50a5cf6fc3 added accelerator for input dialog 2020-06-04 11:33:26 +03:00
DYefremov
6ef844157e added check for unsaved changes 2020-06-04 11:32:53 +03:00
DYefremov
72583ba879 rm unavailable iptv fix 2020-06-03 11:25:53 +03:00
DYefremov
e7d96b0cbb copy pl *.mo file 2020-06-02 10:01:32 +03:00
DYefremov
a7458494a3 Polish translation correction 2020-06-02 09:37:45 +03:00
Wieslaw Weglowski
747c1a9722 Polish translation update(#23) 2020-06-02 09:15:54 +03:00
DYefremov
dac2fe17a6 improved functionality of the picons explorer 2020-06-01 21:36:56 +03:00
DYefremov
fb8cf6c882 added youtube-dl options 2020-05-29 16:43:23 +03:00
DYefremov
bcf69231a8 added app settings read exception 2020-05-29 13:24:55 +03:00
DYefremov
5352d87b82 zap mode fix 2020-05-28 11:30:32 +03:00
DYefremov
c978f5abab added services filtering from picons manager 2020-05-25 18:25:36 +03:00
DYefremov
636442bcd3 adding picons to src via dnd 2020-05-25 11:42:41 +03:00
DYefremov
9e74f0f525 added basic screenshots support 2020-05-24 18:47:56 +03:00
DYefremov
b7028ae27d modified cas values 2020-05-23 15:17:45 +03:00
DYefremov
4f3b05ede5 added space [hidden marker] support 2020-05-23 15:16:31 +03:00
DYefremov
e6e6d3510d version update 2020-05-23 15:01:55 +03:00
DYefremov
1d1b4acdca small bq parsing changes (prevent #12) 2020-05-23 14:23:20 +03:00
55 changed files with 4988 additions and 1080 deletions

View File

@@ -1,10 +1,12 @@
# <img src="app/ui/icons/hicolor/96x96/apps/demon-editor.png" width="32" /> DemonEditor
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
## Enigma2 channel and satellites list editor for GNU/Linux.
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) ![platform](https://img.shields.io/badge/platform-linux%20|%20macos-lightgrey)
### Enigma2 channel and 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)
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc).
![Main app window in macOS Big Sur.](https://user-images.githubusercontent.com/7511379/92320982-9b20c780-f02e-11ea-8a43-fc0c70503573.png)
### Main features of the program:
## Main features of the program
* Editing bouquets, channels, satellites.
* Import function.
* Backup function.
@@ -13,12 +15,12 @@ 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 EPGs from DVB or XML for IPTV services (only Enigma2, experimental).
* Assignment of EPG from DVB or XML for IPTV services (only Enigma2, experimental).
* Preview (playback) of IPTV or other streams directly from the bouquet list (should be installed [VLC](https://www.videolan.org/vlc/)).
### Keyboard shortcuts:
#### Keyboard shortcuts
* **Ctrl + X** - only in bouquet list.
* **Ctrl + C** - only in services 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.
@@ -30,40 +32,50 @@ Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **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.
* **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 + 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 + 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.
* **Ctrl + Shift + F** - show/hide filter bar.
For **multiple** selection with the mouse, press and hold the **Ctrl** key!
For multiple mouse selection (including Drag and Drop), press and hold the **Ctrl** key!
### Minimum requirements:
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings, python3-requests.
### Launching:
To start the program, in most cases it is enough to download the archive, unpack and run it by
double clicking on DemonEditor.desktop in the root directory, or launching from the console
with the command: ```./start.py```
## Minimum requirements
*Python >= 3.5.2, GTK+ >= 3.16 with PyGObject bindings, python3-requests.*
## 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*!
### Note:
To create a simple **debian package**, you can use the *build-deb.sh.*
Users of **LTS** versions of [Ubuntu](https://ubuntu.com/) or those based on them can use [PPA](https://launchpad.net/~dmitriy-yefremov/+archive/ubuntu/demon-editor) repository.
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!
The program is tested only with [openATV](https://www.opena.tv/) image and **Formuler F1** receiver in my favourite Linux distributions
(the latest versions of [Linux Mint](https://linuxmint.com/) 18.* and 19* MATE 64-bit)!
### Important:
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2!
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.
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.

View File

@@ -20,8 +20,14 @@ def init_logger():
log("Logging is enabled.", level=logging.INFO)
def log(message, level=logging.ERROR):
logging.getLogger(_LOGGER_NAME).log(level, message)
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 run_idle(func):

View File

@@ -51,6 +51,7 @@ class HttpRequestType(Enum):
PLAYER_PREV = "mediaplayercmd?command=previous"
PLAYER_STOP = "mediaplayercmd?command=stop"
PLAYER_REMOVE = "mediaplayerremove?file="
GRUB = "grab?format=jpg&"
class TestException(Exception):
@@ -61,7 +62,7 @@ class HttpApiException(Exception):
pass
def download_data(*, settings, download_type=DownloadType.ALL, callback=print):
def download_data(*, settings, download_type=DownloadType.ALL, callback=print, files_filter=None):
with FTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
ftp.encoding = "utf-8"
callback("FTP OK.\n")
@@ -71,8 +72,7 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=print):
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
for file in filter(lambda f: f.endswith(file_list), ftp.nlst()):
download_file(ftp, file, save_path, callback)
download_files(ftp, save_path, file_list, 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)
@@ -82,7 +82,7 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=print):
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)
download_picons(ftp, settings.picons_path, picons_path, callback, files_filter)
# epg.dat
if download_type is DownloadType.EPG:
stb_path = settings.services_path
@@ -92,14 +92,13 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=print):
save_path = epg_options.get("epg_dat_path", save_path)
ftp.cwd(stb_path)
for file in filter(lambda f: f.endswith("epg.dat"), ftp.nlst()):
download_file(ftp, file, save_path, callback)
download_files(ftp, save_path, "epg.dat", callback)
callback("\nDone.\n")
def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False,
callback=print, done_callback=None, use_http=False):
callback=print, done_callback=None, use_http=False, files_filter=None):
s_type = settings.setting_type
data_path = settings.data_local_path
host = settings.host
@@ -118,6 +117,8 @@ 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... "))
@@ -127,14 +128,15 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
ht.send((url + "powerstate?newstate=0", "Toggle Standby "))
time.sleep(2)
else:
# 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")
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
tn.send("init 4")
with FTP(host=host, user=settings.user, passwd=settings.password) as ftp:
ftp.encoding = "utf-8"
@@ -162,7 +164,7 @@ 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)
upload_picons(ftp, settings.picons_local_path, settings.picons_path, callback, files_filter)
if tn and not use_http:
# resume enigma or restart neutrino
@@ -200,11 +202,10 @@ def upload_files(ftp, data_path, file_list, callback):
def remove_unused_bouquets(ftp, callback):
files = []
ftp.dir(files.append)
for file in files:
name = str(file).strip()
if name.endswith(("tv", "radio", "bouquets.xml", "ubouquets.xml")):
name = name.split()[-1]
callback("Deleting file: {}. Status: {}\n".format(name, ftp.delete(name)))
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)))
def upload_xml(ftp, data_path, xml_path, xml_files, callback):
@@ -217,12 +218,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)
list(map(lambda f: download_file(ftp, f, data_path, callback), (f for f in ftp.nlst() if f.endswith(xml_files))))
download_files(ftp, data_path, xml_files, callback)
# ***************** Picons *******************#
def upload_picons(ftp, src, dest, callback):
def upload_picons(ftp, src, dest, callback, files_filter=None):
try:
ftp.cwd(dest)
except error_perm as e:
@@ -230,25 +231,25 @@ def upload_picons(ftp, src, dest, callback):
ftp.mkd(dest) # if not exist
ftp.cwd(dest)
delete_picons(ftp, callback)
for file_name in os.listdir(src):
if file_name.endswith(PICONS_SUF):
send_file(file_name, src, ftp, callback)
for file_name in filter(picons_filter_function(files_filter), os.listdir(src)):
send_file(file_name, src, ftp, callback)
def download_picons(ftp, src, dest, callback):
def download_picons(ftp, src, dest, callback, files_filter=None):
try:
ftp.cwd(src)
except error_perm as e:
callback(str(e))
return
for file in filter(lambda f: f.endswith(PICONS_SUF), ftp.nlst()):
files = []
ftp.dir(files.append)
for file in filter(picons_filter_function(files_filter), map(lambda f: f.split()[-1], map(str.rstrip, files))):
download_file(ftp, file, dest, callback)
def delete_picons(ftp, callback, dest=None):
def delete_picons(ftp, callback, dest=None, files_filter=None):
if dest:
try:
ftp.cwd(dest)
@@ -258,22 +259,33 @@ def delete_picons(ftp, callback, dest=None):
files = []
ftp.dir(files.append)
for file in files:
name = str(file).strip()
if name.endswith(PICONS_SUF):
name = name.split()[-1]
callback("Delete file: {}. Status: {}\n".format(name, ftp.delete(name)))
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)))
def remove_picons(*, settings, callback, done_callback=None):
def remove_picons(*, settings, callback, done_callback=None, files_filter=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)
delete_picons(ftp, callback, settings.picons_path, files_filter)
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))))
@@ -331,45 +343,65 @@ class HttpAPI:
__MAX_WORKERS = 4
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 HttpRequestType.ZAP or req_type is HttpRequestType.STREAM:
url += urllib.parse.quote(ref)
elif req_type is HttpRequestType.PLAY or req_type is HttpRequestType.PLAYER_REMOVE:
url += "{}{}".format(ref_prefix, urllib.parse.quote(ref).replace("%3A", "%253A"))
elif req_type is HttpRequestType.GRUB:
data = None # Must be disabled for token-based security.
url = "{}/{}{}".format(self._main_url, req_type.value, ref)
def done_callback(f):
callback(f.result())
future = self._executor.submit(get_response, req_type, url, self._data)
future = self._executor.submit(get_response, req_type, url, 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
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)
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, 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(HttpRequestType.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
@@ -381,6 +413,8 @@ def get_response(req_type, url, data=None):
with urlopen(Request(url, data=data), timeout=10) as f:
if req_type is HttpRequestType.STREAM or req_type is HttpRequestType.STREAM_CURRENT:
return {"m3u": f.read().decode("utf-8")}
elif req_type is HttpRequestType.GRUB:
return {"img_data": f.read()}
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

View File

@@ -13,6 +13,7 @@ class BqServiceType(Enum):
DEFAULT = "DEFAULT"
IPTV = "IPTV"
MARKER = "MARKER" # 64
SPACE = "SPACE" # 832 [hidden marker]
Bouquet = namedtuple("Bouquet", ["name", "type", "services", "locked", "hidden"])
@@ -135,7 +136,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"}
@@ -145,10 +146,8 @@ 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: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"}
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"}
# 'on' attribute 0070(hex) = 112(int) = ONID(ONID-TID on www.lyngsat.com)
PROVIDER = {112: "HTB+", 253: "Tricolor TV"}

View File

@@ -24,7 +24,8 @@ 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_()]+")
current_marker = [0]
m_index = [0]
s_index = [0]
for bqs in bouquets:
line.clear()
@@ -37,24 +38,30 @@ 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, current_marker)
write_bouquet(path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services, m_index, s_index)
with open(path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
file.writelines(line)
def write_bouquet(path, name, services, current_marker):
def write_bouquet(path, name, services, current_marker, current_space):
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:
if srv.service_type == BqServiceType.IPTV.name:
s_type = srv.service_type
if s_type == BqServiceType.IPTV.name:
bouquet.append("#SERVICE {}\n".format(srv.fav_id.strip()))
elif srv.service_type == BqServiceType.MARKER.name:
elif s_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:
@@ -75,28 +82,39 @@ def to_bouquet_id(srv):
return "{}:0:{:X}:{}:0:0:0:".format(1, data_type, srv.fav_id)
def get_bouquet(path, name, bq_type):
def get_bouquet(path, bq_name, bq_type):
""" Parsing services ids from bouquet file. """
with open(path + "userbouquet.{}.{}".format(name, bq_type), encoding="utf-8", errors="replace") as file:
with open(path + "userbouquet.{}.{}".format(bq_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 ['']
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(ch_data[3], ch_data[4], ch_data[5], ch_data[6])
name = None
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))
# 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
return srvs[0].lstrip("#NAME").strip(), 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))
else:
fav_id = "{}:{}:{}:{}".format(srv_data[3], srv_data[4], srv_data[5], srv_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))
return bq_name.lstrip("#NAME").strip(), services
def parse_bouquets(path, bq_name, bq_type):

View File

@@ -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,6 +18,7 @@ class StreamType(Enum):
NONE_TS = "4097"
NONE_REC_1 = "5001"
NONE_REC_2 = "5002"
E_SERVICE_URI = "8193"
def parse_m3u(path, s_type):
@@ -64,7 +65,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(urllib.request.unquote(data.strip())))
lines.append("{}\n".format(unquote(data.strip())))
elif s_type is BqServiceType.MARKER:
current_grp = "#EXTGRP:{}\n".format(s.name)
@@ -75,9 +76,8 @@ 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, url, service_name, service_name, None)
return ENIGMA2_FAV_ID_FORMAT.format(stream_type, 1, 0, 0, 0, 0, quote(url), service_name, service_name, None)
elif s_type is SettingsType.NEUTRINO_MP:
return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1)

View File

@@ -39,8 +39,6 @@ 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())
@@ -77,6 +75,7 @@ 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=" ")
@@ -125,6 +124,10 @@ class SettingsException(Exception):
pass
class SettingsReadException(SettingsException):
pass
class PlayStreamsMode(IntEnum):
""" Behavior mode when opening streams. """
BUILT_IN = 0
@@ -137,7 +140,10 @@ class Settings:
__VERSION = 1
def __init__(self, ext_settings=None):
settings = ext_settings or get_settings()
try:
settings = ext_settings or get_settings()
except PermissionError as e:
raise SettingsReadException(e)
if self.__VERSION > settings.get("version", 0):
raise SettingsException("Outdated version of the settings format!")
@@ -522,6 +528,14 @@ 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)
@@ -598,6 +612,14 @@ 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)
@@ -636,6 +658,45 @@ 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

View File

@@ -2,11 +2,10 @@ import glob
import os
import re
import shutil
from collections import namedtuple
from html.parser import HTMLParser
from app.commons import run_task
from app.commons import run_task, log
from app.settings import SettingsType
_ENIGMA2_PICON_KEY = "{:X}:{:X}:{}"
@@ -33,9 +32,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])
@@ -46,16 +45,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)
@@ -80,6 +79,10 @@ 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")
@@ -105,8 +108,7 @@ 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)
print(msg)
log(msg)
@staticmethod
def format(ssid, on_id, namespace, picon_ids, s_type):
@@ -125,10 +127,7 @@ class ProviderParser(HTMLParser):
_POSITION_PATTERN = re.compile("at\s\d+\..*(?:E|W)']")
_ONID_TID_PATTERN = re.compile("^\d+-\d+.*")
_TRANSPONDER_FREQUENCY_PATTERN = re.compile("^\d+ [HVLR]+")
_DOMAIN = "http://www.lyngsat.com"
_TV_DOMAIN = _DOMAIN + "/tvchannels/"
_RADIO_DOMAIN = _DOMAIN + "/radiochannels/"
_PKG_DOMAIN = _DOMAIN + "/packages/"
_DOMAINS = {"/tvchannels/", "/radiochannels/", "/packages/"}
def __init__(self, entities=False, separator=' '):
@@ -160,7 +159,7 @@ class ProviderParser(HTMLParser):
self._current_row.append(attrs[0][1])
if tag == "a":
url = attrs[0][1]
if url.startswith((self._PKG_DOMAIN, self._TV_DOMAIN, self._RADIO_DOMAIN)):
if any(d in url for d in self._DOMAINS):
self._current_row.append(url)
if tag == "font" and len(attrs) == 1:
atr = attrs[0]

View File

@@ -1,16 +1,21 @@
""" Module for working with YouTube service """
import gzip
import json
import os
import re
import urllib
import shutil
import sys
from html.parser import HTMLParser
from json import JSONDecodeError
from urllib.request import Request
from urllib.error import URLError
from urllib.parse import unquote
from urllib.request import Request, urlopen, urlretrieve
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-]{23,})?.*")
_YT_LIST_PATTERN = re.compile(r"https://www.youtube.com/.+?(?:list=)([\w-]{18,})?.*")
_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",
@@ -20,9 +25,35 @@ 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)
@@ -41,17 +72,29 @@ class YouTube:
if yt:
return yt.group(1)
@staticmethod
def get_yt_link(video_id):
""" Getting link to YouTube video by id.
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
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)
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)
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))}
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.
Returns tuple from the video links dict and title.
"""
req = Request(YouTube._VIDEO_INFO_LINK.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))}
player_resp = out.get("player_response", None)
if player_resp:
@@ -76,7 +119,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 = urllib.request.unquote(url) if url else "", title.replace("+", " ") if title else ""
url, title = unquote(url) if url else "", title.replace("+", " ") if title else ""
if url and title:
return {Quality[0]: url}, title.replace("+", " ")
@@ -121,7 +164,7 @@ class PlayListParser(HTMLParser):
ct = resp.get("contents", None)
if ct:
for d in [(d.get("title", {}).get("simpleText", ""),
for d in [(d.get("title", {}).get("runs", [{}])[0].get("text", ""),
d.get("videoId", "")) for d in flat("playlistVideoRenderer", ct)]:
self._playlist.append(d)
self._is_script = False
@@ -145,13 +188,152 @@ class PlayListParser(HTMLParser):
"""
request = Request("https://www.youtube.com/playlist?list={}&hl=en".format(play_list_id), headers=_HEADERS)
with urllib.request.urlopen(request, timeout=2) as resp:
with 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:

View File

@@ -59,14 +59,13 @@ class BackupDialog:
def show(self):
self._dialog_window.show()
@run_idle
def init_data(self):
try:
files = os.listdir(self._backup_path)
except FileNotFoundError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
for file in filter(lambda x: x.endswith(".zip"), files):
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))
else:
os.makedirs(os.path.dirname(self._backup_path), exist_ok=True)
def on_restore_bouquets(self, item):
self.restore(RestoreType.BOUQUETS)
@@ -129,6 +128,8 @@ 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()

View File

@@ -40,7 +40,7 @@ Author: Dmitriy Yefremov
<property name="icon_name">system-help</property>
<property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property>
<property name="version">0.4.8 Pre-alpha</property>
<property name="version">1.0.1 Beta</property>
<property name="copyright">2018-2020 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellites list editor for GNU/Linux</property>
@@ -108,6 +108,7 @@ Author: Dmitriy Yefremov
<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">
@@ -212,7 +213,7 @@ Author: Dmitriy Yefremov
</packing>
</child>
<style>
<class name="primary-toolbar"/>
<class name="app-notification"/>
</style>
</object>
</child>

View File

@@ -2,6 +2,7 @@
import locale
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
@@ -72,21 +73,23 @@ class WaitDialog:
self._dialog.destroy()
def show_dialog(dialog_type: DialogType, transient, text=None, settings=None, action_type=None, file_filter=None):
""" Shows dialogs by name """
def show_dialog(dialog_type, transient, text=None, settings=None, action_type=None, file_filter=None, buttons=None,
title=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)
return get_file_chooser_dialog(transient, text, settings, action_type, file_filter, buttons, title)
elif dialog_type is DialogType.INPUT:
return get_input_dialog(transient, text)
elif dialog_type is DialogType.QUESTION:
return get_message_dialog(transient, DialogType.QUESTION, Gtk.ButtonsType.OK_CANCEL, text or "Are you sure?")
action = action_type if action_type else Gtk.ButtonsType.OK_CANCEL
return get_message_dialog(transient, DialogType.QUESTION, action, text or "Are you sure?")
elif dialog_type is DialogType.ABOUT:
return get_about_dialog(transient)
def get_chooser_dialog(transient, settings, name, patterns):
def get_chooser_dialog(transient, settings, name, patterns, title=None):
file_filter = Gtk.FileFilter()
file_filter.set_name(name)
for p in patterns:
@@ -96,26 +99,30 @@ def get_chooser_dialog(transient, settings, name, patterns):
transient=transient,
settings=settings,
action_type=Gtk.FileChooserAction.OPEN,
file_filter=file_filter)
file_filter=file_filter,
title=title)
def get_file_chooser_dialog(transient, text, settings, action_type, file_filter):
dialog = Gtk.FileChooserDialog(get_message(text) if text else "", transient,
action_type if action_type is not None else Gtk.FileChooserAction.SELECT_FOLDER,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK),
use_header_bar=IS_GNOME_SESSION)
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 "")
dialog.set_create_folders(False)
if file_filter is not None:
dialog.add_filter(file_filter)
path = settings.data_local_path
dialog.set_current_folder(path)
dialog.set_current_folder(settings.data_local_path)
response = dialog.run()
if response == Gtk.ResponseType.OK:
if dialog.get_filename():
path = dialog.get_filename()
if action_type is not Gtk.FileChooserAction.OPEN:
path = path + "/"
response = path
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())
dialog.destroy()
return response

View File

@@ -357,6 +357,7 @@ 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>
@@ -387,6 +388,7 @@ 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>

View File

@@ -2,7 +2,7 @@ import os
from gi.repository import GLib
from app.commons import run_idle, run_task
from app.commons import run_idle, run_task, log
from app.connections import download_data, DownloadType, upload_data
from app.settings import SettingsType
from app.ui.backup import backup_data, restore_data
@@ -24,6 +24,8 @@ 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()
@@ -64,7 +66,6 @@ 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)
@@ -72,7 +73,8 @@ 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)
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)
def update_profiles(self):
self._profile_combo_box.remove_all()
@@ -143,13 +145,19 @@ 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 """
self._expander.set_expanded(True)
GLib.idle_add(self._expander.set_expanded, True)
self.clear_output()
backup, backup_src, data_path = self._settings.backup_before_downloading, None, None
@@ -171,8 +179,9 @@ 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:
message = str(getattr(e, "message", str(e)))
self.show_info_message(message, Gtk.MessageType.ERROR)
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)
if all((download, backup, data_path)):
restore_data(backup_src, data_path)
else:

View File

@@ -1,7 +1,7 @@
from contextlib import suppress
from pathlib import Path
from app.commons import run_idle
from app.commons import run_idle, log
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):
def import_bouquet(transient, model, path, settings, services, appender, file_path=None):
""" 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):
elif bq_type is BqType.WEBTV:
f_pattern = "webtv.xml"
file_path = get_chooser_dialog(transient, settings, "bouquet files", (f_pattern,))
file_path = file_path or get_chooser_dialog(transient, settings, "bouquet files", (f_pattern,))
if file_path == Gtk.ResponseType.CANCEL:
return
@@ -119,6 +119,7 @@ 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:
@@ -129,6 +130,7 @@ 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):
@@ -139,9 +141,17 @@ 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]:
@@ -151,19 +161,16 @@ 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()

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2019 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
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkDialog" id="search_unavailable_streams_dialog">
<property name="use-header-bar">1</property>
@@ -48,9 +48,6 @@ 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>
@@ -222,6 +219,9 @@ 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">
@@ -267,9 +267,6 @@ Author: Dmitriy Yefremov
<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>

View File

@@ -2,7 +2,7 @@ import concurrent.futures
import re
import urllib
from urllib.error import HTTPError
from urllib.parse import urlparse
from urllib.parse import urlparse, unquote, quote
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 YouTube, PlayListParser
from app.tools.yt import PlayListParser, YouTubeException, YouTube
from .dialogs import Action, show_dialog, DialogType, get_dialogs_string, get_message
from .main_helper import get_base_model, get_iptv_url, on_popup_menu
from .uicommons import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON, Column, IS_GNOME_SESSION, KeyboardKey, \
get_yt_icon
from .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,12 +38,14 @@ def get_stream_type(box):
return StreamType.NONE_TS.value
elif active == 2:
return StreamType.NONE_REC_1.value
return StreamType.NONE_REC_2.value
elif active == 3:
return StreamType.NONE_REC_2.value
return StreamType.E_SERVICE_URI.value
class IptvDialog:
def __init__(self, transient, view, services, bouquet, profile=SettingsType.ENIGMA_2, action=Action.ADD):
def __init__(self, transient, view, services, bouquet, settings, action=Action.ADD):
handlers = {"on_response": self.on_response,
"on_entry_changed": self.on_entry_changed,
"on_url_changed": self.on_url_changed,
@@ -52,18 +54,20 @@ 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")
@@ -91,7 +95,7 @@ class IptvDialog:
for el in self._digit_elems:
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
if profile is SettingsType.NEUTRINO_MP:
if self._s_type 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)
@@ -104,8 +108,8 @@ class IptvDialog:
if self._action is Action.ADD:
self._save_button.set_visible(False)
self._add_button.set_visible(True)
if self._profile is SettingsType.ENIGMA_2:
self._update_reference_entry()
if self._s_type 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][:]
@@ -115,7 +119,7 @@ class IptvDialog:
self._dialog.run()
def on_response(self, dialog, response):
if response == Gtk.ResponseType.CANCEL:
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
self._dialog.destroy()
def on_save(self, item):
@@ -126,16 +130,16 @@ class IptvDialog:
self.show_info_message(get_message("Error. Verify the data!"), Gtk.MessageType.ERROR)
return
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
if show_dialog(DialogType.QUESTION, self._dialog) in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
self.save_enigma2_data() if self._profile is SettingsType.ENIGMA_2 else self.save_neutrino_data()
self.save_enigma2_data() if self._s_type is SettingsType.ENIGMA_2 else self.save_neutrino_data()
self._dialog.destroy()
def init_data(self, srv):
name, fav_id = srv[2], srv[7]
self._name_entry.set_text(name)
self.init_enigma2_data(fav_id) if self._profile is SettingsType.ENIGMA_2 else self.init_neutrino_data(fav_id)
self.init_enigma2_data(fav_id) if self._s_type 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")
@@ -155,6 +159,8 @@ 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)
@@ -163,16 +169,17 @@ 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(urllib.request.unquote(data[10].strip()))
self._update_reference_entry()
self._url_entry.set_text(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._profile is SettingsType.ENIGMA_2:
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)
self._reference_entry.set_text(_ENIGMA2_REFERENCE.format(self.get_type(),
self._srv_type_entry.get_text(),
int(self._sid_entry.get_text()),
@@ -188,12 +195,13 @@ 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)
entry.set_name("GtkEntry" if all([url.scheme, url.netloc, url.path]) else _DIGIT_ENTRY_NAME)
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)
yt_id = YouTube.get_yt_id(url_str)
if yt_id:
@@ -211,10 +219,21 @@ class IptvDialog:
def set_yt_url(self, entry, video_id):
try:
links, title = YouTube.get_yt_link(video_id)
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
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)
@@ -232,7 +251,9 @@ class IptvDialog:
yield True
def on_stream_type_changed(self, item):
self._update_reference_entry()
if self.get_type() == StreamType.E_SERVICE_URI.value:
self.show_info_message("DreamOS only!", Gtk.MessageType.WARNING)
self.update_reference_entry()
def on_yt_quality_changed(self, box):
model = box.get_model()
@@ -248,7 +269,7 @@ class IptvDialog:
int(self._tr_id_entry.get_text()),
int(self._net_id_entry.get_text()),
int(self._namespace_entry.get_text()),
urllib.request.quote(self._url_entry.get_text()),
quote(self._url_entry.get_text()),
name, name)
self.update_bouquet_data(name, fav_id)
@@ -291,7 +312,7 @@ class IptvDialog:
class SearchUnavailableDialog:
def __init__(self, transient, model, fav_bouquet, iptv_rows, profile):
def __init__(self, transient, model, fav_bouquet, iptv_rows, s_type):
handlers = {"on_response": self.on_response}
builder = Gtk.Builder()
@@ -305,7 +326,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._profile = profile
self._s_type = s_type
self._iptv_rows = iptv_rows
self._counter = -1
self._max_rows = len(self._iptv_rows)
@@ -333,7 +354,7 @@ class SearchUnavailableDialog:
if not self._download_task:
return
try:
req = Request(get_iptv_url(row, self._profile))
req = Request(get_iptv_url(row, self._s_type))
self.update_bar()
urlopen(req, timeout=2)
except HTTPError as e:
@@ -375,7 +396,7 @@ class SearchUnavailableDialog:
class IptvListConfigurationDialog:
def __init__(self, transient, services, iptv_rows, bouquet, fav_model, profile):
def __init__(self, transient, services, iptv_rows, bouquet, fav_model, s_type):
handlers = {"on_apply": self.on_apply,
"on_response": self.on_response,
"on_stream_type_default_togged": self.on_stream_type_default_togged,
@@ -389,18 +410,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")
@@ -487,7 +508,7 @@ class IptvListConfigurationDialog:
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return
if self._profile is SettingsType.ENIGMA_2:
if self._s_type is SettingsType.ENIGMA_2:
reset = self._reset_to_default_switch.get_active()
type_default = self._type_check_button.get_active()
tid_default = self._tid_check_button.get_active()
@@ -541,7 +562,7 @@ class IptvListConfigurationDialog:
class YtListImportDialog:
def __init__(self, transient, profile, appender):
def __init__(self, transient, settings, appender):
handlers = {"on_import": self.on_import,
"on_receive": self.on_receive,
"on_yt_url_entry_changed": self.on_url_entry_changed,
@@ -553,6 +574,14 @@ 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),
@@ -583,12 +612,6 @@ 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()
@@ -602,7 +625,11 @@ class YtListImportDialog:
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
done_links = {}
rows = list(filter(lambda r: r[2], self._model))
futures = {executor.submit(YouTube.get_yt_link, r[1]): r for r in rows}
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}
size = len(futures)
counter = 0
@@ -614,6 +641,8 @@ 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:
@@ -647,8 +676,8 @@ class YtListImportDialog:
self.update_active_elements(True)
def update_links(self, links):
for l in links:
yield self._model.append((l[0], l[1], True, None))
for link in links:
yield self._model.append((link[0], link[1], True, None))
size = len(self._model)
self._yt_count_label.set_text(str(size))
@@ -668,11 +697,11 @@ class YtListImportDialog:
act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0)
for link in links:
lnk, title = link
lnk, title = link or (None, None)
if not lnk:
continue
ln = lnk.get(act) if act in lnk else lnk[sorted(lnk, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
fav_id = get_fav_id(ln, title, self._profile)
fav_id = get_fav_id(ln, title, self._s_type)
srv = Service(None, None, IPTV_ICON, title, *aggr[0:3], BqServiceType.IPTV.name, *aggr, None, fav_id, None)
srvs.append(srv)
self.appender(srvs)

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
""" This is helper module for ui """
""" Helper module for the ui. """
import os
import shutil
import urllib.request
from urllib.parse import unquote
from gi.repository import GdkPixbuf, GLib
@@ -16,23 +16,28 @@ from .uicommons import ViewTarget, BqGenType, Gtk, Gdk, HIDE_ICON, LOCKED_ICON,
# ***************** Markers *******************#
def insert_marker(view, bouquets, selected_bouquet, services, parent_window):
def insert_marker(view, bouquets, selected_bouquet, services, parent_window, m_type=BqServiceType.MARKER):
"""" Inserts marker into bouquet services list. """
response = show_dialog(DialogType.INPUT, parent_window)
if response == Gtk.ResponseType.CANCEL:
return
fav_id, text = "1:832:D:0:0:0:0:0:0:0:\n", None
if not response.strip():
show_dialog(DialogType.ERROR, parent_window, "The text of marker is empty, please try again!")
return
if m_type is BqServiceType.MARKER:
response = show_dialog(DialogType.INPUT, parent_window)
if response == Gtk.ResponseType.CANCEL:
return
fav_id = "1:64:0:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n".format(response, response)
s_type = BqServiceType.MARKER.name
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
model, paths = view.get_selection().get_selected_rows()
marker = (None, None, response, None, None, s_type, None, fav_id, None, None, None)
marker = (None, None, text, None, None, s_type, None, fav_id, None, None, None)
itr = model.insert_before(model.get_iter(paths[0]), marker) if paths else model.insert(0, marker)
bouquets[selected_bouquet].insert(model.get_path(itr)[0], fav_id)
services[fav_id] = Service(None, None, None, response, None, None, None, s_type, *[None] * 9, 0, fav_id, None)
services[fav_id] = Service(None, None, None, text, None, None, None, s_type, *[None] * 9, 0, fav_id, None)
# ***************** Movement *******************#
@@ -41,54 +46,57 @@ 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
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 = 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:
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 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)
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):
@@ -156,7 +164,8 @@ def rename(view, parent_window, target, fav_view=None, service_view=None, servic
return
srv_name = response
model.set_value(itr, Column.FAV_SERVICE, response)
if not model.get_value(itr, Column.FAV_BACKGROUND):
model.set_value(itr, Column.FAV_SERVICE, response)
if service_view is not None:
for row in get_base_model(service_view.get_model()):
@@ -208,6 +217,7 @@ 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:
@@ -236,13 +246,14 @@ 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:
bq_id = to_bouquet_id(srv)
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 not bq_id:
continue
blacklist.discard(bq_id) if locked else blacklist.add(bq_id)
@@ -362,18 +373,20 @@ def append_picons(picons, model):
GLib.idle_add(lambda: next(app, False), priority=GLib.PRIORITY_LOW)
def assign_picon(target, srv_view, fav_view, transient, picons, settings, services, p_path=None):
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. """
view = srv_view if target is ViewTarget.SERVICES else fav_view
model, paths = view.get_selection().get_selected_rows()
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 src_path:
src_path = get_chooser_dialog(transient, settings, "*.png files", ("*.png",))
if src_path == Gtk.ResponseType.CANCEL:
return picons_files
if not str(p_path).endswith(".png") or not os.path.isfile(p_path):
if not str(src_path).endswith(".png") or not os.path.isfile(src_path):
show_dialog(DialogType.ERROR, transient, text="No png file is selected!")
return
return picons_files
p_pos = Column.SRV_PICON
col_num = Column.SRV_FAV_ID if target is ViewTarget.SERVICES else Column.FAV_ID
@@ -389,24 +402,32 @@ def assign_picon(target, srv_view, fav_view, transient, picons, settings, servic
picon_id = services.get(fav_id)[Column.SRV_PICON_ID]
if picon_id:
picons_path = settings.picons_local_path
picons_path = dst_path or settings.picons_local_path
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
picon_file = picons_path + picon_id
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)
try:
shutil.copy(src_path, picon_file)
except shutil.SameFileError:
pass # NOP
else:
set_picon(fav_id, get_base_model(srv_view.get_model()), picon, Column.SRV_FAV_ID, p_pos)
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
def set_picon(fav_id, model, picon, fav_id_pos, picon_pos):
for row in model:
if row[fav_id_pos] == fav_id:
row[picon_pos] = picon
break
return True
return True
def remove_picon(target, srv_view, fav_view, picons, settings):
@@ -579,12 +600,28 @@ def update_entry_data(entry, dialog, settings):
def get_base_model(model):
""" Returns base tree model if has wrappers ("TreeModelSort" and "TreeModelFilter") """
""" Returns base tree model if has wrappers [TreeModelSort, 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())
@@ -607,7 +644,7 @@ def get_iptv_url(row, s_type):
data = list(filter(lambda x: "http" in x, data))
if data:
url = data[0]
return urllib.request.unquote(url) if s_type is SettingsType.ENIGMA_2 else url
return unquote(url) if s_type is SettingsType.ENIGMA_2 else url
def on_popup_menu(menu, event):

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1
<!-- Generated with glade 3.22.2
The MIT License (MIT)
@@ -307,6 +307,11 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="icon_name">insert-link</property>
</object>
<object class="GtkImage" id="insert_space_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">format-text-underline</property>
</object>
<object class="GtkImage" id="insert_text_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -411,7 +416,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">0</property>
</packing>
</child>
<child>
@@ -425,7 +430,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
<property name="position">1</property>
</packing>
</child>
<child>
@@ -436,7 +441,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
<property name="position">2</property>
</packing>
</child>
<child>
@@ -450,7 +455,21 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="open_archive_menu_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">app.on_archive_open</property>
<property name="text" translatable="yes">Extract...</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
@@ -648,6 +667,70 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="stock">gtk-save-as</property>
</object>
<object class="GtkPopoverMenu" id="screenshots_menu">
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="screenshots_menu_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkModelButton" id="screenshot_button_all">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">app.on_screenshot_all</property>
<property name="text" translatable="yes">All</property>
<property name="centered">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="screenshot_button_video">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">app.on_screenshot_video</property>
<property name="text" translatable="yes">Video</property>
<property name="centered">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="screenshot_button_osd">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">app.on_screenshot_osd</property>
<property name="text" translatable="yes">OSD</property>
<property name="centered">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="submenu">main</property>
<property name="position">1</property>
</packing>
</child>
</object>
<object class="GtkImage" id="select_all_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -955,7 +1038,7 @@ Author: Dmitriy Yefremov
<object class="GtkTreeModelFilter" id="services_model_filter">
<property name="child_model">services_list_store</property>
</object>
<object class="GtkTreeModelSort" id="services_model_tree_model_sort">
<object class="GtkTreeModelSort" id="services_model_sort">
<property name="model">services_model_filter</property>
</object>
<object class="GtkApplicationWindow" id="main_window">
@@ -1802,7 +1885,7 @@ Author: Dmitriy Yefremov
<object class="GtkTreeView" id="services_tree_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">services_model_tree_model_sort</property>
<property name="model">services_model_sort</property>
<property name="enable_search">False</property>
<property name="search_column">3</property>
<property name="rubber_banding">True</property>
@@ -1811,15 +1894,17 @@ Author: Dmitriy Yefremov
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="services_popup_menu" swapped="no"/>
<signal name="button-press-event" handler="on_view_press" swapped="no"/>
<signal name="drag-begin" handler="on_view_drag_begin" swapped="no"/>
<signal name="button-release-event" handler="on_view_release" swapped="no"/>
<signal name="drag-begin" handler="on_view_drag_begin" after="yes" swapped="no"/>
<signal name="drag-data-get" handler="on_view_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_services_view_drag_data_received" swapped="no"/>
<signal name="drag-drop" handler="on_services_view_drag_drop" swapped="no"/>
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
<signal name="focus-in-event" handler="on_view_focus" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<signal name="key-release-event" handler="on_tree_view_key_release" swapped="no"/>
<signal name="query-tooltip" handler="on_services_view_query_tooltip" swapped="no"/>
<signal name="row-activated" handler="on_services_selection" object="services_list_store" swapped="no"/>
<signal name="row-activated" handler="on_services_selection" object="services_model_sort" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="services_selection">
<property name="mode">multiple</property>
@@ -2356,9 +2441,11 @@ Author: Dmitriy Yefremov
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_fav_press" object="fav_popup_menu" swapped="no"/>
<signal name="button-press-event" handler="on_view_press" swapped="yes"/>
<signal name="drag-begin" handler="on_view_drag_begin" swapped="no"/>
<signal name="button-release-event" handler="on_view_release" swapped="no"/>
<signal name="drag-begin" handler="on_view_drag_begin" after="yes" swapped="no"/>
<signal name="drag-data-get" handler="on_view_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_view_drag_data_received" swapped="no"/>
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
<signal name="focus-in-event" handler="on_view_focus" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<signal name="key-release-event" handler="on_tree_view_key_release" swapped="no"/>
@@ -2369,11 +2456,13 @@ Author: Dmitriy Yefremov
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="num_column">
<object class="GtkTreeViewColumn" id="fav_num_column">
<property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">Num</property>
<property name="clickable">True</property>
<property name="alignment">0.5</property>
<signal name="clicked" handler="on_fav_sort" swapped="no"/>
<child>
<object class="GtkCellRendererText" id="num_cellrenderertext">
<property name="xalign">0.20000000298023224</property>
@@ -2382,6 +2471,7 @@ Author: Dmitriy Yefremov
</object>
<attributes>
<attribute name="cell-background-rgba">10</attribute>
<attribute name="visible">0</attribute>
<attribute name="text">0</attribute>
</attributes>
</child>
@@ -2393,7 +2483,9 @@ Author: Dmitriy Yefremov
<property name="min_width">50</property>
<property name="title" translatable="yes">Service</property>
<property name="expand">True</property>
<property name="clickable">True</property>
<property name="alignment">0.5</property>
<signal name="clicked" handler="on_fav_sort" swapped="no"/>
<child>
<object class="GtkCellRendererPixbuf" id="fav_picon_cellrendererpixbuf">
<property name="xpad">2</property>
@@ -2448,7 +2540,9 @@ Author: Dmitriy Yefremov
<property name="min_width">25</property>
<property name="title" translatable="yes">Type</property>
<property name="expand">True</property>
<property name="clickable">True</property>
<property name="alignment">0.5</property>
<signal name="clicked" handler="on_fav_sort" swapped="no"/>
<child>
<object class="GtkCellRendererText" id="type_cellrenderertext">
<property name="xalign">0.50999999046325684</property>
@@ -2465,7 +2559,9 @@ Author: Dmitriy Yefremov
<property name="min_width">25</property>
<property name="title" translatable="yes">Pos</property>
<property name="expand">True</property>
<property name="clickable">True</property>
<property name="alignment">0.5</property>
<signal name="clicked" handler="on_fav_sort" swapped="no"/>
<child>
<object class="GtkCellRendererText" id="pos_cellrenderertext">
<property name="xalign">0.50999999046325684</property>
@@ -2632,9 +2728,11 @@ Author: Dmitriy Yefremov
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="bouquets_popup_menu" swapped="no"/>
<signal name="button-press-event" handler="on_view_press" swapped="yes"/>
<signal name="drag-begin" handler="on_view_drag_begin" swapped="no"/>
<signal name="button-release-event" handler="on_view_release" swapped="no"/>
<signal name="drag-begin" handler="on_view_drag_begin" after="yes" swapped="no"/>
<signal name="drag-data-get" handler="on_view_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_bq_view_drag_data_received" swapped="no"/>
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
<signal name="focus-in-event" handler="on_view_focus" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<signal name="key-release-event" handler="on_tree_view_key_release" swapped="no"/>
@@ -2789,6 +2887,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="orientation">vertical</property>
<signal name="drag-data-received" handler="on_view_drag_data_received" object="services_tree_view" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" object="services_tree_view" swapped="no"/>
<signal name="key-release-event" handler="on_tree_view_key_release" object="services_tree_view" swapped="no"/>
<child>
@@ -2824,7 +2923,7 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="app_ver_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">0.4.8 Pre-alpha</property>
<property name="label" translatable="yes">1.0.1 Beta</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
@@ -2859,6 +2958,8 @@ Author: Dmitriy Yefremov
<object class="GtkImage" id="info_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="stock">gtk-info</property>
</object>
<packing>
@@ -2905,13 +3006,38 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child type="center">
<object class="GtkMenuButton" id="screenshots_button">
<property name="name">status-bar-button</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Screenshot</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="popover">screenshots_menu</property>
<child>
<object class="GtkImage" id="screenshots_menu_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">zoom-best-fit</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkImage" id="http_status_image">
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">No connection to the receiver!</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="icon_name">network-offline</property>
<property name="icon_name">network-offline-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
@@ -2948,6 +3074,8 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Record</property>
<property name="halign">center</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_record" swapped="no"/>
<child>
<object class="GtkImage" id="record_button_image">
@@ -3173,6 +3301,17 @@ Author: Dmitriy Yefremov
<signal name="activate" handler="on_insert_marker" object="fav_tree_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="fav_insert_space_popup_item">
<property name="label" translatable="yes">Insert space</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="image">insert_space_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_insert_space" object="fav_tree_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_4">
<property name="visible">True</property>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1
<!-- Generated with glade 3.22.2
The MIT License (MIT)
@@ -28,24 +28,134 @@ Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.16"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="picons_list_store">
<object class="GtkListStore" id="picons_dest_list_store">
<columns>
<!-- column-name picon -->
<column type="GdkPixbuf"/>
<!-- column-name title -->
<column type="gchararray"/>
<!-- column-name path -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkTreeModelFilter" id="picons_filter_model">
<property name="child_model">picons_list_store</property>
<object class="GtkTreeModelFilter" id="picons_dst_filter_model">
<property name="child_model">picons_dest_list_store</property>
</object>
<object class="GtkTreeModelSort" id="picons_sort_model">
<property name="model">picons_filter_model</property>
<object class="GtkTreeModelSort" id="picons_dst_sort_model">
<property name="model">picons_dst_filter_model</property>
</object>
<object class="GtkMenu" id="picons_dest_view_popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="dest_transfer_to_popup_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Transfer to receiver</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_selective_send" object="picons_dest_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="dest_download_to_popup_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Download from the receiver</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_selective_download" object="picons_dest_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="dest_popup_separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="dest_remove_popup_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Remove</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_local_remove" object="picons_dest_view" swapped="no"/>
<accelerator key="Delete" signal="activate"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="dest_remove_from_popup_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Remove from the receiver</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_selective_remove" object="picons_dest_view" swapped="no"/>
</object>
</child>
</object>
<object class="GtkListStore" id="picons_src_list_store">
<columns>
<!-- column-name picon -->
<column type="GdkPixbuf"/>
<!-- column-name title -->
<column type="gchararray"/>
<!-- column-name path -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkTreeModelFilter" id="picons_src_filter_model">
<property name="child_model">picons_src_list_store</property>
</object>
<object class="GtkTreeModelSort" id="picons_src_sort_model">
<property name="model">picons_src_filter_model</property>
</object>
<object class="GtkMenu" id="picons_src_view_popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="src_transfer_to_popup_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Transfer to receiver</property>
<signal name="activate" handler="on_selective_send" object="picons_src_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="src_download_to_popup_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Download from the receiver</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_selective_download" object="picons_src_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="src_popup_separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="src_remove_popup_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Remove</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_local_remove" object="picons_src_view" swapped="no"/>
<accelerator key="Delete" signal="activate"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="src_remove_from_popup_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Remove from the receiver</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_selective_remove" object="picons_src_view" swapped="no"/>
</object>
</child>
</object>
<object class="GtkListStore" id="providers_list_store">
<columns>
@@ -118,12 +228,11 @@ Author: Dmitriy Yefremov
<object class="GtkHeaderBar" id="header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Picons manager</property>
<property name="spacing">2</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="can_focus">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Cancel</property>
<property name="always_show_image">True</property>
@@ -146,7 +255,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkButton" id="load_providers_button">
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Load providers</property>
<property name="halign">center</property>
@@ -169,7 +278,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkButton" id="receive_button">
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Receive picons</property>
<property name="halign">center</property>
@@ -189,29 +298,20 @@ Author: Dmitriy Yefremov
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="download_box_separator">
<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="send_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Transfer to receiver</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_send" swapped="no"/>
<signal name="drag-data-received" handler="on_send_button_drag_data_received" swapped="no"/>
<child>
<object class="GtkImage" id="send_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-goto-top</property>
<property name="stock">gtk-go-up</property>
</object>
</child>
</object>
@@ -224,10 +324,11 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkButton" id="download_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Download from the receiver</property>
<signal name="clicked" handler="on_download" swapped="no"/>
<signal name="drag-data-received" handler="on_download_button_drag_data_received" swapped="no"/>
<child>
<object class="GtkImage" id="download_button_image">
<property name="visible">True</property>
@@ -245,10 +346,11 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkButton" id="remove_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Remove all picons from the receiver</property>
<signal name="clicked" handler="on_remove" swapped="no"/>
<signal name="drag-data-received" handler="on_remove_button_drag_data_received" swapped="no"/>
<child>
<object class="GtkImage" id="remove_button_image">
<property name="visible">True</property>
@@ -268,9 +370,16 @@ Author: Dmitriy Yefremov
<property name="position">6</property>
</packing>
</child>
<child type="title">
<object class="GtkStackSwitcher" id="sctack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stack">stack</property>
</object>
</child>
<child>
<object class="GtkButton" id="convert_button">
<property name="can_focus">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Convert</property>
<property name="halign">center</property>
@@ -286,13 +395,34 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">3</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="info_check_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Details</property>
<property name="draw_indicator">False</property>
<child>
<object class="GtkImage" id="info_check_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-info</property>
</object>
</child>
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="filter_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Filter</property>
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
@@ -308,7 +438,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">3</property>
<property name="position">4</property>
</packing>
</child>
</object>
@@ -322,7 +452,6 @@ Author: Dmitriy Yefremov
<object class="GtkFrame" id="stack_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="label_xalign">0.5</property>
<property name="shadow_type">none</property>
<child>
@@ -331,135 +460,509 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">10</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<signal name="notify::visible-child" handler="on_visible_page" swapped="no"/>
<child>
<object class="GtkFrame" id="explorer_frame">
<object class="GtkBox" id="explorer_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0.5</property>
<property name="shadow_type">in</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="explorer_box">
<property name="visible">True</property>
<object class="GtkBox" id="filter_service_box">
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<property name="tooltip_text" translatable="yes">Filter services in the main list.</property>
<property name="halign">end</property>
<property name="valign">end</property>
<property name="spacing">5</property>
<child>
<object class="GtkSearchBar" id="search_bar">
<object class="GtkLabel" id="filter_services_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<child>
<object class="GtkSearchEntry" id="picons_search_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">edit-find-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
</object>
</child>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Filter services</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSearchBar" id="filter_bar">
<property name="can_focus">False</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<child>
<object class="GtkSearchEntry" id="picons_filter_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_stock">gtk-spell-check</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<signal name="search-changed" handler="on_picons_filter_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFileChooserButton" id="explorer_path_button">
<object class="GtkSwitch" id="filter_services_switch">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="action">select-folder</property>
<property name="title" translatable="yes"/>
<signal name="current-folder-changed" handler="on_picons_folder_changed" swapped="no"/>
<signal name="file-set" handler="on_picons_folder_changed" swapped="no"/>
<property name="halign">end</property>
<property name="valign">center</property>
<signal name="state-set" handler="on_filter_services_switch" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkSearchBar" id="search_bar">
<property name="can_focus">False</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<child>
<object class="GtkScrolledWindow" id="picons_view_sw">
<object class="GtkSearchEntry" id="picons_search_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkIconView" id="picons_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin">5</property>
<property name="model">picons_sort_model</property>
<property name="row_spacing">5</property>
<property name="column_spacing">5</property>
<property name="tooltip_column">1</property>
<property name="item_padding">5</property>
<signal name="drag-data-get" handler="on_picons_view_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_picons_view_drag_data_received" swapped="no"/>
<signal name="drag-drop" handler="on_picons_view_drag_drop" swapped="no"/>
<signal name="realize" handler="on_picons_view_realize" swapped="no"/>
<child>
<object class="GtkCellRendererPixbuf" id="picon_renderer">
<property name="width">128</property>
</object>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererText" id="picon_title_renderer">
<property name="alignment">center</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<property name="primary_icon_name">edit-find-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
</object>
<packing>
<property name="expand">True</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">2</property>
</packing>
</child>
<child type="label">
<object class="GtkLabel" id="picons_path_labe">
<child>
<object class="GtkSearchBar" id="filter_bar">
<property name="can_focus">False</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<child>
<object class="GtkSearchEntry" id="picons_filter_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_stock">gtk-spell-check</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<signal name="search-changed" handler="on_picons_filter_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="explorer_paths_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Current picons path:</property>
<property name="label_xalign">0.5</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkPaned" id="explorer_paned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="wide_handle">True</property>
<child>
<object class="GtkBox" id="src_picons_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkBox" id="src_title_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkGrid" id="src_title_grid">
<property name="can_focus">False</property>
<property name="column_spacing">2</property>
<child>
<object class="GtkCheckButton" id="src_filter_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="focus_on_click">False</property>
<property name="receives_default">False</property>
<property name="valign">center</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_fiter_srcs_toggled" object="picons_src_filter_model" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="src_filter_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Filter</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</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="explorer_src_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Source:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFileChooserButton" id="explorer_src_path_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="action">select-folder</property>
<property name="title" translatable="yes"/>
<signal name="selection-changed" handler="on_picons_src_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="picons_view_sw">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="picons_src_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">picons_src_sort_model</property>
<property name="headers_visible">False</property>
<property name="tooltip_column">1</property>
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_popup_menu" object="picons_src_view_popup_menu" swapped="no"/>
<signal name="drag-data-get" handler="on_picons_view_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_picons_src_view_drag_data_received" swapped="no"/>
<signal name="drag-drop" handler="on_picons_src_view_drag_drop" swapped="no"/>
<signal name="drag-end" handler="on_picons_src_view_drag_end" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="picons_src_view_selection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="src_picon_column">
<property name="title" translatable="yes">column</property>
<child>
<object class="GtkCellRendererPixbuf" id="picons_src_renderer"/>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="src_title_column">
<property name="title" translatable="yes">column</property>
<child>
<object class="GtkCellRendererText" id="title_src_renderer"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="src_path_column">
<property name="visible">False</property>
<property name="title" translatable="yes">column</property>
<child>
<object class="GtkCellRendererText" id="path_src_renderer"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkBox" id="dest_picons_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkBox" id="dst_title_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child type="center">
<object class="GtkLabel" id="explorer_dest_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Destination:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="dst_title_grid">
<property name="can_focus">False</property>
<property name="column_spacing">2</property>
<child>
<object class="GtkCheckButton" id="dst_filter_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="focus_on_click">False</property>
<property name="receives_default">False</property>
<property name="valign">center</property>
<property name="image_position">right</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_fiter_srcs_toggled" object="picons_dst_filter_model" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="dst_filter_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Filter</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
</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>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFileChooserButton" id="explorer_dest_path_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="action">select-folder</property>
<property name="title" translatable="yes"/>
<signal name="selection-changed" handler="on_picons_dest_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="picons_dest_view_sw">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="picons_dest_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">picons_dst_sort_model</property>
<property name="headers_visible">False</property>
<property name="tooltip_column">1</property>
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_popup_menu" object="picons_dest_view_popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_picon_activated" swapped="no"/>
<signal name="drag-data-get" handler="on_picons_view_drag_data_get" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<signal name="realize" handler="on_picons_dest_view_realize" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="picons_dest_view_selection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="dest_picon_column">
<property name="title" translatable="yes">column</property>
<child>
<object class="GtkCellRendererPixbuf" id="picons_dest_renderer"/>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="dest_title_column">
<property name="title" translatable="yes">column</property>
<child>
<object class="GtkCellRendererText" id="title_dest_renderer"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="dest_path_column">
<property name="visible">False</property>
<property name="title" translatable="yes">column</property>
<child>
<object class="GtkCellRendererText" id="path_dest_renderer"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="explorer_paths_frame_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Current picons path:</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="explorer_info_bar_frame">
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkInfoBar" id="explorer_info_bar">
<property name="can_focus">False</property>
<property name="message_type">other</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox" id="explorer_info_bar_box">
<property name="can_focus">False</property>
<property name="spacing">10</property>
<child>
<object class="GtkImage" id="picon_info_image">
<property name="width_request">128</property>
<property name="height_request">72</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
<property name="icon_size">6</property>
<signal name="drag-data-received" handler="on_picon_info_image_drag_data_received" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="picon_info_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
</object>
<packing>
@@ -1173,12 +1676,8 @@ Author: Dmitriy Yefremov
</child>
</object>
</child>
<child type="label">
<object class="GtkStackSwitcher" id="sctack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stack">stack</property>
</object>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>

View File

@@ -3,6 +3,8 @@ import re
import shutil
import subprocess
import tempfile
from pathlib import Path
from urllib.parse import urlparse, unquote
from gi.repository import GLib, GdkPixbuf
@@ -12,8 +14,9 @@ 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
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column
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
class PiconsDialog:
@@ -27,6 +30,9 @@ 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,
@@ -43,16 +49,30 @@ class PiconsDialog:
"on_position_edited": self.on_position_edited,
"on_visible_page": self.on_visible_page,
"on_convert": self.on_convert,
"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_src_changed": self.on_picons_src_changed,
"on_picons_dest_changed": self.on_picons_dest_changed,
"on_picons_view_drag_data_get": self.on_picons_view_drag_data_get,
"on_picons_view_realize": self.on_picons_view_realize,
"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_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()
@@ -61,17 +81,23 @@ class PiconsDialog:
self._dialog = builder.get_object("picons_dialog")
self._dialog.set_transient_for(transient)
self._picons_view = builder.get_object("picons_view")
self._picons_src_view = builder.get_object("picons_src_view")
self._picons_dest_view = builder.get_object("picons_dest_view")
self._providers_view = builder.get_object("providers_view")
self._satellites_view = builder.get_object("satellites_view")
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._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._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")
@@ -80,12 +106,17 @@ 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")
@@ -99,9 +130,18 @@ class PiconsDialog:
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._load_providers_button.bind_property("visible", builder.get_object("download_box_separator"), "visible")
self._filter_bar.bind_property("search-mode-enabled", self._filter_bar, "visible")
self._explorer_path_button.bind_property("sensitive", builder.get_object("picons_view_sw"), "sensitive")
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")
# Init drag-and-drop
self.init_drag_and_drop()
# Style
@@ -128,25 +168,34 @@ class PiconsDialog:
def show(self):
self._dialog.show()
def on_picons_view_realize(self, view):
self._explorer_path_button.set_current_folder(self._settings.picons_local_path)
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_folder_changed(self, button):
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):
path = button.get_filename()
if not path or not os.path.exists(path):
return
GLib.idle_add(self._explorer_path_button.set_sensitive, False)
gen = self.update_picons(path)
GLib.idle_add(button.set_sensitive, False)
gen = self.update_picons(path, view, button)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def update_picons(self, path):
p_model = self._picons_view.get_model()
def update_picons(self, path, view, button):
p_model = view.get_model()
if not p_model:
button.set_sensitive(True)
return
model = get_base_model(p_model)
self._picons_view.set_model(None)
view.set_model(None)
factor = self._app.DEL_FACTOR
for index, itr in enumerate([row.iter for row in model]):
@@ -158,50 +207,91 @@ class PiconsDialog:
if self._terminate:
return
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))
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))
self._picons_view.set_model(p_model)
self._explorer_path_button.set_sensitive(True)
view.set_model(p_model)
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_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_src_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
self._picons_src_view.drag_source_add_uri_targets()
def on_picons_view_drag_drop(self, view, drag_context, x, y, time):
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):
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_view_drag_data_received(self, view, drag_context, x, y, data, info, time):
def on_picons_src_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.show_info_message("Not implemented yet!", Gtk.MessageType.WARNING)
self.update_picons_from_file(view, txt)
return
itr_str, sep, src = txt.partition("::::")
if src == self._app.BQ_MODEL_NAME:
return
path, pos = view.get_dest_item_at_pos(x, y) or (None, None)
path, pos = view.get_dest_row_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
@@ -210,9 +300,28 @@ class PiconsDialog:
c_id = Column.SRV_FAV_ID
t_mod = target_view.get_model()
self._app.on_assign_picon(target_view, p_path)
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.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)
@@ -223,13 +332,138 @@ 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_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_picons_src_view_drag_end(self, view, drag_context):
self.update_picons_dest_view(self._app.picons_buffer)
# ******************** ####### ************************* #
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)
@@ -329,10 +563,10 @@ class PiconsDialog:
return
self.process_provider(Provider(*prv))
if self._resize_no_radio_button.get_active():
if not self._resize_no_radio_button.get_active():
self.resize(self._picons_dir_entry.get_text())
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
else:
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
finally:
GLib.idle_add(self._cancel_button.hide)
self._terminate = False
@@ -361,15 +595,24 @@ 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)
command = "mogrify -resize {}! *.png".format(
"220x132" if self._resize_220_132_radio_button.get_active() else "100x60").split()
try:
self._current_process = subprocess.Popen(command, universal_newlines=True, cwd=path)
self._current_process.wait()
except FileNotFoundError as e:
self.show_info_message("Conversion error. " + str(e), Gtk.MessageType.ERROR)
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)
def on_cancel(self, item=None):
if self.is_task_running() and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
@@ -406,38 +649,6 @@ 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:
@@ -449,7 +660,7 @@ class PiconsDialog:
finally:
GLib.idle_add(self._header_download_box.set_sensitive, True)
if update:
self.on_picons_folder_changed(self._explorer_path_button)
self.on_picons_dest_changed(self._explorer_dest_path_button)
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@@ -479,23 +690,42 @@ 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_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_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("")
@run_with_delay(1)
def on_picons_filter_changed(self, entry):
GLib.idle_add(self._picons_filter_model.refilter, priority=GLib.PRIORITY_LOW)
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)
def picons_filter_function(self, model, itr, data):
if self._picons_filter_model is None or self._picons_filter_model == "None":
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")):
return True
t = model.get_value(itr, 1)
@@ -506,6 +736,49 @@ 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)
@@ -518,7 +791,7 @@ class PiconsDialog:
is_explorer = name == "explorer"
self._filter_button.set_visible(is_explorer)
if is_explorer:
self.on_picons_folder_changed(self._explorer_path_button)
self.on_picons_dest_changed(self._explorer_dest_path_button)
@run_idle
def on_convert(self, item):

View File

@@ -728,6 +728,7 @@ Author: Dmitriy Yefremov
<property name="use-header-bar">{use_header}</property>
<property name="title" translatable="yes">Satellite</property>
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="destroy_with_parent">True</property>

View File

@@ -1,15 +1,16 @@
import re
import os
import re
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, HIERARCHY, T_FEC
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.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"
@@ -438,14 +439,17 @@ 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):
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})
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})
def update_picon_name(self, old_name, new_name):
if not os.path.isdir(self._picons_dir_path):

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1
<!-- Generated with glade 3.22.2
The MIT License (MIT)
@@ -28,6 +28,7 @@ Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.16"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
@@ -980,6 +981,8 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="visibility">False</property>
<property name="invisible_char">●</property>
<property name="width_chars">12</property>
<property name="max_width_chars">15</property>
<property name="primary_icon_name">emblem-readonly</property>
@@ -1828,21 +1831,6 @@ Author: Dmitriy Yefremov
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="h246_radio_button1">
<property name="label">mp3</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">center</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="sample_rate_label">
<property name="visible">True</property>
@@ -1888,6 +1876,21 @@ Author: Dmitriy Yefremov
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="audio_codec_combo_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active_id">mp3</property>
<items>
<item id="mp3" translatable="yes">mp3</item>
<item id="a52" translatable="yes">AC3</item>
</items>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
</child>
<child type="label">
@@ -2065,6 +2068,7 @@ Author: Dmitriy Yefremov
<item id="pl_PL" translatable="yes">Polski</item>
<item id="pt_PT" translatable="yes">Português</item>
<item id="tr_TR" translatable="yes">Türkçe</item>
<item id="be_BY" translatable="yes">Беларуская</item>
<item id="ru_RU" translatable="yes">Русский</item>
</items>
<signal name="changed" handler="on_lang_changed" swapped="no"/>
@@ -2229,7 +2233,6 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkFrame" id="program_frame">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
@@ -2461,6 +2464,57 @@ Author: Dmitriy Yefremov
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkFrame" id="dark_mode_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox" id="dark_mode_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<child>
<object class="GtkLabel" id="dark_mode_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Enable Dark Mode</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="dark_mode_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="state-set" handler="on_icon_theme_changed" swapped="no"/>
</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>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="hemes_support_frame">
<property name="visible">True</property>
@@ -2468,7 +2522,7 @@ Author: Dmitriy Yefremov
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox">
<object class="GtkBox" id="themes_support_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
@@ -2487,6 +2541,18 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child type="center">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">EXPERIMENTAL!</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="themes_support_switch">
<property name="visible">True</property>
@@ -2509,7 +2575,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="position">1</property>
</packing>
</child>
<child>
@@ -2656,7 +2722,47 @@ 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>
<child>
<object class="GtkBox" id="themes_source_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel" id="themes_source_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Gtk3 Themes and Icons:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLinkButton" id="themes_source_link_button">
<property name="label" translatable="yes">www.gnome-look.org</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="uri">https://www.gnome-look.org/</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">3</property>
</packing>
</child>
<child>
@@ -2752,47 +2858,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="themes_source_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<child>
<object class="GtkLabel" id="themes_source_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Gtk3 Themes and Icons:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLinkButton" id="themes_source_link_button">
<property name="label" translatable="yes">www.gnome-look.org</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="uri">https://www.gnome-look.org/</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">3</property>
<property name="position">4</property>
</packing>
</child>
</object>
@@ -2806,72 +2872,17 @@ Author: Dmitriy Yefremov
<object class="GtkBox" id="extra_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkFrame" id="v5_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkGrid" id="v5_support_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_top">5</property>
<property name="margin_bottom">5</property>
<property name="row_spacing">5</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Enable ver. 5 support (experimental)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="support_ver5_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<property name="spacing">10</property>
<child>
<object class="GtkFrame" id="bq_naming_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Allows you to name bouquet files using their names.</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
@@ -2918,96 +2929,35 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="yt_dl_frame">
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkGrid" id="yt_dl_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_top">5</property>
<property name="margin_bottom">5</property>
<child>
<object class="GtkLabel" id="enable_yt_dl_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Enables parsing links using youtube-dl to get direct links to media</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Enable support for youtube-dl (experimental)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="enable_y_dl_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Enables parsing links using youtube-dl to get direct links to media</property>
<property name="halign">end</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</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">2</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="http_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkGrid" id="extra_support_grid">
<object class="GtkBox" id="enable_http_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="row_spacing">5</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkLabel">
<object class="GtkLabel" id="enable_http_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Enable HTTP API (experimental)</property>
<property name="label" translatable="yes">Enable HTTP API</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
@@ -3018,35 +2968,9 @@ Author: Dmitriy Yefremov
<signal name="state-set" handler="on_http_mode_switch" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="enable_send_to_label">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Enable direct playback bar (experimental)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="enable_send_to_switch">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Enables direct sending and playback of media links on the receiver</property>
<property name="halign">end</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
@@ -3058,17 +2982,13 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="double_click_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
@@ -3177,12 +3097,328 @@ Author: Dmitriy Yefremov
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="enable_experimental_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox" id="enable_experimental_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="enable_experimental_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Enable experimental features</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="enable_experimental_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<signal name="state-set" handler="on_experimental_switch" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">15</property>
<property name="label" translatable="yes">EXPERIMENTAL!</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkBox" id="experimental_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkFrame" id="v5_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox" id="v5_support_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Enable lamedb ver. 5 support</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="support_ver5_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</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>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="yt_dl_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox" id="yt_dl_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="enable_yt_dl_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Enables parsing links using youtube-dl to get direct media links.</property>
<child>
<object class="GtkLabel" id="enable_yt_dl_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Enable support for</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="enable_yt_dl_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Enables parsing links using youtube-dl to get direct links to media</property>
<property name="halign">end</property>
<property name="valign">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLinkButton" id="yt_dl_link_button">
<property name="label">youtube-dl</property>
<property name="name">status-bar-button</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="relief">none</property>
<property name="uri">https://youtube-dl.org/</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="yt_dl_update_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="auto_update_yt_dl_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Auto-check for updates</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="enable_update_yt_dl_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</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">2</property>
</packing>
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="direct_playback_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox" id="enable_direct_playback_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<child>
<object class="GtkLabel" id="enable_send_to_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Enable direct playback bar</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="enable_send_to_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Enables direct sending and playback of media links on the receiver</property>
<property name="halign">end</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>
</child>
<child type="label_item">
<placeholder/>
</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="position">8</property>
</packing>
</child>
</object>
<packing>
<property name="name">extra</property>

View File

@@ -36,6 +36,7 @@ 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,
@@ -124,6 +125,7 @@ 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")
@@ -135,8 +137,7 @@ class SettingsDialog:
# Program
self._before_save_switch = builder.get_object("before_save_switch")
self._before_downloading_switch = builder.get_object("before_downloading_switch")
self._program_frame = builder.get_object("program_frame")
self._extra_support_grid = builder.get_object("extra_support_grid")
self._enable_experimental_box = builder.get_object("enable_experimental_box")
self._colors_grid = builder.get_object("colors_grid")
self._set_color_switch = builder.get_object("set_color_switch")
self._new_color_button = builder.get_object("new_color_button")
@@ -145,9 +146,10 @@ 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")
# HTTP API
# Extra
self._support_http_api_switch = builder.get_object("support_http_api_switch")
self._enable_y_dl_switch = builder.get_object("enable_y_dl_switch")
self._enable_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_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")
@@ -156,10 +158,18 @@ 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")
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")
# 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")
# Profiles
self._profile_view = builder.get_object("profile_tree_view")
self._profile_add_button = builder.get_object("profile_add_button")
@@ -185,6 +195,7 @@ 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")
@@ -196,8 +207,6 @@ class SettingsDialog:
self._neutrino_radio_button.set_active(s_type is SettingsType.NEUTRINO_MP)
self.update_header_bar()
self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(is_enigma_profile)
self._program_frame.set_sensitive(is_enigma_profile)
self._extra_support_grid.set_sensitive(is_enigma_profile)
http_active = self._support_http_api_switch.get_active()
self._click_mode_zap_button.set_sensitive(is_enigma_profile and http_active)
self._lang_combo_box.set_active_id(self._ext_settings.language)
@@ -212,6 +221,7 @@ class SettingsDialog:
model.append((p, icon))
if icon:
scroll_to(ind, self._profile_view)
self.on_profile_selected(self._profile_view, False)
self._profile_remove_button.set_sensitive(len(self._profile_view.get_model()) > 1)
def update_header_bar(self):
@@ -282,10 +292,12 @@ 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_y_dl_switch.set_active(self._settings.enable_yt_dl)
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_send_to_switch.set_active(self._settings.enable_send_to)
self._set_color_switch.set_active(self._settings.use_colors)
new_rgb = Gdk.RGBA()
@@ -300,7 +312,7 @@ class SettingsDialog:
else:
self._neutrino_radio_button.activate()
def on_apply_profile_settings(self, item):
def on_apply_profile_settings(self, item=None):
if not self.is_data_correct(self._digit_elems):
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return
@@ -328,9 +340,10 @@ 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.CANCEL:
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.OK:
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()
@@ -347,18 +360,21 @@ 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_y_dl_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_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]
@@ -436,6 +452,12 @@ 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
@@ -466,7 +488,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)
self.on_profile_selected(self._profile_view, False)
self.on_reset()
def on_profile_edit(self, item=None):
@@ -502,7 +524,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)
self.on_profile_selected(self._profile_view, False)
def update_local_paths(self, p_name, old_name, force_rename=False):
data_path = self._settings.data_local_path
@@ -524,7 +546,10 @@ class SettingsDialog:
except OSError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
def on_profile_selected(self, view):
def on_profile_selected(self, view, force=True):
if force:
self.on_apply_profile_settings()
model, paths = self._profile_view.get_selection().get_selected_rows()
if paths:
profile = model.get_value(model.get_iter(paths), 0)
@@ -625,6 +650,7 @@ 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):
@@ -642,6 +668,7 @@ 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)
@@ -674,7 +701,9 @@ class SettingsDialog:
self.add_theme(self._ext_settings.themes_path, self._theme_combo_box)
def on_theme_remove(self, button):
self.remove_theme(self._theme_combo_box, self._ext_settings.themes_path)
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.OK:
Gtk.Settings().get_default().set_property("gtk-theme-name", "")
self.remove_theme(self._theme_combo_box, self._ext_settings.themes_path)
def on_icon_theme_changed(self, button, state=False):
if self._main_stack.get_visible_child_name() != "appearance":
@@ -685,7 +714,9 @@ class SettingsDialog:
self.add_theme(self._ext_settings.icon_themes_path, self._icon_theme_combo_box)
def on_icon_theme_remove(self, button):
self.remove_theme(self._icon_theme_combo_box, self._ext_settings.icon_themes_path)
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.OK:
Gtk.Settings().get_default().set_property("gtk-icon-theme-name", "")
self.remove_theme(self._icon_theme_combo_box, self._ext_settings.icon_themes_path)
@run_idle
def add_theme(self, path, button):
@@ -698,14 +729,17 @@ class SettingsDialog:
@run_task
def unpack_theme(self, src, dst, button):
try:
from shutil import unpack_archive
os.makedirs(os.path.dirname(dst), exist_ok=True)
unpack_archive(src, dst)
except (KeyError, EOFError) as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
self.update_theme_button(button, dst)
import subprocess
log("Unpacking '{}' started...".format(src))
p = subprocess.Popen(["tar", "-xvf", src, "-C", dst],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
p.communicate()
log("Unpacking end.")
finally:
self.update_theme_button(button, dst)
self._appearance_box.set_sensitive(True)
@run_idle
@@ -727,9 +761,6 @@ class SettingsDialog:
self.show_info_message("No selected item!", Gtk.MessageType.ERROR)
return
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.OK:
return
from shutil import rmtree
try:
@@ -737,11 +768,16 @@ class SettingsDialog:
except OSError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
button.remove(button.get_active())
button.set_active(0)
self.theme_button_remove_active(button)
@run_idle
def theme_button_remove_active(self, button):
button.remove(button.get_active())
button.set_active(0)
@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:

View File

@@ -18,7 +18,7 @@ class LinksTransmitter:
"""
__STREAM_PREFIX = "4097:0:1:0:0:0:0:0:0:0:"
def __init__(self, http_api, app_window):
def __init__(self, http_api, app_window, settings):
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,6 +45,7 @@ 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")
@@ -113,7 +114,7 @@ class LinksTransmitter:
if yt_id:
self._url_entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
links, title = YouTube.get_yt_link(yt_id)
links, title = self._yt.get_yt_link(yt_id, url)
yield True
if links:
url = links[sorted(links, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]

View File

@@ -8,8 +8,11 @@ import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
from gi.repository import Gtk, Gdk
gi.require_version("Notify", "0.7")
from gi.repository import Gtk, Gdk, Notify
# Init notify
Notify.init("DemonEditor")
# Setting mod mask for the keyboard depending on the platform.
MOD_MASK = Gdk.ModifierType.MOD2_MASK if IS_DARWIN else Gdk.ModifierType.CONTROL_MASK
# Path to *.glade files.
@@ -49,7 +52,10 @@ DEFAULT_ICON = theme.load_icon("emblem-default", 16, 0) if theme.lookup_icon("em
@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 "Info" icon is returned by default!
"""
default_theme = Gtk.IconTheme.get_default()
if default_theme.has_icon(icon_name):
return default_theme.load_icon(icon_name, size, 0)
@@ -65,6 +71,19 @@ def get_yt_icon(icon_name, size=24):
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()
class KeyboardKey(Enum):
""" The raw(hardware) codes of the keyboard keys. """
E = 26

View File

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

View File

@@ -4,7 +4,19 @@ 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)
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.
@@ -25,24 +37,22 @@ Keyboard shortcuts:
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.
Extra:
Import feature.
Multiple selections in lists only with Space key (as in file managers).
Ability to download picons and update satellites (transponders) from web.
Ability to import into bouquet (Neutrino WEB TV) from m3u.
Ability to export bouquets with IPTV services to m3u.
Assignment EPG from DVB or XML for IPTV services(Enigma2 only).
Preview (playing) IPTV or other streams directly from the bouquet list (should be installed VLC).
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.
Note.
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2!
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings, python3-requests.
Important:
Main supported lamedb format is version 4. Versions 3 and 5 has only experimental support!
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2!
Main supported *lamedb* format is version **4**. Versions **3** and **5** has only **experimental** support!
For version **3** is only read mode available. When saving, version **4** format is used instead!
When using the multiple import feature, from *lamedb* will be taken data **only for channels that are in the
selected bouquets!** If you need full set of the data, including *[satellites, terrestrial, cables].xml* (current files will be overwritten),
just load your data via *"File/Open"* and press *"Save"*. When importing separate bouquet files, only those services
(excluding IPTV) that are in the **current open lamedb** (main list of services) will be imported.

View File

@@ -1,5 +1,5 @@
Package: demon-editor
Version: 0.4.8-Pre-alpha
Version: 1.0.1-Beta
Section: utils
Priority: optional
Architecture: all

Binary file not shown.

1050
po/be/demon-editor.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
msgid "translator-credits"
msgstr "Charly, Dmitriy Yefremov"
msgstr "Charly\nDmitriy Yefremov"
# Main
msgid "Service"
@@ -105,6 +105,9 @@ 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"
@@ -160,7 +163,7 @@ msgid "Remove"
msgstr "Entfernen"
msgid "Remove all unavailable"
msgstr "Entfernt alle nicht verfügbaren"
msgstr "Entfernen alle nicht verfügbaren"
msgid "Satellites editor"
msgstr "Satelliten-Editor"
@@ -516,6 +519,9 @@ 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!"
@@ -671,11 +677,11 @@ msgstr "Play Stream"
msgid "Disabled"
msgstr "Ausgeschaltet"
msgid "Enable ver. 5 support (experimental)"
msgstr "Lamedb ver. 5 Unterstützung aktivieren (experimentell)"
msgid "Enable lamedb ver. 5 support"
msgstr "Lamedb ver. 5 Unterstützung aktivieren"
msgid "Enable HTTP API (experimental)"
msgstr "HTTP-API aktivieren (experimentell)"
msgid "Enable HTTP API"
msgstr "HTTP-API aktivieren"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Umschalten des Kanals (Strg + Z)"
@@ -795,8 +801,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 (experimental)"
msgstr "Aktivieren der direkten Wiedergabeleiste (experimentell)"
msgid "Enable direct playback bar"
msgstr "Aktivieren der direkten Wiedergabeleiste"
msgid "Enables direct sending and playback of media links on the receiver"
msgstr "Ermöglicht das direkte Senden und Abspielen von Medienlinks auf dem Box"
@@ -987,3 +993,77 @@ 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"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2019 Frank Neirynck
# Copyright (C) 2018-2020 Frank Neirynck
# This file is distributed under the MIT license.
#
# Frank Neirynck <frank@insink.be>, 2018-2019.
@@ -54,7 +54,7 @@ msgid "Current IP:"
msgstr "IP actual:"
msgid "Assign"
msgstr "Assignar"
msgstr "Asignar"
msgid "Bouquet details"
msgstr "Detalles bouquet"
@@ -66,7 +66,7 @@ msgid "Copy"
msgstr "Copiar"
msgid "Copy reference"
msgstr "Copia de referencia"
msgstr "Copiar referencia"
msgid "Download"
msgstr "Descargar"
@@ -123,7 +123,7 @@ msgid "New"
msgstr "Nuevo"
msgid "New bouquet"
msgstr "Bouquet nuevo"
msgstr "Nuevo bouquet"
msgid "Create bouquet"
msgstr "Crear bouquet"
@@ -156,10 +156,10 @@ msgid "Picons"
msgstr "Picons"
msgid "Picons downloader"
msgstr "Descargar picons"
msgstr "Descarga de picons"
msgid "Satellites downloader"
msgstr "Descargar satélites"
msgstr "Descarga de satélites"
msgid "Remove"
msgstr "Quitar"
@@ -210,7 +210,7 @@ msgid "Enigma2 channel and satellites list editor for GNU/Linux"
msgstr "Editor de canales y satélites Enigma2 para GNU/Linux"
msgid "Host:"
msgstr "Anfitrión:"
msgstr "Host:"
msgid "Loading data..."
msgstr "Cargando datos..."
@@ -256,7 +256,7 @@ msgstr "Extra:"
# Filter bar
msgid "Only free"
msgstr "Solamente gratis"
msgstr "Solamente libres"
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 flujo en la lista"
msgstr "Anterior stream en la lista"
msgid "Next stream in the list"
msgstr "Siguiente flujo en la lista"
msgstr "Siguiente stream 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 "Picons formato nombres:"
msgstr "Formato del nombre de los picons:"
msgid "Resize:"
msgstr "Redimensionar:"
@@ -433,7 +433,7 @@ msgstr "Buscar"
# IPTV dialog
msgid "Stream data"
msgstr "Transmitir flujo"
msgstr "Transmitir stream"
# 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 flujos IPTV"
msgstr "Configurar lista de streams 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 "¡Aún sin implementar!"
msgstr "¡No implementado!"
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 sólo un elemento!"
msgstr "¡Por favor, seleccione un único 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 "¡Ningún elemento seleccionado!"
msgstr "¡No se ha seleccionado ningún elemento!"
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. ¡Verifique que está instalado!"
msgstr "VLC no encontrado. ¡Compruebe que está instalado!"
# Search unavailable streams dialog
msgid "Please wait, streams testing in progress..."
msgstr "Por favor espere, hay una prueba de flujo en progreso..."
msgstr "Por favor espere, hay una prueba de stream en progreso..."
msgid "Found"
msgstr "Encontrado"
msgid "unavailable streams."
msgstr "Flujos no presentes."
msgstr "Streams no presentes."
msgid "No changes required!"
msgstr "¡Ningún cambio requerido!"
msgstr "¡No se requieren cambios!"
msgid "This list does not contains IPTV streams!"
msgstr "¡La lista no contiene flujos IPTV!"
msgstr "¡La lista no contiene streams IPTV!"
msgid "New empty configuration"
msgstr "Nueva configuración vacía"
@@ -672,16 +672,16 @@ msgid "Zap"
msgstr "Zapear"
msgid "Play stream"
msgstr "Reproducir flujo"
msgstr "Reproducir stream"
msgid "Disabled"
msgstr "Desactivado"
msgid "Enable ver. 5 support (experimental)"
msgstr "Soporte para ver. 5 (experimental)"
msgid "Enable lamedb ver. 5 support"
msgstr "Soporte para lamedb ver. 5"
msgid "Enable HTTP API (experimental)"
msgstr "Habilitar API HTTP (experimental)"
msgid "Enable HTTP API"
msgstr "Habilitar API HTTP"
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 flujo en el programa (Ctrl + P)"
msgstr "Reproducir IPTV u otro stream 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 abierta al iniciar el programa"
msgstr "Cargar la última configuración usada al iniciar el programa"
msgid "Enable direct playback bar (experimental)"
msgstr "Habilitar la barra de reproducción directa (experimental)"
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"
@@ -816,4 +816,212 @@ 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!"
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ео"

View File

@@ -671,11 +671,11 @@ msgstr "Speel stream af"
msgid "Disabled"
msgstr "Uitgeschakeld"
msgid "Enable ver. 5 support (experimental)"
msgstr "Ondersteuning voor ver. 5 inschakelen (experimenteel)"
msgid "Enable lamedb ver. 5 support"
msgstr "Ondersteuning voor lamedb ver. 5 inschakelen"
msgid "Enable HTTP API (experimental)"
msgstr "HTTP API inschakelen (experimenteel)"
msgid "Enable HTTP API"
msgstr "HTTP API inschakelen"
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 (experimental)"
msgstr "Laat onmiddelijk playback bar toe (experimenteel)"
msgid "Enable direct playback bar"
msgstr "Laat onmiddelijk playback bar toe"
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,3 +970,46 @@ 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ео"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2019 Dmitriy Yefremov
# Copyright (C) 2018-2020 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
@@ -179,9 +179,6 @@ msgstr "Zapisz"
msgid "Search"
msgstr "Szukaj"
msgid "Services"
msgstr "Kanały"
msgid "Services filter"
msgstr "Filtr kanałów"
@@ -257,6 +254,21 @@ 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"
@@ -268,9 +280,6 @@ msgid "All types"
msgstr "Wszystkie typy"
# Streams player
msgid "Play"
msgstr "Odtwarzaj"
msgid "Stop playback"
msgstr "Zatrzymaj odtwarzanie"
@@ -381,6 +390,12 @@ 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:"
@@ -591,15 +606,6 @@ 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"
@@ -615,21 +621,6 @@ 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"
@@ -642,6 +633,15 @@ 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,27 +664,12 @@ 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)"
@@ -698,7 +683,7 @@ msgid "Export to m3u"
msgstr "Eksportuj do m3u"
msgid "EPG configuration"
msgstr "Koniguruj EPG"
msgstr "Konfiguruj EPG"
msgid "Apply"
msgstr "Zatwierdź"
@@ -785,26 +770,11 @@ 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 "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)"
msgstr "Ustaw domyślnie"
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"
@@ -825,10 +795,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 ponownie załadować dane do odbiornika."
msgstr "Użyj http aby przeładować dane w odbiorniku."
msgid "Zap and Play"
msgstr "Przełącz i Odtwórz"
msgid "Apply profile settings"
msgstr "Zastosuj ustawienia profilu"
msgid "Drag or paste the link here"
msgstr "Przeciągnij lub wklej tutaj link"
@@ -838,3 +808,197 @@ 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ń:"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2019 Frank Neirynck
# Copyright (C) 2018-2020 Frank Neirynck
# This file is distributed under the MIT license.
#
#Frank Neirynck <frank@insink.be>, 2018-2019.
@@ -663,11 +663,11 @@ msgstr "Play stream"
msgid "Disabled"
msgstr "Desativado"
msgid "Enable ver. 5 support (experimental)"
msgstr "Ativar ver. 5 suporte (experimental)"
msgid "Enable lamedb ver. 5 support"
msgstr "Ativar lamedb ver. 5 suporte"
msgid "Enable HTTP API (experimental)"
msgstr "Ativar HTTP API (experimental)"
msgid "Enable HTTP API"
msgstr "Ativar HTTP API"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Mudar(zap) o canal(Ctrl + Z)"
@@ -763,4 +763,250 @@ msgid "Playlist import"
msgstr "Importação de lista de reprodução"
msgid "Getting link error:"
msgstr "Obtendo erro de link:"
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ео"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2019 Dmitriy Yefremov
# Copyright (C) 2018-2020 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
@@ -104,6 +104,9 @@ msgstr "Имя по умолчанию"
msgid "Insert marker"
msgstr "Вставить маркер"
msgid "Insert space"
msgstr "Вставить пробел"
msgid "Locate in services"
msgstr "Найти в списке сервисов"
@@ -507,6 +510,9 @@ msgstr "Пожалуйста, выберите только один элеме
msgid "No png file is selected!"
msgstr "Не выбран png файл!"
msgid "No profile selected!"
msgstr "Не выбран профиль!"
msgid "No reference is present!"
msgstr "Ссылка не найдена!"
@@ -662,17 +668,17 @@ msgstr "Воспр. потока"
msgid "Disabled"
msgstr "Выкл."
msgid "Enable ver. 5 support (experimental)"
msgstr "Включить поддержку lamedb вер. 5 (экспериментально)"
msgid "Enable lamedb ver. 5 support"
msgstr "Включить поддержку lamedb вер. 5"
msgid "Enable HTTP API (experimental)"
msgstr "Включить HTTP API (экспериментально)"
msgid "Enable HTTP API"
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)"
@@ -782,8 +788,8 @@ msgstr "Язык:"
msgid "Load the last open configuration at program startup"
msgstr "Загружать последнюю открытую конфигурацию при запуске программы"
msgid "Enable direct playback bar (experimental)"
msgstr "Включить панель прямого воспроизведения (экспериментально)"
msgid "Enable direct playback bar"
msgstr "Включить панель прямого воспроизведения"
msgid "Enables direct sending and playback of media links on the receiver"
msgstr "Включает прямую отправку и воспроизведение медиа-ссылок на ресивере"
@@ -974,3 +980,71 @@ 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 "Открыть архив"

View File

@@ -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-05-11 20:02+0300\n"
"PO-Revision-Date: 2020-06-08 21:53+0300\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -213,7 +213,7 @@ msgid "Loading data..."
msgstr "Veriler yükleniyor ..."
msgid "Receive"
msgstr "Al"
msgstr "Cihazdan Al"
msgid "Receive files from receiver"
msgstr "Alıcıdan dosya al"
@@ -237,7 +237,7 @@ msgid "Selected"
msgstr "Seçildi"
msgid "Send"
msgstr "Gönder"
msgstr "Cihaza 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"
msgstr "Uydu Ekle"
msgid "Transponder"
msgstr "Transponder"
msgstr "Transponder Ekle"
msgid "Satellite properties:"
msgstr "Uydu özellikleri:"
@@ -673,11 +673,11 @@ msgstr "Akışı oynat"
msgid "Disabled"
msgstr "Devre dışı"
msgid "Enable ver. 5 support (experimental)"
msgstr "Sürüm 5 desteğini etkinleştir (deneysel)"
msgid "Enable lamedb ver. 5 support"
msgstr "Sürüm 5 desteğini etkinleştir"
msgid "Enable HTTP API (experimental)"
msgstr "HTTP API'sini etkinleştir (deneysel)"
msgid "Enable HTTP API"
msgstr "HTTP API'sini etkinleştir"
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 (experimental)"
msgstr "Doğrudan oynatma çubuğunu etkinleştir (deneysel)"
msgid "Enable direct playback bar"
msgstr "Doğrudan oynatma çubuğunu etkinleştir"
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 "Y.başlat"
msgstr "Yeniden Başlat"
msgid "File"
msgstr "Dosya"
@@ -976,3 +976,15 @@ 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ı"