mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-08 23:05:56 +02:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc24c899af | ||
|
|
a55495fd7c | ||
|
|
ed3aea42f5 | ||
|
|
a6904360f9 | ||
|
|
1e42d693cc | ||
|
|
5e64605be6 | ||
|
|
63c55ea2ed | ||
|
|
c7f85b027d | ||
|
|
b24910a9a5 | ||
|
|
1bdb4f123f | ||
|
|
8f8d7633b8 | ||
|
|
c997724300 | ||
|
|
727a3fa8a2 | ||
|
|
4009f5c2a2 | ||
|
|
ca5d648032 | ||
|
|
938fe297c5 | ||
|
|
db29b78fd7 | ||
|
|
e599ea04c7 | ||
|
|
50a2f66fc3 | ||
|
|
34056b1006 | ||
|
|
6188fecda9 | ||
|
|
eb41b9629e | ||
|
|
7694754919 | ||
|
|
11f240a81f | ||
|
|
1b75034317 | ||
|
|
5b6900cae7 | ||
|
|
eeeca881e8 | ||
|
|
ed16fb0195 | ||
|
|
833b386356 | ||
|
|
83424124d3 | ||
|
|
d77aa68a39 | ||
|
|
94266e13b8 | ||
|
|
8db6fb1b0b | ||
|
|
e07c5d4bf7 | ||
|
|
6f3090a7e1 | ||
|
|
b73c1d1118 | ||
|
|
df127c05f3 | ||
|
|
9b579af528 | ||
|
|
8290e723c9 | ||
|
|
b12c29be84 | ||
|
|
1780fbadbd | ||
|
|
4fe4e92442 | ||
|
|
bfad5cf9ac | ||
|
|
5d285f61c0 | ||
|
|
f61d9a1f61 | ||
|
|
8d115677d1 | ||
|
|
f7c6cd6908 | ||
|
|
2d2a90542c | ||
|
|
535c9c9102 | ||
|
|
866e18762d | ||
|
|
aef5027d23 | ||
|
|
3a142eca4a | ||
|
|
606bad7716 | ||
|
|
fa07f8bf85 | ||
|
|
92280162c6 | ||
|
|
5d285e88d8 | ||
|
|
0355714e92 | ||
|
|
b06e877a0c | ||
|
|
9b479b051d |
@@ -5,6 +5,7 @@ Comment=Channel and satellite list editor for Enigma2
|
||||
Comment[ru]=Редактор списка каналов и спутников для Enigma2
|
||||
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
|
||||
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
|
||||
Comment[tr]=Enigma2 için kanal ve uydu listesi editörü
|
||||
Icon=demon-editor
|
||||
Exec=bash -c 'cd $(dirname %k) && ./start.py'
|
||||
Terminal=false
|
||||
|
||||
66
README.md
66
README.md
@@ -1,33 +1,32 @@
|
||||
# <img src="app/ui/icons/hicolor/96x96/apps/demon-editor.png" width="32" /> DemonEditor
|
||||
[](LICENSE) 
|
||||
### Enigma2 channel and satellite list editor for GNU/Linux.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/118884719-8277e980-b8ff-11eb-8621-c8c4afd6181b.png" width="560"/>](https://user-images.githubusercontent.com/7511379/118884719-8277e980-b8ff-11eb-8621-c8c4afd6181b.png)
|
||||
|
||||
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
|
||||
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc).
|
||||
### Enigma2 channel and satellite list editor for GNU/Linux.
|
||||
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
|
||||
|
||||
## Main features of the program
|
||||
* Editing bouquets, channels, satellites.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/118884747-8ad02480-b8ff-11eb-9104-8cf8fb6e785d.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118884747-8ad02480-b8ff-11eb-9104-8cf8fb6e785d.png)
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/141680963-9b8eb6cc-c712-46b2-aefe-19769e21a7d5.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141680963-9b8eb6cc-c712-46b2-aefe-19769e21a7d5.png)
|
||||
* Import function.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/118526825-4dc23180-b749-11eb-8197-e9bbccbc3bdf.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118526825-4dc23180-b749-11eb-8197-e9bbccbc3bdf.png)
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/141681059-68bc1b55-6fab-436c-aa73-ef24e2e5113b.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681059-68bc1b55-6fab-436c-aa73-ef24e2e5113b.png)
|
||||
* Backup function.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/118528402-f58c2f00-b74a-11eb-9b84-edf220526e6e.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118528402-f58c2f00-b74a-11eb-9b84-edf220526e6e.png)
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/141681104-ed9b5d35-25de-426f-b9bb-2a6e4db022bb.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681104-ed9b5d35-25de-426f-b9bb-2a6e4db022bb.png)
|
||||
* Support of picons.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/118526864-5c104d80-b749-11eb-8497-6e8c78542ab1.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118526864-5c104d80-b749-11eb-8497-6e8c78542ab1.png)
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/141681115-957c63a3-4113-422d-bb27-2d96b1463cd1.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681115-957c63a3-4113-422d-bb27-2d96b1463cd1.png)
|
||||
* Importing services, downloading picons and updating satellites from the Web.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/118530243-1a81a180-b74d-11eb-8e01-aea904d954af.png" width="250"/>](https://user-images.githubusercontent.com/7511379/118530243-1a81a180-b74d-11eb-8e01-aea904d954af.png)
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/118526706-31be9000-b749-11eb-9956-c4bf2e13f968.png" width="292"/>](https://user-images.githubusercontent.com/7511379/118526706-31be9000-b749-11eb-9956-c4bf2e13f968.png)
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/141681075-28f18ea5-e456-4e84-bf64-1b7d9a95324d.png" width="262"/>](https://user-images.githubusercontent.com/7511379/141681075-28f18ea5-e456-4e84-bf64-1b7d9a95324d.png)
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/141681040-b1ad190a-6bc2-4741-bb42-1fb219a0fcab.png" width="250"/>](https://user-images.githubusercontent.com/7511379/141681040-b1ad190a-6bc2-4741-bb42-1fb219a0fcab.png)
|
||||
* Extended support of IPTV.
|
||||
* Import to bouquet(Neutrino WEBTV) from m3u.
|
||||
* Export of bouquets with IPTV services in m3u.
|
||||
* Assignment of EPG from DVB or XML for IPTV services (only Enigma2, experimental).
|
||||
* Preview (playback) of IPTV or other streams directly from the bouquet list.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/118884891-b3f0b500-b8ff-11eb-8717-3588d6e089de.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118884891-b3f0b500-b8ff-11eb-8717-3588d6e089de.png)
|
||||
* Control panel with the ability to view EPG and manage timers (via HTTP API, experimental).
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/118886284-66754780-b901-11eb-9068-29b5a607ccaf.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118886284-66754780-b901-11eb-9068-29b5a607ccaf.png)
|
||||
* Assignment of EPG from DVB or XML for IPTV services (Enigma2 only).
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/141681187-fae4e784-c9e0-43df-b499-4d38e83d6560.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681187-fae4e784-c9e0-43df-b499-4d38e83d6560.png)
|
||||
* Playback of IPTV or other streams directly from the bouquet list.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/141681129-98f78cdc-9a98-46ef-b738-618a327634d4.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681129-98f78cdc-9a98-46ef-b738-618a327634d4.png)
|
||||
* Control panel (via HTTP API).
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/141684475-4511ea4f-b152-42d5-b9c8-f3e1e9a160d0.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141684475-4511ea4f-b152-42d5-b9c8-f3e1e9a160d0.png)
|
||||
* Ability to view EPG and manage timers (via HTTP API).
|
||||
* Simple FTP client (experimental).
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/118527372-e8bb0b80-b749-11eb-9653-4ad64c99a05a.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118527372-e8bb0b80-b749-11eb-9653-4ad64c99a05a.png)
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/141681165-5679c331-72e7-4044-b365-dcdb30b1433c.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681165-5679c331-72e7-4044-b365-dcdb30b1433c.png)
|
||||
|
||||
#### Keyboard shortcuts
|
||||
* **Ctrl + X** - only in bouquet list.
|
||||
@@ -51,8 +50,10 @@ Clipboard is **"rubber"**. There is an accumulation before the insertion!
|
||||
* **Ctrl + D** - load data from receiver.
|
||||
* **Ctrl + U/B** - upload data/bouquets to receiver.
|
||||
* **Ctrl + I** - extra info, details.
|
||||
* **Ctrl + F** - show/hide search bar.
|
||||
* **Ctrl + F** - show search bar.
|
||||
* **Ctrl + Shift + F** - show/hide filter bar.
|
||||
* **Ctrl + T** - show/hide built-in Telnet client.
|
||||
* **Ctrl + Shift + L** - show/hide logging panel.
|
||||
|
||||
For **multiple** selection with the mouse, press and hold the **Ctrl** key!
|
||||
|
||||
@@ -71,13 +72,32 @@ To create a simple **debian package**, you can use the *build-deb.sh.* You can a
|
||||
Users of **LTS** versions of [Ubuntu](https://ubuntu.com/) or distributions based on them can use [PPA](https://launchpad.net/~dmitriy-yefremov/+archive/ubuntu/demon-editor) repository.
|
||||
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 work in this OS, you must use a [separate branch](https://github.com/DYefremov/DemonEditor/tree/experimental-mac). A ready-made package can be downloaded from the [releases](https://github.com/DYefremov/DemonEditor/releases) page.
|
||||
**The functionality and performance of this version may be different from the Linux version!**
|
||||
**This program can be run on macOS.**
|
||||
To run the program on macOS, you need to install [brew](https://brew.sh/).
|
||||
Then install the required components via terminal:
|
||||
```brew install python3 gtk+3 pygobject3 adwaita-icon-theme```
|
||||
```pip3 install requests, pillow```
|
||||
|
||||
Launch is similar to Linux.
|
||||
|
||||
You can also download the ready-made package as a ***.dmg** file from the [releases](https://github.com/DYefremov/DemonEditor/releases) page.
|
||||
Recommended copy the package to the **Application** directory.
|
||||
Perhaps in the security settings it will be necessary to allow the launch of this application!
|
||||
|
||||
* ### MS Windows
|
||||
**Windows users can also run this program.** One way is to use the [MSYS2](https://www.msys2.org/) platform.
|
||||
In addition, you can download a ready-made build (**64-bit**) from the [releases](https://github.com/DYefremov/DemonEditor/releases) page.
|
||||
|
||||
**All builds may contain components distributed under the GPL [v3](http://www.gnu.org/licenses/gpl-3.0.html) or lower license.
|
||||
By downloading and using this packages you agree to the terms of this [license](http://www.gnu.org/licenses/gpl-3.0.html) and the possible inconvenience associated with this!**
|
||||
|
||||
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!
|
||||
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.
|
||||
|
||||
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2.
|
||||
Main supported *lamedb* format is version **4**. Versions **3** and **5** has only **experimental** support! For version **3** is only read mode available. When saving, version **4** format is used instead.
|
||||
|
||||
When using the multiple import feature, from *lamedb* will be taken data **only for channels that are in the selected bouquets!**
|
||||
@@ -85,6 +105,8 @@ If you need full set of the data, including *[satellites, terrestrial, cables].x
|
||||
just load your data via *"File/Open"* and press *"Save"*. When importing separate bouquet files, only those services
|
||||
(excluding IPTV) that are in the **current open lamedb** (main list of services) will be imported.
|
||||
|
||||
**The built-in Telnet client does not support ANSI escape sequences!**
|
||||
|
||||
For streams playback, this app supports [VLC](https://www.videolan.org/vlc/), [MPV](https://mpv.io/) and [GStreamer](https://gstreamer.freedesktop.org/). Depending on your distro, you may need to install additional packages and libraries.
|
||||
#### Command line arguments:
|
||||
* **-l** - write logs to file.
|
||||
|
||||
@@ -1,3 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# Author: Dmitriy Yefremov
|
||||
#
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
@@ -181,7 +209,7 @@ class UtfFTP(FTP):
|
||||
self.send_file(file_name, src, callback)
|
||||
|
||||
def remove_unused_bouquets(self, callback):
|
||||
bq_files = ("userbouquet.", "bouquets.xml", "ubouquets.xml")
|
||||
bq_files = ("userbouquet.", "subbouquet.", "bouquets.xml", "ubouquets.xml")
|
||||
|
||||
for file in filter(lambda f: f.startswith(bq_files), self.nlst()):
|
||||
self.delete_file(file, callback)
|
||||
|
||||
@@ -62,7 +62,7 @@ def get_bouquets(path, s_type):
|
||||
def write_bouquet(path, bq, s_type):
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
writer = BouquetsWriter(path, None)
|
||||
writer.write_bouquet(path + "userbouquet.{}.{}".format(bq.name, bq.type), bq.name, bq.services)
|
||||
writer.write_bouquet(f"{path}userbouquet.{bq.name}.{bq.type}", bq.name, bq.services)
|
||||
elif s_type is SettingsType.NEUTRINO_MP:
|
||||
from .neutrino.bouquets import write_bouquet
|
||||
write_bouquet(path, bq)
|
||||
|
||||
@@ -1,7 +1,37 @@
|
||||
""" Common elements module """
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# Author: Dmitriy Yefremov
|
||||
#
|
||||
|
||||
|
||||
""" Common elements module. """
|
||||
from collections import namedtuple
|
||||
from enum import Enum
|
||||
|
||||
from app.commons import log
|
||||
|
||||
Service = namedtuple("Service", ["flags_cas", "transponder_type", "coded", "service", "locked", "hide", "package",
|
||||
"service_type", "picon", "picon_id", "ssid", "freq", "rate", "pol", "fec",
|
||||
"system", "pos", "data_id", "fav_id", "transponder"])
|
||||
@@ -17,6 +47,10 @@ class BqServiceType(Enum):
|
||||
ALT = "ALT" # Service with alternatives
|
||||
BOUQUET = "BOUQUET" # Sub bouquet.
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
return cls.DEFAULT
|
||||
|
||||
|
||||
Bouquet = namedtuple("Bouquet", ["name", "type", "services", "locked", "hidden", "file"])
|
||||
Bouquet.__new__.__defaults__ = (None, BqServiceType.DEFAULT, [], None, None, None) # For Python3 < 3.7
|
||||
@@ -206,14 +240,15 @@ def get_value_by_name(en, name):
|
||||
|
||||
|
||||
def is_transponder_valid(tr: Transponder):
|
||||
""" Checks transponder validity """
|
||||
""" Checks transponder validity. """
|
||||
try:
|
||||
int(tr.frequency)
|
||||
int(tr.symbol_rate)
|
||||
tr.pls_mode is None or int(tr.pls_mode)
|
||||
tr.pls_code is None or int(tr.pls_code)
|
||||
tr.is_id is None or int(tr.is_id)
|
||||
except TypeError:
|
||||
except (TypeError, ValueError) as e:
|
||||
log(f"Transponder validation error: {e}\n{tr}")
|
||||
return False
|
||||
|
||||
if tr.polarization not in POLARIZATION.values():
|
||||
|
||||
@@ -60,14 +60,14 @@ class BouquetsWriter:
|
||||
self._marker_index = 1
|
||||
self._space_index = 0
|
||||
self._alt_names = set()
|
||||
self._NAME_PATTERN = re.compile("[^\\w_()]+")
|
||||
|
||||
def write(self):
|
||||
line = []
|
||||
pattern = re.compile("[^\\w_()]+")
|
||||
|
||||
for bqs in self._bouquets:
|
||||
line.clear()
|
||||
line.append("#NAME {}\n".format(bqs.name))
|
||||
line.append(f"#NAME {bqs.name}\n")
|
||||
bq_file_names = {b.file for b in bqs.bouquets}
|
||||
count = 1
|
||||
m_count = 0
|
||||
@@ -76,24 +76,29 @@ class BouquetsWriter:
|
||||
bq_name = bq.file
|
||||
if not bq_name:
|
||||
if self._force_bq_names:
|
||||
bq_name = re.sub(pattern, "_", bq.name)
|
||||
bq_name = re.sub(self._NAME_PATTERN, "_", bq.name)
|
||||
else:
|
||||
bq_name = "de{0:02d}".format(count)
|
||||
bq_name = f"de{count:02d}"
|
||||
while bq_name in bq_file_names:
|
||||
count += 1
|
||||
bq_name = "de{0:02d}".format(count)
|
||||
bq_name = f"de{count:02d}"
|
||||
bq_file_names.add(bq_name)
|
||||
|
||||
if BqType(bq.type) is BqType.MARKER:
|
||||
bq_type = BqType(bq.type)
|
||||
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)
|
||||
line.append(self._MARKER.format(m_count, b_name))
|
||||
m_count += 1
|
||||
else:
|
||||
line.append(self._SERVICE.format(2 if bq.type == BqType.RADIO.value else 1, bq_name, bq.type))
|
||||
self.write_bouquet(f"{self._path}userbouquet.{bq_name}.{bq.type}", bq.name, bq.services)
|
||||
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)
|
||||
else:
|
||||
self.write_bouquet(f"{self._path}userbouquet.{bq_name}.{bqs.type}", bq.name, bq.services)
|
||||
line.append(self._SERVICE.format(2 if bqs.type == BqType.RADIO.value else 1, bq_name, bqs.type))
|
||||
|
||||
with open(self._path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
|
||||
with open(f"{self._path}bouquets.{bqs.type}", "w", encoding="utf-8") as file:
|
||||
file.writelines(line)
|
||||
|
||||
def write_bouquet(self, path, name, services):
|
||||
@@ -134,6 +139,18 @@ class BouquetsWriter:
|
||||
with open(path, "w", encoding="utf-8") as file:
|
||||
file.writelines(bouquet)
|
||||
|
||||
def write_sub_bouquet(self, path, file_name, bq, bq_type):
|
||||
bouquet = [f"#NAME {bq.name}\n"]
|
||||
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")
|
||||
|
||||
with open(f"{self._path}userbouquet.{file_name}.{bq_type}", "w", encoding="utf-8") as file:
|
||||
file.writelines(bouquet)
|
||||
|
||||
|
||||
class ServiceType(Enum):
|
||||
SERVICE = "0"
|
||||
@@ -199,7 +216,7 @@ class BouquetsReader:
|
||||
else:
|
||||
s_data = line.split(":")
|
||||
if len(s_data) == 12 and s_data[1] == ServiceType.MARKER.value:
|
||||
b_name = "{}{}".format(_MARKER_PREFIX, s_data[-1].strip())
|
||||
b_name = f"{_MARKER_PREFIX}{s_data[-1].strip()}"
|
||||
bouquets[2].append(Bouquet(b_name, BqType.MARKER.value, [], None, None, line.strip()))
|
||||
else:
|
||||
log(f"Unsupported or invalid data format: [{line}].")
|
||||
@@ -211,14 +228,14 @@ class BouquetsReader:
|
||||
@staticmethod
|
||||
def get_bouquet(path, bq_name, bq_type, prefix="userbouquet"):
|
||||
""" Parsing services ids from bouquet file. """
|
||||
with open(path + "{}.{}.{}".format(prefix, bq_name, bq_type), encoding="utf-8", errors="replace") as file:
|
||||
with open(f"{path}{prefix}.{bq_name}.{bq_type}", encoding="utf-8", errors="replace") as file:
|
||||
chs_list = file.read()
|
||||
services = []
|
||||
srvs = list(filter(None, chs_list.split("\n#SERVICE"))) # filtering ['']
|
||||
# May come across empty[wrong] files!
|
||||
if not srvs:
|
||||
log("Bouquet file 'userbouquet.{}.{}' is empty or wrong!".format(bq_name, bq_type))
|
||||
return "{} [empty]".format(bq_name), services
|
||||
log(f"Bouquet file 'userbouquet.{bq_name}.{bq_type}' is empty or wrong!")
|
||||
return f"{bq_name} [empty]", services
|
||||
|
||||
bq_name = srvs.pop(0)
|
||||
|
||||
@@ -226,7 +243,7 @@ class BouquetsReader:
|
||||
srv_data = srv.strip().split(":")
|
||||
data_len = len(srv_data)
|
||||
if data_len < 10:
|
||||
log("The bouquet [{}] service [{}] has the wrong data format: [{}]".format(bq_name, num, srv))
|
||||
log(f"The bouquet [{bq_name}] service [{num}] has the wrong data format: [{srv}]")
|
||||
continue
|
||||
|
||||
s_type = ServiceType(srv_data[1])
|
||||
@@ -254,7 +271,7 @@ class BouquetsReader:
|
||||
desc = desc.lstrip(":").strip() if desc else srv_data[-1].strip()
|
||||
services.append(BouquetService(desc, BqServiceType.IPTV, srv, num))
|
||||
else:
|
||||
fav_id = "{}:{}:{}:{}".format(srv_data[3], srv_data[4], srv_data[5], srv_data[6])
|
||||
fav_id = f"{srv_data[3]}:{srv_data[4]}:{srv_data[5]}:{srv_data[6]}"
|
||||
name = None
|
||||
if data_len == 12:
|
||||
name, sep, desc = str(srv_data[-1]).partition("\n#DESCRIPTION")
|
||||
|
||||
@@ -52,7 +52,7 @@ class LameDbReader:
|
||||
""" Lamedb parser class.
|
||||
|
||||
Reads and parses the Enigma2 lamedb[5] file.
|
||||
Supports versions 3, 4 and 5..
|
||||
Supports versions 3, 4 and 5.
|
||||
"""
|
||||
__slots__ = ["_path", "_fmt"]
|
||||
|
||||
@@ -99,7 +99,7 @@ class LameDbReader:
|
||||
try:
|
||||
data = str(file.read())
|
||||
except UnicodeDecodeError as e:
|
||||
log("lamedb parse error: " + str(e))
|
||||
log(f"lamedb parse error: {e}")
|
||||
else:
|
||||
return self.get_services_list(data)
|
||||
|
||||
@@ -115,7 +115,7 @@ class LameDbReader:
|
||||
for line in lns:
|
||||
if line.startswith("s:"):
|
||||
srv_data = line.strip("s:").split(",", 2)
|
||||
srv_data[1] = srv_data[1].strip("\"")
|
||||
srv_data[1] = srv_data[1].strip("\"\n")
|
||||
data_len = len(srv_data)
|
||||
if data_len == 3:
|
||||
srv_data[2] = srv_data[2].strip()
|
||||
@@ -129,7 +129,7 @@ class LameDbReader:
|
||||
tr, srv = data[0].strip("t:"), data[1].strip().replace(":", " ", 1)
|
||||
trs[tr] = srv
|
||||
else:
|
||||
log("Error while parsing transponder data [ver. 5] for line: {}".format(line))
|
||||
log(f"Error while parsing transponder data [ver. 5] for line: {line}")
|
||||
|
||||
return self.parse_services(srvs, trs)
|
||||
|
||||
@@ -164,9 +164,9 @@ class LameDbReader:
|
||||
srv_type = int(data[4])
|
||||
transponder_id = f"{data[1]}:{tid}:{nid}"
|
||||
transponder = transponders.get(transponder_id, None)
|
||||
|
||||
tid = tid.lstrip(sp).upper()
|
||||
nid = nid.lstrip(sp).upper()
|
||||
# The tid and nid values can be 0.
|
||||
tid = tid.lstrip(sp).upper() or "0"
|
||||
nid = nid.lstrip(sp).upper() or "0"
|
||||
ssid = str(data[0]).lstrip(sp).upper()
|
||||
onid = str(data[1]).lstrip(sp).upper()
|
||||
# For comparison in bouquets. Needed in upper case!!!
|
||||
@@ -188,7 +188,7 @@ class LameDbReader:
|
||||
tr_type = TrType(tr_type)
|
||||
tr = tr.split(_SEP)
|
||||
service_type = SERVICE_TYPE.get(data[4], SERVICE_TYPE["-2"])
|
||||
# Removing all non printable symbols!
|
||||
# Removing all non-printable symbols!
|
||||
srv_name = "".join(c for c in srv[1] if c.isprintable())
|
||||
freq = tr[0]
|
||||
rate = tr[1]
|
||||
@@ -258,18 +258,17 @@ class LameDbReader:
|
||||
tr_set = set()
|
||||
for srv in services:
|
||||
data_id = str(srv.data_id).split(_SEP)
|
||||
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
|
||||
tr_id = f"{data_id[1]}:{data_id[2]}:{data_id[3]}"
|
||||
if tr_id not in tr_set:
|
||||
transponder = "{}\n\t{}\n/\n".format(tr_id, srv.transponder)
|
||||
tr_lines.append(transponder)
|
||||
tr_lines.append(f"{tr_id}\n\t{srv.transponder}\n/\n")
|
||||
tr_set.add(tr_id)
|
||||
# Services
|
||||
services_lines.append("{}\n{}\n{}\n".format(srv.data_id, srv.service, srv.flags_cas))
|
||||
services_lines.append(f"{srv.data_id}\n{srv.service}\n{srv.flags_cas}\n")
|
||||
|
||||
tr_lines.sort()
|
||||
lines.extend(tr_lines)
|
||||
lines.extend(services_lines)
|
||||
lines.append("end\n" + _END_LINE)
|
||||
lines.append(f"end\n{_END_LINE}")
|
||||
|
||||
return lines
|
||||
|
||||
@@ -324,13 +323,13 @@ class LameDbWriter:
|
||||
|
||||
for srv in self._services:
|
||||
data_id = str(srv.data_id).split(_SEP)
|
||||
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
|
||||
tr_set.add("t:{},{}\n".format(tr_id, srv.transponder.replace(" ", ":", 1)))
|
||||
tr_id = f"{data_id[1]}:{data_id[2]}:{data_id[3]}"
|
||||
tr_set.add(f"t:{tr_id},{srv.transponder.replace(' ', ':', 1)}\n")
|
||||
# Removing empty packages
|
||||
flags = list(filter(lambda x: x != "p:", srv.flags_cas.split(",")))
|
||||
flags = ",".join(flags)
|
||||
flags = "," + flags if flags else ""
|
||||
services_lines.append("s:{},\"{}\"{}\n".format(srv.data_id, srv.service, flags))
|
||||
services_lines.append(f"s:{srv.data_id},\"{srv.service}\"{flags}\n")
|
||||
|
||||
lines.extend(sorted(tr_set))
|
||||
lines.extend(services_lines)
|
||||
|
||||
@@ -112,12 +112,12 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None):
|
||||
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], st, picon, p_id, *s_aggr, url, fav_id, None)
|
||||
services.append(srv)
|
||||
else:
|
||||
log("*.m3u* parse error ['{}']: name[{}], url[{}], fav id[{}]".format(path, name, url, fav_id))
|
||||
log(f"*.m3u* parse error ['{path}']: name[{name}], url[{url}], fav id[{fav_id}]")
|
||||
|
||||
return services
|
||||
|
||||
|
||||
def export_to_m3u(path, bouquet, s_type):
|
||||
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.*?)::::.*")
|
||||
lines = ["#EXTM3U\n"]
|
||||
current_grp = None
|
||||
@@ -128,15 +128,17 @@ def export_to_m3u(path, bouquet, s_type):
|
||||
res = re.match(pattern, s.data)
|
||||
if not res:
|
||||
continue
|
||||
data = res.group(1)
|
||||
lines.append("#EXTINF:-1,{}\n".format(s.name))
|
||||
if current_grp:
|
||||
lines.append(current_grp)
|
||||
lines.append("{}\n".format(unquote(data.strip())))
|
||||
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:
|
||||
current_grp = "#EXTGRP:{}\n".format(s.name)
|
||||
current_grp = f"#EXTGRP:{s.name}\n"
|
||||
elif s_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")
|
||||
|
||||
with open(path + "{}.m3u".format(bouquet.name), "w", encoding="utf-8") as file:
|
||||
with open(f"{path}{bouquet.name}.m3u", "w", encoding="utf-8") as file:
|
||||
file.writelines(lines)
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2022 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
|
||||
@@ -676,6 +676,14 @@ class Settings:
|
||||
def dark_mode(self, value):
|
||||
self._settings["dark_mode"] = value
|
||||
|
||||
@property
|
||||
def display_picons(self):
|
||||
return self._settings.get("display_picons", True)
|
||||
|
||||
@display_picons.setter
|
||||
def display_picons(self, value):
|
||||
self._settings["display_picons"] = value
|
||||
|
||||
@property
|
||||
def alternate_layout(self):
|
||||
return self._settings.get("alternate_layout", IS_DARWIN)
|
||||
@@ -711,7 +719,7 @@ class Settings:
|
||||
@property
|
||||
@lru_cache(1)
|
||||
def themes_path(self):
|
||||
return "{}{}.themes{}".format(HOME_PATH, SEP, SEP)
|
||||
return f"{HOME_PATH}{SEP}.themes{SEP}"
|
||||
|
||||
@property
|
||||
def icon_theme(self):
|
||||
@@ -724,7 +732,7 @@ class Settings:
|
||||
@property
|
||||
@lru_cache(1)
|
||||
def icon_themes_path(self):
|
||||
return "{}{}.icons{}".format(HOME_PATH, SEP, SEP)
|
||||
return f"{HOME_PATH}{SEP}.icons{SEP}"
|
||||
|
||||
@property
|
||||
def is_darwin(self):
|
||||
|
||||
@@ -1,4 +1,32 @@
|
||||
""" Module for working with epg.dat file """
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 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
|
||||
#
|
||||
|
||||
|
||||
""" Module for working with epg.dat file. """
|
||||
import struct
|
||||
from datetime import datetime
|
||||
from xml.dom.minidom import parse, Node, Document
|
||||
@@ -11,7 +39,7 @@ class EPG:
|
||||
@staticmethod
|
||||
def get_epg_refs(path):
|
||||
""" The read algorithm was taken from the eEPGCache::load() function from this source:
|
||||
https://github.com/OpenPLi/enigma2/blob/44d9b92f5260c7de1b3b3a1b9a9cbe0f70ca4bf0/lib/dvb/epgcache.cpp#L1300
|
||||
https://github.com/OpenPLi/enigma2/blob/develop/lib/dvb/epgcache.cpp#L955
|
||||
"""
|
||||
refs = set()
|
||||
|
||||
@@ -21,17 +49,23 @@ class EPG:
|
||||
raise ValueError("Epg file has incorrect byte order!")
|
||||
|
||||
header = f.read(13).decode()
|
||||
if header != "ENIGMA_EPG_V7":
|
||||
if header == "ENIGMA_EPG_V7":
|
||||
epg_ver = 7
|
||||
elif header == "ENIGMA_EPG_V8":
|
||||
epg_ver = 8
|
||||
else:
|
||||
raise ValueError("Unsupported format of epd.dat file!")
|
||||
|
||||
channels_count = struct.unpack("<I", f.read(4))[0]
|
||||
_len_read_size = 3 if epg_ver == 8 else 2
|
||||
_type_read_str = f"<{'H' if epg_ver == 8 else 'B'}B"
|
||||
|
||||
for i in range(channels_count):
|
||||
sid, nid, tsid, events_size = struct.unpack("<IIII", f.read(16))
|
||||
service_id = "{:X}:{:X}:{:X}".format(sid, tsid, nid)
|
||||
service_id = f"{sid:X}:{tsid:X}:{nid:X}"
|
||||
|
||||
for j in range(events_size):
|
||||
_type, _len = struct.unpack("<BB", f.read(2))
|
||||
_type, _len = struct.unpack(_type_read_str, f.read(_len_read_size))
|
||||
f.read(10)
|
||||
n_crc = (_len - 10) // 4
|
||||
if n_crc > 0:
|
||||
@@ -90,14 +124,14 @@ class ChannelsParser:
|
||||
srv_type = srv.type
|
||||
if srv_type is BqServiceType.IPTV:
|
||||
channel_child = doc.createElement("channel")
|
||||
channel_child.setAttribute("id", str(srv.num))
|
||||
channel_child.setAttribute("id", srv.name)
|
||||
data = srv.data.strip().split(":")
|
||||
channel_child.appendChild(doc.createTextNode(":".join(data[:10])))
|
||||
comment = doc.createComment(srv.name)
|
||||
lines.append("{} {}\n".format(str(channel_child.toxml()), str(comment.toxml())))
|
||||
lines.append(f"{channel_child.toxml()} {comment.toxml()}\n")
|
||||
elif srv_type is BqServiceType.MARKER:
|
||||
comment = doc.createComment(srv.name)
|
||||
lines.append("{}\n".format(str(comment.toxml())))
|
||||
lines.append(f"{comment.toxml()}\n")
|
||||
|
||||
lines.append("</channels>")
|
||||
doc.unlink()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2022 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
|
||||
@@ -57,7 +57,7 @@ class PiconsCzDownloader:
|
||||
_PERM_URL = "https://picon.cz/download/7337"
|
||||
_BASE_URL = "https://picon.cz/download/"
|
||||
_BASE_LOGO_URL = "https://picon.cz/picon/0/"
|
||||
_HEADER = {"User-Agent": "DemonEditor/2.0.0", "Referer": ""}
|
||||
_HEADER = {"User-Agent": "DemonEditor/2.1.0", "Referer": ""}
|
||||
_LINK_PATTERN = re.compile(r"((.*)-\d+x\d+)-(.*)_by_chocholousek.7z$")
|
||||
_FILE_PATTERN = re.compile(b"\\s+(1_.*\\.png).*")
|
||||
|
||||
@@ -130,7 +130,7 @@ class PiconsCzDownloader:
|
||||
# TODO: think about https://github.com/miurahr/py7zr
|
||||
exe = "7z"
|
||||
if IS_DARWIN and GTK_PATH:
|
||||
exe = "./7z"
|
||||
exe = "./7zr"
|
||||
|
||||
if IS_LINUX and not os.path.isfile(f"/usr/bin/{exe}"):
|
||||
raise PiconsError("7-zip [7z] archiver not found!")
|
||||
@@ -219,7 +219,8 @@ class PiconsCzDownloader:
|
||||
"picontransparentdark": "td220",
|
||||
"piconoled": "o96",
|
||||
"piconblack80": "b50",
|
||||
"piconblack3d": "b50"
|
||||
"piconblack3d": "b50",
|
||||
"piconwin11": "win11220"
|
||||
}
|
||||
|
||||
def get_name_map(self):
|
||||
|
||||
@@ -1,6 +1,34 @@
|
||||
""" Module for downloading satellites, transponders ans services from the web.
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 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
|
||||
#
|
||||
|
||||
Sources: www.flysat.com, www.lyngsat.com.
|
||||
|
||||
""" Module for downloading satellites, transponders and services from the Web.
|
||||
|
||||
Sources: www.flysat.com, www.lyngsat.com, www.kingofsat.net.
|
||||
Replaces or updates the current satellites.xml file.
|
||||
"""
|
||||
import re
|
||||
@@ -15,10 +43,11 @@ from app.eparser.ecommons import (PLS_MODE, get_key_by_value, FEC, SYSTEM, POLAR
|
||||
Service, CAS)
|
||||
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (Linux x86_64; rv:92.0) Gecko/20100101 Firefox/92.0"}
|
||||
_TIMEOUT = 10
|
||||
|
||||
|
||||
class SatelliteSource(Enum):
|
||||
FLYSAT = ("https://www.flysat.com/satlist.php",)
|
||||
FLYSAT = ("https://www.flysat.com/en/satellitelist",)
|
||||
LYNGSAT = ("https://www.lyngsat.com/asia.html", "https://www.lyngsat.com/europe.html",
|
||||
"https://www.lyngsat.com/atlantic.html", "https://www.lyngsat.com/america.html")
|
||||
KINGOFSAT = ("https://en.kingofsat.net/satellites.php",)
|
||||
@@ -38,10 +67,10 @@ class Cell:
|
||||
self._img = img
|
||||
|
||||
def __repr__(self):
|
||||
return "Cell({}, {}, {})".format(self._text, self._url, self._img)
|
||||
return f"Cell({self._text}, {self._url}, {self._img})"
|
||||
|
||||
def __str__(self):
|
||||
return "<Cell(text={}, link={}, img={})>".format(self._text, self._url, self._img)
|
||||
return f"<Cell(text={self._text}, link={self._url}, img={self._img})>"
|
||||
|
||||
def __iter__(self):
|
||||
return (x for x in (self._text, self._url, self._img))
|
||||
@@ -92,6 +121,7 @@ class SatellitesParser(HTMLParser):
|
||||
self._current_cell = []
|
||||
self._rows = []
|
||||
self._source = source
|
||||
self.pls_modes = {v: k for k, v in PLS_MODE.items()}
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "td":
|
||||
@@ -134,9 +164,9 @@ class SatellitesParser(HTMLParser):
|
||||
|
||||
for src in SatelliteSource.get_sources(self._source):
|
||||
try:
|
||||
request = requests.get(url=src, headers=_HEADERS)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
log(repr(e))
|
||||
request = requests.get(url=src, headers=_HEADERS, timeout=_TIMEOUT)
|
||||
except requests.exceptions.RequestException as e:
|
||||
log(f"Getting satellite list error: {repr(e)}")
|
||||
return []
|
||||
else:
|
||||
reason = request.reason
|
||||
@@ -147,63 +177,99 @@ class SatellitesParser(HTMLParser):
|
||||
|
||||
if self._rows:
|
||||
if self._source is SatelliteSource.FLYSAT:
|
||||
def get_sat(r):
|
||||
return r[1], self.parse_position(r[2]), r[3], r[0], False
|
||||
|
||||
return list(map(get_sat, filter(lambda x: all(x) and len(x) == 5, self._rows)))
|
||||
return self.get_satellites_for_fly_sat()
|
||||
elif self._source is SatelliteSource.LYNGSAT:
|
||||
base_url = "https://www.lyngsat.com/"
|
||||
sats = []
|
||||
cur_pos = "0"
|
||||
for row in filter(lambda x: 3 < len(x) < 8, self._rows):
|
||||
if not row[0]:
|
||||
row = row[1:]
|
||||
|
||||
pos = self.parse_position(row[1])
|
||||
if not self.POS_PAT.match(pos):
|
||||
if len(row) == 4 and row[0].endswith(".html"):
|
||||
sats.append((row[1], cur_pos, row[-2], base_url + row[0], False))
|
||||
continue
|
||||
|
||||
sats.append((row[-3], pos, row[-2], base_url + row[0], False))
|
||||
cur_pos = pos
|
||||
return sats
|
||||
return self.get_satellites_for_lyng_sat()
|
||||
elif source is SatelliteSource.KINGOFSAT:
|
||||
def get_sat(r):
|
||||
return r[3], self.parse_position(r[1]), None, r[2], False
|
||||
|
||||
return list(map(get_sat, filter(lambda x: len(x) == 17, self._rows)))
|
||||
return self.get_satellites_for_king_of_sat()
|
||||
|
||||
def get_satellite(self, sat):
|
||||
pos = sat[1]
|
||||
return Satellite(name="{} {}".format(pos, sat[0]),
|
||||
flags="0",
|
||||
return Satellite(name=f"{pos} {sat[0]}", flags="0",
|
||||
position=self.get_position(pos.replace(".", "")),
|
||||
transponders=self.get_transponders(sat[3]))
|
||||
|
||||
def get_satellites_for_fly_sat(self):
|
||||
sat_pat = re.compile(r"https://.*/satellite/.+")
|
||||
pos_pat = re.compile(r"https://.*/satellite/position/.+")
|
||||
names = []
|
||||
pos = ""
|
||||
pos_url = ""
|
||||
|
||||
def normalize_pos(p):
|
||||
return f"{float(p[:-1])}{p[-1]}" if "." not in p else p
|
||||
|
||||
def get_sat(r):
|
||||
nonlocal pos
|
||||
nonlocal pos_url
|
||||
# Uniting satellites in position.
|
||||
if re.match(pos_pat, r[2]):
|
||||
pos_url = r[2]
|
||||
name = r[1]
|
||||
pos = normalize_pos(self.parse_position(r[3]))
|
||||
|
||||
names.append(name)
|
||||
return name, pos, r[5], r[0], False
|
||||
|
||||
r_size = len(r)
|
||||
if r_size == 5:
|
||||
name = r[1]
|
||||
names.append(name)
|
||||
return name, pos, r[3], r[0], False
|
||||
if r_size == 6:
|
||||
if names:
|
||||
name = "/".join(names)
|
||||
names.clear()
|
||||
return name, pos, None, pos_url, False
|
||||
|
||||
return r[1], normalize_pos(self.parse_position(r[2])), r[4], r[0], False
|
||||
|
||||
return list(filter(None, map(get_sat, filter(lambda row: row and re.match(sat_pat, row[0]), self._rows))))
|
||||
|
||||
def get_satellites_for_lyng_sat(self):
|
||||
base_url = "https://www.lyngsat.com/"
|
||||
sats = []
|
||||
cur_pos = "0"
|
||||
for row in filter(lambda x: 3 < len(x) < 8, self._rows):
|
||||
if not row[0]:
|
||||
row = row[1:]
|
||||
|
||||
pos = self.parse_position(row[1])
|
||||
if not self.POS_PAT.match(pos):
|
||||
if len(row) == 4 and row[0].endswith(".html"):
|
||||
sats.append((row[1], cur_pos, row[-2], base_url + row[0], False))
|
||||
continue
|
||||
|
||||
sats.append((row[-3], pos, row[-2], base_url + row[0], False))
|
||||
cur_pos = pos
|
||||
return sats
|
||||
|
||||
def get_satellites_for_king_of_sat(self):
|
||||
def get_sat(r):
|
||||
return r[3], self.parse_position(r[1]), None, r[2], False
|
||||
|
||||
return list(map(get_sat, filter(lambda x: len(x) == 17, self._rows)))
|
||||
|
||||
@staticmethod
|
||||
def parse_position(pos_str):
|
||||
return "".join(c for c in pos_str if c.isdigit() or c.isalpha() or c == ".")
|
||||
|
||||
@staticmethod
|
||||
def get_position(pos):
|
||||
return "{}{}".format("-" if pos[-1] == "W" else "", pos[:-1])
|
||||
return f"{'-' if pos[-1] == 'W' else ''}{pos[:-1]}"
|
||||
|
||||
def get_transponders(self, sat_url):
|
||||
""" Getting transponders(sorted by frequency). """
|
||||
self._rows.clear()
|
||||
trs = []
|
||||
|
||||
url = sat_url
|
||||
if self._source is SatelliteSource.FLYSAT:
|
||||
url = "https://www.flysat.com/" + sat_url
|
||||
elif self._source is SatelliteSource.KINGOFSAT:
|
||||
url = "https://en.kingofsat.net/" + sat_url
|
||||
if self._source is SatelliteSource.KINGOFSAT:
|
||||
sat_url = "https://en.kingofsat.net/" + sat_url
|
||||
|
||||
try:
|
||||
request = requests.get(url=url, headers=_HEADERS)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
log("Getting transponders error: {}".format(e))
|
||||
request = requests.get(url=sat_url, headers=_HEADERS, timeout=_TIMEOUT)
|
||||
except requests.exceptions.RequestException as e:
|
||||
log(f"Getting transponders error: {e}")
|
||||
else:
|
||||
if request.status_code == 200:
|
||||
self.feed(request.text)
|
||||
@@ -214,87 +280,89 @@ class SatellitesParser(HTMLParser):
|
||||
elif self._source is SatelliteSource.KINGOFSAT:
|
||||
self.get_transponders_for_king_of_sat(trs)
|
||||
else:
|
||||
log("SatellitesParser [get transponders] error: {} {}".format(url, request.reason))
|
||||
log(f"SatellitesParser [get transponders] error: {sat_url} {request.reason}")
|
||||
|
||||
return sorted(trs, key=lambda x: int(x.frequency))
|
||||
|
||||
def get_transponders_for_fly_sat(self, trs):
|
||||
""" Parsing transponders for FlySat """
|
||||
pls_pattern = re.compile("(PLS:)+ (Root|Gold|Combo)+ (\\d+)?")
|
||||
is_id_pattern = re.compile("(Stream) (\\d+)")
|
||||
pls_modes = {v: k for k, v in PLS_MODE.items()}
|
||||
""" Parsing transponders for FlySat. """
|
||||
frq_pol_pattern = re.compile(r"(\d{4,5})+\s+([RLHV]).*(DVB-S[2]?)/(.+PSK)?.*")
|
||||
pls_pattern = re.compile(r".*PLS\s+(Root|Gold|Combo)+\s(\d+)?")
|
||||
is_id_pattern = re.compile(r"Stream\s(\d+)")
|
||||
sr_fec_pattern = re.compile(r"(\d{4,5})+\s+(\d+/\d+).*")
|
||||
n_trs = []
|
||||
|
||||
if self._rows:
|
||||
zeros = "000"
|
||||
is_ids = []
|
||||
for r in self._rows:
|
||||
if len(r) == 1:
|
||||
row_len = len(r)
|
||||
if row_len == 1:
|
||||
is_ids.extend(re.findall(is_id_pattern, r[0]))
|
||||
continue
|
||||
if len(r) < 3:
|
||||
if row_len < 12:
|
||||
continue
|
||||
data = r[2].split(" ")
|
||||
if len(data) != 2:
|
||||
continue
|
||||
sr, fec = data
|
||||
data = r[1].split(" ")
|
||||
if len(data) < 3:
|
||||
continue
|
||||
freq, pol, sys = data[0], data[1], data[2]
|
||||
sys = sys.split("/")
|
||||
if len(sys) != 2:
|
||||
continue
|
||||
sys, mod = sys
|
||||
mod = "QPSK" if sys == "DVB-S" else mod
|
||||
|
||||
pls = re.findall(pls_pattern, r[1])
|
||||
freq = re.findall(frq_pol_pattern, r[2])
|
||||
if not freq:
|
||||
continue
|
||||
freq, pol, sys, mod = freq[0]
|
||||
|
||||
sr_fec = re.match(sr_fec_pattern, r[3])
|
||||
if not sr_fec:
|
||||
continue
|
||||
sr, fec = sr_fec.group(1), sr_fec.group(2)
|
||||
|
||||
pls = re.match(pls_pattern, r[2])
|
||||
pls_code = None
|
||||
pls_mode = None
|
||||
|
||||
if pls:
|
||||
pls_code = pls[0][2]
|
||||
pls_mode = pls_modes.get(pls[0][1], None)
|
||||
pls_mode = self.pls_modes.get(pls.group(1), None)
|
||||
pls_code = pls.group(2)
|
||||
|
||||
if is_ids:
|
||||
tr = trs.pop()
|
||||
for index, is_id in enumerate(is_ids):
|
||||
tr = tr._replace(is_id=is_id[1])
|
||||
tr = tr._replace(is_id=is_id)
|
||||
if is_transponder_valid(tr):
|
||||
n_trs.append(tr)
|
||||
else:
|
||||
tr = Transponder(freq + zeros, sr + zeros, pol, fec, sys, mod, pls_mode, pls_code, None)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
|
||||
tr = Transponder(f"{freq}000", f"{sr}000", pol, fec, sys, mod, pls_mode, pls_code, None)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
|
||||
is_ids.clear()
|
||||
trs.extend(n_trs)
|
||||
|
||||
def get_transponders_for_lyng_sat(self, trs):
|
||||
""" Parsing transponders for LyngSat. """
|
||||
frq_pol_pattern = re.compile("(\\d{4,5})\\s+([RLHV]).*")
|
||||
sr_fec_pattern = re.compile(r"(DVB-S[2]?)\s+(.+PSK)?.*?(\d+)\s+(\d/\d)\s*(?:T2-MI\s+PLP\s+(\d+))?.*")
|
||||
zeros = "000"
|
||||
pls_mode, pls_code, pls_id = None, None, None
|
||||
frq_pol_pattern = re.compile(r"(\d{4,5})\s+([RLHV]).*")
|
||||
sr_fec_pattern = re.compile((r"(DVB-S[2]?)\s+(.+PSK)?.*?(\d+)\s+(\d/\d)\s?"
|
||||
r"(?:T2-MI\s+PLP\s+(\d+))?.*"
|
||||
r"?(?:PLS\s+(Root|Gold|Combo)\s+(\d+))?"
|
||||
r"(?:.*Stream\s+(\d+))?.*"))
|
||||
|
||||
for row in filter(lambda x: len(x) > 8, self._rows):
|
||||
for frq in row[1], row[2], row[3]:
|
||||
freq = re.match(frq_pol_pattern, frq)
|
||||
if freq:
|
||||
for freq in row[1], row[2], row[3]:
|
||||
res = re.match(frq_pol_pattern, freq)
|
||||
if res:
|
||||
break
|
||||
if not freq:
|
||||
if not res:
|
||||
continue
|
||||
|
||||
frq, pol = freq.group(1), freq.group(2)
|
||||
srf = " ".join(row[3:5])
|
||||
sr_fec = re.search(sr_fec_pattern, srf)
|
||||
if not sr_fec:
|
||||
freq, pol = res.group(1), res.group(2)
|
||||
res = re.search(sr_fec_pattern, row[3])
|
||||
if not res:
|
||||
continue
|
||||
|
||||
sys, mod, sr, fec = sr_fec.group(1), sr_fec.group(2), sr_fec.group(3), sr_fec.group(4)
|
||||
sys, mod, sr, fec = res.group(1), res.group(2), res.group(3), res.group(4)
|
||||
mod = mod.strip() if mod else "Auto"
|
||||
pls_id = sr_fec.group(5)
|
||||
plp, pls_mode, pls_code, is_id = res.group(5), res.group(6), res.group(7), res.group(8)
|
||||
pls_mode = self.pls_modes.get(pls_mode, None)
|
||||
|
||||
tr = Transponder(frq + zeros, sr + zeros, pol, fec, sys, mod, pls_mode, pls_code, pls_id)
|
||||
if plp is not None:
|
||||
log(f"Detected T2-MI transponder! [{freq} {sr} {pol}] ")
|
||||
|
||||
tr = Transponder(f"{freq}000", f"{sr}000", pol, fec, sys, mod, pls_mode, pls_code, is_id)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
|
||||
@@ -303,19 +371,37 @@ class SatellitesParser(HTMLParser):
|
||||
|
||||
Since the *.ini file contains incomplete information, it is not used.
|
||||
"""
|
||||
zeros = "000"
|
||||
pat = re.compile(
|
||||
r"(\d+).00\s+([RLHV])\s+(DVB-S[2]?)\s+(?:T2-MI, PLP (\d+)\s+)?(.*PSK).*?(?:Stream\s+(\d+))?\s+(\d+)\s+(\d+/\d+)$")
|
||||
sys_pat = re.compile(r"(DVB-S[2]?)\s?(?:T2-MI,\s+PLP\s+(\d+))?.*?(?:PLS:\s+(Root|Gold|Combo)\+(\d+))?")
|
||||
mod_pat = re.compile(r"(.*PSK).*?(?:.*Stream\s+(\d+))?.*")
|
||||
sr_fec_pattern = re.compile(r"(\d{4,5})+\s+(\d+/\d+).*")
|
||||
|
||||
for row in filter(lambda r: len(r) == 16 and self.POS_PAT.match(r[0]), self._rows):
|
||||
res = pat.search(" ".join((row[0], row[2], row[3], row[8], row[9], row[10])))
|
||||
if res:
|
||||
freq, sr, pol, fec, sys = res.group(1), res.group(7), res.group(2), res.group(8), res.group(3)
|
||||
mod, pls_id, pls_code = res.group(5), res.group(4), res.group(6)
|
||||
freq, pol = row[2].replace(".", "0"), row[3]
|
||||
if not freq.isdigit() or pol not in "VHLR":
|
||||
continue
|
||||
|
||||
tr = Transponder(freq + zeros, sr + zeros, pol, fec, sys, mod, None, pls_code, pls_id)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
res = re.match(sys_pat, row[8])
|
||||
if not res:
|
||||
continue
|
||||
sys, t2_mi, pls_id, pls_code = res.group(1), res.group(2), res.group(3), res.group(4)
|
||||
pls_id = self.pls_modes.get(pls_id, None)
|
||||
|
||||
res = re.match(mod_pat, row[9])
|
||||
if not res:
|
||||
continue
|
||||
mod, is_id = res.group(1), res.group(2)
|
||||
|
||||
res = re.match(sr_fec_pattern, row[10])
|
||||
if not res:
|
||||
continue
|
||||
sr, fec = res.group(1), res.group(2)
|
||||
|
||||
if t2_mi:
|
||||
log(f"Detected T2-MI transponder! [{freq} {sr} {pol}] ")
|
||||
|
||||
tr = Transponder(freq, f"{sr}000", pol, fec, sys, mod, pls_id, pls_code, is_id)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
|
||||
|
||||
class ServicesParser(HTMLParser):
|
||||
@@ -329,16 +415,27 @@ class ServicesParser(HTMLParser):
|
||||
"MPEG-4": "22", "HEVC SD": "22", "MPEG-4/HD": "25", "MPEG-4 HD": "25", "MPEG-4 HD 1080": "25",
|
||||
"MPEG-4 HD 720": "25", "HEVC HD": "25", "HEVC/HD": "25", "HEVC": "31", "HEVC/UHD": "31",
|
||||
"HEVC UHD": "31", "HEVC UHD 4K": "31", "3": "Data"}
|
||||
self._TR_PAT = re.compile(
|
||||
r".*?(\d+)\s+([RLHV]).*(DVB-S[2]?)/?(.*PSK)?\s(T2-MI)?\s?SR-FEC:\s(\d+)-(\d/\d)\s+.*ONID-TID:\s+(\d+)-(\d+).*")
|
||||
self._POS_PAT = re.compile(r".*?(\d+\.\d°[EW]).*")
|
||||
|
||||
self._TR = "s {}000:{}000:{}:{}:{}:{}:{}:{}"
|
||||
self._S2_TR = "{}:{}:{}:{}"
|
||||
|
||||
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._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+))?.*"
|
||||
r"?(?:PLS:\s+(Root|Gold|Combo)\+(\d+))?"
|
||||
r"\s+(.*PSK).*?(?:.*Stream\s+(\d+))?.*"))
|
||||
|
||||
self._parse_html_entities = entities
|
||||
self._separator = separator
|
||||
self._is_td = False
|
||||
self._is_th = False
|
||||
self._is_mux_div = False
|
||||
self._current_row = []
|
||||
self._current_cell_text = []
|
||||
self._current_cell = Cell()
|
||||
@@ -346,6 +443,7 @@ class ServicesParser(HTMLParser):
|
||||
self._source = source
|
||||
self._t_url = ""
|
||||
self._use_short_names = True
|
||||
self._pls_modes = {v: k for k, v in PLS_MODE.items()}
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
@@ -389,12 +487,18 @@ class ServicesParser(HTMLParser):
|
||||
self._current_cell.img = img_link
|
||||
elif self._source is SatelliteSource.KINGOFSAT:
|
||||
self._current_cell.img = img_link
|
||||
elif tag == "div" and self._source is SatelliteSource.LYNGSAT:
|
||||
self._is_mux_div = bool(list(filter(lambda at: at[-1] == "mux-header", attrs)))
|
||||
|
||||
def handle_data(self, data):
|
||||
""" Save content to a cell """
|
||||
if self._is_td or self._is_th:
|
||||
self._current_cell_text.append(data.strip())
|
||||
|
||||
if self._is_mux_div:
|
||||
self._current_cell.url = data.strip()
|
||||
self._is_mux_div = False
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == "td":
|
||||
self._is_td = False
|
||||
@@ -414,21 +518,24 @@ class ServicesParser(HTMLParser):
|
||||
self._current_row = []
|
||||
|
||||
def error(self, message):
|
||||
log("ServicesParser error: {}".format(message))
|
||||
log(f"ServicesParser error: {message}")
|
||||
|
||||
def init_data(self, url):
|
||||
""" Initializes data for the given URL. """
|
||||
if self._source not in (SatelliteSource.LYNGSAT, SatelliteSource.KINGOFSAT):
|
||||
raise ValueError("Unsupported source: {}!".format(self._source.name))
|
||||
raise ValueError(f"Unsupported source: {self._source.name}!")
|
||||
|
||||
self._rows.clear()
|
||||
request = requests.get(url=url, headers=_HEADERS)
|
||||
reason = request.reason
|
||||
|
||||
if reason == "OK":
|
||||
self.feed(request.text)
|
||||
try:
|
||||
request = requests.get(url=url, headers=_HEADERS, timeout=_TIMEOUT)
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise ValueError(e)
|
||||
else:
|
||||
raise ValueError(reason)
|
||||
reason = request.reason
|
||||
if reason == "OK":
|
||||
self.feed(request.text)
|
||||
else:
|
||||
raise ValueError(reason)
|
||||
|
||||
def get_transponders_links(self, sat_url):
|
||||
""" Returns transponder links. """
|
||||
@@ -467,13 +574,13 @@ class ServicesParser(HTMLParser):
|
||||
self.init_data(tr_url)
|
||||
except ValueError as e:
|
||||
log(e)
|
||||
return []
|
||||
else:
|
||||
if self._source is SatelliteSource.LYNGSAT:
|
||||
return self.get_lyngsat_services(sat_position, use_pids)
|
||||
elif self._source is SatelliteSource.KINGOFSAT:
|
||||
return self.get_kingofsat_services(sat_position, use_pids)
|
||||
else:
|
||||
return []
|
||||
return []
|
||||
|
||||
def get_lyngsat_services(self, sat_position=None, use_pids=False):
|
||||
services = []
|
||||
@@ -481,7 +588,10 @@ class ServicesParser(HTMLParser):
|
||||
sys = "DVB-S"
|
||||
pos_found = False
|
||||
tr = None
|
||||
# Transponder
|
||||
# Multi-stream.
|
||||
multi_tr = None
|
||||
multi = False
|
||||
# Transponder.
|
||||
for r in filter(lambda x: x and 6 < len(x) < 9, self._rows):
|
||||
if not pos_found:
|
||||
pos_tr = re.match(self._POS_PAT, r[0].text)
|
||||
@@ -490,7 +600,6 @@ class ServicesParser(HTMLParser):
|
||||
|
||||
if not sat_position:
|
||||
pos = self.get_position(pos_tr.group(1))
|
||||
|
||||
pos_found = True
|
||||
|
||||
if pos_found:
|
||||
@@ -498,34 +607,46 @@ class ServicesParser(HTMLParser):
|
||||
td = re.match(self._TR_PAT, text)
|
||||
if td:
|
||||
freq, pol = int(td.group(1)), get_key_by_value(POLARIZATION, td.group(2))
|
||||
if td.group(5):
|
||||
log("Detected T2-MI transponder!")
|
||||
continue
|
||||
|
||||
sys, mod, sr, _fec, = td.group(3), td.group(4), td.group(6), td.group(7)
|
||||
nid, tid = td.group(8), td.group(9)
|
||||
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, 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)
|
||||
|
||||
if not tr:
|
||||
er = f"Transponder [{freq}] not found or its type [T2-MI, etc] not supported yet."
|
||||
er = f"Transponder [{self._t_url}] not found or its type [T2-MI, etc] not supported yet."
|
||||
log(f"ServicesParser error [get transponder services]: {er}")
|
||||
return services
|
||||
|
||||
# Services
|
||||
for r in filter(lambda x: x and len(x) == 12 and (x[0].text.isdigit()), self._rows):
|
||||
sid, name, s_type, v_pid, a_pid, cas, pkg = r[0].text, r[2].text, r[4].text, r[
|
||||
5].text.strip(), r[6].text.split(), r[9].text, r[10].text.strip()
|
||||
try:
|
||||
s_type = self._S_TYPES.get(s_type, "3") # 3 = Data
|
||||
_s_type = SERVICE_TYPE.get(s_type, SERVICE_TYPE.get("3")) # str repr
|
||||
flags, sid, fav_id, picon_id, data_id = self.get_service_data(s_type, pkg, sid, tid, nid, nsp,
|
||||
v_pid, a_pid, cas, use_pids)
|
||||
services.append(Service(flags, "s", None, name, None, None, pkg, _s_type, r[1].img, picon_id,
|
||||
sid, freq, sr, pol, fec, sys, pos, data_id, fav_id, tr))
|
||||
except ValueError as e:
|
||||
log(f"ServicesParser error [get transponder services]: {e}")
|
||||
# Services.
|
||||
for r in filter(None, self._rows):
|
||||
if multi and r[0].url:
|
||||
res = re.match(self._MULTI_PAT, r[0].url)
|
||||
if res:
|
||||
pls_mode, is_code, is_id = self._pls_modes.get(res.group(1), None), res.group(2), res.group(3)
|
||||
multi_tr = f"{tr}:{is_id}:{is_code}:{pls_mode}" if all((pls_mode, is_code, is_id)) else None
|
||||
tid = int(is_id) if multi_tr else tid
|
||||
|
||||
if len(r) == 12 and r[0].text.isdigit():
|
||||
sid, name, s_type, v_pid, a_pid, cas, pkg = r[0].text, r[2].text, r[4].text, r[
|
||||
5].text.strip(), r[6].text.split(), r[9].text, r[10].text.strip()
|
||||
try:
|
||||
s_type = self._S_TYPES.get(s_type, "3") # 3 = Data
|
||||
_s_type = SERVICE_TYPE.get(s_type, SERVICE_TYPE.get("3")) # str repr
|
||||
flags, sid, fav_id, picon_id, data_id = self.get_service_data(s_type, pkg, sid, tid, nid, nsp,
|
||||
v_pid, a_pid, cas, use_pids)
|
||||
services.append(Service(flags, "s", None, name, None, None, pkg, _s_type, r[1].img, picon_id,
|
||||
sid, freq, sr, pol, fec, sys, pos, data_id, fav_id, multi_tr or tr))
|
||||
except ValueError as e:
|
||||
log(f"ServicesParser error [get transponder services]: {e}")
|
||||
|
||||
return services
|
||||
|
||||
@@ -537,40 +658,60 @@ class ServicesParser(HTMLParser):
|
||||
log(f"ServicesParser error [get transponder services]: Transponder [{self._t_url}] not found!")
|
||||
return services
|
||||
|
||||
tr = tr[0]
|
||||
s_pos, freq, pol, sys, mod, sr_fec = tr[0].text, tr[2].text, tr[3].text, tr[6].text, tr[7].text, tr[8].text
|
||||
tid, nid = tr[10].text, tr[11].text
|
||||
tr, multi_tr, tid, nid, nsp = None, None, None, None, None
|
||||
freq, sr, pol, fec, sys, pos = None, None, None, None, None, None
|
||||
|
||||
pos = sat_position
|
||||
if not sat_position:
|
||||
pos_tr = re.match(self._POS_PAT, s_pos)
|
||||
if pos_tr:
|
||||
pos = self.get_position(pos_tr.group(1))
|
||||
for r in filter(lambda x: len(x) > 12, self._rows):
|
||||
r_size = len(r)
|
||||
if r_size == 13 and r[4].url and r[4].url.startswith("tp.php?tp="):
|
||||
res = re.match(self._KING_TR_PAT, f"{r[6].text} {r[7].text}")
|
||||
if not res:
|
||||
continue
|
||||
|
||||
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)
|
||||
freq, nid, tid = int(float(freq)), int(nid), int(tid)
|
||||
tr = self._TR.format(freq, sr, pol, fec, pos, inv, sys, s2_flags)
|
||||
sys, mod = res.group(1), res.group(5)
|
||||
s_pos, freq, pol, sr_fec = r[0].text, r[2].text, r[3].text, r[8].text
|
||||
nid, tid = r[10].text, r[11].text
|
||||
|
||||
for r in filter(lambda x: len(x) == 14 and not x[1].text and x[7].text and x[7].text.isdigit(), self._rows):
|
||||
if r[1].img == "/radio.gif":
|
||||
s_type = ""
|
||||
elif r[8].img == "/hd.gif":
|
||||
s_type = "HEVC HD"
|
||||
elif r[1].img == "/data.gif":
|
||||
s_type = "Data"
|
||||
else:
|
||||
s_type = "SD"
|
||||
pos = sat_position
|
||||
if not sat_position:
|
||||
pos_tr = re.match(self._POS_PAT, s_pos)
|
||||
if pos_tr:
|
||||
pos = self.get_position(pos_tr.group(1))
|
||||
|
||||
s_type = self._S_TYPES.get(s_type, "3")
|
||||
_s_type = SERVICE_TYPE.get(s_type, SERVICE_TYPE.get("3"))
|
||||
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)
|
||||
freq, nid, tid = int(float(freq)), int(nid), int(tid)
|
||||
tr = self._TR.format(freq, sr, pol, fec, pos, inv, sys, s2_flags)
|
||||
|
||||
name, pkg, cas, sid, v_pid, a_pid = r[2].text, r[5].text, r[6].text, r[7].text, None, None
|
||||
flags, sid, fav_id, picon_id, data_id = self.get_service_data(s_type, pkg, sid, tid, nid, nsp,
|
||||
v_pid, a_pid, cas, use_pids)
|
||||
services.append(Service(flags, "s", None, name, None, None, pkg, _s_type, None, picon_id,
|
||||
sid, str(freq), sr, pol, fec, sys, pos, data_id, fav_id, tr))
|
||||
pls_mode, is_code, is_id = self._pls_modes.get(res.group(3), None), res.group(4), res.group(6)
|
||||
multi_tr = f"{tr}:{is_id}:{is_code}:{pls_mode}" if all((pls_mode, is_code, is_id)) else None
|
||||
tid = int(is_id) if multi_tr else tid
|
||||
|
||||
if res.group(2):
|
||||
log(f"Detected T2-MI transponder! [{freq} {sr}]")
|
||||
|
||||
if multi_tr:
|
||||
log(f"Detected multi-stream transponder! [{freq} {sr}]")
|
||||
|
||||
if tr and r_size == 14 and not r[1].text and r[7].text and r[7].text.isdigit():
|
||||
if r[1].img == "/radio.gif":
|
||||
s_type = ""
|
||||
elif r[8].img == "/hd.gif":
|
||||
s_type = "HEVC HD"
|
||||
elif r[1].img == "/data.gif":
|
||||
s_type = "Data"
|
||||
else:
|
||||
s_type = "SD"
|
||||
|
||||
s_type = self._S_TYPES.get(s_type, "3")
|
||||
_s_type = SERVICE_TYPE.get(s_type, SERVICE_TYPE.get("3"))
|
||||
|
||||
name, pkg, cas, sid, v_pid, a_pid = r[2].text, r[5].text, r[6].text, r[7].text, None, None
|
||||
flags, sid, fav_id, picon_id, data_id = self.get_service_data(s_type, pkg, sid, tid, nid, nsp,
|
||||
v_pid, a_pid, cas, use_pids)
|
||||
services.append(Service(flags, "s", None, name, None, None, pkg, _s_type, None, picon_id,
|
||||
sid, str(freq), sr, pol, fec, sys, pos, data_id, fav_id, multi_tr or tr))
|
||||
|
||||
return services
|
||||
|
||||
@@ -580,7 +721,7 @@ class ServicesParser(HTMLParser):
|
||||
mod = get_key_by_value(MODULATION, mod)
|
||||
fec = get_key_by_value(FEC, fec)
|
||||
# For negative (West) positions: 3600 - numeric position value!!!
|
||||
namespace = "{:04x}0000".format(3600 - pos if pos < 0 else pos)
|
||||
namespace = f"{3600 - pos if pos < 0 else pos:04x}0000"
|
||||
tr_flag = 1
|
||||
roll_off = 0 # 35% DVB-S2/DVB-S (default)
|
||||
pilot = 2 # Auto
|
||||
@@ -592,15 +733,15 @@ class ServicesParser(HTMLParser):
|
||||
@staticmethod
|
||||
def get_service_data(s_type, pkg, sid, tid, nid, namespace, v_pid, a_pid, cas, use_pids=False):
|
||||
sid = int(sid)
|
||||
data_id = "{:04x}:{}:{:04x}:{:04x}:{}:0:0".format(sid, namespace, tid, nid, s_type)
|
||||
fav_id = "{}:{}:{}:{}".format(sid, tid, nid, namespace)
|
||||
picon_id = "1_0_{:X}_{}_{}_{}_{}_0_0_0.png".format(int(s_type), sid, tid, nid, namespace)
|
||||
data_id = f"{sid:04x}:{namespace}:{tid:04x}:{nid:04x}:{s_type}:0:0"
|
||||
fav_id = f"{sid}:{tid}:{nid}:{namespace}"
|
||||
picon_id = f"1_0_{int(s_type):X}_{sid}_{tid}_{nid}_{namespace}_0_0_0.png"
|
||||
# Flags.
|
||||
flags = "p:{}".format(pkg)
|
||||
flags = f"p:{pkg}"
|
||||
cas = ",".join(get_key_by_value(CAS, c) or "C:0000" for c in cas.split()) if cas else None
|
||||
if use_pids:
|
||||
v_pid = "c:00{:04x}".format(int(v_pid)) if v_pid else None
|
||||
a_pid = ",".join(["c:01{:04x}".format(int(p)) for p in a_pid]) if a_pid else None
|
||||
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
|
||||
flags = ",".join(filter(None, (flags, v_pid, a_pid, cas)))
|
||||
else:
|
||||
flags = ",".join(filter(None, (flags, cas)))
|
||||
|
||||
186
app/tools/yt.py
186
app/tools/yt.py
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2022 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,7 +26,7 @@
|
||||
#
|
||||
|
||||
|
||||
""" Module for working with YouTube service """
|
||||
""" Module for working with YouTube service. """
|
||||
import gzip
|
||||
import json
|
||||
import os
|
||||
@@ -35,20 +35,21 @@ import shutil
|
||||
import sys
|
||||
from html.parser import HTMLParser
|
||||
from json import JSONDecodeError
|
||||
from urllib import parse
|
||||
from urllib.error import URLError
|
||||
from urllib.parse import unquote
|
||||
from urllib.request import Request, urlopen, urlretrieve
|
||||
|
||||
from app.commons import log
|
||||
from app.settings import SEP
|
||||
from app.ui.uicommons import show_notification
|
||||
|
||||
_TIMEOUT = 5
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (Linux x86_64; rv:92.0) Gecko/20100101 Firefox/92.0",
|
||||
"DNT": "1",
|
||||
"Accept-Encoding": "gzip, deflate"}
|
||||
_YT_PATTERN = re.compile(r"https://www.youtube.com/.+(?:v=)([\w-]{11}).*")
|
||||
_YT_LIST_PATTERN = re.compile(r"https://www.youtube.com/.+?(?:list=)([\w-]{18,})?.*")
|
||||
_YT_VIDEO_PATTERN = re.compile(r"https://r\d+---sn-[\w]{10}-[\w]{3,5}.googlevideo.com/videoplayback?.*")
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/69.0",
|
||||
"DNT": "1",
|
||||
"Accept-Encoding": "gzip, deflate"}
|
||||
|
||||
Quality = {137: "1080p", 136: "720p", 135: "480p", 134: "360p",
|
||||
133: "240p", 160: "144p", 0: "0p", 18: "360p", 22: "720p"}
|
||||
@@ -109,6 +110,8 @@ class YouTube:
|
||||
if self._settings.enable_yt_dl and url:
|
||||
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.")
|
||||
return self._yt_dl.get_yt_link(url, skip_errors)
|
||||
|
||||
return self.get_yt_link_by_id(video_id)
|
||||
@@ -119,61 +122,117 @@ class YouTube:
|
||||
|
||||
Returns tuple from the video links dict and title.
|
||||
"""
|
||||
req = Request(YouTube._VIDEO_INFO_LINK.format(video_id), headers=_HEADERS)
|
||||
info = InnerTube().player(video_id)
|
||||
det = info.get("videoDetails", None)
|
||||
title = det.get("title", None) if det else None
|
||||
streaming_data = info.get("streamingData", None)
|
||||
fmts = streaming_data.get("formats", None) if streaming_data else None
|
||||
|
||||
with urlopen(req, timeout=2) as resp:
|
||||
data = unquote(gzip.decompress(resp.read()).decode("utf-8")).split("&")
|
||||
out = {k: v for k, sep, v in (str(d).partition("=") for d in map(unquote, data))}
|
||||
player_resp = out.get("player_response", None)
|
||||
if fmts:
|
||||
links = {Quality[i["itag"]]: i["url"] for i in filter(
|
||||
lambda i: i.get("itag", -1) in Quality, fmts) if "url" in i}
|
||||
|
||||
if player_resp:
|
||||
try:
|
||||
resp = json.loads(player_resp)
|
||||
except JSONDecodeError as e:
|
||||
log("{}: Parsing player response error: {}".format(__class__.__name__, e))
|
||||
else:
|
||||
det = resp.get("videoDetails", None)
|
||||
title = det.get("title", None) if det else None
|
||||
streaming_data = resp.get("streamingData", None)
|
||||
fmts = streaming_data.get("formats", None) if streaming_data else None
|
||||
if links and title:
|
||||
return links, title.replace("+", " ")
|
||||
|
||||
if fmts:
|
||||
urls = {Quality[i["itag"]]: i["url"] for i in
|
||||
filter(lambda i: i.get("itag", -1) in Quality, fmts) if "url" in i}
|
||||
cause = None
|
||||
status = info.get("playabilityStatus", None)
|
||||
if status:
|
||||
cause = f"[{status.get('status', '')}] {status.get('reason', '')}"
|
||||
|
||||
if urls and title:
|
||||
return urls, title.replace("+", " ")
|
||||
log(f"{__class__.__name__}: Getting link to video with id '{video_id}' filed! Cause: {cause}")
|
||||
|
||||
stream_map = out.get("url_encoded_fmt_stream_map", None)
|
||||
if stream_map:
|
||||
s_map = {k: v for k, sep, v in (str(d).partition("=") for d in stream_map.split("&"))}
|
||||
url, title = s_map.get("url", None), out.get("title", None)
|
||||
url, title = unquote(url) if url else "", title.replace("+", " ") if title else ""
|
||||
if url and title:
|
||||
return {Quality[0]: url}, title.replace("+", " ")
|
||||
|
||||
rsn = out.get("reason", None)
|
||||
rsn = rsn.replace("+", " ") if rsn else ""
|
||||
log("{}: Getting link to video with id {} filed! Cause: {}".format(__class__.__name__, video_id, rsn))
|
||||
|
||||
return None, rsn
|
||||
return None, cause
|
||||
|
||||
def get_yt_playlist(self, list_id, url=None):
|
||||
""" Returns tuple from the playlist header and list of tuples (title, video id). """
|
||||
if self._settings.enable_yt_dl and url:
|
||||
try:
|
||||
if not self._yt_dl:
|
||||
raise YouTubeException("youtube-dl is not initialized!")
|
||||
|
||||
self._yt_dl.update_options({"noplaylist": False, "extract_flat": True})
|
||||
info = self._yt_dl.get_info(url, skip_errors=False)
|
||||
if "url" in info:
|
||||
info = self._yt_dl.get_info(info.get("url"), skip_errors=False)
|
||||
|
||||
return info.get("title", ""), [(e.get("title", ""), e.get("id", "")) for e in info.get("entries", [])]
|
||||
finally:
|
||||
# Restoring default options
|
||||
self._yt_dl.update_options({"noplaylist": True, "extract_flat": False})
|
||||
if self._yt_dl:
|
||||
self._yt_dl.update_options({"noplaylist": True, "extract_flat": False})
|
||||
|
||||
return PlayListParser.get_yt_playlist(list_id)
|
||||
|
||||
|
||||
class InnerTube:
|
||||
""" Object for interacting with the innertube API.
|
||||
|
||||
Based on InnerTube class from pytube [https://github.com/pytube/pytube] project!
|
||||
"""
|
||||
_BASE_URI = "https://www.youtube.com/youtubei/v1"
|
||||
|
||||
_DEFAULT_CLIENTS = {
|
||||
"ANDROID": {
|
||||
"context": {"client": {"clientName": "ANDROID", "clientVersion": "16.20"}},
|
||||
"api_key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"
|
||||
},
|
||||
"ANDROID_EMBED": {
|
||||
"context": {"client": {"clientName": "ANDROID", "clientVersion": "16.20", "clientScreen": "EMBED"}},
|
||||
"api_key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, client="ANDROID"):
|
||||
""" Initialize an InnerTube object.
|
||||
|
||||
@param client: Client to use for the object. Default to web because it returns the most playback types.
|
||||
"""
|
||||
self.context = self._DEFAULT_CLIENTS[client]["context"]
|
||||
self.api_key = self._DEFAULT_CLIENTS[client]["api_key"]
|
||||
|
||||
@property
|
||||
def base_data(self):
|
||||
"""Return the base json data to transmit to the innertube API."""
|
||||
return {"context": self.context}
|
||||
|
||||
@property
|
||||
def base_params(self):
|
||||
"""Return the base query parameters to transmit to the innertube API."""
|
||||
return {"key": self.api_key, "contentCheckOk": True, "racyCheckOk": True}
|
||||
|
||||
def player(self, video_id):
|
||||
""" Make a request to the player endpoint. Returns raw player info results. """
|
||||
endpoint = f"{self._BASE_URI}/player"
|
||||
query = {"videoId": video_id}
|
||||
query.update(self.base_params)
|
||||
return self._call_api(endpoint, query, self.base_data) or {}
|
||||
|
||||
@staticmethod
|
||||
def _call_api(endpoint, query, data):
|
||||
""" Make a request to a given endpoint with the provided query parameters and data."""
|
||||
headers = {"Content-Type": "application/json", }
|
||||
response = InnerTube._execute(f"{endpoint}?{parse.urlencode(query)}", "POST", headers=headers, data=data)
|
||||
|
||||
try:
|
||||
resp = json.loads(response.read())
|
||||
except JSONDecodeError as e:
|
||||
log(f"{__class__.__name__}: Parsing response error: {e}")
|
||||
else:
|
||||
return resp
|
||||
|
||||
@staticmethod
|
||||
def _execute(url, method=None, headers=None, data=None, timeout=_TIMEOUT):
|
||||
base_headers = {"User-Agent": "Mozilla/5.0", "accept-language": "en-US,en"}
|
||||
if headers:
|
||||
base_headers.update(headers)
|
||||
if data:
|
||||
# Encoding data for request.
|
||||
if not isinstance(data, bytes):
|
||||
data = bytes(json.dumps(data), encoding="utf-8")
|
||||
return urlopen(Request(url, headers=base_headers, method=method, data=data), timeout=timeout)
|
||||
|
||||
|
||||
class PlayListParser(HTMLParser):
|
||||
""" Very simple parser to handle YouTube playlist pages. """
|
||||
|
||||
@@ -200,7 +259,7 @@ class PlayListParser(HTMLParser):
|
||||
try:
|
||||
resp = json.loads(data)
|
||||
except JSONDecodeError as e:
|
||||
log("{}: Parsing data error: {}".format(__class__.__name__, e))
|
||||
log(f"{__class__.__name__}: Parsing data error: {e}")
|
||||
else:
|
||||
sb = resp.get("sidebar", None)
|
||||
if sb:
|
||||
@@ -218,7 +277,7 @@ class PlayListParser(HTMLParser):
|
||||
self._is_script = False
|
||||
|
||||
def error(self, message):
|
||||
log("{} Parsing error: {}".format(__class__.__name__, message))
|
||||
log(f"{__class__.__name__} Parsing error: {message}")
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
@@ -234,9 +293,9 @@ class PlayListParser(HTMLParser):
|
||||
|
||||
returns tuple from the playlist header and list of tuples (title, video id)
|
||||
"""
|
||||
request = Request("https://www.youtube.com/playlist?list={}&hl=en".format(play_list_id), headers=_HEADERS)
|
||||
request = Request(f"https://www.youtube.com/playlist?list={play_list_id}&hl=en", headers=_HEADERS)
|
||||
|
||||
with urlopen(request, timeout=2) as resp:
|
||||
with urlopen(request, timeout=_TIMEOUT) as resp:
|
||||
data = gzip.decompress(resp.read()).decode("utf-8")
|
||||
parser = PlayListParser()
|
||||
parser.feed(data)
|
||||
@@ -259,7 +318,7 @@ class YouTubeDL:
|
||||
"cookiefile": "cookies.txt"} # File name where cookies should be read from and dumped to.
|
||||
|
||||
def __init__(self, settings, callback):
|
||||
self._path = "{}tools{}".format(settings.default_data_path, SEP)
|
||||
self._path = f"{settings.default_data_path}tools{SEP}"
|
||||
self._update = settings.enable_yt_dl_update
|
||||
self._supported = {"22", "18"}
|
||||
self._dl = None
|
||||
@@ -276,7 +335,7 @@ class YouTubeDL:
|
||||
return cls._DL_INSTANCE
|
||||
|
||||
def init(self):
|
||||
if not os.path.isfile("{}youtube_dl{}version.py".format(self._path, SEP)):
|
||||
if not os.path.isfile(f"{self._path}youtube_dl{SEP}version.py"):
|
||||
self.get_latest_release()
|
||||
|
||||
if self._path not in sys.path:
|
||||
@@ -288,17 +347,22 @@ class YouTubeDL:
|
||||
try:
|
||||
import youtube_dl
|
||||
except ModuleNotFoundError as e:
|
||||
log("YouTubeDLHelper error: {}".format(str(e)))
|
||||
log(f"YouTubeDLHelper error: {e}")
|
||||
raise YouTubeException(e)
|
||||
except ImportError as e:
|
||||
log("YouTubeDLHelper error: {}".format(str(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!"
|
||||
log(msg)
|
||||
raise YouTubeException(msg)
|
||||
|
||||
if self._update:
|
||||
if hasattr(youtube_dl.version, "__version__"):
|
||||
l_ver = self.get_last_release_id()
|
||||
cur_ver = youtube_dl.version.__version__
|
||||
if l_ver and youtube_dl.version.__version__ < l_ver:
|
||||
msg = "youtube-dl has new release!\nCurrent: {}. Last: {}.".format(cur_ver, l_ver)
|
||||
msg = f"youtube-dl has new release!\nCurrent: {cur_ver}. Last: {l_ver}."
|
||||
show_notification(msg)
|
||||
log(msg)
|
||||
self._callback(msg, False)
|
||||
@@ -318,7 +382,7 @@ class YouTubeDL:
|
||||
with urlopen(url, timeout=10) as resp:
|
||||
return json.loads(resp.read().decode("utf-8")).get("tag_name", "0")
|
||||
except URLError as e:
|
||||
log("YouTubeDLHelper error [get last release id]: {}".format(e))
|
||||
log(f"YouTubeDLHelper error [get last release id]: {e}")
|
||||
|
||||
def get_latest_release(self):
|
||||
try:
|
||||
@@ -329,6 +393,9 @@ class YouTubeDL:
|
||||
r = json.loads(resp.read().decode("utf-8"))
|
||||
zip_url = r.get("zipball_url", None)
|
||||
if zip_url:
|
||||
if os.path.isdir(self._path):
|
||||
shutil.rmtree(self._path)
|
||||
|
||||
zip_file = self._path + "yt.zip"
|
||||
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
||||
f_name, headers = urlretrieve(zip_url, filename=zip_file)
|
||||
@@ -336,25 +403,22 @@ class YouTubeDL:
|
||||
import zipfile
|
||||
|
||||
with zipfile.ZipFile(f_name) as arch:
|
||||
|
||||
if os.path.isdir(self._path):
|
||||
shutil.rmtree(self._path)
|
||||
else:
|
||||
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
||||
|
||||
for info in arch.infolist():
|
||||
pref, sep, f = info.filename.partition("/youtube_dl/")
|
||||
if sep:
|
||||
arch.extract(info.filename)
|
||||
shutil.move(info.filename, "{}{}{}".format(self._path, sep, f))
|
||||
shutil.move(info.filename, f"{self._path}{sep}{f}")
|
||||
shutil.rmtree(pref)
|
||||
msg = "Getting the last youtube-dl release is done!"
|
||||
show_notification(msg)
|
||||
log(msg)
|
||||
self._callback(msg, False)
|
||||
return True
|
||||
|
||||
if os.path.isfile(zip_file):
|
||||
os.remove(zip_file)
|
||||
return True
|
||||
except URLError as e:
|
||||
log("YouTubeDLHelper error: {}".format(e))
|
||||
log(f"YouTubeDLHelper error: {e}")
|
||||
raise YouTubeException(e)
|
||||
finally:
|
||||
self._is_update_process = False
|
||||
@@ -377,10 +441,10 @@ class YouTubeDL:
|
||||
try:
|
||||
return self._dl.extract_info(url, download=False)
|
||||
except URLError as e:
|
||||
log(str(e))
|
||||
log(f"YouTubeDLHelper error [get info]: {e}")
|
||||
raise YouTubeException(e)
|
||||
except self._DownloadError as e:
|
||||
log(str(e))
|
||||
log(f"YouTubeDLHelper error [get info]: {e}")
|
||||
if not skip_errors:
|
||||
raise YouTubeException(e)
|
||||
|
||||
|
||||
@@ -133,6 +133,16 @@
|
||||
<attribute name="hidden-when">action-disabled</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Display picons</attribute>
|
||||
<attribute name="action">app.display_picons</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Alternate layout</attribute>
|
||||
<attribute name="action">app.set_alternate_layout</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu id="tools_menu">
|
||||
<attribute name="label" translatable="yes">Tools</attribute>
|
||||
@@ -323,6 +333,16 @@
|
||||
<attribute name="hidden-when">action-disabled</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Display picons</attribute>
|
||||
<attribute name="action">app.display_picons</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Alternate layout</attribute>
|
||||
<attribute name="action">app.set_alternate_layout</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">Tools</attribute>
|
||||
@@ -368,7 +388,7 @@
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Export to m3u</attribute>
|
||||
<attribute name="action">app.on_export_to_m3u</attribute>
|
||||
<attribute name="action">app.on_export_iptv_to_m3u</attribute>
|
||||
</item>
|
||||
<section>
|
||||
<item>
|
||||
|
||||
@@ -1158,81 +1158,66 @@ Author: Dmitriy Yefremov
|
||||
<!-- column-name data -->
|
||||
<column type="PyObject"/>
|
||||
</columns>
|
||||
<signal name="row-deleted" handler="on_recordings_model_changed" swapped="no"/>
|
||||
<signal name="row-inserted" handler="on_recordings_model_changed" swapped="no"/>
|
||||
</object>
|
||||
<object class="GtkFrame" id="recordings_frame">
|
||||
<object class="GtkBox" id="recordings_box">
|
||||
<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="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="recordings_box">
|
||||
<object class="GtkPaned" id="recordings_paned">
|
||||
<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="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="wide_handle">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="recordings_header_box">
|
||||
<object class="GtkFrame" id="recordings_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="spacing">5</property>
|
||||
<property name="label_xalign">0.49000000953674316</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="recordings_remove_button">
|
||||
<object class="GtkBox" id="recordings_main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Remove</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_recording_remove" swapped="no"/>
|
||||
<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="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="remove_recording_image">
|
||||
<object class="GtkBox" id="recordings_header_box">
|
||||
<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">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</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">5</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="recordings_search_box">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="fav_search_entry1">
|
||||
<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="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="recordings_remove_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Remove</property>
|
||||
<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">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -1241,196 +1226,198 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="fav_search_down_button1">
|
||||
<object class="GtkScrolledWindow" id="recordings_view_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkArrow" id="fav_down_arrow1">
|
||||
<object class="GtkTreeView" id="recordings_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="arrow_type">down</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">recordings_model</property>
|
||||
<property name="enable_grid_lines">both</property>
|
||||
<property name="tooltip_column">5</property>
|
||||
<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="min_width">100</property>
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_service_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_title_column">
|
||||
<property name="sizing">autosize</property>
|
||||
<property name="min_width">150</property>
|
||||
<property name="title" translatable="yes">Title</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_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="rec_time_column">
|
||||
<property name="min_width">100</property>
|
||||
<property name="title" translatable="yes">Time</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_time_renderer">
|
||||
<property name="xpad">5</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_len_column">
|
||||
<property name="min_width">100</property>
|
||||
<property name="title" translatable="yes">Length</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_len_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_file_column">
|
||||
<property name="resizable">True</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>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_file_renderer">
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_desc_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title" translatable="yes">Description</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_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">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="fav_search_up_button1">
|
||||
<object class="GtkBox" id="recordings_status_box">
|
||||
<property name="height_request">24</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkArrow" id="fav_up_arrow1">
|
||||
<object class="GtkImage" id="recordings_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="arrow_type">up</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">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="group"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="recordings_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Recordings</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkPaned" id="recordings_paned">
|
||||
<object class="GtkFrame" id="recordings_paths_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="wide_handle">True</property>
|
||||
<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_model</property>
|
||||
<property name="enable_grid_lines">both</property>
|
||||
<property name="tooltip_column">5</property>
|
||||
<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="min_width">100</property>
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_service_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_title_column">
|
||||
<property name="sizing">autosize</property>
|
||||
<property name="min_width">150</property>
|
||||
<property name="title" translatable="yes">Title</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_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="rec_time_column">
|
||||
<property name="min_width">100</property>
|
||||
<property name="title" translatable="yes">Time</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_time_renderer">
|
||||
<property name="xpad">5</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_len_column">
|
||||
<property name="min_width">100</property>
|
||||
<property name="title" translatable="yes">Length</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_len_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_file_column">
|
||||
<property name="resizable">True</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>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_file_renderer">
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rec_desc_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title" translatable="yes">Description</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rec_desc_renderer">
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">5</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0.49000000953674316</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<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="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>
|
||||
<child>
|
||||
@@ -1438,6 +1425,7 @@ Author: Dmitriy Yefremov
|
||||
<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>
|
||||
@@ -1479,26 +1467,26 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">False</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Paths</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
<property name="resize">False</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="recordings_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Recordings</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkListStore" id="timer_model">
|
||||
|
||||
@@ -126,7 +126,7 @@ class EpgTool(Gtk.Box):
|
||||
start = int(event.get("e2eventstart", "0"))
|
||||
start_time = datetime.fromtimestamp(start)
|
||||
end_time = datetime.fromtimestamp(start + int(event.get("e2eventduration", "0")))
|
||||
time = "{} - {}".format(start_time.strftime("%A, %H:%M"), end_time.strftime("%H:%M"))
|
||||
time = f"{start_time.strftime('%A, %H:%M')} - {end_time.strftime('%H:%M')}"
|
||||
|
||||
return title, time, desc, event
|
||||
|
||||
@@ -223,7 +223,7 @@ class TimerTool(Gtk.Box):
|
||||
elif self._action is TimerTool.TimerAction.EVENT:
|
||||
self.set_timer_from_event_data()
|
||||
else:
|
||||
log("{} error: No action set for timer!".format(__class__.__name__))
|
||||
log(f"{__class__.__name__} error: No action set for timer!")
|
||||
|
||||
@property
|
||||
def request(self):
|
||||
@@ -242,34 +242,34 @@ class TimerTool(Gtk.Box):
|
||||
s_ref = quote(t_data.get("sRef", ""))
|
||||
|
||||
if self._action is TimerTool.TimerAction.EVENT:
|
||||
args.append("timeraddbyeventid?sRef={}".format(s_ref))
|
||||
args.append("eventid={}".format(t_data.get("eit", "0")))
|
||||
args.append("justplay={}".format(t_data.get("justplay", "")))
|
||||
args.append("tags={}".format(""))
|
||||
args.append(f"timeraddbyeventid?sRef={s_ref}")
|
||||
args.append(f"eventid={t_data.get('eit', '0')}")
|
||||
args.append(f"justplay={t_data.get('justplay', '')}")
|
||||
args.append(f"tags={''}")
|
||||
else:
|
||||
if self._action is TimerTool.TimerAction.ADD:
|
||||
args.append("timeradd?sRef={}".format(s_ref))
|
||||
args.append("deleteOldOnSave={}".format(0))
|
||||
args.append(f"timeradd?sRef={s_ref}")
|
||||
args.append(f"deleteOldOnSave={0}")
|
||||
elif self._action is TimerTool.TimerAction.CHANGE:
|
||||
args.append("timerchange?sRef={}".format(s_ref))
|
||||
args.append("channelOld={}".format(s_ref))
|
||||
args.append("beginOld={}".format(self._timer_data.get("e2timebegin", "0")))
|
||||
args.append("endOld={}".format(self._timer_data.get("e2timeend", "0")))
|
||||
args.append("deleteOldOnSave={}".format(1))
|
||||
args.append(f"timerchange?sRef={s_ref}")
|
||||
args.append(f"channelOld={s_ref}")
|
||||
args.append(f"beginOld={self._timer_data.get('e2timebegin', '0')}")
|
||||
args.append(f"endOld={self._timer_data.get('e2timeend', '0')}")
|
||||
args.append(f"deleteOldOnSave={1}")
|
||||
|
||||
args.append("begin={}".format(t_data.get("begin", "")))
|
||||
args.append("end={}".format(t_data.get("end", "")))
|
||||
args.append("name={}".format(quote(t_data.get("name", ""))))
|
||||
args.append("description={}".format(quote(t_data.get("description", ""))))
|
||||
args.append("tags={}".format(""))
|
||||
args.append("eit={}".format("0"))
|
||||
args.append("disabled={}".format(t_data.get("disabled", "1")))
|
||||
args.append("justplay={}".format(t_data.get("justplay", "1")))
|
||||
args.append("afterevent={}".format(t_data.get("afterevent", "0")))
|
||||
args.append("repeated={}".format(TimerTool.get_repetition_flags(self._days_buttons)))
|
||||
args.append(f"begin={t_data.get('begin', '')}")
|
||||
args.append(f"end={t_data.get('end', '')}")
|
||||
args.append(f"name={quote(t_data.get('name', ''))}")
|
||||
args.append(f"description={quote(t_data.get('description', ''))}")
|
||||
args.append(f"tags={''}")
|
||||
args.append(f"eit={'0'}")
|
||||
args.append(f"disabled={t_data.get('disabled', '1')}")
|
||||
args.append(f"justplay={t_data.get('justplay', '1')}")
|
||||
args.append(f"afterevent={t_data.get('afterevent', '0')}")
|
||||
args.append(f"repeated={TimerTool.get_repetition_flags(self._days_buttons)}")
|
||||
|
||||
if self._timer_location_switch.get_active():
|
||||
args.append("dirname={}".format(self._timer_location_entry.get_text()))
|
||||
args.append(f"dirname={self._timer_location_entry.get_text()}")
|
||||
|
||||
return "&".join(args)
|
||||
|
||||
@@ -292,8 +292,7 @@ class TimerTool(Gtk.Box):
|
||||
self._timer_begins_min_button.set_value(minute)
|
||||
self._timer_begins_calendar.select_day(date.day)
|
||||
self._timer_begins_calendar.select_month(date.month - 1, date.year)
|
||||
self._timer_begins_entry.set_text(
|
||||
"{}-{}-{} {}:{:02d}".format(date.year, date.month, date.day, hour, minute))
|
||||
self._timer_begins_entry.set_text(f"{date.year}-{date.month}-{date.day} {hour}:{minute:02d}")
|
||||
|
||||
def get_ends_date(self):
|
||||
date = self._timer_ends_calendar.get_date()
|
||||
@@ -308,7 +307,7 @@ class TimerTool(Gtk.Box):
|
||||
self._timer_ends_min_button.set_value(minute)
|
||||
self._timer_ends_calendar.select_day(date.day)
|
||||
self._timer_ends_calendar.select_month(date.month - 1, date.year)
|
||||
self._timer_ends_entry.set_text("{}-{}-{} {}:{:02d}".format(date.year, date.month, date.day, hour, minute))
|
||||
self._timer_ends_entry.set_text(f"{date.year}-{date.month}-{date.day} {hour}:{minute:02d}")
|
||||
|
||||
def set_timer_for_add(self):
|
||||
self._timer_service_entry.set_text(self._timer_data.get("e2servicename", ""))
|
||||
@@ -446,7 +445,7 @@ class TimerTool(Gtk.Box):
|
||||
service = timer.get("e2servicename", "") or ""
|
||||
start_time = datetime.fromtimestamp(int(timer.get("e2timebegin", "0")))
|
||||
end_time = datetime.fromtimestamp(int(timer.get("e2timeend", "0")))
|
||||
time = "{} - {}".format(start_time.strftime("%A, %H:%M"), end_time.strftime("%H:%M"))
|
||||
time = f"{start_time.strftime('%A, %H:%M')} - {end_time.strftime('%H:%M')}"
|
||||
|
||||
return disabled, name, service, time, description, timer
|
||||
|
||||
@@ -486,7 +485,7 @@ class TimerTool(Gtk.Box):
|
||||
@run_idle
|
||||
def timer_add_edit_callback(self, resp):
|
||||
if "error_code" in resp:
|
||||
msg = "Error getting timer status.\n{}".format(resp.get("error_code"))
|
||||
msg = f"Error getting timer status.\n{resp.get('error_code')}"
|
||||
self._app.show_error_message(msg)
|
||||
log(msg)
|
||||
return
|
||||
@@ -628,7 +627,7 @@ class TimerTool(Gtk.Box):
|
||||
service = self._app.current_services.get(fav_id, None)
|
||||
if service:
|
||||
if service.service_type == BqServiceType.ALT.name:
|
||||
msg = "Alternative service.\n\n {}".format(get_message("Not implemented yet!"))
|
||||
msg = "Alternative service.\n\n {get_message('Not implemented yet!')}"
|
||||
show_dialog(DialogType.ERROR, transient=self._app._main_window, text=msg)
|
||||
context.finish(False, False, time)
|
||||
return
|
||||
@@ -647,6 +646,7 @@ class RecordingsTool(Gtk.Box):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self._app = app
|
||||
self._app.connect("layout-changed", self.on_layout_changed)
|
||||
self._app.connect("profile-changed", self.init)
|
||||
self._settings = settings
|
||||
self._ftp = None
|
||||
@@ -658,14 +658,18 @@ class RecordingsTool(Gtk.Box):
|
||||
handlers = {"on_path_press": self.on_path_press,
|
||||
"on_path_activated": self.on_path_activated,
|
||||
"on_recordings_activated": self.on_recordings_activated,
|
||||
"on_recording_remove": self.on_recording_remove}
|
||||
"on_recording_remove": self.on_recording_remove,
|
||||
"on_recordings_model_changed": self.on_recordings_model_changed}
|
||||
|
||||
builder = get_builder(UI_RESOURCES_PATH + "control.glade", handlers,
|
||||
objects=("recordings_frame", "recordings_model", "rec_paths_model"))
|
||||
objects=("recordings_box", "recordings_model", "rec_paths_model"))
|
||||
self._rec_view = builder.get_object("recordings_view")
|
||||
self._paths_view = builder.get_object("recordings_paths_view")
|
||||
self._paned = builder.get_object("recordings_paned")
|
||||
self.pack_start(builder.get_object("recordings_frame"), True, True, 0)
|
||||
self._recordings_count_label = builder.get_object("recordings_count_label")
|
||||
self.pack_start(builder.get_object("recordings_box"), True, True, 0)
|
||||
if settings.alternate_layout:
|
||||
self.on_layout_changed(app, True)
|
||||
|
||||
self.init()
|
||||
self.show()
|
||||
@@ -674,6 +678,14 @@ class RecordingsTool(Gtk.Box):
|
||||
self._rec_view.get_model().clear()
|
||||
self._paths_view.get_model().clear()
|
||||
|
||||
def on_layout_changed(self, app, alt_layout):
|
||||
ch1 = self._paned.get_child1()
|
||||
ch2 = self._paned.get_child2()
|
||||
self._paned.remove(ch1)
|
||||
self._paned.remove(ch2)
|
||||
self._paned.add1(ch2)
|
||||
self._paned.add(ch1)
|
||||
|
||||
@run_task
|
||||
def init(self, app=None, arg=None):
|
||||
GLib.idle_add(self.clear_data)
|
||||
@@ -716,10 +728,14 @@ class RecordingsTool(Gtk.Box):
|
||||
|
||||
for f in files:
|
||||
f_data = f.split()
|
||||
if len(f_data) < 9:
|
||||
log(f"{__class__.__name__}. Folder data parsing error. [{f}]")
|
||||
continue
|
||||
|
||||
f_type = f_data[0][0]
|
||||
|
||||
if f_type == "d":
|
||||
model.append((self._icon, f_data[-1], self._ftp.pwd()))
|
||||
model.append((self._icon, " ".join(f_data[8:]), self._ftp.pwd()))
|
||||
|
||||
def on_path_activated(self, view, path, column):
|
||||
row = view.get_model()[path][:]
|
||||
@@ -774,6 +790,9 @@ class RecordingsTool(Gtk.Box):
|
||||
self._app.show_error_message(resp)
|
||||
break
|
||||
|
||||
def on_recordings_model_changed(self, model, path, itr=None):
|
||||
self._recordings_count_label.set_text(str(len(model)))
|
||||
|
||||
def on_playback(self, box, state):
|
||||
""" Updates state of the UI elements for playback mode. """
|
||||
if self._settings.play_streams_mode is PlayStreamsMode.BUILT_IN:
|
||||
@@ -797,6 +816,7 @@ class ControlTool(Gtk.Box):
|
||||
|
||||
self._settings = settings
|
||||
self._app = app
|
||||
self._app.connect("layout-changed", self.on_layout_changed)
|
||||
self._pix = None
|
||||
|
||||
handlers = {"on_volume_changed": self.on_volume_changed,
|
||||
@@ -806,7 +826,7 @@ class ControlTool(Gtk.Box):
|
||||
objects=("control_box", "volume_adjustment"))
|
||||
|
||||
self.pack_start(builder.get_object("control_box"), True, True, 0)
|
||||
self._stack = builder.get_object("stack")
|
||||
self._remote_box = builder.get_object("remote_box")
|
||||
self._screenshot_area = builder.get_object("screenshot_area")
|
||||
self._screenshot_button_box = builder.get_object("screenshot_button_box")
|
||||
self._screenshot_check_button = builder.get_object("screenshot_check_button")
|
||||
@@ -819,6 +839,9 @@ class ControlTool(Gtk.Box):
|
||||
self._agc_level_bar = builder.get_object("agc_level_bar")
|
||||
self._volume_button = builder.get_object("volume_button")
|
||||
self.init_actions(app)
|
||||
if settings.alternate_layout:
|
||||
self.on_layout_changed(app, True)
|
||||
|
||||
self.show()
|
||||
|
||||
def init_actions(self, app):
|
||||
@@ -852,6 +875,9 @@ class ControlTool(Gtk.Box):
|
||||
app.set_action("on_screenshot_video", self.on_screenshot_video)
|
||||
app.set_action("on_screenshot_osd", self.on_screenshot_osd)
|
||||
|
||||
def on_layout_changed(self, app, alt_layout):
|
||||
self._remote_box.reorder_child(self._remote_box.get_children()[0], 1)
|
||||
|
||||
# ***************** Remote controller ********************* #
|
||||
|
||||
def on_remote(self, action, state=False):
|
||||
@@ -871,7 +897,7 @@ class ControlTool(Gtk.Box):
|
||||
|
||||
@run_with_delay(0.5)
|
||||
def on_volume_changed(self, button, value):
|
||||
self._app.send_http_request(HttpAPI.Request.VOL, "{:.0f}".format(value), self.on_response)
|
||||
self._app.send_http_request(HttpAPI.Request.VOL, f"{value:.0f}", self.on_response)
|
||||
|
||||
def update_volume(self, vol):
|
||||
if "error_code" in vol:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2022 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
|
||||
@@ -40,7 +40,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="icon_name">system-help</property>
|
||||
<property name="type_hint">normal</property>
|
||||
<property name="program_name">DemonEditor</property>
|
||||
<property name="version">2.0.0 Beta</property>
|
||||
<property name="version">2.1.0 Beta</property>
|
||||
<property name="copyright">2018-2021 Dmitriy Yefremov
|
||||
</property>
|
||||
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2020 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2022 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
|
||||
@@ -812,17 +812,61 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSearchBar" id="filter_bar">
|
||||
<object class="GtkBox" id="filter_bar">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="filter_auto_switch">
|
||||
<property name="name">filter_switch</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Automatically set the name selected in the bouquet list.</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_right">15</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="filter_auto_label">
|
||||
<property name="name">filter_auto_label</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="label" translatable="yes">Auto</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child type="center">
|
||||
<object class="GtkSearchEntry" id="filter_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="primary_icon_name">edit-find-replace-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
<signal name="search-changed" handler="on_filter_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -865,6 +909,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">service_sort_model</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="fixed_height_mode">True</property>
|
||||
<property name="enable_grid_lines">both</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="source_popup_menu" swapped="no"/>
|
||||
<signal name="drag-begin" handler="on_drag_begin" swapped="no"/>
|
||||
@@ -876,6 +921,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="service_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min_width">50</property>
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="expand">True</property>
|
||||
@@ -894,6 +940,7 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="pos_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min_width">70</property>
|
||||
<property name="title" translatable="yes">Pos</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -912,6 +959,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="ref_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min_width">50</property>
|
||||
<property name="title" translatable="yes">Reference</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -1071,6 +1119,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="enable_grid_lines">both</property>
|
||||
<property name="tooltip_column">9</property>
|
||||
<signal name="button-press-event" handler="on_bouquet_popup_menu" object="bouquet_popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_bq_cursor_changed" swapped="no"/>
|
||||
<signal name="drag-data-received" handler="on_drag_data_received" swapped="no"/>
|
||||
<signal name="key-release-event" handler="on_key_press" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
@@ -1081,6 +1130,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="num_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min_width">30</property>
|
||||
<property name="title" translatable="yes">Num</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -1101,6 +1151,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fav_service_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min_width">50</property>
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="expand">True</property>
|
||||
@@ -1157,7 +1208,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fav_type_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">autosize</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min_width">40</property>
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="expand">True</property>
|
||||
@@ -1177,7 +1228,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkTreeViewColumn" id="fav_pos_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">autosize</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min_width">10</property>
|
||||
<property name="title" translatable="yes">Pos</property>
|
||||
<child>
|
||||
@@ -1194,7 +1245,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fav_id_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="sizing">autosize</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="title">fav_id</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="fav_id_cellrenderertext4"/>
|
||||
@@ -1207,7 +1258,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fav_extra_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="sizing">autosize</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="title" translatable="yes">extra</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="fav_tooltip_cellrenderertext"/>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2022 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
|
||||
@@ -37,7 +37,7 @@ from urllib.error import HTTPError, URLError
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from app.commons import run_idle, run_task
|
||||
from app.commons import run_idle, run_task, run_with_delay
|
||||
from app.connections import download_data, DownloadType
|
||||
from app.eparser.ecommons import BouquetService, BqServiceType
|
||||
from app.settings import SEP
|
||||
@@ -54,7 +54,7 @@ class RefsSource(Enum):
|
||||
|
||||
class EpgDialog:
|
||||
|
||||
def __init__(self, transient, settings, services, bouquet, fav_model, bouquet_name):
|
||||
def __init__(self, app, bouquet, bouquet_name):
|
||||
|
||||
handlers = {"on_close_dialog": self.on_close_dialog,
|
||||
"on_apply": self.on_apply,
|
||||
@@ -80,12 +80,14 @@ class EpgDialog:
|
||||
"on_enable_filtering_switch": self.on_enable_filtering_switch,
|
||||
"on_update_on_start_switch": self.on_update_on_start_switch,
|
||||
"on_field_icon_press": self.on_field_icon_press,
|
||||
"on_key_press": self.on_key_press}
|
||||
"on_key_press": self.on_key_press,
|
||||
"on_bq_cursor_changed": self.on_bq_cursor_changed}
|
||||
|
||||
self._app = app
|
||||
self._services = {}
|
||||
self._ex_services = services
|
||||
self._ex_fav_model = fav_model
|
||||
self._settings = settings
|
||||
self._ex_services = self._app.current_services
|
||||
self._ex_fav_model = self._app.fav_view.get_model()
|
||||
self._settings = self._app.app_settings
|
||||
self._bouquet = bouquet
|
||||
self._bouquet_name = bouquet_name
|
||||
self._current_ref = []
|
||||
@@ -98,7 +100,7 @@ class EpgDialog:
|
||||
builder = get_builder(UI_RESOURCES_PATH + "epg.glade", handlers)
|
||||
|
||||
self._dialog = builder.get_object("epg_dialog_window")
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._dialog.set_transient_for(self._app.app_window)
|
||||
self._source_view = builder.get_object("source_view")
|
||||
self._bouquet_view = builder.get_object("bouquet_view")
|
||||
self._bouquet_model = builder.get_object("bouquet_list_store")
|
||||
@@ -110,8 +112,8 @@ class EpgDialog:
|
||||
self._xml_download_progress_bar = builder.get_object("xml_download_progress_bar")
|
||||
# Filter
|
||||
self._filter_bar = builder.get_object("filter_bar")
|
||||
self._filter_bar.bind_property("search-mode-enabled", self._filter_bar, "visible")
|
||||
self._filter_entry = builder.get_object("filter_entry")
|
||||
self._filter_auto_switch = builder.get_object("filter_auto_switch")
|
||||
self._services_filter_model = builder.get_object("services_filter_model")
|
||||
self._services_filter_model.set_visible_func(self.services_filter_function)
|
||||
# Info
|
||||
@@ -142,6 +144,8 @@ class EpgDialog:
|
||||
header_bar.pack_end(right_box)
|
||||
builder.get_object("toolbar_box").set_visible(False)
|
||||
|
||||
self._app.connect("epg-dat-downloaded", self.on_epg_dat_downloaded)
|
||||
|
||||
# Setting the last size of the dialog window
|
||||
window_size = self._settings.get("epg_tool_window_size")
|
||||
if window_size:
|
||||
@@ -177,8 +181,11 @@ class EpgDialog:
|
||||
def on_update(self, item=None):
|
||||
self.clear_data()
|
||||
self.init_options()
|
||||
gen = self.init_data()
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
if self._update_epg_data_on_start:
|
||||
self.download_epg_from_stb()
|
||||
else:
|
||||
gen = self.init_data()
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def clear_data(self):
|
||||
self._services_model.clear()
|
||||
@@ -194,23 +201,15 @@ class EpgDialog:
|
||||
|
||||
refs = None
|
||||
if self._enable_dat_filter:
|
||||
if self._update_epg_data_on_start:
|
||||
try:
|
||||
self.download_epg_from_stb()
|
||||
except OSError as e:
|
||||
self.show_info_message(f"Download epg.dat file error: {e}", Gtk.MessageType.ERROR)
|
||||
return
|
||||
yield True
|
||||
|
||||
try:
|
||||
refs = EPG.get_epg_refs(self._epg_dat_path_entry.get_text() + "epg.dat")
|
||||
except FileNotFoundError as e:
|
||||
except (OSError, ValueError) as e:
|
||||
self.show_info_message(f"Read data error: {e}", Gtk.MessageType.ERROR)
|
||||
return
|
||||
yield True
|
||||
|
||||
if self._refs_source is RefsSource.SERVICES:
|
||||
self.init_lamedb_source(refs)
|
||||
yield from self.init_lamedb_source(refs)
|
||||
elif self._refs_source is RefsSource.XML:
|
||||
xml_gen = self.init_xml_source(refs)
|
||||
try:
|
||||
@@ -235,8 +234,15 @@ class EpgDialog:
|
||||
s_types = (BqServiceType.MARKER.value, BqServiceType.IPTV.value)
|
||||
filtered = filter(None, [srvs.get(ref) for ref in refs]) if refs else filter(
|
||||
lambda s: s.service_type not in s_types, self._ex_services.values())
|
||||
list(map(self._services_model.append, map(lambda s: (s.service, s.pos, s.fav_id), filtered)))
|
||||
|
||||
factor = self._app.DEL_FACTOR / 4
|
||||
for index, srv in enumerate(filtered):
|
||||
self._services_model.append((srv.service, srv.pos, srv.fav_id))
|
||||
if index % factor == 0:
|
||||
yield True
|
||||
|
||||
self.update_source_count_info()
|
||||
yield True
|
||||
|
||||
def init_xml_source(self, refs):
|
||||
path = self._epg_dat_path_entry.get_text() if self._use_web_source else self._xml_chooser_button.get_filename()
|
||||
@@ -305,7 +311,13 @@ class EpgDialog:
|
||||
else:
|
||||
if refs:
|
||||
s_refs = filter(lambda x: x.num in refs, s_refs)
|
||||
list(map(lambda s: self._services_model.append((s.name, s.data)), s_refs))
|
||||
|
||||
factor = self._app.DEL_FACTOR / 4
|
||||
for index, srv in enumerate(s_refs):
|
||||
self._services_model.append((srv.name, " ", srv.data))
|
||||
if index % factor == 0:
|
||||
yield True
|
||||
|
||||
self.update_source_info(info)
|
||||
self.update_source_count_info()
|
||||
yield True
|
||||
@@ -323,6 +335,13 @@ class EpgDialog:
|
||||
elif ctrl and key is KeyboardKey.V:
|
||||
self.on_assign_ref()
|
||||
|
||||
def on_bq_cursor_changed(self, view):
|
||||
if self._filter_bar.get_visible() and self._filter_auto_switch.get_active():
|
||||
path, column = view.get_cursor()
|
||||
model = view.get_model()
|
||||
if path:
|
||||
self._filter_entry.set_text(model[path][Column.FAV_SERVICE] or "")
|
||||
|
||||
@run_idle
|
||||
def on_save_to_xml(self, item):
|
||||
response = show_dialog(DialogType.CHOOSER, self._dialog, settings=self._settings)
|
||||
@@ -403,12 +422,16 @@ class EpgDialog:
|
||||
self._services[new_fav_id] = service
|
||||
row[Column.FAV_ID] = new_fav_id
|
||||
row[Column.FAV_LOCKED] = EPG_ICON
|
||||
src = f"{get_message('EPG source')}: {data[0]} ({data[1]})"
|
||||
pos = f"({data[1] if self._refs_source is RefsSource.SERVICES else 'XML'})"
|
||||
src = f"{get_message('EPG source')}: {(GLib.markup_escape_text(data[0] or ''))} {pos}"
|
||||
row[Column.FAV_TOOLTIP] = f"{get_message('Service reference')}: {':'.join(fav_id_data[:10])}\n{src}"
|
||||
|
||||
def on_filter_toggled(self, button: Gtk.ToggleButton):
|
||||
self._filter_bar.set_search_mode(button.get_active())
|
||||
def on_filter_toggled(self, button):
|
||||
self._filter_bar.set_visible(button.get_active())
|
||||
if not button.get_active():
|
||||
self._filter_entry.set_text("")
|
||||
|
||||
@run_with_delay(1)
|
||||
def on_filter_changed(self, entry):
|
||||
self._services_filter_model.refilter()
|
||||
|
||||
@@ -582,10 +605,19 @@ class EpgDialog:
|
||||
|
||||
# ***************** Downloads *********************#
|
||||
|
||||
def on_epg_dat_downloaded(self, app, value):
|
||||
gen = self.init_data()
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
@run_task
|
||||
def download_epg_from_stb(self):
|
||||
""" Download the epg.dat file via ftp from the receiver. """
|
||||
download_data(settings=self._settings, download_type=DownloadType.EPG, callback=print)
|
||||
try:
|
||||
download_data(settings=self._settings, download_type=DownloadType.EPG, callback=print)
|
||||
except Exception as e:
|
||||
GLib.idle_add(self.show_info_message, f"Download epg.dat file error: {e}", Gtk.MessageType.ERROR)
|
||||
else:
|
||||
GLib.idle_add(self._app.emit, "epg-dat-downloaded", None)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
453
app/ui/ftp.glade
453
app/ui/ftp.glade
@@ -84,116 +84,59 @@ Author: Dmitriy Yefremov
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkFrame" id="main_frame">
|
||||
<object class="GtkBox" id="main_ftp_box">
|
||||
<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="margin_top">2</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_ftp_box">
|
||||
<object class="GtkPaned" id="paned">
|
||||
<property name="width_request">320</property>
|
||||
<property name="height_request">240</property>
|
||||
<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">True</property>
|
||||
<property name="position_set">True</property>
|
||||
<property name="wide_handle">True</property>
|
||||
<signal name="size-allocate" handler="on_paned_size_allocate" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkBox" id="ftp_button_box">
|
||||
<object class="GtkFrame" id="ftp_frame">
|
||||
<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="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="connect_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</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">False</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="GtkComboBox" id="bookmark_button">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="model">bookmarks_list_store</property>
|
||||
<property name="id_column">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">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkPaned" id="paned">
|
||||
<property name="width_request">320</property>
|
||||
<property name="height_request">240</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="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="label_xalign">0.49000000953674316</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="ftp_bpx">
|
||||
<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="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="ftp_info_box">
|
||||
<object class="GtkBox" id="ftp_button_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="ftp_label">
|
||||
<object class="GtkButton" id="connect_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label">FTP:</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="semibold"/>
|
||||
</attributes>
|
||||
<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>
|
||||
@@ -202,12 +145,93 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="ftp_info_label">
|
||||
<object class="GtkButton" id="disconnect_button">
|
||||
<property name="can_focus">False</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="GtkBox" id="ftp_actions_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="max_width_chars">75</property>
|
||||
<property name="yalign">1</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="ftp_add_folder_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Add folder</property>
|
||||
<signal name="clicked" handler="on_ftp_create_folder" object="ftp_name_column_renderer" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="ftp_add_folder_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">folder-new-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="ftp_edit_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Edit</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_ftp_edit" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="ftp_edit_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-edit-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="ftp_remove_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<signal name="clicked" handler="on_ftp_remove" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="ftp_remove_button_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">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -215,6 +239,19 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="bookmark_button">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="model">bookmarks_list_store</property>
|
||||
<property name="id_column">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -269,7 +306,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkCellRendererText" id="ftp_name_column_renderer">
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<signal name="edited" handler="on_ftp_edited" swapped="no"/>
|
||||
<signal name="edited" handler="on_ftp_renamed" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
@@ -344,37 +381,22 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="file_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="pc_info_box">
|
||||
<object class="GtkBox">
|
||||
<property name="height_request">24</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="pc_label">
|
||||
<object class="GtkImage" id="ftp_info_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="label" translatable="yes">PC:</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="semibold"/>
|
||||
</attributes>
|
||||
<property name="icon_name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -383,11 +405,104 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="pc_info_label">
|
||||
<object class="GtkLabel" id="ftp_info_label">
|
||||
<property name="height_request">16</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="max_width_chars">75</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">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="ftp_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label">FTP</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="semibold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="pc_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>
|
||||
<child>
|
||||
<object class="GtkBox" id="file_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="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="pc_header_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="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="pc_add_folder_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Add folder</property>
|
||||
<signal name="clicked" handler="on_file_create_folder" object="file_name_column_renderer" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="pc_add_folder_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">folder-new-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="pc_remove_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<signal name="clicked" handler="on_file_remove" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="pc_remove_button_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>
|
||||
@@ -448,7 +563,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="file_name_column_renderer">
|
||||
<property name="ellipsize">end</property>
|
||||
<signal name="edited" handler="on_file_edited" swapped="no"/>
|
||||
<signal name="edited" handler="on_file_renamed" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
@@ -523,27 +638,64 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="height_request">24</property>
|
||||
<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="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="pc_info_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="pc_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">PC</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="semibold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="main_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">FTP client</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_image">
|
||||
@@ -559,7 +711,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkImage" id="rename_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">gtk-edit</property>
|
||||
<property name="icon_name">edit-find-replace</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="ftp_popup_menu">
|
||||
<property name="visible">True</property>
|
||||
@@ -577,13 +729,24 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="ftp_edit_menu_item">
|
||||
<property name="label" translatable="yes">Edit</property>
|
||||
<property name="label">gtk-edit</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_ftp_edit" swapped="no"/>
|
||||
<accelerator key="F4" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="ftp_rename_menu_item">
|
||||
<property name="label" translatable="yes">Rename</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">rename_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_ftp_edit" object="ftp_name_column_renderer" swapped="no"/>
|
||||
<accelerator key="r" signal="activate" modifiers="Primary"/>
|
||||
<signal name="activate" handler="on_ftp_rename" object="ftp_name_column_renderer" swapped="no"/>
|
||||
<accelerator key="r" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
<accelerator key="F2" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
@@ -608,7 +771,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkImage" id="rename_image_2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">gtk-edit</property>
|
||||
<property name="icon_name">edit-find-replace</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="file_popup_menu">
|
||||
<property name="visible">True</property>
|
||||
@@ -625,14 +788,14 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="file_edit_menu_item">
|
||||
<property name="label" translatable="yes">Edit</property>
|
||||
<object class="GtkImageMenuItem" id="file_rename_menu_item">
|
||||
<property name="label" translatable="yes">Rename</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">rename_image_2</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_file_edit" object="file_name_column_renderer" swapped="no"/>
|
||||
<accelerator key="r" signal="activate" modifiers="Primary"/>
|
||||
<signal name="activate" handler="on_file_rename" object="file_name_column_renderer" swapped="no"/>
|
||||
<accelerator key="r" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
<accelerator key="F2" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
226
app/ui/ftp.py
226
app/ui/ftp.py
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2022 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,6 +32,7 @@ from collections import namedtuple
|
||||
from datetime import datetime
|
||||
from enum import IntEnum
|
||||
from ftplib import all_errors
|
||||
from io import TextIOWrapper, BytesIO
|
||||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
from urllib.parse import urlparse, unquote
|
||||
@@ -40,9 +41,10 @@ from gi.repository import GLib
|
||||
|
||||
from app.commons import log, run_task, run_idle
|
||||
from app.connections import UtfFTP
|
||||
from app.settings import IS_LINUX, IS_DARWIN, IS_WIN
|
||||
from app.ui.dialogs import show_dialog, DialogType, get_builder
|
||||
from app.ui.main_helper import on_popup_menu
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, IS_GNOME_SESSION
|
||||
|
||||
File = namedtuple("File", ["icon", "name", "size", "date", "attr", "extra"])
|
||||
|
||||
@@ -63,6 +65,86 @@ class FtpClientBox(Gtk.HBox):
|
||||
ATTR = 4
|
||||
EXTRA = 5
|
||||
|
||||
class TextEditDialog(Gtk.Dialog):
|
||||
""" Simple text edit dialog. """
|
||||
|
||||
def __init__(self, path, use_header_bar=0, *args, **kwargs):
|
||||
super().__init__(title=f"DemonEditor [{path}]", use_header_bar=use_header_bar, *args, **kwargs)
|
||||
|
||||
self.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK, )
|
||||
|
||||
content_box = self.get_content_area()
|
||||
self._search_entry = Gtk.SearchEntry(visible=True, primary_icon_name="system-search-symbolic")
|
||||
self._search_entry.connect("search-changed", self.on_search_changed)
|
||||
|
||||
if use_header_bar:
|
||||
bar = self.get_header_bar()
|
||||
bar.pack_start(self._search_entry)
|
||||
bar.set_title("DemonEditor")
|
||||
bar.set_subtitle(path)
|
||||
else:
|
||||
search_bar = Gtk.SearchBar(visible=True)
|
||||
search_bar.add(self._search_entry)
|
||||
search_bar.set_search_mode(True)
|
||||
content_box.pack_start(search_bar, False, False, 0)
|
||||
|
||||
scrolled_window = Gtk.ScrolledWindow(hexpand=True, vexpand=True,
|
||||
min_content_width=720,
|
||||
min_content_height=320)
|
||||
content_box.pack_start(scrolled_window, True, True, 0)
|
||||
|
||||
try:
|
||||
import gi
|
||||
|
||||
gi.require_version("GtkSource", "3.0")
|
||||
from gi.repository import GtkSource
|
||||
except (ImportError, ValueError) as e:
|
||||
self._text_view = Gtk.TextView()
|
||||
self._buf = self._text_view.get_buffer()
|
||||
log(e)
|
||||
else:
|
||||
self._text_view = GtkSource.View(show_line_numbers=True, show_line_marks=True)
|
||||
self._buf = self._text_view.get_buffer()
|
||||
self._buf.set_highlight_syntax(True)
|
||||
self._buf.set_highlight_matching_brackets(True)
|
||||
lang_manager = GtkSource.LanguageManager.new()
|
||||
self._buf.set_language(lang_manager.guess_language(path))
|
||||
# Style
|
||||
self._buf.set_style_scheme(GtkSource.StyleSchemeManager().get_default().get_scheme("tango"))
|
||||
|
||||
self._tag_found = self._buf.create_tag("found", background="yellow")
|
||||
scrolled_window.add(self._text_view)
|
||||
|
||||
self.show_all()
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self._buf.get_text(self._buf.get_start_iter(), self._buf.get_end_iter(), include_hidden_chars=True)
|
||||
|
||||
@text.setter
|
||||
def text(self, value):
|
||||
self._buf.set_text(value)
|
||||
|
||||
def on_search_changed(self, entry):
|
||||
self._buf.remove_tag(self._tag_found, self._buf.get_start_iter(), self._buf.get_end_iter())
|
||||
cursor_mark = self._buf.get_insert()
|
||||
start = self._buf.get_iter_at_mark(cursor_mark)
|
||||
if start.get_offset() == self._buf.get_char_count():
|
||||
start = self._buf.get_start_iter()
|
||||
|
||||
self.search_and_mark(entry.get_text(), start)
|
||||
|
||||
def search_and_mark(self, text, start, first=True):
|
||||
end = self._buf.get_end_iter()
|
||||
match = start.forward_search(text, 0, end)
|
||||
|
||||
if match is not None:
|
||||
match_start, match_end = match
|
||||
self._buf.apply_tag(self._tag_found, match_start, match_end)
|
||||
if first:
|
||||
self._text_view.scroll_to_iter(match_start, 0.0, False, 0.0, 0.0)
|
||||
GLib.idle_add(self.search_and_mark, text, match_end, False)
|
||||
|
||||
def __init__(self, app, settings, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_spacing(2)
|
||||
@@ -78,9 +160,10 @@ class FtpClientBox(Gtk.HBox):
|
||||
"on_ftp_row_activated": self.on_ftp_row_activated,
|
||||
"on_file_row_activated": self.on_file_row_activated,
|
||||
"on_ftp_edit": self.on_ftp_edit,
|
||||
"on_ftp_edited": self.on_ftp_edited,
|
||||
"on_file_edit": self.on_file_edit,
|
||||
"on_file_edited": self.on_file_edited,
|
||||
"on_ftp_rename": self.on_ftp_rename,
|
||||
"on_ftp_renamed": self.on_ftp_renamed,
|
||||
"on_file_rename": self.on_file_rename,
|
||||
"on_file_renamed": self.on_file_renamed,
|
||||
"on_file_remove": self.on_file_remove,
|
||||
"on_ftp_remove": self.on_ftp_file_remove,
|
||||
"on_file_create_folder": self.on_file_create_folder,
|
||||
@@ -94,11 +177,12 @@ class FtpClientBox(Gtk.HBox):
|
||||
"on_view_popup_menu": on_popup_menu,
|
||||
"on_view_key_press": self.on_view_key_press,
|
||||
"on_view_press": self.on_view_press,
|
||||
"on_view_release": self.on_view_release}
|
||||
"on_view_release": self.on_view_release,
|
||||
"on_paned_size_allocate": self.on_paned_size_allocate}
|
||||
|
||||
builder = get_builder(UI_RESOURCES_PATH + "ftp.glade", handlers)
|
||||
|
||||
self.add(builder.get_object("main_frame"))
|
||||
self.add(builder.get_object("main_ftp_box"))
|
||||
self._ftp_info_label = builder.get_object("ftp_info_label")
|
||||
self._ftp_view = builder.get_object("ftp_view")
|
||||
self._ftp_model = builder.get_object("ftp_list_store")
|
||||
@@ -109,8 +193,10 @@ class FtpClientBox(Gtk.HBox):
|
||||
# Buttons
|
||||
self._connect_button = builder.get_object("connect_button")
|
||||
disconnect_button = builder.get_object("disconnect_button")
|
||||
disconnect_button.bind_property("visible", builder.get_object("ftp_actions_box"), "sensitive")
|
||||
disconnect_button.bind_property("visible", builder.get_object("ftp_create_folder_menu_item"), "sensitive")
|
||||
disconnect_button.bind_property("visible", builder.get_object("ftp_edit_menu_item"), "sensitive")
|
||||
disconnect_button.bind_property("visible", builder.get_object("ftp_rename_menu_item"), "sensitive")
|
||||
disconnect_button.bind_property("visible", builder.get_object("ftp_remove_menu_item"), "sensitive")
|
||||
self._connect_button.bind_property("visible", builder.get_object("disconnect_button"), "visible", 4)
|
||||
# Force Ctrl
|
||||
@@ -219,7 +305,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
else:
|
||||
r_size = self.get_size_from_bytes(size)
|
||||
|
||||
date = "{}, {} {}".format(f_data[5], f_data[6], f_data[7])
|
||||
date = f"{f_data[5]}, {f_data[6]} {f_data[7]}"
|
||||
self._ftp_model.append(File(icon, " ".join(f_data[8:]), r_size, date, f_data[0], size))
|
||||
|
||||
def on_connect(self, item=None):
|
||||
@@ -259,23 +345,22 @@ class FtpClientBox(Gtk.HBox):
|
||||
def open_file(self, path):
|
||||
GLib.idle_add(self._file_view.set_sensitive, False)
|
||||
try:
|
||||
cmd = ["open" if self._settings.is_darwin else "xdg-open", path]
|
||||
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
||||
cmd = [self.get_open_file_cmd(), path]
|
||||
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=IS_WIN).communicate()
|
||||
finally:
|
||||
GLib.idle_add(self._file_view.set_sensitive, True)
|
||||
|
||||
@run_task
|
||||
def open_ftp_file(self, f_path):
|
||||
is_darwin = self._settings.is_darwin
|
||||
GLib.idle_add(self._ftp_view.set_sensitive, False)
|
||||
|
||||
try:
|
||||
import tempfile
|
||||
import os
|
||||
path = os.path.expanduser("~/Desktop") if is_darwin else None
|
||||
path = os.path.expanduser("~/Desktop") if not IS_LINUX else None
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="wb", dir=path, delete=not is_darwin) as tf:
|
||||
msg = "Downloading file: {}. Status: {}"
|
||||
with tempfile.NamedTemporaryFile(mode="wb", dir=path, delete=IS_LINUX) as tf:
|
||||
msg = "Downloading file: {}. Status: {}"
|
||||
try:
|
||||
status = self._ftp.retrbinary("RETR " + f_path, tf.write)
|
||||
self.update_ftp_info(msg.format(f_path, status))
|
||||
@@ -283,12 +368,74 @@ class FtpClientBox(Gtk.HBox):
|
||||
self.update_ftp_info(msg.format(f_path, e))
|
||||
|
||||
tf.flush()
|
||||
cmd = ["open" if is_darwin else "xdg-open", tf.name]
|
||||
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
||||
|
||||
cmd = [self.get_open_file_cmd(), tf.name]
|
||||
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=IS_WIN).communicate()
|
||||
finally:
|
||||
GLib.idle_add(self._ftp_view.set_sensitive, True)
|
||||
|
||||
def on_ftp_edit(self, renderer):
|
||||
@staticmethod
|
||||
def get_open_file_cmd():
|
||||
if IS_DARWIN:
|
||||
return "open"
|
||||
elif IS_WIN:
|
||||
return "start"
|
||||
return "xdg-open"
|
||||
|
||||
@run_task
|
||||
def on_ftp_edit(self, item=None):
|
||||
path = self.get_ftp_edit_path()
|
||||
if path:
|
||||
row = self._ftp_model[path]
|
||||
f_path = row[self.Column.NAME]
|
||||
size = row[self.Column.SIZE]
|
||||
|
||||
if size == self.FOLDER or f_path == self.ROOT:
|
||||
self._app.show_error_message("Not allowed in this context!")
|
||||
else:
|
||||
b_size = row[self.Column.EXTRA]
|
||||
if b_size.isdigit() and int(b_size) > self.MAX_SIZE / 5:
|
||||
self._app.show_error_message("The file size is too large!")
|
||||
else:
|
||||
msg = "Retrieving file: {}. Status: {}"
|
||||
io = BytesIO()
|
||||
try:
|
||||
status = self._ftp.retrbinary("RETR " + f_path, io.write)
|
||||
self.update_ftp_info(msg.format(f_path, status))
|
||||
except all_errors as e:
|
||||
self.update_ftp_info(msg.format(f_path, e))
|
||||
else:
|
||||
io.seek(0)
|
||||
self.show_edit_dialog(f_path, TextIOWrapper(io, errors="ignore").read())
|
||||
|
||||
def on_ftp_edited(self, f_path, txt_data):
|
||||
buf = BytesIO()
|
||||
buf.write(txt_data.encode())
|
||||
buf.seek(0)
|
||||
|
||||
msg = "Uploading file: {}. Status: {}"
|
||||
try:
|
||||
status = self._ftp.storbinary(f"STOR {f_path}", buf)
|
||||
self.update_ftp_info(msg.format(f_path, status))
|
||||
except all_errors as e:
|
||||
self.update_ftp_info(msg.format(f_path, e))
|
||||
|
||||
@run_idle
|
||||
def show_edit_dialog(self, f_path, data):
|
||||
dialog = self.TextEditDialog(f_path, IS_GNOME_SESSION)
|
||||
dialog.text = data
|
||||
ok = Gtk.ResponseType.OK
|
||||
if dialog.run() == ok and show_dialog(DialogType.QUESTION, self._app.app_window) == ok:
|
||||
self.on_ftp_edited(f_path, dialog.text)
|
||||
dialog.destroy()
|
||||
|
||||
def on_ftp_rename(self, renderer):
|
||||
path = self.get_ftp_edit_path()
|
||||
if path:
|
||||
renderer.set_property("editable", True)
|
||||
self._ftp_view.set_cursor(path, self._ftp_view.get_column(0), True)
|
||||
|
||||
def get_ftp_edit_path(self):
|
||||
model, paths = self._ftp_view.get_selection().get_selected_rows()
|
||||
if not paths:
|
||||
return
|
||||
@@ -296,11 +443,9 @@ class FtpClientBox(Gtk.HBox):
|
||||
if len(paths) > 1:
|
||||
self._app.show_error_message("Please, select only one item!")
|
||||
return
|
||||
return paths
|
||||
|
||||
renderer.set_property("editable", True)
|
||||
self._ftp_view.set_cursor(paths, self._ftp_view.get_column(0), True)
|
||||
|
||||
def on_ftp_edited(self, renderer, path, new_value):
|
||||
def on_ftp_renamed(self, renderer, path, new_value):
|
||||
renderer.set_property("editable", False)
|
||||
row = self._ftp_model[path]
|
||||
old_name = row[self.Column.NAME]
|
||||
@@ -308,11 +453,11 @@ class FtpClientBox(Gtk.HBox):
|
||||
return
|
||||
|
||||
resp = self._ftp.rename_file(old_name, new_value)
|
||||
self.update_ftp_info("{} Status: {}".format(old_name, resp))
|
||||
self.update_ftp_info(f"{old_name} Status: {resp}")
|
||||
if resp[0] == "2":
|
||||
row[self.Column.NAME] = new_value
|
||||
|
||||
def on_file_edit(self, renderer):
|
||||
def on_file_rename(self, renderer):
|
||||
model, paths = self._file_view.get_selection().get_selected_rows()
|
||||
if len(paths) > 1:
|
||||
self._app.show_error_message("Please, select only one item!")
|
||||
@@ -321,7 +466,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
renderer.set_property("editable", True)
|
||||
self._file_view.set_cursor(paths, self._file_view.get_column(0), True)
|
||||
|
||||
def on_file_edited(self, renderer, path, new_value):
|
||||
def on_file_renamed(self, renderer, path, new_value):
|
||||
renderer.set_property("editable", False)
|
||||
row = self._file_model[path]
|
||||
old_name = row[self.Column.NAME]
|
||||
@@ -331,7 +476,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
path = Path(row[self.Column.ATTR])
|
||||
if path.exists():
|
||||
try:
|
||||
new_path = path.rename("{}/{}".format(path.parent, new_value))
|
||||
new_path = path.rename(f"{path.parent}/{new_value}")
|
||||
except ValueError as e:
|
||||
log(e)
|
||||
self._app.show_error_message(str(e))
|
||||
@@ -341,7 +486,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
row[self.Column.ATTR] = str(new_path.resolve())
|
||||
|
||||
def on_file_remove(self, item=None):
|
||||
if show_dialog(DialogType.QUESTION, self._app._main_window) != Gtk.ResponseType.OK:
|
||||
if show_dialog(DialogType.QUESTION, self._app.app_window) != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
model, paths = self._file_view.get_selection().get_selected_rows()
|
||||
@@ -359,7 +504,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
list(map(model.remove, to_delete))
|
||||
|
||||
def on_ftp_file_remove(self, item=None):
|
||||
if show_dialog(DialogType.QUESTION, self._app._main_window) != Gtk.ResponseType.OK:
|
||||
if show_dialog(DialogType.QUESTION, self._app.app_window) != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
model, paths = self._ftp_view.get_selection().get_selected_rows()
|
||||
@@ -385,7 +530,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
|
||||
name = self.get_new_folder_name(self._file_model)
|
||||
cur_path = self._file_model.get_value(itr, self.Column.ATTR)
|
||||
path = Path("{}/{}".format(cur_path, name))
|
||||
path = Path(f"{cur_path}/{name}")
|
||||
|
||||
try:
|
||||
path.mkdir()
|
||||
@@ -406,13 +551,13 @@ class FtpClientBox(Gtk.HBox):
|
||||
name = self.get_new_folder_name(self._ftp_model)
|
||||
|
||||
try:
|
||||
folder = "{}/{}".format(cur_path, name)
|
||||
folder = f"{cur_path}/{name}"
|
||||
resp = self._ftp.mkd(folder)
|
||||
except all_errors as e:
|
||||
self.update_ftp_info(str(e))
|
||||
log(e)
|
||||
else:
|
||||
if resp == "{}/{}".format(cur_path, name):
|
||||
if resp == f"{cur_path}/{name}":
|
||||
itr = self._ftp_model.append(File(self._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)
|
||||
@@ -424,7 +569,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
count = 0
|
||||
while name in names:
|
||||
count += 1
|
||||
name = "{}{}".format(name, count)
|
||||
name = f"{name}{count}"
|
||||
return name
|
||||
|
||||
# ***************** Drag-and-drop ********************* #
|
||||
@@ -459,7 +604,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
uris = []
|
||||
for r in [model[p][:] for p in paths]:
|
||||
if r[self.Column.SIZE] != self.LINK and r[self.Column.NAME] != self.ROOT:
|
||||
uris.append(Path("/{}:{}".format(r[self.Column.NAME], r[self.Column.ATTR])).as_uri())
|
||||
uris.append(Path(f"/{r[self.Column.NAME]}:{r[self.Column.ATTR]}").as_uri())
|
||||
data.set_uris([sep.join(uris)])
|
||||
|
||||
@run_task
|
||||
@@ -556,9 +701,12 @@ class FtpClientBox(Gtk.HBox):
|
||||
self.on_file_create_folder(self._file_name_renderer)
|
||||
elif key is KeyboardKey.F2 or ctrl and KeyboardKey.R:
|
||||
if self._ftp_view.is_focus():
|
||||
self.on_ftp_edit(self._ftp_name_renderer)
|
||||
self.on_ftp_rename(self._ftp_name_renderer)
|
||||
elif self._file_view.is_focus():
|
||||
self.on_file_edit(self._file_name_renderer)
|
||||
self.on_file_rename(self._file_name_renderer)
|
||||
elif key is KeyboardKey.F4:
|
||||
if self._ftp_view.is_focus():
|
||||
self.on_ftp_edit()
|
||||
elif key is KeyboardKey.DELETE:
|
||||
if self._ftp_view.is_focus():
|
||||
self.on_ftp_file_remove()
|
||||
@@ -581,6 +729,10 @@ class FtpClientBox(Gtk.HBox):
|
||||
# Enable selection.
|
||||
self._select_enabled = True
|
||||
|
||||
def on_paned_size_allocate(self, paned, allocation):
|
||||
""" Sets default homogeneous sizes. """
|
||||
paned.set_position(0.5 * allocation.width)
|
||||
|
||||
def get_size_from_bytes(self, size):
|
||||
""" Simple convert function from bytes to other units like K, M or G. """
|
||||
try:
|
||||
@@ -593,11 +745,11 @@ class FtpClientBox(Gtk.HBox):
|
||||
if b < kb:
|
||||
return str(b)
|
||||
elif kb <= b < mb:
|
||||
return "{0:.1f} K".format(b / kb)
|
||||
return f"{b / kb:.1f} K"
|
||||
elif mb <= b < gb:
|
||||
return "{0:.1f} M".format(b / mb)
|
||||
return f"{b / mb:.1f} M"
|
||||
elif gb <= b:
|
||||
return "{0:.1f} G".format(b / gb)
|
||||
return f"{b / gb:.1f} G"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1253,6 +1253,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">True</property>
|
||||
<property name="width_chars">5</property>
|
||||
<property name="max_width_chars">4</property>
|
||||
<property name="text">0</property>
|
||||
<property name="primary_icon_name">document-edit-symbolic</property>
|
||||
<signal name="changed" handler="on_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
588
app/ui/main.py
588
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-2021 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2022 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
|
||||
@@ -29,10 +29,10 @@
|
||||
""" Helper module for the GUI. """
|
||||
|
||||
__all__ = ("insert_marker", "move_items", "rename", "ViewTarget", "set_flags", "locate_in_services",
|
||||
"scroll_to", "get_base_model", "update_picons_data", "copy_picon_reference", "assign_picons",
|
||||
"remove_picon", "is_only_one_item_selected", "gen_bouquets", "BqGenType", "append_picons",
|
||||
"get_selection", "get_model_data", "remove_all_unused_picons", "get_picon_pixbuf", "get_base_itrs",
|
||||
"get_iptv_url", "update_entry_data", "append_text_to_tview", "on_popup_menu")
|
||||
"scroll_to", "get_base_model", "copy_picon_reference", "assign_picons", "remove_picon",
|
||||
"is_only_one_item_selected", "gen_bouquets", "BqGenType", "get_selection",
|
||||
"get_model_data", "remove_all_unused_picons", "get_picon_pixbuf", "get_base_itrs", "get_iptv_url",
|
||||
"update_entry_data", "append_text_to_tview", "on_popup_menu")
|
||||
|
||||
import os
|
||||
import shutil
|
||||
@@ -44,8 +44,8 @@ from gi.repository import GdkPixbuf, GLib
|
||||
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.settings import SettingsType, SEP, IS_WIN
|
||||
from .dialogs import show_dialog, DialogType, get_chooser_dialog
|
||||
from app.settings import SettingsType, SEP, IS_WIN, IS_DARWIN, IS_LINUX
|
||||
from .dialogs import show_dialog, DialogType, get_message
|
||||
from .uicommons import ViewTarget, BqGenType, Gtk, Gdk, HIDE_ICON, LOCKED_ICON, KeyboardKey, Column
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ def insert_marker(view, bouquets, selected_bouquet, services, parent_window, m_t
|
||||
show_dialog(DialogType.ERROR, parent_window, "The text of marker is empty, please try again!")
|
||||
return
|
||||
|
||||
fav_id = "1:64:0:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n".format(response, response)
|
||||
fav_id = f"1:64:0:0:0:0:0:0:0:0::{response}\n#DESCRIPTION {response}\n"
|
||||
text = response
|
||||
|
||||
s_type = m_type.name
|
||||
@@ -110,8 +110,8 @@ def move_items(key, view: Gtk.TreeView):
|
||||
children_num = model.iter_n_children(parent_itr)
|
||||
if key in (KeyboardKey.PAGE_DOWN, KeyboardKey.END, KeyboardKey.END_KP, KeyboardKey.PAGE_DOWN_KP):
|
||||
children_num -= 1
|
||||
min_path = Gtk.TreePath.new_from_string("{}:{}".format(parent_index, 0))
|
||||
max_path = Gtk.TreePath.new_from_string("{}:{}".format(parent_index, children_num))
|
||||
min_path = Gtk.TreePath.new_from_string(f"{parent_index}:{0}")
|
||||
max_path = Gtk.TreePath.new_from_string(f"{parent_index}:{children_num}")
|
||||
is_tree_store = True
|
||||
|
||||
if key is KeyboardKey.UP:
|
||||
@@ -281,7 +281,7 @@ def set_lock(blacklist, services, model, paths, target, services_model):
|
||||
locked = has_locked_hide(model, paths, col_num)
|
||||
|
||||
ids = []
|
||||
skip_type = {BqServiceType.MARKER.name, BqServiceType.SPACE.name}
|
||||
skip_type = {BqServiceType.MARKER.name, BqServiceType.SPACE.name, BqServiceType.ALT.name}
|
||||
|
||||
for path in paths:
|
||||
itr = model.get_iter(path)
|
||||
@@ -386,26 +386,36 @@ def scroll_to(index, view, paths=None):
|
||||
selection.select_path(index)
|
||||
|
||||
|
||||
# ***************** Picons *********************#
|
||||
# ***************** Picons ********************* #
|
||||
|
||||
def update_picons_data(path, picons, size=32):
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
def get_picon_dialog(transient, title, button_text, multiple=True):
|
||||
""" Returns a copy dialog with a preview of images [picons -> *.png]. """
|
||||
dialog = Gtk.FileChooserNative.new(title, transient, Gtk.FileChooserAction.OPEN, button_text)
|
||||
dialog.set_select_multiple(multiple)
|
||||
dialog.set_modal(True)
|
||||
# Filter.
|
||||
file_filter = Gtk.FileFilter()
|
||||
file_filter.set_name("*.png")
|
||||
file_filter.add_pattern("*.png")
|
||||
file_filter.add_mime_type("image/png") if IS_DARWIN else None
|
||||
dialog.add_filter(file_filter)
|
||||
|
||||
for file in os.listdir(path):
|
||||
pf = get_picon_pixbuf(path + file, size)
|
||||
if pf:
|
||||
picons[file] = pf
|
||||
if IS_LINUX:
|
||||
preview_image = Gtk.Image(margin_right=10)
|
||||
dialog.set_preview_widget(preview_image)
|
||||
|
||||
def update_preview_widget(dlg):
|
||||
path = dialog.get_preview_filename()
|
||||
if not path:
|
||||
return
|
||||
|
||||
def append_picons(picons, model):
|
||||
def append_picons_data(pcs, mod):
|
||||
for r in mod:
|
||||
mod.set_value(mod.get_iter(r.path), Column.SRV_PICON, pcs.get(r[Column.SRV_PICON_ID], None))
|
||||
yield True
|
||||
pix = get_picon_pixbuf(path, 220)
|
||||
preview_image.set_from_pixbuf(pix)
|
||||
dlg.set_preview_widget_active(bool(pix))
|
||||
|
||||
app = append_picons_data(picons, model)
|
||||
GLib.idle_add(lambda: next(app, False), priority=GLib.PRIORITY_LOW)
|
||||
dialog.connect("update-preview", update_preview_widget)
|
||||
|
||||
return dialog
|
||||
|
||||
|
||||
def assign_picons(target, srv_view, fav_view, transient, picons, settings, services, src_path=None, dst_path=None):
|
||||
@@ -415,10 +425,12 @@ def assign_picons(target, srv_view, fav_view, transient, picons, settings, servi
|
||||
picons_files = []
|
||||
|
||||
if not src_path:
|
||||
src_path = get_chooser_dialog(transient, settings, "*.png files", ("*.png",))
|
||||
if src_path == Gtk.ResponseType.CANCEL:
|
||||
dialog = get_picon_dialog(transient, get_message("Picon selection"), get_message("Open"), False)
|
||||
if dialog.run() in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT) or not dialog.get_filenames():
|
||||
return picons_files
|
||||
|
||||
src_path = dialog.get_filenames()[0]
|
||||
|
||||
if IS_WIN:
|
||||
src_path = src_path.lstrip("/")
|
||||
dst_path = dst_path.lstrip("/") if dst_path else dst_path
|
||||
@@ -450,7 +462,7 @@ def assign_picons(target, srv_view, fav_view, transient, picons, settings, servi
|
||||
pass # NOP
|
||||
else:
|
||||
picons_files.append(picon_file)
|
||||
picon = get_picon_pixbuf(picon_file)
|
||||
picon = get_picon_pixbuf(picon_file, settings.list_picon_size)
|
||||
picons[picon_id] = picon
|
||||
model.set_value(itr, p_pos, picon)
|
||||
if target is ViewTarget.SERVICES:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2022 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
|
||||
@@ -609,6 +609,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="primary_icon_name">edit-find-replace-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
<property name="placeholder_text" translatable="yes">Filter: N1|N2|N3, etc..</property>
|
||||
<signal name="search-changed" handler="on_picons_filter_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -707,6 +708,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Drag the services to the desired picon or picon to the list of selected services.</property>
|
||||
<property name="model">picons_src_sort_model</property>
|
||||
<property name="fixed_height_mode">True</property>
|
||||
<property name="enable_grid_lines">horizontal</property>
|
||||
<property name="tooltip_column">0</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
@@ -722,11 +724,14 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="src_picon_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min_width">150</property>
|
||||
<property name="title" translatable="yes">Picon</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="picons_src_renderer"/>
|
||||
<object class="GtkCellRendererPixbuf" id="picons_src_renderer">
|
||||
<property name="ypad">5</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="pixbuf">0</attribute>
|
||||
</attributes>
|
||||
@@ -735,6 +740,7 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="src_title_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="title" translatable="yes">Name</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<child>
|
||||
@@ -750,6 +756,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="src_path_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="title" translatable="yes">column</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="path_src_renderer"/>
|
||||
@@ -835,6 +842,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Drag the services to the desired picon or picon to the list of selected services. </property>
|
||||
<property name="model">picons_dst_sort_model</property>
|
||||
<property name="fixed_height_mode">True</property>
|
||||
<property name="enable_grid_lines">horizontal</property>
|
||||
<property name="tooltip_column">0</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
@@ -851,11 +859,14 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="dest_picon_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min_width">150</property>
|
||||
<property name="title" translatable="yes">Picon</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="picons_dest_renderer"/>
|
||||
<object class="GtkCellRendererPixbuf" id="picons_dest_renderer">
|
||||
<property name="ypad">5</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="pixbuf">0</attribute>
|
||||
</attributes>
|
||||
@@ -864,6 +875,7 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="dest_title_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="title" translatable="yes">Name</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
@@ -880,6 +892,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="dest_path_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="title" translatable="yes">column</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="path_dest_renderer"/>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2022 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
|
||||
@@ -37,14 +37,14 @@ from gi.repository import GLib, GdkPixbuf, Gio
|
||||
|
||||
from app.commons import run_idle, run_task, run_with_delay, log
|
||||
from app.connections import upload_data, DownloadType, download_data, remove_picons
|
||||
from app.settings import SettingsType, Settings, SEP, IS_LINUX, IS_DARWIN
|
||||
from app.settings import SettingsType, Settings, SEP, IS_DARWIN
|
||||
from app.tools.picons import (PiconsParser, parse_providers, Provider, convert_to, download_picon, PiconsCzDownloader,
|
||||
PiconsError)
|
||||
from app.tools.satellites import SatellitesParser, SatelliteSource
|
||||
from .dialogs import show_dialog, DialogType, get_message, get_builder, get_chooser_dialog
|
||||
from .main_helper import (update_entry_data, append_text_to_tview, scroll_to, on_popup_menu, get_base_model, set_picon,
|
||||
get_picon_pixbuf)
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column, KeyboardKey, Page
|
||||
get_picon_pixbuf, get_picon_dialog)
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column, KeyboardKey, Page, ViewTarget
|
||||
|
||||
|
||||
class PiconManager(Gtk.Box):
|
||||
@@ -59,6 +59,7 @@ class PiconManager(Gtk.Box):
|
||||
self._app.connect("page-changed", self.update_picons_dest)
|
||||
self._app.connect("filter-toggled", self.on_app_filter_toggled)
|
||||
self._app.connect("profile-changed", self.on_profile_changed)
|
||||
self._app.connect("picon-assign", self.on_picon_assign)
|
||||
self._app.fav_view.connect("row-activated", self.on_fav_changed)
|
||||
self._picon_ids = picon_ids
|
||||
self._sat_positions = sat_positions
|
||||
@@ -196,6 +197,11 @@ class PiconManager(Gtk.Box):
|
||||
self._manager_button.bind_property("active", builder.get_object("add_menu_button"), "visible")
|
||||
# Init drag-and-drop
|
||||
self.init_drag_and_drop()
|
||||
# Rendering.
|
||||
column = builder.get_object("dest_picon_column")
|
||||
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)
|
||||
# Settings
|
||||
self._settings = settings
|
||||
self._s_type = settings.setting_type
|
||||
@@ -250,6 +256,19 @@ class PiconManager(Gtk.Box):
|
||||
self._current_path_label.set_text(self._settings.profile_picons_path)
|
||||
self.update_picons_dest(app, self._app.page)
|
||||
|
||||
def on_picon_assign(self, app, target):
|
||||
if target is ViewTarget.SERVICES:
|
||||
model, paths = app.services_view.get_selection().get_selected_rows()
|
||||
ids = {model[p][Column.SRV_FAV_ID] for p in paths}
|
||||
else:
|
||||
model, paths = app.fav_view.get_selection().get_selected_rows()
|
||||
ids = {model[p][Column.FAV_ID] for p in paths}
|
||||
|
||||
self._filter_button.set_active(True)
|
||||
self._dst_filter_button.set_active(True)
|
||||
self._picons_filter_entry.set_text(
|
||||
"|".join(s.service for f, s in self._app.current_services.items() if f in ids))
|
||||
|
||||
def update_picons_data(self, view, path=None):
|
||||
if view is self._picons_dest_view:
|
||||
self.update_picon_info()
|
||||
@@ -260,7 +279,7 @@ class PiconManager(Gtk.Box):
|
||||
def update_picons(self, path, view):
|
||||
p_model = view.get_model()
|
||||
model = get_base_model(p_model)
|
||||
factor = self._app.DEL_FACTOR
|
||||
factor = self._app.DEL_FACTOR * 2
|
||||
|
||||
for index, itr in enumerate([row.iter for row in model]):
|
||||
model.remove(itr)
|
||||
@@ -268,18 +287,12 @@ class PiconManager(Gtk.Box):
|
||||
yield True
|
||||
|
||||
self._dst_count_label.set_text("0")
|
||||
if not os.path.isdir(path):
|
||||
return
|
||||
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
for index, file in enumerate(os.listdir(path)):
|
||||
if self._terminate:
|
||||
return
|
||||
|
||||
p_path = f"{path}{SEP}{file}"
|
||||
p = self.get_pixbuf_at_scale(p_path, 72, 48, True)
|
||||
if p:
|
||||
model.append((p, file, p_path))
|
||||
|
||||
model.append((None, file, f"{path}{SEP}{file}"))
|
||||
if index % factor == 0:
|
||||
self._dst_count_label.set_text(str(len(model)))
|
||||
yield True
|
||||
@@ -287,6 +300,9 @@ class PiconManager(Gtk.Box):
|
||||
self._dst_count_label.set_text(str(len(model)))
|
||||
yield True
|
||||
|
||||
def picon_data_func(self, column, renderer, model, itr, data):
|
||||
renderer.set_property("pixbuf", self.get_pixbuf_at_scale(model.get_value(itr, 2), 72, 48, True))
|
||||
|
||||
def update_picons_from_file(self, view, uri):
|
||||
""" Adds picons in the view on dragging from file system. """
|
||||
path = Path(urlparse(unquote(uri)).path.strip())
|
||||
@@ -378,7 +394,7 @@ class PiconManager(Gtk.Box):
|
||||
|
||||
t_mod = target_view.get_model()
|
||||
dest_path = self._settings.profile_picons_path
|
||||
self.update_picons_dest_view(self._app.on_assign_picon(target_view, model[path][-1], dest_path))
|
||||
self.update_picons_dest_view(self._app.on_assign_picon_file(target_view, model[path][-1], dest_path))
|
||||
self.show_assign_info([t_mod.get_value(t_mod.get_iter_from_string(itr), c_id) for itr in itr_str.split(",")])
|
||||
|
||||
@run_idle
|
||||
@@ -464,42 +480,12 @@ class PiconManager(Gtk.Box):
|
||||
|
||||
def on_add(self, item):
|
||||
""" Adds (copies) picons from an external folder to the profile picons folder. """
|
||||
dialog = self.get_copy_dialog()
|
||||
dialog = get_picon_dialog(self._app_window, get_message("Add picons"), get_message("Add"))
|
||||
if dialog.run() in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
return
|
||||
|
||||
self.copy_picons_file(dialog.get_filenames())
|
||||
|
||||
def get_copy_dialog(self):
|
||||
""" Returns a copy dialog with a preview of images [picons -> *.png]. """
|
||||
dialog = Gtk.FileChooserNative.new(get_message("Add picons"), self._app_window,
|
||||
Gtk.FileChooserAction.OPEN, get_message("Add"))
|
||||
dialog.set_select_multiple(True)
|
||||
dialog.set_modal(True)
|
||||
# Filter.
|
||||
file_filter = Gtk.FileFilter()
|
||||
file_filter.set_name("*.png")
|
||||
file_filter.add_pattern("*.png")
|
||||
file_filter.add_mime_type("image/png") if IS_DARWIN else None
|
||||
dialog.add_filter(file_filter)
|
||||
|
||||
if IS_LINUX:
|
||||
preview_image = Gtk.Image(margin_right=10)
|
||||
dialog.set_preview_widget(preview_image)
|
||||
|
||||
def update_preview_widget(dlg):
|
||||
path = dialog.get_preview_filename()
|
||||
if not path:
|
||||
return
|
||||
|
||||
pix = get_picon_pixbuf(path, 220)
|
||||
preview_image.set_from_pixbuf(pix)
|
||||
dlg.set_preview_widget_active(bool(pix))
|
||||
|
||||
dialog.connect("update-preview", update_preview_widget)
|
||||
|
||||
return dialog
|
||||
|
||||
def on_extract(self, item):
|
||||
""" Extracts picons from an archives to the profile picons folder. """
|
||||
file_filter = None
|
||||
@@ -571,6 +557,7 @@ class PiconManager(Gtk.Box):
|
||||
dest_path = path or self._settings.profile_picons_path
|
||||
settings = Settings(self._settings.settings)
|
||||
settings.profile_picons_path = f"{dest_path}{SEP}"
|
||||
settings.current_profile = self._settings.current_profile
|
||||
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
|
||||
self.run_func(lambda: upload_data(settings=settings,
|
||||
download_type=DownloadType.PICONS,
|
||||
@@ -583,6 +570,7 @@ class PiconManager(Gtk.Box):
|
||||
path = path or self._settings.profile_picons_path
|
||||
settings = Settings(self._settings.settings)
|
||||
settings.profile_picons_path = path + SEP
|
||||
settings.current_profile = self._settings.current_profile
|
||||
self.run_func(lambda: download_data(settings=settings,
|
||||
download_type=DownloadType.PICONS,
|
||||
callback=self.append_output,
|
||||
@@ -889,15 +877,6 @@ class PiconManager(Gtk.Box):
|
||||
self._is_downloading = False
|
||||
self.show_info_message(get_message("The task is canceled!"), Gtk.MessageType.WARNING)
|
||||
|
||||
def on_close(self, window, event):
|
||||
if self.on_cancel():
|
||||
return True
|
||||
|
||||
self._terminate = True
|
||||
self._is_downloading = False
|
||||
self._app.update_picons()
|
||||
GLib.idle_add(self._app_window.destroy)
|
||||
|
||||
@run_task
|
||||
def run_func(self, func, update=False):
|
||||
try:
|
||||
@@ -955,10 +934,10 @@ class PiconManager(Gtk.Box):
|
||||
|
||||
@run_with_delay(0.5)
|
||||
def on_picons_filter_changed(self, entry):
|
||||
txt = entry.get_text().upper()
|
||||
self._filter_cache.clear()
|
||||
txt = entry.get_text().upper().split("|")
|
||||
for s in self._app.current_services.values():
|
||||
self._filter_cache[s.picon_id] = txt in s.service.upper()
|
||||
self._filter_cache[s.picon_id] = any(t in s.service.upper() or t in str(s.picon_id) for t in txt)
|
||||
|
||||
GLib.idle_add(self._picons_src_filter_model.refilter, priority=GLib.PRIORITY_LOW)
|
||||
GLib.idle_add(self._picons_dst_filter_model.refilter, priority=GLib.PRIORITY_LOW)
|
||||
|
||||
@@ -1397,6 +1397,9 @@ Author: Dmitriy Yefremov
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<signal name="delete-event" handler="on_quit" swapped="no"/>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="satellites_update_main_box">
|
||||
<property name="visible">True</property>
|
||||
@@ -1787,113 +1790,172 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkFrame" id="sat_update_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="label_xalign">0.49000000953674316</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="sat_update_scrolled_window">
|
||||
<object class="GtkBox" id="sat_update_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">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="shadow_type">in</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="sat_update_tree_view">
|
||||
<object class="GtkScrolledWindow" id="sat_update_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">update_sat_list_model_sort</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="satellites_update_popup_menu" swapped="no"/>
|
||||
<signal name="select-all" handler="on_select_all" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="upd_satellite_column">
|
||||
<property name="title" translatable="yes">Satellite</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="upd_satellite_cellrenderertext">
|
||||
<property name="xalign">0.0099999997764825821</property>
|
||||
<object class="GtkTreeView" id="sat_update_tree_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">update_sat_list_model_sort</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="satellites_update_popup_menu" swapped="no"/>
|
||||
<signal name="select-all" handler="on_select_all" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="upd_position_column">
|
||||
<property name="title" translatable="yes">Position</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">1</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_position_cellrenderertext">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
<object class="GtkTreeViewColumn" id="upd_satellite_column">
|
||||
<property name="title" translatable="yes">Satellite</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="upd_satellite_cellrenderertext">
|
||||
<property name="xalign">0.0099999997764825821</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="upd_type_column">
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">2</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_type_cellrenderertext">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
<object class="GtkTreeViewColumn" id="upd_position_column">
|
||||
<property name="title" translatable="yes">Position</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">1</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_position_cellrenderertext">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="upd_url_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title" translatable="yes">Url</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_url_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="upd_selected_column">
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">4</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="upd_selected_cellrenderer">
|
||||
<signal name="toggled" handler="on_satellite_toggled" swapped="no"/>
|
||||
<object class="GtkTreeViewColumn" id="upd_type_column">
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">2</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_type_cellrenderertext">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="upd_url_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title" translatable="yes">Url</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_url_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="upd_selected_column">
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="sort_column_id">4</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="upd_selected_cellrenderer">
|
||||
<signal name="toggled" handler="on_satellite_toggled" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="sat_update_status_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="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="satellites_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-properties-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="satellites_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>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="sat_update_frame_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Satellites</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -1903,190 +1965,318 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="sat_update_tr_frame">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<object class="GtkPaned" id="sat_update_tr_paned">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="wide_handle">True</property>
|
||||
<child>
|
||||
<object class="GtkPaned" id="sat_update_tr_paned">
|
||||
<object class="GtkFrame" id="sat_update_tr_frame">
|
||||
<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="orientation">vertical</property>
|
||||
<property name="wide_handle">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0.49000000953674316</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="sat_update_tr_scrolled_window">
|
||||
<object class="GtkBox" id="sat_update_tr_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_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="sat_update_tr_view">
|
||||
<object class="GtkScrolledWindow" id="sat_update_tr_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">update_transponder_store</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="tr_view_tr_column">
|
||||
<property name="title" translatable="yes">Transponder</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_tr_renderer">
|
||||
<property name="xalign">0.0099999997764825821</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
<object class="GtkTreeView" id="sat_update_tr_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">update_transponder_store</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="tr_view_link_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title" translatable="yes">link</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_tr_link_renderer"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="tr_view_selected_column">
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="upd_tr_select_renderer">
|
||||
<signal name="toggled" handler="on_transponder_toggled" swapped="no"/>
|
||||
<object class="GtkTreeViewColumn" id="tr_view_tr_column">
|
||||
<property name="title" translatable="yes">Transponder</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_tr_renderer">
|
||||
<property name="xalign">0.0099999997764825821</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="tr_view_link_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title" translatable="yes">link</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="upd_tr_link_renderer"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="tr_view_selected_column">
|
||||
<property name="title" translatable="yes">Selected</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="upd_tr_select_renderer">
|
||||
<signal name="toggled" handler="on_transponder_toggled" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">2</attribute>
|
||||
</attributes>
|
||||
</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_update_tr_status_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="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="transponders_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-properties-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="transponders_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>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="sat_update_srv_scrolled_window">
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="sat_update_tr_frame_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="sat_update_srv_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">update_service_store</property>
|
||||
<property name="search_column">1</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="srv_view_servcie_column">
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="srv_picon_renderer">
|
||||
<property name="xpad">2</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="pixbuf">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="srv_service_renderer">
|
||||
<property name="xalign">0.0099999997764825821</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="srv_view_package_column">
|
||||
<property name="title" translatable="yes">Package</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="srv_package_renderer">
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="srv_view_type_column">
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="srv_type_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="srv_view_sid_column">
|
||||
<property name="title" translatable="yes">SID</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="srv_sid_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="srv_view_cas_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title" translatable="yes">CAS</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="srv_cas_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Transponders</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
<child>
|
||||
<object class="GtkFrame" id="sat_update_srv_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>
|
||||
<child>
|
||||
<object class="GtkBox" id="sat_update_srv_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="sat_update_srv_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="sat_update_srv_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">update_service_store</property>
|
||||
<property name="search_column">1</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="srv_view_servcie_column">
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="srv_picon_renderer">
|
||||
<property name="xpad">2</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="pixbuf">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="srv_service_renderer">
|
||||
<property name="xalign">0.0099999997764825821</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="srv_view_package_column">
|
||||
<property name="title" translatable="yes">Package</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="srv_package_renderer">
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="srv_view_type_column">
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="srv_type_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="srv_view_sid_column">
|
||||
<property name="title" translatable="yes">SID</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="srv_sid_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="srv_view_cas_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title" translatable="yes">CAS</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="srv_cas_renderer">
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
</object>
|
||||
</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_update_srv_status_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="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-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="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>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="sat_update_srv_frame_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Services</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2022 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
|
||||
@@ -170,10 +170,10 @@ class SatellitesTool(Gtk.Box):
|
||||
self.on_edit(view, force=True)
|
||||
|
||||
def on_satellite_add(self, item):
|
||||
self.on_satellite(None)
|
||||
self.on_satellite()
|
||||
|
||||
def on_transponder_add(self, item):
|
||||
self.on_transponder(None)
|
||||
self.on_transponder()
|
||||
|
||||
def on_edit(self, view, force=False):
|
||||
""" Common edit """
|
||||
@@ -442,7 +442,7 @@ class UpdateDialog:
|
||||
if title:
|
||||
self._window.set_title(title)
|
||||
|
||||
self._transponder_frame = builder.get_object("sat_update_tr_frame")
|
||||
self._transponder_paned = builder.get_object("sat_update_tr_paned")
|
||||
self._sat_view = builder.get_object("sat_update_tree_view")
|
||||
self._transponder_view = builder.get_object("sat_update_tr_view")
|
||||
self._service_view = builder.get_object("sat_update_srv_view")
|
||||
@@ -451,6 +451,9 @@ class UpdateDialog:
|
||||
self._receive_button = builder.get_object("receive_data_button")
|
||||
self._sat_update_info_bar = builder.get_object("sat_update_info_bar")
|
||||
self._info_bar_message_label = builder.get_object("info_bar_message_label")
|
||||
self._satellites_count_label = builder.get_object("satellites_count_label")
|
||||
self._transponders_count_label = builder.get_object("transponders_count_label")
|
||||
self._services_count_label = builder.get_object("services_count_label")
|
||||
self._receive_button.bind_property("visible", builder.get_object("cancel_data_button"), "visible", 4)
|
||||
update_button = builder.get_object("sat_update_button")
|
||||
self._sat_view.bind_property("sensitive", update_button, "sensitive")
|
||||
@@ -502,9 +505,7 @@ class UpdateDialog:
|
||||
show_dialog(DialogType.ERROR, self._window, "The task is already running!")
|
||||
return
|
||||
|
||||
get_base_model(self._sat_view.get_model()).clear()
|
||||
self._transponder_view.get_model().clear()
|
||||
self._service_view.get_model().clear()
|
||||
self.clear_data()
|
||||
|
||||
self.is_download = True
|
||||
self._sat_view.set_sensitive(False)
|
||||
@@ -514,6 +515,14 @@ class UpdateDialog:
|
||||
|
||||
self.get_sat_list(src, self.append_satellites)
|
||||
|
||||
def clear_data(self):
|
||||
get_base_model(self._sat_view.get_model()).clear()
|
||||
self._transponder_view.get_model().clear()
|
||||
self._service_view.get_model().clear()
|
||||
self._satellites_count_label.set_text("0")
|
||||
self._transponders_count_label.set_text("0")
|
||||
self._services_count_label.set_text("0")
|
||||
|
||||
@run_task
|
||||
def get_sat_list(self, src, callback):
|
||||
sat_src = SatelliteSource.FLYSAT
|
||||
@@ -523,8 +532,7 @@ class UpdateDialog:
|
||||
sat_src = SatelliteSource.KINGOFSAT
|
||||
|
||||
sats = self._parser.get_satellites_list(sat_src)
|
||||
if sats:
|
||||
callback(sats)
|
||||
callback(sats)
|
||||
self.is_download = False
|
||||
|
||||
@run_idle
|
||||
@@ -534,6 +542,7 @@ class UpdateDialog:
|
||||
model.append(sat)
|
||||
|
||||
self._sat_view.set_sensitive(True)
|
||||
self._satellites_count_label.set_text(str(len(model)))
|
||||
|
||||
@run_idle
|
||||
def on_receive_data(self, item):
|
||||
@@ -678,10 +687,10 @@ class SatellitesUpdateDialog(UpdateDialog):
|
||||
appender.send("-" * 75 + "\n")
|
||||
sat_count = len(sats)
|
||||
|
||||
sats = {s[2]: s for s in sats} # key = position, v = satellite
|
||||
sats = {s[0]: s for s in sats} # key = name, v = satellite
|
||||
|
||||
for row in self._main_model:
|
||||
pos = row[2]
|
||||
pos = row[0]
|
||||
if pos in sats:
|
||||
sat = sats.pop(pos)
|
||||
appender.send(f"Updating satellite: {row[0]}\n")
|
||||
@@ -729,7 +738,7 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
self._transponder_view.connect("button-press-event", lambda w, e: on_popup_menu(tr_popup_menu, e))
|
||||
self._transponder_view.connect("select_all", lambda w: self.update_transponder_selection(True))
|
||||
|
||||
self._transponder_frame.set_visible(True)
|
||||
self._transponder_paned.set_visible(True)
|
||||
self._source_box.remove(0)
|
||||
self._source_box.connect("changed", self.on_update_satellites_list)
|
||||
self._source_box.set_active(0)
|
||||
@@ -754,7 +763,7 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
non_cached_sats = []
|
||||
sat_names = {}
|
||||
t_names = {}
|
||||
t_urls = []
|
||||
t_urls = set()
|
||||
services = []
|
||||
|
||||
for r in (r for r in model if r[-1]):
|
||||
@@ -766,7 +775,7 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
trs = self._transponders.get(url, None)
|
||||
if trs:
|
||||
for t in filter(lambda tp: tp.url in self._selected_transponders, trs):
|
||||
t_urls.append(t.url)
|
||||
t_urls.add(t.url)
|
||||
t_names[t.url] = t.text
|
||||
else:
|
||||
non_cached_sats.append(url)
|
||||
@@ -783,7 +792,7 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
|
||||
appender.send(f"Getting transponders for: {sat_names.get(futures[future])}.\n")
|
||||
for t in future.result():
|
||||
t_urls.append(t.url)
|
||||
t_urls.add(t.url)
|
||||
t_names[t.url] = t.text
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
@@ -804,14 +813,17 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
return
|
||||
|
||||
appender.send(f"Getting services for: {t_names.get(futures[future], '')}.\n")
|
||||
list(map(services.append, future.result()))
|
||||
try:
|
||||
list(map(services.append, future.result()))
|
||||
except ValueError as e:
|
||||
log(f"Getting services error: {e} [{t_names.get(futures[future])}]")
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
appender.send(f"Consumed: {time.time() - start:0.0f}s, {len(services)} services received.")
|
||||
|
||||
try:
|
||||
from app.eparser.enigma.lamedb import LameDbReader
|
||||
# Used for double checking!
|
||||
# Used for double check!
|
||||
reader = LameDbReader(path=None)
|
||||
srvs = reader.get_services_list("".join(reader.get_services_lines(services)))
|
||||
except ValueError as e:
|
||||
@@ -829,8 +841,7 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
self._services_parser.source = sat_src
|
||||
|
||||
sats = self._parser.get_satellites_list(sat_src)
|
||||
if sats:
|
||||
callback(sats)
|
||||
callback(sats)
|
||||
self.is_download = False
|
||||
|
||||
def on_satellite_toggled(self, toggle, path):
|
||||
@@ -896,6 +907,7 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
model.clear()
|
||||
list(map(model.append, [(t.text, t.url, t.url in self._selected_transponders) for t in trs_list]))
|
||||
self._sat_view.set_sensitive(True)
|
||||
self._transponders_count_label.set_text(str(len(model)))
|
||||
|
||||
@run_task
|
||||
def on_activate_transponder(self, view, path, column):
|
||||
@@ -915,6 +927,7 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
model.append((None, s.service, s.package, s.service_type, str(s.ssid), None))
|
||||
|
||||
self._transponder_view.set_sensitive(True)
|
||||
self._services_count_label.set_text(str(len(model)))
|
||||
|
||||
def update_transponder_selection(self, select):
|
||||
m = self._transponder_view.get_model()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1
|
||||
<!-- Generated with glade 3.22.2
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2022 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
|
||||
@@ -3662,6 +3662,7 @@ 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>
|
||||
</packing>
|
||||
</child>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2022 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
|
||||
@@ -192,7 +192,6 @@ class SettingsDialog:
|
||||
self._enable_exp_switch.bind_property("active", builder.get_object("enable_direct_playback_box"), "sensitive")
|
||||
# Enigma2 only.
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("bq_naming_grid"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("enable_http_box"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("enable_experimental_box"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("program_frame"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("experimental_box"), "sensitive")
|
||||
@@ -246,8 +245,7 @@ class SettingsDialog:
|
||||
self._neutrino_radio_button.set_active(self._s_type is SettingsType.NEUTRINO_MP)
|
||||
self.update_picon_paths()
|
||||
self.update_title()
|
||||
http_active = self._support_http_api_switch.get_active()
|
||||
self._click_mode_zap_button.set_sensitive(is_enigma_profile and http_active)
|
||||
self._click_mode_zap_button.set_sensitive(self._support_http_api_switch.get_active())
|
||||
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(
|
||||
"The Neutrino has only experimental support. Not all features are supported!", Gtk.MessageType.WARNING)
|
||||
@@ -338,12 +336,12 @@ class SettingsDialog:
|
||||
self._picons_size_button.set_active_id(str(self._settings.list_picon_size))
|
||||
self._tooltip_logo_size_button.set_active_id(str(self._settings.tooltip_logo_size))
|
||||
self._list_font_button.set_font(self._settings.list_font)
|
||||
self._support_http_api_switch.set_active(self._settings.http_api_support)
|
||||
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self._enable_exp_switch.set_active(self._settings.is_enable_experimental)
|
||||
self._support_ver5_switch.set_active(self._settings.v5_support)
|
||||
self._force_bq_name_switch.set_active(self._settings.force_bq_names)
|
||||
self._support_http_api_switch.set_active(self._settings.http_api_support)
|
||||
self._enable_yt_dl_switch.set_active(self._settings.enable_yt_dl)
|
||||
self._enable_update_yt_dl_switch.set_active(self._settings.enable_yt_dl_update)
|
||||
self._enable_send_to_switch.set_active(self._settings.enable_send_to)
|
||||
@@ -405,6 +403,7 @@ class SettingsDialog:
|
||||
self._ext_settings.list_picon_size = int(self._picons_size_button.get_active_id())
|
||||
self._ext_settings.tooltip_logo_size = int(self._tooltip_logo_size_button.get_active_id())
|
||||
self._ext_settings.list_font = self._list_font_button.get_font()
|
||||
self._ext_settings.http_api_support = self._support_http_api_switch.get_active()
|
||||
|
||||
if not IS_LINUX:
|
||||
self._ext_settings.dark_mode = self._dark_mode_switch.get_active()
|
||||
@@ -420,7 +419,6 @@ class SettingsDialog:
|
||||
self._ext_settings.extra_color = self._extra_color_button.get_rgba().to_string()
|
||||
self._ext_settings.v5_support = self._support_ver5_switch.get_active()
|
||||
self._ext_settings.force_bq_names = self._force_bq_name_switch.get_active()
|
||||
self._ext_settings.http_api_support = self._support_http_api_switch.get_active()
|
||||
self._ext_settings.enable_yt_dl = self._enable_yt_dl_switch.get_active()
|
||||
self._ext_settings.enable_yt_dl_update = self._enable_update_yt_dl_switch.get_active()
|
||||
self._ext_settings.enable_send_to = self._enable_send_to_switch.get_active()
|
||||
@@ -805,15 +803,16 @@ class SettingsDialog:
|
||||
@run_task
|
||||
def unpack_theme(self, src, dst, button):
|
||||
try:
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
from shutil import unpack_archive
|
||||
|
||||
import subprocess
|
||||
log("Unpacking '{}' started...".format(src))
|
||||
p = subprocess.Popen(["tar", "-xvf", src, "-C", dst],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
p.communicate()
|
||||
log(f"Unpacking '{src}' started...")
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
unpack_archive(src, dst)
|
||||
log("Unpacking end.")
|
||||
except (ValueError, OSError) as e:
|
||||
msg = f"Unpacking error: {e}"
|
||||
log(msg)
|
||||
self.show_info_message(msg, Gtk.MessageType.ERROR)
|
||||
finally:
|
||||
self.update_theme_button(button, dst)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
gi.require_version("Gdk", "3.0")
|
||||
from gi.repository import Gtk, Gdk
|
||||
from gi.repository import Gtk, Gdk, GLib
|
||||
|
||||
from app.settings import Settings, SettingsException, IS_DARWIN, GTK_PATH, IS_LINUX
|
||||
|
||||
@@ -106,16 +106,22 @@ else:
|
||||
theme = Gtk.IconTheme.get_default()
|
||||
theme.append_search_path(UI_RESOURCES_PATH + "icons")
|
||||
|
||||
_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None
|
||||
CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(
|
||||
"emblem-readonly", 16, 0) else _IMAGE_MISSING
|
||||
LOCKED_ICON = theme.load_icon("changes-prevent-symbolic", 16, 0) if theme.lookup_icon(
|
||||
"system-lock-screen", 16, 0) else _IMAGE_MISSING
|
||||
HIDE_ICON = theme.load_icon("go-jump", 16, 0) if theme.lookup_icon("go-jump", 16, 0) else _IMAGE_MISSING
|
||||
TV_ICON = theme.load_icon("tv-symbolic", 16, 0) if theme.lookup_icon("tv-symbolic", 16, 0) else _IMAGE_MISSING
|
||||
IPTV_ICON = theme.load_icon("emblem-shared", 16, 0) if theme.lookup_icon("emblem-shared", 16, 0) else None
|
||||
EPG_ICON = theme.load_icon("gtk-index", 16, 0) if theme.lookup_icon("gtk-index", 16, 0) else None
|
||||
DEFAULT_ICON = theme.load_icon("emblem-default", 16, 0) if theme.lookup_icon("emblem-default", 16, 0) else None
|
||||
|
||||
def get_icon(name, size, default=None):
|
||||
try:
|
||||
return theme.load_icon(name, size, 0) if theme.lookup_icon(name, size, 0) else default
|
||||
except GLib.Error:
|
||||
return default
|
||||
|
||||
|
||||
_IMAGE_MISSING = get_icon("image-missing", 16)
|
||||
CODED_ICON = get_icon("emblem-readonly", 16, _IMAGE_MISSING)
|
||||
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)
|
||||
EPG_ICON = get_icon("gtk-index", 16, _IMAGE_MISSING)
|
||||
DEFAULT_ICON = get_icon("emblem-default", 16, _IMAGE_MISSING)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
@@ -291,6 +297,7 @@ if IS_LINUX:
|
||||
LEFT = 113
|
||||
RIGHT = 114
|
||||
F2 = 68
|
||||
F4 = 70
|
||||
F5 = 71
|
||||
F7 = 73
|
||||
SPACE = 65
|
||||
@@ -331,6 +338,7 @@ elif IS_DARWIN:
|
||||
LEFT = 123
|
||||
RIGHT = 123
|
||||
F2 = 120
|
||||
F4 = 118
|
||||
F5 = 96
|
||||
F7 = 98
|
||||
SPACE = 49
|
||||
@@ -369,6 +377,7 @@ else:
|
||||
LEFT = 37
|
||||
RIGHT = 39
|
||||
F2 = 113
|
||||
F4 = 115
|
||||
F5 = 116
|
||||
F7 = 118
|
||||
SPACE = 32
|
||||
|
||||
@@ -6,3 +6,11 @@ button {
|
||||
min-height: 24px;
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
entry {
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
switch {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
VER="2.0.0_Beta"
|
||||
VER="2.1.0_Beta"
|
||||
B_PATH="dist/DemonEditor"
|
||||
DEB_PATH="$B_PATH/usr/share/demoneditor"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: demon-editor
|
||||
Version: 2.0.0-Beta
|
||||
Version: 2.1.0-Beta
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
@@ -10,6 +10,7 @@ Depends: python3 (>= 3.6),
|
||||
python3-gi-cairo,
|
||||
gir1.2-notify-0.7
|
||||
Recommends: libmpv1,
|
||||
python3-chardet
|
||||
python3-chardet,
|
||||
libgtksourceview (>= 3.0)
|
||||
Maintainer: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
|
||||
Description: Enigma2 channel and satellite list editor
|
||||
|
||||
@@ -5,7 +5,7 @@ Source: https://github.com/DYefremov/DemonEditor
|
||||
Files: *
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2022 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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -69,7 +69,7 @@ app = BUNDLE(coll,
|
||||
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
|
||||
'LSApplicationCategoryType': 'public.app-category.utilities',
|
||||
'LSMinimumSystemVersion': '10.13',
|
||||
'CFBundleShortVersionString': f"2.0.0.{BUILD_DATE} Beta",
|
||||
'CFBundleShortVersionString': f"2.1.0.{BUILD_DATE} Beta",
|
||||
'NSHumanReadableCopyright': u"Copyright © 2021, Dmitriy Yefremov",
|
||||
'NSRequiresAquaSystemAppearance': 'false'
|
||||
})
|
||||
|
||||
@@ -50,6 +50,9 @@ msgstr "Цякучы IP:"
|
||||
msgid "Assign"
|
||||
msgstr "Прывязаць"
|
||||
|
||||
msgid "Assign file"
|
||||
msgstr "Прывязаць файл"
|
||||
|
||||
msgid "Bouquet details"
|
||||
msgstr "Сэрвісы букета"
|
||||
|
||||
@@ -230,6 +233,9 @@ msgstr "Скід профілю"
|
||||
msgid "Satellites"
|
||||
msgstr "Спадарожнікі"
|
||||
|
||||
msgid "Transponders"
|
||||
msgstr "Транспондары"
|
||||
|
||||
msgid "Satellites.xml file:"
|
||||
msgstr "Файл satellites.xml:"
|
||||
|
||||
@@ -1300,3 +1306,30 @@ msgstr "Перацягніце сэрвісы на патрэбны пікон
|
||||
|
||||
msgid "Sets the profile folder as default to store picons, backups, etc."
|
||||
msgstr "Усталёўвае тэчку профілю па змаўчанні для захоўвання піконаў, рэзервовых копій і т. п."
|
||||
|
||||
msgid "New sub-bouquet"
|
||||
msgstr "Стварыць саббукет"
|
||||
|
||||
msgid "Mark not presented in Bouquets"
|
||||
msgstr "Адзначыць адсутныя у букетах"
|
||||
|
||||
msgid "Not in Bouquets"
|
||||
msgstr "Адсутныя у букетах"
|
||||
|
||||
msgid "Do not show services present in Bouquets."
|
||||
msgstr "Не паказваць сэрвісы якія прысутнічаюць у букетах."
|
||||
|
||||
msgid "IPTV services only"
|
||||
msgstr "Толькі IPTV сэрвісы"
|
||||
|
||||
msgid "Display picons"
|
||||
msgstr "Адлюстроўваць пiконы"
|
||||
|
||||
msgid "Alternate layout"
|
||||
msgstr "Альтэрнатыўная кампаноўка"
|
||||
|
||||
msgid "Layout of elements has been changed!"
|
||||
msgstr "Зменена месцаванне элементаў!"
|
||||
|
||||
msgid "Restart the program to apply all changes."
|
||||
msgstr "Перазапусціце праграму, каб ужыць усе змены."
|
||||
|
||||
@@ -52,6 +52,9 @@ msgstr "Akt.IP:"
|
||||
msgid "Assign"
|
||||
msgstr "Zuweisen"
|
||||
|
||||
msgid "Assign file"
|
||||
msgstr "Datei zuweisen"
|
||||
|
||||
msgid "Bouquet details"
|
||||
msgstr "Bouquet-Details"
|
||||
|
||||
@@ -232,6 +235,9 @@ msgstr "Profil zurücksetzen"
|
||||
msgid "Satellites"
|
||||
msgstr "Satelliten"
|
||||
|
||||
msgid "Transponders"
|
||||
msgstr "Transpondern"
|
||||
|
||||
msgid "Satellites.xml file:"
|
||||
msgstr "Satellites.xml Datei:"
|
||||
|
||||
@@ -1314,3 +1320,30 @@ msgstr "Ziehe die Dienste auf das gewünschte Picon oder Picon in die Liste der
|
||||
|
||||
msgid "Sets the profile folder as default to store picons, backups, etc."
|
||||
msgstr "Legt den Profilordner als Standardordner zum Speichern von Piconen, Backups usw. fest."
|
||||
|
||||
msgid "New sub-bouquet"
|
||||
msgstr "Neues Sub-Bouquet"
|
||||
|
||||
msgid "Mark not presented in Bouquets"
|
||||
msgstr "Markieren die nicht in Bouquets vorhanden"
|
||||
|
||||
msgid "Not in Bouquets"
|
||||
msgstr "Nicht in Bouquets"
|
||||
|
||||
msgid "Do not show services present in Bouquets."
|
||||
msgstr "Vorhandene in Bouquets Dienste nicht anzeigen."
|
||||
|
||||
msgid "IPTV services only"
|
||||
msgstr "Nur IPTV-Dienste"
|
||||
|
||||
msgid "Display picons"
|
||||
msgstr "Picons anzeigen"
|
||||
|
||||
msgid "Alternate layout"
|
||||
msgstr "Alternatives Layout"
|
||||
|
||||
msgid "Layout of elements has been changed!"
|
||||
msgstr "Das Layout der Elemente wurde geändert!"
|
||||
|
||||
msgid "Restart the program to apply all changes."
|
||||
msgstr "Starte das Programm neu, um alle Änderungen zu übernehmen."
|
||||
|
||||
@@ -1233,7 +1233,7 @@ msgid "Mark duplicates"
|
||||
msgstr "Seleziona duplicati"
|
||||
|
||||
msgid "Load only for selected bouquet"
|
||||
msgstr "Larica solo per i bouquet selezionati"
|
||||
msgstr "Scarica solo per i bouquet selezionati"
|
||||
|
||||
msgid "The task is canceled!"
|
||||
msgstr "Operazione cancellata!"
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
# Copyright (C) 2018-2020 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2019 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Last-Translator: wwns\n"
|
||||
"Last-Translator: wwns <https://github.com/wwns>\n"
|
||||
"Language: pl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Language-Team: \n"
|
||||
"X-Generator: Poedit 3.0\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "wwns"
|
||||
|
||||
# Main
|
||||
msgid "File"
|
||||
msgstr "Plik"
|
||||
|
||||
msgid "View"
|
||||
msgstr "Widok"
|
||||
|
||||
msgid "Lock"
|
||||
msgstr "Zablokuj"
|
||||
|
||||
msgid "Service"
|
||||
msgstr "Serwis"
|
||||
msgstr "Usługa"
|
||||
|
||||
msgid "Package"
|
||||
msgstr "Pakiet"
|
||||
@@ -48,7 +44,7 @@ msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
msgid "Pos"
|
||||
msgstr "Pos"
|
||||
msgstr "Poz"
|
||||
|
||||
msgid "Num"
|
||||
msgstr "Num"
|
||||
@@ -113,6 +109,9 @@ msgstr "Ustaw domyślną nazwę"
|
||||
msgid "Insert marker"
|
||||
msgstr "Wstaw znacznik"
|
||||
|
||||
msgid "Insert space"
|
||||
msgstr "Wstaw spację"
|
||||
|
||||
msgid "Locate in services"
|
||||
msgstr "Znajdź w usługach"
|
||||
|
||||
@@ -179,6 +178,9 @@ msgstr "Zapisz"
|
||||
msgid "Search"
|
||||
msgstr "Szukaj"
|
||||
|
||||
msgid "Services"
|
||||
msgstr "Kanały"
|
||||
|
||||
msgid "Services filter"
|
||||
msgstr "Filtr kanałów"
|
||||
|
||||
@@ -206,8 +208,8 @@ msgstr "Aktualna ścieżka danych:"
|
||||
msgid "Data:"
|
||||
msgstr "Dane:"
|
||||
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "Edytor kanałów Enigma2 i listy satelitów dla GNU/Linux."
|
||||
msgid "Enigma2 channel and satellite list editor."
|
||||
msgstr "Edytor kanałów i list satelitarnych Enigma2."
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Host:"
|
||||
@@ -222,7 +224,7 @@ msgid "Receive files from receiver"
|
||||
msgstr "Pobieranie plików z odbiornika"
|
||||
|
||||
msgid "Receiver IP:"
|
||||
msgstr "Odbiornik IP:"
|
||||
msgstr "Adres IP odbiornika:"
|
||||
|
||||
msgid "Remove unused bouquets"
|
||||
msgstr "Usuń nieużywany bouquet"
|
||||
@@ -252,22 +254,7 @@ msgid "User bouquet files:"
|
||||
msgstr "Pliki bukietu użytkownika:"
|
||||
|
||||
msgid "Extra:"
|
||||
msgstr "Dodatkowe"
|
||||
|
||||
msgid "IPTV tools"
|
||||
msgstr "Narzędzia IPTV"
|
||||
|
||||
msgid "Picons manager"
|
||||
msgstr "Menedżer pikonów"
|
||||
|
||||
msgid "Hide/Skip"
|
||||
msgstr "Ukryj/Pomiń"
|
||||
|
||||
msgid "Parent lock"
|
||||
msgstr "Blokada rodzicielska"
|
||||
|
||||
msgid "There are unsaved changes.\n\n\t Save them now?"
|
||||
msgstr "Istnieją niezapisane zmiany.\n\n\t Zapisać je teraz?"
|
||||
msgstr "Dodatkowe:"
|
||||
|
||||
# Filter bar
|
||||
msgid "Only free"
|
||||
@@ -280,6 +267,9 @@ msgid "All types"
|
||||
msgstr "Wszystkie typy"
|
||||
|
||||
# Streams player
|
||||
msgid "Play"
|
||||
msgstr "Odtwarzaj"
|
||||
|
||||
msgid "Stop playback"
|
||||
msgstr "Zatrzymaj odtwarzanie"
|
||||
|
||||
@@ -311,8 +301,8 @@ msgstr "Format nazw pikon:"
|
||||
msgid "Resize:"
|
||||
msgstr "Zmień rozmiar:"
|
||||
|
||||
msgid "Current picons path:"
|
||||
msgstr "Aktualna ścieżka pikon:"
|
||||
msgid "Current picons path"
|
||||
msgstr "Aktualna ścieżka pikon"
|
||||
|
||||
msgid "Receiver picons path:"
|
||||
msgstr "Ścieżka pikon odbiornika:"
|
||||
@@ -390,12 +380,6 @@ msgid "Remove selection"
|
||||
msgstr "Usuń wybrane"
|
||||
|
||||
# Service details dialog
|
||||
msgid "To the top"
|
||||
msgstr "Przenieś na góre bukietu"
|
||||
|
||||
msgid "To the end"
|
||||
msgstr "Przenieś na koniec bukietu"
|
||||
|
||||
msgid "Service data:"
|
||||
msgstr "Dane usług:"
|
||||
|
||||
@@ -467,7 +451,7 @@ msgid "Preferences"
|
||||
msgstr "Preferencje"
|
||||
|
||||
msgid "Profile:"
|
||||
msgstr "Profil:"
|
||||
msgstr "Profile:"
|
||||
|
||||
msgid "Timeout between commands in seconds"
|
||||
msgstr "Limit czasu między poleceniami w sekundach"
|
||||
@@ -481,6 +465,9 @@ msgstr "Login:"
|
||||
msgid "Options"
|
||||
msgstr "Opcje"
|
||||
|
||||
msgid "Rename"
|
||||
msgstr "Zmień nazwę"
|
||||
|
||||
msgid "Password:"
|
||||
msgstr "Hasło:"
|
||||
|
||||
@@ -497,13 +484,13 @@ msgid "Picons path:"
|
||||
msgstr "Ścieżka pikon:"
|
||||
|
||||
msgid "Network settings:"
|
||||
msgstr "Ustawienia sieci:"
|
||||
msgstr "Ustawienia połaczenia:"
|
||||
|
||||
msgid "STB file paths:"
|
||||
msgstr "Ścieżki do plików w STB:"
|
||||
|
||||
msgid "Local file paths:"
|
||||
msgstr "Lokalne ścieżki plików:"
|
||||
msgstr "Lokalne ścieżki do plików:"
|
||||
|
||||
# Dialogs messages
|
||||
msgid "Error. No bouquet is selected!"
|
||||
@@ -539,6 +526,9 @@ msgstr "Wybierz tylko jeden element!"
|
||||
msgid "No png file is selected!"
|
||||
msgstr "Nie wybrano pliku png!"
|
||||
|
||||
msgid "No profile selected!"
|
||||
msgstr "Nie wybroano profilu !"
|
||||
|
||||
msgid "No reference is present!"
|
||||
msgstr "Brak referencji!"
|
||||
|
||||
@@ -606,6 +596,15 @@ msgstr "Brak danych do zapisania!"
|
||||
msgid "Network"
|
||||
msgstr "Sieć"
|
||||
|
||||
msgid "Paths"
|
||||
msgstr "Ścieżki"
|
||||
|
||||
msgid "Program"
|
||||
msgstr "Program"
|
||||
|
||||
msgid "Backup:"
|
||||
msgstr "Kopia:"
|
||||
|
||||
msgid "Backup"
|
||||
msgstr "Kopia"
|
||||
|
||||
@@ -621,11 +620,26 @@ msgstr "Przywróć bukiety"
|
||||
msgid "Restore all"
|
||||
msgstr "Przywrócić wszystko"
|
||||
|
||||
msgid "Before saving"
|
||||
msgstr "Przed zapisaniem"
|
||||
|
||||
msgid "Before downloading from the receiver"
|
||||
msgstr "Przed pobraniem z odbiornika"
|
||||
|
||||
msgid "Set background color for the services"
|
||||
msgstr "Ustaw kolor tła dla usług"
|
||||
|
||||
msgid "Marked as new:"
|
||||
msgstr "Oznacz jako nowy:"
|
||||
|
||||
msgid "With an extra name in the bouquet:"
|
||||
msgstr "Z dodatkową nazwą w bukiecie:"
|
||||
|
||||
msgid "Select"
|
||||
msgstr "Wybierz"
|
||||
|
||||
msgid "About"
|
||||
msgstr "Wersja"
|
||||
msgstr "O programie"
|
||||
|
||||
msgid "Exit"
|
||||
msgstr "Wyjście"
|
||||
@@ -633,15 +647,6 @@ msgstr "Wyjście"
|
||||
msgid "Tools"
|
||||
msgstr "Narzędzia"
|
||||
|
||||
msgid "Cut"
|
||||
msgstr "Wytnij"
|
||||
|
||||
msgid "Paste"
|
||||
msgstr "Wklej"
|
||||
|
||||
msgid "Insert space"
|
||||
msgstr "Wstaw spację"
|
||||
|
||||
# Import
|
||||
msgid "Import"
|
||||
msgstr "Importuj"
|
||||
@@ -664,12 +669,27 @@ msgstr "Usuń wszystkie nieużywane"
|
||||
msgid "Test"
|
||||
msgstr "Test"
|
||||
|
||||
msgid "Details"
|
||||
msgstr "Właściwości"
|
||||
|
||||
msgid "Test connection"
|
||||
msgstr "Testuj połączenie"
|
||||
|
||||
msgid "Double click on the service in the bouquet list:"
|
||||
msgstr "Kliknij dwukrotnie usługę na liście bukietów:"
|
||||
|
||||
msgid "Zap"
|
||||
msgstr "Przełącz"
|
||||
|
||||
msgid "Play stream"
|
||||
msgstr "Odtwórz strumień"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "Wyłączone"
|
||||
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Włącz wsparcie dla lamedb w wer.5"
|
||||
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "Włącz API HTTP"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Przełącz(zap) kanał(Ctrl + Z)"
|
||||
|
||||
@@ -770,11 +790,26 @@ msgstr "Import listy odtwarzania"
|
||||
msgid "Getting link error:"
|
||||
msgstr "Błąd pobierania łącza:"
|
||||
|
||||
msgid "Extra"
|
||||
msgstr "Ekstra"
|
||||
|
||||
msgid "Apply profile settings"
|
||||
msgstr "Zastosuj ustawienia profilu"
|
||||
|
||||
msgid "Settings type:"
|
||||
msgstr "Ustawienia dla:"
|
||||
|
||||
msgid "Set default"
|
||||
msgstr "Ustaw domyślnie"
|
||||
msgstr "Uataw domyślnie"
|
||||
|
||||
msgid "Language:"
|
||||
msgstr "Język:"
|
||||
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Załaduj ostatnią otwartą konfigurację podczas uruchamiania programu"
|
||||
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Włącz pasek bezpośredniego odtwarzania"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Umożliwia bezpośrednie wysyłanie i odtwarzanie łączy multimedialnych w odbiorniku"
|
||||
@@ -782,23 +817,8 @@ msgstr "Umożliwia bezpośrednie wysyłanie i odtwarzanie łączy multimedialnyc
|
||||
msgid "Watch the channel in the program"
|
||||
msgstr "Obejrzyj kanał w programie"
|
||||
|
||||
msgid "Receiver info"
|
||||
msgstr "Informacje o odbiorniku"
|
||||
|
||||
msgid "Operates in standby mode or current active transponder!"
|
||||
msgstr "Działa w trybie gotowości lub aktualnie jest aktywny transponder!"
|
||||
|
||||
msgid "Download picons from the receiver"
|
||||
msgstr "Pobierz pikony z odbiornika"
|
||||
|
||||
msgid "Remove picons from the receiver"
|
||||
msgstr "Usuń pikony z odbiornika"
|
||||
|
||||
msgid "Use http to reload data in the receiver."
|
||||
msgstr "Użyj http aby przeładować dane w odbiorniku."
|
||||
|
||||
msgid "Apply profile settings"
|
||||
msgstr "Zastosuj ustawienia profilu"
|
||||
msgid "Zap and Play"
|
||||
msgstr "Przełącz i Odtwórz"
|
||||
|
||||
msgid "Drag or paste the link here"
|
||||
msgstr "Przeciągnij lub wklej tutaj link"
|
||||
@@ -809,34 +829,156 @@ msgstr "Usuń dodane linki z listy odtwarzania"
|
||||
msgid "A bouquet with that name exists!"
|
||||
msgstr "Istnieje bukiet o tej nazwie!"
|
||||
|
||||
msgid "Details"
|
||||
msgstr "Właściwości"
|
||||
|
||||
msgid "Profile"
|
||||
msgstr "Profil"
|
||||
|
||||
msgid "Reset"
|
||||
msgstr "Reset profilu"
|
||||
|
||||
msgid "File"
|
||||
msgstr "Plik"
|
||||
|
||||
msgid "Picons manager"
|
||||
msgstr "Menedżer pikonów"
|
||||
|
||||
msgid "Explorer"
|
||||
msgstr "Explorer"
|
||||
|
||||
msgid "Satellite url:"
|
||||
msgstr "Satelitarny url:"
|
||||
|
||||
msgid "Cut"
|
||||
msgstr "Wytnij"
|
||||
|
||||
msgid "Paste"
|
||||
msgstr "Wklej"
|
||||
|
||||
msgid "To the top"
|
||||
msgstr "Przenieś na góre bukietu"
|
||||
|
||||
msgid "To the end"
|
||||
msgstr "Przenieś na koniec bukietu"
|
||||
|
||||
msgid "View"
|
||||
msgstr "Widok"
|
||||
|
||||
msgid "Lock"
|
||||
msgstr "Zablokuj"
|
||||
|
||||
msgid "Parent lock"
|
||||
msgstr "Blokada rodzicielska"
|
||||
|
||||
msgid "Hide/Skip"
|
||||
msgstr "Ukryj/Pomiń"
|
||||
|
||||
msgid "IPTV tools"
|
||||
msgstr "Narzędzia IPTV"
|
||||
|
||||
msgid "Make profile folder as default for the additional data"
|
||||
msgstr "Ustaw folder profilu jako domyślny dla dodatkowych danych"
|
||||
|
||||
msgid "Default data path:"
|
||||
msgstr "Domyślna ścieżka danych:"
|
||||
|
||||
msgid "Streams record path:"
|
||||
msgstr "Ścieżka zapisu nagrań:"
|
||||
|
||||
msgid "Record"
|
||||
msgstr "Nagrania"
|
||||
|
||||
msgid "Record:"
|
||||
msgstr "Nagrania:"
|
||||
|
||||
msgid "Record to disk:"
|
||||
msgstr "Zapis na dysk:"
|
||||
|
||||
msgid "Streaming"
|
||||
msgstr "Transmisja"
|
||||
|
||||
msgid "Activate transcoding"
|
||||
msgstr "Aktywuj"
|
||||
|
||||
msgid "Presets:"
|
||||
msgstr "Ustawienia transkodowania:"
|
||||
|
||||
msgid "Video options:"
|
||||
msgstr "Opcje wideo:"
|
||||
|
||||
msgid "Audio options:"
|
||||
msgstr "Opcje audio:"
|
||||
|
||||
msgid "Bitrate (kb/s):"
|
||||
msgstr "Szybkość transmisji (kb/s):"
|
||||
|
||||
msgid "Codec:"
|
||||
msgstr "Kodek:"
|
||||
|
||||
msgid "Width (px):"
|
||||
msgstr "Szerokość (px):"
|
||||
|
||||
msgid "Height (px):"
|
||||
msgstr "Wysokość (px):"
|
||||
|
||||
msgid "Channels:"
|
||||
msgstr "Kanały:"
|
||||
|
||||
msgid "Remove all picons from the receiver"
|
||||
msgstr "Usuń wszystkie pikony z odbiornika"
|
||||
msgid "Sample rate (Hz):"
|
||||
msgstr "Częstotliwość próbkowania (Hz):"
|
||||
|
||||
msgid "Download from the receiver"
|
||||
msgstr "Pobierz z odbiornika"
|
||||
msgid "Play streams mode:"
|
||||
msgstr "Tryb odtwarzania strumieni:"
|
||||
|
||||
msgid "The Neutrino has only experimental support. Not all features are supported!"
|
||||
msgstr "Neutrino ma jedynie wsparcie eksperymentalne. Nie wszystkie funkcje są obsługiwane!"
|
||||
msgid "Built-in player"
|
||||
msgstr "Wbudowany odtwarzacz"
|
||||
|
||||
msgid "In a separate window"
|
||||
msgstr "W osobnym oknie"
|
||||
|
||||
msgid "Only get m3u file"
|
||||
msgstr "Tylko pobierz plik m3u"
|
||||
|
||||
msgid "Save and restart the program to apply the settings."
|
||||
msgstr "Zapisz i uruchom ponownie program, aby zastosować ustawienia."
|
||||
|
||||
msgid "Some images may have problems displaying the favorites list!"
|
||||
msgstr "Niektóre obrazy mogą mieć problemy z wyświetlaniem listy ulubionych!"
|
||||
|
||||
# Appearance
|
||||
msgid "Operates in standby mode or current active transponder!"
|
||||
msgstr "Działa w trybie gotowości lub aktualnie jest aktywny transponder!"
|
||||
|
||||
msgid "No connection to the receiver!"
|
||||
msgstr "Brak połączenia z odbiornikiem!"
|
||||
|
||||
msgid "Signal level"
|
||||
msgstr "Poziom sygnału"
|
||||
|
||||
msgid "Receiver info"
|
||||
msgstr "Informacje o odbiorniku"
|
||||
|
||||
msgid "A profile with that name exists!"
|
||||
msgstr "Profil o tej nazwie już istnieje!"
|
||||
|
||||
msgid "Show short info as hints in the main services list"
|
||||
msgstr "Pokaż krótkie informacje jako wskazówki na głównej liście usług"
|
||||
|
||||
msgid "Show detailed info as hints in the bouquet list"
|
||||
msgstr "Pokaż szczegółowe informacje jako wskazówki na liście bukietów"
|
||||
|
||||
msgid "Enable alternate bouquet file naming"
|
||||
msgstr "Włącz alternatywne nazewnictwo plików bukietów"
|
||||
|
||||
msgid "Allows you to name bouquet files using their names."
|
||||
msgstr "Pozwala nazwać pliki bukietów przy użyciu ich nazw."
|
||||
|
||||
msgid "Appearance"
|
||||
msgstr "Wygląd"
|
||||
|
||||
msgid "Enable Dark Mode"
|
||||
msgstr "Włącz tryb ciemny"
|
||||
|
||||
msgid "Enable Themes support"
|
||||
msgstr "Włącz obsługę motywów"
|
||||
|
||||
msgid "EXPERIMENTAL!"
|
||||
msgstr "EKSPERYMENTALNE!"
|
||||
|
||||
msgid "Gtk3 Theme:"
|
||||
msgstr "Gtk3 motyw:"
|
||||
|
||||
@@ -846,159 +988,346 @@ msgstr "Motyw ikon:"
|
||||
msgid "Gtk3 Themes and Icons:"
|
||||
msgstr "Gtk3 motywy i ikony:"
|
||||
|
||||
msgid "Save and restart the program to apply the settings."
|
||||
msgstr "Zapisz i uruchom ponownie program, aby zastosować ustawienia."
|
||||
msgid "Deleting data..."
|
||||
msgstr "Usuwanie danych…"
|
||||
|
||||
# Extra
|
||||
msgid "Extra"
|
||||
msgstr "Ekstra"
|
||||
msgid "Download from the receiver"
|
||||
msgstr "Pobierz z odbiornika"
|
||||
|
||||
msgid "Enable alternate bouquet file naming"
|
||||
msgstr "Włącz alternatywne nazewnictwo plików bukietów"
|
||||
msgid "Remove all picons from the receiver"
|
||||
msgstr "Usuń wszystkie pikony z odbiornika"
|
||||
|
||||
msgid "Allows you to name bouquet files using their names."
|
||||
msgstr "Pozwala nazwać pliki bukietów przy użyciu ich nazw."
|
||||
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "Włącz API HTTP"
|
||||
|
||||
msgid "Double click on the service in the bouquet list:"
|
||||
msgstr "Kliknij dwukrotnie usługę na liście bukietów:"
|
||||
|
||||
msgid "Zap"
|
||||
msgstr "Przełącz"
|
||||
|
||||
msgid "Play"
|
||||
msgstr "Odtwarzaj"
|
||||
|
||||
msgid "Zap and Play"
|
||||
msgstr "Przełącz i Odtwórz"
|
||||
|
||||
msgid "Play stream"
|
||||
msgstr "Odtwórz strumień"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "Wyłączone"
|
||||
|
||||
msgid "Enable experimental features"
|
||||
msgstr "Włącz funkcje eksperymentalne"
|
||||
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Włącz wsparcie dla lamedb w wer.5"
|
||||
msgid "Service reference"
|
||||
msgstr "Odniesienie do usługi"
|
||||
|
||||
msgid "Enable support for"
|
||||
msgstr "Włącz obsługę"
|
||||
|
||||
msgid "Enables parsing links using youtube-dl to get direct links to media"
|
||||
msgstr "Umożliwia analizowanie linków za pomocą youtube-dl, aby uzyskać bezpośrednie linki do multimediów"
|
||||
|
||||
msgid "Auto-check for updates"
|
||||
msgstr "Automatyczne sprawdzanie aktualizacji"
|
||||
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Włącz pasek bezpośredniego odtwarzania"
|
||||
msgid "Filter services"
|
||||
msgstr "Filtruj usługi"
|
||||
|
||||
#Program
|
||||
msgid "Program"
|
||||
msgstr "Program"
|
||||
msgid "Filter services in the main list."
|
||||
msgstr "Filtruj usługi na głównej liście."
|
||||
|
||||
msgid "Language:"
|
||||
msgstr "Język:"
|
||||
msgid "Destination:"
|
||||
msgstr "Miejsce docelowe:"
|
||||
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Załaduj ostatnią otwartą konfigurację podczas uruchamiania programu"
|
||||
msgid "EXPERIMENTAL!"
|
||||
msgstr "EKSPERYMENTALNE!"
|
||||
|
||||
msgid "Show short info as hints in the main services list"
|
||||
msgstr "Pokaż krótkie informacje jako wskazówki na głównej liście usług"
|
||||
msgid "Sorting data..."
|
||||
msgstr "Sortowanie danych…"
|
||||
|
||||
msgid "Show detailed info as hints in the bouquet list"
|
||||
msgstr "Pokaż szczegółowe informacje jako wskazówki na liście bukietów"
|
||||
msgid ""
|
||||
"There are unsaved changes.\n"
|
||||
"\n"
|
||||
"\t Save them now?"
|
||||
msgstr ""
|
||||
"Istnieją niezapisane zmiany.\n"
|
||||
"\n"
|
||||
"\t Zapisać je teraz?"
|
||||
|
||||
msgid "Set background color for the services"
|
||||
msgstr "Ustaw kolor tła dla usług"
|
||||
msgid ""
|
||||
"Are you sure you want to change the order\n"
|
||||
"\t of services in this bouquet?"
|
||||
msgstr ""
|
||||
"Czy na pewno chcesz zmienić kolejność?\n"
|
||||
"\t usług w tym bukiecie?"
|
||||
|
||||
msgid "Marked as new:"
|
||||
msgstr "Oznacz jako nowy:"
|
||||
msgid "Remove from the receiver"
|
||||
msgstr "Usuń z odbiornika"
|
||||
|
||||
msgid "With an extra name in the bouquet:"
|
||||
msgstr "Z dodatkową nazwą w bukiecie:"
|
||||
msgid "Screenshot"
|
||||
msgstr "Zrzut ekranu"
|
||||
|
||||
msgid "Backup:"
|
||||
msgstr "Kopia:"
|
||||
msgid "Video"
|
||||
msgstr "Opcje wideo"
|
||||
|
||||
msgid "Before saving"
|
||||
msgstr "Przed zapisaniem"
|
||||
msgid "The Neutrino has only experimental support. Not all features are supported!"
|
||||
msgstr "Neutrino ma jedynie wsparcie eksperymentalne. Nie wszystkie funkcje są obsługiwane!"
|
||||
|
||||
msgid "Before downloading from the receiver"
|
||||
msgstr "Przed pobraniem z odbiornika"
|
||||
msgid "Enable experimental features"
|
||||
msgstr "Włącz funkcje eksperymentalne"
|
||||
|
||||
#Streaming
|
||||
msgid "Streaming"
|
||||
msgstr "Transmisja"
|
||||
msgid "Can't Playback!"
|
||||
msgstr "Nie można odtworzyć!"
|
||||
|
||||
msgid "Record to disk:"
|
||||
msgstr "Nagrywanie na dysk:"
|
||||
msgid "Enable Dark Mode"
|
||||
msgstr "Włącz tryb ciemny"
|
||||
|
||||
msgid "Activate transcoding"
|
||||
msgstr "Aktywuj transkodowanie"
|
||||
msgid "Extract..."
|
||||
msgstr "Rozpakuj…"
|
||||
|
||||
msgid "Presets:"
|
||||
msgstr "Ustawienia wstępne:"
|
||||
msgid "Unsupported format!"
|
||||
msgstr "Format nieobsługiwany!"
|
||||
|
||||
msgid "720p TV/device"
|
||||
msgstr "Dla urządzeń z obsługą rozdzielczości 720p"
|
||||
msgid "Combine with the current data?"
|
||||
msgstr "Połączyć z aktualnymi danymi?"
|
||||
|
||||
msgid "1080p TV/device"
|
||||
msgstr "Dla urządzeń z obsługą rozdzielczości 1080p"
|
||||
msgid "Importing data done!"
|
||||
msgstr "Importowanie danych zakończone!"
|
||||
|
||||
msgid "Video options:"
|
||||
msgstr "Opcje wideo:"
|
||||
msgid "Current service"
|
||||
msgstr "Bieżąca usługa"
|
||||
|
||||
msgid "Width (px):"
|
||||
msgstr "Szerokość (px):"
|
||||
msgid "Open folder"
|
||||
msgstr "Otwórz folder"
|
||||
|
||||
msgid "Height (px):"
|
||||
msgstr "Wysokość (px):"
|
||||
msgid "Open archive"
|
||||
msgstr "Otwórz archiwum"
|
||||
|
||||
msgid "Codec:"
|
||||
msgstr "Kodek:"
|
||||
msgid "Import from Web"
|
||||
msgstr "Importuj z sieci"
|
||||
|
||||
msgid "Audio options:"
|
||||
msgstr "Opcje audio:"
|
||||
msgid "Control"
|
||||
msgstr "Kontrola"
|
||||
|
||||
msgid "Services"
|
||||
msgstr "Kanały"
|
||||
msgid "Timers"
|
||||
msgstr "Timery"
|
||||
|
||||
msgid "Sample rate (Hz):"
|
||||
msgstr "Częstotliwość próbkowania (Hz):"
|
||||
msgid "Timer"
|
||||
msgstr "Timer"
|
||||
|
||||
msgid "Play streams mode:"
|
||||
msgstr "Odtwarzaj tryb strumieni:"
|
||||
msgid "Add timer"
|
||||
msgstr "Dodaj timer"
|
||||
|
||||
msgid "Bulit-in player"
|
||||
msgstr "Wbudowany odtwarzacz"
|
||||
msgid "Hr."
|
||||
msgstr "Hr."
|
||||
|
||||
msgid "VLC media player"
|
||||
msgstr "Odtwarzacz multimedialny VLC"
|
||||
msgid "Min."
|
||||
msgstr "Min."
|
||||
|
||||
msgid "Only get m3u file"
|
||||
msgstr "Tylko pobierz plik m3u"
|
||||
msgid "Power"
|
||||
msgstr "Włącz"
|
||||
|
||||
# Paths
|
||||
msgid "Paths"
|
||||
msgstr "Ścieżki"
|
||||
msgid "Standby"
|
||||
msgstr "Czuwanie"
|
||||
|
||||
msgid "Make profile folder as default for the additional data"
|
||||
msgstr "Ustaw folder profilu jako domyślny dla dodatkowych danych"
|
||||
msgid "Wake Up"
|
||||
msgstr "Wybudź"
|
||||
|
||||
msgid "Reboot"
|
||||
msgstr "Restart"
|
||||
|
||||
msgid "Restart GUI"
|
||||
msgstr "Restart Interfejsu Graficznego"
|
||||
|
||||
msgid "Shutdown"
|
||||
msgstr "Wyłącz"
|
||||
|
||||
msgid "Shut down"
|
||||
msgstr "Wyłacz"
|
||||
|
||||
msgid "Do Nothing"
|
||||
msgstr "Nie rób nic"
|
||||
|
||||
msgid "Auto"
|
||||
msgstr "Auto"
|
||||
|
||||
msgid "Grab screenshot"
|
||||
msgstr "Zrób zrzut ekranu"
|
||||
|
||||
msgid "Enabled:"
|
||||
msgstr "Włączony:"
|
||||
|
||||
msgid "Name:"
|
||||
msgstr "Nazwa:"
|
||||
|
||||
msgid "Description:"
|
||||
msgstr "Opis:"
|
||||
|
||||
msgid "Service:"
|
||||
msgstr "Usługa:"
|
||||
|
||||
msgid "Service reference:"
|
||||
msgstr "Odniesienie do usługi:"
|
||||
|
||||
msgid "Event ID:"
|
||||
msgstr "Identyfikator wydarzenia:"
|
||||
|
||||
msgid "Begins:"
|
||||
msgstr "Początek:"
|
||||
|
||||
msgid "Ends:"
|
||||
msgstr "Koniec:"
|
||||
|
||||
msgid "Repeated:"
|
||||
msgstr "Powtórz:"
|
||||
|
||||
msgid "Action:"
|
||||
msgstr "Akcja:"
|
||||
|
||||
msgid "After event:"
|
||||
msgstr "Po wydarzeniu:"
|
||||
|
||||
msgid "Location:"
|
||||
msgstr "Lokalizacja:"
|
||||
|
||||
msgid "Mo"
|
||||
msgstr "Po"
|
||||
|
||||
msgid "Tu"
|
||||
msgstr "Wt"
|
||||
|
||||
msgid "We"
|
||||
msgstr "Śr"
|
||||
|
||||
msgid "Th"
|
||||
msgstr "Cz"
|
||||
|
||||
msgid "Fr"
|
||||
msgstr "Pt"
|
||||
|
||||
msgid "Sa"
|
||||
msgstr "So"
|
||||
|
||||
msgid "Su"
|
||||
msgstr "Ni"
|
||||
|
||||
msgid "Set"
|
||||
msgstr "Ustaw"
|
||||
|
||||
msgid "Services update"
|
||||
msgstr "Aktualizacja usług"
|
||||
|
||||
msgid "Create folder"
|
||||
msgstr "Utwórz folder"
|
||||
|
||||
msgid "FTP client"
|
||||
msgstr "Klient-FTP"
|
||||
|
||||
msgid "The file size is too large!"
|
||||
msgstr "Rozmiar pliku jest za duży!"
|
||||
|
||||
msgid "Connect"
|
||||
msgstr "Połącz"
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr "Rozłącz"
|
||||
|
||||
msgid "Size"
|
||||
msgstr "Rozmiar"
|
||||
|
||||
msgid "Date"
|
||||
msgstr "Data"
|
||||
|
||||
msgid "Toggle display position"
|
||||
msgstr "Przełącz pozycję wyświetlania"
|
||||
|
||||
msgid "Alternatives"
|
||||
msgstr "Alternatywy"
|
||||
|
||||
msgid "Add alternatives"
|
||||
msgstr "Dodaj alternatywy"
|
||||
|
||||
msgid "DreamOS only!"
|
||||
msgstr "Tylko DreamOS!"
|
||||
|
||||
msgid "A similar service is already in this list!"
|
||||
msgstr "Podobna usługa jest już na tej liście!"
|
||||
|
||||
msgid ""
|
||||
"Play mode has been changed!\n"
|
||||
"Restart the program to apply the settings."
|
||||
msgstr ""
|
||||
"Tryb odtwarzania został zmieniony!\n"
|
||||
"Uruchom ponownie, aby zastosować ustawienia."
|
||||
|
||||
msgid "Set values for TID, NID and Namespace for correct naming of the picons!"
|
||||
msgstr "Ustaw wartości dla TID, NID i Namespace dla poprawnego nazewnictwa pikonów!"
|
||||
|
||||
msgid "Streams detected:"
|
||||
msgstr "Wykryte strumienie:"
|
||||
|
||||
msgid "Download picons"
|
||||
msgstr "Pobierz pikony"
|
||||
|
||||
msgid "Errors:"
|
||||
msgstr "Błędy:"
|
||||
|
||||
msgid "Use to play streams:"
|
||||
msgstr "Użyj do odtwarzania strumieni:"
|
||||
|
||||
msgid "Font in the lists:"
|
||||
msgstr "Czcionka na listach:"
|
||||
|
||||
msgid "Picons size in the lists:"
|
||||
msgstr "Rozmiar pikonów na listach:"
|
||||
|
||||
msgid "Logo size in tooltips:"
|
||||
msgstr "Rozmiar logo w podpowiedziach:"
|
||||
|
||||
msgid "Save as"
|
||||
msgstr "Zapisz jako"
|
||||
|
||||
msgid "Mark duplicates"
|
||||
msgstr "Zaznacz duplikaty"
|
||||
|
||||
msgid "Load only for selected bouquet"
|
||||
msgstr "Załaduj tylko dla wybranego bukietu"
|
||||
|
||||
msgid "The task is canceled!"
|
||||
msgstr "Zadanie anulowane!"
|
||||
|
||||
msgid "Data loading in progress!"
|
||||
msgstr "Trwa ładowanie danych!"
|
||||
|
||||
msgid "Recordings"
|
||||
msgstr "Nagrania"
|
||||
|
||||
msgid "Help"
|
||||
msgstr "Pomoc"
|
||||
|
||||
msgid "HTTP API is not activated. Check your settings!"
|
||||
msgstr "Interfejs API HTTP nie jest aktywowany. Sprawdź swoje ustawienia!"
|
||||
|
||||
msgid "Add picons"
|
||||
msgstr "Dodaj pikony"
|
||||
|
||||
msgid "Logs"
|
||||
msgstr "Logi"
|
||||
|
||||
msgid "Title"
|
||||
msgstr "Tytuł"
|
||||
|
||||
msgid "Time"
|
||||
msgstr "Czas"
|
||||
|
||||
msgid "Length"
|
||||
msgstr "Długość"
|
||||
|
||||
msgid "Additional source"
|
||||
msgstr "Dodatkowe źródło"
|
||||
|
||||
msgid "Automatically set the name selected in the favorites list."
|
||||
msgstr "Automatycznie ustaw nazwę wybraną na liście ulubionych."
|
||||
|
||||
msgid "Playback"
|
||||
msgstr "Odtwarzanie"
|
||||
|
||||
msgid "Audio"
|
||||
msgstr "Audio"
|
||||
|
||||
msgid "Audio Track"
|
||||
msgstr "Ścieżka Audio"
|
||||
|
||||
msgid "Subtitle"
|
||||
msgstr "Napisy"
|
||||
|
||||
msgid "Subtitle Track"
|
||||
msgstr "Ścieżka napisów"
|
||||
|
||||
msgid "Aspect ratio"
|
||||
msgstr "Współczynnik proporcji"
|
||||
|
||||
msgid "This may change the settings of other profiles!"
|
||||
msgstr "Może to zmienić ustawienia innych profili!"
|
||||
|
||||
msgid "Drag the services to the desired picon or picon to the list of selected services."
|
||||
msgstr "Przeciągnij usługi na żądaną ikonę lub na listę wybranych usług."
|
||||
|
||||
msgid "Sets the profile folder as default to store picons, backups, etc."
|
||||
msgstr "Ustawia folder profilu jako domyślny do przechowywania pikonów, kopii zapasowych itp."
|
||||
|
||||
msgid "Default data path:"
|
||||
msgstr "Domyślna ścieżka danych:"
|
||||
|
||||
msgid "Record:"
|
||||
msgstr "Nagrania:"
|
||||
|
||||
msgid "Streams record path:"
|
||||
msgstr "Ścieżka zapisu nagrań:"
|
||||
|
||||
@@ -50,6 +50,9 @@ msgstr "Текущий IP:"
|
||||
msgid "Assign"
|
||||
msgstr "Привязать"
|
||||
|
||||
msgid "Assign file"
|
||||
msgstr "Привязать файл"
|
||||
|
||||
msgid "Bouquet details"
|
||||
msgstr "Сервисы букета"
|
||||
|
||||
@@ -230,6 +233,9 @@ msgstr "Сброс профиля"
|
||||
msgid "Satellites"
|
||||
msgstr "Спутники"
|
||||
|
||||
msgid "Transponders"
|
||||
msgstr "Транспондеры"
|
||||
|
||||
msgid "Satellites.xml file:"
|
||||
msgstr "Файл satellites.xml:"
|
||||
|
||||
@@ -1297,3 +1303,30 @@ msgstr "Перетащите сервисы на нужный пикон или
|
||||
|
||||
msgid "Sets the profile folder as default to store picons, backups, etc."
|
||||
msgstr "Устанавливает папку профиля по умолчанию для хранения пиконов, резервных копий и т. п."
|
||||
|
||||
msgid "New sub-bouquet"
|
||||
msgstr "Создать суббукет"
|
||||
|
||||
msgid "Mark not presented in Bouquets"
|
||||
msgstr "Отметить отсутствующие в букетах"
|
||||
|
||||
msgid "Not in Bouquets"
|
||||
msgstr "Не в букетах"
|
||||
|
||||
msgid "Do not show services present in Bouquets."
|
||||
msgstr "Не показывать сервисы присутствующие в букетах."
|
||||
|
||||
msgid "IPTV services only"
|
||||
msgstr "Только IPTV сервисы"
|
||||
|
||||
msgid "Display picons"
|
||||
msgstr "Отображать пиконы"
|
||||
|
||||
msgid "Alternate layout"
|
||||
msgstr "Альтернативная компоновка"
|
||||
|
||||
msgid "Layout of elements has been changed!"
|
||||
msgstr "Изменено расположение элементов!"
|
||||
|
||||
msgid "Restart the program to apply all changes."
|
||||
msgstr "Перезапустите программу, чтобы применить все изменения."
|
||||
|
||||
@@ -3,13 +3,13 @@ msgstr ""
|
||||
"Project-Id-Version: DemonEditor\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
|
||||
"PO-Revision-Date: 2021-06-13 14:54+0300\n"
|
||||
"PO-Revision-Date: 2021-11-11 23:49+0300\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
|
||||
"Last-Translator: audi06_19 <audi06_19@hotmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"X-Generator: Poedit 2.4.1\n"
|
||||
"X-Generator: Poedit 3.0\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Language: tr\n"
|
||||
|
||||
@@ -206,8 +206,8 @@ msgstr "Mevcut veri yolu:"
|
||||
msgid "Data:"
|
||||
msgstr "Veri:"
|
||||
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "GNU/Linux için Enigma2 kanalı ve uydu listesi editörü."
|
||||
msgid "Enigma2 channel and satellite list editor."
|
||||
msgstr "Enigma2 kanal ve uydu listesi düzenleyicisi."
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Ana bilgisayar:"
|
||||
@@ -299,8 +299,8 @@ msgstr "Piconların adı biçimi:"
|
||||
msgid "Resize:"
|
||||
msgstr "Yeniden boyutlandır:"
|
||||
|
||||
msgid "Current picons path:"
|
||||
msgstr "Geçerli picon yolları:"
|
||||
msgid "Current picons path"
|
||||
msgstr "Mevcut piconların yolu"
|
||||
|
||||
msgid "Receiver picons path:"
|
||||
msgstr "Alıcı picon yolu:"
|
||||
@@ -1269,3 +1269,63 @@ msgstr "Yalnızca seçilen buket için yükle"
|
||||
|
||||
msgid "The task is canceled!"
|
||||
msgstr "Görev iptal edildi!"
|
||||
|
||||
msgid "Data loading in progress!"
|
||||
msgstr "Veri yükleme devam ediyor!"
|
||||
|
||||
msgid "Recordings"
|
||||
msgstr "Kayıtlar"
|
||||
|
||||
msgid "Help"
|
||||
msgstr "Yardım"
|
||||
|
||||
msgid "HTTP API is not activated. Check your settings!"
|
||||
msgstr "HTTP API etkinleştirilmedi. Ayarlarınızı kontrol edin!"
|
||||
|
||||
msgid "Add picons"
|
||||
msgstr "Piconlar ekle"
|
||||
|
||||
msgid "Logs"
|
||||
msgstr "Loglar"
|
||||
|
||||
msgid "Title"
|
||||
msgstr "Başlık"
|
||||
|
||||
msgid "Time"
|
||||
msgstr "Zaman"
|
||||
|
||||
msgid "Length"
|
||||
msgstr "Uzunluk"
|
||||
|
||||
msgid "Additional source"
|
||||
msgstr "Ek kaynak"
|
||||
|
||||
msgid "Automatically set the name selected in the favorites list."
|
||||
msgstr "Favoriler listesinde seçilen adı otomatik olarak ayarlayın."
|
||||
|
||||
msgid "Playback"
|
||||
msgstr "Oynatım"
|
||||
|
||||
msgid "Audio"
|
||||
msgstr "Ses"
|
||||
|
||||
msgid "Audio Track"
|
||||
msgstr "Ses parçası"
|
||||
|
||||
msgid "Subtitle"
|
||||
msgstr "Altyazı"
|
||||
|
||||
msgid "Subtitle Track"
|
||||
msgstr "Altyazı Parçası"
|
||||
|
||||
msgid "Aspect ratio"
|
||||
msgstr "En boy oranı"
|
||||
|
||||
msgid "This may change the settings of other profiles!"
|
||||
msgstr "Bu, diğer profillerin ayarlarını değiştirebilir!"
|
||||
|
||||
msgid "Drag the services to the desired picon or picon to the list of selected services."
|
||||
msgstr "Hizmetleri istediğiniz simgeye veya simgeyi seçili hizmetler listesine sürükleyin."
|
||||
|
||||
msgid "Sets the profile folder as default to store picons, backups, etc."
|
||||
msgstr "Picon'ları, yedekleri vb. depolamak için profil klasörünü varsayılan olarak ayarlar."
|
||||
|
||||
Reference in New Issue
Block a user