mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-08 23:37:36 +02:00
Compare commits
211 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6db03b6cac | ||
|
|
a94c53a9c9 | ||
|
|
b012fccd1a | ||
|
|
4062d206b8 | ||
|
|
a1f656fbca | ||
|
|
84afaee1d0 | ||
|
|
08619dd182 | ||
|
|
04f27eff88 | ||
|
|
6e706dec2d | ||
|
|
3bf787b9fb | ||
|
|
3b1bb80d3c | ||
|
|
05fa5eaf11 | ||
|
|
b558a17d9d | ||
|
|
0ee248a24f | ||
|
|
3a368427fd | ||
|
|
384c30ea18 | ||
|
|
05cf047127 | ||
|
|
621b090a1a | ||
|
|
a8d3f39442 | ||
|
|
02c261b4dd | ||
|
|
5c3532db65 | ||
|
|
fda9780de9 | ||
|
|
6c5bd5d576 | ||
|
|
9c5b7a3901 | ||
|
|
b7f312a35d | ||
|
|
9401b2a7f7 | ||
|
|
682fa341d0 | ||
|
|
c9daa8a599 | ||
|
|
94d3d0d9ac | ||
|
|
2189997122 | ||
|
|
8397efa324 | ||
|
|
d21f9410cd | ||
|
|
be9b3178e0 | ||
|
|
2a8ddc093c | ||
|
|
fa1ec4cdcf | ||
|
|
384da95988 | ||
|
|
960541b56a | ||
|
|
396d10a805 | ||
|
|
30e1c63a47 | ||
|
|
ef7e35378d | ||
|
|
0a1bbab7d0 | ||
|
|
65502018a0 | ||
|
|
cc20042001 | ||
|
|
50c2e831ce | ||
|
|
ea91c39769 | ||
|
|
3dab8ef7b7 | ||
|
|
dd1a543e5c | ||
|
|
0966489024 | ||
|
|
052187359d | ||
|
|
6ca6867ea9 | ||
|
|
d9cdc6458c | ||
|
|
70b9851324 | ||
|
|
2a3b558d83 | ||
|
|
21ea841f34 | ||
|
|
1db0ce3fc5 | ||
|
|
2804a9bc54 | ||
|
|
8976f42974 | ||
|
|
8330104f3c | ||
|
|
3ede2e2b07 | ||
|
|
dd796c0f88 | ||
|
|
f3a432c002 | ||
|
|
4c1cdc4850 | ||
|
|
a062d74e0e | ||
|
|
7bf36c8d6d | ||
|
|
6aad0344c8 | ||
|
|
bb4665d180 | ||
|
|
a455c4569d | ||
|
|
8d5af301fb | ||
|
|
f342b99769 | ||
|
|
a0612e6a98 | ||
|
|
60b8f7642d | ||
|
|
8730cbdb7c | ||
|
|
cefb96ea20 | ||
|
|
b383c2572a | ||
|
|
ee2fcf8082 | ||
|
|
44234fa534 | ||
|
|
4bb66b5cc1 | ||
|
|
3b1ecbfbbf | ||
|
|
a7f7a59c8a | ||
|
|
9068028662 | ||
|
|
40fbc7809f | ||
|
|
6397c2c7f3 | ||
|
|
81e8e30682 | ||
|
|
9b4c6ab14a | ||
|
|
cad1437c33 | ||
|
|
4799a0d464 | ||
|
|
ce890353a4 | ||
|
|
506e07e3f4 | ||
|
|
e7a61e3f05 | ||
|
|
274abec3b8 | ||
|
|
77a5f55522 | ||
|
|
a7f334682f | ||
|
|
a9b9e5865e | ||
|
|
d59458a84b | ||
|
|
076bcb0cce | ||
|
|
8cbf03eb51 | ||
|
|
abed7bf9cb | ||
|
|
388e748673 | ||
|
|
ba3a3ae0aa | ||
|
|
9bb4f6d75d | ||
|
|
06242ce611 | ||
|
|
e4b1c98b2a | ||
|
|
6a8426e6ef | ||
|
|
583927f1b1 | ||
|
|
185a9b0082 | ||
|
|
ee7eb01af5 | ||
|
|
07c8034393 | ||
|
|
8ec73bc0f9 | ||
|
|
940b28ff6d | ||
|
|
316260703c | ||
|
|
e6c7b6572c | ||
|
|
0e50cf4927 | ||
|
|
f55dff1618 | ||
|
|
669916a5a9 | ||
|
|
4154f4d2f5 | ||
|
|
f3d133b7a3 | ||
|
|
a432da60e5 | ||
|
|
d34128b701 | ||
|
|
d16d0e62e2 | ||
|
|
1a6270478a | ||
|
|
4144e37049 | ||
|
|
61faced24d | ||
|
|
add73558f3 | ||
|
|
259f20794c | ||
|
|
afdb9a3c6b | ||
|
|
8581e7be1f | ||
|
|
5496aec95f | ||
|
|
5441bb0c02 | ||
|
|
3991d2935f | ||
|
|
c42115ba7d | ||
|
|
929987b2f1 | ||
|
|
9a0bead39c | ||
|
|
d18e9c116b | ||
|
|
e477e54dcd | ||
|
|
48b9cb23eb | ||
|
|
5db027c1cf | ||
|
|
128fd8a792 | ||
|
|
32043b7df0 | ||
|
|
bb847cd94c | ||
|
|
64b836363b | ||
|
|
5f34652905 | ||
|
|
32078bc7e3 | ||
|
|
9c582c26db | ||
|
|
aa5addb280 | ||
|
|
2d8ae1bbe2 | ||
|
|
5405d3a9a8 | ||
|
|
d91cbc395c | ||
|
|
e44aedebad | ||
|
|
9ccbd46b71 | ||
|
|
3f0e9e44a9 | ||
|
|
32a847fc5d | ||
|
|
795ad51098 | ||
|
|
64827a60d8 | ||
|
|
b98d55ffb6 | ||
|
|
aa21862303 | ||
|
|
f205e4fa66 | ||
|
|
4ffecb9ce4 | ||
|
|
ecc95e9c82 | ||
|
|
4c5cbcb514 | ||
|
|
8bd24e9642 | ||
|
|
64234c26e0 | ||
|
|
0b1d31fb8a | ||
|
|
a638a6b1fc | ||
|
|
917e184486 | ||
|
|
bc17a6720a | ||
|
|
838cdbd350 | ||
|
|
e795b21499 | ||
|
|
2482e1a424 | ||
|
|
3fd9e3ce9d | ||
|
|
67524f7ede | ||
|
|
721a585eb9 | ||
|
|
45e210dade | ||
|
|
1d98803763 | ||
|
|
568a68916c | ||
|
|
89ed1c0715 | ||
|
|
a1ca26c47e | ||
|
|
27392d6000 | ||
|
|
f5ebd06fc0 | ||
|
|
7b933188f8 | ||
|
|
ec5a89e716 | ||
|
|
f8b1c29638 | ||
|
|
cdbee2b429 | ||
|
|
ad748bf14c | ||
|
|
a7ca2ed0fc | ||
|
|
e32042af35 | ||
|
|
40d2ac9ecf | ||
|
|
324b7bf103 | ||
|
|
3dd3b1df34 | ||
|
|
bbd0b07540 | ||
|
|
9a37280d14 | ||
|
|
1b83c0f7f6 | ||
|
|
c83310b98e | ||
|
|
6a4bd4e5da | ||
|
|
bca50a5d30 | ||
|
|
45b856946d | ||
|
|
3fcc96cd02 | ||
|
|
0894cb5a47 | ||
|
|
1ac6537496 | ||
|
|
285ea66197 | ||
|
|
1edb313e2c | ||
|
|
c9755e0116 | ||
|
|
367a4a2e36 | ||
|
|
fc65ee29ee | ||
|
|
f0e9684f51 | ||
|
|
7084eec407 | ||
|
|
ef79dac603 | ||
|
|
89ee80e4ef | ||
|
|
73df75a519 | ||
|
|
7ed0d6d355 | ||
|
|
b4a0a72db3 | ||
|
|
63e0f1ea14 |
20
README.md
20
README.md
@@ -41,10 +41,7 @@ Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
|
||||
* **Ctrl + Alt + R** - rename for bouquet.
|
||||
* **Ctrl + S, T** in Satellites edit tool for create satellite or transponder.
|
||||
* **Ctrl + L** - parental lock.
|
||||
* **Ctrl + H** - hide/skip.
|
||||
* **Ctrl + P** - start play IPTV or other stream in the bouquet list.
|
||||
* **Ctrl + Z** - switch(**zap**) the channel(works when the HTTP API is enabled, Enigma2 only).
|
||||
* **Ctrl + W** - switch to the channel and watch in the program.
|
||||
* **Ctrl + H** - hide/skip.
|
||||
* **Space** - select/deselect.
|
||||
* **Left/Right** - remove selection.
|
||||
* **Ctrl + Up, Down, PageUp, PageDown, Home, End**- move selected items in the list.
|
||||
@@ -56,13 +53,16 @@ Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
|
||||
* **Ctrl + Shift + F** - show/hide filter bar.
|
||||
* **Ctrl + T** - show/hide built-in Telnet client.
|
||||
* **Ctrl + Shift + L** - show/hide logging panel.
|
||||
* **Shift + P** - start play IPTV or other stream in the bouquet list.
|
||||
* **Shift + Z** - switch(**zap**) the channel(works when the HTTP API is enabled, Enigma2 only).
|
||||
* **Shift + W** - switch to the channel and watch in the program.
|
||||
|
||||
For **multiple** selection with the mouse, press and hold the **Ctrl** key!
|
||||
|
||||
## Minimum requirements
|
||||
*Python >= 3.6, GTK+ >= 3.22, python3-gi, python3-gi-cairo, python3-requests.*
|
||||
|
||||
***Optional:** python3-pil, python3-chardet.*
|
||||
***Optional:** python3-pil, python3-chardet, ffmpeg.*
|
||||
## 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
|
||||
@@ -75,10 +75,13 @@ Users of **LTS** versions of [Ubuntu](https://ubuntu.com/) or distributions base
|
||||
A ready-made [package](https://aur.archlinux.org/packages/demoneditor-bin) is also available for [Arch Linux](https://archlinux.org/) users in the [AUR](https://aur.archlinux.org/) repository.
|
||||
* ### macOS
|
||||
**This program can be run on macOS.**
|
||||
To run the program on macOS, you need to install [brew](https://brew.sh/).
|
||||
To run the program on macOS, you need to install [Homebrew](https://brew.sh/).
|
||||
Then install the required components via terminal:
|
||||
```brew install python3 gtk+3 pygobject3 adwaita-icon-theme```
|
||||
```pip3 install requests, pillow```
|
||||
```brew install python3 gtk+3 pygobject3 adwaita-icon-theme gtksourceview3```
|
||||
|
||||
```pip3 install requests telnetlib-313-and-up --break-system-packages```
|
||||
|
||||
*Optional:* ```brew install pillow python-chardet ffmpeg```
|
||||
|
||||
Launch is similar to Linux.
|
||||
|
||||
@@ -98,7 +101,6 @@ THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY.
|
||||
AUTHOR IS NOT LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY CONNECTION WITH THIS SOFTWARE.
|
||||
|
||||
## 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!
|
||||
Support for DVB-T/T2 and DVB-C channels for Neutrino is not fully implemented and has an experimental status.
|
||||
|
||||
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.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -48,10 +48,10 @@ from app.settings import SettingsType
|
||||
BQ_FILES_LIST = ("tv", "radio", # Enigma2.
|
||||
"services.xml", "myservices.xml", "bouquets.xml", "ubouquets.xml") # Neutrino.
|
||||
|
||||
DATA_FILES_LIST = ("lamedb", "lamedb5", "blacklist", "whitelist",)
|
||||
DATA_FILES_LIST = ("lamedb", "lamedb5", "blacklist", "whitelist", "whitelist_streamrelay")
|
||||
|
||||
STC_XML_FILE = ("satellites.xml", "terrestrial.xml", "cables.xml")
|
||||
WEB_TV_XML_FILE = ("webtv.xml",)
|
||||
WEB_TV_XML_FILE = ("webtv.xml", "webtv_usr.xml")
|
||||
PICONS_SUF = (".jpg", ".png")
|
||||
PICONS_MAX_NUM = 1000 # Maximum picon number for sending without compression.
|
||||
|
||||
@@ -59,10 +59,15 @@ PICONS_MAX_NUM = 1000 # Maximum picon number for sending without compression.
|
||||
class DownloadType(Enum):
|
||||
ALL = 0
|
||||
BOUQUETS = 1
|
||||
SATELLITES = 2
|
||||
PICONS = 3
|
||||
WEBTV = 4
|
||||
EPG = 5
|
||||
SERVICES = 2
|
||||
SATELLITES = 3
|
||||
PICONS = 4
|
||||
WEBTV = 5
|
||||
EPG = 6
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
return cls.ALL
|
||||
|
||||
|
||||
class TestException(Exception):
|
||||
@@ -373,9 +378,11 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=log, fil
|
||||
save_path = settings.profile_data_path
|
||||
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
||||
# bouquets
|
||||
if download_type is DownloadType.ALL or download_type is DownloadType.BOUQUETS:
|
||||
if download_type in (DownloadType.ALL, DownloadType.BOUQUETS, DownloadType.SERVICES):
|
||||
ftp.cwd(settings.services_path)
|
||||
file_list = BQ_FILES_LIST + DATA_FILES_LIST if download_type is DownloadType.ALL else BQ_FILES_LIST
|
||||
file_list = BQ_FILES_LIST
|
||||
if download_type is DownloadType.ALL or DownloadType.SERVICES:
|
||||
file_list += DATA_FILES_LIST
|
||||
ftp.download_files(save_path, file_list, callback)
|
||||
# *.xml and webtv
|
||||
if download_type in (DownloadType.ALL, DownloadType.SATELLITES):
|
||||
@@ -426,7 +433,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
|
||||
|
||||
ht.send((f"{url}message?{params}", "Sending info message... "))
|
||||
|
||||
if s_type is SettingsType.ENIGMA_2 and download_type is DownloadType.ALL:
|
||||
if s_type is SettingsType.ENIGMA_2 and download_type in (DownloadType.ALL, DownloadType.SERVICES):
|
||||
time.sleep(5)
|
||||
if not settings.keep_power_mode:
|
||||
ht.send((f"{url}powerstate?newstate=0", "Toggle Standby "))
|
||||
@@ -457,8 +464,10 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
|
||||
ftp.cwd(services_path)
|
||||
ftp.upload_bouquets(data_path, settings.remove_unused_bouquets, callback)
|
||||
|
||||
if download_type is DownloadType.ALL:
|
||||
ftp.upload_xml(data_path, sat_xml_path, STC_XML_FILE, callback)
|
||||
if download_type is DownloadType.ALL or download_type is DownloadType.SERVICES:
|
||||
if download_type is DownloadType.ALL:
|
||||
ftp.upload_xml(data_path, sat_xml_path, STC_XML_FILE, callback)
|
||||
|
||||
if s_type is SettingsType.NEUTRINO_MP:
|
||||
ftp.upload_xml(data_path, sat_xml_path, WEB_TV_XML_FILE, callback)
|
||||
|
||||
@@ -518,10 +527,14 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
if download_type is DownloadType.BOUQUETS:
|
||||
ht.send((f"{url}servicelistreload?mode=2", "Reloading Userbouquets."))
|
||||
elif download_type is DownloadType.ALL:
|
||||
elif download_type is DownloadType.ALL or download_type is DownloadType.SERVICES:
|
||||
ht.send((f"{url}servicelistreload?mode=0", "Reloading lamedb and Userbouquets."))
|
||||
time.sleep(1)
|
||||
ht.send((f"{url}servicelistreload?mode=4", "Updating parental control."))
|
||||
if not settings.keep_power_mode:
|
||||
ht.send((f"{url}powerstate?newstate=4", "Wakeup from Standby."))
|
||||
elif download_type is DownloadType.SATELLITES:
|
||||
ht.send((f"{url}servicelistreload?mode=3", "Reloading transponders."))
|
||||
else:
|
||||
ht.send((f"{url}reloadchannels", "Reloading channels..."))
|
||||
|
||||
@@ -537,6 +550,8 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
|
||||
def get_upload_info_message(download_type):
|
||||
if download_type is DownloadType.BOUQUETS:
|
||||
return "User bouquets will be updated!"
|
||||
if download_type is DownloadType.SERVICES:
|
||||
return "User bouquets and services list will be updated!"
|
||||
elif download_type is DownloadType.ALL:
|
||||
return "All user data will be reloaded!"
|
||||
elif download_type is DownloadType.SATELLITES:
|
||||
|
||||
@@ -29,7 +29,7 @@ from app.commons import run_task
|
||||
from app.settings import SettingsType
|
||||
from .ecommons import Service, Satellite, Transponder, Bouquet, Bouquets, is_transponder_valid
|
||||
from .enigma.blacklist import get_blacklist, write_blacklist
|
||||
from .enigma.bouquets import to_bouquet_id, BouquetsWriter, BouquetsReader
|
||||
from .enigma.bouquets import BouquetsWriter, BouquetsReader
|
||||
from .enigma.lamedb import get_services as get_enigma_services, write_services as write_enigma_services
|
||||
from .iptv import parse_m3u
|
||||
from .neutrino.bouquets import get_bouquets as get_neutrino_bouquets, write_bouquets as write_neutrino_bouquets
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -47,10 +47,10 @@ class BouquetsWriter:
|
||||
If "force_bq_names" then naming the files using the name of the bouquet.
|
||||
Some images may have problems displaying the favorites list!
|
||||
"""
|
||||
_SERVICE = '#SERVICE 1:{}:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
|
||||
_SERVICE = '#SERVICE 1:{}:{}:0:0:0:0:0:0:0:FROM BOUQUET "{}" ORDER BY bouquet\n'
|
||||
_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"
|
||||
_LOCKED = '1:{}:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet'
|
||||
_LOCKED = '1:{}:{}:0:0:0:0:0:0:0:FROM BOUQUET "{}" ORDER BY bouquet'
|
||||
_ALT = '#SERVICE 1:134:1:0:0:0:0:0:0:0:FROM BOUQUET "{}" ORDER BY bouquet\n'
|
||||
_ALT_PAT = r"[<>:\"/\\|?*\-\s]"
|
||||
|
||||
@@ -76,18 +76,18 @@ class BouquetsWriter:
|
||||
m_count = 0
|
||||
|
||||
for bq in bqs.bouquets:
|
||||
bq_name = bq.file
|
||||
if not bq_name:
|
||||
if self._force_bq_names:
|
||||
bq_name = re.sub(self._NAME_PATTERN, "_", bq.name)
|
||||
else:
|
||||
bq_name = f"de{count:02d}"
|
||||
while bq_name in bq_file_names:
|
||||
count += 1
|
||||
bq_name = f"de{count:02d}"
|
||||
bq_file_names.add(bq_name)
|
||||
|
||||
f_name = bq.file
|
||||
bq_type = BqType(bq.type)
|
||||
if not f_name:
|
||||
if self._force_bq_names or bq_type is BqType.BOUQUET:
|
||||
f_name = f"userbouquet.{re.sub(self._NAME_PATTERN, '_', bq.name)}.{bqs.type}"
|
||||
else:
|
||||
f_name = f"userbouquet.de{count:02d}.{bqs.type}"
|
||||
while f_name in bq_file_names:
|
||||
count += 1
|
||||
f_name = f"userbouquet.de{count:02d}.{bqs.type}"
|
||||
bq_file_names.add(f_name)
|
||||
|
||||
if bq_type is BqType.MARKER:
|
||||
m_data = bq.file.split(":") if bq.file else None
|
||||
b_name = m_data[-1].strip() if m_data else bq.name.lstrip(_MARKER_PREFIX)
|
||||
@@ -95,17 +95,16 @@ class BouquetsWriter:
|
||||
m_count += 1
|
||||
else:
|
||||
if bq_type is BqType.BOUQUET:
|
||||
bq_name = re.sub(self._NAME_PATTERN, "_", bq.name)
|
||||
self.write_sub_bouquet(self._path, bq_name, bq, bqs.type)
|
||||
self.write_sub_bouquet(self._path, f_name, bq, bqs.type)
|
||||
else:
|
||||
self.write_bouquet(f"{self._path}userbouquet.{bq_name}.{bqs.type}", bq.name, bq.services)
|
||||
self.write_bouquet(f"{self._path}{f_name}", bq.name, bq.services)
|
||||
bq_type = 2 if bqs.type == BqType.RADIO.value else 1
|
||||
# Parental lock.
|
||||
locked = self._LOCKED.format(ServiceType.SERVICE, bq_type, bq_name, bqs.type)
|
||||
locked = self._LOCKED.format(ServiceType.SERVICE, bq_type, f_name)
|
||||
self._black_list.add(locked) if bq.locked else self._black_list.discard(locked)
|
||||
# Hiding.
|
||||
s_type = ServiceType.HIDDEN if bq.hidden else ServiceType.BOUQUET
|
||||
line.append(self._SERVICE.format(s_type, bq_type, bq_name, bqs.type))
|
||||
line.append(self._SERVICE.format(s_type, bq_type, f_name))
|
||||
|
||||
with open(f"{self._path}bouquets.{bqs.type}", "w", encoding="utf-8", newline="\n") as file:
|
||||
file.writelines(line)
|
||||
@@ -139,11 +138,10 @@ class BouquetsWriter:
|
||||
bouquet.append(self._ALT.format(f_name))
|
||||
self.write_bouquet(f"{p.parent}/{f_name}", srv.service, services)
|
||||
else:
|
||||
data = to_bouquet_id(srv)
|
||||
if srv.service:
|
||||
bouquet.append(f"#SERVICE {data}:{srv.service}\n#DESCRIPTION {srv.service}\n")
|
||||
bouquet.append(f"#SERVICE {srv.fav_id}:{srv.service}\n#DESCRIPTION {srv.service}\n")
|
||||
else:
|
||||
bouquet.append(f"#SERVICE {data}\n")
|
||||
bouquet.append(f"#SERVICE {srv.fav_id}\n")
|
||||
|
||||
with open(path, "w", encoding="utf-8", newline="\n") as file:
|
||||
file.writelines(bouquet)
|
||||
@@ -153,11 +151,11 @@ class BouquetsWriter:
|
||||
sb_type = 2 if bq_type == BqType.RADIO.value else 1
|
||||
|
||||
for sb in bq.services:
|
||||
bq_name = f"subbouquet.{re.sub(self._NAME_PATTERN, '_', sb.name)}.{sb.type}"
|
||||
self.write_bouquet(f"{path}{bq_name}", sb.name, sb.services)
|
||||
bouquet.append(f"#SERVICE 1:7:{sb_type}:0:0:0:0:0:0:0:FROM BOUQUET \"{bq_name}\" ORDER BY bouquet\n")
|
||||
sb_file = sb.file or f"subbouquet.{re.sub(self._NAME_PATTERN, '_', sb.name)}.{sb.type}"
|
||||
self.write_bouquet(f"{path}{sb_file}", sb.name, sb.services)
|
||||
bouquet.append(f"#SERVICE 1:7:{sb_type}:0:0:0:0:0:0:0:FROM BOUQUET \"{sb_file}\" ORDER BY bouquet\n")
|
||||
|
||||
with open(f"{self._path}userbouquet.{file_name}.{bq_type}", "w", encoding="utf-8", newline="\n") as file:
|
||||
with open(f"{self._path}{file_name}", "w", encoding="utf-8", newline="\n") as file:
|
||||
file.writelines(bouquet)
|
||||
|
||||
|
||||
@@ -181,9 +179,9 @@ class ServiceType(Enum):
|
||||
|
||||
class BouquetsReader:
|
||||
""" Class for reading and parsing bouquets. """
|
||||
_ALT_PAT = re.compile(r".*alternatives\.+(.*)\.([tv|radio]+).*")
|
||||
_BQ_PAT = re.compile(r".*\s+\W(.*bouquet)\.+(.*)\.+[tv|radio].*")
|
||||
_SUB_BQ_PAT = re.compile(r".*subbouquet\.+(.*)\.([tv|radio]+).*")
|
||||
_BQ_PAT = re.compile(r".*FROM BOUQUET\s+\"((.*bouquet|alternatives)?\.?([\w-]+)\.?(\w+)?)\"\s+.*$", re.IGNORECASE)
|
||||
_BQ_PAT2 = re.compile(r"#SERVICE:+\s+(?:[0-9a-f]+:+)+([^:]+[.](?:tv|radio))$", re.IGNORECASE)
|
||||
_BQ_POST_PAT = re.compile(r".*FROM BOUQUET\s+\"((.*bouquet|alternatives)?\.?(.*)\.?(\w+)?)\"\s+.*$", re.IGNORECASE)
|
||||
_STREAM_TYPES = {"4097", "5001", "5002", "8193", "8739"}
|
||||
|
||||
__slots__ = ["_path"]
|
||||
@@ -209,29 +207,41 @@ class BouquetsReader:
|
||||
|
||||
for line in file.readlines():
|
||||
if "#SERVICE" in line:
|
||||
name = re.match(self._BQ_PAT, line)
|
||||
s_data = line.split(":")
|
||||
s_type = ServiceType(s_data[1])
|
||||
if name:
|
||||
prefix, b_name = name.group(1), name.group(2)
|
||||
s_type = ServiceType.BOUQUET
|
||||
|
||||
mt = re.match(self._BQ_PAT, line) or re.match(self._BQ_PAT2, line)
|
||||
if not mt:
|
||||
# Additional file name checking.
|
||||
mt = re.match(self._BQ_POST_PAT, line)
|
||||
if mt:
|
||||
log(f"Warning: The bouquet file name may be formed incorrectly. -> {mt.group(1)}")
|
||||
|
||||
if mt:
|
||||
if len(mt.groups()) > 1:
|
||||
file_name, prefix, b_name = mt.group(1), mt.group(2), mt.group(3)
|
||||
s_type = ServiceType(s_data[1])
|
||||
s_data[:2] = "10"
|
||||
else:
|
||||
file_name, prefix, b_name = mt.group(1), "", ""
|
||||
s_type = ServiceType(s_data[2])
|
||||
|
||||
if b_name in b_names:
|
||||
log(f"The list of bouquets contains duplicate [{b_name}] names!")
|
||||
else:
|
||||
b_names.add(b_name)
|
||||
|
||||
rb_name, services = self.get_bouquet(self._path, b_name, bq_type, prefix)
|
||||
rb_name, services = self.get_bouquet(self._path, file_name, b_name)
|
||||
if rb_name in real_b_names:
|
||||
log(f"Bouquet file '{prefix}.{b_name}.{bq_type}' has duplicate name: {rb_name}")
|
||||
log(f"Bouquet file '{file_name}' has duplicate name: {rb_name}")
|
||||
real_b_names[rb_name] += 1
|
||||
rb_name = f"{rb_name} {real_b_names[rb_name]}"
|
||||
else:
|
||||
real_b_names[rb_name] = 0
|
||||
# Locked, hidden.
|
||||
s_data[:2] = "10"
|
||||
locked = ":".join(s_data).rstrip()
|
||||
hidden = s_type is ServiceType.HIDDEN
|
||||
|
||||
bouquets[2].append(Bouquet(rb_name, bq_type, services, locked, hidden, b_name))
|
||||
bouquets[2].append(Bouquet(rb_name, bq_type, services, locked, hidden, file_name))
|
||||
else:
|
||||
if len(s_data) == 12 and s_type is ServiceType.MARKER:
|
||||
b_name = f"{_MARKER_PREFIX}{s_data[-1].strip()}"
|
||||
@@ -244,15 +254,15 @@ class BouquetsReader:
|
||||
return bouquets
|
||||
|
||||
@staticmethod
|
||||
def get_bouquet(path, bq_name, bq_type, prefix="userbouquet"):
|
||||
def get_bouquet(path, f_name, bq_name):
|
||||
""" Parsing services ids from bouquet file. """
|
||||
with open(f"{path}{prefix}.{bq_name}.{bq_type}", encoding="utf-8", errors="replace") as file:
|
||||
with open(f"{path}{f_name}", encoding="utf-8", errors="replace") as file:
|
||||
chs_list = file.read()
|
||||
services = []
|
||||
srvs = list(filter(None, chs_list.split("\n#SERVICE"))) # filtering ['']
|
||||
# May come across empty[wrong] files!
|
||||
if not srvs:
|
||||
log(f"Bouquet file 'userbouquet.{bq_name}.{bq_type}' is empty or wrong!")
|
||||
log(f"Bouquet file '{f_name}' is empty or wrong!")
|
||||
return f"{bq_name} [empty]", services
|
||||
|
||||
bq_name = srvs.pop(0)
|
||||
@@ -272,40 +282,32 @@ class BouquetsReader:
|
||||
m_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
services.append(BouquetService(desc.strip() if desc else "", BqServiceType.SPACE, srv, num))
|
||||
elif s_type is ServiceType.ALT:
|
||||
alt = re.match(BouquetsReader._ALT_PAT, srv)
|
||||
alt = re.match(BouquetsReader._BQ_PAT, srv)
|
||||
if alt:
|
||||
alt_name, alt_type = alt.group(1), alt.group(2)
|
||||
alt_bq_name, alt_srvs = BouquetsReader.get_bouquet(path, alt_name, alt_type, "alternatives")
|
||||
af_name, alt_name = alt.group(1), alt.group(3)
|
||||
alt_bq_name, alt_srvs = BouquetsReader.get_bouquet(path, af_name, alt_name)
|
||||
services.append(BouquetService(alt_bq_name, BqServiceType.ALT, alt_name, tuple(alt_srvs)))
|
||||
elif s_type is ServiceType.BOUQUET:
|
||||
sub = re.match(BouquetsReader._SUB_BQ_PAT, srv)
|
||||
sub = re.match(BouquetsReader._BQ_PAT, srv)
|
||||
if sub:
|
||||
sub_name, sub_type = sub.group(1), sub.group(2)
|
||||
sub_bq_name, sub_srvs = BouquetsReader.get_bouquet(path, sub_name, sub_type, "subbouquet")
|
||||
bq = Bouquet(sub_bq_name, sub_type, tuple(sub_srvs), None, None, sub_name)
|
||||
sf_name, sub_name, sub_type = sub.group(1), sub.group(3), sub.group(4)
|
||||
sub_bq_name, sub_srvs = BouquetsReader.get_bouquet(path, sf_name, sub_name)
|
||||
bq = Bouquet(sub_bq_name, sub_type, tuple(sub_srvs), None, None, sf_name)
|
||||
services.append(BouquetService(sub_bq_name, BqServiceType.BOUQUET, bq, num))
|
||||
elif srv_data[0].strip() in BouquetsReader._STREAM_TYPES or srv_data[10].startswith(("http", "rtsp")):
|
||||
stream_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
desc = desc.lstrip(":").strip() if desc else srv_data[-1].strip()
|
||||
services.append(BouquetService(desc, BqServiceType.IPTV, srv, num))
|
||||
else:
|
||||
fav_id = f"{srv_data[3]}:{srv_data[4]}:{srv_data[5]}:{srv_data[6]}"
|
||||
fav_id = srv.strip().upper()
|
||||
name = None
|
||||
if data_len == 12:
|
||||
fav_id = f":".join(srv_data[:11])
|
||||
name, sep, desc = str(srv_data[-1]).partition("\n#DESCRIPTION")
|
||||
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id.upper(), num))
|
||||
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id, num))
|
||||
|
||||
return bq_name.lstrip("#NAME").strip(), services
|
||||
|
||||
|
||||
def to_bouquet_id(srv):
|
||||
""" Creates bouquet channel id. """
|
||||
data_type = srv.data_id
|
||||
if data_type and len(data_type) > 4:
|
||||
data_type = int(srv.data_id.split(":")[4])
|
||||
|
||||
return "{}:0:{:X}:{}:0:0:0:".format(1, data_type, srv.fav_id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -112,14 +112,17 @@ class LameDbReader:
|
||||
if lns and not lns[0].endswith("/5/\n"):
|
||||
raise SyntaxError("lamedb ver.5 parsing error: unsupported format.")
|
||||
|
||||
trs, srvs = {}, [""]
|
||||
trs, srvs = {}, []
|
||||
for line in lns:
|
||||
if line.startswith("s:"):
|
||||
srv_data = line.strip("s:").split(",", 2)
|
||||
srv_data[1] = srv_data[1].strip("\"\n")
|
||||
data_len = len(srv_data)
|
||||
if data_len == 3:
|
||||
srv_data[2] = srv_data[2].strip()
|
||||
s_data = srv_data[2].strip()
|
||||
if not s_data.startswith("p:"):
|
||||
s_data = f"p:,{s_data}"
|
||||
srv_data[2] = s_data
|
||||
elif data_len == 2:
|
||||
srv_data.append("p:")
|
||||
srvs.extend(srv_data)
|
||||
@@ -168,15 +171,14 @@ class LameDbReader:
|
||||
ssid = str(data[0]).lstrip(sp).upper()
|
||||
onid = str(data[1]).lstrip(sp).upper()
|
||||
# For comparison in bouquets. Needed in upper case!!!
|
||||
fav_id = f"{ssid}:{tid}:{nid}:{onid}"
|
||||
fav_id = f"1:0:{srv_type:X}:{ssid}:{tid}:{nid}:{onid}:0:0:0:"
|
||||
picon_id = f"1_0_{srv_type:X}_{ssid}_{tid}_{nid}_{onid}_0_0_0.png"
|
||||
s_id = f"1:0:{srv_type:X}:{ssid}:{tid}:{nid}:{onid}:0:0:0:"
|
||||
|
||||
all_flags = srv[2].split(",")
|
||||
coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None
|
||||
flags = list(filter(lambda x: x.startswith("f:"), all_flags))
|
||||
hide = HIDE_ICON if flags and Flag.is_hide(Flag.parse(flags[0])) else None
|
||||
locked = LOCKED_ICON if s_id in blacklist else None
|
||||
locked = LOCKED_ICON if fav_id in blacklist else None
|
||||
|
||||
package = list(filter(lambda x: x.startswith("p:"), all_flags))
|
||||
package = package[0][2:] if package else ""
|
||||
|
||||
78
app/eparser/enigma/streamrelay.py
Normal file
78
app/eparser/enigma/streamrelay.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2024 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# Author: Dmitriy Yefremov
|
||||
#
|
||||
|
||||
|
||||
""" Additional module to use stream relay functionality.
|
||||
|
||||
Reads/Writes 'whitelist_streamrelay' file.
|
||||
"""
|
||||
import os.path
|
||||
from contextlib import suppress
|
||||
|
||||
from app.commons import log
|
||||
|
||||
_FILE_NAME = "whitelist_streamrelay"
|
||||
|
||||
|
||||
class StreamRelay(dict):
|
||||
""" Class to hold/process service references used by a stream relay. """
|
||||
|
||||
def refresh(self, path):
|
||||
self.clear()
|
||||
f_path = f"{path}{_FILE_NAME}"
|
||||
if os.path.isfile(f_path):
|
||||
log("Updating stream relay cache...")
|
||||
with suppress(FileNotFoundError):
|
||||
with open(f"{path}{_FILE_NAME}", "r", encoding="utf-8") as file:
|
||||
refs = filter(None, (x.rstrip("\n") for x in file.readlines()))
|
||||
self.update(self.get_ref_data(ref) for ref in refs)
|
||||
|
||||
def get_ref_data(self, ref):
|
||||
""" Returns tuple from FAV ID and ref or ref and None for comments. """
|
||||
data = ref.split(":")
|
||||
if len(data) == 11:
|
||||
if "http" in data[-1]:
|
||||
return ref.replace("%3a", "%3A"), ref
|
||||
return f"{data[3]}:{data[4]}:{data[5]}:{data[6]}", ref
|
||||
return ref, None
|
||||
|
||||
def save(self, path):
|
||||
""" Saves current refs to a file.
|
||||
|
||||
If no refs is present, delites current relay file.
|
||||
"""
|
||||
f_name = f"{path}{_FILE_NAME}"
|
||||
if len(self):
|
||||
with open(f_name, "w", encoding="utf-8") as file:
|
||||
file.writelines([f"{v if v else k}\n\n" for k, v in self.items()])
|
||||
else:
|
||||
if os.path.exists(f_name):
|
||||
os.remove(f_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -42,6 +42,7 @@ ENIGMA2_FAV_ID_FORMAT = " {}:{}:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTI
|
||||
MARKER_FORMAT = " 1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n"
|
||||
PICON_FORMAT = "{}_{}_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png"
|
||||
|
||||
ENCODING_BLACKLIST = {"MacRoman"}
|
||||
|
||||
class StreamType(Enum):
|
||||
DVB_TS = "1"
|
||||
@@ -58,6 +59,9 @@ class StreamType(Enum):
|
||||
|
||||
|
||||
def parse_m3u(path, s_type, detect_encoding=True, params=None):
|
||||
""" Parses *m3u* file and returns tuple with EPG src URLs and services list. """
|
||||
pattern = re.compile(r'(\S+)="(.*?)"')
|
||||
|
||||
with open(path, "rb") as file:
|
||||
data = file.read()
|
||||
encoding = "utf-8"
|
||||
@@ -70,11 +74,14 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None):
|
||||
else:
|
||||
enc = chardet.detect(data)
|
||||
encoding = enc.get("encoding", "utf-8")
|
||||
encoding = "utf-8" if encoding in ENCODING_BLACKLIST else encoding
|
||||
|
||||
aggr = [None] * 10
|
||||
s_aggr = aggr[: -3]
|
||||
services = []
|
||||
epg_src = None
|
||||
group = None
|
||||
groups = set()
|
||||
services = []
|
||||
marker_counter = 1
|
||||
sid_counter = 1
|
||||
name = None
|
||||
@@ -85,66 +92,66 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None):
|
||||
m_name = BqServiceType.MARKER.name
|
||||
|
||||
for line in str(data, encoding=encoding, errors="ignore").splitlines():
|
||||
if line.startswith("#EXTM3U"):
|
||||
data = dict(pattern.findall(line))
|
||||
epg_src = data.get("x-tvg-url", data.get("url-tvg", None))
|
||||
epg_src = epg_src.split(",") if epg_src else None
|
||||
if line.startswith("#EXTINF"):
|
||||
line, sep, name = line.rpartition(",")
|
||||
|
||||
data = re.split('"', line)
|
||||
size = len(data)
|
||||
if size < 3:
|
||||
continue
|
||||
d = {data[i].lower().strip(" ="): data[i + 1] for i in range(0, len(data) - 1, 2)}
|
||||
picon = d.get("tvg-logo", None)
|
||||
data = dict(pattern.findall(line))
|
||||
name = data.get("tvg-name", name)
|
||||
picon = data.get("tvg-logo", None)
|
||||
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
grp_name = d.get("group-title", None)
|
||||
if grp_name not in groups:
|
||||
groups.add(grp_name)
|
||||
fav_id = MARKER_FORMAT.format(marker_counter, grp_name, grp_name)
|
||||
marker_counter += 1
|
||||
mr = Service(None, None, None, grp_name, *aggr[0:3], m_name, *aggr, fav_id, None)
|
||||
services.append(mr)
|
||||
group = data.get("group-title", None)
|
||||
elif line.startswith("#EXTGRP") and s_type is SettingsType.ENIGMA_2:
|
||||
grp_name = line.strip("#EXTGRP:").strip()
|
||||
if grp_name not in groups:
|
||||
groups.add(grp_name)
|
||||
fav_id = MARKER_FORMAT.format(marker_counter, grp_name, grp_name)
|
||||
marker_counter += 1
|
||||
mr = Service(None, None, None, grp_name, *aggr[0:3], m_name, *aggr, fav_id, None)
|
||||
services.append(mr)
|
||||
elif not line.startswith("#"):
|
||||
group = line.strip("#EXTGRP:").strip()
|
||||
elif not line.startswith("#") and "://" in line:
|
||||
url = line.strip()
|
||||
params[0] = sid_counter
|
||||
sid_counter += 1
|
||||
fav_id = get_fav_id(url, name, s_type, params)
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
p_id = get_picon_id(params)
|
||||
if group not in groups:
|
||||
# Some playlists have "random" of group names.
|
||||
# We will take only the first one we found on the list!
|
||||
groups.add(group)
|
||||
m_id = MARKER_FORMAT.format(marker_counter, group, group)
|
||||
marker_counter += 1
|
||||
services.append(Service(None, None, None, group, *aggr[0:3], m_name, *aggr, m_id, None))
|
||||
|
||||
if all((name, url, fav_id)):
|
||||
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], st, picon, p_id, *s_aggr, url, fav_id, None)
|
||||
services.append(srv)
|
||||
services.append(Service(None, None, IPTV_ICON, name, *aggr[0:2], group,
|
||||
st, picon, p_id, *s_aggr, url, fav_id, None))
|
||||
else:
|
||||
log(f"*.m3u* parse error ['{path}']: name[{name}], url[{url}], fav id[{fav_id}]")
|
||||
|
||||
return services
|
||||
return epg_src, services
|
||||
|
||||
|
||||
def export_to_m3u(path, bouquet, s_type, url=None):
|
||||
pattern = re.compile(".*:(http.*):.*") if s_type is SettingsType.ENIGMA_2 else re.compile("(http.*?)::::.*")
|
||||
pattern = re.compile(".*:(http.*).*") if s_type is SettingsType.ENIGMA_2 else re.compile("(http.*?)::::.*")
|
||||
lines = ["#EXTM3U\n"]
|
||||
current_grp = None
|
||||
|
||||
for s in bouquet.services:
|
||||
s_type = s.type
|
||||
if s_type is BqServiceType.IPTV:
|
||||
srv_type = s.type
|
||||
if srv_type is BqServiceType.IPTV:
|
||||
res = re.match(pattern, s.data)
|
||||
if not res:
|
||||
continue
|
||||
lines.append(f"#EXTINF:-1,{s.name}\n")
|
||||
lines.append(current_grp) if current_grp else None
|
||||
lines.append(f"{unquote(res.group(1).strip())}\n")
|
||||
elif s_type is BqServiceType.MARKER:
|
||||
u = res.group(1)
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
index = u.rfind(":")
|
||||
lines.append(f"{unquote(u[:index] if index > 0 else u)}\n")
|
||||
else:
|
||||
lines.append(f"{u}\n")
|
||||
elif srv_type is BqServiceType.MARKER:
|
||||
current_grp = f"#EXTGRP:{s.name}\n"
|
||||
elif s_type is BqServiceType.DEFAULT and url:
|
||||
elif srv_type is BqServiceType.DEFAULT and url:
|
||||
lines.append(f"#EXTINF:-1,{s.name}\n")
|
||||
lines.append(current_grp) if current_grp else None
|
||||
lines.append(f"{url}{s.data}\n")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
import os
|
||||
|
||||
from app.commons import log
|
||||
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT
|
||||
from app.eparser.neutrino import KSP, SP, get_xml_attributes, get_attributes, API_VER
|
||||
from app.eparser.neutrino.nxml import XmlHandler, NeutrinoDocument
|
||||
@@ -35,7 +36,8 @@ from ..ecommons import Bouquets, Bouquet, BouquetService, BqServiceType, PROVIDE
|
||||
|
||||
_FILE = "bouquets.xml"
|
||||
_U_FILE = "ubouquets.xml"
|
||||
_W_FILE = "webtv.xml"
|
||||
_W_FILE = "webtv_usr.xml"
|
||||
_WEB_TV_NAME = "[Web TV]"
|
||||
|
||||
_COMMENT = " File was created in DemonEditor. Enjoy watching! "
|
||||
|
||||
@@ -60,20 +62,27 @@ def parse_bouquets(file, name, bq_type):
|
||||
hidden = bq_attrs.get("hidden", "0")
|
||||
locked = bq_attrs.get("locked", "0")
|
||||
services = []
|
||||
|
||||
for srv_elem in elem.getElementsByTagName("S"):
|
||||
if srv_elem.hasAttributes():
|
||||
s_attrs = get_xml_attributes(srv_elem)
|
||||
ssid = s_attrs.get("i", "0")
|
||||
on = s_attrs.get("on", "0")
|
||||
tr_id = s_attrs.get("t", "0")
|
||||
fav_id = "{}:{}:{}".format(tr_id, on, ssid)
|
||||
services.append(BouquetService(None, BqServiceType.DEFAULT, fav_id, 0))
|
||||
if "i" in s_attrs:
|
||||
ssid = s_attrs.get("i", "0")
|
||||
on = s_attrs.get("on", "0")
|
||||
tr_id = s_attrs.get("t", "0")
|
||||
fav_id = f"{tr_id}:{on}:{ssid}"
|
||||
services.append(BouquetService(None, BqServiceType.DEFAULT, fav_id, 0))
|
||||
elif "u" in s_attrs:
|
||||
services.append(get_webtv_service(s_attrs))
|
||||
else:
|
||||
log(f"Parse bouquets [Neutrino] error: Unknown service type. -> {s_attrs}")
|
||||
|
||||
bouquets[2].append(Bouquet(name=bq_name,
|
||||
type=bq_type,
|
||||
services=services,
|
||||
locked=locked == "1",
|
||||
hidden=hidden == "1",
|
||||
file=SP.join("{}{}{}".format(k, KSP, v) for k, v in bq_attrs.items())))
|
||||
file=SP.join(f"{k}{KSP}{v}" for k, v in bq_attrs.items())))
|
||||
|
||||
if BqType(bq_type) is BqType.BOUQUET:
|
||||
for bq in bouquets.bouquets:
|
||||
@@ -92,31 +101,40 @@ def parse_webtv(path, name, bq_type):
|
||||
return bouquets
|
||||
|
||||
dom = XmlHandler.parse(path)
|
||||
# Display name.
|
||||
name = None
|
||||
for e in dom.childNodes:
|
||||
if e.nodeType == e.ELEMENT_NODE:
|
||||
name = e.getAttribute("name")
|
||||
break
|
||||
|
||||
services = []
|
||||
for elem in dom.getElementsByTagName("webtv"):
|
||||
if elem.hasAttributes():
|
||||
web_attrs = get_xml_attributes(elem)
|
||||
title = web_attrs.get("title", "")
|
||||
url = web_attrs.get("url", "")
|
||||
description = web_attrs.get("description", "")
|
||||
urlkey = web_attrs.get("urlkey", None)
|
||||
account = web_attrs.get("account", None)
|
||||
usrname = web_attrs.get("usrname", None)
|
||||
psw = web_attrs.get("psw", None)
|
||||
s_type = web_attrs.get("type", None)
|
||||
iconsrc = web_attrs.get("iconsrc", None)
|
||||
iconsrc_b = web_attrs.get("iconsrc_b", None)
|
||||
group = web_attrs.get("group", None)
|
||||
fav_id = NEUTRINO_FAV_ID_FORMAT.format(url, description, urlkey, account, usrname, psw, s_type, iconsrc,
|
||||
iconsrc_b, group)
|
||||
services.append(BouquetService(name=title, type=BqServiceType.IPTV, data=fav_id, num=0))
|
||||
services.append(get_webtv_service(web_attrs))
|
||||
|
||||
bouquet = Bouquet(name="default", type=bq_type, services=services, locked=None, hidden=None, file=None)
|
||||
bouquet = Bouquet(name=name or _WEB_TV_NAME, type=bq_type, services=services, locked=None, hidden=None, file=None)
|
||||
bouquets[2].append(bouquet)
|
||||
|
||||
return bouquets
|
||||
|
||||
|
||||
def get_webtv_service(web_attrs):
|
||||
title = web_attrs.get("title", web_attrs.get("n", ""))
|
||||
fav_id = NEUTRINO_FAV_ID_FORMAT.format(web_attrs.get("url", web_attrs.get("u", )),
|
||||
web_attrs.get("description", ""),
|
||||
web_attrs.get("urlkey", None),
|
||||
web_attrs.get("account", None),
|
||||
web_attrs.get("usrname", None),
|
||||
web_attrs.get("psw", None),
|
||||
web_attrs.get("type", None),
|
||||
web_attrs.get("iconsrc", None),
|
||||
web_attrs.get("iconsrc_b", None),
|
||||
web_attrs.get("group", None))
|
||||
return BouquetService(name=title, type=BqServiceType.IPTV, data=fav_id, num=0)
|
||||
|
||||
|
||||
def write_bouquets(path, bouquets):
|
||||
for bq in bouquets:
|
||||
bq_type = BqType(bq.type)
|
||||
@@ -153,14 +171,25 @@ def write_bouquet(file, bouquet):
|
||||
root.appendChild(bq_elem)
|
||||
|
||||
for srv in bq.services:
|
||||
tr_id, on, ssid = srv.fav_id.split(":")
|
||||
srv_elem = doc.createElement("S")
|
||||
srv_elem.setAttribute("i", ssid)
|
||||
srv_elem.setAttribute("n", srv.service)
|
||||
srv_elem.setAttribute("t", tr_id)
|
||||
srv_elem.setAttribute("on", on)
|
||||
srv_elem.setAttribute("frq", srv.freq)
|
||||
srv_elem.setAttribute("s", get_attributes(srv.flags_cas).get("position", "0"))
|
||||
s_type = BqServiceType(srv.service_type)
|
||||
|
||||
if s_type is BqServiceType.DEFAULT:
|
||||
tr_id, on, ssid = srv.fav_id.split(":")
|
||||
srv_elem.setAttribute("i", ssid)
|
||||
srv_elem.setAttribute("t", tr_id)
|
||||
srv_elem.setAttribute("on", on)
|
||||
srv_elem.setAttribute("frq", srv.freq)
|
||||
srv_elem.setAttribute("s", get_attributes(srv.flags_cas).get("position", "0"))
|
||||
elif s_type is BqServiceType.IPTV:
|
||||
s_data = srv.fav_id.split("::")
|
||||
if s_data:
|
||||
srv_elem.setAttribute("n", srv.service)
|
||||
srv_elem.setAttribute("u", s_data[0])
|
||||
else:
|
||||
log(f"Write bouquet [Neutrino] error: Unsupported service type. -> {s_type.value}")
|
||||
|
||||
bq_elem.appendChild(srv_elem)
|
||||
|
||||
doc.write_xml(file)
|
||||
@@ -174,6 +203,7 @@ def write_webtv(file, bouquet):
|
||||
doc.appendChild(comment)
|
||||
|
||||
for bq in bouquet.bouquets:
|
||||
root.setAttribute("name", bq.name or _WEB_TV_NAME)
|
||||
for srv in bq.services:
|
||||
url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group = srv.fav_id.split("::")
|
||||
srv_elem = doc.createElement("webtv")
|
||||
|
||||
129
app/settings.py
129
app/settings.py
@@ -31,7 +31,7 @@ import json
|
||||
import locale
|
||||
import os
|
||||
import sys
|
||||
from enum import Enum, IntEnum
|
||||
from enum import IntEnum
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from pprint import pformat
|
||||
@@ -51,8 +51,8 @@ IS_LINUX = sys.platform == "linux"
|
||||
USE_HEADER_BAR = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
|
||||
|
||||
|
||||
class Defaults(Enum):
|
||||
""" Default program settings """
|
||||
class Defaults:
|
||||
""" Default program settings. """
|
||||
USER = "root"
|
||||
PASSWORD = ""
|
||||
HOST = "127.0.0.1"
|
||||
@@ -114,30 +114,30 @@ class SettingsType(IntEnum):
|
||||
def get_default_settings(self):
|
||||
""" Returns default settings for current type. """
|
||||
if self is self.ENIGMA_2:
|
||||
srv_path = Defaults.BOX_SERVICES_PATH.value
|
||||
sat_path = Defaults.BOX_SATELLITE_PATH.value
|
||||
picons_path = Defaults.BOX_PICON_PATH.value
|
||||
epg_path = Defaults.BOX_EPG_PATH.value
|
||||
srv_path = Defaults.BOX_SERVICES_PATH
|
||||
sat_path = Defaults.BOX_SATELLITE_PATH
|
||||
picons_path = Defaults.BOX_PICON_PATH
|
||||
epg_path = Defaults.BOX_EPG_PATH
|
||||
http_timeout = 5
|
||||
telnet_timeout = 5
|
||||
else:
|
||||
srv_path = Defaults.NEUTRINO_BOX_SERVICES_PATH.value
|
||||
sat_path = Defaults.NEUTRINO_BOX_SATELLITE_PATH.value
|
||||
picons_path = Defaults.NEUTRINO_BOX_PICON_PATH.value
|
||||
srv_path = Defaults.NEUTRINO_BOX_SERVICES_PATH
|
||||
sat_path = Defaults.NEUTRINO_BOX_SATELLITE_PATH
|
||||
picons_path = Defaults.NEUTRINO_BOX_PICON_PATH
|
||||
epg_path = ""
|
||||
http_timeout = 2
|
||||
telnet_timeout = 1
|
||||
|
||||
return {"setting_type": self.value,
|
||||
"host": Defaults.HOST.value,
|
||||
"port": Defaults.FTP_PORT.value,
|
||||
"host": Defaults.HOST,
|
||||
"port": Defaults.FTP_PORT,
|
||||
"timeout": 5,
|
||||
"user": Defaults.USER.value,
|
||||
"password": Defaults.PASSWORD.value,
|
||||
"http_port": Defaults.HTTP_PORT.value,
|
||||
"user": Defaults.USER,
|
||||
"password": Defaults.PASSWORD,
|
||||
"http_port": Defaults.HTTP_PORT,
|
||||
"http_timeout": http_timeout,
|
||||
"http_use_ssl": Defaults.HTTP_USE_SSL.value,
|
||||
"telnet_port": Defaults.TELNET_PORT.value,
|
||||
"http_use_ssl": Defaults.HTTP_USE_SSL,
|
||||
"telnet_port": Defaults.TELNET_PORT,
|
||||
"telnet_timeout": telnet_timeout,
|
||||
"services_path": srv_path,
|
||||
"user_bouquet_path": srv_path,
|
||||
@@ -411,9 +411,9 @@ class Settings:
|
||||
@property
|
||||
def picons_paths(self):
|
||||
if self.setting_type is SettingsType.NEUTRINO_MP:
|
||||
return self._settings.get("neutrino_picon_paths", Defaults.NEUTRINO_BOX_PICON_PATHS.value)
|
||||
return self._settings.get("neutrino_picon_paths", Defaults.NEUTRINO_BOX_PICON_PATHS)
|
||||
else:
|
||||
return self._settings.get("picon_paths", Defaults.BOX_PICON_PATHS.value)
|
||||
return self._settings.get("picon_paths", Defaults.BOX_PICON_PATHS)
|
||||
|
||||
@picons_paths.setter
|
||||
def picons_paths(self, value):
|
||||
@@ -426,12 +426,20 @@ class Settings:
|
||||
|
||||
@property
|
||||
def profile_folder_is_default(self):
|
||||
return self._settings.get("profile_folder_is_default", Defaults.PROFILE_FOLDER_DEFAULT.value)
|
||||
return self._settings.get("profile_folder_is_default", Defaults.PROFILE_FOLDER_DEFAULT)
|
||||
|
||||
@profile_folder_is_default.setter
|
||||
def profile_folder_is_default(self, value):
|
||||
self._settings["profile_folder_is_default"] = value
|
||||
|
||||
@property
|
||||
def use_common_picon_path(self):
|
||||
return self._settings.get("use_common_picon_path", False)
|
||||
|
||||
@use_common_picon_path.setter
|
||||
def use_common_picon_path(self, value):
|
||||
self._settings["use_common_picon_path"] = value
|
||||
|
||||
@property
|
||||
def default_data_path(self):
|
||||
return self._settings.get("default_data_path", DATA_PATH)
|
||||
@@ -442,7 +450,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def default_backup_path(self):
|
||||
return self._settings.get("default_backup_path", Defaults.BACKUP_PATH.value)
|
||||
return self._settings.get("default_backup_path", Defaults.BACKUP_PATH)
|
||||
|
||||
@default_backup_path.setter
|
||||
def default_backup_path(self, value):
|
||||
@@ -450,7 +458,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def default_picon_path(self):
|
||||
return self._settings.get("default_picon_path", Defaults.PICON_PATH.value)
|
||||
return self._settings.get("default_picon_path", Defaults.PICON_PATH)
|
||||
|
||||
@default_picon_path.setter
|
||||
def default_picon_path(self, value):
|
||||
@@ -466,6 +474,9 @@ class Settings:
|
||||
|
||||
@property
|
||||
def profile_picons_path(self):
|
||||
if self.use_common_picon_path:
|
||||
return self.default_picon_path
|
||||
|
||||
if self.profile_folder_is_default:
|
||||
return f"{self.profile_data_path}picons{SEP}"
|
||||
return f"{self.default_picon_path}{self._current_profile}{SEP}"
|
||||
@@ -486,7 +497,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def recordings_path(self):
|
||||
return self._settings.get("recordings_path", Defaults.RECORDINGS_PATH.value)
|
||||
return self._settings.get("recordings_path", Defaults.RECORDINGS_PATH)
|
||||
|
||||
@recordings_path.setter
|
||||
def recordings_path(self, value):
|
||||
@@ -496,7 +507,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def activate_transcoding(self):
|
||||
return self._settings.get("activate_transcoding", Defaults.ACTIVATE_TRANSCODING.value)
|
||||
return self._settings.get("activate_transcoding", Defaults.ACTIVATE_TRANSCODING)
|
||||
|
||||
@activate_transcoding.setter
|
||||
def activate_transcoding(self, value):
|
||||
@@ -504,7 +515,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def active_preset(self):
|
||||
return self._settings.get("active_preset", Defaults.ACTIVE_TRANSCODING_PRESET.value)
|
||||
return self._settings.get("active_preset", Defaults.ACTIVE_TRANSCODING_PRESET)
|
||||
|
||||
@active_preset.setter
|
||||
def active_preset(self, value):
|
||||
@@ -520,7 +531,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def play_streams_mode(self):
|
||||
return PlayStreamsMode(self._settings.get("play_streams_mode", Defaults.PLAY_STREAMS_MODE.value))
|
||||
return PlayStreamsMode(self._settings.get("play_streams_mode", Defaults.PLAY_STREAMS_MODE))
|
||||
|
||||
@play_streams_mode.setter
|
||||
def play_streams_mode(self, value):
|
||||
@@ -528,7 +539,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def stream_lib(self):
|
||||
return self._settings.get("stream_lib", Defaults.STREAM_LIB.value)
|
||||
return self._settings.get("stream_lib", Defaults.STREAM_LIB)
|
||||
|
||||
@stream_lib.setter
|
||||
def stream_lib(self, value):
|
||||
@@ -536,7 +547,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def fav_click_mode(self):
|
||||
return self._settings.get("fav_click_mode", Defaults.FAV_CLICK_MODE.value)
|
||||
return self._settings.get("fav_click_mode", Defaults.FAV_CLICK_MODE)
|
||||
|
||||
@fav_click_mode.setter
|
||||
def fav_click_mode(self, value):
|
||||
@@ -544,7 +555,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def main_list_playback(self):
|
||||
return self._settings.get("main_list_playback", Defaults.MAIN_LIST_PLAYBACK.value)
|
||||
return self._settings.get("main_list_playback", Defaults.MAIN_LIST_PLAYBACK)
|
||||
|
||||
@main_list_playback.setter
|
||||
def main_list_playback(self, value):
|
||||
@@ -585,6 +596,14 @@ class Settings:
|
||||
def epg_xml_source(self, value):
|
||||
self._cp_settings["epg_xml_source"] = value
|
||||
|
||||
@property
|
||||
def epg_xml_sources(self):
|
||||
return self._cp_settings.get("epg_xml_sources", [self.epg_xml_source])
|
||||
|
||||
@epg_xml_sources.setter
|
||||
def epg_xml_sources(self, value):
|
||||
self._cp_settings["epg_xml_sources"] = value
|
||||
|
||||
# *********** FTP ************ #
|
||||
|
||||
@property
|
||||
@@ -599,7 +618,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def backup_before_save(self):
|
||||
return self._settings.get("backup_before_save", Defaults.BACKUP_BEFORE_SAVE.value)
|
||||
return self._settings.get("backup_before_save", Defaults.BACKUP_BEFORE_SAVE)
|
||||
|
||||
@backup_before_save.setter
|
||||
def backup_before_save(self, value):
|
||||
@@ -607,7 +626,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def backup_before_downloading(self):
|
||||
return self._settings.get("backup_before_downloading", Defaults.BACKUP_BEFORE_DOWNLOADING.value)
|
||||
return self._settings.get("backup_before_downloading", Defaults.BACKUP_BEFORE_DOWNLOADING)
|
||||
|
||||
@backup_before_downloading.setter
|
||||
def backup_before_downloading(self, value):
|
||||
@@ -615,7 +634,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def v5_support(self):
|
||||
return self._settings.get("v5_support", Defaults.V5_SUPPORT.value)
|
||||
return self._settings.get("v5_support", Defaults.V5_SUPPORT)
|
||||
|
||||
@v5_support.setter
|
||||
def v5_support(self, value):
|
||||
@@ -623,7 +642,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def unlimited_copy_buffer(self):
|
||||
return self._settings.get("unlimited_copy_buffer", Defaults.UNLIMITED_COPY_BUFFER.value)
|
||||
return self._settings.get("unlimited_copy_buffer", Defaults.UNLIMITED_COPY_BUFFER)
|
||||
|
||||
@unlimited_copy_buffer.setter
|
||||
def unlimited_copy_buffer(self, value):
|
||||
@@ -631,7 +650,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def extensions_support(self):
|
||||
return self._settings.get("extensions_support", Defaults.EXTENSIONS_SUPPORT.value)
|
||||
return self._settings.get("extensions_support", Defaults.EXTENSIONS_SUPPORT)
|
||||
|
||||
@extensions_support.setter
|
||||
def extensions_support(self, value):
|
||||
@@ -639,7 +658,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def force_bq_names(self):
|
||||
return self._settings.get("force_bq_names", Defaults.FORCE_BQ_NAMES.value)
|
||||
return self._settings.get("force_bq_names", Defaults.FORCE_BQ_NAMES)
|
||||
|
||||
@force_bq_names.setter
|
||||
def force_bq_names(self, value):
|
||||
@@ -647,7 +666,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def http_api_support(self):
|
||||
return self._settings.get("http_api_support", Defaults.HTTP_API_SUPPORT.value)
|
||||
return self._settings.get("http_api_support", Defaults.HTTP_API_SUPPORT)
|
||||
|
||||
@http_api_support.setter
|
||||
def http_api_support(self, value):
|
||||
@@ -655,7 +674,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def enable_yt_dl(self):
|
||||
return self._settings.get("enable_yt_dl", Defaults.ENABLE_YT_DL.value)
|
||||
return self._settings.get("enable_yt_dl", Defaults.ENABLE_YT_DL)
|
||||
|
||||
@enable_yt_dl.setter
|
||||
def enable_yt_dl(self, value):
|
||||
@@ -663,7 +682,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def enable_yt_dl_update(self):
|
||||
return self._settings.get("enable_yt_dl_update", Defaults.ENABLE_YT_DL.value)
|
||||
return self._settings.get("enable_yt_dl_update", Defaults.ENABLE_YT_DL)
|
||||
|
||||
@enable_yt_dl_update.setter
|
||||
def enable_yt_dl_update(self, value):
|
||||
@@ -671,7 +690,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def enable_send_to(self):
|
||||
return self._settings.get("enable_send_to", Defaults.ENABLE_SEND_TO.value)
|
||||
return self._settings.get("enable_send_to", Defaults.ENABLE_SEND_TO)
|
||||
|
||||
@enable_send_to.setter
|
||||
def enable_send_to(self, value):
|
||||
@@ -731,7 +750,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def list_picon_size(self):
|
||||
return self._settings.get("list_picon_size", Defaults.LIST_PICON_SIZE.value)
|
||||
return self._settings.get("list_picon_size", Defaults.LIST_PICON_SIZE)
|
||||
|
||||
@list_picon_size.setter
|
||||
def list_picon_size(self, value):
|
||||
@@ -739,7 +758,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def tooltip_logo_size(self):
|
||||
return self._settings.get("tooltip_logo_size", Defaults.TOOLTIP_LOGO_SIZE.value)
|
||||
return self._settings.get("tooltip_logo_size", Defaults.TOOLTIP_LOGO_SIZE)
|
||||
|
||||
@tooltip_logo_size.setter
|
||||
def tooltip_logo_size(self, value):
|
||||
@@ -747,7 +766,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def use_colors(self):
|
||||
return self._settings.get("use_colors", Defaults.USE_COLORS.value)
|
||||
return self._settings.get("use_colors", Defaults.USE_COLORS)
|
||||
|
||||
@use_colors.setter
|
||||
def use_colors(self, value):
|
||||
@@ -755,7 +774,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def new_color(self):
|
||||
return self._settings.get("new_color", Defaults.NEW_COLOR.value)
|
||||
return self._settings.get("new_color", Defaults.NEW_COLOR)
|
||||
|
||||
@new_color.setter
|
||||
def new_color(self, value):
|
||||
@@ -763,7 +782,7 @@ class Settings:
|
||||
|
||||
@property
|
||||
def extra_color(self):
|
||||
return self._settings.get("extra_color", Defaults.EXTRA_COLOR.value)
|
||||
return self._settings.get("extra_color", Defaults.EXTRA_COLOR)
|
||||
|
||||
@extra_color.setter
|
||||
def extra_color(self, value):
|
||||
@@ -929,18 +948,18 @@ class Settings:
|
||||
|
||||
return {
|
||||
"version": Settings.__VERSION,
|
||||
"default_profile": Defaults.DEFAULT_PROFILE.value,
|
||||
"default_profile": Defaults.DEFAULT_PROFILE,
|
||||
"profiles": {profile_name: def_settings},
|
||||
"v5_support": Defaults.V5_SUPPORT.value,
|
||||
"http_api_support": Defaults.HTTP_API_SUPPORT.value,
|
||||
"enable_yt_dl": Defaults.ENABLE_YT_DL.value,
|
||||
"enable_send_to": Defaults.ENABLE_SEND_TO.value,
|
||||
"use_colors": Defaults.USE_COLORS.value,
|
||||
"new_color": Defaults.NEW_COLOR.value,
|
||||
"extra_color": Defaults.EXTRA_COLOR.value,
|
||||
"fav_click_mode": Defaults.FAV_CLICK_MODE.value,
|
||||
"profile_folder_is_default": Defaults.PROFILE_FOLDER_DEFAULT.value,
|
||||
"records_path": Defaults.RECORDINGS_PATH.value
|
||||
"v5_support": Defaults.V5_SUPPORT,
|
||||
"http_api_support": Defaults.HTTP_API_SUPPORT,
|
||||
"enable_yt_dl": Defaults.ENABLE_YT_DL,
|
||||
"enable_send_to": Defaults.ENABLE_SEND_TO,
|
||||
"use_colors": Defaults.USE_COLORS,
|
||||
"new_color": Defaults.NEW_COLOR,
|
||||
"extra_color": Defaults.EXTRA_COLOR,
|
||||
"fav_click_mode": Defaults.FAV_CLICK_MODE,
|
||||
"profile_folder_is_default": Defaults.PROFILE_FOLDER_DEFAULT,
|
||||
"records_path": Defaults.RECORDINGS_PATH
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
||||
100
app/tools/epg.py
100
app/tools/epg.py
@@ -34,7 +34,7 @@ import shutil
|
||||
import struct
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
from collections import namedtuple
|
||||
from collections import namedtuple, defaultdict
|
||||
from datetime import datetime, timezone
|
||||
from tempfile import NamedTemporaryFile
|
||||
from urllib.parse import urlparse
|
||||
@@ -61,6 +61,10 @@ EpgEvent.__new__.__defaults__ = ("N/A", "N/A", 0, 0, 0, "N/A", None) # For Pyth
|
||||
|
||||
class Reader(metaclass=abc.ABCMeta):
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def cache(self) -> dict: pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def download(self, clb=None): pass
|
||||
|
||||
@@ -121,6 +125,10 @@ class EPG:
|
||||
self._refs = {}
|
||||
self._desc = {}
|
||||
|
||||
@property
|
||||
def cache(self) -> dict:
|
||||
return self._refs
|
||||
|
||||
def download(self, clb=None):
|
||||
pass
|
||||
|
||||
@@ -229,13 +237,19 @@ class XmlTvReader(Reader):
|
||||
|
||||
TIME_FORMAT_STR = "%Y%m%d%H%M%S %z"
|
||||
|
||||
SUFFIXES = {".gz", ".xz", ".lzma", ".xml"}
|
||||
|
||||
Service = namedtuple("Service", ["id", "names", "logo", "events"])
|
||||
Event = namedtuple("EpgEvent", ["start", "duration", "title", "desc"])
|
||||
|
||||
def __init__(self, path, url):
|
||||
def __init__(self, path, url=None):
|
||||
self._path = path
|
||||
self._url = url
|
||||
self._ids = {}
|
||||
self._cache = {}
|
||||
|
||||
@property
|
||||
def cache(self) -> dict:
|
||||
return self._cache
|
||||
|
||||
def download(self, clb=None):
|
||||
""" Downloads an XMLTV file. """
|
||||
@@ -244,23 +258,28 @@ class XmlTvReader(Reader):
|
||||
log(f"{self.__class__.__name__} [download] error: Invalid URL {self._url}")
|
||||
return
|
||||
|
||||
with requests.get(url=self._url, stream=True) as request:
|
||||
if request.reason == "OK":
|
||||
with requests.get(url=self._url, stream=True) as resp:
|
||||
if resp.reason == "OK":
|
||||
suf = self._url[self._url.rfind("."):]
|
||||
if suf not in (".gz", ".xz", ".lzma"):
|
||||
if suf not in self.SUFFIXES:
|
||||
log(f"{self.__class__.__name__} [download] error: Unsupported file extension.")
|
||||
return
|
||||
|
||||
data_len = request.headers.get("content-length")
|
||||
data_size = resp.headers.get("content-length")
|
||||
if not data_size:
|
||||
log(f"{self.__class__.__name__} [download *.{suf}] error: Error getting data size.")
|
||||
if clb:
|
||||
clb()
|
||||
return
|
||||
|
||||
with NamedTemporaryFile(suffix=suf, delete=not IS_WIN) as tf:
|
||||
downloaded = 0
|
||||
data_len = int(data_len)
|
||||
data_size = int(data_size)
|
||||
log("Downloading XMLTV file...")
|
||||
for data in request.iter_content(chunk_size=1024):
|
||||
for data in resp.iter_content(chunk_size=1024):
|
||||
downloaded += len(data)
|
||||
tf.write(data)
|
||||
done = int(50 * downloaded / data_len)
|
||||
done = int(50 * downloaded / data_size)
|
||||
sys.stdout.write(f"\rDownloading XMLTV file [{'=' * done}{' ' * (50 - done)}]")
|
||||
sys.stdout.flush()
|
||||
tf.seek(0)
|
||||
@@ -281,47 +300,70 @@ class XmlTvReader(Reader):
|
||||
shutil.copyfileobj(lzf, self._path)
|
||||
except (lzma.LZMAError, OSError) as e:
|
||||
log(f"{self.__class__.__name__} [download *.xz] error: {e}")
|
||||
else:
|
||||
try:
|
||||
import gzip
|
||||
with gzip.open(self._path, "wb") as f_out:
|
||||
shutil.copyfileobj(tf, f_out)
|
||||
except OSError as e:
|
||||
log(f"{self.__class__.__name__} [download *.xml] error: {e}")
|
||||
|
||||
if IS_WIN and os.path.isfile(tf.name):
|
||||
tf.close()
|
||||
os.remove(tf.name)
|
||||
else:
|
||||
log(f"{self.__class__.__name__} [download] error: {request.reason}")
|
||||
log(f"{self.__class__.__name__} [download] error: {resp.reason}")
|
||||
|
||||
if clb:
|
||||
clb()
|
||||
|
||||
def get_current_events(self, names: set) -> dict:
|
||||
events = {}
|
||||
events = defaultdict(list)
|
||||
|
||||
dt = datetime.utcnow()
|
||||
utc = dt.timestamp()
|
||||
offset = datetime.now() - dt
|
||||
|
||||
for srv in filter(lambda s: any(name in names for name in s.names), self._ids.values()):
|
||||
ev = max(filter(lambda s: s.start < utc, srv.events), key=lambda x: x.start, default=None)
|
||||
if ev:
|
||||
start = datetime.fromtimestamp(ev.start) + offset
|
||||
end_time = datetime.fromtimestamp(ev.duration) + offset
|
||||
start = start.timestamp()
|
||||
end_time = end_time.timestamp()
|
||||
|
||||
for n in srv.names:
|
||||
events[n] = EpgEvent(n, ev.title, start, end_time, int(ev.duration), ev.desc, ev)
|
||||
for srv in filter(lambda s: any(name in names for name in s.names), self._cache.values()):
|
||||
[self.process_event(ev, events, offset, srv) for ev in filter(lambda s: s.duration > utc, srv.events)]
|
||||
|
||||
return events
|
||||
|
||||
@staticmethod
|
||||
def process_event(ev, events, offset, srv):
|
||||
start = datetime.fromtimestamp(ev.start) + offset
|
||||
end_time = datetime.fromtimestamp(ev.duration) + offset
|
||||
start = start.timestamp()
|
||||
end_time = end_time.timestamp()
|
||||
duration = end_time - start
|
||||
|
||||
for n in srv.names:
|
||||
data = {"e2eventservicename": n,
|
||||
"e2eventtitle": ev.title,
|
||||
"e2eventdescription": ev.desc,
|
||||
"e2eventstart": start,
|
||||
"e2eventduration": duration}
|
||||
events[n].append(EpgEvent(n, ev.title, start, end_time, duration, ev.desc, data))
|
||||
|
||||
def parse(self):
|
||||
""" Parses XML. """
|
||||
try:
|
||||
import gzip
|
||||
log("Processing XMLTV data...")
|
||||
suf = os.path.splitext(self._path)[1]
|
||||
if suf == ".gz":
|
||||
import gzip
|
||||
|
||||
with gzip.open(self._path, "rb") as gzf:
|
||||
log("Processing XMLTV data...")
|
||||
list(map(self.process_node, ET.iterparse(gzf)))
|
||||
log("XMLTV data parsing is complete.")
|
||||
with gzip.open(self._path, "rb") as gzf:
|
||||
list(map(self.process_node, ET.iterparse(gzf)))
|
||||
elif suf == ".xml":
|
||||
with open(self._path, "rb") as xml:
|
||||
list(map(self.process_node, ET.iterparse(xml)))
|
||||
else:
|
||||
log(f"{self.__class__.__name__} [parse] error: Unsupported file type [{suf}].")
|
||||
except OSError as e:
|
||||
log(f"{self.__class__.__name__} [parse] error: {e}")
|
||||
else:
|
||||
log("XMLTV data parsing is complete.")
|
||||
|
||||
def process_node(self, node):
|
||||
event, element = node
|
||||
@@ -329,9 +371,9 @@ class XmlTvReader(Reader):
|
||||
ch_id = element.get("id", None)
|
||||
logo = None # Currently not in use.
|
||||
# Since a service can have several names, we will store a set of names in the "names" field!
|
||||
self._ids[ch_id] = self.Service(ch_id, {c.text for c in element if c.tag == self.DSP_NAME_TAG}, logo, [])
|
||||
self._cache[ch_id] = self.Service(ch_id, {c.text for c in element if c.tag == self.DSP_NAME_TAG}, logo, [])
|
||||
elif element.tag == self.PR_TAG:
|
||||
channel = self._ids.get(element.get(self.CH_TAG, None), None)
|
||||
channel = self._cache.get(element.get(self.CH_TAG, None), None)
|
||||
if channel:
|
||||
events = channel[-1]
|
||||
start = element.get("start", None)
|
||||
|
||||
@@ -443,10 +443,8 @@ class ServicesParser(HTMLParser):
|
||||
|
||||
self._POS_PAT = re.compile(r".*?(\d+\.\d°[EW]).*")
|
||||
# LyngSat.
|
||||
self._TR_PAT = re.compile((r".*?(\d+)\.?\d?\s+([RLHV]).*(DVB-S[2]?)/?(.*PSK)?\s"
|
||||
r"?(T2-MI)?\s?(PLS\s+Multistream)?\s?"
|
||||
r"SR-FEC:\s(\d+)-(\d/\d)\s+.*ONID-TID:\s+(\d+)-(\d+).*"))
|
||||
|
||||
self._TR_PAT = re.compile(r".*?(\d{4,5})\.?\d?\s+([RLHV]).*(DVB-S2?X?)/?(.*PSK)?.*SR-FEC:\s(\d+)-(\d+/\d+).*")
|
||||
self._ID_PAT = re.compile(r"C/N lock:.*?(?:.*ONID-TID:\s+(\d+)-(\d+))?.*")
|
||||
self._MULTI_PAT = re.compile(r"PLS\s+(Root|Gold|Combo)+\s(\d+)?\s+(?:Stream\s(\d+))")
|
||||
# KingOfSat.
|
||||
self._KING_TR_PAT = re.compile((r"(DVB-S[2]?)\s?(?:T2-MI,\s+PLP\s+(\d+))?.*"
|
||||
@@ -616,13 +614,12 @@ class ServicesParser(HTMLParser):
|
||||
services = []
|
||||
pos, freq, sr, fec, pol, nsp, tid, nid = sat_position or 0, 0, 0, 0, 0, 0, 0, 0
|
||||
sys = "DVB-S"
|
||||
pos_found = False
|
||||
tr = None
|
||||
pos_found, tr, td, t_id = False, None, None, None
|
||||
# Multi-stream.
|
||||
multi_tr = None
|
||||
multi = False
|
||||
# Transponder.
|
||||
for r in filter(lambda x: x and 6 < len(x) < 9, self._rows):
|
||||
for r in self._rows:
|
||||
if not pos_found:
|
||||
pos_tr = re.match(self._POS_PAT, r[0].text)
|
||||
if not pos_tr:
|
||||
@@ -632,22 +629,23 @@ class ServicesParser(HTMLParser):
|
||||
pos = self.get_position(pos_tr.group(1))
|
||||
pos_found = True
|
||||
|
||||
if pos_found:
|
||||
text = " ".join(c.text for c in r[1:])
|
||||
td = re.match(self._TR_PAT, text)
|
||||
if td:
|
||||
if pos_found and not td:
|
||||
td = re.match(self._TR_PAT, " ".join(c.text for c in r))
|
||||
|
||||
if td and not t_id:
|
||||
t_id = re.match(self._ID_PAT, " ".join(c.text for c in r))
|
||||
if t_id:
|
||||
# The ONID-TID values may not present!
|
||||
_nid, _tid = t_id.group(1), t_id.group(2)
|
||||
if _nid and _tid:
|
||||
nid, tid = int(_nid), int(_tid)
|
||||
else:
|
||||
log((f"Values 'ONID-TID' for transponder [{self._t_url}] are not present."
|
||||
" Default values are used."))
|
||||
|
||||
freq, pol = int(td.group(1)), get_key_by_value(POLARIZATION, td.group(2))
|
||||
sys, mod, sr, _fec, = td.group(3), td.group(4), td.group(7), td.group(8)
|
||||
nid, tid = td.group(9), td.group(10)
|
||||
sys, mod, sr, _fec = td.group(3), td.group(4), td.group(5), td.group(6)
|
||||
sys, mod, fec, nsp, s2_flags, roll_off, pilot, inv = self.get_transponder_data(pos, _fec, sys, mod)
|
||||
nid, tid = int(nid), int(tid)
|
||||
|
||||
if td.group(5):
|
||||
log(f"Detected T2-MI transponder! [{freq} {sr} {pol}]")
|
||||
|
||||
if td.group(6):
|
||||
log(f"Detected multi-stream transponder! [{freq} {sr} {pol}]")
|
||||
multi = True
|
||||
|
||||
tr = self._TR.format(freq, sr, pol, fec, pos, inv, sys, s2_flags)
|
||||
|
||||
@@ -711,6 +709,10 @@ class ServicesParser(HTMLParser):
|
||||
sr, fec = sr_fec.split()
|
||||
pol = get_key_by_value(POLARIZATION, pol)
|
||||
sys, mod, fec, nsp, s2_flags, roll_off, pilot, inv = self.get_transponder_data(pos, fec, sys, mod)
|
||||
if not all((freq, nid, tid)):
|
||||
log(f"Error. Not enough parameters [Frequency={freq}, NID={nid}, TID={tid}].")
|
||||
continue
|
||||
|
||||
freq, nid, tid = int(float(freq)), int(nid), int(tid)
|
||||
tr = self._TR.format(freq, sr, pol, fec, pos, inv, sys, s2_flags)
|
||||
|
||||
@@ -765,11 +767,12 @@ class ServicesParser(HTMLParser):
|
||||
def get_service_data(s_type, pkg, sid, tid, nid, namespace, v_pid, a_pid, cas, use_pids=False):
|
||||
sid = int(sid)
|
||||
data_id = f"{sid:04x}:{namespace}:{tid:04x}:{nid:04x}:{s_type}:0:0"
|
||||
fav_id = f"{sid}:{tid}:{nid}:{namespace}"
|
||||
fav_id = f"1:0:{int(s_type):X}:{sid}:{tid}:{nid}:{namespace}:0:0:0:"
|
||||
picon_id = f"1_0_{int(s_type):X}_{sid}_{tid}_{nid}_{namespace}_0_0_0.png"
|
||||
# Flags.
|
||||
flags = f"p:{pkg}"
|
||||
cas = ",".join(get_key_by_value(CAS, c) or "C:0000" for c in cas.split()) if cas else None
|
||||
cas = ",".join(get_key_by_value(CAS, c) or "" for c in cas.split()) if cas else None
|
||||
|
||||
if use_pids:
|
||||
v_pid = f"c:00{int(v_pid):04x}" if v_pid else None
|
||||
a_pid = ",".join([f"c:01{int(p):04x}" for p in a_pid]) if a_pid else None
|
||||
|
||||
@@ -111,7 +111,7 @@ class YouTube:
|
||||
if not self._yt_dl:
|
||||
self._yt_dl = YouTubeDL.get_instance(self._settings, self._callback)
|
||||
if not self._yt_dl:
|
||||
raise YouTubeException("youtube-dl initialization error.")
|
||||
raise YouTubeException("yt-dlp initialization error.")
|
||||
return self._yt_dl.get_yt_link(url, skip_errors)
|
||||
|
||||
return self.get_yt_link_by_id(video_id)
|
||||
@@ -148,7 +148,7 @@ class YouTube:
|
||||
if self._settings.enable_yt_dl and url:
|
||||
try:
|
||||
if not self._yt_dl:
|
||||
raise YouTubeException("youtube-dl is not initialized!")
|
||||
raise YouTubeException("yt-dlp is not initialized!")
|
||||
|
||||
self._yt_dl.update_options({"noplaylist": False, "extract_flat": True})
|
||||
info = self._yt_dl.get_info(url, skip_errors=False)
|
||||
@@ -307,14 +307,14 @@ class PlayListParser(HTMLParser):
|
||||
|
||||
|
||||
class YouTubeDL:
|
||||
""" Utility class [experimental] for working with youtube-dl.
|
||||
""" Utility class [experimental] for working with yt-dlp.
|
||||
|
||||
[https://github.com/ytdl-org/youtube-dl]
|
||||
[https://github.com/yt-dlp/yt-dlp]
|
||||
"""
|
||||
|
||||
_DL_INSTANCE = None
|
||||
_DownloadError = None
|
||||
_LATEST_RELEASE_URL = "https://api.github.com/repos/ytdl-org/youtube-dl/releases/latest"
|
||||
_LATEST_RELEASE_URL = "https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest"
|
||||
_OPTIONS = {"noplaylist": True, # Single video instead of a playlist [ignoring playlist in URL].
|
||||
"extract_flat": False, # Do not resolve URLs, return the immediate result.
|
||||
"quiet": True, # Do not print messages to stdout.
|
||||
@@ -339,7 +339,7 @@ class YouTubeDL:
|
||||
return cls._DL_INSTANCE
|
||||
|
||||
def init(self):
|
||||
if not os.path.isfile(f"{self._path}youtube_dl{SEP}version.py"):
|
||||
if not os.path.isfile(f"{self._path}yt_dlp{SEP}version.py"):
|
||||
self.get_latest_release()
|
||||
|
||||
if self._path not in sys.path:
|
||||
@@ -349,39 +349,39 @@ class YouTubeDL:
|
||||
|
||||
def init_dl(self):
|
||||
try:
|
||||
import youtube_dl
|
||||
import yt_dlp
|
||||
except ModuleNotFoundError as e:
|
||||
log(f"YouTubeDLHelper error: {e}")
|
||||
raise YouTubeException(e)
|
||||
except ImportError as e:
|
||||
log(f"YouTubeDLHelper error: {e}")
|
||||
else:
|
||||
if self._path not in youtube_dl.__file__:
|
||||
msg = "Another version of youtube-dl was found on your system!"
|
||||
if self._path not in yt_dlp.__file__:
|
||||
msg = "Another version of yt-dlp was found on your system!"
|
||||
log(msg)
|
||||
raise YouTubeException(msg)
|
||||
|
||||
if self._update:
|
||||
if hasattr(youtube_dl.version, "__version__"):
|
||||
if hasattr(yt_dlp.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 = f"youtube-dl has new release!\nCurrent: {cur_ver}. Last: {l_ver}."
|
||||
cur_ver = yt_dlp.version.__version__
|
||||
if l_ver and yt_dlp.version.__version__ < l_ver:
|
||||
msg = f"yt-dlp has new release!\nCurrent: {cur_ver}. Last: {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..."
|
||||
self._DownloadError = yt_dlp.utils.DownloadError
|
||||
self._dl = yt_dlp.YoutubeDL(self._OPTIONS)
|
||||
msg = "yt-dlp 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"
|
||||
url = "https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest"
|
||||
try:
|
||||
with urlopen(url, timeout=10) as resp:
|
||||
return json.loads(resp.read().decode("utf-8")).get("tag_name", "0")
|
||||
@@ -391,7 +391,7 @@ class YouTubeDL:
|
||||
def get_latest_release(self):
|
||||
try:
|
||||
self._is_update_process = True
|
||||
log("Getting the last youtube-dl release...")
|
||||
log("Getting the last yt-dlp release...")
|
||||
|
||||
with urlopen(YouTubeDL._LATEST_RELEASE_URL, timeout=10) as resp:
|
||||
r = json.loads(resp.read().decode("utf-8"))
|
||||
@@ -400,7 +400,7 @@ class YouTubeDL:
|
||||
if os.path.isdir(self._path):
|
||||
shutil.rmtree(self._path)
|
||||
|
||||
zip_file = self._path + "yt.zip"
|
||||
zip_file = f"{self._path}yt.zip"
|
||||
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
||||
f_name, headers = urlretrieve(zip_url, filename=zip_file)
|
||||
|
||||
@@ -408,12 +408,12 @@ class YouTubeDL:
|
||||
|
||||
with zipfile.ZipFile(f_name) as arch:
|
||||
for info in arch.infolist():
|
||||
pref, sep, f = info.filename.partition("/youtube_dl/")
|
||||
pref, sep, f = info.filename.partition("/yt_dlp/")
|
||||
if sep:
|
||||
arch.extract(info.filename)
|
||||
shutil.move(info.filename, f"{self._path}{sep}{f}")
|
||||
shutil.rmtree(pref)
|
||||
msg = "Getting the last youtube-dl release is done!"
|
||||
msg = "Getting the last yt-dlp release is done!"
|
||||
show_notification(msg)
|
||||
log(msg)
|
||||
self._callback(msg, False)
|
||||
|
||||
@@ -172,6 +172,11 @@
|
||||
<attribute name="label" translatable="yes">Backups</attribute>
|
||||
<attribute name="action">app.on_backup_tool_show</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Boot Logo</attribute>
|
||||
<attribute name="action">app.on_boot_logo_tool_show</attribute>
|
||||
<attribute name="hidden-when">action-disabled</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Telnet</attribute>
|
||||
<attribute name="action">app.on_telnet_show</attribute>
|
||||
@@ -393,6 +398,11 @@
|
||||
<attribute name="label" translatable="yes">Backups</attribute>
|
||||
<attribute name="action">app.on_backup_tool_show</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Boot Logo</attribute>
|
||||
<attribute name="action">app.on_boot_logo_tool_show</attribute>
|
||||
<attribute name="hidden-when">action-disabled</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Telnet</attribute>
|
||||
<attribute name="action">app.on_telnet_show</attribute>
|
||||
@@ -424,14 +434,6 @@
|
||||
<attribute name="label" translatable="yes">Import YouTube playlist</attribute>
|
||||
<attribute name="action">app.on_import_yt_list</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Import m3u</attribute>
|
||||
<attribute name="action">app.on_import_m3u</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Export to m3u</attribute>
|
||||
<attribute name="action">app.on_export_iptv_to_m3u</attribute>
|
||||
</item>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">EPG configuration</attribute>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -38,9 +38,15 @@ from pathlib import Path
|
||||
from app.commons import run_idle, get_size_from_bytes
|
||||
from app.settings import SettingsType, SEP
|
||||
from app.ui.dialogs import show_dialog, DialogType, get_builder
|
||||
from app.ui.main_helper import append_text_to_tview
|
||||
from app.ui.main_helper import append_text_to_tview, show_info_bar_message
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, HeaderBar
|
||||
|
||||
KEEP_DATA = {"satellites.xml",
|
||||
"terrestrial.xml",
|
||||
"cables.xml",
|
||||
"whitelist",
|
||||
"whitelist_streamrelay"}
|
||||
|
||||
|
||||
class RestoreType(Enum):
|
||||
BOUQUETS = 0
|
||||
@@ -71,7 +77,6 @@ class BackupDialog:
|
||||
self._model = builder.get_object("main_list_store")
|
||||
self._main_view = builder.get_object("main_view")
|
||||
self._text_view = builder.get_object("text_view")
|
||||
self._text_view_scrolled_window = builder.get_object("text_view_scrolled_window")
|
||||
self._info_check_button = builder.get_object("info_check_button")
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._message_label = builder.get_object("message_label")
|
||||
@@ -149,16 +154,12 @@ class BackupDialog:
|
||||
menu.popup(None, None, None, None, event.button, event.time)
|
||||
|
||||
def on_info_button_toggled(self, button):
|
||||
active = button.get_active()
|
||||
self._text_view_scrolled_window.set_visible(active)
|
||||
if active:
|
||||
if button.get_active():
|
||||
self.on_cursor_changed(self._main_view)
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(True)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._message_label.set_text(text)
|
||||
show_info_bar_message(self._info_bar, self._message_label, text, message_type)
|
||||
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
@@ -196,7 +197,7 @@ class BackupDialog:
|
||||
return
|
||||
|
||||
file_name = model.get_value(model.get_iter(paths[0]), 0)
|
||||
full_file_name = self._backup_path + file_name + ".zip"
|
||||
full_file_name = f"{self._backup_path}{file_name}.zip"
|
||||
|
||||
try:
|
||||
if restore_type is RestoreType.ALL:
|
||||
@@ -237,18 +238,19 @@ class BackupDialog:
|
||||
self.restore(RestoreType.BOUQUETS)
|
||||
|
||||
|
||||
def backup_data(path, backup_path, move=True):
|
||||
def backup_data(path, backup_path, move=True, keep=None):
|
||||
""" Creating data backup from a folder at the specified path
|
||||
|
||||
Returns full path to the compressed file.
|
||||
"""
|
||||
keep = keep or KEEP_DATA
|
||||
backup_path = f"{backup_path}{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}{SEP}"
|
||||
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
# Backup files in data dir(skipping dirs and *.xml).
|
||||
for file in filter(lambda f: not f.endswith(".xml") and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
|
||||
# Backup files in data dir.
|
||||
for file in filter(lambda f: os.path.isfile(os.path.join(path, f)), os.listdir(path)):
|
||||
src, dst = os.path.join(path, file), backup_path + file
|
||||
shutil.move(src, dst) if move else shutil.copy(src, dst)
|
||||
shutil.move(src, dst) if move and file not in keep else shutil.copy(src, dst)
|
||||
# Compressing to zip and delete remaining files.
|
||||
zip_file = shutil.make_archive(backup_path.rstrip(SEP), "zip", backup_path)
|
||||
shutil.rmtree(backup_path)
|
||||
@@ -264,7 +266,7 @@ def restore_data(src, dst):
|
||||
|
||||
def clear_data_path(path):
|
||||
""" Clearing data at the specified path excluding *.xml file. """
|
||||
for file in filter(lambda f: not f.endswith(".xml") and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
|
||||
for file in filter(lambda f: f not in KEEP_DATA and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
|
||||
os.remove(os.path.join(path, file))
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1
|
||||
<!-- Generated with glade 3.38.2
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2020 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2024 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
|
||||
@@ -27,18 +27,13 @@ Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2024 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="details_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">emblem-important-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="main_list_store">
|
||||
<columns>
|
||||
<!-- column-name name -->
|
||||
@@ -47,106 +42,51 @@ Author: Dmitriy Yefremov
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkMenu" id="popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="restore_bouquets_popup_menu_item">
|
||||
<property name="label" translatable="yes">Restore bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_restore_bouquets" swapped="no"/>
|
||||
<accelerator key="r" signal="activate" modifiers="Primary"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="restore_all_popup_menu_item">
|
||||
<property name="label" translatable="yes">Restore all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_restore_all" swapped="no"/>
|
||||
<accelerator key="e" signal="activate" modifiers="Primary"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="popup_menu_separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="remove_popup_menu_item">
|
||||
<property name="label">gtk-remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">user-trash-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_all_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-select-all-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_bouquets_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-revert-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="dialog_window">
|
||||
<property name="width_request">560</property>
|
||||
<property name="height_request">320</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="width-request">560</property>
|
||||
<property name="height-request">320</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="title" translatable="yes">Backups</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="icon_name">document-revert</property>
|
||||
<property name="window-position">center-on-parent</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="icon-name">document-revert-symbolic</property>
|
||||
<signal name="check-resize" handler="on_resize" swapped="no"/>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="main_button_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_left">15</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<property name="margin-start">15</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="restore_bouquets_header_button">
|
||||
<property name="label" translatable="yes">Restore bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Restore bouquets</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">restore_bouquets_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_restore_bouquets" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="restore_bouquets_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-revert-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -156,14 +96,21 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="restore_all_header_button">
|
||||
<property name="label" translatable="yes">Restore all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Restore all</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">restore_all_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_restore_all" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="restore_all_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-select-all-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -173,14 +120,21 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_header_button">
|
||||
<property name="label" translatable="yes">Remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Remove</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">remove_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_remove" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="remove_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">user-trash-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="Delete" signal="clicked"/>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -199,19 +153,19 @@ Author: Dmitriy Yefremov
|
||||
<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="can-focus">False</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Details</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_right">15</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<property name="margin-end">15</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="details_image1">
|
||||
<object class="GtkImage" id="details_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">emblem-important-symbolic</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">emblem-important-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
@@ -220,7 +174,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
@@ -237,121 +191,130 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkFrame" id="main_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</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="label-xalign">0</property>
|
||||
<property name="shadow-type">none</property>
|
||||
<child>
|
||||
<object class="GtkPaned" id="main_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>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="wide-handle">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="backups_box">
|
||||
<object class="GtkViewport" id="backups_viewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="main_view_scrolled_window">
|
||||
<object class="GtkBox" id="backups_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="main_view">
|
||||
<object class="GtkScrolledWindow" id="main_view_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="model">main_list_store</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<signal name="button-press-event" handler="on_view_popup_menu" object="popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
|
||||
<signal name="key-release-event" handler="on_key_release" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="main_view_selection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="backup_name_column">
|
||||
<property name="min_width">75</property>
|
||||
<property name="title" translatable="yes">Name</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="name_renderer">
|
||||
<property name="xpad">10</property>
|
||||
<object class="GtkTreeView" id="main_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="model">main_list_store</property>
|
||||
<property name="search-column">0</property>
|
||||
<property name="rubber-banding">True</property>
|
||||
<property name="activate-on-single-click">True</property>
|
||||
<signal name="button-press-event" handler="on_view_popup_menu" object="popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
|
||||
<signal name="key-release-event" handler="on_key_release" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="backup_selection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="backup_name_column">
|
||||
<property name="min-width">75</property>
|
||||
<property name="title" translatable="yes">Name</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort-column-id">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="name_renderer">
|
||||
<property name="xpad">10</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="backup_size_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed-width">120</property>
|
||||
<property name="title" translatable="yes">Size</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="size_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="backup_size_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed_width">120</property>
|
||||
<property name="title" translatable="yes">Size</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="size_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="status_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">2</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="file_count_label">
|
||||
<object class="GtkBox" id="status_box">
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="width_chars">4</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="file_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="width-chars">4</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -360,12 +323,10 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
@@ -373,22 +334,36 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="text_view_scrolled_window">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<object class="GtkViewport" id="text_viewport">
|
||||
<property name="visible" bind-source="info_check_button" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view">
|
||||
<object class="GtkScrolledWindow" id="text_view_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="pixels_above_lines">5</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="left_margin">10</property>
|
||||
<property name="right_margin">10</property>
|
||||
<property name="indent">10</property>
|
||||
<property name="cursor_visible">False</property>
|
||||
<property name="accepts_tab">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="pixels-above-lines">5</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="left-margin">10</property>
|
||||
<property name="right-margin">10</property>
|
||||
<property name="indent">10</property>
|
||||
<property name="cursor-visible">False</property>
|
||||
<property name="accepts-tab">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
@@ -409,14 +384,14 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="info_bar">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="show-close-button">True</property>
|
||||
<signal name="response" handler="on_info_bar_close" swapped="no"/>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="layout_style">end</property>
|
||||
<property name="layout-style">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -426,13 +401,13 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child internal-child="content_area">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">16</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="message_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="label" translatable="yes">message</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -458,4 +433,41 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkMenu" id="popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="restore_bouquets_popup_menu_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Restore bouquets</property>
|
||||
<signal name="activate" handler="on_restore_bouquets" swapped="no"/>
|
||||
<accelerator key="r" signal="activate" modifiers="Primary"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="restore_all_popup_menu_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Restore all</property>
|
||||
<signal name="activate" handler="on_restore_all" swapped="no"/>
|
||||
<accelerator key="e" signal="activate" modifiers="Primary"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="popup_menu_separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="remove_popup_menu_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Remove</property>
|
||||
<signal name="activate" handler="on_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
324
app/ui/bootlogo.py
Normal file
324
app/ui/bootlogo.py
Normal file
@@ -0,0 +1,324 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# Author: Dmitriy Yefremov
|
||||
#
|
||||
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from ftplib import all_errors
|
||||
from pathlib import Path
|
||||
|
||||
from app.commons import log, run_task
|
||||
from app.connections import UtfFTP
|
||||
from app.settings import IS_DARWIN
|
||||
from app.ui.dialogs import translate, get_chooser_dialog, show_dialog, DialogType
|
||||
from app.ui.main_helper import get_picon_pixbuf, redraw_image
|
||||
from app.ui.uicommons import HeaderBar
|
||||
from .uicommons import Gtk, GLib
|
||||
|
||||
_OUTPUT_FILES = ("bootlogo",
|
||||
"bootlogo_wait",
|
||||
"backdrop",
|
||||
"reboot",
|
||||
"shutdown",
|
||||
"radio")
|
||||
_E2_STB_PATHS = ("/usr/share", "/usr/share/enigma2")
|
||||
|
||||
|
||||
class BootLogoManager(Gtk.Window):
|
||||
|
||||
def __init__(self, app, **kwargs):
|
||||
super().__init__(title=translate("Boot Logo"), icon_name="demon-editor", application=app,
|
||||
transient_for=app.app_window, destroy_with_parent=True,
|
||||
window_position=Gtk.WindowPosition.CENTER_ON_PARENT,
|
||||
default_width=560, default_height=320, modal=False, **kwargs)
|
||||
|
||||
self._app = app
|
||||
self._exe = f"{'./' if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS') else ''}ffmpeg"
|
||||
self._pix = None
|
||||
self._img_path = None
|
||||
|
||||
margin = {"margin_start": 5, "margin_end": 5, "margin_top": 5, "margin_bottom": 5}
|
||||
base_margin = {"margin_start": 10, "margin_end": 10, "margin_top": 10, "margin_bottom": 10}
|
||||
|
||||
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
frame = Gtk.Frame(shadow_type=Gtk.ShadowType.IN, **base_margin)
|
||||
frame.get_style_context().add_class("view")
|
||||
data_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.VERTICAL, **base_margin)
|
||||
data_box.set_margin_bottom(margin.get("margin_bottom", 5))
|
||||
data_box.set_margin_start(10)
|
||||
frame.add(data_box)
|
||||
self._image_area = Gtk.DrawingArea()
|
||||
self._image_area.connect("draw", self.on_image_draw)
|
||||
data_box.pack_end(self._image_area, True, True, 5)
|
||||
self.add(main_box)
|
||||
# Buttons
|
||||
add_button = Gtk.Button.new_from_icon_name("insert-image-symbolic", Gtk.IconSize.BUTTON)
|
||||
add_button.set_tooltip_text(translate("Add image"))
|
||||
add_button.set_always_show_image(True)
|
||||
add_button.connect("clicked", self.on_add_image)
|
||||
receive_button = Gtk.Button.new_from_icon_name("network-receive-symbolic", Gtk.IconSize.BUTTON)
|
||||
receive_button.set_tooltip_text(translate("Download from the receiver"))
|
||||
receive_button.set_always_show_image(True)
|
||||
receive_button.connect("clicked", self.on_receive)
|
||||
transmit_button = Gtk.Button.new_from_icon_name("network-transmit-symbolic", Gtk.IconSize.BUTTON)
|
||||
transmit_button.set_tooltip_text(translate("Transfer to receiver"))
|
||||
transmit_button.set_sensitive(False)
|
||||
transmit_button.set_always_show_image(True)
|
||||
transmit_button.connect("clicked", self.on_transmit)
|
||||
self._convert_button = Gtk.Button.new_from_icon_name("object-rotate-right-symbolic", Gtk.IconSize.BUTTON)
|
||||
self._convert_button.set_tooltip_text(translate("Convert"))
|
||||
self._convert_button.set_always_show_image(True)
|
||||
self._convert_button.set_sensitive(False)
|
||||
self._convert_button.connect("clicked", self.on_convert)
|
||||
self._convert_button.bind_property("sensitive", transmit_button, "sensitive", 4)
|
||||
settings_close_button = Gtk.ModelButton(label=translate("Close"), centered=True, margin_top=5)
|
||||
# Formats.
|
||||
self._format_button = Gtk.ComboBoxText()
|
||||
self._format_button.set_tooltip_text(translate("TV Format"))
|
||||
self._format_button.append("hd720", "HD-Ready (720)")
|
||||
self._format_button.append("hd1080", "Full HD (1080)")
|
||||
self._format_button.set_active_id("hd720")
|
||||
|
||||
action_box = Gtk.ButtonBox()
|
||||
action_box.set_layout(Gtk.ButtonBoxStyle.EXPAND)
|
||||
action_box.add(add_button)
|
||||
action_box.add(self._convert_button)
|
||||
action_box.add(self._format_button)
|
||||
data_box.pack_start(action_box, False, False, 0)
|
||||
# Settings.
|
||||
popover = Gtk.Popover()
|
||||
settings_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5, **base_margin)
|
||||
file_name_box = Gtk.Box(spacing=5)
|
||||
file_name_box.add(Gtk.Label(f"{translate('File')}:"))
|
||||
self._file_combo_box = Gtk.ComboBoxText()
|
||||
[self._file_combo_box.append(f"{f}.mvi", f) for f in _OUTPUT_FILES]
|
||||
self._file_combo_box.set_active(0)
|
||||
file_name_box.pack_start(self._file_combo_box, True, True, 0)
|
||||
settings_box.add(file_name_box)
|
||||
|
||||
paths_box = Gtk.Box(spacing=5)
|
||||
paths_box.add(Gtk.Label(translate("STB path:")))
|
||||
self._path_combo_box = Gtk.ComboBoxText()
|
||||
[self._path_combo_box.append(p, p) for p in _E2_STB_PATHS]
|
||||
self._path_combo_box.set_active_id(_E2_STB_PATHS[0])
|
||||
paths_box.pack_start(self._path_combo_box, True, True, 0)
|
||||
settings_box.add(paths_box)
|
||||
|
||||
settings_box.pack_end(settings_close_button, False, False, 0)
|
||||
settings_box.show_all()
|
||||
popover.add(settings_box)
|
||||
settings_button = Gtk.MenuButton(popover=popover, valign=Gtk.Align.CENTER, tooltip_text=translate("Options"))
|
||||
settings_button.add(Gtk.Image.new_from_icon_name("applications-system-symbolic", Gtk.IconSize.BUTTON))
|
||||
|
||||
# Header and toolbar.
|
||||
if app.app_settings.use_header_bar:
|
||||
header = HeaderBar(title=translate("Boot Logo"))
|
||||
header.pack_start(receive_button)
|
||||
header.pack_start(transmit_button)
|
||||
header.pack_end(settings_button)
|
||||
|
||||
self.set_titlebar(header)
|
||||
header.show_all()
|
||||
else:
|
||||
toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
toolbar.get_style_context().add_class("primary-toolbar")
|
||||
margin["margin_start"] = 15
|
||||
margin["margin_top"] = 5
|
||||
button_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL, **margin)
|
||||
button_box.pack_start(receive_button, False, False, 0)
|
||||
button_box.pack_start(transmit_button, False, False, 0)
|
||||
toolbar.pack_start(button_box, True, True, 0)
|
||||
toolbar.pack_end(settings_button, False, False, 0)
|
||||
main_box.pack_start(toolbar, False, False, 0)
|
||||
settings_button.set_margin_end(15)
|
||||
|
||||
main_box.pack_start(frame, True, True, 0)
|
||||
main_box.show_all()
|
||||
|
||||
ws_property = "boot_logo_manager_window_size"
|
||||
window_size = self._app.app_settings.get(ws_property, None)
|
||||
if window_size:
|
||||
self.resize(*window_size)
|
||||
|
||||
self.connect("delete-event", lambda w, e: self._app.app_settings.add(ws_property, w.get_size()))
|
||||
self.connect("realize", self.init)
|
||||
|
||||
def init(self, *args):
|
||||
log(f"{self.__class__.__name__} [init] Checking FFmpeg...")
|
||||
try:
|
||||
out = subprocess.check_output([self._exe, "-version"], stderr=subprocess.STDOUT)
|
||||
except FileNotFoundError as e:
|
||||
msg = translate("Check if FFmpeg is installed!")
|
||||
self._app.show_error_message(f"Error. {e} {msg}")
|
||||
log(e)
|
||||
else:
|
||||
lines = out.decode(errors="ignore").splitlines()
|
||||
log(lines[0] if lines else lines)
|
||||
|
||||
def on_add_image(self, button):
|
||||
file_filter = None
|
||||
if IS_DARWIN:
|
||||
file_filter = Gtk.FileFilter()
|
||||
file_filter.set_name("*.jpg, *.jpeg, *.png")
|
||||
file_filter.add_mime_type("image/jpeg")
|
||||
file_filter.add_mime_type("image/png")
|
||||
|
||||
response = get_chooser_dialog(self._app.app_window, self._app.app_settings, "*.jpg, *.jpeg, *.png files",
|
||||
("*.jpg", "*.jpeg", "*.png"), "Select image", file_filter)
|
||||
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
return
|
||||
|
||||
self._img_path = response
|
||||
self._pix = get_picon_pixbuf(response, -1)
|
||||
self._convert_button.set_sensitive(True)
|
||||
self._image_area.queue_draw()
|
||||
|
||||
def on_receive(self, button):
|
||||
self.download_data(self._file_combo_box.get_active_id())
|
||||
|
||||
def on_transmit(self, button):
|
||||
if show_dialog(DialogType.QUESTION, self) != Gtk.ResponseType.OK:
|
||||
return True
|
||||
|
||||
mvi_file = Path(self._img_path).parent.joinpath(self._file_combo_box.get_active_id())
|
||||
if not mvi_file.is_file():
|
||||
log(self._app.show_error_message(translate("No *.mvi file found for the selected image!")))
|
||||
return
|
||||
|
||||
self.transfer_data(mvi_file)
|
||||
|
||||
def on_convert(self, button):
|
||||
self.convert_to_mvi()
|
||||
|
||||
def convert_to_mvi(self, frame_rate=25, bit_rate=2000):
|
||||
path = Path(self._img_path)
|
||||
if not path.is_file():
|
||||
self._app.show_error_message(translate("No image selected!"))
|
||||
return
|
||||
|
||||
output = path.parent.joinpath(self._file_combo_box.get_active_id())
|
||||
ffmpeg_output = path.parent.joinpath(f"{self._file_combo_box.get_active_text()}.m1v")
|
||||
|
||||
cmd = [self._exe,
|
||||
"-i", self._img_path,
|
||||
"-r", str(frame_rate),
|
||||
"-b", str(bit_rate),
|
||||
"-s", self._format_button.get_active_id(),
|
||||
ffmpeg_output]
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError as e:
|
||||
self._app.show_error_message(f"{translate('Conversion error.')} {e}")
|
||||
else:
|
||||
with Image.open(self._img_path) as img:
|
||||
width, height = img.size
|
||||
if width != 1280 and height != 720:
|
||||
log(f"{self.__class__.__name__} [convert] Resizing image...")
|
||||
img.resize((1280, 720), Image.Resampling.LANCZOS)
|
||||
tmp = path.parent.joinpath(f"{path.name}.tmp{path.suffix}").absolute()
|
||||
cmd[2] = tmp
|
||||
img.save(tmp)
|
||||
|
||||
# Processing image.
|
||||
log(f"{self.__class__.__name__} [convert] Converting...")
|
||||
subprocess.run(cmd)
|
||||
if Path(ffmpeg_output).exists():
|
||||
os.rename(ffmpeg_output, output)
|
||||
log(f"{self.__class__.__name__} [convert] -> '{output}'. Done!")
|
||||
|
||||
if cmd[2] != self._img_path:
|
||||
tmp_path = Path(cmd[2])
|
||||
if tmp_path.exists():
|
||||
tmp_path.unlink()
|
||||
|
||||
self._convert_button.set_sensitive(False)
|
||||
|
||||
def convert_to_image(self, video_path, img_path):
|
||||
cmd = [self._exe, "-y", "-i", video_path, img_path]
|
||||
subprocess.run(cmd)
|
||||
|
||||
@run_task
|
||||
def download_data(self, f_name):
|
||||
try:
|
||||
settings = self._app.app_settings
|
||||
with UtfFTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
|
||||
ftp.encoding = "utf-8"
|
||||
ftp.cwd(self._path_combo_box.get_active_id())
|
||||
|
||||
dest = Path(settings.profile_data_path).joinpath("bootlogo")
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
path = f"{dest}{os.sep}"
|
||||
ftp.download_file(f_name, path)
|
||||
vp = Path(f"{path}{f_name}")
|
||||
img_path = f"{path}{f_name}.jpg"
|
||||
|
||||
if vp.exists():
|
||||
rn_path = f"{path}{self._file_combo_box.get_active_text()}.m1v"
|
||||
vp.rename(rn_path)
|
||||
self.convert_to_image(rn_path, img_path)
|
||||
self._pix = get_picon_pixbuf(img_path, -1)
|
||||
GLib.idle_add(self._image_area.queue_draw)
|
||||
|
||||
except all_errors as e:
|
||||
log(f"{self.__class__.__name__} [download error] {e}")
|
||||
GLib.idle_add(self._app.show_error_message, f"{translate('Failed to download data:')} {e}")
|
||||
|
||||
@run_task
|
||||
def transfer_data(self, f_path):
|
||||
try:
|
||||
settings = self._app.app_settings
|
||||
with UtfFTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
|
||||
ftp.encoding = "utf-8"
|
||||
ftp.cwd(self._path_combo_box.get_active_id())
|
||||
|
||||
log(f"{self.__class__.__name__} [transfer data] Creating backup...")
|
||||
backup_path = Path(settings.profile_backup_path).joinpath("bootlogo")
|
||||
backup_path.mkdir(parents=True, exist_ok=True)
|
||||
ftp.download_file(f_path.name, f"{backup_path}{os.sep}")
|
||||
backup_file = backup_path.joinpath(f_path.name)
|
||||
if backup_file.exists():
|
||||
target = backup_path.joinpath(f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}_{f_path.name}")
|
||||
backup_file.rename(target)
|
||||
|
||||
ftp.send_file(f_path.name, f"{f_path.parent}{os.sep}")
|
||||
|
||||
except all_errors as e:
|
||||
log(f"{self.__class__.__name__} [upload error] {e}")
|
||||
GLib.idle_add(self._app.show_error_message, f"{translate('Data transfer error:')} {e}")
|
||||
else:
|
||||
self._app.show_info_message("Done!")
|
||||
|
||||
def on_image_draw(self, area, cr):
|
||||
if self._pix:
|
||||
redraw_image(area, cr, self._pix)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -336,10 +336,10 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkBox" id="network_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="network_scrolled_window">
|
||||
@@ -422,6 +422,9 @@ Author: Dmitriy Yefremov
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -439,8 +442,8 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkBox" id="info_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-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
@@ -448,10 +451,10 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkViewport" id="screenshot_view_port">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">2</property>
|
||||
<property name="margin-right">2</property>
|
||||
<property name="margin-top">2</property>
|
||||
<property name="margin-bottom">2</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<child>
|
||||
<object class="GtkDrawingArea" id="screenshot_area">
|
||||
<property name="can-focus">False</property>
|
||||
@@ -469,8 +472,8 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkBox" id="remote_signal_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">25</property>
|
||||
<property name="margin-right">25</property>
|
||||
<property name="margin-start">25</property>
|
||||
<property name="margin-end">25</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
@@ -620,6 +623,9 @@ Author: Dmitriy Yefremov
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
@@ -652,8 +658,8 @@ Author: Dmitriy Yefremov
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin-left">5</property>
|
||||
<property name="margin-right">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="row-spacing">5</property>
|
||||
@@ -1336,6 +1342,9 @@ audio-volume-medium-symbolic</property>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -32,8 +32,9 @@ import re
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from .main_helper import redraw_image
|
||||
from .dialogs import get_builder, translate
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH
|
||||
from ..commons import run_task, run_with_delay, log, run_idle
|
||||
from ..connections import HttpAPI
|
||||
from ..settings import IS_DARWIN, IS_LINUX, IS_WIN
|
||||
@@ -201,11 +202,7 @@ class ControlTool(Gtk.Box):
|
||||
def on_screenshot_draw(self, area, cr):
|
||||
""" Called to automatically resize the screenshot. """
|
||||
if self._pix:
|
||||
cr.scale(area.get_allocated_width() / self._pix.get_width(),
|
||||
area.get_allocated_height() / self._pix.get_height())
|
||||
img_surface = Gdk.cairo_surface_create_from_pixbuf(self._pix, 1, None)
|
||||
cr.set_source_surface(img_surface, 0, 0)
|
||||
cr.paint()
|
||||
redraw_image(area, cr, self._pix)
|
||||
|
||||
def on_screenshot_all(self, action, value=None):
|
||||
if self._app.http_api:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2024 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. -->
|
||||
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2024 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkAboutDialog" id="about_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -40,8 +40,8 @@ Author: Dmitriy Yefremov
|
||||
<property name="icon_name">system-help</property>
|
||||
<property name="type_hint">normal</property>
|
||||
<property name="program_name">DemonEditor</property>
|
||||
<property name="version">3.7.0 Alpha</property>
|
||||
<property name="copyright">2018-2023 Dmitriy Yefremov
|
||||
<property name="version">3.11.2 Beta</property>
|
||||
<property name="copyright">2018-2024 Dmitriy Yefremov
|
||||
</property>
|
||||
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>
|
||||
<property name="website">https://dyefremov.github.io/DemonEditor/</property>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -38,16 +38,35 @@ from app.settings import SEP, IS_WIN, USE_HEADER_BAR
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
|
||||
|
||||
|
||||
class BaseDialog(Gtk.Dialog):
|
||||
""" Base dialog class for editing DVB (-> *.xml) data. """
|
||||
DEFAULT_BUTTONS = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK)
|
||||
|
||||
def __init__(self, parent, title, buttons=None, *args, **kwargs):
|
||||
super().__init__(transient_for=parent,
|
||||
title=translate(title),
|
||||
modal=True,
|
||||
resizable=False,
|
||||
default_width=255,
|
||||
skip_taskbar_hint=True,
|
||||
skip_pager_hint=True,
|
||||
destroy_with_parent=True,
|
||||
use_header_bar=USE_HEADER_BAR,
|
||||
window_position=Gtk.WindowPosition.CENTER_ON_PARENT,
|
||||
buttons=buttons or self.DEFAULT_BUTTONS,
|
||||
*args, **kwargs)
|
||||
|
||||
|
||||
class Dialog(Enum):
|
||||
MESSAGE = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<object class="GtkMessageDialog" id="message_dialog">
|
||||
<property name="use-header-bar">{use_header}</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="width_request">250</property>
|
||||
<property name="width_request">255</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
|
||||
""" Module for working with EPG. """
|
||||
import abc
|
||||
import gzip
|
||||
import locale
|
||||
import os
|
||||
@@ -35,6 +36,8 @@ import shutil
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from hashlib import sha1
|
||||
from itertools import chain
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.parse import quote
|
||||
|
||||
@@ -45,10 +48,11 @@ from app.connections import download_data, DownloadType, HttpAPI
|
||||
from app.eparser.ecommons import BouquetService, BqServiceType
|
||||
from app.settings import SEP, EpgSource, IS_WIN
|
||||
from app.tools.epg import EPG, ChannelsParser, EpgEvent, XmlTvReader
|
||||
from app.ui.dialogs import translate, show_dialog, DialogType, get_builder
|
||||
from app.ui.dialogs import translate, show_dialog, DialogType, get_builder, get_chooser_dialog
|
||||
from app.ui.tasks import BGTaskWidget
|
||||
from app.ui.timers import TimerTool
|
||||
from ..main_helper import on_popup_menu, update_entry_data, scroll_to, update_toggle_model, update_filter_sat_positions
|
||||
from ..main_helper import on_popup_menu, update_entry_data, scroll_to, update_toggle_model, update_filter_sat_positions, \
|
||||
show_info_bar_message
|
||||
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column, EPG_ICON, KeyboardKey, Page, HeaderBar
|
||||
|
||||
|
||||
@@ -57,32 +61,105 @@ class RefsSource(Enum):
|
||||
XML = 1
|
||||
|
||||
|
||||
class EpgCache(dict):
|
||||
class EpgCache(abc.ABC):
|
||||
def __init__(self, app):
|
||||
super().__init__()
|
||||
self._current_bq = None
|
||||
self.events = {}
|
||||
|
||||
self._reader = None
|
||||
self._canceled = False
|
||||
self._is_run = False
|
||||
self._current_bq = app.current_bouquet
|
||||
self._page = Page.SERVICES
|
||||
|
||||
self._settings = app.app_settings
|
||||
self._src = self._settings.epg_source
|
||||
self._src = EpgSource.XML
|
||||
self._xml_src = None
|
||||
self._path = None
|
||||
|
||||
self._app = app
|
||||
self._app.connect("bouquet-changed", self.on_bouquet_changed)
|
||||
self._app.connect("profile-changed", self.on_profile_changed)
|
||||
self._app.connect("epg-settings-changed", self.on_settings_changed)
|
||||
self._app.connect("task-canceled", self.on_xml_load_cancel)
|
||||
|
||||
self.init()
|
||||
@property
|
||||
def current_reader(self):
|
||||
return self._reader
|
||||
|
||||
@property
|
||||
def is_run(self):
|
||||
return self._is_run
|
||||
|
||||
@property
|
||||
def current_gz_file_name(self):
|
||||
return self.get_gz_file_name(self._settings.epg_xml_source, self._settings.profile_data_path)
|
||||
|
||||
def on_bouquet_changed(self, app, bq):
|
||||
self._current_bq = bq
|
||||
|
||||
def on_profile_changed(self, app, p):
|
||||
self._xml_src = self._settings.epg_xml_source
|
||||
self.reset()
|
||||
|
||||
def on_settings_changed(self, app, page):
|
||||
if page is self._page:
|
||||
self.on_profile_changed(app, page)
|
||||
|
||||
def on_xml_load_cancel(self, app, widget):
|
||||
self._canceled = True
|
||||
|
||||
@abc.abstractmethod
|
||||
def reset(self) -> None: pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_epg_data(self) -> bool: pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_current_event(self, service_name) -> EpgEvent: pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_current_events(self, service_name) -> list: pass
|
||||
|
||||
@staticmethod
|
||||
def get_gz_file_name(url, path):
|
||||
if not url:
|
||||
return f"{path}epg{os.sep}epg.gz"
|
||||
|
||||
f_sha1 = sha1(url.encode("utf-8", errors="ignore")).hexdigest()
|
||||
return f"{path}epg{os.sep}{f_sha1}_epg.gz"
|
||||
|
||||
|
||||
class FavEpgCache(EpgCache):
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
self._app.connect("epg-cache-initialized", self.on_cache_initialized)
|
||||
GLib.timeout_add_seconds(self._settings.epg_update_interval, self.init)
|
||||
|
||||
def on_cache_initialized(self, app, cache):
|
||||
if cache is not self:
|
||||
return
|
||||
|
||||
self._is_run = True
|
||||
GLib.timeout_add_seconds(self._settings.epg_update_interval, self.update_epg_data, priority=GLib.PRIORITY_LOW)
|
||||
|
||||
@run_with_delay(5)
|
||||
def init(self):
|
||||
self._src = self._settings.epg_source
|
||||
self._xml_src = self._settings.epg_xml_source
|
||||
self._is_run = False
|
||||
if self._src is EpgSource.XML:
|
||||
url = self._settings.epg_xml_source
|
||||
gz_file = f"{self._settings.profile_data_path}epg{os.sep}epg.gz"
|
||||
gz_file = self.current_gz_file_name
|
||||
self._reader = XmlTvReader(gz_file, url)
|
||||
|
||||
@run_with_delay(2)
|
||||
def process_data():
|
||||
t = BGTaskWidget(self._app, "Processing XMLTV data...", self._reader.parse, )
|
||||
def process():
|
||||
self._reader.parse()
|
||||
GLib.idle_add(self._app.emit, "epg-cache-initialized", self)
|
||||
|
||||
t = BGTaskWidget(self._app, "Processing XMLTV data...", process, )
|
||||
self._app.emit("add-background-task", t)
|
||||
|
||||
if os.path.isfile(gz_file):
|
||||
@@ -95,50 +172,158 @@ class EpgCache(dict):
|
||||
else:
|
||||
process_data()
|
||||
else:
|
||||
if not self._canceled:
|
||||
task = BGTaskWidget(self._app, "Downloading EPG...", self._reader.download, process_data, )
|
||||
self._app.emit("add-background-task", task)
|
||||
if not url:
|
||||
self._is_run = True
|
||||
self._app.show_info_message("The EPG source for the favorites list is not set!",
|
||||
Gtk.MessageType.WARNING)
|
||||
GLib.idle_add(self._app.emit, "epg-cache-initialized", self)
|
||||
else:
|
||||
if not self._canceled:
|
||||
task = BGTaskWidget(self._app, "Downloading EPG...", self._reader.download, process_data, )
|
||||
self._app.emit("add-background-task", task)
|
||||
elif self._src is EpgSource.DAT:
|
||||
self._reader = EPG.DatReader(f"{self._settings.profile_data_path}epg{os.sep}epg.dat")
|
||||
self._reader.download()
|
||||
else:
|
||||
GLib.idle_add(self._app.emit, "epg-cache-initialized", self)
|
||||
|
||||
GLib.timeout_add_seconds(self._settings.epg_update_interval, self.update_epg_data, priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def on_bouquet_changed(self, app, bq):
|
||||
self._current_bq = bq
|
||||
|
||||
def on_profile_changed(self, app, p):
|
||||
self.clear()
|
||||
|
||||
def on_xml_load_cancel(self, app, widget):
|
||||
self._canceled = True
|
||||
def reset(self) -> None:
|
||||
self.events.clear()
|
||||
if self._is_run:
|
||||
self._is_run = False
|
||||
GLib.timeout_add_seconds(self._settings.epg_update_interval, self.init)
|
||||
|
||||
def update_epg_data(self):
|
||||
if self._src is EpgSource.HTTP:
|
||||
api = self._app.http_api
|
||||
bq = self._app.current_bouquet_files.get(self._current_bq, None)
|
||||
|
||||
api, bq = self._app.http_api, self._app.current_bouquet_files.get(self._current_bq, None)
|
||||
if bq and api:
|
||||
req = quote(f'FROM BOUQUET "userbouquet.{bq}.{self._current_bq.split(":")[-1]}"')
|
||||
req = quote(f'FROM BOUQUET "{bq}"')
|
||||
api.send(HttpAPI.Request.EPG_NOW, f'1:7:1:0:0:0:0:0:0:0:{req}', self.update_http_data)
|
||||
elif self._src is EpgSource.XML:
|
||||
self.update_xml_data()
|
||||
|
||||
return self._app.display_epg
|
||||
return self._app.display_epg and self._is_run
|
||||
|
||||
def update_http_data(self, epg):
|
||||
for e in (EpgTool.get_event(e, False) for e in epg.get("event_list", []) if e.get("e2eventid", "").isdigit()):
|
||||
self[e.event_data.get("e2eventservicename", "")] = e
|
||||
self.events[e.event_data.get("e2eventservicename", "")] = e
|
||||
|
||||
@run_task
|
||||
def update_xml_data(self):
|
||||
services = self._app.current_services
|
||||
names = {services[s].service for s in self._app.current_bouquets.get(self._current_bq, [])}
|
||||
for name, e in self._reader.get_current_events(names).items():
|
||||
self[name] = e
|
||||
names = {services[s].service for s in self._app.current_bouquets.get(self._current_bq, []) if s in services}
|
||||
for name, events in self._reader.get_current_events(names).items():
|
||||
ev = min(events, key=lambda x: x.start, default=None)
|
||||
if ev:
|
||||
self.events[name] = ev
|
||||
|
||||
def get_current_event(self, service_name):
|
||||
return self.get(service_name, EpgEvent())
|
||||
return self.events.get(service_name, EpgEvent())
|
||||
|
||||
def get_current_events(self, service_name):
|
||||
return [EpgEvent()]
|
||||
|
||||
|
||||
class TabEpgCache(EpgCache):
|
||||
|
||||
def __init__(self, app, path=None, url=None):
|
||||
super().__init__(app)
|
||||
self._page = Page.EPG
|
||||
self._path = path or self.current_gz_file_name
|
||||
self._xml_src = url
|
||||
self._task = None
|
||||
|
||||
self._app.connect("epg-cache-initialized", self.on_cache_initialized)
|
||||
|
||||
self.init()
|
||||
|
||||
def on_bouquet_changed(self, app, bq):
|
||||
self._current_bq = bq
|
||||
self.update_epg_data()
|
||||
|
||||
def init(self):
|
||||
self._is_run = True
|
||||
self._reader = XmlTvReader(self._path, url=self._xml_src)
|
||||
if self._canceled:
|
||||
return
|
||||
|
||||
if self._app.display_epg and self._xml_src == self._settings.epg_xml_source:
|
||||
ext_cache = self._app.current_epg_cache
|
||||
if ext_cache and ext_cache.is_run:
|
||||
self._app.emit("epg-cache-initialized", ext_cache)
|
||||
return
|
||||
|
||||
self.load_data()
|
||||
|
||||
def load_data(self):
|
||||
if os.path.isfile(self._path):
|
||||
if self._xml_src:
|
||||
# Difference calculation between the current time and file modification.
|
||||
dif = datetime.now() - datetime.fromtimestamp(os.path.getmtime(self._path))
|
||||
# We will update daily. -> Temporarily!!! Skip download if FAV cache is enabled.
|
||||
if dif.days > 0 and not self._app.display_epg:
|
||||
self._task = BGTaskWidget(self._app, "Downloading EPG...", self._reader.download,
|
||||
self.process_data, )
|
||||
self._app.emit("add-background-task", self._task)
|
||||
else:
|
||||
self.process_data()
|
||||
else:
|
||||
self._task = BGTaskWidget(self._app, "", self.process_data, )
|
||||
else:
|
||||
if not self._xml_src:
|
||||
self._app.emit("epg-cache-initialized", self)
|
||||
self._app.show_info_message("Select a local file to load EPG data or add a web source.",
|
||||
Gtk.MessageType.WARNING)
|
||||
else:
|
||||
self._task = BGTaskWidget(self._app, "Downloading EPG...", self._reader.download, self.process_data, )
|
||||
self._app.emit("add-background-task", self._task)
|
||||
|
||||
def on_cache_initialized(self, app, cache):
|
||||
if isinstance(cache, FavEpgCache):
|
||||
reader = cache.current_reader
|
||||
if reader:
|
||||
self._reader.cache.update(reader.cache)
|
||||
self._is_run = False
|
||||
else:
|
||||
if not self._app.display_epg or self._settings.epg_source is not EpgSource.XML or self._xml_src is None:
|
||||
self._is_run = False
|
||||
|
||||
self.update_epg_data()
|
||||
|
||||
@run_task
|
||||
def process_data(self):
|
||||
def process():
|
||||
# Skip data parsing data if epg display is enabled and EPG src is XMLTV.
|
||||
if not all((self._xml_src, self._app.display_epg, self._settings.epg_source is EpgSource.XML)):
|
||||
self._reader.parse()
|
||||
self._task = None
|
||||
GLib.idle_add(self._app.emit, "epg-cache-initialized", self)
|
||||
|
||||
self._task = BGTaskWidget(self._app, "Processing XMLTV data...", process, )
|
||||
GLib.idle_add(self._app.emit, "add-background-task", self._task)
|
||||
|
||||
def reset(self) -> None:
|
||||
self._is_run = False
|
||||
if self._task:
|
||||
self._task.cancel()
|
||||
|
||||
self.init()
|
||||
|
||||
def update_epg_data(self) -> bool:
|
||||
services = self._app.current_services
|
||||
names = {services[s].service for s in chain.from_iterable(self._app.current_bouquets.values()) if s in services}
|
||||
for name, events in self._reader.get_current_events(names).items():
|
||||
self.events[name] = events
|
||||
|
||||
self._app.emit("epg-cache-updated", self)
|
||||
|
||||
return self._is_run
|
||||
|
||||
def get_current_event(self, service_name) -> EpgEvent:
|
||||
pass
|
||||
|
||||
def get_current_events(self, service_name) -> list:
|
||||
return self.events.get(service_name, [])
|
||||
|
||||
|
||||
class EpgSettingsPopover(Gtk.Popover):
|
||||
@@ -148,17 +333,29 @@ class EpgSettingsPopover(Gtk.Popover):
|
||||
self._app = app
|
||||
self._app.connect("profile-changed", self.on_profile_changed)
|
||||
|
||||
handlers = {"on_apply": self.on_apply,
|
||||
"on_close": lambda b: self.popdown()}
|
||||
handlers = {"on_add_url": self.on_ad_url,
|
||||
"on_remove_url": self.on_remove_url,
|
||||
"on_apply_url": self.on_apply_url,
|
||||
"on_url_entry_focus_out": self.on_url_entry_focus_out,
|
||||
"on_apply": self.on_apply,
|
||||
"on_close": self.on_close}
|
||||
|
||||
builder = get_builder(f"{UI_RESOURCES_PATH}epg{SEP}settings.glade", handlers)
|
||||
self.add(builder.get_object("main_box"))
|
||||
|
||||
self._src_selection_box = builder.get_object("source_selection_box")
|
||||
self._xml_source_box = builder.get_object("xml_source_box")
|
||||
self._download_interval_box = builder.get_object("download_interval_box")
|
||||
self._interval_box = builder.get_object("interval_box")
|
||||
self._http_src_button = builder.get_object("http_src_button")
|
||||
self._xml_src_button = builder.get_object("xml_src_button")
|
||||
self._dat_src_button = builder.get_object("dat_src_button")
|
||||
self._interval_button = builder.get_object("interval_button")
|
||||
self._download_interval_button = builder.get_object("download_interval_button")
|
||||
self._url_combo_box = builder.get_object("url_combo_box")
|
||||
self._url_entry = builder.get_object("url_entry")
|
||||
self._dat_path_box = builder.get_object("dat_path_box")
|
||||
self._remove_url_button = builder.get_object("remove_url_button")
|
||||
|
||||
self.init()
|
||||
|
||||
@@ -173,46 +370,129 @@ class EpgSettingsPopover(Gtk.Popover):
|
||||
self._dat_src_button.set_active(True)
|
||||
|
||||
self._interval_button.set_value(settings.epg_update_interval)
|
||||
self._url_entry.set_text(settings.epg_xml_source)
|
||||
self._dat_path_box.set_active_id(settings.epg_dat_path)
|
||||
self._url_combo_box.get_model().clear()
|
||||
[self._url_combo_box.append(i, i) for i in settings.epg_xml_sources if i]
|
||||
self._url_combo_box.set_active_id(settings.epg_xml_source)
|
||||
self._remove_url_button.set_sensitive(len(self._url_combo_box.get_model()) > 1)
|
||||
|
||||
def on_ad_url(self, button):
|
||||
self._url_entry.set_can_focus(True)
|
||||
self._url_entry.grab_focus()
|
||||
|
||||
def on_remove_url(self, button):
|
||||
self._url_combo_box.remove(self._url_combo_box.get_active())
|
||||
self._url_combo_box.set_active(0)
|
||||
self._remove_url_button.set_sensitive(len(self._url_combo_box.get_model()) > 1)
|
||||
|
||||
def on_apply_url(self, button):
|
||||
url = self._url_entry.get_text()
|
||||
ids = {r[0] for r in self._url_combo_box.get_model()}
|
||||
if url in ids:
|
||||
self._app.show_error_message("This URL already exists!")
|
||||
return True
|
||||
|
||||
self._url_combo_box.append(url, url)
|
||||
self._url_combo_box.set_active_id(url)
|
||||
self._download_interval_button.grab_focus()
|
||||
self._remove_url_button.set_sensitive(len(ids))
|
||||
|
||||
def on_url_entry_focus_out(self, entry, event):
|
||||
entry.set_can_focus(False)
|
||||
active = self._url_combo_box.get_active_id()
|
||||
txt = entry.get_text()
|
||||
if active != txt:
|
||||
entry.set_text(active or "")
|
||||
|
||||
def on_apply(self, button):
|
||||
settings = self._app.app_settings
|
||||
if self._http_src_button.get_active():
|
||||
settings.epg_source = EpgSource.HTTP
|
||||
src = EpgSource.HTTP
|
||||
elif self._xml_src_button.get_active():
|
||||
settings.epg_source = EpgSource.XML
|
||||
src = EpgSource.XML
|
||||
else:
|
||||
settings.epg_source = EpgSource.DAT
|
||||
src = EpgSource.DAT
|
||||
|
||||
settings.epg_update_interval = self._interval_button.get_value()
|
||||
settings.epg_xml_source = self._url_entry.get_text()
|
||||
settings.epg_dat_path = self._dat_path_box.get_active_id()
|
||||
xml_src = self._url_combo_box.get_active_id()
|
||||
update_interval = self._interval_button.get_value()
|
||||
dat_path = self._dat_path_box.get_active_id()
|
||||
|
||||
if any((src != settings.epg_source,
|
||||
xml_src != settings.epg_xml_source,
|
||||
update_interval != settings.epg_update_interval,
|
||||
dat_path != settings.epg_dat_path)):
|
||||
self._app.emit("epg-settings-changed", Page.SERVICES)
|
||||
|
||||
settings.epg_update_interval = update_interval
|
||||
settings.epg_source = src
|
||||
settings.epg_xml_source = xml_src
|
||||
settings.epg_xml_sources = [r[0] for r in self._url_combo_box.get_model()]
|
||||
settings.epg_dat_path = dat_path
|
||||
self.popdown()
|
||||
|
||||
self._app.change_action_state("display_epg", GLib.Variant.new_boolean(True))
|
||||
def on_close(self, button):
|
||||
self.init()
|
||||
self.popdown()
|
||||
|
||||
def on_profile_changed(self, app, p):
|
||||
self.init()
|
||||
|
||||
|
||||
class TabEpgSettingsPopover(EpgSettingsPopover):
|
||||
|
||||
def init(self):
|
||||
self._xml_src_button.set_active(True)
|
||||
self._http_src_button.set_visible(False)
|
||||
self._src_selection_box.set_visible(False)
|
||||
self._interval_box.set_visible(False)
|
||||
self._xml_source_box.set_margin_top(5)
|
||||
|
||||
settings = self._app.app_settings
|
||||
self._interval_button.set_value(settings.epg_update_interval)
|
||||
self._url_combo_box.get_model().clear()
|
||||
[self._url_combo_box.append(i, i) for i in settings.epg_xml_sources if i]
|
||||
self._url_combo_box.set_active_id(settings.epg_xml_source)
|
||||
|
||||
def on_apply(self, button):
|
||||
settings = self._app.app_settings
|
||||
xml_src = self._url_combo_box.get_active_id()
|
||||
|
||||
if xml_src != settings.epg_xml_source:
|
||||
settings.epg_xml_source = xml_src
|
||||
self._app.emit("epg-settings-changed", Page.EPG)
|
||||
|
||||
settings.epg_xml_sources = [r[0] for r in self._url_combo_box.get_model()]
|
||||
self.popdown()
|
||||
|
||||
|
||||
class EpgTool(Gtk.Box):
|
||||
# Batch size to data load in one pass.
|
||||
LOAD_FACTOR = 100
|
||||
|
||||
def __init__(self, app, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._current_bq = None
|
||||
self._epg_cache = None
|
||||
self._src = EpgSource.HTTP
|
||||
self._current_bq = app.current_bouquet
|
||||
|
||||
self._app = app
|
||||
self._app.connect("data-open", self.on_data_open)
|
||||
self._app.connect("data-extract", self.on_data_extract)
|
||||
self._app.connect("fav-changed", self.on_service_changed)
|
||||
self._app.connect("profile-changed", self.on_profile_changed)
|
||||
self._app.connect("bouquet-changed", self.on_bouquet_changed)
|
||||
self._app.connect("epg-cache-updated", self.on_epg_cache_updated)
|
||||
self._app.connect("epg-display-changed", self.on_epg_display_changed)
|
||||
self._app.connect("filter-toggled", self.on_filter_toggled)
|
||||
|
||||
handlers = {"on_epg_press": self.on_epg_press,
|
||||
"on_timer_add": self.on_timer_add,
|
||||
"on_epg_filter_changed": self.on_epg_filter_changed,
|
||||
handlers = {"on_epg_filter_changed": self.on_epg_filter_changed,
|
||||
"on_epg_filter_toggled": self.on_epg_filter_toggled,
|
||||
"on_view_query_tooltip": self.on_view_query_tooltip,
|
||||
"on_multi_epg_toggled": self.on_multi_epg_toggled}
|
||||
"on_multi_epg_toggled": self.on_multi_epg_toggled,
|
||||
"on_xmltv_toggled": self.on_xmltv_toggled,
|
||||
"on_epg_press": self.on_epg_press,
|
||||
"on_timer_add": self.on_timer_add}
|
||||
|
||||
builder = get_builder(f"{UI_RESOURCES_PATH}epg{SEP}tab.glade", handlers)
|
||||
|
||||
@@ -223,7 +503,12 @@ class EpgTool(Gtk.Box):
|
||||
self._filter_button = builder.get_object("epg_filter_button")
|
||||
self._filter_entry = builder.get_object("epg_filter_entry")
|
||||
self._multi_epg_button = builder.get_object("multi_epg_button")
|
||||
self._src_xmltv_button = builder.get_object("src_xmltv_button")
|
||||
self._epg_options_button = builder.get_object("epg_options_button")
|
||||
self._epg_options_button.connect("realize", lambda b: b.set_popover(TabEpgSettingsPopover(self._app)))
|
||||
self._event_count_label = builder.get_object("event_count_label")
|
||||
self._cache_info_label = builder.get_object("cache_info_label")
|
||||
self.set_cache_info(0, 0)
|
||||
self.pack_start(builder.get_object("epg_frame"), True, True, 0)
|
||||
# Custom data functions.
|
||||
renderer = builder.get_object("epg_start_renderer")
|
||||
@@ -241,12 +526,245 @@ class EpgTool(Gtk.Box):
|
||||
|
||||
self.show()
|
||||
|
||||
def on_data_open(self, app, page):
|
||||
if page is not Page.EPG:
|
||||
return
|
||||
|
||||
response = get_chooser_dialog(self._app.app_window, self._app.app_settings, "XMLTV", ("*.xml",))
|
||||
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
return
|
||||
|
||||
self.open_data(response)
|
||||
|
||||
def on_data_extract(self, app, page):
|
||||
if page is not Page.EPG:
|
||||
return
|
||||
|
||||
f_filter = Gtk.FileFilter()
|
||||
f_filter.set_name("*.zip, *.gz, *.xz")
|
||||
f_filter.add_mime_type("application/zip")
|
||||
f_filter.add_mime_type("application/gzip")
|
||||
f_filter.add_mime_type("application/x-xz")
|
||||
|
||||
response = get_chooser_dialog(self._app.app_window, self._app.app_settings,
|
||||
"*.zip, *.gz, *.xz files", ("*.zip", "*.gz", "*.xz"), "Open archive", f_filter)
|
||||
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
return
|
||||
self.open_data(response)
|
||||
|
||||
def open_data(self, path):
|
||||
if next(self.clear(), False):
|
||||
if not self._epg_cache:
|
||||
self._epg_cache = TabEpgCache(self._app, path)
|
||||
else:
|
||||
if self._epg_cache.is_run:
|
||||
self._app.show_error_message("Data loading in progress!")
|
||||
return
|
||||
|
||||
self._epg_cache._path = path
|
||||
self._epg_cache._xml_src = None
|
||||
self._epg_cache.reset()
|
||||
GLib.idle_add(self._src_xmltv_button.set_active, True)
|
||||
|
||||
def on_service_changed(self, app, srv):
|
||||
if app.page is not Page.EPG:
|
||||
return
|
||||
|
||||
if self._src is EpgSource.HTTP:
|
||||
ref = app.get_service_ref_data(srv)
|
||||
if not ref:
|
||||
return
|
||||
|
||||
if self._multi_epg_button.get_active():
|
||||
ref += ":"
|
||||
path = next((r.path for r in self._model if r[-1].get("e2eventservicereference", None) == ref), None)
|
||||
scroll_to(path, self._view) if path else None
|
||||
else:
|
||||
self._app.wait_dialog.show()
|
||||
self._app.send_http_request(HttpAPI.Request.EPG, quote(ref), self.update_http_epg_data)
|
||||
else:
|
||||
if self._epg_cache.is_run:
|
||||
self._app.show_error_message("Data loading in progress!")
|
||||
return
|
||||
|
||||
if self._multi_epg_button.get_active():
|
||||
path = next((r.path for r in self._model if r[-1].get("e2eventservicename", None) == srv.service), None)
|
||||
scroll_to(path, self._view) if path else None
|
||||
else:
|
||||
self._app.wait_dialog.show()
|
||||
self.update_xmltv_epg_data([srv.service])
|
||||
|
||||
def on_profile_changed(self, app, prf):
|
||||
gen = self.update_epg_data()
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def on_bouquet_changed(self, app, bq):
|
||||
self._current_bq = bq
|
||||
if app.page is Page.EPG and self._multi_epg_button.get_active():
|
||||
self.get_multi_epg()
|
||||
|
||||
def on_epg_cache_updated(self, app, cache):
|
||||
self.set_cache_info(len(cache.events), len(list(chain.from_iterable(cache.events.values()))))
|
||||
|
||||
def set_cache_info(self, s_count, ev_count):
|
||||
self._cache_info_label.set_text(f"{translate('Services')}: {s_count} {translate('Events')}: {ev_count}")
|
||||
|
||||
def on_epg_display_changed(self, app, display):
|
||||
self._epg_options_button.set_visible(not display and self._src is EpgSource.XML)
|
||||
|
||||
def update_http_epg_data(self, epg=None):
|
||||
if epg:
|
||||
events = (self.get_event(e) for e in epg.get("event_list", []) if e.get("e2eventid", "").isdigit())
|
||||
else:
|
||||
events = ()
|
||||
gen = self.update_epg_data(events)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def update_xmltv_epg_data(self, names):
|
||||
gen = self.update_epg_data(chain.from_iterable(self._epg_cache.get_current_events(n) for n in names))
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def update_epg_data(self, events=()):
|
||||
c_gen = self.clear()
|
||||
yield from c_gen
|
||||
for index, e in enumerate(events):
|
||||
if index % self.LOAD_FACTOR == 0:
|
||||
self._event_count_label.set_text(str(len(self._model)))
|
||||
yield True
|
||||
self._model.append(e)
|
||||
self._event_count_label.set_text(str(len(self._model)))
|
||||
self._app.wait_dialog.hide()
|
||||
yield True
|
||||
|
||||
def clear(self):
|
||||
if len(self._model) < self.LOAD_FACTOR * 20:
|
||||
self._model.clear()
|
||||
else:
|
||||
# Init new models.
|
||||
column_types = (self._model.get_column_type(i) for i in range(self._model.get_n_columns()))
|
||||
self._model = Gtk.ListStore(*column_types)
|
||||
self._filter_model = self._model.filter_new()
|
||||
self._filter_model.set_visible_func(self.epg_filter_function)
|
||||
self._view.set_model(Gtk.TreeModelSort(model=self._filter_model))
|
||||
self._event_count_label.set_text("0")
|
||||
yield True
|
||||
|
||||
@staticmethod
|
||||
def get_event(event, show_day=True):
|
||||
s_name = event.get("e2eventservicename", "")
|
||||
title = event.get("e2eventtitle", "") or ""
|
||||
desc = event.get("e2eventdescription", "") or ""
|
||||
desc = desc.strip()
|
||||
start, duration = int(event.get("e2eventstart", "0")), int(event.get("e2eventduration", "0"))
|
||||
|
||||
return EpgEvent(s_name, title, start, start + duration, duration, desc, event)
|
||||
|
||||
def start_data_func(self, column, renderer, model, itr, data):
|
||||
value = datetime.fromtimestamp(model.get_value(itr, Column.EPG_START))
|
||||
renderer.set_property("text", value.strftime(self._time_fmt))
|
||||
|
||||
def end_data_func(self, column, renderer, model, itr, data):
|
||||
value = datetime.fromtimestamp(model.get_value(itr, Column.EPG_END))
|
||||
renderer.set_property("text", value.strftime(self._time_fmt))
|
||||
|
||||
def duration_data_func(self, column, renderer, model, itr, data):
|
||||
value = datetime.utcfromtimestamp(model.get_value(itr, Column.EPG_LENGTH))
|
||||
renderer.set_property("text", value.strftime(self._duration_fmt))
|
||||
|
||||
@run_with_delay(2)
|
||||
def on_epg_filter_changed(self, entry):
|
||||
self._filter_model.refilter()
|
||||
|
||||
def on_epg_filter_toggled(self, button):
|
||||
if not button.get_active():
|
||||
self._filter_entry.set_text("")
|
||||
|
||||
def epg_filter_function(self, model, itr, data):
|
||||
txt = self._filter_entry.get_text().upper()
|
||||
return next((s for s in model.get(itr,
|
||||
Column.EPG_SERVICE,
|
||||
Column.EPG_TITLE,
|
||||
Column.EPG_DESC) if s and txt in s.upper()), False)
|
||||
|
||||
def on_filter_toggled(self, app, value):
|
||||
if self._app.page is Page.EPG:
|
||||
active = not self._filter_button.get_active()
|
||||
self._filter_button.set_active(active)
|
||||
if active:
|
||||
self._filter_entry.grab_focus()
|
||||
|
||||
def on_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
|
||||
dst = view.get_dest_row_at_pos(x, y)
|
||||
if not dst:
|
||||
return False
|
||||
|
||||
path, pos = dst
|
||||
model = view.get_model()
|
||||
data = model[path][-1]
|
||||
if not data:
|
||||
return False
|
||||
|
||||
desc = data.get("e2eventdescription", "") or ""
|
||||
ext_desc = data.get("e2eventdescriptionextended", "") or ""
|
||||
if not any((desc, ext_desc)):
|
||||
return False
|
||||
|
||||
tooltip.set_text(ext_desc if ext_desc else desc)
|
||||
view.set_tooltip_row(tooltip, path)
|
||||
|
||||
return True
|
||||
|
||||
def on_multi_epg_toggled(self, button):
|
||||
if button.get_active():
|
||||
self.get_multi_epg()
|
||||
else:
|
||||
next(self.clear(), False)
|
||||
|
||||
def on_xmltv_toggled(self, button):
|
||||
if button.get_active():
|
||||
self._src = EpgSource.XML
|
||||
if self._epg_cache is None:
|
||||
settings = self._app.app_settings
|
||||
xml_src = self._app.app_settings.epg_xml_source if settings.epg_source is EpgSource.XML else None
|
||||
self._epg_cache = TabEpgCache(self._app, url=xml_src)
|
||||
else:
|
||||
self._src = EpgSource.HTTP
|
||||
self.update_http_epg_data()
|
||||
|
||||
self._epg_options_button.set_visible(not self._app.display_epg and self._src is EpgSource.XML)
|
||||
|
||||
def get_multi_epg(self):
|
||||
if not self._current_bq:
|
||||
return
|
||||
|
||||
self._app.wait_dialog.show()
|
||||
|
||||
if self._src is EpgSource.HTTP:
|
||||
bq, api = self._app.current_bouquet_files.get(self._current_bq, None), self._app.http_api
|
||||
if bq and api:
|
||||
tm = datetime.now().timestamp()
|
||||
req = quote(f'FROM BOUQUET "{bq}"&time={tm}')
|
||||
api.send(HttpAPI.Request.EPG_MULTI, f'1:7:1:0:0:0:0:0:0:0:{req}', self.update_http_epg_data, timeout=15)
|
||||
else:
|
||||
srvs = self._app.current_services
|
||||
bq_names = (srvs[s].service for s in self._app.current_bouquets.get(self._current_bq, []) if s in srvs)
|
||||
self.update_xmltv_epg_data(bq_names)
|
||||
|
||||
# ****************** Timers ***************** #
|
||||
|
||||
def on_epg_press(self, view, event):
|
||||
if self._src_xmltv_button.get_active():
|
||||
return True
|
||||
|
||||
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(view.get_model()) > 0:
|
||||
self.on_timer_add()
|
||||
|
||||
def on_timer_add(self, action=None, value=None):
|
||||
model, paths = self._view.get_selection().get_selected_rows()
|
||||
p_count = len(paths)
|
||||
|
||||
if p_count == 1:
|
||||
dialog = TimerTool.TimerDialog(self._app.app_window, TimerTool.TimerAction.EVENT, model[paths][-1])
|
||||
dialog = TimerTool.TimerDialog(self._app.app_window, TimerTool.TimerAction.EVENT, model[paths][-1] or {})
|
||||
response = dialog.run()
|
||||
if response == Gtk.ResponseType.OK:
|
||||
gen = self.write_timers_list([dialog.get_request()])
|
||||
@@ -280,117 +798,6 @@ class EpgTool(Gtk.Box):
|
||||
|
||||
self._app.emit("change-page", Page.TIMERS.value)
|
||||
|
||||
def on_epg_press(self, view, event):
|
||||
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(view.get_model()) > 0:
|
||||
self.on_timer_add()
|
||||
|
||||
def on_service_changed(self, app, ref):
|
||||
if app.page is Page.EPG:
|
||||
if self._multi_epg_button.get_active():
|
||||
ref += ":"
|
||||
path = next((r.path for r in self._model if r[-1].get("e2eventservicereference", None) == ref), None)
|
||||
scroll_to(path, self._view) if path else None
|
||||
else:
|
||||
self._app.wait_dialog.show()
|
||||
self._app.send_http_request(HttpAPI.Request.EPG, quote(ref), self.update_epg_data)
|
||||
|
||||
def on_profile_changed(self, app, prf):
|
||||
self.update_epg_data()
|
||||
|
||||
@run_idle
|
||||
def update_epg_data(self, epg=None):
|
||||
self._event_count_label.set_text("0")
|
||||
self._model.clear()
|
||||
if epg:
|
||||
list(map(self._model.append, (self.get_event(e) for e in epg.get("event_list", [])
|
||||
if e.get("e2eventid", "").isdigit())))
|
||||
self._event_count_label.set_text(str(len(self._model)))
|
||||
self._app.wait_dialog.hide()
|
||||
|
||||
@staticmethod
|
||||
def get_event(event, show_day=True):
|
||||
s_name = event.get("e2eventservicename", "")
|
||||
title = event.get("e2eventtitle", "") or ""
|
||||
desc = event.get("e2eventdescription", "") or ""
|
||||
desc = desc.strip()
|
||||
start, duration = int(event.get("e2eventstart", "0")), int(event.get("e2eventduration", "0"))
|
||||
|
||||
return EpgEvent(s_name, title, start, start + duration, duration, desc, event)
|
||||
|
||||
def start_data_func(self, column, renderer, model, itr, data):
|
||||
value = datetime.fromtimestamp(model.get_value(itr, Column.EPG_START))
|
||||
renderer.set_property("text", value.strftime(self._time_fmt))
|
||||
|
||||
def end_data_func(self, column, renderer, model, itr, data):
|
||||
value = datetime.fromtimestamp(model.get_value(itr, Column.EPG_END))
|
||||
renderer.set_property("text", value.strftime(self._time_fmt))
|
||||
|
||||
def duration_data_func(self, column, renderer, model, itr, data):
|
||||
value = datetime.utcfromtimestamp(model.get_value(itr, Column.EPG_LENGTH))
|
||||
renderer.set_property("text", value.strftime(self._duration_fmt))
|
||||
|
||||
def on_epg_filter_changed(self, entry):
|
||||
self._filter_model.refilter()
|
||||
|
||||
def on_epg_filter_toggled(self, button):
|
||||
if not button.get_active():
|
||||
self._filter_entry.set_text("")
|
||||
|
||||
def epg_filter_function(self, model, itr, data):
|
||||
txt = self._filter_entry.get_text().upper()
|
||||
return next((s for s in model.get(itr,
|
||||
Column.EPG_SERVICE,
|
||||
Column.EPG_TITLE,
|
||||
Column.EPG_DESC) if txt in s.upper()), False)
|
||||
|
||||
def on_filter_toggled(self, app, value):
|
||||
if self._app.page is Page.EPG:
|
||||
active = not self._filter_button.get_active()
|
||||
self._filter_button.set_active(active)
|
||||
if active:
|
||||
self._filter_entry.grab_focus()
|
||||
|
||||
def on_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
|
||||
dst = view.get_dest_row_at_pos(x, y)
|
||||
if not dst:
|
||||
return False
|
||||
|
||||
path, pos = dst
|
||||
model = view.get_model()
|
||||
data = model[path][-1]
|
||||
desc = data.get("e2eventdescription", "") or ""
|
||||
ext_desc = data.get("e2eventdescriptionextended", "") or ""
|
||||
|
||||
tooltip.set_text(ext_desc if ext_desc else desc)
|
||||
view.set_tooltip_row(tooltip, path)
|
||||
|
||||
return True
|
||||
|
||||
def on_multi_epg_toggled(self, button):
|
||||
self._model.clear()
|
||||
self._event_count_label.set_text("0")
|
||||
|
||||
if button.get_active():
|
||||
self.get_multi_epg()
|
||||
|
||||
def on_bouquet_changed(self, app, bq):
|
||||
self._current_bq = bq
|
||||
if app.page is Page.EPG and self._multi_epg_button.get_active():
|
||||
self.get_multi_epg()
|
||||
|
||||
def get_multi_epg(self):
|
||||
if not self._current_bq:
|
||||
return
|
||||
|
||||
self._app.wait_dialog.show()
|
||||
bq = self._app.current_bouquet_files.get(self._current_bq, None)
|
||||
api = self._app.http_api
|
||||
|
||||
if bq and api:
|
||||
tm = datetime.now().timestamp()
|
||||
req = quote(f'FROM BOUQUET "userbouquet.{bq}.{self._current_bq.split(":")[-1]}"&time={tm}')
|
||||
api.send(HttpAPI.Request.EPG_MULTI, f'1:7:1:0:0:0:0:0:0:0:{req}', self.update_epg_data, timeout=15)
|
||||
|
||||
|
||||
class EpgDialog:
|
||||
|
||||
@@ -423,7 +830,8 @@ class EpgDialog:
|
||||
"on_field_icon_press": self.on_field_icon_press,
|
||||
"on_key_press": self.on_key_press,
|
||||
"on_bq_cursor_changed": self.on_bq_cursor_changed,
|
||||
"on_source_view_query_tooltip": self.on_source_view_query_tooltip}
|
||||
"on_source_view_query_tooltip": self.on_source_view_query_tooltip,
|
||||
"on_paned_size_allocate": lambda p, a: p.set_position(0.5 * a.width)}
|
||||
|
||||
self._app = app
|
||||
self._ex_services = self._app.current_services
|
||||
@@ -455,7 +863,7 @@ class EpgDialog:
|
||||
# Filter
|
||||
self._filter_bar = builder.get_object("filter_bar")
|
||||
self._filter_entry = builder.get_object("filter_entry")
|
||||
self._filter_auto_switch = builder.get_object("filter_auto_switch")
|
||||
self._filter_auto_button = builder.get_object("filter_auto_button")
|
||||
self._services_filter_model = builder.get_object("services_filter_model")
|
||||
self._services_filter_model.set_visible_func(self.services_filter_function)
|
||||
self._sat_pos_filter_model = builder.get_object("sat_pos_filter_model")
|
||||
@@ -699,7 +1107,7 @@ class EpgDialog:
|
||||
self.on_assign_ref()
|
||||
|
||||
def on_bq_cursor_changed(self, view):
|
||||
if self._filter_bar.get_visible() and self._filter_auto_switch.get_active():
|
||||
if self._filter_bar.get_visible() and self._filter_auto_button.get_active():
|
||||
path, column = view.get_cursor()
|
||||
model = view.get_model()
|
||||
if path:
|
||||
@@ -874,9 +1282,7 @@ class EpgDialog:
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(True)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._message_label.set_text(text)
|
||||
show_info_bar_message(self._info_bar, self._message_label, text, message_type)
|
||||
|
||||
@run_idle
|
||||
def update_source_info(self, info):
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.2
|
||||
<!-- Generated with glade 3.38.2
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2023 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
|
||||
@@ -26,33 +26,33 @@ THE SOFTWARE.
|
||||
Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkAdjustment" id="interval_adjustment">
|
||||
<property name="lower">3</property>
|
||||
<property name="upper">60</property>
|
||||
<property name="value">3</property>
|
||||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
<property name="step-increment">1</property>
|
||||
<property name="page-increment">10</property>
|
||||
</object>
|
||||
<object class="GtkBox" id="main_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="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">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="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<object class="GtkLabel" id="src_label">
|
||||
<property name="visible" bind-source="source_selection_box" bind-property="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="label" translatable="yes">Source:</property>
|
||||
</object>
|
||||
@@ -65,22 +65,22 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkBox" id="src_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="source_selection_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="http_src_button">
|
||||
<property name="label" translatable="yes">Receiver</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
<property name="group">dat_src_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -93,10 +93,10 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkRadioButton" id="xml_src_button">
|
||||
<property name="label" translatable="yes">XML TV</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
<property name="group">dat_src_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -108,12 +108,11 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="dat_src_button">
|
||||
<property name="label" translatable="yes">*.dat file</property>
|
||||
<property name="visible">False</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
<property name="group">http_src_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -132,12 +131,12 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkBox" id="interval_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="interval_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Update interval (sec):</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -149,10 +148,10 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="interval_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="max_width_chars">4</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="max-width-chars">4</property>
|
||||
<property name="adjustment">interval_adjustment</property>
|
||||
<property name="climb_rate">1</property>
|
||||
<property name="climb-rate">1</property>
|
||||
<property name="numeric">True</property>
|
||||
<property name="value">3</property>
|
||||
</object>
|
||||
@@ -172,15 +171,14 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkBox" id="xml_source_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="sensitive" bind-source="xml_src_button" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="sensitive" bind-source="xml_src_button" bind-property="active"/>
|
||||
<child>
|
||||
<object class="GtkLabel" id="url_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Url to *.xml.gz file:</property>
|
||||
</object>
|
||||
@@ -191,15 +189,130 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="url_entry">
|
||||
<object class="GtkBox" id="url_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="input_purpose">url</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="url_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="active">0</property>
|
||||
<property name="has-entry">True</property>
|
||||
<child internal-child="entry">
|
||||
<object class="GtkEntry" id="url_entry">
|
||||
<property name="can-focus">False</property>
|
||||
<signal name="focus-out-event" handler="on_url_entry_focus_out" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="url_action_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_url_button">
|
||||
<property name="visible" bind-source="apply_url_button" bind-property="visible" bind-flags="invert-boolean">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Remove</property>
|
||||
<signal name="clicked" handler="on_remove_url" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="remove_url_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">list-remove-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="add_url_button">
|
||||
<property name="visible" bind-source="apply_url_button" bind-property="visible" bind-flags="invert-boolean">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Add</property>
|
||||
<signal name="clicked" handler="on_add_url" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="add_url_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="cancel_url_button">
|
||||
<property name="visible" bind-source="apply_url_button" bind-property="visible">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Cancel</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="cancel_url_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-undo-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="apply_url_button">
|
||||
<property name="visible" bind-source="url_entry" bind-property="has-focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Add</property>
|
||||
<signal name="clicked" handler="on_apply_url" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="apply_url_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Apply</property>
|
||||
<property name="icon-name">insert-link-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
@@ -207,12 +320,12 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkBox" id="download_interval_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="download_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Update:</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -224,11 +337,11 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="download_interval_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="active">0</property>
|
||||
<property name="active_id">daily</property>
|
||||
<property name="active-id">daily</property>
|
||||
<items>
|
||||
<item id="daily" translatable="yes">Daily</item>
|
||||
</items>
|
||||
@@ -243,7 +356,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
@@ -255,16 +368,14 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="dat_source_box">
|
||||
<property name="visible">False</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="sensitive" bind-source="dat_src_button" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="sensitive" bind-source="dat_src_button" bind-property="active"/>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">STB path:</property>
|
||||
</object>
|
||||
@@ -277,9 +388,9 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="dat_path_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="active">0</property>
|
||||
<property name="active_id">/etc/enigma2</property>
|
||||
<property name="active-id">/etc/enigma2</property>
|
||||
<items>
|
||||
<item id="/etc/enigma2/">/etc/enigma2/</item>
|
||||
<item id="/media/hdd/">/media/hdd/</item>
|
||||
@@ -311,18 +422,18 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="actions_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="apply_button">
|
||||
<property name="label" translatable="yes">Apply</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<signal name="clicked" handler="on_apply" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -335,8 +446,8 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkButton" id="close_button">
|
||||
<property name="label" translatable="yes">Close</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<signal name="clicked" handler="on_close" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2024 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
|
||||
@@ -62,148 +62,90 @@ Author: Dmitriy Yefremov
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0.49</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="shadow-type">none</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="epg_box">
|
||||
<object class="GtkViewport" id="viewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">2</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="epg_action_box">
|
||||
<object class="GtkBox" id="epg_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child type="center">
|
||||
<object class="GtkToggleButton" id="multi_epg_button">
|
||||
<property name="label" translatable="yes">Multi EPG</property>
|
||||
<property name="name">header-button</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="epg_action_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<signal name="toggled" handler="on_multi_epg_toggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="src_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">EPG source</property>
|
||||
<property name="active">0</property>
|
||||
<property name="has-entry">True</property>
|
||||
<property name="active-id">0</property>
|
||||
<items>
|
||||
<item id="0" translatable="yes">Receiver</item>
|
||||
</items>
|
||||
<child internal-child="entry">
|
||||
<object class="GtkEntry">
|
||||
<property name="name">header-entry</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="width-chars">10</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="epg_filter_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Filter</property>
|
||||
<signal name="toggled" handler="on_epg_filter_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="epg_filter_button_image">
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child type="center">
|
||||
<object class="GtkButtonBox" id="src_box">
|
||||
<property name="name">header-stack-switcher</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-find-replace-symbolic</property>
|
||||
<property name="tooltip-text" translatable="yes">EPG source</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="src_receiver_button">
|
||||
<property name="label" translatable="yes">Receiver</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
<property name="group">src_xmltv_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="src_xmltv_button">
|
||||
<property name="label" translatable="yes">XML TV</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
<property name="group">src_receiver_button</property>
|
||||
<signal name="toggled" handler="on_xmltv_toggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="epg_add_timer_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Add timer</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_timer_add" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="add_timer_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">alarm-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="epg_fs_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="epg_filter_entry">
|
||||
<property name="visible" bind-source="epg_filter_button" bind-property="active">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="primary-icon-name">edit-find-replace-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="primary-icon-sensitive">False</property>
|
||||
<signal name="search-changed" handler="on_epg_filter_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="fav_search_box">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="epg_search_entry">
|
||||
<object class="GtkToggleButton" id="epg_filter_button">
|
||||
<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>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Filter</property>
|
||||
<signal name="toggled" handler="on_epg_filter_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="epg_filter_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-find-replace-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -212,256 +154,420 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="epg_search_down_button">
|
||||
<object class="GtkButton" id="epg_add_timer_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="sensitive" bind-source="src_xmltv_button" bind-property="active" bind-flags="invert-boolean">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Add timer</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_timer_add" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkArrow" id="epg_down_arrow">
|
||||
<object class="GtkImage" id="add_timer_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="arrow-type">down</property>
|
||||
<property name="icon-name">alarm-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="multi_epg_button">
|
||||
<property name="name">header-button</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Multi EPG</property>
|
||||
<signal name="toggled" handler="on_multi_epg_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="multi_epg_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-select-all-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="epg_options_button">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Options</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="epg_options_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">applications-system-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="epg_fs_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="epg_filter_entry">
|
||||
<property name="visible" bind-source="epg_filter_button" bind-property="active">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="primary-icon-name">edit-find-replace-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="primary-icon-sensitive">False</property>
|
||||
<signal name="search-changed" handler="on_epg_filter_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="fav_search_box">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="epg_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>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="epg_search_down_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkArrow" id="epg_down_arrow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="arrow-type">down</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="epg_search_up_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkArrow" id="epg_up_arrow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="arrow-type">up</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="group"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="epg_view_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="epg_search_up_button">
|
||||
<object class="GtkTreeView" id="epg_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="model">epg_sort_model</property>
|
||||
<property name="fixed-height-mode">True</property>
|
||||
<property name="rubber-banding">True</property>
|
||||
<property name="enable-grid-lines">both</property>
|
||||
<property name="tooltip-column">6</property>
|
||||
<signal name="button-press-event" handler="on_epg_press" swapped="no"/>
|
||||
<signal name="query-tooltip" handler="on_view_query_tooltip" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="epg_view_selection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkArrow" id="epg_up_arrow">
|
||||
<object class="GtkTreeViewColumn" id="epg_service_column">
|
||||
<property name="visible" bind-source="multi_epg_button" bind-property="active">False</property>
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed-width">100</property>
|
||||
<property name="min-width">40</property>
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<property name="sort-column-id">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_service_renderer">
|
||||
<property name="xpad">5</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_title_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">150</property>
|
||||
<property name="title" translatable="yes">Title</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<property name="sort-column-id">1</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_title_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_start_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">50</property>
|
||||
<property name="title" translatable="yes">Start time</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<property name="sort-column-id">2</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_start_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
<property name="width-chars">27</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_end_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">50</property>
|
||||
<property name="title" translatable="yes">End time</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<property name="sort-column-id">3</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_end_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
<property name="width-chars">27</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_length_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed-width">100</property>
|
||||
<property name="min-width">50</property>
|
||||
<property name="title" translatable="yes">Length</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<property name="sort-column-id">4</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_length_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_desc_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">100</property>
|
||||
<property name="title" translatable="yes">Description</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<property name="sort-column-id">5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_desc_renderer">
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">5</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="status_box">
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="event_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="event_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="width-chars">4</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="epg_cache_info_box">
|
||||
<property name="visible" bind-source="src_xmltv_button" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Current EPG cache contents.</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="arrow-type">up</property>
|
||||
<property name="icon-name">edit-select-all-symbolic</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="cache_info_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="group"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="epg_view_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="epg_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">epg_sort_model</property>
|
||||
<property name="fixed-height-mode">True</property>
|
||||
<property name="rubber-banding">True</property>
|
||||
<property name="enable-grid-lines">both</property>
|
||||
<property name="tooltip-column">6</property>
|
||||
<signal name="button-press-event" handler="on_epg_press" swapped="no"/>
|
||||
<signal name="query-tooltip" handler="on_view_query_tooltip" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="epg_selection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_service_column">
|
||||
<property name="visible" bind-source="multi_epg_button" bind-property="active">False</property>
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed-width">100</property>
|
||||
<property name="min-width">40</property>
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="alignment">0.49</property>
|
||||
<property name="sort-column-id">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_service_renderer">
|
||||
<property name="xpad">5</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_title_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">150</property>
|
||||
<property name="title" translatable="yes">Title</property>
|
||||
<property name="alignment">0.49</property>
|
||||
<property name="sort-column-id">1</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_title_renderer">
|
||||
<property name="xpad">5</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_start_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">50</property>
|
||||
<property name="title" translatable="yes">Start time</property>
|
||||
<property name="alignment">0.49</property>
|
||||
<property name="sort-column-id">2</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_start_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="xalign">0.49</property>
|
||||
<property name="width-chars">27</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_end_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">50</property>
|
||||
<property name="title" translatable="yes">End time</property>
|
||||
<property name="alignment">0.49</property>
|
||||
<property name="sort-column-id">3</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_end_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="xalign">0.49</property>
|
||||
<property name="width-chars">27</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_length_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed-width">100</property>
|
||||
<property name="min-width">50</property>
|
||||
<property name="title" translatable="yes">Length</property>
|
||||
<property name="alignment">0.49</property>
|
||||
<property name="sort-column-id">4</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_length_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="xalign">0.49</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_desc_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">100</property>
|
||||
<property name="title" translatable="yes">Description</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.49</property>
|
||||
<property name="sort-column-id">5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_desc_renderer">
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">5</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="status_box">
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="event_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="event_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="width-chars">4</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="epg_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-bottom">2</property>
|
||||
<property name="label" translatable="yes">EPG</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2023-2024 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
|
||||
@@ -25,9 +25,8 @@
|
||||
# Author: Dmitriy Yefremov
|
||||
#
|
||||
|
||||
|
||||
import json
|
||||
import os
|
||||
import pkgutil
|
||||
import shutil
|
||||
from enum import IntEnum
|
||||
from pathlib import Path
|
||||
@@ -41,6 +40,9 @@ from app.ui.uicommons import HeaderBar
|
||||
|
||||
EXT_URL = "https://api.github.com/repos/DYefremov/demoneditor-extensions/contents/extensions/"
|
||||
EXT_LIST_FILE = "https://raw.githubusercontent.com/DYefremov/demoneditor-extensions/main/extensions/extension-list"
|
||||
# Config file name. The config file must be in json format!
|
||||
# E.g. -> {"EXT_URL": "repo URL", "EXT_LIST_FILE": "URL to 'extension-list' file."}
|
||||
EXT_CONFIG_FILE = "ext_sources"
|
||||
HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i686; rv:112.0) Gecko/20100101 Firefox/112.0",
|
||||
"Accept": "application/json"}
|
||||
|
||||
@@ -69,6 +71,7 @@ class ExtensionManager(Gtk.Window):
|
||||
self._ext_path = f"{self._app.app_settings.default_data_path}tools{os.sep}extensions"
|
||||
|
||||
margin = {"margin_start": 5, "margin_end": 5, "margin_top": 5, "margin_bottom": 5}
|
||||
base_margin = {"margin_start": 10, "margin_end": 10, "margin_top": 10, "margin_bottom": 10}
|
||||
# Title, Description, Version, Info, Status, Name, URL, Path.
|
||||
self._model = Gtk.ListStore.new((str, str, str, str, bool, str, str, object))
|
||||
self._model.connect("row-deleted", self.on_model_changed)
|
||||
@@ -110,9 +113,11 @@ class ExtensionManager(Gtk.Window):
|
||||
self._view.append_column(column)
|
||||
self._status_column = column
|
||||
|
||||
main_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.VERTICAL)
|
||||
frame = Gtk.Frame(shadow_type=Gtk.ShadowType.IN, **margin)
|
||||
data_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.VERTICAL, **margin)
|
||||
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
frame = Gtk.Frame(shadow_type=Gtk.ShadowType.IN, **base_margin)
|
||||
frame.get_style_context().add_class("view")
|
||||
data_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.VERTICAL, **base_margin)
|
||||
data_box.set_margin_bottom(margin.get("margin_bottom", 5))
|
||||
# Status bar.
|
||||
status_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL, margin_start=5, margin_end=5)
|
||||
count_icon = Gtk.Image.new_from_icon_name("document-properties", Gtk.IconSize.SMALL_TOOLBAR)
|
||||
@@ -127,38 +132,37 @@ class ExtensionManager(Gtk.Window):
|
||||
self._load_spinner.bind_property("active", self._view, "sensitive", GObject.BindingFlags.INVERT_BOOLEAN)
|
||||
load_box.pack_end(self._load_spinner, False, False, 0)
|
||||
status_box.pack_end(load_box, False, False, 0)
|
||||
|
||||
data_box.pack_end(status_box, False, True, 0)
|
||||
|
||||
scrolled = Gtk.ScrolledWindow(shadow_type=Gtk.ShadowType.IN)
|
||||
scrolled.add(self._view)
|
||||
data_box.pack_start(scrolled, True, True, 0)
|
||||
data_box.set_margin_start(10)
|
||||
frame.add(data_box)
|
||||
self.add(main_box)
|
||||
# Popup menu.
|
||||
menu = Gtk.Menu()
|
||||
item = Gtk.ImageMenuItem.new_from_stock("gtk-goto-bottom")
|
||||
item.set_label(translate("Download"))
|
||||
item.connect("activate", self.on_download)
|
||||
menu.append(item)
|
||||
item = Gtk.ImageMenuItem.new_from_stock("gtk-remove")
|
||||
item.set_label(translate("Remove"))
|
||||
item.connect("activate", self.on_remove)
|
||||
menu.append(item)
|
||||
download_menu_item = Gtk.MenuItem.new_with_label(translate("Download"))
|
||||
download_menu_item.connect("activate", self.on_download)
|
||||
menu.append(download_menu_item)
|
||||
remove_menu_item = Gtk.MenuItem.new_with_label(translate("Remove"))
|
||||
remove_menu_item.connect("activate", self.on_remove)
|
||||
menu.append(remove_menu_item)
|
||||
menu.show_all()
|
||||
self._view.connect("button-press-event", self.on_view_popup_menu, menu)
|
||||
# Header and toolbar.
|
||||
download_button = Gtk.Button.new_from_icon_name("go-bottom-symbolic", Gtk.IconSize.BUTTON)
|
||||
download_button.set_label(translate("Download"))
|
||||
download_button.set_always_show_image(True)
|
||||
download_button.connect("clicked", self.on_download)
|
||||
self._download_button = Gtk.Button.new_from_icon_name("go-bottom-symbolic", Gtk.IconSize.BUTTON)
|
||||
self._download_button.set_tooltip_text(translate("Download"))
|
||||
self._download_button.set_always_show_image(True)
|
||||
self._download_button.connect("clicked", self.on_download)
|
||||
remove_button = Gtk.Button.new_from_icon_name("user-trash-symbolic", Gtk.IconSize.BUTTON)
|
||||
remove_button.set_label(translate("Remove"))
|
||||
remove_button.set_tooltip_text(translate("Remove"))
|
||||
remove_button.set_always_show_image(True)
|
||||
remove_button.connect("clicked", self.on_remove)
|
||||
|
||||
if app.app_settings.use_header_bar:
|
||||
header = HeaderBar()
|
||||
header.pack_start(download_button)
|
||||
header.pack_start(self._download_button)
|
||||
header.pack_start(remove_button)
|
||||
|
||||
self.set_titlebar(header)
|
||||
@@ -166,14 +170,21 @@ class ExtensionManager(Gtk.Window):
|
||||
else:
|
||||
toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
toolbar.get_style_context().add_class("primary-toolbar")
|
||||
button_box = Gtk.Box(spacing=2, orientation=Gtk.Orientation.HORIZONTAL, **margin)
|
||||
button_box.pack_start(download_button, False, False, 0)
|
||||
margin["margin_start"] = 15
|
||||
margin["margin_top"] = 10
|
||||
button_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL, **margin)
|
||||
button_box.pack_start(self._download_button, False, False, 0)
|
||||
button_box.pack_start(remove_button, False, False, 0)
|
||||
toolbar.pack_start(button_box, True, True, 0)
|
||||
main_box.pack_start(toolbar, False, False, 0)
|
||||
|
||||
main_box.pack_start(frame, True, True, 0)
|
||||
main_box.show_all()
|
||||
# Connection status.
|
||||
self._connection_status_image = Gtk.Image.new_from_icon_name("network-offline-symbolic", Gtk.IconSize.BUTTON)
|
||||
status_box.pack_end(self._connection_status_image, False, False, 0)
|
||||
self._download_button.bind_property("visible", self._connection_status_image, "visible", 4)
|
||||
self._download_button.bind_property("visible", download_menu_item, "visible")
|
||||
|
||||
ws_property = "extension_manager_window_size"
|
||||
window_size = self._app.app_settings.get(ws_property, None)
|
||||
@@ -181,35 +192,63 @@ class ExtensionManager(Gtk.Window):
|
||||
self.resize(*window_size)
|
||||
|
||||
self.connect("delete-event", lambda w, e: self._app.app_settings.add(ws_property, w.get_size()))
|
||||
self.connect("realize", self.init)
|
||||
|
||||
def init(self, widget):
|
||||
self._load_spinner.start()
|
||||
scf = f"{os.path.dirname(__file__)}{os.sep}{EXT_CONFIG_FILE}"
|
||||
if os.path.isfile(scf):
|
||||
with (open(scf, "r", encoding="utf-8", errors="ignore") as cf):
|
||||
config = json.load(cf)
|
||||
global EXT_URL, EXT_LIST_FILE
|
||||
EXT_URL = config.get("EXT_URL", EXT_URL)
|
||||
EXT_LIST_FILE = config.get("EXT_LIST_FILE", EXT_LIST_FILE)
|
||||
|
||||
self.update()
|
||||
|
||||
def get_installed(self):
|
||||
import pkgutil
|
||||
from importlib.util import module_from_spec
|
||||
|
||||
ext_paths = [f"{os.path.dirname(__file__)}{os.sep}", self._ext_path, "extensions"]
|
||||
installed = {}
|
||||
|
||||
for importer, name, is_package in pkgutil.iter_modules(ext_paths):
|
||||
if is_package:
|
||||
m = importer.find_module(name).load_module()
|
||||
spec = importer.find_spec(name)
|
||||
if spec is None:
|
||||
log(f"{self.__class__.__name__} [get installed]: Module {name} not found.")
|
||||
continue
|
||||
|
||||
m = module_from_spec(spec)
|
||||
spec.loader.exec_module(m)
|
||||
cls_name = name.capitalize()
|
||||
if hasattr(m, cls_name):
|
||||
cls = getattr(m, cls_name)
|
||||
path = Path(importer.find_module(name).path).parent
|
||||
path = Path(spec.origin).parent
|
||||
installed[name] = (cls, path)
|
||||
|
||||
return installed
|
||||
|
||||
@run_task
|
||||
def update(self):
|
||||
with requests.get(url=EXT_LIST_FILE, stream=True) as resp:
|
||||
if resp.status_code == 200:
|
||||
try:
|
||||
self.update_data(resp.json())
|
||||
except ValueError as e:
|
||||
log(f"{self.__class__.__name__} [update] error: {e}")
|
||||
else:
|
||||
log(f"{self.__class__.__name__} [update] error: {resp.reason}")
|
||||
error_msg = None
|
||||
try:
|
||||
with requests.get(url=EXT_LIST_FILE, stream=True) as resp:
|
||||
if resp.status_code == 200:
|
||||
try:
|
||||
self.update_data(resp.json())
|
||||
except ValueError as e:
|
||||
error_msg = f"{self.__class__.__name__} [update] error: {e}"
|
||||
else:
|
||||
error_msg = f"{self.__class__.__name__} [update] error: {resp.reason}"
|
||||
GLib.idle_add(self._app.show_error_message, "Data loading error!")
|
||||
except OSError as e:
|
||||
error_msg = f"{self.__class__.__name__} [update] error: Connection error. {e}"
|
||||
|
||||
if error_msg:
|
||||
log(error_msg)
|
||||
self.update_local_data()
|
||||
|
||||
@run_idle
|
||||
def update_data(self, data):
|
||||
@@ -217,6 +256,16 @@ class ExtensionManager(Gtk.Window):
|
||||
gen = self.append_data(data)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
@run_idle
|
||||
def update_local_data(self):
|
||||
self._download_button.set_visible(False)
|
||||
self._load_spinner.stop()
|
||||
self._model.clear()
|
||||
|
||||
for ext, d in self.get_installed().items():
|
||||
e, path = d
|
||||
self._model.append((e.LABEL, None, e.VERSION, None, path, ext, None, path))
|
||||
|
||||
def append_data(self, data):
|
||||
installed = self.get_installed()
|
||||
for e, d in data.items():
|
||||
@@ -274,7 +323,7 @@ class ExtensionManager(Gtk.Window):
|
||||
try:
|
||||
for f in resp.json():
|
||||
url = f.get("download_url", None)
|
||||
ver = f.get("version", "1.0")
|
||||
ver = f.get("version", ver)
|
||||
if url:
|
||||
urls[url] = f.get("name", None)
|
||||
except ValueError as e:
|
||||
|
||||
1811
app/ui/ftp.glade
1811
app/ui/ftp.glade
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -45,7 +45,7 @@ from app.connections import UtfFTP
|
||||
from app.settings import IS_LINUX, IS_DARWIN, IS_WIN, SEP, USE_HEADER_BAR
|
||||
from app.ui.dialogs import show_dialog, DialogType, get_builder, translate
|
||||
from app.ui.main_helper import on_popup_menu
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, Page
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, Page, LINK_ICON, FOLDER_ICON
|
||||
|
||||
File = namedtuple("File", ["icon", "name", "size", "date", "attr", "extra"])
|
||||
|
||||
@@ -296,12 +296,6 @@ class FtpClientBox(Gtk.HBox):
|
||||
# Force Ctrl
|
||||
self._ftp_view.connect("key-press-event", self._app.force_ctrl)
|
||||
self._file_view.connect("key-press-event", self._app.force_ctrl)
|
||||
# Icons
|
||||
theme = Gtk.IconTheme.get_default()
|
||||
folder_icon = "folder-symbolic" if settings.is_darwin else "folder"
|
||||
self._folder_icon = theme.load_icon(folder_icon, 16, 0) if theme.lookup_icon(folder_icon, 16, 0) else None
|
||||
self._link_icon = theme.load_icon("emblem-symbolic-link", 16, 0) if theme.lookup_icon("emblem-symbolic-link",
|
||||
16, 0) else None
|
||||
# Initialization
|
||||
self.init_drag_and_drop()
|
||||
self.init_ftp()
|
||||
@@ -377,10 +371,10 @@ class FtpClientBox(Gtk.HBox):
|
||||
icon = None
|
||||
if is_dir:
|
||||
r_size = self.FOLDER
|
||||
icon = self._folder_icon
|
||||
icon = FOLDER_ICON
|
||||
elif p.is_symlink():
|
||||
r_size = self.LINK
|
||||
icon = self._link_icon
|
||||
icon = LINK_ICON
|
||||
else:
|
||||
r_size = get_size_from_bytes(size)
|
||||
|
||||
@@ -401,10 +395,10 @@ class FtpClientBox(Gtk.HBox):
|
||||
icon = None
|
||||
if is_dir:
|
||||
r_size = self.FOLDER
|
||||
icon = self._folder_icon
|
||||
icon = FOLDER_ICON
|
||||
elif is_link:
|
||||
r_size = self.LINK
|
||||
icon = self._link_icon
|
||||
icon = LINK_ICON
|
||||
else:
|
||||
r_size = get_size_from_bytes(size)
|
||||
|
||||
@@ -675,7 +669,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
log(e)
|
||||
self._app.show_error_message(str(e))
|
||||
else:
|
||||
itr = self._file_model.append(File(self._folder_icon, path.name, self.FOLDER, "", str(path.resolve()), "0"))
|
||||
itr = self._file_model.append(File(FOLDER_ICON, path.name, self.FOLDER, "", str(path.resolve()), "0"))
|
||||
renderer.set_property("editable", True)
|
||||
self._file_view.set_cursor(self._file_model.get_path(itr), self._file_view.get_column(0), True)
|
||||
|
||||
@@ -695,7 +689,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
log(e)
|
||||
else:
|
||||
if resp == f"{cur_path}/{name}":
|
||||
itr = self._ftp_model.append(File(self._folder_icon, name, self.FOLDER, "", "drwxr-xr-x", "0"))
|
||||
itr = self._ftp_model.append(File(FOLDER_ICON, name, self.FOLDER, "", "drwxr-xr-x", "0"))
|
||||
renderer.set_property("editable", True)
|
||||
self._ftp_view.set_cursor(self._ftp_model.get_path(itr), self._ftp_view.get_column(0), True)
|
||||
|
||||
|
||||
@@ -434,9 +434,9 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkPaned" id="paned">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="wide-handle">True</property>
|
||||
<signal name="realize" handler="on_main_paned_realize" swapped="no"/>
|
||||
<child>
|
||||
@@ -449,112 +449,124 @@ Author: Dmitriy Yefremov
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0.49000000953674316</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="shadow-type">none</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="bouquets_box">
|
||||
<property name="width-request">100</property>
|
||||
<object class="GtkViewport" id="bouquets_viewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="bouquets_screlled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="bq_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">bq_list_store</property>
|
||||
<property name="headers-clickable">False</property>
|
||||
<property name="search-column">0</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="bq_popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
<signal name="select-all" handler="on_select_all" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="bouquet_name_column">
|
||||
<property name="title" translatable="yes">Name</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="bq_name_renderer">
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="bouquet_type_column">
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="bq_type_renderer">
|
||||
<property name="xalign">0.5099999904632568</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="bouquet_selected_column">
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="bq_selected_renderer">
|
||||
<signal name="toggled" handler="on_bq_selected_toggled" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="bouquets_status_box">
|
||||
<property name="height-request">26</property>
|
||||
<object class="GtkBox" id="bouquets_box">
|
||||
<property name="width-request">100</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="bouquets_count_image">
|
||||
<object class="GtkScrolledWindow" id="bouquets_screlled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="bq_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">bq_list_store</property>
|
||||
<property name="headers-clickable">False</property>
|
||||
<property name="search-column">0</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="bq_popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
<signal name="select-all" handler="on_select_all" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="bouquet_name_column">
|
||||
<property name="title" translatable="yes">Name</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="bq_name_renderer">
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="bouquet_type_column">
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="bq_type_renderer">
|
||||
<property name="xalign">0.5099999904632568</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="bouquet_selected_column">
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="bq_selected_renderer">
|
||||
<signal name="toggled" handler="on_bq_selected_toggled" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="bouquets_count_label">
|
||||
<object class="GtkBox" id="bouquets_status_box">
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">0</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="bouquets_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="bouquets_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -562,25 +574,22 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-bottom">2</property>
|
||||
<property name="label" translatable="yes">Bouquets</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -594,98 +603,110 @@ Author: Dmitriy Yefremov
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0.49000000953674316</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="shadow-type">none</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="satellites_box">
|
||||
<property name="width-request">100</property>
|
||||
<object class="GtkViewport" id="sat_viewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="satellites_screlled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="sat_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">sat_list_store</property>
|
||||
<property name="headers-clickable">False</property>
|
||||
<property name="search-column">0</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="sat_popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
<signal name="realize" handler="on_sat_view_realize" swapped="no"/>
|
||||
<signal name="select-all" handler="on_select_all" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="sat_position_column">
|
||||
<property name="title" translatable="yes">Position</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="sat_position_renderer">
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="sat_selected_column">
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="sat_selected_renderer">
|
||||
<signal name="toggled" handler="on_sat_selected_toggled" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="sat_status_box">
|
||||
<property name="height-request">26</property>
|
||||
<object class="GtkBox" id="satellites_box">
|
||||
<property name="width-request">100</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="sat_count_image">
|
||||
<object class="GtkScrolledWindow" id="satellites_screlled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="sat_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">sat_list_store</property>
|
||||
<property name="headers-clickable">False</property>
|
||||
<property name="search-column">0</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="sat_popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
<signal name="realize" handler="on_sat_view_realize" swapped="no"/>
|
||||
<signal name="select-all" handler="on_select_all" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="sat_position_column">
|
||||
<property name="title" translatable="yes">Position</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="sat_position_renderer">
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="sat_selected_column">
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="sat_selected_renderer">
|
||||
<signal name="toggled" handler="on_sat_selected_toggled" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="sat_count_label">
|
||||
<object class="GtkBox" id="sat_status_box">
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">0</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="sat_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="sat_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -693,25 +714,22 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-bottom">2</property>
|
||||
<property name="label" translatable="yes">Satellites</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -732,183 +750,130 @@ Author: Dmitriy Yefremov
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0.49000000953674316</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="shadow-type">none</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="services_box">
|
||||
<property name="width-request">100</property>
|
||||
<object class="GtkViewport" id="services_viewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="services_view_scrolled_window">
|
||||
<object class="GtkBox" id="services_box">
|
||||
<property name="width-request">100</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="services_view">
|
||||
<object class="GtkScrolledWindow" id="services_view_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">services_list_store</property>
|
||||
<property name="headers-clickable">False</property>
|
||||
<property name="search-column">0</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="services_popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_service_changed" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
<signal name="realize" handler="on_services_view_realize" swapped="no"/>
|
||||
<signal name="select-all" handler="on_select_all" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="service_name_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">50</property>
|
||||
<property name="title" translatable="yes">Name</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="info_name_renderer">
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
<attribute name="background-rgba">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="service_type_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">75</property>
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="info_type_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="xalign">0.5099999904632568</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
<attribute name="background-rgba">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="service_selected_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">75</property>
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="service_selected_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<signal name="toggled" handler="on_service_selected_toggled" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">3</attribute>
|
||||
<attribute name="active">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="service_info_box_frame">
|
||||
<property name="visible" bind-source="details_button" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="label-xalign">0</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="service_info_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="service_info_label">
|
||||
<object class="GtkTreeView" id="services_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">services_list_store</property>
|
||||
<property name="headers-clickable">False</property>
|
||||
<property name="search-column">0</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="services_popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_service_changed" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
<signal name="realize" handler="on_services_view_realize" swapped="no"/>
|
||||
<signal name="select-all" handler="on_select_all" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="service_name_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">50</property>
|
||||
<property name="title" translatable="yes">Name</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="info_name_renderer">
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
<attribute name="background-rgba">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="service_type_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">75</property>
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="info_type_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="xalign">0.5099999904632568</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
<attribute name="background-rgba">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="service_selected_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">75</property>
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="service_selected_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<signal name="toggled" handler="on_service_selected_toggled" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">3</attribute>
|
||||
<attribute name="active">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</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="GtkBox" id="services_status_box">
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="services_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="services_count_label">
|
||||
<property name="visible">True</property>
|
||||
<object class="GtkFrame" id="service_info_box_frame">
|
||||
<property name="visible" bind-source="details_button" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="service_exists_frame">
|
||||
<property name="width-request">32</property>
|
||||
<property name="height-request">16</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="label-xalign">0</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
<object class="GtkBox" id="service_info_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="service_info_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</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/>
|
||||
@@ -917,39 +882,101 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">2</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id=" service_exists_label">
|
||||
<object class="GtkBox" id="services_status_box">
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Already exists</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="services_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="services_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="service_exists_frame">
|
||||
<property name="width-request">32</property>
|
||||
<property name="height-request">16</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="label-xalign">0</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id=" service_exists_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Already exists</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</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">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-bottom">2</property>
|
||||
<property name="label" translatable="yes">Services</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -36,7 +36,7 @@ from app.eparser.ecommons import BqType, BqServiceType, Bouquet
|
||||
from app.eparser.neutrino.bouquets import parse_webtv, parse_bouquets as get_neutrino_bouquets
|
||||
from app.settings import SettingsType, IS_DARWIN, SEP
|
||||
from app.ui.dialogs import show_dialog, DialogType, get_chooser_dialog, translate, get_builder
|
||||
from app.ui.main_helper import on_popup_menu, get_iptv_data
|
||||
from app.ui.main_helper import on_popup_menu, get_iptv_data, show_info_bar_message
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, Column, Page, HeaderBar
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ def import_bouquet(app, model, path, appender, file_path=None):
|
||||
|
||||
if profile is SettingsType.ENIGMA_2:
|
||||
pattern = f".{bq_type.value}"
|
||||
f_pattern = f"{'' if IS_DARWIN else 'userbouquet.'}*{pattern}"
|
||||
f_pattern = f"*{pattern}"
|
||||
elif profile is SettingsType.NEUTRINO_MP:
|
||||
pattern = "webtv.xml" if bq_type is BqType.WEBTV else "bouquets.xml"
|
||||
f_pattern = "bouquets.xml"
|
||||
@@ -96,10 +96,9 @@ def import_bouquet(app, model, path, appender, file_path=None):
|
||||
|
||||
|
||||
def get_enigma2_bouquet(path):
|
||||
path, sep, f_name = path.rpartition("userbouquet.")
|
||||
name, sep, suf = f_name.rpartition(".")
|
||||
bq = BouquetsReader.get_bouquet(path, name, suf)
|
||||
bouquet = Bouquet(name=bq[0], type=BqType(suf).value, services=bq[1], locked=None, hidden=None)
|
||||
p = Path(path)
|
||||
bq = BouquetsReader.get_bouquet(f"{p.parent}{SEP}", f"{p.stem}{p.suffix}", p.stem)
|
||||
bouquet = Bouquet(name=bq[0], type=BqType(p.suffix.lstrip(".")).value, services=bq[1], locked=None, hidden=None)
|
||||
return bouquet
|
||||
|
||||
|
||||
@@ -374,9 +373,7 @@ class ImportDialog:
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(True)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._message_label.set_text(text)
|
||||
show_info_bar_message(self._info_bar, self._message_label, text, message_type)
|
||||
|
||||
@run_idle
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
|
||||
1227
app/ui/iptv.glade
1227
app/ui/iptv.glade
File diff suppressed because it is too large
Load Diff
343
app/ui/iptv.py
343
app/ui/iptv.py
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -30,6 +30,8 @@ import concurrent.futures
|
||||
import os
|
||||
import re
|
||||
import urllib
|
||||
from datetime import date
|
||||
from itertools import groupby, chain
|
||||
from urllib.error import HTTPError
|
||||
from urllib.parse import urlparse, unquote, quote
|
||||
from urllib.request import Request, urlopen
|
||||
@@ -38,19 +40,20 @@ import requests
|
||||
from gi.repository import GLib, Gio, GdkPixbuf
|
||||
|
||||
from app.commons import run_idle, run_task, log
|
||||
from app.eparser.ecommons import BqServiceType, Service
|
||||
from app.eparser.ecommons import BqServiceType, BouquetService, Service
|
||||
from app.eparser.iptv import (NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT, get_fav_id, MARKER_FORMAT,
|
||||
parse_m3u, PICON_FORMAT)
|
||||
from app.settings import SettingsType
|
||||
from app.tools.yt import YouTubeException, YouTube
|
||||
from app.ui.dialogs import Action, show_dialog, DialogType, translate, get_builder
|
||||
from app.ui.main_helper import get_iptv_url, on_popup_menu, get_picon_pixbuf
|
||||
from app.ui.dialogs import Action, show_dialog, DialogType, translate, get_builder, BaseDialog
|
||||
from app.ui.main_helper import get_iptv_url, on_popup_menu, get_picon_pixbuf, show_info_bar_message, gen_bouquet_name
|
||||
from app.ui.uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, IPTV_ICON, Column, KeyboardKey, get_yt_icon, HeaderBar)
|
||||
|
||||
_DIGIT_ENTRY_NAME = "digit-entry"
|
||||
_ENIGMA2_REFERENCE = "{}:{}:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0"
|
||||
_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
|
||||
_UI_PATH = UI_RESOURCES_PATH + "iptv.glade"
|
||||
_UI_PATH = f"{UI_RESOURCES_PATH}iptv.glade"
|
||||
_CSS_PATH = f"{UI_RESOURCES_PATH}style.css"
|
||||
_URL_PREFIXES = {"YT-DLP": "YT-DLP://", "YT-DL": "YT-DL://", "STREAMLINK": "streamlink://", "No": None}
|
||||
|
||||
|
||||
@@ -105,7 +108,7 @@ class IptvDialog:
|
||||
self._name_entry = builder.get_object("name_entry")
|
||||
self._description_entry = builder.get_object("description_entry")
|
||||
self._url_entry = builder.get_object("url_entry")
|
||||
self._reference_entry = builder.get_object("reference_entry")
|
||||
self._reference_label = builder.get_object("iptv_reference_label")
|
||||
self._srv_type_entry = builder.get_object("srv_type_entry")
|
||||
self._srv_id_entry = builder.get_object("srv_id_entry")
|
||||
self._sid_entry = builder.get_object("sid_entry")
|
||||
@@ -124,7 +127,7 @@ class IptvDialog:
|
||||
self._model, self._paths = view.get_selection().get_selected_rows()
|
||||
# Style.
|
||||
self._style_provider = Gtk.CssProvider()
|
||||
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
self._style_provider.load_from_path(_CSS_PATH)
|
||||
self._digit_elems = (self._srv_id_entry, self._srv_type_entry, self._sid_entry, self._tr_id_entry,
|
||||
self._net_id_entry, self._namespace_entry)
|
||||
for el in self._digit_elems:
|
||||
@@ -133,8 +136,7 @@ class IptvDialog:
|
||||
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)
|
||||
builder.get_object("iptv_reference_label").set_visible(False)
|
||||
builder.get_object("iptv_ref_box").set_visible(False)
|
||||
self._stream_type_combobox.set_visible(False)
|
||||
else:
|
||||
self._description_entry.set_visible(False)
|
||||
@@ -164,14 +166,14 @@ class IptvDialog:
|
||||
self.on_url_changed(self._url_entry)
|
||||
|
||||
if not is_data_correct(self._digit_elems) or self._url_entry.get_name() == _DIGIT_ENTRY_NAME:
|
||||
self.show_info_message(translate("Error. Verify the data!"), Gtk.MessageType.ERROR)
|
||||
self.show_info_message("Error. Verify the data!", Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
url = self._url_entry.get_text()
|
||||
if all((self._url_prefix_box.get_visible(),
|
||||
self._url_prefix_combobox.get_active_id(),
|
||||
url.count("http") > 1 or urlparse(url).scheme.upper() in _URL_PREFIXES)):
|
||||
self.show_info_message(translate("Invalid prefix for the given URL!"), Gtk.MessageType.ERROR)
|
||||
self.show_info_message("Invalid prefix for the given URL!", Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
@@ -236,7 +238,7 @@ class IptvDialog:
|
||||
|
||||
def update_reference_entry(self):
|
||||
if self._s_type is SettingsType.ENIGMA_2 and is_data_correct(self._digit_elems):
|
||||
self._reference_entry.set_text(_ENIGMA2_REFERENCE.format(self.get_type(),
|
||||
self._reference_label.set_text(_ENIGMA2_REFERENCE.format(self.get_type(),
|
||||
self._srv_id_entry.get_text(),
|
||||
int(self._srv_type_entry.get_text()),
|
||||
int(self._sid_entry.get_text()),
|
||||
@@ -248,11 +250,8 @@ class IptvDialog:
|
||||
return get_stream_type(self._stream_type_combobox)
|
||||
|
||||
def on_entry_changed(self, entry):
|
||||
if _PATTERN.search(entry.get_text()):
|
||||
entry.set_name(_DIGIT_ENTRY_NAME)
|
||||
else:
|
||||
entry.set_name("GtkEntry")
|
||||
self.update_reference_entry()
|
||||
entry.set_name(_DIGIT_ENTRY_NAME if _PATTERN.search(entry.get_text()) else "GtkEntry")
|
||||
self.update_reference_entry()
|
||||
|
||||
def on_url_changed(self, entry):
|
||||
url_str = entry.get_text()
|
||||
@@ -365,7 +364,7 @@ class IptvDialog:
|
||||
self._dialog.destroy()
|
||||
|
||||
def update_bouquet_data(self, name, fav_id):
|
||||
picon_id = f"{self._reference_entry.get_text().replace(':', '_')}.png"
|
||||
picon_id = f"{self._reference_label.get_text().replace(':', '_')}.png"
|
||||
|
||||
if self._action is Action.EDIT:
|
||||
services = self._app.current_services
|
||||
@@ -391,9 +390,7 @@ class IptvDialog:
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(True)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._message_label.set_text(text)
|
||||
show_info_bar_message(self._info_bar, self._message_label, text, message_type)
|
||||
|
||||
|
||||
class SearchUnavailableDialog:
|
||||
@@ -510,6 +507,7 @@ class IptvListDialog:
|
||||
self._data_box = builder.get_object("iptv_list_data_box")
|
||||
self._start_values_grid = builder.get_object("start_values_grid")
|
||||
self._info_bar = builder.get_object("list_configuration_info_bar")
|
||||
self._message_label = builder.get_object("list_configuration_message_label")
|
||||
self._reference_label = builder.get_object("reference_label")
|
||||
self._stream_type_check_button = builder.get_object("stream_type_default_check_button")
|
||||
self._id_default_check_button = builder.get_object("id_default_check_button")
|
||||
@@ -532,7 +530,7 @@ class IptvListDialog:
|
||||
self._ok_button.bind_property("visible", self._cancel_button, "visible", 4)
|
||||
# Style
|
||||
style_provider = Gtk.CssProvider()
|
||||
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
style_provider.load_from_path(_CSS_PATH)
|
||||
self._default_elems = (self._stream_type_check_button, self._id_default_check_button, self._type_check_button,
|
||||
self._sid_auto_check_button, self._tid_check_button, self._nid_check_button,
|
||||
self._namespace_check_button)
|
||||
@@ -591,6 +589,10 @@ class IptvListDialog:
|
||||
for el in self._default_elems:
|
||||
el.set_active(True)
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type=Gtk.MessageType.INFO):
|
||||
show_info_bar_message(self._info_bar, self._message_label, text, message_type)
|
||||
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
|
||||
@@ -631,7 +633,7 @@ class IptvListConfigurationDialog(IptvListDialog):
|
||||
@run_idle
|
||||
def on_apply(self, item):
|
||||
if not is_data_correct(self._digit_elems):
|
||||
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
|
||||
self.show_info_message("Error. Verify the data!", Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
@@ -678,7 +680,7 @@ class IptvListConfigurationDialog(IptvListDialog):
|
||||
self._bouquet.clear()
|
||||
list(map(lambda r: self._bouquet.append(r[Column.FAV_ID]), self._fav_model))
|
||||
|
||||
self._info_bar.set_visible(True)
|
||||
self.show_info_message("Done!", Gtk.MessageType.INFO)
|
||||
self._ok_button.set_visible(True)
|
||||
|
||||
|
||||
@@ -692,6 +694,7 @@ class M3uImportDialog(IptvListDialog):
|
||||
self._picons = app.picons
|
||||
self._pic_path = app._settings.profile_picons_path
|
||||
self._services = None
|
||||
self._epg_src = None
|
||||
self._url_count = 0
|
||||
self._errors_count = 0
|
||||
self._max_count = 0
|
||||
@@ -700,53 +703,68 @@ class M3uImportDialog(IptvListDialog):
|
||||
self._dialog.set_title(translate("Playlist import"))
|
||||
self._dialog.connect("delete-event", self.on_close)
|
||||
self._apply_button.set_label(translate("Import"))
|
||||
# Progress
|
||||
self._progress_bar = Gtk.ProgressBar(visible=False, valign="center")
|
||||
self._spinner = Gtk.Spinner(active=False)
|
||||
self._info_label = Gtk.Label(visible=True, ellipsize="end", max_width_chars=30)
|
||||
load_label = Gtk.Label(label=translate("Loading data..."))
|
||||
self._spinner.bind_property("active", self._spinner, "visible")
|
||||
self._spinner.bind_property("visible", load_label, "visible")
|
||||
# Extra box.
|
||||
builder = get_builder(f"{UI_RESOURCES_PATH}m3u.glade", use_str=True, objects=("import_m3u_box",))
|
||||
self._info_label = builder.get_object("info_label")
|
||||
self._progress_bar = builder.get_object("progress_bar")
|
||||
self._spinner = builder.get_object("spinner")
|
||||
self._spinner.bind_property("active", self._start_values_grid, "sensitive", 4)
|
||||
self._picon_switch = builder.get_object("picon_switch")
|
||||
self._picon_box = builder.get_object("picon_box")
|
||||
# Type import buttons.
|
||||
self._current_bq_button = builder.get_object("current_bq_button")
|
||||
self._single_bq_button = builder.get_object("single_bq_button")
|
||||
self._group_bq_button = builder.get_object("group_bq_button")
|
||||
self._sub_bq_button = builder.get_object("sub_bq_button")
|
||||
# EPG src.
|
||||
self._epg_links_button = builder.get_object("epg_links_box")
|
||||
self._add_epg_src_switch = builder.get_object("add_epg_src_switch")
|
||||
|
||||
progress_box = Gtk.HBox(visible=True, spacing=2)
|
||||
progress_box.add(self._progress_bar)
|
||||
progress_box.pack_end(self._spinner, False, False, 0)
|
||||
progress_box.pack_start(load_label, False, False, 0)
|
||||
# Picons
|
||||
self._picons_switch = Gtk.Switch(visible=True)
|
||||
self._picon_box = Gtk.HBox(visible=True, sensitive=False, spacing=5)
|
||||
self._picon_box.pack_end(self._picons_switch, False, False, 0)
|
||||
self._picon_box.pack_end(Gtk.Label(visible=True, label=translate("Download picons")), False, False, 0)
|
||||
# Extra box
|
||||
extra_box = Gtk.HBox(visible=True, spacing=2, margin_bottom=5, margin_top=5)
|
||||
extra_box.set_center_widget(progress_box)
|
||||
extra_box.pack_start(self._info_label, False, False, 5)
|
||||
extra_box.pack_end(self._picon_box, True, True, 5)
|
||||
|
||||
frame = Gtk.Frame(visible=True, margin_bottom=5)
|
||||
frame.add(extra_box)
|
||||
self._data_box.add(frame)
|
||||
m3u_box = builder.get_object("import_m3u_box")
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
self._data_box.add(m3u_box)
|
||||
else:
|
||||
self._data_box.set_visible(False)
|
||||
self._group_bq_button.set_sensitive(False)
|
||||
self._sub_bq_button.set_sensitive(False)
|
||||
m3u_box.set_margin_start(5)
|
||||
m3u_box.set_margin_end(5)
|
||||
area = self._dialog.get_content_area()
|
||||
area.pack_start(m3u_box, True, True, 0)
|
||||
area.reorder_child(m3u_box, 0)
|
||||
|
||||
self.get_m3u(m3_path, s_type)
|
||||
|
||||
@run_task
|
||||
def get_m3u(self, path, s_type):
|
||||
try:
|
||||
GLib.idle_add(self._spinner.set_property, "active", True)
|
||||
self._services = parse_m3u(path, s_type)
|
||||
GLib.idle_add(self._spinner.start)
|
||||
self._epg_src, self._services = parse_m3u(path, s_type)
|
||||
for s in self._services:
|
||||
if s.picon:
|
||||
GLib.idle_add(self._picon_box.set_sensitive, True)
|
||||
break
|
||||
finally:
|
||||
msg = f"{translate('Streams detected:')} {len(self._services) if self._services else 0}."
|
||||
GLib.idle_add(self._info_label.set_text, msg)
|
||||
GLib.idle_add(self._spinner.set_property, "active", False)
|
||||
self.update_info()
|
||||
|
||||
@run_idle
|
||||
def update_info(self):
|
||||
msg = f"{translate('Streams detected:')} {len(self._services) if self._services else 0}."
|
||||
self._info_label.set_text(msg)
|
||||
self._spinner.stop()
|
||||
|
||||
if self._epg_src:
|
||||
self._epg_links_button.set_visible(True)
|
||||
[self._epg_links_button.append(u, u) for u in self._epg_src]
|
||||
self._epg_links_button.set_active(0)
|
||||
|
||||
def on_apply(self, item):
|
||||
if self._current_bq_button.get_active() and not self._app.current_bouquet:
|
||||
self.show_info_message("Error. No bouquet is selected!", Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
if not is_data_correct(self._digit_elems):
|
||||
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
|
||||
self.show_info_message("Error. Verify the data!", Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
picons = {}
|
||||
@@ -776,18 +794,76 @@ class M3uImportDialog(IptvListDialog):
|
||||
|
||||
services.append(s._replace(picon=None, picon_id=picon_id, data_id=None, fav_id=fav_id))
|
||||
|
||||
if self._picons_switch.get_active():
|
||||
if self._add_epg_src_switch.get_active():
|
||||
self.on_add_epg_source()
|
||||
|
||||
if self._picon_switch.get_active():
|
||||
if self.is_default_values():
|
||||
show_dialog(DialogType.ERROR, self._dialog,
|
||||
"Set values for TID, NID and Namespace for correct naming of the picons!")
|
||||
msg = "Set values for TID, NID and Namespace for correct naming of the picons!"
|
||||
self.show_info_message(msg, Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
self.download_picons(picons)
|
||||
else:
|
||||
GLib.idle_add(self._ok_button.set_visible, True)
|
||||
GLib.idle_add(self._info_bar.set_visible, True, priority=GLib.PRIORITY_LOW)
|
||||
self.on_apply_done()
|
||||
|
||||
self._app.append_imported_services(services)
|
||||
self.import_services(services)
|
||||
|
||||
def import_services(self, services):
|
||||
if self._current_bq_button.get_active():
|
||||
self._app.append_imported_services(services)
|
||||
return
|
||||
|
||||
s_type = self._app.app_settings.setting_type
|
||||
model = self._app.bouquets_view.get_model()
|
||||
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
itr = model.get_iter_first()
|
||||
else:
|
||||
# We will use the 'FAV' section for Neutrino!
|
||||
itr = model.get_iter(Gtk.TreePath.new_from_indices([1]))
|
||||
|
||||
bqs = self._app.current_bouquets
|
||||
bq_type = model.get_value(itr, Column.BQ_TYPE)
|
||||
def_bq_name = gen_bouquet_name(bqs, f"IPTV {date.today()} ", bq_type)
|
||||
|
||||
if self._single_bq_button.get_active():
|
||||
self.append_bouquet(def_bq_name, bq_type, bqs, model, itr, services)
|
||||
else:
|
||||
# Sub-bouquets.
|
||||
if self._sub_bq_button.get_active():
|
||||
itr = self.append_bouquet(gen_bouquet_name(bqs, def_bq_name, bq_type), bq_type, bqs, model, itr, ())
|
||||
# Generating groups with skipping markers.
|
||||
m_name = BqServiceType.MARKER.value
|
||||
def_bq_name = f"{def_bq_name} [No group]"
|
||||
gr = self.get_services_groups(filter(lambda s: s.service_type != m_name, services), def_bq_name)
|
||||
[self.append_bouquet(gen_bouquet_name(bqs, g, bq_type), bq_type, bqs, model, itr, s) for g, s in gr.items()]
|
||||
|
||||
def append_bouquet(self, bq_name, bq_type, bqs, model, itr, services):
|
||||
""" Adds new bouquet and returns iter of appended row. """
|
||||
cur_services = self._app.current_services
|
||||
bqs[f"{bq_name}:{bq_type}"] = [s.fav_id for s in services]
|
||||
cur_services.update({s.fav_id: s for s in services})
|
||||
bq = (bq_name, None, None, bq_type)
|
||||
return model.append(itr, bq)
|
||||
|
||||
def get_services_groups(self, services, def_gr_name="No group"):
|
||||
def grouper(s):
|
||||
return s.package or def_gr_name
|
||||
|
||||
return {k: list(v) for k, v in groupby(sorted(services, key=grouper), key=grouper)}
|
||||
|
||||
def on_add_epg_source(self):
|
||||
active_src = self._epg_links_button.get_active_id()
|
||||
settings = self._app.app_settings
|
||||
sources = settings.epg_xml_sources
|
||||
log(f"Adding an EPG source -> {active_src}")
|
||||
if active_src not in set(sources):
|
||||
sources.append(active_src)
|
||||
settings.epg_xml_sources = sources
|
||||
self._app.emit("epg-settings-changed", None)
|
||||
else:
|
||||
log(f"{translate('This URL already exists!')}")
|
||||
|
||||
@run_task
|
||||
def download_picons(self, picons):
|
||||
@@ -869,10 +945,15 @@ class M3uImportDialog(IptvListDialog):
|
||||
model.set_value(r.iter, Column.FAV_PICON, picons.get(s.picon_id, None))
|
||||
yield True
|
||||
|
||||
self._info_bar.set_visible(True)
|
||||
self._ok_button.set_visible(True)
|
||||
self.on_apply_done()
|
||||
yield True
|
||||
|
||||
@run_idle
|
||||
def on_apply_done(self):
|
||||
self.show_info_message("Done!", Gtk.MessageType.INFO)
|
||||
self._ok_button.set_visible(True)
|
||||
self._picon_box.set_sensitive(False)
|
||||
|
||||
def on_response(self, dialog, response):
|
||||
if response == Gtk.ResponseType.APPLY:
|
||||
return True
|
||||
@@ -891,6 +972,134 @@ class M3uImportDialog(IptvListDialog):
|
||||
return False
|
||||
|
||||
|
||||
class ExportM3uDialog(BaseDialog):
|
||||
def __init__(self, app, bouquets):
|
||||
super().__init__(app.app_window, "Export to m3u",
|
||||
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, translate("Save"), Gtk.ResponseType.OK))
|
||||
self._app = app
|
||||
self._bouquets = bouquets
|
||||
self._url = None
|
||||
self._default_port = "8001"
|
||||
|
||||
builder = get_builder(f"{UI_RESOURCES_PATH}m3u.glade", use_str=True, objects=("export_m3u_box",))
|
||||
self._main_grid = builder.get_object("export_m3u_grid")
|
||||
self._port_entry = builder.get_object("export_port_entry")
|
||||
self._port_auto_button = builder.get_object("export_auto_button")
|
||||
self._all_type_button = builder.get_object("export_all_button")
|
||||
self._iptv_type_button = builder.get_object("export_iptv_button")
|
||||
self._grp_bq_button = builder.get_object("export_grp_bq_button")
|
||||
self._grp_marker_button = builder.get_object("export_grp_markers_button")
|
||||
self._bq_count_label = builder.get_object("export_bq_count_label")
|
||||
self._services_count_label = builder.get_object("export_services_count_label")
|
||||
self.get_content_area().pack_start(builder.get_object("export_m3u_box"), False, False, 0)
|
||||
|
||||
is_enigma = self._app.is_enigma
|
||||
self._port_auto_button.set_active(True) if is_enigma else self._main_grid.remove_row(0)
|
||||
self._grp_marker_button.set_visible(is_enigma)
|
||||
self._all_type_button.set_active(True) if is_enigma else self._iptv_type_button.set_active(True)
|
||||
self._all_type_button.set_sensitive(is_enigma)
|
||||
|
||||
self.connect("response", self.on_response)
|
||||
self.connect("realize", self.init)
|
||||
|
||||
def init(self, widget=None):
|
||||
self._bq_count_label.set_text(str(len(self._bouquets)))
|
||||
self._services_count_label.set_text(str(len(list(chain.from_iterable(self._bouquets.values())))))
|
||||
|
||||
if self._app.is_enigma:
|
||||
self._port_entry.connect("changed", self.on_port_changed)
|
||||
self._port_auto_button.connect("toggled", self.on_port_auto_toggled)
|
||||
# Add style for the port entry.
|
||||
style_provider = Gtk.CssProvider()
|
||||
style_provider.load_from_path(_CSS_PATH)
|
||||
context = self._port_entry.get_style_context()
|
||||
context.add_provider_for_screen(Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
|
||||
def on_port_changed(self, entry):
|
||||
entry.set_name(_DIGIT_ENTRY_NAME if _PATTERN.search(entry.get_text()) else "GtkEntry")
|
||||
|
||||
def on_port_auto_toggled(self, button):
|
||||
if not button.get_active() and not self._port_entry.get_text():
|
||||
self._port_entry.set_text(self._default_port)
|
||||
|
||||
def on_response(self, dialog, response):
|
||||
if response != Gtk.ResponseType.OK:
|
||||
self.destroy()
|
||||
else:
|
||||
if self._app.is_enigma:
|
||||
if self._port_auto_button.get_active():
|
||||
self.do_export_auto()
|
||||
else:
|
||||
if self._port_entry.get_name() == _DIGIT_ENTRY_NAME:
|
||||
self._app.show_error_message("Error. Verify the data!")
|
||||
else:
|
||||
st = self._app.app_settings
|
||||
self._url = f"http{'s' if st.http_use_ssl else ''}://{st.host}:{self._port_entry.get_text()}/"
|
||||
self.do_export()
|
||||
else:
|
||||
self.do_export()
|
||||
return True
|
||||
|
||||
def do_export_auto(self, button=None):
|
||||
""" Retrieves streaming port from Receiver via HTTP API and starts export.
|
||||
|
||||
Since the streaming port can be changed by the user,
|
||||
we're getting base link to the stream -> http(s)://IP:PORT/
|
||||
"""
|
||||
from app.connections import HttpAPI
|
||||
|
||||
sent = self._app.send_http_request(HttpAPI.Request.STREAM, "", self.start_export)
|
||||
self._port_auto_button.set_active(sent)
|
||||
self._port_auto_button.set_sensitive(sent)
|
||||
|
||||
def start_export(self, data):
|
||||
self._port_auto_button.set_active("error_code" not in data)
|
||||
|
||||
url = self._app.get_url_from_m3u(data)
|
||||
url = urlparse(url)
|
||||
if all((url.scheme, url.port)):
|
||||
self._url = url.geturl()
|
||||
self._port_entry.set_text(str(url.port))
|
||||
self.do_export()
|
||||
|
||||
@run_idle
|
||||
def do_export(self):
|
||||
self.destroy()
|
||||
|
||||
services = self._app.current_services
|
||||
|
||||
def get_service(fav_id, num=0):
|
||||
srv = services.get(fav_id, None)
|
||||
if srv:
|
||||
s_type = BqServiceType(srv.service_type)
|
||||
if s_type is BqServiceType.DEFAULT:
|
||||
srv = services.get(fav_id, None)
|
||||
s_data = srv.picon_id.rstrip(".png").replace("_", ":") if srv.picon_id else None
|
||||
return BouquetService(srv.service, s_type, s_data, num)
|
||||
return BouquetService(srv.service, s_type, fav_id, num)
|
||||
return BouquetService("N/A", BqServiceType.MARKER, fav_id, num)
|
||||
|
||||
# Preparing bouquets data.
|
||||
bouquets = {b[:b.rindex(":")]: [get_service(i) for i in s] for b, s in self._bouquets.items()}
|
||||
|
||||
bq_services = []
|
||||
s_types = {BqServiceType.IPTV}
|
||||
if self._all_type_button.get_active():
|
||||
s_types.add(BqServiceType.DEFAULT)
|
||||
|
||||
if self._grp_bq_button.get_active():
|
||||
for b, bs in bouquets.items():
|
||||
bq_services.append(BouquetService(b, BqServiceType.MARKER, None, 0))
|
||||
bq_services.extend(filter(lambda s: s.type in s_types, bs))
|
||||
elif self._grp_marker_button.get_active():
|
||||
bq_services = chain.from_iterable(bouquets.values())
|
||||
else:
|
||||
bq_services = filter(lambda s: s.type in s_types, chain.from_iterable(bouquets.values()))
|
||||
|
||||
file_name = f"{'_'.join(list(bouquets)[:10])}__{date.today().strftime('%Y_%m_%d')}"
|
||||
self._app.save_bouquet_to_m3u(bq_services, self._url, file_name)
|
||||
|
||||
|
||||
class YtListImportDialog:
|
||||
def __init__(self, app):
|
||||
handlers = {"on_import": self.on_import,
|
||||
@@ -956,7 +1165,7 @@ class YtListImportDialog:
|
||||
self._dialog.resize(*window_size)
|
||||
# Style.
|
||||
style_provider = Gtk.CssProvider()
|
||||
style_provider.load_from_path(f"{UI_RESOURCES_PATH}style.css")
|
||||
style_provider.load_from_path(_CSS_PATH)
|
||||
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
|
||||
@@ -1073,7 +1282,7 @@ class YtListImportDialog:
|
||||
srvs.append(srv)
|
||||
|
||||
self.appender(srvs)
|
||||
self.show_info_message(translate("Done!"), Gtk.MessageType.INFO)
|
||||
self.show_info_message("Done!", Gtk.MessageType.INFO)
|
||||
|
||||
@run_idle
|
||||
def update_active_elements(self, sensitive):
|
||||
@@ -1104,9 +1313,7 @@ class YtListImportDialog:
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(True)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._message_label.set_text(text)
|
||||
show_info_bar_message(self._info_bar, self._message_label, text, message_type)
|
||||
|
||||
def on_selected_toggled(self, toggle, path):
|
||||
self._model.set_value(self._model.get_iter(path), 2, not toggle.get_active())
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.2
|
||||
<!-- Generated with glade 3.38.2
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -36,41 +36,77 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkFrame" id="log_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0.49000000953674316</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0.49000000953674316</property>
|
||||
<property name="shadow-type">none</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<object class="GtkViewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="header_box">
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</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="GtkButton" id="clear_button">
|
||||
<object class="GtkBox" id="header_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Clear</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="clicked" handler="on_clear" swapped="no"/>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="clear_button_image">
|
||||
<object class="GtkButton" id="clear_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-clear</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Clear</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="clicked" handler="on_clear" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="clear_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="stock">gtk-clear</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="close_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Close</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_close" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="close_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="stock">gtk-close</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -80,65 +116,47 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="close_button">
|
||||
<object class="GtkScrolledWindow" id="scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Close</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_close" swapped="no"/>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="close_button_image">
|
||||
<object class="GtkTextView" id="log_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-close</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="left-margin">5</property>
|
||||
<property name="right-margin">5</property>
|
||||
<property name="top-margin">5</property>
|
||||
<property name="bottom-margin">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="log_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="left_margin">5</property>
|
||||
<property name="right_margin">5</property>
|
||||
<property name="top_margin">5</property>
|
||||
<property name="bottom_margin">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="log_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-bottom">2</property>
|
||||
<property name="label" translatable="yes">Logs</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
783
app/ui/m3u.glade
Normal file
783
app/ui/m3u.glade
Normal file
@@ -0,0 +1,783 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.40.0
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellite list editor. -->
|
||||
<!-- interface-copyright 2018-2024 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkBox" id="export_m3u_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkViewport" id="export_viewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<!-- n-columns=2 n-rows=3 -->
|
||||
<object class="GtkGrid" id="export_m3u_grid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="row-spacing">5</property>
|
||||
<property name="column-spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="export_port_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Port:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="export_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Export:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="export_types_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="export_all_button">
|
||||
<property name="label" translatable="yes">All types</property>
|
||||
<property name="name">header-button</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="export_iptv_button">
|
||||
<property name="label" translatable="yes">IPTV only</property>
|
||||
<property name="name">header-button</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
<property name="group">export_all_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="export_grp_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="export_grp_bq_button">
|
||||
<property name="label" translatable="yes">Bouquets</property>
|
||||
<property name="name">header-button</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="export_grp_markers_button">
|
||||
<property name="label" translatable="yes">Markers</property>
|
||||
<property name="name">header-button</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
<property name="group">export_grp_bq_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="export_grp_no_button">
|
||||
<property name="label" translatable="yes">No</property>
|
||||
<property name="name">header-button</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
<property name="group">export_grp_markers_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="export_port_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive" bind-source="export_iptv_button" bind-property="active" bind-flags="invert-boolean">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="export_port_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive" bind-source="export_auto_button" bind-property="active" bind-flags="invert-boolean">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="width-chars">10</property>
|
||||
<property name="primary-icon-name">document-edit-symbolic</property>
|
||||
<property name="placeholder-text" translatable="yes">8001</property>
|
||||
<property name="input-purpose">digits</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="export_auto_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">emblem-synchronizing-symbolic</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Auto</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="export_group_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Group by</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="export_status_box">
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="export_info_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
<property name="icon_size">2</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="export_bq_info_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="export_bq_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Bouquets</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="export_bq_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="label">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="export_service_info_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="spacing">1</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="export_services_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Services</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="export_services_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="label">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkBox" id="import_m3u_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkFrame" id="import_m3u_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="label-xalign">0.02</property>
|
||||
<property name="shadow-type">none</property>
|
||||
<child>
|
||||
<object class="GtkViewport" id="import_viewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="import_main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="import_type_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="current_bq_button">
|
||||
<property name="label" translatable="yes">Current bouquet</property>
|
||||
<property name="name">header-button</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
<property name="group">sub_bq_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="single_bq_button">
|
||||
<property name="label" translatable="yes">Single bouquet</property>
|
||||
<property name="name">header-button</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
<property name="group">current_bq_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="group_bq_button">
|
||||
<property name="label" translatable="yes">Split by groups</property>
|
||||
<property name="name">header-button</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
<property name="group">sub_bq_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="sub_bq_button">
|
||||
<property name="label" translatable="yes">Create sub-bouquets</property>
|
||||
<property name="name">header-button</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">False</property>
|
||||
<property name="group">current_bq_button</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">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="load_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">15</property>
|
||||
<child type="center">
|
||||
<object class="GtkProgressBar" id="progress_bar">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="info_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="info_label">
|
||||
<property name="visible" bind-source="spinner" bind-property="visible" bind-flags="invert-boolean">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="max-width-chars">30</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="load_label">
|
||||
<property name="visible" bind-source="spinner" bind-property="visible">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Loading data...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="spinner">
|
||||
<property name="visible" bind-source="spinner" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="picon_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="picon_switch_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Download picons</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="picon_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</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">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="epg_src_box">
|
||||
<property name="height-request">30</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="epg_source_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">EPG source</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="epg_links_box">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="active">0</property>
|
||||
<property name="has-entry">True</property>
|
||||
<child internal-child="entry">
|
||||
<object class="GtkEntry">
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="epg_info_label">
|
||||
<property name="visible" bind-source="epg_links_box" bind-property="visible" bind-flags="invert-boolean">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="label" translatable="yes">Not found.</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="add_epg_src_box">
|
||||
<property name="visible" bind-source="epg_links_box" bind-property="visible">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="add_epg_src_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Add to EPG sources list</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="add_epg_src_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</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">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="import_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-bottom">2</property>
|
||||
<property name="label" translatable="yes">Import</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
6368
app/ui/main.glade
6368
app/ui/main.glade
File diff suppressed because it is too large
Load Diff
533
app/ui/main.py
533
app/ui/main.py
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -33,7 +33,8 @@ __all__ = ("insert_marker", "move_items", "rename", "ViewTarget", "set_flags", "
|
||||
"is_only_one_item_selected", "gen_bouquets", "BqGenType", "get_selection", "get_service_reference",
|
||||
"get_model_data", "remove_all_unused_picons", "get_picon_pixbuf", "get_base_itrs", "get_iptv_url",
|
||||
"get_iptv_data", "update_entry_data", "append_text_to_tview", "on_popup_menu", "get_picon_file_name",
|
||||
"update_toggle_model", "update_popup_filter_model", "update_filter_sat_positions", "get_pos_num")
|
||||
"update_toggle_model", "update_popup_filter_model", "update_filter_sat_positions", "get_pos_num",
|
||||
"show_info_bar_message", "gen_bouquet_name")
|
||||
|
||||
import os
|
||||
import re
|
||||
@@ -48,7 +49,7 @@ from gi.repository import GdkPixbuf, GLib, Gio
|
||||
|
||||
from app.eparser import Service
|
||||
from app.eparser.ecommons import Flag, BouquetService, Bouquet, BqType
|
||||
from app.eparser.enigma.bouquets import BqServiceType, to_bouquet_id
|
||||
from app.eparser.enigma.bouquets import BqServiceType
|
||||
from app.settings import SettingsType, SEP, IS_WIN, IS_DARWIN, IS_LINUX
|
||||
from .dialogs import show_dialog, DialogType, translate
|
||||
from .uicommons import ViewTarget, BqGenType, Gtk, Gdk, HIDE_ICON, LOCKED_ICON, KeyboardKey, Column
|
||||
@@ -293,7 +294,7 @@ def set_lock(blacklist, services, model, paths, target, services_model):
|
||||
fav_id = model.get_value(itr, Column.SRV_FAV_ID if target is ViewTarget.SERVICES else Column.FAV_ID)
|
||||
srv = services.get(fav_id, None)
|
||||
if srv and srv.service_type not in skip_type:
|
||||
bq_id = srv.data_id if srv.service_type == BqServiceType.IPTV.name else to_bouquet_id(srv)
|
||||
bq_id = srv.data_id if srv.service_type == BqServiceType.IPTV.name else srv.fav_id
|
||||
if not bq_id:
|
||||
continue
|
||||
blacklist.discard(bq_id) if locked else blacklist.add(bq_id)
|
||||
@@ -685,6 +686,19 @@ def get_bouquets_names(model):
|
||||
return bouquets_names
|
||||
|
||||
|
||||
def gen_bouquet_name(bouquets, base_name, bq_type):
|
||||
""" Generates a name for new bouquets. """
|
||||
count = 0
|
||||
key = f"{base_name}:{bq_type}"
|
||||
bq_name = base_name
|
||||
while key in bouquets:
|
||||
count += 1
|
||||
bq_name = f"{base_name}{count}"
|
||||
key = f"{bq_name}:{bq_type}"
|
||||
|
||||
return bq_name
|
||||
|
||||
|
||||
def get_services_type_groups(services):
|
||||
""" Returns services grouped by main types [TV, Radio, Data]. -> dict """
|
||||
|
||||
@@ -819,13 +833,16 @@ def append_text_to_tview(char, view):
|
||||
|
||||
|
||||
def get_iptv_url(row, s_type, column=Column.FAV_ID):
|
||||
""" Returns url from iptv type row """
|
||||
""" Returns URL from IPTV type row. """
|
||||
data = row[column].split(":" if s_type is SettingsType.ENIGMA_2 else "::")
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
data = list(filter(lambda x: "http" in x, data))
|
||||
|
||||
if data:
|
||||
url = data[0]
|
||||
return unquote(url) if s_type is SettingsType.ENIGMA_2 else url
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
if len(data) > 10 and "http" in data[10]:
|
||||
url, sep, desc = data[10].partition("#DESCRIPTION")
|
||||
return unquote(url.strip())
|
||||
else:
|
||||
return data[0]
|
||||
|
||||
|
||||
def get_iptv_data(fav_id):
|
||||
@@ -838,10 +855,27 @@ def get_iptv_data(fav_id):
|
||||
|
||||
|
||||
def on_popup_menu(menu, event):
|
||||
""" Shows popup menu for the view """
|
||||
""" Shows popup menu for the view. """
|
||||
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_SECONDARY:
|
||||
menu.popup(None, None, None, None, event.button, event.time)
|
||||
|
||||
|
||||
def show_info_bar_message(bar, label, text, message_type=Gtk.MessageType.INFO):
|
||||
""" Shows a message for info bars. """
|
||||
bar.set_visible(False)
|
||||
label.set_text(translate(text))
|
||||
bar.set_message_type(message_type)
|
||||
bar.set_visible(True)
|
||||
|
||||
|
||||
def redraw_image(area, cr, pixbuf):
|
||||
""" Helper method to redraw (auto resize) image in the Gtk DrawingArea. """
|
||||
cr.scale(area.get_allocated_width() / pixbuf.get_width(),
|
||||
area.get_allocated_height() / pixbuf.get_height())
|
||||
img_surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, 1, None)
|
||||
cr.set_source_surface(img_surface, 0, 0)
|
||||
cr.paint()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
2786
app/ui/picons.glade
2786
app/ui/picons.glade
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -30,6 +30,7 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
from enum import Enum
|
||||
from html import escape
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse, unquote
|
||||
|
||||
@@ -56,6 +57,7 @@ class PiconManager(Gtk.Box):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._app = app
|
||||
self._app.connect("data-open", self.on_open)
|
||||
self._app.connect("data-receive", self.on_download)
|
||||
self._app.connect("data-send", self.on_send)
|
||||
self._app.connect("page-changed", self.update_picons_dest)
|
||||
@@ -192,6 +194,8 @@ class PiconManager(Gtk.Box):
|
||||
column.set_cell_data_func(builder.get_object("picons_dest_renderer"), self.picon_data_func)
|
||||
column = builder.get_object("src_picon_column")
|
||||
column.set_cell_data_func(builder.get_object("picons_src_renderer"), self.picon_data_func)
|
||||
column = builder.get_object("dest_title_column")
|
||||
column.set_cell_data_func(builder.get_object("title_dest_renderer"), self.title_data_func)
|
||||
# Settings
|
||||
self._settings = settings
|
||||
self._s_type = settings.setting_type
|
||||
@@ -228,8 +232,11 @@ class PiconManager(Gtk.Box):
|
||||
if is_explorer:
|
||||
self.update_picons_data(self._picons_dest_view)
|
||||
|
||||
def on_open(self):
|
||||
def on_open(self, app, page):
|
||||
""" Opens picons from local path [in src view]. """
|
||||
if page is not Page.PICONS:
|
||||
return
|
||||
|
||||
response = show_dialog(DialogType.CHOOSER, self._app.app_window, settings=self._settings, title="Open folder")
|
||||
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
return
|
||||
@@ -293,6 +300,17 @@ class PiconManager(Gtk.Box):
|
||||
def picon_data_func(self, column, renderer, model, itr, data):
|
||||
renderer.set_property("pixbuf", get_pixbuf_at_scale(model.get_value(itr, 2), 72, 48, True))
|
||||
|
||||
def title_data_func(self, column, renderer, model, itr, data):
|
||||
srv = self._services.get(model[itr][1], None)
|
||||
if srv:
|
||||
renderer.set_property("markup", self.get_picon_info_markup(srv))
|
||||
|
||||
def get_picon_info_markup(self, srv):
|
||||
ext_info = "" if srv.service_type == "IPTV" else f" {srv.pos} {srv.freq}"
|
||||
return (f'{escape(srv.picon_id)}\n\n'
|
||||
f'<span size="small" weight="bold">{translate("Service")}: {escape(srv.service)}</span>\n'
|
||||
f'<span size="small" style="italic">{srv.service_type}{ext_info}</span>')
|
||||
|
||||
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())
|
||||
@@ -1021,12 +1039,7 @@ class PiconManager(Gtk.Box):
|
||||
show_dialog(dialog_type, self._app_window, message)
|
||||
|
||||
def get_picons_format(self):
|
||||
picon_format = SettingsType.ENIGMA_2
|
||||
|
||||
if self._neutrino_mp_radio_button.get_active():
|
||||
picon_format = SettingsType.NEUTRINO_MP
|
||||
|
||||
return picon_format
|
||||
return SettingsType.NEUTRINO_MP if self._neutrino_mp_radio_button.get_active() else SettingsType.ENIGMA_2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -158,7 +158,14 @@ class PlayerBox(Gtk.Overlay):
|
||||
return
|
||||
|
||||
ref = self._app.get_service_ref_data(srv)
|
||||
self.zap(ref, self.play_current)
|
||||
if mode is PlaybackMode.PLAY:
|
||||
self.play_service(ref)
|
||||
elif mode is PlaybackMode.ZAP:
|
||||
self.zap(ref)
|
||||
elif mode is PlaybackMode.ZAP_PLAY:
|
||||
self.zap(ref, self.play_current)
|
||||
elif mode is PlaybackMode.STREAM:
|
||||
self._app.show_error_message("Not allowed in this context!")
|
||||
|
||||
def on_iptv_clicked(self, app, mode):
|
||||
if not self._app.http_api:
|
||||
@@ -179,8 +186,8 @@ class PlayerBox(Gtk.Overlay):
|
||||
|
||||
def on_page_changed(self, app, page):
|
||||
self._page = page
|
||||
if self._player:
|
||||
self.update_buttons()
|
||||
if self._player and self.is_visible():
|
||||
self.update_buttons() if not IS_DARWIN else None
|
||||
self.on_close()
|
||||
self.set_visible(False)
|
||||
|
||||
@@ -251,8 +258,7 @@ class PlayerBox(Gtk.Overlay):
|
||||
self.emit("pause", None)
|
||||
|
||||
def on_stop(self, action=None, value=None):
|
||||
if not IS_DARWIN:
|
||||
self._stop_button.set_visible(False)
|
||||
self._stop_button.set_visible(False) if not IS_DARWIN else None
|
||||
self.emit("stop", None)
|
||||
|
||||
def on_next(self, button):
|
||||
@@ -264,7 +270,7 @@ class PlayerBox(Gtk.Overlay):
|
||||
def switch_service(self, count):
|
||||
self._fav_view.grab_focus()
|
||||
if self._fav_view.do_move_cursor(self._fav_view, Gtk.MovementStep.DISPLAY_LINES, count):
|
||||
self.update_buttons()
|
||||
self.update_buttons() if not IS_DARWIN else None
|
||||
self.set_player_action()
|
||||
|
||||
def on_rewind(self, scale, scroll_type, value):
|
||||
@@ -343,9 +349,10 @@ class PlayerBox(Gtk.Overlay):
|
||||
def update_buttons(self):
|
||||
if self._player:
|
||||
path, column = self._fav_view.get_cursor()
|
||||
current_index = path[0]
|
||||
self._prev_button.set_sensitive(current_index != 0)
|
||||
self._next_button.set_sensitive(len(self._fav_view.get_model()) != current_index + 1)
|
||||
if path:
|
||||
current_index = path[0]
|
||||
self._prev_button.set_sensitive(current_index != 0)
|
||||
self._next_button.set_sensitive(len(self._fav_view.get_model()) != current_index + 1)
|
||||
|
||||
self._prev_button.set_visible(self._page is Page.SERVICES)
|
||||
self._next_button.set_visible(self._page is Page.SERVICES)
|
||||
@@ -439,15 +446,17 @@ class PlayerBox(Gtk.Overlay):
|
||||
self.play(url) if url else self.on_error(None, "No reference is present!")
|
||||
|
||||
def on_play_service(self, item=None):
|
||||
""" Playback without switching channel on the Box [returns current reference]"""
|
||||
""" Playback without switching channel on the Box."""
|
||||
ref, path = self.get_ref()
|
||||
if not ref:
|
||||
return
|
||||
|
||||
self.play_service(ref)
|
||||
|
||||
def play_service(self, ref):
|
||||
s_type = self._app.app_settings.setting_type
|
||||
req = HttpAPI.Request.STREAM if s_type is SettingsType.ENIGMA_2 else HttpAPI.Request.N_STREAM
|
||||
self._app.http_api.send(req, ref, self.watch)
|
||||
return ref
|
||||
|
||||
def on_zap(self, callback=None):
|
||||
""" Switch(zap) the channel. """
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.2
|
||||
<!-- Generated with glade 3.38.2
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -36,30 +36,30 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkMenu" id="popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="play_menu_item">
|
||||
<property name="label">gtk-media-play</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="use-stock">True</property>
|
||||
<signal name="activate" handler="on_play" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="menu_separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="remove_menu_item">
|
||||
<property name="label">gtk-remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="use-stock">True</property>
|
||||
<signal name="activate" handler="on_recording_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
@@ -96,7 +96,7 @@ Author: Dmitriy Yefremov
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkTreeModelFilter" id="recordings_filter_model">
|
||||
<property name="child_model">recordings_model</property>
|
||||
<property name="child-model">recordings_model</property>
|
||||
</object>
|
||||
<object class="GtkTreeModelSort" id="recordings_sort_model">
|
||||
<property name="model">recordings_filter_model</property>
|
||||
@@ -105,121 +105,55 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
<object class="GtkBox" id="recordings_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkPaned" id="recordings_paned">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="wide_handle">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="wide-handle">True</property>
|
||||
<child>
|
||||
<object class="GtkFrame" id="recordings_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0.49000000953674316</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0.49000000953674316</property>
|
||||
<property name="shadow-type">none</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="recordings_main_box">
|
||||
<object class="GtkViewport" id="recordings_viewport">
|
||||
<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">2</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="recordings_header_box">
|
||||
<object class="GtkBox" id="recordings_main_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>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="recordings_filter_button">
|
||||
<object class="GtkBox" id="recordings_header_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Filter</property>
|
||||
<signal name="toggled" handler="on_recordings_filter_toggled" swapped="no"/>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="recordings_filter_button_image">
|
||||
<object class="GtkToggleButton" id="recordings_filter_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-find-replace-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="recordings_remove_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Remove</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_recording_remove" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="remove_recording_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">user-trash-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="recordings_fs_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="recordings_filter_entry">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_name">edit-find-replace-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
<property name="visible" bind-source="recordings_filter_button" bind-property="active"/>
|
||||
<signal name="search-changed" handler="on_recordings_filter_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="recordings_search_box">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="recordings_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>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Filter</property>
|
||||
<signal name="toggled" handler="on_recordings_filter_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="recordings_filter_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-find-replace-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -228,227 +162,27 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="recordings_search_down_button">
|
||||
<object class="GtkButton" id="recordings_remove_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Remove</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_recording_remove" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkArrow" id="recordings_down_arrow">
|
||||
<object class="GtkImage" id="remove_recording_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="arrow_type">down</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">user-trash-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="recordings_search_up_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<child>
|
||||
<object class="GtkArrow" id="recordings_up_arrow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="arrow_type">up</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="group"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="recordings_view_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="recordings_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">recordings_sort_model</property>
|
||||
<property name="fixed_height_mode">True</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="enable_grid_lines">both</property>
|
||||
<property name="tooltip_column">6</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="popup_menu" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_recordings_key_press" swapped="no"/>
|
||||
<signal name="row-activated" handler="on_recordings_activated" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="recordings_view_selection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_service_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min_width">150</property>
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort_column_id">1</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="rec_log_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="ypad">2</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="pixbuf">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_service_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_title_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min_width">150</property>
|
||||
<property name="title" translatable="yes">Title</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort_column_id">2</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_title_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_time_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed_width">180</property>
|
||||
<property name="min_width">100</property>
|
||||
<property name="title" translatable="yes">Time</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort_column_id">3</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_time_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_len_column">
|
||||
<property name="min_width">100</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="title" translatable="yes">Length</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort_column_id">4</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_len_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_file_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min_width">100</property>
|
||||
<property name="title" translatable="yes">File</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort_column_id">5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_file_renderer">
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">5</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_desc_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="title" translatable="yes">Description</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort_column_id">6</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_desc_renderer">
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">6</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="recordings_status_box">
|
||||
<property name="height_request">26</property>
|
||||
<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="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="recordings_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -457,10 +191,96 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="recordings_count_label">
|
||||
<object class="GtkBox" id="recordings_fs_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label">0</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="recordings_filter_entry">
|
||||
<property name="visible" bind-source="recordings_filter_button" bind-property="active">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="primary-icon-name">edit-find-replace-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="primary-icon-sensitive">False</property>
|
||||
<signal name="search-changed" handler="on_recordings_filter_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="recordings_search_box">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="recordings_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>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="recordings_search_down_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkArrow" id="recordings_down_arrow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="arrow-type">down</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="recordings_search_up_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkArrow" id="recordings_up_arrow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="arrow-type">up</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="group"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -469,22 +289,210 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
<object class="GtkScrolledWindow" id="recordings_view_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="recordings_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">recordings_sort_model</property>
|
||||
<property name="fixed-height-mode">True</property>
|
||||
<property name="rubber-banding">True</property>
|
||||
<property name="enable-grid-lines">both</property>
|
||||
<property name="tooltip-column">6</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="popup_menu" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_recordings_key_press" swapped="no"/>
|
||||
<signal name="row-activated" handler="on_recordings_activated" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_service_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">150</property>
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort-column-id">1</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="rec_log_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="ypad">2</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="pixbuf">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_service_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_title_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">150</property>
|
||||
<property name="title" translatable="yes">Title</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort-column-id">2</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_title_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_time_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed-width">180</property>
|
||||
<property name="min-width">100</property>
|
||||
<property name="title" translatable="yes">Time</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort-column-id">3</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_time_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_len_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">100</property>
|
||||
<property name="title" translatable="yes">Length</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort-column-id">4</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_len_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_file_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">100</property>
|
||||
<property name="title" translatable="yes">File</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort-column-id">5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_file_renderer">
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">5</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_desc_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="title" translatable="yes">Description</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort-column-id">6</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_desc_renderer">
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">6</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="recordings_status_box">
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="recordings_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="recordings_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="recordings_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-bottom">2</property>
|
||||
<property name="label" translatable="yes">Recordings</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -496,72 +504,81 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkFrame" id="recordings_paths_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0.49000000953674316</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0.49000000953674316</property>
|
||||
<property name="shadow-type">none</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="paths_view_scrolled_window">
|
||||
<property name="width_request">250</property>
|
||||
<object class="GtkViewport" id="paths_viewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="min_content_height">100</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="recordings_paths_view">
|
||||
<object class="GtkScrolledWindow" id="paths_view_scrolled_window">
|
||||
<property name="width-request">250</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">rec_paths_model</property>
|
||||
<property name="headers_visible">False</property>
|
||||
<property name="search_column">1</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<signal name="button-press-event" handler="on_path_press" swapped="no"/>
|
||||
<signal name="row-activated" handler="on_path_activated" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="rec_paths_selection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="min-content-height">100</property>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_paths_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">100</property>
|
||||
<property name="title" translatable="yes">Paths</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort_column_id">1</property>
|
||||
<object class="GtkTreeView" id="recordings_paths_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">rec_paths_model</property>
|
||||
<property name="headers-visible">False</property>
|
||||
<property name="search-column">1</property>
|
||||
<property name="rubber-banding">True</property>
|
||||
<property name="activate-on-single-click">True</property>
|
||||
<signal name="button-press-event" handler="on_path_press" swapped="no"/>
|
||||
<signal name="row-activated" handler="on_path_activated" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="ftp_icon_column_renderer">
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
<object class="GtkTreeViewColumn" id="rec_paths_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min-width">100</property>
|
||||
<property name="title" translatable="yes">Paths</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="sort-column-id">1</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="ftp_icon_column_renderer">
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="pixbuf">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="ftp_name_column_renderer">
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="pixbuf">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="ftp_name_column_renderer">
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="recordings_path_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-bottom">2</property>
|
||||
<property name="label" translatable="yes">Paths</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -46,15 +46,16 @@ class RecordingsTool(Gtk.Box):
|
||||
ROOT = ".."
|
||||
DEFAULT_PATH = "/hdd"
|
||||
|
||||
def __init__(self, app, settings, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
def __init__(self, app, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._app = app
|
||||
self._app.connect("layout-changed", self.on_layout_changed)
|
||||
self._app.connect("data-receive", self.on_data_receive)
|
||||
self._app.connect("profile-changed", self.init)
|
||||
self._app.connect("filter-toggled", self.on_filter_toggled)
|
||||
|
||||
self._settings = settings
|
||||
self._settings = app.app_settings
|
||||
self._ftp = None
|
||||
self._logos = {}
|
||||
# Icon.
|
||||
@@ -82,6 +83,7 @@ class RecordingsTool(Gtk.Box):
|
||||
self._filter_model = builder.get_object("recordings_filter_model")
|
||||
self._filter_model.set_visible_func(self.recordings_filter_function)
|
||||
self._filter_entry = builder.get_object("recordings_filter_entry")
|
||||
self._recordings_filter_button = builder.get_object("recordings_filter_button")
|
||||
self._recordings_count_label = builder.get_object("recordings_count_label")
|
||||
self.pack_start(builder.get_object("recordings_box"), True, True, 0)
|
||||
self._rec_view.get_model().set_sort_func(3, self.time_sort_func, 3)
|
||||
@@ -92,7 +94,7 @@ class RecordingsTool(Gtk.Box):
|
||||
renderer.set_fixed_size(size, size * 0.65)
|
||||
srv_column.set_cell_data_func(renderer, self.logo_data_func)
|
||||
|
||||
if settings.alternate_layout:
|
||||
if self._settings.alternate_layout:
|
||||
self.on_layout_changed(app, True)
|
||||
|
||||
self.init()
|
||||
@@ -293,9 +295,12 @@ class RecordingsTool(Gtk.Box):
|
||||
txt = self._filter_entry.get_text().upper()
|
||||
return next((s for s in model.get(itr, 1, 2, 3, 5, 6) if s and txt in s.upper()), False)
|
||||
|
||||
def on_filter_toggled(self, app, value):
|
||||
if self._app.page is Page.RECORDINGS:
|
||||
self._recordings_filter_button.set_active(not self._recordings_filter_button.get_active())
|
||||
|
||||
def on_recordings_filter_toggled(self, button):
|
||||
if not button.get_active():
|
||||
self._filter_entry.set_text("")
|
||||
self._filter_entry.grab_focus() if button.get_active() else self._filter_entry.set_text("")
|
||||
|
||||
def on_recordings_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -47,7 +47,7 @@ _UI_PATH = UI_RESOURCES_PATH + "service_details_dialog.glade"
|
||||
class ServiceDetailsDialog:
|
||||
_ENIGMA2_DATA_ID = "{:04x}:{:08x}:{:04x}:{:04x}:{}:{}"
|
||||
|
||||
_ENIGMA2_FAV_ID = "{:X}:{:X}:{:X}:{:X}"
|
||||
_ENIGMA2_FAV_ID = "1:0:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0"
|
||||
|
||||
_ENIGMA2_TRANSPONDER_DATA = "{} {}:{}:{}:{}:{}:{}:{}"
|
||||
|
||||
@@ -97,7 +97,7 @@ class ServiceDetailsDialog:
|
||||
self._DIGIT_PATTERN = re.compile("\\D")
|
||||
self._NON_EMPTY_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
|
||||
self._CAID_PATTERN = re.compile("(?:^[\\s]*$)|(C:[0-9a-fA-F]{1,4})(,C:[0-9a-fA-F]{1,4})*")
|
||||
self._PIDS_PATTERN = re.compile("(?:^[\\s]*$)|(c:[0-9]{2}[0-9a-fA-F]{4})(,c:[0-9]{2}[0-9a-fA-F]{4})*")
|
||||
self._PIDS_PATTERN = re.compile("(?:^[\\s]*$)|(c:[0-9]{2}[0-9a-fA-F]{1,4})(,c:[0-9]{2}[0-9a-fA-F]{1,4})*?")
|
||||
# Buttons
|
||||
self._apply_button = builder.get_object("apply_button")
|
||||
self._create_button = builder.get_object("create_button")
|
||||
@@ -140,7 +140,7 @@ class ServiceDetailsDialog:
|
||||
self._srv_type_entry = self._non_empty_elements.get("srv_type_entry")
|
||||
self._service_type_combo_box = builder.get_object("service_type_combo_box")
|
||||
self._cas_entry = builder.get_object("cas_entry")
|
||||
self._reference_entry = builder.get_object("reference_entry")
|
||||
self._reference_label = builder.get_object("reference_label")
|
||||
self._keep_check_button = builder.get_object("keep_check_button")
|
||||
self._hide_check_button = builder.get_object("hide_check_button")
|
||||
self._use_pids_check_button = builder.get_object("use_pids_check_button")
|
||||
@@ -159,7 +159,6 @@ class ServiceDetailsDialog:
|
||||
self._pilot_combo_box = builder.get_object("pilot_combo_box")
|
||||
self._pls_mode_combo_box = builder.get_object("pls_mode_combo_box")
|
||||
self._tr_edit_switch = builder.get_object("tr_edit_switch")
|
||||
self._tr_extra_expander = builder.get_object("tr_extra_expander")
|
||||
|
||||
self._DVB_S2_ELEMENTS = (self._mod_combo_box, self._rolloff_combo_box, self._pilot_combo_box,
|
||||
self._pls_mode_combo_box, self._pls_code_entry, self._stream_id_entry)
|
||||
@@ -186,7 +185,6 @@ class ServiceDetailsDialog:
|
||||
elem.set_text(" ")
|
||||
elem.set_text("")
|
||||
self._new_check_button.set_active(True)
|
||||
self._tr_extra_expander.activate()
|
||||
self._service_type_combo_box.set_active(0)
|
||||
self._pol_combo_box.set_active(0)
|
||||
self._fec_combo_box.set_active(0)
|
||||
@@ -366,8 +364,7 @@ class ServiceDetailsDialog:
|
||||
tr_grid = self._builder.get_object("tr_grid")
|
||||
tr_grid.remove_column(7)
|
||||
tr_grid.set_margin_bottom(5)
|
||||
self._builder.get_object("tr_extra_expander").set_visible(False)
|
||||
self._builder.get_object("srv_separator").set_visible(False)
|
||||
self._builder.get_object("extra_transponder_grid").set_visible(False)
|
||||
self._package_entry.set_sensitive(False)
|
||||
|
||||
# ***************** Init Sat positions *********************#
|
||||
@@ -527,7 +524,7 @@ class ServiceDetailsDialog:
|
||||
package=self._package_entry.get_text(),
|
||||
service_type=SERVICE_TYPE.get(self._srv_type_entry.get_text(), SERVICE_TYPE["3"]),
|
||||
picon=self._old_service.picon,
|
||||
picon_id=self._reference_entry.get_text().replace(":", "_") + ".png",
|
||||
picon_id=self._reference_label.get_text().replace(":", "_") + ".png",
|
||||
ssid="{:04x}".format(int(self._sid_entry.get_text())),
|
||||
freq=freq,
|
||||
rate=rate,
|
||||
@@ -596,7 +593,7 @@ class ServiceDetailsDialog:
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
namespace = int(self._namespace_entry.get_text())
|
||||
data_id = self._ENIGMA2_DATA_ID.format(ssid, namespace, tr_id, net_id, service_type, 0)
|
||||
fav_id = self._ENIGMA2_FAV_ID.format(ssid, tr_id, net_id, namespace)
|
||||
fav_id = f"{self._reference_label.get_text()}:"
|
||||
return fav_id, data_id
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
data = get_attributes(self._old_service.data_id)
|
||||
@@ -618,7 +615,7 @@ class ServiceDetailsDialog:
|
||||
freq = self._freq_entry.get_text()
|
||||
rate = self._rate_entry.get_text()
|
||||
pol = self._pol_combo_box.get_active_id()
|
||||
pos = "{}{}".format(round(self._sat_pos_button.get_value(), 1), self._pos_side_box.get_active_id())
|
||||
pos = f"{round(self._sat_pos_button.get_value(), 1)}{self._pos_side_box.get_active_id()}"
|
||||
return freq, rate, pol, fec, system, pos
|
||||
elif self._tr_type in (TrType.Terrestrial, TrType.ATSC):
|
||||
return freq, o_srv.rate, o_srv.pol, fec, system, o_srv.pos
|
||||
@@ -627,8 +624,8 @@ class ServiceDetailsDialog:
|
||||
|
||||
def get_satellite_transponder_data(self):
|
||||
sys = self._sys_combo_box.get_active_id()
|
||||
freq = "{}000".format(self._freq_entry.get_text())
|
||||
rate = "{}000".format(self._rate_entry.get_text())
|
||||
freq = f"{self._freq_entry.get_text()}000"
|
||||
rate = f"{self._rate_entry.get_text()}000"
|
||||
pol = self.get_value_from_combobox_id(self._pol_combo_box, POLARIZATION)
|
||||
fec = self.get_value_from_combobox_id(self._fec_combo_box, FEC_DEFAULT)
|
||||
sat_pos = self.get_sat_position()
|
||||
@@ -648,9 +645,10 @@ class ServiceDetailsDialog:
|
||||
pls_mode = self.get_value_from_combobox_id(self._pls_mode_combo_box, PLS_MODE)
|
||||
pls_code = self._pls_code_entry.get_text()
|
||||
st_id = self._stream_id_entry.get_text()
|
||||
pls = ":{}:{}:{}".format(st_id, pls_code, pls_mode) if pls_mode and pls_code and st_id else ""
|
||||
pls = f":{st_id}:{pls_code}:{pls_mode}" if pls_mode and pls_code and st_id else ""
|
||||
|
||||
return f"{dvb_s_tr}:{flag}:{mod}:{roll_off}:{pilot}{pls}"
|
||||
|
||||
return "{}:{}:{}:{}:{}{}".format(dvb_s_tr, flag, mod, roll_off, pilot, pls)
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
tr_data = get_attributes(self._old_service.transponder)
|
||||
tr_data["frq"] = freq
|
||||
@@ -661,7 +659,7 @@ class ServiceDetailsDialog:
|
||||
tr_data["id"] = "{:04x}".format(int(self._transponder_id_entry.get_text()))
|
||||
tr_data["inv"] = inv
|
||||
|
||||
return SP.join("{}{}{}".format(k, KSP, v) for k, v in tr_data.items())
|
||||
return SP.join(f"{k}{KSP}{v}" for k, v in tr_data.items())
|
||||
|
||||
def get_sat_position(self):
|
||||
sat_pos = self._sat_pos_button.get_value() * (-1 if self._pos_side_box.get_active_id() == "W" else 1)
|
||||
@@ -669,11 +667,11 @@ class ServiceDetailsDialog:
|
||||
return sat_pos
|
||||
|
||||
def get_terrestrial_transponder_data(self):
|
||||
tr_data = re.split("\s|:", self._old_service.transponder)
|
||||
tr_data = re.split(r"\s|:", self._old_service.transponder)
|
||||
# frequency, bandwidth, code rate HP, code rate LP, modulation, transmission mode, guard interval, hierarchy,
|
||||
# inversion, system, plp_id
|
||||
# Bandwidth -> Pol, Rate HP -> FEC, TransmissionMode -> Roll off, GuardInterval -> Pilot, Hierarchy -> Pls Mode
|
||||
tr_data[1] = "{}000".format(self._freq_entry.get_text())
|
||||
tr_data[1] = f"{self._freq_entry.get_text()}000"
|
||||
tr_data[2] = self.get_value_from_combobox_id(self._pol_combo_box, BANDWIDTH)
|
||||
tr_data[3] = self.get_value_from_combobox_id(self._fec_combo_box, T_FEC)
|
||||
tr_data[4] = self.get_value_from_combobox_id(self._rate_lp_combo_box, T_FEC)
|
||||
@@ -684,28 +682,28 @@ class ServiceDetailsDialog:
|
||||
tr_data[9] = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
|
||||
tr_data[10] = self.get_value_from_combobox_id(self._sys_combo_box, T_SYSTEM)
|
||||
|
||||
return "{} {}".format(tr_data[0], ":".join(tr_data[1:]))
|
||||
return f"{tr_data[0]} {':'.join(tr_data[1:])}"
|
||||
|
||||
def get_cable_transponder_data(self):
|
||||
tr_data = re.split("\s|:", self._old_service.transponder)
|
||||
tr_data = re.split(r"\s|:", self._old_service.transponder)
|
||||
# frequency, symbol_rate, modulation, inversion, fec_inner, system;
|
||||
tr_data[1] = "{}000".format(self._freq_entry.get_text())
|
||||
tr_data[2] = "{}000".format(self._rate_entry.get_text())
|
||||
tr_data[1] = f"{self._freq_entry.get_text()}000"
|
||||
tr_data[2] = f"{self._rate_entry.get_text()}000"
|
||||
tr_data[3] = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
|
||||
tr_data[4] = self.get_value_from_combobox_id(self._mod_combo_box, C_MODULATION)
|
||||
tr_data[5] = self.get_value_from_combobox_id(self._fec_combo_box, FEC_DEFAULT)
|
||||
tr_data[6] = get_value_by_name(SystemCable, self._sys_combo_box.get_active_id())
|
||||
|
||||
return "{} {}".format(tr_data[0], ":".join(tr_data[1:]))
|
||||
return f"{tr_data[0]} {':'.join(tr_data[1:])}"
|
||||
|
||||
def get_atsc_transponder_data(self):
|
||||
tr_data = re.split("\s|:", self._old_service.transponder)
|
||||
tr_data = re.split(r"\s|:", self._old_service.transponder)
|
||||
# frequency, inversion, modulation, system
|
||||
tr_data[1] = "{}000".format(self._freq_entry.get_text())
|
||||
tr_data[1] = f"{self._freq_entry.get_text()}000"
|
||||
tr_data[2] = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
|
||||
tr_data[3] = self.get_value_from_combobox_id(self._mod_combo_box, A_MODULATION)
|
||||
|
||||
return "{} {}".format(tr_data[0], ":".join(tr_data[1:]))
|
||||
return f"{tr_data[0]} {':'.join(tr_data[1:])}"
|
||||
|
||||
def update_transponder_services(self, transponder, sat_pos):
|
||||
for itr in self._transponder_services_iters:
|
||||
@@ -717,13 +715,13 @@ class ServiceDetailsDialog:
|
||||
fav_id = srv[Column.SRV_FAV_ID]
|
||||
old_srv = self._services.pop(fav_id, None)
|
||||
if not old_srv:
|
||||
log("Update transponder services error: No service found for ID {}".format(srv[Column.SRV_FAV_ID]))
|
||||
log(f"Update transponder services error: No service found for ID {srv[Column.SRV_FAV_ID]}")
|
||||
continue
|
||||
|
||||
if self._s_type is SettingsType.NEUTRINO_MP:
|
||||
flags = get_attributes(srv[Column.SRV_CAS_FLAGS])
|
||||
flags["position"] = sat_pos
|
||||
srv[Column.SRV_CAS_FLAGS] = SP.join("{}{}{}".format(k, KSP, v) for k, v in flags.items())
|
||||
srv[Column.SRV_CAS_FLAGS] = SP.join(f"{k}{KSP}{v}" for k, v in flags.items())
|
||||
|
||||
self._services[fav_id] = Service(*srv[:Column.SRV_TOOLTIP])
|
||||
self._current_model.set_row(itr, srv)
|
||||
@@ -797,10 +795,9 @@ class ServiceDetailsDialog:
|
||||
nid = int(self._network_id_entry.get_text())
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
on_id = int(self._namespace_entry.get_text())
|
||||
ref = "1:0:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0".format(srv_type, ssid, tid, nid, on_id)
|
||||
self._reference_entry.set_text(ref)
|
||||
self._reference_label.set_text(self._ENIGMA2_FAV_ID.format(srv_type, ssid, tid, nid, on_id))
|
||||
else:
|
||||
self._reference_entry.set_text("{:x}{:04x}{:04x}".format(tid, nid, ssid))
|
||||
self._reference_label.set_text(f"{tid:x}{nid:04x}{ssid:04x}")
|
||||
|
||||
def update_ui_for_terrestrial(self):
|
||||
tr_grid = self.get_transponder_grid_for_non_satellite()
|
||||
@@ -891,7 +888,6 @@ class ServiceDetailsDialog:
|
||||
# FEC
|
||||
fec_model.append(("None",))
|
||||
# Extra
|
||||
tr_box.remove(self._tr_extra_expander)
|
||||
tr_grid.set_margin_bottom(5)
|
||||
self._freq_entry.set_width_chars(10)
|
||||
self._freq_entry.set_max_width_chars(10)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -34,7 +34,7 @@ from app.commons import run_task, run_idle, log
|
||||
from app.connections import test_telnet, test_ftp, TestException, test_http, HttpApiException
|
||||
from app.settings import SettingsType, Settings, PlayStreamsMode, PlaybackMode, IS_LINUX, SEP, IS_WIN
|
||||
from app.ui.dialogs import show_dialog, DialogType, translate, get_chooser_dialog, get_builder
|
||||
from .main_helper import update_entry_data, scroll_to, get_picon_pixbuf
|
||||
from .main_helper import update_entry_data, scroll_to, get_picon_pixbuf, show_info_bar_message
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, DEFAULT_ICON, APP_FONT, HeaderBar
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ class SettingsDialog:
|
||||
"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_profile_add": self.on_profile_add,
|
||||
"on_profile_edit": self.on_profile_edit,
|
||||
@@ -129,13 +128,13 @@ class SettingsDialog:
|
||||
self._backup_path_field = builder.get_object("backup_path_field")
|
||||
self._recordings_path_field = builder.get_object("recordings_path_field")
|
||||
self._default_data_paths_switch = builder.get_object("default_data_paths_switch")
|
||||
self._default_data_paths_switch.bind_property("active", builder.get_object("picons_path_box"), "sensitive", 4)
|
||||
self._default_data_paths_switch.bind_property("active", builder.get_object("backup_path_box"), "sensitive", 4)
|
||||
self._use_common_picon_path_switch = builder.get_object("use_common_picon_path_switch")
|
||||
# Info bar.
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._message_label = builder.get_object("info_bar_message_label")
|
||||
self._test_spinner = builder.get_object("test_spinner")
|
||||
# Settings type.
|
||||
self._settings_type_box = builder.get_object("settings_type_combo_box")
|
||||
self._enigma_radio_button = builder.get_object("enigma_radio_button")
|
||||
self._neutrino_radio_button = builder.get_object("neutrino_radio_button")
|
||||
# Streaming.
|
||||
@@ -187,18 +186,10 @@ class SettingsDialog:
|
||||
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._enable_exp_switch = builder.get_object("enable_experimental_switch")
|
||||
# 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("program_frame"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("experimental_box"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("allow_double_click_box"), "sensitive")
|
||||
# Profiles.
|
||||
self._profile_view = builder.get_object("profile_tree_view")
|
||||
self._profile_add_button = builder.get_object("profile_add_button")
|
||||
self._profile_remove_button = builder.get_object("profile_remove_button")
|
||||
# Network.
|
||||
# Separated due to a bug with response (presumably in the builder) in ubuntu 18.04 and derivatives.
|
||||
builder.get_object("network_settings_frame").add(builder.get_object("network_grid"))
|
||||
# Style.
|
||||
style_provider = Gtk.CssProvider()
|
||||
style_provider.load_from_path(f"{UI_RESOURCES_PATH}style.css")
|
||||
@@ -223,29 +214,27 @@ class SettingsDialog:
|
||||
|
||||
if not IS_LINUX:
|
||||
# Themes.
|
||||
builder.get_object("style_frame").set_visible(IS_WIN)
|
||||
builder.get_object("themes_support_frame").set_visible(True)
|
||||
self._layout_switch = builder.get_object("layout_switch")
|
||||
self._layout_switch.set_active(self._ext_settings.alternate_layout)
|
||||
self._theme_frame = builder.get_object("theme_frame")
|
||||
self._theme_frame.set_visible(True)
|
||||
builder.get_object("dark_mode_box").set_visible(IS_WIN)
|
||||
builder.get_object("style_box_view").set_visible(True)
|
||||
self._theme_view = builder.get_object("theme_view")
|
||||
self._theme_view.set_visible(True)
|
||||
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._dark_mode_switch.set_active(self._ext_settings.dark_mode)
|
||||
self._themes_support_switch = builder.get_object("themes_support_switch")
|
||||
self._themes_support_switch.bind_property("active", self._theme_frame, "sensitive")
|
||||
self._themes_support_switch.bind_property("active", self._theme_view, "sensitive")
|
||||
self.init_themes()
|
||||
|
||||
def init_ui_elements(self):
|
||||
is_enigma_profile = self._s_type is SettingsType.ENIGMA_2
|
||||
self._neutrino_radio_button.set_active(self._s_type is SettingsType.NEUTRINO_MP)
|
||||
self.update_picon_paths()
|
||||
self.update_title()
|
||||
self._dialog.set_title(f"{translate('Options')} [{self._settings_type_box.get_active_text()}]")
|
||||
self._lang_combo_box.set_active_id(self._ext_settings.language)
|
||||
self.on_info_bar_close() if is_enigma_profile else self.show_info_message(
|
||||
is_enigma = self._s_type is SettingsType.ENIGMA_2
|
||||
self.on_info_bar_close() if is_enigma else self.show_info_message(
|
||||
"The Neutrino has only experimental support. Not all features are supported!", Gtk.MessageType.WARNING)
|
||||
self._epg_dat_box.set_sensitive(is_enigma)
|
||||
|
||||
def init_profiles(self):
|
||||
p_def = self._settings.default_profile
|
||||
@@ -261,13 +250,6 @@ class SettingsDialog:
|
||||
def init_element_style(self, elem, screen, provider):
|
||||
elem.get_style_context().add_provider_for_screen(screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
|
||||
def update_title(self):
|
||||
title = "{} [{}]"
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self._dialog.set_title(title.format(translate("Options"), self._enigma_radio_button.get_label()))
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
self._dialog.set_title(title.format(translate("Options"), self._neutrino_radio_button.get_label()))
|
||||
|
||||
def update_picon_paths(self):
|
||||
model = self._picons_paths_box.get_model()
|
||||
model.clear()
|
||||
@@ -292,7 +274,7 @@ class SettingsDialog:
|
||||
update_entry_data(entry, self._dialog, self._settings)
|
||||
|
||||
def on_settings_type_changed(self, item):
|
||||
s_type = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active() else SettingsType.NEUTRINO_MP
|
||||
s_type = SettingsType(int(self._settings_type_box.get_active_id()))
|
||||
if s_type is not self._s_type:
|
||||
self._settings.setting_type = s_type
|
||||
self._s_type = s_type
|
||||
@@ -334,6 +316,7 @@ class SettingsDialog:
|
||||
self._bouquet_hints_switch.set_active(self._settings.show_bq_hints)
|
||||
self._services_hints_switch.set_active(self._settings.show_srv_hints)
|
||||
self._default_data_paths_switch.set_active(self._settings.profile_folder_is_default)
|
||||
self._use_common_picon_path_switch.set_active(self._settings.use_common_picon_path)
|
||||
self._transcoding_switch.set_active(self._settings.activate_transcoding)
|
||||
self._presets_combo_box.set_active_id(self._settings.active_preset)
|
||||
self.on_transcoding_preset_changed(self._presets_combo_box)
|
||||
@@ -363,17 +346,14 @@ class SettingsDialog:
|
||||
self._new_color_button.set_rgba(new_rgb)
|
||||
self._extra_color_button.set_rgba(extra_rgb)
|
||||
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self._enigma_radio_button.activate()
|
||||
else:
|
||||
self._neutrino_radio_button.activate()
|
||||
self._settings_type_box.set_active_id(str(self._s_type.value))
|
||||
|
||||
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
|
||||
|
||||
self._s_type = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active() else SettingsType.NEUTRINO_MP
|
||||
self._s_type = SettingsType(int(self._settings_type_box.get_active_id()))
|
||||
self._settings.setting_type = self._s_type
|
||||
self._settings.host = self._host_field.get_text()
|
||||
self._settings.hosts = [h[1] for h in self._hosts_box.get_model()]
|
||||
@@ -406,6 +386,7 @@ class SettingsDialog:
|
||||
self._ext_settings.show_bq_hints = self._bouquet_hints_switch.get_active()
|
||||
self._ext_settings.show_srv_hints = self._services_hints_switch.get_active()
|
||||
self._ext_settings.profile_folder_is_default = self._default_data_paths_switch.get_active()
|
||||
self._ext_settings.use_common_picon_path = self._use_common_picon_path_switch.get_active()
|
||||
self._ext_settings.default_data_path = self._data_path_field.get_text()
|
||||
self._ext_settings.default_backup_path = self._backup_path_field.get_text()
|
||||
self._ext_settings.default_picon_path = self._picons_path_field.get_text()
|
||||
@@ -419,7 +400,6 @@ class SettingsDialog:
|
||||
|
||||
if not IS_LINUX:
|
||||
self._ext_settings.dark_mode = self._dark_mode_switch.get_active()
|
||||
self._ext_settings.alternate_layout = self._layout_switch.get_active()
|
||||
self._ext_settings.is_themes_support = self._themes_support_switch.get_active()
|
||||
self._ext_settings.theme = self._theme_combo_box.get_active_id()
|
||||
self._ext_settings.icon_theme = self._icon_theme_combo_box.get_active_id()
|
||||
@@ -495,10 +475,7 @@ class SettingsDialog:
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(False)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._message_label.set_text(translate(text))
|
||||
self._info_bar.set_visible(True)
|
||||
show_info_bar_message(self._info_bar, self._message_label, text, message_type)
|
||||
|
||||
@run_idle
|
||||
def show_spinner(self, show):
|
||||
@@ -532,11 +509,8 @@ class SettingsDialog:
|
||||
else:
|
||||
self.on_info_bar_close()
|
||||
|
||||
def on_yt_dl_switch(self, switch, state):
|
||||
self.show_info_message("Not implemented yet!", Gtk.MessageType.WARNING)
|
||||
|
||||
def on_default_path_mode_switch(self, switch, state):
|
||||
self._settings.profile_folder_is_default = state
|
||||
self._use_common_picon_path_switch.set_active(False) if state else None
|
||||
|
||||
def on_profile_add(self, item):
|
||||
model = self._profile_view.get_model()
|
||||
@@ -799,7 +773,7 @@ class SettingsDialog:
|
||||
response = get_chooser_dialog(self._dialog, self._settings, "Themes Archive [*.xz, *.zip]", ("*.xz", "*.zip"))
|
||||
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
return
|
||||
self._theme_frame.set_sensitive(False)
|
||||
self._theme_view.set_sensitive(False)
|
||||
self.unpack_theme(response, path, button)
|
||||
|
||||
@run_task
|
||||
@@ -829,7 +803,7 @@ class SettingsDialog:
|
||||
button.append(theme, theme)
|
||||
button.set_active_id(theme)
|
||||
self.show_info_message("Done!", Gtk.MessageType.INFO)
|
||||
self._theme_frame.set_sensitive(True)
|
||||
self._theme_view.set_sensitive(True)
|
||||
|
||||
@run_idle
|
||||
def remove_theme(self, button, path):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.2
|
||||
<!-- Generated with glade 3.38.2
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -41,43 +41,98 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkTextBuffer" id="text_buffer">
|
||||
<property name="tag_table">tag_table</property>
|
||||
<property name="tag-table">tag_table</property>
|
||||
</object>
|
||||
<object class="GtkFrame" id="telnet_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0.49000000953674316</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0.49000000953674316</property>
|
||||
<property name="shadow-type">none</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="telnet_main_box">
|
||||
<object class="GtkViewport" id="viewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="commands_entry">
|
||||
<object class="GtkBox" id="telnet_main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="connect_button">
|
||||
<object class="GtkBox" id="commands_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Connect</property>
|
||||
<signal name="clicked" handler="on_connect" swapped="no"/>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="connect_button_image">
|
||||
<object class="GtkButton" id="connect_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-connect</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Connect</property>
|
||||
<signal name="clicked" handler="on_connect" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="connect_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="stock">gtk-connect</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="disconnect_button">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Disconnect</property>
|
||||
<signal name="clicked" handler="on_disconnect" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="disconnect_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="stock">gtk-disconnect</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="clear_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Clear</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="clicked" handler="on_clear" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="clear_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="stock">gtk-clear</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -87,90 +142,49 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="disconnect_button">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Disconnect</property>
|
||||
<signal name="clicked" handler="on_disconnect" swapped="no"/>
|
||||
<object class="GtkScrolledWindow" id="telnet_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="disconnect_button_image">
|
||||
<object class="GtkTextView" id="text_view">
|
||||
<property name="name">textview-large</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-disconnect</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="wrap-mode">char</property>
|
||||
<property name="left-margin">5</property>
|
||||
<property name="right-margin">5</property>
|
||||
<property name="buffer">text_buffer</property>
|
||||
<property name="overwrite">True</property>
|
||||
<property name="input-hints">GTK_INPUT_HINT_WORD_COMPLETION | GTK_INPUT_HINT_NONE</property>
|
||||
<property name="monospace">True</property>
|
||||
<signal name="key-press-event" handler="on_view_key_press" swapped="no"/>
|
||||
<signal name="realize" handler="on_text_view_realize" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="clear_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Clear</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="clicked" handler="on_clear" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="clear_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-clear</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</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="GtkScrolledWindow" id="telnet_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view">
|
||||
<property name="name">textview-large</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="wrap_mode">char</property>
|
||||
<property name="left_margin">5</property>
|
||||
<property name="right_margin">5</property>
|
||||
<property name="buffer">text_buffer</property>
|
||||
<property name="overwrite">True</property>
|
||||
<property name="input_hints">GTK_INPUT_HINT_WORD_COMPLETION | GTK_INPUT_HINT_NONE</property>
|
||||
<property name="monospace">True</property>
|
||||
<signal name="key-press-event" handler="on_view_key_press" swapped="no"/>
|
||||
<signal name="realize" handler="on_text_view_realize" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-bottom">2</property>
|
||||
<property name="label" translatable="yes">Telnet</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
2073
app/ui/timers.glade
2073
app/ui/timers.glade
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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,9 +31,8 @@ from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
from urllib.parse import quote
|
||||
|
||||
from app.settings import USE_HEADER_BAR
|
||||
from app.ui.main_helper import on_popup_menu
|
||||
from .dialogs import get_builder, translate, show_dialog, DialogType
|
||||
from .dialogs import get_builder, translate, show_dialog, DialogType, BaseDialog
|
||||
from .uicommons import Gtk, Gdk, GLib, UI_RESOURCES_PATH, Page, Column, KeyboardKey, MOD_MASK
|
||||
from ..commons import run_idle, log
|
||||
from ..connections import HttpAPI
|
||||
@@ -55,9 +54,11 @@ class TimerTool(Gtk.Box):
|
||||
EVENT = 1
|
||||
CHANGE = 2
|
||||
|
||||
class TimerDialog(Gtk.Dialog):
|
||||
class TimerDialog(BaseDialog):
|
||||
def __init__(self, parent, action=None, timer_data=None, *args, **kwargs):
|
||||
super().__init__(use_header_bar=USE_HEADER_BAR, *args, **kwargs)
|
||||
super().__init__(parent=parent, title="Timer",
|
||||
buttons=(translate("Cancel"), Gtk.ResponseType.CANCEL,
|
||||
translate("Save"), Gtk.ResponseType.OK), *args, **kwargs)
|
||||
|
||||
self._action = action or TimerTool.TimerAction.ADD
|
||||
self._timer_data = timer_data or {}
|
||||
@@ -71,14 +72,6 @@ class TimerTool(Gtk.Box):
|
||||
"min_end_adjustment", "timer_begins_popover", "begins_hour_adjustment",
|
||||
"min_begins_adjustment"))
|
||||
|
||||
self.set_title(translate("Timer"))
|
||||
self.set_modal(True)
|
||||
self.set_skip_pager_hint(True)
|
||||
self.set_skip_taskbar_hint(True)
|
||||
self.set_transient_for(parent)
|
||||
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
|
||||
self.set_resizable(False)
|
||||
|
||||
self._timer_name_entry = builder.get_object("timer_name_entry")
|
||||
self._timer_desc_entry = builder.get_object("timer_desc_entry")
|
||||
self._timer_service_entry = builder.get_object("timer_service_entry")
|
||||
@@ -111,8 +104,7 @@ class TimerTool(Gtk.Box):
|
||||
self._timer_desc_entry.drag_dest_unset()
|
||||
self._timer_service_entry.drag_dest_unset()
|
||||
|
||||
self.add_buttons(translate("Cancel"), Gtk.ResponseType.CANCEL, translate("Save"), Gtk.ResponseType.OK)
|
||||
self.get_content_area().pack_start(builder.get_object("timer_dialog_frame"), True, True, 5)
|
||||
self.get_content_area().pack_start(builder.get_object("timer_dialog_frame"), True, True, 0)
|
||||
|
||||
if self._action is TimerTool.TimerAction.ADD:
|
||||
self.set_timer_for_add()
|
||||
@@ -236,15 +228,15 @@ class TimerTool(Gtk.Box):
|
||||
TimerTool.set_repetition_flags(int(self._timer_data.get("e2repeated", "0")), self._days_buttons)
|
||||
|
||||
def set_timer_from_event_data(self):
|
||||
self._timer_name_entry.set_text(self._timer_data.get("e2eventtitle", ""))
|
||||
self._timer_desc_entry.set_text(self._timer_data.get("e2eventdescription", ""))
|
||||
self._timer_service_entry.set_text(self._timer_data.get("e2eventservicename", ""))
|
||||
self._timer_service_ref_entry.set_text(self._timer_data.get("e2eventservicereference", ""))
|
||||
self._timer_event_id_entry.set_text(self._timer_data.get("e2eventid", ""))
|
||||
self._timer_name_entry.set_text(self._timer_data.get("e2eventtitle", None) or "")
|
||||
self._timer_desc_entry.set_text(self._timer_data.get("e2eventdescription", None) or "")
|
||||
self._timer_service_entry.set_text(self._timer_data.get("e2eventservicename", None) or "")
|
||||
self._timer_service_ref_entry.set_text(self._timer_data.get("e2eventservicereference", None) or "")
|
||||
self._timer_event_id_entry.set_text(self._timer_data.get("e2eventid", None) or "")
|
||||
self._timer_action_combo_box.set_active_id("1")
|
||||
self._timer_after_combo_box.set_active_id("3")
|
||||
start_time = int(self._timer_data.get("e2eventstart", "0"))
|
||||
self.set_time_data(start_time, start_time + int(self._timer_data.get("e2eventduration", "0")))
|
||||
start_time = int(self._timer_data.get("e2eventstart", "0") or "0")
|
||||
self.set_time_data(start_time, start_time + int(self._timer_data.get("e2eventduration", "0") or "0"))
|
||||
|
||||
def set_time_data(self, start_time, end_time):
|
||||
""" Sets values for time widgets. """
|
||||
@@ -292,7 +284,7 @@ class TimerTool(Gtk.Box):
|
||||
"on_timer_remove": self.on_timer_remove,
|
||||
"on_model_changed": self.on_model_changed,
|
||||
"on_timers_press": self.on_timers_press,
|
||||
"on_timers_key_press": self.on_timers_key_press,
|
||||
"on_timers_key_release": self.on_timers_key_release,
|
||||
"on_timer_cursor_changed": self.on_timer_cursor_changed,
|
||||
"on_timers_drag_data_received": self.on_timers_drag_data_received}
|
||||
|
||||
@@ -363,9 +355,11 @@ class TimerTool(Gtk.Box):
|
||||
|
||||
if p_count == 1:
|
||||
service = self._app.current_services.get(model[paths][Column.FAV_ID], None)
|
||||
if service:
|
||||
if service and service.picon_id:
|
||||
self.add_timer({"e2servicename": service.service,
|
||||
"e2servicereference": service.picon_id.rstrip(".png").replace("_", ":")})
|
||||
else:
|
||||
self._app.show_error_message("Not allowed in this context!")
|
||||
elif p_count > 1:
|
||||
self._app.show_error_message("Please, select only one item!")
|
||||
else:
|
||||
@@ -465,7 +459,7 @@ class TimerTool(Gtk.Box):
|
||||
else:
|
||||
on_popup_menu(menu, event)
|
||||
|
||||
def on_timers_key_press(self, view, event):
|
||||
def on_timers_key_release(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
@@ -475,10 +469,10 @@ class TimerTool(Gtk.Box):
|
||||
|
||||
if key is KeyboardKey.DELETE:
|
||||
self.on_timer_remove()
|
||||
elif key is KeyboardKey.INSERT:
|
||||
self.on_timer_add()
|
||||
elif ctrl and key is KeyboardKey.E:
|
||||
self.on_timer_edit()
|
||||
elif ctrl and key is KeyboardKey.INSERT:
|
||||
self.on_timer_add()
|
||||
|
||||
def on_timer_cursor_changed(self, view):
|
||||
path, column = view.get_cursor()
|
||||
|
||||
@@ -119,8 +119,10 @@ LOCKED_ICON = get_icon("changes-prevent-symbolic", 16, _IMAGE_MISSING)
|
||||
HIDE_ICON = get_icon("go-jump", 16, _IMAGE_MISSING)
|
||||
TV_ICON = get_icon("tv-symbolic", 16, _IMAGE_MISSING)
|
||||
IPTV_ICON = get_icon("emblem-shared", 16, _IMAGE_MISSING)
|
||||
LINK_ICON = get_icon("emblem-symbolic-link", 16, _IMAGE_MISSING)
|
||||
FOLDER_ICON = get_icon("folder-symbolic" if IS_DARWIN else "folder", 16, _IMAGE_MISSING)
|
||||
EPG_ICON = get_icon("gtk-index", 16, _IMAGE_MISSING)
|
||||
DEFAULT_ICON = get_icon("emblem-default", 16, _IMAGE_MISSING)
|
||||
DEFAULT_ICON = get_icon("emblem-default", 16, get_icon("emblem-default-symbolic", 16, _IMAGE_MISSING))
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
|
||||
@@ -22,3 +22,7 @@ grid > button {
|
||||
popover .view {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
headerbar .titlebutton > image {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -43,9 +43,9 @@ from app.eparser.ecommons import (PLS_MODE, get_key_by_value, POLARIZATION, FEC,
|
||||
HIERARCHY, Inversion, C_MODULATION, FEC_DEFAULT, TerTransponder, CableTransponder,
|
||||
Bouquet, BouquetService, BqServiceType, Bouquets, BqType)
|
||||
from app.eparser.satxml import get_pos_str
|
||||
from app.settings import USE_HEADER_BAR, Settings, CONFIG_PATH
|
||||
from app.settings import Settings, CONFIG_PATH
|
||||
from app.tools.satellites import SatellitesParser, SatelliteSource, ServicesParser
|
||||
from ..dialogs import show_dialog, DialogType, translate, get_builder
|
||||
from ..dialogs import show_dialog, BaseDialog, DialogType, translate, get_builder
|
||||
from ..main_helper import append_text_to_tview, get_base_model, on_popup_menu, get_services_type_groups
|
||||
from ..search import SearchProvider
|
||||
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HeaderBar
|
||||
@@ -53,25 +53,19 @@ from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HeaderBar
|
||||
_DIALOGS_UI_PATH = f"{UI_RESOURCES_PATH}xml{os.sep}dialogs.glade"
|
||||
|
||||
|
||||
class DVBDialog(Gtk.Dialog):
|
||||
class DVBDialog(BaseDialog):
|
||||
""" Base dialog class for editing DVB (-> *.xml) data. """
|
||||
|
||||
def __init__(self, parent, title, data=None, *args, **kwargs):
|
||||
super().__init__(transient_for=parent,
|
||||
title=translate(title),
|
||||
modal=True,
|
||||
resizable=False,
|
||||
default_width=320,
|
||||
skip_taskbar_hint=True,
|
||||
skip_pager_hint=True,
|
||||
destroy_with_parent=True,
|
||||
use_header_bar=USE_HEADER_BAR,
|
||||
window_position=Gtk.WindowPosition.CENTER_ON_PARENT,
|
||||
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK),
|
||||
*args, **kwargs)
|
||||
super().__init__(parent=parent, title=title, *args, **kwargs)
|
||||
|
||||
self.frame = Gtk.Frame(margin=5, label_xalign=0.02)
|
||||
self.get_content_area().pack_start(self.frame, True, True, 0)
|
||||
self._viewport = Gtk.Viewport(margin_top=2)
|
||||
self._viewport.get_style_context().add_class("view")
|
||||
self._frame = Gtk.Frame(margin=5, label_xalign=0.02, shadow_type=Gtk.ShadowType.NONE)
|
||||
self._label = Gtk.Label(margin_bottom=2, use_markup=True)
|
||||
self._frame.set_label_widget(self._label)
|
||||
self._frame.add(self._viewport)
|
||||
self.get_content_area().pack_start(self._frame, True, True, 0)
|
||||
|
||||
self._data = data
|
||||
|
||||
@@ -79,13 +73,19 @@ class DVBDialog(Gtk.Dialog):
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
def set_content(self, widget):
|
||||
self._viewport.add(widget)
|
||||
|
||||
def set_label_text(self, text):
|
||||
self._label.set_markup(f"<b>{text}</b>")
|
||||
|
||||
|
||||
class TransponderDialog(DVBDialog):
|
||||
""" Base transponder dialog class. """
|
||||
|
||||
def __init__(self, parent, title, data=None, *args, **kwargs):
|
||||
super().__init__(parent, title, data, *args, **kwargs)
|
||||
self.frame.set_label(translate("Transponder properties:"))
|
||||
self.set_label_text(translate("Transponder properties:"))
|
||||
# Pattern for digits entries.
|
||||
self.digit_pattern = re.compile(r"\D")
|
||||
# Style
|
||||
@@ -124,8 +124,8 @@ class TCDialog(DVBDialog):
|
||||
super().__init__(parent, title, data, *args, **kwargs)
|
||||
|
||||
self._entry = Gtk.Entry(margin=5)
|
||||
self.frame.add(self._entry)
|
||||
self.frame.set_label(translate("Name:"))
|
||||
self.set_content(self._entry)
|
||||
self.set_label_text(translate("Name:"))
|
||||
self.show_all()
|
||||
|
||||
if data:
|
||||
@@ -140,8 +140,8 @@ class SatelliteDialog(DVBDialog):
|
||||
builder = get_builder(_DIALOGS_UI_PATH, use_str=True,
|
||||
objects=("sat_dialog_box", "side_store", "pos_adjustment"))
|
||||
|
||||
self.frame.add(builder.get_object("sat_dialog_box"))
|
||||
self.frame.set_label(translate("Satellite properties:"))
|
||||
self.set_content(builder.get_object("sat_dialog_box"))
|
||||
self.set_label_text(translate("Satellite properties:"))
|
||||
self._sat_name = builder.get_object("sat_name_entry")
|
||||
self._sat_position = builder.get_object("sat_position_button")
|
||||
self._side = builder.get_object("side_box")
|
||||
@@ -196,7 +196,7 @@ class SatTransponderDialog(TransponderDialog):
|
||||
objects = ("sat_tr_box", "pol_store", "fec_store", "mod_store", "system_store", "pls_mode_store")
|
||||
builder = get_builder(_DIALOGS_UI_PATH, handlers, use_str=True, objects=objects)
|
||||
|
||||
self.frame.add(builder.get_object("sat_tr_box"))
|
||||
self.set_content(builder.get_object("sat_tr_box"))
|
||||
self._freq_entry = builder.get_object("freq_entry")
|
||||
self._rate_entry = builder.get_object("rate_entry")
|
||||
self._pol_box = builder.get_object("pol_box")
|
||||
@@ -268,7 +268,7 @@ class TerTransponderDialog(TransponderDialog):
|
||||
handlers = {"on_entry_changed": self.on_entry_changed}
|
||||
builder = get_builder(_DIALOGS_UI_PATH, handlers, use_str=True, objects=("ter_tr_box",))
|
||||
|
||||
self.frame.add(builder.get_object("ter_tr_box"))
|
||||
self.set_content(builder.get_object("ter_tr_box"))
|
||||
self._freq_entry = builder.get_object("ter_freq_entry")
|
||||
self._sys_box = builder.get_object("ter_sys_box")
|
||||
self._bandwidth_box = builder.get_object("ter_bandwidth_box")
|
||||
@@ -346,7 +346,7 @@ class CableTransponderDialog(TransponderDialog):
|
||||
handlers = {"on_entry_changed": self.on_entry_changed}
|
||||
builder = get_builder(_DIALOGS_UI_PATH, handlers, use_str=True, objects=("cable_tr_box",))
|
||||
|
||||
self.frame.add(builder.get_object("cable_tr_box"))
|
||||
self.set_content(builder.get_object("cable_tr_box"))
|
||||
|
||||
self._freq_entry = builder.get_object("cable_freq_entry")
|
||||
self._rate_entry = builder.get_object("cable_rate_entry")
|
||||
@@ -401,8 +401,6 @@ class UpdateDialog:
|
||||
"on_satellite_changed": self.on_satellite_changed,
|
||||
"on_transponder_toggled": self.on_transponder_toggled,
|
||||
"on_info_bar_close": self.on_info_bar_close,
|
||||
"on_filter_toggled": self.on_filter_toggled,
|
||||
"on_find_toggled": self.on_find_toggled,
|
||||
"on_popup_menu": on_popup_menu,
|
||||
"on_select_all": self.on_select_all,
|
||||
"on_unselect_all": self.on_unselect_all,
|
||||
@@ -441,7 +439,7 @@ class UpdateDialog:
|
||||
self._left_action_box = builder.get_object("sat_update_left_action_box")
|
||||
self._right_action_box = builder.get_object("sat_update_right_action_box")
|
||||
# Filter
|
||||
self._filter_bar = builder.get_object("sat_update_filter_bar")
|
||||
self._filter_bar_box = builder.get_object("filter_bar_box")
|
||||
self._from_pos_button = builder.get_object("from_pos_button")
|
||||
self._to_pos_button = builder.get_object("to_pos_button")
|
||||
self._filter_from_combo_box = builder.get_object("filter_from_combo_box")
|
||||
@@ -449,18 +447,16 @@ class UpdateDialog:
|
||||
self._filter_model = builder.get_object("update_sat_list_model_filter")
|
||||
self._filter_model.set_visible_func(self.filter_function)
|
||||
self._filter_positions = (0, 0)
|
||||
self._filter_bar.bind_property("search-mode-enabled", self._filter_bar, "visible")
|
||||
# Log.
|
||||
self._log_frame = builder.get_object("log_frame")
|
||||
builder.get_object("log_info_bar").connect("response", lambda b, r: self._log_frame.set_visible(False))
|
||||
# Search.
|
||||
self._search_bar = builder.get_object("sat_update_search_bar")
|
||||
self._search_bar.bind_property("search-mode-enabled", self._search_bar, "visible")
|
||||
self._search_bar_box = builder.get_object("search_bar_box")
|
||||
search_provider = SearchProvider(self._sat_view,
|
||||
builder.get_object("sat_update_search_entry"),
|
||||
builder.get_object("sat_update_search_down_button"),
|
||||
builder.get_object("sat_update_search_up_button"))
|
||||
builder.get_object("sat_update_find_button").connect("toggled", search_provider.on_search_toggled)
|
||||
builder.get_object("search_button").connect("toggled", search_provider.on_search_toggled)
|
||||
# Satellite lists init on dialog start.
|
||||
self._sat_view.connect("realize", self.on_update_satellites_list)
|
||||
# Options.
|
||||
@@ -545,6 +541,8 @@ class UpdateDialog:
|
||||
@run_idle
|
||||
def append_satellites(self, sats):
|
||||
model = get_base_model(self._sat_view.get_model())
|
||||
if not model:
|
||||
return
|
||||
|
||||
for sat in sats:
|
||||
itr = model.append(sat)
|
||||
@@ -593,20 +591,14 @@ class UpdateDialog:
|
||||
def update_receive_button_state(self, model):
|
||||
self._receive_button.set_sensitive((any(r[4] for r in model)))
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._sat_update_info_bar.set_visible(True)
|
||||
self._sat_update_info_bar.set_message_type(message_type)
|
||||
self._info_bar_message_label.set_text(text)
|
||||
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._sat_update_info_bar.set_visible(False)
|
||||
|
||||
def on_find_toggled(self, button: Gtk.ToggleToolButton):
|
||||
self._search_bar.set_search_mode(button.get_active())
|
||||
self._search_bar_box.set_visible(button.get_active())
|
||||
|
||||
def on_filter_toggled(self, button: Gtk.ToggleToolButton):
|
||||
self._filter_bar.set_search_mode(button.get_active())
|
||||
self._filter_bar_box.set_visible(button.get_active())
|
||||
|
||||
@run_idle
|
||||
def on_filter(self, item):
|
||||
@@ -690,8 +682,15 @@ class SatellitesUpdateDialog(UpdateDialog):
|
||||
box.pack_start(Gtk.Label(translate("Merge satellites by positions")), False, True, 0)
|
||||
box.pack_end(self._merge_sat_switch, False, True, 0)
|
||||
self._general_options_box.pack_start(box, True, True, 0)
|
||||
self._general_options_box.show_all()
|
||||
|
||||
self._split_band_switch = Gtk.Switch(active=self._dialog_settings.get("split_by_band", False))
|
||||
self._split_band_switch.connect("state-set", lambda b, s: self._dialog_settings.update({"split_by_band": s}))
|
||||
box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL)
|
||||
box.pack_start(Gtk.Label(translate("Split satellites by bands (C/KU)")), False, True, 0)
|
||||
box.pack_end(self._split_band_switch, False, True, 0)
|
||||
self._general_options_box.pack_start(box, True, True, 0)
|
||||
|
||||
self._general_options_box.show_all()
|
||||
self._skip_c_band_switch.get_parent().set_visible(False)
|
||||
|
||||
@run_idle
|
||||
@@ -762,6 +761,28 @@ class SatellitesUpdateDialog(UpdateDialog):
|
||||
else:
|
||||
sats = {s.name: s for s in sats} # key = name, v = satellite
|
||||
|
||||
# Post-processing if band separation is active.
|
||||
if self._split_band_switch.get_active():
|
||||
appender.send(f"Checking and splitting satellites by band...\n")
|
||||
to_remove = []
|
||||
new_sats = {}
|
||||
for name, sat in sats.items():
|
||||
# Checking for C/KU-transponders.
|
||||
c_tr = []
|
||||
ku_tr = []
|
||||
[c_tr.append(t) if int(t.frequency) < 10000000 else ku_tr.append(t) for t in sat.transponders]
|
||||
|
||||
if ku_tr and c_tr:
|
||||
c_sat = Satellite(f"{name} (C)", sat.flags, sat.position, c_tr)
|
||||
ku_sat = Satellite(f"{name} (KU)", sat.flags, sat.position, ku_tr)
|
||||
new_sats[c_sat.name] = c_sat
|
||||
new_sats[ku_sat.name] = ku_sat
|
||||
to_remove.append(name)
|
||||
|
||||
[sats.pop(n) for n in to_remove]
|
||||
sats.update(new_sats)
|
||||
appender.send("-" * _len + "\n")
|
||||
|
||||
for row in self._main_model:
|
||||
pos = row[0]
|
||||
if pos in sats:
|
||||
@@ -978,14 +999,14 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
no_lb = "No Category"
|
||||
|
||||
if self._kos_bq_groups_switch.get_active():
|
||||
self.gen_bouquet_group(tv_services, tv_bouquets, lambda s: s[4] or no_lb)
|
||||
self.gen_bouquet_group(rd_services, radio_bouquets, lambda s: s[4] or no_lb, bq_type=BqType.RADIO.value)
|
||||
self.gen_bouquet_group(tv_services, tv_bouquets, lambda s: s[5] or no_lb)
|
||||
self.gen_bouquet_group(rd_services, radio_bouquets, lambda s: s[5] or no_lb, bq_type=BqType.RADIO.value)
|
||||
|
||||
if self._kos_bq_lang_switch.get_active():
|
||||
lb = "" if no_lb in {b.name for b in tv_bouquets} else "No Region"
|
||||
self.gen_bouquet_group(tv_services, tv_bouquets, lambda s: s[5] or lb)
|
||||
self.gen_bouquet_group(tv_services, tv_bouquets, lambda s: s[4] or lb)
|
||||
lb = "" if no_lb in {b.name for b in radio_bouquets} else "No Region"
|
||||
self.gen_bouquet_group(rd_services, radio_bouquets, lambda s: s[5] or lb, bq_type=BqType.RADIO.value)
|
||||
self.gen_bouquet_group(rd_services, radio_bouquets, lambda s: s[4] or lb, bq_type=BqType.RADIO.value)
|
||||
|
||||
return Bouquets("", BqType.TV.value, tv_bouquets), Bouquets("", BqType.RADIO.value, radio_bouquets)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 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
|
||||
@@ -60,6 +60,7 @@ class SatellitesTool(Gtk.Box):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._app = app
|
||||
self._app.connect("data-open", self.on_open)
|
||||
self._app.connect("data-save", self.on_save)
|
||||
self._app.connect("data-save-as", self.on_save_as)
|
||||
self._app.connect("data-receive", self.on_download)
|
||||
@@ -522,8 +523,10 @@ class SatellitesTool(Gtk.Box):
|
||||
return self._ter_tr_view
|
||||
return self._cable_tr_view
|
||||
|
||||
@run_idle
|
||||
def on_open(self):
|
||||
def on_open(self, app, page):
|
||||
if page is not Page.SATELLITE:
|
||||
return
|
||||
|
||||
xml_file = "satellites.xml"
|
||||
if self._dvb_type is self.DVB.TERRESTRIAL:
|
||||
xml_file = "terrestrial.xml"
|
||||
@@ -554,18 +557,26 @@ class SatellitesTool(Gtk.Box):
|
||||
@run_idle
|
||||
def on_save(self, app, page):
|
||||
if page is Page.SATELLITE and show_dialog(DialogType.QUESTION, self._app.app_window) == Gtk.ResponseType.OK:
|
||||
if self._dvb_type is self.DVB.SAT:
|
||||
write_satellites((Satellite(*r) for r in self._satellite_view.get_model()),
|
||||
f"{self._settings.profile_data_path}satellites.xml")
|
||||
elif self._dvb_type is self.DVB.TERRESTRIAL:
|
||||
write_terrestrial((Terrestrial(*r) for r in self._terrestrial_view.get_model()),
|
||||
f"{self._settings.profile_data_path}terrestrial.xml")
|
||||
else:
|
||||
write_cable((Cable(*r) for r in self._cable_view.get_model()),
|
||||
f"{self._settings.profile_data_path}cables.xml")
|
||||
self.save_data(self._settings.profile_data_path)
|
||||
|
||||
def save_data(self, path):
|
||||
if self._dvb_type is self.DVB.SAT:
|
||||
write_satellites((Satellite(*r) for r in self._satellite_view.get_model()), f"{path}satellites.xml")
|
||||
elif self._dvb_type is self.DVB.TERRESTRIAL:
|
||||
write_terrestrial((Terrestrial(*r) for r in self._terrestrial_view.get_model()), f"{path}terrestrial.xml")
|
||||
else:
|
||||
write_cable((Cable(*r) for r in self._cable_view.get_model()), f"{path}cables.xml")
|
||||
|
||||
def on_save_as(self, app, page):
|
||||
self._app.show_error_message("Not implemented yet!")
|
||||
if page is not Page.SATELLITE:
|
||||
return
|
||||
|
||||
buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK)
|
||||
response = show_dialog(DialogType.CHOOSER, self._app.app_window, settings=self._settings, buttons=buttons)
|
||||
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
return
|
||||
|
||||
self.save_data(response)
|
||||
|
||||
def on_download(self, app, page):
|
||||
if page is Page.SATELLITE:
|
||||
@@ -576,7 +587,7 @@ class SatellitesTool(Gtk.Box):
|
||||
self._app.upload_data(DownloadType.SATELLITES)
|
||||
|
||||
@run_idle
|
||||
def on_update(self, item):
|
||||
def on_update(self, item=None):
|
||||
SatellitesUpdateDialog(self._app.get_active_window(), self._settings, self._satellite_view.get_model()).show()
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,14 @@ The best way to run this program from source is using of [MSYS2](https://www.msy
|
||||
`pacman -S mingw-w64-x86_64-gtk3 mingw-w64-x86_64-python3 mingw-w64-x86_64-python3-gobject mingw-w64-x86_64-python3-pip mingw-w64-x86_64-python3-requests`
|
||||
Optional: `pacman -S mingw-w64-x86_64-python3-pillow`
|
||||
To support streams playback, install the following packages (the list may not be complete):
|
||||
For [MPV](https://mpv.io/) `pacman -S mingw-w64-x86_64-mpv`,
|
||||
For [GStreamer](https://gstreamer.freedesktop.org/) `pacman -S mingw-w64-x86_64-gst-libav mingw-w64-x86_64-gst-plugins-bad mingw-w64-x86_64-gst-plugins-base mingw-w64-x86_64-gst-plugins-good mingw-w64-x86_64-gstreamer`
|
||||
* For [GStreamer](https://gstreamer.freedesktop.org/) `pacman -S mingw-w64-x86_64-gst-libav mingw-w64-x86_64-gst-plugins-bad mingw-w64-x86_64-gst-plugins-base mingw-w64-x86_64-gst-plugins-good mingw-w64-x86_64-gstreamer`
|
||||
* For [MPV](https://mpv.io/) `pacman -S mingw-w64-x86_64-mpv`,
|
||||
To reduce installation size or try the latest changes, we can install the *libmpv* [build](https://github.com/shinchiro/mpv-winbuild-cmake/releases) (**mpv-dev**-x86_64-v3-*.7z) by [shinchiro](https://github.com/shinchiro).
|
||||
* Download and extract 7z archive.
|
||||
* Copy libmpv-2.dll to *C:\msys64\mingw64\bin*
|
||||
* libmpv.dll.a to *C:\msys64\mingw64\lib*
|
||||
and folder *include\mpv to *C:\msys64\mingw64\include* path.
|
||||
|
||||
5. Download and unzip the archive with sources from preferred branch (e.g. [master](https://github.com/DYefremov/DemonEditor/archive/refs/heads/master.zip)) in to folder where MSYS2 is installed. E.g: `c:\msys64\home\username\`
|
||||
6. Run mingw64 shell. Go to the folder where the program was unpacked. E.g: `cd DemonEditor/`
|
||||
And run: `./start.py`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
VER="3.7.0_Alpha"
|
||||
VER="3.11.2_Beta"
|
||||
B_PATH="dist/DemonEditor"
|
||||
DEB_PATH="$B_PATH/usr/share/demoneditor"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: demon-editor
|
||||
Version: 3.7.0-Alpha
|
||||
Version: 3.11.2-Beta
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
@@ -10,7 +10,8 @@ Depends: python3 (>= 3.6),
|
||||
python3-gi-cairo,
|
||||
gir1.2-notify-0.7,
|
||||
p7zip-full
|
||||
Recommends: libmpv1,
|
||||
Recommends: ffmpeg,
|
||||
libmpv1,
|
||||
python3-chardet,
|
||||
libgtksourceview (>= 3.0)
|
||||
Maintainer: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
|
||||
|
||||
@@ -5,7 +5,7 @@ Source: https://github.com/DYefremov/DemonEditor
|
||||
Files: *
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2024 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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/bin/bash
|
||||
python3 /usr/share/demoneditor/start.py $1
|
||||
python3 /usr/share/demoneditor/start.py $@
|
||||
|
||||
@@ -2,14 +2,27 @@
|
||||
Version=1.0
|
||||
Name=DemonEditor
|
||||
GenericName=Enigma2 bouquets editor
|
||||
GenericName[it]=Editor di bouquet per Enigma2
|
||||
GenericName[be]=Рэдактар букетаў Enigma2
|
||||
GenericName[de]=Enigma2 Bouquet-Editor
|
||||
GenericName[es]=Editor de ramos de Enigma2
|
||||
GenericName[it]=Editor di bouquet Enigma2
|
||||
GenericName[nl]=Enigma2 boeket editor
|
||||
GenericName[pl]=Edytor bukietów Enigma2
|
||||
GenericName[pt]=Editor de buquês Enigma2
|
||||
GenericName[ru]=Редактор букетов Enigma2
|
||||
GenericName[tr]=Enigma2 buket düzenleyici
|
||||
GenericName[zh_CN]=Enigma2频道编辑器
|
||||
Comment=Channel and satellite list editor for Enigma2
|
||||
Comment[be]=Рэдактар спісу каналаў і супутнікаў для Enigma2
|
||||
Comment[de]=Kanal- und Satellitenlisten-Editor für Enigma2
|
||||
Comment[es]=Editor de lista de canales y satélites para Enigma2
|
||||
Comment[it]=Editor di elenchi di canali e satelliti per Enigma2
|
||||
Comment[nl]=Kanaal- en satellietlijsteditor voor Enigma2
|
||||
Comment[pl]=Edytor list kanałów i satelitów dla Enigma2
|
||||
Comment[pt]=Editor de lista de canais e satélites para Enigma2
|
||||
Comment[ru]=Редактор списка каналов и спутников для Enigma2
|
||||
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
|
||||
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
|
||||
Comment[it]=Editor di liste canali e satelliti per Enigma2
|
||||
Comment[tr]=Enigma2 için kanal ve uydu listesi editörü
|
||||
Comment[es]=Editor de listas de canales y satélites para Enigma2
|
||||
Comment[tr]=Enigma2 için Kanal ve uydu listesi düzenleyici
|
||||
Comment[zh_CN]=Enigma2频道和卫星列表编辑器
|
||||
Icon=demon-editor
|
||||
Exec=/usr/bin/demon-editor
|
||||
Terminal=false
|
||||
|
||||
@@ -32,7 +32,7 @@ a = Analysis([EXE_NAME],
|
||||
pathex=PATH_EXE,
|
||||
binaries=None,
|
||||
datas=ui_files,
|
||||
hiddenimports=['fileinput', 'uuid'],
|
||||
hiddenimports=['fileinput', 'uuid', 'asyncio'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
hooksconfig={
|
||||
@@ -81,8 +81,8 @@ app = BUNDLE(coll,
|
||||
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
|
||||
'LSApplicationCategoryType': 'public.app-category.utilities',
|
||||
'LSMinimumSystemVersion': '10.13',
|
||||
'CFBundleShortVersionString': f"3.7.0.{BUILD_DATE} Alpha",
|
||||
'NSHumanReadableCopyright': u"Copyright © 2023, Dmitriy Yefremov",
|
||||
'CFBundleShortVersionString': f"3.11.2.{BUILD_DATE} Beta",
|
||||
'NSHumanReadableCopyright': u"Copyright © 2024, Dmitriy Yefremov",
|
||||
'NSRequiresAquaSystemAppearance': 'false',
|
||||
'NSHighResolutionCapable': 'true'
|
||||
})
|
||||
|
||||
@@ -30,7 +30,7 @@ a = Analysis([EXE_NAME],
|
||||
pathex=PATH_EXE,
|
||||
binaries=[],
|
||||
datas=ui_files,
|
||||
hiddenimports=['fileinput', 'uuid', 'ctypes.wintypes'],
|
||||
hiddenimports=['fileinput', 'uuid', 'ctypes.wintypes', 'asyncio'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
hooksconfig={
|
||||
@@ -57,9 +57,11 @@ exe = EXE(pyz,
|
||||
name='DemonEditor',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
contents_directory='.',
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=False, icon='icon.ico')
|
||||
console=False,
|
||||
icon='icon.ico')
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
|
||||
@@ -2,14 +2,27 @@
|
||||
Version=1.0
|
||||
Name=DemonEditor
|
||||
GenericName=Enigma2 bouquets editor
|
||||
GenericName[it]=Editor di bouquet per Enigma2
|
||||
GenericName[be]=Рэдактар букетаў Enigma2
|
||||
GenericName[de]=Enigma2 Bouquet-Editor
|
||||
GenericName[es]=Editor de ramos de Enigma2
|
||||
GenericName[it]=Editor di bouquet Enigma2
|
||||
GenericName[nl]=Enigma2 boeket editor
|
||||
GenericName[pl]=Edytor bukietów Enigma2
|
||||
GenericName[pt]=Editor de buquês Enigma2
|
||||
GenericName[ru]=Редактор букетов Enigma2
|
||||
GenericName[tr]=Enigma2 buket düzenleyici
|
||||
GenericName[zh_CN]=Enigma2频道编辑器
|
||||
Comment=Channel and satellite list editor for Enigma2
|
||||
Comment[be]=Рэдактар спісу каналаў і супутнікаў для Enigma2
|
||||
Comment[de]=Kanal- und Satellitenlisten-Editor für Enigma2
|
||||
Comment[es]=Editor de lista de canales y satélites para Enigma2
|
||||
Comment[it]=Editor di elenchi di canali e satelliti per Enigma2
|
||||
Comment[nl]=Kanaal- en satellietlijsteditor voor Enigma2
|
||||
Comment[pl]=Edytor list kanałów i satelitów dla Enigma2
|
||||
Comment[pt]=Editor de lista de canais e satélites para Enigma2
|
||||
Comment[ru]=Редактор списка каналов и спутников для Enigma2
|
||||
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
|
||||
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
|
||||
Comment[it]=Editor di liste canali e satelliti per Enigma2
|
||||
Comment[tr]=Enigma2 için kanal ve uydu listesi editörü
|
||||
Comment[es]=Editor de listas de canales y satélites para Enigma2
|
||||
Comment[tr]=Enigma2 için Kanal ve uydu listesi düzenleyici
|
||||
Comment[zh_CN]=Enigma2频道和卫星列表编辑器
|
||||
Icon=demon-editor
|
||||
Exec=bash -c 'cd $(dirname %k) && ./start.py'
|
||||
Terminal=false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2024 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
@@ -1358,8 +1358,8 @@ msgstr "Усе букеты"
|
||||
msgid "Playback from the main list"
|
||||
msgstr "Прайграванне з асноўнага спіса"
|
||||
|
||||
msgid "Enables URL parsing using youtube-dl to get direct links to media."
|
||||
msgstr "Улучае аналіз URL-адрасоў з дапамогай youtube-dl для атрымання прамых спасылак на медыя."
|
||||
msgid "Enables URL parsing using yt-dlp to get direct links to media."
|
||||
msgstr "Улучае аналіз URL-адрасоў з дапамогай yt-dlp для атрымання прамых спасылак на медыя."
|
||||
|
||||
msgid "Permissions..."
|
||||
msgstr "Дазволы..."
|
||||
@@ -1497,3 +1497,60 @@ msgstr "Вер."
|
||||
|
||||
msgid "Installed"
|
||||
msgstr "Усталявана"
|
||||
|
||||
msgid "Use common folder for picons"
|
||||
msgstr "Скарыстаць агульную тэчку для піконаў"
|
||||
|
||||
msgid "Activates single folder use for several profiles."
|
||||
msgstr "Актывуе выкарыстанне адзінай тэчкі для некалькіх профіляў."
|
||||
|
||||
msgid "Events"
|
||||
msgstr "Падзеі"
|
||||
|
||||
msgid "Markers"
|
||||
msgstr "Маркеры"
|
||||
|
||||
msgid "IPTV only"
|
||||
msgstr "Толькі IPTV"
|
||||
|
||||
msgid "No"
|
||||
msgstr "Не"
|
||||
|
||||
msgid "Not found."
|
||||
msgstr "Не знойдзена."
|
||||
|
||||
msgid "Current EPG cache contents."
|
||||
msgstr "Змесціва бягучага EPG кэша."
|
||||
|
||||
msgid "Source error!"
|
||||
msgstr "Памылка крыніцы!"
|
||||
|
||||
msgid "The EPG source for the favorites list is not set!"
|
||||
msgstr "Не ўсталявана крыніца EPG для спіса абранага!"
|
||||
|
||||
msgid "Add to EPG sources list"
|
||||
msgstr "Дадаць у спіс крыніц EPG"
|
||||
|
||||
msgid "Current bouquet"
|
||||
msgstr "Абраны букет"
|
||||
|
||||
msgid "Single bouquet"
|
||||
msgstr "Адзінкавы букет"
|
||||
|
||||
msgid "Split by groups"
|
||||
msgstr "Падзел па групах"
|
||||
|
||||
msgid "Create sub-bouquets"
|
||||
msgstr "Стварыць падбукеты"
|
||||
|
||||
msgid "Add image"
|
||||
msgstr "Дадаць выяву"
|
||||
|
||||
msgid "TV Format"
|
||||
msgstr "ТБ-фармат"
|
||||
|
||||
msgid "Use with Streamrelay"
|
||||
msgstr "Скарыстаць Streamrelay"
|
||||
|
||||
msgid "Remove use with Streamrelay"
|
||||
msgstr "Не выкарыстоўваць Streamrelay"
|
||||
|
||||
6
po/build.sh
Normal file → Executable file
6
po/build.sh
Normal file → Executable file
@@ -1,3 +1,7 @@
|
||||
#!/bin/bash
|
||||
#xgettext --keyword=translatable --sort-output -L Glade -o po/demon-editor.po app/ui/main_window.glade
|
||||
#msgfmt demon-editor.po -o demon-editor.mo
|
||||
|
||||
for dir in */;
|
||||
do
|
||||
msgfmt $dir* -o ../app/ui/lang/${dir%/}/LC_MESSAGES/demon-editor.mo
|
||||
done
|
||||
@@ -1,8 +1,8 @@
|
||||
# Copyright (C) 2018-2021 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2024 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
# Charly, 2019.
|
||||
# Dmitriy Yefremov, 2020-2023.
|
||||
# Dmitriy Yefremov, 2020-2024.
|
||||
# Thomas Schmidt, 2021.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -797,7 +797,7 @@ msgid "Apply profile settings"
|
||||
msgstr "Profileinstellungen anwenden"
|
||||
|
||||
msgid "Settings type:"
|
||||
msgstr "Art der Einstellungen:"
|
||||
msgstr "Einstellungstyp:"
|
||||
|
||||
msgid "Set default"
|
||||
msgstr "Standard wiederherstellen"
|
||||
@@ -1372,8 +1372,8 @@ msgstr "Alle Bouquets"
|
||||
msgid "Playback from the main list"
|
||||
msgstr "Wiedergabe aus der Hauptliste"
|
||||
|
||||
msgid "Enables URL parsing using youtube-dl to get direct links to media."
|
||||
msgstr "Aktiviert URL-Parsing mit youtube-dl, um direkte Links zu Medien zu erhalten."
|
||||
msgid "Enables URL parsing using yt-dlp to get direct links to media."
|
||||
msgstr "Aktiviert URL-Parsing mit yt-dlp, um direkte Links zu Medien zu erhalten."
|
||||
|
||||
msgid "Permissions..."
|
||||
msgstr "Berechtigungen..."
|
||||
@@ -1511,3 +1511,60 @@ msgstr "Ver."
|
||||
|
||||
msgid "Installed"
|
||||
msgstr "Installiert"
|
||||
|
||||
msgid "Use common folder for picons"
|
||||
msgstr "Gemeinsamen Ordner für Picons verwenden"
|
||||
|
||||
msgid "Activates single folder use for several profiles."
|
||||
msgstr "Aktiviert die Verwendung eines einzelnen Ordners für mehrere Profile."
|
||||
|
||||
msgid "Events"
|
||||
msgstr "Events"
|
||||
|
||||
msgid "Markers"
|
||||
msgstr "Markers"
|
||||
|
||||
msgid "IPTV only"
|
||||
msgstr "Nur IPTV"
|
||||
|
||||
msgid "No"
|
||||
msgstr "Nein"
|
||||
|
||||
msgid "Not found."
|
||||
msgstr "Nicht gefunden."
|
||||
|
||||
msgid "Current EPG cache contents."
|
||||
msgstr "Aktueller EPG-Cache-Inhalt."
|
||||
|
||||
msgid "Source error!"
|
||||
msgstr "Quellfehler!"
|
||||
|
||||
msgid "The EPG source for the favorites list is not set!"
|
||||
msgstr "Die EPG-Quelle für die Favoritenliste ist nicht eingestellt!"
|
||||
|
||||
msgid "Add to EPG sources list"
|
||||
msgstr "Zur EPG-Quellenliste hinzufügen"
|
||||
|
||||
msgid "Current bouquet"
|
||||
msgstr "Aktuelles Bouquet"
|
||||
|
||||
msgid "Single bouquet"
|
||||
msgstr "Einzel Bouquet"
|
||||
|
||||
msgid "Split by groups"
|
||||
msgstr "Aufteilung nach Gruppen"
|
||||
|
||||
msgid "Create sub-bouquets"
|
||||
msgstr "Sub-Bouquets erstellen"
|
||||
|
||||
msgid "Add image"
|
||||
msgstr "Bild hinzufügen"
|
||||
|
||||
msgid "TV Format"
|
||||
msgstr "TV-Format"
|
||||
|
||||
msgid "Use with Streamrelay"
|
||||
msgstr "Benutzung mit Streamrelay"
|
||||
|
||||
msgid "Remove use with Streamrelay"
|
||||
msgstr "Streamrelay nutzung löschen"
|
||||
|
||||
@@ -1386,8 +1386,8 @@ msgstr "Todos los bouquets"
|
||||
msgid "Playback from the main list"
|
||||
msgstr "Reproducción desde la lista principal"
|
||||
|
||||
msgid "Enables URL parsing using youtube-dl to get direct links to media."
|
||||
msgstr "Habilita el análisis de URL usando youtube-dl para obtener enlaces directos a los medios."
|
||||
msgid "Enables URL parsing using yt-dlp to get direct links to media."
|
||||
msgstr "Habilita el análisis de URL usando yt-dlp para obtener enlaces directos a los medios."
|
||||
|
||||
msgid "Permissions..."
|
||||
msgstr "Permisos..."
|
||||
@@ -1495,3 +1495,33 @@ msgstr "Omitir banda C"
|
||||
|
||||
msgid "Automatically set the name selected in the bouquet list."
|
||||
msgstr "Establece automáticamente el nombre seleccionado en la lista de bouquets."
|
||||
|
||||
msgid "Merge satellites by positions"
|
||||
msgstr "Combinar satélites por posiciones"
|
||||
|
||||
msgid "Save satellite selection"
|
||||
msgstr "Guardar selección de satélites"
|
||||
|
||||
msgid "Extract direct links"
|
||||
msgstr "Extraer enlaces directos"
|
||||
|
||||
msgid "URL prefix:"
|
||||
msgstr "Prefijo URL:"
|
||||
|
||||
msgid "Invalid prefix for the given URL!"
|
||||
msgstr "¡Prefijo inválido para la URL dada!"
|
||||
|
||||
msgid "Alternate window title"
|
||||
msgstr "Título alternativo de ventana"
|
||||
|
||||
msgid "Selected type:"
|
||||
msgstr "Tipo elegido:"
|
||||
|
||||
msgid "Extension Manager"
|
||||
msgstr "Gestor de extensiones"
|
||||
|
||||
msgid "Ver."
|
||||
msgstr "Ver."
|
||||
|
||||
msgid "Installed"
|
||||
msgstr "Instalado"
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
# Copyright (C) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2024 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
# Nicola Fanghella, 2021
|
||||
# Massimo Pissarello <mapi68@gmail.com>, 2022, 2023.
|
||||
# SPDX-FileCopyrightText: 2022, 2023, 2024 Massimo Pissarello <mapi68@gmail.com>
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"PO-Revision-Date: 2023-06-02 06:05+0200\n"
|
||||
"PO-Revision-Date: 2024-08-16 08:21+0200\n"
|
||||
"Last-Translator: Massimo Pissarello <mapi68@gmail.com>\n"
|
||||
"Language-Team: Italian <>\n"
|
||||
"Language: it\n"
|
||||
@@ -14,12 +13,10 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Lokalize 23.04.1\n"
|
||||
"X-Generator: Lokalize 24.05.2\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr ""
|
||||
"Nicola Fanghella\n"
|
||||
"Massimo Pissarello"
|
||||
msgstr "Massimo Pissarello\nNicola Fanghella"
|
||||
|
||||
# Main
|
||||
msgid "Service"
|
||||
@@ -296,10 +293,10 @@ msgstr "Chiudi"
|
||||
|
||||
# Picons dialog
|
||||
msgid "Load providers"
|
||||
msgstr "Carica provider"
|
||||
msgstr "Carica fornitori"
|
||||
|
||||
msgid "Providers"
|
||||
msgstr "Provider"
|
||||
msgstr "Fornitori"
|
||||
|
||||
msgid "Receive picons"
|
||||
msgstr "Scarica picon"
|
||||
@@ -338,16 +335,16 @@ msgid "Path to Enigma2 picons:"
|
||||
msgstr "Persorso picon su Enigma2:"
|
||||
|
||||
msgid "Specify the correct position value for the provider!"
|
||||
msgstr "Specifica il valore di posizione corretto per il provider!"
|
||||
msgstr "Specifica il valore di posizione corretto per il fornitore!"
|
||||
|
||||
msgid "Converter between name formats"
|
||||
msgstr "Convertitore tra formati di nome"
|
||||
|
||||
msgid "Receive picons for providers"
|
||||
msgstr "Scarica picon per provider"
|
||||
msgstr "Scarica picon per fornitore"
|
||||
|
||||
msgid "Load satellite providers."
|
||||
msgstr "Carica provider satellitari"
|
||||
msgstr "Carica fornitori satellitari"
|
||||
|
||||
msgid ""
|
||||
"To automatically set the identifiers for picons,\n"
|
||||
@@ -413,7 +410,7 @@ msgid "Reference"
|
||||
msgstr "Riferimento"
|
||||
|
||||
msgid "Namespace"
|
||||
msgstr "Spazio dei nomi"
|
||||
msgstr "Namespace"
|
||||
|
||||
msgid "Flags:"
|
||||
msgstr "Flag:"
|
||||
@@ -514,8 +511,7 @@ msgstr "Non consentito in questo contesto!"
|
||||
|
||||
msgid "Please, download files from receiver or setup your path for read data!"
|
||||
msgstr ""
|
||||
"Per favore, scarica i file dal ricevitore o imposta il percorso da cui"
|
||||
" leggere i dati!"
|
||||
"Scarica i file dal ricevitore o imposta il percorso da cui leggere i dati!"
|
||||
|
||||
msgid "Reading data error!"
|
||||
msgstr "Errore lettura dati!"
|
||||
@@ -530,7 +526,7 @@ msgid "The text of marker is empty, please try again!"
|
||||
msgstr "Il testo del marcatore è vuoto, riprova!"
|
||||
|
||||
msgid "Please, select only one item!"
|
||||
msgstr "Per favore, seleziona solo un elemento!"
|
||||
msgstr "Seleziona solo un elemento!"
|
||||
|
||||
msgid "No png file is selected!"
|
||||
msgstr "Nessun file png selezionato!"
|
||||
@@ -551,7 +547,7 @@ msgid "Done!"
|
||||
msgstr "Fatto!"
|
||||
|
||||
msgid "Please, wait..."
|
||||
msgstr "Attendere prego..."
|
||||
msgstr "Attendi..."
|
||||
|
||||
msgid "Resizing..."
|
||||
msgstr "Ridimensionamento..."
|
||||
@@ -563,10 +559,10 @@ msgid "No satellite is selected!"
|
||||
msgstr "Nessun satellite selezionato!"
|
||||
|
||||
msgid "Please, select only one satellite!"
|
||||
msgstr "Per favore, seleziona solo un satellite!"
|
||||
msgstr "Seleziona solo un satellite!"
|
||||
|
||||
msgid "Please check your parameters and try again."
|
||||
msgstr "Per favore, controlla i tuoi parametri e riprova di nuovo."
|
||||
msgstr "Controlla i parametri e riprova di nuovo."
|
||||
|
||||
msgid "No satellites.xml file is selected!"
|
||||
msgstr "Nessun file satellites.xml selezionato!"
|
||||
@@ -582,7 +578,7 @@ msgstr "VLC non trovato! Controlla che sia installato!"
|
||||
|
||||
# Search unavailable streams dialog
|
||||
msgid "Please wait, streams testing in progress..."
|
||||
msgstr "Attendere prego, test degli stream in corso..."
|
||||
msgstr "Test degli stream in corso..."
|
||||
|
||||
msgid "Found"
|
||||
msgstr "Trovato"
|
||||
@@ -594,7 +590,7 @@ msgid "No changes required!"
|
||||
msgstr "Non sono richiesti cambiamenti!"
|
||||
|
||||
msgid "This list does not contains IPTV streams!"
|
||||
msgstr "L'elenco non contiene stream IPTV!"
|
||||
msgstr "Questo elenco non contiene stream IPTV!"
|
||||
|
||||
msgid "New empty configuration"
|
||||
msgstr "Nuova configurazione vuota"
|
||||
@@ -730,7 +726,7 @@ msgid "XML file"
|
||||
msgstr "File XML"
|
||||
|
||||
msgid "Use web source"
|
||||
msgstr "Utilizza fonte web"
|
||||
msgstr "Usa fonte web"
|
||||
|
||||
msgid "Url to *.xml.gz file:"
|
||||
msgstr "Da URL a file *.xml.gz:"
|
||||
@@ -782,7 +778,7 @@ msgstr ""
|
||||
" bouquet!"
|
||||
|
||||
msgid "Use HTTP"
|
||||
msgstr "Utilizza HTTP"
|
||||
msgstr "Usa HTTP"
|
||||
|
||||
msgid "Close playback"
|
||||
msgstr "Ferma riproduzione"
|
||||
@@ -826,7 +822,7 @@ msgstr "Abilita barra di riproduzione diretta"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr ""
|
||||
"Consenti l'invio diretto e la riproduzione di collegamenti multimediali sul"
|
||||
"Abilita l'invio e la riproduzione diretta di collegamenti multimediali sul"
|
||||
" ricevitore"
|
||||
|
||||
msgid "Watch the channel in the program"
|
||||
@@ -964,7 +960,7 @@ msgstr ""
|
||||
" dell'elenco dei preferiti!"
|
||||
|
||||
msgid "Operates in standby mode or current active transponder!"
|
||||
msgstr "Funziona in modalità standby o transponder attivo corrente!"
|
||||
msgstr "Funziona in modalità standby o con il transponder attualmente attivo!"
|
||||
|
||||
msgid "No connection to the receiver!"
|
||||
msgstr "Nessuna connessione con il ricevitore!"
|
||||
@@ -1263,7 +1259,7 @@ msgstr ""
|
||||
" picon!"
|
||||
|
||||
msgid "Streams detected:"
|
||||
msgstr "Rilevati stream:"
|
||||
msgstr "Stream rilevati:"
|
||||
|
||||
msgid "Download picons"
|
||||
msgstr "Scarica picon"
|
||||
@@ -1290,7 +1286,7 @@ msgid "Mark duplicates"
|
||||
msgstr "Contrassegna duplicati"
|
||||
|
||||
msgid "Load only for selected bouquet"
|
||||
msgstr "Scarica solo per i bouquet selezionati"
|
||||
msgstr "Carica solo per i bouquet selezionati"
|
||||
|
||||
msgid "The task is canceled!"
|
||||
msgstr "L'attività è stata annullata!"
|
||||
@@ -1308,13 +1304,13 @@ msgid "Help"
|
||||
msgstr "Aiuto"
|
||||
|
||||
msgid "HTTP API is not activated. Check your settings!"
|
||||
msgstr "API HTTP non attivata. Controlla le tue impostazioni!"
|
||||
msgstr "API HTTP non attivata. Controlla le impostazioni!"
|
||||
|
||||
msgid "Add picons"
|
||||
msgstr "Aggiungi picon"
|
||||
|
||||
msgid "Logs"
|
||||
msgstr "Log"
|
||||
msgstr "Registri"
|
||||
|
||||
msgid "Title"
|
||||
msgstr "Titolo"
|
||||
@@ -1412,10 +1408,10 @@ msgstr "Tutti i bouquet"
|
||||
msgid "Playback from the main list"
|
||||
msgstr "Riproduci dall'elenco principale"
|
||||
|
||||
msgid "Enables URL parsing using youtube-dl to get direct links to media."
|
||||
msgid "Enables URL parsing using yt-dlp to get direct links to media."
|
||||
msgstr ""
|
||||
"Abilita l'analisi degli URL utilizzando youtube-dl per ottenere collegamenti"
|
||||
" diretti ai media."
|
||||
"Abilita l'analisi degli URL utilizzando yt-dlp per ottenere collegamenti"
|
||||
" diretti ai contenuti multimediali."
|
||||
|
||||
msgid "Permissions..."
|
||||
msgstr "Permessi..."
|
||||
@@ -1427,7 +1423,7 @@ msgid "EPG *.dat file:"
|
||||
msgstr "File EPG *.dat:"
|
||||
|
||||
msgid "Use HTTP to reload data in the receiver"
|
||||
msgstr "Utilizza HTTP per ricaricare i dati nel ricevitore"
|
||||
msgstr "Usa HTTP per ricaricare i dati nel ricevitore"
|
||||
|
||||
msgid "Enable picons compression"
|
||||
msgstr "Abilita compressione picon"
|
||||
@@ -1461,7 +1457,7 @@ msgid "Region"
|
||||
msgstr "Regione"
|
||||
|
||||
msgid "Provider"
|
||||
msgstr "Provider"
|
||||
msgstr "Fornitore"
|
||||
|
||||
msgid ""
|
||||
"Enables upload as an archive if a large number of picon (> 1000) is"
|
||||
@@ -1511,10 +1507,11 @@ msgid "Removed"
|
||||
msgstr "Rimosso"
|
||||
|
||||
msgid "Enables overwriting existing main list services."
|
||||
msgstr "Consente di sovrascrivere i servizi esistenti dell'elenco principale."
|
||||
msgstr ""
|
||||
"Abilita la sovrascrittura dei servizi esistenti dell'elenco principale."
|
||||
|
||||
msgid "Enables skipping services import from lamedb."
|
||||
msgstr "Consente di saltare l'importazione dei servizi da lamedb."
|
||||
msgstr "Abilita il salto dell'importazione dei servizi da lamedb."
|
||||
|
||||
msgid "Bouquets data only"
|
||||
msgstr "Bouquet solo dati"
|
||||
@@ -1553,10 +1550,69 @@ msgid "Selected type:"
|
||||
msgstr "Tipo selezionato:"
|
||||
|
||||
msgid "Extension Manager"
|
||||
msgstr "Manager estensioni"
|
||||
msgstr "Gestore estensioni"
|
||||
|
||||
msgid "Ver."
|
||||
msgstr "Ver."
|
||||
|
||||
msgid "Installed"
|
||||
msgstr "Installato"
|
||||
|
||||
msgid "Use common folder for picons"
|
||||
msgstr "Usa la cartella comune per i picon"
|
||||
|
||||
msgid "Activates single folder use for several profiles."
|
||||
msgstr "Attiva l'uso di una singola cartella per diversi profili."
|
||||
|
||||
msgid "Events"
|
||||
msgstr "Eventi"
|
||||
|
||||
msgid "Markers"
|
||||
msgstr "Marcatori"
|
||||
|
||||
msgid "IPTV only"
|
||||
msgstr "Solo IPTV"
|
||||
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
msgid "Not found."
|
||||
msgstr "Non trovato."
|
||||
|
||||
msgid "Current EPG cache contents."
|
||||
msgstr "Contenuto attuale cache EPG."
|
||||
|
||||
msgid "Source error!"
|
||||
msgstr "Errore fonte!"
|
||||
|
||||
msgid "The EPG source for the favorites list is not set!"
|
||||
msgstr "La fonte EPG per l'elenco dei preferiti non è impostata!"
|
||||
|
||||
msgid "Add to EPG sources list"
|
||||
msgstr "Aggiungi all'elenco delle fonti EPG"
|
||||
|
||||
msgid "Current bouquet"
|
||||
msgstr "Bouquet attuale"
|
||||
|
||||
msgid "Single bouquet"
|
||||
msgstr "Bouquet singolo"
|
||||
|
||||
msgid "Split by groups"
|
||||
msgstr "Dividi per gruppi"
|
||||
|
||||
msgid "Create sub-bouquets"
|
||||
msgstr "Crea sotto-bouquet"
|
||||
|
||||
msgid "Add image"
|
||||
msgstr "Aggiungi immagine"
|
||||
|
||||
msgid "TV Format"
|
||||
msgstr "Formato TV"
|
||||
|
||||
msgid "Use with Streamrelay"
|
||||
msgstr "Usa con Streamrelay"
|
||||
|
||||
msgid "Remove use with Streamrelay"
|
||||
msgstr "Rimuovi usa con Streamrelay"
|
||||
|
||||
|
||||
|
||||
@@ -1346,8 +1346,8 @@ msgstr "Alle boeketten"
|
||||
msgid "Playback from the main list"
|
||||
msgstr "Afspelen vanuit de hoofdlijst"
|
||||
|
||||
msgid "Enables URL parsing using youtube-dl to get direct links to media."
|
||||
msgstr "Schakelt URL-parsing met behulp van youtube-dl in, om directe links naar media te krijgen."
|
||||
msgid "Enables URL parsing using yt-dlp to get direct links to media."
|
||||
msgstr "Schakelt URL-parsing met behulp van yt-dlp in, om directe links naar media te krijgen."
|
||||
|
||||
msgid "Permissions..."
|
||||
msgstr "Rechten..."
|
||||
@@ -1455,3 +1455,33 @@ msgstr "C-band overslaan"
|
||||
|
||||
msgid "Automatically set the name selected in the bouquet list."
|
||||
msgstr "Stel automatisch de naam in die is geselecteerd in de boeketlijst."
|
||||
|
||||
msgid "Merge satellites by positions"
|
||||
msgstr "Satellieten samenvoegen op basis van positie"
|
||||
|
||||
msgid "Save satellite selection"
|
||||
msgstr "Satellietselectie opslaan"
|
||||
|
||||
msgid "Extract direct links"
|
||||
msgstr "Directe links uitpakken"
|
||||
|
||||
msgid "URL prefix:"
|
||||
msgstr "Prefix URL:"
|
||||
|
||||
msgid "Invalid prefix for the given URL!"
|
||||
msgstr "Ongeldige prefix voor de opgegeven URL!"
|
||||
|
||||
msgid "Alternate window title"
|
||||
msgstr "Alternatieve venstertitel"
|
||||
|
||||
msgid "Selected type:"
|
||||
msgstr "Geselecteerde type:"
|
||||
|
||||
msgid "Extension Manager"
|
||||
msgstr "Uitbreidings Manager"
|
||||
|
||||
msgid "Ver."
|
||||
msgstr "Ver."
|
||||
|
||||
msgid "Installed"
|
||||
msgstr "Geïnstalleerd"
|
||||
|
||||
@@ -1376,8 +1376,8 @@ msgstr "Wszystkie bukiety"
|
||||
msgid "Playback from the main list"
|
||||
msgstr "Odtwarzanie z listy głównej"
|
||||
|
||||
msgid "Enables URL parsing using youtube-dl to get direct links to media."
|
||||
msgstr "Włącza parsowanie adresów URL przy użyciu youtube-dl w celu uzyskania bezpośrednich linków do multimediów."
|
||||
msgid "Enables URL parsing using yt-dlp to get direct links to media."
|
||||
msgstr "Włącza parsowanie adresów URL przy użyciu yt-dlp w celu uzyskania bezpośrednich linków do multimediów."
|
||||
|
||||
msgid "Permissions..."
|
||||
msgstr "Uprawnienia..."
|
||||
|
||||
@@ -1010,3 +1010,476 @@ msgstr "Captura de pantalla"
|
||||
|
||||
msgid "Video"
|
||||
msgstr "Vidео"
|
||||
|
||||
msgid "The Neutrino has only experimental support. Not all features are supported!"
|
||||
msgstr "Neutrino имеет только экспериментальную поддержку. Поддерживаются не все функции!"
|
||||
|
||||
msgid "Enable experimental features"
|
||||
msgstr "Ativar funcionalidades experimentais"
|
||||
|
||||
msgid "Can't Playback!"
|
||||
msgstr "Não consigo reproduzir!"
|
||||
|
||||
msgid "Enable Dark Mode"
|
||||
msgstr "Ativar o modo escuro"
|
||||
|
||||
msgid "Extract..."
|
||||
msgstr "Extrair..."
|
||||
|
||||
msgid "Unsupported format!"
|
||||
msgstr "Formato não suportado!"
|
||||
|
||||
msgid "Combine with the current data?"
|
||||
msgstr "Combinar com os dados actuais?"
|
||||
|
||||
msgid "Importing data done!"
|
||||
msgstr "Importação de dados concluída!"
|
||||
|
||||
msgid "Current service"
|
||||
msgstr "Serviço atual"
|
||||
|
||||
msgid "Open folder"
|
||||
msgstr "Abrir pasta"
|
||||
|
||||
msgid "Open archive"
|
||||
msgstr "Abrir ficheiro"
|
||||
|
||||
msgid "Import from Web"
|
||||
msgstr "Importar da Web"
|
||||
|
||||
msgid "Control"
|
||||
msgstr "Control"
|
||||
|
||||
msgid "Timers"
|
||||
msgstr "Temporizadores"
|
||||
|
||||
msgid "Timer"
|
||||
msgstr "Temporizador"
|
||||
|
||||
msgid "Add timer"
|
||||
msgstr "Adicionar temporizador"
|
||||
|
||||
msgid "Hr."
|
||||
msgstr "Hr."
|
||||
|
||||
msgid "Min."
|
||||
msgstr "Min."
|
||||
|
||||
msgid "Power"
|
||||
msgstr "Ligar"
|
||||
|
||||
msgid "Standby"
|
||||
msgstr "Em espera"
|
||||
|
||||
msgid "Wake Up"
|
||||
msgstr "Despertar"
|
||||
|
||||
msgid "Reboot"
|
||||
msgstr "Reinicialização"
|
||||
|
||||
msgid "Restart GUI"
|
||||
msgstr "Reiniciar a GUI"
|
||||
|
||||
msgid "Shutdown"
|
||||
msgstr "Desativação"
|
||||
|
||||
msgid "Shut down"
|
||||
msgstr "Desativar"
|
||||
|
||||
msgid "Do Nothing"
|
||||
msgstr "Não fazer nada"
|
||||
|
||||
msgid "Auto"
|
||||
msgstr "Auto"
|
||||
|
||||
msgid "Grab screenshot"
|
||||
msgstr "Captura de ecrã"
|
||||
|
||||
msgid "Enabled:"
|
||||
msgstr "Ativado:"
|
||||
|
||||
msgid "Name:"
|
||||
msgstr "Nome:"
|
||||
|
||||
msgid "Description:"
|
||||
msgstr "Descrição:"
|
||||
|
||||
msgid "Service:"
|
||||
msgstr "Serviço:"
|
||||
|
||||
msgid "Service reference:"
|
||||
msgstr "Referência do serviço:"
|
||||
|
||||
msgid "Event ID:"
|
||||
msgstr "ID do evento:"
|
||||
|
||||
msgid "Begins:"
|
||||
msgstr "Começa:"
|
||||
|
||||
msgid "Ends:"
|
||||
msgstr "Termina:"
|
||||
|
||||
msgid "Repeated:"
|
||||
msgstr "Repetido:"
|
||||
|
||||
msgid "Action:"
|
||||
msgstr "Ação:"
|
||||
|
||||
msgid "After event:"
|
||||
msgstr "Após o evento:"
|
||||
|
||||
msgid "Location:"
|
||||
msgstr "Localização:"
|
||||
|
||||
msgid "Mo"
|
||||
msgstr "Seg"
|
||||
|
||||
msgid "Tu"
|
||||
msgstr "Ter"
|
||||
|
||||
msgid "We"
|
||||
msgstr "Qua"
|
||||
|
||||
msgid "Th"
|
||||
msgstr "Qui"
|
||||
|
||||
msgid "Fr"
|
||||
msgstr "Sex"
|
||||
|
||||
msgid "Sa"
|
||||
msgstr "Sáb"
|
||||
|
||||
msgid "Dom"
|
||||
msgstr "Вс"
|
||||
|
||||
msgid "Set"
|
||||
msgstr "Set"
|
||||
|
||||
msgid "Services update"
|
||||
msgstr "Atualização de serviços"
|
||||
|
||||
msgid "Create folder"
|
||||
msgstr "Criar pasta"
|
||||
|
||||
msgid "FTP client"
|
||||
msgstr "Cliente FTP"
|
||||
|
||||
msgid "The file size is too large!"
|
||||
msgstr "O ficheiro é demasiado grande!"
|
||||
|
||||
msgid "Connect"
|
||||
msgstr "Ligar"
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr "Desligar"
|
||||
|
||||
msgid "Size"
|
||||
msgstr "Tamanho"
|
||||
|
||||
msgid "Date"
|
||||
msgstr "Data"
|
||||
|
||||
msgid "Toggle display position"
|
||||
msgstr "Alternar a posição de visualização"
|
||||
|
||||
msgid "Alternatives"
|
||||
msgstr "Alternativas"
|
||||
|
||||
msgid "Add alternatives"
|
||||
msgstr "Adicionar alternativas"
|
||||
|
||||
msgid "DreamOS only!"
|
||||
msgstr "Apenas DreamOS!"
|
||||
|
||||
msgid "A similar service is already in this list!"
|
||||
msgstr "Um serviço semelhante já consta desta lista!"
|
||||
|
||||
msgid "Play mode has been changed!\nRestart the program to apply the settings."
|
||||
msgstr "O modo de reprodução foi alterado!\nReinicie o programa para aplicar as definições."
|
||||
|
||||
msgid "Set values for TID, NID and Namespace for correct naming of the picons!"
|
||||
msgstr "Definir valores para TID, NID e Namespace para nomear corretamente os picons!"
|
||||
|
||||
msgid "Streams detected:"
|
||||
msgstr "Fluxos detectados:"
|
||||
|
||||
msgid "Download picons"
|
||||
msgstr "Descarregar picons"
|
||||
|
||||
msgid "Errors:"
|
||||
msgstr "Erros:"
|
||||
|
||||
msgid "Use to play streams:"
|
||||
msgstr "Utilizar para reproduzir fluxos:"
|
||||
|
||||
msgid "Font in the lists:"
|
||||
msgstr "Fonte nas listas:"
|
||||
|
||||
msgid "Picons size in the lists:"
|
||||
msgstr "Tamanho dos picões nas listas:"
|
||||
|
||||
msgid "Logo size in tooltips:"
|
||||
msgstr "Tamanho do logótipo nas dicas de ferramenta:"
|
||||
|
||||
msgid "Save as"
|
||||
msgstr "Salvar como"
|
||||
|
||||
msgid "Mark duplicates"
|
||||
msgstr "Marcar duplicações"
|
||||
|
||||
msgid "Load only for selected bouquet"
|
||||
msgstr "Carregar apenas para o bouquet selecionado"
|
||||
|
||||
msgid "The task is canceled!"
|
||||
msgstr "A tarefa foi cancelada!"
|
||||
|
||||
msgid "Data loading in progress!"
|
||||
msgstr "Carregamento de dados em curso!"
|
||||
|
||||
msgid "Recordings"
|
||||
msgstr "Gravações"
|
||||
|
||||
msgid "Recordings:"
|
||||
msgstr "Gravações:"
|
||||
|
||||
msgid "Help"
|
||||
msgstr "Ajuda"
|
||||
|
||||
msgid "HTTP API is not activated. Check your settings!"
|
||||
msgstr "A API HTTP não está activada. Verifique as suas definições!"
|
||||
|
||||
msgid "Add picons"
|
||||
msgstr "Adicionar picons"
|
||||
|
||||
msgid "Logs"
|
||||
msgstr "Registos"
|
||||
|
||||
msgid "Title"
|
||||
msgstr "Título"
|
||||
|
||||
msgid "Time"
|
||||
msgstr "Tempo"
|
||||
|
||||
msgid "Length"
|
||||
msgstr "Duração"
|
||||
|
||||
msgid "Additional source"
|
||||
msgstr "Fonte complementar"
|
||||
|
||||
msgid "Automatically set the name selected in the favorites list."
|
||||
msgstr "Definir automaticamente o nome selecionado na lista de favoritos."
|
||||
|
||||
msgid "Playback"
|
||||
msgstr "Reprodução"
|
||||
|
||||
msgid "Playback:"
|
||||
msgstr "Reprodução:"
|
||||
|
||||
msgid "Audio"
|
||||
msgstr "Áudio"
|
||||
|
||||
msgid "Audio Track"
|
||||
msgstr "Pista áudio"
|
||||
|
||||
msgid "Subtitle"
|
||||
msgstr "Legenda"
|
||||
|
||||
msgid "Subtitle Track"
|
||||
msgstr "Faixa de legenda"
|
||||
|
||||
msgid "Aspect ratio"
|
||||
msgstr "Rácio de aspeto"
|
||||
|
||||
msgid "This may change the settings of other profiles!"
|
||||
msgstr "Isto pode alterar as definições de outros perfis!"
|
||||
|
||||
msgid "Drag the services to the desired picon or picon to the list of selected services."
|
||||
msgstr "Arraste os serviços para o picon pretendido ou picon para a lista de serviços seleccionados."
|
||||
|
||||
msgid "Sets the profile folder as default to store picons, backups, etc."
|
||||
msgstr "Define a pasta de perfil como predefinida para armazenar picons, cópias de segurança, etc."
|
||||
|
||||
msgid "New sub-bouquet"
|
||||
msgstr "Novo sub-bouquet"
|
||||
|
||||
msgid "Mark not presented in Bouquets"
|
||||
msgstr "Marca não apresentada em bouquets"
|
||||
|
||||
msgid "Not in Bouquets"
|
||||
msgstr "Não em bouquets"
|
||||
|
||||
msgid "Do not show services present in Bouquets."
|
||||
msgstr "Não mostrar serviços presentes em bouquets."
|
||||
|
||||
msgid "IPTV services only"
|
||||
msgstr "Apenas serviços IPTV"
|
||||
|
||||
msgid "Display picons"
|
||||
msgstr "Mostrar picons"
|
||||
|
||||
msgid "Alternate layout"
|
||||
msgstr "Esquema alternativo"
|
||||
|
||||
msgid "Layout of elements has been changed!"
|
||||
msgstr "O esquema dos elementos foi alterado!"
|
||||
|
||||
msgid "Restart the program to apply all changes."
|
||||
msgstr "Reinicie o programa para aplicar todas as alterações."
|
||||
|
||||
msgid "New folder"
|
||||
msgstr "Novo ficheiro"
|
||||
|
||||
msgid "Rename"
|
||||
msgstr "Renomear"
|
||||
|
||||
msgid "Bookmarks"
|
||||
msgstr "Marcadores"
|
||||
|
||||
msgid "Add bookmark"
|
||||
msgstr "Adicionar marcador"
|
||||
|
||||
msgid "All bouquets"
|
||||
msgstr "Todos os bouquets"
|
||||
|
||||
msgid "Playback from the main list"
|
||||
msgstr "Reprodução da lista principal"
|
||||
|
||||
msgid "Enables URL parsing using yt-dlp to get direct links to media."
|
||||
msgstr "Permite a análise de URLs usando yt-dlp para obter links diretos para mídia."
|
||||
|
||||
msgid "Permissions..."
|
||||
msgstr "Permissões..."
|
||||
|
||||
msgid "Display EPG in bouquet list"
|
||||
msgstr "Apresentar EPG na lista de bouquet"
|
||||
|
||||
msgid "EPG *.dat file:"
|
||||
msgstr "Ficheiro EPG *.dat:"
|
||||
|
||||
msgid "Use HTTP to reload data in the receiver"
|
||||
msgstr "Utilizar HTTP para recarregar dados no recetor"
|
||||
|
||||
msgid "Enable picons compression"
|
||||
msgstr "Ativar a compressão de picons"
|
||||
|
||||
msgid "Update interval (sec):"
|
||||
msgstr "Intervalo de atualização (seg):"
|
||||
|
||||
msgid "Update:"
|
||||
msgstr "Atualização:"
|
||||
|
||||
msgid "Daily"
|
||||
msgstr "Diário"
|
||||
|
||||
msgid "Assign reference"
|
||||
msgstr "Atribuir referência"
|
||||
|
||||
msgid "Specify hostname or IP address"
|
||||
msgstr "Especificar o nome do anfitrião ou o endereço IP"
|
||||
|
||||
msgid "Default selection"
|
||||
msgstr "Seleção predefinida"
|
||||
|
||||
msgid "Don't change power state"
|
||||
msgstr "Não alterar o estado de alimentação"
|
||||
|
||||
msgid "Don't toggle standby mode when updating bouquets and services."
|
||||
msgstr "Não alterne o modo de espera ao atualizar bouquets e serviços."
|
||||
|
||||
msgid "Region"
|
||||
msgstr "Região"
|
||||
|
||||
msgid "Provider"
|
||||
msgstr "Provedor"
|
||||
|
||||
msgid "Enables upload as an archive if a large number of picon (> 1000) is selected.\n"
|
||||
" Recommended only if you have external storage."
|
||||
msgstr "Permite o carregamento como um arquivo se for selecionado um grande número de picons (> 1000).\n"
|
||||
" Recomendado apenas se tiver armazenamento externo."
|
||||
|
||||
msgid "Clear \"New\" flag"
|
||||
msgstr "Apagar a bandeira \"Novo\""
|
||||
|
||||
msgid "Group by"
|
||||
msgstr "Agrupar por"
|
||||
|
||||
msgid "Replace existing"
|
||||
msgstr "Substituir o existente"
|
||||
|
||||
msgid "Already exists"
|
||||
msgstr "Já existe"
|
||||
|
||||
msgid "Enable unlimited copy buffer"
|
||||
msgstr "Ativar o buffer de cópia ilimitado"
|
||||
|
||||
msgid "Enables unlimited copy buffer for the bouquets tab."
|
||||
msgstr "Ativa a memória intermédia de cópia ilimitada para o separador bouquets."
|
||||
|
||||
msgid "Start time"
|
||||
msgstr "Hora de início"
|
||||
|
||||
msgid "End time"
|
||||
msgstr "Hora de fim"
|
||||
|
||||
msgid "Enable extensions support"
|
||||
msgstr "Ativar o suporte de extensões"
|
||||
|
||||
msgid "After uploading the changes you may need to completely reboot the receiver!"
|
||||
msgstr "Depois de carregar as alterações, poderá ser necessário reiniciar completamente o recetor!"
|
||||
|
||||
msgid "Remove duplicates"
|
||||
msgstr "Eliminar duplicados"
|
||||
|
||||
msgid "Removed"
|
||||
msgstr "Removido"
|
||||
|
||||
msgid "Enables overwriting existing main list services."
|
||||
msgstr "Permite substituir os serviços da lista principal existentes."
|
||||
|
||||
msgid "Enables skipping services import from lamedb."
|
||||
msgstr "Permite saltar a importação de serviços de lamedb."
|
||||
|
||||
msgid "Bouquets data only"
|
||||
msgstr "Apenas dados de bouquets"
|
||||
|
||||
msgid "Create Category bouquets"
|
||||
msgstr "Criar bouquets de categoria"
|
||||
|
||||
msgid "Create Regional bouquets"
|
||||
msgstr "Criar bouquets regionais"
|
||||
|
||||
msgid "Skip C-band"
|
||||
msgstr "Saltar a banda C"
|
||||
|
||||
msgid "Automatically set the name selected in the bouquet list."
|
||||
msgstr "Definir automaticamente o nome selecionado na lista de bouquets."
|
||||
|
||||
msgid "Merge satellites by positions"
|
||||
msgstr "Unir satélites por posições"
|
||||
|
||||
msgid "Save satellite selection"
|
||||
msgstr "Guardar a seleção de satélite"
|
||||
|
||||
msgid "Extract direct links"
|
||||
msgstr "Extrair ligações directas"
|
||||
|
||||
msgid "URL prefix:"
|
||||
msgstr "Prefixo URL:"
|
||||
|
||||
msgid "Invalid prefix for the given URL!"
|
||||
msgstr "Prefixo inválido para o URL indicado!"
|
||||
|
||||
msgid "Alternate window title"
|
||||
msgstr "Título alternativo da janela"
|
||||
|
||||
msgid "Selected type:"
|
||||
msgstr "Tipo selecionado:"
|
||||
|
||||
msgid "Extension Manager"
|
||||
msgstr "Gestor de extensões"
|
||||
|
||||
msgid "Ver."
|
||||
msgstr "Ver."
|
||||
|
||||
msgid "Installed"
|
||||
msgstr "Instalado"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2024 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
@@ -1311,7 +1311,7 @@ msgid "Sets the profile folder as default to store picons, backups, etc."
|
||||
msgstr "Устанавливает папку профиля по умолчанию для хранения пиконов, резервных копий и т. п."
|
||||
|
||||
msgid "New sub-bouquet"
|
||||
msgstr "Создать суббукет"
|
||||
msgstr "Создать подбукет"
|
||||
|
||||
msgid "Mark not presented in Bouquets"
|
||||
msgstr "Отметить отсутствующие в букетах"
|
||||
@@ -1355,8 +1355,8 @@ msgstr "Все букеты"
|
||||
msgid "Playback from the main list"
|
||||
msgstr "Воспроизведение из основного списка"
|
||||
|
||||
msgid "Enables URL parsing using youtube-dl to get direct links to media."
|
||||
msgstr "Включает анализ URL-адресов с помощью youtube-dl для получения прямых ссылок на медиа."
|
||||
msgid "Enables URL parsing using yt-dlp to get direct links to media."
|
||||
msgstr "Включает анализ URL-адресов с помощью yt-dlp для получения прямых ссылок на медиа."
|
||||
|
||||
msgid "Permissions..."
|
||||
msgstr "Разрешения..."
|
||||
@@ -1494,3 +1494,60 @@ msgstr "Вер."
|
||||
|
||||
msgid "Installed"
|
||||
msgstr "Установлено"
|
||||
|
||||
msgid "Use common folder for picons"
|
||||
msgstr "Использовать общую папку для пиконов"
|
||||
|
||||
msgid "Activates single folder use for several profiles."
|
||||
msgstr "Активирует использование одной папки для нескольких профилей."
|
||||
|
||||
msgid "Events"
|
||||
msgstr "События"
|
||||
|
||||
msgid "Markers"
|
||||
msgstr "Маркеры"
|
||||
|
||||
msgid "IPTV only"
|
||||
msgstr "Только IPTV"
|
||||
|
||||
msgid "No"
|
||||
msgstr "Нет"
|
||||
|
||||
msgid "Not found."
|
||||
msgstr "Не найдено."
|
||||
|
||||
msgid "Current EPG cache contents."
|
||||
msgstr "Содержимое текущего EPG кэша."
|
||||
|
||||
msgid "Source error!"
|
||||
msgstr "Ошибка источника!"
|
||||
|
||||
msgid "The EPG source for the favorites list is not set!"
|
||||
msgstr "Не установлен источник EPG для списка избранного!"
|
||||
|
||||
msgid "Add to EPG sources list"
|
||||
msgstr "Добавить в список источников EPG"
|
||||
|
||||
msgid "Current bouquet"
|
||||
msgstr "Текущий букет"
|
||||
|
||||
msgid "Single bouquet"
|
||||
msgstr "Одиночный букет"
|
||||
|
||||
msgid "Split by groups"
|
||||
msgstr "Разбить по группам"
|
||||
|
||||
msgid "Create sub-bouquets"
|
||||
msgstr "Создать подбукеты"
|
||||
|
||||
msgid "Add image"
|
||||
msgstr "Добавить изображение"
|
||||
|
||||
msgid "TV Format"
|
||||
msgstr "ТВ-формат"
|
||||
|
||||
msgid "Use with Streamrelay"
|
||||
msgstr "Использовать Streamrelay"
|
||||
|
||||
msgid "Remove use with Streamrelay"
|
||||
msgstr "Не использовать Streamrelay"
|
||||
|
||||
@@ -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: 2023-04-22 16:34+0300\n"
|
||||
"PO-Revision-Date: 2024-09-29 22:18+0300\n"
|
||||
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
|
||||
"Language-Team: audi06_19 <info@dreamosat-forum.com>\n"
|
||||
"Language: tr\n"
|
||||
@@ -11,7 +11,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.2.2\n"
|
||||
"X-Generator: Poedit 3.5\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "audi06_19 <info@dreamosat-forum.com>"
|
||||
@@ -1387,8 +1387,8 @@ msgstr "Tüm buketler"
|
||||
msgid "Playback from the main list"
|
||||
msgstr "Ana listeden oynatma"
|
||||
|
||||
msgid "Enables URL parsing using youtube-dl to get direct links to media."
|
||||
msgstr "Medyaya doğrudan bağlantılar almak için youtube-dl kullanarak URL ayrıştırmayı etkinleştirir."
|
||||
msgid "Enables URL parsing using yt-dlp to get direct links to media."
|
||||
msgstr "Medyaya doğrudan bağlantılar almak için yt-dlp kullanarak URL ayrıştırmayı etkinleştirir."
|
||||
|
||||
msgid "Permissions..."
|
||||
msgstr "İzinler..."
|
||||
@@ -1519,3 +1519,69 @@ msgstr "Alternatif pencere başlığı"
|
||||
|
||||
msgid "Selected type:"
|
||||
msgstr "Seçilen tip:"
|
||||
|
||||
msgid "Extension Manager"
|
||||
msgstr "Eklenti Yöneticisi"
|
||||
|
||||
msgid "Ver."
|
||||
msgstr "Ver."
|
||||
|
||||
msgid "Installed"
|
||||
msgstr "Yüklenmiş"
|
||||
|
||||
msgid "Use common folder for picons"
|
||||
msgstr "Piconlar için ortak klasörü kullan"
|
||||
|
||||
msgid "Activates single folder use for several profiles."
|
||||
msgstr "Birden fazla profil için tek klasör kullanımını etkinleştirir."
|
||||
|
||||
msgid "Events"
|
||||
msgstr "Olaylar"
|
||||
|
||||
msgid "Markers"
|
||||
msgstr "İşaretleyiciler"
|
||||
|
||||
msgid "IPTV only"
|
||||
msgstr "Yalnızca IPTV"
|
||||
|
||||
msgid "No"
|
||||
msgstr "Hayır"
|
||||
|
||||
msgid "Not found."
|
||||
msgstr "Bulunamadı."
|
||||
|
||||
msgid "Current EPG cache contents."
|
||||
msgstr "Geçerli EPG önbellek içerikleri."
|
||||
|
||||
msgid "Source error!"
|
||||
msgstr "Kaynak hatası!"
|
||||
|
||||
msgid "The EPG source for the favorites list is not set!"
|
||||
msgstr "Favoriler listesi için EPG kaynağı ayarlanmamış!"
|
||||
|
||||
msgid "Add to EPG sources list"
|
||||
msgstr "EPG kaynakları listesine ekle"
|
||||
|
||||
msgid "Current bouquet"
|
||||
msgstr "Mevcut buket"
|
||||
|
||||
msgid "Single bouquet"
|
||||
msgstr "Tek buket"
|
||||
|
||||
msgid "Split by groups"
|
||||
msgstr "Gruplara göre böl"
|
||||
|
||||
msgid "Create sub-bouquets"
|
||||
msgstr "Alt buketler oluştur"
|
||||
|
||||
msgid "Add image"
|
||||
msgstr "Resim ekle"
|
||||
|
||||
msgid "TV Format"
|
||||
msgstr "TV Formatı"
|
||||
|
||||
msgid "Use with Streamrelay"
|
||||
msgstr "Streamrelay ile kullan"
|
||||
|
||||
msgid "Remove use with Streamrelay"
|
||||
msgstr "Streamrelay ile kullanımı kaldır"
|
||||
|
||||
Reference in New Issue
Block a user