mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-08 13:15:29 +02:00
Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b45dda8ada | ||
|
|
e1577d8e0c | ||
|
|
a184a7cc7f | ||
|
|
6a4ca77009 | ||
|
|
c1ed748a91 | ||
|
|
d6791a9c89 | ||
|
|
1f0411fb3d | ||
|
|
27a1838980 | ||
|
|
c1bfb482e1 | ||
|
|
411e012f5c | ||
|
|
a7bc32d7ae | ||
|
|
ef3f69ece1 | ||
|
|
344f4905fc | ||
|
|
51f36d14ec | ||
|
|
f2b31b2ac4 | ||
|
|
118734d7fb | ||
|
|
d7b7f6571b | ||
|
|
9728843b0a | ||
|
|
033ac70c8a | ||
|
|
85247e8307 | ||
|
|
d38a896685 | ||
|
|
880908c163 | ||
|
|
ec94d5ef46 | ||
|
|
49f9863922 | ||
|
|
6d4249cf1e | ||
|
|
0fc0ef1d3e | ||
|
|
c587f2bcdc | ||
|
|
5cd8c68589 | ||
|
|
61690db0ee | ||
|
|
fcc2b6b6a8 | ||
|
|
a5412cd2b3 | ||
|
|
a47a7417c2 | ||
|
|
bdac77e88c | ||
|
|
8ab79a2937 | ||
|
|
8dc880577f | ||
|
|
b1829651d3 | ||
|
|
7339872de6 | ||
|
|
8155643098 | ||
|
|
87a1cde859 | ||
|
|
a591d31d01 | ||
|
|
e863c41117 | ||
|
|
811539ae19 | ||
|
|
bc7327a6d5 | ||
|
|
0c114964f2 | ||
|
|
b8a3e5e4c1 | ||
|
|
1d16e9e220 | ||
|
|
c96b464cbc | ||
|
|
43821e6f50 | ||
|
|
e0e642db5a | ||
|
|
2266fd4d3d | ||
|
|
6b8145c674 | ||
|
|
fa89ab8608 | ||
|
|
da70b0fb18 | ||
|
|
6932465cfd | ||
|
|
303fe0b1ae | ||
|
|
f8dec3140c | ||
|
|
60e1e4f5e6 | ||
|
|
a99d6e26db | ||
|
|
ecf5b6399c | ||
|
|
3c04a00230 | ||
|
|
527a52c87b | ||
|
|
4bcd126947 | ||
|
|
1a3617a6d4 | ||
|
|
d6d7b105ec | ||
|
|
eab34c5ecc | ||
|
|
3cef063aa4 | ||
|
|
958320e573 | ||
|
|
394b7c4c01 | ||
|
|
00f492b0a2 | ||
|
|
0e0abdcf8e | ||
|
|
037b917d3e | ||
|
|
0bc85cb5fa | ||
|
|
8bf6427bbd | ||
|
|
f85c1d2e0d | ||
|
|
03e2fc96ec | ||
|
|
f0d58c0fb4 | ||
|
|
1fc10f0119 | ||
|
|
a21f6faab2 | ||
|
|
d78cee1241 | ||
|
|
145bd75776 |
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2026 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
|
||||
|
||||
@@ -106,9 +106,7 @@ Support for DVB-T/T2 and DVB-C channels for Neutrino is not fully implemented an
|
||||
Main supported *lamedb* format is version **4**. Versions **3** and **5** has only **experimental** support! For version **3** is only read mode available. When saving, version **4** format is used instead.
|
||||
|
||||
When using the multiple import feature, from *lamedb* will be taken data **only for channels that are in the selected bouquets!**
|
||||
If you need full set of the data, including *[satellites, terrestrial, cables].xml* (current files will be overwritten),
|
||||
just load your data via *"File/Open"* and press *"Save"*. When importing separate bouquet files, only those services
|
||||
(excluding IPTV) that are in the **current open lamedb** (main list of services) will be imported.
|
||||
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!**
|
||||
|
||||
|
||||
@@ -1,9 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2026 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 <https://github.com/DYefremov>
|
||||
#
|
||||
|
||||
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from functools import wraps
|
||||
from threading import Thread, Timer
|
||||
from threading import Timer
|
||||
|
||||
from gi.repository import GLib
|
||||
from gi.repository.Gio import Task
|
||||
|
||||
_LOG_FILE = "demon-editor.log"
|
||||
LOG_DATE_FORMAT = "%d-%m-%y %H:%M:%S"
|
||||
@@ -41,12 +70,12 @@ def run_idle(func):
|
||||
|
||||
|
||||
def run_task(func):
|
||||
""" Runs function in separate thread """
|
||||
""" Runs a function in a separate thread. """
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
task = Thread(target=func, args=args, kwargs=kwargs, daemon=True)
|
||||
task.start()
|
||||
task = Task()
|
||||
task.run_in_thread(lambda t, s, d, c: func(*args, **kwargs))
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@@ -443,7 +443,7 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=log, fil
|
||||
ftp.download_files(save_path, file_list, callback)
|
||||
# *.xml and webtv
|
||||
if download_type in (DownloadType.ALL, DownloadType.SATELLITES):
|
||||
ftp.download_xml(save_path, settings.satellites_xml_path, STC_XML_FILE, callback)
|
||||
ftp.download_xml(save_path, settings.satellites_xml_path, files_filter or STC_XML_FILE, callback)
|
||||
if download_type in (DownloadType.ALL, DownloadType.WEBTV):
|
||||
ftp.download_xml(save_path, settings.satellites_xml_path, WEB_TV_XML_FILE, callback)
|
||||
|
||||
@@ -460,10 +460,9 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=log, fil
|
||||
|
||||
|
||||
def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_callback=None,
|
||||
files_filter=None, ext_host=None):
|
||||
files_filter=None, ext_host=None, ext_path=None):
|
||||
s_type = settings.setting_type
|
||||
use_http = s_type is SettingsType.ENIGMA_2 and settings.use_http
|
||||
data_path = settings.profile_data_path
|
||||
host, port, use_ssl = ext_host or settings.host, settings.http_port, settings.http_use_ssl
|
||||
user, password = settings.user, settings.password
|
||||
base_url = f"http{'s' if use_ssl else ''}://{host}:{port}"
|
||||
@@ -471,6 +470,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
|
||||
url = f"{base_url}/{base}/"
|
||||
tn, ht = None, None # Telnet, HTTP.
|
||||
ftp_port, telnet_port = settings.port, settings.telnet_port
|
||||
data_path = ext_path or settings.profile_data_path
|
||||
|
||||
try:
|
||||
use_http = use_http and test_http(host, port, user, password, use_ssl=use_ssl, skip_message=True, s_type=s_type)
|
||||
@@ -513,7 +513,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
|
||||
services_path = settings.services_path
|
||||
|
||||
if download_type is DownloadType.SATELLITES:
|
||||
ftp.upload_xml(data_path, sat_xml_path, STC_XML_FILE, callback)
|
||||
ftp.upload_xml(data_path, sat_xml_path, files_filter or STC_XML_FILE, callback)
|
||||
|
||||
if s_type is SettingsType.NEUTRINO_MP and download_type is DownloadType.WEBTV:
|
||||
ftp.upload_xml(data_path, sat_xml_path, WEB_TV_XML_FILE, callback)
|
||||
@@ -524,7 +524,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
|
||||
|
||||
if download_type is DownloadType.ALL or download_type is DownloadType.SERVICES:
|
||||
if download_type is DownloadType.ALL:
|
||||
ftp.upload_xml(data_path, sat_xml_path, STC_XML_FILE, callback)
|
||||
ftp.upload_xml(data_path, sat_xml_path, files_filter or STC_XML_FILE, callback)
|
||||
|
||||
if s_type is SettingsType.NEUTRINO_MP:
|
||||
ftp.upload_xml(data_path, sat_xml_path, WEB_TV_XML_FILE, callback)
|
||||
@@ -588,7 +588,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
|
||||
ht.send((f"{url}servicelistreload?mode=2", "Reloading Userbouquets."))
|
||||
elif download_type is DownloadType.ALL or download_type is DownloadType.SERVICES:
|
||||
ht.send((f"{url}servicelistreload?mode=0", "Reloading lamedb and Userbouquets."))
|
||||
time.sleep(1)
|
||||
time.sleep(2)
|
||||
ht.send((f"{url}servicelistreload?mode=4", "Updating parental control."))
|
||||
if not settings.keep_power_mode:
|
||||
ht.send((f"{url}powerstate?newstate=4", "Wakeup from Standby."))
|
||||
@@ -614,7 +614,7 @@ def get_upload_info_message(download_type):
|
||||
elif download_type is DownloadType.ALL:
|
||||
return "All user data will be reloaded!"
|
||||
elif download_type is DownloadType.SATELLITES:
|
||||
return "Satellites.xml file will be updated!"
|
||||
return "*.xml file will be updated!"
|
||||
elif download_type is DownloadType.PICONS:
|
||||
return "Picons will be updated!"
|
||||
return ""
|
||||
|
||||
@@ -64,7 +64,7 @@ Terrestrial = namedtuple("Terrestrial", ["name", "flags", "countrycode", "transp
|
||||
Cable = namedtuple("Cable", ["name", "flags", "satfeed", "countrycode", "transponders"])
|
||||
|
||||
Transponder = namedtuple("Transponder", ["frequency", "symbol_rate", "polarization", "fec_inner", "system",
|
||||
"modulation", "pls_mode", "pls_code", "is_id", "t2mi_plp_id"])
|
||||
"modulation", "pls_mode", "pls_code", "is_id", "t2mi_plp_id", "t2mi_pid"])
|
||||
TerTransponder = namedtuple("TerTransponder", ["centre_frequency", "system", "bandwidth", "constellation",
|
||||
"code_rate_hp", "code_rate_lp", "guard_interval", "transmission_mode",
|
||||
"hierarchy_information", "inversion", "plp_id"])
|
||||
@@ -72,12 +72,16 @@ CableTransponder = namedtuple("CableTransponder", ["frequency", "symbol_rate", "
|
||||
|
||||
|
||||
class TrType(Enum):
|
||||
""" Transponders type """
|
||||
""" Transponders type. """
|
||||
Satellite = "s"
|
||||
Terrestrial = "t"
|
||||
Cable = "c"
|
||||
ATSC = "a"
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
return cls.Satellite
|
||||
|
||||
|
||||
class BqType(Enum):
|
||||
""" Bouquet type. """
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2025 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
|
||||
@@ -88,7 +88,8 @@ def get_sat_transponders(elem):
|
||||
e.get("pls_mode", None),
|
||||
e.get("pls_code", None),
|
||||
e.get("is_id", None),
|
||||
e.get("t2mi_plp_id", None)) for e in elem.iter("transponder")]
|
||||
e.get("t2mi_plp_id", None),
|
||||
e.get("t2mi_pid", None)) for e in elem.iter("transponder")]
|
||||
|
||||
|
||||
def get_terrestrial(path):
|
||||
|
||||
102
app/tools/epg.py
102
app/tools/epg.py
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -257,62 +257,64 @@ class XmlTvReader(Reader):
|
||||
log(f"{self.__class__.__name__} [download] error: Invalid URL {self._url}")
|
||||
return
|
||||
|
||||
with requests.get(url=self._url, stream=True) as resp:
|
||||
if resp.reason == "OK":
|
||||
suf = self._url[self._url.rfind("."):]
|
||||
if suf not in self.SUFFIXES:
|
||||
log(f"{self.__class__.__name__} [download] error: Unsupported file extension.")
|
||||
return
|
||||
try:
|
||||
with requests.get(url=self._url, stream=True, timeout=(5, 5)) as resp:
|
||||
if resp.reason == "OK":
|
||||
suf = self._url[self._url.rfind("."):]
|
||||
if suf not in self.SUFFIXES:
|
||||
log(f"{self.__class__.__name__} [download] error: Unsupported file extension.")
|
||||
return
|
||||
|
||||
data_size = resp.headers.get("content-length")
|
||||
if not data_size:
|
||||
log(f"{self.__class__.__name__} [download *.{suf}] error: Error getting data size.")
|
||||
if clb:
|
||||
clb()
|
||||
return
|
||||
data_size = resp.headers.get("content-length")
|
||||
if not data_size:
|
||||
log(f"{self.__class__.__name__} [download *.{suf}] error: Error getting data size.")
|
||||
return
|
||||
|
||||
with NamedTemporaryFile(suffix=suf, delete=not IS_WIN) as tf:
|
||||
downloaded = 0
|
||||
data_size = int(data_size)
|
||||
completed = set()
|
||||
with NamedTemporaryFile(suffix=suf, delete=not IS_WIN) as tf:
|
||||
downloaded = 0
|
||||
data_size = int(data_size)
|
||||
completed = set()
|
||||
|
||||
for data in resp.iter_content(chunk_size=1024):
|
||||
downloaded += len(data)
|
||||
tf.write(data)
|
||||
done = int(100 * downloaded / data_size)
|
||||
if done % 25 == 0 and done not in completed:
|
||||
completed.add(done)
|
||||
log(f"Downloading XMLTV file...{done}%" if done < 100 else "XMLTV file download complete.")
|
||||
tf.seek(0)
|
||||
for data in resp.iter_content(chunk_size=128):
|
||||
downloaded += len(data)
|
||||
tf.write(data)
|
||||
done = int(100 * downloaded / data_size)
|
||||
if done % 25 == 0 and done not in completed:
|
||||
completed.add(done)
|
||||
log(f"Downloading XMLTV file...{done}%" if done < 100 else "XMLTV file download complete.")
|
||||
tf.seek(0)
|
||||
|
||||
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
||||
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
||||
|
||||
if suf.endswith(".gz"):
|
||||
try:
|
||||
shutil.copyfile(tf.name, self._path)
|
||||
except OSError as e:
|
||||
log(f"{self.__class__.__name__} [download *.gz] error: {e}")
|
||||
elif self._url.endswith((".xz", ".lzma")):
|
||||
import lzma
|
||||
if suf.endswith(".gz"):
|
||||
try:
|
||||
shutil.copyfile(tf.name, self._path)
|
||||
except OSError as e:
|
||||
log(f"{self.__class__.__name__} [download *.gz] error: {e}")
|
||||
elif self._url.endswith((".xz", ".lzma")):
|
||||
import lzma
|
||||
|
||||
try:
|
||||
with lzma.open(tf, "rb") as lzf:
|
||||
shutil.copyfileobj(lzf, self._path)
|
||||
except (lzma.LZMAError, OSError) as e:
|
||||
log(f"{self.__class__.__name__} [download *.xz] error: {e}")
|
||||
else:
|
||||
try:
|
||||
import gzip
|
||||
with gzip.open(self._path, "wb") as f_out:
|
||||
shutil.copyfileobj(tf, f_out)
|
||||
except OSError as e:
|
||||
log(f"{self.__class__.__name__} [download *.xml] error: {e}")
|
||||
try:
|
||||
with lzma.open(tf, "rb") as lzf:
|
||||
shutil.copyfileobj(lzf, self._path)
|
||||
except (lzma.LZMAError, OSError) as e:
|
||||
log(f"{self.__class__.__name__} [download *.xz] error: {e}")
|
||||
else:
|
||||
try:
|
||||
import gzip
|
||||
with gzip.open(self._path, "wb") as f_out:
|
||||
shutil.copyfileobj(tf, f_out)
|
||||
except OSError as e:
|
||||
log(f"{self.__class__.__name__} [download *.xml] error: {e}")
|
||||
|
||||
if IS_WIN and os.path.isfile(tf.name):
|
||||
tf.close()
|
||||
os.remove(tf.name)
|
||||
else:
|
||||
log(f"{self.__class__.__name__} [download] error: {resp.reason}")
|
||||
if IS_WIN and os.path.isfile(tf.name):
|
||||
tf.close()
|
||||
os.remove(tf.name)
|
||||
else:
|
||||
log(f"{self.__class__.__name__} [download] error: {resp.reason}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
log(f"{self.__class__.__name__} [download] error: {e}")
|
||||
return
|
||||
|
||||
if clb:
|
||||
clb()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,9 +31,13 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from collections import namedtuple
|
||||
from datetime import datetime
|
||||
from enum import IntEnum
|
||||
from html.parser import HTMLParser
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
@@ -74,46 +78,76 @@ class PiconsCzDownloader:
|
||||
self._provider_logos = {}
|
||||
self._picon_ids = picon_ids
|
||||
self._appender = appender
|
||||
self._logo_map = self.get_logos_map()
|
||||
self._name_map = self.get_name_map()
|
||||
self._perm_cache_file = Path(tempfile.gettempdir()).joinpath("picon_cz_links")
|
||||
# subprocess creation flags
|
||||
self._sbp_flags = subprocess.CREATE_NO_WINDOW if IS_WIN else 0
|
||||
|
||||
@property
|
||||
def providers(self):
|
||||
return self._providers
|
||||
|
||||
def init(self):
|
||||
""" Initializes dict with values: download_id -> perm link and provider data. """
|
||||
if self._perm_links:
|
||||
return
|
||||
|
||||
self._HEADER["Referer"] = self._PERM_URL
|
||||
if self._perm_cache_file.exists():
|
||||
st = self._perm_cache_file.stat()
|
||||
dif = datetime.now() - datetime.fromtimestamp(st.st_mtime)
|
||||
# We will update daily.
|
||||
if dif.days > 0:
|
||||
self.download_permalinks()
|
||||
else:
|
||||
self.download_permalinks()
|
||||
|
||||
self.read_permalinks()
|
||||
|
||||
def download_permalinks(self):
|
||||
self._HEADER["Referer"] = self._PERM_URL
|
||||
with requests.get(url=self._PERM_URL, headers=self._HEADER, stream=True) as request:
|
||||
if request.reason == "OK":
|
||||
logo_map = self.get_logos_map()
|
||||
name_map = self.get_name_map()
|
||||
log(f"{self.__class__.__name__}: downloading permalinks file...")
|
||||
buf = BytesIO()
|
||||
[buf.write(chunk) for chunk in request.iter_content(chunk_size=128)]
|
||||
buf.seek(0)
|
||||
|
||||
for line in request.iter_lines():
|
||||
data = line.decode(encoding="utf-8", errors="ignore").split(maxsplit=1)
|
||||
if len(data) != 2:
|
||||
continue
|
||||
|
||||
l_id, perm_link = data
|
||||
self._perm_links[str(l_id)] = str(perm_link)
|
||||
data = re.match(self._LINK_PATTERN, perm_link)
|
||||
if data:
|
||||
sat_pos = data.group(3)
|
||||
# Logo url.
|
||||
logo = logo_map.get(data.group(2), None)
|
||||
l_name = name_map.get(sat_pos, None) or sat_pos.replace(".", "")
|
||||
logo_url = f"{self._BASE_LOGO_URL}{logo}/{l_name}.png" if logo else None
|
||||
|
||||
prv = Provider(None, data.group(1), sat_pos, self._BASE_URL + l_id, l_id, logo_url, None, False)
|
||||
if sat_pos in self._providers:
|
||||
self._providers[sat_pos].append(prv)
|
||||
else:
|
||||
self._providers[sat_pos] = [prv]
|
||||
self._perm_cache_file.touch()
|
||||
self._perm_cache_file.write_bytes(buf.read())
|
||||
else:
|
||||
log(f"{self.__class__.__name__} [get permalinks] error: {request.reason}")
|
||||
raise PiconsError(request.reason)
|
||||
|
||||
@property
|
||||
def providers(self):
|
||||
return self._providers
|
||||
def read_permalinks(self):
|
||||
with self._perm_cache_file.open(encoding="utf-8", errors="ignore") as f:
|
||||
for l in f.readlines():
|
||||
data = l.split(maxsplit=1)
|
||||
if len(data) != 2:
|
||||
continue
|
||||
|
||||
data = l.split(maxsplit=1)
|
||||
if len(data) != 2:
|
||||
continue
|
||||
|
||||
l_id, perm_link = data
|
||||
self._perm_links[str(l_id)] = str(perm_link)
|
||||
self.update_provider_data(l_id, perm_link)
|
||||
|
||||
def update_provider_data(self, l_id, perm_link):
|
||||
data = re.match(self._LINK_PATTERN, perm_link)
|
||||
if data:
|
||||
sat_pos = data.group(3)
|
||||
# Logo url.
|
||||
logo = self._logo_map.get(data.group(2), None)
|
||||
l_name = self._name_map.get(sat_pos, None) or sat_pos.replace(".", "")
|
||||
logo_url = f"{self._BASE_LOGO_URL}{logo}/{l_name}.png" if logo else None
|
||||
|
||||
prv = Provider(None, data.group(1), sat_pos, self._BASE_URL + l_id, l_id, logo_url, None, False)
|
||||
if sat_pos in self._providers:
|
||||
self._providers[sat_pos].append(prv)
|
||||
else:
|
||||
self._providers[sat_pos] = [prv]
|
||||
|
||||
def get_sat_providers(self, url):
|
||||
return self._providers.get(url, [])
|
||||
@@ -149,7 +183,10 @@ class PiconsCzDownloader:
|
||||
|
||||
cmd = [exe, "l", src]
|
||||
try:
|
||||
out, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
||||
out, err = subprocess.Popen(cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
creationflags=self._sbp_flags).communicate()
|
||||
if err:
|
||||
log(f"{self.__class__.__name__} [extract] error: {err}")
|
||||
raise PiconsError(err)
|
||||
@@ -174,7 +211,10 @@ class PiconsCzDownloader:
|
||||
cmd = [exe, "e", src, "-o{}".format(dest), "-y", "-r"]
|
||||
cmd.extend(to_extract)
|
||||
try:
|
||||
out, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
||||
out, err = subprocess.Popen(cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
creationflags=self._sbp_flags).communicate()
|
||||
if err:
|
||||
log(f"{self.__class__.__name__} [extract] error: {err}")
|
||||
raise PiconsError(err)
|
||||
@@ -207,7 +247,8 @@ class PiconsCzDownloader:
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
log(f"{self.__class__.__name__} error [get provider logo]: {e}")
|
||||
|
||||
def get_logos_map(self):
|
||||
@staticmethod
|
||||
def get_logos_map():
|
||||
return {"piconblack": "b50",
|
||||
"picontransparent": "t50",
|
||||
"piconwhite": "w50",
|
||||
@@ -232,7 +273,8 @@ class PiconsCzDownloader:
|
||||
"piconSNPblack": "b50",
|
||||
}
|
||||
|
||||
def get_name_map(self):
|
||||
@staticmethod
|
||||
def get_name_map():
|
||||
return {"antiksat": "ANTIK",
|
||||
"digiczsk": "DIGI",
|
||||
"DTTitaly": "picon_trs-it",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2025 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
|
||||
@@ -338,7 +338,7 @@ class SatellitesParser(HTMLParser):
|
||||
self.FEC.get(fec, None),
|
||||
self.SYSTEM.get(sys, None),
|
||||
self.MODULATION.get(mod, None),
|
||||
pls_mode, pls_code, None, None)
|
||||
pls_mode, pls_code, None, None, None)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
|
||||
@@ -379,7 +379,7 @@ class SatellitesParser(HTMLParser):
|
||||
self.FEC.get(fec, None),
|
||||
self.SYSTEM.get(sys, None),
|
||||
self.MODULATION.get(mod, None),
|
||||
pls_mode, pls_code, is_id, None)
|
||||
pls_mode, pls_code, is_id, None, None)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
|
||||
@@ -392,7 +392,7 @@ class SatellitesParser(HTMLParser):
|
||||
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):
|
||||
for row in filter(lambda r: len(r) == 14 and self.POS_PAT.match(r[0]), self._rows):
|
||||
freq, pol = row[2].replace(".", "0"), row[3]
|
||||
if not freq.isdigit() or pol not in "VHLR":
|
||||
continue
|
||||
@@ -421,7 +421,7 @@ class SatellitesParser(HTMLParser):
|
||||
self.FEC.get(fec, None),
|
||||
self.SYSTEM.get(sys, None),
|
||||
self.MODULATION.get(mod, None),
|
||||
pls_id, pls_code, is_id, None)
|
||||
pls_id, pls_code, is_id, None, None)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
|
||||
@@ -581,9 +581,9 @@ class ServicesParser(HTMLParser):
|
||||
elif self._source is SatelliteSource.KINGOFSAT:
|
||||
trs = []
|
||||
for r in self._rows:
|
||||
if len(r) == 13 and SatellitesParser.POS_PAT.match(r[0].text):
|
||||
if len(r) == 12 and SatellitesParser.POS_PAT.match(r[0].text):
|
||||
t_cell = r[4]
|
||||
if t_cell.url and t_cell.url.startswith("tp.php?tp="):
|
||||
if t_cell.url and t_cell.url.startswith("tp"):
|
||||
t_cell.url = f"https://{self._lang}.kingofsat.tv/{t_cell.url}"
|
||||
t_cell.text = f"{r[2].text} {r[3].text} {r[6].text} {r[8].text}"
|
||||
trs.append(t_cell)
|
||||
@@ -681,7 +681,7 @@ class ServicesParser(HTMLParser):
|
||||
def get_kingofsat_services(self, sat_position=None, use_pids=False):
|
||||
services = []
|
||||
# Transponder
|
||||
tr = list(filter(lambda r: len(r) == 13 and r[4].url and r[4].url.startswith("tp.php?tp="), self._rows))
|
||||
tr = list(filter(lambda r: len(r) == 12 and r[4].url and r[4].url.startswith("tp"), self._rows))
|
||||
if not tr:
|
||||
log(f"ServicesParser error [get transponder services]: Transponder [{self._t_url}] not found!")
|
||||
return services
|
||||
@@ -689,9 +689,9 @@ class ServicesParser(HTMLParser):
|
||||
tr, multi_tr, tid, nid, nsp = None, None, None, None, None
|
||||
freq, sr, pol, fec, sys, pos = None, None, None, None, None, None
|
||||
|
||||
for r in filter(lambda x: len(x) > 12, self._rows):
|
||||
for r in filter(lambda x: len(x) > 11, self._rows):
|
||||
r_size = len(r)
|
||||
if r_size == 13 and r[4].url and r[4].url.startswith("tp.php?tp="):
|
||||
if r_size == 12 and r[4].url and r[4].url.startswith("tp"):
|
||||
res = re.match(self._KING_TR_PAT, f"{r[6].text} {r[7].text}")
|
||||
if not res:
|
||||
continue
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2025 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
|
||||
@@ -39,7 +39,7 @@ from urllib import parse
|
||||
from urllib.error import URLError
|
||||
from urllib.request import Request, urlopen, urlretrieve
|
||||
|
||||
from app.commons import log
|
||||
from app.commons import log, run_task
|
||||
from app.settings import SEP
|
||||
from app.ui.uicommons import show_notification
|
||||
|
||||
@@ -172,22 +172,21 @@ class InnerTube:
|
||||
_BASE_URI = "https://www.youtube.com/youtubei/v1"
|
||||
|
||||
_DEFAULT_CLIENTS = {
|
||||
"WEB_EMBED": {"context": {"client": {"clientName": "WEB_EMBEDDED_PLAYER",
|
||||
"clientVersion": "2.20210721.00.00",
|
||||
"clientScreen": "EMBED"}},
|
||||
"header": {"User-Agent": "Mozilla/5.0"},
|
||||
"api_key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"},
|
||||
|
||||
"ANDROID_EMBED": {"context": {"client": {"clientName": "ANDROID_EMBEDDED_PLAYER",
|
||||
"clientVersion": "17.31.35",
|
||||
"clientScreen": "EMBED",
|
||||
"androidSdkVersion": 30}},
|
||||
"header": {"User-Agent": "com.google.android.youtube/"},
|
||||
"api_key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"}
|
||||
|
||||
# The client is taken from -> https://github.com/JuanBindez/pytubefix
|
||||
"ANDROID": {"context": {"client": {"clientName": "ANDROID",
|
||||
"clientVersion": "19.44.38",
|
||||
"platform": "MOBILE",
|
||||
"osName": "Android",
|
||||
"osVersion": "14",
|
||||
"androidSdkVersion": "34"}},
|
||||
"header": {"User-Agent": "com.google.android.youtube/",
|
||||
"X-Youtube-Client-Name": "3"},
|
||||
"api_key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8",
|
||||
"require_js_player": False,
|
||||
"require_po_token": True}
|
||||
}
|
||||
|
||||
def __init__(self, client="ANDROID_EMBED"):
|
||||
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.
|
||||
@@ -339,14 +338,14 @@ class YouTubeDL:
|
||||
return cls._DL_INSTANCE
|
||||
|
||||
def init(self):
|
||||
if not os.path.isfile(f"{self._path}yt_dlp{SEP}version.py"):
|
||||
if os.path.isfile(f"{self._path}yt_dlp{SEP}version.py"):
|
||||
if self._path not in sys.path:
|
||||
sys.path.append(self._path)
|
||||
|
||||
self.init_dl()
|
||||
else:
|
||||
self.get_latest_release()
|
||||
|
||||
if self._path not in sys.path:
|
||||
sys.path.append(self._path)
|
||||
|
||||
self.init_dl()
|
||||
|
||||
def init_dl(self):
|
||||
try:
|
||||
import yt_dlp
|
||||
@@ -361,23 +360,16 @@ class YouTubeDL:
|
||||
log(msg)
|
||||
raise YouTubeException(msg)
|
||||
|
||||
if self._update:
|
||||
if hasattr(yt_dlp.version, "__version__"):
|
||||
l_ver = self.get_last_release_id()
|
||||
cur_ver = yt_dlp.version.__version__
|
||||
if l_ver and yt_dlp.version.__version__ < l_ver:
|
||||
msg = f"yt-dlp has new release!\nCurrent: {cur_ver}. Last: {l_ver}."
|
||||
show_notification(msg)
|
||||
log(msg)
|
||||
self._callback(msg, False)
|
||||
self.get_latest_release()
|
||||
|
||||
self._DownloadError = yt_dlp.utils.DownloadError
|
||||
self._dl = yt_dlp.YoutubeDL(self._OPTIONS)
|
||||
msg = "yt-dlp initialized..."
|
||||
show_notification(msg)
|
||||
log(msg)
|
||||
|
||||
if self._update:
|
||||
if hasattr(yt_dlp.version, "__version__"):
|
||||
self.update(yt_dlp.version.__version__)
|
||||
|
||||
@staticmethod
|
||||
def get_last_release_id():
|
||||
""" Getting last release id. """
|
||||
@@ -388,7 +380,18 @@ class YouTubeDL:
|
||||
except URLError as e:
|
||||
log(f"YouTubeDLHelper error [get last release id]: {e}")
|
||||
|
||||
def get_latest_release(self):
|
||||
@run_task
|
||||
def update(self, current_version):
|
||||
l_ver = self.get_last_release_id()
|
||||
if l_ver and current_version < l_ver:
|
||||
msg = f"yt-dlp has new release!\nCurrent: {current_version}. Last: {l_ver}."
|
||||
show_notification(msg)
|
||||
log(msg)
|
||||
self._callback(msg, False)
|
||||
self.get_latest_release(update=True)
|
||||
|
||||
@run_task
|
||||
def get_latest_release(self, update=False):
|
||||
try:
|
||||
self._is_update_process = True
|
||||
log("Getting the last yt-dlp release...")
|
||||
@@ -426,6 +429,8 @@ class YouTubeDL:
|
||||
raise YouTubeException(e)
|
||||
finally:
|
||||
self._is_update_process = False
|
||||
if not update:
|
||||
self.init()
|
||||
|
||||
def get_yt_link(self, url, skip_errors=False):
|
||||
""" Returns tuple from the video links [dict] and title. """
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -223,11 +223,11 @@ class BackupDialog:
|
||||
self._settings.add("backup_tool_window_size", window.get_size())
|
||||
|
||||
def on_key_release(self, view, event):
|
||||
""" Handling keystrokes """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
""" Handling keystrokes. """
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if key is KeyboardKey.DELETE:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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 gi.repository.GObject import BindingFlags
|
||||
|
||||
from app.commons import log, run_task
|
||||
from app.connections import UtfFTP
|
||||
from app.settings import IS_DARWIN
|
||||
from app.settings import IS_DARWIN, IS_WIN
|
||||
from app.ui.dialogs import translate, get_chooser_dialog, show_dialog, DialogType
|
||||
from app.ui.main_helper import get_picon_pixbuf, redraw_image
|
||||
from app.ui.uicommons import HeaderBar
|
||||
@@ -64,6 +64,8 @@ class BootLogoManager(Gtk.Window):
|
||||
self._exe = f"{'./' if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS') else ''}ffmpeg"
|
||||
self._pix = None
|
||||
self._img_path = None
|
||||
# subprocess creation flags
|
||||
self._sbp_flags = subprocess.CREATE_NO_WINDOW if IS_WIN else 0
|
||||
|
||||
margin = {"margin_start": 5, "margin_end": 5, "margin_top": 5, "margin_bottom": 5}
|
||||
base_margin = {"margin_start": 10, "margin_end": 10, "margin_top": 10, "margin_bottom": 10}
|
||||
@@ -209,7 +211,9 @@ class BootLogoManager(Gtk.Window):
|
||||
def init(self, *args):
|
||||
log(f"{self.__class__.__name__} [init] Checking FFmpeg...")
|
||||
try:
|
||||
out = subprocess.check_output([self._exe, "-version"], stderr=subprocess.STDOUT)
|
||||
out = subprocess.check_output([self._exe, "-version"],
|
||||
stderr=subprocess.STDOUT,
|
||||
creationflags=self._sbp_flags)
|
||||
except FileNotFoundError as e:
|
||||
msg = translate("Check if FFmpeg is installed!")
|
||||
self._app.show_error_message(f"Error. {e} {msg}")
|
||||
@@ -297,6 +301,11 @@ class BootLogoManager(Gtk.Window):
|
||||
return
|
||||
|
||||
output = path.parent.joinpath(self._file_combo_box.get_active_id())
|
||||
if Path(output).exists():
|
||||
msg = f"\n{translate('The file already exists!')}\n\n\t{translate('Are you sure?')}"
|
||||
if show_dialog(DialogType.QUESTION, self, msg) != Gtk.ResponseType.OK:
|
||||
return True
|
||||
|
||||
ffmpeg_output = path.parent.joinpath(f"{self._file_combo_box.get_active_text()}.m2v")
|
||||
|
||||
cmd = [self._exe,
|
||||
@@ -322,9 +331,9 @@ class BootLogoManager(Gtk.Window):
|
||||
|
||||
# Processing image.
|
||||
log(f"{self.__class__.__name__} [convert] Converting...")
|
||||
subprocess.run(cmd)
|
||||
subprocess.run(cmd, creationflags=self._sbp_flags)
|
||||
if Path(ffmpeg_output).exists():
|
||||
os.rename(ffmpeg_output, output)
|
||||
os.replace(ffmpeg_output, output)
|
||||
log(f"{self.__class__.__name__} [convert] -> '{output}'. Done!")
|
||||
|
||||
if cmd[2] != self._img_path:
|
||||
@@ -336,7 +345,7 @@ class BootLogoManager(Gtk.Window):
|
||||
|
||||
def convert_to_image(self, video_path, img_path):
|
||||
cmd = [self._exe, "-y", "-i", video_path, img_path]
|
||||
subprocess.run(cmd)
|
||||
subprocess.run(cmd, creationflags=self._sbp_flags)
|
||||
|
||||
@run_task
|
||||
def download_data(self, f_name):
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2026 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor. -->
|
||||
<!-- interface-copyright 2018-2025 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2026 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkAboutDialog" id="about_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -40,8 +40,8 @@ Author: Dmitriy Yefremov
|
||||
<property name="icon_name">system-help</property>
|
||||
<property name="type_hint">normal</property>
|
||||
<property name="program_name">DemonEditor</property>
|
||||
<property name="version">3.13.2 Beta</property>
|
||||
<property name="copyright">2018-2025 Dmitriy Yefremov
|
||||
<property name="version">3.14.4 Beta</property>
|
||||
<property name="copyright">2018-2026 Dmitriy Yefremov
|
||||
</property>
|
||||
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>
|
||||
<property name="website">https://dyefremov.github.io/DemonEditor/</property>
|
||||
@@ -158,6 +158,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">False</property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="width-request">170</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="type_hint">splashscreen</property>
|
||||
@@ -166,19 +167,19 @@ Author: Dmitriy Yefremov
|
||||
<property name="decorated">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="wait_dialog_box">
|
||||
<property name="width_request">100</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin_end">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="spinner">
|
||||
<property name="width_request">150</property>
|
||||
<property name="height_request">45</property>
|
||||
<property name="visible">True</property>
|
||||
<object class="LoadingProgressBar" id="progress">
|
||||
<property name="visible" bind-source="wait_dialog" bind-property="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="show-text">True</property>
|
||||
<property name="text" translatable="yes">Loading data...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -186,24 +187,10 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="wait_dialog_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="label" translatable="yes">Loading data...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child> <!-- NOP -->
|
||||
<style>
|
||||
<class name="app-notification"/>
|
||||
<class name="primary-toolbar"/>
|
||||
</style>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -34,7 +34,7 @@ from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from app.commons import run_idle
|
||||
from app.settings import SEP, IS_WIN, USE_HEADER_BAR
|
||||
from app.settings import SEP, USE_HEADER_BAR, IS_LINUX
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
|
||||
|
||||
|
||||
@@ -101,8 +101,8 @@ class WaitDialog:
|
||||
builder, dialog = get_dialog_from_xml(DialogType.WAIT, transient)
|
||||
self._dialog = dialog
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._label = builder.get_object("wait_dialog_label")
|
||||
self._default_text = text or self._label.get_text()
|
||||
self._progress = builder.get_object("progress")
|
||||
self._default_text = text or self._progress.get_text()
|
||||
|
||||
def show(self, text=None):
|
||||
self.set_text(text)
|
||||
@@ -110,7 +110,7 @@ class WaitDialog:
|
||||
|
||||
@run_idle
|
||||
def set_text(self, text):
|
||||
self._label.set_text(translate(text or self._default_text))
|
||||
self._progress.set_text(translate(text or self._default_text))
|
||||
|
||||
@run_idle
|
||||
def hide(self):
|
||||
@@ -228,7 +228,7 @@ def translate(message):
|
||||
|
||||
@lru_cache(maxsize=5)
|
||||
def get_dialogs_string(path, tag="property"):
|
||||
if IS_WIN:
|
||||
if not IS_LINUX:
|
||||
return translate_xml(path, tag)
|
||||
else:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
@@ -257,7 +257,7 @@ def get_builder(path, handlers=None, use_str=False, objects=None, tag="property"
|
||||
|
||||
|
||||
def translate_xml(path, tag="property"):
|
||||
""" Used to translate GUI from * .glade files in MS Windows.
|
||||
""" Used to translate GUI from *.glade files to macOS and MS Windows.
|
||||
|
||||
More info: https://gitlab.gnome.org/GNOME/gtk/-/issues/569
|
||||
"""
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -568,6 +568,7 @@ class TabEpgSettingsPopover(EpgSettingsPopover):
|
||||
self._url_combo_box.get_model().clear()
|
||||
[self._url_combo_box.append(i, i) for i in settings.epg_xml_sources if i]
|
||||
self._url_combo_box.set_active_id(settings.epg_xml_source)
|
||||
self._remove_url_button.set_sensitive(len(self._url_combo_box.get_model()) > 1)
|
||||
|
||||
def on_apply(self, button):
|
||||
settings = self._app.app_settings
|
||||
@@ -1217,10 +1218,10 @@ class EpgDialog:
|
||||
|
||||
def on_key_press(self, view, event):
|
||||
""" Handling keystrokes """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
|
||||
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
|
||||
|
||||
if ctrl and key is KeyboardKey.C:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2023-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2023-2026 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
|
||||
@@ -35,7 +35,7 @@ import requests
|
||||
from gi.repository import Gtk, Gdk, GLib, Pango, GObject
|
||||
|
||||
from app.commons import log, run_task, run_idle
|
||||
from app.ui.dialogs import translate
|
||||
from app.ui.dialogs import translate, show_dialog, DialogType
|
||||
from app.ui.uicommons import HeaderBar
|
||||
|
||||
EXT_URL = "https://api.github.com/repos/DYefremov/demoneditor-extensions/contents/extensions/"
|
||||
@@ -48,7 +48,7 @@ HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i686; rv:112.0) Gecko/20100101
|
||||
|
||||
|
||||
class ExtensionManager(Gtk.Window):
|
||||
ICON_INFO = "emblem-important-symbolic"
|
||||
ICON_INFO = "emblem-synchronizing-symbolic"
|
||||
ICON_UPDATE = "network-receive-symbolic"
|
||||
|
||||
class Column(IntEnum):
|
||||
@@ -193,6 +193,22 @@ class ExtensionManager(Gtk.Window):
|
||||
|
||||
self.connect("delete-event", lambda w, e: self._app.app_settings.add(ws_property, w.get_size()))
|
||||
self.connect("realize", self.init)
|
||||
self.connect("show", self.on_show)
|
||||
|
||||
def on_show(self, window):
|
||||
enabled = self._app.app_settings.extensions_support
|
||||
self.set_sensitive(enabled)
|
||||
if not enabled:
|
||||
msg = f"\n{translate('Extension support is disabled!')}\n\n\t{translate('Do you want to enable it?')}"
|
||||
if show_dialog(DialogType.QUESTION, self, msg) != Gtk.ResponseType.OK:
|
||||
self.close()
|
||||
return True
|
||||
|
||||
self._app.app_settings.extensions_support = True
|
||||
self._app.show_info_message(translate('Restart the program to apply all changes.'), Gtk.MessageType.WARNING)
|
||||
self.close()
|
||||
|
||||
return False
|
||||
|
||||
def init(self, widget):
|
||||
self._load_spinner.start()
|
||||
@@ -281,6 +297,7 @@ class ExtensionManager(Gtk.Window):
|
||||
ext_ver = ext[0].VERSION
|
||||
path = ext[1]
|
||||
if ext_ver < ver:
|
||||
desc = f"[ Update -> ver. {ver} ] {desc}"
|
||||
ver = ext_ver
|
||||
info = self.ICON_INFO
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -856,11 +856,10 @@ class FtpClientBox(Gtk.HBox):
|
||||
self._settings.ftp_bookmarks = [r[0] for r in self._bookmark_model]
|
||||
|
||||
def on_view_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if key is KeyboardKey.F7:
|
||||
|
||||
@@ -426,10 +426,9 @@ class ImportDialog:
|
||||
|
||||
def on_key_press(self, view, event):
|
||||
""" Handling keystrokes """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
|
||||
if key is KeyboardKey.SPACE:
|
||||
model = view.get_model()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -1331,10 +1331,9 @@ class YtListImportDialog:
|
||||
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 2, select))
|
||||
|
||||
def on_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
|
||||
if key is KeyboardKey.SPACE:
|
||||
path, column = view.get_cursor()
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
app/ui/lang/sk/LC_MESSAGES/demon-editor.mo
Normal file
BIN
app/ui/lang/sk/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
Binary file not shown.
@@ -1551,9 +1551,42 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="services_add_new_popup_item">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">New</property>
|
||||
<signal name="activate" handler="on_services_add_new" swapped="no"/>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child type="submenu">
|
||||
<object class="GtkMenu" id="et_popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Satellite channel</property>
|
||||
<property name="name">s</property>
|
||||
<signal name="activate" handler="on_services_add_new" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Terrestrial channel</property>
|
||||
<property name="name">t</property>
|
||||
<signal name="activate" handler="on_services_add_new" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Cable channel</property>
|
||||
<property name="name">c</property>
|
||||
<signal name="activate" handler="on_services_add_new" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
@@ -1668,6 +1701,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
<object class="GtkComboBoxText" id="profile_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive" bind-source="iptv_progress_bar" bind-property="visible" bind-flags="invert-boolean">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="active">0</property>
|
||||
@@ -1818,7 +1852,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkLabel" id="app_ver_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">3.13.2 Beta</property>
|
||||
<property name="label">3.14.4 Beta</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
@@ -1872,6 +1906,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="label" translatable="yes">DVB</property>
|
||||
<property name="width-request">80</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive" bind-source="iptv_progress_bar" bind-property="visible" bind-flags="invert-boolean">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
@@ -1888,6 +1923,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkRadioButton" id="iptv_button">
|
||||
<property name="label" translatable="yes">IPTV</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive" bind-source="services_progress_bar" bind-property="visible" bind-flags="invert-boolean">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
@@ -1911,7 +1947,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="filter_services_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="sensitive" bind-source="services_progress_bar" bind-property="visible" bind-flags="invert-boolean">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
@@ -1933,6 +1969,7 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="filter_iptv_services_button">
|
||||
<property name="sensitive" bind-source="iptv_progress_bar" bind-property="visible" bind-flags="invert-boolean">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
@@ -2692,8 +2729,8 @@ Author: Dmitriy Yefremov
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">5</property>
|
||||
<property name="margin-right">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="resize-mode">queue</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
@@ -2826,12 +2863,13 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="services_load_spinner">
|
||||
<property name="visible">True</property>
|
||||
<object class="GtkProgressBar" id="services_progress_bar">
|
||||
<property name="visible">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Loading data...</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin-start">15</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -2840,6 +2878,37 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">10</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage" id="current_data_path_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Current data path</property>
|
||||
<property name="icon-name">document-open-symbolic</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">11</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="current_data_path_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Current data path</property>
|
||||
<property name="label"> </property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="xalign">0</property>
|
||||
<attributes>
|
||||
<attribute name="size" value="8000"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">12</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -3174,8 +3243,8 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="iptv_services_load_spinner">
|
||||
<property name="visible">True</property>
|
||||
<object class="GtkProgressBar" id="iptv_progress_bar">
|
||||
<property name="visible">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Loading data...</property>
|
||||
<property name="halign">center</property>
|
||||
@@ -4737,97 +4806,185 @@ Author: Dmitriy Yefremov
|
||||
<property name="can-focus">False</property>
|
||||
<property name="stack">stack</property>
|
||||
</object>
|
||||
<object class="GtkButtonBox" id="toolbar_main_box">
|
||||
<object class="GtkPopover" id="send_popover">
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkButtonBox">
|
||||
<property name="width-request">100</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="send_srv_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="action-name">app.on_send</property>
|
||||
<property name="text" translatable="yes">All</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="send_bq_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="action-name">app.upload_bouquets</property>
|
||||
<property name="text" translatable="yes">Bouquets</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkBox" id="toolbar_main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="open_tool_button">
|
||||
<object class="GtkButtonBox" id="osv_button_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Open</property>
|
||||
<property name="action-name">app.on_data_open</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="open_image">
|
||||
<object class="GtkButton" id="open_tool_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-open-symbolic</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Open</property>
|
||||
<property name="action-name">app.on_data_open</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="open_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-open-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="save_tool_button">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Save</property>
|
||||
<property name="action-name">app.on_data_save</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="save_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-save-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="receive_button">
|
||||
<object class="GtkButtonBox" id="send_receive_button_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Download from the receiver</property>
|
||||
<property name="action-name">app.on_receive</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="download_image">
|
||||
<property name="width-request">32</property>
|
||||
<object class="GtkButton" id="receive_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">network-receive-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Download from the receiver</property>
|
||||
<property name="action-name">app.on_receive</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="download_image">
|
||||
<property name="width-request">32</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">network-receive-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="send_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Transfer to receiver</property>
|
||||
<property name="action-name">app.on_send</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="send_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">network-transmit-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="send_menu_button">
|
||||
<property name="visible" bind-source="send_button" bind-property="visible" bind-flags="invert-boolean">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Transfer to receiver</property>
|
||||
<property name="popover">send_popover</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="send_menu_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">network-transmit-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="send_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Transfer to receiver</property>
|
||||
<property name="action-name">app.on_send</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="send_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">network-transmit-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="save_tool_button">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Save</property>
|
||||
<property name="action-name">app.on_data_save</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="save_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-save-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
220
app/ui/main.py
220
app/ui/main.py
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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,10 +40,10 @@ from urllib.parse import urlparse, unquote
|
||||
from gi.repository import GLib, Gio, GObject
|
||||
|
||||
from app.commons import run_idle, log, run_task, run_with_delay, init_logger, DefaultDict
|
||||
from app.connections import (HttpAPI, download_data, DownloadType, upload_data, STC_XML_FILE)
|
||||
from app.connections import (HttpAPI, download_data, DownloadType, upload_data)
|
||||
from app.eparser import get_blacklist, write_blacklist, write_bouquet
|
||||
from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service
|
||||
from app.eparser.ecommons import CAS, Flag, BouquetService
|
||||
from app.eparser.ecommons import CAS, Flag, BouquetService, TrType
|
||||
from app.eparser.enigma.bouquets import BqServiceType
|
||||
from app.eparser.enigma.streamrelay import StreamRelay
|
||||
from app.eparser.iptv import export_to_m3u, StreamType
|
||||
@@ -69,7 +69,7 @@ from .iptv import (IptvDialog, SearchUnavailableDialog, IptvListConfigurationDia
|
||||
from .main_helper import *
|
||||
from .picons import PiconManager
|
||||
from .search import SearchProvider
|
||||
from .service_details_dialog import ServiceDetailsDialog, Action
|
||||
from .service_dialog import ServiceDetailsDialog, Action
|
||||
from .settings_dialog import SettingsDialog
|
||||
from .uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON, MOVE_KEYS, KeyboardKey, Column,
|
||||
MOD_MASK, APP_FONT, Page, HeaderBar, LINK_ICON)
|
||||
@@ -79,7 +79,7 @@ from .xml.edit import SatellitesTool
|
||||
|
||||
class Application(Gtk.Application):
|
||||
""" Main application class. """
|
||||
VERSION = "3.13.2"
|
||||
VERSION = "3.14.4"
|
||||
|
||||
SERVICE_MODEL = "services_list_store"
|
||||
FAV_MODEL = "fav_list_store"
|
||||
@@ -88,12 +88,13 @@ class Application(Gtk.Application):
|
||||
IPTV_MODEL = "iptv_list_store"
|
||||
DRAG_SEP = "::::"
|
||||
|
||||
MARKER_TYPES = {BqServiceType.MARKER.name, BqServiceType.SPACE.name, BqServiceType.ALT.name}
|
||||
MARKER_TYPES = {BqServiceType.MARKER.name, BqServiceType.SPACE.name}
|
||||
NON_REF_TYPES = {BqServiceType.MARKER.name, BqServiceType.SPACE.name, BqServiceType.ALT.name}
|
||||
|
||||
DEL_FACTOR = 100 # Batch size to delete in one pass.
|
||||
FAV_FACTOR = DEL_FACTOR * 5
|
||||
|
||||
_TV_TYPES = ("TV", "TV (HD)", "TV (UHD)", "TV (H264)")
|
||||
_TV_TYPES = {"TV", "TV (HD)", "TV (UHD)", "TV (H264)"}
|
||||
|
||||
BG_TASK_LIMIT = 5
|
||||
|
||||
@@ -102,7 +103,9 @@ class Application(Gtk.Application):
|
||||
# Dynamically active elements depending on the selected view
|
||||
_SERVICE_ELEMENTS = ("services_to_fav_end_move_popup_item", "services_to_fav_move_popup_item",
|
||||
"services_create_bouquet_popup_item", "services_copy_popup_item", "services_edit_popup_item",
|
||||
"services_add_new_popup_item", "services_picon_popup_item", "services_remove_popup_item")
|
||||
"services_mark_not_in_bq_popup_item", "services_clear_not_in_bq_popup_item",
|
||||
"services_clear_new_flag_item", "services_picon_popup_item", "services_remove_popup_item",
|
||||
"services_reference_popup_item")
|
||||
|
||||
_FAV_ELEMENTS = ("fav_cut_popup_item", "fav_paste_popup_item", "fav_locate_popup_item", "fav_iptv_popup_item",
|
||||
"fav_insert_marker_popup_item", "fav_insert_space_popup_item", "fav_edit_sub_menu_popup_item",
|
||||
@@ -246,6 +249,8 @@ class Application(Gtk.Application):
|
||||
self._is_data_open_enabled = True
|
||||
self._is_data_extract_enabled = False
|
||||
self._is_data_save_enabled = False
|
||||
self._ext_data_path = None
|
||||
self._arch_data_path = None
|
||||
# Used for copy/paste. When adding the previous data will not be deleted.
|
||||
# Clearing only after the insertion!
|
||||
self._rows_buffer = []
|
||||
@@ -395,6 +400,7 @@ class Application(Gtk.Application):
|
||||
self._bq_name_label = builder.get_object("bq_name_label")
|
||||
self._iptv_model = builder.get_object("iptv_list_store")
|
||||
self._iptv_menu_button = builder.get_object("iptv_menu_button")
|
||||
self._send_button = builder.get_object("send_button")
|
||||
# Setting custom sort function for position column.
|
||||
self._services_view.get_model().set_sort_func(Column.SRV_POS, self.position_sort_func, Column.SRV_POS)
|
||||
# App info
|
||||
@@ -420,9 +426,10 @@ class Application(Gtk.Application):
|
||||
self._tv_count_label = builder.get_object("tv_count_label")
|
||||
self._radio_count_label = builder.get_object("radio_count_label")
|
||||
self._data_count_label = builder.get_object("data_count_label")
|
||||
self._current_data_path_label = builder.get_object("current_data_path_label")
|
||||
self._iptv_count_label = builder.get_object("iptv_count_label")
|
||||
self._services_load_spinner = builder.get_object("services_load_spinner")
|
||||
self._iptv_services_load_spinner = builder.get_object("iptv_services_load_spinner")
|
||||
self._services_progress_bar = builder.get_object("services_progress_bar")
|
||||
self._iptv_progress_bar = builder.get_object("iptv_progress_bar")
|
||||
self._save_tool_button = builder.get_object("save_tool_button")
|
||||
self.bind_property("is-data-save-enabled", self._save_tool_button, "visible")
|
||||
self._receiver_info_box.bind_property("visible", self._http_status_image, "visible", 4)
|
||||
@@ -463,7 +470,6 @@ class Application(Gtk.Application):
|
||||
self._filter_free_button = builder.get_object("filter_free_button")
|
||||
self._filter_coded_button = builder.get_object("filter_coded_button")
|
||||
self._filter_not_in_bq_button = builder.get_object("filter_not_in_bq_button")
|
||||
self._services_load_spinner.bind_property("active", self._filter_services_button, "sensitive", 4)
|
||||
self._filter_iptv_services_button = builder.get_object("filter_iptv_services_button")
|
||||
# Search.
|
||||
services_search_provider = SearchProvider(self._services_view,
|
||||
@@ -598,10 +604,6 @@ class Application(Gtk.Application):
|
||||
iptv_button.bind_property("active", self._filter_iptv_services_button, "visible")
|
||||
iptv_button.bind_property("active", self._iptv_search_button, "visible")
|
||||
iptv_button.bind_property("active", builder.get_object("iptv_export_to_m3u_button"), "visible")
|
||||
self._iptv_services_load_spinner.bind_property("active", self._filter_iptv_services_button, "sensitive", 4)
|
||||
self._iptv_services_load_spinner.bind_property("active", self._profile_combo_box, "sensitive", 4)
|
||||
self._iptv_services_load_spinner.bind_property("active", self._dvb_button, "sensitive", 4)
|
||||
self._services_load_spinner.bind_property("active", self._iptv_button, "sensitive", 4)
|
||||
self.connect("profile-changed", self.init_iptv)
|
||||
self.connect("iptv-service-added", self.on_iptv_service_added)
|
||||
self.connect("iptv-service-edited", self.on_iptv_service_edited)
|
||||
@@ -720,18 +722,21 @@ class Application(Gtk.Application):
|
||||
self.on_iptv_list_configuration, self.on_remove_all_unavailable):
|
||||
iptv_elem.bind_property("sensitive", self.set_action(h.__name__, h, False), "enabled")
|
||||
|
||||
if self._settings.extensions_support:
|
||||
self.init_extensions(builder)
|
||||
self.init_extensions(builder)
|
||||
|
||||
def init_extensions(self, builder):
|
||||
import pkgutil
|
||||
from importlib.util import module_from_spec
|
||||
from app.ui.extensions.management import ExtensionManager
|
||||
# Extensions (Plugins) section.
|
||||
ext_section = builder.get_object(f"{'mac_' if IS_DARWIN else ''}extension_section")
|
||||
self.set_action("on_extension_manager", lambda a, v: ExtensionManager(self).show())
|
||||
ext_section.append_item(Gio.MenuItem.new(translate("Extension Manager"), "app.on_extension_manager"))
|
||||
|
||||
if not self._settings.extensions_support:
|
||||
return
|
||||
|
||||
import pkgutil
|
||||
from importlib.util import module_from_spec
|
||||
|
||||
ext_path = f"{self._settings.default_data_path}tools{os.sep}extensions"
|
||||
ext_paths = [f"{os.path.dirname(__file__)}{os.sep}extensions", ext_path, "extensions"]
|
||||
extensions = {}
|
||||
@@ -810,7 +815,7 @@ class Application(Gtk.Application):
|
||||
# Open and download/upload data.
|
||||
self.set_action("open_data", lambda a, v: self.open_data())
|
||||
self.set_action("upload_all", lambda a, v: self.emit("data-send", self._page))
|
||||
self.set_action("upload_bouquets", lambda a, v: self.on_upload_data(DownloadType.BOUQUETS))
|
||||
self.set_action("upload_bouquets", self.on_upload_bouquets)
|
||||
sa = self.set_action("on_data_save", lambda a, v: self.emit("data-save", self._page), False)
|
||||
self.bind_property("is-data-save-enabled", sa, "enabled")
|
||||
sa = self.set_action("on_data_save_as", lambda a, v: self.emit("data-save-as", self._page), False)
|
||||
@@ -1103,7 +1108,17 @@ class Application(Gtk.Application):
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
return True
|
||||
else:
|
||||
GLib.idle_add(self.quit)
|
||||
if len(self._task_box):
|
||||
msg = f"{translate('There are running background tasks!')}\n\n\t\t{translate('Are you sure?')}"
|
||||
if show_dialog(DialogType.QUESTION, self._main_window, msg) != Gtk.ResponseType.OK:
|
||||
return True
|
||||
log("Terminating the application...")
|
||||
import signal
|
||||
|
||||
os.kill(os.getpid(), signal.SIGTERM)
|
||||
GLib.idle_add(self.quit, priority=GLib.PRIORITY_HIGH)
|
||||
|
||||
return False
|
||||
|
||||
def on_main_window_state(self, window, event):
|
||||
if event.new_window_state & Gdk.WindowState.FULLSCREEN or event.new_window_state & Gdk.WindowState.MAXIMIZED:
|
||||
@@ -1171,6 +1186,7 @@ class Application(Gtk.Application):
|
||||
|
||||
def on_visible_page(self, stack, param):
|
||||
self._page = Page(stack.get_visible_child_name())
|
||||
self._send_button.set_visible(self._page is not Page.SERVICES)
|
||||
self._fav_paned.set_visible(self._page in self._fav_pages)
|
||||
self.is_data_save_enabled = self._page in self.DATA_SAVE_PAGES
|
||||
self.is_data_open_enabled = self._page in self.DATA_OPEN_PAGES
|
||||
@@ -1212,7 +1228,7 @@ class Application(Gtk.Application):
|
||||
|
||||
msg = translate("Restart the program to apply all changes.")
|
||||
if value:
|
||||
warn = "It can cause some problems."
|
||||
warn = translate("It can cause some problems.")
|
||||
msg = f"{translate('EXPERIMENTAL!')} {warn} {msg}"
|
||||
self.show_info_message(msg, Gtk.MessageType.WARNING)
|
||||
|
||||
@@ -1286,7 +1302,7 @@ class Application(Gtk.Application):
|
||||
def fav_service_data_func(self, column, renderer, model, itr, data):
|
||||
if self._display_epg and self._s_type is SettingsType.ENIGMA_2:
|
||||
srv_name = model.get_value(itr, Column.FAV_SERVICE)
|
||||
if model.get_value(itr, Column.FAV_TYPE) in self.MARKER_TYPES:
|
||||
if model.get_value(itr, Column.FAV_TYPE) in self.NON_REF_TYPES:
|
||||
return True
|
||||
|
||||
event = self._epg_cache.get_current_event(srv_name)
|
||||
@@ -1471,7 +1487,7 @@ class Application(Gtk.Application):
|
||||
priority = GLib.PRIORITY_LOW
|
||||
|
||||
if model_name == self.FAV_MODEL:
|
||||
gen = self.remove_favs(itrs, model)
|
||||
gen = self.remove_favorites(itrs, model)
|
||||
elif model_name == self.BQ_MODEL:
|
||||
gen = self.delete_bouquets(itrs, model)
|
||||
priority = GLib.PRIORITY_DEFAULT
|
||||
@@ -1487,18 +1503,24 @@ class Application(Gtk.Application):
|
||||
|
||||
return rows
|
||||
|
||||
def remove_favs(self, itrs, model):
|
||||
def remove_favorites(self, itrs, model):
|
||||
""" Deleting bouquet services. """
|
||||
if self._bq_selected:
|
||||
fav_bouquet = self._bouquets.get(self._bq_selected, None)
|
||||
if fav_bouquet:
|
||||
removed = []
|
||||
for index, itr in enumerate(itrs):
|
||||
del fav_bouquet[int(model.get_path(itr)[0])]
|
||||
path = model.get_path(itr)
|
||||
p_index = int(path[0])
|
||||
removed.append((p_index, tuple(model[path])))
|
||||
del fav_bouquet[p_index]
|
||||
self._fav_model.remove(itr)
|
||||
|
||||
if index % self.DEL_FACTOR == 0:
|
||||
yield True
|
||||
|
||||
self.update_fav_num_column(model)
|
||||
self.emit("fav-removed", self._bq_selected)
|
||||
self.emit("fav-removed", removed)
|
||||
|
||||
self.on_model_changed(self._fav_model)
|
||||
self._wait_dialog.hide()
|
||||
@@ -2128,11 +2150,15 @@ class Application(Gtk.Application):
|
||||
name, model = get_model_data(view)
|
||||
self.delete_views_selection(name)
|
||||
elif event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and event.button == Gdk.BUTTON_PRIMARY:
|
||||
self._select_enabled = True
|
||||
if self._settings.main_list_playback and self._fav_click_mode is not PlaybackMode.DISABLED:
|
||||
if view is self._services_view:
|
||||
self.emit("srv-clicked", self._fav_click_mode)
|
||||
elif view is self._iptv_services_view:
|
||||
self.emit("iptv-clicked", self._fav_click_mode)
|
||||
else:
|
||||
if view is not self._fav_view:
|
||||
self.on_edit()
|
||||
|
||||
def on_view_release(self, view, event):
|
||||
""" Handles a mouse click (release) to view. """
|
||||
@@ -2196,6 +2222,10 @@ class Application(Gtk.Application):
|
||||
elif page is Page.SERVICES:
|
||||
self.on_upload_data(DownloadType.SERVICES)
|
||||
|
||||
def on_upload_bouquets(self, action, value=None):
|
||||
self.change_action_state("on_logs_show", GLib.Variant.new_boolean(True))
|
||||
self.on_upload_data(DownloadType.BOUQUETS)
|
||||
|
||||
def on_bg_task_add(self, app, task):
|
||||
if len(self._task_box) <= self.BG_TASK_LIMIT:
|
||||
self._task_box.add(task)
|
||||
@@ -2212,7 +2242,7 @@ class Application(Gtk.Application):
|
||||
self.on_task_done(app, task)
|
||||
|
||||
@run_task
|
||||
def on_download_data(self, download_type=DownloadType.ALL):
|
||||
def on_download_data(self, download_type=DownloadType.ALL, files_filter=None):
|
||||
backup, backup_src, data_path = self._settings.backup_before_downloading, None, None
|
||||
try:
|
||||
if backup and download_type is not DownloadType.SATELLITES:
|
||||
@@ -2220,7 +2250,7 @@ class Application(Gtk.Application):
|
||||
backup_path = self._settings.profile_backup_path or self._settings.default_backup_path
|
||||
backup_src = backup_data(data_path, backup_path, download_type is DownloadType.ALL)
|
||||
|
||||
download_data(settings=self._settings, download_type=download_type)
|
||||
download_data(settings=self._settings, download_type=download_type, files_filter=files_filter)
|
||||
except Exception as e:
|
||||
msg = "Downloading data error: {}"
|
||||
log(msg.format(e), debug=self._settings.debug_mode, fmt_message=msg)
|
||||
@@ -2233,22 +2263,26 @@ class Application(Gtk.Application):
|
||||
else:
|
||||
GLib.idle_add(self.open_data)
|
||||
|
||||
def on_upload_data(self, download_type=DownloadType.ALL):
|
||||
def on_upload_data(self, download_type=DownloadType.ALL, files_filter=None):
|
||||
if not self.is_data_saved():
|
||||
gen = self.save_data(lambda: self.upload_data(download_type))
|
||||
gen = self.save_data(lambda: self.upload_data(download_type, files_filter=files_filter))
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
else:
|
||||
self.upload_data(download_type)
|
||||
self.upload_data(download_type, files_filter=files_filter)
|
||||
|
||||
@run_task
|
||||
def upload_data(self, download_type):
|
||||
def upload_data(self, download_type, files_filter=None):
|
||||
opts = self._settings
|
||||
multiple = len(self._settings.hosts) > 1
|
||||
for host in self._settings.hosts:
|
||||
if multiple:
|
||||
log(f"##### Uploading data on [{host}] #####")
|
||||
try:
|
||||
upload_data(settings=opts, download_type=download_type, ext_host=host)
|
||||
upload_data(settings=opts,
|
||||
download_type=download_type,
|
||||
files_filter=files_filter,
|
||||
ext_host=host,
|
||||
ext_path=self._ext_data_path)
|
||||
except Exception as e:
|
||||
msg = "Uploading data error: {}"
|
||||
log(msg.format(e), debug=self._settings.debug_mode, fmt_message=msg)
|
||||
@@ -2262,6 +2296,7 @@ class Application(Gtk.Application):
|
||||
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings, title="Open folder")
|
||||
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
return
|
||||
|
||||
self.open_data(response)
|
||||
|
||||
def on_data_extract(self, app, page):
|
||||
@@ -2294,9 +2329,9 @@ class Application(Gtk.Application):
|
||||
|
||||
def open_compressed_data(self, data_path):
|
||||
""" Opening archived data. """
|
||||
arch_path = self.get_archive_path(data_path)
|
||||
if arch_path:
|
||||
gen = self.update_data(f"{arch_path.name}{os.sep}", callback=arch_path.cleanup)
|
||||
self._arch_data_path = self.get_archive_path(data_path)
|
||||
if self._arch_data_path:
|
||||
gen = self.update_data(f"{self._arch_data_path.name}{os.sep}")
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def get_archive_path(self, data_path):
|
||||
@@ -2331,12 +2366,13 @@ class Application(Gtk.Application):
|
||||
return tmp_path
|
||||
|
||||
def update_data(self, data_path, callback=None):
|
||||
self._services_load_spinner.start()
|
||||
self._ext_data_path = data_path
|
||||
self.on_info_bar_close()
|
||||
self._profile_combo_box.set_sensitive(False)
|
||||
self._alt_revealer.set_visible(False)
|
||||
self._filter_services_button.set_active(False)
|
||||
self._wait_dialog.show()
|
||||
self._services_progress_bar.show()
|
||||
|
||||
yield from self.clear_current_data()
|
||||
# Reset of sorting
|
||||
@@ -2354,17 +2390,10 @@ class Application(Gtk.Application):
|
||||
self.init_profiles()
|
||||
|
||||
data_path = self._settings.profile_data_path if data_path is None else data_path
|
||||
self._current_data_path_label.set_text(data_path)
|
||||
local_path = self._settings.profile_data_path
|
||||
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
||||
|
||||
if data_path != local_path:
|
||||
from shutil import copyfile
|
||||
|
||||
for f in STC_XML_FILE:
|
||||
xml_src = data_path + f
|
||||
if os.path.isfile(xml_src):
|
||||
copyfile(xml_src, local_path + f)
|
||||
|
||||
prf = self._s_type
|
||||
black_list = get_blacklist(data_path)
|
||||
self._stream_relay.refresh(data_path)
|
||||
@@ -2378,21 +2407,19 @@ class Application(Gtk.Application):
|
||||
except FileNotFoundError as e:
|
||||
msg = translate("Please, download files from receiver or setup your path for read data!")
|
||||
self.show_error_message(getattr(e, "message", str(e)) + "\n\n" + msg)
|
||||
self._services_load_spinner.stop()
|
||||
return
|
||||
except SyntaxError as e:
|
||||
self.show_error_message(str(e))
|
||||
self._services_load_spinner.stop()
|
||||
return
|
||||
except Exception as e:
|
||||
msg = "Reading data error: {}"
|
||||
log(msg.format(e), debug=self._settings.debug_mode, fmt_message=msg)
|
||||
self.show_error_message("{}\n{}".format(translate("Reading data error!"), e))
|
||||
self._services_load_spinner.stop()
|
||||
return
|
||||
else:
|
||||
self.append_blacklist(black_list)
|
||||
yield from self.append_data(bouquets, services)
|
||||
data_gen = self.append_data(bouquets, services)
|
||||
yield from data_gen
|
||||
if callback:
|
||||
callback()
|
||||
yield True
|
||||
@@ -2404,13 +2431,15 @@ class Application(Gtk.Application):
|
||||
finally:
|
||||
self._profile_combo_box.set_sensitive(True)
|
||||
self._wait_dialog.hide()
|
||||
self._services_progress_bar.hide()
|
||||
self.emit("data-load-done", self._settings.current_profile)
|
||||
|
||||
def append_data(self, bouquets, services):
|
||||
if self._app_info_box.get_visible():
|
||||
yield from self.show_app_info(False)
|
||||
self.append_bouquets(bouquets)
|
||||
yield from self.append_services(services)
|
||||
s_gen = self.append_services(services)
|
||||
yield from s_gen
|
||||
self.update_sat_positions()
|
||||
yield True
|
||||
|
||||
@@ -2540,33 +2569,38 @@ class Application(Gtk.Application):
|
||||
to_add.append(srv)
|
||||
# Adding channels to dict with fav_id as keys.
|
||||
self._services[srv.fav_id] = srv
|
||||
|
||||
self.update_services_counts(len(self._services.values()))
|
||||
self._wait_dialog.hide()
|
||||
factor = self.DEL_FACTOR / 4
|
||||
size = len(to_add)
|
||||
|
||||
for index, srv in enumerate(to_add):
|
||||
background = self.get_new_background(srv.flags_cas)
|
||||
s = srv + (None, background)
|
||||
self._services_model.append(s)
|
||||
if index % factor == 0:
|
||||
self._services_progress_bar.set_fraction(index / size)
|
||||
yield True
|
||||
|
||||
self._services_load_spinner.stop()
|
||||
yield True
|
||||
|
||||
def append_iptv_data(self, services=None):
|
||||
self._iptv_services_load_spinner.start()
|
||||
self._iptv_progress_bar.show()
|
||||
services = services or self._services.values()
|
||||
services = [s for s in services if s.service_type == BqServiceType.IPTV.name]
|
||||
size = len(services)
|
||||
|
||||
for index, s in enumerate(filter(lambda x: x.service_type == BqServiceType.IPTV.name, services), start=1):
|
||||
for index, s in enumerate(services, start=1):
|
||||
ref, url = get_iptv_data(s.fav_id)
|
||||
self._iptv_model.append((s.service, None, None, ref, url, s.fav_id, s.picon_id, None))
|
||||
if index % self.DEL_FACTOR == 0:
|
||||
self._iptv_count_label.set_text(str(index))
|
||||
self._iptv_progress_bar.set_fraction(index / size)
|
||||
yield True
|
||||
|
||||
self._iptv_count_label.set_text(str(len(self._iptv_model)))
|
||||
self._iptv_services_load_spinner.stop()
|
||||
self._iptv_progress_bar.hide()
|
||||
yield True
|
||||
|
||||
def get_new_background(self, flags):
|
||||
@@ -2651,7 +2685,7 @@ class Application(Gtk.Application):
|
||||
def save_data(self, callback=None, ext_path=None):
|
||||
self._save_tool_button.set_sensitive(False)
|
||||
profile = self._s_type
|
||||
path = ext_path or self._settings.profile_data_path
|
||||
path = ext_path or self._ext_data_path or self._settings.profile_data_path
|
||||
backup_path = self._settings.profile_backup_path
|
||||
# Backup data or clearing data path
|
||||
backup_data(path, backup_path) if not ext_path and self._settings.backup_before_save else clear_data_path(path)
|
||||
@@ -2770,7 +2804,7 @@ class Application(Gtk.Application):
|
||||
self._alt_revealer.set_visible(False)
|
||||
self.on_info_bar_close()
|
||||
|
||||
if self._page is Page.EPG and srv.service_type not in self.MARKER_TYPES:
|
||||
if self._page is Page.EPG and srv.service_type not in self.NON_REF_TYPES:
|
||||
self.emit("fav-changed", srv)
|
||||
|
||||
def on_services_selection(self, model, path, column):
|
||||
@@ -2954,11 +2988,10 @@ class Application(Gtk.Application):
|
||||
|
||||
def on_tree_view_key_press(self, view, event):
|
||||
""" Handling keystrokes on press """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return False
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
if key is KeyboardKey.F:
|
||||
if ctrl:
|
||||
@@ -3007,11 +3040,10 @@ class Application(Gtk.Application):
|
||||
|
||||
def on_tree_view_key_release(self, view, event):
|
||||
""" Handling keystrokes on release """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
shift = event.state & Gdk.ModifierType.SHIFT_MASK
|
||||
model_name, model = get_model_data(view)
|
||||
@@ -3069,18 +3101,15 @@ class Application(Gtk.Application):
|
||||
self._tool_elements[elem].set_sensitive(self._bq_selected and not is_service)
|
||||
else:
|
||||
self._tool_elements[elem].set_sensitive(not_empty and not is_service)
|
||||
for elem in self._SERVICE_ELEMENTS:
|
||||
self._tool_elements[elem].set_sensitive(not_empty and is_service)
|
||||
for elem in self._BOUQUET_ELEMENTS:
|
||||
self._tool_elements[elem].set_sensitive(False)
|
||||
|
||||
[self._tool_elements[elem].set_sensitive(not_empty and is_service) for elem in self._SERVICE_ELEMENTS]
|
||||
[self._tool_elements[elem].set_sensitive(False) for elem in self._BOUQUET_ELEMENTS]
|
||||
for elem in self._FAV_IPTV_ELEMENTS:
|
||||
is_iptv = self._bq_selected and not is_service
|
||||
if self._s_type is SettingsType.NEUTRINO_MP:
|
||||
is_iptv = is_iptv and BqType(self._bq_selected.split(":")[1]) is BqType.WEBTV
|
||||
self._tool_elements[elem].set_sensitive(is_iptv)
|
||||
for elem in self._COMMONS_ELEMENTS:
|
||||
self._tool_elements[elem].set_sensitive(not_empty)
|
||||
|
||||
[self._tool_elements[elem].set_sensitive(not_empty) for elem in self._COMMONS_ELEMENTS]
|
||||
|
||||
if self._s_type is not SettingsType.ENIGMA_2:
|
||||
for elem in self._FAV_ENIGMA_ELEMENTS:
|
||||
@@ -3152,9 +3181,9 @@ class Application(Gtk.Application):
|
||||
def on_fav_press(self, menu, event):
|
||||
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS:
|
||||
if self._fav_click_mode is PlaybackMode.DISABLED:
|
||||
return
|
||||
|
||||
self.emit("fav-clicked", self._fav_click_mode)
|
||||
self.on_service_edit(self._fav_view)
|
||||
else:
|
||||
self.emit("fav-clicked", self._fav_click_mode)
|
||||
else:
|
||||
return self.on_view_popup_menu(menu, event)
|
||||
|
||||
@@ -3239,7 +3268,7 @@ class Application(Gtk.Application):
|
||||
fav_bqt = self._bouquets.get(self._bq_selected, None)
|
||||
response = SearchUnavailableDialog(self._main_window, self._fav_model, fav_bqt, iptv_rows, self._s_type).show()
|
||||
if response:
|
||||
gen = self.remove_favs(response, self._fav_model)
|
||||
gen = self.remove_favorites(response, self._fav_model)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def on_reference_assign(self, view):
|
||||
@@ -3670,7 +3699,7 @@ class Application(Gtk.Application):
|
||||
row = self._fav_model[path][:]
|
||||
srv_type, fav_id = row[Column.FAV_TYPE], row[Column.FAV_ID]
|
||||
|
||||
if srv_type in self.MARKER_TYPES and show_error:
|
||||
if srv_type in self.NON_REF_TYPES and show_error:
|
||||
self.show_error_message("Not allowed in this context!")
|
||||
return
|
||||
|
||||
@@ -3905,7 +3934,7 @@ class Application(Gtk.Application):
|
||||
|
||||
@run_with_delay(1)
|
||||
def on_filter_changed(self, item=None):
|
||||
self._services_load_spinner.start()
|
||||
self._services_progress_bar.show()
|
||||
self.update_filter_cache()
|
||||
self.update_filter_state()
|
||||
|
||||
@@ -3935,7 +3964,7 @@ class Application(Gtk.Application):
|
||||
yield True
|
||||
|
||||
view.set_model(main_model)
|
||||
GLib.idle_add(self._services_load_spinner.stop)
|
||||
GLib.idle_add(self._services_progress_bar.hide)
|
||||
|
||||
def update_filter_cache(self):
|
||||
self._filter_cache.clear()
|
||||
@@ -4030,7 +4059,7 @@ class Application(Gtk.Application):
|
||||
if srv_type == BqServiceType.ALT.name:
|
||||
return self.show_error_message("Operation not allowed in this context!")
|
||||
|
||||
if srv_type in self.MARKER_TYPES:
|
||||
if srv_type in self.NON_REF_TYPES:
|
||||
return self.on_rename(view)
|
||||
elif srv_type == BqServiceType.IPTV.name:
|
||||
return self.on_iptv_service_edit(model[paths][Column.FAV_ID], view)
|
||||
@@ -4041,10 +4070,14 @@ class Application(Gtk.Application):
|
||||
if model_name == self.IPTV_MODEL:
|
||||
self.on_iptv_service_edit(model[paths][Column.IPTV_FAV_ID], view)
|
||||
else:
|
||||
ServiceDetailsDialog(self, self._NEW_COLOR).show()
|
||||
ServiceDetailsDialog(self).show()
|
||||
|
||||
def on_services_add_new(self, item):
|
||||
ServiceDetailsDialog(self, action=Action.ADD).show()
|
||||
if self._s_type is SettingsType.NEUTRINO_MP:
|
||||
self.show_error_message("Neutrino at the moment not supported!")
|
||||
return
|
||||
|
||||
ServiceDetailsDialog(self, action=Action.ADD, tr_type=TrType(item.get_name())).show()
|
||||
|
||||
def on_bouquets_edit(self, view):
|
||||
""" Renaming bouquets. """
|
||||
@@ -4156,7 +4189,7 @@ class Application(Gtk.Application):
|
||||
""" Marks services with duplicate [names] in the fav list. """
|
||||
from collections import Counter
|
||||
|
||||
dup = Counter(r[Column.FAV_SERVICE] for r in self._fav_model if r[Column.FAV_TYPE] not in self.MARKER_TYPES)
|
||||
dup = Counter(r[Column.FAV_SERVICE] for r in self._fav_model if r[Column.FAV_TYPE] not in self.NON_REF_TYPES)
|
||||
dup = {k for k, v in dup.items() if v > 1}
|
||||
|
||||
for r in self._fav_model:
|
||||
@@ -4177,7 +4210,7 @@ class Application(Gtk.Application):
|
||||
if count:
|
||||
if show_dialog(DialogType.QUESTION, self._main_window) != Gtk.ResponseType.OK:
|
||||
return
|
||||
gen = self.remove_favs(to_remove, self._fav_model)
|
||||
gen = self.remove_favorites(to_remove, self._fav_model)
|
||||
GLib.idle_add(lambda: next(gen, False))
|
||||
self.show_info_message(f"{translate('Done!')} {translate('Removed')}: {count}")
|
||||
else:
|
||||
@@ -4192,7 +4225,7 @@ class Application(Gtk.Application):
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def mark_not_in_bouquets(self):
|
||||
self._services_load_spinner.start()
|
||||
self._services_progress_bar.show()
|
||||
ids = set(chain.from_iterable(self._bouquets.values()))
|
||||
|
||||
for index, row in enumerate(self._services_model):
|
||||
@@ -4203,7 +4236,7 @@ class Application(Gtk.Application):
|
||||
if index % self.FAV_FACTOR == 0:
|
||||
yield True
|
||||
|
||||
self._services_load_spinner.stop()
|
||||
self._services_progress_bar.hide()
|
||||
yield True
|
||||
|
||||
def on_services_clear_marked(self, item):
|
||||
@@ -4215,13 +4248,13 @@ class Application(Gtk.Application):
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def clear_marked(self):
|
||||
self._services_load_spinner.start()
|
||||
self._services_progress_bar.show()
|
||||
for index, row in enumerate(self._services_model):
|
||||
row[Column.SRV_BACKGROUND] = self.get_new_background(row[Column.SRV_CAS_FLAGS])
|
||||
if index % self.FAV_FACTOR == 0:
|
||||
yield True
|
||||
|
||||
self._services_load_spinner.stop()
|
||||
self._services_progress_bar.hide()
|
||||
yield True
|
||||
|
||||
def on_services_clear_new_marked(self, item):
|
||||
@@ -4238,7 +4271,7 @@ class Application(Gtk.Application):
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def clear_new_marked(self, model, paths):
|
||||
self._services_load_spinner.start()
|
||||
self._services_progress_bar.show()
|
||||
|
||||
paths = get_base_paths(paths, model)
|
||||
model = get_base_model(model)
|
||||
@@ -4269,7 +4302,7 @@ class Application(Gtk.Application):
|
||||
yield True
|
||||
|
||||
self.show_info_message("Done!", Gtk.MessageType.INFO)
|
||||
self._services_load_spinner.stop()
|
||||
self._services_progress_bar.hide()
|
||||
yield True
|
||||
|
||||
# ***************** Picons ********************* #
|
||||
@@ -4517,11 +4550,10 @@ class Application(Gtk.Application):
|
||||
self.emit("fav-changed", srv)
|
||||
|
||||
def on_alt_view_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if ctrl and key == KeyboardKey.V:
|
||||
@@ -4626,8 +4658,8 @@ class Application(Gtk.Application):
|
||||
self._info_bar.set_visible(False)
|
||||
|
||||
def is_data_loading(self):
|
||||
is_services_loading = self._services_load_spinner.get_property("active")
|
||||
return is_services_loading or self._iptv_services_load_spinner.get_property("active")
|
||||
is_services_loading = self._services_progress_bar.get_visible()
|
||||
return is_services_loading or self._iptv_progress_bar.get_visible()
|
||||
|
||||
def is_data_saved(self):
|
||||
if self._data_hash != 0 and self._data_hash != self.get_data_hash():
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -976,11 +976,10 @@ class PiconManager(Gtk.Box):
|
||||
return True
|
||||
|
||||
def on_tree_view_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
if key is KeyboardKey.DELETE:
|
||||
self.on_local_remove(view)
|
||||
|
||||
|
||||
@@ -390,7 +390,7 @@ class PlayerBox(Gtk.Overlay):
|
||||
width, height = size
|
||||
|
||||
if self._playback_window:
|
||||
self._playback_window.show()
|
||||
self._playback_window.present()
|
||||
self._playback_window.set_title(title or self.get_playback_title())
|
||||
else:
|
||||
self._playback_window = Gtk.Window(title=title or self.get_playback_title(),
|
||||
|
||||
@@ -304,11 +304,10 @@ class RecordingsTool(Gtk.Box):
|
||||
self._filter_entry.grab_focus() if button.get_active() else self._filter_entry.set_text("")
|
||||
|
||||
def on_recordings_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
if key is KeyboardKey.DELETE:
|
||||
self.on_recording_remove()
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2026 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,7 +32,7 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellite list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2024 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2026 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkListStore" id="fec_list_store">
|
||||
<columns>
|
||||
@@ -290,7 +290,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Save current service</property>
|
||||
<property name="tooltip_text" translatable="yes">Save current changes</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="clicked" handler="on_save" swapped="no"/>
|
||||
<accelerator key="Return" signal="activate"/>
|
||||
@@ -298,12 +298,13 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="create_button">
|
||||
<property name="label" translatable="yes">Create</property>
|
||||
<property name="label" translatable="yes">Add</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Create and save as new service</property>
|
||||
<property name="tooltip_text" translatable="yes">Create a new service</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="clicked" handler="on_create_new" swapped="no"/>
|
||||
<signal name="clicked" handler="on_save" swapped="no"/>
|
||||
<accelerator key="Return" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -38,10 +38,10 @@ from app.eparser.ecommons import (MODULATION, Inversion, ROLL_OFF, Pilot, Flag,
|
||||
from app.eparser.neutrino import get_attributes, SP, KSP
|
||||
from app.settings import SettingsType
|
||||
from .dialogs import show_dialog, DialogType, Action, get_builder
|
||||
from .main_helper import get_base_model
|
||||
from .main_helper import get_base_model, scroll_to
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, CODED_ICON, Column
|
||||
|
||||
_UI_PATH = UI_RESOURCES_PATH + "service_details_dialog.glade"
|
||||
_UI_PATH = f"{UI_RESOURCES_PATH}service_dialog.glade"
|
||||
|
||||
|
||||
class ServiceDetailsDialog:
|
||||
@@ -62,10 +62,9 @@ class ServiceDetailsDialog:
|
||||
|
||||
_DIGIT_ENTRY_NAME = "digit-entry"
|
||||
|
||||
def __init__(self, app, new_color, action=Action.EDIT):
|
||||
def __init__(self, app, action=Action.EDIT, tr_type=TrType.Satellite):
|
||||
handlers = {"on_system_changed": self.on_system_changed,
|
||||
"on_save": self.on_save,
|
||||
"on_create_new": self.on_create_new,
|
||||
"on_tr_edit_toggled": self.on_tr_edit_toggled,
|
||||
"update_reference": self.update_reference,
|
||||
"on_cas_entry_changed": self.on_cas_entry_changed,
|
||||
@@ -81,7 +80,7 @@ class ServiceDetailsDialog:
|
||||
self._dialog = builder.get_object("service_details_dialog")
|
||||
self._dialog.set_transient_for(app.app_window)
|
||||
self._s_type = settings.setting_type
|
||||
self._tr_type = TrType.Satellite
|
||||
self._tr_type = tr_type
|
||||
self._picons_path = settings.profile_picons_path
|
||||
self._services_view = app.services_view
|
||||
self._fav_view = app.fav_view
|
||||
@@ -89,19 +88,19 @@ class ServiceDetailsDialog:
|
||||
self._old_service = None
|
||||
self._services = app.current_services
|
||||
self._bouquets = app.current_bouquets
|
||||
self._new_color = new_color
|
||||
self._new_color = app._NEW_COLOR
|
||||
self._transponder_services_iters = None
|
||||
self._current_model = None
|
||||
self._current_itr = None
|
||||
# Patterns
|
||||
# Patterns.
|
||||
self._DIGIT_PATTERN = re.compile("\\D")
|
||||
self._NON_EMPTY_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
|
||||
self._CAID_PATTERN = re.compile("(?:^[\\s]*$)|(C:[0-9a-fA-F]{1,4})(,C:[0-9a-fA-F]{1,4})*")
|
||||
self._PIDS_PATTERN = re.compile("(?:^[\\s]*$)|(c:[0-9]{2}[0-9a-fA-F]{1,4})(,c:[0-9]{2}[0-9a-fA-F]{1,4})*?")
|
||||
# Buttons
|
||||
# Buttons.
|
||||
self._apply_button = builder.get_object("apply_button")
|
||||
self._create_button = builder.get_object("create_button")
|
||||
# style
|
||||
# Style.
|
||||
self._style_provider = Gtk.CssProvider()
|
||||
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
# initialization only digit elements
|
||||
@@ -175,21 +174,53 @@ class ServiceDetailsDialog:
|
||||
def show(self):
|
||||
self._dialog.show()
|
||||
|
||||
@run_idle
|
||||
def init_default_data_elements(self):
|
||||
srv_data = [None] * 20
|
||||
srv_data[Column.SRV_CAS_FLAGS] = "f:40"
|
||||
srv_data[Column.SRV_SERVICE] = "New"
|
||||
srv_data[Column.SRV_PACKAGE] = "New"
|
||||
srv_data[Column.SRV_SSID] = "0"
|
||||
srv_data[Column.SRV_PICON_ID] = "1_0_1_0_0_0_000000_0_0_0.png"
|
||||
srv_data[Column.SRV_FAV_ID] = "1:0:1:0:0:0:000000:0:0:0::0:0:0:0"
|
||||
|
||||
if self._tr_type is TrType.Cable:
|
||||
srv_data[Column.SRV_STANDARD] = "c"
|
||||
srv_data[Column.SRV_FREQ] = "300"
|
||||
srv_data[Column.SRV_RATE] = "6000"
|
||||
srv_data[Column.SRV_SYSTEM] = "DVB-C"
|
||||
srv_data[Column.SRV_POS] = "C"
|
||||
srv_data[Column.SRV_DATA_ID] = "0000:00000000:0:0:1:0"
|
||||
srv_data[Column.SRV_TRANSPONDER] = "t 300000000:0:0:0:0:0:0:0:0:0:0:0"
|
||||
elif self._tr_type is TrType.Terrestrial:
|
||||
srv_data[Column.SRV_STANDARD] = "t"
|
||||
srv_data[Column.SRV_FREQ] = "420000"
|
||||
srv_data[Column.SRV_RATE] = "0"
|
||||
srv_data[Column.SRV_SYSTEM] = "DVB-T2"
|
||||
srv_data[Column.SRV_POS] = "T"
|
||||
srv_data[Column.SRV_DATA_ID] = "0000:00000000:0:0:1:0"
|
||||
srv_data[Column.SRV_TRANSPONDER] = "t 420000000:0:5:5:3:2:4:4:2:1:0:0"
|
||||
else:
|
||||
srv_data[Column.SRV_STANDARD] = "s"
|
||||
srv_data[Column.SRV_FREQ] = "10720"
|
||||
srv_data[Column.SRV_RATE] = "27500"
|
||||
srv_data[Column.SRV_POL] = "H"
|
||||
srv_data[Column.SRV_FEC] = "Auto"
|
||||
srv_data[Column.SRV_SYSTEM] = "DVB-S"
|
||||
srv_data[Column.SRV_POS] = "0.0E"
|
||||
srv_data[Column.SRV_DATA_ID] = "0:00000000:0:0000:1:0:0:0:0:0"
|
||||
srv_data[Column.SRV_TRANSPONDER] = "s 10720000:27500000:0:1:0:0:0:0:0"
|
||||
|
||||
srv = Service(*srv_data)
|
||||
|
||||
self._old_service = srv
|
||||
self._apply_button.set_visible(False)
|
||||
self._create_button.set_visible(True)
|
||||
self._tr_edit_switch.set_sensitive(False)
|
||||
self.on_tr_edit_toggled(self._tr_edit_switch.set_active(True), True)
|
||||
for elem in self._non_empty_elements.values():
|
||||
elem.set_text(" ")
|
||||
elem.set_text("")
|
||||
self._new_check_button.set_active(True)
|
||||
self._service_type_combo_box.set_active(0)
|
||||
self._pol_combo_box.set_active(0)
|
||||
self._fec_combo_box.set_active(0)
|
||||
self._sys_combo_box.set_active(0)
|
||||
self._invertion_combo_box.set_active(2)
|
||||
|
||||
self.init_service_data(srv)
|
||||
|
||||
self._current_model = get_base_model(self._services_view.get_model())
|
||||
|
||||
def update_data_elements(self):
|
||||
model, paths = self._services_view.get_selection().get_selected_rows()
|
||||
@@ -215,6 +246,9 @@ class ServiceDetailsDialog:
|
||||
srv = Service(*self._current_model[itr][: Column.SRV_TOOLTIP])
|
||||
self._old_service = srv
|
||||
self._current_itr = itr
|
||||
self.init_service_data(srv)
|
||||
|
||||
def init_service_data(self, srv):
|
||||
# Service
|
||||
self._name_entry.set_text(srv.service)
|
||||
self._package_entry.set_text(srv.package)
|
||||
@@ -226,6 +260,15 @@ class ServiceDetailsDialog:
|
||||
self.select_active_text(self._pol_combo_box, srv.pol)
|
||||
self.select_active_text(self._fec_combo_box, srv.fec)
|
||||
self.select_active_text(self._sys_combo_box, srv.system)
|
||||
self.update_ui(srv)
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self.init_enigma2_service_data(srv)
|
||||
self.init_enigma2_transponder_data(srv)
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
self.init_neutrino_data(srv)
|
||||
self.init_neutrino_ui_elements()
|
||||
|
||||
def update_ui(self, srv):
|
||||
if self._tr_type is TrType.Terrestrial:
|
||||
self.update_ui_for_terrestrial()
|
||||
elif self._tr_type is TrType.Cable:
|
||||
@@ -235,13 +278,6 @@ class ServiceDetailsDialog:
|
||||
else:
|
||||
self.set_sat_positions(srv.pos)
|
||||
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self.init_enigma2_service_data(srv)
|
||||
self.init_enigma2_transponder_data(srv)
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
self.init_neutrino_data(srv)
|
||||
self.init_neutrino_ui_elements()
|
||||
|
||||
# ***************** Init Enigma2 data *********************#
|
||||
|
||||
@run_idle
|
||||
@@ -316,7 +352,7 @@ class ServiceDetailsDialog:
|
||||
self.select_active_text(self._mod_combo_box, MODULATION.get(tr_data[8]))
|
||||
self.select_active_text(self._rolloff_combo_box, ROLL_OFF.get(tr_data[9]))
|
||||
self.select_active_text(self._pilot_combo_box, Pilot(tr_data[10]).name)
|
||||
self._tr_flag_entry.set_text(tr_data[7])
|
||||
self._tr_flag_entry.set_text(tr_data[6])
|
||||
if data_len > 12:
|
||||
self._stream_id_entry.set_text(tr_data[11])
|
||||
self._pls_code_entry.set_text(tr_data[12])
|
||||
@@ -375,6 +411,12 @@ class ServiceDetailsDialog:
|
||||
""" Sat positions initialisation """
|
||||
self._sat_pos_button.set_value(float(sat_pos[:-1]))
|
||||
self._pos_side_box.set_active_id(sat_pos[-1:])
|
||||
self._sat_pos_button.connect("value-changed", self.on_sat_value_changed)
|
||||
|
||||
def on_sat_value_changed(self, button):
|
||||
pos = int(self.get_sat_position())
|
||||
namespace = int(f"{3600 - abs(pos) if pos < 0 else pos:04x}0000", 16)
|
||||
self._namespace_entry.set_text(str(namespace))
|
||||
|
||||
def on_system_changed(self, box):
|
||||
if not self._tr_edit_switch.get_active():
|
||||
@@ -403,11 +445,8 @@ class ServiceDetailsDialog:
|
||||
def on_save(self, item):
|
||||
self.save_data()
|
||||
|
||||
def on_create_new(self, item):
|
||||
self.save_data()
|
||||
|
||||
def save_data(self):
|
||||
if self._s_type is SettingsType.NEUTRINO_MP and self._tr_type is not TrType.Satellite:
|
||||
if self._s_type is SettingsType.NEUTRINO_MP:
|
||||
show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
|
||||
return
|
||||
|
||||
@@ -423,12 +462,25 @@ class ServiceDetailsDialog:
|
||||
|
||||
def on_new(self):
|
||||
""" Create new service. """
|
||||
service = self.get_service(*self.get_srv_data(), self.get_satellite_transponder_data())
|
||||
show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
|
||||
srv_data = self.update_service_data()
|
||||
if not srv_data:
|
||||
return False
|
||||
|
||||
service, data = srv_data
|
||||
itr = self._current_model.append(service + (None, data.get(Column.SRV_BACKGROUND, None)))
|
||||
scroll_to(self._current_model.get_path(itr), self._services_view)
|
||||
|
||||
return True
|
||||
|
||||
def on_edit(self):
|
||||
""" Edit current service. """
|
||||
service, extra_data = self.update_service_data()
|
||||
self._current_model.set(self._current_itr, extra_data)
|
||||
self._current_model.set(self._current_itr, {i: v for i, v in enumerate(service)})
|
||||
self.update_fav_view(self._old_service, service)
|
||||
return True
|
||||
|
||||
def update_service_data(self):
|
||||
fav_id, data_id = self.get_srv_data()
|
||||
# Transponder
|
||||
transponder = self._old_service.transponder
|
||||
@@ -443,8 +495,9 @@ class ServiceDetailsDialog:
|
||||
elif self._tr_type is TrType.ATSC:
|
||||
transponder = self.get_atsc_transponder_data()
|
||||
except Exception as e:
|
||||
log("Edit service error: {}".format(e))
|
||||
log(f"Edit service error: {e}")
|
||||
show_dialog(DialogType.ERROR, transient=self._dialog, text="Error getting transponder parameters!")
|
||||
return False
|
||||
else:
|
||||
if self._transponder_services_iters:
|
||||
self.update_transponder_services(transponder, self.get_sat_position())
|
||||
@@ -470,11 +523,9 @@ class ServiceDetailsDialog:
|
||||
if f_flags and Flag.is_new(Flag.parse(f_flags[0])):
|
||||
extra_data[Column.SRV_BACKGROUND] = self._new_color
|
||||
|
||||
self._current_model.set(self._current_itr, extra_data)
|
||||
self._current_model.set(self._current_itr, {i: v for i, v in enumerate(service)})
|
||||
self.update_fav_view(self._old_service, service)
|
||||
self._old_service = service
|
||||
return True
|
||||
|
||||
return service, extra_data
|
||||
|
||||
def update_bouquets(self, fav_id, old_fav_id):
|
||||
self._services.pop(old_fav_id, None)
|
||||
@@ -633,14 +684,13 @@ class ServiceDetailsDialog:
|
||||
sat_pos = self.get_sat_position()
|
||||
|
||||
inv = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
|
||||
srv_sys = "0" # !!!
|
||||
flag = self._tr_flag_entry.get_text() or "0"
|
||||
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
dvb_s_tr = self._ENIGMA2_TRANSPONDER_DATA.format("s", freq, rate, pol, fec, sat_pos, inv, srv_sys)
|
||||
dvb_s_tr = self._ENIGMA2_TRANSPONDER_DATA.format("s", freq, rate, pol, fec, sat_pos, inv, flag)
|
||||
if sys == "DVB-S":
|
||||
return dvb_s_tr
|
||||
if sys == "DVB-S2":
|
||||
flag = self._tr_flag_entry.get_text()
|
||||
mod = self.get_value_from_combobox_id(self._mod_combo_box, MODULATION)
|
||||
roll_off = self.get_value_from_combobox_id(self._rolloff_combo_box, ROLL_OFF)
|
||||
pilot = get_value_by_name(Pilot, self._pilot_combo_box.get_active_id())
|
||||
@@ -649,7 +699,7 @@ class ServiceDetailsDialog:
|
||||
st_id = self._stream_id_entry.get_text()
|
||||
pls = f":{st_id}:{pls_code}:{pls_mode}" if pls_mode and pls_code and st_id else ""
|
||||
|
||||
return f"{dvb_s_tr}:{flag}:{mod}:{roll_off}:{pilot}{pls}"
|
||||
return f"{dvb_s_tr}:1:{mod}:{roll_off}:{pilot}{pls}"
|
||||
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
tr_data = get_attributes(self._old_service.transponder)
|
||||
@@ -2418,6 +2418,7 @@ Author: Dmitriy Yefremov
|
||||
<item id="nl_NL">Nederlands</item>
|
||||
<item id="pl_PL">Polski</item>
|
||||
<item id="pt_PT">Português</item>
|
||||
<item id="sk_SK">Slovák</item>
|
||||
<item id="tr_TR">Türkçe</item>
|
||||
<item id="be_BY">Беларуская</item>
|
||||
<item id="ru_RU">Русский</item>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -692,7 +692,7 @@ class SettingsDialog:
|
||||
if mode is PlaybackMode.PLAY:
|
||||
self.show_info_message("Operates in standby mode or current active transponder!", Gtk.MessageType.WARNING)
|
||||
elif mode is PlaybackMode.STREAM:
|
||||
self.show_info_message("Playback IPTV streams only!", Gtk.MessageType.WARNING)
|
||||
self.show_info_message("Playback of IPTV streams only!", Gtk.MessageType.WARNING)
|
||||
elif mode is PlaybackMode.DISABLED:
|
||||
self._allow_main_list_playback_switch.set_active(False)
|
||||
else:
|
||||
|
||||
@@ -49,6 +49,10 @@ paned.vertical > separator {
|
||||
background-size: 24px 2px;
|
||||
}
|
||||
|
||||
progressbar > trough {
|
||||
min-width: 75px;
|
||||
}
|
||||
|
||||
.red-button {
|
||||
background-image: none;
|
||||
background-color: red;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -24,6 +24,8 @@
|
||||
#
|
||||
# Author: Dmitriy Yefremov
|
||||
#
|
||||
|
||||
|
||||
from app.ui.dialogs import translate
|
||||
from .uicommons import Gtk, GLib
|
||||
|
||||
@@ -52,14 +54,13 @@ class BGTaskWidget(Gtk.Box):
|
||||
self.pack_start(close_button, False, False, 0)
|
||||
|
||||
self.show_all()
|
||||
|
||||
# Just prototype. -> It may not work properly!
|
||||
# TODO: Different options need to be tested. Possibly with normal threads.
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from gi.repository.Gio import Task, Cancellable
|
||||
|
||||
self._executor = ThreadPoolExecutor(max_workers=self.TASK_LIMIT)
|
||||
future = self._executor.submit(target, *args)
|
||||
future.add_done_callback(lambda f: GLib.idle_add(self._app.emit, "task-done", self))
|
||||
self._task = Task.new(self, Cancellable.new(), lambda s, t: GLib.idle_add(self._app.emit, "task-done", self))
|
||||
self._task.set_priority(GLib.PRIORITY_LOW)
|
||||
self._task.set_return_on_cancel(True)
|
||||
self._task.run_in_thread(lambda t, s, d, c: target(*args))
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
@@ -78,7 +79,10 @@ class BGTaskWidget(Gtk.Box):
|
||||
self.set_tooltip_text(value)
|
||||
|
||||
def cancel(self):
|
||||
self._executor.shutdown(wait=False)
|
||||
cancelable = self._task.get_cancellable()
|
||||
if cancelable:
|
||||
cancelable.cancel()
|
||||
|
||||
self._app.emit("task-canceled", None)
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -129,11 +129,10 @@ class TelnetClient(Gtk.Box):
|
||||
self.do_command()
|
||||
return True
|
||||
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return None
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return False
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
if ctrl and key is KeyboardKey.C:
|
||||
if self._tn and self._tn.sock:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -460,11 +460,10 @@ class TimerTool(Gtk.Box):
|
||||
on_popup_menu(menu, event)
|
||||
|
||||
def on_timers_key_release(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if key is KeyboardKey.DELETE:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -143,7 +143,7 @@ def get_yt_icon(icon_name, size=24):
|
||||
if n_theme.has_icon(icon_name):
|
||||
return n_theme.load_icon(icon_name, size, 0)
|
||||
|
||||
return default_theme.load_icon("emblem-important-symbolic", size, 0)
|
||||
return get_icon("emblem-important-symbolic", size, LINK_ICON)
|
||||
|
||||
|
||||
def show_notification(message, timeout=10000, urgency=1):
|
||||
@@ -290,14 +290,8 @@ class Column(IntEnum):
|
||||
|
||||
# *************** Keyboard keys *************** #
|
||||
|
||||
class BaseKeyboardKey(Enum):
|
||||
@classmethod
|
||||
def value_exist(cls, value):
|
||||
return value in (val.value for val in cls.__members__.values())
|
||||
|
||||
|
||||
if IS_LINUX:
|
||||
class KeyboardKey(BaseKeyboardKey):
|
||||
class KeyboardKey(IntEnum):
|
||||
""" The raw(hardware) codes [Linux] of the keyboard keys. """
|
||||
E = 26
|
||||
R = 27
|
||||
@@ -335,8 +329,14 @@ if IS_LINUX:
|
||||
PAGE_UP_KP = 81
|
||||
PAGE_DOWN_KP = 89
|
||||
|
||||
UNDEFINED = -1
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
return cls.UNDEFINED
|
||||
|
||||
elif IS_DARWIN:
|
||||
class KeyboardKey(BaseKeyboardKey):
|
||||
class KeyboardKey(IntEnum):
|
||||
""" The raw(hardware) codes [macOS] of the keyboard keys. """
|
||||
F = 3
|
||||
E = 14
|
||||
@@ -376,8 +376,14 @@ elif IS_DARWIN:
|
||||
PAGE_UP_KP = -1
|
||||
PAGE_DOWN_KP = -1
|
||||
|
||||
UNDEFINED = -1
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
return cls.UNDEFINED
|
||||
|
||||
else:
|
||||
class KeyboardKey(BaseKeyboardKey):
|
||||
class KeyboardKey(IntEnum):
|
||||
""" The raw(hardware) codes [Windows] of the keyboard keys. """
|
||||
E = 69
|
||||
R = 82
|
||||
@@ -415,6 +421,12 @@ else:
|
||||
PAGE_UP_KP = -1
|
||||
PAGE_DOWN_KP = -1
|
||||
|
||||
UNDEFINED = -1
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
return cls.UNDEFINED
|
||||
|
||||
# Keys for move in lists. KEY_KP_(NAME) for laptop!
|
||||
MOVE_KEYS = {KeyboardKey.UP, KeyboardKey.PAGE_UP,
|
||||
KeyboardKey.DOWN, KeyboardKey.PAGE_DOWN,
|
||||
@@ -422,5 +434,27 @@ MOVE_KEYS = {KeyboardKey.UP, KeyboardKey.PAGE_UP,
|
||||
KeyboardKey.HOME_KP, KeyboardKey.END_KP,
|
||||
KeyboardKey.PAGE_UP_KP, KeyboardKey.PAGE_DOWN_KP}
|
||||
|
||||
|
||||
class LoadingProgressBar(Gtk.ProgressBar):
|
||||
""" A custom class for a progress bar.
|
||||
|
||||
Used as an alternative to Gtk.Spinner to reduce CPU load.
|
||||
"""
|
||||
__gtype_name__ = "LoadingProgressBar"
|
||||
|
||||
def __init__(self, **properties):
|
||||
super().__init__(**properties)
|
||||
|
||||
self.connect("notify::visible", self.on_visible)
|
||||
|
||||
def on_visible(self, bar, param):
|
||||
if self.get_visible():
|
||||
GLib.timeout_add(500, self.update, priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def update(self):
|
||||
self.pulse()
|
||||
return self.get_visible()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.2
|
||||
<!-- Generated with glade 3.40.0
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-copyright 2018-2024 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2025 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<!-- n-columns=2 n-rows=4 -->
|
||||
<object class="GtkGrid" id="cable_tr_box">
|
||||
@@ -879,7 +879,7 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<!-- n-columns=2 n-rows=4 -->
|
||||
<!-- n-columns=2 n-rows=5 -->
|
||||
<object class="GtkGrid" id="tr_dialog_grid2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
@@ -911,7 +911,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="max-width-chars">12</property>
|
||||
<property name="primary-icon-name">document-edit-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="placeholder-text" translatable="yes">0 - 262142</property>
|
||||
<property name="placeholder-text">0 - 262142</property>
|
||||
<property name="input-purpose">digits</property>
|
||||
<signal name="changed" handler="on_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
@@ -928,7 +928,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="max-width-chars">12</property>
|
||||
<property name="primary-icon-name">document-edit-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="placeholder-text" translatable="yes">0 - 255</property>
|
||||
<property name="placeholder-text">0 - 255</property>
|
||||
<property name="input-purpose">digits</property>
|
||||
<signal name="changed" handler="on_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
@@ -945,7 +945,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="max-width-chars">12</property>
|
||||
<property name="primary-icon-name">document-edit-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="placeholder-text" translatable="yes">0 - 255</property>
|
||||
<property name="placeholder-text">0 - 255</property>
|
||||
<property name="input-purpose">digits</property>
|
||||
<signal name="changed" handler="on_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
@@ -1095,6 +1095,58 @@ Author: Dmitriy Yefremov
|
||||
<property name="top-attach">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="tr_t2mi_pid_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">T2-MI PID</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="t2mi_pid_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="width-chars">5</property>
|
||||
<property name="max-width-chars">12</property>
|
||||
<property name="primary-icon-name">document-edit-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="placeholder-text">0 - 8191</property>
|
||||
<property name="input-purpose">digits</property>
|
||||
<signal name="changed" handler="on_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2025 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
|
||||
@@ -207,6 +207,7 @@ class SatTransponderDialog(TransponderDialog):
|
||||
self._pls_code_entry = builder.get_object("pls_code_entry")
|
||||
self._is_id_entry = builder.get_object("is_id_entry")
|
||||
self._t2mi_plp_id_entry = builder.get_object("t2mi_plp_id_entry")
|
||||
self._t2mi_pid_entry = builder.get_object("t2mi_pid_entry")
|
||||
|
||||
self.set_style_provider(self._freq_entry)
|
||||
self.set_style_provider(self._rate_entry)
|
||||
@@ -230,6 +231,7 @@ class SatTransponderDialog(TransponderDialog):
|
||||
self._is_id_entry.set_text(transponder.is_id if transponder.is_id else "")
|
||||
self._pls_code_entry.set_text(transponder.pls_code if transponder.pls_code else "")
|
||||
self._t2mi_plp_id_entry.set_text(transponder.t2mi_plp_id if transponder.t2mi_plp_id else "")
|
||||
self._t2mi_pid_entry.set_text(transponder.t2mi_pid if transponder.t2mi_pid else "")
|
||||
|
||||
def to_transponder(self):
|
||||
return Transponder(frequency=self._freq_entry.get_text(),
|
||||
@@ -241,7 +243,8 @@ class SatTransponderDialog(TransponderDialog):
|
||||
pls_mode=get_key_by_value(PLS_MODE, self._pls_mode_box.get_active_id()),
|
||||
pls_code=self._pls_code_entry.get_text(),
|
||||
is_id=self._is_id_entry.get_text(),
|
||||
t2mi_plp_id=self._t2mi_plp_id_entry.get_text())
|
||||
t2mi_plp_id=self._t2mi_plp_id_entry.get_text(),
|
||||
t2mi_pid=self._t2mi_pid_entry.get_text())
|
||||
|
||||
def is_accept(self):
|
||||
tr = self.to_transponder()
|
||||
@@ -255,6 +258,8 @@ class SatTransponderDialog(TransponderDialog):
|
||||
return False
|
||||
elif self.digit_pattern.search(tr.t2mi_plp_id):
|
||||
return False
|
||||
elif self.digit_pattern.search(tr.t2mi_pid):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -239,12 +239,6 @@ class SatellitesTool(Gtk.Box):
|
||||
self._transponders_stack.set_visible_child_name(self._dvb_type)
|
||||
self._update_header_button.set_sensitive(self._dvb_type is self.DVB.SAT)
|
||||
|
||||
if self._dvb_type is self.DVB.SAT:
|
||||
self._app.on_info_bar_close()
|
||||
|
||||
else:
|
||||
self._app.show_info_message("EXPERIMENTAL!", Gtk.MessageType.WARNING)
|
||||
|
||||
def on_satellite_selection(self, view):
|
||||
model = self._sat_tr_view.get_model()
|
||||
model.clear()
|
||||
@@ -310,11 +304,10 @@ class SatellitesTool(Gtk.Box):
|
||||
|
||||
def on_key_press(self, view, event):
|
||||
""" Handling keystrokes. """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if key is KeyboardKey.DELETE:
|
||||
@@ -329,12 +322,11 @@ class SatellitesTool(Gtk.Box):
|
||||
view.do_unselect_all(view)
|
||||
|
||||
def on_tr_key_press(self, view, event):
|
||||
""" Handling transponder view keystrokes. """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
""" Handling transponder view keystrokes. """
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if key is KeyboardKey.DELETE:
|
||||
@@ -580,11 +572,11 @@ class SatellitesTool(Gtk.Box):
|
||||
|
||||
def on_download(self, app, page):
|
||||
if page is Page.SATELLITE:
|
||||
self._app.on_download_data(DownloadType.SATELLITES)
|
||||
self._app.on_download_data(DownloadType.SATELLITES, files_filter=(f"{self._dvb_type}.xml",))
|
||||
|
||||
def on_upload(self, app, page):
|
||||
if page is Page.SATELLITE:
|
||||
self._app.upload_data(DownloadType.SATELLITES)
|
||||
self._app.upload_data(DownloadType.SATELLITES, files_filter=(f"{self._dvb_type}.xml",))
|
||||
|
||||
@run_idle
|
||||
def on_update(self, item=None):
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2025 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
|
||||
@@ -128,6 +128,8 @@ Author: Dmitriy Yefremov
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name t2mi_plp_id -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name t2mi_pid -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
<signal name="row-deleted" handler="on_sat_tr_model_changed" swapped="no"/>
|
||||
<signal name="row-inserted" handler="on_sat_tr_model_changed" swapped="no"/>
|
||||
@@ -424,7 +426,9 @@ Author: Dmitriy Yefremov
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
<signal name="realize" handler="on_terrestrial_view_realize" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
<object class="GtkTreeSelection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="ter_name_column">
|
||||
@@ -522,7 +526,9 @@ Author: Dmitriy Yefremov
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
<signal name="realize" handler="on_cable_view_realize" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
<object class="GtkTreeSelection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="cable_name_column">
|
||||
@@ -947,7 +953,9 @@ Author: Dmitriy Yefremov
|
||||
<signal name="button-press-event" handler="on_tr_button_press" object="transponder_popup_menu" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_tr_key_press" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
<object class="GtkTreeSelection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="ter_freq_column">
|
||||
@@ -1188,7 +1196,9 @@ Author: Dmitriy Yefremov
|
||||
<signal name="button-press-event" handler="on_tr_button_press" object="transponder_popup_menu" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_tr_key_press" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
<object class="GtkTreeSelection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="cable_freq_column">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
VER="3.13.2_Beta"
|
||||
VER="3.14.4_Beta"
|
||||
B_PATH="dist/DemonEditor"
|
||||
DEB_PATH="$B_PATH/usr/share/demoneditor"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: demon-editor
|
||||
Version: 3.13.2-Beta
|
||||
Version: 3.14.4-Beta
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
|
||||
@@ -5,7 +5,7 @@ Source: https://github.com/DYefremov/DemonEditor
|
||||
Files: *
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2026 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,13 +32,13 @@ a = Analysis([EXE_NAME],
|
||||
pathex=PATH_EXE,
|
||||
binaries=None,
|
||||
datas=ui_files,
|
||||
hiddenimports=['fileinput', 'uuid', 'asyncio'],
|
||||
hiddenimports=['fileinput', 'uuid', 'asyncio', 'getpass'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
hooksconfig={
|
||||
"gi": {
|
||||
"languages": ["en", "be", "es", "it", "nl",
|
||||
"pl", "pt", "ru", "tr", "zh_CN"],
|
||||
"languages": ["en", "be", "es", "it", "nl", "pl",
|
||||
"pt", "ru", "sk", "tr", "zh_CN"],
|
||||
"module-versions": {
|
||||
"Gtk": "3.0"
|
||||
},
|
||||
@@ -81,8 +81,8 @@ app = BUNDLE(coll,
|
||||
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
|
||||
'LSApplicationCategoryType': 'public.app-category.utilities',
|
||||
'LSMinimumSystemVersion': '10.13',
|
||||
'CFBundleShortVersionString': f"3.13.2.{BUILD_DATE} Beta",
|
||||
'NSHumanReadableCopyright': u"Copyright © 2018-2025, Dmitriy Yefremov",
|
||||
'CFBundleShortVersionString': f"3.14.4.{BUILD_DATE} Beta",
|
||||
'NSHumanReadableCopyright': u"Copyright © 2018-2026, Dmitriy Yefremov",
|
||||
'NSRequiresAquaSystemAppearance': 'false',
|
||||
'NSHighResolutionCapable': 'true'
|
||||
})
|
||||
|
||||
@@ -7,8 +7,7 @@ PATH_EXE = [os.path.join(DIR_PATH, EXE_NAME)]
|
||||
block_cipher = None
|
||||
|
||||
|
||||
excludes = ['app.tools.mpv',
|
||||
'gi.repository.Gst',
|
||||
excludes = ['gi.repository.Gst',
|
||||
'gi.repository.GstBase',
|
||||
'gi.repository.GstVideo',
|
||||
'youtube_dl',
|
||||
@@ -30,13 +29,13 @@ a = Analysis([EXE_NAME],
|
||||
pathex=PATH_EXE,
|
||||
binaries=[],
|
||||
datas=ui_files,
|
||||
hiddenimports=['fileinput', 'uuid', 'ctypes.wintypes', 'asyncio'],
|
||||
hiddenimports=['fileinput', 'uuid', 'ctypes.wintypes', 'asyncio', 'getpass'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
hooksconfig={
|
||||
"gi": {
|
||||
"languages": ["en", "be", "es", "it", "nl",
|
||||
"pl", "pt", "ru", "tr", "zh_CN"],
|
||||
"languages": ["en", "be", "es", "it", "nl", "pl",
|
||||
"pt", "ru", "sk", "tr", "zh_CN"],
|
||||
"module-versions": {
|
||||
"Gtk": "3.0",
|
||||
"GtkSource": "3",
|
||||
@@ -48,9 +47,19 @@ a = Analysis([EXE_NAME],
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False)
|
||||
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
|
||||
|
||||
splash = Splash('logo.png',
|
||||
binaries=a.binaries,
|
||||
datas=a.datas)
|
||||
|
||||
|
||||
exe = EXE(pyz,
|
||||
splash,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
@@ -59,13 +68,16 @@ exe = EXE(pyz,
|
||||
bootloader_ignore_signals=False,
|
||||
contents_directory='.',
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx=False,
|
||||
console=False,
|
||||
icon='icon.ico')
|
||||
|
||||
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
splash.binaries,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
|
||||
26
build/win/hide_splash.patch
Normal file
26
build/win/hide_splash.patch
Normal file
@@ -0,0 +1,26 @@
|
||||
Subject: [PATCH] hide_splash
|
||||
---
|
||||
Index: app/ui/main.py
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/app/ui/main.py b/app/ui/main.py
|
||||
--- a/app/ui/main.py (revision 0fc0ef1d3e80fc84f4da81e1117db63a1f1d3467)
|
||||
+++ b/app/ui/main.py (date 1771419933854)
|
||||
@@ -651,6 +651,15 @@
|
||||
gen = self.init_http_api()
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
+ if hasattr(sys, "_MEIPASS"):
|
||||
+ import pyi_splash
|
||||
+
|
||||
+ if pyi_splash.is_alive():
|
||||
+ pyi_splash.close()
|
||||
+
|
||||
+ if self._main_window.is_suspended():
|
||||
+ self._main_window.present()
|
||||
+
|
||||
def do_shutdown(self):
|
||||
""" Performs shutdown tasks """
|
||||
if self._settings.load_last_config:
|
||||
BIN
build/win/logo.png
Executable file
BIN
build/win/logo.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
@@ -1,14 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import ssl
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
from multiprocessing import freeze_support
|
||||
if hasattr(sys, "_MEIPASS"):
|
||||
import os
|
||||
import ssl
|
||||
from multiprocessing import freeze_support
|
||||
|
||||
os.environ["PYTHONUTF8"] = "1"
|
||||
freeze_support()
|
||||
# TODO There needs to be a more "correct" way.
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
from app.ui.main import start_app
|
||||
|
||||
os.environ["PYTHONUTF8"] = "1"
|
||||
# TODO There needs to be a more "correct" way.
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
freeze_support()
|
||||
start_app()
|
||||
|
||||
@@ -10,6 +10,7 @@ GenericName[nl]=Enigma2 boeket editor
|
||||
GenericName[pl]=Edytor bukietów Enigma2
|
||||
GenericName[pt]=Editor de buquês Enigma2
|
||||
GenericName[ru]=Редактор букетов Enigma2
|
||||
GenericName[sk]=Enigma2 editor balíčkov
|
||||
GenericName[tr]=Enigma2 buket düzenleyici
|
||||
GenericName[zh_CN]=Enigma2频道编辑器
|
||||
Comment=Channel and satellite list editor for Enigma2
|
||||
@@ -21,6 +22,7 @@ Comment[nl]=Kanaal- en satellietlijsteditor voor Enigma2
|
||||
Comment[pl]=Edytor list kanałów i satelitów dla Enigma2
|
||||
Comment[pt]=Editor de lista de canais e satélites para Enigma2
|
||||
Comment[ru]=Редактор списка каналов и спутников для Enigma2
|
||||
Comment[sk]=Editor zoznamu kanálov a satelitov pre Enigma2
|
||||
Comment[tr]=Enigma2 için Kanal ve uydu listesi düzenleyici
|
||||
Comment[zh_CN]=Enigma2频道和卫星列表编辑器
|
||||
Icon=demon-editor
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2026 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
@@ -11,7 +11,7 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "Dmitriy Yefremov"
|
||||
msgstr "Дзмітрый Яфрэмаў"
|
||||
|
||||
# Main
|
||||
msgid "Service"
|
||||
@@ -1581,3 +1581,36 @@ msgstr "Пры загрузцы букетаў узніклі памылкі [%s
|
||||
|
||||
msgid "Check the log for more info."
|
||||
msgstr "Глядзіце логі для пашыранай інфармацыі."
|
||||
|
||||
msgid "Satellite channel"
|
||||
msgstr "Спадарожнікавы канал"
|
||||
|
||||
msgid "Terrestrial channel"
|
||||
msgstr "Эфірны канал"
|
||||
|
||||
msgid "Cable channel"
|
||||
msgstr "Кабельны канал"
|
||||
|
||||
msgid "Save current changes"
|
||||
msgstr "Захаваць бягучыя змены"
|
||||
|
||||
msgid "Create a new service"
|
||||
msgstr "Стварыць новы сэрвіс"
|
||||
|
||||
msgid "Extension support is disabled!"
|
||||
msgstr "Падтрымка пашырэнняў адключана!"
|
||||
|
||||
msgid "Do you want to enable it?"
|
||||
msgstr "Жадаеце ўключыць?"
|
||||
|
||||
msgid "Playback of IPTV streams only!"
|
||||
msgstr "Прайграванне толькі IPTV-патокаў!"
|
||||
|
||||
msgid "There are running background tasks!"
|
||||
msgstr "Ідзе выкананне фонавых задач!"
|
||||
|
||||
msgid "Check if FFmpeg is installed!"
|
||||
msgstr "Праверце, ці ўсталяваны FFmpeg!"
|
||||
|
||||
msgid "It can cause some problems."
|
||||
msgstr "Гэта можа выклікаць некаторыя праблемы."
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Copyright (C) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2026 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
# Charly, 2019.
|
||||
# Dmitriy Yefremov, 2020-2025.
|
||||
# Dmitriy Yefremov, 2020-2026.
|
||||
# Thomas Schmidt, 2021.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -1595,3 +1595,36 @@ msgstr "Beim Laden der Bouquets sind Fehler [%s] aufgetreten!"
|
||||
|
||||
msgid "Check the log for more info."
|
||||
msgstr "Weitere Informationen finden Sie im Log."
|
||||
|
||||
msgid "Satellite channel"
|
||||
msgstr "Satellitenkanal"
|
||||
|
||||
msgid "Terrestrial channel"
|
||||
msgstr "Terrestrischer Kanal"
|
||||
|
||||
msgid "Cable channel"
|
||||
msgstr "Kabelkanal"
|
||||
|
||||
msgid "Save current changes"
|
||||
msgstr "Aktuelle Änderungen speichern"
|
||||
|
||||
msgid "Create a new service"
|
||||
msgstr "Erstellen eines neuen Service"
|
||||
|
||||
msgid "Extension support is disabled!"
|
||||
msgstr "Die Unterstützung für Erweiterungen ist deaktiviert!"
|
||||
|
||||
msgid "Do you want to enable it?"
|
||||
msgstr "Möchtest du aktivieren?"
|
||||
|
||||
msgid "Playback of IPTV streams only!"
|
||||
msgstr "Wiedergabe nur IPTV-Streams!"
|
||||
|
||||
msgid "There are running background tasks!"
|
||||
msgstr "Es laufen Hintergrundprozesse!"
|
||||
|
||||
msgid "Check if FFmpeg is installed!"
|
||||
msgstr "Prüfe ob FFmpeg installiert ist!"
|
||||
|
||||
msgid "It can cause some problems."
|
||||
msgstr "Das kann zu einigen Problemen führen."
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Copyright (C) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2026 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2022, 2023, 2024, 2025 Massimo Pissarello <mapi68@gmail.com>
|
||||
# SPDX-FileCopyrightText: 2022, 2023, 2024, 2025, 2026 Massimo Pissarello <mapi68@gmail.com>
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"PO-Revision-Date: 2025-08-04 17:49+0200\n"
|
||||
"PO-Revision-Date: 2026-01-30 17:27+0100\n"
|
||||
"Last-Translator: Massimo Pissarello <mapi68@gmail.com>\n"
|
||||
"Language-Team: Italian <>\n"
|
||||
"Language: it\n"
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Lokalize 25.08.0\n"
|
||||
"X-Generator: Lokalize 25.12.1\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "Massimo Pissarello\nNicola Fanghella"
|
||||
@@ -1645,3 +1645,24 @@ msgstr "Si sono verificati errori [%s] durante il caricamento dei bouquet!"
|
||||
|
||||
msgid "Check the log for more info."
|
||||
msgstr "Per maggiori informazioni consulta il log."
|
||||
|
||||
msgid "Satellite channel"
|
||||
msgstr "Canale satellitare"
|
||||
|
||||
msgid "Terrestrial channel"
|
||||
msgstr "Canale terrestre"
|
||||
|
||||
msgid "Cable channel"
|
||||
msgstr "Canale via cavo"
|
||||
|
||||
msgid "Save current changes"
|
||||
msgstr "Salva le modifiche attuali"
|
||||
|
||||
msgid "Create a new service"
|
||||
msgstr "Crea un nuovo servizio"
|
||||
|
||||
msgid "Extension support is disabled!"
|
||||
msgstr "Il supporto dell'estensione è disabilitato!"
|
||||
|
||||
msgid "Do you want to enable it?"
|
||||
msgstr "Vuoi abilitarlo?"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2026 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
@@ -11,7 +11,7 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "Dmitriy Yefremov"
|
||||
msgstr "Дмитрий Ефремов"
|
||||
|
||||
# Main
|
||||
msgid "Service"
|
||||
@@ -1578,3 +1578,36 @@ msgstr "При загрузке букетов возникли ошибки [%s
|
||||
|
||||
msgid "Check the log for more info."
|
||||
msgstr "Смотрите журнал для расширенной информации."
|
||||
|
||||
msgid "Satellite channel"
|
||||
msgstr "Спутниковый канал"
|
||||
|
||||
msgid "Terrestrial channel"
|
||||
msgstr "Эфирный канал"
|
||||
|
||||
msgid "Cable channel"
|
||||
msgstr "Кабельный канал"
|
||||
|
||||
msgid "Save current changes"
|
||||
msgstr "Сохранить текущие изменения"
|
||||
|
||||
msgid "Create a new service"
|
||||
msgstr "Создать новый сервис"
|
||||
|
||||
msgid "Extension support is disabled!"
|
||||
msgstr "Поддержка расширений отключена!"
|
||||
|
||||
msgid "Do you want to enable it?"
|
||||
msgstr "Желаете включить?"
|
||||
|
||||
msgid "Playback of IPTV streams only!"
|
||||
msgstr "Воспроизведение только IPTV-потоков!"
|
||||
|
||||
msgid "There are running background tasks!"
|
||||
msgstr "Идет выполнение фоновых задач!"
|
||||
|
||||
msgid "Check if FFmpeg is installed!"
|
||||
msgstr "Проверьте, установлен ли FFmpeg!"
|
||||
|
||||
msgid "It can cause some problems."
|
||||
msgstr "Это может вызывать некоторые проблемы."
|
||||
|
||||
1603
po/sk/demon-editor.po
Normal file
1603
po/sk/demon-editor.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: DemonEditor\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
|
||||
"PO-Revision-Date: 2025-08-03 21:34+0300\n"
|
||||
"PO-Revision-Date: 2026-01-25 17:07+0300\n"
|
||||
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
|
||||
"Language-Team: audi06_19 <info@dreamosat-forum.com>\n"
|
||||
"Language: tr\n"
|
||||
@@ -11,7 +11,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.6\n"
|
||||
"X-Generator: Poedit 3.8\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "audi06_19 <info@dreamosat-forum.com>"
|
||||
@@ -1612,3 +1612,24 @@ msgstr "Buketler yüklenirken [%s] hata oluştu!"
|
||||
|
||||
msgid "Check the log for more info."
|
||||
msgstr "Daha fazla bilgi için günlüğü kontrol edin."
|
||||
|
||||
msgid "Satellite channel"
|
||||
msgstr "Uydu kanalı"
|
||||
|
||||
msgid "Terrestrial channel"
|
||||
msgstr "Karasal kanal"
|
||||
|
||||
msgid "Cable channel"
|
||||
msgstr "Kablo kanalı"
|
||||
|
||||
msgid "Save current changes"
|
||||
msgstr "Mevcut değişiklikleri kaydet"
|
||||
|
||||
msgid "Create a new service"
|
||||
msgstr "Yeni bir hizmet oluşturun"
|
||||
|
||||
msgid "Extension support is disabled!"
|
||||
msgstr "Uzantı desteği devre dışı bırakıldı!"
|
||||
|
||||
msgid "Do you want to enable it?"
|
||||
msgstr "Etkinleştirmek ister misiniz?"
|
||||
|
||||
Reference in New Issue
Block a user